From e27a3e37b32f890bff0c621080b10b89a936a8a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Schmitz?= <152157960+bahkauv70@users.noreply.github.com> Date: Wed, 4 Dec 2024 17:46:52 +0100 Subject: [PATCH 01/28] feature: add command and sub-commands for security groups --- .../cmd/auth/security_group/create/create.go | 31 ++++++++++++++++ .../cmd/auth/security_group/delete/delete.go | 31 ++++++++++++++++ .../auth/security_group/describe/describe.go | 31 ++++++++++++++++ internal/cmd/auth/security_group/list/list.go | 31 ++++++++++++++++ .../cmd/auth/security_group/security_group.go | 37 +++++++++++++++++++ .../cmd/auth/security_group/update/update.go | 31 ++++++++++++++++ internal/cmd/beta/beta.go | 2 + 7 files changed, 194 insertions(+) create mode 100644 internal/cmd/auth/security_group/create/create.go create mode 100644 internal/cmd/auth/security_group/delete/delete.go create mode 100644 internal/cmd/auth/security_group/describe/describe.go create mode 100644 internal/cmd/auth/security_group/list/list.go create mode 100644 internal/cmd/auth/security_group/security_group.go create mode 100644 internal/cmd/auth/security_group/update/update.go diff --git a/internal/cmd/auth/security_group/create/create.go b/internal/cmd/auth/security_group/create/create.go new file mode 100644 index 000000000..b42801d92 --- /dev/null +++ b/internal/cmd/auth/security_group/create/create.go @@ -0,0 +1,31 @@ +package create + +import ( + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" +) + +func NewCmd(p *print.Printer) *cobra.Command { + cmd := &cobra.Command{ + Use: "create", + Short: "create security groups", + Long: "create security groups", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample(`example 1`, `foo bar baz`), + examples.NewExample(`example 2`, `foo bar baz`), + ), + RunE: func(cmd *cobra.Command, args []string) error { + return executeCreate(cmd, p, args) + }, + } + cmd.Flags().String("dummy", "foo", "fooify") + return cmd +} + +func executeCreate(cmd *cobra.Command, p *print.Printer, args []string) error { + p.Info("executing create command") + return nil +} diff --git a/internal/cmd/auth/security_group/delete/delete.go b/internal/cmd/auth/security_group/delete/delete.go new file mode 100644 index 000000000..d660a7a95 --- /dev/null +++ b/internal/cmd/auth/security_group/delete/delete.go @@ -0,0 +1,31 @@ +package delete + +import ( + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" +) + +func NewCmd(p *print.Printer) *cobra.Command { + cmd := &cobra.Command{ + Use: "delete", + Short: "delete security groups", + Long: "delete security groups", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample(`example 1`, `foo bar baz`), + examples.NewExample(`example 2`, `foo bar baz`), + ), + RunE: func(cmd *cobra.Command, args []string) error { + return executeDelete(cmd, p, args) + }, + } + cmd.Flags().String("dummy", "foo", "fooify") + return cmd +} + +func executeDelete(cmd *cobra.Command, p *print.Printer, args []string) error { + p.Info("executing create command") + return nil +} diff --git a/internal/cmd/auth/security_group/describe/describe.go b/internal/cmd/auth/security_group/describe/describe.go new file mode 100644 index 000000000..e4d85977a --- /dev/null +++ b/internal/cmd/auth/security_group/describe/describe.go @@ -0,0 +1,31 @@ +package describe + +import ( + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" +) + +func NewCmd(p *print.Printer) *cobra.Command { + cmd := &cobra.Command{ + Use: "describe", + Short: "describe security groups", + Long: "describe security groups", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample(`example 1`, `foo bar baz`), + examples.NewExample(`example 2`, `foo bar baz`), + ), + RunE: func(cmd *cobra.Command, args []string) error { + return executeDescribe(cmd, p, args) + }, + } + cmd.Flags().String("dummy", "foo", "fooify") + return cmd +} + +func executeDescribe(cmd *cobra.Command, p *print.Printer, args []string) error { + p.Info("executing describe command") + return nil +} diff --git a/internal/cmd/auth/security_group/list/list.go b/internal/cmd/auth/security_group/list/list.go new file mode 100644 index 000000000..08ba47c1c --- /dev/null +++ b/internal/cmd/auth/security_group/list/list.go @@ -0,0 +1,31 @@ +package list + +import ( + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" +) + +func NewCmd(p *print.Printer) *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "list security groups", + Long: "list security groups", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample(`example 1`, `foo bar baz`), + examples.NewExample(`example 2`, `foo bar baz`), + ), + RunE: func(cmd *cobra.Command, args []string) error { + return executeList(cmd, p, args) + }, + } + cmd.Flags().String("dummy", "foo", "fooify") + return cmd +} + +func executeList(cmd *cobra.Command, p *print.Printer, args []string) error { + p.Info("executing list command") + return nil +} diff --git a/internal/cmd/auth/security_group/security_group.go b/internal/cmd/auth/security_group/security_group.go new file mode 100644 index 000000000..15e19e1e3 --- /dev/null +++ b/internal/cmd/auth/security_group/security_group.go @@ -0,0 +1,37 @@ +package security_group + +import ( + "github.com/stackitcloud/stackit-cli/internal/cmd/auth/security_group/create" + "github.com/stackitcloud/stackit-cli/internal/cmd/auth/security_group/delete" + "github.com/stackitcloud/stackit-cli/internal/cmd/auth/security_group/describe" + "github.com/stackitcloud/stackit-cli/internal/cmd/auth/security_group/list" + "github.com/stackitcloud/stackit-cli/internal/cmd/auth/security_group/update" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/spf13/cobra" +) + +func NewCmd(p *print.Printer) *cobra.Command { + cmd := &cobra.Command{ + Use: "security-group", + Short: "manage security groups.", + Long: "manage the lifecycle of security groups.", + Args: args.NoArgs, + Run: utils.CmdHelp, + } + addSubcommands(cmd, p) + return cmd +} + +func addSubcommands(cmd *cobra.Command, p *print.Printer) { + cmd.AddCommand( + create.NewCmd(p), + delete.NewCmd(p), + describe.NewCmd(p), + list.NewCmd(p), + update.NewCmd(p), + ) +} diff --git a/internal/cmd/auth/security_group/update/update.go b/internal/cmd/auth/security_group/update/update.go new file mode 100644 index 000000000..2e426e510 --- /dev/null +++ b/internal/cmd/auth/security_group/update/update.go @@ -0,0 +1,31 @@ +package update + +import ( + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" +) + +func NewCmd(p *print.Printer) *cobra.Command { + cmd := &cobra.Command{ + Use: "update", + Short: "update security groups", + Long: "update security groups", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample(`example 1`, `foo bar baz`), + examples.NewExample(`example 2`, `foo bar baz`), + ), + RunE: func(cmd *cobra.Command, args []string) error { + return executeUpdate(cmd, p, args) + }, + } + cmd.Flags().String("dummy", "foo", "fooify") + return cmd +} + +func executeUpdate(cmd *cobra.Command, p *print.Printer, args []string) error { + p.Info("executing update command") + return nil +} diff --git a/internal/cmd/beta/beta.go b/internal/cmd/beta/beta.go index 0ec43d0f2..a997c6352 100644 --- a/internal/cmd/beta/beta.go +++ b/internal/cmd/beta/beta.go @@ -3,6 +3,7 @@ package beta import ( "fmt" + "github.com/stackitcloud/stackit-cli/internal/cmd/auth/security_group" keypair "github.com/stackitcloud/stackit-cli/internal/cmd/beta/key-pair" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network" networkArea "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-area" @@ -50,6 +51,7 @@ func addSubcommands(cmd *cobra.Command, p *print.Printer) { cmd.AddCommand(volume.NewCmd(p)) cmd.AddCommand(networkinterface.NewCmd(p)) cmd.AddCommand(publicip.NewCmd(p)) + cmd.AddCommand(security_group.NewCmd(p)) cmd.AddCommand(securitygroup.NewCmd(p)) cmd.AddCommand(keypair.NewCmd(p)) } From 4dceff53d5c012c7ecaac2ea4f90102a7f2695c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Schmitz?= <152157960+bahkauv70@users.noreply.github.com> Date: Thu, 5 Dec 2024 16:40:49 +0100 Subject: [PATCH 02/28] feature: implement create command --- .../cmd/auth/security_group/create/create.go | 131 +++++++- .../auth/security_group/create/create_test.go | 281 ++++++++++++++++++ 2 files changed, 408 insertions(+), 4 deletions(-) create mode 100644 internal/cmd/auth/security_group/create/create_test.go diff --git a/internal/cmd/auth/security_group/create/create.go b/internal/cmd/auth/security_group/create/create.go index b42801d92..d7b0e81ca 100644 --- a/internal/cmd/auth/security_group/create/create.go +++ b/internal/cmd/auth/security_group/create/create.go @@ -1,12 +1,30 @@ package create import ( + "context" + "fmt" + "strings" + "github.com/spf13/cobra" "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/iaas/client" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) +type inputModel struct { + *globalflags.GlobalFlagModel + Labels map[string]any + Description string + Name string + Stateful bool +} + func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: "create", @@ -14,18 +32,123 @@ func NewCmd(p *print.Printer) *cobra.Command { Long: "create security groups", Args: args.NoArgs, Example: examples.Build( - examples.NewExample(`example 1`, `foo bar baz`), - examples.NewExample(`example 2`, `foo bar baz`), + examples.NewExample(`create a named group`, `$ stackit beta security-group create --name my-new-group`), + examples.NewExample(`create a named group with labels`, `$ stackit beta security-group create --name my-new-group --labels label1=value1,label2=value2`), ), RunE: func(cmd *cobra.Command, args []string) error { return executeCreate(cmd, p, args) }, } - cmd.Flags().String("dummy", "foo", "fooify") + + configureFlags(cmd) return cmd } -func executeCreate(cmd *cobra.Command, p *print.Printer, args []string) error { +func configureFlags(cmd *cobra.Command) { + cmd.Flags().String("name", "", "the name of the security group") + cmd.Flags().String("description", "", "an optional description of the security group. Must be <= 127 chars") + cmd.Flags().Bool("stateful", false, "create a stateful or a stateless security group") + cmd.Flags().StringSlice("labels", nil, "a list of labels in the form =") + + if err := flags.MarkFlagsRequired(cmd, "name"); err != nil { + cobra.CheckErr(err) + } +} + +func executeCreate(cmd *cobra.Command, p *print.Printer, _ []string) error { p.Info("executing create command") + ctx := context.Background() + model, err := parseInput(p, cmd) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(p) + if err != nil { + return err + } + + projectLabel, err := projectname.GetProjectName(ctx, p, cmd) + if err != nil { + p.Debug(print.ErrorLevel, "get project name: %v", err) + projectLabel = model.ProjectId + } + + // Call API + req := buildRequest(ctx, model, apiClient) + _, err = req.Execute() + if err != nil { + return fmt.Errorf("create security group: %w", err) + } + + operationState := "Enabled" + if model.Async { + operationState = "Triggered enablement of" + } + p.Info("%s security group %q for %q\n", operationState, model.Name, projectLabel) return nil } + +func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + + labels := make(map[string]any) + for _, label := range flags.FlagToStringSliceValue(p, cmd, "labels") { + parts := strings.Split(label, "=") + if len(parts) != 2 { + return nil, &errors.ArgValidationError{ + Arg: "labels", + Details: "invalid label declaration. Must be in the form =", + } + } + labels[parts[0]] = parts[1] + + } + description := flags.FlagToStringValue(p, cmd, "description") + if len(description) >= 128 { + return nil, &errors.ArgValidationError{ + Arg: "invalid description", + Details: "description exceeds 127 characters in length", + } + } + model := inputModel{ + GlobalFlagModel: globalFlags, + Name: flags.FlagToStringValue(p, cmd, "name"), + + Labels: labels, + Description: description, + Stateful: flags.FlagToBoolValue(p, cmd, "stateful"), + } + + 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 *iaas.APIClient) iaas.ApiCreateSecurityGroupRequest { + request := apiClient.CreateSecurityGroup(ctx, model.ProjectId) + payload := iaas.NewCreateSecurityGroupPayload(&model.Name) + payload.Description = &model.Description + if model.Labels != nil { + // this check assure that we don't end up with a pointer to nil + // which is a thing in go! + payload.Labels = &model.Labels + } + payload.Name = &model.Name + payload.Stateful = &model.Stateful + request = request.CreateSecurityGroupPayload(*payload) + + return request + +} diff --git a/internal/cmd/auth/security_group/create/create_test.go b/internal/cmd/auth/security_group/create/create_test.go new file mode 100644 index 000000000..52e61809f --- /dev/null +++ b/internal/cmd/auth/security_group/create/create_test.go @@ -0,0 +1,281 @@ +package create + +import ( + "context" + "strings" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" +) + +var projectIdFlag = globalflags.ProjectIdFlag + +type testCtxKey struct{} + +var ( + testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + testClient = &iaas.APIClient{} + testProjectId = uuid.NewString() + testName = "new-security-group" + testDescription = "a test description" + testLabels = map[string]any{ + "fooKey": "fooValue", + "barKey": "barValue", + "bazKey": "bazValue", + } + testStateful = true +) + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + projectIdFlag: testProjectId, + "description": testDescription, + "labels": "fooKey=fooValue,barKey=barValue,bazKey=bazValue", + "stateful": "true", + "name": testName, + } + 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}, + Labels: testLabels, + Description: testDescription, + Name: testName, + Stateful: testStateful, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *iaas.ApiCreateSecurityGroupRequest)) iaas.ApiCreateSecurityGroupRequest { + request := testClient.CreateSecurityGroup(testCtx, testProjectId) + request = request.CreateSecurityGroupPayload(iaas.CreateSecurityGroupPayload{ + Description: utils.Ptr(testDescription), + Labels: &testLabels, + Name: utils.Ptr(testName), + Rules: nil, + Stateful: utils.Ptr(testStateful), + }) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description 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: "project id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, projectIdFlag) + }), + isValid: false, + }, + { + description: "project id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "" + }), + isValid: false, + }, + { + description: "project id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "name missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, "name") + }), + isValid: false, + }, + { + description: "description too long", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues["description"] = strings.Repeat("toolong", 1000) + }), + isValid: false, + }, + { + description: "no labels", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, "labels") + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Labels = map[string]any{} + }), + }, + { + description: "single label", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues["labels"] = "foo=bar" + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Labels = map[string]any{ + "foo": "bar", + } + }), + }, + { + description: "malformed labels 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues["labels"] = "foo=bar=baz" + }), + isValid: false, + }, + { + description: "malformed labels 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues["labels"] = "foobarbaz" + }), + isValid: false, + }, + { + description: "stateless security group", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues["stateful"] = "false" + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Stateful = false + }), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + cmd := &cobra.Command{} + configureFlags(cmd) + 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.ValidateRequiredFlags() + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error validating flags: %v", err) + } + + p := print.NewPrinter() + model, err := parseInput(p, cmd) + 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 iaas.ApiCreateSecurityGroupRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + { + description: "no labels", + model: fixtureInputModel(func(model *inputModel) { + model.Labels = nil + }), + expectedRequest: fixtureRequest(func(request *iaas.ApiCreateSecurityGroupRequest) { + *request = request.CreateSecurityGroupPayload(iaas.CreateSecurityGroupPayload{ + Description: &testDescription, + Labels: nil, + Name: &testName, + Stateful: &testStateful, + }) + }), + }, + { + description: "stateless security group", + model: fixtureInputModel(func(model *inputModel) { + model.Stateful = false + }), + expectedRequest: fixtureRequest(func(request *iaas.ApiCreateSecurityGroupRequest) { + *request = request.CreateSecurityGroupPayload(iaas.CreateSecurityGroupPayload{ + Description: &testDescription, + Labels: &testLabels, + Name: &testName, + Stateful: utils.Ptr(false), + }) + }), + }, + } + + 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) + } + }) + } +} From 60653ff848fd805cca3dd3b8e52c2362f33159c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Schmitz?= <152157960+bahkauv70@users.noreply.github.com> Date: Thu, 5 Dec 2024 17:05:10 +0100 Subject: [PATCH 03/28] feature: add list command --- internal/cmd/auth/security_group/list/list.go | 89 +++++++- .../cmd/auth/security_group/list/list_test.go | 211 ++++++++++++++++++ 2 files changed, 296 insertions(+), 4 deletions(-) create mode 100644 internal/cmd/auth/security_group/list/list_test.go diff --git a/internal/cmd/auth/security_group/list/list.go b/internal/cmd/auth/security_group/list/list.go index 08ba47c1c..71ce1ff15 100644 --- a/internal/cmd/auth/security_group/list/list.go +++ b/internal/cmd/auth/security_group/list/list.go @@ -1,12 +1,26 @@ package list import ( + "context" + "fmt" + "github.com/spf13/cobra" "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/iaas/client" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) +type inputModel struct { + *globalflags.GlobalFlagModel + Labels string +} + func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: "list", @@ -14,18 +28,85 @@ func NewCmd(p *print.Printer) *cobra.Command { Long: "list security groups", Args: args.NoArgs, Example: examples.Build( - examples.NewExample(`example 1`, `foo bar baz`), - examples.NewExample(`example 2`, `foo bar baz`), + examples.NewExample(`list all groups`, `$ stackit beta security-group list`), + examples.NewExample(`list groups with labels`, `$ stackit beta security-group list --labels label1=value1,label2=value2`), ), RunE: func(cmd *cobra.Command, args []string) error { return executeList(cmd, p, args) }, } - cmd.Flags().String("dummy", "foo", "fooify") + + configureFlags(cmd) return cmd } -func executeList(cmd *cobra.Command, p *print.Printer, args []string) error { +func configureFlags(cmd *cobra.Command) { + cmd.Flags().String("labels", "", "a list of labels in the form =") +} + +func executeList(cmd *cobra.Command, p *print.Printer, _ []string) error { p.Info("executing list command") + ctx := context.Background() + model, err := parseInput(p, cmd) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(p) + if err != nil { + return err + } + + projectLabel, err := projectname.GetProjectName(ctx, p, cmd) + if err != nil { + p.Debug(print.ErrorLevel, "get project name: %v", err) + projectLabel = model.ProjectId + } + + // Call API + req := buildRequest(ctx, model, apiClient) + _, err = req.Execute() + if err != nil { + return fmt.Errorf("list security group: %w", err) + } + + operationState := "Enabled" + if model.Async { + operationState = "Triggered enablement of" + } + p.Info("%s security group for %q\n", operationState, projectLabel) return nil } + +func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + + Labels: flags.FlagToStringValue(p, cmd, "labels"), + } + + 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 *iaas.APIClient) iaas.ApiListSecurityGroupsRequest { + request := apiClient.ListSecurityGroups(ctx, model.ProjectId) + request = request.LabelSelector(model.Labels) + + return request + +} diff --git a/internal/cmd/auth/security_group/list/list_test.go b/internal/cmd/auth/security_group/list/list_test.go new file mode 100644 index 000000000..5cc8c70e7 --- /dev/null +++ b/internal/cmd/auth/security_group/list/list_test.go @@ -0,0 +1,211 @@ +package list + +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/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" +) + +var projectIdFlag = globalflags.ProjectIdFlag + +type testCtxKey struct{} + +var ( + testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + testClient = &iaas.APIClient{} + testProjectId = uuid.NewString() + testLabels = "fooKey=fooValue,barKey=barValue,bazKey=bazValue" + testStateful = true +) + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + projectIdFlag: testProjectId, + "labels": 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}, + Labels: testLabels, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *iaas.ApiListSecurityGroupsRequest)) iaas.ApiListSecurityGroupsRequest { + request := testClient.ListSecurityGroups(testCtx, testProjectId) + request = request.LabelSelector(testLabels) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description 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: "project id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, projectIdFlag) + }), + isValid: false, + }, + { + description: "project id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "" + }), + isValid: false, + }, + { + description: "project id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "no labels", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, "labels") + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Labels = "" + }), + }, + { + description: "single label", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues["labels"] = "foo=bar" + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Labels = "foo=bar" + }), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + cmd := &cobra.Command{} + configureFlags(cmd) + 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.ValidateRequiredFlags() + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error validating flags: %v", err) + } + + p := print.NewPrinter() + model, err := parseInput(p, cmd) + 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 iaas.ApiListSecurityGroupsRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + { + description: "no labels", + model: fixtureInputModel(func(model *inputModel) { + model.Labels = "" + }), + expectedRequest: fixtureRequest(func(request *iaas.ApiListSecurityGroupsRequest) { + *request = request.LabelSelector("") + }), + }, + { + description: "single label", + model: fixtureInputModel(func(model *inputModel) { + model.Labels = "foo=bar" + }), + expectedRequest: fixtureRequest(func(request *iaas.ApiListSecurityGroupsRequest) { + *request = request.LabelSelector("foo=bar") + }), + }, + } + + 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) + } + }) + } +} From 784c4b05248488e5b44e6bf9a8077ea0ea64f290 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Schmitz?= <152157960+bahkauv70@users.noreply.github.com> Date: Thu, 5 Dec 2024 17:10:25 +0100 Subject: [PATCH 04/28] fixup: check name length --- internal/cmd/auth/security_group/create/create.go | 11 +++++++++-- .../cmd/auth/security_group/create/create_test.go | 7 +++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/internal/cmd/auth/security_group/create/create.go b/internal/cmd/auth/security_group/create/create.go index d7b0e81ca..6e601ea15 100644 --- a/internal/cmd/auth/security_group/create/create.go +++ b/internal/cmd/auth/security_group/create/create.go @@ -45,7 +45,7 @@ func NewCmd(p *print.Printer) *cobra.Command { } func configureFlags(cmd *cobra.Command) { - cmd.Flags().String("name", "", "the name of the security group") + cmd.Flags().String("name", "", "the name of the security group. Must be <= 63 chars") cmd.Flags().String("description", "", "an optional description of the security group. Must be <= 127 chars") cmd.Flags().Bool("stateful", false, "create a stateful or a stateless security group") cmd.Flags().StringSlice("labels", nil, "a list of labels in the form =") @@ -95,6 +95,13 @@ func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { if globalFlags.ProjectId == "" { return nil, &errors.ProjectIdError{} } + name := flags.FlagToStringValue(p, cmd, "name") + if len(name) >= 64 { + return nil, &errors.ArgValidationError{ + Arg: "invalid name", + Details: "name exceeds 63 characters in length", + } + } labels := make(map[string]any) for _, label := range flags.FlagToStringSliceValue(p, cmd, "labels") { @@ -117,7 +124,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { } model := inputModel{ GlobalFlagModel: globalFlags, - Name: flags.FlagToStringValue(p, cmd, "name"), + Name: name, Labels: labels, Description: description, diff --git a/internal/cmd/auth/security_group/create/create_test.go b/internal/cmd/auth/security_group/create/create_test.go index 52e61809f..b0778cd30 100644 --- a/internal/cmd/auth/security_group/create/create_test.go +++ b/internal/cmd/auth/security_group/create/create_test.go @@ -123,6 +123,13 @@ func TestParseInput(t *testing.T) { }), isValid: false, }, + { + description: "name too long", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues["name"] = strings.Repeat("toolong", 1000) + }), + isValid: false, + }, { description: "description too long", flagValues: fixtureFlagValues(func(flagValues map[string]string) { From 9597756bf930857c3d113627bc988f7239f81612 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Schmitz?= <152157960+bahkauv70@users.noreply.github.com> Date: Thu, 5 Dec 2024 17:33:07 +0100 Subject: [PATCH 05/28] fixup: added placeholder for client invocation --- internal/cmd/auth/security_group/create/create.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/cmd/auth/security_group/create/create.go b/internal/cmd/auth/security_group/create/create.go index 6e601ea15..aeb4eab2d 100644 --- a/internal/cmd/auth/security_group/create/create.go +++ b/internal/cmd/auth/security_group/create/create.go @@ -87,6 +87,9 @@ func executeCreate(cmd *cobra.Command, p *print.Printer, _ []string) error { operationState = "Triggered enablement of" } p.Info("%s security group %q for %q\n", operationState, model.Name, projectLabel) + + panic("todo: invocation not implemented!") + return nil } From 8add98e058178b20841e8de621cf59885618212a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Schmitz?= <152157960+bahkauv70@users.noreply.github.com> Date: Thu, 5 Dec 2024 17:33:16 +0100 Subject: [PATCH 06/28] feature: implement calling service and output of results --- .../cmd/auth/security_group/create/create.go | 57 +++- internal/cmd/auth/security_group/list/list.go | 2 + .../cmd/auth/security_group/update/update.go | 146 ++++++++- .../auth/security_group/update/update_test.go | 288 ++++++++++++++++++ 4 files changed, 475 insertions(+), 18 deletions(-) create mode 100644 internal/cmd/auth/security_group/update/update_test.go diff --git a/internal/cmd/auth/security_group/create/create.go b/internal/cmd/auth/security_group/create/create.go index aeb4eab2d..0e1c76e94 100644 --- a/internal/cmd/auth/security_group/create/create.go +++ b/internal/cmd/auth/security_group/create/create.go @@ -2,9 +2,11 @@ package create import ( "context" + "encoding/json" "fmt" "strings" + "github.com/goccy/go-yaml" "github.com/spf13/cobra" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" @@ -12,7 +14,6 @@ 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/projectname" "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) @@ -69,26 +70,34 @@ func executeCreate(cmd *cobra.Command, p *print.Printer, _ []string) error { return err } - projectLabel, err := projectname.GetProjectName(ctx, p, cmd) - if err != nil { - p.Debug(print.ErrorLevel, "get project name: %v", err) - projectLabel = model.ProjectId + if !model.AssumeYes { + prompt := fmt.Sprintf("Are you sure you want to create the security group %q?", model.Name) + err = p.PromptForConfirmation(prompt) + if err != nil { + return err + } } // Call API - req := buildRequest(ctx, model, apiClient) - _, err = req.Execute() + request := buildRequest(ctx, model, apiClient) + _, err = request.Execute() if err != nil { return fmt.Errorf("create security group: %w", err) } operationState := "Enabled" if model.Async { - operationState = "Triggered enablement of" + operationState = "Triggered label creation" } - p.Info("%s security group %q for %q\n", operationState, model.Name, projectLabel) + p.Info("%s security group %q for %q\n", operationState, model.Name, model.ProjectId) - panic("todo: invocation not implemented!") + group, err := request.Execute() + if err != nil { + return fmt.Errorf("create security group: %w", err) + } + if err:=outputResult(p, model, group);err != nil { + return err + } return nil } @@ -162,3 +171,31 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APICli return request } + +func outputResult(p *print.Printer, model *inputModel, resp *iaas.SecurityGroup) error { + switch model.OutputFormat { + case print.JSONOutputFormat: + details, err := json.MarshalIndent(resp, "", " ") + if err != nil { + return fmt.Errorf("marshal security group: %w", err) + } + p.Outputln(string(details)) + + return nil + case print.YAMLOutputFormat: + details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true)) + if err != nil { + return fmt.Errorf("marshal security group: %w", err) + } + p.Outputln(string(details)) + + return nil + default: + operationState := "Created" + if model.Async { + operationState = "Triggered creation of" + } + p.Outputf("%s security group %q\n", operationState, model.Name) + return nil + } +} diff --git a/internal/cmd/auth/security_group/list/list.go b/internal/cmd/auth/security_group/list/list.go index 71ce1ff15..440232e39 100644 --- a/internal/cmd/auth/security_group/list/list.go +++ b/internal/cmd/auth/security_group/list/list.go @@ -76,6 +76,8 @@ func executeList(cmd *cobra.Command, p *print.Printer, _ []string) error { operationState = "Triggered enablement of" } p.Info("%s security group for %q\n", operationState, projectLabel) + + panic("todo: implement client invocation and output") return nil } diff --git a/internal/cmd/auth/security_group/update/update.go b/internal/cmd/auth/security_group/update/update.go index 2e426e510..90b4acb7a 100644 --- a/internal/cmd/auth/security_group/update/update.go +++ b/internal/cmd/auth/security_group/update/update.go @@ -1,31 +1,161 @@ package update import ( + "context" + "fmt" + "strings" + "github.com/spf13/cobra" "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/iaas/client" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) +type inputModel struct { + *globalflags.GlobalFlagModel + Labels map[string]any + Description string + Name string + Stateful bool + SecurityGroupId string +} + func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ - Use: "update", - Short: "update security groups", - Long: "update security groups", + Use: "Update", + Short: "Update a security group", + Long: "Update a security group", Args: args.NoArgs, Example: examples.Build( - examples.NewExample(`example 1`, `foo bar baz`), - examples.NewExample(`example 2`, `foo bar baz`), + examples.NewExample(`Update a named group`, `$ stackit beta security-group Update --name my-new-group`), + examples.NewExample(`Update a named group with labels`, `$ stackit beta security-group Update --name my-new-group --labels label1=value1,label2=value2`), ), RunE: func(cmd *cobra.Command, args []string) error { return executeUpdate(cmd, p, args) }, } - cmd.Flags().String("dummy", "foo", "fooify") + + configureFlags(cmd) return cmd } -func executeUpdate(cmd *cobra.Command, p *print.Printer, args []string) error { - p.Info("executing update command") +func configureFlags(cmd *cobra.Command) { + cmd.Flags().String("name", "", "the name of the security group. Must be <= 63 chars") + cmd.Flags().String("description", "", "an optional description of the security group. Must be <= 127 chars") + cmd.Flags().Bool("stateful", false, "Update a stateful or a stateless security group") + cmd.Flags().StringSlice("labels", nil, "a list of labels in the form =") + + if err := flags.MarkFlagsRequired(cmd, "name"); err != nil { + cobra.CheckErr(err) + } +} + +func executeUpdate(cmd *cobra.Command, p *print.Printer, _ []string) error { + p.Info("executing Update command") + ctx := context.Background() + model, err := parseInput(p, cmd) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(p) + if err != nil { + return err + } + + projectLabel, err := projectname.GetProjectName(ctx, p, cmd) + if err != nil { + p.Debug(print.ErrorLevel, "get project name: %v", err) + projectLabel = model.ProjectId + } + + // Call API + req := buildRequest(ctx, model, apiClient) + _, err = req.Execute() + if err != nil { + return fmt.Errorf("Update security group: %w", err) + } + + operationState := "Enabled" + if model.Async { + operationState = "Triggered enablement of" + } + p.Info("%s security group %q for %q\n", operationState, model.Name, projectLabel) return nil } + +func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + name := flags.FlagToStringValue(p, cmd, "name") + if len(name) >= 64 { + return nil, &errors.ArgValidationError{ + Arg: "invalid name", + Details: "name exceeds 63 characters in length", + } + } + + labels := make(map[string]any) + for _, label := range flags.FlagToStringSliceValue(p, cmd, "labels") { + parts := strings.Split(label, "=") + if len(parts) != 2 { + return nil, &errors.ArgValidationError{ + Arg: "labels", + Details: "invalid label declaration. Must be in the form =", + } + } + labels[parts[0]] = parts[1] + + } + description := flags.FlagToStringValue(p, cmd, "description") + if len(description) >= 128 { + return nil, &errors.ArgValidationError{ + Arg: "invalid description", + Details: "description exceeds 127 characters in length", + } + } + model := inputModel{ + GlobalFlagModel: globalFlags, + Name: name, + + Labels: labels, + Description: description, + Stateful: flags.FlagToBoolValue(p, cmd, "stateful"), + } + + 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 *iaas.APIClient) iaas.ApiUpdateSecurityGroupRequest { + request := apiClient.UpdateSecurityGroup(ctx, model.ProjectId, model.SecurityGroupId) + payload := iaas.NewUpdateSecurityGroupPayload() + payload.Description = &model.Description + if model.Labels != nil { + // this check assure that we don't end up with a pointer to nil + // which is a thing in go! + payload.Labels = &model.Labels + } + payload.Name = &model.Name + request = request.UpdateSecurityGroupPayload(*payload) + + return request + +} diff --git a/internal/cmd/auth/security_group/update/update_test.go b/internal/cmd/auth/security_group/update/update_test.go new file mode 100644 index 000000000..935d81f11 --- /dev/null +++ b/internal/cmd/auth/security_group/update/update_test.go @@ -0,0 +1,288 @@ +package update + +import ( + "context" + "strings" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" +) + +var projectIdFlag = globalflags.ProjectIdFlag + +type testCtxKey struct{} + +var ( + testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + testClient = &iaas.APIClient{} + testProjectId = uuid.NewString() + testName = "new-security-group" + testDescription = "a test description" + testLabels = map[string]any{ + "fooKey": "fooValue", + "barKey": "barValue", + "bazKey": "bazValue", + } + testStateful = true +) + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + projectIdFlag: testProjectId, + "description": testDescription, + "labels": "fooKey=fooValue,barKey=barValue,bazKey=bazValue", + "stateful": "true", + "name": testName, + } + 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}, + Labels: testLabels, + Description: testDescription, + Name: testName, + Stateful: testStateful, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *iaas.ApiCreateSecurityGroupRequest)) iaas.ApiCreateSecurityGroupRequest { + request := testClient.CreateSecurityGroup(testCtx, testProjectId) + request = request.CreateSecurityGroupPayload(iaas.CreateSecurityGroupPayload{ + Description: utils.Ptr(testDescription), + Labels: &testLabels, + Name: utils.Ptr(testName), + Rules: nil, + Stateful: utils.Ptr(testStateful), + }) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description 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: "project id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, projectIdFlag) + }), + isValid: false, + }, + { + description: "project id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "" + }), + isValid: false, + }, + { + description: "project id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "name missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, "name") + }), + isValid: false, + }, + { + description: "name too long", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues["name"] = strings.Repeat("toolong", 1000) + }), + isValid: false, + }, + { + description: "description too long", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues["description"] = strings.Repeat("toolong", 1000) + }), + isValid: false, + }, + { + description: "no labels", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, "labels") + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Labels = map[string]any{} + }), + }, + { + description: "single label", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues["labels"] = "foo=bar" + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Labels = map[string]any{ + "foo": "bar", + } + }), + }, + { + description: "malformed labels 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues["labels"] = "foo=bar=baz" + }), + isValid: false, + }, + { + description: "malformed labels 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues["labels"] = "foobarbaz" + }), + isValid: false, + }, + { + description: "stateless security group", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues["stateful"] = "false" + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Stateful = false + }), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + cmd := &cobra.Command{} + configureFlags(cmd) + 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.ValidateRequiredFlags() + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error validating flags: %v", err) + } + + p := print.NewPrinter() + model, err := parseInput(p, cmd) + 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 iaas.ApiCreateSecurityGroupRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + { + description: "no labels", + model: fixtureInputModel(func(model *inputModel) { + model.Labels = nil + }), + expectedRequest: fixtureRequest(func(request *iaas.ApiCreateSecurityGroupRequest) { + *request = request.CreateSecurityGroupPayload(iaas.CreateSecurityGroupPayload{ + Description: &testDescription, + Labels: nil, + Name: &testName, + Stateful: &testStateful, + }) + }), + }, + { + description: "stateless security group", + model: fixtureInputModel(func(model *inputModel) { + model.Stateful = false + }), + expectedRequest: fixtureRequest(func(request *iaas.ApiCreateSecurityGroupRequest) { + *request = request.CreateSecurityGroupPayload(iaas.CreateSecurityGroupPayload{ + Description: &testDescription, + Labels: &testLabels, + Name: &testName, + Stateful: utils.Ptr(false), + }) + }), + }, + } + + 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) + } + }) + } +} From 43d5c103fbc9f7a35c67f2b02138eb9f145dd2c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Schmitz?= <152157960+bahkauv70@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:04:19 +0100 Subject: [PATCH 07/28] feature: implement calling service and output of results for list --- internal/cmd/auth/security_group/list/list.go | 61 ++++++++++++++++--- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/internal/cmd/auth/security_group/list/list.go b/internal/cmd/auth/security_group/list/list.go index 440232e39..8c3d28e1e 100644 --- a/internal/cmd/auth/security_group/list/list.go +++ b/internal/cmd/auth/security_group/list/list.go @@ -2,8 +2,11 @@ package list import ( "context" + "encoding/json" "fmt" + "strings" + "github.com/goccy/go-yaml" "github.com/spf13/cobra" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" @@ -13,6 +16,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) @@ -65,19 +69,17 @@ func executeList(cmd *cobra.Command, p *print.Printer, _ []string) error { } // Call API - req := buildRequest(ctx, model, apiClient) - _, err = req.Execute() + request := buildRequest(ctx, model, apiClient) + response, err := request.Execute() if err != nil { return fmt.Errorf("list security group: %w", err) } - - operationState := "Enabled" - if model.Async { - operationState = "Triggered enablement of" + if items := response.GetItems(); items == nil || len(*items) > 0 { + p.Info("no security groups found for %q", projectLabel) + } else { + outputResult(p, model.OutputFormat, *items) } - p.Info("%s security group for %q\n", operationState, projectLabel) - panic("todo: implement client invocation and output") return nil } @@ -112,3 +114,46 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APICli return request } +func outputResult(p *print.Printer, outputFormat string, items []iaas.SecurityGroup) error { + switch outputFormat { + case print.JSONOutputFormat: + details, err := json.MarshalIndent(items, "", " ") + if err != nil { + return fmt.Errorf("marshal PostgreSQL Flex instance list: %w", err) + } + p.Outputln(string(details)) + + return nil + case print.YAMLOutputFormat: + details, err := yaml.MarshalWithOptions(items, yaml.IndentSequence(true)) + if err != nil { + return fmt.Errorf("marshal PostgreSQL Flex instance list: %w", err) + } + p.Outputln(string(details)) + + return nil + default: + table := tables.NewTable() + table.SetHeader("ID", "NAME", "LABELS", "STATEFUL") + for _, item := range items { + table.AddRow(item.Id, item.Name, concatLabels(item.Labels), item.Stateful) + } + err := table.Display(p) + if err != nil { + return fmt.Errorf("render table: %w", err) + } + + return nil + } +} + +func concatLabels(item *map[string]any) string { + if item == nil { + return "" + } + var builder strings.Builder + for k, v := range *item { + builder.WriteString(fmt.Sprintf("%s=%v ", k, v)) + } + return builder.String() +} From 64683e627b3b17b2d0a9b04aa8e0581ffee799e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Schmitz?= <152157960+bahkauv70@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:08:40 +0100 Subject: [PATCH 08/28] feature: move command to beta package --- internal/cmd/beta/beta.go | 2 +- .../cmd/{auth => beta}/security_group/create/create.go | 0 .../security_group/create/create_test.go | 0 .../cmd/{auth => beta}/security_group/delete/delete.go | 0 .../{auth => beta}/security_group/describe/describe.go | 0 .../cmd/{auth => beta}/security_group/list/list.go | 0 .../{auth => beta}/security_group/list/list_test.go | 0 .../{auth => beta}/security_group/security_group.go | 10 +++++----- .../cmd/{auth => beta}/security_group/update/update.go | 0 .../security_group/update/update_test.go | 0 10 files changed, 6 insertions(+), 6 deletions(-) rename internal/cmd/{auth => beta}/security_group/create/create.go (100%) rename internal/cmd/{auth => beta}/security_group/create/create_test.go (100%) rename internal/cmd/{auth => beta}/security_group/delete/delete.go (100%) rename internal/cmd/{auth => beta}/security_group/describe/describe.go (100%) rename internal/cmd/{auth => beta}/security_group/list/list.go (100%) rename internal/cmd/{auth => beta}/security_group/list/list_test.go (100%) rename internal/cmd/{auth => beta}/security_group/security_group.go (70%) rename internal/cmd/{auth => beta}/security_group/update/update.go (100%) rename internal/cmd/{auth => beta}/security_group/update/update_test.go (100%) diff --git a/internal/cmd/beta/beta.go b/internal/cmd/beta/beta.go index a997c6352..a509106a4 100644 --- a/internal/cmd/beta/beta.go +++ b/internal/cmd/beta/beta.go @@ -3,12 +3,12 @@ package beta import ( "fmt" - "github.com/stackitcloud/stackit-cli/internal/cmd/auth/security_group" keypair "github.com/stackitcloud/stackit-cli/internal/cmd/beta/key-pair" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network" networkArea "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-area" networkinterface "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-interface" publicip "github.com/stackitcloud/stackit-cli/internal/cmd/beta/public-ip" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security_group" securitygroup "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/server" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex" diff --git a/internal/cmd/auth/security_group/create/create.go b/internal/cmd/beta/security_group/create/create.go similarity index 100% rename from internal/cmd/auth/security_group/create/create.go rename to internal/cmd/beta/security_group/create/create.go diff --git a/internal/cmd/auth/security_group/create/create_test.go b/internal/cmd/beta/security_group/create/create_test.go similarity index 100% rename from internal/cmd/auth/security_group/create/create_test.go rename to internal/cmd/beta/security_group/create/create_test.go diff --git a/internal/cmd/auth/security_group/delete/delete.go b/internal/cmd/beta/security_group/delete/delete.go similarity index 100% rename from internal/cmd/auth/security_group/delete/delete.go rename to internal/cmd/beta/security_group/delete/delete.go diff --git a/internal/cmd/auth/security_group/describe/describe.go b/internal/cmd/beta/security_group/describe/describe.go similarity index 100% rename from internal/cmd/auth/security_group/describe/describe.go rename to internal/cmd/beta/security_group/describe/describe.go diff --git a/internal/cmd/auth/security_group/list/list.go b/internal/cmd/beta/security_group/list/list.go similarity index 100% rename from internal/cmd/auth/security_group/list/list.go rename to internal/cmd/beta/security_group/list/list.go diff --git a/internal/cmd/auth/security_group/list/list_test.go b/internal/cmd/beta/security_group/list/list_test.go similarity index 100% rename from internal/cmd/auth/security_group/list/list_test.go rename to internal/cmd/beta/security_group/list/list_test.go diff --git a/internal/cmd/auth/security_group/security_group.go b/internal/cmd/beta/security_group/security_group.go similarity index 70% rename from internal/cmd/auth/security_group/security_group.go rename to internal/cmd/beta/security_group/security_group.go index 15e19e1e3..757c204af 100644 --- a/internal/cmd/auth/security_group/security_group.go +++ b/internal/cmd/beta/security_group/security_group.go @@ -1,11 +1,11 @@ package security_group import ( - "github.com/stackitcloud/stackit-cli/internal/cmd/auth/security_group/create" - "github.com/stackitcloud/stackit-cli/internal/cmd/auth/security_group/delete" - "github.com/stackitcloud/stackit-cli/internal/cmd/auth/security_group/describe" - "github.com/stackitcloud/stackit-cli/internal/cmd/auth/security_group/list" - "github.com/stackitcloud/stackit-cli/internal/cmd/auth/security_group/update" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security_group/create" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security_group/delete" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security_group/describe" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security_group/list" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security_group/update" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/print" diff --git a/internal/cmd/auth/security_group/update/update.go b/internal/cmd/beta/security_group/update/update.go similarity index 100% rename from internal/cmd/auth/security_group/update/update.go rename to internal/cmd/beta/security_group/update/update.go diff --git a/internal/cmd/auth/security_group/update/update_test.go b/internal/cmd/beta/security_group/update/update_test.go similarity index 100% rename from internal/cmd/auth/security_group/update/update_test.go rename to internal/cmd/beta/security_group/update/update_test.go From deadda427da8de902f43e9dd795266483f1b4646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Schmitz?= <152157960+bahkauv70@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:43:43 +0100 Subject: [PATCH 09/28] feature: removed duplicate invocation of API --- internal/cmd/beta/security_group/create/create.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/internal/cmd/beta/security_group/create/create.go b/internal/cmd/beta/security_group/create/create.go index 0e1c76e94..79a036d0a 100644 --- a/internal/cmd/beta/security_group/create/create.go +++ b/internal/cmd/beta/security_group/create/create.go @@ -80,14 +80,10 @@ func executeCreate(cmd *cobra.Command, p *print.Printer, _ []string) error { // Call API request := buildRequest(ctx, model, apiClient) - _, err = request.Execute() - if err != nil { - return fmt.Errorf("create security group: %w", err) - } operationState := "Enabled" if model.Async { - operationState = "Triggered label creation" + operationState = "Triggered security group creation" } p.Info("%s security group %q for %q\n", operationState, model.Name, model.ProjectId) From 6340e35efbdaa1b41bc4bd8ede4ce82e510cc92a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Schmitz?= <152157960+bahkauv70@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:43:52 +0100 Subject: [PATCH 10/28] feature: completed delete command --- .../cmd/beta/security_group/delete/delete.go | 120 +++++++++++- .../beta/security_group/delete/delete_test.go | 172 ++++++++++++++++++ 2 files changed, 284 insertions(+), 8 deletions(-) create mode 100644 internal/cmd/beta/security_group/delete/delete_test.go diff --git a/internal/cmd/beta/security_group/delete/delete.go b/internal/cmd/beta/security_group/delete/delete.go index d660a7a95..695c7ea47 100644 --- a/internal/cmd/beta/security_group/delete/delete.go +++ b/internal/cmd/beta/security_group/delete/delete.go @@ -1,31 +1,135 @@ package delete import ( + "context" + "fmt" + "strings" + "github.com/spf13/cobra" - "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/iaas/client" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) +type inputModel struct { + *globalflags.GlobalFlagModel + Id string +} + func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: "delete", - Short: "delete security groups", - Long: "delete security groups", - Args: args.NoArgs, + Short: "delete a security group", + Long: "delete a security group by its internal id", + Args: cobra.ExactArgs(1), Example: examples.Build( - examples.NewExample(`example 1`, `foo bar baz`), - examples.NewExample(`example 2`, `foo bar baz`), + examples.NewExample(`delete a named group`, `$ stackit beta security-group delete 43ad419a-c68b-4911-87cd-e05752ac1e31`), ), RunE: func(cmd *cobra.Command, args []string) error { return executeDelete(cmd, p, args) }, } - cmd.Flags().String("dummy", "foo", "fooify") + return cmd } func executeDelete(cmd *cobra.Command, p *print.Printer, args []string) error { - p.Info("executing create command") + p.Info("executing delete command") + 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 delete the security group %q?", model.Id) + err = p.PromptForConfirmation(prompt) + if err != nil { + return err + } + } + + // Call API + request := buildRequest(ctx, model, apiClient) + + operationState := "Enabled" + if model.Async { + operationState = "Triggered security group deletion" + } + p.Info("%s security group %q for %q\n", operationState, model.Id, model.ProjectId) + + if err := request.Execute(); err != nil { + return fmt.Errorf("delete security group: %w", err) + } + return nil } + +func parseInput(p *print.Printer, cmd *cobra.Command, args []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + + if len(args) != 1 { + return nil,&errors.ArgValidationError{} + } + + name := flags.FlagToStringValue(p, cmd, "name") + if len(name) >= 64 { + return nil, &errors.ArgValidationError{ + Arg: "invalid name", + Details: "name exceeds 63 characters in length", + } + } + + labels := make(map[string]any) + for _, label := range flags.FlagToStringSliceValue(p, cmd, "labels") { + parts := strings.Split(label, "=") + if len(parts) != 2 { + return nil, &errors.ArgValidationError{ + Arg: "labels", + Details: "invalid label declaration. Must be in the form =", + } + } + labels[parts[0]] = parts[1] + + } + description := flags.FlagToStringValue(p, cmd, "description") + if len(description) >= 128 { + return nil, &errors.ArgValidationError{ + Arg: "invalid description", + Details: "description exceeds 127 characters in length", + } + } + model := inputModel{ + GlobalFlagModel: globalFlags, + Id: args[0], + } + + 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 *iaas.APIClient) iaas.ApiDeleteSecurityGroupRequest { + request := apiClient.DeleteSecurityGroup(ctx, model.ProjectId, model.Id) + return request +} diff --git a/internal/cmd/beta/security_group/delete/delete_test.go b/internal/cmd/beta/security_group/delete/delete_test.go new file mode 100644 index 000000000..e344aa562 --- /dev/null +++ b/internal/cmd/beta/security_group/delete/delete_test.go @@ -0,0 +1,172 @@ +package delete + +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/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" +) + +var projectIdFlag = globalflags.ProjectIdFlag + +type testCtxKey struct{} + +var ( + testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + testClient = &iaas.APIClient{} + testProjectId = uuid.NewString() + testGroupId = uuid.NewString() +) + +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}, + Id: testGroupId, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *iaas.ApiDeleteSecurityGroupRequest)) iaas.ApiDeleteSecurityGroupRequest { + request := testClient.DeleteSecurityGroup(testCtx, testProjectId, testGroupId) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + flagValues map[string]string + args []string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + args: []string{testGroupId}, + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "project id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "" + }), + isValid: false, + }, + { + description: "project id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "no arguments", + flagValues: fixtureFlagValues(), + args: nil, + isValid: false, + }, + { + description: "multiple arguments", + flagValues: fixtureFlagValues(), + args: []string{"foo","bar"}, + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + cmd := &cobra.Command{} + err := globalflags.Configure(cmd.Flags()) + if err != nil { + t.Fatalf("configure global flags: %v", err) + } + cmd.SetArgs(tt.args) + + 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.ValidateRequiredFlags() + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error validating flags: %v", err) + } + + p := print.NewPrinter() + model, err := parseInput(p, cmd, tt.args) + 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 iaas.ApiDeleteSecurityGroupRequest + }{ + { + 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) + } + }) + } +} From 643099b0ca0abfe5fc3951834879500437b6baf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Schmitz?= <152157960+bahkauv70@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:45:57 +0100 Subject: [PATCH 11/28] feature: renamed package to conform to standards --- internal/cmd/beta/beta.go | 2 +- .../create/create.go | 0 .../create/create_test.go | 0 .../delete/delete.go | 0 .../delete/delete_test.go | 0 .../describe/describe.go | 0 .../list/list.go | 0 .../list/list_test.go | 0 .../cmd/beta/security-group/security_group.go | 21 ++++++++--- .../update/update.go | 0 .../update/update_test.go | 0 .../cmd/beta/security_group/security_group.go | 37 ------------------- 12 files changed, 17 insertions(+), 43 deletions(-) rename internal/cmd/beta/{security_group => security-group}/create/create.go (100%) rename internal/cmd/beta/{security_group => security-group}/create/create_test.go (100%) rename internal/cmd/beta/{security_group => security-group}/delete/delete.go (100%) rename internal/cmd/beta/{security_group => security-group}/delete/delete_test.go (100%) rename internal/cmd/beta/{security_group => security-group}/describe/describe.go (100%) rename internal/cmd/beta/{security_group => security-group}/list/list.go (100%) rename internal/cmd/beta/{security_group => security-group}/list/list_test.go (100%) rename internal/cmd/beta/{security_group => security-group}/update/update.go (100%) rename internal/cmd/beta/{security_group => security-group}/update/update_test.go (100%) delete mode 100644 internal/cmd/beta/security_group/security_group.go diff --git a/internal/cmd/beta/beta.go b/internal/cmd/beta/beta.go index a509106a4..ebcdb3d0d 100644 --- a/internal/cmd/beta/beta.go +++ b/internal/cmd/beta/beta.go @@ -8,7 +8,7 @@ import ( networkArea "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-area" networkinterface "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-interface" publicip "github.com/stackitcloud/stackit-cli/internal/cmd/beta/public-ip" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security_group" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group" securitygroup "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/server" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex" diff --git a/internal/cmd/beta/security_group/create/create.go b/internal/cmd/beta/security-group/create/create.go similarity index 100% rename from internal/cmd/beta/security_group/create/create.go rename to internal/cmd/beta/security-group/create/create.go diff --git a/internal/cmd/beta/security_group/create/create_test.go b/internal/cmd/beta/security-group/create/create_test.go similarity index 100% rename from internal/cmd/beta/security_group/create/create_test.go rename to internal/cmd/beta/security-group/create/create_test.go diff --git a/internal/cmd/beta/security_group/delete/delete.go b/internal/cmd/beta/security-group/delete/delete.go similarity index 100% rename from internal/cmd/beta/security_group/delete/delete.go rename to internal/cmd/beta/security-group/delete/delete.go diff --git a/internal/cmd/beta/security_group/delete/delete_test.go b/internal/cmd/beta/security-group/delete/delete_test.go similarity index 100% rename from internal/cmd/beta/security_group/delete/delete_test.go rename to internal/cmd/beta/security-group/delete/delete_test.go diff --git a/internal/cmd/beta/security_group/describe/describe.go b/internal/cmd/beta/security-group/describe/describe.go similarity index 100% rename from internal/cmd/beta/security_group/describe/describe.go rename to internal/cmd/beta/security-group/describe/describe.go diff --git a/internal/cmd/beta/security_group/list/list.go b/internal/cmd/beta/security-group/list/list.go similarity index 100% rename from internal/cmd/beta/security_group/list/list.go rename to internal/cmd/beta/security-group/list/list.go diff --git a/internal/cmd/beta/security_group/list/list_test.go b/internal/cmd/beta/security-group/list/list_test.go similarity index 100% rename from internal/cmd/beta/security_group/list/list_test.go rename to internal/cmd/beta/security-group/list/list_test.go diff --git a/internal/cmd/beta/security-group/security_group.go b/internal/cmd/beta/security-group/security_group.go index 53f380d90..64b932090 100644 --- a/internal/cmd/beta/security-group/security_group.go +++ b/internal/cmd/beta/security-group/security_group.go @@ -1,9 +1,14 @@ -package securitygroup +package security_group import ( - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/rule" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/create" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/delete" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/describe" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/list" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/update" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/spf13/cobra" @@ -12,8 +17,8 @@ import ( func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: "security-group", - Short: "Provides functionality for security groups", - Long: "Provides functionality for security groups.", + Short: "manage security groups.", + Long: "manage the lifecycle of security groups.", Args: args.NoArgs, Run: utils.CmdHelp, } @@ -22,5 +27,11 @@ func NewCmd(p *print.Printer) *cobra.Command { } func addSubcommands(cmd *cobra.Command, p *print.Printer) { - cmd.AddCommand(rule.NewCmd(p)) + cmd.AddCommand( + create.NewCmd(p), + delete.NewCmd(p), + describe.NewCmd(p), + list.NewCmd(p), + update.NewCmd(p), + ) } diff --git a/internal/cmd/beta/security_group/update/update.go b/internal/cmd/beta/security-group/update/update.go similarity index 100% rename from internal/cmd/beta/security_group/update/update.go rename to internal/cmd/beta/security-group/update/update.go diff --git a/internal/cmd/beta/security_group/update/update_test.go b/internal/cmd/beta/security-group/update/update_test.go similarity index 100% rename from internal/cmd/beta/security_group/update/update_test.go rename to internal/cmd/beta/security-group/update/update_test.go diff --git a/internal/cmd/beta/security_group/security_group.go b/internal/cmd/beta/security_group/security_group.go deleted file mode 100644 index 757c204af..000000000 --- a/internal/cmd/beta/security_group/security_group.go +++ /dev/null @@ -1,37 +0,0 @@ -package security_group - -import ( - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security_group/create" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security_group/delete" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security_group/describe" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security_group/list" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security_group/update" - "github.com/stackitcloud/stackit-cli/internal/pkg/args" - "github.com/stackitcloud/stackit-cli/internal/pkg/print" - - "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - - "github.com/spf13/cobra" -) - -func NewCmd(p *print.Printer) *cobra.Command { - cmd := &cobra.Command{ - Use: "security-group", - Short: "manage security groups.", - Long: "manage the lifecycle of security groups.", - Args: args.NoArgs, - Run: utils.CmdHelp, - } - addSubcommands(cmd, p) - return cmd -} - -func addSubcommands(cmd *cobra.Command, p *print.Printer) { - cmd.AddCommand( - create.NewCmd(p), - delete.NewCmd(p), - describe.NewCmd(p), - list.NewCmd(p), - update.NewCmd(p), - ) -} From 41c4e1b95246bd981735d841bf01c660ae9e5337 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Schmitz?= <152157960+bahkauv70@users.noreply.github.com> Date: Fri, 6 Dec 2024 17:37:41 +0100 Subject: [PATCH 12/28] feature: refactored tests to reused actual cobra command --- .../beta/security-group/create/create_test.go | 19 +-- .../beta/security-group/delete/delete_test.go | 11 +- .../cmd/beta/security-group/list/list_test.go | 13 +- .../beta/security-group/update/update_test.go | 138 ++++++++++-------- 4 files changed, 94 insertions(+), 87 deletions(-) diff --git a/internal/cmd/beta/security-group/create/create_test.go b/internal/cmd/beta/security-group/create/create_test.go index b0778cd30..20d90cab8 100644 --- a/internal/cmd/beta/security-group/create/create_test.go +++ b/internal/cmd/beta/security-group/create/create_test.go @@ -12,7 +12,6 @@ import ( "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/iaas" ) @@ -65,11 +64,11 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { func fixtureRequest(mods ...func(request *iaas.ApiCreateSecurityGroupRequest)) iaas.ApiCreateSecurityGroupRequest { request := testClient.CreateSecurityGroup(testCtx, testProjectId) request = request.CreateSecurityGroupPayload(iaas.CreateSecurityGroupPayload{ - Description: utils.Ptr(testDescription), + Description: &testDescription, Labels: &testLabels, - Name: utils.Ptr(testName), + Name: &testName, Rules: nil, - Stateful: utils.Ptr(testStateful), + Stateful: &testStateful, }) for _, mod := range mods { mod(&request) @@ -187,12 +186,8 @@ func TestParseInput(t *testing.T) { for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - cmd := &cobra.Command{} - configureFlags(cmd) - err := globalflags.Configure(cmd.Flags()) - if err != nil { - t.Fatalf("configure global flags: %v", err) - } + p := print.NewPrinter() + cmd := NewCmd(p) for flag, value := range tt.flagValues { err := cmd.Flags().Set(flag, value) @@ -204,15 +199,13 @@ func TestParseInput(t *testing.T) { } } - err = cmd.ValidateRequiredFlags() - if err != nil { + if err := cmd.ValidateRequiredFlags(); err != nil { if !tt.isValid { return } t.Fatalf("error validating flags: %v", err) } - p := print.NewPrinter() model, err := parseInput(p, cmd) if err != nil { if !tt.isValid { diff --git a/internal/cmd/beta/security-group/delete/delete_test.go b/internal/cmd/beta/security-group/delete/delete_test.go index e344aa562..8f2d36b23 100644 --- a/internal/cmd/beta/security-group/delete/delete_test.go +++ b/internal/cmd/beta/security-group/delete/delete_test.go @@ -10,7 +10,6 @@ import ( "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/iaas" ) @@ -95,11 +94,18 @@ func TestParseInput(t *testing.T) { args: []string{"foo","bar"}, isValid: false, }, + { + description: "invalid group id", + flagValues: fixtureFlagValues(), + args: []string{"foo"}, + isValid: false, + }, } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - cmd := &cobra.Command{} + p := print.NewPrinter() + cmd:=NewCmd(p) err := globalflags.Configure(cmd.Flags()) if err != nil { t.Fatalf("configure global flags: %v", err) @@ -124,7 +130,6 @@ func TestParseInput(t *testing.T) { t.Fatalf("error validating flags: %v", err) } - p := print.NewPrinter() model, err := parseInput(p, cmd, tt.args) if err != nil { if !tt.isValid { diff --git a/internal/cmd/beta/security-group/list/list_test.go b/internal/cmd/beta/security-group/list/list_test.go index 5cc8c70e7..62145b44f 100644 --- a/internal/cmd/beta/security-group/list/list_test.go +++ b/internal/cmd/beta/security-group/list/list_test.go @@ -10,7 +10,6 @@ import ( "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/iaas" ) @@ -120,12 +119,8 @@ func TestParseInput(t *testing.T) { for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - cmd := &cobra.Command{} - configureFlags(cmd) - err := globalflags.Configure(cmd.Flags()) - if err != nil { - t.Fatalf("configure global flags: %v", err) - } + p := print.NewPrinter() + cmd := NewCmd(p) for flag, value := range tt.flagValues { err := cmd.Flags().Set(flag, value) @@ -137,15 +132,13 @@ func TestParseInput(t *testing.T) { } } - err = cmd.ValidateRequiredFlags() - if err != nil { + if err := cmd.ValidateRequiredFlags(); err != nil { if !tt.isValid { return } t.Fatalf("error validating flags: %v", err) } - p := print.NewPrinter() model, err := parseInput(p, cmd) if err != nil { if !tt.isValid { diff --git a/internal/cmd/beta/security-group/update/update_test.go b/internal/cmd/beta/security-group/update/update_test.go index 935d81f11..c59d6be19 100644 --- a/internal/cmd/beta/security-group/update/update_test.go +++ b/internal/cmd/beta/security-group/update/update_test.go @@ -7,12 +7,10 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - "github.com/spf13/cobra" "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) @@ -24,6 +22,7 @@ var ( testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") testClient = &iaas.APIClient{} testProjectId = uuid.NewString() + testGroupId = []string{uuid.NewString()} testName = "new-security-group" testDescription = "a test description" testLabels = map[string]any{ @@ -31,7 +30,6 @@ var ( "barKey": "barValue", "bazKey": "bazValue", } - testStateful = true ) func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { @@ -39,7 +37,6 @@ func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]st projectIdFlag: testProjectId, "description": testDescription, "labels": "fooKey=fooValue,barKey=barValue,bazKey=bazValue", - "stateful": "true", "name": testName, } for _, mod := range mods { @@ -51,10 +48,10 @@ func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]st func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { model := &inputModel{ GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectId, Verbosity: globalflags.VerbosityDefault}, - Labels: testLabels, - Description: testDescription, - Name: testName, - Stateful: testStateful, + Labels: &testLabels, + Description: &testDescription, + Name: &testName, + SecurityGroupId: testGroupId[0], } for _, mod := range mods { mod(model) @@ -62,14 +59,12 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { return model } -func fixtureRequest(mods ...func(request *iaas.ApiCreateSecurityGroupRequest)) iaas.ApiCreateSecurityGroupRequest { - request := testClient.CreateSecurityGroup(testCtx, testProjectId) - request = request.CreateSecurityGroupPayload(iaas.CreateSecurityGroupPayload{ - Description: utils.Ptr(testDescription), +func fixtureRequest(mods ...func(request *iaas.ApiUpdateSecurityGroupRequest)) iaas.ApiUpdateSecurityGroupRequest { + request := testClient.UpdateSecurityGroup(testCtx, testProjectId, testGroupId[0]) + request = request.UpdateSecurityGroupPayload(iaas.UpdateSecurityGroupPayload{ + Description: &testDescription, Labels: &testLabels, - Name: utils.Ptr(testName), - Rules: nil, - Stateful: utils.Ptr(testStateful), + Name: &testName, }) for _, mod := range mods { mod(&request) @@ -81,6 +76,7 @@ func TestParseInput(t *testing.T) { tests := []struct { description string flagValues map[string]string + args []string isValid bool expectedModel *inputModel }{ @@ -88,18 +84,28 @@ func TestParseInput(t *testing.T) { description: "base", flagValues: fixtureFlagValues(), isValid: true, + args: testGroupId, expectedModel: fixtureInputModel(), }, { - description: "no values", - flagValues: map[string]string{}, - isValid: false, + description: "no values but valid group id", + flagValues: map[string]string{ + projectIdFlag: testProjectId, + }, + args: testGroupId, + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Labels = nil + model.Name = nil + model.Description = nil + }), }, { description: "project id missing", flagValues: fixtureFlagValues(func(flagValues map[string]string) { delete(flagValues, projectIdFlag) }), + args: testGroupId, isValid: false, }, { @@ -107,6 +113,7 @@ func TestParseInput(t *testing.T) { flagValues: fixtureFlagValues(func(flagValues map[string]string) { flagValues[projectIdFlag] = "" }), + args: testGroupId, isValid: false, }, { @@ -114,20 +121,37 @@ func TestParseInput(t *testing.T) { flagValues: fixtureFlagValues(func(flagValues map[string]string) { flagValues[projectIdFlag] = "invalid-uuid" }), + args: testGroupId, isValid: false, }, { - description: "name missing", + description: "no name passed", flagValues: fixtureFlagValues(func(flagValues map[string]string) { delete(flagValues, "name") }), - isValid: false, + args: testGroupId, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Name = nil + }), + isValid: true, + }, + { + description: "no description passed", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, "description") + }), + args: testGroupId, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Description = nil + }), + isValid: true, }, { description: "name too long", flagValues: fixtureFlagValues(func(flagValues map[string]string) { flagValues["name"] = strings.Repeat("toolong", 1000) }), + args: testGroupId, isValid: false, }, { @@ -135,6 +159,7 @@ func TestParseInput(t *testing.T) { flagValues: fixtureFlagValues(func(flagValues map[string]string) { flagValues["description"] = strings.Repeat("toolong", 1000) }), + args: testGroupId, isValid: false, }, { @@ -142,19 +167,21 @@ func TestParseInput(t *testing.T) { flagValues: fixtureFlagValues(func(flagValues map[string]string) { delete(flagValues, "labels") }), - isValid: true, + args: testGroupId, expectedModel: fixtureInputModel(func(model *inputModel) { - model.Labels = map[string]any{} + model.Labels = nil }), + isValid: true, }, { description: "single label", flagValues: fixtureFlagValues(func(flagValues map[string]string) { flagValues["labels"] = "foo=bar" }), + args: testGroupId, isValid: true, expectedModel: fixtureInputModel(func(model *inputModel) { - model.Labels = map[string]any{ + model.Labels = &map[string]any{ "foo": "bar", } }), @@ -164,6 +191,7 @@ func TestParseInput(t *testing.T) { flagValues: fixtureFlagValues(func(flagValues map[string]string) { flagValues["labels"] = "foo=bar=baz" }), + args: testGroupId, isValid: false, }, { @@ -171,32 +199,37 @@ func TestParseInput(t *testing.T) { flagValues: fixtureFlagValues(func(flagValues map[string]string) { flagValues["labels"] = "foobarbaz" }), + args: testGroupId, isValid: false, }, { - description: "stateless security group", - flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues["stateful"] = "false" - }), - isValid: true, - expectedModel: fixtureInputModel(func(model *inputModel) { - model.Stateful = false - }), + description: "no group id passed", + flagValues: fixtureFlagValues(), + args: nil, + isValid: false, + }, + { + description: "invalid group id passed", + flagValues: fixtureFlagValues(), + args: []string{"foobar"}, + isValid: false, + }, + { + description: "multiple group ids passed", + flagValues: fixtureFlagValues(), + args: []string{uuid.NewString(), uuid.NewString()}, + isValid: false, }, } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - cmd := &cobra.Command{} - configureFlags(cmd) - err := globalflags.Configure(cmd.Flags()) - if err != nil { - t.Fatalf("configure global flags: %v", err) - } + p := print.NewPrinter() + cmd := NewCmd(p) for flag, value := range tt.flagValues { - err := cmd.Flags().Set(flag, value) - if err != nil { + + if err := cmd.Flags().Set(flag, value); err != nil { if !tt.isValid { return } @@ -204,16 +237,14 @@ func TestParseInput(t *testing.T) { } } - err = cmd.ValidateRequiredFlags() - if err != nil { + if err := cmd.ValidateRequiredFlags(); err != nil { if !tt.isValid { return } t.Fatalf("error validating flags: %v", err) } - p := print.NewPrinter() - model, err := parseInput(p, cmd) + model, err := parseInput(p, cmd, tt.args) if err != nil { if !tt.isValid { return @@ -236,7 +267,7 @@ func TestBuildRequest(t *testing.T) { tests := []struct { description string model *inputModel - expectedRequest iaas.ApiCreateSecurityGroupRequest + expectedRequest iaas.ApiUpdateSecurityGroupRequest }{ { description: "base", @@ -248,26 +279,11 @@ func TestBuildRequest(t *testing.T) { model: fixtureInputModel(func(model *inputModel) { model.Labels = nil }), - expectedRequest: fixtureRequest(func(request *iaas.ApiCreateSecurityGroupRequest) { - *request = request.CreateSecurityGroupPayload(iaas.CreateSecurityGroupPayload{ + expectedRequest: fixtureRequest(func(request *iaas.ApiUpdateSecurityGroupRequest) { + *request = request.UpdateSecurityGroupPayload(iaas.UpdateSecurityGroupPayload{ Description: &testDescription, Labels: nil, Name: &testName, - Stateful: &testStateful, - }) - }), - }, - { - description: "stateless security group", - model: fixtureInputModel(func(model *inputModel) { - model.Stateful = false - }), - expectedRequest: fixtureRequest(func(request *iaas.ApiCreateSecurityGroupRequest) { - *request = request.CreateSecurityGroupPayload(iaas.CreateSecurityGroupPayload{ - Description: &testDescription, - Labels: &testLabels, - Name: &testName, - Stateful: utils.Ptr(false), }) }), }, From 0d324824e0a4460123228d12f9141075c9aa2ac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Schmitz?= <152157960+bahkauv70@users.noreply.github.com> Date: Fri, 6 Dec 2024 17:38:04 +0100 Subject: [PATCH 13/28] feature: refactored flag initialization --- .../cmd/beta/security-group/create/create.go | 1 + .../cmd/beta/security-group/delete/delete.go | 17 +++++++++++++++-- internal/cmd/beta/security-group/list/list.go | 1 + 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/internal/cmd/beta/security-group/create/create.go b/internal/cmd/beta/security-group/create/create.go index 79a036d0a..f3a866a26 100644 --- a/internal/cmd/beta/security-group/create/create.go +++ b/internal/cmd/beta/security-group/create/create.go @@ -46,6 +46,7 @@ func NewCmd(p *print.Printer) *cobra.Command { } func configureFlags(cmd *cobra.Command) { + globalflags.Configure(cmd.Flags()) cmd.Flags().String("name", "", "the name of the security group. Must be <= 63 chars") cmd.Flags().String("description", "", "an optional description of the security group. Must be <= 127 chars") cmd.Flags().Bool("stateful", false, "create a stateful or a stateless security group") diff --git a/internal/cmd/beta/security-group/delete/delete.go b/internal/cmd/beta/security-group/delete/delete.go index 695c7ea47..5cdfd64e5 100644 --- a/internal/cmd/beta/security-group/delete/delete.go +++ b/internal/cmd/beta/security-group/delete/delete.go @@ -6,12 +6,14 @@ import ( "strings" "github.com/spf13/cobra" + "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/iaas/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) @@ -20,12 +22,14 @@ type inputModel struct { Id string } +const argNameGroupId = "groupId" + func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: "delete", Short: "delete a security group", Long: "delete a security group by its internal id", - Args: cobra.ExactArgs(1), + Args: args.SingleArg(argNameGroupId, utils.ValidateUUID), Example: examples.Build( examples.NewExample(`delete a named group`, `$ stackit beta security-group delete 43ad419a-c68b-4911-87cd-e05752ac1e31`), ), @@ -80,9 +84,18 @@ func parseInput(p *print.Printer, cmd *cobra.Command, args []string) (*inputMode if globalFlags.ProjectId == "" { return nil, &errors.ProjectIdError{} } + if err := cmd.ValidateArgs(args); err != nil { + return nil, &errors.ArgValidationError{ + Arg: argNameGroupId, + Details: fmt.Sprintf("arg validation failed: %v", err), + } + } if len(args) != 1 { - return nil,&errors.ArgValidationError{} + return nil, &errors.ArgValidationError{ + Arg: argNameGroupId, + Details: "wrong number of arguments", + } } name := flags.FlagToStringValue(p, cmd, "name") diff --git a/internal/cmd/beta/security-group/list/list.go b/internal/cmd/beta/security-group/list/list.go index 8c3d28e1e..e06e9066f 100644 --- a/internal/cmd/beta/security-group/list/list.go +++ b/internal/cmd/beta/security-group/list/list.go @@ -45,6 +45,7 @@ func NewCmd(p *print.Printer) *cobra.Command { } func configureFlags(cmd *cobra.Command) { + globalflags.Configure(cmd.Flags()) cmd.Flags().String("labels", "", "a list of labels in the form =") } From cde67b00e80711b8f5ece529bd8847183abb859f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Schmitz?= <152157960+bahkauv70@users.noreply.github.com> Date: Fri, 6 Dec 2024 17:38:22 +0100 Subject: [PATCH 14/28] feature: implemented update command --- .../cmd/beta/security-group/update/update.go | 113 ++++++++++-------- 1 file changed, 63 insertions(+), 50 deletions(-) diff --git a/internal/cmd/beta/security-group/update/update.go b/internal/cmd/beta/security-group/update/update.go index 90b4acb7a..25cd5a3ec 100644 --- a/internal/cmd/beta/security-group/update/update.go +++ b/internal/cmd/beta/security-group/update/update.go @@ -14,27 +14,29 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) type inputModel struct { *globalflags.GlobalFlagModel - Labels map[string]any - Description string - Name string - Stateful bool + Labels *map[string]any + Description *string + Name *string SecurityGroupId string } +const argNameGroupId = "argGroupId" + func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: "Update", Short: "Update a security group", - Long: "Update a security group", - Args: args.NoArgs, + Long: "Update a named security group", + Args: args.SingleArg(argNameGroupId, utils.ValidateUUID), Example: examples.Build( - examples.NewExample(`Update a named group`, `$ stackit beta security-group Update --name my-new-group`), - examples.NewExample(`Update a named group with labels`, `$ stackit beta security-group Update --name my-new-group --labels label1=value1,label2=value2`), + examples.NewExample(`Update the name of a group`, `$ stackit beta security-group update 541d122f-0a5f-4bb0-94b9-b1ccbd7ba776 --name my-new-name`), + examples.NewExample(`Update the labels of a group`, `$ stackit beta security-group update 541d122f-0a5f-4bb0-94b9-b1ccbd7ba776 --labels label1=value1,label2=value2`), ), RunE: func(cmd *cobra.Command, args []string) error { return executeUpdate(cmd, p, args) @@ -46,20 +48,16 @@ func NewCmd(p *print.Printer) *cobra.Command { } func configureFlags(cmd *cobra.Command) { + globalflags.Configure(cmd.Flags()) cmd.Flags().String("name", "", "the name of the security group. Must be <= 63 chars") cmd.Flags().String("description", "", "an optional description of the security group. Must be <= 127 chars") - cmd.Flags().Bool("stateful", false, "Update a stateful or a stateless security group") cmd.Flags().StringSlice("labels", nil, "a list of labels in the form =") - - if err := flags.MarkFlagsRequired(cmd, "name"); err != nil { - cobra.CheckErr(err) - } } -func executeUpdate(cmd *cobra.Command, p *print.Printer, _ []string) error { - p.Info("executing Update command") +func executeUpdate(cmd *cobra.Command, p *print.Printer, args []string) error { + p.Info("executing update command") ctx := context.Background() - model, err := parseInput(p, cmd) + model, err := parseInput(p, cmd, args) if err != nil { return err } @@ -80,56 +78,75 @@ func executeUpdate(cmd *cobra.Command, p *print.Printer, _ []string) error { req := buildRequest(ctx, model, apiClient) _, err = req.Execute() if err != nil { - return fmt.Errorf("Update security group: %w", err) + return fmt.Errorf("^date security group: %w", err) } operationState := "Enabled" if model.Async { operationState = "Triggered enablement of" } - p.Info("%s security group %q for %q\n", operationState, model.Name, projectLabel) + p.Info("%s security group \"%v\" for %q\n", operationState, model.Name, projectLabel) return nil } -func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { +func parseInput(p *print.Printer, cmd *cobra.Command, args []string) (*inputModel, error) { globalFlags := globalflags.Parse(p, cmd) if globalFlags.ProjectId == "" { return nil, &errors.ProjectIdError{} } - name := flags.FlagToStringValue(p, cmd, "name") - if len(name) >= 64 { + + if err := cmd.ValidateArgs(args); err != nil { + return nil, &errors.ArgValidationError{ + Arg: argNameGroupId, + Details: fmt.Sprintf("argument validation failed: %v", err), + } + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + } + if len(args) != 1 { return nil, &errors.ArgValidationError{ - Arg: "invalid name", - Details: "name exceeds 63 characters in length", + Arg: argNameGroupId, + Details: "wrong number of arguments", } } + model.SecurityGroupId = args[0] - labels := make(map[string]any) - for _, label := range flags.FlagToStringSliceValue(p, cmd, "labels") { - parts := strings.Split(label, "=") - if len(parts) != 2 { + if cmd.Flags().Lookup("name").Changed { + name := flags.FlagToStringValue(p, cmd, "name") + if len(name) >= 64 { return nil, &errors.ArgValidationError{ - Arg: "labels", - Details: "invalid label declaration. Must be in the form =", + Arg: "invalid name", + Details: "name exceeds 63 characters in length", } } - labels[parts[0]] = parts[1] - + model.Name = &name } - description := flags.FlagToStringValue(p, cmd, "description") - if len(description) >= 128 { - return nil, &errors.ArgValidationError{ - Arg: "invalid description", - Details: "description exceeds 127 characters in length", + + if cmd.Flags().Lookup("labels").Changed { + labels := make(map[string]any) + for _, label := range flags.FlagToStringSliceValue(p, cmd, "labels") { + parts := strings.Split(label, "=") + if len(parts) != 2 { + return nil, &errors.ArgValidationError{ + Arg: "labels", + Details: "invalid label declaration. Must be in the form =", + } + } + labels[parts[0]] = parts[1] } + model.Labels = &labels } - model := inputModel{ - GlobalFlagModel: globalFlags, - Name: name, - - Labels: labels, - Description: description, - Stateful: flags.FlagToBoolValue(p, cmd, "stateful"), + if cmd.Flags().Lookup("description").Changed { + description := flags.FlagToStringValue(p, cmd, "description") + if len(description) >= 128 { + return nil, &errors.ArgValidationError{ + Arg: "invalid description", + Details: "description exceeds 127 characters in length", + } + } + model.Description = &description } if p.IsVerbosityDebug() { @@ -147,13 +164,9 @@ func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiUpdateSecurityGroupRequest { request := apiClient.UpdateSecurityGroup(ctx, model.ProjectId, model.SecurityGroupId) payload := iaas.NewUpdateSecurityGroupPayload() - payload.Description = &model.Description - if model.Labels != nil { - // this check assure that we don't end up with a pointer to nil - // which is a thing in go! - payload.Labels = &model.Labels - } - payload.Name = &model.Name + payload.Description = model.Description + payload.Labels = model.Labels + payload.Name = model.Name request = request.UpdateSecurityGroupPayload(*payload) return request From 55b2f282a27560b837736b75a93d328ece358282 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Schmitz?= <152157960+bahkauv70@users.noreply.github.com> Date: Fri, 6 Dec 2024 18:05:00 +0100 Subject: [PATCH 15/28] feature: implemented describe command --- .../beta/security-group/describe/describe.go | 148 +++++++++++++- .../security-group/describe/describe_test.go | 186 ++++++++++++++++++ 2 files changed, 330 insertions(+), 4 deletions(-) create mode 100644 internal/cmd/beta/security-group/describe/describe_test.go diff --git a/internal/cmd/beta/security-group/describe/describe.go b/internal/cmd/beta/security-group/describe/describe.go index e4d85977a..84b854d47 100644 --- a/internal/cmd/beta/security-group/describe/describe.go +++ b/internal/cmd/beta/security-group/describe/describe.go @@ -1,31 +1,171 @@ package describe import ( + "context" + "encoding/json" + "fmt" + "strings" + + "github.com/goccy/go-yaml" "github.com/spf13/cobra" "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/iaas/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) +type inputModel struct { + *globalflags.GlobalFlagModel + SecurityGroupId string +} + +const argNameGroupId = "argGroupId" + func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: "describe", Short: "describe security groups", Long: "describe security groups", - Args: args.NoArgs, + Args: args.SingleArg(argNameGroupId, utils.ValidateUUID), Example: examples.Build( - examples.NewExample(`example 1`, `foo bar baz`), - examples.NewExample(`example 2`, `foo bar baz`), + examples.NewExample(`describe an existing group`, `$ stackit beta security-group describe 9e9c44fe-eb9a-4d45-bf08-365e961845d1`), ), RunE: func(cmd *cobra.Command, args []string) error { return executeDescribe(cmd, p, args) }, } - cmd.Flags().String("dummy", "foo", "fooify") + configureFlags(cmd) + return cmd } func executeDescribe(cmd *cobra.Command, p *print.Printer, args []string) error { p.Info("executing describe command") + 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 + } + + // Call API + request := buildRequest(ctx, model, apiClient) + + p.Info("security group %q for %q\n", model.SecurityGroupId, model.ProjectId) + + group, err := request.Execute() + if err != nil { + return fmt.Errorf("get security group: %w", err) + } + if err := outputResult(p, model, group); err != nil { + return err + } + return nil } + +func configureFlags(cmd *cobra.Command) { + globalflags.Configure(cmd.Flags()) +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiGetSecurityGroupRequest { + request := apiClient.GetSecurityGroup(ctx, model.ProjectId, model.SecurityGroupId) + return request + +} + +func parseInput(p *print.Printer, cmd *cobra.Command, args []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + if err := cmd.ValidateArgs(args); err != nil { + return nil, &errors.ArgValidationError{ + Arg: argNameGroupId, + Details: fmt.Sprintf("argument validation failed: %v", err), + } + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + SecurityGroupId: args[0], + } + + 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 outputResult(p *print.Printer, model *inputModel, resp *iaas.SecurityGroup) error { + switch model.OutputFormat { + case print.JSONOutputFormat: + details, err := json.MarshalIndent(resp, "", " ") + if err != nil { + return fmt.Errorf("marshal security group: %w", err) + } + p.Outputln(string(details)) + + return nil + case print.YAMLOutputFormat: + details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true)) + if err != nil { + return fmt.Errorf("marshal security group: %w", err) + } + p.Outputln(string(details)) + + return nil + default: + table := tables.NewTable() + if id := resp.Id; id != nil { + table.AddRow("ID", *id) + } + table.AddSeparator() + + if name := resp.Name; name != nil { + table.AddRow("NAME", name) + table.AddSeparator() + } + + if description := resp.Description; description != nil { + table.AddRow("DESCRIPTION", description) + table.AddSeparator() + } + + if labels := resp.Labels; labels != nil { + var builder strings.Builder + for k, v := range *labels { + builder.WriteString(fmt.Sprintf("%s=%s ", k, v)) + } + table.AddRow("LABELS", builder.String()) + table.AddSeparator() + } + + if stateful := resp.Stateful; stateful != nil { + table.AddRow("STATEFUL", stateful) + table.AddSeparator() + } + + if err := table.Display(p); err != nil { + return fmt.Errorf("render table: %w", err) + } + + return nil + } +} diff --git a/internal/cmd/beta/security-group/describe/describe_test.go b/internal/cmd/beta/security-group/describe/describe_test.go new file mode 100644 index 000000000..104c28b59 --- /dev/null +++ b/internal/cmd/beta/security-group/describe/describe_test.go @@ -0,0 +1,186 @@ +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/iaas" +) + +var projectIdFlag = globalflags.ProjectIdFlag + +type testCtxKey struct{} + +var ( + testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") + testClient = &iaas.APIClient{} + testProjectId = uuid.NewString() + testSecurityGroupId = []string{uuid.NewString()} +) + +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}, + SecurityGroupId: testSecurityGroupId[0], + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *iaas.ApiGetSecurityGroupRequest)) iaas.ApiGetSecurityGroupRequest { + request := testClient.GetSecurityGroup(testCtx, testProjectId, testSecurityGroupId[0]) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + flagValues map[string]string + isValid bool + args []string + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + expectedModel: fixtureInputModel(), + args: testSecurityGroupId, + isValid: true, + }, + { + description: "no values", + flagValues: map[string]string{}, + args: testSecurityGroupId, + isValid: false, + }, + { + description: "project id missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, projectIdFlag) + }), + args: testSecurityGroupId, + isValid: false, + }, + { + description: "project id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "" + }), + args: testSecurityGroupId, + isValid: false, + }, + { + description: "project id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "invalid-uuid" + }), + args: testSecurityGroupId, + isValid: false, + }, + { + description: "no group id passed", + flagValues: fixtureFlagValues(), + args: nil, + isValid: false, + }, + { + description: "multiple group ids passed", + flagValues: fixtureFlagValues(), + args: []string{uuid.NewString(), uuid.NewString()}, + isValid: false, + }, + { + description: "invalid group id passed", + flagValues: fixtureFlagValues(), + args: []string{"foobar"}, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + p := print.NewPrinter() + cmd := NewCmd(p) + + 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) + } + } + + if err := cmd.ValidateRequiredFlags(); err != nil { + if !tt.isValid { + return + } + t.Fatalf("error validating flags: %v", err) + } + + model, err := parseInput(p, cmd, tt.args) + 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 iaas.ApiGetSecurityGroupRequest + }{ + { + 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) + } + }) + } +} From f2fc1dc973549bc21ddeac17c0ffeedca4af2a8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Schmitz?= <152157960+bahkauv70@users.noreply.github.com> Date: Mon, 9 Dec 2024 09:18:01 +0100 Subject: [PATCH 16/28] feature: fixed parsing of global flags --- internal/cmd/beta/security-group/create/create.go | 1 - internal/cmd/beta/security-group/create/create_test.go | 3 +++ internal/cmd/beta/security-group/describe/describe.go | 5 ----- internal/cmd/beta/security-group/describe/describe_test.go | 4 +++- internal/cmd/beta/security-group/list/list.go | 1 - internal/cmd/beta/security-group/list/list_test.go | 3 +++ internal/cmd/beta/security-group/update/update.go | 1 - internal/cmd/beta/security-group/update/update_test.go | 4 +++- 8 files changed, 12 insertions(+), 10 deletions(-) diff --git a/internal/cmd/beta/security-group/create/create.go b/internal/cmd/beta/security-group/create/create.go index f3a866a26..79a036d0a 100644 --- a/internal/cmd/beta/security-group/create/create.go +++ b/internal/cmd/beta/security-group/create/create.go @@ -46,7 +46,6 @@ func NewCmd(p *print.Printer) *cobra.Command { } func configureFlags(cmd *cobra.Command) { - globalflags.Configure(cmd.Flags()) cmd.Flags().String("name", "", "the name of the security group. Must be <= 63 chars") cmd.Flags().String("description", "", "an optional description of the security group. Must be <= 127 chars") cmd.Flags().Bool("stateful", false, "create a stateful or a stateless security group") diff --git a/internal/cmd/beta/security-group/create/create_test.go b/internal/cmd/beta/security-group/create/create_test.go index 20d90cab8..1ecfce82e 100644 --- a/internal/cmd/beta/security-group/create/create_test.go +++ b/internal/cmd/beta/security-group/create/create_test.go @@ -188,6 +188,9 @@ func TestParseInput(t *testing.T) { t.Run(tt.description, func(t *testing.T) { p := print.NewPrinter() cmd := NewCmd(p) + if err := globalflags.Configure(cmd.Flags()); err != nil { + t.Errorf("cannot configure global flags: %v", err) + } for flag, value := range tt.flagValues { err := cmd.Flags().Set(flag, value) diff --git a/internal/cmd/beta/security-group/describe/describe.go b/internal/cmd/beta/security-group/describe/describe.go index 84b854d47..7cf5cc716 100644 --- a/internal/cmd/beta/security-group/describe/describe.go +++ b/internal/cmd/beta/security-group/describe/describe.go @@ -39,7 +39,6 @@ func NewCmd(p *print.Printer) *cobra.Command { return executeDescribe(cmd, p, args) }, } - configureFlags(cmd) return cmd } @@ -74,10 +73,6 @@ func executeDescribe(cmd *cobra.Command, p *print.Printer, args []string) error return nil } -func configureFlags(cmd *cobra.Command) { - globalflags.Configure(cmd.Flags()) -} - func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiGetSecurityGroupRequest { request := apiClient.GetSecurityGroup(ctx, model.ProjectId, model.SecurityGroupId) return request diff --git a/internal/cmd/beta/security-group/describe/describe_test.go b/internal/cmd/beta/security-group/describe/describe_test.go index 104c28b59..a1c0ac80e 100644 --- a/internal/cmd/beta/security-group/describe/describe_test.go +++ b/internal/cmd/beta/security-group/describe/describe_test.go @@ -121,7 +121,9 @@ func TestParseInput(t *testing.T) { t.Run(tt.description, func(t *testing.T) { p := print.NewPrinter() cmd := NewCmd(p) - + if err := globalflags.Configure(cmd.Flags()); err != nil { + t.Errorf("cannot configure global flags: %v", err) + } for flag, value := range tt.flagValues { err := cmd.Flags().Set(flag, value) if err != nil { diff --git a/internal/cmd/beta/security-group/list/list.go b/internal/cmd/beta/security-group/list/list.go index e06e9066f..8c3d28e1e 100644 --- a/internal/cmd/beta/security-group/list/list.go +++ b/internal/cmd/beta/security-group/list/list.go @@ -45,7 +45,6 @@ func NewCmd(p *print.Printer) *cobra.Command { } func configureFlags(cmd *cobra.Command) { - globalflags.Configure(cmd.Flags()) cmd.Flags().String("labels", "", "a list of labels in the form =") } diff --git a/internal/cmd/beta/security-group/list/list_test.go b/internal/cmd/beta/security-group/list/list_test.go index 62145b44f..385f3e4a7 100644 --- a/internal/cmd/beta/security-group/list/list_test.go +++ b/internal/cmd/beta/security-group/list/list_test.go @@ -121,6 +121,9 @@ func TestParseInput(t *testing.T) { t.Run(tt.description, func(t *testing.T) { p := print.NewPrinter() cmd := NewCmd(p) + if err := globalflags.Configure(cmd.Flags()); err != nil { + t.Errorf("cannot configure global flags: %v", err) + } for flag, value := range tt.flagValues { err := cmd.Flags().Set(flag, value) diff --git a/internal/cmd/beta/security-group/update/update.go b/internal/cmd/beta/security-group/update/update.go index 25cd5a3ec..7b4b006c0 100644 --- a/internal/cmd/beta/security-group/update/update.go +++ b/internal/cmd/beta/security-group/update/update.go @@ -48,7 +48,6 @@ func NewCmd(p *print.Printer) *cobra.Command { } func configureFlags(cmd *cobra.Command) { - globalflags.Configure(cmd.Flags()) cmd.Flags().String("name", "", "the name of the security group. Must be <= 63 chars") cmd.Flags().String("description", "", "an optional description of the security group. Must be <= 127 chars") cmd.Flags().StringSlice("labels", nil, "a list of labels in the form =") diff --git a/internal/cmd/beta/security-group/update/update_test.go b/internal/cmd/beta/security-group/update/update_test.go index c59d6be19..5420b3710 100644 --- a/internal/cmd/beta/security-group/update/update_test.go +++ b/internal/cmd/beta/security-group/update/update_test.go @@ -226,7 +226,9 @@ func TestParseInput(t *testing.T) { t.Run(tt.description, func(t *testing.T) { p := print.NewPrinter() cmd := NewCmd(p) - + if err := globalflags.Configure(cmd.Flags()); err != nil { + t.Errorf("cannot configure global flags: %v", err) + } for flag, value := range tt.flagValues { if err := cmd.Flags().Set(flag, value); err != nil { From 9392cd3c4bc14714158f175544d164c1bc55216f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Schmitz?= <152157960+bahkauv70@users.noreply.github.com> Date: Mon, 9 Dec 2024 09:42:44 +0100 Subject: [PATCH 17/28] feature: fix list command --- internal/cmd/beta/security-group/list/list.go | 20 +++++++++++++------ .../cmd/beta/security-group/list/list_test.go | 11 +++++----- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/internal/cmd/beta/security-group/list/list.go b/internal/cmd/beta/security-group/list/list.go index 8c3d28e1e..f3630166e 100644 --- a/internal/cmd/beta/security-group/list/list.go +++ b/internal/cmd/beta/security-group/list/list.go @@ -22,7 +22,7 @@ import ( type inputModel struct { *globalflags.GlobalFlagModel - Labels string + Labels *string } func NewCmd(p *print.Printer) *cobra.Command { @@ -74,7 +74,7 @@ func executeList(cmd *cobra.Command, p *print.Printer, _ []string) error { if err != nil { return fmt.Errorf("list security group: %w", err) } - if items := response.GetItems(); items == nil || len(*items) > 0 { + if items := response.GetItems(); items == nil || len(*items) == 0 { p.Info("no security groups found for %q", projectLabel) } else { outputResult(p, model.OutputFormat, *items) @@ -91,8 +91,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { model := inputModel{ GlobalFlagModel: globalFlags, - - Labels: flags.FlagToStringValue(p, cmd, "labels"), + Labels: flags.FlagToStringPointer(p, cmd, "labels"), } if p.IsVerbosityDebug() { @@ -109,7 +108,9 @@ func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiListSecurityGroupsRequest { request := apiClient.ListSecurityGroups(ctx, model.ProjectId) - request = request.LabelSelector(model.Labels) + if model.Labels != nil { + request = request.LabelSelector(*model.Labels) + } return request @@ -136,7 +137,7 @@ func outputResult(p *print.Printer, outputFormat string, items []iaas.SecurityGr table := tables.NewTable() table.SetHeader("ID", "NAME", "LABELS", "STATEFUL") for _, item := range items { - table.AddRow(item.Id, item.Name, concatLabels(item.Labels), item.Stateful) + table.AddRow(ptrString(item.Id), ptrString(item.Name), concatLabels(item.Labels), ptrString(item.Stateful)) } err := table.Display(p) if err != nil { @@ -147,6 +148,13 @@ func outputResult(p *print.Printer, outputFormat string, items []iaas.SecurityGr } } +func ptrString[T any](t*T) string { + if t != nil { + return fmt.Sprintf("%v",*t) + } + return "" +} + func concatLabels(item *map[string]any) string { if item == nil { return "" diff --git a/internal/cmd/beta/security-group/list/list_test.go b/internal/cmd/beta/security-group/list/list_test.go index 385f3e4a7..da88a69d2 100644 --- a/internal/cmd/beta/security-group/list/list_test.go +++ b/internal/cmd/beta/security-group/list/list_test.go @@ -6,6 +6,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" @@ -39,7 +40,7 @@ func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]st func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { model := &inputModel{ GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectId, Verbosity: globalflags.VerbosityDefault}, - Labels: testLabels, + Labels: utils.Ptr(testLabels), } for _, mod := range mods { mod(model) @@ -102,7 +103,7 @@ func TestParseInput(t *testing.T) { }), isValid: true, expectedModel: fixtureInputModel(func(model *inputModel) { - model.Labels = "" + model.Labels = nil }), }, { @@ -112,7 +113,7 @@ func TestParseInput(t *testing.T) { }), isValid: true, expectedModel: fixtureInputModel(func(model *inputModel) { - model.Labels = "foo=bar" + model.Labels = utils.Ptr("foo=bar") }), }, } @@ -175,7 +176,7 @@ func TestBuildRequest(t *testing.T) { { description: "no labels", model: fixtureInputModel(func(model *inputModel) { - model.Labels = "" + model.Labels = utils.Ptr("") }), expectedRequest: fixtureRequest(func(request *iaas.ApiListSecurityGroupsRequest) { *request = request.LabelSelector("") @@ -184,7 +185,7 @@ func TestBuildRequest(t *testing.T) { { description: "single label", model: fixtureInputModel(func(model *inputModel) { - model.Labels = "foo=bar" + model.Labels = utils.Ptr("foo=bar") }), expectedRequest: fixtureRequest(func(request *iaas.ApiListSecurityGroupsRequest) { *request = request.LabelSelector("foo=bar") From 3aa6eabd993e1e7a88ca76cf2dcfa77663482072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Schmitz?= <152157960+bahkauv70@users.noreply.github.com> Date: Mon, 9 Dec 2024 09:54:00 +0100 Subject: [PATCH 18/28] feature: fixed describe command --- internal/cmd/beta/security-group/describe/describe.go | 6 +++--- internal/cmd/beta/security-group/update/update.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/cmd/beta/security-group/describe/describe.go b/internal/cmd/beta/security-group/describe/describe.go index 7cf5cc716..ee0f497fc 100644 --- a/internal/cmd/beta/security-group/describe/describe.go +++ b/internal/cmd/beta/security-group/describe/describe.go @@ -134,12 +134,12 @@ func outputResult(p *print.Printer, model *inputModel, resp *iaas.SecurityGroup) table.AddSeparator() if name := resp.Name; name != nil { - table.AddRow("NAME", name) + table.AddRow("NAME", *name) table.AddSeparator() } if description := resp.Description; description != nil { - table.AddRow("DESCRIPTION", description) + table.AddRow("DESCRIPTION", *description) table.AddSeparator() } @@ -153,7 +153,7 @@ func outputResult(p *print.Printer, model *inputModel, resp *iaas.SecurityGroup) } if stateful := resp.Stateful; stateful != nil { - table.AddRow("STATEFUL", stateful) + table.AddRow("STATEFUL", *stateful) table.AddSeparator() } diff --git a/internal/cmd/beta/security-group/update/update.go b/internal/cmd/beta/security-group/update/update.go index 7b4b006c0..6faee92ef 100644 --- a/internal/cmd/beta/security-group/update/update.go +++ b/internal/cmd/beta/security-group/update/update.go @@ -30,7 +30,7 @@ const argNameGroupId = "argGroupId" func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ - Use: "Update", + Use: "update", Short: "Update a security group", Long: "Update a named security group", Args: args.SingleArg(argNameGroupId, utils.ValidateUUID), From 1b5d90b8f5b2cff75ee624f9eeb79028d0711750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Schmitz?= <152157960+bahkauv70@users.noreply.github.com> Date: Mon, 9 Dec 2024 10:47:35 +0100 Subject: [PATCH 19/28] feature: fixed typo --- internal/cmd/beta/security-group/update/update.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/beta/security-group/update/update.go b/internal/cmd/beta/security-group/update/update.go index 6faee92ef..f8771a5d8 100644 --- a/internal/cmd/beta/security-group/update/update.go +++ b/internal/cmd/beta/security-group/update/update.go @@ -82,7 +82,7 @@ func executeUpdate(cmd *cobra.Command, p *print.Printer, args []string) error { operationState := "Enabled" if model.Async { - operationState = "Triggered enablement of" + operationState = "Triggered update of" } p.Info("%s security group \"%v\" for %q\n", operationState, model.Name, projectLabel) return nil From a2f3617065900664c23b94a1991199c019b9a0be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Schmitz?= <152157960+bahkauv70@users.noreply.github.com> Date: Mon, 9 Dec 2024 10:47:53 +0100 Subject: [PATCH 20/28] feature: updated generated documentation --- docs/stackit_beta.md | 2 +- docs/stackit_beta_security-group_create.md | 46 ++++++++++++++++++++ docs/stackit_beta_security-group_delete.md | 39 +++++++++++++++++ docs/stackit_beta_security-group_describe.md | 39 +++++++++++++++++ docs/stackit_beta_security-group_list.md | 43 ++++++++++++++++++ docs/stackit_beta_security-group_update.md | 45 +++++++++++++++++++ 6 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 docs/stackit_beta_security-group_create.md create mode 100644 docs/stackit_beta_security-group_delete.md create mode 100644 docs/stackit_beta_security-group_describe.md create mode 100644 docs/stackit_beta_security-group_list.md create mode 100644 docs/stackit_beta_security-group_update.md diff --git a/docs/stackit_beta.md b/docs/stackit_beta.md index 4ab26fe12..24e153bdb 100644 --- a/docs/stackit_beta.md +++ b/docs/stackit_beta.md @@ -45,7 +45,7 @@ stackit beta [flags] * [stackit beta network-area](./stackit_beta_network-area.md) - Provides functionality for STACKIT Network Area (SNA) * [stackit beta network-interface](./stackit_beta_network-interface.md) - Provides functionality for network interfaces * [stackit beta public-ip](./stackit_beta_public-ip.md) - Provides functionality for public IPs -* [stackit beta security-group](./stackit_beta_security-group.md) - Provides functionality for security groups +* [stackit beta security-group](./stackit_beta_security-group.md) - manage security groups. * [stackit beta server](./stackit_beta_server.md) - Provides functionality for servers * [stackit beta sqlserverflex](./stackit_beta_sqlserverflex.md) - Provides functionality for SQLServer Flex * [stackit beta volume](./stackit_beta_volume.md) - Provides functionality for volumes diff --git a/docs/stackit_beta_security-group_create.md b/docs/stackit_beta_security-group_create.md new file mode 100644 index 000000000..1a0ca4620 --- /dev/null +++ b/docs/stackit_beta_security-group_create.md @@ -0,0 +1,46 @@ +## stackit beta security-group create + +create security groups + +### Synopsis + +create security groups + +``` +stackit beta security-group create [flags] +``` + +### Examples + +``` + create a named group + $ stackit beta security-group create --name my-new-group + + create a named group with labels + $ stackit beta security-group create --name my-new-group --labels label1=value1,label2=value2 +``` + +### Options + +``` + --description string an optional description of the security group. Must be <= 127 chars + -h, --help Help for "stackit beta security-group create" + --labels strings a list of labels in the form = + --name string the name of the security group. Must be <= 63 chars + --stateful create a stateful or a stateless security group +``` + +### 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 + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit beta security-group](./stackit_beta_security-group.md) - manage security groups. + diff --git a/docs/stackit_beta_security-group_delete.md b/docs/stackit_beta_security-group_delete.md new file mode 100644 index 000000000..d33b21601 --- /dev/null +++ b/docs/stackit_beta_security-group_delete.md @@ -0,0 +1,39 @@ +## stackit beta security-group delete + +delete a security group + +### Synopsis + +delete a security group by its internal id + +``` +stackit beta security-group delete [flags] +``` + +### Examples + +``` + delete a named group + $ stackit beta security-group delete 43ad419a-c68b-4911-87cd-e05752ac1e31 +``` + +### Options + +``` + -h, --help Help for "stackit beta security-group 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 + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit beta security-group](./stackit_beta_security-group.md) - manage security groups. + diff --git a/docs/stackit_beta_security-group_describe.md b/docs/stackit_beta_security-group_describe.md new file mode 100644 index 000000000..8e8fb6dff --- /dev/null +++ b/docs/stackit_beta_security-group_describe.md @@ -0,0 +1,39 @@ +## stackit beta security-group describe + +describe security groups + +### Synopsis + +describe security groups + +``` +stackit beta security-group describe [flags] +``` + +### Examples + +``` + describe an existing group + $ stackit beta security-group describe 9e9c44fe-eb9a-4d45-bf08-365e961845d1 +``` + +### Options + +``` + -h, --help Help for "stackit beta security-group 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 + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit beta security-group](./stackit_beta_security-group.md) - manage security groups. + diff --git a/docs/stackit_beta_security-group_list.md b/docs/stackit_beta_security-group_list.md new file mode 100644 index 000000000..634d55ac8 --- /dev/null +++ b/docs/stackit_beta_security-group_list.md @@ -0,0 +1,43 @@ +## stackit beta security-group list + +list security groups + +### Synopsis + +list security groups + +``` +stackit beta security-group list [flags] +``` + +### Examples + +``` + list all groups + $ stackit beta security-group list + + list groups with labels + $ stackit beta security-group list --labels label1=value1,label2=value2 +``` + +### Options + +``` + -h, --help Help for "stackit beta security-group list" + --labels string a list of labels in the form = +``` + +### 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 + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit beta security-group](./stackit_beta_security-group.md) - manage security groups. + diff --git a/docs/stackit_beta_security-group_update.md b/docs/stackit_beta_security-group_update.md new file mode 100644 index 000000000..3e4fb3922 --- /dev/null +++ b/docs/stackit_beta_security-group_update.md @@ -0,0 +1,45 @@ +## stackit beta security-group update + +Update a security group + +### Synopsis + +Update a named security group + +``` +stackit beta security-group update [flags] +``` + +### Examples + +``` + Update the name of a group + $ stackit beta security-group update 541d122f-0a5f-4bb0-94b9-b1ccbd7ba776 --name my-new-name + + Update the labels of a group + $ stackit beta security-group update 541d122f-0a5f-4bb0-94b9-b1ccbd7ba776 --labels label1=value1,label2=value2 +``` + +### Options + +``` + --description string an optional description of the security group. Must be <= 127 chars + -h, --help Help for "stackit beta security-group update" + --labels strings a list of labels in the form = + --name string the name of the security group. Must be <= 63 chars +``` + +### 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 + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit beta security-group](./stackit_beta_security-group.md) - manage security groups. + From 41ad303d15f00865353a906274e13774b5c1efcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Schmitz?= <152157960+bahkauv70@users.noreply.github.com> Date: Mon, 9 Dec 2024 13:48:01 +0100 Subject: [PATCH 21/28] feature: fix review comments --- docs/stackit_beta.md | 2 +- docs/stackit_beta_security-group.md | 4 +- docs/stackit_beta_security-group_create.md | 8 +- docs/stackit_beta_security-group_delete.md | 6 +- docs/stackit_beta_security-group_describe.md | 6 +- docs/stackit_beta_security-group_list.md | 10 +- docs/stackit_beta_security-group_update.md | 4 +- internal/cmd/beta/beta.go | 4 +- .../cmd/beta/security-group/create/create.go | 180 ++++++++---------- .../beta/security-group/create/create_test.go | 80 +++----- .../cmd/beta/security-group/delete/delete.go | 118 ++++-------- .../beta/security-group/delete/delete_test.go | 26 ++- .../beta/security-group/describe/describe.go | 86 ++++----- .../security-group/describe/describe_test.go | 16 +- internal/cmd/beta/security-group/list/list.go | 119 +++++------- .../cmd/beta/security-group/list/list_test.go | 19 +- .../cmd/beta/security-group/security_group.go | 4 +- .../cmd/beta/security-group/update/update.go | 158 ++++++--------- .../beta/security-group/update/update_test.go | 75 +++----- .../cmd/beta/security-group/utils/utils.go | 12 ++ 20 files changed, 384 insertions(+), 553 deletions(-) create mode 100644 internal/cmd/beta/security-group/utils/utils.go diff --git a/docs/stackit_beta.md b/docs/stackit_beta.md index 24e153bdb..b89e3dbbe 100644 --- a/docs/stackit_beta.md +++ b/docs/stackit_beta.md @@ -45,7 +45,7 @@ stackit beta [flags] * [stackit beta network-area](./stackit_beta_network-area.md) - Provides functionality for STACKIT Network Area (SNA) * [stackit beta network-interface](./stackit_beta_network-interface.md) - Provides functionality for network interfaces * [stackit beta public-ip](./stackit_beta_public-ip.md) - Provides functionality for public IPs -* [stackit beta security-group](./stackit_beta_security-group.md) - manage security groups. +* [stackit beta security-group](./stackit_beta_security-group.md) - Manage security groups * [stackit beta server](./stackit_beta_server.md) - Provides functionality for servers * [stackit beta sqlserverflex](./stackit_beta_sqlserverflex.md) - Provides functionality for SQLServer Flex * [stackit beta volume](./stackit_beta_volume.md) - Provides functionality for volumes diff --git a/docs/stackit_beta_security-group.md b/docs/stackit_beta_security-group.md index fcc28bdd7..f7f064408 100644 --- a/docs/stackit_beta_security-group.md +++ b/docs/stackit_beta_security-group.md @@ -1,10 +1,10 @@ ## stackit beta security-group -Provides functionality for security groups +Manage security groups ### Synopsis -Provides functionality for security groups. +Manage the lifecycle of security groups. ``` stackit beta security-group [flags] diff --git a/docs/stackit_beta_security-group_create.md b/docs/stackit_beta_security-group_create.md index 1a0ca4620..ce9f91bd0 100644 --- a/docs/stackit_beta_security-group_create.md +++ b/docs/stackit_beta_security-group_create.md @@ -1,10 +1,10 @@ ## stackit beta security-group create -create security groups +Create security groups ### Synopsis -create security groups +Create security groups. ``` stackit beta security-group create [flags] @@ -25,7 +25,7 @@ stackit beta security-group create [flags] ``` --description string an optional description of the security group. Must be <= 127 chars -h, --help Help for "stackit beta security-group create" - --labels strings a list of labels in the form = + --labels strings Labels are key-value string pairs which can be attached to a network-interface. E.g. '--labels key1=value1,key2=value2,...' --name string the name of the security group. Must be <= 63 chars --stateful create a stateful or a stateless security group ``` @@ -42,5 +42,5 @@ stackit beta security-group create [flags] ### SEE ALSO -* [stackit beta security-group](./stackit_beta_security-group.md) - manage security groups. +* [stackit beta security-group](./stackit_beta_security-group.md) - Manage security groups diff --git a/docs/stackit_beta_security-group_delete.md b/docs/stackit_beta_security-group_delete.md index d33b21601..85d047775 100644 --- a/docs/stackit_beta_security-group_delete.md +++ b/docs/stackit_beta_security-group_delete.md @@ -1,10 +1,10 @@ ## stackit beta security-group delete -delete a security group +Delete a security group ### Synopsis -delete a security group by its internal id +Delete a security group by its internal id. ``` stackit beta security-group delete [flags] @@ -35,5 +35,5 @@ stackit beta security-group delete [flags] ### SEE ALSO -* [stackit beta security-group](./stackit_beta_security-group.md) - manage security groups. +* [stackit beta security-group](./stackit_beta_security-group.md) - Manage security groups diff --git a/docs/stackit_beta_security-group_describe.md b/docs/stackit_beta_security-group_describe.md index 8e8fb6dff..e6b945f3d 100644 --- a/docs/stackit_beta_security-group_describe.md +++ b/docs/stackit_beta_security-group_describe.md @@ -1,10 +1,10 @@ ## stackit beta security-group describe -describe security groups +Describe security groups ### Synopsis -describe security groups +Describe security groups. ``` stackit beta security-group describe [flags] @@ -35,5 +35,5 @@ stackit beta security-group describe [flags] ### SEE ALSO -* [stackit beta security-group](./stackit_beta_security-group.md) - manage security groups. +* [stackit beta security-group](./stackit_beta_security-group.md) - Manage security groups diff --git a/docs/stackit_beta_security-group_list.md b/docs/stackit_beta_security-group_list.md index 634d55ac8..053116257 100644 --- a/docs/stackit_beta_security-group_list.md +++ b/docs/stackit_beta_security-group_list.md @@ -1,10 +1,10 @@ ## stackit beta security-group list -list security groups +List security groups ### Synopsis -list security groups +List security groups. ``` stackit beta security-group list [flags] @@ -23,8 +23,8 @@ stackit beta security-group list [flags] ### Options ``` - -h, --help Help for "stackit beta security-group list" - --labels string a list of labels in the form = + -h, --help Help for "stackit beta security-group list" + --label-selector string Filter by label ``` ### Options inherited from parent commands @@ -39,5 +39,5 @@ stackit beta security-group list [flags] ### SEE ALSO -* [stackit beta security-group](./stackit_beta_security-group.md) - manage security groups. +* [stackit beta security-group](./stackit_beta_security-group.md) - Manage security groups diff --git a/docs/stackit_beta_security-group_update.md b/docs/stackit_beta_security-group_update.md index 3e4fb3922..075204249 100644 --- a/docs/stackit_beta_security-group_update.md +++ b/docs/stackit_beta_security-group_update.md @@ -25,7 +25,7 @@ stackit beta security-group update [flags] ``` --description string an optional description of the security group. Must be <= 127 chars -h, --help Help for "stackit beta security-group update" - --labels strings a list of labels in the form = + --labels strings Labels are key-value string pairs which can be attached to a network-interface. E.g. '--labels key1=value1,key2=value2,...' --name string the name of the security group. Must be <= 63 chars ``` @@ -41,5 +41,5 @@ stackit beta security-group update [flags] ### SEE ALSO -* [stackit beta security-group](./stackit_beta_security-group.md) - manage security groups. +* [stackit beta security-group](./stackit_beta_security-group.md) - Manage security groups diff --git a/internal/cmd/beta/beta.go b/internal/cmd/beta/beta.go index ebcdb3d0d..5ad1bfdf3 100644 --- a/internal/cmd/beta/beta.go +++ b/internal/cmd/beta/beta.go @@ -8,8 +8,7 @@ import ( networkArea "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-area" networkinterface "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-interface" publicip "github.com/stackitcloud/stackit-cli/internal/cmd/beta/public-ip" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group" - securitygroup "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group" + security_group "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/server" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/volume" @@ -52,6 +51,5 @@ func addSubcommands(cmd *cobra.Command, p *print.Printer) { cmd.AddCommand(networkinterface.NewCmd(p)) cmd.AddCommand(publicip.NewCmd(p)) cmd.AddCommand(security_group.NewCmd(p)) - cmd.AddCommand(securitygroup.NewCmd(p)) cmd.AddCommand(keypair.NewCmd(p)) } diff --git a/internal/cmd/beta/security-group/create/create.go b/internal/cmd/beta/security-group/create/create.go index 79a036d0a..e70273a44 100644 --- a/internal/cmd/beta/security-group/create/create.go +++ b/internal/cmd/beta/security-group/create/create.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "strings" "github.com/goccy/go-yaml" "github.com/spf13/cobra" @@ -15,87 +14,85 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) +const ( + nameFlag = "name" + descriptionFlag = "description" + statefulFlag = "stateful" + labelsFlag = "labels" +) + type inputModel struct { *globalflags.GlobalFlagModel - Labels map[string]any - Description string - Name string - Stateful bool + Labels *map[string]string + Description *string + Name *string + Stateful *bool } func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: "create", - Short: "create security groups", - Long: "create security groups", + Short: "Creates security groups", + Long: "Creates security groups.", Args: args.NoArgs, Example: examples.Build( - examples.NewExample(`create a named group`, `$ stackit beta security-group create --name my-new-group`), - examples.NewExample(`create a named group with labels`, `$ stackit beta security-group create --name my-new-group --labels label1=value1,label2=value2`), + examples.NewExample(`Create a named group`, `$ stackit beta security-group create --name my-new-group`), + examples.NewExample(`Create a named group with labels`, `$ stackit beta security-group create --name my-new-group --labels label1=value1,label2=value2`), ), - RunE: func(cmd *cobra.Command, args []string) error { - return executeCreate(cmd, p, args) - }, - } + RunE: func(cmd *cobra.Command, _ []string) error { + ctx := context.Background() + model, err := parseInput(p, cmd) + if err != nil { + return err + } - configureFlags(cmd) - return cmd -} + // Configure API client + apiClient, err := client.ConfigureClient(p) + if err != nil { + return err + } -func configureFlags(cmd *cobra.Command) { - cmd.Flags().String("name", "", "the name of the security group. Must be <= 63 chars") - cmd.Flags().String("description", "", "an optional description of the security group. Must be <= 127 chars") - cmd.Flags().Bool("stateful", false, "create a stateful or a stateless security group") - cmd.Flags().StringSlice("labels", nil, "a list of labels in the form =") + if !model.AssumeYes { + prompt := fmt.Sprintf("Are you sure you want to create the security group %q?", *model.Name) + err = p.PromptForConfirmation(prompt) + if err != nil { + return err + } + } - if err := flags.MarkFlagsRequired(cmd, "name"); err != nil { - cobra.CheckErr(err) - } -} + // Call API + request := buildRequest(ctx, model, apiClient) -func executeCreate(cmd *cobra.Command, p *print.Printer, _ []string) error { - p.Info("executing create command") - ctx := context.Background() - model, err := parseInput(p, cmd) - if err != nil { - return err - } + group, err := request.Execute() + if err != nil { + return fmt.Errorf("create security group: %w", err) + } - // Configure API client - apiClient, err := client.ConfigureClient(p) - if err != nil { - return err - } + if err := outputResult(p, model, group); err != nil { + return err + } - if !model.AssumeYes { - prompt := fmt.Sprintf("Are you sure you want to create the security group %q?", model.Name) - err = p.PromptForConfirmation(prompt) - if err != nil { - return err - } + return nil + }, } - // Call API - request := buildRequest(ctx, model, apiClient) + configureFlags(cmd) + return cmd +} - operationState := "Enabled" - if model.Async { - operationState = "Triggered security group creation" - } - p.Info("%s security group %q for %q\n", operationState, model.Name, model.ProjectId) +func configureFlags(cmd *cobra.Command) { + cmd.Flags().String(nameFlag, "", "The name of the security group.") + cmd.Flags().String(descriptionFlag, "", "An optional description of the security group.") + cmd.Flags().Bool(statefulFlag, false, "Create a stateful or a stateless security group") + cmd.Flags().StringToString(labelsFlag, nil, "Labels are key-value string pairs which can be attached to a network-interface. E.g. '--labels key1=value1,key2=value2,...'") - group, err := request.Execute() - if err != nil { - return fmt.Errorf("create security group: %w", err) - } - if err:=outputResult(p, model, group);err != nil { - return err + if err := flags.MarkFlagsRequired(cmd, nameFlag); err != nil { + cobra.CheckErr(err) } - - return nil } func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { @@ -103,40 +100,15 @@ func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { if globalFlags.ProjectId == "" { return nil, &errors.ProjectIdError{} } - name := flags.FlagToStringValue(p, cmd, "name") - if len(name) >= 64 { - return nil, &errors.ArgValidationError{ - Arg: "invalid name", - Details: "name exceeds 63 characters in length", - } - } + name := flags.FlagToStringValue(p, cmd, nameFlag) - labels := make(map[string]any) - for _, label := range flags.FlagToStringSliceValue(p, cmd, "labels") { - parts := strings.Split(label, "=") - if len(parts) != 2 { - return nil, &errors.ArgValidationError{ - Arg: "labels", - Details: "invalid label declaration. Must be in the form =", - } - } - labels[parts[0]] = parts[1] - - } - description := flags.FlagToStringValue(p, cmd, "description") - if len(description) >= 128 { - return nil, &errors.ArgValidationError{ - Arg: "invalid description", - Details: "description exceeds 127 characters in length", - } - } model := inputModel{ GlobalFlagModel: globalFlags, - Name: name, + Name: &name, - Labels: labels, - Description: description, - Stateful: flags.FlagToBoolValue(p, cmd, "stateful"), + Labels: flags.FlagToStringToStringPointer(p, cmd, labelsFlag), + Description: flags.FlagToStringPointer(p, cmd, descriptionFlag), + Stateful: flags.FlagToBoolPointer(p, cmd, statefulFlag), } if p.IsVerbosityDebug() { @@ -153,19 +125,23 @@ func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiCreateSecurityGroupRequest { request := apiClient.CreateSecurityGroup(ctx, model.ProjectId) - payload := iaas.NewCreateSecurityGroupPayload(&model.Name) - payload.Description = &model.Description - if model.Labels != nil { - // this check assure that we don't end up with a pointer to nil - // which is a thing in go! - payload.Labels = &model.Labels - } - payload.Name = &model.Name - payload.Stateful = &model.Stateful - request = request.CreateSecurityGroupPayload(*payload) - return request + var labelsMap *map[string]any + if model.Labels != nil && len(*model.Labels) > 0 { + // convert map[string]string to map[string]interface{} + labelsMap = utils.Ptr(map[string]interface{}{}) + for k, v := range *model.Labels { + (*labelsMap)[k] = v + } + } + payload := iaas.CreateSecurityGroupPayload{ + Description: model.Description, + Labels: labelsMap, + Name: model.Name, + Stateful: model.Stateful, + } + return request.CreateSecurityGroupPayload(payload) } func outputResult(p *print.Printer, model *inputModel, resp *iaas.SecurityGroup) error { @@ -187,11 +163,7 @@ func outputResult(p *print.Printer, model *inputModel, resp *iaas.SecurityGroup) return nil default: - operationState := "Created" - if model.Async { - operationState = "Triggered creation of" - } - p.Outputf("%s security group %q\n", operationState, model.Name) + p.Outputf("Created security group %q\n", *model.Name) return nil } } diff --git a/internal/cmd/beta/security-group/create/create_test.go b/internal/cmd/beta/security-group/create/create_test.go index 1ecfce82e..1833e5c57 100644 --- a/internal/cmd/beta/security-group/create/create_test.go +++ b/internal/cmd/beta/security-group/create/create_test.go @@ -2,7 +2,6 @@ package create import ( "context" - "strings" "testing" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" @@ -25,7 +24,7 @@ var ( testProjectId = uuid.NewString() testName = "new-security-group" testDescription = "a test description" - testLabels = map[string]any{ + testLabels = map[string]string{ "fooKey": "fooValue", "barKey": "barValue", "bazKey": "bazValue", @@ -35,11 +34,11 @@ var ( func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { flagValues := map[string]string{ - projectIdFlag: testProjectId, - "description": testDescription, - "labels": "fooKey=fooValue,barKey=barValue,bazKey=bazValue", - "stateful": "true", - "name": testName, + projectIdFlag: testProjectId, + descriptionFlag: testDescription, + labelsFlag: "fooKey=fooValue,barKey=barValue,bazKey=bazValue", + statefulFlag: "true", + nameFlag: testName, } for _, mod := range mods { mod(flagValues) @@ -50,10 +49,10 @@ func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]st func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { model := &inputModel{ GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectId, Verbosity: globalflags.VerbosityDefault}, - Labels: testLabels, - Description: testDescription, - Name: testName, - Stateful: testStateful, + Labels: &testLabels, + Description: &testDescription, + Name: &testName, + Stateful: &testStateful, } for _, mod := range mods { mod(model) @@ -61,11 +60,22 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { return model } +func toStringAnyMapPtr(m map[string]string) map[string]any { + if m == nil { + return nil + } + result := map[string]any{} + for k, v := range m { + result[k] = v + } + return result +} func fixtureRequest(mods ...func(request *iaas.ApiCreateSecurityGroupRequest)) iaas.ApiCreateSecurityGroupRequest { request := testClient.CreateSecurityGroup(testCtx, testProjectId) + request = request.CreateSecurityGroupPayload(iaas.CreateSecurityGroupPayload{ Description: &testDescription, - Labels: &testLabels, + Labels: utils.Ptr(toStringAnyMapPtr(testLabels)), Name: &testName, Rules: nil, Stateful: &testStateful, @@ -118,68 +128,40 @@ func TestParseInput(t *testing.T) { { description: "name missing", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - delete(flagValues, "name") - }), - isValid: false, - }, - { - description: "name too long", - flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues["name"] = strings.Repeat("toolong", 1000) - }), - isValid: false, - }, - { - description: "description too long", - flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues["description"] = strings.Repeat("toolong", 1000) + delete(flagValues, nameFlag) }), isValid: false, }, { description: "no labels", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - delete(flagValues, "labels") + delete(flagValues, labelsFlag) }), isValid: true, expectedModel: fixtureInputModel(func(model *inputModel) { - model.Labels = map[string]any{} + model.Labels = nil }), }, { description: "single label", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues["labels"] = "foo=bar" + flagValues[labelsFlag] = "foo=bar" }), isValid: true, expectedModel: fixtureInputModel(func(model *inputModel) { - model.Labels = map[string]any{ + model.Labels = &map[string]string{ "foo": "bar", } }), }, - { - description: "malformed labels 1", - flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues["labels"] = "foo=bar=baz" - }), - isValid: false, - }, - { - description: "malformed labels 2", - flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues["labels"] = "foobarbaz" - }), - isValid: false, - }, { description: "stateless security group", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues["stateful"] = "false" + flagValues[statefulFlag] = "false" }), isValid: true, expectedModel: fixtureInputModel(func(model *inputModel) { - model.Stateful = false + model.Stateful = utils.Ptr(false) }), }, } @@ -256,12 +238,12 @@ func TestBuildRequest(t *testing.T) { { description: "stateless security group", model: fixtureInputModel(func(model *inputModel) { - model.Stateful = false + model.Stateful = utils.Ptr(false) }), expectedRequest: fixtureRequest(func(request *iaas.ApiCreateSecurityGroupRequest) { *request = request.CreateSecurityGroupPayload(iaas.CreateSecurityGroupPayload{ Description: &testDescription, - Labels: &testLabels, + Labels: utils.Ptr(toStringAnyMapPtr(testLabels)), Name: &testName, Stateful: utils.Ptr(false), }) diff --git a/internal/cmd/beta/security-group/delete/delete.go b/internal/cmd/beta/security-group/delete/delete.go index 5cdfd64e5..4c93ae79d 100644 --- a/internal/cmd/beta/security-group/delete/delete.go +++ b/internal/cmd/beta/security-group/delete/delete.go @@ -3,13 +3,11 @@ package delete import ( "context" "fmt" - "strings" "github.com/spf13/cobra" "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/iaas/client" @@ -22,112 +20,62 @@ type inputModel struct { Id string } -const argNameGroupId = "groupId" +const groupIdArg = "GROUP_ID" func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: "delete", - Short: "delete a security group", - Long: "delete a security group by its internal id", - Args: args.SingleArg(argNameGroupId, utils.ValidateUUID), + Short: "Deletes a security group", + Long: "Deletes a security group by its internal ID.", + Args: args.SingleArg(groupIdArg, utils.ValidateUUID), Example: examples.Build( - examples.NewExample(`delete a named group`, `$ stackit beta security-group delete 43ad419a-c68b-4911-87cd-e05752ac1e31`), + examples.NewExample(`Delete a named group with ID "xxx"`, `$ stackit beta security-group delete xxx`), ), RunE: func(cmd *cobra.Command, args []string) error { - return executeDelete(cmd, p, args) - }, - } - - return cmd -} - -func executeDelete(cmd *cobra.Command, p *print.Printer, args []string) error { - p.Info("executing delete command") - ctx := context.Background() - model, err := parseInput(p, cmd, args) - if err != nil { - return err - } + 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 - } + // 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 delete the security group %q?", model.Id) - err = p.PromptForConfirmation(prompt) - if err != nil { - return err - } - } + if !model.AssumeYes { + prompt := fmt.Sprintf("Are you sure you want to delete the security group %q?", model.Id) + err = p.PromptForConfirmation(prompt) + if err != nil { + return err + } + } - // Call API - request := buildRequest(ctx, model, apiClient) + // Call API + request := buildRequest(ctx, model, apiClient) - operationState := "Enabled" - if model.Async { - operationState = "Triggered security group deletion" - } - p.Info("%s security group %q for %q\n", operationState, model.Id, model.ProjectId) + if err := request.Execute(); err != nil { + return fmt.Errorf("delete security group: %w", err) + } + p.Info("Deleted security group %q for %q\n", model.Id, model.ProjectId) - if err := request.Execute(); err != nil { - return fmt.Errorf("delete security group: %w", err) + return nil + }, } - return nil + return cmd } -func parseInput(p *print.Printer, cmd *cobra.Command, args []string) (*inputModel, error) { +func parseInput(p *print.Printer, cmd *cobra.Command, cliArgs []string) (*inputModel, error) { globalFlags := globalflags.Parse(p, cmd) if globalFlags.ProjectId == "" { return nil, &errors.ProjectIdError{} } - if err := cmd.ValidateArgs(args); err != nil { - return nil, &errors.ArgValidationError{ - Arg: argNameGroupId, - Details: fmt.Sprintf("arg validation failed: %v", err), - } - } - - if len(args) != 1 { - return nil, &errors.ArgValidationError{ - Arg: argNameGroupId, - Details: "wrong number of arguments", - } - } - - name := flags.FlagToStringValue(p, cmd, "name") - if len(name) >= 64 { - return nil, &errors.ArgValidationError{ - Arg: "invalid name", - Details: "name exceeds 63 characters in length", - } - } - - labels := make(map[string]any) - for _, label := range flags.FlagToStringSliceValue(p, cmd, "labels") { - parts := strings.Split(label, "=") - if len(parts) != 2 { - return nil, &errors.ArgValidationError{ - Arg: "labels", - Details: "invalid label declaration. Must be in the form =", - } - } - labels[parts[0]] = parts[1] - } - description := flags.FlagToStringValue(p, cmd, "description") - if len(description) >= 128 { - return nil, &errors.ArgValidationError{ - Arg: "invalid description", - Details: "description exceeds 127 characters in length", - } - } model := inputModel{ GlobalFlagModel: globalFlags, - Id: args[0], + Id: cliArgs[0], } if p.IsVerbosityDebug() { diff --git a/internal/cmd/beta/security-group/delete/delete_test.go b/internal/cmd/beta/security-group/delete/delete_test.go index 8f2d36b23..ac6e3f425 100644 --- a/internal/cmd/beta/security-group/delete/delete_test.go +++ b/internal/cmd/beta/security-group/delete/delete_test.go @@ -84,28 +84,28 @@ func TestParseInput(t *testing.T) { }, { description: "no arguments", - flagValues: fixtureFlagValues(), - args: nil, - isValid: false, + flagValues: fixtureFlagValues(), + args: nil, + isValid: false, }, { description: "multiple arguments", - flagValues: fixtureFlagValues(), - args: []string{"foo","bar"}, - isValid: false, + flagValues: fixtureFlagValues(), + args: []string{"foo", "bar"}, + isValid: false, }, { description: "invalid group id", - flagValues: fixtureFlagValues(), - args: []string{"foo"}, - isValid: false, + flagValues: fixtureFlagValues(), + args: []string{"foo"}, + isValid: false, }, } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { p := print.NewPrinter() - cmd:=NewCmd(p) + cmd := NewCmd(p) err := globalflags.Configure(cmd.Flags()) if err != nil { t.Fatalf("configure global flags: %v", err) @@ -122,6 +122,12 @@ func TestParseInput(t *testing.T) { } } + if err := cmd.ValidateArgs(tt.args); err != nil { + if !tt.isValid { + return + } + } + err = cmd.ValidateRequiredFlags() if err != nil { if !tt.isValid { diff --git a/internal/cmd/beta/security-group/describe/describe.go b/internal/cmd/beta/security-group/describe/describe.go index ee0f497fc..68cd63eff 100644 --- a/internal/cmd/beta/security-group/describe/describe.go +++ b/internal/cmd/beta/security-group/describe/describe.go @@ -24,76 +24,63 @@ type inputModel struct { SecurityGroupId string } -const argNameGroupId = "argGroupId" +const groupIdArg = "GROUP_ID" func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: "describe", - Short: "describe security groups", - Long: "describe security groups", - Args: args.SingleArg(argNameGroupId, utils.ValidateUUID), + Short: "Describes security groups", + Long: "Describes security groups by its internal ID.", + Args: args.SingleArg(groupIdArg, utils.ValidateUUID), Example: examples.Build( - examples.NewExample(`describe an existing group`, `$ stackit beta security-group describe 9e9c44fe-eb9a-4d45-bf08-365e961845d1`), + examples.NewExample(`Describe group "xxx"`, `$ stackit beta security-group describe xxx`), ), RunE: func(cmd *cobra.Command, args []string) error { - return executeDescribe(cmd, p, args) - }, - } - - return cmd -} + ctx := context.Background() + model, err := parseInput(p, cmd, args) + if err != nil { + return err + } -func executeDescribe(cmd *cobra.Command, p *print.Printer, args []string) error { - p.Info("executing describe command") - 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 + } - // Configure API client - apiClient, err := client.ConfigureClient(p) - if err != nil { - return err - } + // Call API + request := buildRequest(ctx, model, apiClient) - // Call API - request := buildRequest(ctx, model, apiClient) + group, err := request.Execute() + if err != nil { + return fmt.Errorf("get security group: %w", err) + } - p.Info("security group %q for %q\n", model.SecurityGroupId, model.ProjectId) + if err := outputResult(p, model, group); err != nil { + return err + } - group, err := request.Execute() - if err != nil { - return fmt.Errorf("get security group: %w", err) - } - if err := outputResult(p, model, group); err != nil { - return err + return nil + }, } - return nil + return cmd } func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiGetSecurityGroupRequest { request := apiClient.GetSecurityGroup(ctx, model.ProjectId, model.SecurityGroupId) return request - } -func parseInput(p *print.Printer, cmd *cobra.Command, args []string) (*inputModel, error) { +func parseInput(p *print.Printer, cmd *cobra.Command, cliArgs []string) (*inputModel, error) { globalFlags := globalflags.Parse(p, cmd) if globalFlags.ProjectId == "" { return nil, &errors.ProjectIdError{} } - if err := cmd.ValidateArgs(args); err != nil { - return nil, &errors.ArgValidationError{ - Arg: argNameGroupId, - Details: fmt.Sprintf("argument validation failed: %v", err), - } - } model := inputModel{ GlobalFlagModel: globalFlags, - SecurityGroupId: args[0], + SecurityGroupId: cliArgs[0], } if p.IsVerbosityDebug() { @@ -143,17 +130,12 @@ func outputResult(p *print.Printer, model *inputModel, resp *iaas.SecurityGroup) table.AddSeparator() } - if labels := resp.Labels; labels != nil { - var builder strings.Builder - for k, v := range *labels { - builder.WriteString(fmt.Sprintf("%s=%s ", k, v)) + if resp.Labels != nil && len(*resp.Labels) > 0 { + labels := []string{} + for key, value := range *resp.Labels { + labels = append(labels, fmt.Sprintf("%s: %s", key, value)) } - table.AddRow("LABELS", builder.String()) - table.AddSeparator() - } - - if stateful := resp.Stateful; stateful != nil { - table.AddRow("STATEFUL", *stateful) + table.AddRow("LABELS", strings.Join(labels, "\n")) table.AddSeparator() } diff --git a/internal/cmd/beta/security-group/describe/describe_test.go b/internal/cmd/beta/security-group/describe/describe_test.go index a1c0ac80e..13a98fc84 100644 --- a/internal/cmd/beta/security-group/describe/describe_test.go +++ b/internal/cmd/beta/security-group/describe/describe_test.go @@ -65,13 +65,13 @@ func TestParseInput(t *testing.T) { description: "base", flagValues: fixtureFlagValues(), expectedModel: fixtureInputModel(), - args: testSecurityGroupId, + args: testSecurityGroupId, isValid: true, }, { description: "no values", flagValues: map[string]string{}, - args: testSecurityGroupId, + args: testSecurityGroupId, isValid: false, }, { @@ -79,7 +79,7 @@ func TestParseInput(t *testing.T) { flagValues: fixtureFlagValues(func(flagValues map[string]string) { delete(flagValues, projectIdFlag) }), - args: testSecurityGroupId, + args: testSecurityGroupId, isValid: false, }, { @@ -87,7 +87,7 @@ func TestParseInput(t *testing.T) { flagValues: fixtureFlagValues(func(flagValues map[string]string) { flagValues[projectIdFlag] = "" }), - args: testSecurityGroupId, + args: testSecurityGroupId, isValid: false, }, { @@ -95,7 +95,7 @@ func TestParseInput(t *testing.T) { flagValues: fixtureFlagValues(func(flagValues map[string]string) { flagValues[projectIdFlag] = "invalid-uuid" }), - args: testSecurityGroupId, + args: testSecurityGroupId, isValid: false, }, { @@ -141,6 +141,12 @@ func TestParseInput(t *testing.T) { t.Fatalf("error validating flags: %v", err) } + if err := cmd.ValidateArgs(tt.args); err != nil { + if !tt.isValid { + return + } + } + model, err := parseInput(p, cmd, tt.args) if err != nil { if !tt.isValid { diff --git a/internal/cmd/beta/security-group/list/list.go b/internal/cmd/beta/security-group/list/list.go index f3630166e..9f7b38d80 100644 --- a/internal/cmd/beta/security-group/list/list.go +++ b/internal/cmd/beta/security-group/list/list.go @@ -4,10 +4,10 @@ import ( "context" "encoding/json" "fmt" - "strings" "github.com/goccy/go-yaml" "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -22,21 +22,58 @@ import ( type inputModel struct { *globalflags.GlobalFlagModel - Labels *string + LabelSelector *string } +const ( + labelSelectorFlag = "label-selector" +) + func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: "list", - Short: "list security groups", - Long: "list security groups", + Short: "Lists security groups", + Long: "Lists security groups by its internal ID.", Args: args.NoArgs, Example: examples.Build( - examples.NewExample(`list all groups`, `$ stackit beta security-group list`), - examples.NewExample(`list groups with labels`, `$ stackit beta security-group list --labels label1=value1,label2=value2`), + examples.NewExample(`List all groups`, `$ stackit beta security-group list`), + examples.NewExample(`List groups with labels`, `$ stackit beta security-group list --labels label1=value1,label2=value2`), ), - RunE: func(cmd *cobra.Command, args []string) error { - return executeList(cmd, p, args) + RunE: func(cmd *cobra.Command, _ []string) error { + ctx := context.Background() + model, err := parseInput(p, cmd) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(p) + if err != nil { + return err + } + + projectLabel, err := projectname.GetProjectName(ctx, p, cmd) + if err != nil { + p.Debug(print.ErrorLevel, "get project name: %v", err) + projectLabel = model.ProjectId + } + + // Call API + request := buildRequest(ctx, model, apiClient) + + response, err := request.Execute() + if err != nil { + return fmt.Errorf("list security group: %w", err) + } + + if items := response.GetItems(); items == nil || len(*items) == 0 { + p.Info("No security groups found for project %q", projectLabel) + if err := outputResult(p, model.OutputFormat, *items); err != nil { + return fmt.Errorf("output security groups: %w", err) + } + } + + return nil }, } @@ -45,42 +82,7 @@ func NewCmd(p *print.Printer) *cobra.Command { } func configureFlags(cmd *cobra.Command) { - cmd.Flags().String("labels", "", "a list of labels in the form =") -} - -func executeList(cmd *cobra.Command, p *print.Printer, _ []string) error { - p.Info("executing list command") - ctx := context.Background() - model, err := parseInput(p, cmd) - if err != nil { - return err - } - - // Configure API client - apiClient, err := client.ConfigureClient(p) - if err != nil { - return err - } - - projectLabel, err := projectname.GetProjectName(ctx, p, cmd) - if err != nil { - p.Debug(print.ErrorLevel, "get project name: %v", err) - projectLabel = model.ProjectId - } - - // Call API - request := buildRequest(ctx, model, apiClient) - response, err := request.Execute() - if err != nil { - return fmt.Errorf("list security group: %w", err) - } - if items := response.GetItems(); items == nil || len(*items) == 0 { - p.Info("no security groups found for %q", projectLabel) - } else { - outputResult(p, model.OutputFormat, *items) - } - - return nil + cmd.Flags().String(labelSelectorFlag, "", "Filter by label") } func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { @@ -91,7 +93,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { model := inputModel{ GlobalFlagModel: globalFlags, - Labels: flags.FlagToStringPointer(p, cmd, "labels"), + LabelSelector: flags.FlagToStringPointer(p, cmd, labelSelectorFlag), } if p.IsVerbosityDebug() { @@ -108,12 +110,11 @@ func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiListSecurityGroupsRequest { request := apiClient.ListSecurityGroups(ctx, model.ProjectId) - if model.Labels != nil { - request = request.LabelSelector(*model.Labels) + if model.LabelSelector != nil { + request = request.LabelSelector(*model.LabelSelector) } return request - } func outputResult(p *print.Printer, outputFormat string, items []iaas.SecurityGroup) error { switch outputFormat { @@ -135,9 +136,9 @@ func outputResult(p *print.Printer, outputFormat string, items []iaas.SecurityGr return nil default: table := tables.NewTable() - table.SetHeader("ID", "NAME", "LABELS", "STATEFUL") + table.SetHeader("ID", "NAME", "STATEFUL") for _, item := range items { - table.AddRow(ptrString(item.Id), ptrString(item.Name), concatLabels(item.Labels), ptrString(item.Stateful)) + table.AddRow(utils.PtrString(item.Id), utils.PtrString(item.Name), utils.PtrString(item.Stateful)) } err := table.Display(p) if err != nil { @@ -147,21 +148,3 @@ func outputResult(p *print.Printer, outputFormat string, items []iaas.SecurityGr return nil } } - -func ptrString[T any](t*T) string { - if t != nil { - return fmt.Sprintf("%v",*t) - } - return "" -} - -func concatLabels(item *map[string]any) string { - if item == nil { - return "" - } - var builder strings.Builder - for k, v := range *item { - builder.WriteString(fmt.Sprintf("%s=%v ", k, v)) - } - return builder.String() -} diff --git a/internal/cmd/beta/security-group/list/list_test.go b/internal/cmd/beta/security-group/list/list_test.go index da88a69d2..c6f3a0c93 100644 --- a/internal/cmd/beta/security-group/list/list_test.go +++ b/internal/cmd/beta/security-group/list/list_test.go @@ -23,13 +23,12 @@ var ( testClient = &iaas.APIClient{} testProjectId = uuid.NewString() testLabels = "fooKey=fooValue,barKey=barValue,bazKey=bazValue" - testStateful = true ) func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { flagValues := map[string]string{ - projectIdFlag: testProjectId, - "labels": testLabels, + projectIdFlag: testProjectId, + labelSelectorFlag: testLabels, } for _, mod := range mods { mod(flagValues) @@ -40,7 +39,7 @@ func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]st func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { model := &inputModel{ GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectId, Verbosity: globalflags.VerbosityDefault}, - Labels: utils.Ptr(testLabels), + LabelSelector: utils.Ptr(testLabels), } for _, mod := range mods { mod(model) @@ -99,21 +98,21 @@ func TestParseInput(t *testing.T) { { description: "no labels", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - delete(flagValues, "labels") + delete(flagValues, labelSelectorFlag) }), isValid: true, expectedModel: fixtureInputModel(func(model *inputModel) { - model.Labels = nil + model.LabelSelector = nil }), }, { description: "single label", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues["labels"] = "foo=bar" + flagValues[labelSelectorFlag] = "foo=bar" }), isValid: true, expectedModel: fixtureInputModel(func(model *inputModel) { - model.Labels = utils.Ptr("foo=bar") + model.LabelSelector = utils.Ptr("foo=bar") }), }, } @@ -176,7 +175,7 @@ func TestBuildRequest(t *testing.T) { { description: "no labels", model: fixtureInputModel(func(model *inputModel) { - model.Labels = utils.Ptr("") + model.LabelSelector = utils.Ptr("") }), expectedRequest: fixtureRequest(func(request *iaas.ApiListSecurityGroupsRequest) { *request = request.LabelSelector("") @@ -185,7 +184,7 @@ func TestBuildRequest(t *testing.T) { { description: "single label", model: fixtureInputModel(func(model *inputModel) { - model.Labels = utils.Ptr("foo=bar") + model.LabelSelector = utils.Ptr("foo=bar") }), expectedRequest: fixtureRequest(func(request *iaas.ApiListSecurityGroupsRequest) { *request = request.LabelSelector("foo=bar") diff --git a/internal/cmd/beta/security-group/security_group.go b/internal/cmd/beta/security-group/security_group.go index 64b932090..2dea72f6e 100644 --- a/internal/cmd/beta/security-group/security_group.go +++ b/internal/cmd/beta/security-group/security_group.go @@ -17,8 +17,8 @@ import ( func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: "security-group", - Short: "manage security groups.", - Long: "manage the lifecycle of security groups.", + Short: "Manage security groups", + Long: "Manage the lifecycle of security groups.", Args: args.NoArgs, Run: utils.CmdHelp, } diff --git a/internal/cmd/beta/security-group/update/update.go b/internal/cmd/beta/security-group/update/update.go index f8771a5d8..36fce2969 100644 --- a/internal/cmd/beta/security-group/update/update.go +++ b/internal/cmd/beta/security-group/update/update.go @@ -3,7 +3,6 @@ package update import ( "context" "fmt" - "strings" "github.com/spf13/cobra" "github.com/stackitcloud/stackit-cli/internal/pkg/args" @@ -20,132 +19,84 @@ import ( type inputModel struct { *globalflags.GlobalFlagModel - Labels *map[string]any + Labels *map[string]string Description *string Name *string SecurityGroupId string } -const argNameGroupId = "argGroupId" +const groupNameArg = "GROUP_ID" + +const ( + nameArg = "name" + descriptionArg = "description" + labelsArg = "labels" +) func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: "update", - Short: "Update a security group", - Long: "Update a named security group", - Args: args.SingleArg(argNameGroupId, utils.ValidateUUID), + Short: "Updates a security group", + Long: "Updates a named security group", + Args: args.SingleArg(groupNameArg, utils.ValidateUUID), Example: examples.Build( - examples.NewExample(`Update the name of a group`, `$ stackit beta security-group update 541d122f-0a5f-4bb0-94b9-b1ccbd7ba776 --name my-new-name`), - examples.NewExample(`Update the labels of a group`, `$ stackit beta security-group update 541d122f-0a5f-4bb0-94b9-b1ccbd7ba776 --labels label1=value1,label2=value2`), + examples.NewExample(`Update the name of group "xxx"`, `$ stackit beta security-group update xxx --name my-new-name`), + examples.NewExample(`Update the labels of group "xxx"`, `$ stackit beta security-group update xxx --labels label1=value1,label2=value2`), ), RunE: func(cmd *cobra.Command, args []string) error { - return executeUpdate(cmd, p, args) - }, - } + ctx := context.Background() + model, err := parseInput(p, cmd, args) + if err != nil { + return err + } - configureFlags(cmd) - return cmd -} + // Configure API client + apiClient, err := client.ConfigureClient(p) + if err != nil { + return err + } -func configureFlags(cmd *cobra.Command) { - cmd.Flags().String("name", "", "the name of the security group. Must be <= 63 chars") - cmd.Flags().String("description", "", "an optional description of the security group. Must be <= 127 chars") - cmd.Flags().StringSlice("labels", nil, "a list of labels in the form =") -} + projectLabel, err := projectname.GetProjectName(ctx, p, cmd) + if err != nil { + p.Debug(print.ErrorLevel, "get project name: %v", err) + projectLabel = model.ProjectId + } -func executeUpdate(cmd *cobra.Command, p *print.Printer, args []string) error { - p.Info("executing update command") - ctx := context.Background() - model, err := parseInput(p, cmd, args) - if err != nil { - return err - } + // Call API + req := buildRequest(ctx, model, apiClient) - // Configure API client - apiClient, err := client.ConfigureClient(p) - if err != nil { - return err - } + _, err = req.Execute() + if err != nil { + return fmt.Errorf("update security group: %w", err) + } + p.Info("Updated security group \"%v\" for %q\n", model.Name, projectLabel) - projectLabel, err := projectname.GetProjectName(ctx, p, cmd) - if err != nil { - p.Debug(print.ErrorLevel, "get project name: %v", err) - projectLabel = model.ProjectId + return nil + }, } - // Call API - req := buildRequest(ctx, model, apiClient) - _, err = req.Execute() - if err != nil { - return fmt.Errorf("^date security group: %w", err) - } + configureFlags(cmd) + return cmd +} - operationState := "Enabled" - if model.Async { - operationState = "Triggered update of" - } - p.Info("%s security group \"%v\" for %q\n", operationState, model.Name, projectLabel) - return nil +func configureFlags(cmd *cobra.Command) { + cmd.Flags().String(nameArg, "", "The name of the security group.") + cmd.Flags().String(descriptionArg, "", "An optional description of the security group.") + cmd.Flags().StringToString(labelsArg, nil, "Labels are key-value string pairs which can be attached to a network-interface. E.g. '--labels key1=value1,key2=value2,...'") } -func parseInput(p *print.Printer, cmd *cobra.Command, args []string) (*inputModel, error) { +func parseInput(p *print.Printer, cmd *cobra.Command, cliArgs []string) (*inputModel, error) { globalFlags := globalflags.Parse(p, cmd) if globalFlags.ProjectId == "" { return nil, &errors.ProjectIdError{} } - if err := cmd.ValidateArgs(args); err != nil { - return nil, &errors.ArgValidationError{ - Arg: argNameGroupId, - Details: fmt.Sprintf("argument validation failed: %v", err), - } - } - model := inputModel{ GlobalFlagModel: globalFlags, - } - if len(args) != 1 { - return nil, &errors.ArgValidationError{ - Arg: argNameGroupId, - Details: "wrong number of arguments", - } - } - model.SecurityGroupId = args[0] - - if cmd.Flags().Lookup("name").Changed { - name := flags.FlagToStringValue(p, cmd, "name") - if len(name) >= 64 { - return nil, &errors.ArgValidationError{ - Arg: "invalid name", - Details: "name exceeds 63 characters in length", - } - } - model.Name = &name - } - - if cmd.Flags().Lookup("labels").Changed { - labels := make(map[string]any) - for _, label := range flags.FlagToStringSliceValue(p, cmd, "labels") { - parts := strings.Split(label, "=") - if len(parts) != 2 { - return nil, &errors.ArgValidationError{ - Arg: "labels", - Details: "invalid label declaration. Must be in the form =", - } - } - labels[parts[0]] = parts[1] - } - model.Labels = &labels - } - if cmd.Flags().Lookup("description").Changed { - description := flags.FlagToStringValue(p, cmd, "description") - if len(description) >= 128 { - return nil, &errors.ArgValidationError{ - Arg: "invalid description", - Details: "description exceeds 127 characters in length", - } - } - model.Description = &description + Labels: flags.FlagToStringToStringPointer(p, cmd, labelsArg), + Description: flags.FlagToStringPointer(p, cmd, descriptionArg), + Name: flags.FlagToStringPointer(p, cmd, nameArg), + SecurityGroupId: cliArgs[0], } if p.IsVerbosityDebug() { @@ -164,10 +115,17 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APICli request := apiClient.UpdateSecurityGroup(ctx, model.ProjectId, model.SecurityGroupId) payload := iaas.NewUpdateSecurityGroupPayload() payload.Description = model.Description - payload.Labels = model.Labels + var labelsMap *map[string]any + if model.Labels != nil && len(*model.Labels) > 0 { + // convert map[string]string to map[string]interface{} + labelsMap = utils.Ptr(map[string]interface{}{}) + for k, v := range *model.Labels { + (*labelsMap)[k] = v + } + } + payload.Labels = labelsMap payload.Name = model.Name request = request.UpdateSecurityGroupPayload(*payload) return request - } diff --git a/internal/cmd/beta/security-group/update/update_test.go b/internal/cmd/beta/security-group/update/update_test.go index 5420b3710..c192cfb8d 100644 --- a/internal/cmd/beta/security-group/update/update_test.go +++ b/internal/cmd/beta/security-group/update/update_test.go @@ -2,11 +2,11 @@ package update import ( "context" - "strings" "testing" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" @@ -25,19 +25,30 @@ var ( testGroupId = []string{uuid.NewString()} testName = "new-security-group" testDescription = "a test description" - testLabels = map[string]any{ + testLabels = map[string]string{ "fooKey": "fooValue", "barKey": "barValue", "bazKey": "bazValue", } ) +func toStringAnyMapPtr(m map[string]string) map[string]any { + if m == nil { + return nil + } + result := map[string]any{} + for k, v := range m { + result[k] = v + } + return result +} + func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { flagValues := map[string]string{ - projectIdFlag: testProjectId, - "description": testDescription, - "labels": "fooKey=fooValue,barKey=barValue,bazKey=bazValue", - "name": testName, + projectIdFlag: testProjectId, + descriptionArg: testDescription, + labelsArg: "fooKey=fooValue,barKey=barValue,bazKey=bazValue", + nameArg: testName, } for _, mod := range mods { mod(flagValues) @@ -63,7 +74,7 @@ func fixtureRequest(mods ...func(request *iaas.ApiUpdateSecurityGroupRequest)) i request := testClient.UpdateSecurityGroup(testCtx, testProjectId, testGroupId[0]) request = request.UpdateSecurityGroupPayload(iaas.UpdateSecurityGroupPayload{ Description: &testDescription, - Labels: &testLabels, + Labels: utils.Ptr(toStringAnyMapPtr(testLabels)), Name: &testName, }) for _, mod := range mods { @@ -127,7 +138,7 @@ func TestParseInput(t *testing.T) { { description: "no name passed", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - delete(flagValues, "name") + delete(flagValues, nameArg) }), args: testGroupId, expectedModel: fixtureInputModel(func(model *inputModel) { @@ -138,7 +149,7 @@ func TestParseInput(t *testing.T) { { description: "no description passed", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - delete(flagValues, "description") + delete(flagValues, descriptionArg) }), args: testGroupId, expectedModel: fixtureInputModel(func(model *inputModel) { @@ -146,26 +157,10 @@ func TestParseInput(t *testing.T) { }), isValid: true, }, - { - description: "name too long", - flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues["name"] = strings.Repeat("toolong", 1000) - }), - args: testGroupId, - isValid: false, - }, - { - description: "description too long", - flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues["description"] = strings.Repeat("toolong", 1000) - }), - args: testGroupId, - isValid: false, - }, { description: "no labels", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - delete(flagValues, "labels") + delete(flagValues, labelsArg) }), args: testGroupId, expectedModel: fixtureInputModel(func(model *inputModel) { @@ -176,32 +171,16 @@ func TestParseInput(t *testing.T) { { description: "single label", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues["labels"] = "foo=bar" + flagValues[labelsArg] = "foo=bar" }), args: testGroupId, isValid: true, expectedModel: fixtureInputModel(func(model *inputModel) { - model.Labels = &map[string]any{ + model.Labels = &map[string]string{ "foo": "bar", } }), }, - { - description: "malformed labels 1", - flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues["labels"] = "foo=bar=baz" - }), - args: testGroupId, - isValid: false, - }, - { - description: "malformed labels 2", - flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues["labels"] = "foobarbaz" - }), - args: testGroupId, - isValid: false, - }, { description: "no group id passed", flagValues: fixtureFlagValues(), @@ -229,8 +208,8 @@ func TestParseInput(t *testing.T) { if err := globalflags.Configure(cmd.Flags()); err != nil { t.Errorf("cannot configure global flags: %v", err) } - for flag, value := range tt.flagValues { + for flag, value := range tt.flagValues { if err := cmd.Flags().Set(flag, value); err != nil { if !tt.isValid { return @@ -246,6 +225,12 @@ func TestParseInput(t *testing.T) { t.Fatalf("error validating flags: %v", err) } + if err := cmd.ValidateArgs(tt.args); err != nil { + if !tt.isValid { + return + } + } + model, err := parseInput(p, cmd, tt.args) if err != nil { if !tt.isValid { diff --git a/internal/cmd/beta/security-group/utils/utils.go b/internal/cmd/beta/security-group/utils/utils.go new file mode 100644 index 000000000..f34ee657d --- /dev/null +++ b/internal/cmd/beta/security-group/utils/utils.go @@ -0,0 +1,12 @@ +package utils + +import "fmt" + +// PtrString creates a string representation of a passed object pointer or returns +// an empty string, if the passed object is _nil_. +func PtrString[T any](t *T) string { + if t != nil { + return fmt.Sprintf("%v", *t) + } + return "" +} From a90f3c57ab92bd19193343cf77bc025576c05a50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Schmitz?= <152157960+bahkauv70@users.noreply.github.com> Date: Tue, 10 Dec 2024 08:53:50 +0100 Subject: [PATCH 22/28] feature: refresh documentation --- docs/stackit_beta_security-group_create.md | 18 +++++++++--------- docs/stackit_beta_security-group_delete.md | 8 ++++---- docs/stackit_beta_security-group_describe.md | 8 ++++---- docs/stackit_beta_security-group_list.md | 8 ++++---- docs/stackit_beta_security-group_update.md | 20 ++++++++++---------- 5 files changed, 31 insertions(+), 31 deletions(-) diff --git a/docs/stackit_beta_security-group_create.md b/docs/stackit_beta_security-group_create.md index ce9f91bd0..e2ebd9b21 100644 --- a/docs/stackit_beta_security-group_create.md +++ b/docs/stackit_beta_security-group_create.md @@ -1,10 +1,10 @@ ## stackit beta security-group create -Create security groups +Creates security groups ### Synopsis -Create security groups. +Creates security groups. ``` stackit beta security-group create [flags] @@ -13,21 +13,21 @@ stackit beta security-group create [flags] ### Examples ``` - create a named group + Create a named group $ stackit beta security-group create --name my-new-group - create a named group with labels + Create a named group with labels $ stackit beta security-group create --name my-new-group --labels label1=value1,label2=value2 ``` ### Options ``` - --description string an optional description of the security group. Must be <= 127 chars - -h, --help Help for "stackit beta security-group create" - --labels strings Labels are key-value string pairs which can be attached to a network-interface. E.g. '--labels key1=value1,key2=value2,...' - --name string the name of the security group. Must be <= 63 chars - --stateful create a stateful or a stateless security group + --description string An optional description of the security group. + -h, --help Help for "stackit beta security-group create" + --labels stringToString Labels are key-value string pairs which can be attached to a network-interface. E.g. '--labels key1=value1,key2=value2,...' (default []) + --name string The name of the security group. + --stateful Create a stateful or a stateless security group ``` ### Options inherited from parent commands diff --git a/docs/stackit_beta_security-group_delete.md b/docs/stackit_beta_security-group_delete.md index 85d047775..dd3d7935f 100644 --- a/docs/stackit_beta_security-group_delete.md +++ b/docs/stackit_beta_security-group_delete.md @@ -1,10 +1,10 @@ ## stackit beta security-group delete -Delete a security group +Deletes a security group ### Synopsis -Delete a security group by its internal id. +Deletes a security group by its internal ID. ``` stackit beta security-group delete [flags] @@ -13,8 +13,8 @@ stackit beta security-group delete [flags] ### Examples ``` - delete a named group - $ stackit beta security-group delete 43ad419a-c68b-4911-87cd-e05752ac1e31 + Delete a named group with ID "xxx" + $ stackit beta security-group delete xxx ``` ### Options diff --git a/docs/stackit_beta_security-group_describe.md b/docs/stackit_beta_security-group_describe.md index e6b945f3d..b1856566d 100644 --- a/docs/stackit_beta_security-group_describe.md +++ b/docs/stackit_beta_security-group_describe.md @@ -1,10 +1,10 @@ ## stackit beta security-group describe -Describe security groups +Describes security groups ### Synopsis -Describe security groups. +Describes security groups by its internal ID. ``` stackit beta security-group describe [flags] @@ -13,8 +13,8 @@ stackit beta security-group describe [flags] ### Examples ``` - describe an existing group - $ stackit beta security-group describe 9e9c44fe-eb9a-4d45-bf08-365e961845d1 + Describe group "xxx" + $ stackit beta security-group describe xxx ``` ### Options diff --git a/docs/stackit_beta_security-group_list.md b/docs/stackit_beta_security-group_list.md index 053116257..3bffc83c6 100644 --- a/docs/stackit_beta_security-group_list.md +++ b/docs/stackit_beta_security-group_list.md @@ -1,10 +1,10 @@ ## stackit beta security-group list -List security groups +Lists security groups ### Synopsis -List security groups. +Lists security groups by its internal ID. ``` stackit beta security-group list [flags] @@ -13,10 +13,10 @@ stackit beta security-group list [flags] ### Examples ``` - list all groups + List all groups $ stackit beta security-group list - list groups with labels + List groups with labels $ stackit beta security-group list --labels label1=value1,label2=value2 ``` diff --git a/docs/stackit_beta_security-group_update.md b/docs/stackit_beta_security-group_update.md index 075204249..acba6414e 100644 --- a/docs/stackit_beta_security-group_update.md +++ b/docs/stackit_beta_security-group_update.md @@ -1,10 +1,10 @@ ## stackit beta security-group update -Update a security group +Updates a security group ### Synopsis -Update a named security group +Updates a named security group ``` stackit beta security-group update [flags] @@ -13,20 +13,20 @@ stackit beta security-group update [flags] ### Examples ``` - Update the name of a group - $ stackit beta security-group update 541d122f-0a5f-4bb0-94b9-b1ccbd7ba776 --name my-new-name + Update the name of group "xxx" + $ stackit beta security-group update xxx --name my-new-name - Update the labels of a group - $ stackit beta security-group update 541d122f-0a5f-4bb0-94b9-b1ccbd7ba776 --labels label1=value1,label2=value2 + Update the labels of group "xxx" + $ stackit beta security-group update xxx --labels label1=value1,label2=value2 ``` ### Options ``` - --description string an optional description of the security group. Must be <= 127 chars - -h, --help Help for "stackit beta security-group update" - --labels strings Labels are key-value string pairs which can be attached to a network-interface. E.g. '--labels key1=value1,key2=value2,...' - --name string the name of the security group. Must be <= 63 chars + --description string An optional description of the security group. + -h, --help Help for "stackit beta security-group update" + --labels stringToString Labels are key-value string pairs which can be attached to a network-interface. E.g. '--labels key1=value1,key2=value2,...' (default []) + --name string The name of the security group. ``` ### Options inherited from parent commands From dae9cd037100ef5751ff34955199815124e1492c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Schmitz?= <152157960+bahkauv70@users.noreply.github.com> Date: Tue, 10 Dec 2024 10:58:41 +0100 Subject: [PATCH 23/28] feature: fix a regression in list command --- internal/cmd/beta/security-group/list/list.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/cmd/beta/security-group/list/list.go b/internal/cmd/beta/security-group/list/list.go index 9f7b38d80..7f1fe371c 100644 --- a/internal/cmd/beta/security-group/list/list.go +++ b/internal/cmd/beta/security-group/list/list.go @@ -68,6 +68,7 @@ func NewCmd(p *print.Printer) *cobra.Command { if items := response.GetItems(); items == nil || len(*items) == 0 { p.Info("No security groups found for project %q", projectLabel) + } else { if err := outputResult(p, model.OutputFormat, *items); err != nil { return fmt.Errorf("output security groups: %w", err) } From 1eaa75687a5bb2169c4c1af69cc6201e7931091c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Schmitz?= Date: Wed, 11 Dec 2024 16:50:50 +0100 Subject: [PATCH 24/28] feature: fix regressions --- .../cmd/beta/security-group/delete/delete.go | 21 ++++++++++++++----- .../beta/security-group/delete/delete_test.go | 2 +- internal/cmd/beta/security-group/list/list.go | 2 +- .../cmd/beta/security-group/update/update.go | 17 +++++++++++++-- .../beta/security-group/update/update_test.go | 2 +- 5 files changed, 34 insertions(+), 10 deletions(-) diff --git a/internal/cmd/beta/security-group/delete/delete.go b/internal/cmd/beta/security-group/delete/delete.go index 4c93ae79d..80258f6bc 100644 --- a/internal/cmd/beta/security-group/delete/delete.go +++ b/internal/cmd/beta/security-group/delete/delete.go @@ -10,6 +10,7 @@ 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/projectname" "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/stackitcloud/stackit-sdk-go/services/iaas" @@ -17,7 +18,7 @@ import ( type inputModel struct { *globalflags.GlobalFlagModel - Id string + SecurityGroupId string } const groupIdArg = "GROUP_ID" @@ -44,8 +45,18 @@ func NewCmd(p *print.Printer) *cobra.Command { return err } + projectLabel, err := projectname.GetProjectName(ctx, p, cmd) + if err != nil { + return fmt.Errorf("get project name: %w", err) + } + + securityGroupResp, err := apiClient.GetSecurityGroup(ctx, model.ProjectId, model.SecurityGroupId).Execute() + if err != nil { + return fmt.Errorf("get security group %q: %w", model.SecurityGroupId, err) + } + if !model.AssumeYes { - prompt := fmt.Sprintf("Are you sure you want to delete the security group %q?", model.Id) + prompt := fmt.Sprintf("Are you sure you want to delete the security group %q for %q?", *securityGroupResp.Name, projectLabel) err = p.PromptForConfirmation(prompt) if err != nil { return err @@ -58,7 +69,7 @@ func NewCmd(p *print.Printer) *cobra.Command { if err := request.Execute(); err != nil { return fmt.Errorf("delete security group: %w", err) } - p.Info("Deleted security group %q for %q\n", model.Id, model.ProjectId) + p.Info("Deleted security group %q for %q\n", *securityGroupResp.Name, projectLabel) return nil }, @@ -75,7 +86,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, cliArgs []string) (*inputM model := inputModel{ GlobalFlagModel: globalFlags, - Id: cliArgs[0], + SecurityGroupId: cliArgs[0], } if p.IsVerbosityDebug() { @@ -91,6 +102,6 @@ func parseInput(p *print.Printer, cmd *cobra.Command, cliArgs []string) (*inputM } func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiDeleteSecurityGroupRequest { - request := apiClient.DeleteSecurityGroup(ctx, model.ProjectId, model.Id) + request := apiClient.DeleteSecurityGroup(ctx, model.ProjectId, model.SecurityGroupId) return request } diff --git a/internal/cmd/beta/security-group/delete/delete_test.go b/internal/cmd/beta/security-group/delete/delete_test.go index ac6e3f425..7666e1585 100644 --- a/internal/cmd/beta/security-group/delete/delete_test.go +++ b/internal/cmd/beta/security-group/delete/delete_test.go @@ -37,7 +37,7 @@ func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]st func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { model := &inputModel{ GlobalFlagModel: &globalflags.GlobalFlagModel{ProjectId: testProjectId, Verbosity: globalflags.VerbosityDefault}, - Id: testGroupId, + SecurityGroupId: testGroupId, } for _, mod := range mods { mod(model) diff --git a/internal/cmd/beta/security-group/list/list.go b/internal/cmd/beta/security-group/list/list.go index 7f1fe371c..c74182a88 100644 --- a/internal/cmd/beta/security-group/list/list.go +++ b/internal/cmd/beta/security-group/list/list.go @@ -37,7 +37,7 @@ func NewCmd(p *print.Printer) *cobra.Command { Args: args.NoArgs, Example: examples.Build( examples.NewExample(`List all groups`, `$ stackit beta security-group list`), - examples.NewExample(`List groups with labels`, `$ stackit beta security-group list --labels label1=value1,label2=value2`), + examples.NewExample(`List groups with labels`, `$ stackit beta security-group list --label-selector label1=value1,label2=value2`), ), RunE: func(cmd *cobra.Command, _ []string) error { ctx := context.Background() diff --git a/internal/cmd/beta/security-group/update/update.go b/internal/cmd/beta/security-group/update/update.go index 36fce2969..92135ea04 100644 --- a/internal/cmd/beta/security-group/update/update.go +++ b/internal/cmd/beta/security-group/update/update.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/spf13/cobra" + cmd_utils "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -62,14 +63,22 @@ func NewCmd(p *print.Printer) *cobra.Command { projectLabel = model.ProjectId } + if !model.AssumeYes { + prompt := fmt.Sprintf("Are you sure you want to update the security group %q?", model.SecurityGroupId) + err = p.PromptForConfirmation(prompt) + if err != nil { + return err + } + } + // Call API req := buildRequest(ctx, model, apiClient) - _, err = req.Execute() + resp, err := req.Execute() if err != nil { return fmt.Errorf("update security group: %w", err) } - p.Info("Updated security group \"%v\" for %q\n", model.Name, projectLabel) + p.Info("Updated security group \"%v\" for %q\n", cmd_utils.PtrString(resp.Name), projectLabel) return nil }, @@ -99,6 +108,10 @@ func parseInput(p *print.Printer, cmd *cobra.Command, cliArgs []string) (*inputM SecurityGroupId: cliArgs[0], } + if model.Labels == nil && model.Description == nil && model.Name == nil { + return nil, fmt.Errorf("no flags have been passed") + } + if p.IsVerbosityDebug() { modelStr, err := print.BuildDebugStrFromInputModel(model) if err != nil { diff --git a/internal/cmd/beta/security-group/update/update_test.go b/internal/cmd/beta/security-group/update/update_test.go index c192cfb8d..f27cbfc25 100644 --- a/internal/cmd/beta/security-group/update/update_test.go +++ b/internal/cmd/beta/security-group/update/update_test.go @@ -104,7 +104,7 @@ func TestParseInput(t *testing.T) { projectIdFlag: testProjectId, }, args: testGroupId, - isValid: true, + isValid: false, expectedModel: fixtureInputModel(func(model *inputModel) { model.Labels = nil model.Name = nil From 0976afc4c0fc851ebdeb65e04ff2f345e6dbbc0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Schmitz?= Date: Thu, 12 Dec 2024 16:32:18 +0100 Subject: [PATCH 25/28] feature: implemented review suggestions --- internal/cmd/beta/security-group/delete/delete.go | 13 ++++++++----- internal/cmd/beta/security-group/list/list.go | 2 +- internal/cmd/beta/security-group/update/update.go | 12 +++++++++--- internal/cmd/beta/security-group/utils/utils.go | 12 ------------ internal/pkg/services/iaas/utils/utils_test.go | 2 ++ internal/pkg/utils/utils.go | 9 +++++++++ 6 files changed, 29 insertions(+), 21 deletions(-) delete mode 100644 internal/cmd/beta/security-group/utils/utils.go diff --git a/internal/cmd/beta/security-group/delete/delete.go b/internal/cmd/beta/security-group/delete/delete.go index 80258f6bc..580dbd96f 100644 --- a/internal/cmd/beta/security-group/delete/delete.go +++ b/internal/cmd/beta/security-group/delete/delete.go @@ -12,6 +12,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" + iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) @@ -47,16 +48,18 @@ func NewCmd(p *print.Printer) *cobra.Command { projectLabel, err := projectname.GetProjectName(ctx, p, cmd) if err != nil { - return fmt.Errorf("get project name: %w", err) + p.Debug(print.ErrorLevel, "get project name: %v", err) + projectLabel = model.ProjectId } - securityGroupResp, err := apiClient.GetSecurityGroup(ctx, model.ProjectId, model.SecurityGroupId).Execute() + groupLabel, err := iaasUtils.GetSecurityGroupName(ctx, apiClient, model.ProjectId, model.SecurityGroupId) if err != nil { - return fmt.Errorf("get security group %q: %w", model.SecurityGroupId, err) + p.Warn("get security group name: %v", err) + groupLabel = model.SecurityGroupId } if !model.AssumeYes { - prompt := fmt.Sprintf("Are you sure you want to delete the security group %q for %q?", *securityGroupResp.Name, projectLabel) + prompt := fmt.Sprintf("Are you sure you want to delete the security group %q for %q?", groupLabel, projectLabel) err = p.PromptForConfirmation(prompt) if err != nil { return err @@ -69,7 +72,7 @@ func NewCmd(p *print.Printer) *cobra.Command { if err := request.Execute(); err != nil { return fmt.Errorf("delete security group: %w", err) } - p.Info("Deleted security group %q for %q\n", *securityGroupResp.Name, projectLabel) + p.Info("Deleted security group %q for %q\n", groupLabel, projectLabel) return nil }, diff --git a/internal/cmd/beta/security-group/list/list.go b/internal/cmd/beta/security-group/list/list.go index c74182a88..6c2bf6fcf 100644 --- a/internal/cmd/beta/security-group/list/list.go +++ b/internal/cmd/beta/security-group/list/list.go @@ -7,7 +7,6 @@ import ( "github.com/goccy/go-yaml" "github.com/spf13/cobra" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -17,6 +16,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) diff --git a/internal/cmd/beta/security-group/update/update.go b/internal/cmd/beta/security-group/update/update.go index 92135ea04..961229053 100644 --- a/internal/cmd/beta/security-group/update/update.go +++ b/internal/cmd/beta/security-group/update/update.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/spf13/cobra" - cmd_utils "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -14,6 +13,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" + iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/stackitcloud/stackit-sdk-go/services/iaas" ) @@ -63,8 +63,14 @@ func NewCmd(p *print.Printer) *cobra.Command { projectLabel = model.ProjectId } + groupLabel, err := iaasUtils.GetSecurityGroupName(ctx, apiClient, model.ProjectId, model.SecurityGroupId) + if err != nil { + p.Warn("cannot retrieve groupname: %v", err) + groupLabel = model.SecurityGroupId + } + if !model.AssumeYes { - prompt := fmt.Sprintf("Are you sure you want to update the security group %q?", model.SecurityGroupId) + prompt := fmt.Sprintf("Are you sure you want to update the security group %q?", groupLabel) err = p.PromptForConfirmation(prompt) if err != nil { return err @@ -78,7 +84,7 @@ func NewCmd(p *print.Printer) *cobra.Command { if err != nil { return fmt.Errorf("update security group: %w", err) } - p.Info("Updated security group \"%v\" for %q\n", cmd_utils.PtrString(resp.Name), projectLabel) + p.Info("Updated security group \"%v\" for %q\n", utils.PtrString(resp.Name), projectLabel) return nil }, diff --git a/internal/cmd/beta/security-group/utils/utils.go b/internal/cmd/beta/security-group/utils/utils.go deleted file mode 100644 index f34ee657d..000000000 --- a/internal/cmd/beta/security-group/utils/utils.go +++ /dev/null @@ -1,12 +0,0 @@ -package utils - -import "fmt" - -// PtrString creates a string representation of a passed object pointer or returns -// an empty string, if the passed object is _nil_. -func PtrString[T any](t *T) string { - if t != nil { - return fmt.Sprintf("%v", *t) - } - return "" -} diff --git a/internal/pkg/services/iaas/utils/utils_test.go b/internal/pkg/services/iaas/utils/utils_test.go index c7e75a683..b65f67112 100644 --- a/internal/pkg/services/iaas/utils/utils_test.go +++ b/internal/pkg/services/iaas/utils/utils_test.go @@ -662,3 +662,5 @@ func TestGetNetworkRangeFromAPIResponse(t *testing.T) { }) } } + + diff --git a/internal/pkg/utils/utils.go b/internal/pkg/utils/utils.go index 205bff1cf..dc1cfeffb 100644 --- a/internal/pkg/utils/utils.go +++ b/internal/pkg/utils/utils.go @@ -16,6 +16,15 @@ func Ptr[T any](v T) *T { return &v } +// PtrString creates a string representation of a passed object pointer or returns +// an empty string, if the passed object is _nil_. +func PtrString[T any](t *T) string { + if t != nil { + return fmt.Sprintf("%v", *t) + } + return "" +} + // Int64Ptr returns a pointer to an int64 // Needed because the Ptr function only returns pointer to int func Int64Ptr(i int64) *int64 { From f77a9d631f77439ff0447dbb7a36243cb5fbce33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Schmitz?= Date: Thu, 12 Dec 2024 17:59:03 +0100 Subject: [PATCH 26/28] restructed security-group command --- internal/cmd/beta/beta.go | 4 +- .../{ => group}/create/create.go | 0 .../{ => group}/create/create_test.go | 0 .../{ => group}/delete/delete.go | 0 .../{ => group}/delete/delete_test.go | 0 .../{ => group}/describe/describe.go | 0 .../{ => group}/describe/describe_test.go | 0 .../security-group/{ => group}/list/list.go | 0 .../{ => group}/list/list_test.go | 0 .../security-group/group/security_group.go | 37 +++++++++++++++++++ .../{ => group}/update/update.go | 0 .../{ => group}/update/update_test.go | 0 .../cmd/beta/security-group/security_group.go | 16 +++----- 13 files changed, 44 insertions(+), 13 deletions(-) rename internal/cmd/beta/security-group/{ => group}/create/create.go (100%) rename internal/cmd/beta/security-group/{ => group}/create/create_test.go (100%) rename internal/cmd/beta/security-group/{ => group}/delete/delete.go (100%) rename internal/cmd/beta/security-group/{ => group}/delete/delete_test.go (100%) rename internal/cmd/beta/security-group/{ => group}/describe/describe.go (100%) rename internal/cmd/beta/security-group/{ => group}/describe/describe_test.go (100%) rename internal/cmd/beta/security-group/{ => group}/list/list.go (100%) rename internal/cmd/beta/security-group/{ => group}/list/list_test.go (100%) create mode 100644 internal/cmd/beta/security-group/group/security_group.go rename internal/cmd/beta/security-group/{ => group}/update/update.go (100%) rename internal/cmd/beta/security-group/{ => group}/update/update_test.go (100%) diff --git a/internal/cmd/beta/beta.go b/internal/cmd/beta/beta.go index 5ad1bfdf3..0ec43d0f2 100644 --- a/internal/cmd/beta/beta.go +++ b/internal/cmd/beta/beta.go @@ -8,7 +8,7 @@ import ( networkArea "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-area" networkinterface "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-interface" publicip "github.com/stackitcloud/stackit-cli/internal/cmd/beta/public-ip" - security_group "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group" + securitygroup "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/server" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/volume" @@ -50,6 +50,6 @@ func addSubcommands(cmd *cobra.Command, p *print.Printer) { cmd.AddCommand(volume.NewCmd(p)) cmd.AddCommand(networkinterface.NewCmd(p)) cmd.AddCommand(publicip.NewCmd(p)) - cmd.AddCommand(security_group.NewCmd(p)) + cmd.AddCommand(securitygroup.NewCmd(p)) cmd.AddCommand(keypair.NewCmd(p)) } diff --git a/internal/cmd/beta/security-group/create/create.go b/internal/cmd/beta/security-group/group/create/create.go similarity index 100% rename from internal/cmd/beta/security-group/create/create.go rename to internal/cmd/beta/security-group/group/create/create.go diff --git a/internal/cmd/beta/security-group/create/create_test.go b/internal/cmd/beta/security-group/group/create/create_test.go similarity index 100% rename from internal/cmd/beta/security-group/create/create_test.go rename to internal/cmd/beta/security-group/group/create/create_test.go diff --git a/internal/cmd/beta/security-group/delete/delete.go b/internal/cmd/beta/security-group/group/delete/delete.go similarity index 100% rename from internal/cmd/beta/security-group/delete/delete.go rename to internal/cmd/beta/security-group/group/delete/delete.go diff --git a/internal/cmd/beta/security-group/delete/delete_test.go b/internal/cmd/beta/security-group/group/delete/delete_test.go similarity index 100% rename from internal/cmd/beta/security-group/delete/delete_test.go rename to internal/cmd/beta/security-group/group/delete/delete_test.go diff --git a/internal/cmd/beta/security-group/describe/describe.go b/internal/cmd/beta/security-group/group/describe/describe.go similarity index 100% rename from internal/cmd/beta/security-group/describe/describe.go rename to internal/cmd/beta/security-group/group/describe/describe.go diff --git a/internal/cmd/beta/security-group/describe/describe_test.go b/internal/cmd/beta/security-group/group/describe/describe_test.go similarity index 100% rename from internal/cmd/beta/security-group/describe/describe_test.go rename to internal/cmd/beta/security-group/group/describe/describe_test.go diff --git a/internal/cmd/beta/security-group/list/list.go b/internal/cmd/beta/security-group/group/list/list.go similarity index 100% rename from internal/cmd/beta/security-group/list/list.go rename to internal/cmd/beta/security-group/group/list/list.go diff --git a/internal/cmd/beta/security-group/list/list_test.go b/internal/cmd/beta/security-group/group/list/list_test.go similarity index 100% rename from internal/cmd/beta/security-group/list/list_test.go rename to internal/cmd/beta/security-group/group/list/list_test.go diff --git a/internal/cmd/beta/security-group/group/security_group.go b/internal/cmd/beta/security-group/group/security_group.go new file mode 100644 index 000000000..e6038db5d --- /dev/null +++ b/internal/cmd/beta/security-group/group/security_group.go @@ -0,0 +1,37 @@ +package group + +import ( + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/group/create" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/group/delete" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/group/describe" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/group/list" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/group/update" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/spf13/cobra" +) + +func NewCmd(p *print.Printer) *cobra.Command { + cmd := &cobra.Command{ + Use: "group", + Short: "Manage security groups", + Long: "Manage the lifecycle of security groups.", + Args: args.NoArgs, + Run: utils.CmdHelp, + } + addSubcommands(cmd, p) + return cmd +} + +func addSubcommands(cmd *cobra.Command, p *print.Printer) { + cmd.AddCommand( + create.NewCmd(p), + delete.NewCmd(p), + describe.NewCmd(p), + list.NewCmd(p), + update.NewCmd(p), + ) +} diff --git a/internal/cmd/beta/security-group/update/update.go b/internal/cmd/beta/security-group/group/update/update.go similarity index 100% rename from internal/cmd/beta/security-group/update/update.go rename to internal/cmd/beta/security-group/group/update/update.go diff --git a/internal/cmd/beta/security-group/update/update_test.go b/internal/cmd/beta/security-group/group/update/update_test.go similarity index 100% rename from internal/cmd/beta/security-group/update/update_test.go rename to internal/cmd/beta/security-group/group/update/update_test.go diff --git a/internal/cmd/beta/security-group/security_group.go b/internal/cmd/beta/security-group/security_group.go index 2dea72f6e..47cef1851 100644 --- a/internal/cmd/beta/security-group/security_group.go +++ b/internal/cmd/beta/security-group/security_group.go @@ -1,11 +1,8 @@ package security_group import ( - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/create" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/delete" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/describe" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/list" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/update" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/group" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/rule" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/print" @@ -18,7 +15,7 @@ func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: "security-group", Short: "Manage security groups", - Long: "Manage the lifecycle of security groups.", + Long: "Manage the lifecycle of security groups and rules.", Args: args.NoArgs, Run: utils.CmdHelp, } @@ -28,10 +25,7 @@ func NewCmd(p *print.Printer) *cobra.Command { func addSubcommands(cmd *cobra.Command, p *print.Printer) { cmd.AddCommand( - create.NewCmd(p), - delete.NewCmd(p), - describe.NewCmd(p), - list.NewCmd(p), - update.NewCmd(p), + rule.NewCmd(p), + group.NewCmd(p), ) } From f28609555680eab7eff52bdbe0acddf68c3e8145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Schmitz?= Date: Thu, 12 Dec 2024 18:02:29 +0100 Subject: [PATCH 27/28] feature: updated documentation --- docs/stackit_beta_security-group.md | 3 +- docs/stackit_beta_security-group_group.md | 37 +++++++++++++++++++ ...ackit_beta_security-group_group_create.md} | 8 ++-- ...ackit_beta_security-group_group_delete.md} | 8 ++-- ...kit_beta_security-group_group_describe.md} | 8 ++-- ...stackit_beta_security-group_group_list.md} | 10 ++--- ...ackit_beta_security-group_group_update.md} | 8 ++-- docs/stackit_beta_security-group_rule.md | 2 +- .../pkg/services/iaas/utils/utils_test.go | 2 - 9 files changed, 61 insertions(+), 25 deletions(-) create mode 100644 docs/stackit_beta_security-group_group.md rename docs/{stackit_beta_security-group_create.md => stackit_beta_security-group_group_create.md} (85%) rename docs/{stackit_beta_security-group_delete.md => stackit_beta_security-group_group_delete.md} (71%) rename docs/{stackit_beta_security-group_describe.md => stackit_beta_security-group_group_describe.md} (70%) rename docs/{stackit_beta_security-group_list.md => stackit_beta_security-group_group_list.md} (72%) rename docs/{stackit_beta_security-group_update.md => stackit_beta_security-group_group_update.md} (84%) diff --git a/docs/stackit_beta_security-group.md b/docs/stackit_beta_security-group.md index f7f064408..04bfca52b 100644 --- a/docs/stackit_beta_security-group.md +++ b/docs/stackit_beta_security-group.md @@ -4,7 +4,7 @@ Manage security groups ### Synopsis -Manage the lifecycle of security groups. +Manage the lifecycle of security groups and rules. ``` stackit beta security-group [flags] @@ -29,5 +29,6 @@ stackit beta security-group [flags] ### SEE ALSO * [stackit beta](./stackit_beta.md) - Contains beta STACKIT CLI commands +* [stackit beta security-group group](./stackit_beta_security-group_group.md) - Manage security groups * [stackit beta security-group rule](./stackit_beta_security-group_rule.md) - Provides functionality for security group rules diff --git a/docs/stackit_beta_security-group_group.md b/docs/stackit_beta_security-group_group.md new file mode 100644 index 000000000..4e3d8bde8 --- /dev/null +++ b/docs/stackit_beta_security-group_group.md @@ -0,0 +1,37 @@ +## stackit beta security-group group + +Manage security groups + +### Synopsis + +Manage the lifecycle of security groups. + +``` +stackit beta security-group group [flags] +``` + +### Options + +``` + -h, --help Help for "stackit beta security-group group" +``` + +### 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 + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit beta security-group](./stackit_beta_security-group.md) - Manage security groups +* [stackit beta security-group group create](./stackit_beta_security-group_group_create.md) - Creates security groups +* [stackit beta security-group group delete](./stackit_beta_security-group_group_delete.md) - Deletes a security group +* [stackit beta security-group group describe](./stackit_beta_security-group_group_describe.md) - Describes security groups +* [stackit beta security-group group list](./stackit_beta_security-group_group_list.md) - Lists security groups +* [stackit beta security-group group update](./stackit_beta_security-group_group_update.md) - Updates a security group + diff --git a/docs/stackit_beta_security-group_create.md b/docs/stackit_beta_security-group_group_create.md similarity index 85% rename from docs/stackit_beta_security-group_create.md rename to docs/stackit_beta_security-group_group_create.md index e2ebd9b21..7cbd00919 100644 --- a/docs/stackit_beta_security-group_create.md +++ b/docs/stackit_beta_security-group_group_create.md @@ -1,4 +1,4 @@ -## stackit beta security-group create +## stackit beta security-group group create Creates security groups @@ -7,7 +7,7 @@ Creates security groups Creates security groups. ``` -stackit beta security-group create [flags] +stackit beta security-group group create [flags] ``` ### Examples @@ -24,7 +24,7 @@ stackit beta security-group create [flags] ``` --description string An optional description of the security group. - -h, --help Help for "stackit beta security-group create" + -h, --help Help for "stackit beta security-group group create" --labels stringToString Labels are key-value string pairs which can be attached to a network-interface. E.g. '--labels key1=value1,key2=value2,...' (default []) --name string The name of the security group. --stateful Create a stateful or a stateless security group @@ -42,5 +42,5 @@ stackit beta security-group create [flags] ### SEE ALSO -* [stackit beta security-group](./stackit_beta_security-group.md) - Manage security groups +* [stackit beta security-group group](./stackit_beta_security-group_group.md) - Manage security groups diff --git a/docs/stackit_beta_security-group_delete.md b/docs/stackit_beta_security-group_group_delete.md similarity index 71% rename from docs/stackit_beta_security-group_delete.md rename to docs/stackit_beta_security-group_group_delete.md index dd3d7935f..51546d067 100644 --- a/docs/stackit_beta_security-group_delete.md +++ b/docs/stackit_beta_security-group_group_delete.md @@ -1,4 +1,4 @@ -## stackit beta security-group delete +## stackit beta security-group group delete Deletes a security group @@ -7,7 +7,7 @@ Deletes a security group Deletes a security group by its internal ID. ``` -stackit beta security-group delete [flags] +stackit beta security-group group delete [flags] ``` ### Examples @@ -20,7 +20,7 @@ stackit beta security-group delete [flags] ### Options ``` - -h, --help Help for "stackit beta security-group delete" + -h, --help Help for "stackit beta security-group group delete" ``` ### Options inherited from parent commands @@ -35,5 +35,5 @@ stackit beta security-group delete [flags] ### SEE ALSO -* [stackit beta security-group](./stackit_beta_security-group.md) - Manage security groups +* [stackit beta security-group group](./stackit_beta_security-group_group.md) - Manage security groups diff --git a/docs/stackit_beta_security-group_describe.md b/docs/stackit_beta_security-group_group_describe.md similarity index 70% rename from docs/stackit_beta_security-group_describe.md rename to docs/stackit_beta_security-group_group_describe.md index b1856566d..d78b306a4 100644 --- a/docs/stackit_beta_security-group_describe.md +++ b/docs/stackit_beta_security-group_group_describe.md @@ -1,4 +1,4 @@ -## stackit beta security-group describe +## stackit beta security-group group describe Describes security groups @@ -7,7 +7,7 @@ Describes security groups Describes security groups by its internal ID. ``` -stackit beta security-group describe [flags] +stackit beta security-group group describe [flags] ``` ### Examples @@ -20,7 +20,7 @@ stackit beta security-group describe [flags] ### Options ``` - -h, --help Help for "stackit beta security-group describe" + -h, --help Help for "stackit beta security-group group describe" ``` ### Options inherited from parent commands @@ -35,5 +35,5 @@ stackit beta security-group describe [flags] ### SEE ALSO -* [stackit beta security-group](./stackit_beta_security-group.md) - Manage security groups +* [stackit beta security-group group](./stackit_beta_security-group_group.md) - Manage security groups diff --git a/docs/stackit_beta_security-group_list.md b/docs/stackit_beta_security-group_group_list.md similarity index 72% rename from docs/stackit_beta_security-group_list.md rename to docs/stackit_beta_security-group_group_list.md index 3bffc83c6..9d8f0c339 100644 --- a/docs/stackit_beta_security-group_list.md +++ b/docs/stackit_beta_security-group_group_list.md @@ -1,4 +1,4 @@ -## stackit beta security-group list +## stackit beta security-group group list Lists security groups @@ -7,7 +7,7 @@ Lists security groups Lists security groups by its internal ID. ``` -stackit beta security-group list [flags] +stackit beta security-group group list [flags] ``` ### Examples @@ -17,13 +17,13 @@ stackit beta security-group list [flags] $ stackit beta security-group list List groups with labels - $ stackit beta security-group list --labels label1=value1,label2=value2 + $ stackit beta security-group list --label-selector label1=value1,label2=value2 ``` ### Options ``` - -h, --help Help for "stackit beta security-group list" + -h, --help Help for "stackit beta security-group group list" --label-selector string Filter by label ``` @@ -39,5 +39,5 @@ stackit beta security-group list [flags] ### SEE ALSO -* [stackit beta security-group](./stackit_beta_security-group.md) - Manage security groups +* [stackit beta security-group group](./stackit_beta_security-group_group.md) - Manage security groups diff --git a/docs/stackit_beta_security-group_update.md b/docs/stackit_beta_security-group_group_update.md similarity index 84% rename from docs/stackit_beta_security-group_update.md rename to docs/stackit_beta_security-group_group_update.md index acba6414e..5d130812b 100644 --- a/docs/stackit_beta_security-group_update.md +++ b/docs/stackit_beta_security-group_group_update.md @@ -1,4 +1,4 @@ -## stackit beta security-group update +## stackit beta security-group group update Updates a security group @@ -7,7 +7,7 @@ Updates a security group Updates a named security group ``` -stackit beta security-group update [flags] +stackit beta security-group group update [flags] ``` ### Examples @@ -24,7 +24,7 @@ stackit beta security-group update [flags] ``` --description string An optional description of the security group. - -h, --help Help for "stackit beta security-group update" + -h, --help Help for "stackit beta security-group group update" --labels stringToString Labels are key-value string pairs which can be attached to a network-interface. E.g. '--labels key1=value1,key2=value2,...' (default []) --name string The name of the security group. ``` @@ -41,5 +41,5 @@ stackit beta security-group update [flags] ### SEE ALSO -* [stackit beta security-group](./stackit_beta_security-group.md) - Manage security groups +* [stackit beta security-group group](./stackit_beta_security-group_group.md) - Manage security groups diff --git a/docs/stackit_beta_security-group_rule.md b/docs/stackit_beta_security-group_rule.md index a680f5bfc..8d47d0b21 100644 --- a/docs/stackit_beta_security-group_rule.md +++ b/docs/stackit_beta_security-group_rule.md @@ -28,7 +28,7 @@ stackit beta security-group rule [flags] ### SEE ALSO -* [stackit beta security-group](./stackit_beta_security-group.md) - Provides functionality for security groups +* [stackit beta security-group](./stackit_beta_security-group.md) - Manage security groups * [stackit beta security-group rule create](./stackit_beta_security-group_rule_create.md) - Creates a security group rule * [stackit beta security-group rule delete](./stackit_beta_security-group_rule_delete.md) - Deletes a security group rule * [stackit beta security-group rule describe](./stackit_beta_security-group_rule_describe.md) - Shows details of a security group rule diff --git a/internal/pkg/services/iaas/utils/utils_test.go b/internal/pkg/services/iaas/utils/utils_test.go index b65f67112..c7e75a683 100644 --- a/internal/pkg/services/iaas/utils/utils_test.go +++ b/internal/pkg/services/iaas/utils/utils_test.go @@ -662,5 +662,3 @@ func TestGetNetworkRangeFromAPIResponse(t *testing.T) { }) } } - - From d4579924636d084ab5e572f48b2297b98de2c89b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Schmitz?= Date: Thu, 12 Dec 2024 18:17:44 +0100 Subject: [PATCH 28/28] feature: restructured sub commands to canonical structure --- docs/stackit_beta_security-group.md | 6 ++- ... => stackit_beta_security-group_create.md} | 8 ++-- ... => stackit_beta_security-group_delete.md} | 8 ++-- ...> stackit_beta_security-group_describe.md} | 8 ++-- docs/stackit_beta_security-group_group.md | 37 ------------------- ...md => stackit_beta_security-group_list.md} | 8 ++-- ... => stackit_beta_security-group_update.md} | 8 ++-- .../{group => }/create/create.go | 0 .../{group => }/create/create_test.go | 0 .../{group => }/delete/delete.go | 0 .../{group => }/delete/delete_test.go | 0 .../{group => }/describe/describe.go | 0 .../{group => }/describe/describe_test.go | 0 .../security-group/group/security_group.go | 37 ------------------- .../security-group/{group => }/list/list.go | 0 .../{group => }/list/list_test.go | 0 .../cmd/beta/security-group/security_group.go | 12 +++++- .../{group => }/update/update.go | 0 .../{group => }/update/update_test.go | 0 19 files changed, 35 insertions(+), 97 deletions(-) rename docs/{stackit_beta_security-group_group_create.md => stackit_beta_security-group_create.md} (85%) rename docs/{stackit_beta_security-group_group_delete.md => stackit_beta_security-group_delete.md} (71%) rename docs/{stackit_beta_security-group_group_describe.md => stackit_beta_security-group_describe.md} (70%) delete mode 100644 docs/stackit_beta_security-group_group.md rename docs/{stackit_beta_security-group_group_list.md => stackit_beta_security-group_list.md} (80%) rename docs/{stackit_beta_security-group_group_update.md => stackit_beta_security-group_update.md} (84%) rename internal/cmd/beta/security-group/{group => }/create/create.go (100%) rename internal/cmd/beta/security-group/{group => }/create/create_test.go (100%) rename internal/cmd/beta/security-group/{group => }/delete/delete.go (100%) rename internal/cmd/beta/security-group/{group => }/delete/delete_test.go (100%) rename internal/cmd/beta/security-group/{group => }/describe/describe.go (100%) rename internal/cmd/beta/security-group/{group => }/describe/describe_test.go (100%) delete mode 100644 internal/cmd/beta/security-group/group/security_group.go rename internal/cmd/beta/security-group/{group => }/list/list.go (100%) rename internal/cmd/beta/security-group/{group => }/list/list_test.go (100%) rename internal/cmd/beta/security-group/{group => }/update/update.go (100%) rename internal/cmd/beta/security-group/{group => }/update/update_test.go (100%) diff --git a/docs/stackit_beta_security-group.md b/docs/stackit_beta_security-group.md index 04bfca52b..0c3954e95 100644 --- a/docs/stackit_beta_security-group.md +++ b/docs/stackit_beta_security-group.md @@ -29,6 +29,10 @@ stackit beta security-group [flags] ### SEE ALSO * [stackit beta](./stackit_beta.md) - Contains beta STACKIT CLI commands -* [stackit beta security-group group](./stackit_beta_security-group_group.md) - Manage security groups +* [stackit beta security-group create](./stackit_beta_security-group_create.md) - Creates security groups +* [stackit beta security-group delete](./stackit_beta_security-group_delete.md) - Deletes a security group +* [stackit beta security-group describe](./stackit_beta_security-group_describe.md) - Describes security groups +* [stackit beta security-group list](./stackit_beta_security-group_list.md) - Lists security groups * [stackit beta security-group rule](./stackit_beta_security-group_rule.md) - Provides functionality for security group rules +* [stackit beta security-group update](./stackit_beta_security-group_update.md) - Updates a security group diff --git a/docs/stackit_beta_security-group_group_create.md b/docs/stackit_beta_security-group_create.md similarity index 85% rename from docs/stackit_beta_security-group_group_create.md rename to docs/stackit_beta_security-group_create.md index 7cbd00919..e2ebd9b21 100644 --- a/docs/stackit_beta_security-group_group_create.md +++ b/docs/stackit_beta_security-group_create.md @@ -1,4 +1,4 @@ -## stackit beta security-group group create +## stackit beta security-group create Creates security groups @@ -7,7 +7,7 @@ Creates security groups Creates security groups. ``` -stackit beta security-group group create [flags] +stackit beta security-group create [flags] ``` ### Examples @@ -24,7 +24,7 @@ stackit beta security-group group create [flags] ``` --description string An optional description of the security group. - -h, --help Help for "stackit beta security-group group create" + -h, --help Help for "stackit beta security-group create" --labels stringToString Labels are key-value string pairs which can be attached to a network-interface. E.g. '--labels key1=value1,key2=value2,...' (default []) --name string The name of the security group. --stateful Create a stateful or a stateless security group @@ -42,5 +42,5 @@ stackit beta security-group group create [flags] ### SEE ALSO -* [stackit beta security-group group](./stackit_beta_security-group_group.md) - Manage security groups +* [stackit beta security-group](./stackit_beta_security-group.md) - Manage security groups diff --git a/docs/stackit_beta_security-group_group_delete.md b/docs/stackit_beta_security-group_delete.md similarity index 71% rename from docs/stackit_beta_security-group_group_delete.md rename to docs/stackit_beta_security-group_delete.md index 51546d067..dd3d7935f 100644 --- a/docs/stackit_beta_security-group_group_delete.md +++ b/docs/stackit_beta_security-group_delete.md @@ -1,4 +1,4 @@ -## stackit beta security-group group delete +## stackit beta security-group delete Deletes a security group @@ -7,7 +7,7 @@ Deletes a security group Deletes a security group by its internal ID. ``` -stackit beta security-group group delete [flags] +stackit beta security-group delete [flags] ``` ### Examples @@ -20,7 +20,7 @@ stackit beta security-group group delete [flags] ### Options ``` - -h, --help Help for "stackit beta security-group group delete" + -h, --help Help for "stackit beta security-group delete" ``` ### Options inherited from parent commands @@ -35,5 +35,5 @@ stackit beta security-group group delete [flags] ### SEE ALSO -* [stackit beta security-group group](./stackit_beta_security-group_group.md) - Manage security groups +* [stackit beta security-group](./stackit_beta_security-group.md) - Manage security groups diff --git a/docs/stackit_beta_security-group_group_describe.md b/docs/stackit_beta_security-group_describe.md similarity index 70% rename from docs/stackit_beta_security-group_group_describe.md rename to docs/stackit_beta_security-group_describe.md index d78b306a4..b1856566d 100644 --- a/docs/stackit_beta_security-group_group_describe.md +++ b/docs/stackit_beta_security-group_describe.md @@ -1,4 +1,4 @@ -## stackit beta security-group group describe +## stackit beta security-group describe Describes security groups @@ -7,7 +7,7 @@ Describes security groups Describes security groups by its internal ID. ``` -stackit beta security-group group describe [flags] +stackit beta security-group describe [flags] ``` ### Examples @@ -20,7 +20,7 @@ stackit beta security-group group describe [flags] ### Options ``` - -h, --help Help for "stackit beta security-group group describe" + -h, --help Help for "stackit beta security-group describe" ``` ### Options inherited from parent commands @@ -35,5 +35,5 @@ stackit beta security-group group describe [flags] ### SEE ALSO -* [stackit beta security-group group](./stackit_beta_security-group_group.md) - Manage security groups +* [stackit beta security-group](./stackit_beta_security-group.md) - Manage security groups diff --git a/docs/stackit_beta_security-group_group.md b/docs/stackit_beta_security-group_group.md deleted file mode 100644 index 4e3d8bde8..000000000 --- a/docs/stackit_beta_security-group_group.md +++ /dev/null @@ -1,37 +0,0 @@ -## stackit beta security-group group - -Manage security groups - -### Synopsis - -Manage the lifecycle of security groups. - -``` -stackit beta security-group group [flags] -``` - -### Options - -``` - -h, --help Help for "stackit beta security-group group" -``` - -### 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 - --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") -``` - -### SEE ALSO - -* [stackit beta security-group](./stackit_beta_security-group.md) - Manage security groups -* [stackit beta security-group group create](./stackit_beta_security-group_group_create.md) - Creates security groups -* [stackit beta security-group group delete](./stackit_beta_security-group_group_delete.md) - Deletes a security group -* [stackit beta security-group group describe](./stackit_beta_security-group_group_describe.md) - Describes security groups -* [stackit beta security-group group list](./stackit_beta_security-group_group_list.md) - Lists security groups -* [stackit beta security-group group update](./stackit_beta_security-group_group_update.md) - Updates a security group - diff --git a/docs/stackit_beta_security-group_group_list.md b/docs/stackit_beta_security-group_list.md similarity index 80% rename from docs/stackit_beta_security-group_group_list.md rename to docs/stackit_beta_security-group_list.md index 9d8f0c339..f64f6ec99 100644 --- a/docs/stackit_beta_security-group_group_list.md +++ b/docs/stackit_beta_security-group_list.md @@ -1,4 +1,4 @@ -## stackit beta security-group group list +## stackit beta security-group list Lists security groups @@ -7,7 +7,7 @@ Lists security groups Lists security groups by its internal ID. ``` -stackit beta security-group group list [flags] +stackit beta security-group list [flags] ``` ### Examples @@ -23,7 +23,7 @@ stackit beta security-group group list [flags] ### Options ``` - -h, --help Help for "stackit beta security-group group list" + -h, --help Help for "stackit beta security-group list" --label-selector string Filter by label ``` @@ -39,5 +39,5 @@ stackit beta security-group group list [flags] ### SEE ALSO -* [stackit beta security-group group](./stackit_beta_security-group_group.md) - Manage security groups +* [stackit beta security-group](./stackit_beta_security-group.md) - Manage security groups diff --git a/docs/stackit_beta_security-group_group_update.md b/docs/stackit_beta_security-group_update.md similarity index 84% rename from docs/stackit_beta_security-group_group_update.md rename to docs/stackit_beta_security-group_update.md index 5d130812b..acba6414e 100644 --- a/docs/stackit_beta_security-group_group_update.md +++ b/docs/stackit_beta_security-group_update.md @@ -1,4 +1,4 @@ -## stackit beta security-group group update +## stackit beta security-group update Updates a security group @@ -7,7 +7,7 @@ Updates a security group Updates a named security group ``` -stackit beta security-group group update [flags] +stackit beta security-group update [flags] ``` ### Examples @@ -24,7 +24,7 @@ stackit beta security-group group update [flags] ``` --description string An optional description of the security group. - -h, --help Help for "stackit beta security-group group update" + -h, --help Help for "stackit beta security-group update" --labels stringToString Labels are key-value string pairs which can be attached to a network-interface. E.g. '--labels key1=value1,key2=value2,...' (default []) --name string The name of the security group. ``` @@ -41,5 +41,5 @@ stackit beta security-group group update [flags] ### SEE ALSO -* [stackit beta security-group group](./stackit_beta_security-group_group.md) - Manage security groups +* [stackit beta security-group](./stackit_beta_security-group.md) - Manage security groups diff --git a/internal/cmd/beta/security-group/group/create/create.go b/internal/cmd/beta/security-group/create/create.go similarity index 100% rename from internal/cmd/beta/security-group/group/create/create.go rename to internal/cmd/beta/security-group/create/create.go diff --git a/internal/cmd/beta/security-group/group/create/create_test.go b/internal/cmd/beta/security-group/create/create_test.go similarity index 100% rename from internal/cmd/beta/security-group/group/create/create_test.go rename to internal/cmd/beta/security-group/create/create_test.go diff --git a/internal/cmd/beta/security-group/group/delete/delete.go b/internal/cmd/beta/security-group/delete/delete.go similarity index 100% rename from internal/cmd/beta/security-group/group/delete/delete.go rename to internal/cmd/beta/security-group/delete/delete.go diff --git a/internal/cmd/beta/security-group/group/delete/delete_test.go b/internal/cmd/beta/security-group/delete/delete_test.go similarity index 100% rename from internal/cmd/beta/security-group/group/delete/delete_test.go rename to internal/cmd/beta/security-group/delete/delete_test.go diff --git a/internal/cmd/beta/security-group/group/describe/describe.go b/internal/cmd/beta/security-group/describe/describe.go similarity index 100% rename from internal/cmd/beta/security-group/group/describe/describe.go rename to internal/cmd/beta/security-group/describe/describe.go diff --git a/internal/cmd/beta/security-group/group/describe/describe_test.go b/internal/cmd/beta/security-group/describe/describe_test.go similarity index 100% rename from internal/cmd/beta/security-group/group/describe/describe_test.go rename to internal/cmd/beta/security-group/describe/describe_test.go diff --git a/internal/cmd/beta/security-group/group/security_group.go b/internal/cmd/beta/security-group/group/security_group.go deleted file mode 100644 index e6038db5d..000000000 --- a/internal/cmd/beta/security-group/group/security_group.go +++ /dev/null @@ -1,37 +0,0 @@ -package group - -import ( - "github.com/stackitcloud/stackit-cli/internal/pkg/args" - "github.com/stackitcloud/stackit-cli/internal/pkg/print" - - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/group/create" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/group/delete" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/group/describe" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/group/list" - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/group/update" - "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - - "github.com/spf13/cobra" -) - -func NewCmd(p *print.Printer) *cobra.Command { - cmd := &cobra.Command{ - Use: "group", - Short: "Manage security groups", - Long: "Manage the lifecycle of security groups.", - Args: args.NoArgs, - Run: utils.CmdHelp, - } - addSubcommands(cmd, p) - return cmd -} - -func addSubcommands(cmd *cobra.Command, p *print.Printer) { - cmd.AddCommand( - create.NewCmd(p), - delete.NewCmd(p), - describe.NewCmd(p), - list.NewCmd(p), - update.NewCmd(p), - ) -} diff --git a/internal/cmd/beta/security-group/group/list/list.go b/internal/cmd/beta/security-group/list/list.go similarity index 100% rename from internal/cmd/beta/security-group/group/list/list.go rename to internal/cmd/beta/security-group/list/list.go diff --git a/internal/cmd/beta/security-group/group/list/list_test.go b/internal/cmd/beta/security-group/list/list_test.go similarity index 100% rename from internal/cmd/beta/security-group/group/list/list_test.go rename to internal/cmd/beta/security-group/list/list_test.go diff --git a/internal/cmd/beta/security-group/security_group.go b/internal/cmd/beta/security-group/security_group.go index 47cef1851..34e4bb73e 100644 --- a/internal/cmd/beta/security-group/security_group.go +++ b/internal/cmd/beta/security-group/security_group.go @@ -1,8 +1,12 @@ package security_group import ( - "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/group" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/create" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/delete" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/describe" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/list" "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/rule" + "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group/update" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/print" @@ -26,6 +30,10 @@ func NewCmd(p *print.Printer) *cobra.Command { func addSubcommands(cmd *cobra.Command, p *print.Printer) { cmd.AddCommand( rule.NewCmd(p), - group.NewCmd(p), + create.NewCmd(p), + delete.NewCmd(p), + describe.NewCmd(p), + list.NewCmd(p), + update.NewCmd(p), ) } diff --git a/internal/cmd/beta/security-group/group/update/update.go b/internal/cmd/beta/security-group/update/update.go similarity index 100% rename from internal/cmd/beta/security-group/group/update/update.go rename to internal/cmd/beta/security-group/update/update.go diff --git a/internal/cmd/beta/security-group/group/update/update_test.go b/internal/cmd/beta/security-group/update/update_test.go similarity index 100% rename from internal/cmd/beta/security-group/group/update/update_test.go rename to internal/cmd/beta/security-group/update/update_test.go