diff --git a/cmd/completion/ip.go b/cmd/completion/ip.go index 993878b..981129c 100644 --- a/cmd/completion/ip.go +++ b/cmd/completion/ip.go @@ -2,6 +2,7 @@ package completion import ( "github.com/metal-stack/metal-go/api/client/ip" + "github.com/metal-stack/metal-go/api/models" "github.com/spf13/cobra" ) @@ -16,3 +17,7 @@ func (c *Completion) IpListCompletion(cmd *cobra.Command, args []string, toCompl } return names, cobra.ShellCompDirectiveNoFileComp } + +func (c *Completion) IPAddressFamilyCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{models.V1IPAllocateRequestAddressfamilyIPV4, models.V1IPAllocateRequestAddressfamilyIPV6}, cobra.ShellCompDirectiveNoFileComp +} diff --git a/cmd/completion/network.go b/cmd/completion/network.go index 04422ea..8565ebe 100644 --- a/cmd/completion/network.go +++ b/cmd/completion/network.go @@ -2,6 +2,8 @@ package completion import ( "github.com/metal-stack/metal-go/api/client/network" + "github.com/metal-stack/metal-go/api/models" + "github.com/spf13/cobra" ) @@ -28,3 +30,6 @@ func (c *Completion) NetworkDestinationPrefixesCompletion(cmd *cobra.Command, ar } return prefixes, cobra.ShellCompDirectiveNoFileComp } +func (c *Completion) NetworkAddressFamilyCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{models.V1NetworkAllocateRequestAddressfamilyIPV4, models.V1NetworkAllocateRequestAddressfamilyIPV6}, cobra.ShellCompDirectiveNoFileComp +} diff --git a/cmd/ip.go b/cmd/ip.go index af7070e..e6c9ff0 100644 --- a/cmd/ip.go +++ b/cmd/ip.go @@ -3,6 +3,7 @@ package cmd import ( "errors" "fmt" + "net/netip" "strings" "github.com/metal-stack/metal-go/api/client/ip" @@ -49,9 +50,11 @@ func newIPCmd(c *config) *cobra.Command { cmd.Flags().StringP("network", "", "", "network from where the IP should be allocated.") cmd.Flags().StringP("project", "", "", "project for which the IP should be allocated.") cmd.Flags().StringSliceP("tags", "", nil, "tags to attach to the IP.") + cmd.Flags().StringP("addressfamily", "", "", "addressfamily of the ip to acquire, defaults to IPv4 [optional]") genericcli.Must(cmd.RegisterFlagCompletionFunc("network", c.comp.NetworkListCompletion)) genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.comp.ProjectListCompletion)) genericcli.Must(cmd.RegisterFlagCompletionFunc("type", cobra.FixedCompletions([]string{models.V1IPAllocateRequestTypeEphemeral, models.V1IPAllocateRequestTypeStatic}, cobra.ShellCompDirectiveNoFileComp))) + genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.comp.IPAddressFamilyCompletion)) }, ListCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().StringP("ipaddress", "", "", "ipaddress to filter [optional]") @@ -62,11 +65,13 @@ func newIPCmd(c *config) *cobra.Command { cmd.Flags().StringP("network", "", "", "network to filter [optional]") cmd.Flags().StringP("name", "", "", "name to filter [optional]") cmd.Flags().StringSliceP("tags", "", nil, "tags to filter [optional]") + cmd.Flags().StringP("addressfamily", "", "", "addressfamily of the ip to filter, defaults to all addressfamilies [optional]") genericcli.Must(cmd.RegisterFlagCompletionFunc("ipaddress", c.comp.IpListCompletion)) genericcli.Must(cmd.RegisterFlagCompletionFunc("network", c.comp.NetworkListCompletion)) genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.comp.ProjectListCompletion)) genericcli.Must(cmd.RegisterFlagCompletionFunc("type", cobra.FixedCompletions([]string{models.V1IPAllocateRequestTypeEphemeral, models.V1IPAllocateRequestTypeStatic}, cobra.ShellCompDirectiveNoFileComp))) genericcli.Must(cmd.RegisterFlagCompletionFunc("machineid", c.comp.MachineListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.comp.IPAddressFamilyCompletion)) }, DeleteCmdMutateFn: func(cmd *cobra.Command) { cmd.Aliases = append(cmd.Aliases, "free") @@ -110,6 +115,29 @@ func (c ipCmd) List() ([]*models.V1IPResponse, error) { return nil, err } + // actually filtered client side because server side would require a reql filter for addressfamilies + if viper.IsSet("addressfamily") { + af := viper.GetString("addressfamily") + var result []*models.V1IPResponse + for _, ipresp := range resp.Payload { + if ipresp == nil || ipresp.Ipaddress == nil { + continue + } + parsedIP, err := netip.ParseAddr(*ipresp.Ipaddress) + if err != nil { + return nil, err + } + if parsedIP.Is4() && af == models.V1IPAllocateRequestAddressfamilyIPV6 { + continue + } + if parsedIP.Is6() && af == models.V1IPAllocateRequestAddressfamilyIPV4 { + continue + } + result = append(result, ipresp) + } + return result, nil + } + return resp.Payload, nil } @@ -172,12 +200,13 @@ func ipResponseToCreate(r *models.V1IPResponse) *ipAllocateRequest { return &ipAllocateRequest{ SpecificIP: ip, V1IPAllocateRequest: &models.V1IPAllocateRequest{ - Description: r.Description, - Name: r.Name, - Networkid: r.Networkid, - Projectid: r.Projectid, - Tags: r.Tags, - Type: r.Type, + Description: r.Description, + Name: r.Name, + Networkid: r.Networkid, + Projectid: r.Projectid, + Tags: r.Tags, + Type: r.Type, + Addressfamily: pointer.Pointer(models.V1IPAllocateRequestAddressfamilyIPV4), }, } } @@ -193,15 +222,22 @@ func ipResponseToUpdate(r *models.V1IPResponse) *models.V1IPUpdateRequest { } func (c *ipCmd) createRequestFromCLI() (*ipAllocateRequest, error) { + + var af *string + if viper.IsSet("addressfamily") { + af = pointer.Pointer(viper.GetString("addressfamily")) + } + return &ipAllocateRequest{ SpecificIP: viper.GetString("ipaddress"), V1IPAllocateRequest: &models.V1IPAllocateRequest{ - Description: viper.GetString("description"), - Name: viper.GetString("name"), - Networkid: pointer.Pointer(viper.GetString("network")), - Projectid: pointer.Pointer(viper.GetString("project")), - Type: pointer.Pointer(viper.GetString("type")), - Tags: viper.GetStringSlice("tags"), + Description: viper.GetString("description"), + Name: viper.GetString("name"), + Networkid: pointer.Pointer(viper.GetString("network")), + Projectid: pointer.Pointer(viper.GetString("project")), + Type: pointer.Pointer(viper.GetString("type")), + Tags: viper.GetStringSlice("tags"), + Addressfamily: af, }, }, nil } diff --git a/cmd/ip_test.go b/cmd/ip_test.go index bd19ff7..29bea9a 100644 --- a/cmd/ip_test.go +++ b/cmd/ip_test.go @@ -228,6 +228,7 @@ IP ALLOCATION UUID DESCRIPTION NAME NETWORK PROJECT TYPE "--project", *want.Projectid, "--type", *want.Type, "--tags", strings.Join(want.Tags, ","), + "--addressfamily", models.V1IPAllocateRequestAddressfamilyIPV4, } assertExhaustiveArgs(t, args, commonExcludedFileArgs()...) return args diff --git a/cmd/network.go b/cmd/network.go index 85801f6..b462980 100644 --- a/cmd/network.go +++ b/cmd/network.go @@ -70,8 +70,10 @@ func newNetworkCmd(c *config) *cobra.Command { cmd.Flags().Int64P("vrf", "", 0, "vrf to filter [optional]") cmd.Flags().StringSlice("prefixes", []string{}, "prefixes to filter, use it like: --prefixes prefix1,prefix2.") cmd.Flags().StringSlice("destination-prefixes", []string{}, "destination prefixes to filter, use it like: --destination-prefixes prefix1,prefix2.") + cmd.Flags().String("addressfamily", "", "addressfamily to filter, either ipv4 or ipv6 [optional]") genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.comp.ProjectListCompletion)) genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.comp.PartitionListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.comp.NetworkAddressFamilyCompletion)) }, UpdateCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().String("name", "", "the name of the network [optional]") @@ -105,6 +107,20 @@ func newNetworkCmd(c *config) *cobra.Command { return err } + var ( + af *string + length = make(map[string]int64) + ) + if viper.IsSet("ipv4length") { + length[models.V1IPAllocateRequestAddressfamilyIPV4] = viper.GetInt64("ipv4length") + } + if viper.IsSet("ipv6length") { + length[models.V1IPAllocateRequestAddressfamilyIPV6] = viper.GetInt64("ipv6length") + } + if viper.IsSet("addressfamily") { + af = pointer.Pointer(viper.GetString("addressfamily")) + } + return w.childCLI.CreateAndPrint(&models.V1NetworkAllocateRequest{ Description: viper.GetString("description"), Name: viper.GetString("name"), @@ -114,6 +130,8 @@ func newNetworkCmd(c *config) *cobra.Command { Labels: labels, Destinationprefixes: destinationPrefixes, Nat: nat, + Addressfamily: af, + Length: length, }, c.describePrinter) } @@ -142,8 +160,12 @@ func newNetworkCmd(c *config) *cobra.Command { allocateCmd.Flags().StringSlice("labels", []string{}, "labels for this network. [optional]") allocateCmd.Flags().BoolP("dmz", "", false, "use this private network as dmz. [optional]") allocateCmd.Flags().BoolP("shared", "", false, "shared allows usage of this private network from other networks") + allocateCmd.Flags().StringP("addressfamily", "", "ipv4", "addressfamily of the network to acquire [optional]") + allocateCmd.Flags().Int64P("ipv4length", "", 22, "ipv4 bitlength of network to create. [optional]") + allocateCmd.Flags().Int64P("ipv6length", "", 64, "ip6 bitlength of network to create. [optional]") genericcli.Must(allocateCmd.RegisterFlagCompletionFunc("project", c.comp.ProjectListCompletion)) genericcli.Must(allocateCmd.RegisterFlagCompletionFunc("partition", c.comp.PartitionListCompletion)) + genericcli.Must(allocateCmd.RegisterFlagCompletionFunc("addressfamily", c.comp.NetworkAddressFamilyCompletion)) genericcli.Must(allocateCmd.MarkFlagRequired("name")) genericcli.Must(allocateCmd.MarkFlagRequired("project")) @@ -235,6 +257,7 @@ func networkResponseToCreate(r *models.V1NetworkResponse) *models.V1NetworkCreat Nat: r.Nat, Parentnetworkid: r.Parentnetworkid, Partitionid: r.Partitionid, + Defaultchildprefixlength: r.Defaultchildprefixlength, Prefixes: r.Prefixes, Privatesuper: r.Privatesuper, Projectid: r.Projectid, diff --git a/cmd/partition.go b/cmd/partition.go index 61de994..3832b6b 100644 --- a/cmd/partition.go +++ b/cmd/partition.go @@ -140,11 +140,10 @@ func partitionResponseToCreate(r *models.V1PartitionResponse) *models.V1Partitio Imageurl: r.Bootconfig.Imageurl, Kernelurl: r.Bootconfig.Kernelurl, }, - Description: r.Description, - ID: r.ID, - Mgmtserviceaddress: r.Mgmtserviceaddress, - Name: r.Name, - Privatenetworkprefixlength: r.Privatenetworkprefixlength, + Description: r.Description, + ID: r.ID, + Mgmtserviceaddress: r.Mgmtserviceaddress, + Name: r.Name, } } diff --git a/cmd/partition_test.go b/cmd/partition_test.go index 577dafd..f5b9fb3 100644 --- a/cmd/partition_test.go +++ b/cmd/partition_test.go @@ -21,11 +21,10 @@ var ( Imageurl: "imageurl", Kernelurl: "kernelurl", }, - Description: "partition 1", - ID: pointer.Pointer("1"), - Mgmtserviceaddress: "mgmt", - Name: "partition-1", - Privatenetworkprefixlength: 24, + Description: "partition 1", + ID: pointer.Pointer("1"), + Mgmtserviceaddress: "mgmt", + Name: "partition-1", Labels: map[string]string{ "a": "b", }, @@ -36,11 +35,10 @@ var ( Imageurl: "imageurl", Kernelurl: "kernelurl", }, - Description: "partition 2", - ID: pointer.Pointer("2"), - Mgmtserviceaddress: "mgmt", - Name: "partition-2", - Privatenetworkprefixlength: 24, + Description: "partition 2", + ID: pointer.Pointer("2"), + Mgmtserviceaddress: "mgmt", + Name: "partition-2", } ) @@ -239,7 +237,6 @@ ID NAME DESCRIPTION LABELS mocks: &client.MetalMockFns{ Partition: func(mock *mock.Mock) { p := partition1 - p.Privatenetworkprefixlength = 0 mock.On("CreatePartition", testcommon.MatchIgnoreContext(t, partition.NewCreatePartitionParams().WithBody(partitionResponseToCreate(p))), nil).Return(&partition.CreatePartitionCreated{ Payload: partition1, }, nil) diff --git a/cmd/tableprinters/network.go b/cmd/tableprinters/network.go index f21f6b9..4315325 100644 --- a/cmd/tableprinters/network.go +++ b/cmd/tableprinters/network.go @@ -64,29 +64,29 @@ func addNetwork(prefix string, n *models.V1NetworkResponse, wide bool) []string } privateSuper := fmt.Sprintf("%t", flag) nat := fmt.Sprintf("%t", *n.Nat) - - usage := fmt.Sprintf("IPs: %v/%v", *n.Usage.UsedIps, *n.Usage.AvailableIps) - - ipUse := float64(*n.Usage.UsedIps) / float64(*n.Usage.AvailableIps) - shortIPUsage := nbr - if ipUse >= 0.9 { - shortIPUsage += color.RedString(dot) - } else if ipUse >= 0.7 { - shortIPUsage += color.YellowString(dot) + // FIXME add ipv6 usage + ipv4usage := fmt.Sprintf("IPs: %v/%v", *n.Usage.UsedIps, *n.Usage.AvailableIps) + + ipv4Use := float64(*n.Usage.UsedIps) / float64(*n.Usage.AvailableIps) + shortIPv4IPUsage := nbr + if ipv4Use >= 0.9 { + shortIPv4IPUsage += color.RedString(dot) + } else if ipv4Use >= 0.7 { + shortIPv4IPUsage += color.YellowString(dot) } else { - shortIPUsage += color.GreenString(dot) + shortIPv4IPUsage += color.GreenString(dot) } - shortPrefixUsage := "" + shortIPv4PrefixUsage := "" if *n.Usage.AvailablePrefixes > 0 { prefixUse := float64(*n.Usage.UsedPrefixes) / float64(*n.Usage.AvailablePrefixes) if prefixUse >= 0.9 { - shortPrefixUsage = color.RedString(dot) + shortIPv4PrefixUsage = color.RedString(dot) } - usage = fmt.Sprintf("%s\nPrefixes:%d/%d", usage, *n.Usage.UsedPrefixes, *n.Usage.AvailablePrefixes) + ipv4usage = fmt.Sprintf("%s\nPrefixes:%d/%d", ipv4usage, *n.Usage.UsedPrefixes, *n.Usage.AvailablePrefixes) } - max := getMaxLineCount(n.Description, n.Name, n.Projectid, n.Partitionid, nat, prefixes, usage, privateSuper) + max := getMaxLineCount(n.Description, n.Name, n.Projectid, n.Partitionid, nat, prefixes, ipv4usage, privateSuper) for i := 0; i < max-1; i++ { id += "\n│" } @@ -102,9 +102,9 @@ func addNetwork(prefix string, n *models.V1NetworkResponse, wide bool) []string annotations := strings.Join(as, "\n") if wide { - return []string{id, n.Description, n.Name, n.Projectid, n.Partitionid, nat, shared, prefixes, usage, privateSuper, annotations} + return []string{id, n.Description, n.Name, n.Projectid, n.Partitionid, nat, shared, prefixes, ipv4usage, privateSuper, annotations} } else { - return []string{id, n.Name, n.Projectid, n.Partitionid, nat, shared, prefixes, shortPrefixUsage, shortIPUsage} + return []string{id, n.Name, n.Projectid, n.Partitionid, nat, shared, prefixes, shortIPv4PrefixUsage, shortIPv4IPUsage} } } diff --git a/docs/metalctl_network_allocate.md b/docs/metalctl_network_allocate.md index 7fc6039..520fbc4 100644 --- a/docs/metalctl_network_allocate.md +++ b/docs/metalctl_network_allocate.md @@ -9,14 +9,17 @@ metalctl network allocate [flags] ### Options ``` - -d, --description string description of the network to create. [optional] - --dmz use this private network as dmz. [optional] - -h, --help help for allocate - --labels strings labels for this network. [optional] - -n, --name string name of the network to create. [required] - --partition string partition where this network should exist. [required] - --project string partition where this network should exist. [required] - --shared shared allows usage of this private network from other networks + --addressfamily string addressfamily of the network to acquire [optional] (default "ipv4") + -d, --description string description of the network to create. [optional] + --dmz use this private network as dmz. [optional] + -h, --help help for allocate + --ipv4length int ipv4 bitlength of network to create. [optional] (default 22) + --ipv6length int ip6 bitlength of network to create. [optional] (default 64) + --labels strings labels for this network. [optional] + -n, --name string name of the network to create. [required] + --partition string partition where this network should exist. [required] + --project string partition where this network should exist. [required] + --shared shared allows usage of this private network from other networks ``` ### Options inherited from parent commands diff --git a/docs/metalctl_network_ip_create.md b/docs/metalctl_network_ip_create.md index 9d51520..2b37ecc 100644 --- a/docs/metalctl_network_ip_create.md +++ b/docs/metalctl_network_ip_create.md @@ -9,6 +9,7 @@ metalctl network ip create [flags] ### Options ``` + --addressfamily string addressfamily of the ip to acquire, defaults to IPv4 [optional] --bulk-output when used with --file (bulk operation): prints results at the end as a list. default is printing results intermediately during the operation, which causes single entities to be printed in a row. -d, --description string description of the IP to allocate. [optional] -f, --file string filename of the create or update request in yaml format, or - for stdin. diff --git a/docs/metalctl_network_ip_list.md b/docs/metalctl_network_ip_list.md index 3acfee4..52ea88f 100644 --- a/docs/metalctl_network_ip_list.md +++ b/docs/metalctl_network_ip_list.md @@ -9,16 +9,17 @@ metalctl network ip list [flags] ### Options ``` - -h, --help help for list - --ipaddress string ipaddress to filter [optional] - --machineid string machineid to filter [optional] - --name string name to filter [optional] - --network string network to filter [optional] - --prefix string prefix to filter [optional] - --project string project to filter [optional] - --sort-by strings sort by (comma separated) column(s), sort direction can be changed by appending :asc or :desc behind the column identifier. possible values: age|description|id|ipaddress|name|network|type - --tags strings tags to filter [optional] - --type string type to filter [optional] + --addressfamily string addressfamily of the ip to filter, defaults to all addressfamilies [optional] + -h, --help help for list + --ipaddress string ipaddress to filter [optional] + --machineid string machineid to filter [optional] + --name string name to filter [optional] + --network string network to filter [optional] + --prefix string prefix to filter [optional] + --project string project to filter [optional] + --sort-by strings sort by (comma separated) column(s), sort direction can be changed by appending :asc or :desc behind the column identifier. possible values: age|description|id|ipaddress|name|network|type + --tags strings tags to filter [optional] + --type string type to filter [optional] ``` ### Options inherited from parent commands diff --git a/docs/metalctl_network_list.md b/docs/metalctl_network_list.md index 9197c12..595d624 100644 --- a/docs/metalctl_network_list.md +++ b/docs/metalctl_network_list.md @@ -9,6 +9,7 @@ metalctl network list [flags] ### Options ``` + --addressfamily string addressfamily to filter, either ipv4 or ipv6 [optional] --destination-prefixes strings destination prefixes to filter, use it like: --destination-prefixes prefix1,prefix2. -h, --help help for list --id string ID to filter [optional] diff --git a/go.mod b/go.mod index 51f31ba..818fbc7 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/go-openapi/strfmt v0.23.0 github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.6.0 - github.com/metal-stack/metal-go v0.37.1 + github.com/metal-stack/metal-go v0.37.2-0.20241003064756-91453eaa31bd github.com/metal-stack/metal-lib v0.18.3 github.com/metal-stack/updater v1.2.2 github.com/metal-stack/v v1.0.3 diff --git a/go.sum b/go.sum index 3f74bc9..e082e68 100644 --- a/go.sum +++ b/go.sum @@ -244,8 +244,8 @@ github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= -github.com/metal-stack/metal-go v0.37.1 h1:vlvg/MY9Ep61h86GF54DER1VYADcqyHbFPZH3DqEbdM= -github.com/metal-stack/metal-go v0.37.1/go.mod h1:3MJTYCS4YJz8D8oteTKhjpaAKNMMjMKYDrIy9awHGtQ= +github.com/metal-stack/metal-go v0.37.2-0.20241003064756-91453eaa31bd h1:wQvUV9W9j8tm3z+dZFlWWsasJVuxJr+ohBII8vsFWpc= +github.com/metal-stack/metal-go v0.37.2-0.20241003064756-91453eaa31bd/go.mod h1:3MJTYCS4YJz8D8oteTKhjpaAKNMMjMKYDrIy9awHGtQ= github.com/metal-stack/metal-lib v0.18.3 h1:bovFiJPB9SMvuGLqcXVWz6jFB8HrdzwnCX7TFlen4r0= github.com/metal-stack/metal-lib v0.18.3/go.mod h1:Ctyi6zaXFr2NVrQZLFsDLnFCzupKnYErTtgRFKAsnbw= github.com/metal-stack/security v0.8.1 h1:4zmVUxZvDWShVvVIxM3XhIv7pTmPe9DvACRIHW6YTsk=