From ce2eb87b25ba13e27c6792f1234015380c70054a Mon Sep 17 00:00:00 2001 From: vicentepinto98 Date: Mon, 29 Apr 2024 17:52:06 +0100 Subject: [PATCH 1/9] Lb observability-credentials add command --- go.mod | 3 +- go.sum | 6 + internal/cmd/load-balancer/load_balancer.go | 2 + .../observability-credentials/add/add.go | 153 ++++++++++++++ .../observability-credentials/add/add_test.go | 193 ++++++++++++++++++ .../observability-credentials.go | 32 +++ internal/pkg/print/print.go | 15 ++ 7 files changed, 403 insertions(+), 1 deletion(-) create mode 100644 internal/cmd/load-balancer/observability-credentials/add/add.go create mode 100644 internal/cmd/load-balancer/observability-credentials/add/add_test.go create mode 100644 internal/cmd/load-balancer/observability-credentials/observability-credentials.go diff --git a/go.mod b/go.mod index 76bf05339..398d588a3 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,7 @@ require ( github.com/zalando/go-keyring v0.2.4 golang.org/x/mod v0.17.0 golang.org/x/oauth2 v0.19.0 + golang.org/x/term v0.19.0 golang.org/x/text v0.14.0 ) @@ -58,7 +59,7 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect - golang.org/x/sys v0.17.0 // indirect + golang.org/x/sys v0.19.0 // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 6668cd9a3..b1c960f26 100644 --- a/go.sum +++ b/go.sum @@ -126,6 +126,12 @@ golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/cmd/load-balancer/load_balancer.go b/internal/cmd/load-balancer/load_balancer.go index fdce4cdbf..f5febad6b 100644 --- a/internal/cmd/load-balancer/load_balancer.go +++ b/internal/cmd/load-balancer/load_balancer.go @@ -4,6 +4,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/cmd/load-balancer/describe" generatepayload "github.com/stackitcloud/stackit-cli/internal/cmd/load-balancer/generate-payload" "github.com/stackitcloud/stackit-cli/internal/cmd/load-balancer/list" + observabilitycredentials "github.com/stackitcloud/stackit-cli/internal/cmd/load-balancer/observability-credentials" "github.com/stackitcloud/stackit-cli/internal/cmd/load-balancer/quota" "github.com/stackitcloud/stackit-cli/internal/pkg/args" @@ -31,4 +32,5 @@ func addSubcommands(cmd *cobra.Command, p *print.Printer) { cmd.AddCommand(list.NewCmd(p)) cmd.AddCommand(quota.NewCmd(p)) cmd.AddCommand(generatepayload.NewCmd(p)) + cmd.AddCommand(observabilitycredentials.NewCmd(p)) } diff --git a/internal/cmd/load-balancer/observability-credentials/add/add.go b/internal/cmd/load-balancer/observability-credentials/add/add.go new file mode 100644 index 000000000..a9de57450 --- /dev/null +++ b/internal/cmd/load-balancer/observability-credentials/add/add.go @@ -0,0 +1,153 @@ +package add + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/google/uuid" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/load-balancer/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" +) + +const ( + displayNameFlag = "display-name" + usernameFlag = "username" + passwordFlag = "password" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + DisplayName *string + Username *string + Password *string +} + +func NewCmd(p *print.Printer) *cobra.Command { + cmd := &cobra.Command{ + Use: "add", + Short: "Adds observability credentials to a Load Balancer", + Long: "Adds existing observability credentials (username and password) to a Load Balancer. The credentials can be for Argus or another monitoring tool.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `Add credentials to a load balancer with username "xxx" and display name "yyy". The password is entered using the terminal`, + "$ stackit load-balancer observability-credentials add --username xxx --display-name yyy"), + examples.NewExample( + `Add credentials to a load balancer with username "xxx" and display name "yyy", providing the password as flag`, + "$ stackit load-balancer observability-credentials add --username xxx --password pwd --display-name yyy"), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(p, cmd) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(p) + if err != nil { + return err + } + + projectLabel, err := projectname.GetProjectName(ctx, p, cmd) + if err != nil { + p.Debug(print.ErrorLevel, "get project name: %v", err) + projectLabel = model.ProjectId + } + + if !model.AssumeYes { + prompt := fmt.Sprintf("Are you sure you want to add observability credentials for your Load Balancers on project %q?", projectLabel) + err = p.PromptForConfirmation(prompt) + if err != nil { + return err + } + } + + // Prompt for password if not passed in as a flag + if model.Password == nil { + pwd, err := p.PromptForPassword("Enter password: ") + if err != nil { + return fmt.Errorf("prompt for password: %w", err) + } + model.Password = utils.Ptr(pwd) + } + + // Call API + req := buildRequest(ctx, model, apiClient) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("add Load Balancer credentials: %w", err) + } + + return outputResult(p, model, projectLabel, resp) + }, + } + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().String(displayNameFlag, "", "Credentials name") + cmd.Flags().String(usernameFlag, "", "Username") + cmd.Flags().String(passwordFlag, "", "Password") + + err := flags.MarkFlagsRequired(cmd, displayNameFlag, usernameFlag) + cobra.CheckErr(err) +} + +func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + + return &inputModel{ + GlobalFlagModel: globalFlags, + DisplayName: flags.FlagToStringPointer(p, cmd, displayNameFlag), + Username: flags.FlagToStringPointer(p, cmd, usernameFlag), + Password: flags.FlagToStringPointer(p, cmd, passwordFlag), + }, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *loadbalancer.APIClient) loadbalancer.ApiCreateCredentialsRequest { + req := apiClient.CreateCredentials(ctx, model.ProjectId) + req = req.XRequestID(uuid.NewString()) + + req = req.CreateCredentialsPayload(loadbalancer.CreateCredentialsPayload{ + DisplayName: model.DisplayName, + Username: model.Username, + Password: model.Password, + }) + return req +} + +func outputResult(p *print.Printer, model *inputModel, projectLabel string, resp *loadbalancer.CreateCredentialsResponse) error { + if resp.Credential == nil { + return fmt.Errorf("nil credentials response") + } + + switch model.OutputFormat { + case print.JSONOutputFormat: + details, err := json.MarshalIndent(resp, "", " ") + if err != nil { + return fmt.Errorf("marshal Load Balancer credentials: %w", err) + } + p.Outputln(string(details)) + + return nil + default: + p.Outputf("Added credentials for project %q. Credentials reference: %q\n", projectLabel, *resp.Credential.CredentialsRef) + return nil + } +} diff --git a/internal/cmd/load-balancer/observability-credentials/add/add_test.go b/internal/cmd/load-balancer/observability-credentials/add/add_test.go new file mode 100644 index 000000000..c8726b5b7 --- /dev/null +++ b/internal/cmd/load-balancer/observability-credentials/add/add_test.go @@ -0,0 +1,193 @@ +package add + +import ( + "context" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" +) + +var projectIdFlag = globalflags.ProjectIdFlag + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &loadbalancer.APIClient{} +var testProjectId = uuid.NewString() + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + projectIdFlag: testProjectId, + displayNameFlag: "name", + usernameFlag: "username", + passwordFlag: "pwd", + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Verbosity: globalflags.VerbosityDefault, + }, + DisplayName: utils.Ptr("name"), + Username: utils.Ptr("username"), + Password: utils.Ptr("pwd"), + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *loadbalancer.ApiCreateCredentialsRequest)) loadbalancer.ApiCreateCredentialsRequest { + request := testClient.CreateCredentials(testCtx, testProjectId) + request = request.CreateCredentialsPayload(loadbalancer.CreateCredentialsPayload{ + DisplayName: utils.Ptr("name"), + Username: utils.Ptr("username"), + Password: utils.Ptr("pwd"), + }) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "project id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, projectIdFlag) + }), + isValid: false, + }, + { + description: "project id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "" + }), + isValid: false, + }, + { + description: "project id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "display name missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, displayNameFlag) + }), + isValid: false, + }, + { + description: "username name missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, usernameFlag) + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + cmd := NewCmd(nil) + err := globalflags.Configure(cmd.Flags()) + if err != nil { + t.Fatalf("configure global flags: %v", err) + } + + for flag, value := range tt.flagValues { + err := cmd.Flags().Set(flag, value) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("setting flag --%s=%s: %v", flag, value, err) + } + } + + err = cmd.ValidateRequiredFlags() + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error validating flags: %v", err) + } + + model, err := parseInput(nil, cmd) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error parsing flags: %v", err) + } + + if !tt.isValid { + t.Fatalf("did not fail on invalid input") + } + diff := cmp.Diff(model, tt.expectedModel) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest loadbalancer.ApiCreateCredentialsRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, tt.model, testClient) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + cmpopts.IgnoreFields(loadbalancer.ApiCreateCredentialsRequest{}, "xRequestID"), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} diff --git a/internal/cmd/load-balancer/observability-credentials/observability-credentials.go b/internal/cmd/load-balancer/observability-credentials/observability-credentials.go new file mode 100644 index 000000000..a95f61ce7 --- /dev/null +++ b/internal/cmd/load-balancer/observability-credentials/observability-credentials.go @@ -0,0 +1,32 @@ +package credentials + +import ( + "github.com/stackitcloud/stackit-cli/internal/cmd/load-balancer/observability-credentials/add" + "github.com/stackitcloud/stackit-cli/internal/cmd/load-balancer/observability-credentials/delete" + "github.com/stackitcloud/stackit-cli/internal/cmd/load-balancer/observability-credentials/describe" + "github.com/stackitcloud/stackit-cli/internal/cmd/load-balancer/observability-credentials/list" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/spf13/cobra" +) + +func NewCmd(p *print.Printer) *cobra.Command { + cmd := &cobra.Command{ + Use: "observability-credentials", + Short: "Provides functionality for Load Balancer observability credentials", + Long: `Provides functionality for Load Balancer observability credentials. These commands can be used to store and update existing credentials, which are valid to be used for Load Balancer Observability. This means, e.g. when using Argus, that credentials first must be created for that Argus instance (by using "stackit argus credentials create") and then can be managed for a Load Balancer by using the commands in this group.`, + Args: args.NoArgs, + Run: utils.CmdHelp, + } + addSubcommands(cmd, p) + return cmd +} + +func addSubcommands(cmd *cobra.Command, p *print.Printer) { + cmd.AddCommand(add.NewCmd(p)) + cmd.AddCommand(delete.NewCmd(p)) + cmd.AddCommand(describe.NewCmd(p)) + cmd.AddCommand(list.NewCmd(p)) +} diff --git a/internal/pkg/print/print.go b/internal/pkg/print/print.go index 3115a8d26..38635e6d9 100644 --- a/internal/pkg/print/print.go +++ b/internal/pkg/print/print.go @@ -4,6 +4,7 @@ import ( "bufio" "errors" "fmt" + "syscall" "log/slog" "os" @@ -13,6 +14,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/stackitcloud/stackit-cli/internal/pkg/config" + "golang.org/x/term" ) type Level string @@ -151,6 +153,19 @@ func (p *Printer) PromptForEnter(prompt string) error { return errAborted } +// Prompts the user for a password. +// +// Returns the password that was given, otherwise returns error +func (p *Printer) PromptForPassword(prompt string) (string, error) { + p.Cmd.PrintErr(prompt) + defer p.Outputln("") + bytePassword, err := term.ReadPassword(int(syscall.Stdin)) + if err != nil { + return "", fmt.Errorf("read password: %w", err) + } + return string(bytePassword), nil +} + // Shows the content in the command's stdout using the "less" command // If output format is set to none, it does nothing func (p *Printer) PagerDisplay(content string) error { From 5d3bed20cd94c0aa627476ecf523fdbca4c5081f Mon Sep 17 00:00:00 2001 From: vicentepinto98 Date: Thu, 2 May 2024 15:49:09 +0100 Subject: [PATCH 2/9] Describe and update commands --- .../observability-credentials/add/add.go | 6 +- .../describe/describe.go | 110 +++++++++ .../describe/describe_test.go | 210 ++++++++++++++++ .../observability-credentials.go | 17 +- .../update/update.go | 152 ++++++++++++ .../update/update_test.go | 232 ++++++++++++++++++ .../pkg/services/load-balancer/utils/utils.go | 20 ++ .../load-balancer/utils/utils_test.go | 83 +++++++ 8 files changed, 818 insertions(+), 12 deletions(-) create mode 100644 internal/cmd/load-balancer/observability-credentials/describe/describe.go create mode 100644 internal/cmd/load-balancer/observability-credentials/describe/describe_test.go create mode 100644 internal/cmd/load-balancer/observability-credentials/update/update.go create mode 100644 internal/cmd/load-balancer/observability-credentials/update/update_test.go create mode 100644 internal/pkg/services/load-balancer/utils/utils.go create mode 100644 internal/pkg/services/load-balancer/utils/utils_test.go diff --git a/internal/cmd/load-balancer/observability-credentials/add/add.go b/internal/cmd/load-balancer/observability-credentials/add/add.go index a9de57450..bb0a10c7d 100644 --- a/internal/cmd/load-balancer/observability-credentials/add/add.go +++ b/internal/cmd/load-balancer/observability-credentials/add/add.go @@ -36,8 +36,8 @@ type inputModel struct { func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: "add", - Short: "Adds observability credentials to a Load Balancer", - Long: "Adds existing observability credentials (username and password) to a Load Balancer. The credentials can be for Argus or another monitoring tool.", + Short: "Adds observability credentials to Load Balancer", + Long: "Adds existing observability credentials (username and password) to Load Balancer. The credentials can be for Argus or another monitoring tool.", Args: args.NoArgs, Example: examples.Build( examples.NewExample( @@ -147,7 +147,7 @@ func outputResult(p *print.Printer, model *inputModel, projectLabel string, resp return nil default: - p.Outputf("Added credentials for project %q. Credentials reference: %q\n", projectLabel, *resp.Credential.CredentialsRef) + p.Outputf("Added load balancer observability credentials for project %q. Credentials reference: %q\n", projectLabel, *resp.Credential.CredentialsRef) return nil } } diff --git a/internal/cmd/load-balancer/observability-credentials/describe/describe.go b/internal/cmd/load-balancer/observability-credentials/describe/describe.go new file mode 100644 index 000000000..88861a0c9 --- /dev/null +++ b/internal/cmd/load-balancer/observability-credentials/describe/describe.go @@ -0,0 +1,110 @@ +package describe + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/load-balancer/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" +) + +const ( + credentialsRefArg = "CREDENTIALS_REF" //nolint:gosec // linter false positive +) + +type inputModel struct { + *globalflags.GlobalFlagModel + CredentialsRef string +} + +func NewCmd(p *print.Printer) *cobra.Command { + cmd := &cobra.Command{ + Use: fmt.Sprintf("describe %s", credentialsRefArg), + Short: "Shows details of observability credentials for load balancers", + Long: "Shows details of observability credentials for load balancers.", + Args: args.SingleArg(credentialsRefArg, nil), + Example: examples.Build( + examples.NewExample( + `Get details of credentials with reference "credentials-xxx"`, + "$ stackit load-balancer credentials describe credentials-xxx"), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(p, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(p) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, model, apiClient) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("describe Load Balancer observability credentials: %w", err) + } + + return outputResult(p, model.OutputFormat, resp) + }, + } + return cmd +} + +func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { + credentialsRef := inputArgs[0] + + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + + return &inputModel{ + GlobalFlagModel: globalFlags, + CredentialsRef: credentialsRef, + }, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *loadbalancer.APIClient) loadbalancer.ApiGetCredentialsRequest { + req := apiClient.GetCredentials(ctx, model.ProjectId, model.CredentialsRef) + return req +} + +func outputResult(p *print.Printer, outputFormat string, credentials *loadbalancer.GetCredentialsResponse) error { + switch outputFormat { + case print.PrettyOutputFormat: + table := tables.NewTable() + table.AddRow("REFERENCE", *credentials.Credential.CredentialsRef) + table.AddSeparator() + table.AddRow("DISPLAY NAME", *credentials.Credential.DisplayName) + table.AddSeparator() + table.AddRow("USERNAME", *credentials.Credential.Username) + table.AddSeparator() + err := table.Display(p) + if err != nil { + return fmt.Errorf("render table: %w", err) + } + + return nil + default: + details, err := json.MarshalIndent(credentials, "", " ") + if err != nil { + return fmt.Errorf("marshal Load Balancer observability credentials: %w", err) + } + p.Outputln(string(details)) + + return nil + } +} diff --git a/internal/cmd/load-balancer/observability-credentials/describe/describe_test.go b/internal/cmd/load-balancer/observability-credentials/describe/describe_test.go new file mode 100644 index 000000000..f6e24acd5 --- /dev/null +++ b/internal/cmd/load-balancer/observability-credentials/describe/describe_test.go @@ -0,0 +1,210 @@ +package describe + +import ( + "context" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" +) + +var projectIdFlag = globalflags.ProjectIdFlag + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &loadbalancer.APIClient{} +var testProjectId = uuid.NewString() +var testCredentialsRef = "credentials-test" + +func fixtureArgValues(mods ...func(argValues []string)) []string { + argValues := []string{ + testCredentialsRef, + } + for _, mod := range mods { + mod(argValues) + } + return argValues +} + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + projectIdFlag: testProjectId, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Verbosity: globalflags.VerbosityDefault, + }, + CredentialsRef: testCredentialsRef, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *loadbalancer.ApiGetCredentialsRequest)) loadbalancer.ApiGetCredentialsRequest { + request := testClient.GetCredentials(testCtx, testProjectId, testCredentialsRef) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + argValues: []string{}, + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "no arg values", + argValues: []string{}, + flagValues: fixtureFlagValues(), + isValid: false, + }, + { + description: "no flag values", + argValues: fixtureArgValues(), + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "project id missing", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, projectIdFlag) + }), + isValid: false, + }, + { + description: "project id invalid 1", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "" + }), + isValid: false, + }, + { + description: "project id invalid 2", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "credentials ref invalid 1", + argValues: []string{""}, + flagValues: fixtureFlagValues(), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + cmd := NewCmd(nil) + err := globalflags.Configure(cmd.Flags()) + if err != nil { + t.Fatalf("configure global flags: %v", err) + } + + for flag, value := range tt.flagValues { + err := cmd.Flags().Set(flag, value) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("setting flag --%s=%s: %v", flag, value, err) + } + } + + err = cmd.ValidateArgs(tt.argValues) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error validating args: %v", err) + } + + err = cmd.ValidateRequiredFlags() + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error validating flags: %v", err) + } + + model, err := parseInput(nil, cmd, tt.argValues) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error parsing input: %v", err) + } + + if !tt.isValid { + t.Fatalf("did not fail on invalid input") + } + diff := cmp.Diff(model, tt.expectedModel) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest loadbalancer.ApiGetCredentialsRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, tt.model, testClient) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} diff --git a/internal/cmd/load-balancer/observability-credentials/observability-credentials.go b/internal/cmd/load-balancer/observability-credentials/observability-credentials.go index a95f61ce7..ed5adb3b8 100644 --- a/internal/cmd/load-balancer/observability-credentials/observability-credentials.go +++ b/internal/cmd/load-balancer/observability-credentials/observability-credentials.go @@ -2,9 +2,8 @@ package credentials import ( "github.com/stackitcloud/stackit-cli/internal/cmd/load-balancer/observability-credentials/add" - "github.com/stackitcloud/stackit-cli/internal/cmd/load-balancer/observability-credentials/delete" "github.com/stackitcloud/stackit-cli/internal/cmd/load-balancer/observability-credentials/describe" - "github.com/stackitcloud/stackit-cli/internal/cmd/load-balancer/observability-credentials/list" + "github.com/stackitcloud/stackit-cli/internal/cmd/load-balancer/observability-credentials/update" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" @@ -14,11 +13,12 @@ import ( func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ - Use: "observability-credentials", - Short: "Provides functionality for Load Balancer observability credentials", - Long: `Provides functionality for Load Balancer observability credentials. These commands can be used to store and update existing credentials, which are valid to be used for Load Balancer Observability. This means, e.g. when using Argus, that credentials first must be created for that Argus instance (by using "stackit argus credentials create") and then can be managed for a Load Balancer by using the commands in this group.`, - Args: args.NoArgs, - Run: utils.CmdHelp, + Use: "observability-credentials", + Short: "Provides functionality for Load Balancer observability credentials", + Long: `Provides functionality for Load Balancer observability credentials. These commands can be used to store and update existing credentials, which are valid to be used for Load Balancer Observability. This means, e.g. when using Argus, that credentials first must be created for that Argus instance (by using "stackit argus credentials create") and then can be managed for a Load Balancer by using the commands in this group.`, + Args: args.NoArgs, + Aliases: []string{"credentials"}, + Run: utils.CmdHelp, } addSubcommands(cmd, p) return cmd @@ -26,7 +26,6 @@ func NewCmd(p *print.Printer) *cobra.Command { func addSubcommands(cmd *cobra.Command, p *print.Printer) { cmd.AddCommand(add.NewCmd(p)) - cmd.AddCommand(delete.NewCmd(p)) cmd.AddCommand(describe.NewCmd(p)) - cmd.AddCommand(list.NewCmd(p)) + cmd.AddCommand(update.NewCmd(p)) } diff --git a/internal/cmd/load-balancer/observability-credentials/update/update.go b/internal/cmd/load-balancer/observability-credentials/update/update.go new file mode 100644 index 000000000..0ed7b808a --- /dev/null +++ b/internal/cmd/load-balancer/observability-credentials/update/update.go @@ -0,0 +1,152 @@ +package update + +import ( + "context" + "fmt" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/load-balancer/client" + loadBalancerUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/load-balancer/utils" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" +) + +const ( + displayNameFlag = "display-name" + usernameFlag = "username" + passwordFlag = "password" + + credentialsRefArg = "CREDENTIALS_REF" //nolint:gosec // linter false positive +) + +type inputModel struct { + *globalflags.GlobalFlagModel + CredentialsRef string + DisplayName *string + Username *string + Password *string +} + +func NewCmd(p *print.Printer) *cobra.Command { + cmd := &cobra.Command{ + Use: "update", + Short: "Updates observability credentials for Load Balancer", + Long: "Updates existing observability credentials (username and password) for Load Balancer. The credentials can be for Argus or another monitoring tool.", + Args: args.SingleArg(credentialsRefArg, nil), + Example: examples.Build( + examples.NewExample( + `Update the password of credentials of Load Balancer with credentials reference "credentials-xxx". The password is entered using the terminal`, + "$ stackit load-balancer observability-credentials update credentials-xxx --password "), + examples.NewExample( + `Update the password of credentials of Load Balancer with credentials reference "credentials-xxx", by providing it in the flag`, + "$ stackit load-balancer observability-credentials update credentials-xxx --password new-pwd"), + examples.NewExample( + `Update the display name of credentials of Load Balancer with credentials reference "credentials-xxx".`, + "$ stackit load-balancer observability-credentials update credentials-xxx --display-name yyy"), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(p, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(p) + if err != nil { + return err + } + + projectLabel, err := projectname.GetProjectName(ctx, p, cmd) + if err != nil { + p.Debug(print.ErrorLevel, "get project name: %v", err) + projectLabel = model.ProjectId + } + + credentialsLabel, err := loadBalancerUtils.GetCredentialsDisplayName(ctx, apiClient, model.ProjectId, model.CredentialsRef) + if err != nil { + p.Debug(print.ErrorLevel, "get credentials display name: %v", err) + credentialsLabel = model.CredentialsRef + } + + if !model.AssumeYes { + prompt := fmt.Sprintf("Are you sure you want to update observability credentials %q for your Load Balancers on project %q?", credentialsLabel, projectLabel) + err = p.PromptForConfirmation(prompt) + if err != nil { + return err + } + } + + // Prompt for password if not passed in as a flag + if model.Password != nil && *model.Password == "" { + pwd, err := p.PromptForPassword("Enter new password: ") + if err != nil { + return fmt.Errorf("prompt for password: %w", err) + } + model.Password = utils.Ptr(pwd) + } + + // Call API + req := buildRequest(ctx, model, apiClient) + _, err = req.Execute() + if err != nil { + return fmt.Errorf("update Load Balancer observability credentials: %w", err) + } + + p.Info("Updated observability credentials %q for Load Balancer\n", credentialsLabel) + return nil + }, + } + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().String(displayNameFlag, "", "Credentials name") + cmd.Flags().String(usernameFlag, "", "Username") + cmd.Flags().String(passwordFlag, "", "Password") +} + +func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { + credentialsRef := inputArgs[0] + + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + + displayName := flags.FlagToStringPointer(p, cmd, displayNameFlag) + username := flags.FlagToStringPointer(p, cmd, usernameFlag) + password := flags.FlagToStringPointer(p, cmd, passwordFlag) + + if displayName == nil && username == nil && password == nil { + return nil, &errors.EmptyUpdateError{} + } + + return &inputModel{ + GlobalFlagModel: globalFlags, + CredentialsRef: credentialsRef, + DisplayName: displayName, + Username: username, + Password: password, + }, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *loadbalancer.APIClient) loadbalancer.ApiUpdateCredentialsRequest { + req := apiClient.UpdateCredentials(ctx, model.ProjectId, model.CredentialsRef) + + req = req.UpdateCredentialsPayload(loadbalancer.UpdateCredentialsPayload{ + DisplayName: model.DisplayName, + Username: model.Username, + Password: model.Password, + }) + return req +} diff --git a/internal/cmd/load-balancer/observability-credentials/update/update_test.go b/internal/cmd/load-balancer/observability-credentials/update/update_test.go new file mode 100644 index 000000000..805100fb3 --- /dev/null +++ b/internal/cmd/load-balancer/observability-credentials/update/update_test.go @@ -0,0 +1,232 @@ +package update + +import ( + "context" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" +) + +var projectIdFlag = globalflags.ProjectIdFlag + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &loadbalancer.APIClient{} +var testProjectId = uuid.NewString() +var testCredentialsRef = "credentials-test" + +func fixtureArgValues(mods ...func(argValues []string)) []string { + argValues := []string{ + testCredentialsRef, + } + for _, mod := range mods { + mod(argValues) + } + return argValues +} + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + projectIdFlag: testProjectId, + displayNameFlag: "name", + usernameFlag: "username", + passwordFlag: "pwd", + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Verbosity: globalflags.VerbosityDefault, + }, + DisplayName: utils.Ptr("name"), + Username: utils.Ptr("username"), + Password: utils.Ptr("pwd"), + CredentialsRef: testCredentialsRef, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *loadbalancer.ApiUpdateCredentialsRequest)) loadbalancer.ApiUpdateCredentialsRequest { + request := testClient.UpdateCredentials(testCtx, testProjectId, testCredentialsRef) + request = request.UpdateCredentialsPayload(loadbalancer.UpdateCredentialsPayload{ + DisplayName: utils.Ptr("name"), + Username: utils.Ptr("username"), + Password: utils.Ptr("pwd"), + }) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + argValues: []string{}, + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "no arg values", + argValues: []string{}, + flagValues: fixtureFlagValues(), + isValid: false, + }, + { + description: "no flag values", + argValues: fixtureArgValues(), + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "project id missing", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, projectIdFlag) + }), + isValid: false, + }, + { + description: "project id invalid 1", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "" + }), + isValid: false, + }, + { + description: "project id invalid 2", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "credentials ref invalid 1", + argValues: []string{""}, + flagValues: fixtureFlagValues(), + isValid: false, + }, + { + description: "no flags - nothing to be updated", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, displayNameFlag) + delete(flagValues, usernameFlag) + delete(flagValues, passwordFlag) + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + cmd := NewCmd(nil) + err := globalflags.Configure(cmd.Flags()) + if err != nil { + t.Fatalf("configure global flags: %v", err) + } + + for flag, value := range tt.flagValues { + err := cmd.Flags().Set(flag, value) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("setting flag --%s=%s: %v", flag, value, err) + } + } + + err = cmd.ValidateArgs(tt.argValues) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error validating args: %v", err) + } + + err = cmd.ValidateRequiredFlags() + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error validating flags: %v", err) + } + + model, err := parseInput(nil, cmd, tt.argValues) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error parsing input: %v", err) + } + + if !tt.isValid { + t.Fatalf("did not fail on invalid input") + } + diff := cmp.Diff(model, tt.expectedModel) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest loadbalancer.ApiUpdateCredentialsRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, tt.model, testClient) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} diff --git a/internal/pkg/services/load-balancer/utils/utils.go b/internal/pkg/services/load-balancer/utils/utils.go new file mode 100644 index 000000000..df0dc654f --- /dev/null +++ b/internal/pkg/services/load-balancer/utils/utils.go @@ -0,0 +1,20 @@ +package utils + +import ( + "context" + "fmt" + + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" +) + +type LoadBalancerClient interface { + GetCredentialsExecute(ctx context.Context, projectId, credentialsRef string) (*loadbalancer.GetCredentialsResponse, error) +} + +func GetCredentialsDisplayName(ctx context.Context, apiClient LoadBalancerClient, projectId, credentialsRef string) (string, error) { + resp, err := apiClient.GetCredentialsExecute(ctx, projectId, credentialsRef) + if err != nil { + return "", fmt.Errorf("get Load Balancer credentials: %w", err) + } + return *resp.Credential.DisplayName, nil +} diff --git a/internal/pkg/services/load-balancer/utils/utils_test.go b/internal/pkg/services/load-balancer/utils/utils_test.go new file mode 100644 index 000000000..b0b6574f1 --- /dev/null +++ b/internal/pkg/services/load-balancer/utils/utils_test.go @@ -0,0 +1,83 @@ +package utils + +import ( + "context" + "fmt" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" +) + +var ( + testProjectId = uuid.NewString() + testCredentialsRef = "credentials-test" +) + +const ( + testCredentialsDisplayName = "name" +) + +type loadBalancerClientMocked struct { + getCredentialsFails bool + getCredentialsResp *loadbalancer.GetCredentialsResponse +} + +func (m *loadBalancerClientMocked) GetCredentialsExecute(_ context.Context, _, _ string) (*loadbalancer.GetCredentialsResponse, error) { + if m.getCredentialsFails { + return nil, fmt.Errorf("could not get credentials") + } + return m.getCredentialsResp, nil +} + +func TestGetCredentialsDisplayName(t *testing.T) { + tests := []struct { + description string + getCredentialsFails bool + getCredentialsResp *loadbalancer.GetCredentialsResponse + isValid bool + expectedOutput string + }{ + { + description: "base", + getCredentialsResp: &loadbalancer.GetCredentialsResponse{ + Credential: &loadbalancer.CredentialsResponse{ + DisplayName: utils.Ptr(testCredentialsDisplayName), + }, + }, + isValid: true, + expectedOutput: testCredentialsDisplayName, + }, + { + description: "get credentials fails", + getCredentialsFails: true, + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + client := &loadBalancerClientMocked{ + getCredentialsFails: tt.getCredentialsFails, + getCredentialsResp: tt.getCredentialsResp, + } + + output, err := GetCredentialsDisplayName(context.Background(), client, testProjectId, testCredentialsRef) + + if tt.isValid && err != nil { + t.Errorf("failed on valid input") + } + if !tt.isValid && err == nil { + t.Errorf("did not fail on invalid input") + } + if !tt.isValid { + return + } + if output != tt.expectedOutput { + t.Errorf("expected output to be %s, got %s", tt.expectedOutput, output) + } + }) + } +} From 594ee69310087dc4ac53f08490a8e4f5ff8fe3f0 Mon Sep 17 00:00:00 2001 From: vicentepinto98 Date: Fri, 3 May 2024 11:12:05 +0100 Subject: [PATCH 3/9] Improve update command --- .../delete/delete_test.go | 3 +- .../describe/describe_test.go | 3 +- .../update/update.go | 42 +++++--- .../update/update_test.go | 96 +++++++++++++++++-- 4 files changed, 122 insertions(+), 22 deletions(-) diff --git a/internal/cmd/load-balancer/observability-credentials/delete/delete_test.go b/internal/cmd/load-balancer/observability-credentials/delete/delete_test.go index e257b360e..a0b1f5632 100644 --- a/internal/cmd/load-balancer/observability-credentials/delete/delete_test.go +++ b/internal/cmd/load-balancer/observability-credentials/delete/delete_test.go @@ -19,7 +19,8 @@ type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") var testClient = &loadbalancer.APIClient{} var testProjectId = uuid.NewString() -var testCredentialsRef = "credentials-xxx" + +const testCredentialsRef = "credentials-xxx" func fixtureArgValues(mods ...func(argValues []string)) []string { argValues := []string{ diff --git a/internal/cmd/load-balancer/observability-credentials/describe/describe_test.go b/internal/cmd/load-balancer/observability-credentials/describe/describe_test.go index f6e24acd5..e64390d53 100644 --- a/internal/cmd/load-balancer/observability-credentials/describe/describe_test.go +++ b/internal/cmd/load-balancer/observability-credentials/describe/describe_test.go @@ -19,7 +19,8 @@ type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") var testClient = &loadbalancer.APIClient{} var testProjectId = uuid.NewString() -var testCredentialsRef = "credentials-test" + +const testCredentialsRef = "credentials-test" func fixtureArgValues(mods ...func(argValues []string)) []string { argValues := []string{ diff --git a/internal/cmd/load-balancer/observability-credentials/update/update.go b/internal/cmd/load-balancer/observability-credentials/update/update.go index 0ed7b808a..a1ca41f4e 100644 --- a/internal/cmd/load-balancer/observability-credentials/update/update.go +++ b/internal/cmd/load-balancer/observability-credentials/update/update.go @@ -86,7 +86,7 @@ func NewCmd(p *print.Printer) *cobra.Command { } // Prompt for password if not passed in as a flag - if model.Password != nil && *model.Password == "" { + if model.Password == nil { pwd, err := p.PromptForPassword("Enter new password: ") if err != nil { return fmt.Errorf("prompt for password: %w", err) @@ -95,7 +95,11 @@ func NewCmd(p *print.Printer) *cobra.Command { } // Call API - req := buildRequest(ctx, model, apiClient) + req, err := buildRequest(ctx, model, apiClient) + if err != nil { + return err + } + _, err = req.Execute() if err != nil { return fmt.Errorf("update Load Balancer observability credentials: %w", err) @@ -127,10 +131,6 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu username := flags.FlagToStringPointer(p, cmd, usernameFlag) password := flags.FlagToStringPointer(p, cmd, passwordFlag) - if displayName == nil && username == nil && password == nil { - return nil, &errors.EmptyUpdateError{} - } - return &inputModel{ GlobalFlagModel: globalFlags, CredentialsRef: credentialsRef, @@ -140,13 +140,31 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu }, nil } -func buildRequest(ctx context.Context, model *inputModel, apiClient *loadbalancer.APIClient) loadbalancer.ApiUpdateCredentialsRequest { +type loadBalancerClient interface { + UpdateCredentials(ctx context.Context, instanceId, projectId string) loadbalancer.ApiUpdateCredentialsRequest + GetCredentialsExecute(ctx context.Context, instanceId, projectId string) (*loadbalancer.GetCredentialsResponse, error) +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient loadBalancerClient) (loadbalancer.ApiUpdateCredentialsRequest, error) { req := apiClient.UpdateCredentials(ctx, model.ProjectId, model.CredentialsRef) - req = req.UpdateCredentialsPayload(loadbalancer.UpdateCredentialsPayload{ - DisplayName: model.DisplayName, - Username: model.Username, + currentCredentials, err := apiClient.GetCredentialsExecute(ctx, model.ProjectId, model.CredentialsRef) + if err != nil { + return req, fmt.Errorf("get Load Balancer observability credentials: %w", err) + } + + payload := loadbalancer.UpdateCredentialsPayload{ + DisplayName: currentCredentials.Credential.DisplayName, + Username: currentCredentials.Credential.Username, Password: model.Password, - }) - return req + } + + if model.DisplayName != nil { + payload.DisplayName = model.DisplayName + } + if model.Username != nil { + payload.Username = model.Username + } + req = req.UpdateCredentialsPayload(payload) + return req, nil } diff --git a/internal/cmd/load-balancer/observability-credentials/update/update_test.go b/internal/cmd/load-balancer/observability-credentials/update/update_test.go index 805100fb3..9ba845c3c 100644 --- a/internal/cmd/load-balancer/observability-credentials/update/update_test.go +++ b/internal/cmd/load-balancer/observability-credentials/update/update_test.go @@ -2,6 +2,7 @@ package update import ( "context" + "fmt" "testing" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" @@ -19,8 +20,26 @@ type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") var testClient = &loadbalancer.APIClient{} + +type loadBalancerClientMocked struct { + getCredentialsError bool + getCredentialsResponse *loadbalancer.GetCredentialsResponse +} + +func (c *loadBalancerClientMocked) UpdateCredentials(ctx context.Context, projectId, credentialsRef string) loadbalancer.ApiUpdateCredentialsRequest { + return testClient.UpdateCredentials(ctx, projectId, credentialsRef) +} + +func (c *loadBalancerClientMocked) GetCredentialsExecute(_ context.Context, _, _ string) (*loadbalancer.GetCredentialsResponse, error) { + if c.getCredentialsError { + return nil, fmt.Errorf("get credentials failed") + } + return c.getCredentialsResponse, nil +} + var testProjectId = uuid.NewString() -var testCredentialsRef = "credentials-test" + +const testCredentialsRef = "credentials-test" func fixtureArgValues(mods ...func(argValues []string)) []string { argValues := []string{ @@ -75,6 +94,19 @@ func fixtureRequest(mods ...func(request *loadbalancer.ApiUpdateCredentialsReque return request } +func fixtureGetCredentialsResponse(mods ...func(response *loadbalancer.GetCredentialsResponse)) *loadbalancer.GetCredentialsResponse { + response := &loadbalancer.GetCredentialsResponse{ + Credential: &loadbalancer.CredentialsResponse{ + DisplayName: utils.Ptr("name"), + Username: utils.Ptr("username"), + }, + } + for _, mod := range mods { + mod(response) + } + return response +} + func TestParseInput(t *testing.T) { tests := []struct { description string @@ -205,20 +237,68 @@ func TestParseInput(t *testing.T) { func TestBuildRequest(t *testing.T) { tests := []struct { - description string - model *inputModel - expectedRequest loadbalancer.ApiUpdateCredentialsRequest + description string + model *inputModel + expectedRequest loadbalancer.ApiUpdateCredentialsRequest + getCredentialsFails bool + getCredentialsResponse *loadbalancer.GetCredentialsResponse + isValid bool }{ { - description: "base", - model: fixtureInputModel(), - expectedRequest: fixtureRequest(), + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + getCredentialsResponse: fixtureGetCredentialsResponse(), + isValid: true, + }, + { + description: "no display name", + model: fixtureInputModel( + func(model *inputModel) { + model.DisplayName = nil + }, + ), + expectedRequest: fixtureRequest(), + getCredentialsResponse: fixtureGetCredentialsResponse(), + isValid: true, + }, + { + description: "no username name", + model: fixtureInputModel( + func(model *inputModel) { + model.Username = nil + }, + ), + expectedRequest: fixtureRequest(), + getCredentialsResponse: fixtureGetCredentialsResponse(), + isValid: true, + }, + { + description: "get credentials fails", + model: fixtureInputModel(), + getCredentialsFails: true, + isValid: false, }, } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - request := buildRequest(testCtx, tt.model, testClient) + client := &loadBalancerClientMocked{ + getCredentialsError: tt.getCredentialsFails, + getCredentialsResponse: tt.getCredentialsResponse, + } + request, err := buildRequest(testCtx, tt.model, client) + + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error building request: %v", err) + } + + if !tt.isValid { + t.Fatal("expected error but none thrown") + } diff := cmp.Diff(request, tt.expectedRequest, cmp.AllowUnexported(tt.expectedRequest), From 451f6cde782cd1bef67b10d05f0a7d2e68414125 Mon Sep 17 00:00:00 2001 From: vicentepinto98 Date: Fri, 3 May 2024 11:24:51 +0100 Subject: [PATCH 4/9] List command --- .../observability-credentials/list/list.go | 150 ++++++++++++++ .../list/list_test.go | 185 ++++++++++++++++++ .../observability-credentials.go | 4 + .../update/update_test.go | 10 - 4 files changed, 339 insertions(+), 10 deletions(-) create mode 100644 internal/cmd/load-balancer/observability-credentials/list/list.go create mode 100644 internal/cmd/load-balancer/observability-credentials/list/list_test.go diff --git a/internal/cmd/load-balancer/observability-credentials/list/list.go b/internal/cmd/load-balancer/observability-credentials/list/list.go new file mode 100644 index 000000000..ac2074514 --- /dev/null +++ b/internal/cmd/load-balancer/observability-credentials/list/list.go @@ -0,0 +1,150 @@ +package list + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/load-balancer/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" +) + +const ( + instanceIdFlag = "instance-id" + limitFlag = "limit" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + Limit *int64 +} + +func NewCmd(p *print.Printer) *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "Lists all observability credentials for Load Balancer", + Long: "Lists all observability credentials for Load Balancer.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `List all observability credentials for Load Balancer`, + "$ stackit load-balancer observability-credentials list"), + examples.NewExample( + `List all observability credentials for Load Balancer in JSON format`, + "$ stackit load-balancer observability-credentials list --output-format json"), + examples.NewExample( + `List up to 10 observability credentials for Load Balancer`, + "$ stackit load-balancer observability-credentials list --limit 10"), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(p, cmd) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(p) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, model, apiClient) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("list Load Balancer observability credentials: %w", err) + } + credentialsPtr := resp.Credentials + if credentialsPtr == nil || (credentialsPtr != nil && len(*credentialsPtr) == 0) { + p.Info("No observability credentials found for Load Balancer\n") + return nil + } + + credentials := *credentialsPtr + + // Truncate output + if model.Limit != nil && len(credentials) > int(*model.Limit) { + credentials = credentials[:*model.Limit] + } + return outputResult(p, model.OutputFormat, credentials) + }, + } + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list") +} + +func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + + limit := flags.FlagToInt64Pointer(p, cmd, limitFlag) + if limit != nil && *limit < 1 { + return nil, &errors.FlagValidationError{ + Flag: limitFlag, + Details: "must be greater than 0", + } + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + Limit: limit, + } + + if p.IsVerbosityDebug() { + modelStr, err := print.BuildDebugStrFromInputModel(model) + if err != nil { + p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) + } else { + p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) + } + } + + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *loadbalancer.APIClient) loadbalancer.ApiListCredentialsRequest { + req := apiClient.ListCredentials(ctx, model.ProjectId) + return req +} + +func outputResult(p *print.Printer, outputFormat string, credentials []loadbalancer.CredentialsResponse) error { + switch outputFormat { + case print.JSONOutputFormat: + details, err := json.MarshalIndent(credentials, "", " ") + if err != nil { + return fmt.Errorf("marshal Load Balancer observability credentials list: %w", err) + } + p.Outputln(string(details)) + + return nil + default: + table := tables.NewTable() + table.SetHeader("REFERENCE", "DISPLAY NAME", "USERNAME") + for i := range credentials { + c := credentials[i] + table.AddRow(*c.CredentialsRef, *c.DisplayName, *c.Username) + } + err := table.Display(p) + if err != nil { + return fmt.Errorf("render table: %w", err) + } + + return nil + } +} diff --git a/internal/cmd/load-balancer/observability-credentials/list/list_test.go b/internal/cmd/load-balancer/observability-credentials/list/list_test.go new file mode 100644 index 000000000..9c564117e --- /dev/null +++ b/internal/cmd/load-balancer/observability-credentials/list/list_test.go @@ -0,0 +1,185 @@ +package list + +import ( + "context" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" +) + +var projectIdFlag = globalflags.ProjectIdFlag + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &loadbalancer.APIClient{} +var testProjectId = uuid.NewString() + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + projectIdFlag: testProjectId, + limitFlag: "10", + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Verbosity: globalflags.VerbosityDefault, + }, + Limit: utils.Ptr(int64(10)), + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *loadbalancer.ApiListCredentialsRequest)) loadbalancer.ApiListCredentialsRequest { + request := testClient.ListCredentials(testCtx, testProjectId) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "project id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, projectIdFlag) + }), + isValid: false, + }, + { + description: "project id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "" + }), + isValid: false, + }, + { + description: "project id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "limit invalid", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[limitFlag] = "invalid" + }), + isValid: false, + }, + { + description: "limit invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[limitFlag] = "0" + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + p := print.NewPrinter() + cmd := NewCmd(p) + err := globalflags.Configure(cmd.Flags()) + if err != nil { + t.Fatalf("configure global flags: %v", err) + } + + for flag, value := range tt.flagValues { + err := cmd.Flags().Set(flag, value) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("setting flag --%s=%s: %v", flag, value, err) + } + } + + err = cmd.ValidateRequiredFlags() + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error validating flags: %v", err) + } + + model, err := parseInput(p, cmd) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error parsing flags: %v", err) + } + + if !tt.isValid { + t.Fatalf("did not fail on invalid input") + } + diff := cmp.Diff(model, tt.expectedModel) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest loadbalancer.ApiListCredentialsRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, tt.model, testClient) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} diff --git a/internal/cmd/load-balancer/observability-credentials/observability-credentials.go b/internal/cmd/load-balancer/observability-credentials/observability-credentials.go index ed5adb3b8..d03aa79d9 100644 --- a/internal/cmd/load-balancer/observability-credentials/observability-credentials.go +++ b/internal/cmd/load-balancer/observability-credentials/observability-credentials.go @@ -2,7 +2,9 @@ package credentials import ( "github.com/stackitcloud/stackit-cli/internal/cmd/load-balancer/observability-credentials/add" + "github.com/stackitcloud/stackit-cli/internal/cmd/load-balancer/observability-credentials/delete" "github.com/stackitcloud/stackit-cli/internal/cmd/load-balancer/observability-credentials/describe" + "github.com/stackitcloud/stackit-cli/internal/cmd/load-balancer/observability-credentials/list" "github.com/stackitcloud/stackit-cli/internal/cmd/load-balancer/observability-credentials/update" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/print" @@ -27,5 +29,7 @@ func NewCmd(p *print.Printer) *cobra.Command { func addSubcommands(cmd *cobra.Command, p *print.Printer) { cmd.AddCommand(add.NewCmd(p)) cmd.AddCommand(describe.NewCmd(p)) + cmd.AddCommand(delete.NewCmd(p)) cmd.AddCommand(update.NewCmd(p)) + cmd.AddCommand(list.NewCmd(p)) } diff --git a/internal/cmd/load-balancer/observability-credentials/update/update_test.go b/internal/cmd/load-balancer/observability-credentials/update/update_test.go index 9ba845c3c..e6eec24c9 100644 --- a/internal/cmd/load-balancer/observability-credentials/update/update_test.go +++ b/internal/cmd/load-balancer/observability-credentials/update/update_test.go @@ -170,16 +170,6 @@ func TestParseInput(t *testing.T) { flagValues: fixtureFlagValues(), isValid: false, }, - { - description: "no flags - nothing to be updated", - argValues: fixtureArgValues(), - flagValues: fixtureFlagValues(func(flagValues map[string]string) { - delete(flagValues, displayNameFlag) - delete(flagValues, usernameFlag) - delete(flagValues, passwordFlag) - }), - isValid: false, - }, } for _, tt := range tests { From 9863c76feee115c50187ec93592387034cd4a855 Mon Sep 17 00:00:00 2001 From: vicentepinto98 Date: Fri, 3 May 2024 11:25:11 +0100 Subject: [PATCH 5/9] Generate docs --- docs/stackit_load-balancer.md | 2 + docs/stackit_load-balancer_delete.md | 39 +++++++++++++++ ...load-balancer_observability-credentials.md | 37 ++++++++++++++ ...-balancer_observability-credentials_add.md | 45 +++++++++++++++++ ...lancer_observability-credentials_delete.md | 39 +++++++++++++++ ...ncer_observability-credentials_describe.md | 39 +++++++++++++++ ...balancer_observability-credentials_list.md | 46 ++++++++++++++++++ ...lancer_observability-credentials_update.md | 48 +++++++++++++++++++ 8 files changed, 295 insertions(+) create mode 100644 docs/stackit_load-balancer_delete.md create mode 100644 docs/stackit_load-balancer_observability-credentials.md create mode 100644 docs/stackit_load-balancer_observability-credentials_add.md create mode 100644 docs/stackit_load-balancer_observability-credentials_delete.md create mode 100644 docs/stackit_load-balancer_observability-credentials_describe.md create mode 100644 docs/stackit_load-balancer_observability-credentials_list.md create mode 100644 docs/stackit_load-balancer_observability-credentials_update.md diff --git a/docs/stackit_load-balancer.md b/docs/stackit_load-balancer.md index c9727e1b6..4d8a0fd2c 100644 --- a/docs/stackit_load-balancer.md +++ b/docs/stackit_load-balancer.md @@ -30,9 +30,11 @@ stackit load-balancer [flags] * [stackit](./stackit.md) - Manage STACKIT resources using the command line * [stackit load-balancer create](./stackit_load-balancer_create.md) - Creates a Load Balancer +* [stackit load-balancer delete](./stackit_load-balancer_delete.md) - Deletes a Load Balancer * [stackit load-balancer describe](./stackit_load-balancer_describe.md) - Shows details of a Load Balancer * [stackit load-balancer generate-payload](./stackit_load-balancer_generate-payload.md) - Generates a payload to create/update a Load Balancer * [stackit load-balancer list](./stackit_load-balancer_list.md) - Lists all Load Balancers +* [stackit load-balancer observability-credentials](./stackit_load-balancer_observability-credentials.md) - Provides functionality for Load Balancer observability credentials * [stackit load-balancer quota](./stackit_load-balancer_quota.md) - Shows the configured Load Balancer quota * [stackit load-balancer update](./stackit_load-balancer_update.md) - Updates a Load Balancer diff --git a/docs/stackit_load-balancer_delete.md b/docs/stackit_load-balancer_delete.md new file mode 100644 index 000000000..3dd43b2ae --- /dev/null +++ b/docs/stackit_load-balancer_delete.md @@ -0,0 +1,39 @@ +## stackit load-balancer delete + +Deletes a Load Balancer + +### Synopsis + +Deletes a Load Balancer. + +``` +stackit load-balancer delete LOAD_BALANCER_NAME [flags] +``` + +### Examples + +``` + Deletes a load balancer with name "my-load-balancer" + $ stackit load-balancer delete my-load-balancer +``` + +### Options + +``` + -h, --help Help for "stackit load-balancer delete" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none"] + -p, --project-id string Project ID + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit load-balancer](./stackit_load-balancer.md) - Provides functionality for Load Balancer + diff --git a/docs/stackit_load-balancer_observability-credentials.md b/docs/stackit_load-balancer_observability-credentials.md new file mode 100644 index 000000000..4a1bc17f1 --- /dev/null +++ b/docs/stackit_load-balancer_observability-credentials.md @@ -0,0 +1,37 @@ +## stackit load-balancer observability-credentials + +Provides functionality for Load Balancer observability credentials + +### Synopsis + +Provides functionality for Load Balancer observability credentials. These commands can be used to store and update existing credentials, which are valid to be used for Load Balancer Observability. This means, e.g. when using Argus, that credentials first must be created for that Argus instance (by using "stackit argus credentials create") and then can be managed for a Load Balancer by using the commands in this group. + +``` +stackit load-balancer observability-credentials [flags] +``` + +### Options + +``` + -h, --help Help for "stackit load-balancer observability-credentials" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none"] + -p, --project-id string Project ID + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit load-balancer](./stackit_load-balancer.md) - Provides functionality for Load Balancer +* [stackit load-balancer observability-credentials add](./stackit_load-balancer_observability-credentials_add.md) - Adds observability credentials to Load Balancer +* [stackit load-balancer observability-credentials delete](./stackit_load-balancer_observability-credentials_delete.md) - Deletes observability credentials for Load Balancer +* [stackit load-balancer observability-credentials describe](./stackit_load-balancer_observability-credentials_describe.md) - Shows details of observability credentials for load balancers +* [stackit load-balancer observability-credentials list](./stackit_load-balancer_observability-credentials_list.md) - Lists all observability credentials for Load Balancer +* [stackit load-balancer observability-credentials update](./stackit_load-balancer_observability-credentials_update.md) - Updates observability credentials for Load Balancer + diff --git a/docs/stackit_load-balancer_observability-credentials_add.md b/docs/stackit_load-balancer_observability-credentials_add.md new file mode 100644 index 000000000..92d8a839e --- /dev/null +++ b/docs/stackit_load-balancer_observability-credentials_add.md @@ -0,0 +1,45 @@ +## stackit load-balancer observability-credentials add + +Adds observability credentials to Load Balancer + +### Synopsis + +Adds existing observability credentials (username and password) to Load Balancer. The credentials can be for Argus or another monitoring tool. + +``` +stackit load-balancer observability-credentials add [flags] +``` + +### Examples + +``` + Add credentials to a load balancer with username "xxx" and display name "yyy". The password is entered using the terminal + $ stackit load-balancer observability-credentials add --username xxx --display-name yyy + + Add credentials to a load balancer with username "xxx" and display name "yyy", providing the password as flag + $ stackit load-balancer observability-credentials add --username xxx --password pwd --display-name yyy +``` + +### Options + +``` + --display-name string Credentials name + -h, --help Help for "stackit load-balancer observability-credentials add" + --password string Password + --username string Username +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none"] + -p, --project-id string Project ID + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit load-balancer observability-credentials](./stackit_load-balancer_observability-credentials.md) - Provides functionality for Load Balancer observability credentials + diff --git a/docs/stackit_load-balancer_observability-credentials_delete.md b/docs/stackit_load-balancer_observability-credentials_delete.md new file mode 100644 index 000000000..9440b8988 --- /dev/null +++ b/docs/stackit_load-balancer_observability-credentials_delete.md @@ -0,0 +1,39 @@ +## stackit load-balancer observability-credentials delete + +Deletes observability credentials for Load Balancer + +### Synopsis + +Deletes observability credentials for Load Balancer. + +``` +stackit load-balancer observability-credentials delete CREDENTIALS_REF [flags] +``` + +### Examples + +``` + Delete credentials with reference "credentials-xxx" for Load Balancer + $ stackit loadbalancer credentials delete credentials-xxx +``` + +### Options + +``` + -h, --help Help for "stackit load-balancer observability-credentials delete" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none"] + -p, --project-id string Project ID + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit load-balancer observability-credentials](./stackit_load-balancer_observability-credentials.md) - Provides functionality for Load Balancer observability credentials + diff --git a/docs/stackit_load-balancer_observability-credentials_describe.md b/docs/stackit_load-balancer_observability-credentials_describe.md new file mode 100644 index 000000000..6ad2acbeb --- /dev/null +++ b/docs/stackit_load-balancer_observability-credentials_describe.md @@ -0,0 +1,39 @@ +## stackit load-balancer observability-credentials describe + +Shows details of observability credentials for load balancers + +### Synopsis + +Shows details of observability credentials for load balancers. + +``` +stackit load-balancer observability-credentials describe CREDENTIALS_REF [flags] +``` + +### Examples + +``` + Get details of credentials with reference "credentials-xxx" + $ stackit load-balancer credentials describe credentials-xxx +``` + +### Options + +``` + -h, --help Help for "stackit load-balancer observability-credentials describe" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none"] + -p, --project-id string Project ID + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit load-balancer observability-credentials](./stackit_load-balancer_observability-credentials.md) - Provides functionality for Load Balancer observability credentials + diff --git a/docs/stackit_load-balancer_observability-credentials_list.md b/docs/stackit_load-balancer_observability-credentials_list.md new file mode 100644 index 000000000..ee183e01f --- /dev/null +++ b/docs/stackit_load-balancer_observability-credentials_list.md @@ -0,0 +1,46 @@ +## stackit load-balancer observability-credentials list + +Lists all observability credentials for Load Balancer + +### Synopsis + +Lists all observability credentials for Load Balancer. + +``` +stackit load-balancer observability-credentials list [flags] +``` + +### Examples + +``` + List all observability credentials for Load Balancer + $ stackit load-balancer observability-credentials list + + List all observability credentials for Load Balancer in JSON format + $ stackit load-balancer observability-credentials list --output-format json + + List up to 10 observability credentials for Load Balancer + $ stackit load-balancer observability-credentials list --limit 10 +``` + +### Options + +``` + -h, --help Help for "stackit load-balancer observability-credentials list" + --limit int Maximum number of entries to list +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none"] + -p, --project-id string Project ID + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit load-balancer observability-credentials](./stackit_load-balancer_observability-credentials.md) - Provides functionality for Load Balancer observability credentials + diff --git a/docs/stackit_load-balancer_observability-credentials_update.md b/docs/stackit_load-balancer_observability-credentials_update.md new file mode 100644 index 000000000..6714edb45 --- /dev/null +++ b/docs/stackit_load-balancer_observability-credentials_update.md @@ -0,0 +1,48 @@ +## stackit load-balancer observability-credentials update + +Updates observability credentials for Load Balancer + +### Synopsis + +Updates existing observability credentials (username and password) for Load Balancer. The credentials can be for Argus or another monitoring tool. + +``` +stackit load-balancer observability-credentials update [flags] +``` + +### Examples + +``` + Update the password of credentials of Load Balancer with credentials reference "credentials-xxx". The password is entered using the terminal + $ stackit load-balancer observability-credentials update credentials-xxx --password + + Update the password of credentials of Load Balancer with credentials reference "credentials-xxx", by providing it in the flag + $ stackit load-balancer observability-credentials update credentials-xxx --password new-pwd + + Update the display name of credentials of Load Balancer with credentials reference "credentials-xxx". + $ stackit load-balancer observability-credentials update credentials-xxx --display-name yyy +``` + +### Options + +``` + --display-name string Credentials name + -h, --help Help for "stackit load-balancer observability-credentials update" + --password string Password + --username string Username +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none"] + -p, --project-id string Project ID + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit load-balancer observability-credentials](./stackit_load-balancer_observability-credentials.md) - Provides functionality for Load Balancer observability credentials + From b36f1468626e8382b277b5a87b505f4f3bda048a Mon Sep 17 00:00:00 2001 From: vicentepinto98 Date: Fri, 3 May 2024 11:35:54 +0100 Subject: [PATCH 6/9] Add debug logs, uniformize descriptions --- ...load-balancer_observability-credentials.md | 2 +- ...-balancer_observability-credentials_add.md | 4 +-- ...lancer_observability-credentials_delete.md | 2 +- ...ncer_observability-credentials_describe.md | 8 ++--- ...lancer_observability-credentials_update.md | 6 ++-- .../observability-credentials/add/add.go | 29 ++++++++++++------ .../observability-credentials/add/add_test.go | 4 ++- .../delete/delete.go | 30 +++++++++++++++---- .../delete/delete_test.go | 4 ++- .../describe/describe.go | 23 ++++++++++---- .../describe/describe_test.go | 4 ++- .../observability-credentials/list/list.go | 9 +++++- .../update/update.go | 10 +++---- .../update/update_test.go | 4 ++- 14 files changed, 97 insertions(+), 42 deletions(-) diff --git a/docs/stackit_load-balancer_observability-credentials.md b/docs/stackit_load-balancer_observability-credentials.md index 4a1bc17f1..85ac70f8f 100644 --- a/docs/stackit_load-balancer_observability-credentials.md +++ b/docs/stackit_load-balancer_observability-credentials.md @@ -31,7 +31,7 @@ stackit load-balancer observability-credentials [flags] * [stackit load-balancer](./stackit_load-balancer.md) - Provides functionality for Load Balancer * [stackit load-balancer observability-credentials add](./stackit_load-balancer_observability-credentials_add.md) - Adds observability credentials to Load Balancer * [stackit load-balancer observability-credentials delete](./stackit_load-balancer_observability-credentials_delete.md) - Deletes observability credentials for Load Balancer -* [stackit load-balancer observability-credentials describe](./stackit_load-balancer_observability-credentials_describe.md) - Shows details of observability credentials for load balancers +* [stackit load-balancer observability-credentials describe](./stackit_load-balancer_observability-credentials_describe.md) - Shows details of observability credentials for Load Balancer * [stackit load-balancer observability-credentials list](./stackit_load-balancer_observability-credentials_list.md) - Lists all observability credentials for Load Balancer * [stackit load-balancer observability-credentials update](./stackit_load-balancer_observability-credentials_update.md) - Updates observability credentials for Load Balancer diff --git a/docs/stackit_load-balancer_observability-credentials_add.md b/docs/stackit_load-balancer_observability-credentials_add.md index 92d8a839e..cfccc7084 100644 --- a/docs/stackit_load-balancer_observability-credentials_add.md +++ b/docs/stackit_load-balancer_observability-credentials_add.md @@ -13,10 +13,10 @@ stackit load-balancer observability-credentials add [flags] ### Examples ``` - Add credentials to a load balancer with username "xxx" and display name "yyy". The password is entered using the terminal + Add observability credentials to a load balancer with username "xxx" and display name "yyy". The password is entered using the terminal $ stackit load-balancer observability-credentials add --username xxx --display-name yyy - Add credentials to a load balancer with username "xxx" and display name "yyy", providing the password as flag + Add observability credentials to a load balancer with username "xxx" and display name "yyy", providing the password as flag $ stackit load-balancer observability-credentials add --username xxx --password pwd --display-name yyy ``` diff --git a/docs/stackit_load-balancer_observability-credentials_delete.md b/docs/stackit_load-balancer_observability-credentials_delete.md index 9440b8988..d05feb410 100644 --- a/docs/stackit_load-balancer_observability-credentials_delete.md +++ b/docs/stackit_load-balancer_observability-credentials_delete.md @@ -13,7 +13,7 @@ stackit load-balancer observability-credentials delete CREDENTIALS_REF [flags] ### Examples ``` - Delete credentials with reference "credentials-xxx" for Load Balancer + Delete observability credentials with reference "credentials-xxx" for Load Balancer $ stackit loadbalancer credentials delete credentials-xxx ``` diff --git a/docs/stackit_load-balancer_observability-credentials_describe.md b/docs/stackit_load-balancer_observability-credentials_describe.md index 6ad2acbeb..cf0d63e59 100644 --- a/docs/stackit_load-balancer_observability-credentials_describe.md +++ b/docs/stackit_load-balancer_observability-credentials_describe.md @@ -1,10 +1,10 @@ ## stackit load-balancer observability-credentials describe -Shows details of observability credentials for load balancers +Shows details of observability credentials for Load Balancer ### Synopsis -Shows details of observability credentials for load balancers. +Shows details of observability credentials for Load Balancer. ``` stackit load-balancer observability-credentials describe CREDENTIALS_REF [flags] @@ -13,8 +13,8 @@ stackit load-balancer observability-credentials describe CREDENTIALS_REF [flags] ### Examples ``` - Get details of credentials with reference "credentials-xxx" - $ stackit load-balancer credentials describe credentials-xxx + Get details of observability credentials with reference "credentials-xxx" + $ stackit load-balancer observability-credentials describe credentials-xxx ``` ### Options diff --git a/docs/stackit_load-balancer_observability-credentials_update.md b/docs/stackit_load-balancer_observability-credentials_update.md index 6714edb45..1fbcbd5b7 100644 --- a/docs/stackit_load-balancer_observability-credentials_update.md +++ b/docs/stackit_load-balancer_observability-credentials_update.md @@ -13,13 +13,13 @@ stackit load-balancer observability-credentials update [flags] ### Examples ``` - Update the password of credentials of Load Balancer with credentials reference "credentials-xxx". The password is entered using the terminal + Update the password of observability credentials of Load Balancer with credentials reference "credentials-xxx". The password is entered using the terminal $ stackit load-balancer observability-credentials update credentials-xxx --password - Update the password of credentials of Load Balancer with credentials reference "credentials-xxx", by providing it in the flag + Update the password of observability credentials of Load Balancer with credentials reference "credentials-xxx", by providing it in the flag $ stackit load-balancer observability-credentials update credentials-xxx --password new-pwd - Update the display name of credentials of Load Balancer with credentials reference "credentials-xxx". + Update the display name of observability credentials of Load Balancer with credentials reference "credentials-xxx". $ stackit load-balancer observability-credentials update credentials-xxx --display-name yyy ``` diff --git a/internal/cmd/load-balancer/observability-credentials/add/add.go b/internal/cmd/load-balancer/observability-credentials/add/add.go index bb0a10c7d..1a7148e58 100644 --- a/internal/cmd/load-balancer/observability-credentials/add/add.go +++ b/internal/cmd/load-balancer/observability-credentials/add/add.go @@ -41,10 +41,10 @@ func NewCmd(p *print.Printer) *cobra.Command { Args: args.NoArgs, Example: examples.Build( examples.NewExample( - `Add credentials to a load balancer with username "xxx" and display name "yyy". The password is entered using the terminal`, + `Add observability credentials to a load balancer with username "xxx" and display name "yyy". The password is entered using the terminal`, "$ stackit load-balancer observability-credentials add --username xxx --display-name yyy"), examples.NewExample( - `Add credentials to a load balancer with username "xxx" and display name "yyy", providing the password as flag`, + `Add observability credentials to a load balancer with username "xxx" and display name "yyy", providing the password as flag`, "$ stackit load-balancer observability-credentials add --username xxx --password pwd --display-name yyy"), ), RunE: func(cmd *cobra.Command, args []string) error { @@ -67,7 +67,7 @@ func NewCmd(p *print.Printer) *cobra.Command { } if !model.AssumeYes { - prompt := fmt.Sprintf("Are you sure you want to add observability credentials for your Load Balancers on project %q?", projectLabel) + prompt := fmt.Sprintf("Are you sure you want to add observability credentials for Load Balancer on project %q?", projectLabel) err = p.PromptForConfirmation(prompt) if err != nil { return err @@ -87,7 +87,7 @@ func NewCmd(p *print.Printer) *cobra.Command { req := buildRequest(ctx, model, apiClient) resp, err := req.Execute() if err != nil { - return fmt.Errorf("add Load Balancer credentials: %w", err) + return fmt.Errorf("add Load Balancer observability credentials: %w", err) } return outputResult(p, model, projectLabel, resp) @@ -112,12 +112,23 @@ func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { return nil, &errors.ProjectIdError{} } - return &inputModel{ + model := inputModel{ GlobalFlagModel: globalFlags, DisplayName: flags.FlagToStringPointer(p, cmd, displayNameFlag), Username: flags.FlagToStringPointer(p, cmd, usernameFlag), Password: flags.FlagToStringPointer(p, cmd, passwordFlag), - }, nil + } + + if p.IsVerbosityDebug() { + modelStr, err := print.BuildDebugStrFromInputModel(model) + if err != nil { + p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) + } else { + p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) + } + } + + return &model, nil } func buildRequest(ctx context.Context, model *inputModel, apiClient *loadbalancer.APIClient) loadbalancer.ApiCreateCredentialsRequest { @@ -134,20 +145,20 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *loadbalance func outputResult(p *print.Printer, model *inputModel, projectLabel string, resp *loadbalancer.CreateCredentialsResponse) error { if resp.Credential == nil { - return fmt.Errorf("nil credentials response") + return fmt.Errorf("nil observability credentials response") } switch model.OutputFormat { case print.JSONOutputFormat: details, err := json.MarshalIndent(resp, "", " ") if err != nil { - return fmt.Errorf("marshal Load Balancer credentials: %w", err) + return fmt.Errorf("marshal Load Balancer observability credentials: %w", err) } p.Outputln(string(details)) return nil default: - p.Outputf("Added load balancer observability credentials for project %q. Credentials reference: %q\n", projectLabel, *resp.Credential.CredentialsRef) + p.Outputf("Added Load Balancer observability credentials on project %q. Credentials reference: %q\n", projectLabel, *resp.Credential.CredentialsRef) return nil } } diff --git a/internal/cmd/load-balancer/observability-credentials/add/add_test.go b/internal/cmd/load-balancer/observability-credentials/add/add_test.go index c8726b5b7..1251bd72e 100644 --- a/internal/cmd/load-balancer/observability-credentials/add/add_test.go +++ b/internal/cmd/load-balancer/observability-credentials/add/add_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" @@ -120,7 +121,8 @@ func TestParseInput(t *testing.T) { for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - cmd := NewCmd(nil) + p := print.NewPrinter() + cmd := NewCmd(p) err := globalflags.Configure(cmd.Flags()) if err != nil { t.Fatalf("configure global flags: %v", err) diff --git a/internal/cmd/load-balancer/observability-credentials/delete/delete.go b/internal/cmd/load-balancer/observability-credentials/delete/delete.go index f5772f54f..5e3df8a20 100644 --- a/internal/cmd/load-balancer/observability-credentials/delete/delete.go +++ b/internal/cmd/load-balancer/observability-credentials/delete/delete.go @@ -9,6 +9,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" "github.com/stackitcloud/stackit-cli/internal/pkg/services/load-balancer/client" loadbalancerUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/load-balancer/utils" @@ -33,7 +34,7 @@ func NewCmd(p *print.Printer) *cobra.Command { Args: args.SingleArg(credentialsRefArg, nil), Example: examples.Build( examples.NewExample( - `Delete credentials with reference "credentials-xxx" for Load Balancer`, + `Delete observability credentials with reference "credentials-xxx" for Load Balancer`, "$ stackit loadbalancer credentials delete credentials-xxx"), ), RunE: func(cmd *cobra.Command, args []string) error { @@ -49,14 +50,20 @@ func NewCmd(p *print.Printer) *cobra.Command { return err } + projectLabel, err := projectname.GetProjectName(ctx, p, cmd) + if err != nil { + p.Debug(print.ErrorLevel, "get project name: %v", err) + projectLabel = model.ProjectId + } + credentialsLabel, err := loadbalancerUtils.GetCredentialsDisplayName(ctx, apiClient, model.ProjectId, model.CredentialsRef) if err != nil { - p.Debug(print.ErrorLevel, "get credentials display name: %v", err) + p.Debug(print.ErrorLevel, "get observability credentials display name: %v", err) credentialsLabel = model.CredentialsRef } if !model.AssumeYes { - prompt := fmt.Sprintf("Are you sure you want to delete observability credentials %q? (This cannot be undone)", credentialsLabel) + prompt := fmt.Sprintf("Are you sure you want to delete observability credentials %q on project %q?(This cannot be undone)", credentialsLabel, projectLabel) err = p.PromptForConfirmation(prompt) if err != nil { return err @@ -70,7 +77,7 @@ func NewCmd(p *print.Printer) *cobra.Command { return fmt.Errorf("delete Load Balancer observability credentials: %w", err) } - p.Info("Deleted credentials %q\n", credentialsLabel) + p.Info("Deleted observability credentials %q on project %q\n", credentialsLabel, projectLabel) return nil }, } @@ -85,10 +92,21 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu return nil, &errors.ProjectIdError{} } - return &inputModel{ + model := inputModel{ GlobalFlagModel: globalFlags, CredentialsRef: credentialsRef, - }, nil + } + + if p.IsVerbosityDebug() { + modelStr, err := print.BuildDebugStrFromInputModel(model) + if err != nil { + p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) + } else { + p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) + } + } + + return &model, nil } func buildRequest(ctx context.Context, model *inputModel, apiClient *loadbalancer.APIClient) loadbalancer.ApiDeleteCredentialsRequest { diff --git a/internal/cmd/load-balancer/observability-credentials/delete/delete_test.go b/internal/cmd/load-balancer/observability-credentials/delete/delete_test.go index a0b1f5632..37a9235d7 100644 --- a/internal/cmd/load-balancer/observability-credentials/delete/delete_test.go +++ b/internal/cmd/load-balancer/observability-credentials/delete/delete_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer" "github.com/google/go-cmp/cmp" @@ -131,7 +132,8 @@ func TestParseInput(t *testing.T) { for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - cmd := NewCmd(nil) + p := print.NewPrinter() + cmd := NewCmd(p) err := globalflags.Configure(cmd.Flags()) if err != nil { t.Fatalf("configure global flags: %v", err) diff --git a/internal/cmd/load-balancer/observability-credentials/describe/describe.go b/internal/cmd/load-balancer/observability-credentials/describe/describe.go index 88861a0c9..91a0024ab 100644 --- a/internal/cmd/load-balancer/observability-credentials/describe/describe.go +++ b/internal/cmd/load-balancer/observability-credentials/describe/describe.go @@ -29,13 +29,13 @@ type inputModel struct { func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: fmt.Sprintf("describe %s", credentialsRefArg), - Short: "Shows details of observability credentials for load balancers", - Long: "Shows details of observability credentials for load balancers.", + Short: "Shows details of observability credentials for Load Balancer", + Long: "Shows details of observability credentials for Load Balancer.", Args: args.SingleArg(credentialsRefArg, nil), Example: examples.Build( examples.NewExample( - `Get details of credentials with reference "credentials-xxx"`, - "$ stackit load-balancer credentials describe credentials-xxx"), + `Get details of observability credentials with reference "credentials-xxx"`, + "$ stackit load-balancer observability-credentials describe credentials-xxx"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -71,10 +71,21 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu return nil, &errors.ProjectIdError{} } - return &inputModel{ + model := inputModel{ GlobalFlagModel: globalFlags, CredentialsRef: credentialsRef, - }, nil + } + + if p.IsVerbosityDebug() { + modelStr, err := print.BuildDebugStrFromInputModel(model) + if err != nil { + p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) + } else { + p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) + } + } + + return &model, nil } func buildRequest(ctx context.Context, model *inputModel, apiClient *loadbalancer.APIClient) loadbalancer.ApiGetCredentialsRequest { diff --git a/internal/cmd/load-balancer/observability-credentials/describe/describe_test.go b/internal/cmd/load-balancer/observability-credentials/describe/describe_test.go index e64390d53..79d74a8e9 100644 --- a/internal/cmd/load-balancer/observability-credentials/describe/describe_test.go +++ b/internal/cmd/load-balancer/observability-credentials/describe/describe_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" @@ -131,7 +132,8 @@ func TestParseInput(t *testing.T) { for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - cmd := NewCmd(nil) + p := print.NewPrinter() + cmd := NewCmd(p) err := globalflags.Configure(cmd.Flags()) if err != nil { t.Fatalf("configure global flags: %v", err) diff --git a/internal/cmd/load-balancer/observability-credentials/list/list.go b/internal/cmd/load-balancer/observability-credentials/list/list.go index ac2074514..21cb4dcad 100644 --- a/internal/cmd/load-balancer/observability-credentials/list/list.go +++ b/internal/cmd/load-balancer/observability-credentials/list/list.go @@ -11,6 +11,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" "github.com/stackitcloud/stackit-cli/internal/pkg/services/load-balancer/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" @@ -58,6 +59,12 @@ func NewCmd(p *print.Printer) *cobra.Command { return err } + projectLabel, err := projectname.GetProjectName(ctx, p, cmd) + if err != nil { + p.Debug(print.ErrorLevel, "get project name: %v", err) + projectLabel = model.ProjectId + } + // Call API req := buildRequest(ctx, model, apiClient) resp, err := req.Execute() @@ -66,7 +73,7 @@ func NewCmd(p *print.Printer) *cobra.Command { } credentialsPtr := resp.Credentials if credentialsPtr == nil || (credentialsPtr != nil && len(*credentialsPtr) == 0) { - p.Info("No observability credentials found for Load Balancer\n") + p.Info("No observability credentials found for Load Balancer on project %q\n", projectLabel) return nil } diff --git a/internal/cmd/load-balancer/observability-credentials/update/update.go b/internal/cmd/load-balancer/observability-credentials/update/update.go index a1ca41f4e..a62350a86 100644 --- a/internal/cmd/load-balancer/observability-credentials/update/update.go +++ b/internal/cmd/load-balancer/observability-credentials/update/update.go @@ -43,13 +43,13 @@ func NewCmd(p *print.Printer) *cobra.Command { Args: args.SingleArg(credentialsRefArg, nil), Example: examples.Build( examples.NewExample( - `Update the password of credentials of Load Balancer with credentials reference "credentials-xxx". The password is entered using the terminal`, + `Update the password of observability credentials of Load Balancer with credentials reference "credentials-xxx". The password is entered using the terminal`, "$ stackit load-balancer observability-credentials update credentials-xxx --password "), examples.NewExample( - `Update the password of credentials of Load Balancer with credentials reference "credentials-xxx", by providing it in the flag`, + `Update the password of observability credentials of Load Balancer with credentials reference "credentials-xxx", by providing it in the flag`, "$ stackit load-balancer observability-credentials update credentials-xxx --password new-pwd"), examples.NewExample( - `Update the display name of credentials of Load Balancer with credentials reference "credentials-xxx".`, + `Update the display name of observability credentials of Load Balancer with credentials reference "credentials-xxx".`, "$ stackit load-balancer observability-credentials update credentials-xxx --display-name yyy"), ), RunE: func(cmd *cobra.Command, args []string) error { @@ -78,7 +78,7 @@ func NewCmd(p *print.Printer) *cobra.Command { } if !model.AssumeYes { - prompt := fmt.Sprintf("Are you sure you want to update observability credentials %q for your Load Balancers on project %q?", credentialsLabel, projectLabel) + prompt := fmt.Sprintf("Are you sure you want to update observability credentials %q for Load Balancer on project %q?", credentialsLabel, projectLabel) err = p.PromptForConfirmation(prompt) if err != nil { return err @@ -105,7 +105,7 @@ func NewCmd(p *print.Printer) *cobra.Command { return fmt.Errorf("update Load Balancer observability credentials: %w", err) } - p.Info("Updated observability credentials %q for Load Balancer\n", credentialsLabel) + p.Info("Updated observability credentials %q for Load Balancer on project %q\n", credentialsLabel, projectLabel) return nil }, } diff --git a/internal/cmd/load-balancer/observability-credentials/update/update_test.go b/internal/cmd/load-balancer/observability-credentials/update/update_test.go index e6eec24c9..378d53b67 100644 --- a/internal/cmd/load-balancer/observability-credentials/update/update_test.go +++ b/internal/cmd/load-balancer/observability-credentials/update/update_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/google/go-cmp/cmp" @@ -174,7 +175,8 @@ func TestParseInput(t *testing.T) { for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - cmd := NewCmd(nil) + p := print.NewPrinter() + cmd := NewCmd(p) err := globalflags.Configure(cmd.Flags()) if err != nil { t.Fatalf("configure global flags: %v", err) From d23e827b024642ba03b7e8bbd366de8f34c99cbd Mon Sep 17 00:00:00 2001 From: vicentepinto98 Date: Fri, 3 May 2024 11:40:10 +0100 Subject: [PATCH 7/9] Fix unit tests --- .../cmd/load-balancer/observability-credentials/add/add_test.go | 2 +- .../observability-credentials/delete/delete_test.go | 2 +- .../observability-credentials/describe/describe_test.go | 2 +- .../observability-credentials/update/update_test.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/cmd/load-balancer/observability-credentials/add/add_test.go b/internal/cmd/load-balancer/observability-credentials/add/add_test.go index 1251bd72e..85fc6b7d6 100644 --- a/internal/cmd/load-balancer/observability-credentials/add/add_test.go +++ b/internal/cmd/load-balancer/observability-credentials/add/add_test.go @@ -146,7 +146,7 @@ func TestParseInput(t *testing.T) { t.Fatalf("error validating flags: %v", err) } - model, err := parseInput(nil, cmd) + model, err := parseInput(p, cmd) if err != nil { if !tt.isValid { return diff --git a/internal/cmd/load-balancer/observability-credentials/delete/delete_test.go b/internal/cmd/load-balancer/observability-credentials/delete/delete_test.go index 37a9235d7..a2cbe40a0 100644 --- a/internal/cmd/load-balancer/observability-credentials/delete/delete_test.go +++ b/internal/cmd/load-balancer/observability-credentials/delete/delete_test.go @@ -165,7 +165,7 @@ func TestParseInput(t *testing.T) { t.Fatalf("error validating flags: %v", err) } - model, err := parseInput(nil, cmd, tt.argValues) + model, err := parseInput(p, cmd, tt.argValues) if err != nil { if !tt.isValid { return diff --git a/internal/cmd/load-balancer/observability-credentials/describe/describe_test.go b/internal/cmd/load-balancer/observability-credentials/describe/describe_test.go index 79d74a8e9..d916923fd 100644 --- a/internal/cmd/load-balancer/observability-credentials/describe/describe_test.go +++ b/internal/cmd/load-balancer/observability-credentials/describe/describe_test.go @@ -165,7 +165,7 @@ func TestParseInput(t *testing.T) { t.Fatalf("error validating flags: %v", err) } - model, err := parseInput(nil, cmd, tt.argValues) + model, err := parseInput(p, cmd, tt.argValues) if err != nil { if !tt.isValid { return diff --git a/internal/cmd/load-balancer/observability-credentials/update/update_test.go b/internal/cmd/load-balancer/observability-credentials/update/update_test.go index 378d53b67..6b30da82b 100644 --- a/internal/cmd/load-balancer/observability-credentials/update/update_test.go +++ b/internal/cmd/load-balancer/observability-credentials/update/update_test.go @@ -208,7 +208,7 @@ func TestParseInput(t *testing.T) { t.Fatalf("error validating flags: %v", err) } - model, err := parseInput(nil, cmd, tt.argValues) + model, err := parseInput(p, cmd, tt.argValues) if err != nil { if !tt.isValid { return From dec248d60da6f9e433cc632a87e1b821f852b05a Mon Sep 17 00:00:00 2001 From: Vicente Pinto Date: Fri, 3 May 2024 13:47:00 +0100 Subject: [PATCH 8/9] Update internal/cmd/load-balancer/observability-credentials/observability-credentials.go Co-authored-by: GokceGK <161626272+GokceGK@users.noreply.github.com> --- .../observability-credentials/observability-credentials.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/load-balancer/observability-credentials/observability-credentials.go b/internal/cmd/load-balancer/observability-credentials/observability-credentials.go index d03aa79d9..1c7dcec9c 100644 --- a/internal/cmd/load-balancer/observability-credentials/observability-credentials.go +++ b/internal/cmd/load-balancer/observability-credentials/observability-credentials.go @@ -17,7 +17,7 @@ func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: "observability-credentials", Short: "Provides functionality for Load Balancer observability credentials", - Long: `Provides functionality for Load Balancer observability credentials. These commands can be used to store and update existing credentials, which are valid to be used for Load Balancer Observability. This means, e.g. when using Argus, that credentials first must be created for that Argus instance (by using "stackit argus credentials create") and then can be managed for a Load Balancer by using the commands in this group.`, + Long: `Provides functionality for Load Balancer observability credentials. These commands can be used to store and update existing credentials, which are valid to be used for Load Balancer observability. This means, e.g. when using Argus, first of all these credentials must be created for that Argus instance (by using "stackit argus credentials create") and then can be managed for a Load Balancer by using the commands in this group.`, Args: args.NoArgs, Aliases: []string{"credentials"}, Run: utils.CmdHelp, From 3204a3e61df6876dccb889fd9d912e28aa2cc220 Mon Sep 17 00:00:00 2001 From: Vicente Pinto Date: Fri, 3 May 2024 13:47:12 +0100 Subject: [PATCH 9/9] Update internal/cmd/load-balancer/observability-credentials/delete/delete_test.go Co-authored-by: GokceGK <161626272+GokceGK@users.noreply.github.com> --- .../observability-credentials/delete/delete_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/load-balancer/observability-credentials/delete/delete_test.go b/internal/cmd/load-balancer/observability-credentials/delete/delete_test.go index a2cbe40a0..629b93f5c 100644 --- a/internal/cmd/load-balancer/observability-credentials/delete/delete_test.go +++ b/internal/cmd/load-balancer/observability-credentials/delete/delete_test.go @@ -123,7 +123,7 @@ func TestParseInput(t *testing.T) { isValid: false, }, { - description: "credentials id invalid 1", + description: "credentials ref invalid 1", argValues: []string{""}, flagValues: fixtureFlagValues(), isValid: false,