From b58897e5054d0bdd90ad0860054f5acb425e4987 Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Tue, 24 Feb 2026 18:17:47 +0100 Subject: [PATCH 1/6] feat(ske): split options to own subcommand relates to STACKITCLI-312 --- .../availability_zones/availability_zones.go | 104 ++++++++++++++ .../kubernetes_versions.go | 134 ++++++++++++++++++ .../options/machine_images/machine_images.go | 126 ++++++++++++++++ .../options/machine_types/machine_types.go | 106 ++++++++++++++ internal/cmd/ske/options/options.go | 14 ++ .../ske/options/volume_types/volume_types.go | 102 +++++++++++++ 6 files changed, 586 insertions(+) create mode 100644 internal/cmd/ske/options/availability_zones/availability_zones.go create mode 100644 internal/cmd/ske/options/kubernetes_versions/kubernetes_versions.go create mode 100644 internal/cmd/ske/options/machine_images/machine_images.go create mode 100644 internal/cmd/ske/options/machine_types/machine_types.go create mode 100644 internal/cmd/ske/options/volume_types/volume_types.go diff --git a/internal/cmd/ske/options/availability_zones/availability_zones.go b/internal/cmd/ske/options/availability_zones/availability_zones.go new file mode 100644 index 000000000..727bbe999 --- /dev/null +++ b/internal/cmd/ske/options/availability_zones/availability_zones.go @@ -0,0 +1,104 @@ +package availability_zones + +import ( + "context" + "fmt" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + "github.com/stackitcloud/stackit-sdk-go/services/ske" +) + +type inputModel struct { + *globalflags.GlobalFlagModel +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "availability-zones", + Short: "Lists SKE provider options for availability-zones", + Long: fmt.Sprintf("%s\n%s", + "Lists STACKIT Kubernetes Engine (SKE) provider options for availability-zones.", + ), + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `List SKE options for availability-zones`, + "$ stackit ske options availability-zones"), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, apiClient, model) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("get SKE provider options: %w", err) + } + + return outputResult(params.Printer, model, resp) + }, + } + return cmd +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + + model := inputModel{ + GlobalFlagModel: globalFlags, + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, apiClient *ske.APIClient, model *inputModel) ske.ApiListProviderOptionsRequest { + req := apiClient.ListProviderOptions(ctx, model.Region) + return req +} + +func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error { + if model == nil || model.GlobalFlagModel == nil { + return fmt.Errorf("model is nil") + } else if options == nil { + return fmt.Errorf("options is nil") + } + + options.MachineTypes = nil + options.VolumeTypes = nil + + return p.OutputResult(model.OutputFormat, options, func() error { + zones := *options.AvailabilityZones + + table := tables.NewTable() + table.SetHeader("ZONE") + for i := range zones { + z := zones[i] + table.AddRow(*z.Name) + } + + err := table.Display(p) + if err != nil { + return fmt.Errorf("display output: %w", err) + } + return nil + }) +} diff --git a/internal/cmd/ske/options/kubernetes_versions/kubernetes_versions.go b/internal/cmd/ske/options/kubernetes_versions/kubernetes_versions.go new file mode 100644 index 000000000..8033481b5 --- /dev/null +++ b/internal/cmd/ske/options/kubernetes_versions/kubernetes_versions.go @@ -0,0 +1,134 @@ +package kubernetes_versions + +import ( + "context" + "encoding/json" + "fmt" + "time" + + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + "github.com/stackitcloud/stackit-sdk-go/services/ske" +) + +const ( + supportedFlag = "supported" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + Supported bool +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "kubernetes-versions", + Short: "Lists SKE provider options for kubernetes-versions", + Long: fmt.Sprintf("%s\n%s", + "Lists STACKIT Kubernetes Engine (SKE) provider options for kubernetes-versions.", + ), + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `List SKE options for kubernetes-versions`, + "$ stackit ske options kubernetes-versions"), + examples.NewExample( + `List SKE options for supported kubernetes-versions`, + "$ stackit ske options kubernetes-versions --supported"), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, apiClient, model) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("get SKE provider options: %w", err) + } + + return outputResult(params.Printer, model, resp) + }, + } + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Bool(supportedFlag, false, "List supported versions only") +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + + model := inputModel{ + GlobalFlagModel: globalFlags, + Supported: flags.FlagToBoolValue(p, cmd, supportedFlag), + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, apiClient *ske.APIClient, model *inputModel) ske.ApiListProviderOptionsRequest { + req := apiClient.ListProviderOptions(ctx, model.Region) + if model.Supported { + req = req.VersionState("SUPPORTED") + } + return req +} + +func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error { + if model == nil || model.GlobalFlagModel == nil { + return fmt.Errorf("model is nil") + } else if options == nil { + return fmt.Errorf("options is nil") + } + + return p.OutputResult(model.OutputFormat, options, func() error { + versions := *options.KubernetesVersions + + table := tables.NewTable() + table.SetHeader("VERSION", "STATE", "EXPIRATION DATE", "FEATURE GATES") + for i := range versions { + v := versions[i] + featureGate, err := json.Marshal(*v.FeatureGates) + if err != nil { + return fmt.Errorf("marshal featureGates of Kubernetes version %q: %w", *v.Version, err) + } + expirationDate := "" + if v.ExpirationDate != nil { + expirationDate = v.ExpirationDate.Format(time.RFC3339) + } + table.AddRow( + utils.PtrString(v.Version), + utils.PtrString(v.State), + expirationDate, + string(featureGate)) + } + + err := table.Display(p) + if err != nil { + return fmt.Errorf("display output: %w", err) + } + return nil + }) +} diff --git a/internal/cmd/ske/options/machine_images/machine_images.go b/internal/cmd/ske/options/machine_images/machine_images.go new file mode 100644 index 000000000..761def6a7 --- /dev/null +++ b/internal/cmd/ske/options/machine_images/machine_images.go @@ -0,0 +1,126 @@ +package machine_images + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + "github.com/stackitcloud/stackit-sdk-go/services/ske" +) + +type inputModel struct { + *globalflags.GlobalFlagModel +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "machine-images", + Short: "Lists SKE provider options for machine-images", + Long: fmt.Sprintf("%s\n%s", + "Lists STACKIT Kubernetes Engine (SKE) provider options for machine-images.", + ), + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `List SKE options for machine-images`, + "$ stackit ske options machine-images"), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, apiClient, model) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("get SKE provider options: %w", err) + } + + return outputResult(params.Printer, model, resp) + }, + } + return cmd +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + + model := inputModel{ + GlobalFlagModel: globalFlags, + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, apiClient *ske.APIClient, model *inputModel) ske.ApiListProviderOptionsRequest { + req := apiClient.ListProviderOptions(ctx, model.Region) + return req +} + +func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error { + if model == nil || model.GlobalFlagModel == nil { + return fmt.Errorf("model is nil") + } else if options == nil { + return fmt.Errorf("options is nil") + } + + return p.OutputResult(model.OutputFormat, options, func() error { + images := *options.MachineImages + + table := tables.NewTable() + table.SetHeader("NAME", "VERSION", "STATE", "EXPIRATION DATE", "SUPPORTED CRI") + for i := range images { + image := images[i] + versions := *image.Versions + for j := range versions { + version := versions[j] + criNames := make([]string, 0) + for i := range *version.Cri { + cri := (*version.Cri)[i] + criNames = append(criNames, string(*cri.Name)) + } + criNamesString := strings.Join(criNames, ", ") + + expirationDate := "-" + if version.ExpirationDate != nil { + expirationDate = version.ExpirationDate.Format(time.RFC3339) + } + table.AddRow( + utils.PtrString(image.Name), + utils.PtrString(version.Version), + utils.PtrString(version.State), + expirationDate, + criNamesString, + ) + } + } + table.EnableAutoMergeOnColumns(1) + + err := table.Display(p) + if err != nil { + return fmt.Errorf("display output: %w", err) + } + return nil + }) +} diff --git a/internal/cmd/ske/options/machine_types/machine_types.go b/internal/cmd/ske/options/machine_types/machine_types.go new file mode 100644 index 000000000..67638068e --- /dev/null +++ b/internal/cmd/ske/options/machine_types/machine_types.go @@ -0,0 +1,106 @@ +package machine_types + +import ( + "context" + "fmt" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + "github.com/stackitcloud/stackit-sdk-go/services/ske" +) + +type inputModel struct { + *globalflags.GlobalFlagModel +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "machine-types", + Short: "Lists SKE provider options for machine-types", + Long: fmt.Sprintf("%s\n%s", + "Lists STACKIT Kubernetes Engine (SKE) provider options for machine-types.", + ), + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `List SKE options for machine-types`, + "$ stackit ske options machine-types"), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, apiClient, model) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("get SKE provider options: %w", err) + } + + return outputResult(params.Printer, model, resp) + }, + } + return cmd +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + + model := inputModel{ + GlobalFlagModel: globalFlags, + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, apiClient *ske.APIClient, model *inputModel) ske.ApiListProviderOptionsRequest { + req := apiClient.ListProviderOptions(ctx, model.Region) + return req +} + +func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error { + if model == nil || model.GlobalFlagModel == nil { + return fmt.Errorf("model is nil") + } else if options == nil { + return fmt.Errorf("options is nil") + } + + return p.OutputResult(model.OutputFormat, options, func() error { + machineTypes := *options.MachineTypes + + table := tables.NewTable() + table.SetHeader("TYPE", "CPU", "MEMORY") + for i := range machineTypes { + t := machineTypes[i] + table.AddRow( + utils.PtrString(t.Name), + utils.PtrString(t.Cpu), + utils.PtrString(t.Memory), + ) + } + + err := table.Display(p) + if err != nil { + return fmt.Errorf("display output: %w", err) + } + return nil + }) +} diff --git a/internal/cmd/ske/options/options.go b/internal/cmd/ske/options/options.go index 21f04d028..f4a7bd9b1 100644 --- a/internal/cmd/ske/options/options.go +++ b/internal/cmd/ske/options/options.go @@ -7,6 +7,11 @@ import ( "strings" "time" + "github.com/stackitcloud/stackit-cli/internal/cmd/ske/options/availability_zones" + "github.com/stackitcloud/stackit-cli/internal/cmd/ske/options/kubernetes_versions" + "github.com/stackitcloud/stackit-cli/internal/cmd/ske/options/machine_images" + "github.com/stackitcloud/stackit-cli/internal/cmd/ske/options/machine_types" + "github.com/stackitcloud/stackit-cli/internal/cmd/ske/options/volume_types" "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" @@ -82,9 +87,18 @@ func NewCmd(params *types.CmdParams) *cobra.Command { }, } configureFlags(cmd) + addSubcommands(cmd, params) return cmd } +func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { + cmd.AddCommand(availability_zones.NewCmd(params)) + cmd.AddCommand(kubernetes_versions.NewCmd(params)) + cmd.AddCommand(machine_images.NewCmd(params)) + cmd.AddCommand(machine_types.NewCmd(params)) + cmd.AddCommand(volume_types.NewCmd(params)) +} + func configureFlags(cmd *cobra.Command) { cmd.Flags().Bool(availabilityZonesFlag, false, "Lists availability zones") cmd.Flags().Bool(kubernetesVersionsFlag, false, "Lists supported kubernetes versions") diff --git a/internal/cmd/ske/options/volume_types/volume_types.go b/internal/cmd/ske/options/volume_types/volume_types.go new file mode 100644 index 000000000..0a058131d --- /dev/null +++ b/internal/cmd/ske/options/volume_types/volume_types.go @@ -0,0 +1,102 @@ +package volume_types + +import ( + "context" + "fmt" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + "github.com/stackitcloud/stackit-sdk-go/services/ske" +) + +type inputModel struct { + *globalflags.GlobalFlagModel +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "volume-types", + Short: "Lists SKE provider options for volume-types", + Long: fmt.Sprintf("%s\n%s", + "Lists STACKIT Kubernetes Engine (SKE) provider options for volume-types.", + ), + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `List SKE options for volume-types`, + "$ stackit ske options volume-types"), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, apiClient, model) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("get SKE provider options: %w", err) + } + + return outputResult(params.Printer, model, resp) + }, + } + return cmd +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + + model := inputModel{ + GlobalFlagModel: globalFlags, + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, apiClient *ske.APIClient, model *inputModel) ske.ApiListProviderOptionsRequest { + req := apiClient.ListProviderOptions(ctx, model.Region) + return req +} + +func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error { + if model == nil || model.GlobalFlagModel == nil { + return fmt.Errorf("model is nil") + } else if options == nil { + return fmt.Errorf("options is nil") + } + + return p.OutputResult(model.OutputFormat, options, func() error { + volumeTypes := *options.VolumeTypes + + table := tables.NewTable() + table.SetHeader("TYPE") + for i := range volumeTypes { + z := volumeTypes[i] + table.AddRow(utils.PtrString(z.Name)) + } + + err := table.Display(p) + if err != nil { + return fmt.Errorf("display output: %w", err) + } + return nil + }) +} From 161fee36c28b7d2ed40b0fe2cc547d8b5a2d4527 Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Wed, 25 Feb 2026 12:23:25 +0100 Subject: [PATCH 2/6] checked for possible nil pointers --- docs/stackit_ske_options.md | 5 +++ .../stackit_ske_options_availability-zones.md | 40 +++++++++++++++++ ...stackit_ske_options_kubernetes-versions.md | 44 +++++++++++++++++++ docs/stackit_ske_options_machine-images.md | 40 +++++++++++++++++ docs/stackit_ske_options_machine-types.md | 40 +++++++++++++++++ docs/stackit_ske_options_volume-types.md | 40 +++++++++++++++++ .../availability_zones/availability_zones.go | 20 ++++----- .../kubernetes_versions.go | 18 ++++---- .../options/machine_images/machine_images.go | 22 +++++----- .../options/machine_types/machine_types.go | 14 +++--- internal/cmd/ske/options/options.go | 6 +-- internal/cmd/ske/options/options_test.go | 12 ++--- .../ske/options/volume_types/volume_types.go | 14 +++--- 13 files changed, 256 insertions(+), 59 deletions(-) create mode 100644 docs/stackit_ske_options_availability-zones.md create mode 100644 docs/stackit_ske_options_kubernetes-versions.md create mode 100644 docs/stackit_ske_options_machine-images.md create mode 100644 docs/stackit_ske_options_machine-types.md create mode 100644 docs/stackit_ske_options_volume-types.md diff --git a/docs/stackit_ske_options.md b/docs/stackit_ske_options.md index 76afbe93c..b3440a347 100644 --- a/docs/stackit_ske_options.md +++ b/docs/stackit_ske_options.md @@ -49,4 +49,9 @@ stackit ske options [flags] ### SEE ALSO * [stackit ske](./stackit_ske.md) - Provides functionality for SKE +* [stackit ske options availability-zones](./stackit_ske_options_availability-zones.md) - Lists SKE provider options for availability-zones +* [stackit ske options kubernetes-versions](./stackit_ske_options_kubernetes-versions.md) - Lists SKE provider options for kubernetes-versions +* [stackit ske options machine-images](./stackit_ske_options_machine-images.md) - Lists SKE provider options for machine-images +* [stackit ske options machine-types](./stackit_ske_options_machine-types.md) - Lists SKE provider options for machine-types +* [stackit ske options volume-types](./stackit_ske_options_volume-types.md) - Lists SKE provider options for volume-types diff --git a/docs/stackit_ske_options_availability-zones.md b/docs/stackit_ske_options_availability-zones.md new file mode 100644 index 000000000..4bf77c67f --- /dev/null +++ b/docs/stackit_ske_options_availability-zones.md @@ -0,0 +1,40 @@ +## stackit ske options availability-zones + +Lists SKE provider options for availability-zones + +### Synopsis + +Lists STACKIT Kubernetes Engine (SKE) provider options for availability-zones. + +``` +stackit ske options availability-zones [flags] +``` + +### Examples + +``` + List SKE options for availability-zones + $ stackit ske options availability-zones +``` + +### Options + +``` + -h, --help Help for "stackit ske options availability-zones" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit ske options](./stackit_ske_options.md) - Lists SKE provider options + diff --git a/docs/stackit_ske_options_kubernetes-versions.md b/docs/stackit_ske_options_kubernetes-versions.md new file mode 100644 index 000000000..a2dd50edd --- /dev/null +++ b/docs/stackit_ske_options_kubernetes-versions.md @@ -0,0 +1,44 @@ +## stackit ske options kubernetes-versions + +Lists SKE provider options for kubernetes-versions + +### Synopsis + +Lists STACKIT Kubernetes Engine (SKE) provider options for kubernetes-versions. + +``` +stackit ske options kubernetes-versions [flags] +``` + +### Examples + +``` + List SKE options for kubernetes-versions + $ stackit ske options kubernetes-versions + + List SKE options for supported kubernetes-versions + $ stackit ske options kubernetes-versions --supported +``` + +### Options + +``` + -h, --help Help for "stackit ske options kubernetes-versions" + --supported List supported versions only +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit ske options](./stackit_ske_options.md) - Lists SKE provider options + diff --git a/docs/stackit_ske_options_machine-images.md b/docs/stackit_ske_options_machine-images.md new file mode 100644 index 000000000..f6deb67db --- /dev/null +++ b/docs/stackit_ske_options_machine-images.md @@ -0,0 +1,40 @@ +## stackit ske options machine-images + +Lists SKE provider options for machine-images + +### Synopsis + +Lists STACKIT Kubernetes Engine (SKE) provider options for machine-images. + +``` +stackit ske options machine-images [flags] +``` + +### Examples + +``` + List SKE options for machine-images + $ stackit ske options machine-images +``` + +### Options + +``` + -h, --help Help for "stackit ske options machine-images" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit ske options](./stackit_ske_options.md) - Lists SKE provider options + diff --git a/docs/stackit_ske_options_machine-types.md b/docs/stackit_ske_options_machine-types.md new file mode 100644 index 000000000..333384fc3 --- /dev/null +++ b/docs/stackit_ske_options_machine-types.md @@ -0,0 +1,40 @@ +## stackit ske options machine-types + +Lists SKE provider options for machine-types + +### Synopsis + +Lists STACKIT Kubernetes Engine (SKE) provider options for machine-types. + +``` +stackit ske options machine-types [flags] +``` + +### Examples + +``` + List SKE options for machine-types + $ stackit ske options machine-types +``` + +### Options + +``` + -h, --help Help for "stackit ske options machine-types" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit ske options](./stackit_ske_options.md) - Lists SKE provider options + diff --git a/docs/stackit_ske_options_volume-types.md b/docs/stackit_ske_options_volume-types.md new file mode 100644 index 000000000..aeea921dc --- /dev/null +++ b/docs/stackit_ske_options_volume-types.md @@ -0,0 +1,40 @@ +## stackit ske options volume-types + +Lists SKE provider options for volume-types + +### Synopsis + +Lists STACKIT Kubernetes Engine (SKE) provider options for volume-types. + +``` +stackit ske options volume-types [flags] +``` + +### Examples + +``` + List SKE options for volume-types + $ stackit ske options volume-types +``` + +### Options + +``` + -h, --help Help for "stackit ske options volume-types" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit ske options](./stackit_ske_options.md) - Lists SKE provider options + diff --git a/internal/cmd/ske/options/availability_zones/availability_zones.go b/internal/cmd/ske/options/availability_zones/availability_zones.go index 727bbe999..fd6b89565 100644 --- a/internal/cmd/ske/options/availability_zones/availability_zones.go +++ b/internal/cmd/ske/options/availability_zones/availability_zones.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/spf13/cobra" "github.com/stackitcloud/stackit-cli/internal/pkg/args" @@ -17,17 +18,15 @@ import ( ) type inputModel struct { - *globalflags.GlobalFlagModel + globalflags.GlobalFlagModel } func NewCmd(params *types.CmdParams) *cobra.Command { cmd := &cobra.Command{ Use: "availability-zones", Short: "Lists SKE provider options for availability-zones", - Long: fmt.Sprintf("%s\n%s", - "Lists STACKIT Kubernetes Engine (SKE) provider options for availability-zones.", - ), - Args: args.NoArgs, + Long: "Lists STACKIT Kubernetes Engine (SKE) provider options for availability-zones.", + Args: args.NoArgs, Example: examples.Build( examples.NewExample( `List SKE options for availability-zones`, @@ -63,7 +62,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, globalFlags := globalflags.Parse(p, cmd) model := inputModel{ - GlobalFlagModel: globalFlags, + GlobalFlagModel: utils.PtrValue(globalFlags), } p.DebugInputModel(model) @@ -76,23 +75,20 @@ func buildRequest(ctx context.Context, apiClient *ske.APIClient, model *inputMod } func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error { - if model == nil || model.GlobalFlagModel == nil { + if model == nil { return fmt.Errorf("model is nil") } else if options == nil { return fmt.Errorf("options is nil") } - options.MachineTypes = nil - options.VolumeTypes = nil - return p.OutputResult(model.OutputFormat, options, func() error { - zones := *options.AvailabilityZones + zones := utils.PtrValue(options.AvailabilityZones) table := tables.NewTable() table.SetHeader("ZONE") for i := range zones { z := zones[i] - table.AddRow(*z.Name) + table.AddRow(utils.PtrValue(z.Name)) } err := table.Display(p) diff --git a/internal/cmd/ske/options/kubernetes_versions/kubernetes_versions.go b/internal/cmd/ske/options/kubernetes_versions/kubernetes_versions.go index 8033481b5..4444b23c3 100644 --- a/internal/cmd/ske/options/kubernetes_versions/kubernetes_versions.go +++ b/internal/cmd/ske/options/kubernetes_versions/kubernetes_versions.go @@ -25,7 +25,7 @@ const ( ) type inputModel struct { - *globalflags.GlobalFlagModel + globalflags.GlobalFlagModel Supported bool } @@ -33,10 +33,8 @@ func NewCmd(params *types.CmdParams) *cobra.Command { cmd := &cobra.Command{ Use: "kubernetes-versions", Short: "Lists SKE provider options for kubernetes-versions", - Long: fmt.Sprintf("%s\n%s", - "Lists STACKIT Kubernetes Engine (SKE) provider options for kubernetes-versions.", - ), - Args: args.NoArgs, + Long: "Lists STACKIT Kubernetes Engine (SKE) provider options for kubernetes-versions.", + Args: args.NoArgs, Example: examples.Build( examples.NewExample( `List SKE options for kubernetes-versions`, @@ -80,7 +78,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, globalFlags := globalflags.Parse(p, cmd) model := inputModel{ - GlobalFlagModel: globalFlags, + GlobalFlagModel: utils.PtrValue(globalFlags), Supported: flags.FlagToBoolValue(p, cmd, supportedFlag), } @@ -97,22 +95,22 @@ func buildRequest(ctx context.Context, apiClient *ske.APIClient, model *inputMod } func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error { - if model == nil || model.GlobalFlagModel == nil { + if model == nil { return fmt.Errorf("model is nil") } else if options == nil { return fmt.Errorf("options is nil") } return p.OutputResult(model.OutputFormat, options, func() error { - versions := *options.KubernetesVersions + versions := utils.PtrValue(options.KubernetesVersions) table := tables.NewTable() table.SetHeader("VERSION", "STATE", "EXPIRATION DATE", "FEATURE GATES") for i := range versions { v := versions[i] - featureGate, err := json.Marshal(*v.FeatureGates) + featureGate, err := json.Marshal(utils.PtrValue(v.FeatureGates)) if err != nil { - return fmt.Errorf("marshal featureGates of Kubernetes version %q: %w", *v.Version, err) + return fmt.Errorf("marshal featureGates of Kubernetes version %q: %w", utils.PtrValue(v.Version), err) } expirationDate := "" if v.ExpirationDate != nil { diff --git a/internal/cmd/ske/options/machine_images/machine_images.go b/internal/cmd/ske/options/machine_images/machine_images.go index 761def6a7..77753a0e2 100644 --- a/internal/cmd/ske/options/machine_images/machine_images.go +++ b/internal/cmd/ske/options/machine_images/machine_images.go @@ -20,17 +20,15 @@ import ( ) type inputModel struct { - *globalflags.GlobalFlagModel + globalflags.GlobalFlagModel } func NewCmd(params *types.CmdParams) *cobra.Command { cmd := &cobra.Command{ Use: "machine-images", Short: "Lists SKE provider options for machine-images", - Long: fmt.Sprintf("%s\n%s", - "Lists STACKIT Kubernetes Engine (SKE) provider options for machine-images.", - ), - Args: args.NoArgs, + Long: "Lists STACKIT Kubernetes Engine (SKE) provider options for machine-images.", + Args: args.NoArgs, Example: examples.Build( examples.NewExample( `List SKE options for machine-images`, @@ -66,7 +64,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, globalFlags := globalflags.Parse(p, cmd) model := inputModel{ - GlobalFlagModel: globalFlags, + GlobalFlagModel: utils.PtrValue(globalFlags), } p.DebugInputModel(model) @@ -79,26 +77,26 @@ func buildRequest(ctx context.Context, apiClient *ske.APIClient, model *inputMod } func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error { - if model == nil || model.GlobalFlagModel == nil { + if model == nil { return fmt.Errorf("model is nil") } else if options == nil { return fmt.Errorf("options is nil") } return p.OutputResult(model.OutputFormat, options, func() error { - images := *options.MachineImages + images := utils.PtrValue(options.MachineImages) table := tables.NewTable() table.SetHeader("NAME", "VERSION", "STATE", "EXPIRATION DATE", "SUPPORTED CRI") for i := range images { image := images[i] - versions := *image.Versions + versions := utils.PtrValue(image.Versions) for j := range versions { version := versions[j] criNames := make([]string, 0) - for i := range *version.Cri { - cri := (*version.Cri)[i] - criNames = append(criNames, string(*cri.Name)) + for i := range utils.PtrValue(version.Cri) { + cri := utils.PtrValue(version.Cri)[i] + criNames = append(criNames, utils.PtrString(cri.Name)) } criNamesString := strings.Join(criNames, ", ") diff --git a/internal/cmd/ske/options/machine_types/machine_types.go b/internal/cmd/ske/options/machine_types/machine_types.go index 67638068e..ac45c0ac9 100644 --- a/internal/cmd/ske/options/machine_types/machine_types.go +++ b/internal/cmd/ske/options/machine_types/machine_types.go @@ -18,17 +18,15 @@ import ( ) type inputModel struct { - *globalflags.GlobalFlagModel + globalflags.GlobalFlagModel } func NewCmd(params *types.CmdParams) *cobra.Command { cmd := &cobra.Command{ Use: "machine-types", Short: "Lists SKE provider options for machine-types", - Long: fmt.Sprintf("%s\n%s", - "Lists STACKIT Kubernetes Engine (SKE) provider options for machine-types.", - ), - Args: args.NoArgs, + Long: "Lists STACKIT Kubernetes Engine (SKE) provider options for machine-types.", + Args: args.NoArgs, Example: examples.Build( examples.NewExample( `List SKE options for machine-types`, @@ -64,7 +62,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, globalFlags := globalflags.Parse(p, cmd) model := inputModel{ - GlobalFlagModel: globalFlags, + GlobalFlagModel: utils.PtrValue(globalFlags), } p.DebugInputModel(model) @@ -77,14 +75,14 @@ func buildRequest(ctx context.Context, apiClient *ske.APIClient, model *inputMod } func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error { - if model == nil || model.GlobalFlagModel == nil { + if model == nil { return fmt.Errorf("model is nil") } else if options == nil { return fmt.Errorf("options is nil") } return p.OutputResult(model.OutputFormat, options, func() error { - machineTypes := *options.MachineTypes + machineTypes := utils.PtrValue(options.MachineTypes) table := tables.NewTable() table.SetHeader("TYPE", "CPU", "MEMORY") diff --git a/internal/cmd/ske/options/options.go b/internal/cmd/ske/options/options.go index f4a7bd9b1..3a81e6ed0 100644 --- a/internal/cmd/ske/options/options.go +++ b/internal/cmd/ske/options/options.go @@ -35,7 +35,7 @@ const ( ) type inputModel struct { - *globalflags.GlobalFlagModel + globalflags.GlobalFlagModel AvailabilityZones bool KubernetesVersions bool MachineImages bool @@ -125,7 +125,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, } model := inputModel{ - GlobalFlagModel: globalFlags, + GlobalFlagModel: utils.PtrValue(globalFlags), AvailabilityZones: availabilityZones, KubernetesVersions: kubernetesVersions, MachineImages: machineImages, @@ -143,7 +143,7 @@ func buildRequest(ctx context.Context, apiClient *ske.APIClient, model *inputMod } func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error { - if model == nil || model.GlobalFlagModel == nil { + if model == nil { return fmt.Errorf("model is nil") } else if options == nil { return fmt.Errorf("options is nil") diff --git a/internal/cmd/ske/options/options_test.go b/internal/cmd/ske/options/options_test.go index 43f58c5b4..2fe568f2a 100644 --- a/internal/cmd/ske/options/options_test.go +++ b/internal/cmd/ske/options/options_test.go @@ -39,7 +39,7 @@ func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]st func fixtureInputModelAllFalse(mods ...func(model *inputModel)) *inputModel { model := &inputModel{ - GlobalFlagModel: &globalflags.GlobalFlagModel{Region: testRegion, Verbosity: globalflags.VerbosityDefault}, + GlobalFlagModel: globalflags.GlobalFlagModel{Region: testRegion, Verbosity: globalflags.VerbosityDefault}, AvailabilityZones: false, KubernetesVersions: false, MachineImages: false, @@ -54,7 +54,7 @@ func fixtureInputModelAllFalse(mods ...func(model *inputModel)) *inputModel { func fixtureInputModelAllTrue(mods ...func(model *inputModel)) *inputModel { model := &inputModel{ - GlobalFlagModel: &globalflags.GlobalFlagModel{Region: testRegion, Verbosity: globalflags.VerbosityDefault}, + GlobalFlagModel: globalflags.GlobalFlagModel{Region: testRegion, Verbosity: globalflags.VerbosityDefault}, AvailabilityZones: true, KubernetesVersions: true, MachineImages: true, @@ -187,24 +187,24 @@ func TestOutputResult(t *testing.T) { name: "missing options", args: args{ model: &inputModel{ - GlobalFlagModel: &globalflags.GlobalFlagModel{}, + GlobalFlagModel: globalflags.GlobalFlagModel{}, }, }, wantErr: true, }, { - name: "missing global flags in model", + name: "empty input model", args: args{ model: &inputModel{}, options: &ske.ProviderOptions{}, }, - wantErr: true, + wantErr: false, }, { name: "set model and options", args: args{ model: &inputModel{ - GlobalFlagModel: &globalflags.GlobalFlagModel{}, + GlobalFlagModel: globalflags.GlobalFlagModel{}, }, options: &ske.ProviderOptions{}, }, diff --git a/internal/cmd/ske/options/volume_types/volume_types.go b/internal/cmd/ske/options/volume_types/volume_types.go index 0a058131d..d18832c29 100644 --- a/internal/cmd/ske/options/volume_types/volume_types.go +++ b/internal/cmd/ske/options/volume_types/volume_types.go @@ -18,17 +18,15 @@ import ( ) type inputModel struct { - *globalflags.GlobalFlagModel + globalflags.GlobalFlagModel } func NewCmd(params *types.CmdParams) *cobra.Command { cmd := &cobra.Command{ Use: "volume-types", Short: "Lists SKE provider options for volume-types", - Long: fmt.Sprintf("%s\n%s", - "Lists STACKIT Kubernetes Engine (SKE) provider options for volume-types.", - ), - Args: args.NoArgs, + Long: "Lists STACKIT Kubernetes Engine (SKE) provider options for volume-types.", + Args: args.NoArgs, Example: examples.Build( examples.NewExample( `List SKE options for volume-types`, @@ -64,7 +62,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, globalFlags := globalflags.Parse(p, cmd) model := inputModel{ - GlobalFlagModel: globalFlags, + GlobalFlagModel: utils.PtrValue(globalFlags), } p.DebugInputModel(model) @@ -77,14 +75,14 @@ func buildRequest(ctx context.Context, apiClient *ske.APIClient, model *inputMod } func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error { - if model == nil || model.GlobalFlagModel == nil { + if model == nil { return fmt.Errorf("model is nil") } else if options == nil { return fmt.Errorf("options is nil") } return p.OutputResult(model.OutputFormat, options, func() error { - volumeTypes := *options.VolumeTypes + volumeTypes := utils.PtrValue(options.VolumeTypes) table := tables.NewTable() table.SetHeader("TYPE") From 99c72b2a382803cc69ebf04119420704785aeed2 Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Wed, 25 Feb 2026 16:01:33 +0100 Subject: [PATCH 3/6] added testcases --- .../availability_zones/availability_zones.go | 9 +- .../availability_zones_test.go | 203 +++++++++++++++ .../kubernetes_versions.go | 9 +- .../kubernetes_versions_test.go | 231 ++++++++++++++++++ .../options/machine_images/machine_images.go | 9 +- .../machine_images/machine_images_test.go | 216 ++++++++++++++++ .../options/machine_types/machine_types.go | 9 +- .../machine_types/machine_types_test.go | 211 ++++++++++++++++ .../ske/options/volume_types/volume_types.go | 9 +- .../options/volume_types/volume_types_test.go | 203 +++++++++++++++ 10 files changed, 1094 insertions(+), 15 deletions(-) create mode 100644 internal/cmd/ske/options/availability_zones/availability_zones_test.go create mode 100644 internal/cmd/ske/options/kubernetes_versions/kubernetes_versions_test.go create mode 100644 internal/cmd/ske/options/machine_images/machine_images_test.go create mode 100644 internal/cmd/ske/options/machine_types/machine_types_test.go create mode 100644 internal/cmd/ske/options/volume_types/volume_types_test.go diff --git a/internal/cmd/ske/options/availability_zones/availability_zones.go b/internal/cmd/ske/options/availability_zones/availability_zones.go index fd6b89565..bef599ad4 100644 --- a/internal/cmd/ske/options/availability_zones/availability_zones.go +++ b/internal/cmd/ske/options/availability_zones/availability_zones.go @@ -75,12 +75,15 @@ func buildRequest(ctx context.Context, apiClient *ske.APIClient, model *inputMod } func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error { - if model == nil { - return fmt.Errorf("model is nil") - } else if options == nil { + if options == nil { return fmt.Errorf("options is nil") } + options.KubernetesVersions = nil + options.MachineImages = nil + options.MachineTypes = nil + options.VolumeTypes = nil + return p.OutputResult(model.OutputFormat, options, func() error { zones := utils.PtrValue(options.AvailabilityZones) diff --git a/internal/cmd/ske/options/availability_zones/availability_zones_test.go b/internal/cmd/ske/options/availability_zones/availability_zones_test.go new file mode 100644 index 000000000..c381a9633 --- /dev/null +++ b/internal/cmd/ske/options/availability_zones/availability_zones_test.go @@ -0,0 +1,203 @@ +package availability_zones + +import ( + "context" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stackitcloud/stackit-sdk-go/services/ske" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &ske.APIClient{} + +const testRegion = "eu01" + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.RegionFlag: testRegion, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{ + Region: testRegion, + Verbosity: globalflags.VerbosityDefault, + }, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Region = "" + }), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + inputModel *inputModel + expectedRequest ske.ApiListProviderOptionsRequest + }{ + { + description: "base", + inputModel: fixtureInputModel(), + expectedRequest: testClient.ListProviderOptions(testCtx, testRegion), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, testClient, tt.inputModel) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + model *inputModel + options *ske.ProviderOptions + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty", + args: args{}, + wantErr: true, + }, + { + name: "missing options", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + }, + wantErr: true, + }, + { + name: "empty input model", + args: args{ + model: &inputModel{}, + options: &ske.ProviderOptions{}, + }, + wantErr: false, + }, + { + name: "set model and options", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{}, + }, + wantErr: false, + }, + { + name: "empty values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + AvailabilityZones: &[]ske.AvailabilityZone{}, + }, + }, + wantErr: false, + }, + { + name: "empty value in values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + AvailabilityZones: &[]ske.AvailabilityZone{{}}, + }, + }, + wantErr: false, + }, + { + name: "valid values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + AvailabilityZones: &[]ske.AvailabilityZone{ + ske.AvailabilityZone{ + Name: utils.Ptr("zone1"), + }, + ske.AvailabilityZone{ + Name: utils.Ptr("zone2"), + }, + }, + }, + }, + wantErr: false, + }, + } + p := print.NewPrinter() + p.Cmd = NewCmd(&types.CmdParams{Printer: p}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.args.model, tt.args.options); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/ske/options/kubernetes_versions/kubernetes_versions.go b/internal/cmd/ske/options/kubernetes_versions/kubernetes_versions.go index 4444b23c3..f7e0b8ab2 100644 --- a/internal/cmd/ske/options/kubernetes_versions/kubernetes_versions.go +++ b/internal/cmd/ske/options/kubernetes_versions/kubernetes_versions.go @@ -95,12 +95,15 @@ func buildRequest(ctx context.Context, apiClient *ske.APIClient, model *inputMod } func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error { - if model == nil { - return fmt.Errorf("model is nil") - } else if options == nil { + if options == nil { return fmt.Errorf("options is nil") } + options.AvailabilityZones = nil + options.MachineImages = nil + options.MachineTypes = nil + options.VolumeTypes = nil + return p.OutputResult(model.OutputFormat, options, func() error { versions := utils.PtrValue(options.KubernetesVersions) diff --git a/internal/cmd/ske/options/kubernetes_versions/kubernetes_versions_test.go b/internal/cmd/ske/options/kubernetes_versions/kubernetes_versions_test.go new file mode 100644 index 000000000..d487ac43a --- /dev/null +++ b/internal/cmd/ske/options/kubernetes_versions/kubernetes_versions_test.go @@ -0,0 +1,231 @@ +package kubernetes_versions + +import ( + "context" + "testing" + "time" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stackitcloud/stackit-sdk-go/services/ske" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &ske.APIClient{} + +const testRegion = "eu01" + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.RegionFlag: testRegion, + supportedFlag: "false", + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{ + Region: testRegion, + Verbosity: globalflags.VerbosityDefault, + }, + Supported: false, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Region = "" + }), + }, + { + description: "supported only", + flagValues: map[string]string{ + supportedFlag: "true", + }, + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Supported = true + model.Region = "" + }), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + inputModel *inputModel + expectedRequest ske.ApiListProviderOptionsRequest + }{ + { + description: "base", + inputModel: fixtureInputModel(), + expectedRequest: testClient.ListProviderOptions(testCtx, testRegion), + }, + { + description: "base", + inputModel: fixtureInputModel(func(model *inputModel) { + model.Supported = true + }), + expectedRequest: testClient.ListProviderOptions(testCtx, testRegion).VersionState("SUPPORTED"), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, testClient, tt.inputModel) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + model *inputModel + options *ske.ProviderOptions + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty", + args: args{}, + wantErr: true, + }, + { + name: "missing options", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + }, + wantErr: true, + }, + { + name: "empty input model", + args: args{ + model: &inputModel{}, + options: &ske.ProviderOptions{}, + }, + wantErr: false, + }, + { + name: "set model and options", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{}, + }, + wantErr: false, + }, + { + name: "empty values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + KubernetesVersions: &[]ske.KubernetesVersion{}, + }, + }, + wantErr: false, + }, + { + name: "empty value in values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + KubernetesVersions: &[]ske.KubernetesVersion{{}}, + }, + }, + wantErr: false, + }, + { + name: "valid values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + KubernetesVersions: &[]ske.KubernetesVersion{ + ske.KubernetesVersion{ + FeatureGates: &map[string]string{ + "featureGate1": "foo", + "featureGate2": "bar", + }, + State: utils.Ptr("supported"), + Version: utils.Ptr("0.00.0"), + }, + ske.KubernetesVersion{ + ExpirationDate: utils.Ptr(time.Now()), + State: utils.Ptr("deprecated"), + Version: utils.Ptr("0.00.0"), + }, + }, + }, + }, + wantErr: false, + }, + } + p := print.NewPrinter() + p.Cmd = NewCmd(&types.CmdParams{Printer: p}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.args.model, tt.args.options); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/ske/options/machine_images/machine_images.go b/internal/cmd/ske/options/machine_images/machine_images.go index 77753a0e2..19eae47d5 100644 --- a/internal/cmd/ske/options/machine_images/machine_images.go +++ b/internal/cmd/ske/options/machine_images/machine_images.go @@ -77,12 +77,15 @@ func buildRequest(ctx context.Context, apiClient *ske.APIClient, model *inputMod } func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error { - if model == nil { - return fmt.Errorf("model is nil") - } else if options == nil { + if options == nil { return fmt.Errorf("options is nil") } + options.AvailabilityZones = nil + options.KubernetesVersions = nil + options.MachineTypes = nil + options.VolumeTypes = nil + return p.OutputResult(model.OutputFormat, options, func() error { images := utils.PtrValue(options.MachineImages) diff --git a/internal/cmd/ske/options/machine_images/machine_images_test.go b/internal/cmd/ske/options/machine_images/machine_images_test.go new file mode 100644 index 000000000..aeb8bab4f --- /dev/null +++ b/internal/cmd/ske/options/machine_images/machine_images_test.go @@ -0,0 +1,216 @@ +package machine_images + +import ( + "context" + "testing" + "time" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stackitcloud/stackit-sdk-go/services/ske" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &ske.APIClient{} + +const testRegion = "eu01" + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.RegionFlag: testRegion, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{ + Region: testRegion, + Verbosity: globalflags.VerbosityDefault, + }, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Region = "" + }), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + inputModel *inputModel + expectedRequest ske.ApiListProviderOptionsRequest + }{ + { + description: "base", + inputModel: fixtureInputModel(), + expectedRequest: testClient.ListProviderOptions(testCtx, testRegion), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, testClient, tt.inputModel) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + model *inputModel + options *ske.ProviderOptions + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty", + args: args{}, + wantErr: true, + }, + { + name: "missing options", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + }, + wantErr: true, + }, + { + name: "empty input model", + args: args{ + model: &inputModel{}, + options: &ske.ProviderOptions{}, + }, + wantErr: false, + }, + { + name: "set model and options", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{}, + }, + wantErr: false, + }, + { + name: "empty values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + MachineImages: &[]ske.MachineImage{}, + }, + }, + wantErr: false, + }, + { + name: "empty value in values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + MachineImages: &[]ske.MachineImage{{}}, + }, + }, + wantErr: false, + }, + { + name: "valid values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + MachineImages: &[]ske.MachineImage{ + ske.MachineImage{ + Name: utils.Ptr("image1"), + Versions: &[]ske.MachineImageVersion{ + ske.MachineImageVersion{ + Cri: &[]ske.CRI{ + ske.CRI{ + Name: ske.CRINAME_CONTAINERD.Ptr(), + }, + }, + ExpirationDate: utils.Ptr(time.Now()), + State: utils.Ptr("supported"), + Version: utils.Ptr("0.00.0"), + }, + }, + }, + ske.MachineImage{ + Name: utils.Ptr("zone2"), + }, + }, + }, + }, + wantErr: false, + }, + } + p := print.NewPrinter() + p.Cmd = NewCmd(&types.CmdParams{Printer: p}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.args.model, tt.args.options); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/ske/options/machine_types/machine_types.go b/internal/cmd/ske/options/machine_types/machine_types.go index ac45c0ac9..26014dc59 100644 --- a/internal/cmd/ske/options/machine_types/machine_types.go +++ b/internal/cmd/ske/options/machine_types/machine_types.go @@ -75,12 +75,15 @@ func buildRequest(ctx context.Context, apiClient *ske.APIClient, model *inputMod } func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error { - if model == nil { - return fmt.Errorf("model is nil") - } else if options == nil { + if options == nil { return fmt.Errorf("options is nil") } + options.AvailabilityZones = nil + options.KubernetesVersions = nil + options.MachineImages = nil + options.VolumeTypes = nil + return p.OutputResult(model.OutputFormat, options, func() error { machineTypes := utils.PtrValue(options.MachineTypes) diff --git a/internal/cmd/ske/options/machine_types/machine_types_test.go b/internal/cmd/ske/options/machine_types/machine_types_test.go new file mode 100644 index 000000000..372e1e8b0 --- /dev/null +++ b/internal/cmd/ske/options/machine_types/machine_types_test.go @@ -0,0 +1,211 @@ +package machine_types + +import ( + "context" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stackitcloud/stackit-sdk-go/services/ske" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &ske.APIClient{} + +const testRegion = "eu01" + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.RegionFlag: testRegion, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{ + Region: testRegion, + Verbosity: globalflags.VerbosityDefault, + }, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Region = "" + }), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + inputModel *inputModel + expectedRequest ske.ApiListProviderOptionsRequest + }{ + { + description: "base", + inputModel: fixtureInputModel(), + expectedRequest: testClient.ListProviderOptions(testCtx, testRegion), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, testClient, tt.inputModel) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + model *inputModel + options *ske.ProviderOptions + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty", + args: args{}, + wantErr: true, + }, + { + name: "missing options", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + }, + wantErr: true, + }, + { + name: "empty input model", + args: args{ + model: &inputModel{}, + options: &ske.ProviderOptions{}, + }, + wantErr: false, + }, + { + name: "set model and options", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{}, + }, + wantErr: false, + }, + { + name: "empty values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + MachineTypes: &[]ske.MachineType{}, + }, + }, + wantErr: false, + }, + { + name: "empty value in values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + MachineTypes: &[]ske.MachineType{{}}, + }, + }, + wantErr: false, + }, + { + name: "valid values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + MachineTypes: &[]ske.MachineType{ + ske.MachineType{ + Architecture: utils.Ptr("amd64"), + Cpu: utils.Ptr(int64(2)), + Gpu: utils.Ptr(int64(0)), + Memory: utils.Ptr(int64(16)), + Name: utils.Ptr("type1"), + }, + ske.MachineType{ + Architecture: utils.Ptr("amd64"), + Cpu: utils.Ptr(int64(2)), + Gpu: utils.Ptr(int64(0)), + Memory: utils.Ptr(int64(16)), + Name: utils.Ptr("type2"), + }, + }, + }, + }, + wantErr: false, + }, + } + p := print.NewPrinter() + p.Cmd = NewCmd(&types.CmdParams{Printer: p}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.args.model, tt.args.options); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/ske/options/volume_types/volume_types.go b/internal/cmd/ske/options/volume_types/volume_types.go index d18832c29..8d3983487 100644 --- a/internal/cmd/ske/options/volume_types/volume_types.go +++ b/internal/cmd/ske/options/volume_types/volume_types.go @@ -75,12 +75,15 @@ func buildRequest(ctx context.Context, apiClient *ske.APIClient, model *inputMod } func outputResult(p *print.Printer, model *inputModel, options *ske.ProviderOptions) error { - if model == nil { - return fmt.Errorf("model is nil") - } else if options == nil { + if options == nil { return fmt.Errorf("options is nil") } + options.AvailabilityZones = nil + options.KubernetesVersions = nil + options.MachineImages = nil + options.MachineTypes = nil + return p.OutputResult(model.OutputFormat, options, func() error { volumeTypes := utils.PtrValue(options.VolumeTypes) diff --git a/internal/cmd/ske/options/volume_types/volume_types_test.go b/internal/cmd/ske/options/volume_types/volume_types_test.go new file mode 100644 index 000000000..39eb29ef8 --- /dev/null +++ b/internal/cmd/ske/options/volume_types/volume_types_test.go @@ -0,0 +1,203 @@ +package volume_types + +import ( + "context" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stackitcloud/stackit-sdk-go/services/ske" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &ske.APIClient{} + +const testRegion = "eu01" + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.RegionFlag: testRegion, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{ + Region: testRegion, + Verbosity: globalflags.VerbosityDefault, + }, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Region = "" + }), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + inputModel *inputModel + expectedRequest ske.ApiListProviderOptionsRequest + }{ + { + description: "base", + inputModel: fixtureInputModel(), + expectedRequest: testClient.ListProviderOptions(testCtx, testRegion), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, testClient, tt.inputModel) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + model *inputModel + options *ske.ProviderOptions + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty", + args: args{}, + wantErr: true, + }, + { + name: "missing options", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + }, + wantErr: true, + }, + { + name: "empty input model", + args: args{ + model: &inputModel{}, + options: &ske.ProviderOptions{}, + }, + wantErr: false, + }, + { + name: "set model and options", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{}, + }, + wantErr: false, + }, + { + name: "empty values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + VolumeTypes: &[]ske.VolumeType{}, + }, + }, + wantErr: false, + }, + { + name: "empty value in values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + VolumeTypes: &[]ske.VolumeType{{}}, + }, + }, + wantErr: false, + }, + { + name: "valid values", + args: args{ + model: &inputModel{ + GlobalFlagModel: globalflags.GlobalFlagModel{}, + }, + options: &ske.ProviderOptions{ + VolumeTypes: &[]ske.VolumeType{ + ske.VolumeType{ + Name: utils.Ptr("type1"), + }, + ske.VolumeType{ + Name: utils.Ptr("type2"), + }, + }, + }, + }, + wantErr: false, + }, + } + p := print.NewPrinter() + p.Cmd = NewCmd(&types.CmdParams{Printer: p}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.args.model, tt.args.options); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} From 4c6b0d544d116b2f778828bad7c8d928b69cc402 Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Wed, 25 Feb 2026 16:03:44 +0100 Subject: [PATCH 4/6] format --- .../options/availability_zones/availability_zones_test.go | 4 ++-- .../kubernetes_versions/kubernetes_versions_test.go | 4 ++-- .../cmd/ske/options/machine_images/machine_images_test.go | 8 ++++---- .../cmd/ske/options/machine_types/machine_types_test.go | 4 ++-- .../cmd/ske/options/volume_types/volume_types_test.go | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/cmd/ske/options/availability_zones/availability_zones_test.go b/internal/cmd/ske/options/availability_zones/availability_zones_test.go index c381a9633..e66bca441 100644 --- a/internal/cmd/ske/options/availability_zones/availability_zones_test.go +++ b/internal/cmd/ske/options/availability_zones/availability_zones_test.go @@ -179,10 +179,10 @@ func TestOutputResult(t *testing.T) { }, options: &ske.ProviderOptions{ AvailabilityZones: &[]ske.AvailabilityZone{ - ske.AvailabilityZone{ + { Name: utils.Ptr("zone1"), }, - ske.AvailabilityZone{ + { Name: utils.Ptr("zone2"), }, }, diff --git a/internal/cmd/ske/options/kubernetes_versions/kubernetes_versions_test.go b/internal/cmd/ske/options/kubernetes_versions/kubernetes_versions_test.go index d487ac43a..f8c7bd3c4 100644 --- a/internal/cmd/ske/options/kubernetes_versions/kubernetes_versions_test.go +++ b/internal/cmd/ske/options/kubernetes_versions/kubernetes_versions_test.go @@ -200,7 +200,7 @@ func TestOutputResult(t *testing.T) { }, options: &ske.ProviderOptions{ KubernetesVersions: &[]ske.KubernetesVersion{ - ske.KubernetesVersion{ + { FeatureGates: &map[string]string{ "featureGate1": "foo", "featureGate2": "bar", @@ -208,7 +208,7 @@ func TestOutputResult(t *testing.T) { State: utils.Ptr("supported"), Version: utils.Ptr("0.00.0"), }, - ske.KubernetesVersion{ + { ExpirationDate: utils.Ptr(time.Now()), State: utils.Ptr("deprecated"), Version: utils.Ptr("0.00.0"), diff --git a/internal/cmd/ske/options/machine_images/machine_images_test.go b/internal/cmd/ske/options/machine_images/machine_images_test.go index aeb8bab4f..e1f073b26 100644 --- a/internal/cmd/ske/options/machine_images/machine_images_test.go +++ b/internal/cmd/ske/options/machine_images/machine_images_test.go @@ -180,12 +180,12 @@ func TestOutputResult(t *testing.T) { }, options: &ske.ProviderOptions{ MachineImages: &[]ske.MachineImage{ - ske.MachineImage{ + { Name: utils.Ptr("image1"), Versions: &[]ske.MachineImageVersion{ - ske.MachineImageVersion{ + { Cri: &[]ske.CRI{ - ske.CRI{ + { Name: ske.CRINAME_CONTAINERD.Ptr(), }, }, @@ -195,7 +195,7 @@ func TestOutputResult(t *testing.T) { }, }, }, - ske.MachineImage{ + { Name: utils.Ptr("zone2"), }, }, diff --git a/internal/cmd/ske/options/machine_types/machine_types_test.go b/internal/cmd/ske/options/machine_types/machine_types_test.go index 372e1e8b0..7db75d847 100644 --- a/internal/cmd/ske/options/machine_types/machine_types_test.go +++ b/internal/cmd/ske/options/machine_types/machine_types_test.go @@ -179,14 +179,14 @@ func TestOutputResult(t *testing.T) { }, options: &ske.ProviderOptions{ MachineTypes: &[]ske.MachineType{ - ske.MachineType{ + { Architecture: utils.Ptr("amd64"), Cpu: utils.Ptr(int64(2)), Gpu: utils.Ptr(int64(0)), Memory: utils.Ptr(int64(16)), Name: utils.Ptr("type1"), }, - ske.MachineType{ + { Architecture: utils.Ptr("amd64"), Cpu: utils.Ptr(int64(2)), Gpu: utils.Ptr(int64(0)), diff --git a/internal/cmd/ske/options/volume_types/volume_types_test.go b/internal/cmd/ske/options/volume_types/volume_types_test.go index 39eb29ef8..ce37ce498 100644 --- a/internal/cmd/ske/options/volume_types/volume_types_test.go +++ b/internal/cmd/ske/options/volume_types/volume_types_test.go @@ -179,10 +179,10 @@ func TestOutputResult(t *testing.T) { }, options: &ske.ProviderOptions{ VolumeTypes: &[]ske.VolumeType{ - ske.VolumeType{ + { Name: utils.Ptr("type1"), }, - ske.VolumeType{ + { Name: utils.Ptr("type2"), }, }, From 592688348f932ebb521c5683418d93b5c63bf026 Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Wed, 25 Feb 2026 17:39:32 +0100 Subject: [PATCH 5/6] options deprecated command --- internal/cmd/ske/options/options.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/cmd/ske/options/options.go b/internal/cmd/ske/options/options.go index 3a81e6ed0..e410c164e 100644 --- a/internal/cmd/ske/options/options.go +++ b/internal/cmd/ske/options/options.go @@ -51,7 +51,8 @@ func NewCmd(params *types.CmdParams) *cobra.Command { "Lists STACKIT Kubernetes Engine (SKE) provider options (availability zones, Kubernetes versions, machine images and types, volume types).", "Pass one or more flags to filter what categories are shown.", ), - Args: args.NoArgs, + Deprecated: "splitted in separate subcommands, use them instead.", + Args: args.NoArgs, Example: examples.Build( examples.NewExample( `List SKE options for all categories`, From 00d0a7c1e1dc63f8964b15ec20637f0c8a513574 Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Thu, 26 Feb 2026 15:19:15 +0100 Subject: [PATCH 6/6] deprecated the main options command --- docs/stackit_ske_options.md | 21 ++------------------- internal/cmd/ske/options/options.go | 26 +++++++++++--------------- 2 files changed, 13 insertions(+), 34 deletions(-) diff --git a/docs/stackit_ske_options.md b/docs/stackit_ske_options.md index b3440a347..2590f989c 100644 --- a/docs/stackit_ske_options.md +++ b/docs/stackit_ske_options.md @@ -4,6 +4,7 @@ Lists SKE provider options ### Synopsis +Command "options" is deprecated, use the subcommands instead. Lists STACKIT Kubernetes Engine (SKE) provider options (availability zones, Kubernetes versions, machine images and types, volume types). Pass one or more flags to filter what categories are shown. @@ -11,28 +12,10 @@ Pass one or more flags to filter what categories are shown. stackit ske options [flags] ``` -### Examples - -``` - List SKE options for all categories - $ stackit ske options - - List SKE options regarding Kubernetes versions only - $ stackit ske options --kubernetes-versions - - List SKE options regarding Kubernetes versions and machine images - $ stackit ske options --kubernetes-versions --machine-images -``` - ### Options ``` - --availability-zones Lists availability zones - -h, --help Help for "stackit ske options" - --kubernetes-versions Lists supported kubernetes versions - --machine-images Lists supported machine images - --machine-types Lists supported machine types - --volume-types Lists supported volume types + -h, --help Help for "stackit ske options" ``` ### Options inherited from parent commands diff --git a/internal/cmd/ske/options/options.go b/internal/cmd/ske/options/options.go index e410c164e..7038cee7f 100644 --- a/internal/cmd/ske/options/options.go +++ b/internal/cmd/ske/options/options.go @@ -16,7 +16,6 @@ import ( "github.com/spf13/cobra" "github.com/stackitcloud/stackit-cli/internal/pkg/args" - "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" @@ -47,24 +46,15 @@ func NewCmd(params *types.CmdParams) *cobra.Command { cmd := &cobra.Command{ Use: "options", Short: "Lists SKE provider options", - Long: fmt.Sprintf("%s\n%s", + Long: fmt.Sprintf("%s\n%s\n%s", + "Command \"options\" is deprecated, use the subcommands instead.", "Lists STACKIT Kubernetes Engine (SKE) provider options (availability zones, Kubernetes versions, machine images and types, volume types).", "Pass one or more flags to filter what categories are shown.", ), - Deprecated: "splitted in separate subcommands, use them instead.", - Args: args.NoArgs, - Example: examples.Build( - examples.NewExample( - `List SKE options for all categories`, - "$ stackit ske options"), - examples.NewExample( - `List SKE options regarding Kubernetes versions only`, - "$ stackit ske options --kubernetes-versions"), - examples.NewExample( - `List SKE options regarding Kubernetes versions and machine images`, - "$ stackit ske options --kubernetes-versions --machine-images"), - ), + Args: args.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { + params.Printer.Info("Command \"options\" is deprecated, use the subcommands instead.\n") + ctx := context.Background() model, err := parseInput(params.Printer, cmd, args) if err != nil { @@ -106,6 +96,12 @@ func configureFlags(cmd *cobra.Command) { cmd.Flags().Bool(machineImagesFlag, false, "Lists supported machine images") cmd.Flags().Bool(machineTypesFlag, false, "Lists supported machine types") cmd.Flags().Bool(volumeTypesFlag, false, "Lists supported volume types") + + cobra.CheckErr(cmd.Flags().MarkDeprecated(availabilityZonesFlag, "This flag is deprecated and will be removed on 2026-09-26. Use the availability-zone subcommand instead.")) + cobra.CheckErr(cmd.Flags().MarkDeprecated(kubernetesVersionsFlag, "This flag is deprecated and will be removed on 2026-09-26. Use the kubernetes-versions subcommand instead.")) + cobra.CheckErr(cmd.Flags().MarkDeprecated(machineImagesFlag, "This flag is deprecated and will be removed on 2026-09-26. Use the machine-images subcommand instead.")) + cobra.CheckErr(cmd.Flags().MarkDeprecated(machineTypesFlag, "This flag is deprecated and will be removed on 2026-09-26. Use the machine-types subcommand instead.")) + cobra.CheckErr(cmd.Flags().MarkDeprecated(volumeTypesFlag, "This flag is deprecated and will be removed on 2026-09-26. Use the volume-types subcommand instead.")) } func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) {