diff --git a/internal/cmd/ske/cluster/create/create.go b/internal/cmd/ske/cluster/create/create.go index c1309f00d..31fad09f4 100644 --- a/internal/cmd/ske/cluster/create/create.go +++ b/internal/cmd/ske/cluster/create/create.go @@ -13,6 +13,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" + serviceEnablementClient "github.com/stackitcloud/stackit-cli/internal/pkg/services/service-enablement/client" + serviceEnablementUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/service-enablement/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" skeUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" @@ -87,8 +89,14 @@ func NewCmd(p *print.Printer) *cobra.Command { } } + // Configure ServiceEnable API client + serviceEnablementApiClient, err := serviceEnablementClient.ConfigureClient(p) + if err != nil { + return err + } + // Check if the project is enabled before trying to create - enabled, err := skeUtils.ProjectEnabled(ctx, apiClient, model.ProjectId) + enabled, err := serviceEnablementUtils.ProjectEnabled(ctx, serviceEnablementApiClient, model.ProjectId) if err != nil { return err } diff --git a/internal/cmd/ske/cluster/list/list.go b/internal/cmd/ske/cluster/list/list.go index a4fcd139c..834b2ea38 100644 --- a/internal/cmd/ske/cluster/list/list.go +++ b/internal/cmd/ske/cluster/list/list.go @@ -13,8 +13,9 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" + serviceEnablementClient "github.com/stackitcloud/stackit-cli/internal/pkg/services/service-enablement/client" + serviceEnablementUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/service-enablement/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" - skeUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/spf13/cobra" @@ -60,8 +61,14 @@ func NewCmd(p *print.Printer) *cobra.Command { return err } + // Configure ServiceEnable API client + serviceEnablementApiClient, err := serviceEnablementClient.ConfigureClient(p) + if err != nil { + return err + } + // Check if SKE is enabled for this project - enabled, err := skeUtils.ProjectEnabled(ctx, apiClient, model.ProjectId) + enabled, err := serviceEnablementUtils.ProjectEnabled(ctx, serviceEnablementApiClient, model.ProjectId) if err != nil { return err } diff --git a/internal/cmd/ske/credentials/credentials.go b/internal/cmd/ske/credentials/credentials.go index 9d421ad7d..6fdfb1fc3 100644 --- a/internal/cmd/ske/credentials/credentials.go +++ b/internal/cmd/ske/credentials/credentials.go @@ -2,8 +2,6 @@ package credentials import ( completerotation "github.com/stackitcloud/stackit-cli/internal/cmd/ske/credentials/complete-rotation" - "github.com/stackitcloud/stackit-cli/internal/cmd/ske/credentials/describe" - "github.com/stackitcloud/stackit-cli/internal/cmd/ske/credentials/rotate" startrotation "github.com/stackitcloud/stackit-cli/internal/cmd/ske/credentials/start-rotation" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/print" @@ -25,8 +23,6 @@ func NewCmd(p *print.Printer) *cobra.Command { } func addSubcommands(cmd *cobra.Command, p *print.Printer) { - cmd.AddCommand(describe.NewCmd(p)) - cmd.AddCommand(rotate.NewCmd(p)) cmd.AddCommand(startrotation.NewCmd(p)) cmd.AddCommand(completerotation.NewCmd(p)) } diff --git a/internal/cmd/ske/credentials/describe/describe.go b/internal/cmd/ske/credentials/describe/describe.go deleted file mode 100644 index a793628b5..000000000 --- a/internal/cmd/ske/credentials/describe/describe.go +++ /dev/null @@ -1,146 +0,0 @@ -package describe - -import ( - "context" - "encoding/json" - "fmt" - - "github.com/goccy/go-yaml" - "github.com/stackitcloud/stackit-cli/internal/pkg/args" - "github.com/stackitcloud/stackit-cli/internal/pkg/errors" - "github.com/stackitcloud/stackit-cli/internal/pkg/examples" - "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" - "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" - skeUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/utils" - "github.com/stackitcloud/stackit-cli/internal/pkg/tables" - - "github.com/spf13/cobra" - "github.com/stackitcloud/stackit-sdk-go/services/ske" -) - -const ( - clusterNameArg = "CLUSTER_NAME" -) - -type inputModel struct { - *globalflags.GlobalFlagModel - ClusterName string -} - -func NewCmd(p *print.Printer) *cobra.Command { - cmd := &cobra.Command{ - Use: fmt.Sprintf("describe %s", clusterNameArg), - Short: "Shows details of the credentials associated to a SKE cluster", - Long: "Shows details of the credentials associated to a STACKIT Kubernetes Engine (SKE) cluster", - Args: args.SingleArg(clusterNameArg, nil), - Deprecated: fmt.Sprintf("%s\n%s\n%s\n%s\n", - "and will be removed in a future release.", - "Please use the following command to obtain a kubeconfig file instead:", - " $ stackit ske kubeconfig create CLUSTER_NAME", - "For more information, visit: https://docs.stackit.cloud/stackit/en/how-to-rotate-ske-credentials-200016334.html", - ), - Example: examples.Build( - examples.NewExample( - `Get details of the credentials associated to the SKE cluster with name "my-cluster"`, - "$ stackit ske credentials describe my-cluster"), - examples.NewExample( - `Get details of the credentials associated to the SKE cluster with name "my-cluster" in JSON format`, - "$ stackit ske credentials describe my-cluster --output-format json"), - ), - RunE: func(cmd *cobra.Command, args []string) error { - ctx := context.Background() - model, err := parseInput(p, cmd, args) - if err != nil { - return err - } - - // Configure API client - apiClient, err := client.ConfigureClient(p) - if err != nil { - return err - } - - // Check if SKE is enabled for this project - enabled, err := skeUtils.ProjectEnabled(ctx, apiClient, model.ProjectId) - if err != nil { - return err - } - if !enabled { - return fmt.Errorf("SKE isn't enabled for this project, please run 'stackit ske enable'") - } - - // Call API - req := buildRequest(ctx, model, apiClient) - resp, err := req.Execute() - if err != nil { - return fmt.Errorf("get SKE credentials: %w", err) - } - - return outputResult(p, model.OutputFormat, resp) - }, - } - return cmd -} - -func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { - clusterName := inputArgs[0] - - globalFlags := globalflags.Parse(p, cmd) - if globalFlags.ProjectId == "" { - return nil, &errors.ProjectIdError{} - } - - model := inputModel{ - GlobalFlagModel: globalFlags, - ClusterName: clusterName, - } - - if p.IsVerbosityDebug() { - modelStr, err := print.BuildDebugStrFromInputModel(model) - if err != nil { - p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) - } else { - p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) - } - } - - return &model, nil -} - -func buildRequest(ctx context.Context, model *inputModel, apiClient *ske.APIClient) ske.ApiGetCredentialsRequest { - req := apiClient.GetCredentials(ctx, model.ProjectId, model.ClusterName) //nolint:staticcheck //command will be removed in a later update - return req -} - -func outputResult(p *print.Printer, outputFormat string, credentials *ske.Credentials) error { - switch outputFormat { - case print.JSONOutputFormat: - details, err := json.MarshalIndent(credentials, "", " ") - if err != nil { - return fmt.Errorf("marshal SKE credentials: %w", err) - } - p.Outputln(string(details)) - - return nil - case print.YAMLOutputFormat: - details, err := yaml.MarshalWithOptions(credentials, yaml.IndentSequence(true)) - if err != nil { - return fmt.Errorf("marshal SKE credentials: %w", err) - } - p.Outputln(string(details)) - - return nil - default: - table := tables.NewTable() - table.AddRow("SERVER", *credentials.Server) - table.AddSeparator() - table.AddRow("TOKEN", *credentials.Token) - err := table.Display(p) - if err != nil { - return fmt.Errorf("render table: %w", err) - } - - return nil - } -} diff --git a/internal/cmd/ske/credentials/describe/describe_test.go b/internal/cmd/ske/credentials/describe/describe_test.go deleted file mode 100644 index c589ca77b..000000000 --- a/internal/cmd/ske/credentials/describe/describe_test.go +++ /dev/null @@ -1,206 +0,0 @@ -package describe - -import ( - "context" - "testing" - - "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" - "github.com/stackitcloud/stackit-cli/internal/pkg/print" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/google/uuid" - "github.com/stackitcloud/stackit-sdk-go/services/ske" -) - -var projectIdFlag = globalflags.ProjectIdFlag - -type testCtxKey struct{} - -var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &ske.APIClient{} -var testProjectId = uuid.NewString() -var testClusterName = "cluster" - -func fixtureArgValues(mods ...func(argValues []string)) []string { - argValues := []string{ - testClusterName, - } - for _, mod := range mods { - mod(argValues) - } - return argValues -} - -func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { - flagValues := map[string]string{ - projectIdFlag: testProjectId, - } - for _, mod := range mods { - mod(flagValues) - } - return flagValues -} - -func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { - model := &inputModel{ - GlobalFlagModel: &globalflags.GlobalFlagModel{ - ProjectId: testProjectId, - Verbosity: globalflags.VerbosityDefault, - }, - ClusterName: testClusterName, - } - for _, mod := range mods { - mod(model) - } - return model -} - -func fixtureRequest(mods ...func(request *ske.ApiGetCredentialsRequest)) ske.ApiGetCredentialsRequest { - request := testClient.GetCredentials(testCtx, testProjectId, testClusterName) //nolint:staticcheck //command will be removed in a later update - for _, mod := range mods { - mod(&request) - } - return request -} - -func TestParseInput(t *testing.T) { - tests := []struct { - description string - argValues []string - flagValues map[string]string - isValid bool - expectedModel *inputModel - }{ - { - description: "base", - argValues: fixtureArgValues(), - flagValues: fixtureFlagValues(), - isValid: true, - expectedModel: fixtureInputModel(), - }, - { - description: "no values", - argValues: []string{}, - flagValues: map[string]string{}, - isValid: false, - }, - { - description: "no arg values", - argValues: []string{}, - flagValues: fixtureFlagValues(), - isValid: false, - }, - { - description: "no flag values", - argValues: fixtureArgValues(), - flagValues: map[string]string{}, - isValid: false, - }, - { - description: "project id missing", - argValues: fixtureArgValues(), - flagValues: fixtureFlagValues(func(flagValues map[string]string) { - delete(flagValues, projectIdFlag) - }), - isValid: false, - }, - { - description: "project id invalid 1", - argValues: fixtureArgValues(), - flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues[projectIdFlag] = "" - }), - isValid: false, - }, - { - description: "project id invalid 2", - argValues: fixtureArgValues(), - flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues[projectIdFlag] = "invalid-uuid" - }), - isValid: false, - }, - } - - for _, tt := range tests { - t.Run(tt.description, func(t *testing.T) { - p := print.NewPrinter() - cmd := NewCmd(p) - err := globalflags.Configure(cmd.Flags()) - if err != nil { - t.Fatalf("configure global flags: %v", err) - } - - for flag, value := range tt.flagValues { - err := cmd.Flags().Set(flag, value) - if err != nil { - if !tt.isValid { - return - } - t.Fatalf("setting flag --%s=%s: %v", flag, value, err) - } - } - - err = cmd.ValidateArgs(tt.argValues) - if err != nil { - if !tt.isValid { - return - } - t.Fatalf("error validating args: %v", err) - } - - err = cmd.ValidateRequiredFlags() - if err != nil { - if !tt.isValid { - return - } - t.Fatalf("error validating flags: %v", err) - } - - model, err := parseInput(p, cmd, tt.argValues) - if err != nil { - if !tt.isValid { - return - } - t.Fatalf("error parsing flags: %v", err) - } - - if !tt.isValid { - t.Fatalf("did not fail on invalid input") - } - diff := cmp.Diff(model, tt.expectedModel) - if diff != "" { - t.Fatalf("Data does not match: %s", diff) - } - }) - } -} - -func TestBuildRequest(t *testing.T) { - tests := []struct { - description string - model *inputModel - expectedRequest ske.ApiGetCredentialsRequest - }{ - { - description: "base", - model: fixtureInputModel(), - expectedRequest: fixtureRequest(), - }, - } - - for _, tt := range tests { - t.Run(tt.description, func(t *testing.T) { - request := buildRequest(testCtx, tt.model, testClient) - - diff := cmp.Diff(request, tt.expectedRequest, - cmp.AllowUnexported(tt.expectedRequest), - cmpopts.EquateComparable(testCtx), - ) - if diff != "" { - t.Fatalf("Data does not match: %s", diff) - } - }) - } -} diff --git a/internal/cmd/ske/credentials/rotate/rotate.go b/internal/cmd/ske/credentials/rotate/rotate.go deleted file mode 100644 index 676c712e2..000000000 --- a/internal/cmd/ske/credentials/rotate/rotate.go +++ /dev/null @@ -1,125 +0,0 @@ -package rotate - -import ( - "context" - "fmt" - - "github.com/stackitcloud/stackit-cli/internal/pkg/args" - "github.com/stackitcloud/stackit-cli/internal/pkg/errors" - "github.com/stackitcloud/stackit-cli/internal/pkg/examples" - "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" - "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" - "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" - - "github.com/spf13/cobra" - "github.com/stackitcloud/stackit-sdk-go/services/ske" - "github.com/stackitcloud/stackit-sdk-go/services/ske/wait" -) - -const ( - clusterNameArg = "CLUSTER_NAME" -) - -type inputModel struct { - *globalflags.GlobalFlagModel - ClusterName string -} - -func NewCmd(p *print.Printer) *cobra.Command { - cmd := &cobra.Command{ - Use: fmt.Sprintf("rotate %s", clusterNameArg), - Short: "Rotates credentials associated to a SKE cluster", - Long: "Rotates credentials associated to a STACKIT Kubernetes Engine (SKE) cluster. The old credentials will be invalid after the operation.", - Args: args.SingleArg(clusterNameArg, nil), - Deprecated: fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n", - "and will be removed in a future release.", - "Please use the 2-step credential rotation flow instead, by running the commands:", - " $ stackit ske credentials start-rotation CLUSTER_NAME", - " $ stackit ske credentials complete-rotation CLUSTER_NAME", - "For more information, visit: https://docs.stackit.cloud/stackit/en/how-to-rotate-ske-credentials-200016334.html", - ), - Example: examples.Build( - examples.NewExample( - `Rotate credentials associated to the SKE cluster with name "my-cluster"`, - "$ stackit ske credentials rotate my-cluster"), - ), - RunE: func(cmd *cobra.Command, args []string) error { - ctx := context.Background() - model, err := parseInput(p, cmd, args) - if err != nil { - return err - } - - // Configure API client - apiClient, err := client.ConfigureClient(p) - if err != nil { - return err - } - - if !model.AssumeYes { - prompt := fmt.Sprintf("Are you sure you want to rotate credentials for SKE cluster %q? (The old credentials will be invalid after this operation)", model.ClusterName) - err = p.PromptForConfirmation(prompt) - if err != nil { - return err - } - } - - // Call API - req := buildRequest(ctx, model, apiClient) - _, err = req.Execute() - if err != nil { - return fmt.Errorf("rotate SKE credentials: %w", err) - } - - // Wait for async operation, if async mode not enabled - if !model.Async { - s := spinner.New(p) - s.Start("Rotating credentials") - _, err = wait.RotateCredentialsWaitHandler(ctx, apiClient, model.ProjectId, model.ClusterName).WaitWithContext(ctx) - if err != nil { - return fmt.Errorf("wait for SKE credentials rotation: %w", err) - } - s.Stop() - } - - operationState := "Rotated" - if model.Async { - operationState = "Triggered rotation of" - } - p.Info("%s credentials for cluster %q\n", operationState, model.ClusterName) - return nil - }, - } - return cmd -} - -func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { - clusterName := inputArgs[0] - - globalFlags := globalflags.Parse(p, cmd) - if globalFlags.ProjectId == "" { - return nil, &errors.ProjectIdError{} - } - - model := inputModel{ - GlobalFlagModel: globalFlags, - ClusterName: clusterName, - } - - if p.IsVerbosityDebug() { - modelStr, err := print.BuildDebugStrFromInputModel(model) - if err != nil { - p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) - } else { - p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) - } - } - - return &model, nil -} - -func buildRequest(ctx context.Context, model *inputModel, apiClient *ske.APIClient) ske.ApiTriggerRotateCredentialsRequest { - req := apiClient.TriggerRotateCredentials(ctx, model.ProjectId, model.ClusterName) //nolint:staticcheck //command will be removed in a later update - return req -} diff --git a/internal/cmd/ske/credentials/rotate/rotate_test.go b/internal/cmd/ske/credentials/rotate/rotate_test.go deleted file mode 100644 index 9156795fb..000000000 --- a/internal/cmd/ske/credentials/rotate/rotate_test.go +++ /dev/null @@ -1,206 +0,0 @@ -package rotate - -import ( - "context" - "testing" - - "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" - "github.com/stackitcloud/stackit-cli/internal/pkg/print" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/google/uuid" - "github.com/stackitcloud/stackit-sdk-go/services/ske" -) - -var projectIdFlag = globalflags.ProjectIdFlag - -type testCtxKey struct{} - -var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &ske.APIClient{} -var testProjectId = uuid.NewString() -var testClusterName = "cluster" - -func fixtureArgValues(mods ...func(argValues []string)) []string { - argValues := []string{ - testClusterName, - } - for _, mod := range mods { - mod(argValues) - } - return argValues -} - -func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { - flagValues := map[string]string{ - projectIdFlag: testProjectId, - } - for _, mod := range mods { - mod(flagValues) - } - return flagValues -} - -func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { - model := &inputModel{ - GlobalFlagModel: &globalflags.GlobalFlagModel{ - ProjectId: testProjectId, - Verbosity: globalflags.VerbosityDefault, - }, - ClusterName: testClusterName, - } - for _, mod := range mods { - mod(model) - } - return model -} - -func fixtureRequest(mods ...func(request *ske.ApiTriggerRotateCredentialsRequest)) ske.ApiTriggerRotateCredentialsRequest { - request := testClient.TriggerRotateCredentials(testCtx, testProjectId, testClusterName) //nolint:staticcheck //command will be removed in a later update - for _, mod := range mods { - mod(&request) - } - return request -} - -func TestParseInput(t *testing.T) { - tests := []struct { - description string - argValues []string - flagValues map[string]string - isValid bool - expectedModel *inputModel - }{ - { - description: "base", - argValues: fixtureArgValues(), - flagValues: fixtureFlagValues(), - isValid: true, - expectedModel: fixtureInputModel(), - }, - { - description: "no values", - argValues: []string{}, - flagValues: map[string]string{}, - isValid: false, - }, - { - description: "no arg values", - argValues: []string{}, - flagValues: fixtureFlagValues(), - isValid: false, - }, - { - description: "no flag values", - argValues: fixtureArgValues(), - flagValues: map[string]string{}, - isValid: false, - }, - { - description: "project id missing", - argValues: fixtureArgValues(), - flagValues: fixtureFlagValues(func(flagValues map[string]string) { - delete(flagValues, projectIdFlag) - }), - isValid: false, - }, - { - description: "project id invalid 1", - argValues: fixtureArgValues(), - flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues[projectIdFlag] = "" - }), - isValid: false, - }, - { - description: "project id invalid 2", - argValues: fixtureArgValues(), - flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues[projectIdFlag] = "invalid-uuid" - }), - isValid: false, - }, - } - - for _, tt := range tests { - t.Run(tt.description, func(t *testing.T) { - p := print.NewPrinter() - cmd := NewCmd(p) - err := globalflags.Configure(cmd.Flags()) - if err != nil { - t.Fatalf("configure global flags: %v", err) - } - - for flag, value := range tt.flagValues { - err := cmd.Flags().Set(flag, value) - if err != nil { - if !tt.isValid { - return - } - t.Fatalf("setting flag --%s=%s: %v", flag, value, err) - } - } - - err = cmd.ValidateArgs(tt.argValues) - if err != nil { - if !tt.isValid { - return - } - t.Fatalf("error validating args: %v", err) - } - - err = cmd.ValidateRequiredFlags() - if err != nil { - if !tt.isValid { - return - } - t.Fatalf("error validating flags: %v", err) - } - - model, err := parseInput(p, cmd, tt.argValues) - if err != nil { - if !tt.isValid { - return - } - t.Fatalf("error parsing flags: %v", err) - } - - if !tt.isValid { - t.Fatalf("did not fail on invalid input") - } - diff := cmp.Diff(model, tt.expectedModel) - if diff != "" { - t.Fatalf("Data does not match: %s", diff) - } - }) - } -} - -func TestBuildRequest(t *testing.T) { - tests := []struct { - description string - model *inputModel - expectedRequest ske.ApiTriggerRotateCredentialsRequest - }{ - { - description: "base", - model: fixtureInputModel(), - expectedRequest: fixtureRequest(), - }, - } - - for _, tt := range tests { - t.Run(tt.description, func(t *testing.T) { - request := buildRequest(testCtx, tt.model, testClient) - - diff := cmp.Diff(request, tt.expectedRequest, - cmp.AllowUnexported(tt.expectedRequest), - cmpopts.EquateComparable(testCtx), - ) - if diff != "" { - t.Fatalf("Data does not match: %s", diff) - } - }) - } -} diff --git a/internal/cmd/ske/describe/describe.go b/internal/cmd/ske/describe/describe.go index f1b4cec26..3f54a607d 100644 --- a/internal/cmd/ske/describe/describe.go +++ b/internal/cmd/ske/describe/describe.go @@ -11,11 +11,12 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/service-enablement/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/service-enablement/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement" "github.com/spf13/cobra" - "github.com/stackitcloud/stackit-sdk-go/services/ske" ) type inputModel struct { @@ -52,7 +53,7 @@ func NewCmd(p *print.Printer) *cobra.Command { return fmt.Errorf("read SKE project details: %w", err) } - return outputResult(p, model.OutputFormat, resp) + return outputResult(p, model.OutputFormat, resp, model.ProjectId) }, } return cmd @@ -80,12 +81,12 @@ func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { return &model, nil } -func buildRequest(ctx context.Context, model *inputModel, apiClient *ske.APIClient) ske.ApiGetServiceStatusRequest { - req := apiClient.GetServiceStatus(ctx, model.ProjectId) //nolint:staticcheck //command will be removed in a later update +func buildRequest(ctx context.Context, model *inputModel, apiClient *serviceenablement.APIClient) serviceenablement.ApiGetServiceStatusRequest { + req := apiClient.GetServiceStatus(ctx, model.ProjectId, utils.SKEServiceId) return req } -func outputResult(p *print.Printer, outputFormat string, project *ske.ProjectResponse) error { +func outputResult(p *print.Printer, outputFormat string, project *serviceenablement.ServiceStatus, projectId string) error { switch outputFormat { case print.JSONOutputFormat: details, err := json.MarshalIndent(project, "", " ") @@ -105,7 +106,7 @@ func outputResult(p *print.Printer, outputFormat string, project *ske.ProjectRes return nil default: table := tables.NewTable() - table.AddRow("ID", *project.ProjectId) + table.AddRow("ID", projectId) table.AddSeparator() table.AddRow("STATE", *project.State) err := table.Display(p) diff --git a/internal/cmd/ske/describe/describe_test.go b/internal/cmd/ske/describe/describe_test.go index 5805585aa..fe8e39e71 100644 --- a/internal/cmd/ske/describe/describe_test.go +++ b/internal/cmd/ske/describe/describe_test.go @@ -6,12 +6,13 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" + serviceEnablementUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/service-enablement/utils" + "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" "github.com/spf13/cobra" - "github.com/stackitcloud/stackit-sdk-go/services/ske" ) var projectIdFlag = globalflags.ProjectIdFlag @@ -19,7 +20,7 @@ var projectIdFlag = globalflags.ProjectIdFlag type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") -var testClient = &ske.APIClient{} +var testClient = &serviceenablement.APIClient{} var testProjectId = uuid.NewString() func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { @@ -45,8 +46,8 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { return model } -func fixtureRequest(mods ...func(request *ske.ApiGetServiceStatusRequest)) ske.ApiGetServiceStatusRequest { - request := testClient.GetServiceStatus(testCtx, testProjectId) //nolint:staticcheck //command will be removed in a later update +func fixtureRequest(mods ...func(request *serviceenablement.ApiGetServiceStatusRequest)) serviceenablement.ApiGetServiceStatusRequest { + request := testClient.GetServiceStatus(testCtx, testProjectId, serviceEnablementUtils.SKEServiceId) //nolint:staticcheck //command will be removed in a later update for _, mod := range mods { mod(&request) } @@ -145,7 +146,7 @@ func TestBuildRequest(t *testing.T) { description string model *inputModel isValid bool - expectedRequest ske.ApiGetServiceStatusRequest + expectedRequest serviceenablement.ApiGetServiceStatusRequest }{ { description: "base", diff --git a/internal/pkg/services/service-enablement/utils/utils.go b/internal/pkg/services/service-enablement/utils/utils.go index 27952a763..45e3f819b 100644 --- a/internal/pkg/services/service-enablement/utils/utils.go +++ b/internal/pkg/services/service-enablement/utils/utils.go @@ -1,5 +1,33 @@ package utils +import ( + "context" + "net/http" + + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement" + "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/wait" +) + const ( SKEServiceId = "cloud.stackit.ske" ) + +type ServiceEnablementClient interface { + GetServiceStatusExecute(ctx context.Context, projectId string, serviceId string) (*serviceenablement.ServiceStatus, error) +} + +func ProjectEnabled(ctx context.Context, apiClient ServiceEnablementClient, projectId string) (bool, error) { + project, err := apiClient.GetServiceStatusExecute(ctx, projectId, SKEServiceId) + if err != nil { + oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped + if !ok { + return false, err + } + if oapiErr.StatusCode == http.StatusNotFound { + return false, nil + } + return false, err + } + return *project.State == wait.ServiceStateEnabled, nil +} diff --git a/internal/pkg/services/service-enablement/utils/utils_test.go b/internal/pkg/services/service-enablement/utils/utils_test.go new file mode 100644 index 000000000..4ade8fa58 --- /dev/null +++ b/internal/pkg/services/service-enablement/utils/utils_test.go @@ -0,0 +1,106 @@ +package utils + +import ( + "context" + "fmt" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement" + "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/wait" +) + +var ( + testProjectId = uuid.NewString() +) + +type serviceEnableClientMocked struct { + serviceDisabled bool + getServiceStatusFails bool + getServiceStatusResp *serviceenablement.ServiceStatus +} + +func (m *serviceEnableClientMocked) GetServiceStatusExecute(_ context.Context, _, _ string) (*serviceenablement.ServiceStatus, error) { + if m.getServiceStatusFails { + return nil, fmt.Errorf("could not get service status") + } + if m.serviceDisabled { + return nil, &oapierror.GenericOpenAPIError{StatusCode: 404} + } + return m.getServiceStatusResp, nil +} + +func TestProjectEnabled(t *testing.T) { + tests := []struct { + description string + serviceDisabled bool + getProjectFails bool + getProjectResp *serviceenablement.ServiceStatus + isValid bool + expectedOutput bool + }{ + { + description: "project enabled", + getProjectResp: &serviceenablement.ServiceStatus{State: utils.Ptr(wait.ServiceStateEnabled)}, + isValid: true, + expectedOutput: true, + }, + { + description: "project disabled (404)", + serviceDisabled: true, + isValid: true, + expectedOutput: false, + }, + { + description: "project disabled 1", + getProjectResp: &serviceenablement.ServiceStatus{State: utils.Ptr(wait.ServiceStateEnabling)}, + isValid: true, + expectedOutput: false, + }, + { + description: "project disabled 2", + getProjectResp: &serviceenablement.ServiceStatus{State: utils.Ptr(wait.ServiceStateDisabled)}, + isValid: true, + expectedOutput: false, + }, + { + description: "project disabled 3", + getProjectResp: &serviceenablement.ServiceStatus{State: utils.Ptr(wait.ServiceStateDisabling)}, + isValid: true, + expectedOutput: false, + }, + { + description: "get clusters fails", + getProjectFails: true, + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + client := &serviceEnableClientMocked{ + serviceDisabled: tt.serviceDisabled, + getServiceStatusFails: tt.getProjectFails, + getServiceStatusResp: tt.getProjectResp, + } + + output, err := ProjectEnabled(context.Background(), client, testProjectId) + + if tt.isValid && err != nil { + t.Errorf("failed on valid input") + } + if !tt.isValid && err == nil { + t.Errorf("did not fail on invalid input") + } + if !tt.isValid { + return + } + if output != tt.expectedOutput { + t.Errorf("expected output to be %t, got %t", tt.expectedOutput, output) + } + }) + } +} diff --git a/internal/pkg/services/ske/utils/utils.go b/internal/pkg/services/ske/utils/utils.go index 1ce6c8974..366467643 100644 --- a/internal/pkg/services/ske/utils/utils.go +++ b/internal/pkg/services/ske/utils/utils.go @@ -3,14 +3,11 @@ package utils import ( "context" "fmt" - "net/http" "os" "path/filepath" "strconv" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - - "github.com/stackitcloud/stackit-sdk-go/core/oapierror" "github.com/stackitcloud/stackit-sdk-go/services/ske" "golang.org/x/mod/semver" ) @@ -37,21 +34,6 @@ type SKEClient interface { ListProviderOptionsExecute(ctx context.Context) (*ske.ProviderOptions, error) } -func ProjectEnabled(ctx context.Context, apiClient SKEClient, projectId string) (bool, error) { - project, err := apiClient.GetServiceStatusExecute(ctx, projectId) - if err != nil { - oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped - if !ok { - return false, err - } - if oapiErr.StatusCode == http.StatusNotFound { - return false, nil - } - return false, err - } - return *project.State == ske.PROJECTSTATE_CREATED, nil -} - func ClusterExists(ctx context.Context, apiClient SKEClient, projectId, clusterName string) (bool, error) { clusters, err := apiClient.ListClustersExecute(ctx, projectId) if err != nil { diff --git a/internal/pkg/services/ske/utils/utils_test.go b/internal/pkg/services/ske/utils/utils_test.go index 55331155a..c3efcfe88 100644 --- a/internal/pkg/services/ske/utils/utils_test.go +++ b/internal/pkg/services/ske/utils/utils_test.go @@ -57,72 +57,6 @@ func (m *skeClientMocked) ListProviderOptionsExecute(_ context.Context) (*ske.Pr return m.listProviderOptionsResp, nil } -func TestProjectEnabled(t *testing.T) { - tests := []struct { - description string - serviceDisabled bool - getProjectFails bool - getProjectResp *ske.ProjectResponse - isValid bool - expectedOutput bool - }{ - { - description: "project enabled", - getProjectResp: &ske.ProjectResponse{State: ske.PROJECTSTATE_CREATED.Ptr()}, - isValid: true, - expectedOutput: true, - }, - { - description: "project disabled (404)", - serviceDisabled: true, - isValid: true, - expectedOutput: false, - }, - { - description: "project disabled 1", - getProjectResp: &ske.ProjectResponse{State: ske.PROJECTSTATE_CREATING.Ptr()}, - isValid: true, - expectedOutput: false, - }, - { - description: "project disabled 2", - getProjectResp: &ske.ProjectResponse{State: ske.PROJECTSTATE_DELETING.Ptr()}, - isValid: true, - expectedOutput: false, - }, - { - description: "get clusters fails", - getProjectFails: true, - isValid: false, - }, - } - - for _, tt := range tests { - t.Run(tt.description, func(t *testing.T) { - client := &skeClientMocked{ - serviceDisabled: tt.serviceDisabled, - getServiceStatusFails: tt.getProjectFails, - getServiceStatusResp: tt.getProjectResp, - } - - output, err := ProjectEnabled(context.Background(), client, testProjectId) - - if tt.isValid && err != nil { - t.Errorf("failed on valid input") - } - if !tt.isValid && err == nil { - t.Errorf("did not fail on invalid input") - } - if !tt.isValid { - return - } - if output != tt.expectedOutput { - t.Errorf("expected output to be %t, got %t", tt.expectedOutput, output) - } - }) - } -} - func TestClusterExists(t *testing.T) { tests := []struct { description string