From 165a585f050c5df1e4c368a71ff96d5daff1333e Mon Sep 17 00:00:00 2001 From: Alexandru Bordei Date: Tue, 2 Apr 2024 13:43:39 +0200 Subject: [PATCH] added support for oob subnet manipulation, MS-5367,MS-5369 --- cmd/metalcloud-cli/helpers.go | 2 + go.mod | 2 + go.work.sum | 1 + helpers/mock_client.go | 177 +++++++++++++--- pkg/subnetoob/cmd_subnet_oob.go | 325 ++++++++++++++++++++++++++++++ pkg/subnetpool/cmd_subnet_pool.go | 24 ++- scripts/fix_package.go | 1 + 7 files changed, 496 insertions(+), 36 deletions(-) create mode 100644 pkg/subnetoob/cmd_subnet_oob.go diff --git a/cmd/metalcloud-cli/helpers.go b/cmd/metalcloud-cli/helpers.go index 0231498..ff553ff 100644 --- a/cmd/metalcloud-cli/helpers.go +++ b/cmd/metalcloud-cli/helpers.go @@ -30,6 +30,7 @@ import ( "github.com/metalsoft-io/metalcloud-cli/pkg/shellcompletion" "github.com/metalsoft-io/metalcloud-cli/pkg/stagedefinition" "github.com/metalsoft-io/metalcloud-cli/pkg/storage" + "github.com/metalsoft-io/metalcloud-cli/pkg/subnetoob" "github.com/metalsoft-io/metalcloud-cli/pkg/subnetpool" "github.com/metalsoft-io/metalcloud-cli/pkg/switchcontroller" "github.com/metalsoft-io/metalcloud-cli/pkg/switchdevice" @@ -183,6 +184,7 @@ func getCommands(clients map[string]metalcloud.MetalCloudClient) []command.Comma stagedefinition.StageDefinitionsCmds, storage.StorageCmds, subnetpool.SubnetPoolCmds, + subnetoob.SubnetOOBCmds, switchcontroller.SwitchControllerCmds, switchdevice.SwitchCmds, switchdevice.SwitchDefaultsCmds, diff --git a/go.mod b/go.mod index 5618f86..d274f00 100644 --- a/go.mod +++ b/go.mod @@ -49,3 +49,5 @@ require ( google.golang.org/protobuf v1.28.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect ) + +replace github.com/metalsoft-io/metal-cloud-sdk-go/v3 => /Users/alex/code/metal-cloud-sdk-go \ No newline at end of file diff --git a/go.work.sum b/go.work.sum index 704dbff..b7611ab 100644 --- a/go.work.sum +++ b/go.work.sum @@ -20,3 +20,4 @@ github.com/metalsoft-io/metal-cloud-sdk-go/v2 v2.12.3 h1:zqGgC43n3bnMx48RPaCNLxu github.com/metalsoft-io/metal-cloud-sdk-go/v2 v2.12.3/go.mod h1:4sa1GQG0XISUUkvChL2iliWa0t3KGKAfB9dsbW2zyh4= golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= diff --git a/helpers/mock_client.go b/helpers/mock_client.go index 8b5e6b3..0040eb8 100644 --- a/helpers/mock_client.go +++ b/helpers/mock_client.go @@ -3327,21 +3327,6 @@ func (mr *MockMetalCloudClientMockRecorder) StoragePoolGet(serverID, decryptPass return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StoragePoolGet", reflect.TypeOf((*MockMetalCloudClient)(nil).StoragePoolGet), serverID, decryptPasswd) } -// SubnetPoolCreate mocks base method -func (m *MockMetalCloudClient) SubnetPoolCreate(subnetPool metalcloud.SubnetPool) (*metalcloud.SubnetPool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SubnetPoolCreate", subnetPool) - ret0, _ := ret[0].(*metalcloud.SubnetPool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// SubnetPoolCreate indicates an expected call of SubnetPoolCreate -func (mr *MockMetalCloudClientMockRecorder) SubnetPoolCreate(subnetPool interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubnetPoolCreate", reflect.TypeOf((*MockMetalCloudClient)(nil).SubnetPoolCreate), subnetPool) -} - // SubnetGet mocks base method func (m *MockMetalCloudClient) SubnetGet(subnetID int) (*metalcloud.Subnet, error) { m.ctrl.T.Helper() @@ -3386,6 +3371,124 @@ func (mr *MockMetalCloudClientMockRecorder) SubnetDelete(subnetID interface{}) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubnetDelete", reflect.TypeOf((*MockMetalCloudClient)(nil).SubnetDelete), subnetID) } +// SubnetPoolCreateOrUpdate mocks base method +func (m *MockMetalCloudClient) SubnetPoolCreateOrUpdate(subnetPool metalcloud.SubnetPool) (*metalcloud.SubnetPool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SubnetPoolCreateOrUpdate", subnetPool) + ret0, _ := ret[0].(*metalcloud.SubnetPool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SubnetPoolCreateOrUpdate indicates an expected call of SubnetPoolCreateOrUpdate +func (mr *MockMetalCloudClientMockRecorder) SubnetPoolCreateOrUpdate(subnetPool interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubnetPoolCreateOrUpdate", reflect.TypeOf((*MockMetalCloudClient)(nil).SubnetPoolCreateOrUpdate), subnetPool) +} + +// SubnetOOBGet mocks base method +func (m *MockMetalCloudClient) SubnetOOBGet(subnetOOBID int) (*metalcloud.SubnetOOB, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SubnetOOBGet", subnetOOBID) + ret0, _ := ret[0].(*metalcloud.SubnetOOB) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SubnetOOBGet indicates an expected call of SubnetOOBGet +func (mr *MockMetalCloudClientMockRecorder) SubnetOOBGet(subnetOOBID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubnetOOBGet", reflect.TypeOf((*MockMetalCloudClient)(nil).SubnetOOBGet), subnetOOBID) +} + +// SubnetOOBGetByLabel mocks base method +func (m *MockMetalCloudClient) SubnetOOBGetByLabel(subnetOOBLabel string) (*metalcloud.SubnetOOB, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SubnetOOBGetByLabel", subnetOOBLabel) + ret0, _ := ret[0].(*metalcloud.SubnetOOB) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SubnetOOBGetByLabel indicates an expected call of SubnetOOBGetByLabel +func (mr *MockMetalCloudClientMockRecorder) SubnetOOBGetByLabel(subnetOOBLabel interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubnetOOBGetByLabel", reflect.TypeOf((*MockMetalCloudClient)(nil).SubnetOOBGetByLabel), subnetOOBLabel) +} + +// SubnetOOBCreate mocks base method +func (m *MockMetalCloudClient) SubnetOOBCreate(subnetOOB metalcloud.SubnetOOB) (*metalcloud.SubnetOOB, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SubnetOOBCreate", subnetOOB) + ret0, _ := ret[0].(*metalcloud.SubnetOOB) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SubnetOOBCreate indicates an expected call of SubnetOOBCreate +func (mr *MockMetalCloudClientMockRecorder) SubnetOOBCreate(subnetOOB interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubnetOOBCreate", reflect.TypeOf((*MockMetalCloudClient)(nil).SubnetOOBCreate), subnetOOB) +} + +// SubnetOOBDelete mocks base method +func (m *MockMetalCloudClient) SubnetOOBDelete(subnetOOBID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SubnetOOBDelete", subnetOOBID) + ret0, _ := ret[0].(error) + return ret0 +} + +// SubnetOOBDelete indicates an expected call of SubnetOOBDelete +func (mr *MockMetalCloudClientMockRecorder) SubnetOOBDelete(subnetOOBID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubnetOOBDelete", reflect.TypeOf((*MockMetalCloudClient)(nil).SubnetOOBDelete), subnetOOBID) +} + +// SubnetOOBDeleteByLabel mocks base method +func (m *MockMetalCloudClient) SubnetOOBDeleteByLabel(subnetOOBLabel string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SubnetOOBDeleteByLabel", subnetOOBLabel) + ret0, _ := ret[0].(error) + return ret0 +} + +// SubnetOOBDeleteByLabel indicates an expected call of SubnetOOBDeleteByLabel +func (mr *MockMetalCloudClientMockRecorder) SubnetOOBDeleteByLabel(subnetOOBLabel interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubnetOOBDeleteByLabel", reflect.TypeOf((*MockMetalCloudClient)(nil).SubnetOOBDeleteByLabel), subnetOOBLabel) +} + +// SubnetOOBSearch mocks base method +func (m *MockMetalCloudClient) SubnetOOBSearch(filter string) (*[]metalcloud.SubnetOOB, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SubnetOOBSearch", filter) + ret0, _ := ret[0].(*[]metalcloud.SubnetOOB) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SubnetOOBSearch indicates an expected call of SubnetOOBSearch +func (mr *MockMetalCloudClientMockRecorder) SubnetOOBSearch(filter interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubnetOOBSearch", reflect.TypeOf((*MockMetalCloudClient)(nil).SubnetOOBSearch), filter) +} + +// SubnetPoolCreate mocks base method +func (m *MockMetalCloudClient) SubnetPoolCreate(subnetPool metalcloud.SubnetPool) (*metalcloud.SubnetPool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SubnetPoolCreate", subnetPool) + ret0, _ := ret[0].(*metalcloud.SubnetPool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SubnetPoolCreate indicates an expected call of SubnetPoolCreate +func (mr *MockMetalCloudClientMockRecorder) SubnetPoolCreate(subnetPool interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubnetPoolCreate", reflect.TypeOf((*MockMetalCloudClient)(nil).SubnetPoolCreate), subnetPool) +} + // SubnetPoolGet mocks base method func (m *MockMetalCloudClient) SubnetPoolGet(subnetPoolID int) (*metalcloud.SubnetPool, error) { m.ctrl.T.Helper() @@ -3401,6 +3504,21 @@ func (mr *MockMetalCloudClientMockRecorder) SubnetPoolGet(subnetPoolID interface return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubnetPoolGet", reflect.TypeOf((*MockMetalCloudClient)(nil).SubnetPoolGet), subnetPoolID) } +// SubnetPoolGetByLabel mocks base method +func (m *MockMetalCloudClient) SubnetPoolGetByLabel(subnetPoolLabel string) (*metalcloud.SubnetPool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SubnetPoolGetByLabel", subnetPoolLabel) + ret0, _ := ret[0].(*metalcloud.SubnetPool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SubnetPoolGetByLabel indicates an expected call of SubnetPoolGetByLabel +func (mr *MockMetalCloudClientMockRecorder) SubnetPoolGetByLabel(subnetPoolLabel interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubnetPoolGetByLabel", reflect.TypeOf((*MockMetalCloudClient)(nil).SubnetPoolGetByLabel), subnetPoolLabel) +} + // SubnetPoolPrefixSizesStats mocks base method func (m *MockMetalCloudClient) SubnetPoolPrefixSizesStats(subnetPoolID int) (*metalcloud.SubnetPoolUtilization, error) { m.ctrl.T.Helper() @@ -3430,6 +3548,20 @@ func (mr *MockMetalCloudClientMockRecorder) SubnetPoolDelete(subnetPoolID interf return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubnetPoolDelete", reflect.TypeOf((*MockMetalCloudClient)(nil).SubnetPoolDelete), subnetPoolID) } +// SubnetPoolDeleteByLabel mocks base method +func (m *MockMetalCloudClient) SubnetPoolDeleteByLabel(subnetPoolLabel string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SubnetPoolDeleteByLabel", subnetPoolLabel) + ret0, _ := ret[0].(error) + return ret0 +} + +// SubnetPoolDeleteByLabel indicates an expected call of SubnetPoolDeleteByLabel +func (mr *MockMetalCloudClientMockRecorder) SubnetPoolDeleteByLabel(subnetPoolLabel interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubnetPoolDeleteByLabel", reflect.TypeOf((*MockMetalCloudClient)(nil).SubnetPoolDeleteByLabel), subnetPoolLabel) +} + // SubnetPools mocks base method func (m *MockMetalCloudClient) SubnetPools() (*[]metalcloud.SubnetPool, error) { m.ctrl.T.Helper() @@ -3460,21 +3592,6 @@ func (mr *MockMetalCloudClientMockRecorder) SubnetPoolSearch(filter interface{}) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubnetPoolSearch", reflect.TypeOf((*MockMetalCloudClient)(nil).SubnetPoolSearch), filter) } -// SubnetPoolCreateOrUpdate mocks base method -func (m *MockMetalCloudClient) SubnetPoolCreateOrUpdate(subnetPool metalcloud.SubnetPool) (*metalcloud.SubnetPool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SubnetPoolCreateOrUpdate", subnetPool) - ret0, _ := ret[0].(*metalcloud.SubnetPool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// SubnetPoolCreateOrUpdate indicates an expected call of SubnetPoolCreateOrUpdate -func (mr *MockMetalCloudClientMockRecorder) SubnetPoolCreateOrUpdate(subnetPool interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubnetPoolCreateOrUpdate", reflect.TypeOf((*MockMetalCloudClient)(nil).SubnetPoolCreateOrUpdate), subnetPool) -} - // SwitchDeviceGet mocks base method func (m *MockMetalCloudClient) SwitchDeviceGet(networkEquipmentID int, decryptPasswd bool) (*metalcloud.SwitchDevice, error) { m.ctrl.T.Helper() diff --git a/pkg/subnetoob/cmd_subnet_oob.go b/pkg/subnetoob/cmd_subnet_oob.go new file mode 100644 index 0000000..e7622f4 --- /dev/null +++ b/pkg/subnetoob/cmd_subnet_oob.go @@ -0,0 +1,325 @@ +package subnetoob + +import ( + "flag" + "fmt" + "os" + "strings" + + metalcloud "github.com/metalsoft-io/metal-cloud-sdk-go/v3" + "github.com/metalsoft-io/metalcloud-cli/internal/colors" + "github.com/metalsoft-io/metalcloud-cli/internal/command" + "github.com/metalsoft-io/metalcloud-cli/internal/configuration" + "github.com/metalsoft-io/tableformatter" +) + +var SubnetOOBCmds = []command.Command{ + { + Description: "Lists OOB subnet ", + Subject: "subnet-oob", + AltSubject: "oob-subnet", + Predicate: "list", + AltPredicate: "ls", + FlagSet: flag.NewFlagSet("list oob subnets", flag.ExitOnError), + InitFunc: func(c *command.Command) { + c.Arguments = map[string]interface{}{ + "format": c.FlagSet.String("format", command.NilDefaultStr, "The output format. Supported values are 'json','csv','yaml'. The default format is human readable."), + "filter": c.FlagSet.String("filter", "*", "Filter to restrict the results. Defaults to '*'"), + "datacenter": c.FlagSet.String("datacenter", command.NilDefaultStr, "Quick filter to restrict the results to show only the subnets of a datacenter."), + } + }, + ExecuteFunc: subnetOOBListCmd, + Endpoint: configuration.DeveloperEndpoint, + }, + { + Description: "Get a subnet OOB.", + Subject: "subnet-oob", + AltSubject: "oob-subnet", + Predicate: "get", + AltPredicate: "show", + FlagSet: flag.NewFlagSet("Get an OOB subnet details", flag.ExitOnError), + InitFunc: func(c *command.Command) { + c.Arguments = map[string]interface{}{ + "subnet_oob_id": c.FlagSet.Int("id", command.NilDefaultInt, colors.Red("(Required)")+" Subnet oob's id"), + "format": c.FlagSet.String("format", "", "The output format. Supported values are 'json','csv','yaml'. The default format is human readable."), + "raw": c.FlagSet.Bool("raw", false, colors.Green("(Flag)")+" When set the return will be a full dump of the object. This is useful when copying configurations. Only works with json and yaml formats."), + } + }, + ExecuteFunc: subnetOOBGetCmd, + Endpoint: configuration.DeveloperEndpoint, + }, + { + Description: "Create an oob subnet.", + Subject: "subnet-oob", + AltSubject: "oob-subnet", + Predicate: "create", + AltPredicate: "new", + FlagSet: flag.NewFlagSet("Create an oob subnet", flag.ExitOnError), + InitFunc: func(c *command.Command) { + c.Arguments = map[string]interface{}{ + "format": c.FlagSet.String("format", "json", "The input format. Supported values are 'json','yaml'. The default format is json."), + "read_config_from_file": c.FlagSet.String("raw-config", command.NilDefaultStr, colors.Red("(Required)")+" Read configuration from file"), + "read_config_from_pipe": c.FlagSet.Bool("pipe", false, colors.Green("(Flag)")+" If set, read configuration from pipe instead of from a file. Either this flag or the -raw-config option must be used."), + "return_id": c.FlagSet.Bool("return-id", false, "Will print the ID of the created object. Useful for automating tasks."), + } + }, + ExecuteFunc: subnetOOBCreateCmd, + Endpoint: configuration.DeveloperEndpoint, + }, + { + Description: "Delete an OOB subnet.", + Subject: "subnet-oob", + AltSubject: "oob-subnet", + Predicate: "delete", + AltPredicate: "rm", + FlagSet: flag.NewFlagSet("delete an oob subnet", flag.ExitOnError), + InitFunc: func(c *command.Command) { + c.Arguments = map[string]interface{}{ + "subnet_oob_id": c.FlagSet.Int("id", command.NilDefaultInt, colors.Red("(Required)")+" Subnet's's id"), + "autoconfirm": c.FlagSet.Bool("autoconfirm", false, colors.Green("(Flag)")+" If set it will assume action is confirmed"), + } + }, + ExecuteFunc: subnetOOBDeleteCmd, + Endpoint: configuration.DeveloperEndpoint, + }, +} + +func subnetOOBListCmd(c *command.Command, client metalcloud.MetalCloudClient) (string, error) { + + filter := command.GetStringParam(c.Arguments["filter"]) + if datacenter, ok := command.GetStringParamOk(c.Arguments["datacenter"]); ok { + filter = fmt.Sprintf("datacenter_name: %s %s", datacenter, filter) + } + + list, err := client.SubnetOOBSearch(filter) + + if err != nil { + return "", err + } + + schema := []tableformatter.SchemaField{ + { + FieldName: "ID", + FieldType: tableformatter.TypeInt, + FieldSize: 6, + }, + { + FieldName: "LABEL", + FieldType: tableformatter.TypeString, + FieldSize: 6, + }, + { + FieldName: "DATACENTER", + FieldType: tableformatter.TypeString, + FieldSize: 6, + }, + { + FieldName: "PREFIX", + FieldType: tableformatter.TypeInt, + FieldSize: 2, + }, + { + FieldName: "FOR", + FieldType: tableformatter.TypeString, + FieldSize: 5, + }, + { + FieldName: "AUTOMATIC_ALLOC_ENABLED", + FieldType: tableformatter.TypeBool, + FieldSize: 8, + }, + { + FieldName: "RANGE_START", + FieldType: tableformatter.TypeString, + FieldSize: 5, + }, + { + FieldName: "RANGE_END", + FieldType: tableformatter.TypeString, + FieldSize: 5, + }, + } + + data := [][]interface{}{} + for _, s := range *list { + + data = append(data, []interface{}{ + + s.SubnetOOBID, + s.SubnetOOBLabel, + s.DatacenterName, + s.SubnetOOBPrefixSize, + s.SubnetOOBAllocateForResourceType, + s.SubnetOOBUseForAutoAllocation, + s.SubnetOOBRangeStartHumanReadable, + s.SubnetOOBRangeEndHumanReadable, + }) + + } + + tableformatter.TableSorter(schema).OrderBy( + schema[0].FieldName, + schema[1].FieldName, + schema[2].FieldName).Sort(data) + + table := tableformatter.Table{ + Data: data, + Schema: schema, + } + return table.RenderTable("OOB Subnets", "", command.GetStringParam(c.Arguments["format"])) +} + +func subnetOOBGetCmd(c *command.Command, client metalcloud.MetalCloudClient) (string, error) { + + id, ok := command.GetIntParamOk(c.Arguments["subnet_oob_id"]) + if !ok { + return "", fmt.Errorf("-id is required") + } + + s, err := client.SubnetOOBGet(id) + if err != nil { + return "", err + } + + schema := []tableformatter.SchemaField{ + { + FieldName: "ID", + FieldType: tableformatter.TypeInt, + FieldSize: 6, + }, + { + FieldName: "LABEL", + FieldType: tableformatter.TypeString, + FieldSize: 6, + }, + { + FieldName: "DATACENTER", + FieldType: tableformatter.TypeString, + FieldSize: 6, + }, + { + FieldName: "PREFIX", + FieldType: tableformatter.TypeInt, + FieldSize: 2, + }, + { + FieldName: "FOR", + FieldType: tableformatter.TypeString, + FieldSize: 5, + }, + { + FieldName: "AUTOMATIC_ALLOC_ENABLED", + FieldType: tableformatter.TypeBool, + FieldSize: 8, + }, + { + FieldName: "RANGE_START", + FieldType: tableformatter.TypeString, + FieldSize: 5, + }, + { + FieldName: "RANGE_END", + FieldType: tableformatter.TypeString, + FieldSize: 5, + }, + } + + data := [][]interface{}{{ + s.SubnetOOBID, + s.SubnetOOBLabel, + s.DatacenterName, + s.SubnetOOBPrefixSize, + s.SubnetOOBAllocateForResourceType, + s.SubnetOOBUseForAutoAllocation, + s.SubnetOOBRangeStartHumanReadable, + s.SubnetOOBRangeEndHumanReadable, + }} + + var sb strings.Builder + + format := command.GetStringParam(c.Arguments["format"]) + + if command.GetBoolParam(c.Arguments["raw"]) { + ret, err := tableformatter.RenderRawObject(*s, format, "SubnetOOB") + if err != nil { + return "", err + } + sb.WriteString(ret) + } else { + table := tableformatter.Table{ + Data: data, + Schema: schema, + } + ret, err := table.RenderTransposedTable("subnet oob", "", format) + if err != nil { + return "", err + } + sb.WriteString(ret) + } + + return sb.String(), nil +} + +func subnetOOBCreateCmd(c *command.Command, client metalcloud.MetalCloudClient) (string, error) { + + var sn metalcloud.SubnetOOB + + err := command.GetRawObjectFromCommand(c, &sn) + if err != nil { + return "", err + } + + ret, err := client.SubnetOOBCreate(sn) + if err != nil { + return "", err + } + + if command.GetBoolParam(c.Arguments["return_id"]) { + return fmt.Sprintf("%d", ret.SubnetOOBID), nil + } + + return "", err +} + +func subnetOOBDeleteCmd(c *command.Command, client metalcloud.MetalCloudClient) (string, error) { + + id, ok := command.GetIntParamOk(c.Arguments["subnet_oob_id"]) + if !ok { + return "", fmt.Errorf("-id is required") + } + confirm := false + + obj, err := client.SubnetOOBGet(id) + if err != nil { + return "", err + } + + if command.GetBoolParam(c.Arguments["autoconfirm"]) { + confirm = true + } else { + + confirmationMessage := fmt.Sprintf("Deleting oob subnet #%d (%s-%s). Are you sure? Type \"yes\" to continue:", + obj.SubnetOOBID, + obj.SubnetOOBRangeStartHumanReadable, + obj.SubnetOOBRangeEndHumanReadable) + + //this is simply so that we don't output a text on the command line under go test + if strings.HasSuffix(os.Args[0], ".test") { + confirmationMessage = "" + } + + confirm, err = command.RequestConfirmation(confirmationMessage) + if err != nil { + return "", err + } + + } + + if !confirm { + return "", fmt.Errorf("Operation not confirmed. Aborting") + } + + err = client.SubnetOOBDelete(obj.SubnetOOBID) + + return "", err +} diff --git a/pkg/subnetpool/cmd_subnet_pool.go b/pkg/subnetpool/cmd_subnet_pool.go index f8432d6..d570922 100644 --- a/pkg/subnetpool/cmd_subnet_pool.go +++ b/pkg/subnetpool/cmd_subnet_pool.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" "os" + "strconv" "strings" metalcloud "github.com/metalsoft-io/metal-cloud-sdk-go/v3" @@ -103,6 +104,11 @@ func subnetPoolListCmd(c *command.Command, client metalcloud.MetalCloudClient) ( FieldType: tableformatter.TypeInt, FieldSize: 6, }, + { + FieldName: "LABEL", + FieldType: tableformatter.TypeString, + FieldSize: 6, + }, { FieldName: "DATACENTER", FieldType: tableformatter.TypeString, @@ -161,28 +167,34 @@ func subnetPoolListCmd(c *command.Command, client metalcloud.MetalCloudClient) ( return "", err } - utilizationStr := fmt.Sprintf("%s (%s", utilization.IPAddressesUsableCountFree, utilization.IPAddressesUsableFreePercentOptimistic) + IPAddressesUsableFreePercentOptimistic, err := strconv.ParseFloat(strings.Trim(utilization.IPAddressesUsableFreePercentOptimistic, "%"), 32) + if err != nil { + return "", err + } + + utilizationStr := fmt.Sprintf("%d%% used (%s addresses available)", 100-int(IPAddressesUsableFreePercentOptimistic), utilization.IPAddressesUsableCountFree) networkEquipmentIdentifier := "" if s.NetworkEquipmentID != 0 { sw, err := client.SwitchDeviceGet(s.NetworkEquipmentID, false) - if err != nil { - return "", err + if err == nil { + //sometimes the switch could be deleted without the associated subnets to be present + //so we do it this way and silently fail to retrieve the switch here + networkEquipmentIdentifier = sw.NetworkEquipmentIdentifierString } - - networkEquipmentIdentifier = sw.NetworkEquipmentIdentifierString } data = append(data, []interface{}{ s.SubnetPoolID, + s.SubnetPoolLabel, s.DatacenterName, s.SubnetPoolDestination, prefixStr, networkEquipmentIdentifier, userEmail, s.SubnetPoolIsOnlyForManualAllocation, - utilizationStr + "%%)", + utilizationStr, }) } diff --git a/scripts/fix_package.go b/scripts/fix_package.go index 8931d40..8f46575 100644 --- a/scripts/fix_package.go +++ b/scripts/fix_package.go @@ -51,6 +51,7 @@ var types = [...]string{ "SharedDriveOperation", "SwitchDevice", "SubnetPool", + "SubnetOOB", "SubnetPoolUtilization", "ServerCreateUnmanaged", "CustomISO",