From 1412f7a30721dcb9e7d14075b4cc5fa294707bd9 Mon Sep 17 00:00:00 2001 From: mynk8 Date: Sat, 2 May 2026 20:58:59 +0530 Subject: [PATCH 1/3] chore: update Makefile --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index 11f799a..ace0425 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,10 @@ WATCHER_NAME=watcher HOST_OS=$(shell go env GOOS) HOST_ARCH=$(shell go env GOARCH) +.PHONY: vet +vet: + go vet ./... + .PHONY: build-local build-local: go build -o ${DIST_DIR}/${BIN_NAME} From 7ec36ed64bec6a1deba80c1eef1ef73580cecda8 Mon Sep 17 00:00:00 2001 From: mynk8 Date: Sat, 2 May 2026 20:59:10 +0530 Subject: [PATCH 2/3] fix: replace unused error creation with log output --- pkg/watcher/executor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/watcher/executor.go b/pkg/watcher/executor.go index 3347fc6..069043b 100644 --- a/pkg/watcher/executor.go +++ b/pkg/watcher/executor.go @@ -12,7 +12,7 @@ func TriggerImport(entry config.WatchEntry) { // Retrieve config to get client options. cfgPath, err := config.DefaultLocalConfigPath() if err != nil { - fmt.Errorf("Error while loading config: %s", err.Error()) + fmt.Printf("Error while loading config: %s\n", err.Error()) } fmt.Println("[INFO] Re-importing changed file: " + entry.FilePath) From a632e869ad0da3897c8350dbb07a485473971f00 Mon Sep 17 00:00:00 2001 From: mynk8 Date: Mon, 4 May 2026 21:20:07 +0530 Subject: [PATCH 3/3] refactor: adopt idiomatic cobra patterns across all commands Replace Run with RunE, manual len(args) checks with cobra.Args validators, and os.Exit(1) calls with error returns across all 11 command files. The original cobra migration (PR #125) directly translated the old flag/command.Execute() pattern into cobra's Run, preserving os.Exit(1) for error handling. This meant cobra's PreRunE, Args, and RunE hooks were never adopted, and deferred calls were skipped on errors. Changes: - All commands: Run -> RunE returning error - test: Args ExactArgs(3), PreRunE for runner validation - import: Args MinimumNArgs(1) - import-url: Args MinimumNArgs(1) - import-dir: Args ExactArgs(1) - login: Args ExactArgs(1), PreRunE for --sso-port range validation - logout: Args ExactArgs(1) - context: conditional arg validation in RunE - start: PreRunE for --port range validation - Root command: add SilenceErrors alongside existing SilenceUsage - 24 os.Exit(1) calls removed, replaced by error returns - log.Fatalf calls in start/stop replaced by fmt.Errorf returns - Port validation tests for --port and --sso-port --- cmd/cmd.go | 7 ++-- cmd/cmd_test.go | 88 ++++++++++++++++++++++++++++++++++++++++ cmd/context.go | 12 +++--- cmd/import.go | 102 ++++++++++++++++------------------------------- cmd/importDir.go | 24 ++++------- cmd/importURL.go | 57 ++++++-------------------- cmd/login.go | 38 ++++++++++++------ cmd/logout.go | 14 ++----- cmd/start.go | 25 +++++++----- cmd/stop.go | 15 +++---- cmd/test.go | 90 ++++++++++------------------------------- cmd/version.go | 3 +- 12 files changed, 227 insertions(+), 248 deletions(-) create mode 100644 cmd/cmd_test.go diff --git a/cmd/cmd.go b/cmd/cmd.go index 16df6b9..4acf8b7 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -27,9 +27,10 @@ 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", + SilenceUsage: true, + 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..34c86f1 100644 --- a/cmd/import.go +++ b/cmd/import.go @@ -1,23 +1,9 @@ -/* - * Copyright The Microcks Authors. - * - * 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 cmd import ( "fmt" - "os" + "path/filepath" + "runtime" "strconv" "strings" @@ -28,67 +14,67 @@ 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.MinimumNArgs(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 != "" { - // 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 = "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. mc.SetOAuthToken(oauthToken) - // If no context provided use current one from config file or client server address. - // So that watch config can be updated properly, referencing the right context. if globalClientOpts.Context == "" { if (localConfig != nil) && (localConfig.CurrentContext != "") { globalClientOpts.Context = localConfig.CurrentContext @@ -98,10 +84,8 @@ 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,32 +94,20 @@ func NewImportCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command mc, err = connectors.NewClient(*globalClientOpts) if err != nil { - fmt.Printf("error %v", err) - return + return err } } - // Handle multiple specification files separated by comma. sepSpecificationFiles := strings.Split(specificationFiles, ",") for _, f := range sepSpecificationFiles { 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 { @@ -143,7 +115,6 @@ func NewImportCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command } fmt.Printf("Microcks has %s '%s'\n", action, msg) - // If watch flag is provided, update watch config. if watch { watchFile, err := config.DefaultLocalWatchPath() errors.CheckError(err) @@ -154,25 +125,21 @@ func NewImportCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command watchCfg = &config.WatchConfig{} } - // 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{ FilePath: f, Context: []string{globalClientOpts.Context}, MainArtifact: mainArtifact, }) - // Write watch file. err = config.WriteLocalWatchConfig(*watchCfg, watchFile) errors.CheckError(err) } } - // Start watcher if --watch flag is provided. if watch { watchFile, err := config.DefaultLocalWatchPath() errors.CheckError(err) @@ -183,6 +150,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..9bc997f 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 @@ -163,10 +155,9 @@ func NewImportDirCommand(globalClientOpts *connectors.ClientOptions) *cobra.Comm if err != nil { if validationErr, ok := err.(*ValidationError); ok { fmt.Println(validationErr.Message) - return + return nil } - fmt.Printf("Error: %v\n", err) - os.Exit(1) + return err } // Display results @@ -197,6 +188,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..4adac8c 100644 --- a/cmd/importURL.go +++ b/cmd/importURL.go @@ -1,24 +1,7 @@ -/* - * Copyright The Microcks Authors. - * - * 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 cmd import ( "fmt" - "os" "strconv" "strings" @@ -29,16 +12,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.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { specificationFiles := args[0] config.InsecureTLS = globalClientOpts.InsecureTLS @@ -48,41 +26,33 @@ func NewImportURLCommand(globalClientOpts *connectors.ClientOptions) *cobra.Comm var mc connectors.MicrocksClient if globalClientOpts.ServerAddr != "" && globalClientOpts.ClientId != "" && globalClientOpts.ClientSecret != "" { - // 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 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 +61,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 +69,6 @@ func NewImportURLCommand(globalClientOpts *connectors.ClientOptions) *cobra.Comm mainArtifact := true secret := "" - // 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 +76,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 +85,13 @@ func NewImportURLCommand(globalClientOpts *connectors.ClientOptions) *cobra.Comm } } - // Try downloading the artifcat 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..a9ba445 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 }, } @@ -228,8 +229,6 @@ 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) handleErr(w, "Unable to complete login flow: too many redirects") return } @@ -239,6 +238,21 @@ func oauth2login( return } + handledRequests++ + if handledRequests > 2 { + return + } + + if state := r.FormValue("state"); state != stateNonce { + handleErr(w, "Unknown state nonce") + return + } + + if state := r.FormValue("state"); state != stateNonce { + handleErr(w, "Unknown state nonce") + return + } + tokenString = r.FormValue("id_token") if tokenString == "" { code := r.FormValue("code") 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..5eb5c76 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,15 @@ 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 { _, 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 +66,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 ca14a83..a3b0a42 100644 --- a/cmd/test.go +++ b/cmd/test.go @@ -1,23 +1,7 @@ -/* - * Copyright The Microcks Authors. - * - * 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 cmd import ( "fmt" - "os" "strconv" "strings" "time" @@ -29,7 +13,7 @@ import ( ) var ( - runnerChoices = map[string]bool{"HTTP": true, "SOAP_HTTP": true, "SOAP_UI": true, "POSTMAN": true, "OPEN_API_SCHEMA": true, "ASYNC_API_SCHEMA": true, "GRPC_PROTOBUF": true, "GRAPHQL_SCHEMA": true} + runnerChoices = map[string]bool{"HTTP": true, "SOAP_HTTP": true, "SOAP_UI": true, "POSTMAN": true, "OPEN_API_SCHEMA": true, "ASYNC_API_SCHEMA": true, "GRPC_PROBUF": true, "GRAPHQL_SCHEMA": true} ) func NewTestCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command { @@ -42,42 +26,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_PROBUF, 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. Applying default 5sec") } + 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 @@ -101,42 +67,34 @@ func NewTestCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command { if globalClientOpts.ServerAddr != "" && globalClientOpts.ClientId != "" && globalClientOpts.ClientSecret != "" { - // 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. 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 == "" { @@ -145,8 +103,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) @@ -155,18 +112,13 @@ 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) - // Add 10.000ms to wait time as it's now representing the server timeout. now := nowInMilliseconds() future := now + waitForMilliseconds + 10000 @@ -174,8 +126,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 @@ -192,8 +143,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 }, }