From e07ba99ce63604bab3c34a4576caa52ca3090c7a Mon Sep 17 00:00:00 2001 From: mynk8 Date: Mon, 4 May 2026 21:20:07 +0530 Subject: [PATCH] refactor: adopt idiomatic cobra patterns and fix defer bug on error paths Signed-off-by: mynk8 --- cmd/cmd.go | 6 ++-- cmd/cmd_test.go | 88 ++++++++++++++++++++++++++++++++++++++++++++++++ cmd/context.go | 12 +++---- cmd/import.go | 74 +++++++++++++++++++--------------------- cmd/importDir.go | 26 ++++---------- cmd/importURL.go | 44 +++++++++--------------- cmd/login.go | 23 +++++++------ cmd/logout.go | 14 +++----- cmd/start.go | 25 ++++++++------ cmd/stop.go | 16 ++++----- cmd/test.go | 83 ++++++++++++++------------------------------- cmd/version.go | 3 +- 12 files changed, 220 insertions(+), 194 deletions(-) create mode 100644 cmd/cmd_test.go diff --git a/cmd/cmd.go b/cmd/cmd.go index 16df6b9..718972c 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -27,9 +27,9 @@ func NewCommad() *cobra.Command { var clientOpts connectors.ClientOptions command := &cobra.Command{ - Use: "microcks", - Short: "A CLI tool for Microcks", - SilenceUsage: true, + Use: "microcks", + Short: "A CLI tool for Microcks", + SilenceErrors: true, Run: func(cmd *cobra.Command, args []string) { cmd.HelpFunc()(cmd, args) }, diff --git a/cmd/cmd_test.go b/cmd/cmd_test.go new file mode 100644 index 0000000..7534c4c --- /dev/null +++ b/cmd/cmd_test.go @@ -0,0 +1,88 @@ +package cmd + +import ( + "fmt" + "testing" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/require" +) + +func TestStartCommandPortValidation(t *testing.T) { + tests := []struct { + name string + port string + wantErr bool + }{ + {name: "lowest valid port", port: "1", wantErr: false}, + {name: "default port", port: "8585", wantErr: false}, + {name: "highest valid port", port: "65535", wantErr: false}, + {name: "non numeric port", port: "abc", wantErr: true}, + {name: "zero port", port: "0", wantErr: true}, + {name: "negative port", port: "-1", wantErr: true}, + {name: "port above range", port: "65536", wantErr: true}, + {name: "empty port", port: "", wantErr: true}, + {name: "port with spaces", port: " 8080", wantErr: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := NewStartCommand(nil) + cmd.SetArgs([]string{"--port", tt.port}) + + executed := false + cmd.RunE = func(cmd *cobra.Command, args []string) error { + executed = true + return nil + } + + err := cmd.Execute() + if tt.wantErr { + require.Error(t, err) + require.False(t, executed, "RunE should not execute when validation fails") + } else { + require.NoError(t, err) + require.True(t, executed, "RunE should execute when validation passes") + } + }) + } +} + +func TestLoginCommandSsoPortValidation(t *testing.T) { + tests := []struct { + name string + ssoPort int + wantErr bool + }{ + {name: "lowest valid port", ssoPort: 1, wantErr: false}, + {name: "default port", ssoPort: 58085, wantErr: false}, + {name: "highest valid port", ssoPort: 65535, wantErr: false}, + {name: "zero port", ssoPort: 0, wantErr: true}, + {name: "negative port", ssoPort: -1, wantErr: true}, + {name: "port above range", ssoPort: 65536, wantErr: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := NewLoginCommand(nil) + cmd.SetArgs([]string{"--sso-port", fmt.Sprintf("%d", tt.ssoPort), "http://localhost:8080"}) + + executed := false + originalRun := cmd.Run + cmd.RunE = func(cmd *cobra.Command, args []string) error { + executed = true + return nil + } + + err := cmd.Execute() + if tt.wantErr { + require.Error(t, err) + require.False(t, executed, "RunE should not execute when validation fails") + } else { + require.NoError(t, err) + require.True(t, executed, "RunE should execute when validation passes") + } + _ = originalRun + }) + } +} diff --git a/cmd/context.go b/cmd/context.go index 8b7f418..f207cb5 100644 --- a/cmd/context.go +++ b/cmd/context.go @@ -27,29 +27,28 @@ microcks context http://localhost:8080 # Delete Microcks context microcks context http://localhost:8080 --delete`, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { configPath := globalClientOpts.ConfigPath localCfg, err := config.ReadLocalConfig(configPath) errors.CheckError(err) if delete { if len(args) == 0 { - cmd.HelpFunc()(cmd, args) - os.Exit(1) + return fmt.Errorf("context name is required when using --delete") } err := deleteContext(args[0], configPath) errors.CheckError(err) - return + return nil } if len(args) == 0 { printMicrocksContexts(configPath) - return + return nil } ctxName := args[0] if localCfg.CurrentContext == ctxName { fmt.Printf("Already at context '%s'\n", localCfg.CurrentContext) - return + return nil } if _, err = localCfg.ResolveContext(ctxName); err != nil { log.Fatal(err) @@ -58,6 +57,7 @@ microcks context http://localhost:8080 --delete`, err = config.WriteLocalConfig(*localCfg, configPath) errors.CheckError(err) fmt.Printf("Switched to context '%s'\n", localCfg.CurrentContext) + return nil }, } diff --git a/cmd/import.go b/cmd/import.go index 2747151..bf43c13 100644 --- a/cmd/import.go +++ b/cmd/import.go @@ -17,7 +17,8 @@ package cmd import ( "fmt" - "os" + "path/filepath" + "runtime" "strconv" "strings" @@ -28,37 +29,45 @@ import ( "github.com/spf13/cobra" ) +func parsePathSuffix(arg string) (string, bool) { + colonIdx := strings.LastIndex(arg, ":") + if colonIdx < 0 { + return arg, true + } + if runtime.GOOS == "windows" && colonIdx == 1 && len(arg) > 2 { + if c := arg[0]; (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') { + return arg, true + } + } + pathPart := arg[:colonIdx] + suffixPart := arg[colonIdx+1:] + val, err := strconv.ParseBool(suffixPart) + if err != nil { + return arg, true + } + return pathPart, val +} + func NewImportCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command { var watch bool var importCmd = &cobra.Command{ - Use: "import", + Use: "import ", Short: "import API artifacts on Microcks server", Long: `import API artifacts on Microcks server`, - Args: cobra.MaximumNArgs(1), - Run: func(cmd *cobra.Command, args []string) { - // Parse subcommand args first. - if len(args) == 0 { - fmt.Println("import command require args") - cmd.HelpFunc()(cmd, args) - os.Exit(1) - } - + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { specificationFiles := args[0] - // Initialize config from command options. config.InsecureTLS = globalClientOpts.InsecureTLS config.CaCertPaths = globalClientOpts.CaCertPaths config.Verbose = globalClientOpts.Verbose - // Read local config file in case we need some context info. localConfig, err := config.ReadLocalConfig(globalClientOpts.ConfigPath) if err != nil { - fmt.Println(err) - return + return err } - // Prepare Microcks client. var mc connectors.MicrocksClient if globalClientOpts.ServerAddr != "" && globalClientOpts.ClientId != "" && globalClientOpts.ClientSecret != "" { @@ -67,8 +76,7 @@ func NewImportCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command keycloakURL, err := mc.GetKeycloakURL() if err != nil { - fmt.Printf("Got error when invoking Microcks client retrieving config: %s", err) - os.Exit(1) + return fmt.Errorf("got error when invoking Microcks client retrieving config: %s", err) } var oauthToken string = "unauthenticated-token" @@ -78,10 +86,8 @@ func NewImportCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command oauthToken, err = kc.ConnectAndGetToken() if err != nil { - fmt.Printf("Got error when invoking Keycloack client: %s", err) - os.Exit(1) + return fmt.Errorf("got error when invoking Keycloak client: %s", err) } - //fmt.Printf("Retrieve OAuthToken: %s", oauthToken) } // Set Auth token. @@ -100,8 +106,7 @@ func NewImportCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command } else { // Create client from config file and using the current or provided context. if localConfig == nil { - fmt.Println("Please login to perform operation...") - return + return fmt.Errorf("please login to perform operation") } if globalClientOpts.Context == "" { @@ -110,8 +115,7 @@ func NewImportCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command mc, err = connectors.NewClient(*globalClientOpts) if err != nil { - fmt.Printf("error %v", err) - return + return err } } @@ -121,21 +125,12 @@ func NewImportCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command mainArtifact := true var err error - // Check if mainArtifact flag is provided. - if strings.Contains(f, ":") { - pathAndMainArtifact := strings.Split(f, ":") - f = pathAndMainArtifact[0] - mainArtifact, err = strconv.ParseBool(pathAndMainArtifact[1]) - if err != nil { - fmt.Printf("Cannot parse '%s' as Bool, default to true\n", pathAndMainArtifact[1]) - } - } + f, mainArtifact = parsePathSuffix(f) // Try uploading this artifact. msg, err := mc.UploadArtifact(f, mainArtifact) if err != nil { - fmt.Printf("Got error when invoking Microcks client importing Artifact: %s", err) - os.Exit(1) + return fmt.Errorf("got error when invoking Microcks client importing Artifact: %s", err) } action := "discovered" if !mainArtifact { @@ -155,9 +150,9 @@ func NewImportCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command } // Normalize file path to match the watcher fsnotify events format. - if strings.HasPrefix(f, "./") { - f = strings.TrimPrefix(f, "./") - } + f = strings.TrimPrefix(f, "./") + f = strings.TrimPrefix(f, ".\\") + f = filepath.Clean(f) // Upsert entry. watchCfg.UpsertEntry(config.WatchEntry{ @@ -183,6 +178,7 @@ func NewImportCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command fmt.Println("Watch mode enabled - microcks-watcher started...") wm.Run() } + return nil }, } diff --git a/cmd/importDir.go b/cmd/importDir.go index 16a9157..15119f7 100644 --- a/cmd/importDir.go +++ b/cmd/importDir.go @@ -115,13 +115,8 @@ func NewImportDirCommand(globalClientOpts *connectors.ClientOptions) *cobra.Comm microcks import-dir ./api-specs --recursive microcks import-dir ./api-specs --pattern "*.yaml" microcks import-dir ./api-specs --recursive --pattern "openapi.*"`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) == 0 { - fmt.Println("import-dir command requires a directory path") - cmd.HelpFunc()(cmd, args) - os.Exit(1) - } - + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { dirPath := args[0] config.InsecureTLS = globalClientOpts.InsecureTLS @@ -130,13 +125,11 @@ func NewImportDirCommand(globalClientOpts *connectors.ClientOptions) *cobra.Comm localConfig, err := config.ReadLocalConfig(globalClientOpts.ConfigPath) if err != nil { - fmt.Println(err) - return + return err } if localConfig == nil { - fmt.Println("Please login to perform operation...") - return + return fmt.Errorf("please login to perform operation") } if globalClientOpts.Context == "" { @@ -146,8 +139,7 @@ func NewImportDirCommand(globalClientOpts *connectors.ClientOptions) *cobra.Comm // Create client mc, err := connectors.NewClient(*globalClientOpts) if err != nil { - fmt.Printf("error %v", err) - return + return err } // Set up business logic dependencies @@ -161,12 +153,7 @@ func NewImportDirCommand(globalClientOpts *connectors.ClientOptions) *cobra.Comm // Execute business logic result, err := ImportDirectory(mc, fs, dirPath, importConfig) if err != nil { - if validationErr, ok := err.(*ValidationError); ok { - fmt.Println(validationErr.Message) - return - } - fmt.Printf("Error: %v\n", err) - os.Exit(1) + return err } // Display results @@ -197,6 +184,7 @@ func NewImportDirCommand(globalClientOpts *connectors.ClientOptions) *cobra.Comm } fmt.Printf("\nImport completed: %d/%d files imported successfully\n", result.SuccessCount, result.TotalFiles) + return nil }, } diff --git a/cmd/importURL.go b/cmd/importURL.go index 442e7e2..ea81228 100644 --- a/cmd/importURL.go +++ b/cmd/importURL.go @@ -18,7 +18,6 @@ package cmd import ( "fmt" - "os" "strconv" "strings" @@ -29,16 +28,11 @@ import ( func NewImportURLCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command { var importURLCmd = &cobra.Command{ - Use: "import-url", + Use: "import-url ", Short: "import API artifacts from URL on Microcks server", Long: `import API artifacts from URL on Microcks server`, - Run: func(cmd *cobra.Command, args []string) { - // Parse subcommand args first. - if len(args) == 0 { - fmt.Println("import-url command require args") - os.Exit(1) - } - + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { specificationFiles := args[0] config.InsecureTLS = globalClientOpts.InsecureTLS @@ -48,41 +42,36 @@ func NewImportURLCommand(globalClientOpts *connectors.ClientOptions) *cobra.Comm var mc connectors.MicrocksClient if globalClientOpts.ServerAddr != "" && globalClientOpts.ClientId != "" && globalClientOpts.ClientSecret != "" { - // create client with server address + // Create client with server address. mc = connectors.NewMicrocksClient(globalClientOpts.ServerAddr) keycloakURL, err := mc.GetKeycloakURL() if err != nil { - fmt.Printf("Got error when invoking Microcks client retrieving config: %s", err) - os.Exit(1) + return fmt.Errorf("got error when invoking Microcks client retrieving config: %s", err) } - var oauthToken string = "unauthentifed-token" + var oauthToken string = "unauthenticated-token" if keycloakURL != "null" { // If Keycloak is enabled, retrieve an OAuth token using Keycloak Client. kc := connectors.NewKeycloakClient(keycloakURL, globalClientOpts.ClientId, globalClientOpts.ClientSecret) oauthToken, err = kc.ConnectAndGetToken() if err != nil { - fmt.Printf("Got error when invoking Keycloack client: %s", err) - os.Exit(1) + return fmt.Errorf("got error when invoking Keycloak client: %s", err) } - //fmt.Printf("Retrieve OAuthToken: %s", oauthToken) } - //Set Auth token + // Set Auth token. mc.SetOAuthToken(oauthToken) } else { localConfig, err := config.ReadLocalConfig(globalClientOpts.ConfigPath) if err != nil { - fmt.Println(err) - return + return err } if localConfig == nil { - fmt.Println("Please login to perform opertion...") - return + return fmt.Errorf("please login to perform operation") } if globalClientOpts.Context == "" { @@ -91,8 +80,7 @@ func NewImportURLCommand(globalClientOpts *connectors.ClientOptions) *cobra.Comm mc, err = connectors.NewClient(*globalClientOpts) if err != nil { - fmt.Printf("error %v", err) - return + return err } } sepSpecificationFiles := strings.Split(specificationFiles, ",") @@ -100,7 +88,7 @@ func NewImportURLCommand(globalClientOpts *connectors.ClientOptions) *cobra.Comm mainArtifact := true secret := "" - // Check if URL starts with https or http + // Check if URL starts with https or http. if strings.HasPrefix(f, "https://") || strings.HasPrefix(f, "http://") { urlAndMainAtrifactAndSecretName := strings.Split(f, ":") n := len(urlAndMainAtrifactAndSecretName) @@ -108,7 +96,7 @@ func NewImportURLCommand(globalClientOpts *connectors.ClientOptions) *cobra.Comm if n > 2 { val, err := strconv.ParseBool(urlAndMainAtrifactAndSecretName[2]) if err != nil { - fmt.Println(err) + return fmt.Errorf("failed to parse mainArtifact flag: %w", err) } mainArtifact = val } @@ -117,14 +105,14 @@ func NewImportURLCommand(globalClientOpts *connectors.ClientOptions) *cobra.Comm } } - // Try downloading the artifcat + // Try downloading the artifact. msg, err := mc.DownloadArtifact(f, mainArtifact, secret) if err != nil { - fmt.Printf("Got error when invoking Microcks client importing Artifact: %s", err) - os.Exit(1) + return fmt.Errorf("got error when invoking Microcks client importing Artifact: %s", err) } fmt.Printf("Microcks has discovered '%s'\n", msg) } + return nil }, } diff --git a/cmd/login.go b/cmd/login.go index 2b624f7..57ba7ba 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -57,15 +57,16 @@ microcks login http://localhost:8080 --sso --sso-port # Get OAuth URI instead of getting redirect to browser for SSO login microcks login http://localhost:8080 --sso --sso-launch-browser=false `, - Run: func(cmd *cobra.Command, args []string) { - ctx := cmd.Context() - var server string - - //Chekc if server name is provided or not - if len(args) != 1 { - cmd.HelpFunc()(cmd, args) - os.Exit(1) + Args: cobra.ExactArgs(1), + PreRunE: func(cmd *cobra.Command, args []string) error { + if ssoProt < 1 || ssoProt > 65535 { + return fmt.Errorf("--sso-port must be a number between 1 and 65535, got %d", ssoProt) } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + server := args[0] config.InsecureTLS = globalClientOpts.InsecureTLS config.CaCertPaths = globalClientOpts.CaCertPaths @@ -116,8 +117,7 @@ microcks login http://localhost:8080 --sso --sso-launch-browser=false clientSecret := os.Getenv("MICROCKS_CLIENT_SECRET") if clientID == "" || clientSecret == "" { - fmt.Printf("Please Set 'MICROCKS_CLIENT_ID' & 'MICROCKS_CLIENT_SECRET' to perform password login\n") - os.Exit(1) + return fmt.Errorf("please set MICROCKS_CLIENT_ID and MICROCKS_CLIENT_SECRET environment variables to perform password login") } //Perform login and retrive tokens authToken, refreshToken = passwordLogin(keycloakUrl, clientID, clientSecret, username, password) @@ -170,6 +170,7 @@ microcks login http://localhost:8080 --sso --sso-launch-browser=false errors.CheckError(err) fmt.Printf("Context '%s' updated\n", ctxName) + return nil }, } @@ -229,7 +230,7 @@ func oauth2login( handledRequests++ if handledRequests > 2 { // Since implicit flow will redirect back to ourselves, this counter ensures we do not - // fallinto a redirect loop (e.g. user visits the page by hand) + // fall into a redirect loop (e.g. user visits the page by hand) handleErr(w, "Unable to complete login flow: too many redirects") return } diff --git a/cmd/logout.go b/cmd/logout.go index 8c1fca9..3e26f33 100644 --- a/cmd/logout.go +++ b/cmd/logout.go @@ -3,7 +3,6 @@ package cmd import ( "fmt" "log" - "os" "github.com/microcks/microcks-cli/pkg/config" "github.com/microcks/microcks-cli/pkg/connectors" @@ -19,13 +18,8 @@ func NewLogoutCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command Long: "Log out from Microcks", Example: `# To log out of Microcks $ microcks logout`, - - Run: func(cmd *cobra.Command, args []string) { - if len(args) == 0 { - cmd.HelpFunc()(cmd, args) - os.Exit(1) - } - + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { context := args[0] localCfg, err := config.ReadLocalConfig(globalClientOpts.ConfigPath) errors.CheckError(err) @@ -33,7 +27,6 @@ $ microcks logout`, log.Fatalf("Nothing to logout from") } - // Remove authToken ok := localCfg.RemoveToken(context) if !ok { log.Fatalf("Context %s does not exist", context) @@ -41,12 +34,13 @@ $ microcks logout`, err = config.ValidateLocalConfig(*localCfg) if err != nil { - log.Fatalf("Error in loging out: %s", err) + log.Fatalf("Error in logging out: %s", err) } err = config.WriteLocalConfig(*localCfg, globalClientOpts.ConfigPath) errors.CheckError(err) fmt.Printf("Logged out from '%s'\n", context) + return nil }, } diff --git a/cmd/start.go b/cmd/start.go index f36c22c..d084c24 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -2,7 +2,7 @@ package cmd import ( "fmt" - "log" + "strconv" "github.com/microcks/microcks-cli/pkg/config" "github.com/microcks/microcks-cli/pkg/connectors" @@ -32,7 +32,14 @@ microcks start --driver [driver you wnat either 'docker' or 'podman'] # Define name of your microcks container/instance microcks start --name [name of you container/instance]`, - Run: func(cmd *cobra.Command, args []string) { + PreRunE: func(cmd *cobra.Command, args []string) error { + portNum, err := strconv.Atoi(hostPort) + if err != nil || portNum < 1 || portNum > 65535 { + return fmt.Errorf("--port must be a number between 1 and 65535, got %q", hostPort) + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { configFile := globalClientOpts.ConfigPath localConfig, err := config.ReadLocalConfig(configFile) @@ -49,21 +56,20 @@ microcks start --name [name of you container/instance]`, if instance.Status == "Running" { fmt.Printf("Microcks instance with name %s is already running", name) - return + return nil } switch instance.Status { case "Running": fmt.Printf("Microcks instance with name %s is already running", name) - return + return nil case "Exited": containerClient, err := connectors.NewContainerClient(instance.Driver) errors.CheckError(err) defer containerClient.CloseClient() if err := containerClient.StartContainer(instance.ContainerID); err != nil { - log.Fatalf("failed to start container: %v", err) - return + return fmt.Errorf("failed to start container: %w", err) } instance.Status = "Running" default: @@ -78,13 +84,11 @@ microcks start --name [name of you container/instance]`, AutoRemove: autoRemove, }) if err != nil { - log.Fatalf("Failed to create a container: %v", err) - return + return fmt.Errorf("failed to create a container: %w", err) } if err := containerClient.StartContainer(containerId); err != nil { - log.Fatalf("failed to start container: %v", err) - return + return fmt.Errorf("failed to start container: %w", err) } instance.ContainerID = containerId @@ -141,6 +145,7 @@ microcks start --name [name of you container/instance]`, errors.CheckError(err) fmt.Printf("Microcks started successfully at %s\n", server) + return nil }, } startCmd.Flags().StringVar(&name, "name", "microcks", "name for you microcks instance") diff --git a/cmd/stop.go b/cmd/stop.go index eaba91f..9c4b00c 100644 --- a/cmd/stop.go +++ b/cmd/stop.go @@ -16,7 +16,7 @@ func NewStopCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command { Use: "stop", Short: "stop microcks instance", Long: "stop microcks instance", - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { configFile := globalClientOpts.ConfigPath localConfig, err := config.ReadLocalConfig(configFile) @@ -24,7 +24,7 @@ func NewStopCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command { if localConfig == nil { fmt.Println("Config not found, nothing to stop") - return + return nil } ctx, err := localConfig.ResolveContext("") @@ -33,7 +33,7 @@ func NewStopCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command { if instance.Name == "" { fmt.Println("No instance is associated with this context") - return + return nil } containerClient, err := connectors.NewContainerClient(instance.Driver) @@ -42,19 +42,16 @@ func NewStopCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command { err = containerClient.StopContainer(instance.ContainerID) if err != nil { - log.Fatalf("Failed to stop a container: %v", err) - return + return fmt.Errorf("failed to stop a container: %w", err) } fmt.Println("") log.Printf("Instance %s stopped successfully", instance.Name) - // update configs - if instance.AutoRemove { + // Update config after removal. _, ok := localConfig.RemoveContext(ctx.Name) if !ok { - log.Fatalf("Context %s does not exist", ctx.Name) - return + return fmt.Errorf("context %s does not exist", ctx.Name) } _ = localConfig.RemoveServer(ctx.Server.Server) _ = localConfig.RemoveUser(ctx.User.Name) @@ -70,6 +67,7 @@ func NewStopCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command { } err = config.WriteLocalConfig(*localConfig, configFile) errors.CheckError(err) + return nil }, } diff --git a/cmd/test.go b/cmd/test.go index 1d29a3f..dd31993 100644 --- a/cmd/test.go +++ b/cmd/test.go @@ -17,7 +17,6 @@ package cmd import ( "fmt" - "os" "strconv" "strings" "time" @@ -42,43 +41,24 @@ func NewTestCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command { ) var testCmd = &cobra.Command{ - Use: "test", + Use: "test ", Short: "Run tests on Microcks", Long: `Run tests on Microcks`, - Run: func(cmd *cobra.Command, args []string) { - // Parse subcommand args first. - if len(os.Args) < 4 { - fmt.Println("test command require args") - os.Exit(1) - } - - serviceRef := args[0] - testEndpoint := args[1] + Args: cobra.ExactArgs(3), + PreRunE: func(cmd *cobra.Command, args []string) error { runnerType := args[2] - - // Validate presence and values of args. - if len(serviceRef) == 0 || strings.HasPrefix(serviceRef, "-") { - fmt.Println("test command require args") - os.Exit(1) - } - if len(testEndpoint) == 0 || strings.HasPrefix(testEndpoint, "-") { - fmt.Println("test command require args") - os.Exit(1) - } - if len(runnerType) == 0 || strings.HasPrefix(runnerType, "-") { - fmt.Println("test command require args") - os.Exit(1) - } if _, validChoice := runnerChoices[runnerType]; !validChoice { - fmt.Println(" should be one of: HTTP, SOAP, SOAP_UI, POSTMAN, OPEN_API_SCHEMA, ASYNC_API_SCHEMA, GRPC_PROTOBUF, GRAPHQL_SCHEMA") - os.Exit(1) + return fmt.Errorf(" should be one of: HTTP, SOAP_HTTP, SOAP_UI, POSTMAN, OPEN_API_SCHEMA, ASYNC_API_SCHEMA, GRPC_PROTOBUF, GRAPHQL_SCHEMA") } - - // Validate presence and values of flags. if !strings.HasSuffix(waitFor, "milli") && !strings.HasSuffix(waitFor, "sec") && !strings.HasSuffix(waitFor, "min") { - fmt.Println("--waitFor format is wrong. Accepted units are: milli, sec, min (e.g. 500milli, 30sec, 5min)") - os.Exit(1) + return fmt.Errorf("--waitFor format is wrong. Accepted units are: milli, sec, min (e.g. 500milli, 30sec, 5min)") } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + serviceRef := args[0] + testEndpoint := args[1] + runnerType := args[2] // Collect optional HTTPS transport flags. config.InsecureTLS = globalClientOpts.InsecureTLS @@ -90,22 +70,19 @@ func NewTestCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command { if strings.HasSuffix(waitFor, "milli") { n, err := strconv.ParseInt(waitFor[:len(waitFor)-5], 0, 64) if err != nil { - fmt.Printf("--waitFor value %q is not a valid number\n", waitFor) - os.Exit(1) + return fmt.Errorf("--waitFor value %q is not a valid number", waitFor) } waitForMilliseconds = n } else if strings.HasSuffix(waitFor, "sec") { n, err := strconv.ParseInt(waitFor[:len(waitFor)-3], 0, 64) if err != nil { - fmt.Printf("--waitFor value %q is not a valid number\n", waitFor) - os.Exit(1) + return fmt.Errorf("--waitFor value %q is not a valid number", waitFor) } waitForMilliseconds = n * 1000 } else if strings.HasSuffix(waitFor, "min") { n, err := strconv.ParseInt(waitFor[:len(waitFor)-3], 0, 64) if err != nil { - fmt.Printf("--waitFor value %q is not a valid number\n", waitFor) - os.Exit(1) + return fmt.Errorf("--waitFor value %q is not a valid number", waitFor) } waitForMilliseconds = n * 60 * 1000 } @@ -114,28 +91,24 @@ func NewTestCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command { var serverAddr string if globalClientOpts.ServerAddr != "" && globalClientOpts.ClientId != "" && globalClientOpts.ClientSecret != "" { - - // create client with server address + // Create client with server address. serverAddr = globalClientOpts.ServerAddr mc = connectors.NewMicrocksClient(serverAddr) keycloakURL, err := mc.GetKeycloakURL() if err != nil { - fmt.Printf("Got error when invoking Microcks client retrieving config: %s", err) - os.Exit(1) + return fmt.Errorf("got error when invoking Microcks client retrieving config: %s", err) } - var oauthToken string = "unauthentifed-token" + var oauthToken string = "unauthenticated-token" if keycloakURL != "null" { // If Keycloak is enabled, retrieve an OAuth token using Keycloak Client. kc := connectors.NewKeycloakClient(keycloakURL, globalClientOpts.ClientId, globalClientOpts.ClientSecret) oauthToken, err = kc.ConnectAndGetToken() if err != nil { - fmt.Printf("Got error when invoking Keycloack client: %s", err) - os.Exit(1) + return fmt.Errorf("got error when invoking Keycloak client: %s", err) } - //fmt.Printf("Retrieve OAuthToken: %s", oauthToken) } // Then - launch the test on Microcks Server. @@ -144,13 +117,11 @@ func NewTestCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command { } else { localConfig, err := config.ReadLocalConfig(globalClientOpts.ConfigPath) if err != nil { - fmt.Println(err) - return + return err } if localConfig == nil { - fmt.Println("Please login to perform opertion...") - return + return fmt.Errorf("please login to perform operation") } if globalClientOpts.Context == "" { @@ -159,8 +130,7 @@ func NewTestCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command { mc, err = connectors.NewClient(*globalClientOpts) if err != nil { - fmt.Printf("error %v", err) - return + return err } ctx, err := localConfig.ResolveContext(globalClientOpts.Context) @@ -169,13 +139,10 @@ func NewTestCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command { serverAddr = ctx.Server.Server } - var testResultID string testResultID, err := mc.CreateTestResult(serviceRef, testEndpoint, runnerType, secretName, waitForMilliseconds, filteredOperations, operationsHeaders, oAuth2Context) if err != nil { - fmt.Printf("Got error when invoking Microcks client creating Test: %s", err) - os.Exit(1) + return fmt.Errorf("got error when invoking Microcks client creating Test: %s", err) } - //fmt.Printf("Retrieve TestResult ID: %s", testResultID) // Finally - wait before checking and loop for some time time.Sleep(1 * time.Second) @@ -188,8 +155,7 @@ func NewTestCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command { for nowInMilliseconds() < future { testResultSummary, err := mc.GetTestResult(testResultID) if err != nil { - fmt.Printf("Got error when invoking Microcks client check TestResult: %s", err) - os.Exit(1) + return fmt.Errorf("got error when invoking Microcks client check TestResult: %s", err) } success = testResultSummary.Success inProgress := testResultSummary.InProgress @@ -206,8 +172,9 @@ func NewTestCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command { fmt.Printf("Full TestResult details are available here: %s/#/tests/%s \n", serverAddr, testResultID) if !success { - os.Exit(1) + return fmt.Errorf("test failed") } + return nil }, } diff --git a/cmd/version.go b/cmd/version.go index 6ce7163..8b5e0bf 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -27,8 +27,9 @@ func NewVersionCommand() *cobra.Command { Use: "version", Short: "Print the version number of microkcs CLI", Long: `Print the version number of microkcs CLI`, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { fmt.Printf("Microcks-CLI %s\n", version.Version) + return nil }, }