From 3cb6072c8f6733bb909937a133e558c6f4586c90 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Wed, 14 May 2025 11:39:03 +0200 Subject: [PATCH 01/11] First work on network --- cmd/api/v1/network.go | 460 +++++++++++++++++++++++++++++++++++ cmd/sorters/network.go | 27 ++ cmd/tableprinters/common.go | 25 ++ cmd/tableprinters/network.go | 140 +++++++++++ go.mod | 2 +- go.sum | 4 +- 6 files changed, 655 insertions(+), 3 deletions(-) create mode 100644 cmd/api/v1/network.go create mode 100644 cmd/sorters/network.go create mode 100644 cmd/tableprinters/network.go diff --git a/cmd/api/v1/network.go b/cmd/api/v1/network.go new file mode 100644 index 0000000..5c53931 --- /dev/null +++ b/cmd/api/v1/network.go @@ -0,0 +1,460 @@ +package v1 + +import ( + "errors" + "fmt" + + "slices" + + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/metal-go/api/client/network" + "github.com/metal-stack/metal-go/api/models" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/metal-stack/metal-lib/pkg/pointer" + "github.com/metal-stack/metalctl/cmd/sorters" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "k8s.io/apimachinery/pkg/util/sets" +) + +type networkCmd struct { + *config + childCLI *genericcli.GenericCLI[*apiv2.NetworkServiceCreateRequest, any, *apiv2.Network] +} + +func newNetworkCmd(c *config) *cobra.Command { + w := &networkCmd{ + config: c, + childCLI: genericcli.NewGenericCLI[*apiv2.NetworkServiceCreateRequest, any, *apiv2.Network](networkChildCRUD{config: c}).WithFS(c.fs), + } + + cmdsConfig := &genericcli.CmdsConfig[*models.V1NetworkCreateRequest, *models.V1NetworkUpdateRequest, *apiv2.Network]{ + BinaryName: binaryName, + GenericCLI: genericcli.NewGenericCLI[*models.V1NetworkCreateRequest, *models.V1NetworkUpdateRequest, *apiv2.Network](w).WithFS(c.fs), + Singular: "network", + Plural: "networks", + Description: "networks can be attached to a machine or firewall such that they can communicate with each other.", + CreateRequestFromCLI: w.createRequestFromCLI, + UpdateRequestFromCLI: w.updateRequestFromCLI, + Sorter: sorters.NetworkSorter(), + ValidArgsFn: c.comp.NetworkListCompletion, + DescribePrinter: func() printers.Printer { return c.describePrinter }, + ListPrinter: func() printers.Printer { return c.listPrinter }, + CreateCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().StringP("id", "", "", "id of the network to create. [optional]") + cmd.Flags().StringP("description", "d", "", "description of the network to create. [optional]") + cmd.Flags().StringP("name", "n", "", "name of the network to create. [optional]") + cmd.Flags().StringP("partition", "p", "", "partition where this network should exist.") + cmd.Flags().StringP("project", "", "", "project of the network to create. [optional]") + cmd.Flags().Int64("default-ipv4-child-prefix-length", 0, "default child prefix length for ipv4 prefixes for private super networks.") + cmd.Flags().Int64("default-ipv6-child-prefix-length", 0, "default child prefix length for ipv6 prefixes for private super networks.") + cmd.Flags().StringSlice("prefixes", []string{}, "prefixes in this network.") + cmd.Flags().StringSlice("labels", []string{}, "add initial labels, must be in the form of key=value, use it like: --labels \"key1=value1,key2=value2\".") + cmd.Flags().StringSlice("destination-prefixes", []string{}, "destination prefixes in this network.") + cmd.Flags().StringSlice("additional-announcable-cidrs", []string{}, "list of cidrs which are added to the route maps per tenant private network, these are typically pod- and service cidrs, can only be set in a supernetwork") + cmd.Flags().BoolP("privatesuper", "", false, "set private super flag of network, if set to true, this network is used to start machines there.") + cmd.Flags().BoolP("nat", "", false, "set nat flag of network, if set to true, traffic from this network will be natted.") + cmd.Flags().BoolP("underlay", "", false, "set underlay flag of network, if set to true, this is used to transport underlay network traffic") + cmd.Flags().Int64P("vrf", "", 0, "vrf of this network") + cmd.Flags().BoolP("vrfshared", "", false, "vrf shared allows multiple networks to share a vrf") + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.comp.ProjectListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.comp.PartitionListCompletion)) + }, + ListCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("id", "", "ID to filter [optional]") + cmd.Flags().String("name", "", "name to filter [optional]") + cmd.Flags().String("partition", "", "partition to filter [optional]") + cmd.Flags().String("project", "", "project to filter [optional]") + cmd.Flags().String("parent", "", "parent network to filter [optional]") + cmd.Flags().BoolP("nat", "", false, "nat to filter [optional]") + cmd.Flags().BoolP("privatesuper", "", false, "privatesuper to filter [optional]") + cmd.Flags().BoolP("underlay", "", false, "underlay to filter [optional]") + 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]") + cmd.Flags().String("description", "", "the description of the network [optional]") + cmd.Flags().StringSlice("add-prefixes", []string{}, "prefixes to be added to the network [optional]") + cmd.Flags().StringSlice("remove-prefixes", []string{}, "prefixes to be removed from the network [optional]") + cmd.Flags().StringSlice("add-destinationprefixes", []string{}, "destination prefixes to be added to the network [optional]") + cmd.Flags().StringSlice("remove-destinationprefixes", []string{}, "destination prefixes to be removed from the network [optional]") + cmd.Flags().StringSlice("labels", []string{}, "the labels of the network, must be in the form of key=value, use it like: --labels \"key1=value1,key2=value2\". [optional]") + cmd.Flags().StringSlice("additional-announcable-cidrs", []string{}, "list of cidrs which are added to the route maps per tenant private network, these are typically pod- and service cidrs, can only be set in a supernetwork") + cmd.Flags().Bool("shared", false, "marks a network as shared or not [optional]") + }, + } + + allocateCmd := &cobra.Command{ + Use: "allocate", + Short: "allocate a network", + RunE: func(cmd *cobra.Command, args []string) error { + if !viper.IsSet("file") { + shared := viper.GetBool("shared") + nat := false + var destinationPrefixes []string + if viper.GetBool("dmz") { + shared = true + destinationPrefixes = []string{"0.0.0.0/0"} + nat = true + } + + labels, err := genericcli.LabelsToMap(viper.GetStringSlice("labels")) + if err != nil { + return err + } + + var ( + length = make(map[string]int64) + ) + if viper.IsSet("ipv4-prefix-length") { + length[models.V1IPAllocateRequestAddressfamilyIPV4] = viper.GetInt64("ipv4-prefix-length") + } + if viper.IsSet("ipv6-prefix-length") { + length[models.V1IPAllocateRequestAddressfamilyIPV6] = viper.GetInt64("ipv6-prefix-length") + } + + return w.childCLI.CreateAndPrint(&apiv2.NetworkServiceCreateRequest{ + Description: viper.GetString("description"), + Name: viper.GetString("name"), + Partition: viper.GetString("partition"), + Project: viper.GetString("project"), + Shared: shared, + Labels: labels, + Nat: nat, + AddressFamily: viper.GetString("addressfamily"), + Length: length, + }, c.describePrinter) + } + + return w.childCLI.CreateFromFileAndPrint(viper.GetString("file"), c.describePrinter) + }, + } + + freeCmd := &cobra.Command{ + Use: "free ", + Short: "free a network", + RunE: func(cmd *cobra.Command, args []string) error { + id, err := genericcli.GetExactlyOneArg(args) + if err != nil { + return err + } + + return w.childCLI.DeleteAndPrint(id, c.describePrinter) + }, + ValidArgsFunction: c.comp.NetworkListCompletion, + } + + allocateCmd.Flags().StringP("name", "n", "", "name of the network to create. [required]") + allocateCmd.Flags().StringP("partition", "", "", "partition where this network should exist. [required]") + allocateCmd.Flags().StringP("project", "", "", "partition where this network should exist. [required]") + allocateCmd.Flags().StringP("description", "d", "", "description of the network to create. [optional]") + 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", "", "", "addressfamily of the network to acquire, if not specified the network inherits the address families from the parent [optional]") + allocateCmd.Flags().Int64P("ipv4-prefix-length", "", 0, "ipv4 prefix bit length of the network to create, defaults to default child prefix length of the parent network. [optional]") + allocateCmd.Flags().Int64P("ipv6-prefix-length", "", 0, "ipv6 prefix bit length of the network to create, defaults to default child prefix length of the parent network. [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")) + genericcli.Must(allocateCmd.MarkFlagRequired("partition")) + + return genericcli.NewCmds( + cmdsConfig, + newIPCmd(c), + allocateCmd, + freeCmd, + ) +} + +func (c *networkCmd) Get(id string) (*apiv2.Network, error) { + resp, err := c.client.Network().FindNetwork(network.NewFindNetworkParams().WithID(id), nil) + if err != nil { + return nil, err + } + + return resp.Payload, nil +} + +func (c *networkCmd) List() ([]*apiv2.Network, error) { + resp, err := c.client.Network().FindNetworks(network.NewFindNetworksParams().WithBody(&models.V1NetworkFindRequest{ + ID: viper.GetString("id"), + Name: viper.GetString("name"), + Partitionid: viper.GetString("partition"), + Projectid: viper.GetString("project"), + Nat: viper.GetBool("nat"), + Privatesuper: viper.GetBool("privatesuper"), + Underlay: viper.GetBool("underlay"), + Vrf: viper.GetInt64("vrf"), + Prefixes: viper.GetStringSlice("prefixes"), + Destinationprefixes: viper.GetStringSlice("destination-prefixes"), + Parentnetworkid: viper.GetString("parent"), + Addressfamily: viper.GetString("addressfamily"), + }), nil) + if err != nil { + return nil, err + } + + return resp.Payload, nil +} + +func (c *networkCmd) Delete(id string) (*apiv2.Network, error) { + resp, err := c.client.Network().DeleteNetwork(network.NewDeleteNetworkParams().WithID(id), nil) + if err != nil { + return nil, err + } + + return resp.Payload, nil +} + +func (c *networkCmd) Create(rq *models.V1NetworkCreateRequest) (*apiv2.Network, error) { + resp, err := c.client.Network().CreateNetwork(network.NewCreateNetworkParams().WithBody(rq), nil) + if err != nil { + var r *network.CreateNetworkConflict + if errors.As(err, &r) { + return nil, genericcli.AlreadyExistsError() + } + return nil, err + } + + return resp.Payload, nil +} + +func (c *networkCmd) Update(rq *models.V1NetworkUpdateRequest) (*apiv2.Network, error) { + resp, err := c.client.Network().UpdateNetwork(network.NewUpdateNetworkParams().WithBody(rq).WithForce(pointer.Pointer(viper.GetBool(forceFlag))), nil) + if err != nil { + return nil, err + } + + return resp.Payload, nil +} + +func (c *networkCmd) Convert(r *apiv2.Network) (string, *models.V1NetworkCreateRequest, *models.V1NetworkUpdateRequest, error) { + if r.ID == nil { + return "", nil, nil, fmt.Errorf("id is nil") + } + return *r.ID, networkResponseToCreate(r), networkResponseToUpdate(r), nil +} + +func networkResponseToCreate(r *apiv2.Network) *models.V1NetworkCreateRequest { + return &apiv2.NetworkServiceCreateRequest{ + Description: r.Description, + Labels: r.Meta.Labels, + Name: r.Name, + ParentNetworkId: r.ParentNetworkId, + Partition: r.Partition, + Project: pointer.SafeDeref(r.Project), + AddressFamily: , + Length: , + } +} + +func networkResponseToUpdate(r *apiv2.Network) *models.V1NetworkUpdateRequest { + return &models.V1NetworkUpdateRequest{ + Description: r.Description, + Destinationprefixes: r.Destinationprefixes, + ID: r.ID, + Labels: r.Labels, + Name: r.Name, + Prefixes: r.Prefixes, + Shared: r.Shared, + AdditionalAnnouncableCIDRs: r.AdditionalAnnouncableCIDRs, + Defaultchildprefixlength: r.Defaultchildprefixlength, + } +} + +func (c *networkCmd) createRequestFromCLI() (*models.V1NetworkCreateRequest, error) { + lbs, err := genericcli.LabelsToMap(viper.GetStringSlice("labels")) + if err != nil { + return nil, err + } + + var defaultChildPrefixLengths map[string]int64 + if viper.GetBool("privatesuper") { + defaultChildPrefixLengths = map[string]int64{} + + if length := viper.GetInt64("default-ipv4-child-prefix-length"); length > 0 { + defaultChildPrefixLengths[models.V1IPAllocateRequestAddressfamilyIPV4] = length + } + if length := viper.GetInt64("default-ipv6-child-prefix-length"); length > 0 { + defaultChildPrefixLengths[models.V1IPAllocateRequestAddressfamilyIPV6] = length + } + } + + return &models.V1NetworkCreateRequest{ + ID: pointer.Pointer(viper.GetString("id")), + Description: viper.GetString("description"), + Name: viper.GetString("name"), + Partitionid: viper.GetString("partition"), + Projectid: viper.GetString("project"), + Prefixes: viper.GetStringSlice("prefixes"), + Destinationprefixes: viper.GetStringSlice("destination-prefixes"), + Privatesuper: pointer.Pointer(viper.GetBool("privatesuper")), + Nat: pointer.Pointer(viper.GetBool("nat")), + Underlay: pointer.Pointer(viper.GetBool("underlay")), + Vrf: viper.GetInt64("vrf"), + Vrfshared: viper.GetBool("vrfshared"), + Labels: lbs, + AdditionalAnnouncableCIDRs: viper.GetStringSlice("additional-announcable-cidrs"), + Defaultchildprefixlength: defaultChildPrefixLengths, + }, nil +} + +type networkChildCRUD struct { + *config +} + +func (c networkChildCRUD) Get(id string) (*apiv2.Network, error) { + return nil, fmt.Errorf("not implemented for child networks, use network update") +} + +func (c networkChildCRUD) List() ([]*apiv2.Network, error) { + return nil, fmt.Errorf("not implemented for child networks, use network update") +} + +func (c networkChildCRUD) Delete(id string) (*apiv2.Network, error) { + resp, err := c.client.Network().FreeNetwork(network.NewFreeNetworkParams().WithID(id), nil) + if err != nil { + return nil, err + } + + return resp.Payload, nil +} + +func (c networkChildCRUD) Create(rq *apiv2.NetworkServiceCreateRequest) (*apiv2.Network, error) { + resp, err := c.client.Network().AllocateNetwork(network.NewAllocateNetworkParams().WithBody(rq), nil) + if err != nil { + var r *network.AllocateNetworkConflict + if errors.As(err, &r) { + return nil, genericcli.AlreadyExistsError() + } + return nil, err + } + + return resp.Payload, nil +} + +func (c networkChildCRUD) Update(rq any) (*apiv2.Network, error) { + return nil, fmt.Errorf("not implemented for child networks, use network update") +} + +func (c networkChildCRUD) Convert(r *apiv2.Network) (string, *apiv2.NetworkServiceCreateRequest, any, error) { + if r.ID == nil { + return "", nil, nil, fmt.Errorf("id is nil") + } + return *r.ID, &apiv2.NetworkServiceCreateRequest{ + Description: r.Description, + Destinationprefixes: r.Destinationprefixes, + Labels: r.Labels, + Name: r.Name, + Nat: pointer.SafeDeref(r.Nat), + Partitionid: r.Partitionid, + Projectid: r.Projectid, + Shared: false, + }, nil, nil +} + +func (c *networkCmd) updateRequestFromCLI(args []string) (*models.V1NetworkUpdateRequest, error) { + id, err := genericcli.GetExactlyOneArg(args) + if err != nil { + return nil, err + } + + resp, err := c.Get(id) + if err != nil { + return nil, err + } + + var labels map[string]string + if viper.IsSet("labels") { + labels, err = genericcli.LabelsToMap(viper.GetStringSlice("labels")) + if err != nil { + return nil, err + } + } + + shared := resp.Shared + if viper.IsSet("shared") { + shared = viper.GetBool("shared") + } + + additionalCidrs := resp.AdditionalAnnouncableCIDRs + if viper.IsSet("additional-announcable-cidrs") { + additionalCidrs = viper.GetStringSlice("additional-announcable-cidrs") + } + var ( + ur = &models.V1NetworkUpdateRequest{ + Description: viper.GetString("description"), + Destinationprefixes: nil, + ID: pointer.Pointer(id), + Labels: labels, + Name: viper.GetString("name"), + Prefixes: nil, + Shared: shared, + AdditionalAnnouncableCIDRs: additionalCidrs, + Defaultchildprefixlength: resp.Defaultchildprefixlength, + } + addPrefixes = sets.New(viper.GetStringSlice("add-prefixes")...) + removePrefixes = sets.New(viper.GetStringSlice("remove-prefixes")...) + addDestinationprefixes = sets.New(viper.GetStringSlice("add-destinationprefixes")...) + removeDestinationprefixes = sets.New(viper.GetStringSlice("remove-destinationprefixes")...) + currentPrefixes = sets.New(resp.Prefixes...) + currentDestinationprefixes = sets.New(resp.Destinationprefixes...) + ) + + newPrefixes := currentPrefixes.Clone() + if viper.IsSet("remove-prefixes") { + diff := removePrefixes.Difference(currentPrefixes) + if diff.Len() > 0 { + difflist := diff.UnsortedList() + slices.Sort(difflist) + return nil, fmt.Errorf("cannot remove prefixes because they are currently not present: %s", difflist) + } + newPrefixes = newPrefixes.Difference(removePrefixes) + } + if viper.IsSet("add-prefixes") { + if currentPrefixes.HasAny(addPrefixes.UnsortedList()...) { + intersection := addPrefixes.Intersection(currentPrefixes).UnsortedList() + slices.Sort(intersection) + return nil, fmt.Errorf("cannot add prefixes because they are already present: %s", intersection) + } + newPrefixes = newPrefixes.Union(addPrefixes) + } + if !newPrefixes.Equal(currentPrefixes) { + ur.Prefixes = newPrefixes.UnsortedList() + } + + newDestinationprefixes := currentDestinationprefixes.Clone() + if viper.IsSet("remove-destinationprefixes") { + diff := removeDestinationprefixes.Difference(currentDestinationprefixes) + if diff.Len() > 0 { + difflist := diff.UnsortedList() + slices.Sort(difflist) + return nil, fmt.Errorf("cannot remove destination prefixes because they are currently not present: %s", difflist) + } + newDestinationprefixes = newDestinationprefixes.Difference(removeDestinationprefixes) + } + if viper.IsSet("add-destinationprefixes") { + if currentDestinationprefixes.HasAny(addDestinationprefixes.UnsortedList()...) { + interSection := addDestinationprefixes.Intersection(currentDestinationprefixes).UnsortedList() + slices.Sort(interSection) + return nil, fmt.Errorf("cannot add destination prefixes because they are already present: %s", interSection) + } + newDestinationprefixes = newDestinationprefixes.Union(addDestinationprefixes) + } + if !newDestinationprefixes.Equal(currentDestinationprefixes) { + ur.Destinationprefixes = newDestinationprefixes.UnsortedList() + } + + return ur, nil +} diff --git a/cmd/sorters/network.go b/cmd/sorters/network.go new file mode 100644 index 0000000..a490f7b --- /dev/null +++ b/cmd/sorters/network.go @@ -0,0 +1,27 @@ +package sorters + +import ( + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/metal-lib/pkg/multisort" + "github.com/metal-stack/metal-lib/pkg/pointer" +) + +func NetworkSorter() *multisort.Sorter[*apiv2.Network] { + return multisort.New(multisort.FieldMap[*apiv2.Network]{ + "id": func(a, b *apiv2.Network, descending bool) multisort.CompareResult { + return multisort.Compare(a.Id, b.Id, descending) + }, + "name": func(a, b *apiv2.Network, descending bool) multisort.CompareResult { + return multisort.Compare(pointer.SafeDeref(a.Name), pointer.SafeDeref(b.Name), descending) + }, + "description": func(a, b *apiv2.Network, descending bool) multisort.CompareResult { + return multisort.Compare(pointer.SafeDeref(a.Description), pointer.SafeDeref(b.Description), descending) + }, + "partition": func(a, b *apiv2.Network, descending bool) multisort.CompareResult { + return multisort.Compare(pointer.SafeDeref(a.Partition), pointer.SafeDeref(b.Partition), descending) + }, + "project": func(a, b *apiv2.Network, descending bool) multisort.CompareResult { + return multisort.Compare(pointer.SafeDeref(a.Project), pointer.SafeDeref(b.Project), descending) + }, + }, multisort.Keys{{ID: "partition"}, {ID: "id"}}) +} diff --git a/cmd/tableprinters/common.go b/cmd/tableprinters/common.go index 19c30e7..9fd32a3 100644 --- a/cmd/tableprinters/common.go +++ b/cmd/tableprinters/common.go @@ -12,6 +12,15 @@ import ( "github.com/metal-stack/metal-lib/pkg/pointer" ) +const ( + dot = "●" + halfpie = "◒" + threequarterpie = "◕" + nbr = " " + poweron = "⏻" + powersleep = "⏾" +) + type TablePrinter struct { t *printers.TablePrinter } @@ -40,6 +49,11 @@ func (t *TablePrinter) ToHeaderAndRows(data any, wide bool) ([]string, [][]strin case []*apiv2.Image: return t.ImageTable(d, wide) + case *apiv2.Network: + return t.NetworkTable(pointer.WrapInSlice(d), wide) + case []*apiv2.Network: + return t.NetworkTable(d, wide) + case *apiv2.Project: return t.ProjectTable(pointer.WrapInSlice(d), wide) case []*apiv2.Project: @@ -116,3 +130,14 @@ func humanizeDuration(duration time.Duration) string { } return strings.Join(parts, " ") } + +func getMaxLineCount(ss ...string) int { + max := 0 + for _, s := range ss { + c := strings.Count(s, "\n") + if c > max { + max = c + } + } + return max +} diff --git a/cmd/tableprinters/network.go b/cmd/tableprinters/network.go new file mode 100644 index 0000000..a34a06b --- /dev/null +++ b/cmd/tableprinters/network.go @@ -0,0 +1,140 @@ +package tableprinters + +import ( + "fmt" + "strings" + + "github.com/fatih/color" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/metal-lib/pkg/pointer" +) + +type network struct { + parent *apiv2.Network + children []*apiv2.Network +} + +type networks []*network + +func (t *TablePrinter) NetworkTable(data []*apiv2.Network, wide bool) ([]string, [][]string, error) { + var ( + rows [][]string + ) + + header := []string{"ID", "Name", "Project", "Partition", "Nat", "", "Prefixes", "IP Usage"} + if wide { + header = []string{"ID", "Description", "Name", "Project", "Partition", "Nat", "Prefixes", "Annotations"} + } + + nn := &networks{} + for _, n := range data { + if n.ParentNetworkId == nil { + *nn = append(*nn, &network{parent: n}) + } + } + for _, n := range data { + if n.ParentNetworkId != nil { + if !nn.appendChild(*n.ParentNetworkId, n) { + *nn = append(*nn, &network{parent: n}) + } + } + } + for _, n := range *nn { + rows = append(rows, addNetwork("", n.parent, wide)) + for i, c := range n.children { + prefix := "├" + if i == len(n.children)-1 { + prefix = "└" + } + prefix += "─╴" + rows = append(rows, addNetwork(prefix, c, wide)) + } + } + + return header, rows, nil +} + +func addNetwork(prefix string, n *apiv2.Network, wide bool) []string { + id := fmt.Sprintf("%s%s", prefix, n.Id) + + prefixes := strings.Join(n.Prefixes, ",") + shortIPUsage := nbr + shortPrefixUsage := nbr + ipv4Use := 0.0 + ipv4PrefixUse := 0.0 + ipv6Use := 0.0 + ipv6PrefixUse := 0.0 + + if n.Consumption != nil { + consumption := n.Consumption + if consumption.Ipv4 != nil { + ipv4Consumption := consumption.Ipv4 + ipv4Use = float64(ipv4Consumption.UsedIps) / float64(ipv4Consumption.AvailableIps) + + if ipv4Consumption.AvailablePrefixes > 0 { + ipv4PrefixUse = float64(ipv4Consumption.UsedPrefixes) / float64(ipv4Consumption.AvailablePrefixes) + } + } + if consumption.Ipv6 != nil { + ipv6Consumption := consumption.Ipv6 + ipv6Use = float64(ipv6Consumption.UsedIps) / float64(ipv6Consumption.AvailableIps) + + if ipv6Consumption.AvailablePrefixes > 0 { + ipv6PrefixUse = float64(ipv6Consumption.UsedPrefixes) / float64(ipv6Consumption.AvailablePrefixes) + } + } + + if ipv4Use >= 0.9 || ipv6Use >= 0.9 { + shortIPUsage = color.RedString(threequarterpie) + } else if ipv4Use >= 0.7 || ipv6Use >= 0.7 { + shortIPUsage += color.YellowString(halfpie) + } else { + shortIPUsage += color.GreenString(dot) + } + + if ipv4PrefixUse >= 0.9 || ipv6PrefixUse >= 0.9 { + shortPrefixUsage = color.RedString(threequarterpie) + } else if ipv4PrefixUse >= 0.7 || ipv6PrefixUse >= 0.7 { + shortPrefixUsage = color.YellowString(halfpie) + } else { + shortPrefixUsage = color.GreenString(dot) + } + } + + var ( + description = pointer.SafeDeref(n.Description) + name = pointer.SafeDeref(n.Name) + project = pointer.SafeDeref(n.Project) + partition = pointer.SafeDeref(n.Partition) + ) + + max := getMaxLineCount(description, name, project, partition, n.NatType.String(), prefixes, shortIPUsage) + for i := 0; i < max-1; i++ { + id += "\n│" + } + + var as []string + if n.Meta.Labels != nil { + for k, v := range n.Meta.Labels.Labels { + as = append(as, k+"="+v) + } + } + + annotations := strings.Join(as, "\n") + + if wide { + return []string{id, description, name, project, partition, n.NatType.String(), prefixes, annotations} + } else { + return []string{id, name, project, partition, n.NatType.String(), shortPrefixUsage, prefixes, shortIPUsage} + } +} + +func (nn *networks) appendChild(parentID string, child *apiv2.Network) bool { + for _, n := range *nn { + if n.parent.Id == parentID { + n.children = append(n.children, child) + return true + } + } + return false +} diff --git a/go.mod b/go.mod index 4241e00..029e0b3 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/dustin/go-humanize v1.0.1 github.com/fatih/color v1.18.0 github.com/google/go-cmp v0.7.0 - github.com/metal-stack/api v0.0.0-20250507054936-d3d9fbfb4653 + github.com/metal-stack/api v0.0.0-20250509114457-f9a7a417f35b github.com/metal-stack/metal-lib v0.22.1 github.com/metal-stack/v v1.0.3 github.com/olekukonko/tablewriter v0.0.5 diff --git a/go.sum b/go.sum index a3c17c9..a6a1d1d 100644 --- a/go.sum +++ b/go.sum @@ -53,8 +53,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/metal-stack/api v0.0.0-20250507054936-d3d9fbfb4653 h1:N+CH9POXqK8Dvz7djFZtfWPUF830Ce2DKOqo+fUTqbc= -github.com/metal-stack/api v0.0.0-20250507054936-d3d9fbfb4653/go.mod h1:GBhtI/TaPjmABsx3wUxIlMTXNpm7MB4dmm5hh3SS03k= +github.com/metal-stack/api v0.0.0-20250509114457-f9a7a417f35b h1:F+le8kPDymW9n1nff9jTzGcmCKzVwxZjUsLgCr1rXvs= +github.com/metal-stack/api v0.0.0-20250509114457-f9a7a417f35b/go.mod h1:GBhtI/TaPjmABsx3wUxIlMTXNpm7MB4dmm5hh3SS03k= github.com/metal-stack/metal-lib v0.22.1 h1:kAXOHZOSqBA0NQgYmtONxfY5khyQo8ofEL30QBE5DEY= github.com/metal-stack/metal-lib v0.22.1/go.mod h1:QiFb7TpSrvnLAHOlxLiUm1aG+1t5nPHsoDT/bw+F5r8= github.com/metal-stack/v v1.0.3 h1:Sh2oBlnxrCUD+mVpzfC8HiqL045YWkxs0gpTvkjppqs= From d3bb3124ff83c438d4ff68a16b7c43e54589dee6 Mon Sep 17 00:00:00 2001 From: Gerrit Date: Thu, 15 May 2025 16:22:40 +0200 Subject: [PATCH 02/11] Reough impl for network. --- cmd/api/v1/commands.go | 9 +- cmd/api/v1/ip.go | 6 +- cmd/api/v1/network.go | 492 +++++++++------------------- cmd/completion/ip.go | 4 + cmd/completion/network.go | 24 ++ cmd/completion/partition.go | 21 ++ docs/metalctlv2.md | 1 + docs/metalctlv2_network.md | 38 +++ docs/metalctlv2_network_apply.md | 46 +++ docs/metalctlv2_network_create.md | 55 ++++ docs/metalctlv2_network_delete.md | 46 +++ docs/metalctlv2_network_describe.md | 31 ++ docs/metalctlv2_network_edit.md | 31 ++ docs/metalctlv2_network_list.md | 43 +++ docs/metalctlv2_network_update.md | 50 +++ go.mod | 2 + go.sum | 6 + 17 files changed, 555 insertions(+), 350 deletions(-) create mode 100644 cmd/completion/network.go create mode 100644 cmd/completion/partition.go create mode 100644 docs/metalctlv2_network.md create mode 100644 docs/metalctlv2_network_apply.md create mode 100644 docs/metalctlv2_network_create.md create mode 100644 docs/metalctlv2_network_delete.md create mode 100644 docs/metalctlv2_network_describe.md create mode 100644 docs/metalctlv2_network_edit.md create mode 100644 docs/metalctlv2_network_list.md create mode 100644 docs/metalctlv2_network_update.md diff --git a/cmd/api/v1/commands.go b/cmd/api/v1/commands.go index 1e62d60..09490f3 100644 --- a/cmd/api/v1/commands.go +++ b/cmd/api/v1/commands.go @@ -6,13 +6,14 @@ import ( ) func AddCmds(cmd *cobra.Command, c *config.Config) { - cmd.AddCommand(newVersionCmd(c)) cmd.AddCommand(newHealthCmd(c)) - cmd.AddCommand(newTokenCmd(c)) - cmd.AddCommand(newIPCmd(c)) cmd.AddCommand(newImageCmd(c)) + cmd.AddCommand(newIPCmd(c)) + cmd.AddCommand(newMethodsCmd(c)) + cmd.AddCommand(newNetworkCmd(c)) cmd.AddCommand(newProjectCmd(c)) cmd.AddCommand(newTenantCmd(c)) - cmd.AddCommand(newMethodsCmd(c)) + cmd.AddCommand(newTokenCmd(c)) cmd.AddCommand(newUserCmd(c)) + cmd.AddCommand(newVersionCmd(c)) } diff --git a/cmd/api/v1/ip.go b/cmd/api/v1/ip.go index 3d71d33..82e7650 100644 --- a/cmd/api/v1/ip.go +++ b/cmd/api/v1/ip.go @@ -214,13 +214,17 @@ func IpResponseToCreate(r *apiv2.IP) *apiv2.IPServiceCreateRequest { } func IpResponseToUpdate(r *apiv2.IP) *apiv2.IPServiceUpdateRequest { + meta := pointer.SafeDeref(r.Meta) + return &apiv2.IPServiceUpdateRequest{ Project: r.Project, Ip: r.Ip, Name: &r.Name, Description: &r.Description, Type: &r.Type, - Labels: r.Meta.Labels, + Labels: &apiv2.UpdateLabels{ + Update: meta.Labels, // TODO: this only ensures that the labels are present but it does not cleanup old one's, which would require fetching the current state and calculating the diff + }, } } diff --git a/cmd/api/v1/network.go b/cmd/api/v1/network.go index 5c53931..f7ad58b 100644 --- a/cmd/api/v1/network.go +++ b/cmd/api/v1/network.go @@ -1,460 +1,262 @@ package v1 import ( - "errors" - "fmt" - - "slices" - + "connectrpc.com/connect" apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" - "github.com/metal-stack/metal-go/api/client/network" - "github.com/metal-stack/metal-go/api/models" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/cli/cmd/sorters" "github.com/metal-stack/metal-lib/pkg/genericcli" "github.com/metal-stack/metal-lib/pkg/genericcli/printers" "github.com/metal-stack/metal-lib/pkg/pointer" - "github.com/metal-stack/metalctl/cmd/sorters" + "github.com/metal-stack/metal-lib/pkg/tag" "github.com/spf13/cobra" "github.com/spf13/viper" - "k8s.io/apimachinery/pkg/util/sets" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) type networkCmd struct { - *config - childCLI *genericcli.GenericCLI[*apiv2.NetworkServiceCreateRequest, any, *apiv2.Network] + c *config.Config } -func newNetworkCmd(c *config) *cobra.Command { +func newNetworkCmd(c *config.Config) *cobra.Command { w := &networkCmd{ - config: c, - childCLI: genericcli.NewGenericCLI[*apiv2.NetworkServiceCreateRequest, any, *apiv2.Network](networkChildCRUD{config: c}).WithFS(c.fs), + c: c, } - cmdsConfig := &genericcli.CmdsConfig[*models.V1NetworkCreateRequest, *models.V1NetworkUpdateRequest, *apiv2.Network]{ - BinaryName: binaryName, - GenericCLI: genericcli.NewGenericCLI[*models.V1NetworkCreateRequest, *models.V1NetworkUpdateRequest, *apiv2.Network](w).WithFS(c.fs), + cmdsConfig := &genericcli.CmdsConfig[*apiv2.NetworkServiceCreateRequest, *apiv2.NetworkServiceUpdateRequest, *apiv2.Network]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), Singular: "network", Plural: "networks", Description: "networks can be attached to a machine or firewall such that they can communicate with each other.", CreateRequestFromCLI: w.createRequestFromCLI, UpdateRequestFromCLI: w.updateRequestFromCLI, Sorter: sorters.NetworkSorter(), - ValidArgsFn: c.comp.NetworkListCompletion, - DescribePrinter: func() printers.Printer { return c.describePrinter }, - ListPrinter: func() printers.Printer { return c.listPrinter }, + ValidArgsFn: c.Completion.NetworkListCompletion, + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, CreateCmdMutateFn: func(cmd *cobra.Command) { - cmd.Flags().StringP("id", "", "", "id of the network to create. [optional]") + cmd.Flags().StringP("name", "n", "", "name of the network to create. [required]") + cmd.Flags().StringP("partition", "", "", "partition where this network should exist. [required]") + cmd.Flags().String("project", "", "partition where this network should exist (alternative to parent-network-id). [optional]") + cmd.Flags().String("parent-network-id", "", "the parent of the network (alternative to partition). [optional]") cmd.Flags().StringP("description", "d", "", "description of the network to create. [optional]") - cmd.Flags().StringP("name", "n", "", "name of the network to create. [optional]") - cmd.Flags().StringP("partition", "p", "", "partition where this network should exist.") - cmd.Flags().StringP("project", "", "", "project of the network to create. [optional]") - cmd.Flags().Int64("default-ipv4-child-prefix-length", 0, "default child prefix length for ipv4 prefixes for private super networks.") - cmd.Flags().Int64("default-ipv6-child-prefix-length", 0, "default child prefix length for ipv6 prefixes for private super networks.") - cmd.Flags().StringSlice("prefixes", []string{}, "prefixes in this network.") - cmd.Flags().StringSlice("labels", []string{}, "add initial labels, must be in the form of key=value, use it like: --labels \"key1=value1,key2=value2\".") - cmd.Flags().StringSlice("destination-prefixes", []string{}, "destination prefixes in this network.") - cmd.Flags().StringSlice("additional-announcable-cidrs", []string{}, "list of cidrs which are added to the route maps per tenant private network, these are typically pod- and service cidrs, can only be set in a supernetwork") - cmd.Flags().BoolP("privatesuper", "", false, "set private super flag of network, if set to true, this network is used to start machines there.") - cmd.Flags().BoolP("nat", "", false, "set nat flag of network, if set to true, traffic from this network will be natted.") - cmd.Flags().BoolP("underlay", "", false, "set underlay flag of network, if set to true, this is used to transport underlay network traffic") - cmd.Flags().Int64P("vrf", "", 0, "vrf of this network") - cmd.Flags().BoolP("vrfshared", "", false, "vrf shared allows multiple networks to share a vrf") - genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.comp.ProjectListCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.comp.PartitionListCompletion)) + cmd.Flags().StringSlice("labels", []string{}, "labels for this network. [optional]") + cmd.Flags().StringP("addressfamily", "", "", "addressfamily of the network to acquire, if not specified the network inherits the address families from the parent [optional]") + cmd.Flags().Uint32("ipv4-prefix-length", 0, "ipv4 prefix bit length of the network to create, defaults to default child prefix length of the parent network. [optional]") + cmd.Flags().Uint32("ipv6-prefix-length", 0, "ipv6 prefix bit length of the network to create, defaults to default child prefix length of the parent network. [optional]") + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.PartitionListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.IpAddressFamilyCompletion)) }, ListCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().String("id", "", "ID to filter [optional]") cmd.Flags().String("name", "", "name to filter [optional]") + cmd.Flags().String("description", "", "description to filter [optional]") cmd.Flags().String("partition", "", "partition to filter [optional]") cmd.Flags().String("project", "", "project to filter [optional]") - cmd.Flags().String("parent", "", "parent network to filter [optional]") - cmd.Flags().BoolP("nat", "", false, "nat to filter [optional]") - cmd.Flags().BoolP("privatesuper", "", false, "privatesuper to filter [optional]") - cmd.Flags().BoolP("underlay", "", false, "underlay to filter [optional]") - cmd.Flags().Int64P("vrf", "", 0, "vrf to filter [optional]") + cmd.Flags().String("parent-network-id", "", "parent network 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)) + cmd.Flags().Uint32("vrf", 0, "vrf to filter [optional]") + cmd.Flags().StringSlice("labels", nil, "labels to filter [optional]") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.PartitionListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.IpAddressFamilyCompletion)) }, UpdateCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().String("name", "", "the name of the network [optional]") cmd.Flags().String("description", "", "the description of the network [optional]") - cmd.Flags().StringSlice("add-prefixes", []string{}, "prefixes to be added to the network [optional]") - cmd.Flags().StringSlice("remove-prefixes", []string{}, "prefixes to be removed from the network [optional]") - cmd.Flags().StringSlice("add-destinationprefixes", []string{}, "destination prefixes to be added to the network [optional]") - cmd.Flags().StringSlice("remove-destinationprefixes", []string{}, "destination prefixes to be removed from the network [optional]") cmd.Flags().StringSlice("labels", []string{}, "the labels of the network, must be in the form of key=value, use it like: --labels \"key1=value1,key2=value2\". [optional]") - cmd.Flags().StringSlice("additional-announcable-cidrs", []string{}, "list of cidrs which are added to the route maps per tenant private network, these are typically pod- and service cidrs, can only be set in a supernetwork") - cmd.Flags().Bool("shared", false, "marks a network as shared or not [optional]") - }, - } - - allocateCmd := &cobra.Command{ - Use: "allocate", - Short: "allocate a network", - RunE: func(cmd *cobra.Command, args []string) error { - if !viper.IsSet("file") { - shared := viper.GetBool("shared") - nat := false - var destinationPrefixes []string - if viper.GetBool("dmz") { - shared = true - destinationPrefixes = []string{"0.0.0.0/0"} - nat = true - } - - labels, err := genericcli.LabelsToMap(viper.GetStringSlice("labels")) - if err != nil { - return err - } - - var ( - length = make(map[string]int64) - ) - if viper.IsSet("ipv4-prefix-length") { - length[models.V1IPAllocateRequestAddressfamilyIPV4] = viper.GetInt64("ipv4-prefix-length") - } - if viper.IsSet("ipv6-prefix-length") { - length[models.V1IPAllocateRequestAddressfamilyIPV6] = viper.GetInt64("ipv6-prefix-length") - } - - return w.childCLI.CreateAndPrint(&apiv2.NetworkServiceCreateRequest{ - Description: viper.GetString("description"), - Name: viper.GetString("name"), - Partition: viper.GetString("partition"), - Project: viper.GetString("project"), - Shared: shared, - Labels: labels, - Nat: nat, - AddressFamily: viper.GetString("addressfamily"), - Length: length, - }, c.describePrinter) - } - - return w.childCLI.CreateFromFileAndPrint(viper.GetString("file"), c.describePrinter) - }, - } - - freeCmd := &cobra.Command{ - Use: "free ", - Short: "free a network", - RunE: func(cmd *cobra.Command, args []string) error { - id, err := genericcli.GetExactlyOneArg(args) - if err != nil { - return err - } - - return w.childCLI.DeleteAndPrint(id, c.describePrinter) + cmd.Flags().String("project", "", "project to filter [optional]") }, - ValidArgsFunction: c.comp.NetworkListCompletion, } - allocateCmd.Flags().StringP("name", "n", "", "name of the network to create. [required]") - allocateCmd.Flags().StringP("partition", "", "", "partition where this network should exist. [required]") - allocateCmd.Flags().StringP("project", "", "", "partition where this network should exist. [required]") - allocateCmd.Flags().StringP("description", "d", "", "description of the network to create. [optional]") - 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", "", "", "addressfamily of the network to acquire, if not specified the network inherits the address families from the parent [optional]") - allocateCmd.Flags().Int64P("ipv4-prefix-length", "", 0, "ipv4 prefix bit length of the network to create, defaults to default child prefix length of the parent network. [optional]") - allocateCmd.Flags().Int64P("ipv6-prefix-length", "", 0, "ipv6 prefix bit length of the network to create, defaults to default child prefix length of the parent network. [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")) - genericcli.Must(allocateCmd.MarkFlagRequired("partition")) - - return genericcli.NewCmds( - cmdsConfig, - newIPCmd(c), - allocateCmd, - freeCmd, - ) + return genericcli.NewCmds(cmdsConfig) } func (c *networkCmd) Get(id string) (*apiv2.Network, error) { - resp, err := c.client.Network().FindNetwork(network.NewFindNetworkParams().WithID(id), nil) + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Apiv2().Network().Get(ctx, connect.NewRequest(&apiv2.NetworkServiceGetRequest{ + Id: id, + Project: c.c.GetProject(), + })) if err != nil { return nil, err } - return resp.Payload, nil + return resp.Msg.Network, nil } func (c *networkCmd) List() ([]*apiv2.Network, error) { - resp, err := c.client.Network().FindNetworks(network.NewFindNetworksParams().WithBody(&models.V1NetworkFindRequest{ - ID: viper.GetString("id"), - Name: viper.GetString("name"), - Partitionid: viper.GetString("partition"), - Projectid: viper.GetString("project"), - Nat: viper.GetBool("nat"), - Privatesuper: viper.GetBool("privatesuper"), - Underlay: viper.GetBool("underlay"), - Vrf: viper.GetInt64("vrf"), - Prefixes: viper.GetStringSlice("prefixes"), - Destinationprefixes: viper.GetStringSlice("destination-prefixes"), - Parentnetworkid: viper.GetString("parent"), - Addressfamily: viper.GetString("addressfamily"), - }), nil) + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Apiv2().Network().List(ctx, connect.NewRequest(&apiv2.NetworkServiceListRequest{ + Project: c.c.GetProject(), + Query: &apiv2.NetworkQuery{ + Id: pointer.PointerOrNil(viper.GetString("id")), + Name: pointer.PointerOrNil(viper.GetString("name")), + Description: pointer.PointerOrNil(viper.GetString("description")), + Partition: pointer.PointerOrNil(viper.GetString("partition")), + Project: pointer.PointerOrNil(viper.GetString("project")), + Prefixes: viper.GetStringSlice("prefixes"), + DestinationPrefixes: viper.GetStringSlice("destination-prefixes"), + Vrf: pointer.PointerOrNil(viper.GetUint32("vrf")), + ParentNetworkId: pointer.PointerOrNil(viper.GetString("parent-network-id")), + AddressFamily: addressFamilyToType(viper.GetString("addressfamily")), + Labels: &apiv2.Labels{ + Labels: tag.NewTagMap(viper.GetStringSlice("labels")), + }, + }, + })) + if err != nil { return nil, err } - return resp.Payload, nil + return resp.Msg.Networks, nil } func (c *networkCmd) Delete(id string) (*apiv2.Network, error) { - resp, err := c.client.Network().DeleteNetwork(network.NewDeleteNetworkParams().WithID(id), nil) + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Apiv2().Network().Delete(ctx, connect.NewRequest(&apiv2.NetworkServiceDeleteRequest{ + Id: id, + Project: c.c.GetProject(), + })) if err != nil { return nil, err } - return resp.Payload, nil + return resp.Msg.Network, nil } -func (c *networkCmd) Create(rq *models.V1NetworkCreateRequest) (*apiv2.Network, error) { - resp, err := c.client.Network().CreateNetwork(network.NewCreateNetworkParams().WithBody(rq), nil) +func (c *networkCmd) Create(rq *apiv2.NetworkServiceCreateRequest) (*apiv2.Network, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Apiv2().Network().Create(ctx, connect.NewRequest(rq)) if err != nil { - var r *network.CreateNetworkConflict - if errors.As(err, &r) { + if s, ok := status.FromError(err); ok && s.Code() == codes.AlreadyExists { return nil, genericcli.AlreadyExistsError() } return nil, err } - return resp.Payload, nil + return resp.Msg.Network, nil } -func (c *networkCmd) Update(rq *models.V1NetworkUpdateRequest) (*apiv2.Network, error) { - resp, err := c.client.Network().UpdateNetwork(network.NewUpdateNetworkParams().WithBody(rq).WithForce(pointer.Pointer(viper.GetBool(forceFlag))), nil) +func (c *networkCmd) Update(rq *apiv2.NetworkServiceUpdateRequest) (*apiv2.Network, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Apiv2().Network().Update(ctx, connect.NewRequest(rq)) if err != nil { return nil, err } - return resp.Payload, nil + return resp.Msg.Network, nil } -func (c *networkCmd) Convert(r *apiv2.Network) (string, *models.V1NetworkCreateRequest, *models.V1NetworkUpdateRequest, error) { - if r.ID == nil { - return "", nil, nil, fmt.Errorf("id is nil") - } - return *r.ID, networkResponseToCreate(r), networkResponseToUpdate(r), nil +func (c *networkCmd) Convert(r *apiv2.Network) (string, *apiv2.NetworkServiceCreateRequest, *apiv2.NetworkServiceUpdateRequest, error) { + return r.Id, networkResponseToCreate(r), networkResponseToUpdate(r), nil } -func networkResponseToCreate(r *apiv2.Network) *models.V1NetworkCreateRequest { +func networkResponseToCreate(r *apiv2.Network) *apiv2.NetworkServiceCreateRequest { + meta := pointer.SafeDeref(r.Meta) + return &apiv2.NetworkServiceCreateRequest{ - Description: r.Description, - Labels: r.Meta.Labels, - Name: r.Name, + Project: pointer.SafeDeref(r.Project), + Name: r.Name, + Description: r.Description, + Partition: r.Partition, + Labels: &apiv2.Labels{ + Labels: pointer.SafeDeref(meta.Labels).Labels, + }, ParentNetworkId: r.ParentNetworkId, - Partition: r.Partition, - Project: pointer.SafeDeref(r.Project), - AddressFamily: , - Length: , + // TODO: allow defining length and addressfamilies somehow? } } -func networkResponseToUpdate(r *apiv2.Network) *models.V1NetworkUpdateRequest { - return &models.V1NetworkUpdateRequest{ - Description: r.Description, - Destinationprefixes: r.Destinationprefixes, - ID: r.ID, - Labels: r.Labels, - Name: r.Name, - Prefixes: r.Prefixes, - Shared: r.Shared, - AdditionalAnnouncableCIDRs: r.AdditionalAnnouncableCIDRs, - Defaultchildprefixlength: r.Defaultchildprefixlength, - } +func networkResponseToUpdate(r *apiv2.Network) *apiv2.NetworkServiceUpdateRequest { + meta := pointer.SafeDeref(r.Meta) + + return &apiv2.NetworkServiceUpdateRequest{ + Id: r.Id, + Project: pointer.SafeDeref(r.Project), + Name: r.Name, + Description: r.Description, + Labels: &apiv2.UpdateLabels{ + Update: meta.Labels, // TODO: this only ensures that the labels are present but it does not cleanup old one's, which would require fetching the current state and calculating the diff + }} } -func (c *networkCmd) createRequestFromCLI() (*models.V1NetworkCreateRequest, error) { - lbs, err := genericcli.LabelsToMap(viper.GetStringSlice("labels")) +func (c *networkCmd) createRequestFromCLI() (*apiv2.NetworkServiceCreateRequest, error) { + labels, err := genericcli.LabelsToMap(viper.GetStringSlice("labels")) if err != nil { return nil, err } - var defaultChildPrefixLengths map[string]int64 - if viper.GetBool("privatesuper") { - defaultChildPrefixLengths = map[string]int64{} - - if length := viper.GetInt64("default-ipv4-child-prefix-length"); length > 0 { - defaultChildPrefixLengths[models.V1IPAllocateRequestAddressfamilyIPV4] = length - } - if length := viper.GetInt64("default-ipv6-child-prefix-length"); length > 0 { - defaultChildPrefixLengths[models.V1IPAllocateRequestAddressfamilyIPV6] = length - } - } - - return &models.V1NetworkCreateRequest{ - ID: pointer.Pointer(viper.GetString("id")), - Description: viper.GetString("description"), - Name: viper.GetString("name"), - Partitionid: viper.GetString("partition"), - Projectid: viper.GetString("project"), - Prefixes: viper.GetStringSlice("prefixes"), - Destinationprefixes: viper.GetStringSlice("destination-prefixes"), - Privatesuper: pointer.Pointer(viper.GetBool("privatesuper")), - Nat: pointer.Pointer(viper.GetBool("nat")), - Underlay: pointer.Pointer(viper.GetBool("underlay")), - Vrf: viper.GetInt64("vrf"), - Vrfshared: viper.GetBool("vrfshared"), - Labels: lbs, - AdditionalAnnouncableCIDRs: viper.GetStringSlice("additional-announcable-cidrs"), - Defaultchildprefixlength: defaultChildPrefixLengths, - }, nil -} - -type networkChildCRUD struct { - *config -} - -func (c networkChildCRUD) Get(id string) (*apiv2.Network, error) { - return nil, fmt.Errorf("not implemented for child networks, use network update") -} - -func (c networkChildCRUD) List() ([]*apiv2.Network, error) { - return nil, fmt.Errorf("not implemented for child networks, use network update") -} - -func (c networkChildCRUD) Delete(id string) (*apiv2.Network, error) { - resp, err := c.client.Network().FreeNetwork(network.NewFreeNetworkParams().WithID(id), nil) - if err != nil { - return nil, err + var ( + cpl = &apiv2.ChildPrefixLength{} + ) + if viper.IsSet("ipv4-prefix-length") { + cpl.Ipv4 = pointer.Pointer(viper.GetUint32("ipv4-prefix-length")) } - - return resp.Payload, nil -} - -func (c networkChildCRUD) Create(rq *apiv2.NetworkServiceCreateRequest) (*apiv2.Network, error) { - resp, err := c.client.Network().AllocateNetwork(network.NewAllocateNetworkParams().WithBody(rq), nil) - if err != nil { - var r *network.AllocateNetworkConflict - if errors.As(err, &r) { - return nil, genericcli.AlreadyExistsError() - } - return nil, err + if viper.IsSet("ipv6-prefix-length") { + cpl.Ipv6 = pointer.Pointer(viper.GetUint32("ipv6-prefix-length")) } - return resp.Payload, nil -} - -func (c networkChildCRUD) Update(rq any) (*apiv2.Network, error) { - return nil, fmt.Errorf("not implemented for child networks, use network update") -} - -func (c networkChildCRUD) Convert(r *apiv2.Network) (string, *apiv2.NetworkServiceCreateRequest, any, error) { - if r.ID == nil { - return "", nil, nil, fmt.Errorf("id is nil") - } - return *r.ID, &apiv2.NetworkServiceCreateRequest{ - Description: r.Description, - Destinationprefixes: r.Destinationprefixes, - Labels: r.Labels, - Name: r.Name, - Nat: pointer.SafeDeref(r.Nat), - Partitionid: r.Partitionid, - Projectid: r.Projectid, - Shared: false, - }, nil, nil + return &apiv2.NetworkServiceCreateRequest{ + Description: pointer.PointerOrNil(viper.GetString("description")), + Name: pointer.PointerOrNil(viper.GetString("name")), + Project: c.c.GetProject(), + Partition: pointer.PointerOrNil(viper.GetString("partition")), + Labels: &apiv2.Labels{ + Labels: labels, + }, + ParentNetworkId: pointer.PointerOrNil(viper.GetString("parent-network-id")), + Length: cpl, + AddressFamily: addressFamilyToType(viper.GetString("addressfamily")), + }, nil } -func (c *networkCmd) updateRequestFromCLI(args []string) (*models.V1NetworkUpdateRequest, error) { +func (c *networkCmd) updateRequestFromCLI(args []string) (*apiv2.NetworkServiceUpdateRequest, error) { id, err := genericcli.GetExactlyOneArg(args) if err != nil { return nil, err } - resp, err := c.Get(id) - if err != nil { - return nil, err - } - - var labels map[string]string + var labels *apiv2.UpdateLabels if viper.IsSet("labels") { - labels, err = genericcli.LabelsToMap(viper.GetStringSlice("labels")) + lbls, err := genericcli.LabelsToMap(viper.GetStringSlice("labels")) if err != nil { return nil, err } - } - shared := resp.Shared - if viper.IsSet("shared") { - shared = viper.GetBool("shared") + labels = &apiv2.UpdateLabels{ + Update: &apiv2.Labels{ + Labels: lbls, + }, + } } - additionalCidrs := resp.AdditionalAnnouncableCIDRs - if viper.IsSet("additional-announcable-cidrs") { - additionalCidrs = viper.GetStringSlice("additional-announcable-cidrs") - } var ( - ur = &models.V1NetworkUpdateRequest{ - Description: viper.GetString("description"), - Destinationprefixes: nil, - ID: pointer.Pointer(id), - Labels: labels, - Name: viper.GetString("name"), - Prefixes: nil, - Shared: shared, - AdditionalAnnouncableCIDRs: additionalCidrs, - Defaultchildprefixlength: resp.Defaultchildprefixlength, + ur = &apiv2.NetworkServiceUpdateRequest{ + Id: id, + Project: c.c.GetProject(), + Description: pointer.PointerOrNil(viper.GetString("description")), + Name: pointer.PointerOrNil(viper.GetString("name")), + Labels: labels, } - addPrefixes = sets.New(viper.GetStringSlice("add-prefixes")...) - removePrefixes = sets.New(viper.GetStringSlice("remove-prefixes")...) - addDestinationprefixes = sets.New(viper.GetStringSlice("add-destinationprefixes")...) - removeDestinationprefixes = sets.New(viper.GetStringSlice("remove-destinationprefixes")...) - currentPrefixes = sets.New(resp.Prefixes...) - currentDestinationprefixes = sets.New(resp.Destinationprefixes...) ) - newPrefixes := currentPrefixes.Clone() - if viper.IsSet("remove-prefixes") { - diff := removePrefixes.Difference(currentPrefixes) - if diff.Len() > 0 { - difflist := diff.UnsortedList() - slices.Sort(difflist) - return nil, fmt.Errorf("cannot remove prefixes because they are currently not present: %s", difflist) - } - newPrefixes = newPrefixes.Difference(removePrefixes) - } - if viper.IsSet("add-prefixes") { - if currentPrefixes.HasAny(addPrefixes.UnsortedList()...) { - intersection := addPrefixes.Intersection(currentPrefixes).UnsortedList() - slices.Sort(intersection) - return nil, fmt.Errorf("cannot add prefixes because they are already present: %s", intersection) - } - newPrefixes = newPrefixes.Union(addPrefixes) - } - if !newPrefixes.Equal(currentPrefixes) { - ur.Prefixes = newPrefixes.UnsortedList() - } - - newDestinationprefixes := currentDestinationprefixes.Clone() - if viper.IsSet("remove-destinationprefixes") { - diff := removeDestinationprefixes.Difference(currentDestinationprefixes) - if diff.Len() > 0 { - difflist := diff.UnsortedList() - slices.Sort(difflist) - return nil, fmt.Errorf("cannot remove destination prefixes because they are currently not present: %s", difflist) - } - newDestinationprefixes = newDestinationprefixes.Difference(removeDestinationprefixes) - } - if viper.IsSet("add-destinationprefixes") { - if currentDestinationprefixes.HasAny(addDestinationprefixes.UnsortedList()...) { - interSection := addDestinationprefixes.Intersection(currentDestinationprefixes).UnsortedList() - slices.Sort(interSection) - return nil, fmt.Errorf("cannot add destination prefixes because they are already present: %s", interSection) - } - newDestinationprefixes = newDestinationprefixes.Union(addDestinationprefixes) - } - if !newDestinationprefixes.Equal(currentDestinationprefixes) { - ur.Destinationprefixes = newDestinationprefixes.UnsortedList() - } - return ur, nil } diff --git a/cmd/completion/ip.go b/cmd/completion/ip.go index 06d7241..3b508c1 100644 --- a/cmd/completion/ip.go +++ b/cmd/completion/ip.go @@ -20,3 +20,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{apiv2.IPAddressFamily_IP_ADDRESS_FAMILY_V4.String(), apiv2.IPAddressFamily_IP_ADDRESS_FAMILY_V6.String()}, cobra.ShellCompDirectiveNoFileComp +} diff --git a/cmd/completion/network.go b/cmd/completion/network.go new file mode 100644 index 0000000..848bfd9 --- /dev/null +++ b/cmd/completion/network.go @@ -0,0 +1,24 @@ +package completion + +import ( + "connectrpc.com/connect" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/metal-lib/pkg/pointer" + "github.com/spf13/cobra" +) + +func (c *Completion) NetworkListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + resp, err := c.Client.Apiv2().Network().List(c.Ctx, connect.NewRequest(&apiv2.NetworkServiceListRequest{ + Project: c.Project, + })) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + var names []string + for _, s := range resp.Msg.Networks { + names = append(names, s.Id+"\t"+pointer.SafeDeref(s.Name)) + } + + return names, cobra.ShellCompDirectiveNoFileComp +} diff --git a/cmd/completion/partition.go b/cmd/completion/partition.go new file mode 100644 index 0000000..7f353c6 --- /dev/null +++ b/cmd/completion/partition.go @@ -0,0 +1,21 @@ +package completion + +import ( + "connectrpc.com/connect" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/spf13/cobra" +) + +func (c *Completion) PartitionListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + resp, err := c.Client.Apiv2().Partition().List(c.Ctx, connect.NewRequest(&apiv2.PartitionServiceListRequest{})) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + var names []string + for _, s := range resp.Msg.Partitions { + names = append(names, s.Id+"\t"+s.Description) + } + + return names, cobra.ShellCompDirectiveNoFileComp +} diff --git a/docs/metalctlv2.md b/docs/metalctlv2.md index 4671daf..965a721 100644 --- a/docs/metalctlv2.md +++ b/docs/metalctlv2.md @@ -27,6 +27,7 @@ cli for managing entities in metal-stack * [metalctlv2 login](metalctlv2_login.md) - login * [metalctlv2 logout](metalctlv2_logout.md) - logout * [metalctlv2 markdown](metalctlv2_markdown.md) - create markdown documentation +* [metalctlv2 network](metalctlv2_network.md) - manage network entities * [metalctlv2 project](metalctlv2_project.md) - manage project entities * [metalctlv2 tenant](metalctlv2_tenant.md) - manage tenant entities * [metalctlv2 token](metalctlv2_token.md) - manage token entities diff --git a/docs/metalctlv2_network.md b/docs/metalctlv2_network.md new file mode 100644 index 0000000..cf08271 --- /dev/null +++ b/docs/metalctlv2_network.md @@ -0,0 +1,38 @@ +## metalctlv2 network + +manage network entities + +### Synopsis + +networks can be attached to a machine or firewall such that they can communicate with each other. + +### Options + +``` + -h, --help help for network +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2](metalctlv2.md) - cli for managing entities in metal-stack +* [metalctlv2 network apply](metalctlv2_network_apply.md) - applies one or more networks from a given file +* [metalctlv2 network create](metalctlv2_network_create.md) - creates the network +* [metalctlv2 network delete](metalctlv2_network_delete.md) - deletes the network +* [metalctlv2 network describe](metalctlv2_network_describe.md) - describes the network +* [metalctlv2 network edit](metalctlv2_network_edit.md) - edit the network through an editor and update +* [metalctlv2 network list](metalctlv2_network_list.md) - list all networks +* [metalctlv2 network update](metalctlv2_network_update.md) - updates the network + diff --git a/docs/metalctlv2_network_apply.md b/docs/metalctlv2_network_apply.md new file mode 100644 index 0000000..5f479e9 --- /dev/null +++ b/docs/metalctlv2_network_apply.md @@ -0,0 +1,46 @@ +## metalctlv2 network apply + +applies one or more networks from a given file + +``` +metalctlv2 network apply [flags] +``` + +### Options + +``` + --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. + -f, --file string filename of the create or update request in yaml format, or - for stdin. + + Example: + $ metalctlv2 network describe network-1 -o yaml > network.yaml + $ vi network.yaml + $ # either via stdin + $ cat network.yaml | metalctlv2 network apply -f - + $ # or via file + $ metalctlv2 network apply -f network.yaml + + the file can also contain multiple documents and perform a bulk operation. + + -h, --help help for apply + --skip-security-prompts skips security prompt for bulk operations + --timestamps when used with --file (bulk operation): prints timestamps in-between the operations +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 network](metalctlv2_network.md) - manage network entities + diff --git a/docs/metalctlv2_network_create.md b/docs/metalctlv2_network_create.md new file mode 100644 index 0000000..08b4cc6 --- /dev/null +++ b/docs/metalctlv2_network_create.md @@ -0,0 +1,55 @@ +## metalctlv2 network create + +creates the network + +``` +metalctlv2 network create [flags] +``` + +### Options + +``` + --addressfamily string addressfamily of the network to acquire, if not specified the network inherits the address families from the parent [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 network to create. [optional] + -f, --file string filename of the create or update request in yaml format, or - for stdin. + + Example: + $ metalctlv2 network describe network-1 -o yaml > network.yaml + $ vi network.yaml + $ # either via stdin + $ cat network.yaml | metalctlv2 network create -f - + $ # or via file + $ metalctlv2 network create -f network.yaml + + the file can also contain multiple documents and perform a bulk operation. + + -h, --help help for create + --ipv4-prefix-length uint32 ipv4 prefix bit length of the network to create, defaults to default child prefix length of the parent network. [optional] + --ipv6-prefix-length uint32 ipv6 prefix bit length of the network to create, defaults to default child prefix length of the parent network. [optional] + --labels strings labels for this network. [optional] + -n, --name string name of the network to create. [required] + --parent-network-id string the parent of the network (alternative to partition). [optional] + --partition string partition where this network should exist. [required] + --project string partition where this network should exist (alternative to parent-network-id). [optional] + --skip-security-prompts skips security prompt for bulk operations + --timestamps when used with --file (bulk operation): prints timestamps in-between the operations +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 network](metalctlv2_network.md) - manage network entities + diff --git a/docs/metalctlv2_network_delete.md b/docs/metalctlv2_network_delete.md new file mode 100644 index 0000000..0e7bb2a --- /dev/null +++ b/docs/metalctlv2_network_delete.md @@ -0,0 +1,46 @@ +## metalctlv2 network delete + +deletes the network + +``` +metalctlv2 network delete [flags] +``` + +### Options + +``` + --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. + -f, --file string filename of the create or update request in yaml format, or - for stdin. + + Example: + $ metalctlv2 network describe network-1 -o yaml > network.yaml + $ vi network.yaml + $ # either via stdin + $ cat network.yaml | metalctlv2 network delete -f - + $ # or via file + $ metalctlv2 network delete -f network.yaml + + the file can also contain multiple documents and perform a bulk operation. + + -h, --help help for delete + --skip-security-prompts skips security prompt for bulk operations + --timestamps when used with --file (bulk operation): prints timestamps in-between the operations +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 network](metalctlv2_network.md) - manage network entities + diff --git a/docs/metalctlv2_network_describe.md b/docs/metalctlv2_network_describe.md new file mode 100644 index 0000000..b7c9556 --- /dev/null +++ b/docs/metalctlv2_network_describe.md @@ -0,0 +1,31 @@ +## metalctlv2 network describe + +describes the network + +``` +metalctlv2 network describe [flags] +``` + +### Options + +``` + -h, --help help for describe +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 network](metalctlv2_network.md) - manage network entities + diff --git a/docs/metalctlv2_network_edit.md b/docs/metalctlv2_network_edit.md new file mode 100644 index 0000000..4348569 --- /dev/null +++ b/docs/metalctlv2_network_edit.md @@ -0,0 +1,31 @@ +## metalctlv2 network edit + +edit the network through an editor and update + +``` +metalctlv2 network edit [flags] +``` + +### Options + +``` + -h, --help help for edit +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 network](metalctlv2_network.md) - manage network entities + diff --git a/docs/metalctlv2_network_list.md b/docs/metalctlv2_network_list.md new file mode 100644 index 0000000..9ff0a92 --- /dev/null +++ b/docs/metalctlv2_network_list.md @@ -0,0 +1,43 @@ +## metalctlv2 network list + +list all networks + +``` +metalctlv2 network list [flags] +``` + +### Options + +``` + --addressfamily string addressfamily to filter, either ipv4 or ipv6 [optional] + --description string description to filter [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] + --labels strings labels to filter [optional] + --name string name to filter [optional] + --parent-network-id string parent network to filter [optional] + --partition string partition to filter [optional] + --prefixes strings prefixes to filter, use it like: --prefixes prefix1,prefix2. + --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: description|id|name|partition|project + --vrf uint32 vrf to filter [optional] +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 network](metalctlv2_network.md) - manage network entities + diff --git a/docs/metalctlv2_network_update.md b/docs/metalctlv2_network_update.md new file mode 100644 index 0000000..69323b9 --- /dev/null +++ b/docs/metalctlv2_network_update.md @@ -0,0 +1,50 @@ +## metalctlv2 network update + +updates the network + +``` +metalctlv2 network update [flags] +``` + +### Options + +``` + --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. + --description string the description of the network [optional] + -f, --file string filename of the create or update request in yaml format, or - for stdin. + + Example: + $ metalctlv2 network describe network-1 -o yaml > network.yaml + $ vi network.yaml + $ # either via stdin + $ cat network.yaml | metalctlv2 network update -f - + $ # or via file + $ metalctlv2 network update -f network.yaml + + the file can also contain multiple documents and perform a bulk operation. + + -h, --help help for update + --labels strings the labels of the network, must be in the form of key=value, use it like: --labels "key1=value1,key2=value2". [optional] + --name string the name of the network [optional] + --project string project to filter [optional] + --skip-security-prompts skips security prompt for bulk operations + --timestamps when used with --file (bulk operation): prints timestamps in-between the operations +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 network](metalctlv2_network.md) - manage network entities + diff --git a/go.mod b/go.mod index 029e0b3..df06a41 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/spf13/viper v1.20.1 github.com/stretchr/testify v1.10.0 golang.org/x/net v0.40.0 + google.golang.org/grpc v1.71.0 google.golang.org/protobuf v1.36.6 sigs.k8s.io/yaml v1.4.0 ) @@ -57,6 +58,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.25.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apimachinery v0.33.0 // indirect diff --git a/go.sum b/go.sum index a6a1d1d..6cc1024 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,8 @@ github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIx github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-yaml v1.17.1 h1:LI34wktB2xEE3ONG/2Ar54+/HJVBriAGJ55PHls4YuY= github.com/goccy/go-yaml v1.17.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= @@ -107,6 +109,10 @@ golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2 h1:IqsN8hx+lWLqlN+Sc3DoMy/watjofWiU8sRFgQ8fhKM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= +google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From c1ef75e9321224861209383311a7638b30a0cedd Mon Sep 17 00:00:00 2001 From: Gerrit Date: Thu, 15 May 2025 16:44:33 +0200 Subject: [PATCH 03/11] Fixes. --- cmd/tableprinters/network.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/cmd/tableprinters/network.go b/cmd/tableprinters/network.go index a34a06b..7cfb9a4 100644 --- a/cmd/tableprinters/network.go +++ b/cmd/tableprinters/network.go @@ -21,7 +21,7 @@ func (t *TablePrinter) NetworkTable(data []*apiv2.Network, wide bool) ([]string, rows [][]string ) - header := []string{"ID", "Name", "Project", "Partition", "Nat", "", "Prefixes", "IP Usage"} + header := []string{"ID", "Name", "Project", "Partition", "Nat", "Prefixes", "IP Usage"} if wide { header = []string{"ID", "Description", "Name", "Project", "Partition", "Nat", "Prefixes", "Annotations"} } @@ -87,9 +87,9 @@ func addNetwork(prefix string, n *apiv2.Network, wide bool) []string { if ipv4Use >= 0.9 || ipv6Use >= 0.9 { shortIPUsage = color.RedString(threequarterpie) } else if ipv4Use >= 0.7 || ipv6Use >= 0.7 { - shortIPUsage += color.YellowString(halfpie) + shortIPUsage = color.YellowString(halfpie) } else { - shortIPUsage += color.GreenString(dot) + shortIPUsage = color.GreenString(dot) } if ipv4PrefixUse >= 0.9 || ipv6PrefixUse >= 0.9 { @@ -106,10 +106,11 @@ func addNetwork(prefix string, n *apiv2.Network, wide bool) []string { name = pointer.SafeDeref(n.Name) project = pointer.SafeDeref(n.Project) partition = pointer.SafeDeref(n.Partition) + natType = pointer.SafeDeref(n.NatType).String() ) - max := getMaxLineCount(description, name, project, partition, n.NatType.String(), prefixes, shortIPUsage) - for i := 0; i < max-1; i++ { + max := getMaxLineCount(description, name, project, partition, natType, prefixes, shortIPUsage) + for range max - 1 { id += "\n│" } @@ -123,9 +124,9 @@ func addNetwork(prefix string, n *apiv2.Network, wide bool) []string { annotations := strings.Join(as, "\n") if wide { - return []string{id, description, name, project, partition, n.NatType.String(), prefixes, annotations} + return []string{id, description, name, project, partition, natType, prefixes, annotations} } else { - return []string{id, name, project, partition, n.NatType.String(), shortPrefixUsage, prefixes, shortIPUsage} + return []string{id, name, project, partition, natType, shortPrefixUsage, shortIPUsage} } } From b4a5d185073d30c16e26c33b0930c56d8a78b8eb Mon Sep 17 00:00:00 2001 From: Gerrit Date: Fri, 16 May 2025 16:46:04 +0200 Subject: [PATCH 04/11] Start with admin command (not yet working). --- cmd/admin/v1/network.go | 274 ++++++++++++++++++++++++++++++++++++++++ cmd/api/v1/ip.go | 27 +--- cmd/api/v1/network.go | 90 +++++++++---- pkg/common/ip.go | 12 ++ pkg/common/network.go | 18 +++ 5 files changed, 375 insertions(+), 46 deletions(-) create mode 100644 cmd/admin/v1/network.go create mode 100644 pkg/common/ip.go create mode 100644 pkg/common/network.go diff --git a/cmd/admin/v1/network.go b/cmd/admin/v1/network.go new file mode 100644 index 0000000..aa0dcb8 --- /dev/null +++ b/cmd/admin/v1/network.go @@ -0,0 +1,274 @@ +package v1 + +import ( + "connectrpc.com/connect" + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/cli/cmd/sorters" + "github.com/metal-stack/cli/pkg/common" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/metal-stack/metal-lib/pkg/pointer" + "github.com/metal-stack/metal-lib/pkg/tag" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type networkCmd struct { + c *config.Config +} + +func newNetworkCmd(c *config.Config) *cobra.Command { + w := &networkCmd{ + c: c, + } + + // TODO: move to common? + listFlags := func(cmd *cobra.Command) { + cmd.Flags().String("id", "", "ID to filter [optional]") + cmd.Flags().String("name", "", "name to filter [optional]") + cmd.Flags().String("description", "", "description to filter [optional]") + cmd.Flags().String("partition", "", "partition to filter [optional]") + cmd.Flags().String("project", "", "project to filter [optional]") + cmd.Flags().StringSlice("prefixes", []string{}, "prefixes to filter") + cmd.Flags().StringSlice("destination-prefixes", []string{}, "destination prefixes to filter") + cmd.Flags().String("addressfamily", "", "addressfamily to filter, either ipv4 or ipv6 [optional]") + cmd.Flags().Uint32("vrf", 0, "vrf to filter [optional]") + cmd.Flags().StringSlice("labels", nil, "labels to filter [optional]") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.PartitionListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.IpAddressFamilyCompletion)) + } + + cmdsConfig := &genericcli.CmdsConfig[*adminv2.NetworkServiceCreateRequest, *adminv2.NetworkServiceUpdateRequest, *apiv2.Network]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), + Singular: "network", + Plural: "networks", + Description: "networks can be attached to a machine or firewall such that they can communicate with each other.", + CreateRequestFromCLI: w.createRequestFromCLI, + UpdateRequestFromCLI: w.updateRequestFromCLI, + Sorter: sorters.NetworkSorter(), + ValidArgsFn: c.Completion.NetworkListCompletion, + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + CreateCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("name", "", "name of the network to create. [required]") + cmd.Flags().String("partition", "", "partition where this network should exist. [required]") + cmd.Flags().String("project", "", "partition where this network should exist (alternative to parent-network-id). [optional]") + cmd.Flags().String("parent-network-id", "", "the parent of the network (alternative to partition). [optional]") + cmd.Flags().String("description", "", "description of the network to create. [optional]") + cmd.Flags().StringSlice("labels", nil, "labels for this network. [optional]") + cmd.Flags().String("addressfamily", "", "addressfamily of the network to acquire, if not specified the network inherits the address families from the parent [optional]") + cmd.Flags().Uint32("ipv4-prefix-length", 0, "ipv4 prefix bit length of the network to create, defaults to default child prefix length of the parent network. [optional]") + cmd.Flags().Uint32("ipv6-prefix-length", 0, "ipv6 prefix bit length of the network to create, defaults to default child prefix length of the parent network. [optional]") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.PartitionListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.IpAddressFamilyCompletion)) + }, + ListCmdMutateFn: func(cmd *cobra.Command) { + listFlags(cmd) + cmd.Flags().String("parent-network-id", "", "parent network to filter [optional]") + }, + UpdateCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("name", "", "the name of the network [optional]") + cmd.Flags().String("description", "", "the description of the network [optional]") + cmd.Flags().StringSlice("labels", nil, "the labels of the network, must be in the form of key=value, use it like: --labels \"key1=value1,key2=value2\". [optional]") + cmd.Flags().String("project", "", "project to filter [optional]") + }, + } + + return genericcli.NewCmds(cmdsConfig) +} + +func (c *networkCmd) Get(id string) (*apiv2.Network, error) { + panic("unimplemented") +} + +func (c *networkCmd) List() ([]*apiv2.Network, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().Network().List(ctx, connect.NewRequest(&adminv2.NetworkServiceListRequest{ + Query: &apiv2.NetworkQuery{ + Id: pointer.PointerOrNil(viper.GetString("id")), + Name: pointer.PointerOrNil(viper.GetString("name")), + Description: pointer.PointerOrNil(viper.GetString("description")), + Partition: pointer.PointerOrNil(viper.GetString("partition")), + Project: pointer.PointerOrNil(viper.GetString("project")), + Prefixes: viper.GetStringSlice("prefixes"), + DestinationPrefixes: viper.GetStringSlice("destination-prefixes"), + Vrf: pointer.PointerOrNil(viper.GetUint32("vrf")), + ParentNetworkId: pointer.PointerOrNil(viper.GetString("parent-network-id")), + AddressFamily: common.AddressFamilyToType(viper.GetString("addressfamily")), + Labels: &apiv2.Labels{ + Labels: tag.NewTagMap(viper.GetStringSlice("labels")), + }, + // Type: &0, TODO + // NatType: &0, + }, + })) + + if err != nil { + return nil, err + } + + return resp.Msg.Networks, nil +} + +func (c *networkCmd) Delete(id string) (*apiv2.Network, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().Network().Delete(ctx, connect.NewRequest(&adminv2.NetworkServiceDeleteRequest{ + Id: id, + })) + if err != nil { + return nil, err + } + + return resp.Msg.Network, nil +} + +func (c *networkCmd) Create(rq *adminv2.NetworkServiceCreateRequest) (*apiv2.Network, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().Network().Create(ctx, connect.NewRequest(rq)) + if err != nil { + if s, ok := status.FromError(err); ok && s.Code() == codes.AlreadyExists { + return nil, genericcli.AlreadyExistsError() + } + return nil, err + } + + return resp.Msg.Network, nil +} + +func (c *networkCmd) Update(rq *adminv2.NetworkServiceUpdateRequest) (*apiv2.Network, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().Network().Update(ctx, connect.NewRequest(rq)) + if err != nil { + return nil, err + } + + return resp.Msg.Network, nil +} + +func (c *networkCmd) Convert(r *apiv2.Network) (string, *adminv2.NetworkServiceCreateRequest, *adminv2.NetworkServiceUpdateRequest, error) { + return r.Id, networkResponseToCreate(r), networkResponseToUpdate(r), nil +} + +func networkResponseToCreate(r *apiv2.Network) *adminv2.NetworkServiceCreateRequest { + meta := pointer.SafeDeref(r.Meta) + + return &adminv2.NetworkServiceCreateRequest{ + Project: r.Project, + Name: r.Name, + Description: r.Description, + Partition: r.Partition, + Labels: &apiv2.Labels{ + Labels: pointer.SafeDeref(meta.Labels).Labels, + }, + ParentNetworkId: r.ParentNetworkId, + // TODO: allow defining length and addressfamilies somehow? + } +} + +func networkResponseToUpdate(r *apiv2.Network) *adminv2.NetworkServiceUpdateRequest { + meta := pointer.SafeDeref(r.Meta) + + return &adminv2.NetworkServiceUpdateRequest{ + Id: r.Id, + Name: r.Name, + Description: r.Description, + Labels: &apiv2.UpdateLabels{ + Update: meta.Labels, // TODO: this only ensures that the labels are present but it does not cleanup old one's, which would require fetching the current state and calculating the diff + }, + Prefixes: []string{}, + DestinationPrefixes: []string{}, + DefaultChildPrefixLength: &apiv2.ChildPrefixLength{}, + MinChildPrefixLength: &apiv2.ChildPrefixLength{}, + // NatType: &0, + AdditionalAnnouncableCidrs: []string{}, + Force: false, + } +} + +func (c *networkCmd) createRequestFromCLI() (*adminv2.NetworkServiceCreateRequest, error) { + labels, err := genericcli.LabelsToMap(viper.GetStringSlice("labels")) + if err != nil { + return nil, err + } + + // var ( + // cpl = &adminv2.ChildPrefixLength{} + // ) + // if viper.IsSet("ipv4-prefix-length") { + // cpl.Ipv4 = pointer.Pointer(viper.GetUint32("ipv4-prefix-length")) + // } + // if viper.IsSet("ipv6-prefix-length") { + // cpl.Ipv6 = pointer.Pointer(viper.GetUint32("ipv6-prefix-length")) + // } + + return &adminv2.NetworkServiceCreateRequest{ + Description: pointer.PointerOrNil(viper.GetString("description")), + Name: pointer.PointerOrNil(viper.GetString("name")), + // Project: c.c.GetProject(), + Partition: pointer.PointerOrNil(viper.GetString("partition")), + Labels: &apiv2.Labels{ + Labels: labels, + }, + ParentNetworkId: pointer.PointerOrNil(viper.GetString("parent-network-id")), + AddressFamily: common.AddressFamilyToType(viper.GetString("addressfamily")), + Id: new(string), + Type: 0, + Prefixes: []string{}, + DestinationPrefixes: []string{}, + DefaultChildPrefixLength: &apiv2.ChildPrefixLength{}, + MinChildPrefixLength: &apiv2.ChildPrefixLength{}, + // NatType: &0, + Vrf: new(uint32), + AdditionalAnnouncableCidrs: []string{}, + }, nil +} + +func (c *networkCmd) updateRequestFromCLI(args []string) (*adminv2.NetworkServiceUpdateRequest, error) { + id, err := genericcli.GetExactlyOneArg(args) + if err != nil { + return nil, err + } + + var labels *apiv2.UpdateLabels + if viper.IsSet("labels") { + lbls, err := genericcli.LabelsToMap(viper.GetStringSlice("labels")) + if err != nil { + return nil, err + } + + labels = &apiv2.UpdateLabels{ + Update: &apiv2.Labels{ + Labels: lbls, + }, + } + } + + var ( + ur = &adminv2.NetworkServiceUpdateRequest{ + Id: id, + // Project: c.c.GetProject(), + Description: pointer.PointerOrNil(viper.GetString("description")), + Name: pointer.PointerOrNil(viper.GetString("name")), + Labels: labels, + } + ) + + return ur, nil +} diff --git a/cmd/api/v1/ip.go b/cmd/api/v1/ip.go index 82e7650..31bc919 100644 --- a/cmd/api/v1/ip.go +++ b/cmd/api/v1/ip.go @@ -7,6 +7,7 @@ import ( apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" "github.com/metal-stack/cli/cmd/config" "github.com/metal-stack/cli/cmd/sorters" + "github.com/metal-stack/cli/pkg/common" "github.com/metal-stack/cli/pkg/helpers" "github.com/metal-stack/metal-lib/pkg/genericcli" "github.com/metal-stack/metal-lib/pkg/genericcli/printers" @@ -75,8 +76,8 @@ func newIPCmd(c *config.Config) *cobra.Command { Description: pointer.Pointer(viper.GetString("description")), Network: viper.GetString("network"), // Labels: viper.GetStringSlice("tags"), // FIXME implement - Type: pointer.Pointer(ipStaticToType(viper.GetBool("static"))), - AddressFamily: addressFamilyToType(viper.GetString("addressfamily")), + Type: pointer.Pointer(common.IpStaticToType(viper.GetBool("static"))), + AddressFamily: common.AddressFamilyToType(viper.GetString("addressfamily")), }, nil }, UpdateRequestFromCLI: w.updateFromCLI, @@ -104,7 +105,7 @@ func (c *ip) updateFromCLI(args []string) (*apiv2.IPServiceUpdateRequest, error) ipToUpdate.Description = viper.GetString("description") } if viper.IsSet("static") { - ipToUpdate.Type = ipStaticToType(viper.GetBool("static")) + ipToUpdate.Type = common.IpStaticToType(viper.GetBool("static")) } // if viper.IsSet("tags") { // if ipToUpdate.Meta == nil { @@ -227,23 +228,3 @@ func IpResponseToUpdate(r *apiv2.IP) *apiv2.IPServiceUpdateRequest { }, } } - -func ipStaticToType(b bool) apiv2.IPType { - if b { - return apiv2.IPType_IP_TYPE_STATIC - } - return apiv2.IPType_IP_TYPE_EPHEMERAL -} - -func addressFamilyToType(af string) *apiv2.IPAddressFamily { - switch af { - case "": - return nil - case "ipv4", "IPv4": - return apiv2.IPAddressFamily_IP_ADDRESS_FAMILY_V4.Enum() - case "ipv6", "IPv6": - return apiv2.IPAddressFamily_IP_ADDRESS_FAMILY_V6.Enum() - default: - return apiv2.IPAddressFamily_IP_ADDRESS_FAMILY_UNSPECIFIED.Enum() - } -} diff --git a/cmd/api/v1/network.go b/cmd/api/v1/network.go index f7ad58b..f02753a 100644 --- a/cmd/api/v1/network.go +++ b/cmd/api/v1/network.go @@ -5,6 +5,7 @@ import ( apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" "github.com/metal-stack/cli/cmd/config" "github.com/metal-stack/cli/cmd/sorters" + "github.com/metal-stack/cli/pkg/common" "github.com/metal-stack/metal-lib/pkg/genericcli" "github.com/metal-stack/metal-lib/pkg/genericcli/printers" "github.com/metal-stack/metal-lib/pkg/pointer" @@ -24,6 +25,23 @@ func newNetworkCmd(c *config.Config) *cobra.Command { c: c, } + listFlags := func(cmd *cobra.Command) { + cmd.Flags().String("id", "", "ID to filter [optional]") + cmd.Flags().String("name", "", "name to filter [optional]") + cmd.Flags().String("description", "", "description to filter [optional]") + cmd.Flags().String("partition", "", "partition to filter [optional]") + cmd.Flags().String("project", "", "project to filter [optional]") + cmd.Flags().StringSlice("prefixes", []string{}, "prefixes to filter") + cmd.Flags().StringSlice("destination-prefixes", []string{}, "destination prefixes to filter") + cmd.Flags().String("addressfamily", "", "addressfamily to filter, either ipv4 or ipv6 [optional]") + cmd.Flags().Uint32("vrf", 0, "vrf to filter [optional]") + cmd.Flags().StringSlice("labels", nil, "labels to filter [optional]") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.PartitionListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.IpAddressFamilyCompletion)) + } + cmdsConfig := &genericcli.CmdsConfig[*apiv2.NetworkServiceCreateRequest, *apiv2.NetworkServiceUpdateRequest, *apiv2.Network]{ BinaryName: config.BinaryName, GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), @@ -37,45 +55,42 @@ func newNetworkCmd(c *config.Config) *cobra.Command { DescribePrinter: func() printers.Printer { return c.DescribePrinter }, ListPrinter: func() printers.Printer { return c.ListPrinter }, CreateCmdMutateFn: func(cmd *cobra.Command) { - cmd.Flags().StringP("name", "n", "", "name of the network to create. [required]") - cmd.Flags().StringP("partition", "", "", "partition where this network should exist. [required]") + cmd.Flags().String("name", "", "name of the network to create. [required]") + cmd.Flags().String("partition", "", "partition where this network should exist. [required]") cmd.Flags().String("project", "", "partition where this network should exist (alternative to parent-network-id). [optional]") cmd.Flags().String("parent-network-id", "", "the parent of the network (alternative to partition). [optional]") - cmd.Flags().StringP("description", "d", "", "description of the network to create. [optional]") - cmd.Flags().StringSlice("labels", []string{}, "labels for this network. [optional]") - cmd.Flags().StringP("addressfamily", "", "", "addressfamily of the network to acquire, if not specified the network inherits the address families from the parent [optional]") + cmd.Flags().String("description", "", "description of the network to create. [optional]") + cmd.Flags().StringSlice("labels", nil, "labels for this network. [optional]") + cmd.Flags().String("addressfamily", "", "addressfamily of the network to acquire, if not specified the network inherits the address families from the parent [optional]") cmd.Flags().Uint32("ipv4-prefix-length", 0, "ipv4 prefix bit length of the network to create, defaults to default child prefix length of the parent network. [optional]") cmd.Flags().Uint32("ipv6-prefix-length", 0, "ipv6 prefix bit length of the network to create, defaults to default child prefix length of the parent network. [optional]") + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.PartitionListCompletion)) genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.IpAddressFamilyCompletion)) }, ListCmdMutateFn: func(cmd *cobra.Command) { - cmd.Flags().String("id", "", "ID to filter [optional]") - cmd.Flags().String("name", "", "name to filter [optional]") - cmd.Flags().String("description", "", "description to filter [optional]") - cmd.Flags().String("partition", "", "partition to filter [optional]") - cmd.Flags().String("project", "", "project to filter [optional]") + listFlags(cmd) cmd.Flags().String("parent-network-id", "", "parent network 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]") - cmd.Flags().Uint32("vrf", 0, "vrf to filter [optional]") - cmd.Flags().StringSlice("labels", nil, "labels to filter [optional]") - - genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.PartitionListCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.IpAddressFamilyCompletion)) }, UpdateCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().String("name", "", "the name of the network [optional]") cmd.Flags().String("description", "", "the description of the network [optional]") - cmd.Flags().StringSlice("labels", []string{}, "the labels of the network, must be in the form of key=value, use it like: --labels \"key1=value1,key2=value2\". [optional]") + cmd.Flags().StringSlice("labels", nil, "the labels of the network, must be in the form of key=value, use it like: --labels \"key1=value1,key2=value2\". [optional]") cmd.Flags().String("project", "", "project to filter [optional]") }, } - return genericcli.NewCmds(cmdsConfig) + listBaseNetworksCmd := &cobra.Command{ + Use: "list-base-networks", + Short: "lists base networks that can be used for network creation", + RunE: func(cmd *cobra.Command, _ []string) error { + return w.listBaseNetworks() + }, + ValidArgsFunction: c.Completion.TenantMemberListCompletion, + } + + return genericcli.NewCmds(cmdsConfig, listBaseNetworksCmd) } func (c *networkCmd) Get(id string) (*apiv2.Network, error) { @@ -109,7 +124,7 @@ func (c *networkCmd) List() ([]*apiv2.Network, error) { DestinationPrefixes: viper.GetStringSlice("destination-prefixes"), Vrf: pointer.PointerOrNil(viper.GetUint32("vrf")), ParentNetworkId: pointer.PointerOrNil(viper.GetString("parent-network-id")), - AddressFamily: addressFamilyToType(viper.GetString("addressfamily")), + AddressFamily: common.AddressFamilyToType(viper.GetString("addressfamily")), Labels: &apiv2.Labels{ Labels: tag.NewTagMap(viper.GetStringSlice("labels")), }, @@ -224,7 +239,7 @@ func (c *networkCmd) createRequestFromCLI() (*apiv2.NetworkServiceCreateRequest, }, ParentNetworkId: pointer.PointerOrNil(viper.GetString("parent-network-id")), Length: cpl, - AddressFamily: addressFamilyToType(viper.GetString("addressfamily")), + AddressFamily: common.AddressFamilyToType(viper.GetString("addressfamily")), }, nil } @@ -260,3 +275,32 @@ func (c *networkCmd) updateRequestFromCLI(args []string) (*apiv2.NetworkServiceU return ur, nil } + +func (c *networkCmd) listBaseNetworks() error { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Apiv2().Network().ListBaseNetworks(ctx, connect.NewRequest(&apiv2.NetworkServiceListBaseNetworksRequest{ + Query: &apiv2.NetworkQuery{ + Id: pointer.PointerOrNil(viper.GetString("id")), + Name: pointer.PointerOrNil(viper.GetString("name")), + Description: pointer.PointerOrNil(viper.GetString("description")), + Partition: pointer.PointerOrNil(viper.GetString("partition")), + Project: pointer.PointerOrNil(viper.GetString("project")), + Prefixes: viper.GetStringSlice("prefixes"), + DestinationPrefixes: viper.GetStringSlice("destination-prefixes"), + Vrf: pointer.PointerOrNil(viper.GetUint32("vrf")), + AddressFamily: common.AddressFamilyToType(viper.GetString("addressfamily")), + Labels: &apiv2.Labels{ + Labels: tag.NewTagMap(viper.GetStringSlice("labels")), + }, + // Type: &0, TODO + }, + })) + + if err != nil { + return err + } + + return c.c.ListPrinter.Print(resp.Msg.Networks) +} diff --git a/pkg/common/ip.go b/pkg/common/ip.go new file mode 100644 index 0000000..4fea5cb --- /dev/null +++ b/pkg/common/ip.go @@ -0,0 +1,12 @@ +package common + +import ( + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" +) + +func IpStaticToType(b bool) apiv2.IPType { + if b { + return apiv2.IPType_IP_TYPE_STATIC + } + return apiv2.IPType_IP_TYPE_EPHEMERAL +} diff --git a/pkg/common/network.go b/pkg/common/network.go new file mode 100644 index 0000000..3d2c022 --- /dev/null +++ b/pkg/common/network.go @@ -0,0 +1,18 @@ +package common + +import ( + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" +) + +func AddressFamilyToType(af string) *apiv2.IPAddressFamily { + switch af { + case "": + return nil + case "ipv4", "IPv4": + return apiv2.IPAddressFamily_IP_ADDRESS_FAMILY_V4.Enum() + case "ipv6", "IPv6": + return apiv2.IPAddressFamily_IP_ADDRESS_FAMILY_V6.Enum() + default: + return apiv2.IPAddressFamily_IP_ADDRESS_FAMILY_UNSPECIFIED.Enum() + } +} From f0a7b78f6d4c13036e56ebe386e8f648f626f08d Mon Sep 17 00:00:00 2001 From: Gerrit Date: Mon, 19 May 2025 15:58:42 +0200 Subject: [PATCH 05/11] Update. --- cmd/admin/v1/commands.go | 3 +- cmd/admin/v1/network.go | 159 ++++++++++++++---- cmd/api/v1/network.go | 18 +- cmd/completion/network.go | 21 +++ cmd/tableprinters/network.go | 11 +- docs/metalctlv2_network.md | 1 + docs/metalctlv2_network_create.md | 4 +- docs/metalctlv2_network_list-base-networks.md | 42 +++++ docs/metalctlv2_network_list.md | 5 +- go.mod | 2 +- go.sum | 4 +- 11 files changed, 228 insertions(+), 42 deletions(-) create mode 100644 docs/metalctlv2_network_list-base-networks.md diff --git a/cmd/admin/v1/commands.go b/cmd/admin/v1/commands.go index ddfef8c..80afb07 100644 --- a/cmd/admin/v1/commands.go +++ b/cmd/admin/v1/commands.go @@ -14,8 +14,9 @@ func AddCmds(cmd *cobra.Command, c *config.Config) { Hidden: true, } - adminCmd.AddCommand(newTokenCmd(c)) adminCmd.AddCommand(newImageCmd(c)) + adminCmd.AddCommand(newNetworkCmd(c)) + adminCmd.AddCommand(newTokenCmd(c)) cmd.AddCommand(adminCmd) } diff --git a/cmd/admin/v1/network.go b/cmd/admin/v1/network.go index aa0dcb8..72bbd9c 100644 --- a/cmd/admin/v1/network.go +++ b/cmd/admin/v1/network.go @@ -2,6 +2,7 @@ package v1 import ( "connectrpc.com/connect" + "github.com/metal-stack/api/go/enum" adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" "github.com/metal-stack/cli/cmd/config" @@ -38,10 +39,12 @@ func newNetworkCmd(c *config.Config) *cobra.Command { cmd.Flags().String("addressfamily", "", "addressfamily to filter, either ipv4 or ipv6 [optional]") cmd.Flags().Uint32("vrf", 0, "vrf to filter [optional]") cmd.Flags().StringSlice("labels", nil, "labels to filter [optional]") + cmd.Flags().StringP("type", "t", "", "type of the network. [optional]") genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.PartitionListCompletion)) genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.IpAddressFamilyCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("type", c.Completion.NetworkTypeCompletion)) } cmdsConfig := &genericcli.CmdsConfig[*adminv2.NetworkServiceCreateRequest, *adminv2.NetworkServiceUpdateRequest, *apiv2.Network]{ @@ -57,7 +60,10 @@ func newNetworkCmd(c *config.Config) *cobra.Command { DescribePrinter: func() printers.Printer { return c.DescribePrinter }, ListPrinter: func() printers.Printer { return c.ListPrinter }, CreateCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("id", "", "id of the network to create, defaults to a random uuid if not provided. [optional]") cmd.Flags().String("name", "", "name of the network to create. [required]") + cmd.Flags().StringP("type", "t", "", "type of the network. [required]") + cmd.Flags().String("nat-type", "", "nat-type of the network. [required]") cmd.Flags().String("partition", "", "partition where this network should exist. [required]") cmd.Flags().String("project", "", "partition where this network should exist (alternative to parent-network-id). [optional]") cmd.Flags().String("parent-network-id", "", "the parent of the network (alternative to partition). [optional]") @@ -66,10 +72,20 @@ func newNetworkCmd(c *config.Config) *cobra.Command { cmd.Flags().String("addressfamily", "", "addressfamily of the network to acquire, if not specified the network inherits the address families from the parent [optional]") cmd.Flags().Uint32("ipv4-prefix-length", 0, "ipv4 prefix bit length of the network to create, defaults to default child prefix length of the parent network. [optional]") cmd.Flags().Uint32("ipv6-prefix-length", 0, "ipv6 prefix bit length of the network to create, defaults to default child prefix length of the parent network. [optional]") + cmd.Flags().Uint32("default-ipv4-prefix-length", 0, "default ipv4 prefix bit length of the network to create. [optional]") + cmd.Flags().Uint32("default-ipv6-prefix-length", 0, "default ipv6 prefix bit length of the network to create. [optional]") + cmd.Flags().Uint32("min-ipv4-prefix-length", 0, "min ipv4 prefix bit length of the network to create. [optional]") + cmd.Flags().Uint32("min-ipv6-prefix-length", 0, "min ipv6 prefix bit length of the network to create. [optional]") + cmd.Flags().StringSlice("prefixes", nil, "prefixes for this network. [optional]") + cmd.Flags().StringSlice("destination-prefixes", nil, "destination-prefixes for this network. [optional]") + cmd.Flags().StringSlice("additional-announcable-cidrs", nil, "additional-announcable-cidrs for this network. [optional]") + cmd.Flags().Uint32("vrf", 0, "the vrf of the network to create. [optional]") genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.PartitionListCompletion)) genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.IpAddressFamilyCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("type", c.Completion.NetworkTypeCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("nat-type", c.Completion.NetworkNatTypeCompletion)) }, ListCmdMutateFn: func(cmd *cobra.Command) { listFlags(cmd) @@ -87,13 +103,33 @@ func newNetworkCmd(c *config.Config) *cobra.Command { } func (c *networkCmd) Get(id string) (*apiv2.Network, error) { - panic("unimplemented") + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().Network().Get(ctx, connect.NewRequest(&adminv2.NetworkServiceGetRequest{ + Id: id, + })) + + if err != nil { + return nil, err + } + + return resp.Msg.Network, nil } func (c *networkCmd) List() ([]*apiv2.Network, error) { ctx, cancel := c.c.NewRequestContext() defer cancel() + var nwType *apiv2.NetworkType + if viper.IsSet("type") { + nt, err := enum.GetEnum[apiv2.NetworkType](viper.GetString("type")) + if err != nil { + return nil, err + } + nwType = &nt + } + resp, err := c.c.Client.Adminv2().Network().List(ctx, connect.NewRequest(&adminv2.NetworkServiceListRequest{ Query: &apiv2.NetworkQuery{ Id: pointer.PointerOrNil(viper.GetString("id")), @@ -109,8 +145,8 @@ func (c *networkCmd) List() ([]*apiv2.Network, error) { Labels: &apiv2.Labels{ Labels: tag.NewTagMap(viper.GetStringSlice("labels")), }, - // Type: &0, TODO - // NatType: &0, + Type: nwType, + // NatType: (*apiv2.NATType)(nwType), }, })) @@ -208,35 +244,68 @@ func (c *networkCmd) createRequestFromCLI() (*adminv2.NetworkServiceCreateReques return nil, err } - // var ( - // cpl = &adminv2.ChildPrefixLength{} - // ) - // if viper.IsSet("ipv4-prefix-length") { - // cpl.Ipv4 = pointer.Pointer(viper.GetUint32("ipv4-prefix-length")) - // } - // if viper.IsSet("ipv6-prefix-length") { - // cpl.Ipv6 = pointer.Pointer(viper.GetUint32("ipv6-prefix-length")) - // } + var ( + natType = apiv2.NATType_NAT_TYPE_NONE + defaultCPL = &apiv2.ChildPrefixLength{} + minCPL = &apiv2.ChildPrefixLength{} + length = &apiv2.ChildPrefixLength{} + ) + if viper.IsSet("default-ipv4-prefix-length") { + defaultCPL.Ipv4 = pointer.Pointer(viper.GetUint32("default-ipv4-prefix-length")) + } + if viper.IsSet("default-ipv6-prefix-length") { + defaultCPL.Ipv6 = pointer.Pointer(viper.GetUint32("default-ipv6-prefix-length")) + } + if viper.IsSet("min-ipv4-prefix-length") { + minCPL.Ipv4 = pointer.Pointer(viper.GetUint32("min-ipv4-prefix-length")) + } + if viper.IsSet("min-ipv6-prefix-length") { + minCPL.Ipv6 = pointer.Pointer(viper.GetUint32("min-ipv6-prefix-length")) + } + if viper.IsSet("ipv4-prefix-length") { + length.Ipv4 = pointer.Pointer(viper.GetUint32("ipv4-prefix-length")) + } + if viper.IsSet("ipv6-prefix-length") { + length.Ipv6 = pointer.Pointer(viper.GetUint32("ipv6-prefix-length")) + } + + nwType, err := enum.GetEnum[apiv2.NetworkType](viper.GetString("type")) + if err != nil { + return nil, err + } + + if viper.IsSet("nat-type") { + natType, err = enum.GetEnum[apiv2.NATType](viper.GetString("nat-type")) + if err != nil { + return nil, err + } + } + + var vrf *uint32 + if viper.IsSet("vrf") { + vrf = pointer.Pointer(viper.GetUint32("vrf")) + } return &adminv2.NetworkServiceCreateRequest{ Description: pointer.PointerOrNil(viper.GetString("description")), Name: pointer.PointerOrNil(viper.GetString("name")), - // Project: c.c.GetProject(), - Partition: pointer.PointerOrNil(viper.GetString("partition")), + Project: pointer.PointerOrNil(viper.GetString("project")), + Partition: pointer.PointerOrNil(viper.GetString("partition")), Labels: &apiv2.Labels{ Labels: labels, }, - ParentNetworkId: pointer.PointerOrNil(viper.GetString("parent-network-id")), - AddressFamily: common.AddressFamilyToType(viper.GetString("addressfamily")), - Id: new(string), - Type: 0, - Prefixes: []string{}, - DestinationPrefixes: []string{}, - DefaultChildPrefixLength: &apiv2.ChildPrefixLength{}, - MinChildPrefixLength: &apiv2.ChildPrefixLength{}, - // NatType: &0, - Vrf: new(uint32), - AdditionalAnnouncableCidrs: []string{}, + ParentNetworkId: pointer.PointerOrNil(viper.GetString("parent-network-id")), + AddressFamily: common.AddressFamilyToType(viper.GetString("addressfamily")), + Id: pointer.PointerOrNil(viper.GetString("id")), + Type: nwType, + Prefixes: viper.GetStringSlice("prefixes"), + DestinationPrefixes: viper.GetStringSlice("destination-prefixes"), + DefaultChildPrefixLength: defaultCPL, + MinChildPrefixLength: minCPL, + NatType: &natType, + Vrf: vrf, + AdditionalAnnouncableCidrs: viper.GetStringSlice("additional-announcable-cidrs"), + Length: length, }, nil } @@ -260,13 +329,43 @@ func (c *networkCmd) updateRequestFromCLI(args []string) (*adminv2.NetworkServic } } + var ( + natType = apiv2.NATType_NAT_TYPE_NONE + defaultCPL = &apiv2.ChildPrefixLength{} + minCPL = &apiv2.ChildPrefixLength{} + ) + if viper.IsSet("default-ipv4-prefix-length") { + defaultCPL.Ipv4 = pointer.Pointer(viper.GetUint32("default-ipv4-prefix-length")) + } + if viper.IsSet("default-ipv6-prefix-length") { + defaultCPL.Ipv6 = pointer.Pointer(viper.GetUint32("default-ipv6-prefix-length")) + } + if viper.IsSet("min-ipv4-prefix-length") { + minCPL.Ipv4 = pointer.Pointer(viper.GetUint32("min-ipv4-prefix-length")) + } + if viper.IsSet("min-ipv6-prefix-length") { + minCPL.Ipv6 = pointer.Pointer(viper.GetUint32("min-ipv6-prefix-length")) + } + if viper.IsSet("nat-type") { + natType, err = enum.GetEnum[apiv2.NATType](viper.GetString("nat-type")) + if err != nil { + return nil, err + } + } + var ( ur = &adminv2.NetworkServiceUpdateRequest{ - Id: id, - // Project: c.c.GetProject(), - Description: pointer.PointerOrNil(viper.GetString("description")), - Name: pointer.PointerOrNil(viper.GetString("name")), - Labels: labels, + Id: id, + Description: pointer.PointerOrNil(viper.GetString("description")), + Name: pointer.PointerOrNil(viper.GetString("name")), + Labels: labels, + Prefixes: viper.GetStringSlice("prefixes"), + DestinationPrefixes: viper.GetStringSlice("destination-prefixes"), + DefaultChildPrefixLength: defaultCPL, + MinChildPrefixLength: minCPL, + NatType: &natType, + AdditionalAnnouncableCidrs: viper.GetStringSlice("additional-announcable-cidrs"), + Force: viper.GetBool("force"), } ) diff --git a/cmd/api/v1/network.go b/cmd/api/v1/network.go index f02753a..74709e0 100644 --- a/cmd/api/v1/network.go +++ b/cmd/api/v1/network.go @@ -2,6 +2,7 @@ package v1 import ( "connectrpc.com/connect" + "github.com/metal-stack/api/go/enum" apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" "github.com/metal-stack/cli/cmd/config" "github.com/metal-stack/cli/cmd/sorters" @@ -36,10 +37,12 @@ func newNetworkCmd(c *config.Config) *cobra.Command { cmd.Flags().String("addressfamily", "", "addressfamily to filter, either ipv4 or ipv6 [optional]") cmd.Flags().Uint32("vrf", 0, "vrf to filter [optional]") cmd.Flags().StringSlice("labels", nil, "labels to filter [optional]") + cmd.Flags().StringP("type", "t", "", "type of the network. [optional]") genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.PartitionListCompletion)) genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.IpAddressFamilyCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("type", c.Completion.NetworkTypeCompletion)) } cmdsConfig := &genericcli.CmdsConfig[*apiv2.NetworkServiceCreateRequest, *apiv2.NetworkServiceUpdateRequest, *apiv2.Network]{ @@ -89,6 +92,7 @@ func newNetworkCmd(c *config.Config) *cobra.Command { }, ValidArgsFunction: c.Completion.TenantMemberListCompletion, } + listFlags(listBaseNetworksCmd) return genericcli.NewCmds(cmdsConfig, listBaseNetworksCmd) } @@ -119,7 +123,7 @@ func (c *networkCmd) List() ([]*apiv2.Network, error) { Name: pointer.PointerOrNil(viper.GetString("name")), Description: pointer.PointerOrNil(viper.GetString("description")), Partition: pointer.PointerOrNil(viper.GetString("partition")), - Project: pointer.PointerOrNil(viper.GetString("project")), + Project: pointer.Pointer(c.c.GetProject()), Prefixes: viper.GetStringSlice("prefixes"), DestinationPrefixes: viper.GetStringSlice("destination-prefixes"), Vrf: pointer.PointerOrNil(viper.GetUint32("vrf")), @@ -280,7 +284,17 @@ func (c *networkCmd) listBaseNetworks() error { ctx, cancel := c.c.NewRequestContext() defer cancel() + var nwType *apiv2.NetworkType + if viper.IsSet("type") { + nt, err := enum.GetEnum[apiv2.NetworkType](viper.GetString("type")) + if err != nil { + return err + } + nwType = &nt + } + resp, err := c.c.Client.Apiv2().Network().ListBaseNetworks(ctx, connect.NewRequest(&apiv2.NetworkServiceListBaseNetworksRequest{ + Project: c.c.GetProject(), Query: &apiv2.NetworkQuery{ Id: pointer.PointerOrNil(viper.GetString("id")), Name: pointer.PointerOrNil(viper.GetString("name")), @@ -294,7 +308,7 @@ func (c *networkCmd) listBaseNetworks() error { Labels: &apiv2.Labels{ Labels: tag.NewTagMap(viper.GetStringSlice("labels")), }, - // Type: &0, TODO + Type: nwType, }, })) diff --git a/cmd/completion/network.go b/cmd/completion/network.go index 848bfd9..04b7ab5 100644 --- a/cmd/completion/network.go +++ b/cmd/completion/network.go @@ -2,6 +2,7 @@ package completion import ( "connectrpc.com/connect" + "github.com/metal-stack/api/go/enum" apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" "github.com/metal-stack/metal-lib/pkg/pointer" "github.com/spf13/cobra" @@ -22,3 +23,23 @@ func (c *Completion) NetworkListCompletion(cmd *cobra.Command, args []string, to return names, cobra.ShellCompDirectiveNoFileComp } + +func (c *Completion) NetworkTypeCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + var names []string + for _, val := range apiv2.NetworkType_value { + if e, err := enum.GetStringValue(apiv2.NetworkType(val)); err == nil { + names = append(names, *e) + } + } + return names, cobra.ShellCompDirectiveNoFileComp +} + +func (c *Completion) NetworkNatTypeCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + var names []string + for _, val := range apiv2.NATType_value { + if e, err := enum.GetStringValue(apiv2.NATType(val)); err == nil { + names = append(names, *e) + } + } + return names, cobra.ShellCompDirectiveNoFileComp +} diff --git a/cmd/tableprinters/network.go b/cmd/tableprinters/network.go index 7cfb9a4..d657b12 100644 --- a/cmd/tableprinters/network.go +++ b/cmd/tableprinters/network.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/fatih/color" + "github.com/metal-stack/api/go/enum" apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" "github.com/metal-stack/metal-lib/pkg/pointer" ) @@ -21,7 +22,7 @@ func (t *TablePrinter) NetworkTable(data []*apiv2.Network, wide bool) ([]string, rows [][]string ) - header := []string{"ID", "Name", "Project", "Partition", "Nat", "Prefixes", "IP Usage"} + header := []string{"ID", "Name", "Project", "Partition", "Nat", "Prefixes", "Prefix Usage", "IP Usage"} if wide { header = []string{"ID", "Description", "Name", "Project", "Partition", "Nat", "Prefixes", "Annotations"} } @@ -109,6 +110,12 @@ func addNetwork(prefix string, n *apiv2.Network, wide bool) []string { natType = pointer.SafeDeref(n.NatType).String() ) + if t, err := enum.GetStringValue(pointer.SafeDeref(n.NatType)); err == nil { + natType = *t + } else { + fmt.Println(err) + } + max := getMaxLineCount(description, name, project, partition, natType, prefixes, shortIPUsage) for range max - 1 { id += "\n│" @@ -126,7 +133,7 @@ func addNetwork(prefix string, n *apiv2.Network, wide bool) []string { if wide { return []string{id, description, name, project, partition, natType, prefixes, annotations} } else { - return []string{id, name, project, partition, natType, shortPrefixUsage, shortIPUsage} + return []string{id, name, project, partition, natType, prefixes, shortPrefixUsage, shortIPUsage} } } diff --git a/docs/metalctlv2_network.md b/docs/metalctlv2_network.md index cf08271..4581d1d 100644 --- a/docs/metalctlv2_network.md +++ b/docs/metalctlv2_network.md @@ -34,5 +34,6 @@ networks can be attached to a machine or firewall such that they can communicate * [metalctlv2 network describe](metalctlv2_network_describe.md) - describes the network * [metalctlv2 network edit](metalctlv2_network_edit.md) - edit the network through an editor and update * [metalctlv2 network list](metalctlv2_network_list.md) - list all networks +* [metalctlv2 network list-base-networks](metalctlv2_network_list-base-networks.md) - lists base networks that can be used for network creation * [metalctlv2 network update](metalctlv2_network_update.md) - updates the network diff --git a/docs/metalctlv2_network_create.md b/docs/metalctlv2_network_create.md index 08b4cc6..d356087 100644 --- a/docs/metalctlv2_network_create.md +++ b/docs/metalctlv2_network_create.md @@ -11,7 +11,7 @@ metalctlv2 network create [flags] ``` --addressfamily string addressfamily of the network to acquire, if not specified the network inherits the address families from the parent [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 network to create. [optional] + --description string description of the network to create. [optional] -f, --file string filename of the create or update request in yaml format, or - for stdin. Example: @@ -28,7 +28,7 @@ metalctlv2 network create [flags] --ipv4-prefix-length uint32 ipv4 prefix bit length of the network to create, defaults to default child prefix length of the parent network. [optional] --ipv6-prefix-length uint32 ipv6 prefix bit length of the network to create, defaults to default child prefix length of the parent network. [optional] --labels strings labels for this network. [optional] - -n, --name string name of the network to create. [required] + --name string name of the network to create. [required] --parent-network-id string the parent of the network (alternative to partition). [optional] --partition string partition where this network should exist. [required] --project string partition where this network should exist (alternative to parent-network-id). [optional] diff --git a/docs/metalctlv2_network_list-base-networks.md b/docs/metalctlv2_network_list-base-networks.md new file mode 100644 index 0000000..6626638 --- /dev/null +++ b/docs/metalctlv2_network_list-base-networks.md @@ -0,0 +1,42 @@ +## metalctlv2 network list-base-networks + +lists base networks that can be used for network creation + +``` +metalctlv2 network list-base-networks [flags] +``` + +### Options + +``` + --addressfamily string addressfamily to filter, either ipv4 or ipv6 [optional] + --description string description to filter [optional] + --destination-prefixes strings destination prefixes to filter + -h, --help help for list-base-networks + --id string ID to filter [optional] + --labels strings labels to filter [optional] + --name string name to filter [optional] + --partition string partition to filter [optional] + --prefixes strings prefixes to filter + --project string project to filter [optional] + -t, --type string type of the network. [optional] + --vrf uint32 vrf to filter [optional] +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 network](metalctlv2_network.md) - manage network entities + diff --git a/docs/metalctlv2_network_list.md b/docs/metalctlv2_network_list.md index 9ff0a92..7af715b 100644 --- a/docs/metalctlv2_network_list.md +++ b/docs/metalctlv2_network_list.md @@ -11,16 +11,17 @@ metalctlv2 network list [flags] ``` --addressfamily string addressfamily to filter, either ipv4 or ipv6 [optional] --description string description to filter [optional] - --destination-prefixes strings destination prefixes to filter, use it like: --destination-prefixes prefix1,prefix2. + --destination-prefixes strings destination prefixes to filter -h, --help help for list --id string ID to filter [optional] --labels strings labels to filter [optional] --name string name to filter [optional] --parent-network-id string parent network to filter [optional] --partition string partition to filter [optional] - --prefixes strings prefixes to filter, use it like: --prefixes prefix1,prefix2. + --prefixes strings prefixes to filter --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: description|id|name|partition|project + -t, --type string type of the network. [optional] --vrf uint32 vrf to filter [optional] ``` diff --git a/go.mod b/go.mod index df06a41..2cca597 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/dustin/go-humanize v1.0.1 github.com/fatih/color v1.18.0 github.com/google/go-cmp v0.7.0 - github.com/metal-stack/api v0.0.0-20250509114457-f9a7a417f35b + github.com/metal-stack/api v0.0.0-20250519133343-5ef4e0524630 github.com/metal-stack/metal-lib v0.22.1 github.com/metal-stack/v v1.0.3 github.com/olekukonko/tablewriter v0.0.5 diff --git a/go.sum b/go.sum index 6cc1024..bc04045 100644 --- a/go.sum +++ b/go.sum @@ -55,8 +55,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/metal-stack/api v0.0.0-20250509114457-f9a7a417f35b h1:F+le8kPDymW9n1nff9jTzGcmCKzVwxZjUsLgCr1rXvs= -github.com/metal-stack/api v0.0.0-20250509114457-f9a7a417f35b/go.mod h1:GBhtI/TaPjmABsx3wUxIlMTXNpm7MB4dmm5hh3SS03k= +github.com/metal-stack/api v0.0.0-20250519133343-5ef4e0524630 h1:YCRavb8B1MjstXdWMFBnfml2EBqrZTek6epU/uLe/pQ= +github.com/metal-stack/api v0.0.0-20250519133343-5ef4e0524630/go.mod h1:gLRg6Rxkt0FGJnTNmmhclgrdMKT3el6O85+KgqYYCqE= github.com/metal-stack/metal-lib v0.22.1 h1:kAXOHZOSqBA0NQgYmtONxfY5khyQo8ofEL30QBE5DEY= github.com/metal-stack/metal-lib v0.22.1/go.mod h1:QiFb7TpSrvnLAHOlxLiUm1aG+1t5nPHsoDT/bw+F5r8= github.com/metal-stack/v v1.0.3 h1:Sh2oBlnxrCUD+mVpzfC8HiqL045YWkxs0gpTvkjppqs= From f47065e93c8657195a8c7759199b1a5e260ea203 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Tue, 20 May 2025 14:58:31 +0200 Subject: [PATCH 06/11] Namespaced ip get --- cmd/admin/v1/network.go | 46 ++++++++++++++++++++++++++-------- cmd/api/v1/ip.go | 13 ++++++++-- cmd/tableprinters/ip.go | 10 +++++--- docs/metalctlv2_ip_describe.md | 5 ++-- go.mod | 12 ++++----- go.sum | 20 +++++++-------- 6 files changed, 71 insertions(+), 35 deletions(-) diff --git a/cmd/admin/v1/network.go b/cmd/admin/v1/network.go index 72bbd9c..93db25e 100644 --- a/cmd/admin/v1/network.go +++ b/cmd/admin/v1/network.go @@ -246,26 +246,41 @@ func (c *networkCmd) createRequestFromCLI() (*adminv2.NetworkServiceCreateReques var ( natType = apiv2.NATType_NAT_TYPE_NONE - defaultCPL = &apiv2.ChildPrefixLength{} - minCPL = &apiv2.ChildPrefixLength{} - length = &apiv2.ChildPrefixLength{} + defaultCPL *apiv2.ChildPrefixLength + minCPL *apiv2.ChildPrefixLength + length *apiv2.ChildPrefixLength ) if viper.IsSet("default-ipv4-prefix-length") { - defaultCPL.Ipv4 = pointer.Pointer(viper.GetUint32("default-ipv4-prefix-length")) + defaultCPL = &apiv2.ChildPrefixLength{ + Ipv4: pointer.Pointer(viper.GetUint32("default-ipv4-prefix-length")), + } } if viper.IsSet("default-ipv6-prefix-length") { + if defaultCPL == nil { + defaultCPL = &apiv2.ChildPrefixLength{} + } defaultCPL.Ipv6 = pointer.Pointer(viper.GetUint32("default-ipv6-prefix-length")) } if viper.IsSet("min-ipv4-prefix-length") { - minCPL.Ipv4 = pointer.Pointer(viper.GetUint32("min-ipv4-prefix-length")) + minCPL = &apiv2.ChildPrefixLength{ + Ipv4: pointer.Pointer(viper.GetUint32("min-ipv4-prefix-length")), + } } if viper.IsSet("min-ipv6-prefix-length") { + if minCPL == nil { + minCPL = &apiv2.ChildPrefixLength{} + } minCPL.Ipv6 = pointer.Pointer(viper.GetUint32("min-ipv6-prefix-length")) } if viper.IsSet("ipv4-prefix-length") { - length.Ipv4 = pointer.Pointer(viper.GetUint32("ipv4-prefix-length")) + length = &apiv2.ChildPrefixLength{ + Ipv4: pointer.Pointer(viper.GetUint32("ipv4-prefix-length")), + } } if viper.IsSet("ipv6-prefix-length") { + if length == nil { + length = &apiv2.ChildPrefixLength{} + } length.Ipv6 = pointer.Pointer(viper.GetUint32("ipv6-prefix-length")) } @@ -331,21 +346,32 @@ func (c *networkCmd) updateRequestFromCLI(args []string) (*adminv2.NetworkServic var ( natType = apiv2.NATType_NAT_TYPE_NONE - defaultCPL = &apiv2.ChildPrefixLength{} - minCPL = &apiv2.ChildPrefixLength{} + defaultCPL *apiv2.ChildPrefixLength + minCPL *apiv2.ChildPrefixLength ) if viper.IsSet("default-ipv4-prefix-length") { - defaultCPL.Ipv4 = pointer.Pointer(viper.GetUint32("default-ipv4-prefix-length")) + defaultCPL = &apiv2.ChildPrefixLength{ + Ipv4: pointer.Pointer(viper.GetUint32("default-ipv4-prefix-length")), + } } if viper.IsSet("default-ipv6-prefix-length") { + if defaultCPL == nil { + defaultCPL = &apiv2.ChildPrefixLength{} + } defaultCPL.Ipv6 = pointer.Pointer(viper.GetUint32("default-ipv6-prefix-length")) } if viper.IsSet("min-ipv4-prefix-length") { - minCPL.Ipv4 = pointer.Pointer(viper.GetUint32("min-ipv4-prefix-length")) + minCPL = &apiv2.ChildPrefixLength{ + Ipv4: pointer.Pointer(viper.GetUint32("min-ipv4-prefix-length")), + } } if viper.IsSet("min-ipv6-prefix-length") { + if minCPL == nil { + minCPL = &apiv2.ChildPrefixLength{} + } minCPL.Ipv6 = pointer.Pointer(viper.GetUint32("min-ipv6-prefix-length")) } + if viper.IsSet("nat-type") { natType, err = enum.GetEnum[apiv2.NATType](viper.GetString("nat-type")) if err != nil { diff --git a/cmd/api/v1/ip.go b/cmd/api/v1/ip.go index 31bc919..4cf5cef 100644 --- a/cmd/api/v1/ip.go +++ b/cmd/api/v1/ip.go @@ -61,6 +61,7 @@ func newIPCmd(c *config.Config) *cobra.Command { }, DescribeCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().StringP("project", "p", "", "project of the ip") + cmd.Flags().StringP("namespace", "n", "", "namespace of the ip") genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) }, @@ -163,9 +164,17 @@ func (c *ip) Get(id string) (*apiv2.IP, error) { ctx, cancel := c.c.NewRequestContext() defer cancel() + var ( + namespace *string + ) + if viper.IsSet("namespace") { + namespace = pointer.Pointer(viper.GetString("namespace")) + } + resp, err := c.c.Client.Apiv2().IP().Get(ctx, connect.NewRequest(&apiv2.IPServiceGetRequest{ - Project: c.c.GetProject(), - Ip: id, + Project: c.c.GetProject(), + Ip: id, + Namespace: namespace, })) if err != nil { return nil, err diff --git a/cmd/tableprinters/ip.go b/cmd/tableprinters/ip.go index a625ccf..f9c01b6 100644 --- a/cmd/tableprinters/ip.go +++ b/cmd/tableprinters/ip.go @@ -5,17 +5,18 @@ import ( "strings" apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/metal-lib/pkg/pointer" "github.com/olekukonko/tablewriter" ) func (t *TablePrinter) IPTable(data []*apiv2.IP, wide bool) ([]string, [][]string, error) { var ( rows [][]string - header = []string{"IP", "Project", "ID", "Type", "Name", "Attached Service"} + header = []string{"IP", "NS", "Project", "ID", "Type", "Name", "Attached Service"} ) if wide { - header = []string{"IP", "Project", "ID", "Type", "Name", "Description", "Labels"} + header = []string{"IP", "NS", "Project", "ID", "Type", "Name", "Description", "Labels"} } for _, ip := range data { @@ -42,11 +43,12 @@ func (t *TablePrinter) IPTable(data []*apiv2.IP, wide bool) ([]string, [][]strin labels = append(labels, fmt.Sprintf("%s=%s", k, v)) } } + ns := pointer.SafeDeref(ip.Namespace) if wide { - rows = append(rows, []string{ip.Ip, ip.Project, ip.Uuid, t, ip.Name, ip.Description, strings.Join(labels, "\n")}) + rows = append(rows, []string{ip.Ip, ns, ip.Project, ip.Uuid, t, ip.Name, ip.Description, strings.Join(labels, "\n")}) } else { - rows = append(rows, []string{ip.Ip, ip.Project, ip.Uuid, t, ip.Name, attachedService}) + rows = append(rows, []string{ip.Ip, ns, ip.Project, ip.Uuid, t, ip.Name, attachedService}) } } diff --git a/docs/metalctlv2_ip_describe.md b/docs/metalctlv2_ip_describe.md index aae77b5..984fbc5 100644 --- a/docs/metalctlv2_ip_describe.md +++ b/docs/metalctlv2_ip_describe.md @@ -9,8 +9,9 @@ metalctlv2 ip describe [flags] ### Options ``` - -h, --help help for describe - -p, --project string project of the ip + -h, --help help for describe + -n, --namespace string namespace of the ip + -p, --project string project of the ip ``` ### Options inherited from parent commands diff --git a/go.mod b/go.mod index 2cca597..15c5397 100644 --- a/go.mod +++ b/go.mod @@ -2,16 +2,14 @@ module github.com/metal-stack/cli go 1.24.0 -toolchain go1.24.2 - require ( bou.ke/monkey v1.0.2 connectrpc.com/connect v1.18.1 github.com/dustin/go-humanize v1.0.1 github.com/fatih/color v1.18.0 github.com/google/go-cmp v0.7.0 - github.com/metal-stack/api v0.0.0-20250519133343-5ef4e0524630 - github.com/metal-stack/metal-lib v0.22.1 + github.com/metal-stack/api v0.0.0-20250520125519-0fa114b11f2d + github.com/metal-stack/metal-lib v0.23.0 github.com/metal-stack/v v1.0.3 github.com/olekukonko/tablewriter v0.0.5 github.com/spf13/afero v1.14.0 @@ -20,7 +18,7 @@ require ( github.com/spf13/viper v1.20.1 github.com/stretchr/testify v1.10.0 golang.org/x/net v0.40.0 - google.golang.org/grpc v1.71.0 + google.golang.org/grpc v1.72.1 google.golang.org/protobuf v1.36.6 sigs.k8s.io/yaml v1.4.0 ) @@ -58,9 +56,9 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.25.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apimachinery v0.33.0 // indirect + k8s.io/apimachinery v0.33.1 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect ) diff --git a/go.sum b/go.sum index bc04045..a950966 100644 --- a/go.sum +++ b/go.sum @@ -55,10 +55,10 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/metal-stack/api v0.0.0-20250519133343-5ef4e0524630 h1:YCRavb8B1MjstXdWMFBnfml2EBqrZTek6epU/uLe/pQ= -github.com/metal-stack/api v0.0.0-20250519133343-5ef4e0524630/go.mod h1:gLRg6Rxkt0FGJnTNmmhclgrdMKT3el6O85+KgqYYCqE= -github.com/metal-stack/metal-lib v0.22.1 h1:kAXOHZOSqBA0NQgYmtONxfY5khyQo8ofEL30QBE5DEY= -github.com/metal-stack/metal-lib v0.22.1/go.mod h1:QiFb7TpSrvnLAHOlxLiUm1aG+1t5nPHsoDT/bw+F5r8= +github.com/metal-stack/api v0.0.0-20250520125519-0fa114b11f2d h1:ECKVEPIPYV5mXol92A95vDqoAdqoh98sY7aEsMLVbfY= +github.com/metal-stack/api v0.0.0-20250520125519-0fa114b11f2d/go.mod h1:gLRg6Rxkt0FGJnTNmmhclgrdMKT3el6O85+KgqYYCqE= +github.com/metal-stack/metal-lib v0.23.0 h1:O0I/kF49GeJjMkvrhSdNS/bIcXd5KcqyX2fNngefF6E= +github.com/metal-stack/metal-lib v0.23.0/go.mod h1:QiFb7TpSrvnLAHOlxLiUm1aG+1t5nPHsoDT/bw+F5r8= github.com/metal-stack/v v1.0.3 h1:Sh2oBlnxrCUD+mVpzfC8HiqL045YWkxs0gpTvkjppqs= github.com/metal-stack/v v1.0.3/go.mod h1:YTahEu7/ishwpYKnp/VaW/7nf8+PInogkfGwLcGPdXg= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -109,10 +109,10 @@ golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2 h1:IqsN8hx+lWLqlN+Sc3DoMy/watjofWiU8sRFgQ8fhKM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= -google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= +google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -122,8 +122,8 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ= -k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4= +k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= From 0b47606e6727fd1cac798d46849bbc7e61f28a03 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Wed, 21 May 2025 07:54:33 +0200 Subject: [PATCH 07/11] Admin IP List --- cmd/admin/v1/commands.go | 1 + cmd/admin/v1/ip.go | 75 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 cmd/admin/v1/ip.go diff --git a/cmd/admin/v1/commands.go b/cmd/admin/v1/commands.go index 80afb07..0410c4f 100644 --- a/cmd/admin/v1/commands.go +++ b/cmd/admin/v1/commands.go @@ -15,6 +15,7 @@ func AddCmds(cmd *cobra.Command, c *config.Config) { } adminCmd.AddCommand(newImageCmd(c)) + adminCmd.AddCommand(newIPCmd(c)) adminCmd.AddCommand(newNetworkCmd(c)) adminCmd.AddCommand(newTokenCmd(c)) diff --git a/cmd/admin/v1/ip.go b/cmd/admin/v1/ip.go new file mode 100644 index 0000000..e44d37d --- /dev/null +++ b/cmd/admin/v1/ip.go @@ -0,0 +1,75 @@ +package v1 + +import ( + "connectrpc.com/connect" + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/cli/cmd/sorters" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/spf13/cobra" +) + +type ip struct { + c *config.Config +} + +func newIPCmd(c *config.Config) *cobra.Command { + w := &ip{ + c: c, + } + + cmdsConfig := &genericcli.CmdsConfig[any, any, *apiv2.IP]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI[any, any, *apiv2.IP](w).WithFS(c.Fs), + Singular: "ip", + Plural: "ips", + Description: "an ip address of metal-stack.io", + Sorter: sorters.IPSorter(), + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + OnlyCmds: genericcli.OnlyCmds(genericcli.ListCmd), + ListCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("ip", "", "ipaddress to filter [optional]") + cmd.Flags().String("name", "", "name to filter [optional]") + cmd.Flags().String("network", "", "network to filter [optional]") + cmd.Flags().String("description", "", "description to filter [optional]") + genericcli.Must(cmd.RegisterFlagCompletionFunc("network", c.Completion.NetworkListCompletion)) + }, + ValidArgsFn: c.Completion.IpListCompletion, + } + + return genericcli.NewCmds(cmdsConfig) +} + +func (c *ip) List() ([]*apiv2.IP, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().IP().List(ctx, connect.NewRequest(&adminv2.IPServiceListRequest{ + Query: &apiv2.IPQuery{}, + })) + if err != nil { + return nil, err + } + + return resp.Msg.Ips, nil +} + +func (t *ip) Get(id string) (*apiv2.IP, error) { + panic("unimplemented") +} +func (c *ip) Delete(id string) (*apiv2.IP, error) { + panic("unimplemented") +} +func (t *ip) Create(rq any) (*apiv2.IP, error) { + panic("unimplemented") +} +func (t *ip) Convert(r *apiv2.IP) (string, any, any, error) { + panic("unimplemented") +} + +func (t *ip) Update(rq any) (*apiv2.IP, error) { + panic("unimplemented") +} From 4888ed3fd0aefe6aa058d93b11501a6b3f1ac1c1 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Mon, 26 May 2025 13:03:43 +0200 Subject: [PATCH 08/11] pin api --- cmd/admin/v1/network.go | 8 ++++---- cmd/api/v1/ip.go | 2 +- cmd/api/v1/network.go | 10 +++++----- cmd/completion/network.go | 8 ++++++++ go.mod | 4 ++-- go.sum | 4 ++++ pkg/common/network.go | 17 ++++++++++++++++- 7 files changed, 40 insertions(+), 13 deletions(-) diff --git a/cmd/admin/v1/network.go b/cmd/admin/v1/network.go index 93db25e..bdb7199 100644 --- a/cmd/admin/v1/network.go +++ b/cmd/admin/v1/network.go @@ -43,7 +43,7 @@ func newNetworkCmd(c *config.Config) *cobra.Command { genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.PartitionListCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.IpAddressFamilyCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.NetworkAddressFamilyCompletion)) genericcli.Must(cmd.RegisterFlagCompletionFunc("type", c.Completion.NetworkTypeCompletion)) } @@ -83,7 +83,7 @@ func newNetworkCmd(c *config.Config) *cobra.Command { genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.PartitionListCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.IpAddressFamilyCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.NetworkAddressFamilyCompletion)) genericcli.Must(cmd.RegisterFlagCompletionFunc("type", c.Completion.NetworkTypeCompletion)) genericcli.Must(cmd.RegisterFlagCompletionFunc("nat-type", c.Completion.NetworkNatTypeCompletion)) }, @@ -141,7 +141,7 @@ func (c *networkCmd) List() ([]*apiv2.Network, error) { DestinationPrefixes: viper.GetStringSlice("destination-prefixes"), Vrf: pointer.PointerOrNil(viper.GetUint32("vrf")), ParentNetworkId: pointer.PointerOrNil(viper.GetString("parent-network-id")), - AddressFamily: common.AddressFamilyToType(viper.GetString("addressfamily")), + AddressFamily: common.NetworkAddressFamilyToType(viper.GetString("addressfamily")), Labels: &apiv2.Labels{ Labels: tag.NewTagMap(viper.GetStringSlice("labels")), }, @@ -310,7 +310,7 @@ func (c *networkCmd) createRequestFromCLI() (*adminv2.NetworkServiceCreateReques Labels: labels, }, ParentNetworkId: pointer.PointerOrNil(viper.GetString("parent-network-id")), - AddressFamily: common.AddressFamilyToType(viper.GetString("addressfamily")), + AddressFamily: common.NetworkAddressFamilyToType(viper.GetString("addressfamily")), Id: pointer.PointerOrNil(viper.GetString("id")), Type: nwType, Prefixes: viper.GetStringSlice("prefixes"), diff --git a/cmd/api/v1/ip.go b/cmd/api/v1/ip.go index 4cf5cef..48f9495 100644 --- a/cmd/api/v1/ip.go +++ b/cmd/api/v1/ip.go @@ -78,7 +78,7 @@ func newIPCmd(c *config.Config) *cobra.Command { Network: viper.GetString("network"), // Labels: viper.GetStringSlice("tags"), // FIXME implement Type: pointer.Pointer(common.IpStaticToType(viper.GetBool("static"))), - AddressFamily: common.AddressFamilyToType(viper.GetString("addressfamily")), + AddressFamily: common.IPAddressFamilyToType(viper.GetString("addressfamily")), }, nil }, UpdateRequestFromCLI: w.updateFromCLI, diff --git a/cmd/api/v1/network.go b/cmd/api/v1/network.go index 74709e0..59d0c8e 100644 --- a/cmd/api/v1/network.go +++ b/cmd/api/v1/network.go @@ -41,7 +41,7 @@ func newNetworkCmd(c *config.Config) *cobra.Command { genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.PartitionListCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.IpAddressFamilyCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.NetworkAddressFamilyCompletion)) genericcli.Must(cmd.RegisterFlagCompletionFunc("type", c.Completion.NetworkTypeCompletion)) } @@ -70,7 +70,7 @@ func newNetworkCmd(c *config.Config) *cobra.Command { genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.PartitionListCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.IpAddressFamilyCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.NetworkAddressFamilyCompletion)) }, ListCmdMutateFn: func(cmd *cobra.Command) { listFlags(cmd) @@ -128,7 +128,7 @@ func (c *networkCmd) List() ([]*apiv2.Network, error) { DestinationPrefixes: viper.GetStringSlice("destination-prefixes"), Vrf: pointer.PointerOrNil(viper.GetUint32("vrf")), ParentNetworkId: pointer.PointerOrNil(viper.GetString("parent-network-id")), - AddressFamily: common.AddressFamilyToType(viper.GetString("addressfamily")), + AddressFamily: common.NetworkAddressFamilyToType(viper.GetString("addressfamily")), Labels: &apiv2.Labels{ Labels: tag.NewTagMap(viper.GetStringSlice("labels")), }, @@ -243,7 +243,7 @@ func (c *networkCmd) createRequestFromCLI() (*apiv2.NetworkServiceCreateRequest, }, ParentNetworkId: pointer.PointerOrNil(viper.GetString("parent-network-id")), Length: cpl, - AddressFamily: common.AddressFamilyToType(viper.GetString("addressfamily")), + AddressFamily: common.NetworkAddressFamilyToType(viper.GetString("addressfamily")), }, nil } @@ -304,7 +304,7 @@ func (c *networkCmd) listBaseNetworks() error { Prefixes: viper.GetStringSlice("prefixes"), DestinationPrefixes: viper.GetStringSlice("destination-prefixes"), Vrf: pointer.PointerOrNil(viper.GetUint32("vrf")), - AddressFamily: common.AddressFamilyToType(viper.GetString("addressfamily")), + AddressFamily: common.NetworkAddressFamilyToType(viper.GetString("addressfamily")), Labels: &apiv2.Labels{ Labels: tag.NewTagMap(viper.GetStringSlice("labels")), }, diff --git a/cmd/completion/network.go b/cmd/completion/network.go index 04b7ab5..e6c119c 100644 --- a/cmd/completion/network.go +++ b/cmd/completion/network.go @@ -43,3 +43,11 @@ func (c *Completion) NetworkNatTypeCompletion(cmd *cobra.Command, args []string, } return names, cobra.ShellCompDirectiveNoFileComp } + +func (c *Completion) NetworkAddressFamilyCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{ + apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_V4.String(), + apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_V6.String(), + apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_DUAL_STACK.String(), + }, cobra.ShellCompDirectiveNoFileComp +} diff --git a/go.mod b/go.mod index 15c5397..ca2e0f1 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/dustin/go-humanize v1.0.1 github.com/fatih/color v1.18.0 github.com/google/go-cmp v0.7.0 - github.com/metal-stack/api v0.0.0-20250520125519-0fa114b11f2d + github.com/metal-stack/api v0.0.1 github.com/metal-stack/metal-lib v0.23.0 github.com/metal-stack/v v1.0.3 github.com/olekukonko/tablewriter v0.0.5 @@ -18,7 +18,7 @@ require ( github.com/spf13/viper v1.20.1 github.com/stretchr/testify v1.10.0 golang.org/x/net v0.40.0 - google.golang.org/grpc v1.72.1 + google.golang.org/grpc v1.72.2 google.golang.org/protobuf v1.36.6 sigs.k8s.io/yaml v1.4.0 ) diff --git a/go.sum b/go.sum index a950966..f06a390 100644 --- a/go.sum +++ b/go.sum @@ -57,6 +57,8 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/metal-stack/api v0.0.0-20250520125519-0fa114b11f2d h1:ECKVEPIPYV5mXol92A95vDqoAdqoh98sY7aEsMLVbfY= github.com/metal-stack/api v0.0.0-20250520125519-0fa114b11f2d/go.mod h1:gLRg6Rxkt0FGJnTNmmhclgrdMKT3el6O85+KgqYYCqE= +github.com/metal-stack/api v0.0.1 h1:kuxvFiWuMFcvvAecKY4OhSQxeBJfuojAYq5SUbJvK8U= +github.com/metal-stack/api v0.0.1/go.mod h1:gLRg6Rxkt0FGJnTNmmhclgrdMKT3el6O85+KgqYYCqE= github.com/metal-stack/metal-lib v0.23.0 h1:O0I/kF49GeJjMkvrhSdNS/bIcXd5KcqyX2fNngefF6E= github.com/metal-stack/metal-lib v0.23.0/go.mod h1:QiFb7TpSrvnLAHOlxLiUm1aG+1t5nPHsoDT/bw+F5r8= github.com/metal-stack/v v1.0.3 h1:Sh2oBlnxrCUD+mVpzfC8HiqL045YWkxs0gpTvkjppqs= @@ -113,6 +115,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= +google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/common/network.go b/pkg/common/network.go index 3d2c022..dd6c1df 100644 --- a/pkg/common/network.go +++ b/pkg/common/network.go @@ -4,7 +4,7 @@ import ( apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" ) -func AddressFamilyToType(af string) *apiv2.IPAddressFamily { +func IPAddressFamilyToType(af string) *apiv2.IPAddressFamily { switch af { case "": return nil @@ -16,3 +16,18 @@ func AddressFamilyToType(af string) *apiv2.IPAddressFamily { return apiv2.IPAddressFamily_IP_ADDRESS_FAMILY_UNSPECIFIED.Enum() } } + +func NetworkAddressFamilyToType(af string) *apiv2.NetworkAddressFamily { + switch af { + case "": + return nil + case "ipv4", "IPv4": + return apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_V4.Enum() + case "ipv6", "IPv6": + return apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_V6.Enum() + case "dual-stack": + return apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_DUAL_STACK.Enum() + default: + return apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_UNSPECIFIED.Enum() + } +} From 81d5626874b11b2020e57356bdcb3a66e8066db6 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Tue, 27 May 2025 09:09:37 +0200 Subject: [PATCH 09/11] Improvements --- cmd/api/v1/ip.go | 3 +++ cmd/api/v1/network.go | 1 + cmd/completion/ip.go | 14 +++++++++++++- cmd/completion/network.go | 32 +++++++++++++++++++++++++------- cmd/tableprinters/network.go | 11 +++++++---- 5 files changed, 49 insertions(+), 12 deletions(-) diff --git a/cmd/api/v1/ip.go b/cmd/api/v1/ip.go index 48f9495..9dd70d7 100644 --- a/cmd/api/v1/ip.go +++ b/cmd/api/v1/ip.go @@ -48,7 +48,10 @@ func newIPCmd(c *config.Config) *cobra.Command { cmd.Flags().BoolP("static", "", false, "make this ip static") cmd.Flags().StringP("addressfamily", "", "", "addressfamily, can be either IPv4|IPv6, defaults to IPv4 (optional)") + genericcli.Must(cmd.RegisterFlagCompletionFunc("network", c.Completion.NetworkListCompletion)) genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.IpAddressFamilyCompletion)) + }, UpdateCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().StringP("project", "p", "", "project of the ip") diff --git a/cmd/api/v1/network.go b/cmd/api/v1/network.go index 59d0c8e..a2803ed 100644 --- a/cmd/api/v1/network.go +++ b/cmd/api/v1/network.go @@ -71,6 +71,7 @@ func newNetworkCmd(c *config.Config) *cobra.Command { genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.PartitionListCompletion)) genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.NetworkAddressFamilyCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("parent-network-id", c.Completion.NetworkListCompletion)) }, ListCmdMutateFn: func(cmd *cobra.Command) { listFlags(cmd) diff --git a/cmd/completion/ip.go b/cmd/completion/ip.go index 3b508c1..9208684 100644 --- a/cmd/completion/ip.go +++ b/cmd/completion/ip.go @@ -2,6 +2,7 @@ package completion import ( "connectrpc.com/connect" + "github.com/metal-stack/api/go/enum" apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" "github.com/spf13/cobra" ) @@ -22,5 +23,16 @@ func (c *Completion) IpListCompletion(cmd *cobra.Command, args []string, toCompl } func (c *Completion) IpAddressFamilyCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - return []string{apiv2.IPAddressFamily_IP_ADDRESS_FAMILY_V4.String(), apiv2.IPAddressFamily_IP_ADDRESS_FAMILY_V6.String()}, cobra.ShellCompDirectiveNoFileComp + var afs []string + for _, af := range []apiv2.IPAddressFamily{ + apiv2.IPAddressFamily_IP_ADDRESS_FAMILY_V4, + apiv2.IPAddressFamily_IP_ADDRESS_FAMILY_V6} { + stringValue, err := enum.GetStringValue(af) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + afs = append(afs, *stringValue) + } + + return afs, cobra.ShellCompDirectiveNoFileComp } diff --git a/cmd/completion/network.go b/cmd/completion/network.go index e6c119c..4efc971 100644 --- a/cmd/completion/network.go +++ b/cmd/completion/network.go @@ -9,7 +9,14 @@ import ( ) func (c *Completion) NetworkListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - resp, err := c.Client.Apiv2().Network().List(c.Ctx, connect.NewRequest(&apiv2.NetworkServiceListRequest{ + ownNetworks, err := c.Client.Apiv2().Network().List(c.Ctx, connect.NewRequest(&apiv2.NetworkServiceListRequest{ + Project: c.Project, + })) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + baseNetworks, err := c.Client.Apiv2().Network().ListBaseNetworks(c.Ctx, connect.NewRequest(&apiv2.NetworkServiceListBaseNetworksRequest{ Project: c.Project, })) if err != nil { @@ -17,7 +24,10 @@ func (c *Completion) NetworkListCompletion(cmd *cobra.Command, args []string, to } var names []string - for _, s := range resp.Msg.Networks { + for _, s := range baseNetworks.Msg.Networks { + names = append(names, s.Id+"\t"+pointer.SafeDeref(s.Name)) + } + for _, s := range ownNetworks.Msg.Networks { names = append(names, s.Id+"\t"+pointer.SafeDeref(s.Name)) } @@ -45,9 +55,17 @@ func (c *Completion) NetworkNatTypeCompletion(cmd *cobra.Command, args []string, } func (c *Completion) NetworkAddressFamilyCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - return []string{ - apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_V4.String(), - apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_V6.String(), - apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_DUAL_STACK.String(), - }, cobra.ShellCompDirectiveNoFileComp + var afs []string + for _, af := range []apiv2.NetworkAddressFamily{ + apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_DUAL_STACK, + apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_V4, + apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_V6} { + stringValue, err := enum.GetStringValue(af) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + afs = append(afs, *stringValue) + } + + return afs, cobra.ShellCompDirectiveNoFileComp } diff --git a/cmd/tableprinters/network.go b/cmd/tableprinters/network.go index d657b12..57b3e7d 100644 --- a/cmd/tableprinters/network.go +++ b/cmd/tableprinters/network.go @@ -22,9 +22,9 @@ func (t *TablePrinter) NetworkTable(data []*apiv2.Network, wide bool) ([]string, rows [][]string ) - header := []string{"ID", "Name", "Project", "Partition", "Nat", "Prefixes", "Prefix Usage", "IP Usage"} + header := []string{"ID", "Name", "Type", "Project", "Partition", "Nat", "Prefixes", "Prefix Usage", "IP Usage"} if wide { - header = []string{"ID", "Description", "Name", "Project", "Partition", "Nat", "Prefixes", "Annotations"} + header = []string{"ID", "Description", "Name", "Type", "Project", "Partition", "Nat", "Prefixes", "Annotations"} } nn := &networks{} @@ -130,10 +130,13 @@ func addNetwork(prefix string, n *apiv2.Network, wide bool) []string { annotations := strings.Join(as, "\n") + var networkType *string + networkType, _ = enum.GetStringValue(n.Type) + if wide { - return []string{id, description, name, project, partition, natType, prefixes, annotations} + return []string{id, description, name, *networkType, project, partition, natType, prefixes, annotations} } else { - return []string{id, name, project, partition, natType, prefixes, shortPrefixUsage, shortIPUsage} + return []string{id, name, *networkType, project, partition, natType, prefixes, shortPrefixUsage, shortIPUsage} } } From 1e61919896dc36e3cea98e8d9e3e8ed09fd534e8 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Mon, 16 Jun 2025 12:32:40 +0200 Subject: [PATCH 10/11] Network improvements --- cmd/admin/v1/network.go | 10 +++++----- cmd/tableprinters/network.go | 13 +++++++++---- go.mod | 18 ++++++++++-------- go.sum | 36 ++++++++++++++++-------------------- 4 files changed, 40 insertions(+), 37 deletions(-) diff --git a/cmd/admin/v1/network.go b/cmd/admin/v1/network.go index bdb7199..415c9ea 100644 --- a/cmd/admin/v1/network.go +++ b/cmd/admin/v1/network.go @@ -228,12 +228,12 @@ func networkResponseToUpdate(r *apiv2.Network) *adminv2.NetworkServiceUpdateRequ Labels: &apiv2.UpdateLabels{ Update: meta.Labels, // TODO: this only ensures that the labels are present but it does not cleanup old one's, which would require fetching the current state and calculating the diff }, - Prefixes: []string{}, - DestinationPrefixes: []string{}, - DefaultChildPrefixLength: &apiv2.ChildPrefixLength{}, - MinChildPrefixLength: &apiv2.ChildPrefixLength{}, + Prefixes: r.Prefixes, + DestinationPrefixes: r.DestinationPrefixes, + DefaultChildPrefixLength: r.DefaultChildPrefixLength, + MinChildPrefixLength: r.MinChildPrefixLength, // NatType: &0, - AdditionalAnnouncableCidrs: []string{}, + AdditionalAnnouncableCidrs: r.AdditionalAnnouncableCidrs, Force: false, } } diff --git a/cmd/tableprinters/network.go b/cmd/tableprinters/network.go index 57b3e7d..08c17d3 100644 --- a/cmd/tableprinters/network.go +++ b/cmd/tableprinters/network.go @@ -130,13 +130,18 @@ func addNetwork(prefix string, n *apiv2.Network, wide bool) []string { annotations := strings.Join(as, "\n") - var networkType *string - networkType, _ = enum.GetStringValue(n.Type) + var networkType string + nt, err := enum.GetStringValue(n.Type) + if err != nil { + networkType = "unknown" + } else { + networkType = *nt + } if wide { - return []string{id, description, name, *networkType, project, partition, natType, prefixes, annotations} + return []string{id, description, name, networkType, project, partition, natType, prefixes, annotations} } else { - return []string{id, name, *networkType, project, partition, natType, prefixes, shortPrefixUsage, shortIPUsage} + return []string{id, name, networkType, project, partition, natType, prefixes, shortPrefixUsage, shortIPUsage} } } diff --git a/go.mod b/go.mod index ca2e0f1..9db3f6d 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/metal-stack/cli go 1.24.0 +toolchain go1.24.4 + require ( bou.ke/monkey v1.0.2 connectrpc.com/connect v1.18.1 @@ -17,14 +19,14 @@ require ( github.com/spf13/pflag v1.0.6 github.com/spf13/viper v1.20.1 github.com/stretchr/testify v1.10.0 - golang.org/x/net v0.40.0 - google.golang.org/grpc v1.72.2 + golang.org/x/net v0.41.0 + google.golang.org/grpc v1.73.0 google.golang.org/protobuf v1.36.6 sigs.k8s.io/yaml v1.4.0 ) require ( - buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1 // indirect + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250613105001-9f2d3c737feb.1 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -33,7 +35,7 @@ require ( github.com/go-openapi/strfmt v0.23.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect - github.com/goccy/go-yaml v1.17.1 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/klauspost/compress v1.18.0 // indirect @@ -49,14 +51,14 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.9.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/cast v1.8.0 // indirect + github.com/spf13/cast v1.9.2 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect - go.mongodb.org/mongo-driver v1.17.3 // indirect + go.mongodb.org/mongo-driver v1.17.4 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.25.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect + golang.org/x/text v0.26.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apimachinery v0.33.1 // indirect diff --git a/go.sum b/go.sum index f06a390..5538c96 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ bou.ke/monkey v1.0.2 h1:kWcnsrCNUatbxncxR/ThdYqbytgOIArtYWqcQLQzKLI= bou.ke/monkey v1.0.2/go.mod h1:OqickVX3tNx6t33n1xvtTtu85YN5s6cKwVug+oHMaIA= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1 h1:YhMSc48s25kr7kv31Z8vf7sPUIq5YJva9z1mn/hAt0M= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1/go.mod h1:avRlCjnFzl98VPaeCtJ24RrV/wwHFzB8sWXhj26+n/U= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250613105001-9f2d3c737feb.1 h1:AUL6VF5YWL01j/1H/DQbPUSDkEwYqwVCNw7yhbpOxSQ= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250613105001-9f2d3c737feb.1/go.mod h1:avRlCjnFzl98VPaeCtJ24RrV/wwHFzB8sWXhj26+n/U= connectrpc.com/connect v1.18.1 h1:PAg7CjSAGvscaf6YZKUefjoih5Z/qYkyaTrBW8xvYPw= connectrpc.com/connect v1.18.1/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= @@ -29,8 +29,8 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/goccy/go-yaml v1.17.1 h1:LI34wktB2xEE3ONG/2Ar54+/HJVBriAGJ55PHls4YuY= -github.com/goccy/go-yaml v1.17.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -55,8 +55,6 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/metal-stack/api v0.0.0-20250520125519-0fa114b11f2d h1:ECKVEPIPYV5mXol92A95vDqoAdqoh98sY7aEsMLVbfY= -github.com/metal-stack/api v0.0.0-20250520125519-0fa114b11f2d/go.mod h1:gLRg6Rxkt0FGJnTNmmhclgrdMKT3el6O85+KgqYYCqE= github.com/metal-stack/api v0.0.1 h1:kuxvFiWuMFcvvAecKY4OhSQxeBJfuojAYq5SUbJvK8U= github.com/metal-stack/api v0.0.1/go.mod h1:gLRg6Rxkt0FGJnTNmmhclgrdMKT3el6O85+KgqYYCqE= github.com/metal-stack/metal-lib v0.23.0 h1:O0I/kF49GeJjMkvrhSdNS/bIcXd5KcqyX2fNngefF6E= @@ -86,8 +84,8 @@ github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9yS github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= -github.com/spf13/cast v1.8.0 h1:gEN9K4b8Xws4EX0+a0reLmhq8moKn7ntRlQYgjPeCDk= -github.com/spf13/cast v1.8.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= +github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= @@ -100,23 +98,21 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ= -go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= +go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw= +go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= -google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= -google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= -google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= +google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 03fdc62723edd02ab83cce42e6d00bb6192ddaf5 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Thu, 26 Jun 2025 11:43:04 +0200 Subject: [PATCH 11/11] Update api --- .github/workflows/docker.yaml | 2 +- go.mod | 11 ++++++----- go.sum | 25 ++++++++++++++----------- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index a0c6534..fb38046 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -26,7 +26,7 @@ jobs: cache: false - name: Lint - uses: golangci/golangci-lint-action@v7 + uses: golangci/golangci-lint-action@v8 with: args: --build-tags integration -D protogetter --timeout=5m diff --git a/go.mod b/go.mod index 9db3f6d..c719dd2 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/dustin/go-humanize v1.0.1 github.com/fatih/color v1.18.0 github.com/google/go-cmp v0.7.0 - github.com/metal-stack/api v0.0.1 + github.com/metal-stack/api v0.0.2-0.20250618071913-3260d4be4c0a github.com/metal-stack/metal-lib v0.23.0 github.com/metal-stack/v v1.0.3 github.com/olekukonko/tablewriter v0.0.5 @@ -22,11 +22,11 @@ require ( golang.org/x/net v0.41.0 google.golang.org/grpc v1.73.0 google.golang.org/protobuf v1.36.6 - sigs.k8s.io/yaml v1.4.0 + sigs.k8s.io/yaml v1.5.0 ) require ( - buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250613105001-9f2d3c737feb.1 // indirect + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250625184727-c923a0c2a132.1 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -34,7 +34,7 @@ require ( github.com/go-openapi/errors v0.22.1 // indirect github.com/go-openapi/strfmt v0.23.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/go-viper/mapstructure/v2 v2.3.0 // indirect github.com/goccy/go-yaml v1.18.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -56,11 +56,12 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect go.mongodb.org/mongo-driver v1.17.4 // indirect go.uber.org/multierr v1.11.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.26.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apimachinery v0.33.1 // indirect + k8s.io/apimachinery v0.33.2 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect ) diff --git a/go.sum b/go.sum index 5538c96..f5a2968 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ bou.ke/monkey v1.0.2 h1:kWcnsrCNUatbxncxR/ThdYqbytgOIArtYWqcQLQzKLI= bou.ke/monkey v1.0.2/go.mod h1:OqickVX3tNx6t33n1xvtTtu85YN5s6cKwVug+oHMaIA= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250613105001-9f2d3c737feb.1 h1:AUL6VF5YWL01j/1H/DQbPUSDkEwYqwVCNw7yhbpOxSQ= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250613105001-9f2d3c737feb.1/go.mod h1:avRlCjnFzl98VPaeCtJ24RrV/wwHFzB8sWXhj26+n/U= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250625184727-c923a0c2a132.1 h1:6tCo3lsKNLqUjRPhyc8JuYWYUiQkulufxSDOfG1zgWQ= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250625184727-c923a0c2a132.1/go.mod h1:avRlCjnFzl98VPaeCtJ24RrV/wwHFzB8sWXhj26+n/U= connectrpc.com/connect v1.18.1 h1:PAg7CjSAGvscaf6YZKUefjoih5Z/qYkyaTrBW8xvYPw= connectrpc.com/connect v1.18.1/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= @@ -27,13 +27,12 @@ github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMg github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= -github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk= +github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -55,8 +54,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/metal-stack/api v0.0.1 h1:kuxvFiWuMFcvvAecKY4OhSQxeBJfuojAYq5SUbJvK8U= -github.com/metal-stack/api v0.0.1/go.mod h1:gLRg6Rxkt0FGJnTNmmhclgrdMKT3el6O85+KgqYYCqE= +github.com/metal-stack/api v0.0.2-0.20250618071913-3260d4be4c0a h1:L9YYgqs53q73w4FuBEQXMtnYM6W9q8L9SC+EASmeGZY= +github.com/metal-stack/api v0.0.2-0.20250618071913-3260d4be4c0a/go.mod h1:hWv9QiPqNsiur7iE+Xa6byMhx5WznHMSQqsbjH0HfUg= github.com/metal-stack/metal-lib v0.23.0 h1:O0I/kF49GeJjMkvrhSdNS/bIcXd5KcqyX2fNngefF6E= github.com/metal-stack/metal-lib v0.23.0/go.mod h1:QiFb7TpSrvnLAHOlxLiUm1aG+1t5nPHsoDT/bw+F5r8= github.com/metal-stack/v v1.0.3 h1:Sh2oBlnxrCUD+mVpzfC8HiqL045YWkxs0gpTvkjppqs= @@ -102,6 +101,10 @@ go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFX go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= +go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -122,9 +125,9 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4= -k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY= +k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ= +sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4=