fix: add iam super feat

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>
pull/1010/head
Xinwei Xiong(cubxxw-openim) 2 years ago
parent 0a27b6e0a6
commit 1a5e86fabf
No known key found for this signature in database
GPG Key ID: 1BAD6F395338EFDE

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// iamctl is the command line tool for iam platform.
// iactl is the command line tool for openim platform.
package main
import (

@ -21,12 +21,12 @@ import (
"github.com/fatih/color"
"github.com/olekukonko/tablewriter"
"github.com/openim-sigs/component-base/pkg/util/stringutil"
"github.com/openim-sigs/component-base/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/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/util/templates"
"github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/cli/genericclioptions"
cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util"
)
// ColorOptions is an options struct to support color subcommands.

@ -21,8 +21,8 @@ import (
"github.com/spf13/cobra"
"github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/util/templates"
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 = `
@ -45,11 +45,11 @@ 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
completion of imctl 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
http://github.com/marmotedu/iam/docs/installation/imctl.md#enabling-shell-autocompletion
Note for zsh users: [1] zsh completions are only supported in versions of zsh >= 5.2`)
@ -59,28 +59,28 @@ var (
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 imctl 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
imctl completion bash > $(brew --prefix)/etc/bash_completion.d/imctl
# 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)
## Load the imctl completion code for bash into the current shell
source <(imctl completion bash)
## Write bash completion code to a file and source if from .bash_profile
iamctl completion bash > ~/.iam/completion.bash.inc
imctl 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"`)
# Load the imctl completion code for zsh[1] into the current shell
source <(imctl completion zsh)
# Set the imctl completion code for zsh[1] to autoload on startup
imctl completion zsh > "${fpath[1]}/_imctl"`)
)
var completionShells = map[string]func(out io.Writer, boilerPlate string, cmd *cobra.Command) error{
@ -129,7 +129,7 @@ func RunCompletion(out io.Writer, boilerPlate string, cmd *cobra.Command, args [
return run(out, boilerPlate, cmd.Parent())
}
func runCompletionBash(out io.Writer, boilerPlate string, iamctl *cobra.Command) error {
func runCompletionBash(out io.Writer, boilerPlate string, imctl *cobra.Command) error {
if len(boilerPlate) == 0 {
boilerPlate = defaultBoilerPlate
}
@ -138,11 +138,11 @@ func runCompletionBash(out io.Writer, boilerPlate string, iamctl *cobra.Command)
return err
}
return iamctl.GenBashCompletion(out)
return imctl.GenBashCompletion(out)
}
func runCompletionZsh(out io.Writer, boilerPlate string, iamctl *cobra.Command) error {
zshHead := "#compdef iamctl\n"
func runCompletionZsh(out io.Writer, boilerPlate string, imctl *cobra.Command) error {
zshHead := "#compdef imctl\n"
if _, err := out.Write([]byte(zshHead)); err != nil {
return err
@ -157,7 +157,7 @@ func runCompletionZsh(out io.Writer, boilerPlate string, iamctl *cobra.Command)
}
zshInitialization := `
__iamctl_bash_source() {
__imctl_bash_source() {
alias shopt=':'
emulate -L sh
setopt kshglob noshglob braceexpand
@ -165,7 +165,7 @@ __iamctl_bash_source() {
source "$@"
}
__iamctl_type() {
__imctl_type() {
# -t is not supported by zsh
if [ "$1" == "-t" ]; then
shift
@ -174,7 +174,7 @@ __iamctl_type() {
# "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
if [ "$1" = "__imctl_compopt" ]; then
echo builtin
return 0
fi
@ -182,7 +182,7 @@ __iamctl_type() {
type "$@"
}
__iamctl_compgen() {
__imctl_compgen() {
local completions w
completions=( $(compgen "$@") ) || return $?
@ -201,11 +201,11 @@ __iamctl_compgen() {
done
}
__iamctl_compopt() {
__imctl_compopt() {
true # don't do anything. Not supported by bashcompinit in zsh
}
__iamctl_ltrim_colon_completions()
__imctl_ltrim_colon_completions()
{
if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then
# Remove colon-word prefix from COMPREPLY items
@ -217,14 +217,14 @@ __iamctl_ltrim_colon_completions()
fi
}
__iamctl_get_comp_words_by_ref() {
__imctl_get_comp_words_by_ref() {
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[${COMP_CWORD}-1]}"
words=("${COMP_WORDS[@]}")
cword=("${COMP_CWORD[@]}")
}
__iamctl_filedir() {
__imctl_filedir() {
# Don't need to do anything here.
# Otherwise we will get trailing space without "compopt -o nospace"
true
@ -240,20 +240,20 @@ if sed --help 2>&1 | grep -q 'GNU\|BusyBox'; then
RWORD='\>'
fi
__iamctl_convert_bash_to_zsh() {
__imctl_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}_filedir${RWORD}/__imctl_filedir/g" \
-e "s/${LWORD}_get_comp_words_by_ref${RWORD}/__imctl_get_comp_words_by_ref/g" \
-e "s/${LWORD}__ltrim_colon_completions${RWORD}/__imctl_ltrim_colon_completions/g" \
-e "s/${LWORD}compgen${RWORD}/__imctl_compgen/g" \
-e "s/${LWORD}compopt${RWORD}/__imctl_compopt/g" \
-e "s/${LWORD}declare${RWORD}/builtin declare/g" \
-e "s/\\\$(type${RWORD}/\$(__iamctl_type/g" \
-e "s/\\\$(type${RWORD}/\$(__imctl_type/g" \
<<'BASH_COMPLETION_EOF'
`
if _, err := out.Write([]byte(zshInitialization)); err != nil {
@ -261,7 +261,7 @@ __iamctl_convert_bash_to_zsh() {
}
buf := new(bytes.Buffer)
if err := iamctl.GenBashCompletion(buf); err != nil {
if err := imctl.GenBashCompletion(buf); err != nil {
return err
}
@ -273,7 +273,7 @@ __iamctl_convert_bash_to_zsh() {
BASH_COMPLETION_EOF
}
__iamctl_bash_source <(__iamctl_convert_bash_to_zsh)
__imctl_bash_source <(__imctl_convert_bash_to_zsh)
`
if _, err := out.Write([]byte(zshTail)); err != nil {
return err

@ -21,12 +21,12 @@ import (
"strconv"
hoststat "github.com/likexian/host-stat-go"
"github.com/openim-sigs/component-base/pkg/util/iputil"
"github.com/openim-sigs/component-base/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/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/util/templates"
"github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/cli/genericclioptions"
cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util"
)
// Info defines the host information struct.
@ -46,7 +46,7 @@ type InfoOptions struct {
var infoExample = templates.Examples(`
# Print the host information
iamctl info`)
imctl info`)
// NewInfoOptions returns an initialized InfoOptions instance.
func NewInfoOptions(ioStreams genericclioptions.IOStreams) *InfoOptions {

@ -1,48 +0,0 @@
// 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"
}

@ -1,47 +0,0 @@
// 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/OpenIMSDK/Open-IM-Server/tools/imctl/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
}

@ -1,144 +0,0 @@
// 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/OpenIMSDK/Open-IM-Server/tools/imctl/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
}

@ -1,186 +0,0 @@
// 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"
"github.com/marmotedu/iam/internal/pkg/middleware/auth"
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/OpenIMSDK/Open-IM-Server/tools/imctl/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
}

@ -1,150 +0,0 @@
// 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/OpenIMSDK/Open-IM-Server/tools/imctl/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
}

@ -28,9 +28,9 @@ import (
"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/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/cli/genericclioptions"
cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/util"
)
const (
@ -45,16 +45,16 @@ This can improve your R&D efficiency.`)
newExample = templates.Examples(`
# Create a default 'test' command file without a description
iamctl new test
imctl new test
# Create a default 'test' command file in /tmp/
iamctl new test -d /tmp/
imctl new test -d /tmp/
# Create a default 'test' command file with a description
iamctl new test "This is a test command"
imctl 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"`)
imctl 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",
@ -68,7 +68,7 @@ import (
"github.com/spf13/cobra"
cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util"
cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/util"
"github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates"
"github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/cli/genericclioptions"
)
@ -103,7 +103,7 @@ to quickly create a Cobra application.{{.Dot}})
{{.CommandName}}Example = templates.Examples({{.Dot}}
# Print all option values for {{.CommandName}}
iamctl {{.CommandName}} marmotedu marmotedupass{{.Dot}})
imctl {{.CommandName}} marmotedu marmotedupass{{.Dot}})
{{.CommandName}}UsageErrStr = fmt.Sprintf("expected '%s'.\nUSERNAME and PASSWORD are required arguments for the {{.CommandName}} command", {{.CommandName}}UsageStr)
)
@ -205,7 +205,7 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util"
cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/util"
"github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates"
"github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/cli/genericclioptions"
)
@ -253,7 +253,7 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util"
cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/util"
"github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates"
"github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/cli/genericclioptions"
)
@ -288,10 +288,10 @@ to quickly create a Cobra application.{{.Dot}})
subcmd1Example = templates.Examples({{.Dot}}
# Print all option values for subcmd1
iamctl {{.CommandName}} subcmd1 marmotedu marmotedupass
imctl {{.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}})
imctl {{.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)
)
@ -386,7 +386,7 @@ import (
"github.com/spf13/cobra"
cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util"
cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/util"
"github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates"
"github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/cli/genericclioptions"
)
@ -411,7 +411,7 @@ to quickly create a Cobra application.{{.Dot}})
subcmd2Example = templates.Examples({{.Dot}}
# Print all option values for subcmd2
iamctl {{.CommandName}} subcmd2{{.Dot}})
imctl {{.CommandName}} subcmd2{{.Dot}})
)
// NewSubCmd2Options returns an initialized SubCmd2Options instance.

@ -20,7 +20,7 @@ import (
"github.com/spf13/cobra"
"github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates"
"github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/util/templates"
)
var optionsExample = templates.Examples(`

@ -1,48 +0,0 @@
// 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/OpenIMSDK/Open-IM-Server/tools/imctl/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
}

@ -1,137 +0,0 @@
// 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/OpenIMSDK/Open-IM-Server/tools/imctl/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:<peter|ken>","users:maria","groups:admins"],"actions":["delete","<create|update>"],"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
}

@ -1,115 +0,0 @@
// 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/OpenIMSDK/Open-IM-Server/tools/imctl/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
}

@ -1,123 +0,0 @@
// 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/OpenIMSDK/Open-IM-Server/tools/imctl/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
}

@ -1,127 +0,0 @@
// 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/OpenIMSDK/Open-IM-Server/tools/imctl/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
}

@ -1,131 +0,0 @@
// 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/OpenIMSDK/Open-IM-Server/tools/imctl/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:<peter|ken>","users:maria","groups:admins"],"actions":["delete","<create|update>"],"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
}

@ -1,61 +0,0 @@
// 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/OpenIMSDK/Open-IM-Server/tools/imctl/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
}

@ -1,145 +0,0 @@
// 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/OpenIMSDK/Open-IM-Server/tools/imctl/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
}

@ -1,115 +0,0 @@
// 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/OpenIMSDK/Open-IM-Server/tools/imctl/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
}

@ -1,130 +0,0 @@
// 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/OpenIMSDK/Open-IM-Server/tools/imctl/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
}

@ -1,131 +0,0 @@
// 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/OpenIMSDK/Open-IM-Server/tools/imctl/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
}

@ -1,128 +0,0 @@
// 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/OpenIMSDK/Open-IM-Server/tools/imctl/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
}

@ -1,45 +0,0 @@
// 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/OpenIMSDK/Open-IM-Server/tools/imctl/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
}

@ -1,213 +0,0 @@
// 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/OpenIMSDK/Open-IM-Server/tools/imctl/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
}

@ -1,62 +0,0 @@
// 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/OpenIMSDK/Open-IM-Server/tools/imctl/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
}

@ -1,149 +0,0 @@
// 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/OpenIMSDK/Open-IM-Server/tools/imctl/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
}

@ -1,114 +0,0 @@
// 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/OpenIMSDK/Open-IM-Server/tools/imctl/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
}

@ -1,129 +0,0 @@
// 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/OpenIMSDK/Open-IM-Server/tools/imctl/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
}

@ -1,128 +0,0 @@
// 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/OpenIMSDK/Open-IM-Server/tools/imctl/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
}

@ -1,142 +0,0 @@
// 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/OpenIMSDK/Open-IM-Server/tools/imctl/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
}

@ -1,43 +0,0 @@
// 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/OpenIMSDK/Open-IM-Server/tools/imctl/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)
}

@ -1,66 +0,0 @@
// 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/OpenIMSDK/Open-IM-Server/tools/imctl/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)
}

@ -1,397 +0,0 @@
// 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/OpenIMSDK/Open-IM-Server/tools/imctl/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
}

@ -1,141 +0,0 @@
// 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/OpenIMSDK/Open-IM-Server/tools/imctl/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)
}

@ -1,126 +0,0 @@
// 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/OpenIMSDK/Open-IM-Server/tools/imctl/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
}

@ -21,14 +21,13 @@ import (
"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/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/cli/genericclioptions"
cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/util"
)
// Version is a struct for version information.
@ -39,7 +38,7 @@ type Version struct {
var versionExample = templates.Examples(`
# Print the client and server versions for the current context
iamctl version`)
imctl version`)
// Options is a struct to support version command.
type Options struct {

@ -1,103 +0,0 @@
// 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 interrupt deal with signals.
package interrupt
import (
"os"
"os/signal"
"sync"
"syscall"
)
// terminationSignals are signals that cause the program to exit in the
// supported platforms (linux, darwin, windows).
var terminationSignals = []os.Signal{syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT}
// Handler guarantees execution of notifications after a critical section (the function passed
// to a Run method), even in the presence of process termination. It guarantees exactly once
// invocation of the provided notify functions.
type Handler struct {
notify []func()
final func(os.Signal)
once sync.Once
}
// Chain creates a new handler that invokes all notify functions when the critical section exits
// and then invokes the optional handler's notifications. This allows critical sections to be
// nested without losing exactly once invocations. Notify functions can invoke any cleanup needed
// but should not exit (which is the responsibility of the parent handler).
func Chain(handler *Handler, notify ...func()) *Handler {
if handler == nil {
return New(nil, notify...)
}
return New(handler.Signal, append(notify, handler.Close)...)
}
// New creates a new handler that guarantees all notify functions are run after the critical
// section exits (or is interrupted by the OS), then invokes the final handler. If no final
// handler is specified, the default final is `os.Exit(1)`. A handler can only be used for
// one critical section.
func New(final func(os.Signal), notify ...func()) *Handler {
return &Handler{
final: final,
notify: notify,
}
}
// Close executes all the notification handlers if they have not yet been executed.
func (h *Handler) Close() {
h.once.Do(func() {
for _, fn := range h.notify {
fn()
}
})
}
// Signal is called when an os.Signal is received, and guarantees that all notifications
// are executed, then the final handler is executed. This function should only be called once
// per Handler instance.
func (h *Handler) Signal(s os.Signal) {
h.once.Do(func() {
for _, fn := range h.notify {
fn()
}
if h.final == nil {
os.Exit(1)
}
h.final(s)
})
}
// Run ensures that any notifications are invoked after the provided fn exits (even if the
// process is interrupted by an OS termination signal). Notifications are only invoked once
// per Handler instance, so calling Run more than once will not behave as the user expects.
func (h *Handler) Run(fn func() error) error {
ch := make(chan os.Signal, 1)
signal.Notify(ch, terminationSignals...)
defer func() {
signal.Stop(ch)
close(ch)
}()
go func() {
sig, ok := <-ch
if !ok {
return
}
h.Signal(sig)
}()
defer h.Close()
return fn()
}

@ -1,57 +0,0 @@
// 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 templates
import (
"github.com/spf13/cobra"
)
type CommandGroup struct {
Message string
Commands []*cobra.Command
}
type CommandGroups []CommandGroup
func (g CommandGroups) Add(c *cobra.Command) {
for _, group := range g {
c.AddCommand(group.Commands...)
}
}
func (g CommandGroups) Has(c *cobra.Command) bool {
for _, group := range g {
for _, command := range group.Commands {
if command == c {
return true
}
}
}
return false
}
func AddAdditionalCommands(g CommandGroups, message string, cmds []*cobra.Command) CommandGroups {
group := CommandGroup{Message: message}
for _, c := range cmds {
// Don't show commands that have no short description
if !g.Has(c) && len(c.Short) != 0 {
group.Commands = append(group.Commands, c)
}
}
if len(group.Commands) == 0 {
return g
}
return append(g, group)
}

@ -1,149 +0,0 @@
// 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 templates
import (
"bytes"
"fmt"
"io"
"strings"
"github.com/russross/blackfriday"
)
const linebreak = "\n"
// ASCIIRenderer implements blackfriday.Renderer.
var _ blackfriday.Renderer = &ASCIIRenderer{}
// ASCIIRenderer is a blackfriday.Renderer intended for rendering markdown
// documents as plain text, well suited for human reading on terminals.
type ASCIIRenderer struct {
Indentation string
listItemCount uint
listLevel uint
}
// NormalText gets a text chunk *after* the markdown syntax was already
// processed and does a final cleanup on things we don't expect here, like
// removing linebreaks on things that are not a paragraph break (auto unwrap).
func (r *ASCIIRenderer) NormalText(out *bytes.Buffer, text []byte) {
raw := string(text)
lines := strings.Split(raw, linebreak)
for _, line := range lines {
trimmed := strings.Trim(line, " \n\t")
if len(trimmed) > 0 && trimmed[0] != '_' {
out.WriteString(" ")
}
out.WriteString(trimmed)
}
}
// List renders the start and end of a list.
func (r *ASCIIRenderer) List(out *bytes.Buffer, text func() bool, flags int) {
r.listLevel++
out.WriteString(linebreak)
text()
r.listLevel--
}
// ListItem renders list items and supports both ordered and unordered lists.
func (r *ASCIIRenderer) ListItem(out *bytes.Buffer, text []byte, flags int) {
if flags&blackfriday.LIST_ITEM_BEGINNING_OF_LIST != 0 {
r.listItemCount = 1
} else {
r.listItemCount++
}
indent := strings.Repeat(r.Indentation, int(r.listLevel))
var bullet string
if flags&blackfriday.LIST_TYPE_ORDERED != 0 {
bullet += fmt.Sprintf("%d.", r.listItemCount)
} else {
bullet += "*"
}
out.WriteString(indent + bullet + " ")
r.fw(out, text)
out.WriteString(linebreak)
}
// Paragraph renders the start and end of a paragraph.
func (r *ASCIIRenderer) Paragraph(out *bytes.Buffer, text func() bool) {
out.WriteString(linebreak)
text()
out.WriteString(linebreak)
}
// BlockCode renders a chunk of text that represents source code.
func (r *ASCIIRenderer) BlockCode(out *bytes.Buffer, text []byte, lang string) {
out.WriteString(linebreak)
lines := []string{}
for _, line := range strings.Split(string(text), linebreak) {
indented := r.Indentation + line
lines = append(lines, indented)
}
out.WriteString(strings.Join(lines, linebreak))
}
func (r *ASCIIRenderer) GetFlags() int { return 0 }
func (r *ASCIIRenderer) HRule(out *bytes.Buffer) {
out.WriteString(linebreak + "----------" + linebreak)
}
func (r *ASCIIRenderer) LineBreak(out *bytes.Buffer) { out.WriteString(linebreak) }
func (r *ASCIIRenderer) TitleBlock(out *bytes.Buffer, text []byte) { r.fw(out, text) }
func (r *ASCIIRenderer) Header(out *bytes.Buffer, text func() bool, level int, id string) { text() }
func (r *ASCIIRenderer) BlockHtml(out *bytes.Buffer, text []byte) { r.fw(out, text) }
func (r *ASCIIRenderer) BlockQuote(out *bytes.Buffer, text []byte) { r.fw(out, text) }
func (r *ASCIIRenderer) TableRow(out *bytes.Buffer, text []byte) { r.fw(out, text) }
func (r *ASCIIRenderer) TableHeaderCell(out *bytes.Buffer, text []byte, align int) { r.fw(out, text) }
func (r *ASCIIRenderer) TableCell(out *bytes.Buffer, text []byte, align int) { r.fw(out, text) }
func (r *ASCIIRenderer) Footnotes(out *bytes.Buffer, text func() bool) { text() }
func (r *ASCIIRenderer) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) {
r.fw(out, text)
}
func (r *ASCIIRenderer) AutoLink(out *bytes.Buffer, link []byte, kind int) { r.fw(out, link) }
func (r *ASCIIRenderer) CodeSpan(out *bytes.Buffer, text []byte) { r.fw(out, text) }
func (r *ASCIIRenderer) DoubleEmphasis(out *bytes.Buffer, text []byte) { r.fw(out, text) }
func (r *ASCIIRenderer) Emphasis(out *bytes.Buffer, text []byte) { r.fw(out, text) }
func (r *ASCIIRenderer) RawHtmlTag(out *bytes.Buffer, text []byte) { r.fw(out, text) }
func (r *ASCIIRenderer) TripleEmphasis(out *bytes.Buffer, text []byte) { r.fw(out, text) }
func (r *ASCIIRenderer) StrikeThrough(out *bytes.Buffer, text []byte) { r.fw(out, text) }
func (r *ASCIIRenderer) FootnoteRef(out *bytes.Buffer, ref []byte, id int) { r.fw(out, ref) }
func (r *ASCIIRenderer) Entity(out *bytes.Buffer, entity []byte) { r.fw(out, entity) }
func (r *ASCIIRenderer) Smartypants(out *bytes.Buffer, text []byte) { r.fw(out, text) }
func (r *ASCIIRenderer) DocumentHeader(out *bytes.Buffer) {}
func (r *ASCIIRenderer) DocumentFooter(out *bytes.Buffer) {}
func (r *ASCIIRenderer) TocHeaderWithAnchor(text []byte, level int, anchor string) {}
func (r *ASCIIRenderer) TocHeader(text []byte, level int) {}
func (r *ASCIIRenderer) TocFinalize() {}
func (r *ASCIIRenderer) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {
r.fw(out, header, body)
}
func (r *ASCIIRenderer) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
out.WriteString(" ")
r.fw(out, link)
}
func (r *ASCIIRenderer) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
r.fw(out, link)
}
func (r *ASCIIRenderer) fw(out io.Writer, text ...[]byte) {
for _, t := range text {
out.Write(t)
}
}

@ -1,99 +0,0 @@
// 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 templates
import (
"strings"
"github.com/MakeNowJust/heredoc/v2"
"github.com/russross/blackfriday"
"github.com/spf13/cobra"
)
const Indentation = ` `
// LongDesc normalizes a command's long description to follow the conventions.
func LongDesc(s string) string {
if len(s) == 0 {
return s
}
return normalizer{s}.heredoc().markdown().trim().string
}
// Examples normalizes a command's examples to follow the conventions.
func Examples(s string) string {
if len(s) == 0 {
return s
}
return normalizer{s}.trim().indent().string
}
// Normalize perform all required normalizations on a given command.
func Normalize(cmd *cobra.Command) *cobra.Command {
if len(cmd.Long) > 0 {
cmd.Long = LongDesc(cmd.Long)
}
if len(cmd.Example) > 0 {
cmd.Example = Examples(cmd.Example)
}
return cmd
}
// NormalizeAll perform all required normalizations in the entire command tree.
func NormalizeAll(cmd *cobra.Command) *cobra.Command {
if cmd.HasSubCommands() {
for _, subCmd := range cmd.Commands() {
NormalizeAll(subCmd)
}
}
Normalize(cmd)
return cmd
}
type normalizer struct {
string
}
func (s normalizer) markdown() normalizer {
bytes := []byte(s.string)
formatted := blackfriday.Markdown(
bytes,
&ASCIIRenderer{Indentation: Indentation},
blackfriday.EXTENSION_NO_INTRA_EMPHASIS,
)
s.string = string(formatted)
return s
}
func (s normalizer) heredoc() normalizer {
s.string = heredoc.Doc(s.string)
return s
}
func (s normalizer) trim() normalizer {
s.string = strings.TrimSpace(s.string)
return s
}
func (s normalizer) indent() normalizer {
indentedLines := []string{}
for _, line := range strings.Split(s.string, "\n") {
trimmed := strings.TrimSpace(line)
indented := Indentation + trimmed
indentedLines = append(indentedLines, indented)
}
s.string = strings.Join(indentedLines, "\n")
return s
}

@ -1,296 +0,0 @@
// 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 templates
import (
"bytes"
"fmt"
"strings"
"text/template"
"unicode"
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"
"github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/util/term"
)
type FlagExposer interface {
ExposeFlags(cmd *cobra.Command, flags ...string) FlagExposer
}
func ActsAsRootCommand(cmd *cobra.Command, filters []string, groups ...CommandGroup) FlagExposer {
if cmd == nil {
panic("nil root command")
}
templater := &templater{
RootCmd: cmd,
UsageTemplate: MainUsageTemplate(),
HelpTemplate: MainHelpTemplate(),
CommandGroups: groups,
Filtered: filters,
}
cmd.SetFlagErrorFunc(templater.FlagErrorFunc())
cmd.SilenceUsage = true
cmd.SetUsageFunc(templater.UsageFunc())
cmd.SetHelpFunc(templater.HelpFunc())
return templater
}
func UseOptionsTemplates(cmd *cobra.Command) {
templater := &templater{
UsageTemplate: OptionsUsageTemplate(),
HelpTemplate: OptionsHelpTemplate(),
}
cmd.SetUsageFunc(templater.UsageFunc())
cmd.SetHelpFunc(templater.HelpFunc())
}
type templater struct {
UsageTemplate string
HelpTemplate string
RootCmd *cobra.Command
CommandGroups
Filtered []string
}
func (t *templater) FlagErrorFunc(exposedFlags ...string) func(*cobra.Command, error) error {
return func(c *cobra.Command, err error) error {
c.SilenceUsage = true
switch c.CalledAs() {
case "options":
return fmt.Errorf("%s\nrun '%s' without flags", err, c.CommandPath())
default:
return fmt.Errorf("%s\nsee '%s --help' for usage", err, c.CommandPath())
}
}
}
func (t *templater) ExposeFlags(cmd *cobra.Command, flags ...string) FlagExposer {
cmd.SetUsageFunc(t.UsageFunc(flags...))
return t
}
func (t *templater) HelpFunc() func(*cobra.Command, []string) {
return func(c *cobra.Command, s []string) {
tt := template.New("help")
tt.Funcs(t.templateFuncs())
template.Must(tt.Parse(t.HelpTemplate))
out := term.NewResponsiveWriter(c.OutOrStdout())
err := tt.Execute(out, c)
if err != nil {
c.Println(err)
}
}
}
func (t *templater) UsageFunc(exposedFlags ...string) func(*cobra.Command) error {
return func(c *cobra.Command) error {
tt := template.New("usage")
tt.Funcs(t.templateFuncs(exposedFlags...))
template.Must(tt.Parse(t.UsageTemplate))
out := term.NewResponsiveWriter(c.OutOrStderr())
return tt.Execute(out, c)
}
}
func (t *templater) templateFuncs(exposedFlags ...string) template.FuncMap {
return template.FuncMap{
"trim": strings.TrimSpace,
"trimRight": func(s string) string { return strings.TrimRightFunc(s, unicode.IsSpace) },
"trimLeft": func(s string) string { return strings.TrimLeftFunc(s, unicode.IsSpace) },
"gt": cobra.Gt,
"eq": cobra.Eq,
"rpad": rpad,
"appendIfNotPresent": appendIfNotPresent,
"flagsNotIntersected": flagsNotIntersected,
"visibleFlags": visibleFlags,
"flagsUsages": flagsUsages,
"cmdGroups": t.cmdGroups,
"cmdGroupsString": t.cmdGroupsString,
"rootCmd": t.rootCmdName,
"isRootCmd": t.isRootCmd,
"optionsCmdFor": t.optionsCmdFor,
"usageLine": t.usageLine,
"exposed": func(c *cobra.Command) *flag.FlagSet {
exposed := flag.NewFlagSet("exposed", flag.ContinueOnError)
if len(exposedFlags) > 0 {
for _, name := range exposedFlags {
if flag := c.Flags().Lookup(name); flag != nil {
exposed.AddFlag(flag)
}
}
}
return exposed
},
}
}
func (t *templater) cmdGroups(c *cobra.Command, all []*cobra.Command) []CommandGroup {
if len(t.CommandGroups) > 0 && c == t.RootCmd {
all = filter(all, t.Filtered...)
return AddAdditionalCommands(t.CommandGroups, "Other Commands:", all)
}
all = filter(all, "options")
return []CommandGroup{
{
Message: "Available Commands:",
Commands: all,
},
}
}
func (t *templater) cmdGroupsString(c *cobra.Command) string {
groups := []string{}
for _, cmdGroup := range t.cmdGroups(c, c.Commands()) {
cmds := []string{cmdGroup.Message}
for _, cmd := range cmdGroup.Commands {
if cmd.IsAvailableCommand() {
cmds = append(cmds, " "+rpad(cmd.Name(), cmd.NamePadding())+" "+cmd.Short)
}
}
groups = append(groups, strings.Join(cmds, "\n"))
}
return strings.Join(groups, "\n\n")
}
func (t *templater) rootCmdName(c *cobra.Command) string {
return t.rootCmd(c).CommandPath()
}
func (t *templater) isRootCmd(c *cobra.Command) bool {
return t.rootCmd(c) == c
}
func (t *templater) parents(c *cobra.Command) []*cobra.Command {
parents := []*cobra.Command{c}
for current := c; !t.isRootCmd(current) && current.HasParent(); {
current = current.Parent()
parents = append(parents, current)
}
return parents
}
func (t *templater) rootCmd(c *cobra.Command) *cobra.Command {
if c != nil && !c.HasParent() {
return c
}
if t.RootCmd == nil {
panic("nil root cmd")
}
return t.RootCmd
}
func (t *templater) optionsCmdFor(c *cobra.Command) string {
if !c.Runnable() {
return ""
}
rootCmdStructure := t.parents(c)
for i := len(rootCmdStructure) - 1; i >= 0; i-- {
cmd := rootCmdStructure[i]
if _, _, err := cmd.Find([]string{"options"}); err == nil {
return cmd.CommandPath() + " options"
}
}
return ""
}
func (t *templater) usageLine(c *cobra.Command) string {
usage := c.UseLine()
suffix := "[options]"
if c.HasFlags() && !strings.Contains(usage, suffix) {
usage += " " + suffix
}
return usage
}
func flagsUsages(f *flag.FlagSet) string {
x := new(bytes.Buffer)
f.VisitAll(func(flag *flag.Flag) {
if flag.Hidden {
return
}
format := "--%s=%s: %s\n"
if flag.Value.Type() == "string" {
format = "--%s='%s': %s\n"
}
if len(flag.Shorthand) > 0 {
format = " -%s, " + format
} else {
format = " %s " + format
}
fmt.Fprintf(x, format, flag.Shorthand, flag.Name, flag.DefValue, flag.Usage)
})
return x.String()
}
func rpad(s string, padding int) string {
template := fmt.Sprintf("%%-%ds", padding)
return fmt.Sprintf(template, s)
}
func appendIfNotPresent(s, stringToAppend string) string {
if strings.Contains(s, stringToAppend) {
return s
}
return s + " " + stringToAppend
}
func flagsNotIntersected(l *flag.FlagSet, r *flag.FlagSet) *flag.FlagSet {
f := flag.NewFlagSet("notIntersected", flag.ContinueOnError)
l.VisitAll(func(flag *flag.Flag) {
if r.Lookup(flag.Name) == nil {
f.AddFlag(flag)
}
})
return f
}
func visibleFlags(l *flag.FlagSet) *flag.FlagSet {
hidden := "help"
f := flag.NewFlagSet("visible", flag.ContinueOnError)
l.VisitAll(func(flag *flag.Flag) {
if flag.Name != hidden {
f.AddFlag(flag)
}
})
return f
}
func filter(cmds []*cobra.Command, names ...string) []*cobra.Command {
out := []*cobra.Command{}
for _, c := range cmds {
if c.Hidden {
continue
}
skip := false
for _, name := range names {
if name == c.Name() {
skip = true
break
}
}
if skip {
continue
}
out = append(out, c)
}
return out
}

@ -1,103 +0,0 @@
// 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 templates provides template functions for working with templates.
package templates
import (
"strings"
"unicode"
)
const (
// SectionVars is the help template section that declares variables to be used in the template.
SectionVars = `{{$isRootCmd := isRootCmd .}}` +
`{{$rootCmd := rootCmd .}}` +
`{{$visibleFlags := visibleFlags (flagsNotIntersected .LocalFlags .PersistentFlags)}}` +
`{{$explicitlyExposedFlags := exposed .}}` +
`{{$optionsCmdFor := optionsCmdFor .}}` +
`{{$usageLine := usageLine .}}`
// SectionAliases is the help template section that displays command aliases.
SectionAliases = `{{if gt .Aliases 0}}Aliases:
{{.NameAndAliases}}
{{end}}`
// SectionExamples is the help template section that displays command examples.
SectionExamples = `{{if .HasExample}}Examples:
{{trimRight .Example}}
{{end}}`
// SectionSubcommands is the help template section that displays the command's subcommands.
SectionSubcommands = `{{if .HasAvailableSubCommands}}{{cmdGroupsString .}}
{{end}}`
// SectionFlags is the help template section that displays the command's flags.
SectionFlags = `{{ if or $visibleFlags.HasFlags $explicitlyExposedFlags.HasFlags}}Options:
{{ if $visibleFlags.HasFlags}}{{trimRight (flagsUsages $visibleFlags)}}{{end}}{{ if $explicitlyExposedFlags.HasFlags}}{{ if $visibleFlags.HasFlags}}
{{end}}{{trimRight (flagsUsages $explicitlyExposedFlags)}}{{end}}
{{end}}`
// SectionUsage is the help template section that displays the command's usage.
SectionUsage = `{{if and .Runnable (ne .UseLine "") (ne .UseLine $rootCmd)}}Usage:
{{$usageLine}}
{{end}}`
// SectionTipsHelp is the help template section that displays the '--help' hint.
SectionTipsHelp = `{{if .HasSubCommands}}Use "{{$rootCmd}} <command> --help" for more information about a given command.
{{end}}`
// SectionTipsGlobalOptions is the help template section that displays the 'options' hint for displaying global
// flags.
SectionTipsGlobalOptions = `{{if $optionsCmdFor}}Use "{{$optionsCmdFor}}" for a list of global command-line options (applies to all commands).
{{end}}`
)
// MainHelpTemplate if the template for 'help' used by most commands.
func MainHelpTemplate() string {
return `{{with or .Long .Short }}{{. | trim}}{{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`
}
// MainUsageTemplate if the template for 'usage' used by most commands.
func MainUsageTemplate() string {
sections := []string{
"\n\n",
SectionVars,
SectionAliases,
SectionExamples,
SectionSubcommands,
SectionFlags,
SectionUsage,
SectionTipsHelp,
SectionTipsGlobalOptions,
}
return strings.TrimRightFunc(strings.Join(sections, ""), unicode.IsSpace)
}
// OptionsHelpTemplate if the template for 'help' used by the 'options' command.
func OptionsHelpTemplate() string {
return ""
}
// OptionsUsageTemplate if the template for 'usage' used by the 'options' command.
func OptionsUsageTemplate() string {
return `{{ if .HasInheritedFlags}}The following options can be passed to any command:
{{flagsUsages .InheritedFlags}}{{end}}`
}

@ -1,53 +0,0 @@
// 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 term
import (
"github.com/moby/term"
)
// TerminalSize represents the width and height of a terminal.
type TerminalSize struct {
Width uint16
Height uint16
}
// TerminalSizeQueue is capable of returning terminal resize events as they occur.
type TerminalSizeQueue interface {
// Next returns the new terminal size after the terminal has been resized. It returns nil when
// monitoring has been stopped.
Next() *TerminalSize
}
// GetSize returns the current size of the user's terminal. If it isn't a terminal,
// nil is returned.
func (t TTY) GetSize() *TerminalSize {
outFd, isTerminal := term.GetFdInfo(t.Out)
if !isTerminal {
return nil
}
return GetSize(outFd)
}
// GetSize returns the current size of the terminal associated with fd.
func GetSize(fd uintptr) *TerminalSize {
winsize, err := term.GetWinsize(fd)
if err != nil {
// runtime.HandleError(fmt.Errorf("unable to get terminal size: %v", err))
return nil
}
return &TerminalSize{Width: winsize.Width, Height: winsize.Height}
}

@ -1,37 +0,0 @@
// 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 term provides structures and helper functions to work with
// terminal (state, sizes).
package term
import (
"io"
)
// TTY helps invoke a function and preserve the state of the terminal, even if the process is
// terminated during execution. It also provides support for terminal resizing for remote command
// execution/attachment.
type TTY struct {
// In is a reader representing stdin. It is a required field.
In io.Reader
// Out is a writer representing stdout. It must be set to support terminal resizing. It is an
// optional field.
Out io.Writer
// Raw is true if the terminal should be set raw.
Raw bool
// TryDev indicates the TTY should try to open /dev/tty if the provided input
// is not a file descriptor.
TryDev bool
}

@ -1,124 +0,0 @@
// 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 term
import (
"io"
"os"
wordwrap "github.com/mitchellh/go-wordwrap"
"github.com/moby/term"
)
type wordWrapWriter struct {
limit uint
writer io.Writer
}
// NewResponsiveWriter creates a Writer that detects the column width of the
// terminal we are in, and adjusts every line width to fit and use recommended
// terminal sizes for better readability. Does proper word wrapping automatically.
//
// if terminal width >= 120 columns use 120 columns
// if terminal width >= 100 columns use 100 columns
// if terminal width >= 80 columns use 80 columns
//
// In case we're not in a terminal or if it's smaller than 80 columns width,
// doesn't do any wrapping.
func NewResponsiveWriter(w io.Writer) io.Writer {
file, ok := w.(*os.File)
if !ok {
return w
}
fd := file.Fd()
if !term.IsTerminal(fd) {
return w
}
terminalSize := GetSize(fd)
if terminalSize == nil {
return w
}
var limit uint
switch {
case terminalSize.Width >= 120:
limit = 120
case terminalSize.Width >= 100:
limit = 100
case terminalSize.Width >= 80:
limit = 80
}
return NewWordWrapWriter(w, limit)
}
// NewWordWrapWriter is a Writer that supports a limit of characters on every line
// and does auto word wrapping that respects that limit.
func NewWordWrapWriter(w io.Writer, limit uint) io.Writer {
return &wordWrapWriter{
limit: limit,
writer: w,
}
}
func (w wordWrapWriter) Write(p []byte) (nn int, err error) {
if w.limit == 0 {
return w.writer.Write(p)
}
original := string(p)
wrapped := wordwrap.WrapString(original, w.limit)
return w.writer.Write([]byte(wrapped))
}
// NewPunchCardWriter is a NewWordWrapWriter that limits the line width to 80 columns.
func NewPunchCardWriter(w io.Writer) io.Writer {
return NewWordWrapWriter(w, 80)
}
type maxWidthWriter struct {
maxWidth uint
currentWidth uint
written uint
writer io.Writer
}
// NewMaxWidthWriter is a Writer that supports a limit of characters on every
// line, but doesn't do any word wrapping automatically.
func NewMaxWidthWriter(w io.Writer, maxWidth uint) io.Writer {
return &maxWidthWriter{
maxWidth: maxWidth,
writer: w,
}
}
func (m maxWidthWriter) Write(p []byte) (nn int, err error) {
for _, b := range p {
if m.currentWidth == m.maxWidth {
m.writer.Write([]byte{'\n'})
m.currentWidth = 0
}
if b == '\n' {
m.currentWidth = 0
}
_, err := m.writer.Write([]byte{b})
if err != nil {
return int(m.written), err
}
m.written++
m.currentWidth++
}
return len(p), nil
}

@ -1,114 +0,0 @@
// 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 term
import (
"bytes"
"strings"
"testing"
)
const test = "Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam"
func TestWordWrapWriter(t *testing.T) {
testcases := map[string]struct {
input string
maxWidth uint
}{
"max 10": {input: test, maxWidth: 10},
"max 80": {input: test, maxWidth: 80},
"max 120": {input: test, maxWidth: 120},
"max 5000": {input: test, maxWidth: 5000},
}
for k, tc := range testcases {
b := bytes.NewBufferString("")
w := NewWordWrapWriter(b, tc.maxWidth)
_, err := w.Write([]byte(tc.input))
if err != nil {
t.Errorf("%s: Unexpected error: %v", k, err)
}
result := b.String()
if !strings.Contains(result, "Iam") {
t.Errorf("%s: Expected to contain \"Iam\"", k)
}
if len(result) < len(tc.input) {
t.Errorf(
"%s: Unexpectedly short string, got %d wanted at least %d chars: %q",
k,
len(result),
len(tc.input),
result,
)
}
for _, line := range strings.Split(result, "\n") {
if len(line) > int(tc.maxWidth) {
t.Errorf("%s: Every line must be at most %d chars long, got %d: %q", k, tc.maxWidth, len(line), line)
}
}
for _, word := range strings.Split(result, " ") {
if !strings.Contains(word, "Iam") {
t.Errorf("%s: Unexpected broken word: %q", k, word)
}
}
}
}
func TestMaxWidthWriter(t *testing.T) {
testcases := map[string]struct {
input string
maxWidth uint
}{
"max 10": {input: test, maxWidth: 10},
"max 80": {input: test, maxWidth: 80},
"max 120": {input: test, maxWidth: 120},
"max 5000": {input: test, maxWidth: 5000},
}
for k, tc := range testcases {
b := bytes.NewBufferString("")
w := NewMaxWidthWriter(b, tc.maxWidth)
_, err := w.Write([]byte(tc.input))
if err != nil {
t.Errorf("%s: Unexpected error: %v", k, err)
}
result := b.String()
if !strings.Contains(result, "Iam") {
t.Errorf("%s: Expected to contain \"Iam\"", k)
}
if len(result) < len(tc.input) {
t.Errorf(
"%s: Unexpectedly short string, got %d wanted at least %d chars: %q",
k,
len(result),
len(tc.input),
result,
)
}
lines := strings.Split(result, "\n")
for i, line := range lines {
if len(line) > int(tc.maxWidth) {
t.Errorf("%s: Every line must be at most %d chars long, got %d: %q", k, tc.maxWidth, len(line), line)
}
if i < len(lines)-1 && len(line) != int(tc.maxWidth) {
t.Errorf(
"%s: Lines except the last one are expected to be exactly %d chars long, got %d: %q",
k,
tc.maxWidth,
len(line),
line,
)
}
}
}
}
Loading…
Cancel
Save