diff --git a/deployments/openim-chat/Chart.yaml b/deployments/openim-chat/Chart.yaml index 7bf73b390..4466fc928 100644 --- a/deployments/openim-chat/Chart.yaml +++ b/deployments/openim-chat/Chart.yaml @@ -1,3 +1,17 @@ +# Copyright © 2023 OpenIM. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + apiVersion: v2 name: openim-chat description: A Helm chart for Kubernetes diff --git a/deployments/openim-chat/templates/deployment.yaml b/deployments/openim-chat/templates/deployment.yaml index eda29d234..47d88c590 100644 --- a/deployments/openim-chat/templates/deployment.yaml +++ b/deployments/openim-chat/templates/deployment.yaml @@ -1,3 +1,17 @@ +# Copyright © 2023 OpenIM. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + apiVersion: apps/v1 kind: Deployment metadata: diff --git a/deployments/openim-chat/templates/hpa.yaml b/deployments/openim-chat/templates/hpa.yaml index 72d3a0262..5f28b13fe 100644 --- a/deployments/openim-chat/templates/hpa.yaml +++ b/deployments/openim-chat/templates/hpa.yaml @@ -1,3 +1,17 @@ +# Copyright © 2023 OpenIM. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + {{- if .Values.autoscaling.enabled }} apiVersion: autoscaling/v2beta1 kind: HorizontalPodAutoscaler diff --git a/deployments/openim-chat/templates/ingress.yaml b/deployments/openim-chat/templates/ingress.yaml index 153597a4f..3faa231d0 100644 --- a/deployments/openim-chat/templates/ingress.yaml +++ b/deployments/openim-chat/templates/ingress.yaml @@ -1,3 +1,17 @@ +# Copyright © 2023 OpenIM. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + {{- if .Values.ingress.enabled -}} {{- $fullName := include "openim-chat.fullname" . -}} {{- $svcPort := .Values.service.port -}} diff --git a/deployments/openim-chat/templates/service.yaml b/deployments/openim-chat/templates/service.yaml index 8193cf69f..c504be550 100644 --- a/deployments/openim-chat/templates/service.yaml +++ b/deployments/openim-chat/templates/service.yaml @@ -1,3 +1,17 @@ +# Copyright © 2023 OpenIM. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + apiVersion: v1 kind: Service metadata: diff --git a/deployments/openim-chat/templates/serviceaccount.yaml b/deployments/openim-chat/templates/serviceaccount.yaml index 17c529e75..8fd4f4e21 100644 --- a/deployments/openim-chat/templates/serviceaccount.yaml +++ b/deployments/openim-chat/templates/serviceaccount.yaml @@ -1,3 +1,17 @@ +# Copyright © 2023 OpenIM. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + {{- if .Values.serviceAccount.create -}} apiVersion: v1 kind: ServiceAccount diff --git a/deployments/openim-chat/values.yaml b/deployments/openim-chat/values.yaml index 21c7785ff..b1c083fee 100644 --- a/deployments/openim-chat/values.yaml +++ b/deployments/openim-chat/values.yaml @@ -1,3 +1,17 @@ +# Copyright © 2023 OpenIM. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + # Default values for openim-chat. # This is a YAML-formatted file. # Declare variables to be passed into your templates. diff --git a/deployments/openim-server/Chart.yaml b/deployments/openim-server/Chart.yaml index c22eeb938..d9d07eccb 100644 --- a/deployments/openim-server/Chart.yaml +++ b/deployments/openim-server/Chart.yaml @@ -1,3 +1,17 @@ +# Copyright © 2023 OpenIM. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + apiVersion: v2 name: openim-server description: A Helm chart for Kubernetes diff --git a/deployments/openim-server/templates/deployment.yaml b/deployments/openim-server/templates/deployment.yaml index 7d14f6551..9c630e89a 100644 --- a/deployments/openim-server/templates/deployment.yaml +++ b/deployments/openim-server/templates/deployment.yaml @@ -1,3 +1,17 @@ +# Copyright © 2023 OpenIM. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + apiVersion: apps/v1 kind: Deployment metadata: diff --git a/deployments/openim-server/templates/hpa.yaml b/deployments/openim-server/templates/hpa.yaml index 128588fc4..c7726c969 100644 --- a/deployments/openim-server/templates/hpa.yaml +++ b/deployments/openim-server/templates/hpa.yaml @@ -1,3 +1,17 @@ +# Copyright © 2023 OpenIM. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + {{- if .Values.autoscaling.enabled }} apiVersion: autoscaling/v2beta1 kind: HorizontalPodAutoscaler diff --git a/deployments/openim-server/templates/ingress.yaml b/deployments/openim-server/templates/ingress.yaml index da7f07dd4..c269ecc26 100644 --- a/deployments/openim-server/templates/ingress.yaml +++ b/deployments/openim-server/templates/ingress.yaml @@ -1,3 +1,17 @@ +# Copyright © 2023 OpenIM. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + {{- if .Values.ingress.enabled -}} {{- $fullName := include "openim-server.fullname" . -}} {{- $svcPort := .Values.service.port -}} diff --git a/deployments/openim-server/templates/service.yaml b/deployments/openim-server/templates/service.yaml index 38799f5a4..a12825bfb 100644 --- a/deployments/openim-server/templates/service.yaml +++ b/deployments/openim-server/templates/service.yaml @@ -1,3 +1,17 @@ +# Copyright © 2023 OpenIM. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + apiVersion: v1 kind: Service metadata: diff --git a/deployments/openim-server/templates/serviceaccount.yaml b/deployments/openim-server/templates/serviceaccount.yaml index c77b8410f..6e31d9c83 100644 --- a/deployments/openim-server/templates/serviceaccount.yaml +++ b/deployments/openim-server/templates/serviceaccount.yaml @@ -1,3 +1,17 @@ +# Copyright © 2023 OpenIM. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + {{- if .Values.serviceAccount.create -}} apiVersion: v1 kind: ServiceAccount diff --git a/deployments/openim-server/values.yaml b/deployments/openim-server/values.yaml index 8861e40ca..99bddb99b 100644 --- a/deployments/openim-server/values.yaml +++ b/deployments/openim-server/values.yaml @@ -1,3 +1,17 @@ +# Copyright © 2023 OpenIM. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + # Default values for openim-server. # This is a YAML-formatted file. # Declare variables to be passed into your templates. diff --git a/deployments/templates/chat.yaml b/deployments/templates/chat.yaml index ed06f7206..4f4936b13 100644 --- a/deployments/templates/chat.yaml +++ b/deployments/templates/chat.yaml @@ -1,3 +1,17 @@ +# Copyright © 2023 OpenIM. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + # Configuration for OpenIMServer # ----------------------------------------------------------------- diff --git a/deployments/templates/env_template.yaml b/deployments/templates/env_template.yaml index c197cac4f..c6a2d7465 100644 --- a/deployments/templates/env_template.yaml +++ b/deployments/templates/env_template.yaml @@ -1,3 +1,17 @@ +# Copyright © 2023 OpenIM. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + # ====================================== # ========= Basic Configuration ======== # ====================================== diff --git a/scripts/create_topic.sh b/scripts/create_topic.sh index 4d0e17f06..5c291ff08 100755 --- a/scripts/create_topic.sh +++ b/scripts/create_topic.sh @@ -1,4 +1,18 @@ #!/usr/bin/env bash +# Copyright © 2023 OpenIM. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + # Wait for Kafka to be ready until /opt/bitnami/kafka/bin/kafka-topics.sh --list --bootstrap-server localhost:9092; do echo "Waiting for Kafka to be ready..." diff --git a/tools/imctl/internal/imctl/cmd/cmd.go b/tools/imctl/internal/imctl/cmd/cmd.go index f315d01f5..99f8a52dd 100644 --- a/tools/imctl/internal/imctl/cmd/cmd.go +++ b/tools/imctl/internal/imctl/cmd/cmd.go @@ -24,20 +24,20 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/iamctl/cmd/color" - "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/iamctl/cmd/completion" - "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/iamctl/cmd/info" - "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/iamctl/cmd/jwt" - "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/iamctl/cmd/new" - "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/iamctl/cmd/options" - "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/iamctl/cmd/policy" - "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/iamctl/cmd/secret" - "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/iamctl/cmd/set" - "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/iamctl/cmd/user" - cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/iamctl/cmd/util" - "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/iamctl/cmd/validate" - "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/iamctl/cmd/version" - "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/iamctl/util/templates" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/color" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/completion" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/info" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/jwt" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/new" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/options" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/policy" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/secret" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/set" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/user" + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/inernal/iamctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/validate" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/version" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" genericapiserver "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/pkg/server" "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/cli/genericclioptions" ) diff --git a/tools/imctl/internal/imctl/cmd/color/color.go b/tools/imctl/internal/imctl/cmd/color/color.go new file mode 100644 index 000000000..047638f3c --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/color/color.go @@ -0,0 +1,352 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package color print colors supported by the current terminal. +package color + +import ( + "fmt" + "strings" + + "github.com/fatih/color" + "github.com/olekukonko/tablewriter" + "github.com/openim-sigs/component-base/pkg/util/stringutil" + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +// ColorOptions is an options struct to support color subcommands. +type ColorOptions struct { + Type []string + Example bool + + genericclioptions.IOStreams +} + +var ( + colorLong = templates.LongDesc(`Print the colors supported by the current terminal. + +Color lets you use colorized outputs in terms of ANSI Escape Codes in Go (Golang). +It has support for Windows too! The API can be used in several ways, pick one that suits you. + +Find more information at: + https://github.com/fatih/color`) + + colorExample = templates.Examples(` + # Print supported foreground and background colors + iamctl color + + # Print supported colors by type + iamctl color -t fg-hi + + # Print all supported colors + iamctl color -t all`) + + availableTypes = []string{"fg", "fg-hi", "bg", "bg-hi", "base", "all"} + + colorCodeExample = templates.Examples(` +### 1. Standard colors + +// Print with default helper functions +color.Cyan("Prints text in cyan.") + +// A newline will be appended automatically +color.Blue("Prints %s in blue.", "text") + +// These are using the default foreground colors +color.Red("We have red") +color.Magenta("And many others ..") + +### 2. Mix and reuse colors + +// Create a new color object +c := color.New(color.FgCyan).Add(color.Underline) +c.Println("Prints cyan text with an underline.") + +// Or just add them to New() +d := color.New(color.FgCyan, color.Bold) +d.Printf("This prints bold cyan %s\n", "too!.") + +// Mix up foreground and background colors, create new mixes! +red := color.New(color.FgRed) + +boldRed := red.Add(color.Bold) +boldRed.Println("This will print text in bold red.") + +whiteBackground := red.Add(color.BgWhite) +whiteBackground.Println("Red text with white background.") + +### 3. Use your own output (io.Writer) + +// Use your own io.Writer output +color.New(color.FgBlue).Fprintln(myWriter, "blue color!") + +blue := color.New(color.FgBlue) +blue.Fprint(writer, "This will print text in blue.") + +### 4. Custom print functions (PrintFunc) + +// Create a custom print function for convenience +red := color.New(color.FgRed).PrintfFunc() +red("Warning") +red("Error: %s", err) + +// Mix up multiple attributes +notice := color.New(color.Bold, color.FgGreen).PrintlnFunc() +notice("Don't forget this...") + +### 5. Custom fprint functions (FprintFunc) + +blue := color.New(FgBlue).FprintfFunc() +blue(myWriter, "important notice: %s", stars) + +// Mix up with multiple attributes +success := color.New(color.Bold, color.FgGreen).FprintlnFunc() +success(myWriter, "Don't forget this...") + +### 6. Insert into noncolor strings (SprintFunc) + +// Create SprintXxx functions to mix strings with other non-colorized strings: +yellow := color.New(color.FgYellow).SprintFunc() +red := color.New(color.FgRed).SprintFunc() +fmt.Printf("This is a %s and this is %s.\n", yellow("warning"), red("error")) + +info := color.New(color.FgWhite, color.BgGreen).SprintFunc() +fmt.Printf("This %s rocks!\n", info("package")) + +// Use helper functions +fmt.Println("This", color.RedString("warning"), "should be not neglected.") +fmt.Printf("%v %v\n", color.GreenString("Info:"), "an important message.") + +// Windows supported too! Just don't forget to change the output to color.Output +fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS")) + +### 7. Plug into existing code + +// Use handy standard colors +color.Set(color.FgYellow) + +fmt.Println("Existing text will now be in yellow") +fmt.Printf("This one %s\n", "too") + +color.Unset() // Don't forget to unset + +// You can mix up parameters +color.Set(color.FgMagenta, color.Bold) +defer color.Unset() // Use it in your function + +fmt.Println("All text will now be bold magenta.") + +### 8. Disable/Enable color + +There might be a case where you want to explicitly disable/enable color output. the +go-isatty package will automatically disable color output for non-tty output streams +(for example if the output were piped directly to less) + +Color has support to disable/enable colors both globally and for single color +definitions. For example suppose you have a CLI app and a --no-color bool flag. You +can easily disable the color output with: + + +var flagNoColor = flag.Bool("no-color", false, "Disable color output") + +if *flagNoColor { + color.NoColor = true // disables colorized output +} + +It also has support for single color definitions (local). You can +disable/enable color output on the fly: + +c := color.New(color.FgCyan) +c.Println("Prints cyan text") + +c.DisableColor() +c.Println("This is printed without any color") + +c.EnableColor() +c.Println("This prints again cyan...")`) +) + +// NewColorOptions returns an initialized ColorOptions instance. +func NewColorOptions(ioStreams genericclioptions.IOStreams) *ColorOptions { + return &ColorOptions{ + Type: []string{}, + Example: false, + IOStreams: ioStreams, + } +} + +// NewCmdColor returns new initialized instance of color sub command. +func NewCmdColor(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewColorOptions(ioStreams) + + cmd := &cobra.Command{ + Use: "color", + DisableFlagsInUseLine: true, + Aliases: []string{}, + Short: "Print colors supported by the current terminal", + TraverseChildren: true, + Long: colorLong, + Example: colorExample, + // NoArgs, ArbitraryArgs, OnlyValidArgs, MinimumArgs, MaximumArgs, ExactArgs, ExactArgs + ValidArgs: []string{}, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd, args)) + cmdutil.CheckErr(o.Run(args)) + }, + SuggestFor: []string{}, + } + + cmd.Flags().StringSliceVarP(&o.Type, "type", "t", o.Type, + fmt.Sprintf("Specify the color type, available types: `%s`.", strings.Join(availableTypes, ","))) + cmd.Flags().BoolVar(&o.Example, "example", o.Example, "Print code to show how to use color package.") + + return cmd +} + +// Complete completes all the required options. +func (o *ColorOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + if len(o.Type) == 0 { + o.Type = []string{"fg", "bg"} + + return nil + } + + if stringutil.StringIn("all", o.Type) { + o.Type = []string{"fg", "fg-hi", "bg", "bg-hi", "base"} + } + + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *ColorOptions) Validate(cmd *cobra.Command, args []string) error { + for _, t := range o.Type { + if !stringutil.StringIn(t, availableTypes) { + return cmdutil.UsageErrorf(cmd, "--type must be in: %s", strings.Join(availableTypes, ",")) + } + } + + return nil +} + +// Run executes a color subcommand using the specified options. +func (o *ColorOptions) Run(args []string) error { + if o.Example { + fmt.Fprint(o.Out, colorCodeExample+"\n") + + return nil + } + + var data [][]string + + // print table to stdout + table := tablewriter.NewWriter(o.Out) + + // set table attributes + table.SetAutoMergeCells(true) + table.SetRowLine(false) + table.SetBorder(false) + table.SetAutoWrapText(false) + table.SetAutoFormatHeaders(true) + table.SetHeaderAlignment(tablewriter.ALIGN_CENTER) + table.SetAlignment(tablewriter.ALIGN_LEFT) + table.SetHeader([]string{"Category", "Color Name", "Effect"}) + table.SetHeaderColor(tablewriter.Colors{tablewriter.FgGreenColor}, + tablewriter.Colors{tablewriter.FgRedColor}, + tablewriter.Colors{tablewriter.FgCyanColor}) + + for _, t := range o.Type { + switch t { + // Foreground text colors + case "fg": + fg := [][]string{ + {"fg", "black", color.BlackString("color.BlackString")}, + {"fg", "red", color.RedString("color.RedString")}, + {"fg", "green", color.GreenString("color.GreenString")}, + {"fg", "yellow", color.YellowString("color.YellowString")}, + {"fg", "blue", color.BlueString("color.BlueString")}, + {"fg", "magenta", color.MagentaString("color.MagentaString")}, + {"fg", "Cyan", color.CyanString("color.CyanString")}, + {"fg", "white", color.WhiteString("color.WhiteString")}, + } + data = append(data, fg...) + // Foreground Hi-Intensity text colors + case "fg-hi": + fg := [][]string{ + {"fg-hi", "black", color.HiBlackString("color.HiBlackString")}, + {"fg-hi", "red", color.HiRedString("color.HiRedString")}, + {"fg-hi", "green", color.HiGreenString("color.HiGreenString")}, + {"fg-hi", "yellow", color.HiYellowString("color.HiYellowString")}, + {"fg-hi", "blue", color.HiBlueString("color.HiBlueString")}, + {"fg-hi", "magenta", color.HiMagentaString("color.HiMagentaString")}, + {"fg-hi", "Cyan", color.HiCyanString("color.HiCyanString")}, + {"fg-hi", "white", color.HiWhiteString("color.HiWhiteString")}, + } + data = append(data, fg...) + + // Background text colors + case "bg": + bg := [][]string{ + {"bg", "black", color.New(color.FgWhite, color.BgBlack).SprintFunc()("color.BgBlack")}, + {"bg", "red", color.New(color.FgBlack, color.BgRed).SprintFunc()("color.BgRed")}, + {"bg", "greep", color.New(color.FgBlack, color.BgGreen).SprintFunc()("color.BgGreen")}, + {"bg", "yellow", color.New(color.FgWhite, color.BgYellow).SprintFunc()("color.BgYellow")}, + {"bg", "blue", color.New(color.FgWhite, color.BgBlue).SprintFunc()("color.BgBlue")}, + {"bg", "magenta", color.New(color.FgWhite, color.BgMagenta).SprintFunc()("color.BgMagenta")}, + {"bg", "cyan", color.New(color.FgWhite, color.BgCyan).SprintFunc()("color.BgCyan")}, + {"bg", "white", color.New(color.FgBlack, color.BgWhite).SprintFunc()("color.BgWhite")}, + } + data = append(data, bg...) + // Background Hi-Intensity text colors + case "bg-hi": + bghi := [][]string{ + {"bg-hi", "black", color.New(color.FgWhite, color.BgHiBlack).SprintFunc()("color.BgHiBlack")}, + {"bg-hi", "red", color.New(color.FgBlack, color.BgHiRed).SprintFunc()("color.BgHiRed")}, + {"bg-hi", "greep", color.New(color.FgBlack, color.BgHiGreen).SprintFunc()("color.BgHiGreen")}, + {"bg-hi", "yellow", color.New(color.FgWhite, color.BgHiYellow).SprintFunc()("color.BgHiYellow")}, + {"bg-hi", "blue", color.New(color.FgWhite, color.BgHiBlue).SprintFunc()("color.BgHiBlue")}, + {"bg-hi", "magenta", color.New(color.FgWhite, color.BgHiMagenta).SprintFunc()("color.BgHiMagenta")}, + {"bg-hi", "cyan", color.New(color.FgWhite, color.BgHiCyan).SprintFunc()("color.BgHiCyan")}, + {"bg-hi", "white", color.New(color.FgBlack, color.BgHiWhite).SprintFunc()("color.BgHiWhite")}, + } + data = append(data, bghi...) + // Base attributes + case "base": + base := [][]string{ + {"base", "black", color.New(color.FgGreen, color.Reset).SprintFunc()("color.Reset")}, + {"base", "red", color.New(color.FgGreen, color.Bold).SprintFunc()("color.Bold")}, + {"base", "greep", color.New(color.FgGreen, color.Faint).SprintFunc()("color.Faint")}, + {"base", "yellow", color.New(color.FgGreen, color.Italic).SprintFunc()("color.Italic")}, + {"base", "blue", color.New(color.FgGreen, color.Underline).SprintFunc()("color.Underline")}, + {"base", "magenta", color.New(color.FgGreen, color.BlinkSlow).SprintFunc()("color.BlinkSlow")}, + {"base", "cyan", color.New(color.FgGreen, color.BlinkRapid).SprintFunc()("color.BlinkRapid")}, + {"base", "white", color.New(color.FgGreen, color.ReverseVideo).SprintFunc()("color.ReverseVideo")}, + {"base", "white", color.New(color.FgGreen, color.Concealed).SprintFunc()("color.Concealed")}, + {"base", "white", color.New(color.FgGreen, color.CrossedOut).SprintFunc()("color.CrossedOut")}, + } + data = append(data, base...) + default: + } + } + + table.AppendBulk(data) + table.Render() + + return nil +} diff --git a/tools/imctl/internal/imctl/cmd/completion/completion.go b/tools/imctl/internal/imctl/cmd/completion/completion.go new file mode 100644 index 000000000..c9605a22d --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/completion/completion.go @@ -0,0 +1,283 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package completion output shell completion code for the specified shell (bash or zsh). +package completion + +import ( + "bytes" + "io" + + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" +) + +const defaultBoilerPlate = ` +# Copyright © 2023 OpenIM. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +` + +var ( + completionLong = templates.LongDesc(` + Output shell completion code for the specified shell (bash or zsh). + The shell code must be evaluated to provide interactive + completion of iamctl commands. This can be done by sourcing it from + the .bash_profile. + + Detailed instructions on how to do this are available here: + http://github.com/marmotedu/iam/docs/installation/iamctl.md#enabling-shell-autocompletion + + Note for zsh users: [1] zsh completions are only supported in versions of zsh >= 5.2`) + + completionExample = templates.Examples(` + # Installing bash completion on macOS using homebrew + ## If running Bash 3.2 included with macOS + brew install bash-completion + ## or, if running Bash 4.1+ + brew install bash-completion@2 + ## If iamctl is installed via homebrew, this should start working immediately. + ## If you've installed via other means, you may need add the completion to your completion directory + iamctl completion bash > $(brew --prefix)/etc/bash_completion.d/iamctl + + + # Installing bash completion on Linux + ## If bash-completion is not installed on Linux, please install the 'bash-completion' package + ## via your distribution's package manager. + ## Load the iamctl completion code for bash into the current shell + source <(iamctl completion bash) + ## Write bash completion code to a file and source if from .bash_profile + iamctl completion bash > ~/.iam/completion.bash.inc + printf " + # IAM shell completion + source '$HOME/.iam/completion.bash.inc' + " >> $HOME/.bash_profile + source $HOME/.bash_profile + + # Load the iamctl completion code for zsh[1] into the current shell + source <(iamctl completion zsh) + # Set the iamctl completion code for zsh[1] to autoload on startup + iamctl completion zsh > "${fpath[1]}/_iamctl"`) +) + +var completionShells = map[string]func(out io.Writer, boilerPlate string, cmd *cobra.Command) error{ + "bash": runCompletionBash, + "zsh": runCompletionZsh, +} + +// NewCmdCompletion creates the `completion` command. +func NewCmdCompletion(out io.Writer, boilerPlate string) *cobra.Command { + shells := []string{} + for s := range completionShells { + shells = append(shells, s) + } + + cmd := &cobra.Command{ + Use: "completion SHELL", + DisableFlagsInUseLine: true, + Short: "Output shell completion code for the specified shell (bash or zsh)", + Long: completionLong, + Example: completionExample, + Run: func(cmd *cobra.Command, args []string) { + err := RunCompletion(out, boilerPlate, cmd, args) + cmdutil.CheckErr(err) + }, + ValidArgs: shells, + } + + return cmd +} + +// RunCompletion checks given arguments and executes command. +func RunCompletion(out io.Writer, boilerPlate string, cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return cmdutil.UsageErrorf(cmd, "Shell not specified.") + } + + if len(args) > 1 { + return cmdutil.UsageErrorf(cmd, "Too many arguments. Expected only the shell type.") + } + + run, found := completionShells[args[0]] + if !found { + return cmdutil.UsageErrorf(cmd, "Unsupported shell type %q.", args[0]) + } + + return run(out, boilerPlate, cmd.Parent()) +} + +func runCompletionBash(out io.Writer, boilerPlate string, iamctl *cobra.Command) error { + if len(boilerPlate) == 0 { + boilerPlate = defaultBoilerPlate + } + + if _, err := out.Write([]byte(boilerPlate)); err != nil { + return err + } + + return iamctl.GenBashCompletion(out) +} + +func runCompletionZsh(out io.Writer, boilerPlate string, iamctl *cobra.Command) error { + zshHead := "#compdef iamctl\n" + + if _, err := out.Write([]byte(zshHead)); err != nil { + return err + } + + if len(boilerPlate) == 0 { + boilerPlate = defaultBoilerPlate + } + + if _, err := out.Write([]byte(boilerPlate)); err != nil { + return err + } + + zshInitialization := ` +__iamctl_bash_source() { + alias shopt=':' + emulate -L sh + setopt kshglob noshglob braceexpand + + source "$@" +} + +__iamctl_type() { + # -t is not supported by zsh + if [ "$1" == "-t" ]; then + shift + + # fake Bash 4 to disable "complete -o nospace". Instead + # "compopt +-o nospace" is used in the code to toggle trailing + # spaces. We don't support that, but leave trailing spaces on + # all the time + if [ "$1" = "__iamctl_compopt" ]; then + echo builtin + return 0 + fi + fi + type "$@" +} + +__iamctl_compgen() { + local completions w + completions=( $(compgen "$@") ) || return $? + + # filter by given word as prefix + while [[ "$1" = -* && "$1" != -- ]]; do + shift + shift + done + if [[ "$1" == -- ]]; then + shift + fi + for w in "${completions[@]}"; do + if [[ "${w}" = "$1"* ]]; then + echo "${w}" + fi + done +} + +__iamctl_compopt() { + true # don't do anything. Not supported by bashcompinit in zsh +} + +__iamctl_ltrim_colon_completions() +{ + if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then + # Remove colon-word prefix from COMPREPLY items + local colon_word=${1%${1##*:}} + local i=${#COMPREPLY[*]} + while [[ $((--i)) -ge 0 ]]; do + COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"} + done + fi +} + +__iamctl_get_comp_words_by_ref() { + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[${COMP_CWORD}-1]}" + words=("${COMP_WORDS[@]}") + cword=("${COMP_CWORD[@]}") +} + +__iamctl_filedir() { + # Don't need to do anything here. + # Otherwise we will get trailing space without "compopt -o nospace" + true +} + +autoload -U +X bashcompinit && bashcompinit + +# use word boundary patterns for BSD or GNU sed +LWORD='[[:<:]]' +RWORD='[[:>:]]' +if sed --help 2>&1 | grep -q 'GNU\|BusyBox'; then + LWORD='\<' + RWORD='\>' +fi + +__iamctl_convert_bash_to_zsh() { + sed \ + -e 's/declare -F/whence -w/' \ + -e 's/_get_comp_words_by_ref "\$@"/_get_comp_words_by_ref "\$*"/' \ + -e 's/local \([a-zA-Z0-9_]*\)=/local \1; \1=/' \ + -e 's/flags+=("\(--.*\)=")/flags+=("\1"); two_word_flags+=("\1")/' \ + -e 's/must_have_one_flag+=("\(--.*\)=")/must_have_one_flag+=("\1")/' \ + -e "s/${LWORD}_filedir${RWORD}/__iamctl_filedir/g" \ + -e "s/${LWORD}_get_comp_words_by_ref${RWORD}/__iamctl_get_comp_words_by_ref/g" \ + -e "s/${LWORD}__ltrim_colon_completions${RWORD}/__iamctl_ltrim_colon_completions/g" \ + -e "s/${LWORD}compgen${RWORD}/__iamctl_compgen/g" \ + -e "s/${LWORD}compopt${RWORD}/__iamctl_compopt/g" \ + -e "s/${LWORD}declare${RWORD}/builtin declare/g" \ + -e "s/\\\$(type${RWORD}/\$(__iamctl_type/g" \ + <<'BASH_COMPLETION_EOF' +` + if _, err := out.Write([]byte(zshInitialization)); err != nil { + return err + } + + buf := new(bytes.Buffer) + if err := iamctl.GenBashCompletion(buf); err != nil { + return err + } + + if _, err := out.Write(buf.Bytes()); err != nil { + return err + } + + zshTail := ` +BASH_COMPLETION_EOF +} + +__iamctl_bash_source <(__iamctl_convert_bash_to_zsh) +` + if _, err := out.Write([]byte(zshTail)); err != nil { + return err + } + + return nil +} diff --git a/tools/imctl/internal/imctl/cmd/info/info.go b/tools/imctl/internal/imctl/cmd/info/info.go new file mode 100644 index 000000000..fd635cb8f --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/info/info.go @@ -0,0 +1,119 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package info print the host information. +package info + +import ( + "fmt" + "reflect" + "strconv" + + hoststat "github.com/likexian/host-stat-go" + "github.com/openim-sigs/component-base/pkg/util/iputil" + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +// Info defines the host information struct. +type Info struct { + HostName string + IPAddress string + OSRelease string + CPUCore uint64 + MemTotal string + MemFree string +} + +// InfoOptions is an options struct to support 'info' sub command. +type InfoOptions struct { + genericclioptions.IOStreams +} + +var infoExample = templates.Examples(` + # Print the host information + iamctl info`) + +// NewInfoOptions returns an initialized InfoOptions instance. +func NewInfoOptions(ioStreams genericclioptions.IOStreams) *InfoOptions { + return &InfoOptions{ + IOStreams: ioStreams, + } +} + +// NewCmdInfo returns new initialized instance of 'info' sub command. +func NewCmdInfo(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewInfoOptions(ioStreams) + + cmd := &cobra.Command{ + Use: "info", + DisableFlagsInUseLine: true, + Aliases: []string{}, + Short: "Print the host information", + Long: "Print the host information.", + Example: infoExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Run(args)) + }, + SuggestFor: []string{}, + } + + return cmd +} + +// Run executes an info sub command using the specified options. +func (o *InfoOptions) Run(args []string) error { + var info Info + + hostInfo, err := hoststat.GetHostInfo() + if err != nil { + return fmt.Errorf("get host info failed!error:%w", err) + } + + info.HostName = hostInfo.HostName + info.OSRelease = hostInfo.Release + " " + hostInfo.OSBit + + memStat, err := hoststat.GetMemStat() + if err != nil { + return fmt.Errorf("get mem stat failed!error:%w", err) + } + + info.MemTotal = strconv.FormatUint(memStat.MemTotal, 10) + "M" + info.MemFree = strconv.FormatUint(memStat.MemFree, 10) + "M" + info.IPAddress = iputil.GetLocalIP() + + cpuStat, err := hoststat.GetCPUInfo() + if err != nil { + return fmt.Errorf("get cpu stat failed!error:%w", err) + } + + info.CPUCore = cpuStat.CoreCount + + s := reflect.ValueOf(&info).Elem() + typeOfInfo := s.Type() + + for i := 0; i < s.NumField(); i++ { + f := s.Field(i) + + v := fmt.Sprintf("%v", f.Interface()) + if v != "" { + fmt.Fprintf(o.Out, "%12s %v\n", typeOfInfo.Field(i).Name+":", f.Interface()) + } + } + + return nil +} diff --git a/tools/imctl/internal/imctl/cmd/jwt/args.go b/tools/imctl/internal/imctl/cmd/jwt/args.go new file mode 100644 index 000000000..58a22a4f0 --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/jwt/args.go @@ -0,0 +1,48 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jwt + +import ( + "fmt" + "strings" + + "github.com/openim-sigs/component-base/pkg/json" +) + +// ArgList defines a new pflag Value. +type ArgList map[string]string + +// String return value of ArgList in string format. +func (l ArgList) String() string { + data, _ := json.Marshal(l) + + return string(data) +} + +// Set sets the value of ArgList. +func (l ArgList) Set(arg string) error { + parts := strings.SplitN(arg, "=", 2) + if len(parts) != 2 { + return fmt.Errorf("invalid argument '%v'. Must use format 'key=value'. %v", arg, parts) + } + l[parts[0]] = parts[1] + + return nil +} + +// Type returns the type name of ArgList. +func (l ArgList) Type() string { + return "map" +} diff --git a/tools/imctl/internal/imctl/cmd/jwt/jwt.go b/tools/imctl/internal/imctl/cmd/jwt/jwt.go new file mode 100644 index 000000000..ce48633b6 --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/jwt/jwt.go @@ -0,0 +1,47 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package jwt can be used to sign/show/verify jwt token with given secretID and secretKey. +package jwt + +import ( + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +var jwtLong = templates.LongDesc(` + JWT command. + + This commands is used to sigin/show/verify jwt token.`) + +// NewCmdJWT returns new initialized instance of 'jwt' sub command. +func NewCmdJWT(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + cmd := &cobra.Command{ + Use: "jwt SUBCOMMAND", + DisableFlagsInUseLine: true, + Short: "JWT command-line tool", + Long: jwtLong, + Run: cmdutil.DefaultSubCommandRun(ioStreams.ErrOut), + } + + // add subcommands + cmd.AddCommand(NewCmdSign(f, ioStreams)) + cmd.AddCommand(NewCmdShow(f, ioStreams)) + cmd.AddCommand(NewCmdVerify(f, ioStreams)) + + return cmd +} diff --git a/tools/imctl/internal/imctl/cmd/jwt/jwt_show.go b/tools/imctl/internal/imctl/cmd/jwt/jwt_show.go new file mode 100644 index 000000000..14797d43f --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/jwt/jwt_show.go @@ -0,0 +1,144 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jwt + +import ( + "fmt" + "regexp" + + "github.com/golang-jwt/jwt/v4" + "github.com/openim-sigs/component-base/pkg/json" + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +const ( + showUsageStr = "show TOKEN" +) + +// ShowOptions is an options struct to support show subcommands. +type ShowOptions struct { + Compact bool + + genericclioptions.IOStreams +} + +var ( + showExample = templates.Examples(` + # Show header and Claims for a JWT token + iamctl jwt show XXX.XXX.XXX`) + + showUsageErrStr = fmt.Sprintf("expected '%s'.\nTOKEN is required arguments for the show command", showUsageStr) +) + +// NewShowOptions returns an initialized ShowOptions instance. +func NewShowOptions(ioStreams genericclioptions.IOStreams) *ShowOptions { + return &ShowOptions{ + Compact: false, + + IOStreams: ioStreams, + } +} + +// NewCmdShow returns new initialized instance of show sub command. +func NewCmdShow(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewShowOptions(ioStreams) + + cmd := &cobra.Command{ + Use: showUsageStr, + DisableFlagsInUseLine: true, + Aliases: []string{}, + Short: "Show header and claims for a JWT token", + Long: "Show header and claims for a JWT token", + TraverseChildren: true, + Example: showExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd, args)) + cmdutil.CheckErr(o.Run(args)) + }, + SuggestFor: []string{}, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return cmdutil.UsageErrorf(cmd, showUsageErrStr) + } + + return nil + }, + } + + // mark flag as deprecated + cmd.Flags().BoolVar(&o.Compact, "compact", o.Compact, "output compact JSON.") + + return cmd +} + +// Complete completes all the required options. +func (o *ShowOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *ShowOptions) Validate(cmd *cobra.Command, args []string) error { + return nil +} + +// Run executes a show subcommand using the specified options. +func (o *ShowOptions) Run(args []string) error { + // get the token + tokenData := []byte(args[0]) + + // trim possible whitespace from token + tokenData = regexp.MustCompile(`\s*$`).ReplaceAll(tokenData, []byte{}) + + token, err := jwt.Parse(string(tokenData), nil) + if token == nil { + return fmt.Errorf("malformed token: %w", err) + } + + // Print the token details + fmt.Println("Header:") + if err := printJSON(o.Compact, token.Header); err != nil { + return fmt.Errorf("failed to output header: %w", err) + } + + fmt.Println("Claims:") + if err := printJSON(o.Compact, token.Claims); err != nil { + return fmt.Errorf("failed to output claims: %w", err) + } + + return nil +} + +// printJSON print a json object in accordance with the prophecy (or the command line options). +func printJSON(compact bool, j interface{}) error { + var out []byte + var err error + + if !compact { + out, err = json.MarshalIndent(j, "", " ") + } else { + out, err = json.Marshal(j) + } + + if err == nil { + fmt.Println(string(out)) + } + + return err +} diff --git a/tools/imctl/internal/imctl/cmd/jwt/jwt_sign.go b/tools/imctl/internal/imctl/cmd/jwt/jwt_sign.go new file mode 100644 index 000000000..477ea0652 --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/jwt/jwt_sign.go @@ -0,0 +1,185 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jwt + +import ( + "errors" + "fmt" + "time" + + "github.com/golang-jwt/jwt/v4" + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/internal/pkg/middleware/auth" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +const ( + signUsageStr = "sign SECRETID SECRETKEY" +) + +// ErrSigningMethod defines invalid signing method error. +var ErrSigningMethod = errors.New("invalid signing method") + +// SignOptions is an options struct to support sign subcommands. +type SignOptions struct { + Timeout time.Duration + NotBefore time.Duration + Algorithm string + Audience string + Issuer string + Claims ArgList + Head ArgList + + genericclioptions.IOStreams +} + +var ( + signExample = templates.Examples(` + # Sign a token with secretID and secretKey + iamctl sign tgydj8d9EQSnFqKf iBdEdFNBLN1nR3fV + + # Sign a token with expires and sign method + iamctl sign tgydj8d9EQSnFqKf iBdEdFNBLN1nR3fV --timeout=2h --algorithm=HS256`) + + signUsageErrStr = fmt.Sprintf( + "expected '%s'.\nSECRETID and SECRETKEY are required arguments for the sign command", + signUsageStr, + ) +) + +// NewSignOptions returns an initialized SignOptions instance. +func NewSignOptions(ioStreams genericclioptions.IOStreams) *SignOptions { + return &SignOptions{ + Timeout: 2 * time.Hour, + Algorithm: "HS256", + Audience: auth.AuthzAudience, + Issuer: "iamctl", + Claims: make(ArgList), + Head: make(ArgList), + + IOStreams: ioStreams, + } +} + +// NewCmdSign returns new initialized instance of sign sub command. +func NewCmdSign(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewSignOptions(ioStreams) + + cmd := &cobra.Command{ + Use: signUsageStr, + DisableFlagsInUseLine: true, + Aliases: []string{}, + Short: "Sign a jwt token with given secretID and secretKey", + Long: "Sign a jwt token with given secretID and secretKey", + TraverseChildren: true, + Example: signExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd, args)) + cmdutil.CheckErr(o.Run(args)) + }, + SuggestFor: []string{}, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 2 { + return cmdutil.UsageErrorf(cmd, signUsageErrStr) + } + + return nil + }, + } + + // mark flag as deprecated + cmd.Flags().DurationVar(&o.Timeout, "timeout", o.Timeout, "JWT token expires time.") + cmd.Flags().DurationVar( + &o.NotBefore, + "not-before", + o.NotBefore, + "Identifies the time before which the JWT MUST NOT be accepted for processing.", + ) + cmd.Flags().StringVar( + &o.Algorithm, + "algorithm", + o.Algorithm, + "Signing algorithm - possible values are HS256, HS384, HS512.", + ) + cmd.Flags().StringVar( + &o.Audience, + "audience", + o.Audience, + "Identifies the recipients that the JWT is intended for.", + ) + cmd.Flags().StringVar(&o.Issuer, "issuer", o.Issuer, "Identifies the principal that issued the JWT.") + cmd.Flags().Var(&o.Claims, "claim", "Add additional claims. may be used more than once.") + cmd.Flags().Var(&o.Head, "header", "Add additional header params. may be used more than once.") + + return cmd +} + +// Complete completes all the required options. +func (o *SignOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *SignOptions) Validate(cmd *cobra.Command, args []string) error { + switch o.Algorithm { + case "HS256", "HS384", "HS512": + default: + return ErrSigningMethod + } + + return nil +} + +// Run executes a sign subcommand using the specified options. +func (o *SignOptions) Run(args []string) error { + claims := jwt.MapClaims{ + "exp": time.Now().Add(o.Timeout).Unix(), + "iat": time.Now().Unix(), + "nbf": time.Now().Add(o.NotBefore).Unix(), + "aud": o.Audience, + "iss": o.Issuer, + } + + // add command line claims + if len(o.Claims) > 0 { + for k, v := range o.Claims { + claims[k] = v + } + } + + // create a new token + token := jwt.NewWithClaims(jwt.GetSigningMethod(o.Algorithm), claims) + + // add command line headers + if len(o.Head) > 0 { + for k, v := range o.Head { + token.Header[k] = v + } + } + token.Header["kid"] = args[0] + + tokenString, err := token.SignedString([]byte(args[1])) + if err != nil { + return err + } + + fmt.Fprintf(o.Out, tokenString+"\n") + + return nil +} diff --git a/tools/imctl/internal/imctl/cmd/jwt/jwt_verify.go b/tools/imctl/internal/imctl/cmd/jwt/jwt_verify.go new file mode 100644 index 000000000..c06b91066 --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/jwt/jwt_verify.go @@ -0,0 +1,150 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jwt + +import ( + "fmt" + "regexp" + + "github.com/golang-jwt/jwt/v4" + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +const ( + veirfyUsageStr = "veirfy SECRETKEY TOKEN" +) + +// VerifyOptions is an options struct to support verify subcommands. +type VerifyOptions struct { + Compact bool + Debug bool + + genericclioptions.IOStreams +} + +var ( + verifyExample = templates.Examples(` + # Verify a JWT token + iamctl jwt verify XXX xxxxx.yyyyy.zzzzz`) + + verifyUsageErrStr = fmt.Sprintf( + "expected '%s'.\nSECRETKEY and TOKEN are required arguments for the subcmd1 command", + veirfyUsageStr, + ) +) + +// NewVerifyOptions returns an initialized VerifyOptions instance. +func NewVerifyOptions(ioStreams genericclioptions.IOStreams) *VerifyOptions { + return &VerifyOptions{ + Compact: false, + Debug: false, + + IOStreams: ioStreams, + } +} + +// NewCmdVerify returns new initialized instance of verify sub command. +func NewCmdVerify(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewVerifyOptions(ioStreams) + + cmd := &cobra.Command{ + Use: "verify", + DisableFlagsInUseLine: true, + Aliases: []string{"sub2"}, + Short: "Verify a JWT token", + Long: "Verify a JWT token", + TraverseChildren: true, + Example: verifyExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd, args)) + cmdutil.CheckErr(o.Run(args)) + }, + SuggestFor: []string{}, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 2 { + return cmdutil.UsageErrorf(cmd, verifyUsageErrStr) + } + + return nil + }, + } + + // mark flag as deprecated + cmd.Flags().BoolVar(&o.Compact, "compact", o.Compact, "Output compact JSON.") + cmd.Flags().BoolVar(&o.Debug, "debug", o.Debug, "Print out all kinds of debug data.") + + return cmd +} + +// Complete completes all the required options. +func (o *VerifyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *VerifyOptions) Validate(cmd *cobra.Command, args []string) error { + return nil +} + +// Run executes a verify subcommand using the specified options. +func (o *VerifyOptions) Run(args []string) error { + // get the token + tokenData := []byte(args[1]) + + // trim possible whitespace from token + tokenData = regexp.MustCompile(`\s*$`).ReplaceAll(tokenData, []byte{}) + + // Parse the token. Load the key from command line option + token, err := jwt.Parse(string(tokenData), func(t *jwt.Token) (interface{}, error) { + return []byte(args[0]), nil + }) + + // Print some debug data + if o.Debug && token != nil { + fmt.Println("Header:") + if pErr := printJSON(o.Compact, token.Header); pErr != nil { + return fmt.Errorf("failed to output header: %w", pErr) + } + + fmt.Println("Claims:") + if pErr := printJSON(o.Compact, token.Claims); pErr != nil { + return fmt.Errorf("failed to output claims: %w", pErr) + } + } + + // Print an error if we can't parse for some reason + if err != nil { + return fmt.Errorf("couldn't parse token: %w", err) + } + + // Is token invalid? + if !token.Valid { + return fmt.Errorf("token is invalid") + } + + if !o.Debug { + // Print the token details + if err := printJSON(o.Compact, token.Claims); err != nil { + return fmt.Errorf("failed to output claims: %w", err) + } + } + + return nil +} diff --git a/tools/imctl/internal/imctl/cmd/new/new.go b/tools/imctl/internal/imctl/cmd/new/new.go new file mode 100644 index 000000000..5f03863a0 --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/new/new.go @@ -0,0 +1,619 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package new used to generate demo command code. +// nolint: predeclared +package new + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "text/template" + + "github.com/openim-sigs/component-base/pkg/util/fileutil" + "github.com/spf13/cobra" + "golang.org/x/text/cases" + "golang.org/x/text/language" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +const ( + newUsageStr = "new CMD_NAME | CMD_NAME CMD_DESCRIPTION" +) + +var ( + newLong = templates.LongDesc(`Used to generate demo command source code file. + +Can use this command generate a command template file, and do some modify based on your needs. +This can improve your R&D efficiency.`) + + newExample = templates.Examples(` + # Create a default 'test' command file without a description + iamctl new test + + # Create a default 'test' command file in /tmp/ + iamctl new test -d /tmp/ + + # Create a default 'test' command file with a description + iamctl new test "This is a test command" + + # Create command 'test' with two subcommands + iamctl new -g test "This is a test command with two subcommands"`) + + newUsageErrStr = fmt.Sprintf( + "expected '%s'.\nat least CMD_NAME is a required argument for the new command", + newUsageStr, + ) + + cmdTemplate = `package {{.CommandName}} + +import ( + "fmt" + + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +const ( + {{.CommandName}}UsageStr = "{{.CommandName}} USERNAME PASSWORD" + maxStringLength = 17 +) + +// {{.CommandFunctionName}}Options is an options struct to support '{{.CommandName}}' sub command. +type {{.CommandFunctionName}}Options struct { + // options + StringOption string + StringSliceOption []string + IntOption int + BoolOption bool + + // args + Username string + Password string + + genericclioptions.IOStreams +} + +var ( + {{.CommandName}}Long = templates.LongDesc({{.Dot}}A longer description that spans multiple lines and likely contains examples +and usage of using your command. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.{{.Dot}}) + + {{.CommandName}}Example = templates.Examples({{.Dot}} + # Print all option values for {{.CommandName}} + iamctl {{.CommandName}} marmotedu marmotedupass{{.Dot}}) + + {{.CommandName}}UsageErrStr = fmt.Sprintf("expected '%s'.\nUSERNAME and PASSWORD are required arguments for the {{.CommandName}} command", {{.CommandName}}UsageStr) +) + +// New{{.CommandFunctionName}}Options returns an initialized {{.CommandFunctionName}}Options instance. +func New{{.CommandFunctionName}}Options(ioStreams genericclioptions.IOStreams) *{{.CommandFunctionName}}Options { + return &{{.CommandFunctionName}}Options{ + StringOption: "default", + IOStreams: ioStreams, + } +} + +// NewCmd{{.CommandFunctionName}} returns new initialized instance of '{{.CommandName}}' sub command. +func NewCmd{{.CommandFunctionName}}(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := New{{.CommandFunctionName}}Options(ioStreams) + + cmd := &cobra.Command{ + Use: {{.CommandName}}UsageStr, + DisableFlagsInUseLine: true, + Aliases: []string{}, + Short: "{{.CommandDescription}}", + TraverseChildren: true, + Long: {{.CommandName}}Long, + Example: {{.CommandName}}Example, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd, args)) + cmdutil.CheckErr(o.Run(args)) + }, + SuggestFor: []string{}, + Args: func(cmd *cobra.Command, args []string) error { + // nolint: gomnd // no need + if len(args) < 2 { + return cmdutil.UsageErrorf(cmd, {{.CommandName}}UsageErrStr) + } + + // if need args equal to zero, uncomment the following code + /* + if len(args) != 0 { + return cmdutil.UsageErrorf(cmd, "Unexpected args: %v", args) + } + */ + + return nil + }, + } + + // mark flag as deprecated + _ = cmd.Flags().MarkDeprecated("deprecated-opt", "This flag is deprecated and will be removed in future.") + cmd.Flags().StringVarP(&o.StringOption, "string", "", o.StringOption, "String option.") + cmd.Flags().StringSliceVar(&o.StringSliceOption, "slice", o.StringSliceOption, "String slice option.") + cmd.Flags().IntVarP(&o.IntOption, "int", "i", o.IntOption, "Int option.") + cmd.Flags().BoolVarP(&o.BoolOption, "bool", "b", o.BoolOption, "Bool option.") + + return cmd +} + +// Complete completes all the required options. +func (o *{{.CommandFunctionName}}Options) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + if o.StringOption != "" { + o.StringOption += "(complete)" + } + + o.Username = args[0] + o.Password = args[1] + + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *{{.CommandFunctionName}}Options) Validate(cmd *cobra.Command, args []string) error { + if len(o.StringOption) > maxStringLength { + return cmdutil.UsageErrorf(cmd, "--string length must less than 18") + } + + if o.IntOption < 0 { + return cmdutil.UsageErrorf(cmd, "--int must be a positive integer: %v", o.IntOption) + } + + return nil +} + +// Run executes a {{.CommandName}} sub command using the specified options. +func (o *{{.CommandFunctionName}}Options) Run(args []string) error { + fmt.Fprintf(o.Out, "The following is option values:\n") + fmt.Fprintf(o.Out, "==> --string: %v\n==> --slice: %v\n==> --int: %v\n==> --bool: %v\n", + o.StringOption, o.StringSliceOption, o.IntOption, o.BoolOption) + + fmt.Fprintf(o.Out, "\nThe following is args values:\n") + fmt.Fprintf(o.Out, "==> username: %v\n==> password: %v\n", o.Username, o.Password) + + return nil +} +` + + maincmdTemplate = `package {{.CommandName}} + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +const maxStringLength = 17 + +var ( + {{.CommandName}}Long = templates.LongDesc({{.Dot}} + Demo command. + + This commands show you how to implement a command with two sub commands.{{.Dot}}) +) + +// NewCmd{{.CommandFunctionName}} returns new initialized instance of '{{.CommandName}}' sub command. +func NewCmd{{.CommandFunctionName}}(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + cmd := &cobra.Command{ + Use: "{{.CommandName}} SUBCOMMAND", + DisableFlagsInUseLine: true, + Short: "{{.CommandDescription}}", + Long: {{.CommandName}}Long, + Run: cmdutil.DefaultSubCommandRun(ioStreams.ErrOut), + } + + // add subcommands + cmd.AddCommand(NewCmdSubCmd1(f, ioStreams)) + cmd.AddCommand(NewCmdSubCmd2(f, ioStreams)) + + // add persistent flags for '{{.CommandName}}' + cmdutil.AddCleanFlags(cmd) + + // persistent flag, we can get the value in subcommand via {{.Dot}}viper.Get{{.Dot}} + cmd.PersistentFlags().StringP("persistent", "p", "this is a persistent option", "Cobra persistent option.") + + // bind flags with viper + viper.BindPFlag("persistent", cmd.PersistentFlags().Lookup("persistent")) + + return cmd +} +` + subcmd1Template = `package {{.CommandName}} + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +const ( + subcmd1UsageStr = "subcmd1 USERNAME PASSWORD" +) + +// SubCmd1Options is an options struct to support subcmd1 subcommands. +type SubCmd1Options struct { + // options + StringOption string + StringSliceOption []string + IntOption int + BoolOption bool + PersistentOption string + + // args + Username string + Password string + + genericclioptions.IOStreams +} + +var ( + subcmd1Long = templates.LongDesc({{.Dot}}A longer description that spans multiple lines and likely contains examples +and usage of using your command. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.{{.Dot}}) + + subcmd1Example = templates.Examples({{.Dot}} + # Print all option values for subcmd1 + iamctl {{.CommandName}} subcmd1 marmotedu marmotedupass + + # Print all option values for subcmd1 with --persistent specified + iamctl {{.CommandName}} subcmd1 marmotedu marmotedupass --persistent="specified persistent option in command line"{{.Dot}}) + + subcmd1UsageErrStr = fmt.Sprintf("expected '%s'.\nUSERNAME and PASSWORD are required arguments for the subcmd1 command", subcmd1UsageStr) +) + +// NewSubCmd1Options returns an initialized SubCmd1Options instance. +func NewSubCmd1Options(ioStreams genericclioptions.IOStreams) *SubCmd1Options { + return &SubCmd1Options{ + StringOption: "default", + IOStreams: ioStreams, + } +} + +// NewCmdSubCmd1 returns new initialized instance of subcmd1 sub command. +func NewCmdSubCmd1(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewSubCmd1Options(ioStreams) + + cmd := &cobra.Command{ + Use: "subcmd1 USERNAME PASSWORD", + DisableFlagsInUseLine: true, + Aliases: []string{"sub1"}, + Short: "A brief description of your command", + TraverseChildren: true, + Long: subcmd1Long, + Example: subcmd1Example, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd, args)) + cmdutil.CheckErr(o.Run(args)) + }, + SuggestFor: []string{}, + Args: func(cmd *cobra.Command, args []string) error { + // nolint: gomnd // no need + if len(args) < 2 { + return cmdutil.UsageErrorf(cmd, subcmd1UsageErrStr) + } + + return nil + }, + } + + // mark flag as deprecated + _ = cmd.Flags().MarkDeprecated("deprecated-opt", "This flag is deprecated and will be removed in future.") + cmd.Flags().StringVarP(&o.StringOption, "string", "", o.StringOption, "String option.") + cmd.Flags().StringSliceVar(&o.StringSliceOption, "slice", o.StringSliceOption, "String slice option.") + cmd.Flags().IntVarP(&o.IntOption, "int", "i", o.IntOption, "Int option.") + cmd.Flags().BoolVarP(&o.BoolOption, "bool", "b", o.BoolOption, "Bool option.") + + return cmd +} + +// Complete completes all the required options. +func (o *SubCmd1Options) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + if o.StringOption != "" { + o.StringOption += "(complete)" + } + + o.PersistentOption = viper.GetString("persistent") + o.Username = args[0] + o.Password = args[1] + + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *SubCmd1Options) Validate(cmd *cobra.Command, args []string) error { + if len(o.StringOption) > maxStringLength { + return cmdutil.UsageErrorf(cmd, "--string length must less than 18") + } + + if o.IntOption < 0 { + return cmdutil.UsageErrorf(cmd, "--int must be a positive integer: %v", o.IntOption) + } + + return nil +} + +// Run executes a subcmd1 subcommand using the specified options. +func (o *SubCmd1Options) Run(args []string) error { + fmt.Fprintf(o.Out, "The following is option values:\n") + fmt.Fprintf(o.Out, "==> --string: %v\n==> --slice: %v\n==> --int: %v\n==> --bool: %v\n==> --persistent: %v\n", + o.StringOption, o.StringSliceOption, o.IntOption, o.BoolOption, o.PersistentOption) + + fmt.Fprintf(o.Out, "\nThe following is args values:\n") + fmt.Fprintf(o.Out, "==> username: %v\n==> password: %v\n", o.Username, o.Password) + return nil +} +` + subcmd2Template = `package {{.CommandName}} + +import ( + "fmt" + + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +// SubCmd2Options is an options struct to support subcmd2 subcommands. +type SubCmd2Options struct { + StringOption string + StringSliceOption []string + IntOption int + BoolOption bool + + genericclioptions.IOStreams +} + +var ( + subcmd2Long = templates.LongDesc({{.Dot}}A longer description that spans multiple lines and likely contains examples +and usage of using your command. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.{{.Dot}}) + + subcmd2Example = templates.Examples({{.Dot}} + # Print all option values for subcmd2 + iamctl {{.CommandName}} subcmd2{{.Dot}}) +) + +// NewSubCmd2Options returns an initialized SubCmd2Options instance. +func NewSubCmd2Options(ioStreams genericclioptions.IOStreams) *SubCmd2Options { + return &SubCmd2Options{ + StringOption: "default", + IOStreams: ioStreams, + } +} + +// NewCmdSubCmd2 returns new initialized instance of subcmd2 sub command. +func NewCmdSubCmd2(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewSubCmd2Options(ioStreams) + + cmd := &cobra.Command{ + Use: "subcmd2", + DisableFlagsInUseLine: true, + Aliases: []string{"sub2"}, + Short: "A brief description of your command", + TraverseChildren: true, + Long: subcmd2Long, + Example: subcmd2Example, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd, args)) + cmdutil.CheckErr(o.Run(args)) + }, + SuggestFor: []string{}, + } + + // mark flag as deprecated + cmd.Flags().StringVarP(&o.StringOption, "string", "", o.StringOption, "String option.") + cmd.Flags().StringSliceVar(&o.StringSliceOption, "slice", o.StringSliceOption, "String slice option.") + cmd.Flags().IntVarP(&o.IntOption, "int", "i", o.IntOption, "Int option.") + cmd.Flags().BoolVarP(&o.BoolOption, "bool", "b", o.BoolOption, "Bool option.") + + return cmd +} + +// Complete completes all the required options. +func (o *SubCmd2Options) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + if len(args) != 0 { + return cmdutil.UsageErrorf(cmd, "Unexpected args: %v", args) + } + + if o.StringOption != "" { + o.StringOption += "(complete)" + } + + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *SubCmd2Options) Validate(cmd *cobra.Command, args []string) error { + if len(o.StringOption) > maxStringLength { + return cmdutil.UsageErrorf(cmd, "--string length must less than 18") + } + + if o.IntOption < 0 { + return cmdutil.UsageErrorf(cmd, "--int must be a positive integer: %v", o.IntOption) + } + + return nil +} + +// Run executes a subcmd2 subcommand using the specified options. +func (o *SubCmd2Options) Run(args []string) error { + fmt.Fprintf(o.Out, "The following is option values:\n") + fmt.Fprintf(o.Out, "==> --string: %v\n==> --slice: %v\n==> --int: %v\n==> --bool: %v\n", + o.StringOption, o.StringSliceOption, o.IntOption, o.BoolOption) + return nil +} +` +) + +// NewOptions is an options struct to support 'new' sub command. +type NewOptions struct { + Group bool + Outdir string + + // command template options, will render to command template + CommandName string + CommandDescription string + CommandFunctionName string + Dot string + + genericclioptions.IOStreams +} + +// NewNewOptions returns an initialized NewOptions instance. +func NewNewOptions(ioStreams genericclioptions.IOStreams) *NewOptions { + return &NewOptions{ + Group: false, + Outdir: ".", + CommandDescription: "A brief description of your command", + Dot: "`", + IOStreams: ioStreams, + } +} + +// NewCmdNew returns new initialized instance of 'new' sub command. +func NewCmdNew(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewNewOptions(ioStreams) + + cmd := &cobra.Command{ + Use: newUsageStr, + DisableFlagsInUseLine: true, + Short: "Generate demo command code", + Long: newLong, + Example: newExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(cmd, args)) + cmdutil.CheckErr(o.Validate(cmd)) + cmdutil.CheckErr(o.Run(args)) + }, + Aliases: []string{}, + SuggestFor: []string{}, + } + + cmd.Flags().BoolVarP(&o.Group, "group", "g", o.Group, "Generate two subcommands.") + cmd.Flags().StringVarP(&o.Outdir, "outdir", "d", o.Outdir, "Where to create demo command files.") + + return cmd +} + +// Complete completes all the required options. +func (o *NewOptions) Complete(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return cmdutil.UsageErrorf(cmd, newUsageErrStr) + } + + o.CommandName = strings.ToLower(args[0]) + if len(args) > 1 { + o.CommandDescription = args[1] + } + + o.CommandFunctionName = cases.Title(language.English).String(o.CommandName) + + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *NewOptions) Validate(cmd *cobra.Command) error { + return nil +} + +// Run executes a new sub command using the specified options. +func (o *NewOptions) Run(args []string) error { + if o.Group { + return o.CreateCommandWithSubCommands() + } + + return o.CreateCommand() +} + +// CreateCommand create the command with options. +func (o *NewOptions) CreateCommand() error { + return o.GenerateGoCode(o.CommandName+".go", cmdTemplate) +} + +// CreateCommandWithSubCommands create sub commands with options. +func (o *NewOptions) CreateCommandWithSubCommands() error { + if err := o.GenerateGoCode(o.CommandName+".go", maincmdTemplate); err != nil { + return err + } + + if err := o.GenerateGoCode(o.CommandName+"_subcmd1.go", subcmd1Template); err != nil { + return err + } + + if err := o.GenerateGoCode(o.CommandName+"_subcmd2.go", subcmd2Template); err != nil { + return err + } + + return nil +} + +// GenerateGoCode generate go source file. +func (o *NewOptions) GenerateGoCode(name, codeTemplate string) error { + tmpl, err := template.New("cmd").Parse(codeTemplate) + if err != nil { + return err + } + + err = fileutil.EnsureDirAll(o.Outdir) + if err != nil { + return err + } + + filename := filepath.Join(o.Outdir, name) + fd, err := os.Create(filename) + if err != nil { + return err + } + defer fd.Close() + + err = tmpl.Execute(fd, o) + if err != nil { + return err + } + + fmt.Printf("Command file generated: %s\n", filename) + + return nil +} diff --git a/tools/imctl/internal/imctl/cmd/options/options.go b/tools/imctl/internal/imctl/cmd/options/options.go new file mode 100644 index 000000000..7f1dcfa9d --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/options/options.go @@ -0,0 +1,50 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package options print a list of global command-line options (applies to all commands). +package options + +import ( + "io" + + "github.com/spf13/cobra" + + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" +) + +var optionsExample = templates.Examples(` + # Print flags inherited by all commands + iamctl options`) + +// NewCmdOptions implements the options command. +func NewCmdOptions(out io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "options", + Short: "Print the list of flags inherited by all commands", + Long: "Print the list of flags inherited by all commands", + Example: optionsExample, + Run: func(cmd *cobra.Command, args []string) { + _ = cmd.Usage() + }, + } + + // The `options` command needs write its output to the `out` stream + // (typically stdout). Without calling SetOutput here, the Usage() + // function call will fall back to stderr. + cmd.SetOutput(out) + + templates.UseOptionsTemplates(cmd) + + return cmd +} diff --git a/tools/imctl/internal/imctl/cmd/policy/policy.go b/tools/imctl/internal/imctl/cmd/policy/policy.go new file mode 100644 index 000000000..4c6d0cbd6 --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/policy/policy.go @@ -0,0 +1,48 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package policy provides functions to manage authorization policies on iam platform. +package policy + +import ( + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +var policyLong = templates.LongDesc(` + Authorization policy management commands. + + This commands allow you to manage your authorization policy on iam platform.`) + +// NewCmdPolicy returns new initialized instance of 'policy' sub command. +func NewCmdPolicy(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + cmd := &cobra.Command{ + Use: "policy SUBCOMMAND", + DisableFlagsInUseLine: true, + Short: "Manage authorization policies on iam platform", + Long: policyLong, + Run: cmdutil.DefaultSubCommandRun(ioStreams.ErrOut), + } + + cmd.AddCommand(NewCmdCreate(f, ioStreams)) + cmd.AddCommand(NewCmdGet(f, ioStreams)) + cmd.AddCommand(NewCmdList(f, ioStreams)) + cmd.AddCommand(NewCmdDelete(f, ioStreams)) + cmd.AddCommand(NewCmdUpdate(f, ioStreams)) + + return cmd +} diff --git a/tools/imctl/internal/imctl/cmd/policy/policy_create.go b/tools/imctl/internal/imctl/cmd/policy/policy_create.go new file mode 100644 index 000000000..8f5e99c9b --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/policy/policy_create.go @@ -0,0 +1,137 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package policy + +import ( + "context" + "fmt" + + v1 "github.com/marmotedu/api/apiserver/v1" + apiclientv1 "github.com/marmotedu/marmotedu-sdk-go/marmotedu/service/iam/apiserver/v1" + "github.com/openim-sigs/component-base/pkg/json" + metav1 "github.com/openim-sigs/component-base/pkg/meta/v1" + "github.com/ory/ladon" + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +const ( + createUsageStr = "create POLICY_NAME POLICY" +) + +// CreateOptions is an options struct to support create subcommands. +type CreateOptions struct { + Policy *v1.Policy + + Client apiclientv1.APIV1Interface + genericclioptions.IOStreams +} + +var ( + createExample = templates.Examples(` + # Create a authorization policy + iamctl policy create foo '{"description":"This is a updated policy","subjects":["users:","users:maria","groups:admins"],"actions":["delete",""],"effect":"allow","resources":["resources:articles:<.*>","resources:printer"],"conditions":{"remoteIPAddress":{"type":"CIDRCondition","options":{"cidr":"192.168.0.1/16"}}}}'`) + + createUsageErrStr = fmt.Sprintf( + "expected '%s'.\nPOLICY_NAME and POLICY are required arguments for the create command", + createUsageStr, + ) +) + +// NewCreateOptions returns an initialized CreateOptions instance. +func NewCreateOptions(ioStreams genericclioptions.IOStreams) *CreateOptions { + return &CreateOptions{ + IOStreams: ioStreams, + } +} + +// NewCmdCreate returns new initialized instance of create sub command. +func NewCmdCreate(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewCreateOptions(ioStreams) + + cmd := &cobra.Command{ + Use: createUsageStr, + DisableFlagsInUseLine: true, + Aliases: []string{}, + Short: "Create a authorization policy resource", + TraverseChildren: true, + Long: "Create a authorization policy resource.", + Example: createExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd, args)) + cmdutil.CheckErr(o.Run(args)) + }, + SuggestFor: []string{}, + } + + return cmd +} + +// Complete completes all the required options. +func (o *CreateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return cmdutil.UsageErrorf(cmd, createUsageErrStr) + } + + var pol ladon.DefaultPolicy + if err := json.Unmarshal([]byte(args[1]), &pol); err != nil { + return err + } + + o.Policy = &v1.Policy{ + ObjectMeta: metav1.ObjectMeta{ + Name: args[0], + }, + Policy: v1.AuthzPolicy{ + DefaultPolicy: pol, + }, + } + + clientConfig, err := f.ToRESTConfig() + if err != nil { + return err + } + o.Client, err = apiclientv1.NewForConfig(clientConfig) + if err != nil { + return err + } + + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *CreateOptions) Validate(cmd *cobra.Command, args []string) error { + if errs := o.Policy.Validate(); len(errs) != 0 { + return errs.ToAggregate() + } + + return nil +} + +// Run executes a create subcommand using the specified options. +func (o *CreateOptions) Run(args []string) error { + ret, err := o.Client.Policies().Create(context.TODO(), o.Policy, metav1.CreateOptions{}) + if err != nil { + return err + } + + fmt.Fprintf(o.Out, "policy/%s created\n", ret.Name) + + return nil +} diff --git a/tools/imctl/internal/imctl/cmd/policy/policy_delete.go b/tools/imctl/internal/imctl/cmd/policy/policy_delete.go new file mode 100644 index 000000000..5bddd1a87 --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/policy/policy_delete.go @@ -0,0 +1,115 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package policy + +import ( + "context" + "fmt" + + "github.com/marmotedu/marmotedu-sdk-go/marmotedu/service/iam" + metav1 "github.com/openim-sigs/component-base/pkg/meta/v1" + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +const ( + deleteUsageStr = "delete POLICY_NAME" +) + +// DeleteOptions is an options struct to support delete subcommands. +type DeleteOptions struct { + Name string + + iamclient iam.IamInterface + genericclioptions.IOStreams +} + +var ( + deleteExample = templates.Examples(` + # Delete a policy resource + iamctl policy delete foo`) + + deleteUsageErrStr = fmt.Sprintf( + "expected '%s'.\nPOLICY_NAME is required arguments for the delete command", + deleteUsageStr, + ) +) + +// NewDeleteOptions returns an initialized DeleteOptions instance. +func NewDeleteOptions(ioStreams genericclioptions.IOStreams) *DeleteOptions { + return &DeleteOptions{ + IOStreams: ioStreams, + } +} + +// NewCmdDelete returns new initialized instance of delete sub command. +func NewCmdDelete(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewDeleteOptions(ioStreams) + + cmd := &cobra.Command{ + Use: "delete", + DisableFlagsInUseLine: true, + Aliases: []string{}, + Short: "Delete a authorization policy resource", + TraverseChildren: true, + Long: "Delete a authorization policy resource.", + Example: deleteExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd, args)) + cmdutil.CheckErr(o.Run()) + }, + SuggestFor: []string{}, + } + + return cmd +} + +// Complete completes all the required options. +func (o *DeleteOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + var err error + + if len(args) == 0 { + return cmdutil.UsageErrorf(cmd, deleteUsageErrStr) + } + + o.Name = args[0] + + o.iamclient, err = f.IAMClient() + if err != nil { + return err + } + + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *DeleteOptions) Validate(cmd *cobra.Command, args []string) error { + return nil +} + +// Run executes a delete subcommand using the specified options. +func (o *DeleteOptions) Run() error { + if err := o.iamclient.APIV1().Policies().Delete(context.TODO(), o.Name, metav1.DeleteOptions{}); err != nil { + return err + } + + fmt.Fprintf(o.Out, "policy/%s deleted\n", o.Name) + + return nil +} diff --git a/tools/imctl/internal/imctl/cmd/policy/policy_get.go b/tools/imctl/internal/imctl/cmd/policy/policy_get.go new file mode 100644 index 000000000..c2309388c --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/policy/policy_get.go @@ -0,0 +1,123 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package policy + +import ( + "bytes" + "context" + "fmt" + + "github.com/fatih/color" + "github.com/marmotedu/marmotedu-sdk-go/marmotedu/service/iam" + "github.com/openim-sigs/component-base/pkg/json" + metav1 "github.com/openim-sigs/component-base/pkg/meta/v1" + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +const ( + getUsageStr = "get POLICY_NAME" +) + +// GetOptions is an options struct to support get subcommands. +type GetOptions struct { + Name string + + iamclient iam.IamInterface + genericclioptions.IOStreams +} + +var ( + getExample = templates.Examples(` + # Display a policy resource + iamctl policy get foo`) + + getUsageErrStr = fmt.Sprintf("expected '%s'.\nPOLICY_NAME is required arguments for the get command", getUsageStr) +) + +// NewGetOptions returns an initialized GetOptions instance. +func NewGetOptions(ioStreams genericclioptions.IOStreams) *GetOptions { + return &GetOptions{ + IOStreams: ioStreams, + } +} + +// NewCmdGet returns new initialized instance of get sub command. +func NewCmdGet(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewGetOptions(ioStreams) + + cmd := &cobra.Command{ + Use: getUsageStr, + DisableFlagsInUseLine: true, + Aliases: []string{}, + Short: "Display a authorization policy resource", + TraverseChildren: true, + Long: "Display a authorization policy resource.", + Example: getExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd, args)) + cmdutil.CheckErr(o.Run(args)) + }, + SuggestFor: []string{}, + } + + return cmd +} + +// Complete completes all the required options. +func (o *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + var err error + + if len(args) == 0 { + return cmdutil.UsageErrorf(cmd, getUsageErrStr) + } + + o.Name = args[0] + + o.iamclient, err = f.IAMClient() + if err != nil { + return err + } + + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *GetOptions) Validate(cmd *cobra.Command, args []string) error { + return nil +} + +// Run executes a get subcommand using the specified options. +func (o *GetOptions) Run(args []string) error { + policy, err := o.iamclient.APIV1().Policies().Get(context.TODO(), o.Name, metav1.GetOptions{}) + if err != nil { + return err + } + + bf := bytes.NewBuffer([]byte{}) + jsonEncoder := json.NewEncoder(bf) + jsonEncoder.SetEscapeHTML(false) + if err := jsonEncoder.Encode(policy.Policy); err != nil { + return err + } + + fmt.Fprintf(o.Out, "%12s %s\n", color.RedString(policy.Name+":"), bf.String()) + + return nil +} diff --git a/tools/imctl/internal/imctl/cmd/policy/policy_list.go b/tools/imctl/internal/imctl/cmd/policy/policy_list.go new file mode 100644 index 000000000..eca0c75ea --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/policy/policy_list.go @@ -0,0 +1,127 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package policy + +import ( + "bytes" + "context" + "fmt" + + "github.com/fatih/color" + "github.com/marmotedu/marmotedu-sdk-go/marmotedu/service/iam" + "github.com/openim-sigs/component-base/pkg/json" + metav1 "github.com/openim-sigs/component-base/pkg/meta/v1" + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +const ( + defaultLimit = 1000 +) + +// ListOptions is an options struct to support list subcommands. +type ListOptions struct { + Offset int64 + Limit int64 + + iamclient iam.IamInterface + genericclioptions.IOStreams +} + +var listExample = templates.Examples(` + # Display all policy resources + iamctl poicy list + + # Display all policy resources with offset and limit + iamctl policy list --offset=0 --limit=10`) + +// NewListOptions returns an initialized ListOptions instance. +func NewListOptions(ioStreams genericclioptions.IOStreams) *ListOptions { + return &ListOptions{ + Offset: 0, + Limit: defaultLimit, + IOStreams: ioStreams, + } +} + +// NewCmdList returns new initialized instance of list sub command. +func NewCmdList(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewListOptions(ioStreams) + + cmd := &cobra.Command{ + Use: "list", + DisableFlagsInUseLine: true, + Aliases: []string{}, + Short: "Display all authorization policy resources", + TraverseChildren: true, + Long: "Display all authorization policy resources.", + Example: listExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd, args)) + cmdutil.CheckErr(o.Run(args)) + }, + SuggestFor: []string{}, + } + + cmd.Flags().Int64VarP(&o.Offset, "offset", "o", o.Offset, "Specify the offset of the first row to be returned.") + cmd.Flags().Int64VarP(&o.Limit, "limit", "l", o.Limit, "Specify the amount records to be returned.") + + return cmd +} + +// Complete completes all the required options. +func (o *ListOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + var err error + + o.iamclient, err = f.IAMClient() + if err != nil { + return err + } + + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *ListOptions) Validate(cmd *cobra.Command, args []string) error { + return nil +} + +// Run executes a list subcommand using the specified options. +func (o *ListOptions) Run(args []string) error { + policies, err := o.iamclient.APIV1().Policies().List(context.TODO(), metav1.ListOptions{ + Offset: &o.Offset, + Limit: &o.Limit, + }) + if err != nil { + return err + } + + for _, pol := range policies.Items { + bf := bytes.NewBuffer([]byte{}) + jsonEncoder := json.NewEncoder(bf) + jsonEncoder.SetEscapeHTML(false) + if err := jsonEncoder.Encode(pol.Policy); err != nil { + return err + } + + fmt.Fprintf(o.Out, "%12s %s\n", color.RedString(pol.Name+":"), bf.String()) + } + + return nil +} diff --git a/tools/imctl/internal/imctl/cmd/policy/policy_update.go b/tools/imctl/internal/imctl/cmd/policy/policy_update.go new file mode 100644 index 000000000..f23e0964f --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/policy/policy_update.go @@ -0,0 +1,131 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package policy + +import ( + "context" + "fmt" + + v1 "github.com/marmotedu/api/apiserver/v1" + "github.com/marmotedu/marmotedu-sdk-go/marmotedu/service/iam" + "github.com/openim-sigs/component-base/pkg/json" + metav1 "github.com/openim-sigs/component-base/pkg/meta/v1" + "github.com/ory/ladon" + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +const ( + updateUsageStr = "update POLICY_NAME POLICY" +) + +// UpdateOptions is an options struct to support update subcommands. +type UpdateOptions struct { + Policy *v1.Policy + + iamclient iam.IamInterface + genericclioptions.IOStreams +} + +var ( + updateExample = templates.Examples(` + # Update a authorization policy with new policy. + iamctl policy update foo "{"description":"This is a updated policy","subjects":["users:","users:maria","groups:admins"],"actions":["delete",""],"effect":"allow","resources":["resources:articles:<.*>","resources:printer"],"conditions":{"remoteIPAddress":{"type":"CIDRCondition","options":{"cidr":"192.168.0.1/16"}}}}"`) + + updateUsageErrStr = fmt.Sprintf( + "expected '%s'.\nPOLICY_NAME and POLICY is required arguments for the update command", + updateUsageStr, + ) +) + +// NewUpdateOptions returns an initialized UpdateOptions instance. +func NewUpdateOptions(ioStreams genericclioptions.IOStreams) *UpdateOptions { + return &UpdateOptions{ + IOStreams: ioStreams, + } +} + +// NewCmdUpdate returns new initialized instance of update sub command. +func NewCmdUpdate(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewUpdateOptions(ioStreams) + + cmd := &cobra.Command{ + Use: updateUsageStr, + DisableFlagsInUseLine: true, + Aliases: []string{}, + Short: "Update a authorization policy resource", + TraverseChildren: true, + Long: "Update a authorization policy resource.", + Example: updateExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd, args)) + cmdutil.CheckErr(o.Run(args)) + }, + SuggestFor: []string{}, + } + + return cmd +} + +// Complete completes all the required options. +func (o *UpdateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + var err error + + if len(args) < 2 { + return cmdutil.UsageErrorf(cmd, updateUsageErrStr) + } + + var pol ladon.DefaultPolicy + if err = json.Unmarshal([]byte(args[1]), &pol); err != nil { + return err + } + + o.Policy = &v1.Policy{ + ObjectMeta: metav1.ObjectMeta{ + Name: args[0], + }, + Policy: v1.AuthzPolicy{ + DefaultPolicy: pol, + }, + } + + o.iamclient, err = f.IAMClient() + if err != nil { + return err + } + + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *UpdateOptions) Validate(cmd *cobra.Command, args []string) error { + return nil +} + +// Run executes a update subcommand using the specified options. +func (o *UpdateOptions) Run(args []string) error { + ret, err := o.iamclient.APIV1().Policies().Update(context.TODO(), o.Policy, metav1.UpdateOptions{}) + if err != nil { + return err + } + + fmt.Fprintf(o.Out, "policy/%s updated\n", ret.Name) + + return nil +} diff --git a/tools/imctl/internal/imctl/cmd/secret/secret.go b/tools/imctl/internal/imctl/cmd/secret/secret.go new file mode 100644 index 000000000..b210fc7a7 --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/secret/secret.go @@ -0,0 +1,61 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package secret provides functions to manage secrets on iam platform. +package secret + +import ( + "github.com/olekukonko/tablewriter" + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +var secretLong = templates.LongDesc(` + Secret management commands. + + This commands allow you to manage your secret on iam platform.`) + +// NewCmdSecret returns new initialized instance of 'secret' sub command. +func NewCmdSecret(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + cmd := &cobra.Command{ + Use: "secret SUBCOMMAND", + DisableFlagsInUseLine: true, + Short: "Manage secrets on iam platform", + Long: secretLong, + Run: cmdutil.DefaultSubCommandRun(ioStreams.ErrOut), + } + + cmd.AddCommand(NewCmdCreate(f, ioStreams)) + cmd.AddCommand(NewCmdGet(f, ioStreams)) + cmd.AddCommand(NewCmdList(f, ioStreams)) + cmd.AddCommand(NewCmdDelete(f, ioStreams)) + cmd.AddCommand(NewCmdUpdate(f, ioStreams)) + + return cmd +} + +// setHeader set headers for secret commands. +func setHeader(table *tablewriter.Table) *tablewriter.Table { + table.SetHeader([]string{"Name", "SecretID", "SecretKey", "Expires", "Created"}) + table.SetHeaderColor(tablewriter.Colors{tablewriter.FgGreenColor}, + tablewriter.Colors{tablewriter.FgRedColor}, + tablewriter.Colors{tablewriter.FgCyanColor}, + tablewriter.Colors{tablewriter.FgMagentaColor}, + tablewriter.Colors{tablewriter.FgGreenColor}) + + return table +} diff --git a/tools/imctl/internal/imctl/cmd/secret/secret_create.go b/tools/imctl/internal/imctl/cmd/secret/secret_create.go new file mode 100644 index 000000000..4a27cf654 --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/secret/secret_create.go @@ -0,0 +1,145 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package secret + +import ( + "context" + "fmt" + "time" + + v1 "github.com/marmotedu/api/apiserver/v1" + apiclientv1 "github.com/marmotedu/marmotedu-sdk-go/marmotedu/service/iam/apiserver/v1" + metav1 "github.com/openim-sigs/component-base/pkg/meta/v1" + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +const ( + createUsageStr = "create SECRET_NAME" +) + +// CreateOptions is an options struct to support create subcommands. +type CreateOptions struct { + Description string + Expires int64 + + Secret *v1.Secret + + Client apiclientv1.APIV1Interface + + genericclioptions.IOStreams +} + +var ( + createLong = templates.LongDesc(`Create secret resource. + +This will generate secretID and secretKey which can be used to sign JWT token.`) + + createExample = templates.Examples(` + # Create secret which will expired after 2 hours + iamctl secret create foo + + # Create secret with a specified expire time and description + iamctl secret create foo --expires=1988121600 --description="secret for iam"`) + + createUsageErrStr = fmt.Sprintf( + "expected '%s'.\nSECRET_NAME is required arguments for the create command", + createUsageStr, + ) +) + +// NewCreateOptions returns an initialized CreateOptions instance. +func NewCreateOptions(ioStreams genericclioptions.IOStreams) *CreateOptions { + return &CreateOptions{ + Expires: time.Now().Add(144 * time.Hour).Unix(), + IOStreams: ioStreams, + } +} + +// NewCmdCreate returns new initialized instance of create sub command. +func NewCmdCreate(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewCreateOptions(ioStreams) + + cmd := &cobra.Command{ + Use: createUsageStr, + DisableFlagsInUseLine: true, + Aliases: []string{}, + Short: "Create secret resource", + TraverseChildren: true, + Long: createLong, + Example: createExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd, args)) + cmdutil.CheckErr(o.Run(args)) + }, + SuggestFor: []string{}, + } + + cmd.Flags().StringVar(&o.Description, "description", o.Description, "The descriptin of the secret.") + cmd.Flags().Int64Var(&o.Expires, "expires", o.Expires, "The expire time of the secret.") + + return cmd +} + +// Complete completes all the required options. +func (o *CreateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return cmdutil.UsageErrorf(cmd, createUsageErrStr) + } + + o.Secret = &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: args[0], + }, + Expires: o.Expires, + Description: o.Description, + } + + clientConfig, err := f.ToRESTConfig() + if err != nil { + return err + } + o.Client, err = apiclientv1.NewForConfig(clientConfig) + if err != nil { + return err + } + + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *CreateOptions) Validate(cmd *cobra.Command, args []string) error { + if errs := o.Secret.Validate(); len(errs) != 0 { + return errs.ToAggregate() + } + + return nil +} + +// Run executes a create subcommand using the specified options. +func (o *CreateOptions) Run(args []string) error { + secret, err := o.Client.Secrets().Create(context.TODO(), o.Secret, metav1.CreateOptions{}) + if err != nil { + return err + } + + fmt.Fprintf(o.Out, "secret/%s created\n", secret.Name) + + return nil +} diff --git a/tools/imctl/internal/imctl/cmd/secret/secret_delete.go b/tools/imctl/internal/imctl/cmd/secret/secret_delete.go new file mode 100644 index 000000000..b9eaf95dd --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/secret/secret_delete.go @@ -0,0 +1,115 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package secret + +import ( + "context" + "fmt" + + "github.com/marmotedu/marmotedu-sdk-go/marmotedu/service/iam" + metav1 "github.com/openim-sigs/component-base/pkg/meta/v1" + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +const ( + deleteUsageStr = "delete SECRET_NAME" +) + +// DeleteOptions is an options struct to support delete subcommands. +type DeleteOptions struct { + Name string + + genericclioptions.IOStreams + + iamclient iam.IamInterface +} + +var ( + deleteExample = templates.Examples(` + # Delete secret foo + iamctl secret delete foo`) + + deleteUsageErrStr = fmt.Sprintf( + "expected '%s'.\nSECRET_NAME is required arguments for the delete command", + deleteUsageStr, + ) +) + +// NewDeleteOptions returns an initialized DeleteOptions instance. +func NewDeleteOptions(ioStreams genericclioptions.IOStreams) *DeleteOptions { + return &DeleteOptions{ + IOStreams: ioStreams, + } +} + +// NewCmdDelete returns new initialized instance of delete sub command. +func NewCmdDelete(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewDeleteOptions(ioStreams) + + cmd := &cobra.Command{ + Use: deleteUsageStr, + DisableFlagsInUseLine: true, + Aliases: []string{}, + Short: "Delete a secret resource", + TraverseChildren: true, + Long: "Delete a secret resource.", + Example: deleteExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd, args)) + cmdutil.CheckErr(o.Run()) + }, + SuggestFor: []string{}, + } + + return cmd +} + +// Complete completes all the required options. +func (o *DeleteOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + var err error + if len(args) == 0 { + return cmdutil.UsageErrorf(cmd, deleteUsageErrStr) + } + + o.Name = args[0] + + o.iamclient, err = f.IAMClient() + if err != nil { + return err + } + + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *DeleteOptions) Validate(cmd *cobra.Command, args []string) error { + return nil +} + +// Run executes a delete subcommand using the specified options. +func (o *DeleteOptions) Run() error { + if err := o.iamclient.APIV1().Secrets().Delete(context.TODO(), o.Name, metav1.DeleteOptions{}); err != nil { + return err + } + + fmt.Fprintf(o.Out, "secret/%s deleted\n", o.Name) + + return nil +} diff --git a/tools/imctl/internal/imctl/cmd/secret/secret_get.go b/tools/imctl/internal/imctl/cmd/secret/secret_get.go new file mode 100644 index 000000000..d9811cb09 --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/secret/secret_get.go @@ -0,0 +1,130 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package secret + +import ( + "context" + "fmt" + "time" + + "github.com/marmotedu/marmotedu-sdk-go/marmotedu/service/iam" + "github.com/olekukonko/tablewriter" + metav1 "github.com/openim-sigs/component-base/pkg/meta/v1" + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +const ( + getUsageStr = "get SECRET_NAME" +) + +// GetOptions is an options struct to support get subcommands. +type GetOptions struct { + Name string + + iamclient iam.IamInterface + + genericclioptions.IOStreams +} + +var ( + getExample = templates.Examples(` + # Get a specified secret information + iamctl secret get foo`) + + getUsageErrStr = fmt.Sprintf("expected '%s'.\nSECRET_NAME is required arguments for the get command", getUsageStr) +) + +// NewGetOptions returns an initialized GetOptions instance. +func NewGetOptions(ioStreams genericclioptions.IOStreams) *GetOptions { + return &GetOptions{ + IOStreams: ioStreams, + } +} + +// NewCmdGet returns new initialized instance of get sub command. +func NewCmdGet(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewGetOptions(ioStreams) + + cmd := &cobra.Command{ + Use: "get SECRET_NAME", + DisableFlagsInUseLine: true, + Aliases: []string{}, + Short: "Display a secret resource", + TraverseChildren: true, + Long: "Display a secret resource.", + Example: getExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd, args)) + cmdutil.CheckErr(o.Run(args)) + }, + SuggestFor: []string{}, + } + + return cmd +} + +// Complete completes all the required options. +func (o *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + var err error + if len(args) == 0 { + return cmdutil.UsageErrorf(cmd, getUsageErrStr) + } + + o.Name = args[0] + + o.iamclient, err = f.IAMClient() + if err != nil { + return err + } + + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *GetOptions) Validate(cmd *cobra.Command, args []string) error { + return nil +} + +// Run executes a get subcommand using the specified options. +func (o *GetOptions) Run(args []string) error { + secret, err := o.iamclient.APIV1().Secrets().Get(context.TODO(), o.Name, metav1.GetOptions{}) + if err != nil { + return err + } + + table := tablewriter.NewWriter(o.Out) + + data := [][]string{ + { + secret.Name, + secret.SecretID, + secret.SecretKey, + time.Unix(secret.Expires, 0).Format("2006-01-02 15:04:05"), + secret.CreatedAt.Format("2006-01-02 15:04:05"), + }, + } + + table = setHeader(table) + table = cmdutil.TableWriterDefaultConfig(table) + table.AppendBulk(data) + table.Render() + + return nil +} diff --git a/tools/imctl/internal/imctl/cmd/secret/secret_list.go b/tools/imctl/internal/imctl/cmd/secret/secret_list.go new file mode 100644 index 000000000..121a73d6f --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/secret/secret_list.go @@ -0,0 +1,131 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package secret + +import ( + "context" + "time" + + "github.com/marmotedu/marmotedu-sdk-go/marmotedu/service/iam" + "github.com/olekukonko/tablewriter" + metav1 "github.com/openim-sigs/component-base/pkg/meta/v1" + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +const ( + defaltLimit = 1000 +) + +// ListOptions is an options struct to support list subcommands. +type ListOptions struct { + Offset int64 + Limit int64 + + iamclient iam.IamInterface + genericclioptions.IOStreams +} + +var listExample = templates.Examples(` + # List all secrets + iamctl secret list + + # List secrets with limit and offset + iamctl secret list --offset=0 --limit=5`) + +// NewListOptions returns an initialized ListOptions instance. +func NewListOptions(ioStreams genericclioptions.IOStreams) *ListOptions { + return &ListOptions{ + IOStreams: ioStreams, + Offset: 0, + Limit: defaltLimit, + } +} + +// NewCmdList returns new initialized instance of list sub command. +func NewCmdList(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewListOptions(ioStreams) + + cmd := &cobra.Command{ + Use: "list", + DisableFlagsInUseLine: true, + Aliases: []string{}, + Short: "Display all secret resources", + TraverseChildren: true, + Long: "Display all secret resources.", + Example: listExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd, args)) + cmdutil.CheckErr(o.Run(args)) + }, + SuggestFor: []string{}, + } + + cmd.Flags().Int64VarP(&o.Offset, "offset", "o", o.Offset, "Specify the offset of the first row to be returned.") + cmd.Flags().Int64VarP(&o.Limit, "limit", "l", o.Limit, "Specify the amount records to be returned.") + + return cmd +} + +// Complete completes all the required options. +func (o *ListOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + var err error + o.iamclient, err = f.IAMClient() + if err != nil { + return err + } + + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *ListOptions) Validate(cmd *cobra.Command, args []string) error { + return nil +} + +// Run executes a list subcommand using the specified options. +func (o *ListOptions) Run(args []string) error { + secrets, err := o.iamclient.APIV1().Secrets().List(context.TODO(), metav1.ListOptions{ + Offset: &o.Offset, + Limit: &o.Limit, + }) + if err != nil { + return err + } + + data := make([][]string, 0, len(secrets.Items)) + table := tablewriter.NewWriter(o.Out) + + for _, secret := range secrets.Items { + data = append(data, []string{ + secret.Name, + secret.SecretID, + secret.SecretKey, + time.Unix(secret.Expires, 0).Format("2006-01-02 15:04:05"), + secret.CreatedAt.Format("2006-01-02 15:04:05"), + }) + } + + table = setHeader(table) + table = cmdutil.TableWriterDefaultConfig(table) + table.AppendBulk(data) + table.Render() + + return nil +} diff --git a/tools/imctl/internal/imctl/cmd/secret/secret_update.go b/tools/imctl/internal/imctl/cmd/secret/secret_update.go new file mode 100644 index 000000000..9b0f98352 --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/secret/secret_update.go @@ -0,0 +1,128 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package secret + +import ( + "context" + "fmt" + + v1 "github.com/marmotedu/api/apiserver/v1" + "github.com/marmotedu/marmotedu-sdk-go/marmotedu/service/iam" + metav1 "github.com/openim-sigs/component-base/pkg/meta/v1" + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +const ( + updateUsageStr = "update SECRET_NAME" +) + +// UpdateOptions is an options struct to support update subcommands. +type UpdateOptions struct { + Description string + Expires int64 + + Secret *v1.Secret + + iamclient iam.IamInterface + genericclioptions.IOStreams +} + +var ( + updateExample = templates.Examples(` + # Update a secret resource + iamctl secret update foo --expires=4h --description="new description"`) + + updateUsageErrStr = fmt.Sprintf( + "expected '%s'.\nSECRET_NAME is required arguments for the update command", + updateUsageStr, + ) +) + +// NewUpdateOptions returns an initialized UpdateOptions instance. +func NewUpdateOptions(ioStreams genericclioptions.IOStreams) *UpdateOptions { + return &UpdateOptions{ + IOStreams: ioStreams, + } +} + +// NewCmdUpdate returns new initialized instance of update sub command. +func NewCmdUpdate(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewUpdateOptions(ioStreams) + + cmd := &cobra.Command{ + Use: "update SECRET_NAME", + DisableFlagsInUseLine: true, + Aliases: []string{}, + Short: "Update a secret resource", + TraverseChildren: true, + Long: "Update a secret resource.", + Example: updateExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd, args)) + cmdutil.CheckErr(o.Run(args)) + }, + SuggestFor: []string{}, + } + + cmd.Flags().StringVar(&o.Description, "description", o.Description, "The description of the secret.") + cmd.Flags().Int64Var(&o.Expires, "expires", o.Expires, "The expires of the secret.") + + return cmd +} + +// Complete completes all the required options. +func (o *UpdateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + var err error + if len(args) == 0 { + return cmdutil.UsageErrorf(cmd, updateUsageErrStr) + } + + o.Secret = &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: args[0], + }, + Description: o.Description, + Expires: o.Expires, + } + + o.iamclient, err = f.IAMClient() + if err != nil { + return err + } + + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *UpdateOptions) Validate(cmd *cobra.Command, args []string) error { + return nil +} + +// Run executes a update subcommand using the specified options. +func (o *UpdateOptions) Run(args []string) error { + secret, err := o.iamclient.APIV1().Secrets().Update(context.TODO(), o.Secret, metav1.UpdateOptions{}) + if err != nil { + return err + } + + fmt.Fprintf(o.Out, "secret/%s updated\n", secret.Name) + + return nil +} diff --git a/tools/imctl/internal/imctl/cmd/set/set.go b/tools/imctl/internal/imctl/cmd/set/set.go new file mode 100644 index 000000000..3c318da7c --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/set/set.go @@ -0,0 +1,45 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package set used to set specific features on objects. +package set + +import ( + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +var setLong = templates.LongDesc(` + Configure objects. + + These commands help you make changes to existing objects.`) + +// NewCmdSet returns an initialized Command instance for 'set' sub command. +func NewCmdSet(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + cmd := &cobra.Command{ + Use: "set SUBCOMMAND", + DisableFlagsInUseLine: true, + Short: "Set specific features on objects", + Long: setLong, + Run: cmdutil.DefaultSubCommandRun(ioStreams.ErrOut), + } + + // add subcommands + // cmd.AddCommand(NewCmdDB(f, ioStreams)) + + return cmd +} diff --git a/tools/imctl/internal/imctl/cmd/set/set_db.go b/tools/imctl/internal/imctl/cmd/set/set_db.go new file mode 100644 index 000000000..e885a9d98 --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/set/set_db.go @@ -0,0 +1,213 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package set + +import ( + "fmt" + + "github.com/jinzhu/gorm" + // import mysql driver. + _ "github.com/jinzhu/gorm/dialects/mysql" + v1 "github.com/marmotedu/api/apiserver/v1" + metav1 "github.com/openim-sigs/component-base/pkg/meta/v1" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +// DBOptions is an options struct to support 'db' sub command. +type DBOptions struct { + host string + username string + password string + Database string + + drop bool + admin bool + + genericclioptions.IOStreams +} + +var setExample = templates.Examples(` + # Create new iam platform database and tables + iamctl set db --mysql.host=127.0.0.1:3306 --mysql.username=iam --mysql.password=iamxxxx --mysql.database=iam + + # Create new iam platform database and tables with a administrator inserted + iamctl set db --admin --mysql.host=127.0.0.1:3306 --mysql.username=iam --mysql.password=iamxxxx --mysql.database=iam + + # drop and create iam platform database and tables + iamctl set db -d --mysql.host=127.0.0.1:3306 --mysql.username=iam --mysql.password=iamxxxx --mysql.database=iam`) + +// NewDBOptions returns an initialized DBOptions instance. +func NewDBOptions(ioStreams genericclioptions.IOStreams) *DBOptions { + return &DBOptions{ + host: "127.0.0.1:3306", + username: "root", + password: "root", + Database: "iam", + + drop: false, + admin: false, + IOStreams: ioStreams, + } +} + +// NewCmdDB returns new initialized instance of 'db' sub command. +func NewCmdDB(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewDBOptions(ioStreams) + + cmd := &cobra.Command{ + Use: "db", + DisableFlagsInUseLine: true, + Short: "Initialize the iam database", + Long: "Initialize the iam database.", + Example: setExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete()) + cmdutil.CheckErr(o.Validate()) + cmdutil.CheckErr(o.Run()) + }, + Aliases: []string{}, + SuggestFor: []string{}, + } + + cmd.Flags().StringVar(&o.host, "host", o.host, "MySQL service host address.") + cmd.Flags().StringVar(&o.username, "username", o.username, "username for access to mysql service.") + cmd.Flags().StringVar(&o.password, "password", o.password, + "password for access to mysql, should be used pair with password.") + cmd.Flags().StringVar(&o.Database, "database", o.Database, "Database name for the server to use.") + cmd.Flags().BoolVarP(&o.drop, "drop", "d", o.drop, "drop database if exists, pls double check the db name!") + cmd.Flags().BoolVar(&o.admin, "admin", o.drop, "Insert a administrator user to the database.") + + _ = viper.BindPFlags(cmd.Flags()) + + return cmd +} + +// Complete completes all the required options. +func (o *DBOptions) Complete() error { + // o.host = viper.GetString("host") + // o.username = viper.GetString("username") + // o.password = viper.GetString("password") + // o.Database = viper.GetString("database") + // o.drop = viper.GetBool("drop") + // o.admin = viper.GetBool("admin") + + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *DBOptions) Validate() error { + return nil +} + +// Run executes a db sub command using the specified options. +func (o *DBOptions) Run() error { + if err := o.ensureSchema(); err != nil { + return err + } + + dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s", + o.username, o.password, o.host, o.Database, true, "Local") + + db, err := gorm.Open("mysql", dsn) + if err != nil { + return err + } + defer db.Close() + + if db.HasTable(&v1.User{}) { + db.AutoMigrate(&v1.User{}) + } else { + db.Debug().CreateTable(&v1.User{}) + } + + if db.HasTable(&v1.Secret{}) { + db.AutoMigrate(&v1.Secret{}) + } else { + db.CreateTable(&v1.Secret{}) + } + + if db.HasTable(&v1.Policy{}) { + db.AutoMigrate(&v1.Policy{}) + } else { + db.CreateTable(&v1.Policy{}) + } + fmt.Fprintf(o.Out, "update table success\n") + + if o.admin { + if err := o.insertAdministrator(db); err != nil { + return err + } + } + + return nil +} + +func (o *DBOptions) ensureSchema() error { + dsn := fmt.Sprintf("%s:%s@tcp(%s)/?charset=utf8", o.username, o.password, o.host) + + db, err := gorm.Open("mysql", dsn) + if err != nil { + return err + } + defer db.Close() + + if o.drop { + dropSQL := fmt.Sprintf("DROP DATABASE IF EXISTS %s", o.Database) + if err := db.Exec(dropSQL).Error; err != nil { + return err + } + fmt.Fprintf(o.Out, "drop database %s success\n", o.Database) + } + + createSQL := fmt.Sprintf("CREATE DATABASE if not exists %s CHARSET utf8 COLLATE utf8_general_ci", o.Database) + if err := db.Exec(createSQL).Error; err != nil { + return err + } + + fmt.Fprintf(o.Out, "create database %s success\n", o.Database) + + return nil +} + +func (o *DBOptions) insertAdministrator(db *gorm.DB) error { + if !o.drop { + return nil + } + + // insert administrator user + user := v1.User{ + ObjectMeta: metav1.ObjectMeta{ + Name: "admin", + }, + Nickname: "admin", + Password: "Admin@2020", + Email: "colin404@foxmail.com", + Phone: "1812884xxxx", + IsAdmin: 1, + } + + if err := db.Create(&user).Error; err != nil { + return err + } + + fmt.Fprintf(o.Out, "insert administrator success\n") + + return nil +} diff --git a/tools/imctl/internal/imctl/cmd/user/user.go b/tools/imctl/internal/imctl/cmd/user/user.go new file mode 100644 index 000000000..7db64508b --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/user/user.go @@ -0,0 +1,62 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package user provides functions to manage users on iam platform. +package user + +import ( + "github.com/olekukonko/tablewriter" + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +var userLong = templates.LongDesc(` + User management commands. + +Administrator can use all subcommands, non-administrator only allow to use create/get/upate. When call get/update non-administrator only allow to operate their own resources, if permission not allowed, will return an 'Permission denied' error.`) + +// NewCmdUser returns new initialized instance of 'user' sub command. +func NewCmdUser(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + cmd := &cobra.Command{ + Use: "user SUBCOMMAND", + DisableFlagsInUseLine: true, + Short: "Manage users on iam platform", + Long: userLong, + Run: cmdutil.DefaultSubCommandRun(ioStreams.ErrOut), + } + + cmd.AddCommand(NewCmdCreate(f, ioStreams)) + cmd.AddCommand(NewCmdGet(f, ioStreams)) + cmd.AddCommand(NewCmdList(f, ioStreams)) + cmd.AddCommand(NewCmdDelete(f, ioStreams)) + cmd.AddCommand(NewCmdUpdate(f, ioStreams)) + + return cmd +} + +// setHeader set headers for user commands. +func setHeader(table *tablewriter.Table) *tablewriter.Table { + table.SetHeader([]string{"Name", "Nickname", "Email", "Phone", "Created", "Updated"}) + table.SetHeaderColor(tablewriter.Colors{tablewriter.FgGreenColor}, + tablewriter.Colors{tablewriter.FgRedColor}, + tablewriter.Colors{tablewriter.FgCyanColor}, + tablewriter.Colors{tablewriter.FgMagentaColor}, + tablewriter.Colors{tablewriter.FgGreenColor}, + tablewriter.Colors{tablewriter.FgWhiteColor}) + + return table +} diff --git a/tools/imctl/internal/imctl/cmd/user/user_create.go b/tools/imctl/internal/imctl/cmd/user/user_create.go new file mode 100644 index 000000000..57766f341 --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/user/user_create.go @@ -0,0 +1,149 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package user + +import ( + "context" + "fmt" + + v1 "github.com/marmotedu/api/apiserver/v1" + apiclientv1 "github.com/marmotedu/marmotedu-sdk-go/marmotedu/service/iam/apiserver/v1" + metav1 "github.com/openim-sigs/component-base/pkg/meta/v1" + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +const ( + createUsageStr = "create USERNAME PASSWORD EMAIL" +) + +// CreateOptions is an options struct to support create subcommands. +type CreateOptions struct { + Nickname string + Phone string + + User *v1.User + + Client apiclientv1.APIV1Interface + genericclioptions.IOStreams +} + +var ( + createLong = templates.LongDesc(`Create a user on iam platform. +If nickname not specified, username will be used.`) + + createExample = templates.Examples(` + # Create user with given input + iamctl user create foo Foo@2020 foo@foxmail.com + + # Create user wt + iamctl user create foo Foo@2020 foo@foxmail.com --phone=18128845xxx --nickname=colin`) + + createUsageErrStr = fmt.Sprintf( + "expected '%s'.\nUSERNAME, PASSWORD and EMAIL are required arguments for the create command", + createUsageStr, + ) +) + +// NewCreateOptions returns an initialized CreateOptions instance. +func NewCreateOptions(ioStreams genericclioptions.IOStreams) *CreateOptions { + return &CreateOptions{ + IOStreams: ioStreams, + } +} + +// NewCmdCreate returns new initialized instance of create sub command. +func NewCmdCreate(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewCreateOptions(ioStreams) + + cmd := &cobra.Command{ + Use: createUsageStr, + DisableFlagsInUseLine: true, + Aliases: []string{}, + Short: "Create a user resource", + TraverseChildren: true, + Long: createLong, + Example: createExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd, args)) + cmdutil.CheckErr(o.Run(args)) + }, + SuggestFor: []string{}, + } + + // mark flag as deprecated + cmd.Flags().StringVar(&o.Nickname, "nickname", o.Nickname, "The nickname of the user.") + cmd.Flags().StringVar(&o.Phone, "phone", o.Phone, "The phone number of the user.") + + return cmd +} + +// Complete completes all the required options. +func (o *CreateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + var err error + if len(args) < 3 { + return cmdutil.UsageErrorf(cmd, createUsageErrStr) + } + + if o.Nickname == "" { + o.Nickname = args[0] + } + + o.User = &v1.User{ + ObjectMeta: metav1.ObjectMeta{ + Name: args[0], + }, + Nickname: o.Nickname, + Password: args[1], + Email: args[2], + Phone: o.Phone, + } + + clientConfig, err := f.ToRESTConfig() + if err != nil { + return err + } + o.Client, err = apiclientv1.NewForConfig(clientConfig) + if err != nil { + return err + } + + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *CreateOptions) Validate(cmd *cobra.Command, args []string) error { + if errs := o.User.Validate(); len(errs) != 0 { + return errs.ToAggregate() + } + + return nil +} + +// Run executes a create subcommand using the specified options. +func (o *CreateOptions) Run(args []string) error { + ret, err := o.Client.Users().Create(context.TODO(), o.User, metav1.CreateOptions{}) + if err != nil { + return err + } + + fmt.Fprintf(o.Out, "user/%s created\n", ret.Name) + + return nil +} diff --git a/tools/imctl/internal/imctl/cmd/user/user_delete.go b/tools/imctl/internal/imctl/cmd/user/user_delete.go new file mode 100644 index 000000000..da070ccc8 --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/user/user_delete.go @@ -0,0 +1,114 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package user + +import ( + "context" + "fmt" + + "github.com/marmotedu/marmotedu-sdk-go/marmotedu/service/iam" + metav1 "github.com/openim-sigs/component-base/pkg/meta/v1" + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +const ( + deleteUsageStr = "delete USERNAME" +) + +// DeleteOptions is an options struct to support delete subcommands. +type DeleteOptions struct { + Name string + + iamclient iam.IamInterface + genericclioptions.IOStreams +} + +var ( + deleteExample = templates.Examples(` + # Delete user foo from platform + iamctl user delete foo`) + + deleteUsageErrStr = fmt.Sprintf( + "expected '%s'.\nUSERNAME is required arguments for the delete command", + deleteUsageStr, + ) +) + +// NewDeleteOptions returns an initialized DeleteOptions instance. +func NewDeleteOptions(ioStreams genericclioptions.IOStreams) *DeleteOptions { + return &DeleteOptions{ + IOStreams: ioStreams, + } +} + +// NewCmdDelete returns new initialized instance of delete sub command. +func NewCmdDelete(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewDeleteOptions(ioStreams) + + cmd := &cobra.Command{ + Use: deleteUsageStr, + DisableFlagsInUseLine: true, + Aliases: []string{}, + Short: "Delete a user resource from iam platform (Administrator rights required)", + TraverseChildren: true, + Long: "Delete a user resource from iam platform, only administrator can do this operation.", + Example: deleteExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd, args)) + cmdutil.CheckErr(o.Run()) + }, + SuggestFor: []string{}, + } + + return cmd +} + +// Complete completes all the required options. +func (o *DeleteOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + var err error + if len(args) == 0 { + return cmdutil.UsageErrorf(cmd, deleteUsageErrStr) + } + + o.Name = args[0] + + o.iamclient, err = f.IAMClient() + if err != nil { + return err + } + + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *DeleteOptions) Validate(cmd *cobra.Command, args []string) error { + return nil +} + +// Run executes a delete subcommand using the specified options. +func (o *DeleteOptions) Run() error { + if err := o.iamclient.APIV1().Users().Delete(context.TODO(), o.Name, metav1.DeleteOptions{}); err != nil { + return err + } + + fmt.Fprintf(o.Out, "user/%s deleted\n", o.Name) + + return nil +} diff --git a/tools/imctl/internal/imctl/cmd/user/user_get.go b/tools/imctl/internal/imctl/cmd/user/user_get.go new file mode 100644 index 000000000..c0b621ebd --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/user/user_get.go @@ -0,0 +1,129 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package user + +import ( + "context" + "fmt" + + "github.com/marmotedu/marmotedu-sdk-go/marmotedu/service/iam" + "github.com/olekukonko/tablewriter" + metav1 "github.com/openim-sigs/component-base/pkg/meta/v1" + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +const ( + getUsageStr = "get USERNAME" +) + +// GetOptions is an options struct to support get subcommands. +type GetOptions struct { + Name string + + iamclient iam.IamInterface + genericclioptions.IOStreams +} + +var ( + getExample = templates.Examples(` + # Get user foo detail information + iamctl user get foo`) + + getUsageErrStr = fmt.Sprintf("expected '%s'.\nUSERNAME is required arguments for the get command", getUsageStr) +) + +// NewGetOptions returns an initialized GetOptions instance. +func NewGetOptions(ioStreams genericclioptions.IOStreams) *GetOptions { + return &GetOptions{ + IOStreams: ioStreams, + } +} + +// NewCmdGet returns new initialized instance of get sub command. +func NewCmdGet(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewGetOptions(ioStreams) + + cmd := &cobra.Command{ + Use: getUsageStr, + DisableFlagsInUseLine: true, + Aliases: []string{}, + Short: "Display a user resource.", + TraverseChildren: true, + Long: `Display a user resource.`, + Example: getExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd, args)) + cmdutil.CheckErr(o.Run(args)) + }, + SuggestFor: []string{}, + } + + return cmd +} + +// Complete completes all the required options. +func (o *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + var err error + if len(args) == 0 { + return cmdutil.UsageErrorf(cmd, getUsageErrStr) + } + + o.Name = args[0] + + o.iamclient, err = f.IAMClient() + if err != nil { + return err + } + + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *GetOptions) Validate(cmd *cobra.Command, args []string) error { + return nil +} + +// Run executes a get subcommand using the specified options. +func (o *GetOptions) Run(args []string) error { + user, err := o.iamclient.APIV1().Users().Get(context.TODO(), o.Name, metav1.GetOptions{}) + if err != nil { + return err + } + + table := tablewriter.NewWriter(o.Out) + + data := [][]string{ + { + user.Name, + user.Nickname, + user.Email, + user.Phone, + user.CreatedAt.Format("2006-01-02 15:04:05"), + user.UpdatedAt.Format("2006-01-02 15:04:05"), + }, + } + + table = setHeader(table) + table = cmdutil.TableWriterDefaultConfig(table) + table.AppendBulk(data) + table.Render() + + return nil +} diff --git a/tools/imctl/internal/imctl/cmd/user/user_list.go b/tools/imctl/internal/imctl/cmd/user/user_list.go new file mode 100644 index 000000000..9726f35a8 --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/user/user_list.go @@ -0,0 +1,128 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package user + +import ( + "context" + + "github.com/marmotedu/marmotedu-sdk-go/marmotedu/service/iam" + "github.com/olekukonko/tablewriter" + metav1 "github.com/openim-sigs/component-base/pkg/meta/v1" + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +const ( + defaultLimit = 1000 +) + +// ListOptions is an options struct to support list subcommands. +type ListOptions struct { + Offset int64 + Limit int64 + + iamclient iam.IamInterface + genericclioptions.IOStreams +} + +var listExample = templates.Examples(` + # List all users + iamctl user list + + # List users with limit and offset + iamctl user list --offset=0 --limit=10`) + +// NewListOptions returns an initialized ListOptions instance. +func NewListOptions(ioStreams genericclioptions.IOStreams) *ListOptions { + return &ListOptions{ + IOStreams: ioStreams, + Offset: 0, + Limit: defaultLimit, + } +} + +// NewCmdList returns new initialized instance of list sub command. +func NewCmdList(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewListOptions(ioStreams) + + cmd := &cobra.Command{ + Use: "list", + DisableFlagsInUseLine: true, + Aliases: []string{}, + Short: "Display all users in iam platform (Administrator rights required)", + TraverseChildren: true, + Long: "Display all users in iam platform (Administrator rights required).", + Example: listExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd, args)) + cmdutil.CheckErr(o.Run(args)) + }, + SuggestFor: []string{}, + } + + cmd.Flags().Int64VarP(&o.Offset, "offset", "o", o.Offset, "Specify the offset of the first row to be returned.") + cmd.Flags().Int64VarP(&o.Limit, "limit", "l", o.Limit, "Specify the amount records to be returned.") + + return cmd +} + +// Complete completes all the required options. +func (o *ListOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + var err error + + o.iamclient, err = f.IAMClient() + if err != nil { + return err + } + + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *ListOptions) Validate(cmd *cobra.Command, args []string) error { + return nil +} + +// Run executes a list subcommand using the specified options. +func (o *ListOptions) Run(args []string) error { + users, err := o.iamclient.APIV1().Users().List(context.TODO(), metav1.ListOptions{ + Offset: &o.Offset, + Limit: &o.Limit, + }) + if err != nil { + return err + } + + data := make([][]string, 0, 1) + table := tablewriter.NewWriter(o.Out) + + for _, user := range users.Items { + data = append(data, []string{ + user.Name, user.Nickname, user.Email, + user.Phone, user.CreatedAt.Format("2006-01-02 15:04:05"), user.UpdatedAt.Format("2006-01-02 15:04:05"), + }) + } + + table = setHeader(table) + table = cmdutil.TableWriterDefaultConfig(table) + table.AppendBulk(data) + table.Render() + + return nil +} diff --git a/tools/imctl/internal/imctl/cmd/user/user_update.go b/tools/imctl/internal/imctl/cmd/user/user_update.go new file mode 100644 index 000000000..e6e569331 --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/user/user_update.go @@ -0,0 +1,142 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package user + +import ( + "context" + "fmt" + + "github.com/marmotedu/marmotedu-sdk-go/marmotedu/service/iam" + metav1 "github.com/openim-sigs/component-base/pkg/meta/v1" + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +const ( + updateUsageStr = "update USERNAME" +) + +// UpdateOptions is an options struct to support update subcommands. +type UpdateOptions struct { + Name string + Nickname string + Email string + Phone string + + iamclient iam.IamInterface + genericclioptions.IOStreams +} + +var ( + updateLong = templates.LongDesc(`Update a user resource. + +Can only update nickname, email and phone. + +NOTICE: field will be updated to zero value if not specified.`) + + updateExample = templates.Examples(` + # Update use foo's information + iamctl user update foo --nickname=foo2 --email=foo@qq.com --phone=1812883xxxx`) + + updateUsageErrStr = fmt.Sprintf( + "expected '%s'.\nUSERNAME is required arguments for the update command", + updateUsageStr, + ) +) + +// NewUpdateOptions returns an initialized UpdateOptions instance. +func NewUpdateOptions(ioStreams genericclioptions.IOStreams) *UpdateOptions { + return &UpdateOptions{ + IOStreams: ioStreams, + } +} + +// NewCmdUpdate returns new initialized instance of update sub command. +func NewCmdUpdate(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewUpdateOptions(ioStreams) + + cmd := &cobra.Command{ + Use: updateUsageStr, + DisableFlagsInUseLine: true, + Aliases: []string{}, + Short: "Update a user resource", + TraverseChildren: true, + Long: updateLong, + Example: updateExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd, args)) + cmdutil.CheckErr(o.Run(args)) + }, + SuggestFor: []string{}, + } + + cmd.Flags().StringVar(&o.Nickname, "nickname", o.Nickname, "The nickname of the user.") + cmd.Flags().StringVar(&o.Email, "email", o.Email, "The email of the user.") + cmd.Flags().StringVar(&o.Phone, "phone", o.Phone, "The phone number of the user.") + + return cmd +} + +// Complete completes all the required options. +func (o *UpdateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + var err error + if len(args) == 0 { + return cmdutil.UsageErrorf(cmd, updateUsageErrStr) + } + + o.Name = args[0] + o.iamclient, err = f.IAMClient() + if err != nil { + return err + } + + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *UpdateOptions) Validate(cmd *cobra.Command, args []string) error { + return nil +} + +// Run executes an update subcommand using the specified options. +func (o *UpdateOptions) Run(args []string) error { + user, err := o.iamclient.APIV1().Users().Get(context.TODO(), o.Name, metav1.GetOptions{}) + if err != nil { + return err + } + + if o.Nickname != "" { + user.Nickname = o.Nickname + } + if o.Email != "" { + user.Email = o.Email + } + if o.Phone != "" { + user.Phone = o.Phone + } + + ret, err := o.iamclient.APIV1().Users().Update(context.TODO(), user, metav1.UpdateOptions{}) + if err != nil { + return err + } + + fmt.Fprintf(o.Out, "user/%s updated\n", ret.Name) + + return nil +} diff --git a/tools/imctl/internal/imctl/cmd/util/factory.go b/tools/imctl/internal/imctl/cmd/util/factory.go new file mode 100644 index 000000000..65634b014 --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/util/factory.go @@ -0,0 +1,43 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package util + +import ( + "github.com/marmotedu/marmotedu-sdk-go/marmotedu/service/iam" + restclient "github.com/marmotedu/marmotedu-sdk-go/rest" + + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +// Factory provides abstractions that allow the IAM command to be extended across multiple types +// of resources and different API sets. +// The rings are here for a reason. In order for composers to be able to provide alternative factory implementations +// they need to provide low level pieces of *certain* functions so that when the factory calls back into itself +// it uses the custom version of the function. Rather than try to enumerate everything that someone would want to +// override +// we split the factory into rings, where each ring can depend on methods in an earlier ring, but cannot depend +// upon peer methods in its own ring. +// TODO: make the functions interfaces +// TODO: pass the various interfaces on the factory directly into the command constructors (so the +// commands are decoupled from the factory). +type Factory interface { + genericclioptions.RESTClientGetter + + // IAMClient gives you back an external iamclient + IAMClient() (*iam.IamClient, error) + + // Returns a RESTClient for accessing IAM resources or an error. + RESTClient() (*restclient.RESTClient, error) +} diff --git a/tools/imctl/internal/imctl/cmd/util/factory_client_access.go b/tools/imctl/internal/imctl/cmd/util/factory_client_access.go new file mode 100644 index 000000000..9285245f4 --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/util/factory_client_access.go @@ -0,0 +1,66 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// this file contains factories with no other dependencies + +package util + +import ( + "github.com/marmotedu/marmotedu-sdk-go/marmotedu/service/iam" + restclient "github.com/marmotedu/marmotedu-sdk-go/rest" + "github.com/marmotedu/marmotedu-sdk-go/tools/clientcmd" + + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +type factoryImpl struct { + clientGetter genericclioptions.RESTClientGetter +} + +func NewFactory(clientGetter genericclioptions.RESTClientGetter) Factory { + if clientGetter == nil { + panic("attempt to instantiate client_access_factory with nil clientGetter") + } + + f := &factoryImpl{ + clientGetter: clientGetter, + } + + return f +} + +func (f *factoryImpl) ToRESTConfig() (*restclient.Config, error) { + return f.clientGetter.ToRESTConfig() +} + +func (f *factoryImpl) ToRawIAMConfigLoader() clientcmd.ClientConfig { + return f.clientGetter.ToRawIAMConfigLoader() +} + +func (f *factoryImpl) IAMClient() (*iam.IamClient, error) { + clientConfig, err := f.ToRESTConfig() + if err != nil { + return nil, err + } + return iam.NewForConfig(clientConfig) +} + +func (f *factoryImpl) RESTClient() (*restclient.RESTClient, error) { + clientConfig, err := f.ToRESTConfig() + if err != nil { + return nil, err + } + setIAMDefaults(clientConfig) + return restclient.RESTClientFor(clientConfig) +} diff --git a/tools/imctl/internal/imctl/cmd/util/helpers.go b/tools/imctl/internal/imctl/cmd/util/helpers.go new file mode 100644 index 000000000..064a1e191 --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/util/helpers.go @@ -0,0 +1,397 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package util + +import ( + "bytes" + "fmt" + "io" + "net/http" + "net/url" + "os" + "strings" + "time" + + "github.com/marmotedu/errors" + "github.com/marmotedu/marmotedu-sdk-go/marmotedu" + restclient "github.com/marmotedu/marmotedu-sdk-go/rest" + "github.com/olekukonko/tablewriter" + "github.com/parnurzeal/gorequest" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/marmotedu/iam/pkg/log" +) + +const ( + // DefaultErrorExitCode defines the default exit code. + DefaultErrorExitCode = 1 +) + +type debugError interface { + DebugError() (msg string, args []interface{}) +} + +var fatalErrHandler = fatal + +// BehaviorOnFatal allows you to override the default behavior when a fatal +// error occurs, which is to call os.Exit(code). You can pass 'panic' as a function +// here if you prefer the panic() over os.Exit(1). +func BehaviorOnFatal(f func(string, int)) { + fatalErrHandler = f +} + +// DefaultBehaviorOnFatal allows you to undo any previous override. Useful in +// tests. +func DefaultBehaviorOnFatal() { + fatalErrHandler = fatal +} + +// fatal prints the message (if provided) and then exits. +func fatal(msg string, code int) { + if len(msg) > 0 { + // add newline if needed + if !strings.HasSuffix(msg, "\n") { + msg += "\n" + } + fmt.Fprint(os.Stderr, msg) + } + os.Exit(code) +} + +// ErrExit may be passed to CheckError to instruct it to output nothing but exit with +// status code 1. +var ErrExit = fmt.Errorf("exit") + +// CheckErr prints a user-friendly error to STDERR and exits with a non-zero +// exit code. Unrecognized errors will be printed with an "error: " prefix. +// +// This method is generic to the command in use and may be used by non-IAM +// commands. +func CheckErr(err error) { + checkErr(err, fatalErrHandler) +} + +// CheckDiffErr prints a user-friendly error to STDERR and exits with a +// non-zero and non-one exit code. Unrecognized errors will be printed +// with an "error: " prefix. +// +// This method is meant specifically for `iamctl diff` and may be used +// by other commands. +func CheckDiffErr(err error) { + checkErr(err, func(msg string, code int) { + fatalErrHandler(msg, code+1) + }) +} + +// checkErr formats a given error as a string and calls the passed handleErr +// func with that string and an iamctl exit code. +func checkErr(err error, handleErr func(string, int)) { + // unwrap aggregates of 1 + if agg, ok := err.(errors.Aggregate); ok && len(agg.Errors()) == 1 { + err = agg.Errors()[0] + } + + if err == nil { + return + } + + switch { + case err == ErrExit: + handleErr("", DefaultErrorExitCode) + default: + switch err := err.(type) { + case errors.Aggregate: + handleErr(MultipleErrors(``, err.Errors()), DefaultErrorExitCode) + default: // for any other error type + msg, ok := StandardErrorMessage(err) + if !ok { + msg = err.Error() + if !strings.HasPrefix(msg, "error: ") { + msg = fmt.Sprintf("error: %s", msg) + } + } + handleErr(msg, DefaultErrorExitCode) + } + } +} + +// StandardErrorMessage translates common errors into a human readable message, or returns +// false if the error is not one of the recognized types. It may also log extended information to klog. +// +// This method is generic to the command in use and may be used by non-IAM +// commands. +func StandardErrorMessage(err error) (string, bool) { + if debugErr, ok := err.(debugError); ok { + log.Infof(debugErr.DebugError()) + } + if t, ok := err.(*url.Error); ok { + log.Infof("Connection error: %s %s: %v", t.Op, t.URL, t.Err) + if strings.Contains(t.Err.Error(), "connection refused") { + host := t.URL + if server, err := url.Parse(t.URL); err == nil { + host = server.Host + } + return fmt.Sprintf( + "The connection to the server %s was refused - did you specify the right host or port?", + host, + ), true + } + + return fmt.Sprintf("Unable to connect to the server: %v", t.Err), true + } + return "", false +} + +// MultilineError returns a string representing an error that splits sub errors into their own +// lines. The returned string will end with a newline. +func MultilineError(prefix string, err error) string { + if agg, ok := err.(errors.Aggregate); ok { + errs := errors.Flatten(agg).Errors() + buf := &bytes.Buffer{} + switch len(errs) { + case 0: + return fmt.Sprintf("%s%v\n", prefix, err) + case 1: + return fmt.Sprintf("%s%v\n", prefix, messageForError(errs[0])) + default: + fmt.Fprintln(buf, prefix) + for _, err := range errs { + fmt.Fprintf(buf, "* %v\n", messageForError(err)) + } + return buf.String() + } + } + return fmt.Sprintf("%s%s\n", prefix, err) +} + +// MultipleErrors returns a newline delimited string containing +// the prefix and referenced errors in standard form. +func MultipleErrors(prefix string, errs []error) string { + buf := &bytes.Buffer{} + for _, err := range errs { + fmt.Fprintf(buf, "%s%v\n", prefix, messageForError(err)) + } + return buf.String() +} + +// messageForError returns the string representing the error. +func messageForError(err error) string { + msg, ok := StandardErrorMessage(err) + if !ok { + msg = err.Error() + } + return msg +} + +// UsageErrorf returns error with command path. +func UsageErrorf(cmd *cobra.Command, format string, args ...interface{}) error { + msg := fmt.Sprintf(format, args...) + return fmt.Errorf("%s\nSee '%s -h' for help and examples", msg, cmd.CommandPath()) +} + +// IsFilenameSliceEmpty checkes where filenames and directory are both zero value. +func IsFilenameSliceEmpty(filenames []string, directory string) bool { + return len(filenames) == 0 && directory == "" +} + +// GetFlagString returns the value of the given flag. +func GetFlagString(cmd *cobra.Command, flag string) string { + s, err := cmd.Flags().GetString(flag) + if err != nil { + log.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err) + } + return s +} + +// GetFlagStringSlice can be used to accept multiple argument with flag repetition (e.g. -f arg1,arg2 -f arg3 ...). +func GetFlagStringSlice(cmd *cobra.Command, flag string) []string { + s, err := cmd.Flags().GetStringSlice(flag) + if err != nil { + log.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err) + } + return s +} + +// GetFlagStringArray can be used to accept multiple argument with flag repetition (e.g. -f arg1 -f arg2 ...). +func GetFlagStringArray(cmd *cobra.Command, flag string) []string { + s, err := cmd.Flags().GetStringArray(flag) + if err != nil { + log.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err) + } + return s +} + +// GetFlagBool returns the value of the given flag. +func GetFlagBool(cmd *cobra.Command, flag string) bool { + b, err := cmd.Flags().GetBool(flag) + if err != nil { + log.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err) + } + return b +} + +// GetFlagInt returns the value of the given flag. +func GetFlagInt(cmd *cobra.Command, flag string) int { + i, err := cmd.Flags().GetInt(flag) + if err != nil { + log.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err) + } + return i +} + +// GetFlagInt32 returns the value of the given flag. +func GetFlagInt32(cmd *cobra.Command, flag string) int32 { + i, err := cmd.Flags().GetInt32(flag) + if err != nil { + log.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err) + } + return i +} + +// GetFlagInt64 returns the value of the given flag. +func GetFlagInt64(cmd *cobra.Command, flag string) int64 { + i, err := cmd.Flags().GetInt64(flag) + if err != nil { + log.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err) + } + return i +} + +// GetFlagDuration return the value of the given flag. +func GetFlagDuration(cmd *cobra.Command, flag string) time.Duration { + d, err := cmd.Flags().GetDuration(flag) + if err != nil { + log.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err) + } + return d +} + +// AddCleanFlags add clean flags. +func AddCleanFlags(cmd *cobra.Command) { + cmd.Flags().StringP("user", "u", "", "Specify the user name.") + cmd.Flags().BoolP("erase", "c", false, "Erase the records from the db") +} + +// ValidateOptions defines the validate options. +type ValidateOptions struct { + EnableValidation bool +} + +// IsSiblingCommandExists receives a pointer to a cobra command and a target string. +// Returns true if the target string is found in the list of sibling commands. +func IsSiblingCommandExists(cmd *cobra.Command, targetCmdName string) bool { + for _, c := range cmd.Parent().Commands() { + if c.Name() == targetCmdName { + return true + } + } + + return false +} + +// DefaultSubCommandRun prints a command's help string to the specified output if no +// arguments (sub-commands) are provided, or a usage error otherwise. +func DefaultSubCommandRun(out io.Writer) func(c *cobra.Command, args []string) { + return func(c *cobra.Command, args []string) { + c.SetOutput(out) + RequireNoArguments(c, args) + c.Help() + CheckErr(ErrExit) + } +} + +// RequireNoArguments exits with a usage error if extra arguments are provided. +func RequireNoArguments(c *cobra.Command, args []string) { + if len(args) > 0 { + CheckErr(UsageErrorf(c, "unknown command %q", strings.Join(args, " "))) + } +} + +// ManualStrip is used for dropping comments from a YAML file. +func ManualStrip(file []byte) []byte { + stripped := []byte{} + lines := bytes.Split(file, []byte("\n")) + for i, line := range lines { + if bytes.HasPrefix(bytes.TrimSpace(line), []byte("#")) { + continue + } + stripped = append(stripped, line...) + if i < len(lines)-1 { + stripped = append(stripped, '\n') + } + } + return stripped +} + +// Warning write warning message to io.Writer. +func Warning(cmdErr io.Writer, newGeneratorName, oldGeneratorName string) { + fmt.Fprintf(cmdErr, "WARNING: New generator %q specified, "+ + "but it isn't available. "+ + "Falling back to %q.\n", + newGeneratorName, + oldGeneratorName, + ) +} + +// CombineRequestErr combines the http response error and error in errs array. +func CombineRequestErr(resp gorequest.Response, body string, errs []error) error { + var e, sep string + if len(errs) > 0 { + for _, err := range errs { + e = sep + err.Error() + sep = "\n" + } + return errors.New(e) + } + + if resp.StatusCode != http.StatusOK { + return errors.New(body) + } + + return nil +} + +func NewForConfigOrDie() *marmotedu.Clientset { + clientConfig := &restclient.Config{ + Host: viper.GetString("server.address"), + BearerToken: viper.GetString("user.token"), + Username: viper.GetString("user.username"), + Password: viper.GetString("user.password"), + SecretID: viper.GetString("user.secret-id"), + SecretKey: viper.GetString("user.secret-key"), + Timeout: viper.GetDuration("server.timeout"), + MaxRetries: viper.GetInt("server.max-retries"), + RetryInterval: viper.GetDuration("server.retry-interval"), + } + + return marmotedu.NewForConfigOrDie(clientConfig) +} + +func TableWriterDefaultConfig(table *tablewriter.Table) *tablewriter.Table { + table.SetAutoWrapText(false) + table.SetAutoFormatHeaders(true) + table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) + table.SetAlignment(tablewriter.ALIGN_LEFT) + table.SetCenterSeparator("") + table.SetColumnSeparator("") + table.SetRowSeparator("") + table.SetHeaderLine(false) + table.SetBorder(false) + table.SetTablePadding(" ") // pad with two space + table.SetNoWhiteSpace(true) + + return table +} diff --git a/tools/imctl/internal/imctl/cmd/util/iam_match_version.go b/tools/imctl/internal/imctl/cmd/util/iam_match_version.go new file mode 100644 index 000000000..8bfd4e0ef --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/util/iam_match_version.go @@ -0,0 +1,141 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package util + +import ( + "context" + "fmt" + "sync" + + "github.com/marmotedu/marmotedu-sdk-go/rest" + "github.com/marmotedu/marmotedu-sdk-go/tools/clientcmd" + "github.com/openim-sigs/component-base/pkg/runtime" + "github.com/openim-sigs/component-base/pkg/scheme" + "github.com/openim-sigs/component-base/pkg/version" + "github.com/spf13/pflag" + + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +const ( + flagMatchBinaryVersion = "match-server-version" +) + +// MatchVersionFlags is for setting the "match server version" function. +type MatchVersionFlags struct { + Delegate genericclioptions.RESTClientGetter + + RequireMatchedServerVersion bool + checkServerVersion sync.Once + matchesServerVersionErr error +} + +var _ genericclioptions.RESTClientGetter = &MatchVersionFlags{} + +func (f *MatchVersionFlags) checkMatchingServerVersion() error { + f.checkServerVersion.Do(func() { + if !f.RequireMatchedServerVersion { + return + } + + clientConfig, err := f.Delegate.ToRESTConfig() + if err != nil { + f.matchesServerVersionErr = err + return + } + + setIAMDefaults(clientConfig) + restClient, err := rest.RESTClientFor(clientConfig) + if err != nil { + f.matchesServerVersionErr = err + return + } + + var sVer *version.Info + if err := restClient.Get().AbsPath("/version").Do(context.TODO()).Into(&sVer); err != nil { + f.matchesServerVersionErr = err + return + } + + clientVersion := version.Get() + + // GitVersion includes GitCommit and GitTreeState, but best to be safe? + if clientVersion.GitVersion != sVer.GitVersion || clientVersion.GitCommit != sVer.GitCommit || + clientVersion.GitTreeState != sVer.GitTreeState { + f.matchesServerVersionErr = fmt.Errorf( + "server version (%#v) differs from client version (%#v)", + sVer, + version.Get(), + ) + } + }) + + return f.matchesServerVersionErr +} + +// ToRESTConfig implements RESTClientGetter. +// Returns a REST client configuration based on a provided path +// to a .iamconfig file, loading rules, and config flag overrides. +// Expects the AddFlags method to have been called. +func (f *MatchVersionFlags) ToRESTConfig() (*rest.Config, error) { + if err := f.checkMatchingServerVersion(); err != nil { + return nil, err + } + clientConfig, err := f.Delegate.ToRESTConfig() + if err != nil { + return nil, err + } + // TODO we should not have to do this. It smacks of something going wrong. + setIAMDefaults(clientConfig) + return clientConfig, nil +} + +func (f *MatchVersionFlags) ToRawIAMConfigLoader() clientcmd.ClientConfig { + return f.Delegate.ToRawIAMConfigLoader() +} + +func (f *MatchVersionFlags) AddFlags(flags *pflag.FlagSet) { + flags.BoolVar( + &f.RequireMatchedServerVersion, + flagMatchBinaryVersion, + f.RequireMatchedServerVersion, + "Require server version to match client version", + ) +} + +func NewMatchVersionFlags(delegate genericclioptions.RESTClientGetter) *MatchVersionFlags { + return &MatchVersionFlags{ + Delegate: delegate, + } +} + +// setIAMDefaults sets default values on the provided client config for accessing the +// IAM API or returns an error if any of the defaults are impossible or invalid. +// TODO this isn't what we want. Each iamclient should be setting defaults as it sees fit. +func setIAMDefaults(config *rest.Config) error { + // TODO remove this hack. This is allowing the GetOptions to be serialized. + config.GroupVersion = &scheme.GroupVersion{Group: "iam.api", Version: "v1"} + + if config.APIPath == "" { + config.APIPath = "/api" + } + if config.Negotiator == nil { + // This codec factory ensures the resources are not converted. Therefore, resources + // will not be round-tripped through internal versions. Defaulting does not happen + // on the client. + config.Negotiator = runtime.NewSimpleClientNegotiator() + } + return rest.SetIAMDefaults(config) +} diff --git a/tools/imctl/internal/imctl/cmd/validate/validate.go b/tools/imctl/internal/imctl/cmd/validate/validate.go new file mode 100644 index 000000000..5b2e86388 --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/validate/validate.go @@ -0,0 +1,126 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package validate validate the basic environment for iamctl to run. +package validate + +import ( + "fmt" + "net" + "net/url" + + "github.com/fatih/color" + "github.com/olekukonko/tablewriter" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +// ValidateOptions is an options struct to support 'validate' sub command. +type ValidateOptions struct { + genericclioptions.IOStreams +} + +// ValidateInfo defines the validate information. +type ValidateInfo struct { + ItemName string + Status string + Message string +} + +var validateExample = templates.Examples(` + # Validate the basic environment for iamctl to run + iamctl validate`) + +// NewValidateOptions returns an initialized ValidateOptions instance. +func NewValidateOptions(ioStreams genericclioptions.IOStreams) *ValidateOptions { + return &ValidateOptions{ + IOStreams: ioStreams, + } +} + +// NewCmdValidate returns new initialized instance of 'validate' sub command. +func NewCmdValidate(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewValidateOptions(ioStreams) + + cmd := &cobra.Command{ + Use: "validate", + DisableFlagsInUseLine: true, + Aliases: []string{}, + Short: "Validate the basic environment for iamctl to run", + TraverseChildren: true, + Long: "Validate the basic environment for iamctl to run.", + Example: validateExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd, args)) + cmdutil.CheckErr(o.Run(args)) + }, + SuggestFor: []string{}, + } + + return cmd +} + +// Complete completes all the required options. +func (o *ValidateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *ValidateOptions) Validate(cmd *cobra.Command, args []string) error { + return nil +} + +// Run executes a validate sub command using the specified options. +func (o *ValidateOptions) Run(args []string) error { + data := [][]string{} + FAIL := color.RedString("FAIL") + PASS := color.GreenString("PASS") + validateInfo := ValidateInfo{} + + // check if can access db + validateInfo.ItemName = "iam-apiserver" + target, err := url.Parse(viper.GetString("server.address")) + if err != nil { + return err + } + _, err = net.Dial("tcp", target.Host) + // defer client.Close() + if err != nil { + validateInfo.Status = FAIL + validateInfo.Message = fmt.Sprintf("%v", err) + } else { + validateInfo.Status = PASS + validateInfo.Message = "" + } + + data = append(data, []string{validateInfo.ItemName, validateInfo.Status, validateInfo.Message}) + + table := tablewriter.NewWriter(o.Out) + table.SetAlignment(tablewriter.ALIGN_LEFT) + table.SetColWidth(iamctl.TableWidth) + table.SetHeader([]string{"ValidateItem", "Result", "Message"}) + + for _, v := range data { + table.Append(v) + } + + table.Render() + + return nil +} diff --git a/tools/imctl/internal/imctl/cmd/version/version.go b/tools/imctl/internal/imctl/cmd/version/version.go new file mode 100644 index 000000000..c71025aa7 --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/version/version.go @@ -0,0 +1,166 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package version print the client and server version information. +package version + +import ( + "context" + "errors" + "fmt" + + "github.com/ghodss/yaml" + restclient "github.com/marmotedu/marmotedu-sdk-go/rest" + "github.com/openim-sigs/component-base/pkg/json" + "github.com/openim-sigs/component-base/pkg/version" + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/marmotedu/iam/pkg/cli/genericclioptions" +) + +// Version is a struct for version information. +type Version struct { + ClientVersion *version.Info `json:"clientVersion,omitempty" yaml:"clientVersion,omitempty"` + ServerVersion *version.Info `json:"serverVersion,omitempty" yaml:"serverVersion,omitempty"` +} + +var versionExample = templates.Examples(` + # Print the client and server versions for the current context + iamctl version`) + +// Options is a struct to support version command. +type Options struct { + ClientOnly bool + Short bool + Output string + + client *restclient.RESTClient + genericclioptions.IOStreams +} + +// NewOptions returns initialized Options. +func NewOptions(ioStreams genericclioptions.IOStreams) *Options { + return &Options{ + IOStreams: ioStreams, + } +} + +// NewCmdVersion returns a cobra command for fetching versions. +func NewCmdVersion(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewOptions(ioStreams) + cmd := &cobra.Command{ + Use: "version", + Short: "Print the client and server version information", + Long: "Print the client and server version information for the current context", + Example: versionExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd)) + cmdutil.CheckErr(o.Validate()) + cmdutil.CheckErr(o.Run()) + }, + } + + cmd.Flags().BoolVar( + &o.ClientOnly, + "client", + o.ClientOnly, + "If true, shows client version only (no server required).", + ) + cmd.Flags().BoolVar(&o.Short, "short", o.Short, "If true, print just the version number.") + cmd.Flags().StringVarP(&o.Output, "output", "o", o.Output, "One of 'yaml' or 'json'.") + + return cmd +} + +// Complete completes all the required options. +func (o *Options) Complete(f cmdutil.Factory, cmd *cobra.Command) error { + var err error + if o.ClientOnly { + return nil + } + + o.client, err = f.RESTClient() + if err != nil { + return err + } + + return nil +} + +// Validate validates the provided options. +func (o *Options) Validate() error { + if o.Output != "" && o.Output != "yaml" && o.Output != "json" { + return errors.New(`--output must be 'yaml' or 'json'`) + } + + return nil +} + +// Run executes version command. +func (o *Options) Run() error { + var ( + serverVersion *version.Info + serverErr error + versionInfo Version + ) + + clientVersion := version.Get() + versionInfo.ClientVersion = &clientVersion + + if !o.ClientOnly && o.client != nil { + // Always request fresh data from the server + if err := o.client.Get().AbsPath("/version").Do(context.TODO()).Into(&serverVersion); err != nil { + return err + } + versionInfo.ServerVersion = serverVersion + } + + switch o.Output { + case "": + if o.Short { + fmt.Fprintf(o.Out, "Client Version: %s\n", clientVersion.GitVersion) + + if serverVersion != nil { + fmt.Fprintf(o.Out, "Server Version: %s\n", serverVersion.GitVersion) + } + } else { + fmt.Fprintf(o.Out, "Client Version: %s\n", fmt.Sprintf("%#v", clientVersion)) + if serverVersion != nil { + fmt.Fprintf(o.Out, "Server Version: %s\n", fmt.Sprintf("%#v", *serverVersion)) + } + } + case "yaml": + marshaled, err := yaml.Marshal(&versionInfo) + if err != nil { + return err + } + + fmt.Fprintln(o.Out, string(marshaled)) + case "json": + marshaled, err := json.MarshalIndent(&versionInfo, "", " ") + if err != nil { + return err + } + + fmt.Fprintln(o.Out, string(marshaled)) + default: + // There is a bug in the program if we hit this case. + // However, we follow a policy of never panicking. + return fmt.Errorf("VersionOptions were not validated: --output=%q should have been rejected", o.Output) + } + + return serverErr +} diff --git a/tools/openim-web/Dockerfile b/tools/openim-web/Dockerfile index aad95b8bc..2e0cd9e8e 100644 --- a/tools/openim-web/Dockerfile +++ b/tools/openim-web/Dockerfile @@ -1,3 +1,17 @@ +# Copyright © 2023 OpenIM. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + # 使用官方Go镜像作为基础镜像 FROM golang:1.21 AS build-env ENV CGO_ENABLED=0 diff --git a/tools/openim-web/openim-web.go b/tools/openim-web/openim-web.go index 6b7b3a360..c913e35e7 100644 --- a/tools/openim-web/openim-web.go +++ b/tools/openim-web/openim-web.go @@ -1,3 +1,17 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package main import ( diff --git a/tools/openim-web/openim-web_test.go b/tools/openim-web/openim-web_test.go index 6d19deb77..8d4f650a2 100644 --- a/tools/openim-web/openim-web_test.go +++ b/tools/openim-web/openim-web_test.go @@ -1,3 +1,17 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package main import (