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} 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 }, } 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)