diff --git a/internal/activity/command/command.go b/internal/activity/command/command.go index bdb44936..1cd314aa 100644 --- a/internal/activity/command/command.go +++ b/internal/activity/command/command.go @@ -1,8 +1,6 @@ package command import ( - "context" - "github.com/nais/cli/internal/activity/command/flag" "github.com/nais/cli/internal/flags" "github.com/nais/cli/internal/validation" @@ -11,15 +9,12 @@ import ( func Activity(parentFlags *flags.GlobalFlags) *naistrix.Command { f := &flag.Activity{GlobalFlags: parentFlags} - return &naistrix.Command{ - Name: "activity", - Title: "List team activity.", - Description: "View recent activity across all resources in a team, such as deployments, configuration changes, and other events.", - StickyFlags: f, - ValidateFunc: func(context.Context, *naistrix.Arguments) error { - return validation.CheckTeam(f.Team) - }, + Name: "activity", + Title: "List team activity.", + Description: "View recent activity across all resources in a team, such as deployments, configuration changes, and other events.", + StickyFlags: f, + ValidateFunc: validation.RequireTeam(f), SubCommands: []*naistrix.Command{ list(f), }, diff --git a/internal/app/command/command.go b/internal/app/command/command.go index 1103ec5e..87114eed 100644 --- a/internal/app/command/command.go +++ b/internal/app/command/command.go @@ -1,8 +1,6 @@ package command import ( - "context" - "github.com/nais/cli/internal/app/command/flag" "github.com/nais/cli/internal/flags" "github.com/nais/cli/internal/validation" @@ -12,14 +10,12 @@ import ( func App(parentFlags *flags.GlobalFlags) *naistrix.Command { flags := &flag.App{GlobalFlags: parentFlags} return &naistrix.Command{ - Name: "app", - Aliases: []string{"apps", "application", "applications"}, - Title: "Interact with applications.", - Description: "Commands for managing and inspecting your team's applications, including listing, viewing activity and issues, restarting, and tailing logs.", - ValidateFunc: func(context.Context, *naistrix.Arguments) error { - return validation.CheckTeam(flags.Team) - }, - StickyFlags: flags, + Name: "app", + Aliases: []string{"apps", "application", "applications"}, + Title: "Interact with applications.", + Description: "Commands for managing and inspecting your team's applications, including listing, viewing activity and issues, restarting, and tailing logs.", + ValidateFunc: validation.RequireTeam(flags), + StickyFlags: flags, SubCommands: []*naistrix.Command{ list(flags), activity(flags), diff --git a/internal/apply/command/apply.go b/internal/apply/command/apply.go index 448b53e6..ecf00151 100644 --- a/internal/apply/command/apply.go +++ b/internal/apply/command/apply.go @@ -22,21 +22,15 @@ func Apply(parentFlags *alpha.Alpha) *naistrix.Command { }, AutoCompleteExtensions: []string{"yaml", "yml"}, Flags: flags, - ValidateFunc: func(_ context.Context, args *naistrix.Arguments) error { - if args.Get("file") == "" { - return fmt.Errorf("file cannot be empty") - } - - if err := validation.CheckEnvironment(string(flags.Environment)); err != nil { - return err - } - - if err := validation.CheckTeam(flags.Team); err != nil { - return err - } - - return nil - }, + ValidateFunc: naistrix.ValidateFuncs( + validation.RequireTeamAndEnvironment(flags), + func(ctx context.Context, args *naistrix.Arguments) error { + if args.Get("file") == "" { + return fmt.Errorf("file cannot be empty") + } + return nil + }, + ), RunFunc: func(ctx context.Context, args *naistrix.Arguments, out *naistrix.OutputWriter) error { return apply.Run(ctx, string(flags.Environment), args.Get("file"), flags, out) }, diff --git a/internal/config/command/activity.go b/internal/config/command/activity.go index 63a04f53..236f5219 100644 --- a/internal/config/command/activity.go +++ b/internal/config/command/activity.go @@ -6,6 +6,7 @@ import ( activityutil "github.com/nais/cli/internal/activity" "github.com/nais/cli/internal/config" "github.com/nais/cli/internal/config/command/flag" + "github.com/nais/cli/internal/validation" "github.com/nais/naistrix" "github.com/nais/naistrix/output" ) @@ -16,18 +17,17 @@ func activity(parentFlags *flag.Config) *naistrix.Command { Output: "table", Limit: 20, } - return &naistrix.Command{ Name: "activity", Title: "Show activity for a config.", Description: "Show the activity log for a specific configuration, including creation, updates, and deletions. Optionally filter by activity type.", Args: defaultArgs, Flags: f, + ValidateFunc: naistrix.ValidateFuncs( + validation.RequireTeamAndEnvironment(f), + validateArgs, + ), RunFunc: func(ctx context.Context, args *naistrix.Arguments, out *naistrix.OutputWriter) error { - if err := validateArgs(args); err != nil { - return err - } - activityTypes, err := activityutil.ParseActivityTypes(f.ActivityType) if err != nil { return err diff --git a/internal/config/command/command.go b/internal/config/command/command.go index 4ee1089a..89c9eba0 100644 --- a/internal/config/command/command.go +++ b/internal/config/command/command.go @@ -15,13 +15,11 @@ import ( func Config(parentFlags *flags.GlobalFlags) *naistrix.Command { f := &flag.Config{GlobalFlags: parentFlags} return &naistrix.Command{ - Name: "config", - Title: "Manage config for a team.", - Description: "Commands for listing, creating, viewing, updating, and deleting configuration values for a team across environments.", - StickyFlags: f, - ValidateFunc: func(context.Context, *naistrix.Arguments) error { - return validation.CheckTeam(f.Team) - }, + Name: "config", + Title: "Manage config for a team.", + Description: "Commands for listing, creating, viewing, updating, and deleting configuration values for a team across environments.", + StickyFlags: f, + ValidateFunc: validation.RequireTeam(f), SubCommands: []*naistrix.Command{ list(f), activity(f), @@ -38,7 +36,7 @@ var defaultArgs = []naistrix.Argument{ {Name: "name"}, } -func validateArgs(args *naistrix.Arguments) error { +func validateArgs(_ context.Context, args *naistrix.Arguments) error { if args.Len() != 1 { return fmt.Errorf("expected 1 argument, got %d", args.Len()) } diff --git a/internal/config/command/create.go b/internal/config/command/create.go index 62eee1ac..f8c0b3dd 100644 --- a/internal/config/command/create.go +++ b/internal/config/command/create.go @@ -18,12 +18,10 @@ func create(parentFlags *flag.Config) *naistrix.Command { Description: "This command creates a new empty config in a team environment.", Flags: f, Args: defaultArgs, - ValidateFunc: func(_ context.Context, args *naistrix.Arguments) error { - if err := validation.CheckEnvironment(string(f.Environment)); err != nil { - return err - } - return validateArgs(args) - }, + ValidateFunc: naistrix.ValidateFuncs( + validation.RequireEnvironment(f), + validateArgs, + ), Examples: []naistrix.Example{ { Description: "Create a config named my-config in environment dev.", diff --git a/internal/config/command/delete.go b/internal/config/command/delete.go index fa9ef1f3..7ae1dd51 100644 --- a/internal/config/command/delete.go +++ b/internal/config/command/delete.go @@ -19,15 +19,10 @@ func deleteConfig(parentFlags *flag.Config) *naistrix.Command { Description: "This command deletes a config and all its values.", Flags: f, Args: defaultArgs, - ValidateFunc: func(_ context.Context, args *naistrix.Arguments) error { - if err := validateSingleEnvironmentFlagUsage(); err != nil { - return err - } - if err := validation.CheckEnvironment(string(f.Environment)); err != nil { - return err - } - return validateArgs(args) - }, + ValidateFunc: naistrix.ValidateFuncs( + validation.RequireEnvironment(f), + validateArgs, + ), AutoCompleteFunc: autoCompleteConfigNames(parentFlags), Examples: []naistrix.Example{ { diff --git a/internal/config/command/environment.go b/internal/config/command/environment.go index 24e22372..ef494db5 100644 --- a/internal/config/command/environment.go +++ b/internal/config/command/environment.go @@ -3,12 +3,10 @@ package command import ( "context" "fmt" - "os" "slices" "sort" "strings" - "github.com/nais/cli/internal/cliflags" "github.com/nais/cli/internal/config" ) @@ -45,14 +43,3 @@ func selectConfigEnvironment(team, name, provided string, envs []string) (string return "", fmt.Errorf("config %q exists in multiple environments (%s); specify -e, --environment", name, strings.Join(envs, ", ")) } } - -func validateSingleEnvironmentFlagUsage() error { - if countEnvironmentFlagsInCLIArgs() > 1 { - return fmt.Errorf("only one -e, --environment flag may be provided") - } - return nil -} - -func countEnvironmentFlagsInCLIArgs() int { - return cliflags.CountFlagOccurrences(os.Args, "-e", "--environment") -} diff --git a/internal/config/command/get.go b/internal/config/command/get.go index 324cfd63..c94b5858 100644 --- a/internal/config/command/get.go +++ b/internal/config/command/get.go @@ -38,26 +38,19 @@ func get(parentFlags *flag.Config) *naistrix.Command { Description: "This command shows details about a config, including its key-value pairs, workloads using it, and last modification info.", Flags: f, Args: defaultArgs, - ValidateFunc: func(_ context.Context, args *naistrix.Arguments) error { - if err := validateSingleEnvironmentFlagUsage(); err != nil { - return err - } - if providedEnvironment := string(f.Environment); providedEnvironment != "" { - if err := validation.CheckEnvironment(providedEnvironment); err != nil { - return err + ValidateFunc: naistrix.ValidateFuncs( + validation.RequireEnvironment(f), + validateArgs, + func(_ context.Context, args *naistrix.Arguments) error { + if f.ToFile != "" && f.Key == "" { + return fmt.Errorf("--to-file requires --key to specify which key to extract") } - } - if err := validateArgs(args); err != nil { - return err - } - if f.ToFile != "" && f.Key == "" { - return fmt.Errorf("--to-file requires --key to specify which key to extract") - } - if f.Key != "" && f.ToFile == "" { - return fmt.Errorf("--key is only used with --to-file") - } - return nil - }, + if f.Key != "" && f.ToFile == "" { + return fmt.Errorf("--key is only used with --to-file") + } + return nil + }, + ), AutoCompleteFunc: autoCompleteConfigNames(parentFlags), Examples: []naistrix.Example{ { diff --git a/internal/config/command/set.go b/internal/config/command/set.go index 29ef6de7..580311c7 100644 --- a/internal/config/command/set.go +++ b/internal/config/command/set.go @@ -24,39 +24,34 @@ func set(parentFlags *flag.Config) *naistrix.Command { Description: "Set a key-value pair in a config. If the key already exists, its value is updated. If the key does not exist, it is added. Updating a value will cause a restart of workloads referencing the config.", Flags: f, Args: defaultArgs, - ValidateFunc: func(_ context.Context, args *naistrix.Arguments) error { - if err := validateSingleEnvironmentFlagUsage(); err != nil { - return err - } - if err := validation.CheckEnvironment(string(f.Environment)); err != nil { - return err - } - if err := validateArgs(args); err != nil { - return err - } - if f.Key == "" { - return fmt.Errorf("--key is required") - } + ValidateFunc: naistrix.ValidateFuncs( + validation.RequireEnvironment(f), + validateArgs, + func(_ context.Context, args *naistrix.Arguments) error { + if f.Key == "" { + return fmt.Errorf("--key is required") + } - // Count the number of value sources provided - sources := 0 - if f.Value != "" { - sources++ - } - if f.ValueFromStdin { - sources++ - } - if f.ValueFromFile != "" { - sources++ - } - if sources == 0 { - return fmt.Errorf("--value, --value-from-stdin, or --value-from-file is required") - } - if sources > 1 { - return fmt.Errorf("--value, --value-from-stdin, and --value-from-file are mutually exclusive") - } - return nil - }, + // Count the number of value sources provided + sources := 0 + if f.Value != "" { + sources++ + } + if f.ValueFromStdin { + sources++ + } + if f.ValueFromFile != "" { + sources++ + } + if sources == 0 { + return fmt.Errorf("--value, --value-from-stdin, or --value-from-file is required") + } + if sources > 1 { + return fmt.Errorf("--value, --value-from-stdin, and --value-from-file are mutually exclusive") + } + return nil + }, + ), AutoCompleteFunc: autoCompleteConfigNames(parentFlags), Examples: []naistrix.Example{ { diff --git a/internal/config/command/unset.go b/internal/config/command/unset.go index 3c480b47..43eaa6ed 100644 --- a/internal/config/command/unset.go +++ b/internal/config/command/unset.go @@ -19,21 +19,16 @@ func unset(parentFlags *flag.Config) *naistrix.Command { Description: "This command removes a key-value pair from a config.", Flags: f, Args: defaultArgs, - ValidateFunc: func(_ context.Context, args *naistrix.Arguments) error { - if err := validateSingleEnvironmentFlagUsage(); err != nil { - return err - } - if err := validation.CheckEnvironment(string(f.Environment)); err != nil { - return err - } - if err := validateArgs(args); err != nil { - return err - } - if f.Key == "" { - return fmt.Errorf("--key is required") - } - return nil - }, + ValidateFunc: naistrix.ValidateFuncs( + validation.RequireEnvironment(f), + validateArgs, + func(_ context.Context, args *naistrix.Arguments) error { + if f.Key == "" { + return fmt.Errorf("--key is required") + } + return nil + }, + ), AutoCompleteFunc: autoCompleteConfigNames(parentFlags), Examples: []naistrix.Example{ { diff --git a/internal/debug/command.go b/internal/debug/command.go index 1e9bf3c8..39731aa5 100644 --- a/internal/debug/command.go +++ b/internal/debug/command.go @@ -11,8 +11,8 @@ import ( const debugImageDefault = "europe-north1-docker.pkg.dev/nais-io/nais/images/debug:latest" -func Run(workloadName string, flags *flag.Debug, out *naistrix.OutputWriter) error { - clientSet, err := SetupClient(flags.DebugSticky) +func Run(workloadName, team, environment string, flags *flag.Debug, out *naistrix.OutputWriter) error { + clientSet, err := SetupClient(team, environment) if err != nil { return err } @@ -25,16 +25,10 @@ func Run(workloadName string, flags *flag.Debug, out *naistrix.OutputWriter) err return nil } -func SetupClient(flags *flag.DebugSticky) (kubernetes.Interface, error) { - client := k8s.SetupControllerRuntimeClient(k8s.WithKubeContext(string(flags.Environment))) - - team, err := flags.RequiredTeam() - if err != nil { - return nil, err - } +func SetupClient(team, environment string) (kubernetes.Interface, error) { + client := k8s.SetupControllerRuntimeClient(k8s.WithKubeContext(environment)) client.CurrentNamespace = team - - clientSet, err := k8s.SetupClientGo(string(flags.Environment)) + clientSet, err := k8s.SetupClientGo(environment) if err != nil { return nil, err } diff --git a/internal/debug/command/debug.go b/internal/debug/command/debug.go index cf38b5a4..f51e3833 100644 --- a/internal/debug/command/debug.go +++ b/internal/debug/command/debug.go @@ -2,24 +2,18 @@ package command import ( "context" - "fmt" "github.com/MakeNowJust/heredoc/v2" "github.com/nais/cli/internal/debug" "github.com/nais/cli/internal/debug/command/flag" "github.com/nais/cli/internal/flags" + "github.com/nais/cli/internal/validation" "github.com/nais/naistrix" ) func Debug(parentFlags *flags.GlobalFlags) *naistrix.Command { - stickyFlags := &flag.DebugSticky{ - GlobalFlags: parentFlags, - } - - debugFlags := &flag.Debug{ - DebugSticky: stickyFlags, - } - + stickyFlags := &flag.DebugSticky{GlobalFlags: parentFlags} + debugFlags := &flag.Debug{DebugSticky: stickyFlags} return &naistrix.Command{ Name: "debug", Title: "Create and attach to a debug container.", @@ -33,19 +27,11 @@ func Debug(parentFlags *flags.GlobalFlags) *naistrix.Command { Args: []naistrix.Argument{ {Name: "app_name"}, }, - Flags: debugFlags, - StickyFlags: stickyFlags, - ValidateFunc: func(ctx context.Context, args *naistrix.Arguments) error { - if _, err := debugFlags.RequiredTeam(); err != nil { - return err - } - if debugFlags.Environment == "" { - return fmt.Errorf("the -e, --environment flag is required") - } - return nil - }, + Flags: debugFlags, + StickyFlags: stickyFlags, + ValidateFunc: validation.RequireTeamAndEnvironment(debugFlags), RunFunc: func(ctx context.Context, args *naistrix.Arguments, out *naistrix.OutputWriter) error { - return debug.Run(args.Get("app_name"), debugFlags, out) + return debug.Run(args.Get("app_name"), debugFlags.Team, string(debugFlags.Environment), debugFlags, out) }, } } diff --git a/internal/flags/flag.go b/internal/flags/flag.go index 87b007d9..30611c1a 100644 --- a/internal/flags/flag.go +++ b/internal/flags/flag.go @@ -1,8 +1,6 @@ package flags import ( - "fmt" - "github.com/nais/naistrix" ) @@ -16,9 +14,12 @@ type AdditionalFlags struct { Environment Environment `name:"environment" short:"e" usage:"Specify the |environment| to use for this command. Overrides the default environment from configuration."` } -func (a AdditionalFlags) RequiredTeam() (string, error) { - if a.Team == "" { - return "", fmt.Errorf("team flag is required (use --team flag)") - } - return a.Team, nil +// HasTeam returns true if the value is not nil and that the [AdditionalFlags.Team] field is not empty. +func (a *AdditionalFlags) HasTeam() bool { + return a != nil && a.Team != "" +} + +// HasEnvironment returns true if the value is not nil and that the [AdditionalFlags.Environment] field is not empty. +func (a *AdditionalFlags) HasEnvironment() bool { + return a != nil && a.Environment != "" } diff --git a/internal/issues/command/command.go b/internal/issues/command/command.go index 1a7f48eb..522fe63e 100644 --- a/internal/issues/command/command.go +++ b/internal/issues/command/command.go @@ -1,8 +1,6 @@ package command import ( - "context" - "github.com/nais/cli/internal/flags" "github.com/nais/cli/internal/issues/command/flag" "github.com/nais/cli/internal/validation" @@ -12,14 +10,12 @@ import ( func Issues(parentFlags *flags.GlobalFlags) *naistrix.Command { flags := &flag.Issues{GlobalFlags: parentFlags} return &naistrix.Command{ - Name: "issues", - Aliases: []string{"issue"}, - Title: "Manage issues.", - Description: "Commands for listing and managing critical issues detected for your team's workloads.", - StickyFlags: flags, - ValidateFunc: func(context.Context, *naistrix.Arguments) error { - return validation.CheckTeam(flags.Team) - }, + Name: "issues", + Aliases: []string{"issue"}, + Title: "Manage issues.", + Description: "Commands for listing and managing critical issues detected for your team's workloads.", + StickyFlags: flags, + ValidateFunc: validation.RequireTeam(flags), SubCommands: []*naistrix.Command{ listIssues(flags), }, diff --git a/internal/job/command/command.go b/internal/job/command/command.go index eec6ccfe..eaff832b 100644 --- a/internal/job/command/command.go +++ b/internal/job/command/command.go @@ -1,8 +1,6 @@ package command import ( - "context" - "github.com/nais/cli/internal/flags" "github.com/nais/cli/internal/job/command/flag" "github.com/nais/cli/internal/validation" @@ -12,14 +10,12 @@ import ( func Job(parentFlags *flags.GlobalFlags) *naistrix.Command { flags := &flag.Job{GlobalFlags: parentFlags} return &naistrix.Command{ - Name: "job", - Aliases: []string{"jobs"}, - Title: "Interact with jobs.", - Description: "Commands for managing and inspecting your team's jobs, including listing, viewing activity and issues, triggering runs, and tailing logs.", - ValidateFunc: func(context.Context, *naistrix.Arguments) error { - return validation.CheckTeam(flags.Team) - }, - StickyFlags: flags, + Name: "job", + Aliases: []string{"jobs"}, + Title: "Interact with jobs.", + Description: "Commands for managing and inspecting your team's jobs, including listing, viewing activity and issues, triggering runs, and tailing logs.", + ValidateFunc: validation.RequireTeam(flags), + StickyFlags: flags, SubCommands: []*naistrix.Command{ list(flags), activity(flags), diff --git a/internal/kafka/command/command.go b/internal/kafka/command/command.go index 22cf5702..838cf13c 100644 --- a/internal/kafka/command/command.go +++ b/internal/kafka/command/command.go @@ -1,8 +1,6 @@ package command import ( - "context" - "github.com/nais/cli/internal/flags" "github.com/nais/cli/internal/kafka/command/flag" "github.com/nais/cli/internal/validation" @@ -11,16 +9,13 @@ import ( func Kafka(parentFlags *flags.GlobalFlags) *naistrix.Command { flags := &flag.Kafka{GlobalFlags: parentFlags} - return &naistrix.Command{ - Name: "kafka", - Aliases: []string{"kafkas"}, - Title: "Interact with Kafka topics.", - Description: "Commands for managing Kafka topics and credentials for your team.", - StickyFlags: flags, - ValidateFunc: func(context.Context, *naistrix.Arguments) error { - return validation.CheckTeam(flags.Team) - }, + Name: "kafka", + Aliases: []string{"kafkas"}, + Title: "Interact with Kafka topics.", + Description: "Commands for managing Kafka topics and credentials for your team.", + StickyFlags: flags, + ValidateFunc: validation.RequireTeam(flags), SubCommands: []*naistrix.Command{ credentials(flags), grantAccess(flags), diff --git a/internal/kafka/command/credentials.go b/internal/kafka/command/credentials.go index d1b001a2..e4d6c9a1 100644 --- a/internal/kafka/command/credentials.go +++ b/internal/kafka/command/credentials.go @@ -23,22 +23,19 @@ func credentials(parentFlags *flag.Kafka) *naistrix.Command { Title: "Create temporary credentials for Kafka.", Description: "Creates temporary credentials for accessing Kafka. Output format can be env (default), kcat, or java. The env format prints environment variables to stdout. The kcat and java formats write configuration files to a temporary directory.", Flags: flags, - ValidateFunc: func(ctx context.Context, args *naistrix.Arguments) error { - if flags.Environment == "" { - return fmt.Errorf("exactly one environment is required, set using -e, --environment flag") - } - if err := validation.CheckEnvironment(string(flags.Environment)); err != nil { - return err - } - if flags.TTL == "" { - return fmt.Errorf("ttl is required, set using --ttl flag (e.g. '1d', '7d')") - } - output := string(flags.Output) - if output != "" && output != "env" && output != "kcat" && output != "java" { - return fmt.Errorf("invalid output format %q, must be one of: env, kcat, java", output) - } - return nil - }, + ValidateFunc: naistrix.ValidateFuncs( + validation.RequireEnvironment(flags), + func(ctx context.Context, args *naistrix.Arguments) error { + if flags.TTL == "" { + return fmt.Errorf("ttl is required, set using --ttl flag (e.g. '1d', '7d')") + } + output := string(flags.Output) + if output != "" && output != "env" && output != "kcat" && output != "java" { + return fmt.Errorf("invalid output format %q, must be one of: env, kcat, java", output) + } + return nil + }, + ), Examples: []naistrix.Example{ { Description: "Create Kafka credentials in environment dev, valid for 1 day, output as environment variables.", diff --git a/internal/kafka/command/grant_access.go b/internal/kafka/command/grant_access.go index 1b95bbb8..d10086ab 100644 --- a/internal/kafka/command/grant_access.go +++ b/internal/kafka/command/grant_access.go @@ -5,6 +5,7 @@ import ( "github.com/nais/cli/internal/aiven" "github.com/nais/cli/internal/kafka/command/flag" + "github.com/nais/cli/internal/validation" nais_kafka "github.com/nais/liberator/pkg/apis/kafka.nais.io/v1" "github.com/nais/naistrix" ) @@ -14,7 +15,6 @@ func grantAccess(parentFlags *flag.Kafka) *naistrix.Command { Kafka: parentFlags, Access: "read", } - return &naistrix.Command{ Name: "grant-access", Title: "Grant a user's service-user access to a Kafka Topic.", @@ -24,10 +24,7 @@ func grantAccess(parentFlags *flag.Kafka) *naistrix.Command { {Name: "username"}, {Name: "topic"}, }, - ValidateFunc: func(context.Context, *naistrix.Arguments) error { - _, err := grantAccessTopicFlags.RequiredTeam() - return err - }, + ValidateFunc: validation.RequireTeam(grantAccessTopicFlags), RunFunc: func(ctx context.Context, args *naistrix.Arguments, out *naistrix.OutputWriter) error { access := grantAccessTopicFlags.Access namespace := grantAccessTopicFlags.Team diff --git a/internal/member/command/command.go b/internal/member/command/command.go index 2f8d4b0d..c0f755b7 100644 --- a/internal/member/command/command.go +++ b/internal/member/command/command.go @@ -1,8 +1,6 @@ package command import ( - "context" - "github.com/nais/cli/internal/flags" "github.com/nais/cli/internal/member/command/flag" "github.com/nais/cli/internal/validation" @@ -12,14 +10,12 @@ import ( func Members(parentFlags *flags.GlobalFlags) *naistrix.Command { flags := &flag.Member{GlobalFlags: parentFlags} return &naistrix.Command{ - Name: "members", - Aliases: []string{"member"}, - Title: "Interact with Nais team members.", - Description: "Commands for listing, adding, removing, and managing roles of team members.", - StickyFlags: flags, - ValidateFunc: func(context.Context, *naistrix.Arguments) error { - return validation.CheckTeam(flags.Team) - }, + Name: "members", + Aliases: []string{"member"}, + Title: "Interact with Nais team members.", + Description: "Commands for listing, adding, removing, and managing roles of team members.", + StickyFlags: flags, + ValidateFunc: validation.RequireTeam(parentFlags), SubCommands: []*naistrix.Command{ list(flags), add(flags), diff --git a/internal/opensearch/command/command.go b/internal/opensearch/command/command.go index 9765fe84..316eee43 100644 --- a/internal/opensearch/command/command.go +++ b/internal/opensearch/command/command.go @@ -3,10 +3,8 @@ package command import ( "context" "fmt" - "os" "sort" - "github.com/nais/cli/internal/cliflags" "github.com/nais/cli/internal/flags" "github.com/nais/cli/internal/naisapi/gql" "github.com/nais/cli/internal/opensearch" @@ -18,14 +16,12 @@ import ( func OpenSearch(parentFlags *flags.GlobalFlags) *naistrix.Command { f := &flag.OpenSearch{GlobalFlags: parentFlags} return &naistrix.Command{ - Name: "opensearch", - Aliases: []string{"opensearches", "os"}, - Title: "Manage OpenSearch instances.", - Description: "Commands for creating, updating, deleting, and inspecting OpenSearch instances and their credentials.", - StickyFlags: f, - ValidateFunc: func(context.Context, *naistrix.Arguments) error { - return validation.CheckTeam(f.Team) - }, + Name: "opensearch", + Aliases: []string{"opensearches", "os"}, + Title: "Manage OpenSearch instances.", + Description: "Commands for creating, updating, deleting, and inspecting OpenSearch instances and their credentials.", + StickyFlags: f, + ValidateFunc: validation.RequireTeam(f), SubCommands: []*naistrix.Command{ create(f), credentials(f), @@ -37,7 +33,7 @@ func OpenSearch(parentFlags *flags.GlobalFlags) *naistrix.Command { } } -func validateArgs(args *naistrix.Arguments) error { +func validateArgs(_ context.Context, args *naistrix.Arguments) error { if args.Len() != 1 { return fmt.Errorf("expected 1 arguments, got %d", args.Len()) } @@ -61,17 +57,6 @@ func autoCompleteOpenSearchNames(ctx context.Context, team, environment string, return nil, "Please provide team to auto-complete OpenSearch instance names. 'nais defaults set team ', or '--team ' flag." } - if environmentFlagOccurrencesFromCLIArgs() > 1 { - return nil, "Please specify exactly one environment to auto-complete OpenSearch instance names. '-e, --environment ' flag." - } - - if environment == "" { - envs := environmentValuesFromCLIArgs() - if len(envs) == 1 { - environment = envs[0] - } - } - if requireEnvironment && environment == "" { return nil, "Please provide environment to auto-complete OpenSearch instance names. '-e, --environment ' flag." } @@ -102,21 +87,6 @@ func autoCompleteOpenSearchNames(ctx context.Context, team, environment string, return names, "Select an OpenSearch instance." } -func environmentValuesFromCLIArgs() []string { - return cliflags.UniqueFlagValues(os.Args, "-e", "--environment") -} - -func environmentFlagOccurrencesFromCLIArgs() int { - return cliflags.CountFlagOccurrences(os.Args, "-e", "--environment") -} - -func validateSingleEnvironmentFlagUsage() error { - if environmentFlagOccurrencesFromCLIArgs() > 1 { - return fmt.Errorf("only one -e, --environment flag may be provided") - } - return nil -} - func normalizeStorage(tier gql.OpenSearchTier, memory gql.OpenSearchMemory, storage int) (int, error) { memories, ok := storageRanges[tier] if !ok { diff --git a/internal/opensearch/command/create.go b/internal/opensearch/command/create.go index a82f9a95..8e15879c 100644 --- a/internal/opensearch/command/create.go +++ b/internal/opensearch/command/create.go @@ -23,19 +23,13 @@ func create(parentFlags *flag.OpenSearch) *naistrix.Command { Args: []naistrix.Argument{ {Name: "name"}, }, - ValidateFunc: func(ctx context.Context, args *naistrix.Arguments) error { - if err := validateSingleEnvironmentFlagUsage(); err != nil { - return err - } - if err := flags.Validate(); err != nil { - return err - } - err := validation.CheckEnvironment(string(flags.Environment)) - if err != nil { - return err - } - return validateArgs(args) - }, + ValidateFunc: naistrix.ValidateFuncs( + validation.RequireEnvironment(flags), + validateArgs, + func(ctx context.Context, args *naistrix.Arguments) error { + return flags.Validate() + }, + ), Examples: []naistrix.Example{ { Description: "Create an OpenSearch instance named some-opensearch with default settings.", diff --git a/internal/opensearch/command/credentials.go b/internal/opensearch/command/credentials.go index 83dc5dd3..099c34f0 100644 --- a/internal/opensearch/command/credentials.go +++ b/internal/opensearch/command/credentials.go @@ -23,27 +23,22 @@ func credentials(parentFlags *flag.OpenSearch) *naistrix.Command { Args: []naistrix.Argument{ {Name: "name"}, }, - ValidateFunc: func(ctx context.Context, args *naistrix.Arguments) error { - if err := validateSingleEnvironmentFlagUsage(); err != nil { - return err - } - if err := validation.CheckEnvironment(string(flags.Environment)); err != nil { - return err - } - if err := validateArgs(args); err != nil { - return err - } - if flags.Permission == "" { - return fmt.Errorf("permission is required, set using --permission/-p flag (READ, WRITE, READWRITE, ADMIN)") - } - if !aiven.IsValidPermission(gql.CredentialPermission(flags.Permission)) { - return fmt.Errorf("invalid permission %q, must be one of: %v", flags.Permission, gql.AllCredentialPermission) - } - if flags.TTL == "" { - return fmt.Errorf("ttl is required, set using --ttl flag (e.g. '1d', '7d')") - } - return nil - }, + ValidateFunc: naistrix.ValidateFuncs( + validation.RequireEnvironment(flags), + validateArgs, + func(ctx context.Context, args *naistrix.Arguments) error { + if flags.Permission == "" { + return fmt.Errorf("permission is required, set using --permission/-p flag (READ, WRITE, READWRITE, ADMIN)") + } + if !aiven.IsValidPermission(gql.CredentialPermission(flags.Permission)) { + return fmt.Errorf("invalid permission %q, must be one of: %v", flags.Permission, gql.AllCredentialPermission) + } + if flags.TTL == "" { + return fmt.Errorf("ttl is required, set using --ttl flag (e.g. '1d', '7d')") + } + return nil + }, + ), AutoCompleteFunc: func(ctx context.Context, args *naistrix.Arguments, _ string) ([]string, string) { if args.Len() != 0 { return nil, "" diff --git a/internal/opensearch/command/delete.go b/internal/opensearch/command/delete.go index 329be9ba..980f39a5 100644 --- a/internal/opensearch/command/delete.go +++ b/internal/opensearch/command/delete.go @@ -22,16 +22,10 @@ func delete(parentFlags *flag.OpenSearch) *naistrix.Command { Args: []naistrix.Argument{ {Name: "name"}, }, - ValidateFunc: func(ctx context.Context, args *naistrix.Arguments) error { - if err := validateSingleEnvironmentFlagUsage(); err != nil { - return err - } - err := validation.CheckEnvironment(string(flags.Environment)) - if err != nil { - return err - } - return validateArgs(args) - }, + ValidateFunc: naistrix.ValidateFuncs( + validation.RequireEnvironment(flags), + validateArgs, + ), AutoCompleteFunc: func(ctx context.Context, args *naistrix.Arguments, _ string) ([]string, string) { if args.Len() == 0 { return autoCompleteOpenSearchNames(ctx, flags.Team, string(flags.Environment), true) diff --git a/internal/opensearch/command/get.go b/internal/opensearch/command/get.go index 68844830..dd1d7f84 100644 --- a/internal/opensearch/command/get.go +++ b/internal/opensearch/command/get.go @@ -21,16 +21,10 @@ func get(parentFlags *flag.OpenSearch) *naistrix.Command { Args: []naistrix.Argument{ {Name: "name"}, }, - ValidateFunc: func(ctx context.Context, args *naistrix.Arguments) error { - if err := validateSingleEnvironmentFlagUsage(); err != nil { - return err - } - err := validation.CheckEnvironment(string(flags.Environment)) - if err != nil { - return err - } - return validateArgs(args) - }, + ValidateFunc: naistrix.ValidateFuncs( + validation.RequireEnvironment(flags), + validateArgs, + ), AutoCompleteFunc: func(ctx context.Context, args *naistrix.Arguments, _ string) ([]string, string) { if args.Len() == 0 { return autoCompleteOpenSearchNames(ctx, flags.Team, string(flags.Environment), true) diff --git a/internal/opensearch/command/update.go b/internal/opensearch/command/update.go index a375f1e8..21d9575e 100644 --- a/internal/opensearch/command/update.go +++ b/internal/opensearch/command/update.go @@ -24,19 +24,16 @@ func update(parentFlags *flag.OpenSearch) *naistrix.Command { Args: []naistrix.Argument{ {Name: "name"}, }, - ValidateFunc: func(ctx context.Context, args *naistrix.Arguments) error { - if err := validateSingleEnvironmentFlagUsage(); err != nil { - return err - } - if err := flags.Validate(); err != nil { - return err - } - err := validation.CheckEnvironment(string(flags.Environment)) - if err != nil { - return err - } - return validateArgs(args) - }, + ValidateFunc: naistrix.ValidateFuncs( + validation.RequireEnvironment(flags), + validateArgs, + func(ctx context.Context, args *naistrix.Arguments) error { + if err := flags.Validate(); err != nil { + return err + } + return nil + }, + ), AutoCompleteFunc: func(ctx context.Context, args *naistrix.Arguments, _ string) ([]string, string) { if args.Len() == 0 { return autoCompleteOpenSearchNames(ctx, flags.Team, string(flags.Environment), true) diff --git a/internal/postgres/access.go b/internal/postgres/access.go index dd9d2ff5..4e2a7503 100644 --- a/internal/postgres/access.go +++ b/internal/postgres/access.go @@ -6,7 +6,6 @@ import ( "strings" "github.com/lib/pq" - "github.com/nais/cli/internal/flags" "github.com/nais/cli/internal/postgres/command/flag" "github.com/nais/naistrix" ) @@ -35,9 +34,9 @@ var ( revokeUsage = `REVOKE USAGE ON SCHEMA $schema FROM cloudsqliamuser;` ) -func PrepareAccess(ctx context.Context, appName string, fl *flag.Prepare, out *naistrix.OutputWriter) error { +func PrepareAccess(ctx context.Context, appName, team, environment string, fl *flag.Prepare, out *naistrix.OutputWriter) error { // Get secret values (access is logged for audit purposes) - sv, err := GetSecretValues(ctx, appName, fl.Postgres, ReasonPrepareAccess, out) + sv, err := GetSecretValues(ctx, appName, team, environment, fl.Postgres, ReasonPrepareAccess, out) if err != nil { return err } @@ -50,15 +49,15 @@ func PrepareAccess(ctx context.Context, appName string, fl *flag.Prepare, out *n } if fl.AllPrivileges { - return sqlExecAsAppUser(ctx, appName, fl.Team, fl.Environment, fl.Schema, prependUsageIfNotPublic(grantAllPrivs), sv) + return sqlExecAsAppUser(ctx, appName, team, environment, fl.Schema, prependUsageIfNotPublic(grantAllPrivs), sv) } else { - return sqlExecAsAppUser(ctx, appName, fl.Team, fl.Environment, fl.Schema, prependUsageIfNotPublic(grantSelectPrivs), sv) + return sqlExecAsAppUser(ctx, appName, team, environment, fl.Schema, prependUsageIfNotPublic(grantSelectPrivs), sv) } } -func RevokeAccess(ctx context.Context, appName string, fl *flag.Revoke, out *naistrix.OutputWriter) error { +func RevokeAccess(ctx context.Context, appName, team, environment string, fl *flag.Revoke, out *naistrix.OutputWriter) error { // Get secret values (access is logged for audit purposes) - sv, err := GetSecretValues(ctx, appName, fl.Postgres, ReasonRevokeAccess, out) + sv, err := GetSecretValues(ctx, appName, team, environment, fl.Postgres, ReasonRevokeAccess, out) if err != nil { return err } @@ -67,11 +66,11 @@ func RevokeAccess(ctx context.Context, appName string, fl *flag.Revoke, out *nai if fl.Schema != "public" { q += "\n" + revokeUsage } - return sqlExecAsAppUser(ctx, appName, fl.Team, fl.Environment, fl.Schema, q, sv) + return sqlExecAsAppUser(ctx, appName, team, environment, fl.Schema, q, sv) } -func sqlExecAsAppUser(ctx context.Context, appName string, namespace string, cluster flags.Environment, schema, statement string, sv *SecretValues) error { - dbInfo, err := NewDBInfo(ctx, appName, namespace, cluster) +func sqlExecAsAppUser(ctx context.Context, appName, team, environment string, schema, statement string, sv *SecretValues) error { + dbInfo, err := NewDBInfo(ctx, appName, team, environment) if err != nil { return err } diff --git a/internal/postgres/audit.go b/internal/postgres/audit.go index bc8acd98..c40f7233 100644 --- a/internal/postgres/audit.go +++ b/internal/postgres/audit.go @@ -6,34 +6,33 @@ import ( "fmt" "github.com/lib/pq" - "github.com/nais/cli/internal/flags" "github.com/nais/cli/internal/postgres/command/flag" "github.com/nais/naistrix" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" ) -func EnableAuditLogging(ctx context.Context, appName string, fl *flag.EnableAudit, out *naistrix.OutputWriter) error { +func EnableAuditLogging(ctx context.Context, appName, team, environment string, fl *flag.EnableAudit, out *naistrix.OutputWriter) error { // Get secret values (access is logged for audit purposes) - sv, err := GetSecretValues(ctx, appName, fl.Postgres, ReasonEnableAudit, out) + sv, err := GetSecretValues(ctx, appName, team, environment, fl.Postgres, ReasonEnableAudit, out) if err != nil { return err } - return enableAuditAsAppUser(ctx, appName, fl.Team, fl.Environment, sv, out) + return enableAuditAsAppUser(ctx, appName, team, environment, sv, out) } -func VerifyAuditLogging(ctx context.Context, appName string, fl *flag.VerifyAudit, out *naistrix.OutputWriter) error { +func VerifyAuditLogging(ctx context.Context, appName, team, environment string, fl *flag.VerifyAudit, out *naistrix.OutputWriter) error { // Get secret values (access is logged for audit purposes) - sv, err := GetSecretValues(ctx, appName, fl.Postgres, ReasonVerifyAudit, out) + sv, err := GetSecretValues(ctx, appName, team, environment, fl.Postgres, ReasonVerifyAudit, out) if err != nil { return err } - _, err = verifyAuditAsAppUser(ctx, appName, fl.Team, fl.Environment, sv, out) + _, err = verifyAuditAsAppUser(ctx, appName, team, environment, sv, out) return err } -func enableAuditAsAppUser(ctx context.Context, appName string, namespace string, cluster flags.Environment, sv *SecretValues, out *naistrix.OutputWriter) error { - dbInfo, err := NewDBInfo(ctx, appName, namespace, cluster) +func enableAuditAsAppUser(ctx context.Context, appName, team, environment string, sv *SecretValues, out *naistrix.OutputWriter) error { + dbInfo, err := NewDBInfo(ctx, appName, team, environment) if err != nil { return err } @@ -160,7 +159,7 @@ func getDBFlags(ctx context.Context, info *CloudSQLDBInfo) (map[string]string, e Group: "sql.cnrm.cloud.google.com", Version: "v1beta1", Resource: "sqlinstances", - }).Namespace(string(info.namespace)).List(ctx, v1.ListOptions{ + }).Namespace(info.namespace).List(ctx, v1.ListOptions{ LabelSelector: "app=" + info.appName, }) if err != nil { @@ -202,8 +201,8 @@ func getDBFlags(ctx context.Context, info *CloudSQLDBInfo) (map[string]string, e return dbFlags, nil } -func verifyAuditAsAppUser(ctx context.Context, appName string, namespace string, cluster flags.Environment, sv *SecretValues, out *naistrix.OutputWriter) (bool, error) { - dbInfo, err := NewDBInfo(ctx, appName, namespace, cluster) +func verifyAuditAsAppUser(ctx context.Context, appName, team, environment string, sv *SecretValues, out *naistrix.OutputWriter) (bool, error) { + dbInfo, err := NewDBInfo(ctx, appName, team, environment) if err != nil { return false, err } diff --git a/internal/postgres/command/enable_audit.go b/internal/postgres/command/enable_audit.go index 7086d8d5..dfb78644 100644 --- a/internal/postgres/command/enable_audit.go +++ b/internal/postgres/command/enable_audit.go @@ -6,6 +6,7 @@ import ( "github.com/nais/cli/internal/metric" "github.com/nais/cli/internal/postgres" "github.com/nais/cli/internal/postgres/command/flag" + "github.com/nais/cli/internal/validation" "github.com/nais/naistrix" ) @@ -18,9 +19,10 @@ func enableAuditCommand(parentFlags *flag.Postgres) *naistrix.Command { Args: []naistrix.Argument{ {Name: "app_name"}, }, - Flags: flags, + Flags: flags, + ValidateFunc: validation.RequireTeamAndEnvironment(flags), RunFunc: func(ctx context.Context, args *naistrix.Arguments, out *naistrix.OutputWriter) error { - err := postgres.EnableAuditLogging(ctx, args.Get("app_name"), flags, out) + err := postgres.EnableAuditLogging(ctx, args.Get("app_name"), flags.Team, string(flags.Environment), flags, out) if err != nil { metric.CreateAndIncreaseCounter(ctx, "enable_audit_logging_error") } diff --git a/internal/postgres/command/grant.go b/internal/postgres/command/grant.go index bcf0f787..8cf39f73 100644 --- a/internal/postgres/command/grant.go +++ b/internal/postgres/command/grant.go @@ -5,6 +5,7 @@ import ( "github.com/nais/cli/internal/postgres" "github.com/nais/cli/internal/postgres/command/flag" + "github.com/nais/cli/internal/validation" "github.com/nais/naistrix" ) @@ -17,9 +18,10 @@ func grantCommand(parentFlags *flag.Postgres) *naistrix.Command { Args: []naistrix.Argument{ {Name: "app_name"}, }, - Flags: flags, + Flags: flags, + ValidateFunc: validation.RequireTeamAndEnvironment(flags), RunFunc: func(ctx context.Context, args *naistrix.Arguments, out *naistrix.OutputWriter) error { - return postgres.GrantAndCreateSQLUser(ctx, args.Get("app_name"), flags.Environment, flags.Team, out) + return postgres.GrantAndCreateSQLUser(ctx, args.Get("app_name"), flags.Team, string(flags.Environment), out) }, } } diff --git a/internal/postgres/command/migrate.go b/internal/postgres/command/migrate.go index 0d7c17ea..171f4344 100644 --- a/internal/postgres/command/migrate.go +++ b/internal/postgres/command/migrate.go @@ -12,16 +12,18 @@ import ( "github.com/nais/cli/internal/postgres/migrate/promote" "github.com/nais/cli/internal/postgres/migrate/rollback" "github.com/nais/cli/internal/postgres/migrate/setup" + "github.com/nais/cli/internal/validation" "github.com/nais/naistrix" ) func migrateCommand(parentFlags *flag.Postgres) *naistrix.Command { flags := &flag.Migrate{Postgres: parentFlags} return &naistrix.Command{ - Name: "migrate", - Title: "Migrate to a new SQL instance.", - Description: "Commands for migrating a Postgres database to a new Cloud SQL instance, including setup, promotion, finalization, and rollback.", - StickyFlags: flags, + Name: "migrate", + Title: "Migrate to a new SQL instance.", + Description: "Commands for migrating a Postgres database to a new Cloud SQL instance, including setup, promotion, finalization, and rollback.", + StickyFlags: flags, + ValidateFunc: validation.RequireTeamAndEnvironment(flags), SubCommands: []*naistrix.Command{ migrateSetupCommand(flags), migratePromoteCommand(flags), @@ -66,7 +68,7 @@ func migrateSetupCommand(parentFlags *flag.Migrate) *naistrix.Command { }, Flags: flags, RunFunc: func(ctx context.Context, args *naistrix.Arguments, out *naistrix.OutputWriter) error { - return setup.Run(ctx, args.Get("app_name"), args.Get("target_sql_instance_name"), flags) + return setup.Run(ctx, args.Get("app_name"), args.Get("target_sql_instance_name"), flags.Team, string(flags.Environment), flags) }, } } @@ -83,7 +85,7 @@ func migratePromoteCommand(parentFlags *flag.Migrate) *naistrix.Command { {Name: "target_sql_instance_name"}, }, RunFunc: func(ctx context.Context, args *naistrix.Arguments, out *naistrix.OutputWriter) error { - return promote.Run(ctx, args.Get("app_name"), args.Get("target_sql_instance_name"), flags) + return promote.Run(ctx, args.Get("app_name"), args.Get("target_sql_instance_name"), flags.Team, string(flags.Environment), flags) }, } } @@ -100,7 +102,7 @@ func migrateFinalizeCommand(parentFlags *flag.Migrate) *naistrix.Command { }, Flags: flags, RunFunc: func(ctx context.Context, args *naistrix.Arguments, out *naistrix.OutputWriter) error { - return finalize.Run(ctx, args.Get("app_name"), args.Get("target_sql_instance_name"), flags) + return finalize.Run(ctx, args.Get("app_name"), args.Get("target_sql_instance_name"), flags.Team, string(flags.Environment), flags.DryRun) }, } } @@ -117,7 +119,7 @@ func migrateRollbackCommand(parentFlags *flag.Migrate) *naistrix.Command { }, Flags: flags, RunFunc: func(ctx context.Context, args *naistrix.Arguments, out *naistrix.OutputWriter) error { - return rollback.Run(ctx, args.Get("app_name"), args.Get("target_sql_instance_name"), flags) + return rollback.Run(ctx, args.Get("app_name"), args.Get("target_sql_instance_name"), flags.Team, string(flags.Environment), flags) }, } } diff --git a/internal/postgres/command/password.go b/internal/postgres/command/password.go index 5779880d..778675cb 100644 --- a/internal/postgres/command/password.go +++ b/internal/postgres/command/password.go @@ -5,6 +5,7 @@ import ( "github.com/nais/cli/internal/postgres" "github.com/nais/cli/internal/postgres/command/flag" + "github.com/nais/cli/internal/validation" "github.com/nais/naistrix" ) @@ -23,8 +24,9 @@ func passwordCommand(parentFlags *flag.Postgres) *naistrix.Command { Args: []naistrix.Argument{ {Name: "app_name"}, }, + ValidateFunc: validation.RequireTeamAndEnvironment(flags), RunFunc: func(ctx context.Context, args *naistrix.Arguments, out *naistrix.OutputWriter) error { - return postgres.RotatePassword(ctx, args.Get("app_name"), flags, out) + return postgres.RotatePassword(ctx, args.Get("app_name"), flags.Team, string(flags.Environment), flags, out) }, }, }, diff --git a/internal/postgres/command/prepare.go b/internal/postgres/command/prepare.go index b44a5226..83095005 100644 --- a/internal/postgres/command/prepare.go +++ b/internal/postgres/command/prepare.go @@ -8,8 +8,9 @@ import ( "github.com/MakeNowJust/heredoc/v2" "github.com/nais/cli/internal/postgres" "github.com/nais/cli/internal/postgres/command/flag" + "github.com/nais/cli/internal/validation" "github.com/nais/naistrix" - "github.com/pterm/pterm" + "github.com/nais/naistrix/input" ) func prepareCommand(parentFlags *flag.Postgres) *naistrix.Command { @@ -31,14 +32,16 @@ func prepareCommand(parentFlags *flag.Postgres) *naistrix.Command { Args: []naistrix.Argument{ {Name: "app_name"}, }, - Flags: flags, + Flags: flags, + ValidateFunc: validation.RequireTeamAndEnvironment(flags), RunFunc: func(ctx context.Context, args *naistrix.Arguments, out *naistrix.OutputWriter) error { - result, _ := pterm.DefaultInteractiveConfirm.Show("Are you sure you want to continue?") - if !result { + if result, err := input.Confirm("Are you sure you want to continue?"); err != nil { + return err + } else if !result { return fmt.Errorf("cancelled by user") } - return postgres.PrepareAccess(ctx, args.Get("app_name"), flags, out) + return postgres.PrepareAccess(ctx, args.Get("app_name"), flags.Team, string(flags.Environment), flags, out) }, } } diff --git a/internal/postgres/command/proxy.go b/internal/postgres/command/proxy.go index 679b6325..701828f6 100644 --- a/internal/postgres/command/proxy.go +++ b/internal/postgres/command/proxy.go @@ -5,6 +5,7 @@ import ( "github.com/nais/cli/internal/postgres" "github.com/nais/cli/internal/postgres/command/flag" + "github.com/nais/cli/internal/validation" "github.com/nais/naistrix" ) @@ -14,7 +15,6 @@ func proxyCommand(parentFlags *flag.Postgres) *naistrix.Command { Port: 5432, Host: "localhost", } - return &naistrix.Command{ Name: "proxy", Title: "Create a proxy to a SQL instance.", @@ -22,9 +22,10 @@ func proxyCommand(parentFlags *flag.Postgres) *naistrix.Command { Args: []naistrix.Argument{ {Name: "app_name"}, }, - Flags: flags, + Flags: flags, + ValidateFunc: validation.RequireTeamAndEnvironment(flags), RunFunc: func(ctx context.Context, args *naistrix.Arguments, out *naistrix.OutputWriter) error { - return postgres.RunProxy(ctx, args.Get("app_name"), flags, out) + return postgres.RunProxy(ctx, args.Get("app_name"), flags.Team, string(flags.Environment), flags, out) }, } } diff --git a/internal/postgres/command/psql.go b/internal/postgres/command/psql.go index 5ed31aa5..505e7edf 100644 --- a/internal/postgres/command/psql.go +++ b/internal/postgres/command/psql.go @@ -5,6 +5,7 @@ import ( "github.com/nais/cli/internal/postgres" "github.com/nais/cli/internal/postgres/command/flag" + "github.com/nais/cli/internal/validation" "github.com/nais/naistrix" ) @@ -17,9 +18,10 @@ func psqlCommand(parentFlags *flag.Postgres) *naistrix.Command { Args: []naistrix.Argument{ {Name: "app_name"}, }, - Flags: flags, + Flags: flags, + ValidateFunc: validation.RequireTeamAndEnvironment(flags), RunFunc: func(ctx context.Context, args *naistrix.Arguments, out *naistrix.OutputWriter) error { - return postgres.RunPSQL(ctx, args.Get("app_name"), flags, out) + return postgres.RunPSQL(ctx, args.Get("app_name"), flags.Team, string(flags.Environment), flags, out) }, } } diff --git a/internal/postgres/command/revoke.go b/internal/postgres/command/revoke.go index 1044d009..1a45a10f 100644 --- a/internal/postgres/command/revoke.go +++ b/internal/postgres/command/revoke.go @@ -8,8 +8,9 @@ import ( "github.com/MakeNowJust/heredoc/v2" "github.com/nais/cli/internal/postgres" "github.com/nais/cli/internal/postgres/command/flag" + "github.com/nais/cli/internal/validation" "github.com/nais/naistrix" - "github.com/pterm/pterm" + "github.com/nais/naistrix/input" ) func revokeCommand(parentFlags *flag.Postgres) *naistrix.Command { @@ -17,7 +18,6 @@ func revokeCommand(parentFlags *flag.Postgres) *naistrix.Command { Postgres: parentFlags, Schema: "public", } - return &naistrix.Command{ Name: "revoke", Title: `Revoke access to your SQL instance for the role "cloudsqliamuser".`, @@ -31,14 +31,16 @@ func revokeCommand(parentFlags *flag.Postgres) *naistrix.Command { Args: []naistrix.Argument{ {Name: "app_name"}, }, - Flags: flags, + Flags: flags, + ValidateFunc: validation.RequireTeamAndEnvironment(flags), RunFunc: func(ctx context.Context, args *naistrix.Arguments, out *naistrix.OutputWriter) error { - result, _ := pterm.DefaultInteractiveConfirm.Show("Are you sure you want to continue?") - if !result { + if result, err := input.Confirm("Are you sure you want to continue?"); err != nil { + return err + } else if !result { return fmt.Errorf("cancelled by user") } - return postgres.RevokeAccess(ctx, args.Get("app_name"), flags, out) + return postgres.RevokeAccess(ctx, args.Get("app_name"), flags.Team, string(flags.Environment), flags, out) }, } } diff --git a/internal/postgres/command/users.go b/internal/postgres/command/users.go index 0dc58072..cda82424 100644 --- a/internal/postgres/command/users.go +++ b/internal/postgres/command/users.go @@ -5,16 +5,18 @@ import ( "github.com/nais/cli/internal/postgres" "github.com/nais/cli/internal/postgres/command/flag" + "github.com/nais/cli/internal/validation" "github.com/nais/naistrix" ) func usersCommand(parentFlags *flag.Postgres) *naistrix.Command { flags := &flag.User{Postgres: parentFlags} return &naistrix.Command{ - Name: "users", - Title: "Manage users in your SQL instance.", - Description: "Commands for adding, listing, and dropping users in a Postgres SQL instance.", - StickyFlags: flags, + Name: "users", + Title: "Manage users in your SQL instance.", + Description: "Commands for adding, listing, and dropping users in a Postgres SQL instance.", + StickyFlags: flags, + ValidateFunc: validation.RequireTeamAndEnvironment(flags), SubCommands: []*naistrix.Command{ addCommand(flags), dropCommand(flags), @@ -24,7 +26,7 @@ func usersCommand(parentFlags *flag.Postgres) *naistrix.Command { } func addCommand(parentFlags *flag.User) *naistrix.Command { - userAddFlags := &flag.UserAdd{ + flags := &flag.UserAdd{ User: parentFlags, Privilege: "select", } @@ -37,9 +39,9 @@ func addCommand(parentFlags *flag.User) *naistrix.Command { {Name: "username"}, {Name: "password"}, }, - Flags: userAddFlags, + Flags: flags, RunFunc: func(ctx context.Context, args *naistrix.Arguments, out *naistrix.OutputWriter) error { - return postgres.AddUser(ctx, args.Get("app_name"), args.Get("username"), args.Get("password"), userAddFlags, out) + return postgres.AddUser(ctx, args.Get("app_name"), flags.Team, string(flags.Environment), args.Get("username"), args.Get("password"), flags, out) }, } } @@ -55,14 +57,13 @@ func listUsersCommand(parentFlags *flag.User) *naistrix.Command { }, Flags: flags, RunFunc: func(ctx context.Context, args *naistrix.Arguments, out *naistrix.OutputWriter) error { - return postgres.ListUsers(ctx, args.Get("app_name"), flags, out) + return postgres.ListUsers(ctx, args.Get("app_name"), flags.Team, string(flags.Environment), flags, out) }, } } func dropCommand(parentFlags *flag.User) *naistrix.Command { flags := &flag.UserDrop{User: parentFlags} - return &naistrix.Command{ Name: "drop", Title: "Drop a user from a SQL instance database.", @@ -73,7 +74,7 @@ func dropCommand(parentFlags *flag.User) *naistrix.Command { }, Flags: flags, RunFunc: func(ctx context.Context, args *naistrix.Arguments, out *naistrix.OutputWriter) error { - return postgres.DropUser(ctx, args.Get("app_name"), args.Get("username"), flags, out) + return postgres.DropUser(ctx, args.Get("app_name"), flags.Team, string(flags.Environment), args.Get("username"), flags, out) }, } } diff --git a/internal/postgres/command/verify_audit.go b/internal/postgres/command/verify_audit.go index 2896f7c4..ca735c14 100644 --- a/internal/postgres/command/verify_audit.go +++ b/internal/postgres/command/verify_audit.go @@ -6,6 +6,7 @@ import ( "github.com/nais/cli/internal/metric" "github.com/nais/cli/internal/postgres" "github.com/nais/cli/internal/postgres/command/flag" + "github.com/nais/cli/internal/validation" "github.com/nais/naistrix" ) @@ -18,9 +19,10 @@ func verifyAuditCommand(parentFlags *flag.Postgres) *naistrix.Command { Args: []naistrix.Argument{ {Name: "app_name"}, }, - Flags: flags, + Flags: flags, + ValidateFunc: validation.RequireTeamAndEnvironment(flags), RunFunc: func(ctx context.Context, args *naistrix.Arguments, out *naistrix.OutputWriter) error { - err := postgres.VerifyAuditLogging(ctx, args.Get("app_name"), flags, out) + err := postgres.VerifyAuditLogging(ctx, args.Get("app_name"), flags.Team, string(flags.Environment), flags, out) if err != nil { metric.CreateAndIncreaseCounter(ctx, "verify_audit_logging_error") } diff --git a/internal/postgres/dbinfo.go b/internal/postgres/dbinfo.go index fcc344ef..2829a64e 100644 --- a/internal/postgres/dbinfo.go +++ b/internal/postgres/dbinfo.go @@ -6,7 +6,6 @@ import ( "fmt" "net/url" - "github.com/nais/cli/internal/flags" "github.com/nais/naistrix" "golang.org/x/oauth2" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -39,10 +38,10 @@ func (d *DBInfo) AppName() string { return d.appName } -func NewDBInfo(ctx context.Context, appName string, namespace string, context flags.Environment) (DB, error) { +func NewDBInfo(ctx context.Context, appName, team, environment string) (DB, error) { loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() configOverrides := &clientcmd.ConfigOverrides{ - CurrentContext: string(context), + CurrentContext: environment, } kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides) config, err := kubeConfig.ClientConfig() @@ -50,12 +49,12 @@ func NewDBInfo(ctx context.Context, appName string, namespace string, context fl return nil, fmt.Errorf("NewDBInfo: unable to get kubeconfig: %w", err) } - if namespace == "" { + if team == "" { ns, _, err := kubeConfig.Namespace() if err != nil { return nil, fmt.Errorf("NewDBInfo: unable to get namespace: %w", err) } - namespace = ns + team = ns } k8sClient, err := kubernetes.NewForConfig(config) @@ -72,7 +71,7 @@ func NewDBInfo(ctx context.Context, appName string, namespace string, context fl k8sClient: k8sClient, dynamicClient: dynamicClient, config: kubeConfig, - namespace: namespace, + namespace: team, appName: appName, } diff --git a/internal/postgres/iam.go b/internal/postgres/iam.go index 0f119a24..6daa8f6a 100644 --- a/internal/postgres/iam.go +++ b/internal/postgres/iam.go @@ -13,13 +13,12 @@ import ( "strings" "time" - "github.com/nais/cli/internal/flags" "github.com/nais/cli/internal/postgres/command/flag" "github.com/nais/naistrix" ) -func GrantAndCreateSQLUser(ctx context.Context, appName string, cluster flags.Environment, namespace string, out *naistrix.OutputWriter) error { - dbInfo, err := NewDBInfo(ctx, appName, namespace, cluster) +func GrantAndCreateSQLUser(ctx context.Context, appName, team, environment string, out *naistrix.OutputWriter) error { + dbInfo, err := NewDBInfo(ctx, appName, team, environment) if err != nil { return err } @@ -213,14 +212,14 @@ func formatCondition(expr, title string) string { return fmt.Sprintf("expression=%v,title=%v", expr, title) } -func ListUsers(ctx context.Context, appName string, fl *flag.UserList, out *naistrix.OutputWriter) error { +func ListUsers(ctx context.Context, appName, team, environment string, fl *flag.UserList, out *naistrix.OutputWriter) error { // Get secret values (access is logged for audit purposes) - sv, err := GetSecretValues(ctx, appName, fl.Postgres, ReasonListUsers, out) + sv, err := GetSecretValues(ctx, appName, team, environment, fl.Postgres, ReasonListUsers, out) if err != nil { return err } - dbInfo, err := NewDBInfo(ctx, appName, fl.Team, fl.Environment) + dbInfo, err := NewDBInfo(ctx, appName, team, environment) if err != nil { return err } @@ -260,19 +259,19 @@ func ListUsers(ctx context.Context, appName string, fl *flag.UserList, out *nais return err } -func AddUser(ctx context.Context, appName, username, password string, fl *flag.UserAdd, out *naistrix.OutputWriter) error { +func AddUser(ctx context.Context, appName, team, environment, username, password string, fl *flag.UserAdd, out *naistrix.OutputWriter) error { err := validateSQLVariables(username, password, fl.Privilege) if err != nil { return err } // Get secret values (access is logged for audit purposes) - sv, err := GetSecretValues(ctx, appName, fl.Postgres, ReasonAddUser, out) + sv, err := GetSecretValues(ctx, appName, team, environment, fl.Postgres, ReasonAddUser, out) if err != nil { return err } - dbInfo, err := NewDBInfo(ctx, appName, fl.Team, fl.Environment) + dbInfo, err := NewDBInfo(ctx, appName, team, environment) if err != nil { return err } @@ -308,14 +307,14 @@ func AddUser(ctx context.Context, appName, username, password string, fl *flag.U return nil } -func DropUser(ctx context.Context, appName string, username string, fl *flag.UserDrop, out *naistrix.OutputWriter) error { +func DropUser(ctx context.Context, appName, team, environment, username string, fl *flag.UserDrop, out *naistrix.OutputWriter) error { // Get secret values (access is logged for audit purposes) - sv, err := GetSecretValues(ctx, appName, fl.Postgres, ReasonDropUser, out) + sv, err := GetSecretValues(ctx, appName, team, environment, fl.Postgres, ReasonDropUser, out) if err != nil { return err } - dbInfo, err := NewDBInfo(ctx, appName, fl.Team, fl.Environment) + dbInfo, err := NewDBInfo(ctx, appName, team, environment) if err != nil { return err } diff --git a/internal/postgres/migrate/config/config.go b/internal/postgres/migrate/config/config.go index 2eaf0eef..77f9d88e 100644 --- a/internal/postgres/migrate/config/config.go +++ b/internal/postgres/migrate/config/config.go @@ -37,9 +37,9 @@ func (ic *InstanceConfig) String() string { return fmt.Sprintf("Name: %v\nTier: %v\nDiskSize: %v\nType: %v\n", ic.InstanceName, ic.Tier, ic.DiskSize, ic.Type) } -func (ic *InstanceConfig) Resolve(ctx context.Context, client ctrl.Client, appName string, namespace string) error { +func (ic *InstanceConfig) Resolve(ctx context.Context, client ctrl.Client, appName, team string) error { app := &nais_io_v1alpha1.Application{} - err := client.Get(ctx, ctrl.ObjectKey{Namespace: namespace, Name: appName}, app) + err := client.Get(ctx, ctrl.ObjectKey{Namespace: team, Name: appName}, app) if err != nil { return err } diff --git a/internal/postgres/migrate/finalize/command.go b/internal/postgres/migrate/finalize/command.go index e90d446a..8ee95394 100644 --- a/internal/postgres/migrate/finalize/command.go +++ b/internal/postgres/migrate/finalize/command.go @@ -6,12 +6,11 @@ import ( "github.com/nais/cli/internal/k8s" "github.com/nais/cli/internal/option" - "github.com/nais/cli/internal/postgres/command/flag" "github.com/nais/cli/internal/postgres/migrate" "github.com/nais/cli/internal/postgres/migrate/config" ) -func Run(ctx context.Context, applicationName, targetInstanceName string, flags *flag.MigrateFinalize) error { +func Run(ctx context.Context, applicationName, targetInstanceName, team, environment string, dryRun bool) error { cfg := config.Config{ AppName: applicationName, Target: config.InstanceConfig{ @@ -19,19 +18,14 @@ func Run(ctx context.Context, applicationName, targetInstanceName string, flags }, } - client := k8s.SetupControllerRuntimeClient(k8s.WithKubeContext(string(flags.Environment))) - team, err := flags.RequiredTeam() - if err != nil { - return err - } + client := k8s.SetupControllerRuntimeClient(k8s.WithKubeContext(environment)) cfg.Team = team - - clientSet, err := k8s.SetupClientGo(string(flags.Environment)) + clientSet, err := k8s.SetupClientGo(environment) if err != nil { return err } - migrator := migrate.NewMigrator(client, clientSet, cfg, flags.DryRun, false) + migrator := migrate.NewMigrator(client, clientSet, cfg, dryRun, false) if err := migrator.Finalize(ctx); err != nil { return fmt.Errorf("error cleaning up instance: %w", err) } diff --git a/internal/postgres/migrate/promote/command.go b/internal/postgres/migrate/promote/command.go index 46937832..3fcf1de0 100644 --- a/internal/postgres/migrate/promote/command.go +++ b/internal/postgres/migrate/promote/command.go @@ -11,7 +11,7 @@ import ( "github.com/nais/cli/internal/postgres/migrate/config" ) -func Run(ctx context.Context, applicationName, targetInstanceName string, flags *flag.MigratePromote) error { +func Run(ctx context.Context, applicationName, targetInstanceName, team, environment string, flags *flag.MigratePromote) error { cfg := config.Config{ AppName: applicationName, Target: config.InstanceConfig{ @@ -19,14 +19,9 @@ func Run(ctx context.Context, applicationName, targetInstanceName string, flags }, } - client := k8s.SetupControllerRuntimeClient(k8s.WithKubeContext(string(flags.Environment))) - team, err := flags.RequiredTeam() - if err != nil { - return err - } + client := k8s.SetupControllerRuntimeClient(k8s.WithKubeContext(environment)) cfg.Team = team - - clientSet, err := k8s.SetupClientGo(string(flags.Environment)) + clientSet, err := k8s.SetupClientGo(environment) if err != nil { return err } diff --git a/internal/postgres/migrate/rollback/command.go b/internal/postgres/migrate/rollback/command.go index 80114164..aad1ba25 100644 --- a/internal/postgres/migrate/rollback/command.go +++ b/internal/postgres/migrate/rollback/command.go @@ -11,7 +11,7 @@ import ( "github.com/nais/cli/internal/postgres/migrate/config" ) -func Run(ctx context.Context, applicationName, targetInstanceName string, flags *flag.MigrateRollback) error { +func Run(ctx context.Context, applicationName, targetInstanceName, team, environment string, flags *flag.MigrateRollback) error { cfg := config.Config{ AppName: applicationName, Target: config.InstanceConfig{ @@ -19,14 +19,9 @@ func Run(ctx context.Context, applicationName, targetInstanceName string, flags }, } - client := k8s.SetupControllerRuntimeClient(k8s.WithKubeContext(string(flags.Environment))) - team, err := flags.RequiredTeam() - if err != nil { - return err - } + client := k8s.SetupControllerRuntimeClient(k8s.WithKubeContext(environment)) cfg.Team = team - - clientset, err := k8s.SetupClientGo(string(flags.Environment)) + clientset, err := k8s.SetupClientGo(environment) if err != nil { return err } diff --git a/internal/postgres/migrate/setup/command.go b/internal/postgres/migrate/setup/command.go index a411048f..c0816bde 100644 --- a/internal/postgres/migrate/setup/command.go +++ b/internal/postgres/migrate/setup/command.go @@ -11,7 +11,7 @@ import ( "github.com/nais/cli/internal/postgres/migrate/config" ) -func Run(ctx context.Context, applicationName, targetInstanceName string, flags *flag.MigrateSetup) error { +func Run(ctx context.Context, applicationName, targetInstanceName, team, environment string, flags *flag.MigrateSetup) error { cfg := config.Config{ AppName: applicationName, Target: config.InstanceConfig{ @@ -19,25 +19,19 @@ func Run(ctx context.Context, applicationName, targetInstanceName string, flags }, } - environment := flags.Environment tier := flags.Tier diskAutoresize := flags.DiskAutoResize diskSize := flags.DiskSize instanceType := flags.InstanceType - team, err := flags.RequiredTeam() - if err != nil { - return err - } cfg.Target.Tier = isSet(tier) cfg.Target.DiskAutoresize = isSetBool(diskAutoresize) cfg.Target.DiskSize = isSetInt(diskSize) cfg.Target.Type = isSet(instanceType) - client := k8s.SetupControllerRuntimeClient(k8s.WithKubeContext(string(environment))) + client := k8s.SetupControllerRuntimeClient(k8s.WithKubeContext(environment)) cfg.Team = team - - clientSet, err := k8s.SetupClientGo(string(environment)) + clientSet, err := k8s.SetupClientGo(environment) if err != nil { return err } diff --git a/internal/postgres/password.go b/internal/postgres/password.go index 70490754..1497c029 100644 --- a/internal/postgres/password.go +++ b/internal/postgres/password.go @@ -17,14 +17,14 @@ import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func RotatePassword(ctx context.Context, appName string, fl *flag.Password, out *naistrix.OutputWriter) error { +func RotatePassword(ctx context.Context, appName, team, environment string, fl *flag.Password, out *naistrix.OutputWriter) error { // Get secret values (access is logged for audit purposes) - sv, err := GetSecretValues(ctx, appName, fl.Postgres, ReasonPasswordRotate, out) + sv, err := GetSecretValues(ctx, appName, team, environment, fl.Postgres, ReasonPasswordRotate, out) if err != nil { return err } - dbInfo, err := NewDBInfo(ctx, appName, fl.Team, fl.Environment) + dbInfo, err := NewDBInfo(ctx, appName, team, environment) if err != nil { return err } diff --git a/internal/postgres/proxy.go b/internal/postgres/proxy.go index 50c5bc2f..76100235 100644 --- a/internal/postgres/proxy.go +++ b/internal/postgres/proxy.go @@ -11,14 +11,14 @@ import ( "github.com/nais/naistrix" ) -func RunProxy(ctx context.Context, appName string, fl *flag.Proxy, out *naistrix.OutputWriter) error { +func RunProxy(ctx context.Context, appName, team, environment string, fl *flag.Proxy, out *naistrix.OutputWriter) error { // Get secret values with user-provided reason (access is logged for audit purposes) - sv, err := GetSecretValuesWithUserReason(ctx, appName, fl.Postgres, fl.Reason, out) + sv, err := GetSecretValuesWithUserReason(ctx, appName, team, environment, fl.Postgres, fl.Reason, out) if err != nil { return err } - dbInfo, err := NewDBInfo(ctx, appName, fl.Team, fl.Environment) + dbInfo, err := NewDBInfo(ctx, appName, team, environment) if err != nil { return err } diff --git a/internal/postgres/psql.go b/internal/postgres/psql.go index a256a793..8bb0786e 100644 --- a/internal/postgres/psql.go +++ b/internal/postgres/psql.go @@ -11,9 +11,9 @@ import ( "github.com/nais/naistrix" ) -func RunPSQL(ctx context.Context, appName string, fl *flag.Psql, out *naistrix.OutputWriter) error { +func RunPSQL(ctx context.Context, appName, team, environment string, fl *flag.Psql, out *naistrix.OutputWriter) error { // Get secret values with user-provided reason (access is logged for audit purposes) - sv, err := GetSecretValuesWithUserReason(ctx, appName, fl.Postgres, fl.Reason, out) + sv, err := GetSecretValuesWithUserReason(ctx, appName, team, environment, fl.Postgres, fl.Reason, out) if err != nil { return err } @@ -23,7 +23,7 @@ func RunPSQL(ctx context.Context, appName string, fl *flag.Psql, out *naistrix.O return err } - dbInfo, err := NewDBInfo(ctx, appName, fl.Team, fl.Environment) + dbInfo, err := NewDBInfo(ctx, appName, team, environment) if err != nil { return err } diff --git a/internal/postgres/secret.go b/internal/postgres/secret.go index 3298fb05..6bd58eda 100644 --- a/internal/postgres/secret.go +++ b/internal/postgres/secret.go @@ -50,7 +50,7 @@ func (s *SecretValues) Get(suffix string) string { // For CloudSQL databases, this retrieves the secret values directly. // For in-cluster postgres, this grants temporary access to the database. // In both cases, the access is logged for audit purposes. -func GetSecretValues(ctx context.Context, appName string, fl *flag.Postgres, reason string, out *naistrix.OutputWriter) (*SecretValues, error) { +func GetSecretValues(ctx context.Context, appName, team, environment string, fl *flag.Postgres, reason string, out *naistrix.OutputWriter) (*SecretValues, error) { if reason == "" { reason = fl.Reason if reason == "" { @@ -58,21 +58,8 @@ func GetSecretValues(ctx context.Context, appName string, fl *flag.Postgres, rea } } - team, err := fl.RequiredTeam() - if err != nil { - return nil, err - } - out.Printf("Using team %q\n", team) - environment := string(fl.Environment) - if environment == "" { - environment = string(fl.Environment) - if environment == "" { - return nil, fmt.Errorf("environment is required") - } - } - // Check if this is a CloudSQL or in-cluster postgres database isCloudSQL, err := isCloudSQLDatabase(ctx, appName, fl) if err != nil { @@ -89,7 +76,7 @@ func GetSecretValues(ctx context.Context, appName string, fl *flag.Postgres, rea // GetSecretValuesWithUserReason retrieves secret values with a user-provided reason. // This should be used for interactive operations like proxy and psql where the user // should provide justification for accessing the database. -func GetSecretValuesWithUserReason(ctx context.Context, appName string, fl *flag.Postgres, reason string, out *naistrix.OutputWriter) (*SecretValues, error) { +func GetSecretValuesWithUserReason(ctx context.Context, appName, team, environment string, fl *flag.Postgres, reason string, out *naistrix.OutputWriter) (*SecretValues, error) { if reason == "" { reason = fl.Reason if reason == "" { @@ -101,7 +88,7 @@ func GetSecretValuesWithUserReason(ctx context.Context, appName string, fl *flag return nil, fmt.Errorf("reason must be at least 10 characters") } - return GetSecretValues(ctx, appName, fl, reason, out) + return GetSecretValues(ctx, appName, team, environment, fl, reason, out) } // isCloudSQLDatabase checks if the given app uses CloudSQL or in-cluster postgres diff --git a/internal/secret/activity.go b/internal/secret/activity.go index 840df2f5..614c3bb1 100644 --- a/internal/secret/activity.go +++ b/internal/secret/activity.go @@ -29,7 +29,7 @@ type secretActivityResource struct { Entries []secretActivityEntry } -func GetActivity(ctx context.Context, team, name string, environment string, activityTypes []gql.ActivityLogActivityType, limit int) ([]SecretActivity, bool, error) { +func GetActivity(ctx context.Context, secretName, team, environment string, activityTypes []gql.ActivityLogActivityType, limit int) ([]SecretActivity, bool, error) { _ = `# @genqlient query GetSecretActivity($team: Slug!, $name: String!, $activityTypes: [ActivityLogActivityType!], $first: Int) { team(slug: $team) { @@ -60,7 +60,7 @@ func GetActivity(ctx context.Context, team, name string, environment string, act return nil, false, err } - resp, err := gql.GetSecretActivity(ctx, client, team, name, activityTypes, limit) + resp, err := gql.GetSecretActivity(ctx, client, team, secretName, activityTypes, limit) if err != nil { return nil, false, err } @@ -84,7 +84,7 @@ func GetActivity(ctx context.Context, team, name string, environment string, act }) } - ret, found := buildSecretActivity(resources, name, environment) + ret, found := buildSecretActivity(resources, secretName, environment) return ret, found, nil } diff --git a/internal/secret/command/activity.go b/internal/secret/command/activity.go index ab75b813..09b881ea 100644 --- a/internal/secret/command/activity.go +++ b/internal/secret/command/activity.go @@ -6,6 +6,7 @@ import ( activityutil "github.com/nais/cli/internal/activity" "github.com/nais/cli/internal/secret" "github.com/nais/cli/internal/secret/command/flag" + "github.com/nais/cli/internal/validation" "github.com/nais/naistrix" "github.com/nais/naistrix/output" ) @@ -16,24 +17,23 @@ func activity(parentFlags *flag.Secret) *naistrix.Command { Output: "table", Limit: 20, } - return &naistrix.Command{ Name: "activity", Title: "Show activity for a secret.", Description: "Show the activity log for a specific secret, including creation, updates, and deletions. Optionally filter by activity type.", Args: defaultArgs, Flags: f, + ValidateFunc: naistrix.ValidateFuncs( + validation.RequireTeamAndEnvironment(f), + validateArgs, + ), RunFunc: func(ctx context.Context, args *naistrix.Arguments, out *naistrix.OutputWriter) error { - if err := validateArgs(args); err != nil { - return err - } - activityTypes, err := activityutil.ParseActivityTypes(f.ActivityType) if err != nil { return err } - ret, found, err := secret.GetActivity(ctx, f.Team, args.Get("name"), string(f.Environment), activityTypes, f.Limit) + ret, found, err := secret.GetActivity(ctx, args.Get("name"), f.Team, string(f.Environment), activityTypes, f.Limit) if err != nil { return err } diff --git a/internal/secret/command/command.go b/internal/secret/command/command.go index 8c0383f5..2bb8ed76 100644 --- a/internal/secret/command/command.go +++ b/internal/secret/command/command.go @@ -15,14 +15,12 @@ import ( func Secrets(parentFlags *flags.GlobalFlags) *naistrix.Command { f := &flag.Secret{GlobalFlags: parentFlags} return &naistrix.Command{ - Name: "secret", - Aliases: []string{"secrets"}, - Title: "Manage secrets for a team.", - Description: "Commands for listing, creating, viewing, updating, and deleting secrets for a team across environments.", - StickyFlags: f, - ValidateFunc: func(context.Context, *naistrix.Arguments) error { - return validation.CheckTeam(f.Team) - }, + Name: "secret", + Aliases: []string{"secrets"}, + Title: "Manage secrets for a team.", + Description: "Commands for listing, creating, viewing, updating, and deleting secrets for a team across environments.", + StickyFlags: f, + ValidateFunc: validation.RequireTeam(f), SubCommands: []*naistrix.Command{ list(f), activity(f), @@ -39,7 +37,7 @@ var defaultArgs = []naistrix.Argument{ {Name: "name"}, } -func validateArgs(args *naistrix.Arguments) error { +func validateArgs(_ context.Context, args *naistrix.Arguments) error { if args.Len() != 1 { return fmt.Errorf("expected 1 argument, got %d", args.Len()) } diff --git a/internal/secret/command/create.go b/internal/secret/command/create.go index dbbd0dd3..49fc7144 100644 --- a/internal/secret/command/create.go +++ b/internal/secret/command/create.go @@ -18,12 +18,10 @@ func create(parentFlags *flag.Secret) *naistrix.Command { Description: "This command creates a new empty secret in a team environment.", Flags: f, Args: defaultArgs, - ValidateFunc: func(_ context.Context, args *naistrix.Arguments) error { - if err := validation.CheckEnvironment(string(f.Environment)); err != nil { - return err - } - return validateArgs(args) - }, + ValidateFunc: naistrix.ValidateFuncs( + validation.RequireEnvironment(f), + validateArgs, + ), Examples: []naistrix.Example{ { Description: "Create a secret named my-secret in environment dev.", diff --git a/internal/secret/command/delete.go b/internal/secret/command/delete.go index 79605d30..ceed6f0d 100644 --- a/internal/secret/command/delete.go +++ b/internal/secret/command/delete.go @@ -19,15 +19,10 @@ func deleteSecret(parentFlags *flag.Secret) *naistrix.Command { Description: "This command deletes a secret and all its values.", Flags: f, Args: defaultArgs, - ValidateFunc: func(_ context.Context, args *naistrix.Arguments) error { - if err := validateSingleEnvironmentFlagUsage(); err != nil { - return err - } - if err := validation.CheckEnvironment(string(f.Environment)); err != nil { - return err - } - return validateArgs(args) - }, + ValidateFunc: naistrix.ValidateFuncs( + validation.RequireEnvironment(f), + validateArgs, + ), AutoCompleteFunc: autoCompleteSecretNames(parentFlags), Examples: []naistrix.Example{ { diff --git a/internal/secret/command/environment.go b/internal/secret/command/environment.go index ce9f85ad..60511efe 100644 --- a/internal/secret/command/environment.go +++ b/internal/secret/command/environment.go @@ -3,12 +3,10 @@ package command import ( "context" "fmt" - "os" "slices" "sort" "strings" - "github.com/nais/cli/internal/cliflags" "github.com/nais/cli/internal/secret" ) @@ -45,14 +43,3 @@ func selectSecretEnvironment(team, name, provided string, envs []string) (string return "", fmt.Errorf("secret %q exists in multiple environments (%s); specify -e, --environment", name, strings.Join(envs, ", ")) } } - -func validateSingleEnvironmentFlagUsage() error { - if countEnvironmentFlagsInCLIArgs() > 1 { - return fmt.Errorf("only one -e, --environment flag may be provided") - } - return nil -} - -func countEnvironmentFlagsInCLIArgs() int { - return cliflags.CountFlagOccurrences(os.Args, "-e", "--environment") -} diff --git a/internal/secret/command/get.go b/internal/secret/command/get.go index b15e9ff3..f7da566d 100644 --- a/internal/secret/command/get.go +++ b/internal/secret/command/get.go @@ -42,32 +42,25 @@ func get(parentFlags *flag.Secret) *naistrix.Command { Description: "This command shows details about a secret, including its keys, workloads using it, and last modification info. Use --with-values to also fetch and display the actual secret values (access is logged for auditing).", Flags: f, Args: defaultArgs, - ValidateFunc: func(_ context.Context, args *naistrix.Arguments) error { - if err := validateSingleEnvironmentFlagUsage(); err != nil { - return err - } - if providedEnvironment := string(f.Environment); providedEnvironment != "" { - if err := validation.CheckEnvironment(providedEnvironment); err != nil { - return err + ValidateFunc: naistrix.ValidateFuncs( + validation.RequireEnvironment(f), + validateArgs, + func(_ context.Context, args *naistrix.Arguments) error { + if f.ToFile != "" && f.Key == "" { + return fmt.Errorf("--to-file requires --key to specify which key to extract") } - } - if err := validateArgs(args); err != nil { - return err - } - if f.ToFile != "" && f.Key == "" { - return fmt.Errorf("--to-file requires --key to specify which key to extract") - } - if f.Key != "" && f.ToFile == "" { - return fmt.Errorf("--key is only used with --to-file") - } - if f.Reason != "" && !f.WithValues && f.ToFile == "" { - return fmt.Errorf("--reason can only be used together with --with-values or --to-file") - } - if (f.WithValues || f.ToFile != "") && f.Reason != "" && len(f.Reason) < 10 { - return fmt.Errorf("reason must be at least 10 characters") - } - return nil - }, + if f.Key != "" && f.ToFile == "" { + return fmt.Errorf("--key is only used with --to-file") + } + if f.Reason != "" && !f.WithValues && f.ToFile == "" { + return fmt.Errorf("--reason can only be used together with --with-values or --to-file") + } + if (f.WithValues || f.ToFile != "") && f.Reason != "" && len(f.Reason) < 10 { + return fmt.Errorf("reason must be at least 10 characters") + } + return nil + }, + ), AutoCompleteFunc: autoCompleteSecretNames(parentFlags), Examples: []naistrix.Example{ { diff --git a/internal/secret/command/set.go b/internal/secret/command/set.go index bd9f1160..7ab8df5d 100644 --- a/internal/secret/command/set.go +++ b/internal/secret/command/set.go @@ -24,39 +24,34 @@ func set(parentFlags *flag.Secret) *naistrix.Command { Description: "Set a key-value pair in a secret. If the key already exists, its value is updated. If the key does not exist, it is added. Updating a value will cause a restart of workloads referencing the secret.", Flags: f, Args: defaultArgs, - ValidateFunc: func(_ context.Context, args *naistrix.Arguments) error { - if err := validateSingleEnvironmentFlagUsage(); err != nil { - return err - } - if err := validation.CheckEnvironment(string(f.Environment)); err != nil { - return err - } - if err := validateArgs(args); err != nil { - return err - } - if f.Key == "" { - return fmt.Errorf("--key is required") - } + ValidateFunc: naistrix.ValidateFuncs( + validation.RequireEnvironment(f), + validateArgs, + func(_ context.Context, args *naistrix.Arguments) error { + if f.Key == "" { + return fmt.Errorf("--key is required") + } - // Count the number of value sources provided - sources := 0 - if f.Value != "" { - sources++ - } - if f.ValueFromStdin { - sources++ - } - if f.ValueFromFile != "" { - sources++ - } - if sources == 0 { - return fmt.Errorf("--value, --value-from-stdin, or --value-from-file is required") - } - if sources > 1 { - return fmt.Errorf("--value, --value-from-stdin, and --value-from-file are mutually exclusive") - } - return nil - }, + // Count the number of value sources provided + sources := 0 + if f.Value != "" { + sources++ + } + if f.ValueFromStdin { + sources++ + } + if f.ValueFromFile != "" { + sources++ + } + if sources == 0 { + return fmt.Errorf("--value, --value-from-stdin, or --value-from-file is required") + } + if sources > 1 { + return fmt.Errorf("--value, --value-from-stdin, and --value-from-file are mutually exclusive") + } + return nil + }, + ), AutoCompleteFunc: autoCompleteSecretNames(parentFlags), Examples: []naistrix.Example{ { diff --git a/internal/secret/command/unset.go b/internal/secret/command/unset.go index 81268fa6..6f2dc8d9 100644 --- a/internal/secret/command/unset.go +++ b/internal/secret/command/unset.go @@ -19,21 +19,16 @@ func unset(parentFlags *flag.Secret) *naistrix.Command { Description: "This command removes a key-value pair from a secret.", Flags: f, Args: defaultArgs, - ValidateFunc: func(_ context.Context, args *naistrix.Arguments) error { - if err := validateSingleEnvironmentFlagUsage(); err != nil { - return err - } - if err := validation.CheckEnvironment(string(f.Environment)); err != nil { - return err - } - if err := validateArgs(args); err != nil { - return err - } - if f.Key == "" { - return fmt.Errorf("--key is required") - } - return nil - }, + ValidateFunc: naistrix.ValidateFuncs( + validation.RequireEnvironment(f), + validateArgs, + func(_ context.Context, args *naistrix.Arguments) error { + if f.Key == "" { + return fmt.Errorf("--key is required") + } + return nil + }, + ), AutoCompleteFunc: autoCompleteSecretNames(parentFlags), Examples: []naistrix.Example{ { diff --git a/internal/validation/validation.go b/internal/validation/validation.go new file mode 100644 index 00000000..665008e0 --- /dev/null +++ b/internal/validation/validation.go @@ -0,0 +1,51 @@ +package validation + +import ( + "context" + "fmt" + + "github.com/nais/naistrix" +) + +type teamChecker interface { + HasTeam() bool +} + +// RequireTeam returns a [naistrix.ValidateFunc] that ensures a team is set on the given argument. The argument to this +// function is typically a pointer to a flag struct. +func RequireTeam(f any) naistrix.ValidateFunc { + return func(context.Context, *naistrix.Arguments) error { + c, ok := f.(teamChecker) + if ok && c.HasTeam() { + return nil + } + + return fmt.Errorf("missing required team, specify a team using `nais defaults set team ` or by using the -t, --team flag") + } +} + +type envChecker interface { + HasEnvironment() bool +} + +// RequireEnvironment returns a [naistrix.ValidateFunc] that ensures an environment is set on the given argument. The +// argument to this function is typically a pointer to a flag struct. +func RequireEnvironment(f any) naistrix.ValidateFunc { + return func(context.Context, *naistrix.Arguments) error { + c, ok := f.(envChecker) + if ok && c.HasEnvironment() { + return nil + } + + return fmt.Errorf("missing required environment, specify an environment using `nais defaults set environment ` or by using the -e, --environment flag") + } +} + +// RequireTeamAndEnvironment returns a [naistrix.ValidateFunc] that ensures that the provided argument has both team and +// environment fields set. +func RequireTeamAndEnvironment(f any) naistrix.ValidateFunc { + return naistrix.ValidateFuncs( + RequireTeam(f), + RequireEnvironment(f), + ) +} diff --git a/internal/validation/validation_test.go b/internal/validation/validation_test.go new file mode 100644 index 00000000..ca24f1c5 --- /dev/null +++ b/internal/validation/validation_test.go @@ -0,0 +1,131 @@ +package validation + +import ( + "context" + "testing" + + "github.com/nais/cli/internal/flags" +) + +func TestRequireTeam(t *testing.T) { + tests := []struct { + name string + input any + wantErr bool + }{ + { + name: "valid AdditionalFlags with team", + input: &flags.AdditionalFlags{Team: "my-team"}, + wantErr: false, + }, + { + name: "AdditionalFlags with empty team", + input: &flags.AdditionalFlags{Team: ""}, + wantErr: true, + }, + { + name: "AdditionalFlags zero value", + input: &flags.AdditionalFlags{}, + wantErr: true, + }, + { + name: "non-flag type (string)", + input: "my-team", + wantErr: true, + }, + { + name: "nil", + input: nil, + wantErr: true, + }, + { + name: "GlobalFlags embedding *AdditionalFlags with team", + input: &flags.GlobalFlags{ + AdditionalFlags: &flags.AdditionalFlags{Team: "my-team"}, + }, + wantErr: false, + }, + { + name: "GlobalFlags embedding *AdditionalFlags without team", + input: &flags.GlobalFlags{ + AdditionalFlags: &flags.AdditionalFlags{}, + }, + wantErr: true, + }, + { + name: "GlobalFlags with nil embedded *AdditionalFlags", + input: &flags.GlobalFlags{ + AdditionalFlags: nil, + }, + wantErr: true, + }, + { + name: "struct embedding AdditionalFlags by value with team", + input: struct { + *flags.AdditionalFlags + }{ + AdditionalFlags: &flags.AdditionalFlags{Team: "my-team"}, + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + validate := RequireTeam(tt.input) + err := validate(context.Background(), nil) + if (err != nil) != tt.wantErr { + t.Errorf("RequireTeam() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestRequireTeamAndEnvironment(t *testing.T) { + tests := []struct { + name string + input any + wantErr bool + }{ + { + name: "AdditionalFlags with team and environment", + input: &flags.AdditionalFlags{Team: "my-team", Environment: "dev"}, + wantErr: false, + }, + { + name: "AdditionalFlags with team but no environment", + input: &flags.AdditionalFlags{Team: "my-team"}, + wantErr: true, + }, + { + name: "AdditionalFlags with environment but no team", + input: &flags.AdditionalFlags{Environment: "dev"}, + wantErr: true, + }, + { + name: "AdditionalFlags zero value", + input: &flags.AdditionalFlags{}, + wantErr: true, + }, + { + name: "non-flag type (string)", + input: "my-team", + wantErr: true, + }, + { + name: "nil", + input: nil, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + validate := RequireTeamAndEnvironment(tt.input) + err := validate(context.Background(), nil) + if (err != nil) != tt.wantErr { + t.Errorf("RequireTeamAndEnvironment() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/validation/validator.go b/internal/validation/validator.go deleted file mode 100644 index 2f729e37..00000000 --- a/internal/validation/validator.go +++ /dev/null @@ -1,19 +0,0 @@ -package validation - -import ( - "fmt" -) - -var CheckEnvironment = func(env string) error { - if env == "" { - return fmt.Errorf("environment cannot be empty, set environment using -e, --environment flag") - } - return nil -} - -var CheckTeam = func(team string) error { - if team == "" { - return fmt.Errorf("team cannot be empty, set team using 'nais defaults set team ' or the --team flag") - } - return nil -} diff --git a/internal/valkey/command/command.go b/internal/valkey/command/command.go index 62389e83..96e889fe 100644 --- a/internal/valkey/command/command.go +++ b/internal/valkey/command/command.go @@ -3,10 +3,8 @@ package command import ( "context" "fmt" - "os" "sort" - "github.com/nais/cli/internal/cliflags" "github.com/nais/cli/internal/flags" "github.com/nais/cli/internal/validation" "github.com/nais/cli/internal/valkey" @@ -17,14 +15,12 @@ import ( func Valkey(parentFlags *flags.GlobalFlags) *naistrix.Command { f := &flag.Valkey{GlobalFlags: parentFlags} return &naistrix.Command{ - Name: "valkey", - Aliases: []string{"valkeys"}, - Title: "Manage Valkey instances.", - Description: "Commands for creating, updating, deleting, and inspecting Valkey instances and their credentials.", - StickyFlags: f, - ValidateFunc: func(context.Context, *naistrix.Arguments) error { - return validation.CheckTeam(f.Team) - }, + Name: "valkey", + Aliases: []string{"valkeys"}, + Title: "Manage Valkey instances.", + Description: "Commands for creating, updating, deleting, and inspecting Valkey instances and their credentials.", + StickyFlags: f, + ValidateFunc: validation.RequireTeam(f), SubCommands: []*naistrix.Command{ create(f), credentials(f), @@ -40,7 +36,7 @@ var defaultArgs = []naistrix.Argument{ {Name: "name"}, } -func validateArgs(args *naistrix.Arguments) error { +func validateArgs(_ context.Context, args *naistrix.Arguments) error { if args.Len() != 1 { return fmt.Errorf("expected 1 argument, got %d", args.Len()) } @@ -63,17 +59,6 @@ func autoCompleteValkeyNames(ctx context.Context, team, environment string, requ return nil, "Please provide team to auto-complete Valkey instance names. 'nais defaults set team ', or '--team ' flag." } - if environmentFlagOccurrencesFromCLIArgs() > 1 { - return nil, "Please specify exactly one environment to auto-complete Valkey instance names. '-e, --environment ' flag." - } - - if environment == "" { - envs := environmentValuesFromCLIArgs() - if len(envs) == 1 { - environment = envs[0] - } - } - if requireEnvironment && environment == "" { return nil, "Please provide environment to auto-complete Valkey instance names. '-e, --environment ' flag." } @@ -103,18 +88,3 @@ func autoCompleteValkeyNames(ctx context.Context, team, environment string, requ return names, "Select a Valkey instance." } - -func environmentValuesFromCLIArgs() []string { - return cliflags.UniqueFlagValues(os.Args, "-e", "--environment") -} - -func environmentFlagOccurrencesFromCLIArgs() int { - return cliflags.CountFlagOccurrences(os.Args, "-e", "--environment") -} - -func validateSingleEnvironmentFlagUsage() error { - if environmentFlagOccurrencesFromCLIArgs() > 1 { - return fmt.Errorf("only one -e, --environment flag may be provided") - } - return nil -} diff --git a/internal/valkey/command/create.go b/internal/valkey/command/create.go index b78f5dcb..6c71adfd 100644 --- a/internal/valkey/command/create.go +++ b/internal/valkey/command/create.go @@ -21,19 +21,13 @@ func create(parentFlags *flag.Valkey) *naistrix.Command { Description: "This command creates a Valkey instance.", Flags: flags, Args: defaultArgs, - ValidateFunc: func(ctx context.Context, args *naistrix.Arguments) error { - if err := validateSingleEnvironmentFlagUsage(); err != nil { - return err - } - if err := validation.CheckEnvironment(string(flags.Environment)); err != nil { - return err - } - if err := flags.Validate(); err != nil { - return err - } - - return validateArgs(args) - }, + ValidateFunc: naistrix.ValidateFuncs( + validation.RequireEnvironment(flags), + validateArgs, + func(ctx context.Context, args *naistrix.Arguments) error { + return flags.Validate() + }, + ), Examples: []naistrix.Example{ { Description: "Create a Valkey instance named some-valkey with default settings.", diff --git a/internal/valkey/command/credentials.go b/internal/valkey/command/credentials.go index 261832dc..497d8070 100644 --- a/internal/valkey/command/credentials.go +++ b/internal/valkey/command/credentials.go @@ -21,27 +21,22 @@ func credentials(parentFlags *flag.Valkey) *naistrix.Command { Description: "Creates temporary credentials for accessing a Valkey instance. The credentials are printed to stdout as environment variables.", Flags: flags, Args: defaultArgs, - ValidateFunc: func(ctx context.Context, args *naistrix.Arguments) error { - if err := validateSingleEnvironmentFlagUsage(); err != nil { - return err - } - if err := validation.CheckEnvironment(string(flags.Environment)); err != nil { - return err - } - if err := validateArgs(args); err != nil { - return err - } - if flags.Permission == "" { - return fmt.Errorf("permission is required, set using --permission/-p flag (READ, WRITE, READWRITE, ADMIN)") - } - if !aiven.IsValidPermission(gql.CredentialPermission(flags.Permission)) { - return fmt.Errorf("invalid permission %q, must be one of: %v", flags.Permission, gql.AllCredentialPermission) - } - if flags.TTL == "" { - return fmt.Errorf("ttl is required, set using --ttl flag (e.g. '1d', '7d')") - } - return nil - }, + ValidateFunc: naistrix.ValidateFuncs( + validation.RequireEnvironment(flags), + validateArgs, + func(ctx context.Context, args *naistrix.Arguments) error { + if flags.Permission == "" { + return fmt.Errorf("permission is required, set using --permission/-p flag (READ, WRITE, READWRITE, ADMIN)") + } + if !aiven.IsValidPermission(gql.CredentialPermission(flags.Permission)) { + return fmt.Errorf("invalid permission %q, must be one of: %v", flags.Permission, gql.AllCredentialPermission) + } + if flags.TTL == "" { + return fmt.Errorf("ttl is required, set using --ttl flag (e.g. '1d', '7d')") + } + return nil + }, + ), AutoCompleteFunc: func(ctx context.Context, args *naistrix.Arguments, _ string) ([]string, string) { if args.Len() != 0 { return nil, "" diff --git a/internal/valkey/command/delete.go b/internal/valkey/command/delete.go index 19b70732..a7e011ce 100644 --- a/internal/valkey/command/delete.go +++ b/internal/valkey/command/delete.go @@ -20,15 +20,10 @@ func delete(parentFlags *flag.Valkey) *naistrix.Command { Description: "This command deletes an existing Valkey instance.", Flags: flags, Args: defaultArgs, - ValidateFunc: func(ctx context.Context, args *naistrix.Arguments) error { - if err := validateSingleEnvironmentFlagUsage(); err != nil { - return err - } - if err := validation.CheckEnvironment(string(flags.Environment)); err != nil { - return err - } - return validateArgs(args) - }, + ValidateFunc: naistrix.ValidateFuncs( + validation.RequireEnvironment(flags), + validateArgs, + ), AutoCompleteFunc: func(ctx context.Context, args *naistrix.Arguments, _ string) ([]string, string) { if args.Len() == 0 { return autoCompleteValkeyNames(ctx, flags.Team, string(flags.Environment), true) diff --git a/internal/valkey/command/get.go b/internal/valkey/command/get.go index 46ee7dc6..b0d405e0 100644 --- a/internal/valkey/command/get.go +++ b/internal/valkey/command/get.go @@ -19,15 +19,10 @@ func get(parentFlags *flag.Valkey) *naistrix.Command { Description: "This command describes a Valkey instance, listing its current configuration and access list.", Flags: flags, Args: defaultArgs, - ValidateFunc: func(ctx context.Context, args *naistrix.Arguments) error { - if err := validateSingleEnvironmentFlagUsage(); err != nil { - return err - } - if err := validation.CheckEnvironment(string(flags.Environment)); err != nil { - return err - } - return validateArgs(args) - }, + ValidateFunc: naistrix.ValidateFuncs( + validation.RequireEnvironment(flags), + validateArgs, + ), AutoCompleteFunc: func(ctx context.Context, args *naistrix.Arguments, _ string) ([]string, string) { if args.Len() == 0 { return autoCompleteValkeyNames(ctx, flags.Team, string(flags.Environment), true) diff --git a/internal/valkey/command/update.go b/internal/valkey/command/update.go index 3247437a..f8bcf197 100644 --- a/internal/valkey/command/update.go +++ b/internal/valkey/command/update.go @@ -21,19 +21,13 @@ func updateValkey(parentFlags *flag.Valkey) *naistrix.Command { Description: "This command updates an existing Valkey instance.", Flags: flags, Args: defaultArgs, - ValidateFunc: func(ctx context.Context, args *naistrix.Arguments) error { - if err := validateSingleEnvironmentFlagUsage(); err != nil { - return err - } - if err := validation.CheckEnvironment(string(flags.Environment)); err != nil { - return err - } - if err := flags.Validate(); err != nil { - return err - } - - return validateArgs(args) - }, + ValidateFunc: naistrix.ValidateFuncs( + validation.RequireEnvironment(flags), + validateArgs, + func(ctx context.Context, args *naistrix.Arguments) error { + return flags.Validate() + }, + ), AutoCompleteFunc: func(ctx context.Context, args *naistrix.Arguments, _ string) ([]string, string) { if args.Len() == 0 { return autoCompleteValkeyNames(ctx, flags.Team, string(flags.Environment), true) diff --git a/internal/vulnerability/command/list.go b/internal/vulnerability/command/list.go index b46bb476..e3ae7444 100644 --- a/internal/vulnerability/command/list.go +++ b/internal/vulnerability/command/list.go @@ -27,13 +27,11 @@ func list(parentFlags *flag.Vulnerabilities) *naistrix.Command { func listSummary(parentFlags *flag.List) *naistrix.Command { f := &flag.ListSummary{List: parentFlags, Output: "table"} return &naistrix.Command{ - Name: "summary", - Title: "List vulnerability summaries per workload.", - Description: "Show a vulnerability summary for each workload in a team, including risk score, severity breakdown, and SBOM status.", - Flags: f, - ValidateFunc: func(context.Context, *naistrix.Arguments) error { - return validation.CheckTeam(f.Team) - }, + Name: "summary", + Title: "List vulnerability summaries per workload.", + Description: "Show a vulnerability summary for each workload in a team, including risk score, severity breakdown, and SBOM status.", + Flags: f, + ValidateFunc: validation.RequireTeam(f), RunFunc: func(ctx context.Context, args *naistrix.Arguments, out *naistrix.OutputWriter) error { ret, err := vulnerability.ListWorkloadSummaries(ctx, f.Team, string(f.Environment)) if err != nil { @@ -96,13 +94,11 @@ func listSummary(parentFlags *flag.List) *naistrix.Command { func listAll(parentFlags *flag.List) *naistrix.Command { f := &flag.ListAll{List: parentFlags, Output: "table"} return &naistrix.Command{ - Name: "all", - Title: "List all vulnerabilities for team workloads.", - Description: "List every individual CVE affecting a team's workloads, showing severity, CVSS score, affected package, and suppression state.", - Flags: f, - ValidateFunc: func(context.Context, *naistrix.Arguments) error { - return validation.CheckTeam(f.Team) - }, + Name: "all", + Title: "List all vulnerabilities for team workloads.", + Description: "List every individual CVE affecting a team's workloads, showing severity, CVSS score, affected package, and suppression state.", + Flags: f, + ValidateFunc: validation.RequireTeam(f), RunFunc: func(ctx context.Context, args *naistrix.Arguments, out *naistrix.OutputWriter) error { ret, err := vulnerability.ListCVEs(ctx, f.Team, string(f.Environment)) if err != nil { diff --git a/internal/vulnerability/command/search.go b/internal/vulnerability/command/search.go index cd2d91d3..cc2ab695 100644 --- a/internal/vulnerability/command/search.go +++ b/internal/vulnerability/command/search.go @@ -29,7 +29,7 @@ func search(parentFlags *flag.Vulnerabilities) *naistrix.Command { return nil } - if err := validation.CheckTeam(f.Team); err != nil { + if err := validation.RequireTeam(f)(ctx, args); err != nil { return fmt.Errorf("%w (alternatively use -A/--all-teams)", err) } diff --git a/internal/vulnerability/command/summary.go b/internal/vulnerability/command/summary.go index 7af9582e..63cdb8b9 100644 --- a/internal/vulnerability/command/summary.go +++ b/internal/vulnerability/command/summary.go @@ -21,13 +21,11 @@ func formatCoveragePercent(v float64) string { func summary(parentFlags *flag.Vulnerabilities) *naistrix.Command { f := &flag.Summary{Vulnerabilities: parentFlags, Output: "table"} return &naistrix.Command{ - Name: "summary", - Title: "Get vulnerability summary for a team.", - Description: "Show an aggregated vulnerability summary for a team, including risk score, severity counts, SBOM coverage, and trend.", - Flags: f, - ValidateFunc: func(context.Context, *naistrix.Arguments) error { - return validation.CheckTeam(f.Team) - }, + Name: "summary", + Title: "Get vulnerability summary for a team.", + Description: "Show an aggregated vulnerability summary for a team, including risk score, severity counts, SBOM coverage, and trend.", + Flags: f, + ValidateFunc: validation.RequireTeam(f), RunFunc: func(ctx context.Context, args *naistrix.Arguments, out *naistrix.OutputWriter) error { summary, err := vulnerability.GetTeamSummary(ctx, f.Team, string(f.Environment)) if err != nil {