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 e31b739..a736775 100644 --- a/cmd/entities/l2_segments/add.go +++ b/cmd/entities/l2_segments/add.go @@ -1,18 +1,27 @@ package l2segments import ( - "log" - + "fmt" serverscom "github.com/serverscom/serverscom-go-client/pkg" "github.com/serverscom/srvctl/cmd/base" "github.com/spf13/cobra" + "strings" ) +type AddedFlags struct { + InputPath string + Name string + Type string + LocationGroupID int64 + Members []string + Labels []string +} + func newAddCmd(cmdContext *base.CmdContext) *cobra.Command { - var path string + flags := &AddedFlags{} 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,7 +34,19 @@ func newAddCmd(cmdContext *base.CmdContext) *cobra.Command { base.SetupProxy(cmd, manager) input := &serverscom.L2SegmentCreateInput{} - 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 { + required := []string{"type", "member"} + if err := base.ValidateFlags(cmd, required); err != nil { + return err + } + } + + if err := flags.FillInput(cmd, input); err != nil { return err } @@ -43,10 +64,75 @@ 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(&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 (f *AddedFlags) FillInput(cmd *cobra.Command, input *serverscom.L2SegmentCreateInput) error { + if cmd.Flags().Changed("name") { + input.Name = &f.Name + } + if cmd.Flags().Changed("type") { + input.Type = f.Type + } + if cmd.Flags().Changed("location-group-id") { + input.LocationGroupID = f.LocationGroupID + } + if cmd.Flags().Changed("member") { + membersMap, err := parseMembers(f.Members) + if err != nil { + return err + } + + input.Members = membersMap + } + if cmd.Flags().Changed("label") { + labelsMap, err := base.ParseLabels(f.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,