From 5986f535a78d20908512c9b4882bcfa2e3965852 Mon Sep 17 00:00:00 2001 From: Anton Bozhiy Date: Wed, 14 Jan 2026 14:57:44 +0200 Subject: [PATCH 1/3] Expand l2 segment add command --- cmd/entities/l2_segments/add.go | 122 +++++++++++++++++-- cmd/entities/l2_segments/l2_segments_test.go | 38 +++++- 2 files changed, 149 insertions(+), 11 deletions(-) diff --git a/cmd/entities/l2_segments/add.go b/cmd/entities/l2_segments/add.go index e31b739..fc19c78 100644 --- a/cmd/entities/l2_segments/add.go +++ b/cmd/entities/l2_segments/add.go @@ -1,18 +1,25 @@ package l2segments import ( - "log" - + "fmt" serverscom "github.com/serverscom/serverscom-go-client/pkg" "github.com/serverscom/srvctl/cmd/base" "github.com/spf13/cobra" + "strings" ) -func newAddCmd(cmdContext *base.CmdContext) *cobra.Command { - var path string +var ( + path string + name string + l2Type string + locationGroupID int64 + members []string + labels []string +) +func newAddCmd(cmdContext *base.CmdContext) *cobra.Command { cmd := &cobra.Command{ - Use: "add --input ", + Use: "add", Short: "Add a new L2 segment", Long: "Add a new L2 segment", Args: cobra.ExactArgs(0), @@ -25,8 +32,18 @@ func newAddCmd(cmdContext *base.CmdContext) *cobra.Command { base.SetupProxy(cmd, manager) input := &serverscom.L2SegmentCreateInput{} - if err := base.ReadInputJSON(path, cmd.InOrStdin(), input); err != nil { - return err + + if cmd.Flags().Changed("input") { + if err := base.ReadInputJSON(path, cmd.InOrStdin(), input); err != nil { + return err + } + } else { + if err := validateFlags(cmd); err != nil { + return err + } + if err := fillInput(cmd, input); err != nil { + return err + } } scClient := cmdContext.GetClient().SetVerbose(manager.GetVerbose(cmd)).GetScClient() @@ -44,9 +61,94 @@ func newAddCmd(cmdContext *base.CmdContext) *cobra.Command { } cmd.Flags().StringVarP(&path, "input", "i", "", "path to input file or '-' to read from stdin") - if err := cmd.MarkFlagRequired("input"); err != nil { - log.Fatal(err) - } + cmd.Flags().StringVarP(&name, "name", "n", "", "A name of a L2 segment") + cmd.Flags().StringVarP(&l2Type, "type", "", "", "A type of a L2 segment") + cmd.Flags().Int64VarP(&locationGroupID, "location-group-id", "", 0, "A private-key of a L2 segment") + cmd.Flags().StringArrayVarP(&members, "member", "m", []string{}, "L2 segment member: id=,mode=") + cmd.Flags().StringArrayVarP(&labels, "label", "l", []string{}, "string in key=value format") return cmd } + +func validateFlags(cmd *cobra.Command) error { + required := []string{"name", "type", "location-group-id", "member"} + var missing []string + + for _, flag := range required { + if !cmd.Flags().Changed(flag) { + missing = append(missing, "--"+flag) + } + } + + if len(missing) > 0 { + return fmt.Errorf( + "use --input or provide all required flags (missing: %s)", + strings.Join(missing, ", "), + ) + } + + return nil +} + +func fillInput(cmd *cobra.Command, input *serverscom.L2SegmentCreateInput) error { + if cmd.Flags().Changed("name") { + input.Name = &name + } + if cmd.Flags().Changed("type") { + input.Type = l2Type + } + if cmd.Flags().Changed("location-group-id") { + input.LocationGroupID = locationGroupID + } + if cmd.Flags().Changed("member") { + membersMap, err := parseMembers(members) + if err != nil { + return err + } + + input.Members = membersMap + } + if cmd.Flags().Changed("label") { + labelsMap, err := base.ParseLabels(labels) + if err != nil { + return err + } + + input.Labels = labelsMap + } + + return nil +} + +func parseMembers(members []string) ([]serverscom.L2SegmentMemberInput, error) { + var res []serverscom.L2SegmentMemberInput + + for _, member := range members { + m := serverscom.L2SegmentMemberInput{} + parts := strings.Split(member, ",") + + for _, p := range parts { + props := strings.SplitN(p, "=", 2) + if len(props) != 2 { + return nil, fmt.Errorf("invalid member format: %s", p) + } + + switch props[0] { + case "id": + m.ID = props[1] + case "mode": + m.Mode = props[1] + default: + return nil, fmt.Errorf("unknown member field: %s", props[0]) + } + } + + if m.ID == "" || m.Mode == "" { + return nil, fmt.Errorf("member must include id and mode: %s", member) + } + + res = append(res, m) + } + + return res, nil +} diff --git a/cmd/entities/l2_segments/l2_segments_test.go b/cmd/entities/l2_segments/l2_segments_test.go index 3da5261..2307a05 100644 --- a/cmd/entities/l2_segments/l2_segments_test.go +++ b/cmd/entities/l2_segments/l2_segments_test.go @@ -291,7 +291,7 @@ func TestAddL2SegmentCmd(t *testing.T) { expectError bool }{ { - name: "create l2 segment", + name: "create l2 segment with input", output: "json", expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "get.json")), args: []string{"--input", filepath.Join(fixtureBasePath, "create.json")}, @@ -301,6 +301,42 @@ func TestAddL2SegmentCmd(t *testing.T) { Return(&testL2Segment, nil) }, }, + { + name: "create l2 segment", + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "get.json")), + args: []string{ + "--name", testL2SegmentName, + "--type", "public", + "--location-group-id", "58", + "--member", "id=LDdwmwa1,mode=native", + "--member", "id=LDdwmwa2,mode=trunk", + "--member", "id=LDdwmwa3,mode=native", + "--label", "foo=bar", + "--label", "bar=foo", + }, + configureMock: func(mock *mocks.MockL2SegmentsService) { + expectedMembers := []serverscom.L2SegmentMemberInput{ + {ID: "LDdwmwa1", Mode: "native"}, + {ID: "LDdwmwa2", Mode: "trunk"}, + {ID: "LDdwmwa3", Mode: "native"}, + } + expectedLabels := map[string]string{ + "foo": "bar", + "bar": "foo", + } + + mock.EXPECT(). + Create(gomock.Any(), serverscom.L2SegmentCreateInput{ + Name: &testL2SegmentName, + Type: "public", + LocationGroupID: 58, + Members: expectedMembers, + Labels: expectedLabels, + }). + Return(&testL2Segment, nil) + }, + }, { name: "create l2 segment with error", expectError: true, From 7e9f04064dda1e636219cee170b750d613701be7 Mon Sep 17 00:00:00 2001 From: Anton Bozhiy Date: Wed, 14 Jan 2026 16:45:34 +0200 Subject: [PATCH 2/3] refactoring --- cmd/base/utils.go | 19 +++++++++ cmd/entities/l2_segments/add.go | 76 +++++++++++++-------------------- 2 files changed, 49 insertions(+), 46 deletions(-) diff --git a/cmd/base/utils.go b/cmd/base/utils.go index e26a5c5..be8c7af 100644 --- a/cmd/base/utils.go +++ b/cmd/base/utils.go @@ -194,3 +194,22 @@ func fetchItems[T any](ctx context.Context, collection serverscom.Collection[T], return collection.List(ctx) } + +func ValidateFlags(cmd *cobra.Command, required []string) error { + var missing []string + + for _, flag := range required { + if !cmd.Flags().Changed(flag) { + missing = append(missing, "--"+flag) + } + } + + if len(missing) > 0 { + return fmt.Errorf( + "provide all required flags (missing: %s)", + strings.Join(missing, ", "), + ) + } + + return nil +} diff --git a/cmd/entities/l2_segments/add.go b/cmd/entities/l2_segments/add.go index fc19c78..ff9c289 100644 --- a/cmd/entities/l2_segments/add.go +++ b/cmd/entities/l2_segments/add.go @@ -8,16 +8,18 @@ import ( "strings" ) -var ( - path string - name string - l2Type string - locationGroupID int64 - members []string - labels []string -) +type AddedFlags struct { + InputPath string + Name string + Type string + LocationGroupID int64 + Members []string + Labels []string +} func newAddCmd(cmdContext *base.CmdContext) *cobra.Command { + flags := &AddedFlags{} + cmd := &cobra.Command{ Use: "add", Short: "Add a new L2 segment", @@ -33,19 +35,21 @@ func newAddCmd(cmdContext *base.CmdContext) *cobra.Command { input := &serverscom.L2SegmentCreateInput{} - if cmd.Flags().Changed("input") { - if err := base.ReadInputJSON(path, cmd.InOrStdin(), input); err != nil { + if flags.InputPath != "" { + if err := base.ReadInputJSON(flags.InputPath, cmd.InOrStdin(), input); err != nil { return err } } else { - if err := validateFlags(cmd); err != nil { - return err - } - if err := fillInput(cmd, input); err != nil { + required := []string{"name", "type", "location-group-id", "member"} + if err := base.ValidateFlags(cmd, required); err != nil { return err } } + if err := flags.FillInput(cmd, input); err != nil { + return err + } + scClient := cmdContext.GetClient().SetVerbose(manager.GetVerbose(cmd)).GetScClient() l2Segment, err := scClient.L2Segments.Create(ctx, *input) if err != nil { @@ -60,48 +64,28 @@ func newAddCmd(cmdContext *base.CmdContext) *cobra.Command { }, } - cmd.Flags().StringVarP(&path, "input", "i", "", "path to input file or '-' to read from stdin") - cmd.Flags().StringVarP(&name, "name", "n", "", "A name of a L2 segment") - cmd.Flags().StringVarP(&l2Type, "type", "", "", "A type of a L2 segment") - cmd.Flags().Int64VarP(&locationGroupID, "location-group-id", "", 0, "A private-key of a L2 segment") - cmd.Flags().StringArrayVarP(&members, "member", "m", []string{}, "L2 segment member: id=,mode=") - cmd.Flags().StringArrayVarP(&labels, "label", "l", []string{}, "string in key=value format") + cmd.Flags().StringVarP(&flags.InputPath, "input", "i", "", "path to input file or '-' to read from stdin") + cmd.Flags().StringVarP(&flags.Name, "name", "n", "", "A name of a L2 segment") + cmd.Flags().StringVarP(&flags.Type, "type", "", "", "A type of a L2 segment") + cmd.Flags().Int64VarP(&flags.LocationGroupID, "location-group-id", "", 0, "A private-key of a L2 segment") + cmd.Flags().StringArrayVarP(&flags.Members, "member", "m", []string{}, "L2 segment member: id=,mode=") + cmd.Flags().StringArrayVarP(&flags.Labels, "label", "l", []string{}, "string in key=value format") return cmd } -func validateFlags(cmd *cobra.Command) error { - required := []string{"name", "type", "location-group-id", "member"} - var missing []string - - for _, flag := range required { - if !cmd.Flags().Changed(flag) { - missing = append(missing, "--"+flag) - } - } - - if len(missing) > 0 { - return fmt.Errorf( - "use --input or provide all required flags (missing: %s)", - strings.Join(missing, ", "), - ) - } - - return nil -} - -func fillInput(cmd *cobra.Command, input *serverscom.L2SegmentCreateInput) error { +func (f *AddedFlags) FillInput(cmd *cobra.Command, input *serverscom.L2SegmentCreateInput) error { if cmd.Flags().Changed("name") { - input.Name = &name + input.Name = &f.Name } if cmd.Flags().Changed("type") { - input.Type = l2Type + input.Type = f.Type } if cmd.Flags().Changed("location-group-id") { - input.LocationGroupID = locationGroupID + input.LocationGroupID = f.LocationGroupID } if cmd.Flags().Changed("member") { - membersMap, err := parseMembers(members) + membersMap, err := parseMembers(f.Members) if err != nil { return err } @@ -109,7 +93,7 @@ func fillInput(cmd *cobra.Command, input *serverscom.L2SegmentCreateInput) error input.Members = membersMap } if cmd.Flags().Changed("label") { - labelsMap, err := base.ParseLabels(labels) + labelsMap, err := base.ParseLabels(f.Labels) if err != nil { return err } From 1bbf8fd1a1cd1a3f418abac352d1a385aa0c956e Mon Sep 17 00:00:00 2001 From: Anton Bozhiy Date: Wed, 14 Jan 2026 18:20:23 +0200 Subject: [PATCH 3/3] fix required flags list --- cmd/entities/l2_segments/add.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/entities/l2_segments/add.go b/cmd/entities/l2_segments/add.go index ff9c289..a736775 100644 --- a/cmd/entities/l2_segments/add.go +++ b/cmd/entities/l2_segments/add.go @@ -40,7 +40,7 @@ func newAddCmd(cmdContext *base.CmdContext) *cobra.Command { return err } } else { - required := []string{"name", "type", "location-group-id", "member"} + required := []string{"type", "member"} if err := base.ValidateFlags(cmd, required); err != nil { return err }