diff --git a/docs/stackit_argus_grafana.md b/docs/stackit_argus_grafana.md index c1ce6b5b5..c6743ac86 100644 --- a/docs/stackit_argus_grafana.md +++ b/docs/stackit_argus_grafana.md @@ -30,5 +30,6 @@ stackit argus grafana [flags] * [stackit argus](./stackit_argus.md) - Provides functionality for Argus * [stackit argus grafana describe](./stackit_argus_grafana_describe.md) - Shows details of the Grafana configuration of an Argus instance +* [stackit argus grafana public-read-access](./stackit_argus_grafana_public-read-access.md) - Enable or disable public read access for Grafana in Argus instances * [stackit argus grafana single-sign-on](./stackit_argus_grafana_single-sign-on.md) - Enable or disable single sign-on for Grafana in Argus instances diff --git a/docs/stackit_argus_grafana_public-read-access.md b/docs/stackit_argus_grafana_public-read-access.md new file mode 100644 index 000000000..bc0f7791e --- /dev/null +++ b/docs/stackit_argus_grafana_public-read-access.md @@ -0,0 +1,35 @@ +## stackit argus grafana public-read-access + +Enable or disable public read access for Grafana in Argus instances + +### Synopsis + +Enable or disable public read access for Grafana in Argus instances. +When enabled, anyone can access the Grafana dashboards of the instance without logging in. Otherwise, a login is required. + +``` +stackit argus grafana public-read-access [flags] +``` + +### Options + +``` + -h, --help Help for "stackit argus grafana public-read-access" +``` + +### 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"] + -p, --project-id string Project ID + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit argus grafana](./stackit_argus_grafana.md) - Provides functionality for the Grafana configuration of Argus instances +* [stackit argus grafana public-read-access disable](./stackit_argus_grafana_public-read-access_disable.md) - Disables public read access for Grafana on Argus instances +* [stackit argus grafana public-read-access enable](./stackit_argus_grafana_public-read-access_enable.md) - Enables public read access for Grafana on Argus instances + diff --git a/docs/stackit_argus_grafana_public-read-access_disable.md b/docs/stackit_argus_grafana_public-read-access_disable.md new file mode 100644 index 000000000..9f1b821f3 --- /dev/null +++ b/docs/stackit_argus_grafana_public-read-access_disable.md @@ -0,0 +1,40 @@ +## stackit argus grafana public-read-access disable + +Disables public read access for Grafana on Argus instances + +### Synopsis + +Disables public read access for Grafana on Argus instances. +When disabled, a login is required to access the Grafana dashboards of the instance. Otherwise, anyone can access the dashboards. + +``` +stackit argus grafana public-read-access disable INSTANCE_ID [flags] +``` + +### Examples + +``` + Disable public read access for Grafana on an Argus instance with ID "xxx" + $ stackit argus grafana public-read-access disable xxx +``` + +### Options + +``` + -h, --help Help for "stackit argus grafana public-read-access disable" +``` + +### 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"] + -p, --project-id string Project ID + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit argus grafana public-read-access](./stackit_argus_grafana_public-read-access.md) - Enable or disable public read access for Grafana in Argus instances + diff --git a/docs/stackit_argus_grafana_public-read-access_enable.md b/docs/stackit_argus_grafana_public-read-access_enable.md new file mode 100644 index 000000000..b66012713 --- /dev/null +++ b/docs/stackit_argus_grafana_public-read-access_enable.md @@ -0,0 +1,40 @@ +## stackit argus grafana public-read-access enable + +Enables public read access for Grafana on Argus instances + +### Synopsis + +Enables public read access for Grafana on Argus instances. +When enabled, anyone can access the Grafana dashboards of the instance without logging in. Otherwise, a login is required. + +``` +stackit argus grafana public-read-access enable INSTANCE_ID [flags] +``` + +### Examples + +``` + Enable public read access for Grafana on an Argus instance with ID "xxx" + $ stackit argus grafana public-read-access enable xxx +``` + +### Options + +``` + -h, --help Help for "stackit argus grafana public-read-access enable" +``` + +### 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"] + -p, --project-id string Project ID + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit argus grafana public-read-access](./stackit_argus_grafana_public-read-access.md) - Enable or disable public read access for Grafana in Argus instances + diff --git a/docs/stackit_argus_grafana_single-sign-on_disable.md b/docs/stackit_argus_grafana_single-sign-on_disable.md index 143273e20..0baf8e753 100644 --- a/docs/stackit_argus_grafana_single-sign-on_disable.md +++ b/docs/stackit_argus_grafana_single-sign-on_disable.md @@ -8,21 +8,20 @@ Disables single sign-on for Grafana on Argus instances. When disabled for an instance, the generic OAuth2 authentication is used for that instance. ``` -stackit argus grafana single-sign-on disable [flags] +stackit argus grafana single-sign-on disable INSTANCE_ID [flags] ``` ### Examples ``` Disable single sign-on for Grafana on an Argus instance with ID "xxx" - $ stackit argus grafana single-sign-on disable --instance-id xxx + $ stackit argus grafana single-sign-on disable xxx ``` ### Options ``` - -h, --help Help for "stackit argus grafana single-sign-on disable" - --instance-id string Instance ID + -h, --help Help for "stackit argus grafana single-sign-on disable" ``` ### Options inherited from parent commands diff --git a/docs/stackit_argus_grafana_single-sign-on_enable.md b/docs/stackit_argus_grafana_single-sign-on_enable.md index ad83abb99..34bb9c6a6 100644 --- a/docs/stackit_argus_grafana_single-sign-on_enable.md +++ b/docs/stackit_argus_grafana_single-sign-on_enable.md @@ -8,21 +8,20 @@ Enables single sign-on for Grafana on Argus instances. When enabled for an instance, overwrites the generic OAuth2 authentication and configures STACKIT single sign-on for that instance. ``` -stackit argus grafana single-sign-on enable [flags] +stackit argus grafana single-sign-on enable INSTANCE_ID [flags] ``` ### Examples ``` Enable single sign-on for Grafana on an Argus instance with ID "xxx" - $ stackit argus grafana single-sign-on enable --instance-id xxx + $ stackit argus grafana single-sign-on enable xxx ``` ### Options ``` - -h, --help Help for "stackit argus grafana single-sign-on enable" - --instance-id string Instance ID + -h, --help Help for "stackit argus grafana single-sign-on enable" ``` ### Options inherited from parent commands diff --git a/internal/cmd/argus/grafana/grafana.go b/internal/cmd/argus/grafana/grafana.go index 0ce98f4c8..19ed4368a 100644 --- a/internal/cmd/argus/grafana/grafana.go +++ b/internal/cmd/argus/grafana/grafana.go @@ -2,6 +2,7 @@ package grafana import ( "github.com/stackitcloud/stackit-cli/internal/cmd/argus/grafana/describe" + publicreadaccess "github.com/stackitcloud/stackit-cli/internal/cmd/argus/grafana/public-read-access" singlesignon "github.com/stackitcloud/stackit-cli/internal/cmd/argus/grafana/single-sign-on" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/print" @@ -24,5 +25,6 @@ func NewCmd(p *print.Printer) *cobra.Command { func addSubcommands(cmd *cobra.Command, p *print.Printer) { cmd.AddCommand(describe.NewCmd(p)) + cmd.AddCommand(publicreadaccess.NewCmd(p)) cmd.AddCommand(singlesignon.NewCmd(p)) } diff --git a/internal/cmd/argus/grafana/public-read-access/disable/disable.go b/internal/cmd/argus/grafana/public-read-access/disable/disable.go new file mode 100644 index 000000000..e06157372 --- /dev/null +++ b/internal/cmd/argus/grafana/public-read-access/disable/disable.go @@ -0,0 +1,108 @@ +package disable + +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/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/argus/client" + argusUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/argus/utils" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/argus" +) + +const ( + instanceIdArg = "INSTANCE_ID" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + InstanceId string +} + +func NewCmd(p *print.Printer) *cobra.Command { + cmd := &cobra.Command{ + Use: fmt.Sprintf("disable %s", instanceIdArg), + Short: "Disables public read access for Grafana on Argus instances", + Long: fmt.Sprintf("%s\n%s", + "Disables public read access for Grafana on Argus instances.", + "When disabled, a login is required to access the Grafana dashboards of the instance. Otherwise, anyone can access the dashboards.", + ), + Args: args.SingleArg(instanceIdArg, utils.ValidateUUID), + Example: examples.Build( + examples.NewExample( + `Disable public read access for Grafana on an Argus instance with ID "xxx"`, + "$ stackit argus grafana public-read-access disable xxx"), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(p) + if err != nil { + return err + } + + instanceLabel, err := argusUtils.GetInstanceName(ctx, apiClient, model.InstanceId, model.ProjectId) + if err != nil || instanceLabel == "" { + instanceLabel = model.InstanceId + } + + if !model.AssumeYes { + prompt := fmt.Sprintf("Are you sure you want to disable Grafana public read access for instance %q?", instanceLabel) + err = p.PromptForConfirmation(prompt) + if err != nil { + return err + } + } + + // Call API + req, err := buildRequest(ctx, model, apiClient) + if err != nil { + return fmt.Errorf("build request: %w", err) + } + _, err = req.Execute() + if err != nil { + return fmt.Errorf("disable grafana public read access: %w", err) + } + + p.Info("Disabled Grafana public read access for instance %q\n", instanceLabel) + return nil + }, + } + return cmd +} + +func parseInput(cmd *cobra.Command, inputArgs []string) (*inputModel, error) { + instanceId := inputArgs[0] + + globalFlags := globalflags.Parse(cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + + return &inputModel{ + GlobalFlagModel: globalFlags, + InstanceId: instanceId, + }, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient argusUtils.ArgusClient) (argus.ApiUpdateGrafanaConfigsRequest, error) { + req := apiClient.UpdateGrafanaConfigs(ctx, model.InstanceId, model.ProjectId) + payload, err := argusUtils.GetPartialUpdateGrafanaConfigsPayload(ctx, apiClient, model.InstanceId, model.ProjectId, nil, utils.Ptr(false)) + if err != nil { + return req, fmt.Errorf("build request payload: %w", err) + } + req = req.UpdateGrafanaConfigsPayload(*payload) + return req, nil +} diff --git a/internal/cmd/argus/grafana/public-read-access/disable/disable_test.go b/internal/cmd/argus/grafana/public-read-access/disable/disable_test.go new file mode 100644 index 000000000..9ef2296f9 --- /dev/null +++ b/internal/cmd/argus/grafana/public-read-access/disable/disable_test.go @@ -0,0 +1,304 @@ +package disable + +import ( + "context" + "fmt" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + argusUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/argus/utils" + "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/argus" +) + +var projectIdFlag = globalflags.ProjectIdFlag + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &argus.APIClient{} +var testProjectId = uuid.NewString() +var testInstanceId = uuid.NewString() + +type argusClientMocked struct { + getGrafanaConfigsFails bool + getGrafanaConfigsResp *argus.GrafanaConfigs +} + +func (c *argusClientMocked) GetInstanceExecute(ctx context.Context, instanceId, projectId string) (*argus.GetInstanceResponse, error) { + return testClient.GetInstanceExecute(ctx, instanceId, projectId) +} + +func (c *argusClientMocked) UpdateGrafanaConfigs(ctx context.Context, instanceId, projectId string) argus.ApiUpdateGrafanaConfigsRequest { + return testClient.UpdateGrafanaConfigs(ctx, instanceId, projectId) +} + +func (c *argusClientMocked) GetGrafanaConfigsExecute(_ context.Context, _, _ string) (*argus.GrafanaConfigs, error) { + if c.getGrafanaConfigsFails { + return nil, fmt.Errorf("get payload failed") + } + return c.getGrafanaConfigsResp, nil +} + +func fixtureArgValues(mods ...func(argValues []string)) []string { + argValues := []string{ + testInstanceId, + } + 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, + }, + InstanceId: testInstanceId, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureGrafanaConfigs(mods ...func(gc *argus.GrafanaConfigs)) *argus.GrafanaConfigs { + gc := argus.GrafanaConfigs{ + GenericOauth: &argus.GrafanaOauth{ + ApiUrl: utils.Ptr("apiUrl"), + AuthUrl: utils.Ptr("authUrl"), + Enabled: utils.Ptr(true), + Name: utils.Ptr("name"), + OauthClientId: utils.Ptr("oauthClientId"), + OauthClientSecret: utils.Ptr("oauthClientSecret"), + RoleAttributePath: utils.Ptr("roleAttributePath"), + RoleAttributeStrict: utils.Ptr(true), + Scopes: utils.Ptr("scopes"), + TokenUrl: utils.Ptr("tokenUrl"), + UsePkce: utils.Ptr(true), + }, + PublicReadAccess: utils.Ptr(false), + UseStackitSso: utils.Ptr(false), + } + for _, mod := range mods { + mod(&gc) + } + return &gc +} + +func fixturePayload(mods ...func(payload *argus.UpdateGrafanaConfigsPayload)) *argus.UpdateGrafanaConfigsPayload { + payload := &argus.UpdateGrafanaConfigsPayload{ + GenericOauth: argusUtils.ToPayloadGenericOAuth(fixtureGrafanaConfigs().GenericOauth), + PublicReadAccess: utils.Ptr(false), + UseStackitSso: fixtureGrafanaConfigs().UseStackitSso, + } + for _, mod := range mods { + mod(payload) + } + return payload +} + +func fixtureRequest(mods ...func(request *argus.ApiUpdateGrafanaConfigsRequest)) argus.ApiUpdateGrafanaConfigsRequest { + request := testClient.UpdateGrafanaConfigs(testCtx, testInstanceId, testProjectId) + request = request.UpdateGrafanaConfigsPayload(*fixturePayload()) + 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 arg values", + argValues: []string{}, + flagValues: fixtureFlagValues(), + 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: "instance id invalid 1", + argValues: []string{""}, + flagValues: fixtureFlagValues(), + isValid: false, + }, + { + description: "instance id invalid 2", + argValues: []string{"invalid-uuid"}, + 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(cmd, tt.argValues) + 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 + getGrafanaConfigsFails bool + getGrafanaConfigsResp *argus.GrafanaConfigs + isValid bool + expectedRequest argus.ApiUpdateGrafanaConfigsRequest + }{ + { + description: "base", + model: fixtureInputModel(), + getGrafanaConfigsResp: fixtureGrafanaConfigs(), + isValid: true, + expectedRequest: fixtureRequest(), + }, + { + description: "nil generic oauth", + model: fixtureInputModel(), + getGrafanaConfigsResp: fixtureGrafanaConfigs(func(gc *argus.GrafanaConfigs) { + gc.GenericOauth = nil + }), + isValid: true, + expectedRequest: fixtureRequest(func(request *argus.ApiUpdateGrafanaConfigsRequest) { + *request = request.UpdateGrafanaConfigsPayload(*fixturePayload(func(payload *argus.UpdateGrafanaConfigsPayload) { + payload.GenericOauth = nil + })) + }), + }, + { + description: "get grafana configs fails", + model: fixtureInputModel(), + getGrafanaConfigsFails: true, + isValid: false, + }, + { + description: "no grafana configs", + model: fixtureInputModel(), + getGrafanaConfigsResp: nil, + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + client := &argusClientMocked{ + getGrafanaConfigsFails: tt.getGrafanaConfigsFails, + getGrafanaConfigsResp: tt.getGrafanaConfigsResp, + } + request, err := buildRequest(testCtx, tt.model, client) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error building request: %v", err) + } + + 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/argus/grafana/public-read-access/enable/enable.go b/internal/cmd/argus/grafana/public-read-access/enable/enable.go new file mode 100644 index 000000000..df0ccbd3b --- /dev/null +++ b/internal/cmd/argus/grafana/public-read-access/enable/enable.go @@ -0,0 +1,108 @@ +package enable + +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/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/argus/client" + argusUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/argus/utils" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/argus" +) + +const ( + instanceIdArg = "INSTANCE_ID" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + InstanceId string +} + +func NewCmd(p *print.Printer) *cobra.Command { + cmd := &cobra.Command{ + Use: fmt.Sprintf("enable %s", instanceIdArg), + Short: "Enables public read access for Grafana on Argus instances", + Long: fmt.Sprintf("%s\n%s", + "Enables public read access for Grafana on Argus instances.", + "When enabled, anyone can access the Grafana dashboards of the instance without logging in. Otherwise, a login is required.", + ), + Args: args.SingleArg(instanceIdArg, utils.ValidateUUID), + Example: examples.Build( + examples.NewExample( + `Enable public read access for Grafana on an Argus instance with ID "xxx"`, + "$ stackit argus grafana public-read-access enable xxx"), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(p) + if err != nil { + return err + } + + instanceLabel, err := argusUtils.GetInstanceName(ctx, apiClient, model.InstanceId, model.ProjectId) + if err != nil || instanceLabel == "" { + instanceLabel = model.InstanceId + } + + if !model.AssumeYes { + prompt := fmt.Sprintf("Are you sure you want to enable Grafana public read access for instance %q?", instanceLabel) + err = p.PromptForConfirmation(prompt) + if err != nil { + return err + } + } + + // Call API + req, err := buildRequest(ctx, model, apiClient) + if err != nil { + return fmt.Errorf("build request: %w", err) + } + _, err = req.Execute() + if err != nil { + return fmt.Errorf("enable grafana public read access: %w", err) + } + + p.Info("Enabled Grafana public read access for instance %q\n", instanceLabel) + return nil + }, + } + return cmd +} + +func parseInput(cmd *cobra.Command, inputArgs []string) (*inputModel, error) { + instanceId := inputArgs[0] + + globalFlags := globalflags.Parse(cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + + return &inputModel{ + GlobalFlagModel: globalFlags, + InstanceId: instanceId, + }, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient argusUtils.ArgusClient) (argus.ApiUpdateGrafanaConfigsRequest, error) { + req := apiClient.UpdateGrafanaConfigs(ctx, model.InstanceId, model.ProjectId) + payload, err := argusUtils.GetPartialUpdateGrafanaConfigsPayload(ctx, apiClient, model.InstanceId, model.ProjectId, nil, utils.Ptr(true)) + if err != nil { + return req, fmt.Errorf("build request payload: %w", err) + } + req = req.UpdateGrafanaConfigsPayload(*payload) + return req, nil +} diff --git a/internal/cmd/argus/grafana/public-read-access/enable/enable_test.go b/internal/cmd/argus/grafana/public-read-access/enable/enable_test.go new file mode 100644 index 000000000..1e0794bdd --- /dev/null +++ b/internal/cmd/argus/grafana/public-read-access/enable/enable_test.go @@ -0,0 +1,304 @@ +package enable + +import ( + "context" + "fmt" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + argusUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/argus/utils" + "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/argus" +) + +var projectIdFlag = globalflags.ProjectIdFlag + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &argus.APIClient{} +var testProjectId = uuid.NewString() +var testInstanceId = uuid.NewString() + +type argusClientMocked struct { + getGrafanaConfigsFails bool + getGrafanaConfigsResp *argus.GrafanaConfigs +} + +func (c *argusClientMocked) GetInstanceExecute(ctx context.Context, instanceId, projectId string) (*argus.GetInstanceResponse, error) { + return testClient.GetInstanceExecute(ctx, instanceId, projectId) +} + +func (c *argusClientMocked) UpdateGrafanaConfigs(ctx context.Context, instanceId, projectId string) argus.ApiUpdateGrafanaConfigsRequest { + return testClient.UpdateGrafanaConfigs(ctx, instanceId, projectId) +} + +func (c *argusClientMocked) GetGrafanaConfigsExecute(_ context.Context, _, _ string) (*argus.GrafanaConfigs, error) { + if c.getGrafanaConfigsFails { + return nil, fmt.Errorf("get payload failed") + } + return c.getGrafanaConfigsResp, nil +} + +func fixtureArgValues(mods ...func(argValues []string)) []string { + argValues := []string{ + testInstanceId, + } + 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, + }, + InstanceId: testInstanceId, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureGrafanaConfigs(mods ...func(gc *argus.GrafanaConfigs)) *argus.GrafanaConfigs { + gc := argus.GrafanaConfigs{ + GenericOauth: &argus.GrafanaOauth{ + ApiUrl: utils.Ptr("apiUrl"), + AuthUrl: utils.Ptr("authUrl"), + Enabled: utils.Ptr(true), + Name: utils.Ptr("name"), + OauthClientId: utils.Ptr("oauthClientId"), + OauthClientSecret: utils.Ptr("oauthClientSecret"), + RoleAttributePath: utils.Ptr("roleAttributePath"), + RoleAttributeStrict: utils.Ptr(true), + Scopes: utils.Ptr("scopes"), + TokenUrl: utils.Ptr("tokenUrl"), + UsePkce: utils.Ptr(true), + }, + PublicReadAccess: utils.Ptr(false), + UseStackitSso: utils.Ptr(false), + } + for _, mod := range mods { + mod(&gc) + } + return &gc +} + +func fixturePayload(mods ...func(payload *argus.UpdateGrafanaConfigsPayload)) *argus.UpdateGrafanaConfigsPayload { + payload := &argus.UpdateGrafanaConfigsPayload{ + GenericOauth: argusUtils.ToPayloadGenericOAuth(fixtureGrafanaConfigs().GenericOauth), + PublicReadAccess: utils.Ptr(true), + UseStackitSso: fixtureGrafanaConfigs().UseStackitSso, + } + for _, mod := range mods { + mod(payload) + } + return payload +} + +func fixtureRequest(mods ...func(request *argus.ApiUpdateGrafanaConfigsRequest)) argus.ApiUpdateGrafanaConfigsRequest { + request := testClient.UpdateGrafanaConfigs(testCtx, testInstanceId, testProjectId) + request = request.UpdateGrafanaConfigsPayload(*fixturePayload()) + 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 arg values", + argValues: []string{}, + flagValues: fixtureFlagValues(), + 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: "instance id invalid 1", + argValues: []string{""}, + flagValues: fixtureFlagValues(), + isValid: false, + }, + { + description: "instance id invalid 2", + argValues: []string{"invalid-uuid"}, + 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(cmd, tt.argValues) + 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 + getGrafanaConfigsFails bool + getGrafanaConfigsResp *argus.GrafanaConfigs + isValid bool + expectedRequest argus.ApiUpdateGrafanaConfigsRequest + }{ + { + description: "base", + model: fixtureInputModel(), + getGrafanaConfigsResp: fixtureGrafanaConfigs(), + isValid: true, + expectedRequest: fixtureRequest(), + }, + { + description: "nil generic oauth", + model: fixtureInputModel(), + getGrafanaConfigsResp: fixtureGrafanaConfigs(func(gc *argus.GrafanaConfigs) { + gc.GenericOauth = nil + }), + isValid: true, + expectedRequest: fixtureRequest(func(request *argus.ApiUpdateGrafanaConfigsRequest) { + *request = request.UpdateGrafanaConfigsPayload(*fixturePayload(func(payload *argus.UpdateGrafanaConfigsPayload) { + payload.GenericOauth = nil + })) + }), + }, + { + description: "get grafana configs fails", + model: fixtureInputModel(), + getGrafanaConfigsFails: true, + isValid: false, + }, + { + description: "no grafana configs", + model: fixtureInputModel(), + getGrafanaConfigsResp: nil, + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + client := &argusClientMocked{ + getGrafanaConfigsFails: tt.getGrafanaConfigsFails, + getGrafanaConfigsResp: tt.getGrafanaConfigsResp, + } + request, err := buildRequest(testCtx, tt.model, client) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error building request: %v", err) + } + + 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/argus/grafana/public-read-access/public_read_access.go b/internal/cmd/argus/grafana/public-read-access/public_read_access.go new file mode 100644 index 000000000..736a6e265 --- /dev/null +++ b/internal/cmd/argus/grafana/public-read-access/public_read_access.go @@ -0,0 +1,33 @@ +package publicreadaccess + +import ( + "fmt" + + "github.com/stackitcloud/stackit-cli/internal/cmd/argus/grafana/public-read-access/disable" + "github.com/stackitcloud/stackit-cli/internal/cmd/argus/grafana/public-read-access/enable" + "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: "public-read-access", + Short: "Enable or disable public read access for Grafana in Argus instances", + Long: fmt.Sprintf("%s\n%s", + "Enable or disable public read access for Grafana in Argus instances.", + "When enabled, anyone can access the Grafana dashboards of the instance without logging in. Otherwise, a login is required.", + ), + Args: args.NoArgs, + Run: utils.CmdHelp, + } + addSubcommands(cmd, p) + return cmd +} + +func addSubcommands(cmd *cobra.Command, p *print.Printer) { + cmd.AddCommand(enable.NewCmd(p)) + cmd.AddCommand(disable.NewCmd(p)) +} diff --git a/internal/cmd/argus/grafana/single-sign-on/disable/disable.go b/internal/cmd/argus/grafana/single-sign-on/disable/disable.go index 090320e4c..1a69a516c 100644 --- a/internal/cmd/argus/grafana/single-sign-on/disable/disable.go +++ b/internal/cmd/argus/grafana/single-sign-on/disable/disable.go @@ -7,7 +7,6 @@ import ( "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/argus/client" @@ -19,7 +18,7 @@ import ( ) const ( - instanceIdFlag = "instance-id" + instanceIdArg = "INSTANCE_ID" ) type inputModel struct { @@ -29,21 +28,21 @@ type inputModel struct { func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ - Use: "disable", + Use: fmt.Sprintf("disable %s", instanceIdArg), Short: "Disables single sign-on for Grafana on Argus instances", Long: fmt.Sprintf("%s\n%s", "Disables single sign-on for Grafana on Argus instances.", "When disabled for an instance, the generic OAuth2 authentication is used for that instance.", ), - Args: args.NoArgs, + Args: args.SingleArg(instanceIdArg, utils.ValidateUUID), Example: examples.Build( examples.NewExample( `Disable single sign-on for Grafana on an Argus instance with ID "xxx"`, - "$ stackit argus grafana single-sign-on disable --instance-id xxx"), + "$ stackit argus grafana single-sign-on disable xxx"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() - model, err := parseInput(cmd) + model, err := parseInput(cmd, args) if err != nil { return err } @@ -60,7 +59,7 @@ func NewCmd(p *print.Printer) *cobra.Command { } if !model.AssumeYes { - prompt := fmt.Sprintf("Are you sure you want to disable single sign-on for instance %q?", instanceLabel) + prompt := fmt.Sprintf("Are you sure you want to disable single sign-on for Grafana for instance %q?", instanceLabel) err = p.PromptForConfirmation(prompt) if err != nil { return err @@ -74,25 +73,19 @@ func NewCmd(p *print.Printer) *cobra.Command { } _, err = req.Execute() if err != nil { - return fmt.Errorf("disable single sign-on: %w", err) + return fmt.Errorf("disable single sign-on for grafana: %w", err) } - p.Info("Disabled single sign-on for instance %q\n", instanceLabel) + p.Info("Disabled single sign-on for Grafana for instance %q\n", instanceLabel) return nil }, } - configureFlags(cmd) return cmd } -func configureFlags(cmd *cobra.Command) { - cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "Instance ID") +func parseInput(cmd *cobra.Command, inputArgs []string) (*inputModel, error) { + instanceId := inputArgs[0] - err := flags.MarkFlagsRequired(cmd, instanceIdFlag) - cobra.CheckErr(err) -} - -func parseInput(cmd *cobra.Command) (*inputModel, error) { globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, &errors.ProjectIdError{} @@ -100,7 +93,7 @@ func parseInput(cmd *cobra.Command) (*inputModel, error) { return &inputModel{ GlobalFlagModel: globalFlags, - InstanceId: flags.FlagToStringValue(cmd, instanceIdFlag), + InstanceId: instanceId, }, nil } diff --git a/internal/cmd/argus/grafana/single-sign-on/disable/disable_test.go b/internal/cmd/argus/grafana/single-sign-on/disable/disable_test.go index b654bbb32..32cdd0449 100644 --- a/internal/cmd/argus/grafana/single-sign-on/disable/disable_test.go +++ b/internal/cmd/argus/grafana/single-sign-on/disable/disable_test.go @@ -44,10 +44,19 @@ func (c *argusClientMocked) GetGrafanaConfigsExecute(_ context.Context, _, _ str return c.getGrafanaConfigsResp, nil } +func fixtureArgValues(mods ...func(argValues []string)) []string { + argValues := []string{ + testInstanceId, + } + 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, - instanceIdFlag: testInstanceId, + projectIdFlag: testProjectId, } for _, mod := range mods { mod(flagValues) @@ -117,23 +126,27 @@ func fixtureRequest(mods ...func(request *argus.ApiUpdateGrafanaConfigsRequest)) 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", - flagValues: map[string]string{}, + description: "no arg values", + argValues: []string{}, + flagValues: fixtureFlagValues(), isValid: false, }, { description: "project id missing", + argValues: fixtureArgValues(), flagValues: fixtureFlagValues(func(flagValues map[string]string) { delete(flagValues, projectIdFlag) }), @@ -141,6 +154,7 @@ func TestParseInput(t *testing.T) { }, { description: "project id invalid 1", + argValues: fixtureArgValues(), flagValues: fixtureFlagValues(func(flagValues map[string]string) { flagValues[projectIdFlag] = "" }), @@ -148,11 +162,24 @@ func TestParseInput(t *testing.T) { }, { description: "project id invalid 2", + argValues: fixtureArgValues(), flagValues: fixtureFlagValues(func(flagValues map[string]string) { flagValues[projectIdFlag] = "invalid-uuid" }), isValid: false, }, + { + description: "instance id invalid 1", + argValues: []string{""}, + flagValues: fixtureFlagValues(), + isValid: false, + }, + { + description: "instance id invalid 2", + argValues: []string{"invalid-uuid"}, + flagValues: fixtureFlagValues(), + isValid: false, + }, } for _, tt := range tests { @@ -173,6 +200,14 @@ func TestParseInput(t *testing.T) { } } + 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 { @@ -181,7 +216,7 @@ func TestParseInput(t *testing.T) { t.Fatalf("error validating flags: %v", err) } - model, err := parseInput(cmd) + model, err := parseInput(cmd, tt.argValues) if err != nil { if !tt.isValid { return diff --git a/internal/cmd/argus/grafana/single-sign-on/enable/enable.go b/internal/cmd/argus/grafana/single-sign-on/enable/enable.go index 711e68fb1..7c6c1179f 100644 --- a/internal/cmd/argus/grafana/single-sign-on/enable/enable.go +++ b/internal/cmd/argus/grafana/single-sign-on/enable/enable.go @@ -7,7 +7,6 @@ import ( "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/argus/client" @@ -19,7 +18,7 @@ import ( ) const ( - instanceIdFlag = "instance-id" + instanceIdArg = "INSTANCE_ID" ) type inputModel struct { @@ -29,21 +28,21 @@ type inputModel struct { func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ - Use: "enable", + Use: fmt.Sprintf("enable %s", instanceIdArg), Short: "Enables single sign-on for Grafana on Argus instances", Long: fmt.Sprintf("%s\n%s", "Enables single sign-on for Grafana on Argus instances.", "When enabled for an instance, overwrites the generic OAuth2 authentication and configures STACKIT single sign-on for that instance.", ), - Args: args.NoArgs, + Args: args.SingleArg(instanceIdArg, utils.ValidateUUID), Example: examples.Build( examples.NewExample( `Enable single sign-on for Grafana on an Argus instance with ID "xxx"`, - "$ stackit argus grafana single-sign-on enable --instance-id xxx"), + "$ stackit argus grafana single-sign-on enable xxx"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() - model, err := parseInput(cmd) + model, err := parseInput(cmd, args) if err != nil { return err } @@ -60,7 +59,7 @@ func NewCmd(p *print.Printer) *cobra.Command { } if !model.AssumeYes { - prompt := fmt.Sprintf("Are you sure you want to enable single sign-on for instance %q?", instanceLabel) + prompt := fmt.Sprintf("Are you sure you want to enable single sign-on for Grafana for instance %q?", instanceLabel) err = p.PromptForConfirmation(prompt) if err != nil { return err @@ -74,25 +73,19 @@ func NewCmd(p *print.Printer) *cobra.Command { } _, err = req.Execute() if err != nil { - return fmt.Errorf("enable single sign-on: %w", err) + return fmt.Errorf("enable single sign-on for grafana: %w", err) } - p.Info("Enabled single sign-on for instance %q\n", instanceLabel) + p.Info("Enabled single sign-on for Grafana for instance %q\n", instanceLabel) return nil }, } - configureFlags(cmd) return cmd } -func configureFlags(cmd *cobra.Command) { - cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "Instance ID") +func parseInput(cmd *cobra.Command, inputArgs []string) (*inputModel, error) { + instanceId := inputArgs[0] - err := flags.MarkFlagsRequired(cmd, instanceIdFlag) - cobra.CheckErr(err) -} - -func parseInput(cmd *cobra.Command) (*inputModel, error) { globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, &errors.ProjectIdError{} @@ -100,7 +93,7 @@ func parseInput(cmd *cobra.Command) (*inputModel, error) { return &inputModel{ GlobalFlagModel: globalFlags, - InstanceId: flags.FlagToStringValue(cmd, instanceIdFlag), + InstanceId: instanceId, }, nil } diff --git a/internal/cmd/argus/grafana/single-sign-on/enable/enable_test.go b/internal/cmd/argus/grafana/single-sign-on/enable/enable_test.go index 7544b81b4..c95378136 100644 --- a/internal/cmd/argus/grafana/single-sign-on/enable/enable_test.go +++ b/internal/cmd/argus/grafana/single-sign-on/enable/enable_test.go @@ -44,10 +44,19 @@ func (c *argusClientMocked) GetGrafanaConfigsExecute(_ context.Context, _, _ str return c.getGrafanaConfigsResp, nil } +func fixtureArgValues(mods ...func(argValues []string)) []string { + argValues := []string{ + testInstanceId, + } + 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, - instanceIdFlag: testInstanceId, + projectIdFlag: testProjectId, } for _, mod := range mods { mod(flagValues) @@ -117,23 +126,27 @@ func fixtureRequest(mods ...func(request *argus.ApiUpdateGrafanaConfigsRequest)) 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", - flagValues: map[string]string{}, + description: "no arg values", + argValues: []string{}, + flagValues: fixtureFlagValues(), isValid: false, }, { description: "project id missing", + argValues: fixtureArgValues(), flagValues: fixtureFlagValues(func(flagValues map[string]string) { delete(flagValues, projectIdFlag) }), @@ -141,6 +154,7 @@ func TestParseInput(t *testing.T) { }, { description: "project id invalid 1", + argValues: fixtureArgValues(), flagValues: fixtureFlagValues(func(flagValues map[string]string) { flagValues[projectIdFlag] = "" }), @@ -148,11 +162,24 @@ func TestParseInput(t *testing.T) { }, { description: "project id invalid 2", + argValues: fixtureArgValues(), flagValues: fixtureFlagValues(func(flagValues map[string]string) { flagValues[projectIdFlag] = "invalid-uuid" }), isValid: false, }, + { + description: "instance id invalid 1", + argValues: []string{""}, + flagValues: fixtureFlagValues(), + isValid: false, + }, + { + description: "instance id invalid 2", + argValues: []string{"invalid-uuid"}, + flagValues: fixtureFlagValues(), + isValid: false, + }, } for _, tt := range tests { @@ -173,6 +200,14 @@ func TestParseInput(t *testing.T) { } } + 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 { @@ -181,7 +216,7 @@ func TestParseInput(t *testing.T) { t.Fatalf("error validating flags: %v", err) } - model, err := parseInput(cmd) + model, err := parseInput(cmd, tt.argValues) if err != nil { if !tt.isValid { return