diff --git a/docs/stackit.md b/docs/stackit.md index 81c7aef64..3232bd140 100644 --- a/docs/stackit.md +++ b/docs/stackit.md @@ -27,6 +27,7 @@ stackit [flags] ### SEE ALSO * [stackit affinity-group](./stackit_affinity-group.md) - Manage server affinity groups +* [stackit alpha](./stackit_alpha.md) - Contains alpha STACKIT CLI commands * [stackit auth](./stackit_auth.md) - Authenticates the STACKIT CLI * [stackit beta](./stackit_beta.md) - Contains beta STACKIT CLI commands * [stackit config](./stackit_config.md) - Provides functionality for CLI configuration options diff --git a/docs/stackit_alpha.md b/docs/stackit_alpha.md new file mode 100644 index 000000000..d47173934 --- /dev/null +++ b/docs/stackit_alpha.md @@ -0,0 +1,45 @@ +## stackit alpha + +Contains alpha STACKIT CLI commands + +### Synopsis + +Contains alpha STACKIT CLI commands. +The commands under this group are still in an alpha state, and functionality may be incomplete or have breaking changes. + +``` +stackit alpha [flags] +``` + +### Examples + +``` + See the currently available alpha commands + $ stackit alpha --help + + Execute an alpha command + $ stackit alpha MY_COMMAND +``` + +### Options + +``` + -h, --help Help for "stackit alpha" +``` + +### 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](./stackit.md) - Manage STACKIT resources using the command line +* [stackit alpha vpc](./stackit_alpha_vpc.md) - Manages vpcs + diff --git a/docs/stackit_alpha_vpc.md b/docs/stackit_alpha_vpc.md new file mode 100644 index 000000000..7df257803 --- /dev/null +++ b/docs/stackit_alpha_vpc.md @@ -0,0 +1,38 @@ +## stackit alpha vpc + +Manages vpcs + +### Synopsis + +Manages the lifecycle of vpcs. + +``` +stackit alpha vpc [flags] +``` + +### Options + +``` + -h, --help Help for "stackit alpha vpc" +``` + +### 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 alpha](./stackit_alpha.md) - Contains alpha STACKIT CLI commands +* [stackit alpha vpc create](./stackit_alpha_vpc_create.md) - Creates a VPC +* [stackit alpha vpc delete](./stackit_alpha_vpc_delete.md) - Deletes a VPC +* [stackit alpha vpc describe](./stackit_alpha_vpc_describe.md) - Shows details of a VPC +* [stackit alpha vpc list](./stackit_alpha_vpc_list.md) - Lists all VPC of a project +* [stackit alpha vpc update](./stackit_alpha_vpc_update.md) - Updates a VPC + diff --git a/docs/stackit_alpha_vpc_create.md b/docs/stackit_alpha_vpc_create.md new file mode 100644 index 000000000..212490e48 --- /dev/null +++ b/docs/stackit_alpha_vpc_create.md @@ -0,0 +1,44 @@ +## stackit alpha vpc create + +Creates a VPC + +### Synopsis + +Creates a VPC. +VPC can only be used in projects with enabled VPC. + +``` +stackit alpha vpc create [flags] +``` + +### Examples + +``` + Create a vpc with name "my-vpc" + $ stackit alpha vpc create --name my-vpc +``` + +### Options + +``` + --description string Description of the VPC + -h, --help Help for "stackit alpha vpc create" + --labels stringToString Comma separated list of labels of the VPC (default []) + --name string Name of the VPC +``` + +### 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 alpha vpc](./stackit_alpha_vpc.md) - Manages vpcs + diff --git a/docs/stackit_alpha_vpc_delete.md b/docs/stackit_alpha_vpc_delete.md new file mode 100644 index 000000000..705a36fcf --- /dev/null +++ b/docs/stackit_alpha_vpc_delete.md @@ -0,0 +1,40 @@ +## stackit alpha vpc delete + +Deletes a VPC + +### Synopsis + +Deletes a VPC. + +``` +stackit alpha vpc delete VPC_ID [flags] +``` + +### Examples + +``` + Delete a vpc with ID "xxx" + $ stackit alpha vpc delete xxx +``` + +### Options + +``` + -h, --help Help for "stackit alpha vpc delete" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, (one of: [json, pretty, none, 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 alpha vpc](./stackit_alpha_vpc.md) - Manages vpcs + diff --git a/docs/stackit_alpha_vpc_describe.md b/docs/stackit_alpha_vpc_describe.md new file mode 100644 index 000000000..bf5ce86f5 --- /dev/null +++ b/docs/stackit_alpha_vpc_describe.md @@ -0,0 +1,43 @@ +## stackit alpha vpc describe + +Shows details of a VPC + +### Synopsis + +Shows details of a VPC. + +``` +stackit alpha vpc describe VPC_ID [flags] +``` + +### Examples + +``` + Show details of a vpc with ID "xxx" + $ stackit alpha vpc describe xxx + + Show details of a vpc with ID "xxx" in JSON format + $ stackit alpha vpc describe xxx --output-format json +``` + +### Options + +``` + -h, --help Help for "stackit alpha vpc describe" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, (one of: [json, pretty, none, 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 alpha vpc](./stackit_alpha_vpc.md) - Manages vpcs + diff --git a/docs/stackit_alpha_vpc_list.md b/docs/stackit_alpha_vpc_list.md new file mode 100644 index 000000000..457471fdf --- /dev/null +++ b/docs/stackit_alpha_vpc_list.md @@ -0,0 +1,51 @@ +## stackit alpha vpc list + +Lists all VPC of a project + +### Synopsis + +Lists all VPC of a project. + +``` +stackit alpha vpc list [flags] +``` + +### Examples + +``` + Lists all VPCs + $ stackit alpha vpc list + + Lists all VPCs in JSON format + $ stackit alpha vpc list --output-format json + + Lists up to 10 VPCs + $ stackit alpha vpc list --limit 10 + + Lists all VPCs which has the name "my-vpc" + $ stackit alpha vpc list --filter "name == 'my-vpc'" +``` + +### Options + +``` + --filter string Filter resources by fields. A subset of expr-lang is supported. See https://expr-lang.org/docs/language-definition for usage details + -h, --help Help for "stackit alpha vpc list" + --limit int Maximum number of VPCs to return +``` + +### 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 alpha vpc](./stackit_alpha_vpc.md) - Manages vpcs + diff --git a/docs/stackit_alpha_vpc_update.md b/docs/stackit_alpha_vpc_update.md new file mode 100644 index 000000000..d500e3279 --- /dev/null +++ b/docs/stackit_alpha_vpc_update.md @@ -0,0 +1,46 @@ +## stackit alpha vpc update + +Updates a VPC + +### Synopsis + +Updates a VPC. + +``` +stackit alpha vpc update VPC_ID [flags] +``` + +### Examples + +``` + Update a vpc with ID "xxx" to new name "vpc-new" + $ stackit alpha vpc update xxx --name vpc-new + + Update a vpc with ID "xxx" to new description "updated vpc" + $ stackit alpha vpc update xxx --description "updated vpc" +``` + +### Options + +``` + --description string Description of the VPC + -h, --help Help for "stackit alpha vpc update" + --labels stringToString Comma separated list of labels of the VPC (default []) + --name string Name of the VPC +``` + +### 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 alpha vpc](./stackit_alpha_vpc.md) - Manages vpcs + diff --git a/docs/stackit_config_set.md b/docs/stackit_config_set.md index b371db82e..9f21f4095 100644 --- a/docs/stackit_config_set.md +++ b/docs/stackit_config_set.md @@ -37,6 +37,7 @@ stackit config set [flags] --dns-custom-endpoint string DNS API base URL, used in calls to this API --edge-custom-endpoint string Edge API base URL, used in calls to this API -h, --help Help for "stackit config set" + --iaas-alpha-custom-endpoint string IaaS Alpha API base URL, used in calls to this API --iaas-custom-endpoint string IaaS API base URL, used in calls to this API --identity-provider-custom-client-id string Identity Provider client ID, used for user authentication --identity-provider-custom-well-known-configuration string Identity Provider well-known OpenID configuration URL, used for user authentication diff --git a/docs/stackit_config_unset.md b/docs/stackit_config_unset.md index d809e5e30..5f64ad2e4 100644 --- a/docs/stackit_config_unset.md +++ b/docs/stackit_config_unset.md @@ -36,6 +36,7 @@ stackit config unset [flags] --dns-custom-endpoint DNS API base URL. If unset, uses the default base URL --edge-custom-endpoint Edge API base URL. If unset, uses the default base URL -h, --help Help for "stackit config unset" + --iaas-alpha-custom-endpoint IaaS Alpha API base URL. If unset, uses the default base URL --iaas-custom-endpoint IaaS API base URL. If unset, uses the default base URL --identity-provider-custom-client-id Identity Provider client ID, used for user authentication --identity-provider-custom-well-known-configuration Identity Provider well-known OpenID configuration URL. If unset, uses the default identity provider diff --git a/internal/cmd/alpha/alpha.go b/internal/cmd/alpha/alpha.go new file mode 100644 index 000000000..dac1d5ce1 --- /dev/null +++ b/internal/cmd/alpha/alpha.go @@ -0,0 +1,39 @@ +package alpha + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/stackitcloud/stackit-cli/internal/cmd/alpha/vpc" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "alpha", + Short: "Contains alpha STACKIT CLI commands", + Long: fmt.Sprintf("%s\n%s", + "Contains alpha STACKIT CLI commands.", + "The commands under this group are still in an alpha state, and functionality may be incomplete or have breaking changes."), + Args: args.NoArgs, + Run: utils.CmdHelp, + Example: examples.Build( + examples.NewExample( + "See the currently available alpha commands", + "$ stackit alpha --help"), + examples.NewExample( + "Execute an alpha command", + "$ stackit alpha MY_COMMAND"), + ), + } + addSubcommands(cmd, params) + return cmd +} + +func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { + cmd.AddCommand(vpc.NewCmd(params)) +} diff --git a/internal/cmd/alpha/vpc/create/create.go b/internal/cmd/alpha/vpc/create/create.go new file mode 100644 index 000000000..57669841b --- /dev/null +++ b/internal/cmd/alpha/vpc/create/create.go @@ -0,0 +1,130 @@ +package create + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2alpha1api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaasalpha/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" +) + +const ( + nameFlag = "name" + descriptionFlag = "description" + labelsFlag = "labels" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + Name string + Description *string + Labels map[string]any +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "create", + Short: "Creates a VPC", + Long: fmt.Sprintf("%s\n%s", + "Creates a VPC.", + "VPC can only be used in projects with enabled VPC."), + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `Create a vpc with name "my-vpc"`, + "$ stackit alpha vpc create --name my-vpc", + ), + ), + 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 + } + + projectLabel, err := projectname.GetProjectName(ctx, params.Printer, params.CliVersion, cmd) + if err != nil { + params.Printer.Debug(print.ErrorLevel, "get project name: %v", err) + projectLabel = model.ProjectId + } + + prompt := fmt.Sprintf("Are you sure you want to create a vpc for project %q?", projectLabel) + err = params.Printer.PromptForConfirmation(prompt) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, model, apiClient) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("create vpc: %w", err) + } + + return outputResult(params.Printer, model.OutputFormat, projectLabel, resp) + }, + } + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().String(nameFlag, "", "Name of the VPC") + cmd.Flags().String(descriptionFlag, "", "Description of the VPC") + cmd.Flags().StringToString(labelsFlag, nil, "Comma separated list of labels of the VPC") + + err := cmd.MarkFlagRequired(nameFlag) + cobra.CheckErr(err) +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + Name: flags.FlagToStringValue(p, cmd, nameFlag), + Description: flags.FlagToStringPointer(p, cmd, descriptionFlag), + Labels: flags.FlagToStringToAny(p, cmd, labelsFlag), + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiCreateVPCRequest { + payload := iaas.CreateVPCPayload{ + Name: model.Name, + Description: model.Description, + Labels: model.Labels, + } + return apiClient.DefaultAPI.CreateVPC(ctx, model.ProjectId).CreateVPCPayload(payload) +} + +func outputResult(p *print.Printer, outputFormat, projectLabel string, resp *iaas.VPC) error { + if resp == nil { + return fmt.Errorf("create vpc response is nil") + } + return p.OutputResult(outputFormat, resp, func() error { + p.Outputf("Created vpc for %q. VPC ID: %s\n", projectLabel, resp.Id) + return nil + }) +} diff --git a/internal/cmd/alpha/vpc/create/create_test.go b/internal/cmd/alpha/vpc/create/create_test.go new file mode 100644 index 000000000..c7e0ecc6c --- /dev/null +++ b/internal/cmd/alpha/vpc/create/create_test.go @@ -0,0 +1,229 @@ +package create + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2alpha1api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/testparams" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" +) + +type testCtxKey struct{} + +var ( + testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + testClient = &iaas.APIClient{DefaultAPI: &iaas.DefaultAPIService{}} + + testProjectId = uuid.NewString() + testName = "test-vpc" + testDescription = "test-vpc-description" + testLabels = "environment=dev,endpoint=alpha" +) + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.ProjectIdFlag: testProjectId, + nameFlag: testName, + descriptionFlag: testDescription, + labelsFlag: testLabels, + } + 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, + }, + Name: testName, + Description: new(testDescription), + Labels: map[string]any{ + "environment": "dev", + "endpoint": "alpha", + }, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixturePayload(mods ...func(payload *iaas.CreateVPCPayload)) (payload iaas.CreateVPCPayload) { + payload = iaas.CreateVPCPayload{ + Name: testName, + Description: new(testDescription), + Labels: map[string]any{"environment": "dev", "endpoint": "alpha"}, + } + for _, f := range mods { + f(&payload) + } + return payload +} + +func fixtureRequest(mods ...func(request *iaas.ApiCreateVPCRequest)) iaas.ApiCreateVPCRequest { + request := testClient.DefaultAPI.CreateVPC(testCtx, testProjectId) + + request = request.CreateVPCPayload(fixturePayload()) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "required fields only", + flagValues: map[string]string{ + globalflags.ProjectIdFlag: testProjectId, + nameFlag: testName, + }, + isValid: true, + expectedModel: &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Verbosity: globalflags.VerbosityDefault, + }, + Name: testName, + }, + }, + { + description: "project id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, globalflags.ProjectIdFlag) + }), + isValid: false, + }, + { + description: "project id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[globalflags.ProjectIdFlag] = "" + }), + isValid: false, + }, + { + description: "project id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[globalflags.ProjectIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "name missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, nameFlag) + }), + isValid: false, + }, + } + + 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 + model *inputModel + expectedRequest iaas.ApiCreateVPCRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + { + description: "required fields only", + model: &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Verbosity: globalflags.VerbosityDefault, + }, + Name: testName, + }, + expectedRequest: fixtureRequest(func(request *iaas.ApiCreateVPCRequest) { + *request = request.CreateVPCPayload(iaas.CreateVPCPayload{ + Name: testName, + }) + }), + }, + } + + 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, iaas.DefaultAPIService{}), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + outputFormat string + projectLabel string + resp *iaas.VPC + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty", + args: args{}, + wantErr: true, + }, + { + name: "empty vpc response", + args: args{ + projectLabel: testProjectId, + resp: &iaas.VPC{}, + }, + }, + } + params := testparams.NewTestParams() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(params.Printer, tt.args.outputFormat, tt.args.projectLabel, tt.args.resp); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/alpha/vpc/delete/delete.go b/internal/cmd/alpha/vpc/delete/delete.go new file mode 100644 index 000000000..3495d9503 --- /dev/null +++ b/internal/cmd/alpha/vpc/delete/delete.go @@ -0,0 +1,101 @@ +package delete + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2alpha1api" + + "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/iaasalpha/client" + iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaasalpha/utils" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +const ( + vpcIdArg = "VPC_ID" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + VpcId string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: fmt.Sprintf("delete %s", vpcIdArg), + Short: "Deletes a VPC", + Long: "Deletes a VPC.", + Args: args.SingleArg(vpcIdArg, utils.ValidateUUID), + Example: examples.Build( + examples.NewExample( + `Delete a vpc with ID "xxx"`, + "$ stackit alpha vpc delete xxx"), + ), + 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 + } + + vpcLabel, err := iaasUtils.GetVPCName(ctx, apiClient.DefaultAPI, model.ProjectId, model.VpcId) + if err != nil { + params.Printer.Debug(print.ErrorLevel, "get VPC name: %v", err) + vpcLabel = model.VpcId + } else if vpcLabel == "" { + vpcLabel = model.VpcId + } + + prompt := fmt.Sprintf("Are you sure you want to delete vpc %q?", vpcLabel) + err = params.Printer.PromptForConfirmation(prompt) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, model, apiClient) + err = req.Execute() + if err != nil { + return fmt.Errorf("delete vpc: %w", err) + } + + params.Printer.Outputf("Deleted vpc %q\n", vpcLabel) + return nil + }, + } + return cmd +} + +func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { + vpcId := inputArgs[0] + + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + VpcId: vpcId, + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiDeleteVPCRequest { + return apiClient.DefaultAPI.DeleteVPC(ctx, model.ProjectId, model.VpcId) +} diff --git a/internal/cmd/alpha/vpc/delete/delete_test.go b/internal/cmd/alpha/vpc/delete/delete_test.go new file mode 100644 index 000000000..688ac031c --- /dev/null +++ b/internal/cmd/alpha/vpc/delete/delete_test.go @@ -0,0 +1,171 @@ +package delete + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2alpha1api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" +) + +type testCtxKey struct{} + +var ( + testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + testClient = &iaas.APIClient{DefaultAPI: &iaas.DefaultAPIService{}} + testProjectId = uuid.NewString() + testVpcId = uuid.NewString() +) + +func fixtureArgValues(mods ...func(argValues []string)) []string { + argValues := []string{ + testVpcId, + } + for _, mod := range mods { + mod(argValues) + } + return argValues +} + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.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, + }, + VpcId: testVpcId, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *iaas.ApiDeleteVPCRequest)) iaas.ApiDeleteVPCRequest { + request := testClient.DefaultAPI.DeleteVPC(testCtx, testProjectId, testVpcId) + 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, globalflags.ProjectIdFlag) + }), + isValid: false, + }, + { + description: "project id invalid 1", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[globalflags.ProjectIdFlag] = "" + }), + isValid: false, + }, + { + description: "project id invalid 2", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[globalflags.ProjectIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "vpc id invalid 1", + argValues: []string{""}, + flagValues: fixtureFlagValues(), + isValid: false, + }, + { + description: "vpc id invalid 2", + argValues: []string{"invalid-uuid"}, + flagValues: fixtureFlagValues(), + isValid: false, + }, + } + + 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 + model *inputModel + expectedRequest iaas.ApiDeleteVPCRequest + }{ + { + 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, iaas.DefaultAPIService{}), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} diff --git a/internal/cmd/alpha/vpc/describe/describe.go b/internal/cmd/alpha/vpc/describe/describe.go new file mode 100644 index 000000000..357e75c36 --- /dev/null +++ b/internal/cmd/alpha/vpc/describe/describe.go @@ -0,0 +1,123 @@ +package describe + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/spf13/cobra" + iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2alpha1api" + + "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/iaasalpha/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +const ( + vpcIdArg = "VPC_ID" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + VpcId string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: fmt.Sprintf("describe %s", vpcIdArg), + Short: "Shows details of a VPC", + Long: "Shows details of a VPC.", + Args: args.SingleArg(vpcIdArg, utils.ValidateUUID), + Example: examples.Build( + examples.NewExample( + `Show details of a vpc with ID "xxx"`, + "$ stackit alpha vpc describe xxx"), + examples.NewExample( + `Show details of a vpc with ID "xxx" in JSON format`, + "$ stackit alpha vpc describe xxx --output-format json"), + ), + 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, model, apiClient) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("read vpc: %w", err) + } + + return outputResult(params.Printer, model.OutputFormat, resp) + }, + } + return cmd +} + +func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { + vpcId := inputArgs[0] + + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + VpcId: vpcId, + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiGetVPCRequest { + return apiClient.DefaultAPI.GetVPC(ctx, model.ProjectId, model.VpcId) +} + +func outputResult(p *print.Printer, outputFormat string, resp *iaas.VPC) error { + if resp == nil { + return fmt.Errorf("nil response") + } + return p.OutputResult(outputFormat, resp, func() error { + table := tables.NewTable() + table.AddRow("ID", resp.Id) + table.AddSeparator() + table.AddRow("NAME", resp.Name) + table.AddSeparator() + table.AddRow("DESCRIPTION", resp.Description) + table.AddSeparator() + if len(resp.Labels) > 0 { + var labels []string + for key, value := range resp.Labels { + labels = append(labels, fmt.Sprintf("%s: %s", key, value)) + } + table.AddRow("LABELS", strings.Join(labels, "\n")) + table.AddSeparator() + } + table.AddRow("CREATED AT", resp.CreatedAt.Format(time.DateTime)) + table.AddSeparator() + table.AddRow("UPDATED AT", resp.UpdatedAt.Format(time.DateTime)) + table.AddSeparator() + + if err := table.Display(p); err != nil { + return fmt.Errorf("render table: %w", err) + } + return nil + }) +} diff --git a/internal/cmd/alpha/vpc/describe/describe_test.go b/internal/cmd/alpha/vpc/describe/describe_test.go new file mode 100644 index 000000000..87d0e1705 --- /dev/null +++ b/internal/cmd/alpha/vpc/describe/describe_test.go @@ -0,0 +1,205 @@ +package describe + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2alpha1api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/testparams" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" +) + +type testCtxKey struct{} + +var ( + testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + testClient = &iaas.APIClient{DefaultAPI: &iaas.DefaultAPIService{}} + testProjectId = uuid.NewString() + testVpcId = uuid.NewString() +) + +func fixtureArgValues(mods ...func(argValues []string)) []string { + argValues := []string{ + testVpcId, + } + for _, mod := range mods { + mod(argValues) + } + return argValues +} + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.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, + }, + VpcId: testVpcId, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *iaas.ApiGetVPCRequest)) iaas.ApiGetVPCRequest { + request := testClient.DefaultAPI.GetVPC(testCtx, testProjectId, testVpcId) + 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, globalflags.ProjectIdFlag) + }), + isValid: false, + }, + { + description: "project id invalid 1", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[globalflags.ProjectIdFlag] = "" + }), + isValid: false, + }, + { + description: "project id invalid 2", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[globalflags.ProjectIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "vpc id invalid 1", + argValues: []string{""}, + flagValues: fixtureFlagValues(), + isValid: false, + }, + { + description: "vpc id invalid 2", + argValues: []string{"invalid-uuid"}, + flagValues: fixtureFlagValues(), + isValid: false, + }, + } + + 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 + model *inputModel + expectedRequest iaas.ApiGetVPCRequest + }{ + { + 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, iaas.DefaultAPIService{}), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + outputFormat string + vpc *iaas.VPC + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty", + args: args{}, + wantErr: true, + }, + { + name: "set empty vpc", + args: args{ + vpc: &iaas.VPC{}, + }, + wantErr: false, + }, + } + params := testparams.NewTestParams() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(params.Printer, tt.args.outputFormat, tt.args.vpc); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/alpha/vpc/list/list.go b/internal/cmd/alpha/vpc/list/list.go new file mode 100644 index 000000000..6b585dec2 --- /dev/null +++ b/internal/cmd/alpha/vpc/list/list.go @@ -0,0 +1,155 @@ +package list + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2alpha1api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaasalpha/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" +) + +const ( + limitFlag = "limit" + filterFlag = "filter" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + Limit *int64 + Filter *string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "Lists all VPC of a project", + Long: "Lists all VPC of a project.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `Lists all VPCs`, + "$ stackit alpha vpc list"), + examples.NewExample( + `Lists all VPCs in JSON format`, + "$ stackit alpha vpc list --output-format json"), + examples.NewExample( + `Lists up to 10 VPCs`, + "$ stackit alpha vpc list --limit 10"), + examples.NewExample( + `Lists all VPCs which has the name "my-vpc"`, + `$ stackit alpha vpc list --filter "name == 'my-vpc'"`), + ), + 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, model, apiClient) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("read vpc: %w", err) + } + + projectLabel, err := projectname.GetProjectName(ctx, params.Printer, params.CliVersion, cmd) + if err != nil { + params.Printer.Debug(print.ErrorLevel, "get project name: %v", err) + projectLabel = model.ProjectId + } + + items := resp.GetItems() + // Truncate output + if model.Limit != nil && len(items) > int(*model.Limit) { + items = items[:*model.Limit] + } + + return outputResult(params.Printer, model.OutputFormat, projectLabel, items) + }, + } + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Int64(limitFlag, 0, "Maximum number of VPCs to return") + cmd.Flags().String(filterFlag, "", "Filter resources by fields. A subset of expr-lang is supported. See https://expr-lang.org/docs/language-definition for usage details") +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + + limit := flags.FlagToInt64Pointer(p, cmd, limitFlag) + if limit != nil && *limit < 1 { + return nil, &errors.FlagValidationError{ + Flag: limitFlag, + Details: "must be greater than 0", + } + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + Limit: limit, + Filter: flags.FlagToStringPointer(p, cmd, filterFlag), + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiListVPCsRequest { + req := apiClient.DefaultAPI.ListVPCs(ctx, model.ProjectId) + if model.Filter != nil { + req = req.Filter(*model.Filter) + } + return req +} + +func outputResult(p *print.Printer, outputFormat, projectLabel string, items []iaas.VPC) error { + return p.OutputResult(outputFormat, items, func() error { + if len(items) == 0 { + p.Outputf("No VPCs found for project %q\n", projectLabel) + return nil + } + + table := tables.NewTable() + table.SetHeader("ID", "NAME", "DESCRIPTION") + + for i := range items { + vpc := items[i] + + table.AddRow( + vpc.Id, + vpc.Name, + vpc.Description, + ) + table.AddSeparator() + } + + if err := table.Display(p); err != nil { + return fmt.Errorf("render table: %w", err) + } + return nil + }) +} diff --git a/internal/cmd/alpha/vpc/list/list_test.go b/internal/cmd/alpha/vpc/list/list_test.go new file mode 100644 index 000000000..4fc9e312d --- /dev/null +++ b/internal/cmd/alpha/vpc/list/list_test.go @@ -0,0 +1,178 @@ +package list + +import ( + "context" + "strconv" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2alpha1api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/testparams" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" +) + +type testCtxKey struct{} + +var ( + testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + testClient = &iaas.APIClient{DefaultAPI: &iaas.DefaultAPIService{}} + testProjectId = uuid.NewString() + testLimit int64 = 10 + testFilter = "filter" +) + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.ProjectIdFlag: testProjectId, + limitFlag: strconv.Itoa(int(testLimit)), + filterFlag: testFilter, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Verbosity: globalflags.VerbosityDefault, + }, + Limit: new(testLimit), + Filter: new(testFilter), + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *iaas.ApiListVPCsRequest)) iaas.ApiListVPCsRequest { + request := testClient.DefaultAPI.ListVPCs(testCtx, testProjectId).Filter(testFilter) + 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", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "no flag values", + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "project id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, globalflags.ProjectIdFlag) + }), + isValid: false, + }, + { + description: "project id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[globalflags.ProjectIdFlag] = "" + }), + isValid: false, + }, + { + description: "project id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[globalflags.ProjectIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + } + + 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 + model *inputModel + expectedRequest iaas.ApiListVPCsRequest + }{ + { + 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, iaas.DefaultAPIService{}), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + outputFormat string + projectLabel string + items []iaas.VPC + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty", + wantErr: false, + }, + { + name: "empty vpc", + args: args{ + items: []iaas.VPC{ + {}, + }, + }, + wantErr: false, + }, + } + params := testparams.NewTestParams() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(params.Printer, tt.args.outputFormat, tt.args.projectLabel, tt.args.items); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/alpha/vpc/update/update.go b/internal/cmd/alpha/vpc/update/update.go new file mode 100644 index 000000000..a6ebbad39 --- /dev/null +++ b/internal/cmd/alpha/vpc/update/update.go @@ -0,0 +1,137 @@ +package update + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2alpha1api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaasalpha/client" + iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaasalpha/utils" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +const ( + vpcIdArg = "VPC_ID" + + nameFlag = "name" + descriptionFlag = "description" + labelsFlag = "labels" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + VpcId string + Name *string + Description *string + Labels map[string]any +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: fmt.Sprintf("update %s", vpcIdArg), + Short: "Updates a VPC", + Long: "Updates a VPC.", + Args: args.SingleArg(vpcIdArg, utils.ValidateUUID), + Example: examples.Build( + examples.NewExample( + `Update a vpc with ID "xxx" to new name "vpc-new"`, + "$ stackit alpha vpc update xxx --name vpc-new", + ), + examples.NewExample( + `Update a vpc with ID "xxx" to new description "updated vpc"`, + `$ stackit alpha vpc update xxx --description "updated vpc"`, + ), + ), + 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 + } + + vpcLabel, err := iaasUtils.GetVPCName(ctx, apiClient.DefaultAPI, model.ProjectId, model.VpcId) + if err != nil { + params.Printer.Debug(print.ErrorLevel, "get vpc name: %v", err) + vpcLabel = model.VpcId + } + + prompt := fmt.Sprintf("Are you sure you want to update vpc %q?", vpcLabel) + err = params.Printer.PromptForConfirmation(prompt) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, model, apiClient) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("update vpc: %w", err) + } + + return outputResult(params.Printer, model.OutputFormat, vpcLabel, resp) + }, + } + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().String(nameFlag, "", "Name of the VPC") + cmd.Flags().String(descriptionFlag, "", "Description of the VPC") + cmd.Flags().StringToString(labelsFlag, nil, "Comma separated list of labels of the VPC") + + cmd.MarkFlagsOneRequired(nameFlag, descriptionFlag, labelsFlag) +} + +func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { + vpcId := inputArgs[0] + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + VpcId: vpcId, + Name: flags.FlagToStringPointer(p, cmd, nameFlag), + Description: flags.FlagToStringPointer(p, cmd, descriptionFlag), + Labels: flags.FlagToStringToAny(p, cmd, labelsFlag), + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiPartialUpdateVPCRequest { + payload := iaas.PartialUpdateVPCPayload{ + Name: model.Name, + Description: model.Description, + Labels: model.Labels, + } + return apiClient.DefaultAPI.PartialUpdateVPC(ctx, model.ProjectId, model.VpcId).PartialUpdateVPCPayload(payload) +} + +func outputResult(p *print.Printer, outputFormat, vpcLabel string, resp *iaas.VPC) error { + if resp == nil { + return fmt.Errorf("update vpc response is nil") + } + return p.OutputResult(outputFormat, resp, func() error { + p.Outputf("Updated vpc %q.\n", vpcLabel) + return nil + }) +} diff --git a/internal/cmd/alpha/vpc/update/update_test.go b/internal/cmd/alpha/vpc/update/update_test.go new file mode 100644 index 000000000..56c7d33e3 --- /dev/null +++ b/internal/cmd/alpha/vpc/update/update_test.go @@ -0,0 +1,226 @@ +package update + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2alpha1api" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/testparams" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" +) + +type testCtxKey struct{} + +var ( + testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + testClient = &iaas.APIClient{DefaultAPI: &iaas.DefaultAPIService{}} + + testProjectId = uuid.NewString() + testVpcId = uuid.NewString() + testName = "test-vpc" + testDescription = "test-vpc-description" + testLabels = "environment=dev,endpoint=alpha" +) + +func fixtureArgValues(mods ...func(argValues []string)) []string { + argValues := []string{ + testVpcId, + } + for _, mod := range mods { + mod(argValues) + } + return argValues +} + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + globalflags.ProjectIdFlag: testProjectId, + nameFlag: testName, + descriptionFlag: testDescription, + labelsFlag: testLabels, + } + 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, + }, + VpcId: testVpcId, + Name: new(testName), + Description: new(testDescription), + Labels: map[string]any{ + "environment": "dev", + "endpoint": "alpha", + }, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixturePayload(mods ...func(payload *iaas.PartialUpdateVPCPayload)) (payload iaas.PartialUpdateVPCPayload) { + payload = iaas.PartialUpdateVPCPayload{ + Name: new(testName), + Description: new(testDescription), + Labels: map[string]any{"environment": "dev", "endpoint": "alpha"}, + } + for _, f := range mods { + f(&payload) + } + return payload +} + +func fixtureRequest(mods ...func(request *iaas.ApiPartialUpdateVPCRequest)) iaas.ApiPartialUpdateVPCRequest { + request := testClient.DefaultAPI.PartialUpdateVPC(testCtx, testProjectId, testVpcId) + + request = request.PartialUpdateVPCPayload(fixturePayload()) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values - nothing to update", + argValues: fixtureArgValues(), + flagValues: map[string]string{}, + isValid: false, + }, + { + description: "required fields only", + argValues: fixtureArgValues(), + flagValues: map[string]string{ + globalflags.ProjectIdFlag: testProjectId, + nameFlag: testName, + }, + isValid: true, + expectedModel: &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Verbosity: globalflags.VerbosityDefault, + }, + VpcId: testVpcId, + Name: new(testName), + }, + }, + { + description: "project id missing", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, globalflags.ProjectIdFlag) + }), + isValid: false, + }, + { + description: "project id invalid 1", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[globalflags.ProjectIdFlag] = "" + }), + isValid: false, + }, + { + description: "project id invalid 2", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[globalflags.ProjectIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + } + + 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 + model *inputModel + expectedRequest iaas.ApiPartialUpdateVPCRequest + }{ + { + 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, iaas.DefaultAPIService{}), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + outputFormat string + vpcLabel string + resp *iaas.VPC + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty", + args: args{}, + wantErr: true, + }, + { + name: "empty vpc response", + args: args{ + vpcLabel: testVpcId, + resp: &iaas.VPC{}, + }, + }, + } + params := testparams.NewTestParams() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(params.Printer, tt.args.outputFormat, tt.args.vpcLabel, tt.args.resp); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/alpha/vpc/vpc.go b/internal/cmd/alpha/vpc/vpc.go new file mode 100644 index 000000000..a9209b56c --- /dev/null +++ b/internal/cmd/alpha/vpc/vpc.go @@ -0,0 +1,34 @@ +package vpc + +import ( + "github.com/spf13/cobra" + + "github.com/stackitcloud/stackit-cli/internal/cmd/alpha/vpc/create" + "github.com/stackitcloud/stackit-cli/internal/cmd/alpha/vpc/delete" + "github.com/stackitcloud/stackit-cli/internal/cmd/alpha/vpc/describe" + "github.com/stackitcloud/stackit-cli/internal/cmd/alpha/vpc/list" + "github.com/stackitcloud/stackit-cli/internal/cmd/alpha/vpc/update" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" +) + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "vpc", + Short: "Manages vpcs", + Long: "Manages the lifecycle of vpcs.", + Args: args.NoArgs, + Run: utils.CmdHelp, + } + addSubcommands(cmd, params) + return cmd +} + +func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { + cmd.AddCommand(create.NewCmd(params)) + cmd.AddCommand(delete.NewCmd(params)) + cmd.AddCommand(describe.NewCmd(params)) + cmd.AddCommand(list.NewCmd(params)) + cmd.AddCommand(update.NewCmd(params)) +} diff --git a/internal/cmd/config/set/set.go b/internal/cmd/config/set/set.go index 5a37ee68c..194291a3d 100644 --- a/internal/cmd/config/set/set.go +++ b/internal/cmd/config/set/set.go @@ -49,6 +49,7 @@ const ( serviceEnablementCustomEndpointFlag = "service-enablement-custom-endpoint" skeCustomEndpointFlag = "ske-custom-endpoint" sqlServerFlexCustomEndpointFlag = "sqlserverflex-custom-endpoint" + iaasAlphaCustomEndpointFlag = "iaas-alpha-custom-endpoint" iaasCustomEndpointFlag = "iaas-custom-endpoint" tokenCustomEndpointFlag = "token-custom-endpoint" intakeCustomEndpointFlag = "intake-custom-endpoint" @@ -171,6 +172,7 @@ func configureFlags(cmd *cobra.Command) { cmd.Flags().String(runCommandCustomEndpointFlag, "", "Run Command API base URL, used in calls to this API") cmd.Flags().String(skeCustomEndpointFlag, "", "SKE API base URL, used in calls to this API") cmd.Flags().String(sqlServerFlexCustomEndpointFlag, "", "SQLServer Flex API base URL, used in calls to this API") + cmd.Flags().String(iaasAlphaCustomEndpointFlag, "", "IaaS Alpha API base URL, used in calls to this API") cmd.Flags().String(iaasCustomEndpointFlag, "", "IaaS API base URL, used in calls to this API") cmd.Flags().String(tokenCustomEndpointFlag, "", "Custom token endpoint of the Service Account API, which is used to request access tokens when the service account authentication is activated. Not relevant for user authentication.") cmd.Flags().String(intakeCustomEndpointFlag, "", "Intake API base URL, used in calls to this API") @@ -238,6 +240,8 @@ func configureFlags(cmd *cobra.Command) { cobra.CheckErr(err) err = viper.BindPFlag(config.SQLServerFlexCustomEndpointKey, cmd.Flags().Lookup(sqlServerFlexCustomEndpointFlag)) cobra.CheckErr(err) + err = viper.BindPFlag(config.IaaSAlphaCustomEndpointKey, cmd.Flags().Lookup(iaasAlphaCustomEndpointFlag)) + cobra.CheckErr(err) err = viper.BindPFlag(config.IaaSCustomEndpointKey, cmd.Flags().Lookup(iaasCustomEndpointFlag)) cobra.CheckErr(err) err = viper.BindPFlag(config.TokenCustomEndpointKey, cmd.Flags().Lookup(tokenCustomEndpointFlag)) diff --git a/internal/cmd/config/unset/unset.go b/internal/cmd/config/unset/unset.go index ba128c62f..5aff526eb 100644 --- a/internal/cmd/config/unset/unset.go +++ b/internal/cmd/config/unset/unset.go @@ -53,6 +53,7 @@ const ( sfsCustomEndpointFlag = "sfs-custom-endpoint" skeCustomEndpointFlag = "ske-custom-endpoint" sqlServerFlexCustomEndpointFlag = "sqlserverflex-custom-endpoint" + iaasAlphaCustomEndpointFlag = "iaas-alpha-custom-endpoint" iaasCustomEndpointFlag = "iaas-custom-endpoint" tokenCustomEndpointFlag = "token-custom-endpoint" intakeCustomEndpointFlag = "intake-custom-endpoint" @@ -105,6 +106,7 @@ type inputModel struct { SfsCustomEndpoint bool SKECustomEndpoint bool SQLServerFlexCustomEndpoint bool + IaaSAlphaCustomEndpoint bool IaaSCustomEndpoint bool TokenCustomEndpoint bool IntakeCustomEndpoint bool @@ -240,6 +242,9 @@ func NewCmd(params *types.CmdParams) *cobra.Command { if model.SQLServerFlexCustomEndpoint { viper.Set(config.SQLServerFlexCustomEndpointKey, "") } + if model.IaaSAlphaCustomEndpoint { + viper.Set(config.IaaSAlphaCustomEndpointKey, "") + } if model.IaaSCustomEndpoint { viper.Set(config.IaaSCustomEndpointKey, "") } @@ -311,6 +316,7 @@ func configureFlags(cmd *cobra.Command) { cmd.Flags().Bool(runCommandCustomEndpointFlag, false, "Server Command base URL. If unset, uses the default base URL") cmd.Flags().Bool(skeCustomEndpointFlag, false, "SKE API base URL. If unset, uses the default base URL") cmd.Flags().Bool(sqlServerFlexCustomEndpointFlag, false, "SQLServer Flex API base URL. If unset, uses the default base URL") + cmd.Flags().Bool(iaasAlphaCustomEndpointFlag, false, "IaaS Alpha API base URL. If unset, uses the default base URL") cmd.Flags().Bool(iaasCustomEndpointFlag, false, "IaaS API base URL. If unset, uses the default base URL") cmd.Flags().Bool(tokenCustomEndpointFlag, false, "Custom token endpoint of the Service Account API, which is used to request access tokens when the service account authentication is activated. Not relevant for user authentication.") cmd.Flags().Bool(intakeCustomEndpointFlag, false, "Intake API base URL. If unset, uses the default base URL") @@ -360,6 +366,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command) *inputModel { SKECustomEndpoint: flags.FlagToBoolValue(p, cmd, skeCustomEndpointFlag), SfsCustomEndpoint: flags.FlagToBoolValue(p, cmd, sfsCustomEndpointFlag), SQLServerFlexCustomEndpoint: flags.FlagToBoolValue(p, cmd, sqlServerFlexCustomEndpointFlag), + IaaSAlphaCustomEndpoint: flags.FlagToBoolValue(p, cmd, iaasAlphaCustomEndpointFlag), IaaSCustomEndpoint: flags.FlagToBoolValue(p, cmd, iaasCustomEndpointFlag), TokenCustomEndpoint: flags.FlagToBoolValue(p, cmd, tokenCustomEndpointFlag), IntakeCustomEndpoint: flags.FlagToBoolValue(p, cmd, intakeCustomEndpointFlag), diff --git a/internal/cmd/config/unset/unset_test.go b/internal/cmd/config/unset/unset_test.go index 5ca73e03a..46a41ee21 100644 --- a/internal/cmd/config/unset/unset_test.go +++ b/internal/cmd/config/unset/unset_test.go @@ -45,6 +45,7 @@ func fixtureFlagValues(mods ...func(flagValues map[string]bool)) map[string]bool sfsCustomEndpointFlag: true, skeCustomEndpointFlag: true, sqlServerFlexCustomEndpointFlag: true, + iaasAlphaCustomEndpointFlag: true, iaasCustomEndpointFlag: true, tokenCustomEndpointFlag: true, intakeCustomEndpointFlag: true, @@ -94,6 +95,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { SfsCustomEndpoint: true, SKECustomEndpoint: true, SQLServerFlexCustomEndpoint: true, + IaaSAlphaCustomEndpoint: true, IaaSCustomEndpoint: true, TokenCustomEndpoint: true, IntakeCustomEndpoint: true, @@ -159,6 +161,7 @@ func TestParseInput(t *testing.T) { model.SfsCustomEndpoint = false model.SKECustomEndpoint = false model.SQLServerFlexCustomEndpoint = false + model.IaaSAlphaCustomEndpoint = false model.IaaSCustomEndpoint = false model.TokenCustomEndpoint = false model.IntakeCustomEndpoint = false diff --git a/internal/cmd/root.go b/internal/cmd/root.go index bbb05aca3..5c3cd8fcf 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -5,10 +5,8 @@ import ( "strings" "time" - "github.com/stackitcloud/stackit-cli/internal/cmd/sqlserverflex" - "github.com/stackitcloud/stackit-cli/internal/pkg/types" - affinityGroups "github.com/stackitcloud/stackit-cli/internal/cmd/affinity-groups" + "github.com/stackitcloud/stackit-cli/internal/cmd/alpha" "github.com/stackitcloud/stackit-cli/internal/cmd/auth" "github.com/stackitcloud/stackit-cli/internal/cmd/beta" configCmd "github.com/stackitcloud/stackit-cli/internal/cmd/config" @@ -41,6 +39,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/cmd/server" serviceaccount "github.com/stackitcloud/stackit-cli/internal/cmd/service-account" "github.com/stackitcloud/stackit-cli/internal/cmd/ske" + "github.com/stackitcloud/stackit-cli/internal/cmd/sqlserverflex" "github.com/stackitcloud/stackit-cli/internal/cmd/valkey" "github.com/stackitcloud/stackit-cli/internal/cmd/volume" "github.com/stackitcloud/stackit-cli/internal/pkg/args" @@ -49,6 +48,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -171,6 +171,7 @@ func configureFlags(cmd *cobra.Command) error { } func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { + cmd.AddCommand(alpha.NewCmd(params)) cmd.AddCommand(auth.NewCmd(params)) cmd.AddCommand(configCmd.NewCmd(params)) cmd.AddCommand(beta.NewCmd(params)) diff --git a/internal/pkg/config/config.go b/internal/pkg/config/config.go index b0ad97036..db2441f6d 100644 --- a/internal/pkg/config/config.go +++ b/internal/pkg/config/config.go @@ -51,6 +51,7 @@ const ( SfsCustomEndpointKey = "sfs_custom_endpoint" SKECustomEndpointKey = "ske_custom_endpoint" SQLServerFlexCustomEndpointKey = "sqlserverflex_custom_endpoint" + IaaSAlphaCustomEndpointKey = "iaas_alpha_custom_endpoint" IaaSCustomEndpointKey = "iaas_custom_endpoint" TokenCustomEndpointKey = "token_custom_endpoint" GitCustomEndpointKey = "git_custom_endpoint" @@ -103,6 +104,7 @@ var ConfigKeys = []string{ DNSCustomEndpointKey, EdgeCustomEndpointKey, GitCustomEndpointKey, + IaaSAlphaCustomEndpointKey, IaaSCustomEndpointKey, IntakeCustomEndpointKey, KMSCustomEndpointKey, @@ -215,6 +217,7 @@ func setConfigDefaults() { viper.SetDefault(RunCommandCustomEndpointKey, "") viper.SetDefault(SKECustomEndpointKey, "") viper.SetDefault(SQLServerFlexCustomEndpointKey, "") + viper.SetDefault(IaaSAlphaCustomEndpointKey, "") viper.SetDefault(IaaSCustomEndpointKey, "") viper.SetDefault(TokenCustomEndpointKey, "") viper.SetDefault(GitCustomEndpointKey, "") diff --git a/internal/pkg/services/iaasalpha/client/client.go b/internal/pkg/services/iaasalpha/client/client.go new file mode 100644 index 000000000..45b5582a4 --- /dev/null +++ b/internal/pkg/services/iaasalpha/client/client.go @@ -0,0 +1,14 @@ +package client + +import ( + "github.com/stackitcloud/stackit-cli/internal/pkg/config" + genericclient "github.com/stackitcloud/stackit-cli/internal/pkg/generic-client" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + + "github.com/spf13/viper" + iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2alpha1api" +) + +func ConfigureClient(p *print.Printer, cliVersion string) (*iaas.APIClient, error) { + return genericclient.ConfigureClientGeneric(p, cliVersion, viper.GetString(config.IaaSAlphaCustomEndpointKey), false, iaas.NewAPIClient) +} diff --git a/internal/pkg/services/iaasalpha/utils/utils.go b/internal/pkg/services/iaasalpha/utils/utils.go new file mode 100644 index 000000000..eda55e037 --- /dev/null +++ b/internal/pkg/services/iaasalpha/utils/utils.go @@ -0,0 +1,24 @@ +package utils + +import ( + "context" + "errors" + "fmt" + + iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2alpha1api" +) + +var ( + ErrResponseNil = errors.New("response is nil") +) + +func GetVPCName(ctx context.Context, apiClient iaas.DefaultAPI, projectId, vpcId string) (string, error) { + resp, err := apiClient.GetVPC(ctx, projectId, vpcId).Execute() + if err != nil { + return "", fmt.Errorf("get vpc: %w", err) + } + if resp == nil { + return "", ErrResponseNil + } + return resp.Name, nil +}