diff --git a/docs/stackit_beta_network-area_create.md b/docs/stackit_beta_network-area_create.md index ed018d657..5edf5af6e 100644 --- a/docs/stackit_beta_network-area_create.md +++ b/docs/stackit_beta_network-area_create.md @@ -21,6 +21,9 @@ stackit beta network-area create [flags] Create a network area with name "network-area-3" in organization with ID "xxx" with network ranges, transfer network and additional options $ stackit beta network-area create --name network-area-3 --organization-id xxx --network-ranges "1.1.1.0/24,192.123.1.0/24" --transfer-network "192.160.0.0/24" --default-prefix-length 25 --max-prefix-length 29 --min-prefix-length 24 + + Create a network area with name "network-area-1" in organization with ID "xxx" with network ranges and a transfer network and labels "key=value,key1=value1" + $ stackit beta network-area create --name network-area-1 --organization-id xxx --network-ranges "1.1.1.0/24,192.123.1.0/24" --transfer-network "192.160.0.0/24" --labels key=value,key1=value1 ``` ### Options @@ -29,6 +32,7 @@ stackit beta network-area create [flags] --default-prefix-length int The default prefix length for networks in the network area --dns-name-servers strings List of DNS name server IPs -h, --help Help for "stackit beta network-area create" + --labels stringToString Labels are key-value string pairs which can be attached to a network-area. E.g. '--labels key1=value1,key2=value2,...' (default []) --max-prefix-length int The maximum prefix length for networks in the network area --min-prefix-length int The minimum prefix length for networks in the network area -n, --name string Network area name diff --git a/docs/stackit_beta_network-area_list.md b/docs/stackit_beta_network-area_list.md index a76462e33..2cdfb14b5 100644 --- a/docs/stackit_beta_network-area_list.md +++ b/docs/stackit_beta_network-area_list.md @@ -21,12 +21,16 @@ stackit beta network-area list [flags] Lists up to 10 network areas of organization "xxx" $ stackit beta network-area list --organization-id xxx --limit 10 + + Lists all network areas of organization "xxx" which contains the label yyy + $ stackit beta network-area list --organization-id xxx --label-selector yyy ``` ### Options ``` -h, --help Help for "stackit beta network-area list" + --label-selector string Filter by label --limit int Maximum number of entries to list --organization-id string Organization ID ``` diff --git a/docs/stackit_beta_network-area_update.md b/docs/stackit_beta_network-area_update.md index 3616afaef..8c149885e 100644 --- a/docs/stackit_beta_network-area_update.md +++ b/docs/stackit_beta_network-area_update.md @@ -23,6 +23,7 @@ stackit beta network-area update AREA_ID [flags] --default-prefix-length int The default prefix length for networks in the network area --dns-name-servers strings List of DNS name server IPs -h, --help Help for "stackit beta network-area update" + --labels stringToString Labels are key-value string pairs which can be attached to a network-area. E.g. '--labels key1=value1,key2=value2,...' (default []) --max-prefix-length int The maximum prefix length for networks in the network area --min-prefix-length int The minimum prefix length for networks in the network area -n, --name string Network area name diff --git a/docs/stackit_beta_network_create.md b/docs/stackit_beta_network_create.md index 11e816773..9b4c711af 100644 --- a/docs/stackit_beta_network_create.md +++ b/docs/stackit_beta_network_create.md @@ -22,6 +22,9 @@ stackit beta network create [flags] Create a network with name "network-1" and no gateway $ stackit beta network create --name network-1 --no-ipv4-gateway + Create a network with name "network-1" and labels "key=value,key1=value1" + $ stackit beta network create --name network-1 --labels key=value,key1=value1 + Create an IPv4 network with name "network-1" with DNS name servers, a prefix and a gateway $ stackit beta network create --name network-1 --ipv4-dns-name-servers "1.1.1.1,8.8.8.8,9.9.9.9" --ipv4-prefix "10.1.2.0/24" --ipv4-gateway "10.1.2.3" @@ -41,6 +44,7 @@ stackit beta network create [flags] --ipv6-gateway string The IPv6 gateway of a network. If not specified, the first IP of the network will be assigned as the gateway --ipv6-prefix string The IPv6 prefix of the network (CIDR) --ipv6-prefix-length int The prefix length of the IPv6 network + --labels stringToString Labels are key-value string pairs which can be attached to a network. E.g. '--labels key1=value1,key2=value2,...' (default []) -n, --name string Network name --no-ipv4-gateway If set to true, the network doesn't have an IPv4 gateway --no-ipv6-gateway If set to true, the network doesn't have an IPv6 gateway diff --git a/docs/stackit_beta_network_list.md b/docs/stackit_beta_network_list.md index 0aa13029c..0edb4bbec 100644 --- a/docs/stackit_beta_network_list.md +++ b/docs/stackit_beta_network_list.md @@ -21,13 +21,17 @@ stackit beta network list [flags] Lists up to 10 networks $ stackit beta network list --limit 10 + + Lists all networks which contains the label xxx + $ tackit beta network list --label-selector xxx ``` ### Options ``` - -h, --help Help for "stackit beta network list" - --limit int Maximum number of entries to list + -h, --help Help for "stackit beta network list" + --label-selector string Filter by label + --limit int Maximum number of entries to list ``` ### Options inherited from parent commands diff --git a/docs/stackit_beta_network_update.md b/docs/stackit_beta_network_update.md index 24b1326d2..35b76c078 100644 --- a/docs/stackit_beta_network_update.md +++ b/docs/stackit_beta_network_update.md @@ -34,6 +34,7 @@ stackit beta network update NETWORK_ID [flags] --ipv4-gateway string The IPv4 gateway of a network. If not specified, the first IP of the network will be assigned as the gateway --ipv6-dns-name-servers strings List of DNS name servers for IPv6. Nameservers cannot be defined for routed networks --ipv6-gateway string The IPv6 gateway of a network. If not specified, the first IP of the network will be assigned as the gateway + --labels stringToString Labels are key-value string pairs which can be attached to a network. E.g. '--labels key1=value1,key2=value2,...' (default []) -n, --name string Network name --no-ipv4-gateway If set to true, the network doesn't have an IPv4 gateway --no-ipv6-gateway If set to true, the network doesn't have an IPv6 gateway diff --git a/internal/cmd/beta/network-area/create/create.go b/internal/cmd/beta/network-area/create/create.go index 2df562c6d..44cec6e82 100644 --- a/internal/cmd/beta/network-area/create/create.go +++ b/internal/cmd/beta/network-area/create/create.go @@ -29,6 +29,7 @@ const ( defaultPrefixLengthFlag = "default-prefix-length" maxPrefixLengthFlag = "max-prefix-length" minPrefixLengthFlag = "min-prefix-length" + labelFlag = "labels" ) type inputModel struct { @@ -41,6 +42,7 @@ type inputModel struct { DefaultPrefixLength *int64 MaxPrefixLength *int64 MinPrefixLength *int64 + Labels *map[string]string } func NewCmd(p *print.Printer) *cobra.Command { @@ -62,6 +64,10 @@ func NewCmd(p *print.Printer) *cobra.Command { `Create a network area with name "network-area-3" in organization with ID "xxx" with network ranges, transfer network and additional options`, `$ stackit beta network-area create --name network-area-3 --organization-id xxx --network-ranges "1.1.1.0/24,192.123.1.0/24" --transfer-network "192.160.0.0/24" --default-prefix-length 25 --max-prefix-length 29 --min-prefix-length 24`, ), + examples.NewExample( + `Create a network area with name "network-area-1" in organization with ID "xxx" with network ranges and a transfer network and labels "key=value,key1=value1"`, + `$ stackit beta network-area create --name network-area-1 --organization-id xxx --network-ranges "1.1.1.0/24,192.123.1.0/24" --transfer-network "192.160.0.0/24" --labels key=value,key1=value1`, + ), ), RunE: func(cmd *cobra.Command, _ []string) error { ctx := context.Background() @@ -119,6 +125,7 @@ func configureFlags(cmd *cobra.Command) { cmd.Flags().Int64(defaultPrefixLengthFlag, 0, "The default prefix length for networks in the network area") cmd.Flags().Int64(maxPrefixLengthFlag, 0, "The maximum prefix length for networks in the network area") cmd.Flags().Int64(minPrefixLengthFlag, 0, "The minimum prefix length for networks in the network area") + cmd.Flags().StringToString(labelFlag, nil, "Labels are key-value string pairs which can be attached to a network-area. E.g. '--labels key1=value1,key2=value2,...'") err := flags.MarkFlagsRequired(cmd, nameFlag, organizationIdFlag, networkRangesFlag, transferNetworkFlag) cobra.CheckErr(err) @@ -137,6 +144,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { DefaultPrefixLength: flags.FlagToInt64Pointer(p, cmd, defaultPrefixLengthFlag), MaxPrefixLength: flags.FlagToInt64Pointer(p, cmd, maxPrefixLengthFlag), MinPrefixLength: flags.FlagToInt64Pointer(p, cmd, minPrefixLengthFlag), + Labels: flags.FlagToStringToStringPointer(p, cmd, labelFlag), } if p.IsVerbosityDebug() { @@ -161,8 +169,18 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APICli } } + var labelsMap *map[string]interface{} + 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.CreateNetworkAreaPayload{ - Name: model.Name, + Name: model.Name, + Labels: labelsMap, AddressFamily: &iaas.CreateAreaAddressFamily{ Ipv4: &iaas.CreateAreaIPv4{ DefaultNameservers: model.DnsNameServers, diff --git a/internal/cmd/beta/network-area/create/create_test.go b/internal/cmd/beta/network-area/create/create_test.go index 2912000db..3950a2093 100644 --- a/internal/cmd/beta/network-area/create/create_test.go +++ b/internal/cmd/beta/network-area/create/create_test.go @@ -31,6 +31,7 @@ func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]st defaultPrefixLengthFlag: "24", maxPrefixLengthFlag: "24", minPrefixLengthFlag: "24", + labelFlag: "key=value", } for _, mod := range mods { mod(flagValues) @@ -51,6 +52,9 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { DefaultPrefixLength: utils.Ptr(int64(24)), MaxPrefixLength: utils.Ptr(int64(24)), MinPrefixLength: utils.Ptr(int64(24)), + Labels: utils.Ptr(map[string]string{ + "key": "value", + }), } for _, mod := range mods { mod(model) @@ -70,6 +74,9 @@ func fixtureRequest(mods ...func(request *iaas.ApiCreateNetworkAreaRequest)) iaa func fixturePayload(mods ...func(payload *iaas.CreateNetworkAreaPayload)) iaas.CreateNetworkAreaPayload { payload := iaas.CreateNetworkAreaPayload{ Name: utils.Ptr("example-network-area-name"), + Labels: utils.Ptr(map[string]interface{}{ + "key": "value", + }), AddressFamily: &iaas.CreateAreaAddressFamily{ Ipv4: &iaas.CreateAreaIPv4{ DefaultNameservers: utils.Ptr([]string{"1.1.1.0", "1.1.2.0"}), @@ -171,6 +178,16 @@ func TestParseInput(t *testing.T) { }), isValid: false, }, + { + description: "labels missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, labelFlag) + }), + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Labels = nil + }), + isValid: true, + }, } for _, tt := range tests { diff --git a/internal/cmd/beta/network-area/describe/describe.go b/internal/cmd/beta/network-area/describe/describe.go index 13d2707d4..661015a0d 100644 --- a/internal/cmd/beta/network-area/describe/describe.go +++ b/internal/cmd/beta/network-area/describe/describe.go @@ -196,6 +196,14 @@ func outputResult(p *print.Printer, outputFormat string, networkArea *iaas.Netwo table.AddSeparator() } } + if networkArea.Labels != nil && len(*networkArea.Labels) > 0 { + var labels []string + for key, value := range *networkArea.Labels { + labels = append(labels, fmt.Sprintf("%s: %s", key, value)) + } + table.AddRow("LABELS", strings.Join(labels, "\n")) + table.AddSeparator() + } if len(attachedProjects) > 0 { table.AddRow("ATTACHED PROJECTS IDS", strings.Join(attachedProjects, "\n")) table.AddSeparator() diff --git a/internal/cmd/beta/network-area/list/list.go b/internal/cmd/beta/network-area/list/list.go index cb4916a79..6f38c1b66 100644 --- a/internal/cmd/beta/network-area/list/list.go +++ b/internal/cmd/beta/network-area/list/list.go @@ -24,12 +24,14 @@ import ( const ( limitFlag = "limit" organizationIdFlag = "organization-id" + labelSelectorFlag = "label-selector" ) type inputModel struct { *globalflags.GlobalFlagModel Limit *int64 OrganizationId *string + LabelSelector *string } func NewCmd(p *print.Printer) *cobra.Command { @@ -51,6 +53,10 @@ func NewCmd(p *print.Printer) *cobra.Command { `Lists up to 10 network areas of organization "xxx"`, "$ stackit beta network-area list --organization-id xxx --limit 10", ), + examples.NewExample( + `Lists all network areas of organization "xxx" which contains the label yyy`, + "$ stackit beta network-area list --organization-id xxx --label-selector yyy", + ), ), RunE: func(cmd *cobra.Command, _ []string) error { ctx := context.Background() @@ -104,6 +110,7 @@ func NewCmd(p *print.Printer) *cobra.Command { func configureFlags(cmd *cobra.Command) { cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list") cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID") + cmd.Flags().String(labelSelectorFlag, "", "Filter by label") err := flags.MarkFlagsRequired(cmd, organizationIdFlag) cobra.CheckErr(err) @@ -123,6 +130,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { GlobalFlagModel: globalFlags, Limit: limit, OrganizationId: flags.FlagToStringPointer(p, cmd, organizationIdFlag), + LabelSelector: flags.FlagToStringPointer(p, cmd, labelSelectorFlag), } if p.IsVerbosityDebug() { @@ -138,7 +146,11 @@ func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { } func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiListNetworkAreasRequest { - return apiClient.ListNetworkAreas(ctx, *model.OrganizationId) + req := apiClient.ListNetworkAreas(ctx, *model.OrganizationId) + if model.LabelSelector != nil { + req = req.LabelSelector(*model.LabelSelector) + } + return req } func outputResult(p *print.Printer, outputFormat string, networkAreas []iaas.NetworkArea) error { diff --git a/internal/cmd/beta/network-area/list/list_test.go b/internal/cmd/beta/network-area/list/list_test.go index 46282da26..8a5d3f6cc 100644 --- a/internal/cmd/beta/network-area/list/list_test.go +++ b/internal/cmd/beta/network-area/list/list_test.go @@ -19,11 +19,13 @@ type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") var testClient = &iaas.APIClient{} var testOrganizationId = uuid.NewString() +var testLabelSelector = "foo=bar" func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { flagValues := map[string]string{ organizationIdFlag: testOrganizationId, limitFlag: "10", + labelSelectorFlag: testLabelSelector, } for _, mod := range mods { mod(flagValues) @@ -38,6 +40,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { }, OrganizationId: &testOrganizationId, Limit: utils.Ptr(int64(10)), + LabelSelector: utils.Ptr(testLabelSelector), } for _, mod := range mods { mod(model) @@ -47,6 +50,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { func fixtureRequest(mods ...func(request *iaas.ApiListNetworkAreasRequest)) iaas.ApiListNetworkAreasRequest { request := testClient.ListNetworkAreas(testCtx, testOrganizationId) + request = request.LabelSelector(testLabelSelector) for _, mod := range mods { mod(&request) } @@ -111,6 +115,16 @@ func TestParseInput(t *testing.T) { }), isValid: false, }, + { + description: "label selector empty", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[labelSelectorFlag] = "" + }), + isValid: true, + expectedModel: fixtureInputModel(func(inputModel *inputModel) { + inputModel.LabelSelector = utils.Ptr("") + }), + }, } for _, tt := range tests { diff --git a/internal/cmd/beta/network-area/update/update.go b/internal/cmd/beta/network-area/update/update.go index a7d7bb722..105efff3a 100644 --- a/internal/cmd/beta/network-area/update/update.go +++ b/internal/cmd/beta/network-area/update/update.go @@ -30,6 +30,7 @@ const ( defaultPrefixLengthFlag = "default-prefix-length" maxPrefixLengthFlag = "max-prefix-length" minPrefixLengthFlag = "min-prefix-length" + labelFlag = "labels" ) type inputModel struct { @@ -41,6 +42,7 @@ type inputModel struct { DefaultPrefixLength *int64 MaxPrefixLength *int64 MinPrefixLength *int64 + Labels *map[string]string } func NewCmd(p *print.Printer) *cobra.Command { @@ -109,6 +111,7 @@ func configureFlags(cmd *cobra.Command) { cmd.Flags().Int64(defaultPrefixLengthFlag, 0, "The default prefix length for networks in the network area") cmd.Flags().Int64(maxPrefixLengthFlag, 0, "The maximum prefix length for networks in the network area") cmd.Flags().Int64(minPrefixLengthFlag, 0, "The minimum prefix length for networks in the network area") + cmd.Flags().StringToString(labelFlag, nil, "Labels are key-value string pairs which can be attached to a network-area. E.g. '--labels key1=value1,key2=value2,...'") err := flags.MarkFlagsRequired(cmd, organizationIdFlag) cobra.CheckErr(err) @@ -128,6 +131,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu DefaultPrefixLength: flags.FlagToInt64Pointer(p, cmd, defaultPrefixLengthFlag), MaxPrefixLength: flags.FlagToInt64Pointer(p, cmd, maxPrefixLengthFlag), MinPrefixLength: flags.FlagToInt64Pointer(p, cmd, minPrefixLengthFlag), + Labels: flags.FlagToStringToStringPointer(p, cmd, labelFlag), } if p.IsVerbosityDebug() { @@ -145,8 +149,18 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiPartialUpdateNetworkAreaRequest { req := apiClient.PartialUpdateNetworkArea(ctx, *model.OrganizationId, model.AreaId) + var labelsMap *map[string]interface{} + 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.PartialUpdateNetworkAreaPayload{ - Name: model.Name, + Name: model.Name, + Labels: labelsMap, AddressFamily: &iaas.UpdateAreaAddressFamily{ Ipv4: &iaas.UpdateAreaIPv4{ DefaultNameservers: model.DnsNameServers, diff --git a/internal/cmd/beta/network-area/update/update_test.go b/internal/cmd/beta/network-area/update/update_test.go index f91e0b583..1ce7994f6 100644 --- a/internal/cmd/beta/network-area/update/update_test.go +++ b/internal/cmd/beta/network-area/update/update_test.go @@ -40,6 +40,7 @@ func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]st defaultPrefixLengthFlag: "24", maxPrefixLengthFlag: "24", minPrefixLengthFlag: "24", + labelFlag: "key=value", } for _, mod := range mods { mod(flagValues) @@ -59,6 +60,9 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { DefaultPrefixLength: utils.Ptr(int64(24)), MaxPrefixLength: utils.Ptr(int64(24)), MinPrefixLength: utils.Ptr(int64(24)), + Labels: utils.Ptr(map[string]string{ + "key": "value", + }), } for _, mod := range mods { mod(model) @@ -78,6 +82,9 @@ func fixtureRequest(mods ...func(request *iaas.ApiPartialUpdateNetworkAreaReques func fixturePayload(mods ...func(payload *iaas.PartialUpdateNetworkAreaPayload)) iaas.PartialUpdateNetworkAreaPayload { payload := iaas.PartialUpdateNetworkAreaPayload{ Name: utils.Ptr("example-network-area-name"), + Labels: utils.Ptr(map[string]interface{}{ + "key": "value", + }), AddressFamily: &iaas.UpdateAreaAddressFamily{ Ipv4: &iaas.UpdateAreaIPv4{ DefaultNameservers: utils.Ptr([]string{"1.1.1.0", "1.1.2.0"}), @@ -183,6 +190,17 @@ func TestParseInput(t *testing.T) { }), isValid: false, }, + { + description: "labels missing", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, labelFlag) + }), + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Labels = nil + }), + isValid: true, + }, } for _, tt := range tests { diff --git a/internal/cmd/beta/network/create/create.go b/internal/cmd/beta/network/create/create.go index 2d902e599..38edf55ac 100644 --- a/internal/cmd/beta/network/create/create.go +++ b/internal/cmd/beta/network/create/create.go @@ -35,6 +35,7 @@ const ( nonRoutedFlag = "non-routed" noIpv4GatewayFlag = "no-ipv4-gateway" noIpv6GatewayFlag = "no-ipv6-gateway" + labelFlag = "labels" ) type inputModel struct { @@ -51,6 +52,7 @@ type inputModel struct { NonRouted bool NoIPv4Gateway bool NoIPv6Gateway bool + Labels *map[string]string } func NewCmd(p *print.Printer) *cobra.Command { @@ -72,6 +74,10 @@ func NewCmd(p *print.Printer) *cobra.Command { `Create a network with name "network-1" and no gateway`, `$ stackit beta network create --name network-1 --no-ipv4-gateway`, ), + examples.NewExample( + `Create a network with name "network-1" and labels "key=value,key1=value1"`, + `$ stackit beta network create --name network-1 --labels key=value,key1=value1`, + ), examples.NewExample( `Create an IPv4 network with name "network-1" with DNS name servers, a prefix and a gateway`, `$ stackit beta network create --name network-1 --ipv4-dns-name-servers "1.1.1.1,8.8.8.8,9.9.9.9" --ipv4-prefix "10.1.2.0/24" --ipv4-gateway "10.1.2.3"`, @@ -149,6 +155,7 @@ func configureFlags(cmd *cobra.Command) { cmd.Flags().Bool(nonRoutedFlag, false, "If set to true, the network is not routed and therefore not accessible from other networks") cmd.Flags().Bool(noIpv4GatewayFlag, false, "If set to true, the network doesn't have an IPv4 gateway") cmd.Flags().Bool(noIpv6GatewayFlag, false, "If set to true, the network doesn't have an IPv6 gateway") + cmd.Flags().StringToString(labelFlag, nil, "Labels are key-value string pairs which can be attached to a network. E.g. '--labels key1=value1,key2=value2,...'") err := flags.MarkFlagsRequired(cmd, nameFlag) cobra.CheckErr(err) @@ -174,6 +181,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { NonRouted: flags.FlagToBoolValue(p, cmd, nonRoutedFlag), NoIPv4Gateway: flags.FlagToBoolValue(p, cmd, noIpv4GatewayFlag), NoIPv6Gateway: flags.FlagToBoolValue(p, cmd, noIpv6GatewayFlag), + Labels: flags.FlagToStringToStringPointer(p, cmd, labelFlag), } if p.IsVerbosityDebug() { @@ -220,6 +228,15 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APICli } } + var labelsMap *map[string]interface{} + 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 + } + } + routed := true if model.NonRouted { routed = false @@ -227,6 +244,7 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APICli payload := iaas.CreateNetworkPayload{ Name: model.Name, + Labels: labelsMap, Routed: &routed, } diff --git a/internal/cmd/beta/network/create/create_test.go b/internal/cmd/beta/network/create/create_test.go index 804cd0d25..19edc1d40 100644 --- a/internal/cmd/beta/network/create/create_test.go +++ b/internal/cmd/beta/network/create/create_test.go @@ -36,6 +36,7 @@ func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]st ipv6PrefixFlag: "2001:4860:4860::8888", ipv6GatewayFlag: "2001:4860:4860::8888", nonRoutedFlag: "false", + labelFlag: "key=value", } for _, mod := range mods { mod(flagValues) @@ -59,6 +60,9 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { IPv6Prefix: utils.Ptr("2001:4860:4860::8888"), IPv6Gateway: utils.Ptr("2001:4860:4860::8888"), NonRouted: false, + Labels: utils.Ptr(map[string]string{ + "key": "value", + }), } for _, mod := range mods { mod(model) @@ -91,6 +95,9 @@ func fixturePayload(mods ...func(payload *iaas.CreateNetworkPayload)) iaas.Creat payload := iaas.CreateNetworkPayload{ Name: utils.Ptr("example-network-name"), Routed: utils.Ptr(true), + Labels: utils.Ptr(map[string]interface{}{ + "key": "value", + }), AddressFamily: &iaas.CreateNetworkAddressFamily{ Ipv4: &iaas.CreateNetworkIPv4Body{ Nameservers: utils.Ptr([]string{"1.1.1.0", "1.1.2.0"}), @@ -236,6 +243,16 @@ func TestParseInput(t *testing.T) { model.NonRouted = true }), }, + { + description: "labels missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, labelFlag) + }), + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Labels = nil + }), + isValid: true, + }, } for _, tt := range tests { diff --git a/internal/cmd/beta/network/describe/describe.go b/internal/cmd/beta/network/describe/describe.go index 356e2540d..e98cd01e6 100644 --- a/internal/cmd/beta/network/describe/describe.go +++ b/internal/cmd/beta/network/describe/describe.go @@ -190,6 +190,14 @@ func outputResult(p *print.Printer, outputFormat string, network *iaas.Network) table.AddRow("IPv6 PREFIXES", strings.Join(ipv6prefixes, ", ")) } table.AddSeparator() + if network.Labels != nil && len(*network.Labels) > 0 { + var labels []string + for key, value := range *network.Labels { + labels = append(labels, fmt.Sprintf("%s: %s", key, value)) + } + table.AddRow("LABELS", strings.Join(labels, "\n")) + table.AddSeparator() + } err := table.Display(p) if err != nil { diff --git a/internal/cmd/beta/network/list/list.go b/internal/cmd/beta/network/list/list.go index 9b3a9a0cc..7ed81d62c 100644 --- a/internal/cmd/beta/network/list/list.go +++ b/internal/cmd/beta/network/list/list.go @@ -22,12 +22,14 @@ import ( ) const ( - limitFlag = "limit" + limitFlag = "limit" + labelSelectorFlag = "label-selector" ) type inputModel struct { *globalflags.GlobalFlagModel - Limit *int64 + Limit *int64 + LabelSelector *string } func NewCmd(p *print.Printer) *cobra.Command { @@ -49,6 +51,10 @@ func NewCmd(p *print.Printer) *cobra.Command { `Lists up to 10 networks`, "$ stackit beta network list --limit 10", ), + examples.NewExample( + `Lists all networks which contains the label xxx`, + "$ tackit beta network list --label-selector xxx", + ), ), RunE: func(cmd *cobra.Command, _ []string) error { ctx := context.Background() @@ -97,6 +103,7 @@ func NewCmd(p *print.Printer) *cobra.Command { func configureFlags(cmd *cobra.Command) { cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list") + cmd.Flags().String(labelSelectorFlag, "", "Filter by label") } func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { @@ -116,6 +123,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { model := inputModel{ GlobalFlagModel: globalFlags, Limit: limit, + LabelSelector: flags.FlagToStringPointer(p, cmd, labelSelectorFlag), } if p.IsVerbosityDebug() { @@ -131,7 +139,11 @@ func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { } func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiListNetworksRequest { - return apiClient.ListNetworks(ctx, model.ProjectId) + req := apiClient.ListNetworks(ctx, model.ProjectId) + if model.LabelSelector != nil { + req = req.LabelSelector(*model.LabelSelector) + } + return req } func outputResult(p *print.Printer, outputFormat string, networks []iaas.Network) error { diff --git a/internal/cmd/beta/network/list/list_test.go b/internal/cmd/beta/network/list/list_test.go index 0d69a3f5e..9bc47dfb5 100644 --- a/internal/cmd/beta/network/list/list_test.go +++ b/internal/cmd/beta/network/list/list_test.go @@ -21,11 +21,13 @@ type testCtxKey struct{} var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") var testClient = &iaas.APIClient{} var testProjectId = uuid.NewString() +var testLabelSelector = "foo=bar" func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { flagValues := map[string]string{ - projectIdFlag: testProjectId, - limitFlag: "10", + projectIdFlag: testProjectId, + limitFlag: "10", + labelSelectorFlag: testLabelSelector, } for _, mod := range mods { mod(flagValues) @@ -39,7 +41,8 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { Verbosity: globalflags.VerbosityDefault, ProjectId: testProjectId, }, - Limit: utils.Ptr(int64(10)), + Limit: utils.Ptr(int64(10)), + LabelSelector: utils.Ptr(testLabelSelector), } for _, mod := range mods { mod(model) @@ -49,6 +52,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { func fixtureRequest(mods ...func(request *iaas.ApiListNetworksRequest)) iaas.ApiListNetworksRequest { request := testClient.ListNetworks(testCtx, testProjectId) + request = request.LabelSelector(testLabelSelector) for _, mod := range mods { mod(&request) } @@ -113,6 +117,16 @@ func TestParseInput(t *testing.T) { }), isValid: false, }, + { + description: "label selector empty", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[labelSelectorFlag] = "" + }), + isValid: true, + expectedModel: fixtureInputModel(func(inputModel *inputModel) { + inputModel.LabelSelector = utils.Ptr("") + }), + }, } for _, tt := range tests { diff --git a/internal/cmd/beta/network/update/update.go b/internal/cmd/beta/network/update/update.go index 2148fa58d..24c1c5e74 100644 --- a/internal/cmd/beta/network/update/update.go +++ b/internal/cmd/beta/network/update/update.go @@ -30,6 +30,7 @@ const ( ipv6GatewayFlag = "ipv6-gateway" noIpv4GatewayFlag = "no-ipv4-gateway" noIpv6GatewayFlag = "no-ipv6-gateway" + labelFlag = "labels" ) type inputModel struct { @@ -42,6 +43,7 @@ type inputModel struct { IPv6Gateway *string NoIPv4Gateway bool NoIPv6Gateway bool + Labels *map[string]string } func NewCmd(p *print.Printer) *cobra.Command { @@ -136,6 +138,7 @@ func configureFlags(cmd *cobra.Command) { cmd.Flags().String(ipv6GatewayFlag, "", "The IPv6 gateway of a network. If not specified, the first IP of the network will be assigned as the gateway") cmd.Flags().Bool(noIpv4GatewayFlag, false, "If set to true, the network doesn't have an IPv4 gateway") cmd.Flags().Bool(noIpv6GatewayFlag, false, "If set to true, the network doesn't have an IPv6 gateway") + cmd.Flags().StringToString(labelFlag, nil, "Labels are key-value string pairs which can be attached to a network. E.g. '--labels key1=value1,key2=value2,...'") } func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { @@ -156,6 +159,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu IPv6Gateway: flags.FlagToStringPointer(p, cmd, ipv6GatewayFlag), NoIPv4Gateway: flags.FlagToBoolValue(p, cmd, noIpv4GatewayFlag), NoIPv6Gateway: flags.FlagToBoolValue(p, cmd, noIpv6GatewayFlag), + Labels: flags.FlagToStringToStringPointer(p, cmd, labelFlag), } if p.IsVerbosityDebug() { @@ -174,6 +178,15 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APICli req := apiClient.PartialUpdateNetwork(ctx, model.ProjectId, model.NetworkId) addressFamily := &iaas.UpdateNetworkAddressFamily{} + var labelsMap *map[string]interface{} + 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 + } + } + if model.IPv6DnsNameServers != nil || model.NoIPv6Gateway || model.IPv6Gateway != nil { addressFamily.Ipv6 = &iaas.UpdateNetworkIPv6Body{ Nameservers: model.IPv6DnsNameServers, @@ -199,7 +212,8 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APICli } payload := iaas.PartialUpdateNetworkPayload{ - Name: model.Name, + Name: model.Name, + Labels: labelsMap, } if addressFamily.Ipv4 != nil || addressFamily.Ipv6 != nil { diff --git a/internal/cmd/beta/network/update/update_test.go b/internal/cmd/beta/network/update/update_test.go index 99b3e8ba6..7a1b243c5 100644 --- a/internal/cmd/beta/network/update/update_test.go +++ b/internal/cmd/beta/network/update/update_test.go @@ -42,6 +42,7 @@ func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]st ipv4GatewayFlag: "10.1.2.3", ipv6DnsNameServersFlag: "2001:4860:4860::8888,2001:4860:4860::8844", ipv6GatewayFlag: "2001:4860:4860::8888", + labelFlag: "key=value", } for _, mod := range mods { mod(flagValues) @@ -61,6 +62,9 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { IPv4Gateway: utils.Ptr("10.1.2.3"), IPv6DnsNameServers: utils.Ptr([]string{"2001:4860:4860::8888", "2001:4860:4860::8844"}), IPv6Gateway: utils.Ptr("2001:4860:4860::8888"), + Labels: utils.Ptr(map[string]string{ + "key": "value", + }), } for _, mod := range mods { mod(model) @@ -80,6 +84,9 @@ func fixtureRequest(mods ...func(request *iaas.ApiPartialUpdateNetworkRequest)) func fixturePayload(mods ...func(payload *iaas.PartialUpdateNetworkPayload)) iaas.PartialUpdateNetworkPayload { payload := iaas.PartialUpdateNetworkPayload{ Name: utils.Ptr("example-network-name"), + Labels: utils.Ptr(map[string]interface{}{ + "key": "value", + }), AddressFamily: &iaas.UpdateNetworkAddressFamily{ Ipv4: &iaas.UpdateNetworkIPv4Body{ Nameservers: utils.Ptr([]string{"1.1.1.0", "1.1.2.0"}), @@ -218,6 +225,17 @@ func TestParseInput(t *testing.T) { model.IPv6Gateway = nil }), }, + { + description: "labels missing", + argValues: fixtureArgValues(), + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, labelFlag) + }), + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Labels = nil + }), + isValid: true, + }, } for _, tt := range tests {