From 3cb6072c8f6733bb909937a133e558c6f4586c90 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Wed, 14 May 2025 11:39:03 +0200 Subject: [PATCH 01/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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= From 90ffb0acd0a0a04b279ed2ed9468ec0cccaac01a Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Fri, 6 Feb 2026 15:36:54 +0100 Subject: [PATCH 12/12] size, admin not finished yet (#4) --- .github/workflows/docker.yaml | 6 +- README.md | 2 +- cmd/admin/{v1 => v2}/commands.go | 8 +- cmd/admin/{v1 => v2}/image.go | 2 +- cmd/admin/{v1 => v2}/ip.go | 6 +- cmd/admin/v2/machine.go | 191 ++++++++++++++++++++++++++++ cmd/admin/{v1 => v2}/network.go | 5 +- cmd/admin/v2/project.go | 77 +++++++++++ cmd/admin/v2/size.go | 123 ++++++++++++++++++ cmd/admin/v2/task.go | 126 ++++++++++++++++++ cmd/admin/v2/tenant.go | 105 +++++++++++++++ cmd/admin/{v1 => v2}/token.go | 6 +- cmd/admin/v2/vpn.go | 115 +++++++++++++++++ cmd/api/{v1 => v2}/commands.go | 4 +- cmd/api/{v1 => v2}/health.go | 6 +- cmd/api/{v1 => v2}/image.go | 2 +- cmd/api/{v1 => v2}/ip.go | 4 +- cmd/api/v2/machine.go | 108 ++++++++++++++++ cmd/api/{v1 => v2}/methods.go | 2 +- cmd/api/{v1 => v2}/network.go | 2 +- cmd/api/{v1 => v2}/project.go | 2 +- cmd/api/v2/size.go | 92 ++++++++++++++ cmd/api/{v1 => v2}/tenant.go | 3 +- cmd/api/{v1 => v2}/token.go | 4 +- cmd/api/{v1 => v2}/user.go | 2 +- cmd/api/{v1 => v2}/version.go | 8 +- cmd/common_test.go | 8 -- cmd/completion/machine.go | 35 +++++ cmd/completion/network.go | 15 +++ cmd/completion/size.go | 19 +++ cmd/completion/token.go | 16 +-- cmd/login.go | 4 +- cmd/logout.go | 4 +- cmd/printers.go | 1 + cmd/root.go | 6 +- cmd/sorters/machine.go | 33 +++++ cmd/tableprinters/common.go | 28 +++- cmd/tableprinters/machine.go | 165 ++++++++++++++++++++++++ cmd/tableprinters/size.go | 45 +++++++ cmd/tableprinters/task.go | 51 ++++++++ cmd/tableprinters/vpn.go | 35 +++++ docs/metalctlv2.md | 2 + docs/metalctlv2_ip.md | 2 +- docs/metalctlv2_login.md | 2 +- docs/metalctlv2_logout.md | 2 +- docs/metalctlv2_machine.md | 38 ++++++ docs/metalctlv2_machine_apply.md | 46 +++++++ docs/metalctlv2_machine_create.md | 46 +++++++ docs/metalctlv2_machine_delete.md | 46 +++++++ docs/metalctlv2_machine_describe.md | 32 +++++ docs/metalctlv2_machine_edit.md | 31 +++++ docs/metalctlv2_machine_list.md | 33 +++++ docs/metalctlv2_machine_update.md | 46 +++++++ docs/metalctlv2_size.md | 33 +++++ docs/metalctlv2_size_describe.md | 31 +++++ docs/metalctlv2_size_list.md | 34 +++++ docs/metalctlv2_tenant_create.md | 1 - docs/metalctlv2_token.md | 2 +- go.mod | 54 ++++---- go.sum | 106 +++++++-------- pkg/helpers/emoji.go | 27 ++++ 61 files changed, 1946 insertions(+), 144 deletions(-) rename cmd/admin/{v1 => v2}/commands.go (66%) rename cmd/admin/{v1 => v2}/image.go (99%) rename cmd/admin/{v1 => v2}/ip.go (93%) create mode 100644 cmd/admin/v2/machine.go rename cmd/admin/{v1 => v2}/network.go (99%) create mode 100644 cmd/admin/v2/project.go create mode 100644 cmd/admin/v2/size.go create mode 100644 cmd/admin/v2/task.go create mode 100644 cmd/admin/v2/tenant.go rename cmd/admin/{v1 => v2}/token.go (93%) create mode 100644 cmd/admin/v2/vpn.go rename cmd/api/{v1 => v2}/commands.go (85%) rename cmd/api/{v1 => v2}/health.go (82%) rename cmd/api/{v1 => v2}/image.go (99%) rename cmd/api/{v1 => v2}/ip.go (99%) create mode 100644 cmd/api/v2/machine.go rename cmd/api/{v1 => v2}/methods.go (99%) rename cmd/api/{v1 => v2}/network.go (99%) rename cmd/api/{v1 => v2}/project.go (99%) create mode 100644 cmd/api/v2/size.go rename cmd/api/{v1 => v2}/tenant.go (99%) rename cmd/api/{v1 => v2}/token.go (98%) rename cmd/api/{v1 => v2}/user.go (99%) rename cmd/api/{v1 => v2}/version.go (84%) create mode 100644 cmd/completion/machine.go create mode 100644 cmd/completion/size.go create mode 100644 cmd/sorters/machine.go create mode 100644 cmd/tableprinters/machine.go create mode 100644 cmd/tableprinters/size.go create mode 100644 cmd/tableprinters/task.go create mode 100644 cmd/tableprinters/vpn.go create mode 100644 docs/metalctlv2_machine.md create mode 100644 docs/metalctlv2_machine_apply.md create mode 100644 docs/metalctlv2_machine_create.md create mode 100644 docs/metalctlv2_machine_delete.md create mode 100644 docs/metalctlv2_machine_describe.md create mode 100644 docs/metalctlv2_machine_edit.md create mode 100644 docs/metalctlv2_machine_list.md create mode 100644 docs/metalctlv2_machine_update.md create mode 100644 docs/metalctlv2_size.md create mode 100644 docs/metalctlv2_size_describe.md create mode 100644 docs/metalctlv2_size_list.md create mode 100644 pkg/helpers/emoji.go diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index 189060f..c02ff18 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -21,7 +21,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Go uses: actions/setup-go@v6 @@ -30,7 +30,7 @@ jobs: cache: false - name: Lint - uses: golangci/golangci-lint-action@v8 + uses: golangci/golangci-lint-action@v9 with: args: --build-tags integration --timeout=5m @@ -58,7 +58,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Go uses: actions/setup-go@v6 diff --git a/README.md b/README.md index e8cb47c..04a72be 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ In addition to the standard API services, there are also admin services that req You can access help for every service and command by using `--help` or `-h`. If you encounter any issues not covered in the help prompt, or if you have suggestions for improvement, please feel free to [contact us](mailto:support@metal-stack.io) or open an issue in this repository. Your feedback is greatly appreciated! -A list of all available services (excluding admin topics). For their associated commands, arguments and flags visit the correct [documentation](./docs/metal.md). +A list of all available services (excluding admin topics). For their associated commands, arguments and flags visit the [metalctlv2 documentation](./docs/metalctlv2.md). | Entity | Description | Documentation | |---------------|------------------------------------------------------------|-------------------------------------------------------| diff --git a/cmd/admin/v1/commands.go b/cmd/admin/v2/commands.go similarity index 66% rename from cmd/admin/v1/commands.go rename to cmd/admin/v2/commands.go index 0410c4f..484241d 100644 --- a/cmd/admin/v1/commands.go +++ b/cmd/admin/v2/commands.go @@ -1,4 +1,4 @@ -package v1 +package v2 import ( "github.com/metal-stack/cli/cmd/config" @@ -17,7 +17,13 @@ func AddCmds(cmd *cobra.Command, c *config.Config) { adminCmd.AddCommand(newImageCmd(c)) adminCmd.AddCommand(newIPCmd(c)) adminCmd.AddCommand(newNetworkCmd(c)) + adminCmd.AddCommand(newSizeCmd(c)) + adminCmd.AddCommand(newTenantCmd(c)) adminCmd.AddCommand(newTokenCmd(c)) + adminCmd.AddCommand(newProjectCmd(c)) + adminCmd.AddCommand(newVPNCmd(c)) + adminCmd.AddCommand(newMachineCmd(c)) + adminCmd.AddCommand(newTaskCmd(c)) cmd.AddCommand(adminCmd) } diff --git a/cmd/admin/v1/image.go b/cmd/admin/v2/image.go similarity index 99% rename from cmd/admin/v1/image.go rename to cmd/admin/v2/image.go index be9e7d0..4aa0fda 100644 --- a/cmd/admin/v1/image.go +++ b/cmd/admin/v2/image.go @@ -1,4 +1,4 @@ -package v1 +package v2 import ( "fmt" diff --git a/cmd/admin/v1/ip.go b/cmd/admin/v2/ip.go similarity index 93% rename from cmd/admin/v1/ip.go rename to cmd/admin/v2/ip.go index 454e06f..637080d 100644 --- a/cmd/admin/v1/ip.go +++ b/cmd/admin/v2/ip.go @@ -1,4 +1,4 @@ -package v1 +package v2 import ( adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" @@ -21,10 +21,10 @@ func newIPCmd(c *config.Config) *cobra.Command { cmdsConfig := &genericcli.CmdsConfig[any, any, *apiv2.IP]{ BinaryName: config.BinaryName, - GenericCLI: genericcli.NewGenericCLI[any, any, *apiv2.IP](w).WithFS(c.Fs), + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), Singular: "ip", Plural: "ips", - Description: "an ip address of metal-stack.io", + Description: "manage ip addresses", Sorter: sorters.IPSorter(), DescribePrinter: func() printers.Printer { return c.DescribePrinter }, ListPrinter: func() printers.Printer { return c.ListPrinter }, diff --git a/cmd/admin/v2/machine.go b/cmd/admin/v2/machine.go new file mode 100644 index 0000000..8a0f492 --- /dev/null +++ b/cmd/admin/v2/machine.go @@ -0,0 +1,191 @@ +package v2 + +import ( + "fmt" + + 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/metal-stack/metal-lib/pkg/pointer" + "github.com/metal-stack/metal-lib/pkg/tag" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +type machine struct { + c *config.Config +} + +func newMachineCmd(c *config.Config) *cobra.Command { + w := &machine{ + c: c, + } + + cmdsConfig := &genericcli.CmdsConfig[any, any, *apiv2.Machine]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), + Singular: "machine", + Plural: "machines", + Description: "manage machines", + Sorter: sorters.MachineSorter(), + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + ListCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().StringP("project", "p", "", "project from where machines should be listed") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + }, + DescribeCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().StringP("project", "p", "", "project of the machine") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + }, + ValidArgsFn: c.Completion.MachineListCompletion, + } + + bmcCommandCmd := &cobra.Command{ + Use: "bmc-command", + Short: "send a command to the bmc of a machine", + RunE: func(cmd *cobra.Command, args []string) error { + return w.bmcCommand() + }, + } + bmcCommandCmd.Flags().String("id", "", "id of the machine where the command should be sent to") + bmcCommandCmd.Flags().String("command", "", "the actual command to send to the machine") + bmcCommandCmd.RegisterFlagCompletionFunc("id", c.Completion.MachineListCompletion) + bmcCommandCmd.RegisterFlagCompletionFunc("command", c.Completion.BMCCommandListCompletion) + genericcli.Must(bmcCommandCmd.MarkFlagRequired("id")) + genericcli.Must(bmcCommandCmd.MarkFlagRequired("command")) + + return genericcli.NewCmds(cmdsConfig, bmcCommandCmd) +} + +func (c *machine) bmcCommand() error { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + commandString := viper.GetString("command") + + cmd, ok := apiv2.MachineBMCCommand_value[commandString] + if !ok { + return fmt.Errorf("unknown command: %s", commandString) + } + _, err := c.c.Client.Adminv2().Machine().BMCCommand(ctx, &adminv2.MachineServiceBMCCommandRequest{ + Uuid: viper.GetString("id"), + Command: apiv2.MachineBMCCommand(cmd), + }) + if err != nil { + return err + } + return err +} + +func (c *machine) updateFromCLI(args []string) (any, error) { + panic("unimplemented") +} + +func (c *machine) Create(rq any) (*apiv2.Machine, error) { + panic("unimplemented") +} + +func (c *machine) Delete(id string) (*apiv2.Machine, error) { + panic("unimplemented") +} + +func (c *machine) Get(id string) (*apiv2.Machine, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().Machine().Get(ctx, &adminv2.MachineServiceGetRequest{ + Uuid: id, + }) + if err != nil { + return nil, err + } + + return resp.Machine, nil +} + +func (c *machine) List() ([]*apiv2.Machine, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().Machine().List(ctx, &adminv2.MachineServiceListRequest{ + Query: &apiv2.MachineQuery{ + Uuid: pointer.PointerOrNil(viper.GetString("id")), + Name: pointer.PointerOrNil(viper.GetString("name")), + Partition: pointer.PointerOrNil(viper.GetString("partition")), + Size: pointer.PointerOrNil(viper.GetString("size")), + Rack: pointer.PointerOrNil(viper.GetString("rack")), + Labels: &apiv2.Labels{ + Labels: tag.NewTagMap(viper.GetStringSlice("labels")), + }, + Allocation: &apiv2.MachineAllocationQuery{ + Uuid: pointer.PointerOrNil(viper.GetString("allocation-uuid")), + Name: pointer.PointerOrNil(viper.GetString("allocation-name")), + Project: pointer.PointerOrNil(viper.GetString("project")), + Image: pointer.PointerOrNil(viper.GetString("image")), + FilesystemLayout: pointer.PointerOrNil(viper.GetString("file-system-layout-id")), + Hostname: pointer.PointerOrNil(viper.GetString("hostname")), + // AllocationType: &0, + Labels: &apiv2.Labels{ + Labels: tag.NewTagMap(viper.GetStringSlice("allocation-labels")), + }, + // Vpn: &apiv2.MachineVPN{}, these query fields are no pointers and somehow seem wrong? how to search for vpn key? + }, + Network: &apiv2.MachineNetworkQuery{}, + Nic: &apiv2.MachineNicQuery{}, + Disk: &apiv2.MachineDiskQuery{ + Names: viper.GetStringSlice("disk-names"), + // Sizes: + }, + Bmc: &apiv2.MachineBMCQuery{ + Address: pointer.PointerOrNil(viper.GetString("bmc-address")), + Mac: pointer.PointerOrNil(viper.GetString("bmc-mac")), + User: pointer.PointerOrNil(viper.GetString("bmc-user")), + Interface: pointer.PointerOrNil(viper.GetString("bmc-interface")), + }, + Fru: &apiv2.MachineFRUQuery{ + ChassisPartNumber: pointer.PointerOrNil(viper.GetString("chassis-part-number")), + ChassisPartSerial: pointer.PointerOrNil(viper.GetString("chassis-part-serial")), + BoardMfg: pointer.PointerOrNil(viper.GetString("board-mfg")), + BoardSerial: pointer.PointerOrNil(viper.GetString("board-serial")), + BoardPartNumber: pointer.PointerOrNil(viper.GetString("board-part-number")), + ProductManufacturer: pointer.PointerOrNil(viper.GetString("product-manufacturer")), + ProductPartNumber: pointer.PointerOrNil(viper.GetString("product-part-number")), + ProductSerial: pointer.PointerOrNil(viper.GetString("product-serial")), + }, + Hardware: &apiv2.MachineHardwareQuery{ + Memory: pointer.PointerOrNil(viper.GetUint64("memory")), + CpuCores: pointer.PointerOrNil(viper.GetUint32("cpu-cores")), + }, + // State: &0, + }, + Partition: nil, // again partition? + }) + if err != nil { + return nil, err + } + + return resp.Machines, nil +} + +func (c *machine) Update(rq any) (*apiv2.Machine, error) { + panic("unimplemented") +} + +func (c *machine) Convert(r *apiv2.Machine) (string, any, any, error) { + panic("unimplemented") + +} + +func (c *machine) MachineResponseToCreate(r *apiv2.Machine) any { + panic("unimplemented") +} + +func (c *machine) MachineResponseToUpdate(desired *apiv2.Machine) (any, error) { + panic("unimplemented") +} diff --git a/cmd/admin/v1/network.go b/cmd/admin/v2/network.go similarity index 99% rename from cmd/admin/v1/network.go rename to cmd/admin/v2/network.go index d57798c..f6ef299 100644 --- a/cmd/admin/v1/network.go +++ b/cmd/admin/v2/network.go @@ -1,4 +1,4 @@ -package v1 +package v2 import ( "github.com/metal-stack/api/go/enum" @@ -55,7 +55,7 @@ func newNetworkCmd(c *config.Config) *cobra.Command { CreateRequestFromCLI: w.createRequestFromCLI, UpdateRequestFromCLI: w.updateRequestFromCLI, Sorter: sorters.NetworkSorter(), - ValidArgsFn: c.Completion.NetworkListCompletion, + ValidArgsFn: c.Completion.NetworkAdminListCompletion, DescribePrinter: func() printers.Printer { return c.DescribePrinter }, ListPrinter: func() printers.Printer { return c.ListPrinter }, CreateCmdMutateFn: func(cmd *cobra.Command) { @@ -79,7 +79,6 @@ func newNetworkCmd(c *config.Config) *cobra.Command { 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.NetworkAddressFamilyCompletion)) diff --git a/cmd/admin/v2/project.go b/cmd/admin/v2/project.go new file mode 100644 index 0000000..49071fc --- /dev/null +++ b/cmd/admin/v2/project.go @@ -0,0 +1,77 @@ +package v2 + +import ( + "fmt" + + 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/metal-stack/metal-lib/pkg/pointer" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +type project struct { + c *config.Config +} + +func newProjectCmd(c *config.Config) *cobra.Command { + w := &project{ + c: c, + } + + cmdsConfig := &genericcli.CmdsConfig[*apiv2.ProjectServiceCreateRequest, *apiv2.ProjectServiceUpdateRequest, *apiv2.Project]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), + Singular: "project", + Plural: "projects", + Description: "manage api projects", + Sorter: sorters.ProjectSorter(), + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + ListCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("tenant", "", "lists only projects with the given tenant") + }, + } + + return genericcli.NewCmds(cmdsConfig) +} + +func (c *project) Get(id string) (*apiv2.Project, error) { + panic("unimplemented") +} + +func (c *project) List() ([]*apiv2.Project, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &adminv2.ProjectServiceListRequest{ + Tenant: pointer.PointerOrNil(viper.GetString("tenant")), + } + + resp, err := c.c.Client.Adminv2().Project().List(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to list projects: %w", err) + } + + return resp.GetProjects(), nil +} + +func (c *project) Create(rq *apiv2.ProjectServiceCreateRequest) (*apiv2.Project, error) { + panic("unimplemented") +} + +func (c *project) Delete(id string) (*apiv2.Project, error) { + panic("unimplemented") +} + +func (c *project) Convert(r *apiv2.Project) (string, *apiv2.ProjectServiceCreateRequest, *apiv2.ProjectServiceUpdateRequest, error) { + panic("unimplemented") +} + +func (c *project) Update(rq *apiv2.ProjectServiceUpdateRequest) (*apiv2.Project, error) { + panic("unimplemented") +} diff --git a/cmd/admin/v2/size.go b/cmd/admin/v2/size.go new file mode 100644 index 0000000..962ad71 --- /dev/null +++ b/cmd/admin/v2/size.go @@ -0,0 +1,123 @@ +package v2 + +import ( + "fmt" + + 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/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/metal-stack/metal-lib/pkg/pointer" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +type size struct { + c *config.Config +} + +func newSizeCmd(c *config.Config) *cobra.Command { + w := &size{ + c: c, + } + gcli := genericcli.NewGenericCLI(w).WithFS(c.Fs) + + cmdsConfig := &genericcli.CmdsConfig[*adminv2.SizeServiceCreateRequest, *adminv2.SizeServiceUpdateRequest, *apiv2.Size]{ + BinaryName: config.BinaryName, + GenericCLI: gcli, + Singular: "size", + Plural: "sizes", + Description: "manage sizes which defines the cpu, gpu, memory and storage properties of machines", + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + ValidArgsFn: c.Completion.SizeListCompletion, + OnlyCmds: genericcli.OnlyCmds(genericcli.CreateCmd, genericcli.UpdateCmd, genericcli.DeleteCmd, genericcli.EditCmd), + } + + return genericcli.NewCmds(cmdsConfig) +} + +func (c *size) Get(id string) (*apiv2.Size, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &apiv2.SizeServiceGetRequest{Id: id} + + resp, err := c.c.Client.Apiv2().Size().Get(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get size: %w", err) + } + + return resp.Size, nil +} + +func (c *size) Create(rq *adminv2.SizeServiceCreateRequest) (*apiv2.Size, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().Size().Create(ctx, rq) + if err != nil { + return nil, fmt.Errorf("failed to get size: %w", err) + } + + return resp.Size, nil +} + +func (c *size) Delete(id string) (*apiv2.Size, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &adminv2.SizeServiceDeleteRequest{Id: id} + + resp, err := c.c.Client.Adminv2().Size().Delete(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to delete size: %w", err) + } + + return resp.Size, nil +} +func (c *size) List() ([]*apiv2.Size, error) { + panic("unimplemented") + +} +func (c *size) Convert(r *apiv2.Size) (string, *adminv2.SizeServiceCreateRequest, *adminv2.SizeServiceUpdateRequest, error) { + + return r.Id, &adminv2.SizeServiceCreateRequest{ + Size: &apiv2.Size{ + Id: r.Id, + Name: r.Name, + Description: r.Description, + Meta: r.Meta, + Constraints: r.Constraints, + }, + }, &adminv2.SizeServiceUpdateRequest{ + Id: r.Id, + Name: r.Name, + Description: r.Description, + Constraints: r.Constraints, + // FIXME + Labels: &apiv2.UpdateLabels{}, + }, nil + +} + +func (c *size) Update(rq *adminv2.SizeServiceUpdateRequest) (*apiv2.Size, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &adminv2.SizeServiceUpdateRequest{ + Id: viper.GetString("id"), + Name: pointer.PointerOrNil(viper.GetString("name")), + Description: pointer.PointerOrNil(viper.GetString("description")), + Constraints: rq.Constraints, + Labels: &apiv2.UpdateLabels{}, // FIXME + } + + resp, err := c.c.Client.Adminv2().Size().Update(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get size: %w", err) + } + + return resp.Size, nil +} diff --git a/cmd/admin/v2/task.go b/cmd/admin/v2/task.go new file mode 100644 index 0000000..2826138 --- /dev/null +++ b/cmd/admin/v2/task.go @@ -0,0 +1,126 @@ +package v2 + +import ( + "fmt" + "strings" + + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + "github.com/metal-stack/cli/cmd/config" + "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/spf13/cobra" + "github.com/spf13/viper" +) + +type task struct { + c *config.Config +} + +func newTaskCmd(c *config.Config) *cobra.Command { + w := &task{ + c: c, + } + + cmdsConfig := &genericcli.CmdsConfig[any, any, *adminv2.TaskInfo]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), + Singular: "task", + Plural: "tasks", + Description: "get task insights", + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + DescribeCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("queue", "default", "the queue for which tasks should be described") + }, + ListCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("queue", "", "the queue for which tasks should be listed") + }, + DeleteCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("queue", "default", "the queue of the task which should be delete") + }, + OnlyCmds: genericcli.OnlyCmds(genericcli.ListCmd, genericcli.DescribeCmd, genericcli.DeleteCmd), + } + + queueCmd := &cobra.Command{ + Use: "queues", + Short: "list all queues", + RunE: func(cmd *cobra.Command, args []string) error { + return w.queues() + }, + } + + return genericcli.NewCmds(cmdsConfig, queueCmd) +} + +func (t *task) queues() error { + ctx, cancel := t.c.NewRequestContext() + defer cancel() + + req := &adminv2.TaskServiceQueuesRequest{} + + resp, err := t.c.Client.Adminv2().Task().Queues(ctx, req) + if err != nil { + return fmt.Errorf("failed to get task queues: %w", err) + } + + _, _ = fmt.Fprint(t.c.Out, strings.Join(resp.Queues, "\n")) + _, _ = fmt.Fprint(t.c.Out, "\n") + return err +} + +func (t *task) Get(id string) (*adminv2.TaskInfo, error) { + ctx, cancel := t.c.NewRequestContext() + defer cancel() + + req := &adminv2.TaskServiceGetRequest{TaskId: id, Queue: viper.GetString("queue")} + + resp, err := t.c.Client.Adminv2().Task().Get(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get task: %w", err) + } + + return resp.Task, nil +} +func (t *task) List() ([]*adminv2.TaskInfo, error) { + ctx, cancel := t.c.NewRequestContext() + defer cancel() + + req := &adminv2.TaskServiceListRequest{} + if viper.IsSet("queue") { + req.Queue = pointer.Pointer(viper.GetString("queue")) + } + + resp, err := t.c.Client.Adminv2().Task().List(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to list tasks: %w", err) + } + + return resp.Tasks, nil +} + +func (t *task) Create(rq any) (*adminv2.TaskInfo, error) { + panic("unimplemented") +} + +func (t *task) Delete(id string) (*adminv2.TaskInfo, error) { + ctx, cancel := t.c.NewRequestContext() + defer cancel() + + req := &adminv2.TaskServiceDeleteRequest{TaskId: id, Queue: viper.GetString("queue")} + + _, err := t.c.Client.Adminv2().Task().Delete(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get task: %w", err) + } + + return nil, nil +} + +func (t *task) Convert(r *adminv2.TaskInfo) (string, any, any, error) { + panic("unimplemented") +} + +func (t *task) Update(rq any) (*adminv2.TaskInfo, error) { + panic("unimplemented") +} diff --git a/cmd/admin/v2/tenant.go b/cmd/admin/v2/tenant.go new file mode 100644 index 0000000..df4f8c2 --- /dev/null +++ b/cmd/admin/v2/tenant.go @@ -0,0 +1,105 @@ +package v2 + +import ( + "fmt" + + 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/metal-stack/metal-lib/pkg/pointer" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +type tenant struct { + c *config.Config +} + +func newTenantCmd(c *config.Config) *cobra.Command { + w := &tenant{ + c: c, + } + + cmdsConfig := &genericcli.CmdsConfig[*adminv2.TenantServiceCreateRequest, any, *apiv2.Tenant]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), + Singular: "tenant", + Plural: "tenants", + Description: "manage api tenants", + Sorter: sorters.TenantSorter(), + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + ListCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("name", "", "lists only tenants with the given name") + cmd.Flags().String("id", "", "lists only tenant with the given tenant id") + cmd.Flags().String("email", "", "lists only tenant with the given email address") + }, + CreateCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("name", "", "the name of the tenant to create") + cmd.Flags().String("description", "", "the description of the tenant to create") + cmd.Flags().String("email", "", "the email of the tenant to create") + cmd.Flags().String("avatar-url", "", "the avatar url of the tenant to create") + }, + CreateRequestFromCLI: func() (*adminv2.TenantServiceCreateRequest, error) { + return &adminv2.TenantServiceCreateRequest{ + Name: viper.GetString("name"), + Description: pointer.PointerOrNil(viper.GetString("description")), + Email: pointer.PointerOrNil(viper.GetString("email")), + AvatarUrl: pointer.PointerOrNil(viper.GetString("avatar-url")), + }, nil + }, + OnlyCmds: genericcli.OnlyCmds(genericcli.ListCmd, genericcli.CreateCmd), + ValidArgsFn: w.c.Completion.AdminTenantListCompletion, + } + + return genericcli.NewCmds(cmdsConfig) +} + +func (c *tenant) Get(id string) (*apiv2.Tenant, error) { + panic("unimplemented") +} + +func (c *tenant) List() ([]*apiv2.Tenant, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &adminv2.TenantServiceListRequest{ + Name: pointer.PointerOrNil(viper.GetString("name")), + Login: pointer.PointerOrNil(viper.GetString("id")), + Email: pointer.PointerOrNil(viper.GetString("email")), + } + + resp, err := c.c.Client.Adminv2().Tenant().List(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to list tenants: %w", err) + } + + return resp.GetTenants(), nil +} + +func (c *tenant) Create(rq *adminv2.TenantServiceCreateRequest) (*apiv2.Tenant, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().Tenant().Create(ctx, rq) + if err != nil { + return nil, fmt.Errorf("failed to create tenant: %w", err) + } + + return resp.Tenant, nil +} + +func (c *tenant) Delete(id string) (*apiv2.Tenant, error) { + panic("unimplemented") +} + +func (c *tenant) Convert(r *apiv2.Tenant) (string, *adminv2.TenantServiceCreateRequest, any, error) { + panic("unimplemented") +} + +func (c *tenant) Update(rq any) (*apiv2.Tenant, error) { + panic("unimplemented") +} diff --git a/cmd/admin/v1/token.go b/cmd/admin/v2/token.go similarity index 93% rename from cmd/admin/v1/token.go rename to cmd/admin/v2/token.go index 91f0bbd..c835a12 100644 --- a/cmd/admin/v1/token.go +++ b/cmd/admin/v2/token.go @@ -1,4 +1,4 @@ -package v1 +package v2 import ( "fmt" @@ -25,10 +25,10 @@ func newTokenCmd(c *config.Config) *cobra.Command { cmdsConfig := &genericcli.CmdsConfig[any, any, *apiv2.Token]{ BinaryName: config.BinaryName, - GenericCLI: genericcli.NewGenericCLI[any, any, *apiv2.Token](w).WithFS(c.Fs), + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), Singular: "token", Plural: "tokens", - Description: "manage api tokens for accessing the metal-stack.io api", + Description: "manage api tokens", Sorter: sorters.TokenSorter(), DescribePrinter: func() printers.Printer { return c.DescribePrinter }, ListPrinter: func() printers.Printer { return c.ListPrinter }, diff --git a/cmd/admin/v2/vpn.go b/cmd/admin/v2/vpn.go new file mode 100644 index 0000000..07cb1f4 --- /dev/null +++ b/cmd/admin/v2/vpn.go @@ -0,0 +1,115 @@ +package v2 + +import ( + "fmt" + "time" + + 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/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/metal-stack/metal-lib/pkg/pointer" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "google.golang.org/protobuf/types/known/durationpb" +) + +type vpn struct { + c *config.Config +} + +func newVPNCmd(c *config.Config) *cobra.Command { + w := &vpn{ + c: c, + } + + cmdsConfig := &genericcli.CmdsConfig[any, any, *apiv2.VPNNode]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), + Singular: "vpn", + Plural: "vpn", + Description: "manage vpn keys and list nodes connected", + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + ListCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("project", "", "the project for which vpn nodes should be listed") + }, + OnlyCmds: genericcli.OnlyCmds(genericcli.ListCmd), + ValidArgsFn: w.c.Completion.ProjectListCompletion, + } + + authKeyCmd := &cobra.Command{ + Use: "authkey", + Short: "generate a authkey to connect to the vpn", + RunE: func(cmd *cobra.Command, args []string) error { + return w.authKey() + }, + ValidArgsFunction: c.Completion.ProjectListCompletion, + } + authKeyCmd.Flags().String("project", "", "the project for which the authkey should be generated") + authKeyCmd.Flags().Bool("ephemeral", true, "ephemeral defines if the key can only be used once") + authKeyCmd.Flags().Duration("expires", 1*time.Hour, "the duration after the generated key is not valid anymore") + genericcli.Must(authKeyCmd.MarkFlagRequired("project")) + + return genericcli.NewCmds(cmdsConfig, authKeyCmd) +} + +func (v *vpn) authKey() error { + ctx, cancel := v.c.NewRequestContext() + defer cancel() + + req := &adminv2.VPNServiceAuthKeyRequest{ + Project: viper.GetString("project"), + Ephemeral: viper.GetBool("ephemeral"), + Expires: durationpb.New(viper.GetDuration("expires")), + } + + resp, err := v.c.Client.Adminv2().VPN().AuthKey(ctx, req) + if err != nil { + return err + } + + _, _ = fmt.Fprintf(v.c.Out, "authkey: %s ephemeral:%t created at:%s expires at:%s\n", resp.AuthKey, resp.Ephemeral, resp.CreatedAt, resp.ExpiresAt) + _, _ = fmt.Fprintf(v.c.Out, "vpn endpoint: %s\n", resp.Address) + + return nil +} + +func (v *vpn) Get(id string) (*apiv2.VPNNode, error) { + panic("unimplemented") +} + +func (v *vpn) List() ([]*apiv2.VPNNode, error) { + ctx, cancel := v.c.NewRequestContext() + defer cancel() + + req := &adminv2.VPNServiceListNodesRequest{} + + if viper.IsSet("project") { + req.Project = pointer.Pointer(viper.GetString("project")) + } + + resp, err := v.c.Client.Adminv2().VPN().ListNodes(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to list vpn nodes: %w", err) + } + + return resp.Nodes, nil +} + +func (v *vpn) Create(rq any) (*apiv2.VPNNode, error) { + panic("unimplemented") +} + +func (v *vpn) Delete(id string) (*apiv2.VPNNode, error) { + panic("unimplemented") +} + +func (v *vpn) Convert(r *apiv2.VPNNode) (string, any, any, error) { + panic("unimplemented") +} + +func (v *vpn) Update(rq any) (*apiv2.VPNNode, error) { + panic("unimplemented") +} diff --git a/cmd/api/v1/commands.go b/cmd/api/v2/commands.go similarity index 85% rename from cmd/api/v1/commands.go rename to cmd/api/v2/commands.go index 09490f3..e80bbef 100644 --- a/cmd/api/v1/commands.go +++ b/cmd/api/v2/commands.go @@ -1,4 +1,4 @@ -package v1 +package v2 import ( "github.com/metal-stack/cli/cmd/config" @@ -12,8 +12,10 @@ func AddCmds(cmd *cobra.Command, c *config.Config) { cmd.AddCommand(newMethodsCmd(c)) cmd.AddCommand(newNetworkCmd(c)) cmd.AddCommand(newProjectCmd(c)) + cmd.AddCommand(newSizeCmd(c)) cmd.AddCommand(newTenantCmd(c)) cmd.AddCommand(newTokenCmd(c)) cmd.AddCommand(newUserCmd(c)) cmd.AddCommand(newVersionCmd(c)) + cmd.AddCommand(newMachineCmd(c)) } diff --git a/cmd/api/v1/health.go b/cmd/api/v2/health.go similarity index 82% rename from cmd/api/v1/health.go rename to cmd/api/v2/health.go index eac6678..75e037a 100644 --- a/cmd/api/v1/health.go +++ b/cmd/api/v2/health.go @@ -1,9 +1,9 @@ -package v1 +package v2 import ( "fmt" - v1 "github.com/metal-stack/api/go/metalstack/api/v2" + v2 "github.com/metal-stack/api/go/metalstack/api/v2" "github.com/metal-stack/cli/cmd/config" "github.com/spf13/cobra" ) @@ -17,7 +17,7 @@ func newHealthCmd(c *config.Config) *cobra.Command { ctx, cancel := c.NewRequestContext() defer cancel() - resp, err := c.Client.Apiv2().Health().Get(ctx, &v1.HealthServiceGetRequest{}) + resp, err := c.Client.Apiv2().Health().Get(ctx, &v2.HealthServiceGetRequest{}) if err != nil { return fmt.Errorf("failed to get health: %w", err) } diff --git a/cmd/api/v1/image.go b/cmd/api/v2/image.go similarity index 99% rename from cmd/api/v1/image.go rename to cmd/api/v2/image.go index 14da8c9..1275fb2 100644 --- a/cmd/api/v1/image.go +++ b/cmd/api/v2/image.go @@ -1,4 +1,4 @@ -package v1 +package v2 import ( "fmt" diff --git a/cmd/api/v1/ip.go b/cmd/api/v2/ip.go similarity index 99% rename from cmd/api/v1/ip.go rename to cmd/api/v2/ip.go index 2a4201c..0454e86 100644 --- a/cmd/api/v1/ip.go +++ b/cmd/api/v2/ip.go @@ -1,4 +1,4 @@ -package v1 +package v2 import ( "fmt" @@ -29,7 +29,7 @@ func newIPCmd(c *config.Config) *cobra.Command { GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), Singular: "ip", Plural: "ips", - Description: "an ip address of metal-stack.io", + Description: "manage ip addresses", Sorter: sorters.IPSorter(), DescribePrinter: func() printers.Printer { return c.DescribePrinter }, ListPrinter: func() printers.Printer { return c.ListPrinter }, diff --git a/cmd/api/v2/machine.go b/cmd/api/v2/machine.go new file mode 100644 index 0000000..b7b9cbe --- /dev/null +++ b/cmd/api/v2/machine.go @@ -0,0 +1,108 @@ +package v2 + +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/helpers" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/spf13/cobra" +) + +type machine struct { + c *config.Config +} + +func newMachineCmd(c *config.Config) *cobra.Command { + w := &machine{ + c: c, + } + + cmdsConfig := &genericcli.CmdsConfig[*apiv2.MachineServiceCreateRequest, *apiv2.MachineServiceUpdateRequest, *apiv2.Machine]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), + Singular: "machine", + Plural: "machines", + Description: "an machine of metal-stack.io", + Sorter: sorters.MachineSorter(), + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + ListCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().StringP("project", "p", "", "project from where machines should be listed") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + }, + DescribeCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().StringP("project", "p", "", "project of the machine") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + }, + ValidArgsFn: c.Completion.MachineListCompletion, + } + + return genericcli.NewCmds(cmdsConfig) +} + +func (c *machine) updateFromCLI(args []string) (*apiv2.MachineServiceUpdateRequest, error) { + panic("unimplemented") +} + +func (c *machine) Create(rq *apiv2.MachineServiceCreateRequest) (*apiv2.Machine, error) { + panic("unimplemented") +} + +func (c *machine) Delete(id string) (*apiv2.Machine, error) { + panic("unimplemented") +} + +func (c *machine) Get(id string) (*apiv2.Machine, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Apiv2().Machine().Get(ctx, &apiv2.MachineServiceGetRequest{ + Project: c.c.GetProject(), + Uuid: id, + }) + if err != nil { + return nil, err + } + + return resp.Machine, nil +} + +func (c *machine) List() ([]*apiv2.Machine, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Apiv2().Machine().List(ctx, &apiv2.MachineServiceListRequest{ + Project: c.c.GetProject(), + Query: &apiv2.MachineQuery{ + // FIXME implement + }, + }) + if err != nil { + return nil, err + } + + return resp.Machines, nil +} + +func (c *machine) Update(rq *apiv2.MachineServiceUpdateRequest) (*apiv2.Machine, error) { + panic("unimplemented") +} + +func (c *machine) Convert(r *apiv2.Machine) (string, *apiv2.MachineServiceCreateRequest, *apiv2.MachineServiceUpdateRequest, error) { + responseToUpdate, err := c.MachineResponseToUpdate(r) + return helpers.EncodeProject(r.Uuid, r.Allocation.Project), c.MachineResponseToCreate(r), responseToUpdate, err +} + +func (c *machine) MachineResponseToCreate(r *apiv2.Machine) *apiv2.MachineServiceCreateRequest { + return &apiv2.MachineServiceCreateRequest{ + // FIXME + } +} + +func (c *machine) MachineResponseToUpdate(desired *apiv2.Machine) (*apiv2.MachineServiceUpdateRequest, error) { + panic("unimplemented") +} diff --git a/cmd/api/v1/methods.go b/cmd/api/v2/methods.go similarity index 99% rename from cmd/api/v1/methods.go rename to cmd/api/v2/methods.go index ff71c49..bdda922 100644 --- a/cmd/api/v1/methods.go +++ b/cmd/api/v2/methods.go @@ -1,4 +1,4 @@ -package v1 +package v2 import ( "fmt" diff --git a/cmd/api/v1/network.go b/cmd/api/v2/network.go similarity index 99% rename from cmd/api/v1/network.go rename to cmd/api/v2/network.go index 9754f2e..15082a6 100644 --- a/cmd/api/v1/network.go +++ b/cmd/api/v2/network.go @@ -1,4 +1,4 @@ -package v1 +package v2 import ( "github.com/metal-stack/api/go/enum" diff --git a/cmd/api/v1/project.go b/cmd/api/v2/project.go similarity index 99% rename from cmd/api/v1/project.go rename to cmd/api/v2/project.go index 023708e..46fa3f4 100644 --- a/cmd/api/v1/project.go +++ b/cmd/api/v2/project.go @@ -1,4 +1,4 @@ -package v1 +package v2 import ( "fmt" diff --git a/cmd/api/v2/size.go b/cmd/api/v2/size.go new file mode 100644 index 0000000..dc6cd13 --- /dev/null +++ b/cmd/api/v2/size.go @@ -0,0 +1,92 @@ +package v2 + +import ( + "fmt" + + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/cli/cmd/config" + "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/spf13/cobra" + "github.com/spf13/viper" +) + +type size struct { + c *config.Config +} + +func newSizeCmd(c *config.Config) *cobra.Command { + w := &size{ + c: c, + } + + gcli := genericcli.NewGenericCLI(w).WithFS(c.Fs) + + cmdsConfig := &genericcli.CmdsConfig[any, any, *apiv2.Size]{ + BinaryName: config.BinaryName, + GenericCLI: gcli, + Singular: "size", + Plural: "sizes", + Description: "manage sizes which defines the cpu, gpu, memory and storage properties of machines", + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + ValidArgsFn: c.Completion.SizeListCompletion, + OnlyCmds: genericcli.OnlyCmds(genericcli.DescribeCmd, genericcli.ListCmd), + ListCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().StringP("id", "", "", "size id to filter for") + cmd.Flags().StringP("name", "", "", "size name to filter for") + cmd.Flags().StringP("description", "", "", "size description to filter for") + }, + } + + return genericcli.NewCmds(cmdsConfig) +} + +func (c *size) Get(id string) (*apiv2.Size, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &apiv2.SizeServiceGetRequest{Id: id} + + resp, err := c.c.Client.Apiv2().Size().Get(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get size: %w", err) + } + + return resp.Size, nil +} + +func (c *size) List() ([]*apiv2.Size, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &apiv2.SizeServiceListRequest{Query: &apiv2.SizeQuery{ + Id: pointer.PointerOrNil(viper.GetString("id")), + Name: pointer.PointerOrNil(viper.GetString("name")), + Description: pointer.PointerOrNil(viper.GetString("description")), + }} + + resp, err := c.c.Client.Apiv2().Size().List(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get sizes: %w", err) + } + + return resp.Sizes, nil +} + +func (c *size) Create(rq any) (*apiv2.Size, error) { + panic("unimplemented") +} + +func (c *size) Delete(id string) (*apiv2.Size, error) { + panic("unimplemented") +} + +func (t *size) Convert(r *apiv2.Size) (string, any, any, error) { + panic("unimplemented") +} + +func (t *size) Update(rq any) (*apiv2.Size, error) { + panic("unimplemented") +} diff --git a/cmd/api/v1/tenant.go b/cmd/api/v2/tenant.go similarity index 99% rename from cmd/api/v1/tenant.go rename to cmd/api/v2/tenant.go index c26fe7e..30ee3e1 100644 --- a/cmd/api/v1/tenant.go +++ b/cmd/api/v2/tenant.go @@ -1,4 +1,4 @@ -package v1 +package v2 import ( "fmt" @@ -41,7 +41,6 @@ func newTenantCmd(c *config.Config) *cobra.Command { cmd.Flags().String("name", "", "the name of the tenant to create") cmd.Flags().String("description", "", "the description of the tenant to create") cmd.Flags().String("email", "", "the email of the tenant to create") - cmd.Flags().String("phone", "", "the phone number of the tenant to create") cmd.Flags().String("avatar-url", "", "the avatar url of the tenant to create") }, CreateRequestFromCLI: w.createRequestFromCLI, diff --git a/cmd/api/v1/token.go b/cmd/api/v2/token.go similarity index 98% rename from cmd/api/v1/token.go rename to cmd/api/v2/token.go index 7ca3cc7..da097c8 100644 --- a/cmd/api/v1/token.go +++ b/cmd/api/v2/token.go @@ -1,4 +1,4 @@ -package v1 +package v2 import ( "fmt" @@ -30,7 +30,7 @@ func newTokenCmd(c *config.Config) *cobra.Command { GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), Singular: "token", Plural: "tokens", - Description: "manage api tokens for accessing the metal-stack.io api", + Description: "manage api tokens", Sorter: sorters.TokenSorter(), DescribePrinter: func() printers.Printer { return c.DescribePrinter }, ListPrinter: func() printers.Printer { return c.ListPrinter }, diff --git a/cmd/api/v1/user.go b/cmd/api/v2/user.go similarity index 99% rename from cmd/api/v1/user.go rename to cmd/api/v2/user.go index 329944e..abcc820 100644 --- a/cmd/api/v1/user.go +++ b/cmd/api/v2/user.go @@ -1,4 +1,4 @@ -package v1 +package v2 import ( "fmt" diff --git a/cmd/api/v1/version.go b/cmd/api/v2/version.go similarity index 84% rename from cmd/api/v1/version.go rename to cmd/api/v2/version.go index c62219f..cea0450 100644 --- a/cmd/api/v1/version.go +++ b/cmd/api/v2/version.go @@ -1,9 +1,9 @@ -package v1 +package v2 import ( "fmt" - v1 "github.com/metal-stack/api/go/metalstack/api/v2" + v2 "github.com/metal-stack/api/go/metalstack/api/v2" "github.com/metal-stack/cli/cmd/config" "github.com/metal-stack/v" "github.com/spf13/cobra" @@ -11,7 +11,7 @@ import ( type version struct { Client string - Server *v1.Version + Server *v2.Version } func newVersionCmd(c *config.Config) *cobra.Command { @@ -27,7 +27,7 @@ func newVersionCmd(c *config.Config) *cobra.Command { Client: v.V.String(), } - resp, err := c.Client.Apiv2().Version().Get(ctx, &v1.VersionServiceGetRequest{}) + resp, err := c.Client.Apiv2().Version().Get(ctx, &v2.VersionServiceGetRequest{}) if err == nil { v.Server = resp.Version } diff --git a/cmd/common_test.go b/cmd/common_test.go index ce7f960..bd80e37 100644 --- a/cmd/common_test.go +++ b/cmd/common_test.go @@ -8,11 +8,9 @@ import ( "os" "strings" "testing" - "time" "slices" - "bou.ke/monkey" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" apitests "github.com/metal-stack/api/go/tests" @@ -28,12 +26,6 @@ import ( "sigs.k8s.io/yaml" ) -var testTime = time.Date(2022, time.May, 19, 1, 2, 3, 4, time.UTC) - -func init() { - _ = monkey.Patch(time.Now, func() time.Time { return testTime }) -} - type Test[R any] struct { Name string Cmd func(want R) []string diff --git a/cmd/completion/machine.go b/cmd/completion/machine.go new file mode 100644 index 0000000..d54f93d --- /dev/null +++ b/cmd/completion/machine.go @@ -0,0 +1,35 @@ +package completion + +import ( + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/spf13/cobra" +) + +func (c *Completion) MachineListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + req := &apiv2.MachineServiceListRequest{ + Project: c.Project, + } + resp, err := c.Client.Apiv2().Machine().List(c.Ctx, req) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + var names []string + for _, s := range resp.Machines { + names = append(names, s.Uuid) + } + return names, cobra.ShellCompDirectiveNoFileComp +} + +func (c *Completion) BMCCommandListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{ + apiv2.MachineBMCCommand_MACHINE_BMC_COMMAND_ON.String(), + apiv2.MachineBMCCommand_MACHINE_BMC_COMMAND_OFF.String(), + apiv2.MachineBMCCommand_MACHINE_BMC_COMMAND_RESET.String(), + apiv2.MachineBMCCommand_MACHINE_BMC_COMMAND_BOOT_FROM_DISK.String(), + apiv2.MachineBMCCommand_MACHINE_BMC_COMMAND_BOOT_FROM_PXE.String(), + apiv2.MachineBMCCommand_MACHINE_BMC_COMMAND_BOOT_TO_BIOS.String(), + apiv2.MachineBMCCommand_MACHINE_BMC_COMMAND_CYCLE.String(), + apiv2.MachineBMCCommand_MACHINE_BMC_COMMAND_IDENTIFY_LED_OFF.String(), + apiv2.MachineBMCCommand_MACHINE_BMC_COMMAND_IDENTIFY_LED_ON.String(), + }, cobra.ShellCompDirectiveNoFileComp +} diff --git a/cmd/completion/network.go b/cmd/completion/network.go index 8df4e73..962ce64 100644 --- a/cmd/completion/network.go +++ b/cmd/completion/network.go @@ -2,6 +2,7 @@ package completion import ( "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/metal-lib/pkg/pointer" "github.com/spf13/cobra" @@ -68,3 +69,17 @@ func (c *Completion) NetworkAddressFamilyCompletion(cmd *cobra.Command, args []s return afs, cobra.ShellCompDirectiveNoFileComp } + +func (c *Completion) NetworkAdminListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + networks, err := c.Client.Adminv2().Network().List(c.Ctx, &adminv2.NetworkServiceListRequest{}) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + var names []string + for _, s := range networks.Networks { + names = append(names, s.Id+"\t"+pointer.SafeDeref(s.Name)) + } + + return names, cobra.ShellCompDirectiveNoFileComp +} diff --git a/cmd/completion/size.go b/cmd/completion/size.go new file mode 100644 index 0000000..8ebd814 --- /dev/null +++ b/cmd/completion/size.go @@ -0,0 +1,19 @@ +package completion + +import ( + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/spf13/cobra" +) + +func (c *Completion) SizeListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + req := &apiv2.SizeServiceListRequest{} + resp, err := c.Client.Apiv2().Size().List(c.Ctx, req) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + var names []string + for _, s := range resp.Sizes { + names = append(names, s.Id) + } + return names, cobra.ShellCompDirectiveNoFileComp +} diff --git a/cmd/completion/token.go b/cmd/completion/token.go index b7498f5..70cc895 100644 --- a/cmd/completion/token.go +++ b/cmd/completion/token.go @@ -7,6 +7,7 @@ import ( "github.com/spf13/cobra" apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/api/go/permissions" ) func (c *Completion) TokenListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { @@ -66,11 +67,8 @@ func (c *Completion) TokenAdminRoleCompletion(cmd *cobra.Command, args []string, } func (c *Completion) TokenPermissionsCompletionfunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - methods, err := c.Client.Apiv2().Method().TokenScopedList(c.Ctx, &apiv2.MethodServiceTokenScopedListRequest{}) - if err != nil { - return nil, cobra.ShellCompDirectiveError - } + methods := permissions.GetServicePermissions().Methods subject := "" if s, _, ok := strings.Cut(toComplete, "="); ok { subject = s @@ -79,19 +77,17 @@ func (c *Completion) TokenPermissionsCompletionfunc(cmd *cobra.Command, args []s if subject == "" { var perms []string - for _, p := range methods.Permissions { - perms = append(perms, p.Subject) + for p := range methods { + perms = append(perms, p) } return perms, cobra.ShellCompDirectiveNoFileComp } - // FIXME: completion does not work at this point, investigate why - var perms []string - for _, p := range methods.Permissions { - perms = append(perms, p.Methods...) + for p := range methods { + perms = append(perms, p) } return perms, cobra.ShellCompDirectiveDefault diff --git a/cmd/login.go b/cmd/login.go index 739d77d..960a172 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -36,12 +36,12 @@ func newLoginCmd(c *config.Config) *cobra.Command { }, } - loginCmd.Flags().String("provider", "oidc", "the provider used to login with") + loginCmd.Flags().String("provider", "openid-connect", "the provider used to login with") loginCmd.Flags().String("context", "", "the context into which the token gets injected, if not specified it uses the current context or creates a context named default in case there is no current context set") loginCmd.Flags().String("admin-role", "", "operators can use this flag to issue an admin token with the token retrieved from login and store this into context") genericcli.Must(loginCmd.Flags().MarkHidden("admin-role")) - genericcli.Must(loginCmd.RegisterFlagCompletionFunc("provider", cobra.FixedCompletions([]string{"oidc"}, cobra.ShellCompDirectiveNoFileComp))) + genericcli.Must(loginCmd.RegisterFlagCompletionFunc("provider", cobra.FixedCompletions([]string{"openid-connect"}, cobra.ShellCompDirectiveNoFileComp))) genericcli.Must(loginCmd.RegisterFlagCompletionFunc("admin-role", c.Completion.TokenAdminRoleCompletion)) return loginCmd diff --git a/cmd/logout.go b/cmd/logout.go index 19788e2..1cd4f5b 100644 --- a/cmd/logout.go +++ b/cmd/logout.go @@ -33,10 +33,10 @@ func newLogoutCmd(c *config.Config) *cobra.Command { }, } - logoutCmd.Flags().String("provider", "oidc", "the provider used to logout with") + logoutCmd.Flags().String("provider", "openid-connect", "the provider used to logout with") logoutCmd.Flags().String("context-name", "", "the context into which the token gets injected, if not specified it uses the current context or creates a context named default in case there is no current context set") - genericcli.Must(logoutCmd.RegisterFlagCompletionFunc("provider", cobra.FixedCompletions([]string{"oidc"}, cobra.ShellCompDirectiveNoFileComp))) + genericcli.Must(logoutCmd.RegisterFlagCompletionFunc("provider", cobra.FixedCompletions([]string{"openid-connect"}, cobra.ShellCompDirectiveNoFileComp))) return logoutCmd } diff --git a/cmd/printers.go b/cmd/printers.go index 3668f2c..2e27033 100644 --- a/cmd/printers.go +++ b/cmd/printers.go @@ -32,6 +32,7 @@ func newPrinterFromCLI(out io.Writer) (printers.Printer, error) { } tablePrinter := printers.NewTablePrinter(cfg).WithOut(out) tp.SetPrinter(tablePrinter) + tp.SetLastEventErrorThreshold(viper.GetDuration("last-event-error-threshold")) printer = tablePrinter case "template": printer = printers.NewTemplatePrinter(viper.GetString("template")).WithOut(out) diff --git a/cmd/root.go b/cmd/root.go index 9bb0ce1..f561d97 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,14 +1,15 @@ package cmd import ( + "context" "log/slog" "os" client "github.com/metal-stack/api/go/client" "github.com/metal-stack/metal-lib/pkg/genericcli" - adminv2 "github.com/metal-stack/cli/cmd/admin/v1" - apiv2 "github.com/metal-stack/cli/cmd/api/v1" + adminv2 "github.com/metal-stack/cli/cmd/admin/v2" + apiv2 "github.com/metal-stack/cli/cmd/api/v2" "github.com/metal-stack/cli/cmd/completion" "github.com/metal-stack/cli/cmd/config" @@ -16,7 +17,6 @@ import ( "github.com/spf13/cobra" "github.com/spf13/cobra/doc" "github.com/spf13/viper" - "golang.org/x/net/context" ) func Execute() { diff --git a/cmd/sorters/machine.go b/cmd/sorters/machine.go new file mode 100644 index 0000000..e1ba415 --- /dev/null +++ b/cmd/sorters/machine.go @@ -0,0 +1,33 @@ +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 MachineSorter() *multisort.Sorter[*apiv2.Machine] { + return multisort.New(multisort.FieldMap[*apiv2.Machine]{ + "partition": func(a, b *apiv2.Machine, descending bool) multisort.CompareResult { + return multisort.Compare(a.Partition.Id, b.Partition.Id, descending) + }, + "size": func(a, b *apiv2.Machine, descending bool) multisort.CompareResult { + return multisort.Compare(a.Size.Id, b.Size.Id, descending) + }, + "uuid": func(a, b *apiv2.Machine, descending bool) multisort.CompareResult { + return multisort.Compare(a.Uuid, b.Uuid, descending) + }, + "image": func(a, b *apiv2.Machine, descending bool) multisort.CompareResult { + return multisort.Compare(pointer.SafeDeref(a.Allocation).Image.Id, pointer.SafeDeref(b.Allocation).Image.Id, descending) + }, + "rack": func(a, b *apiv2.Machine, descending bool) multisort.CompareResult { + return multisort.Compare(a.Rack, b.Rack, descending) + }, + "project": func(a, b *apiv2.Machine, descending bool) multisort.CompareResult { + return multisort.Compare(pointer.SafeDeref(a.Allocation).Project, pointer.SafeDeref(b.Allocation).Project, descending) + }, + "age": func(a, b *apiv2.Machine, descending bool) multisort.CompareResult { + return multisort.Compare(pointer.SafeDeref(a.Allocation).Meta.CreatedAt.AsTime().Unix(), pointer.SafeDeref(b.Allocation).Meta.CreatedAt.AsTime().Unix(), descending) + }, + }, multisort.Keys{{ID: "uuid"}, {ID: "size"}, {ID: "partition"}}) +} diff --git a/cmd/tableprinters/common.go b/cmd/tableprinters/common.go index 9fd32a3..77b97cc 100644 --- a/cmd/tableprinters/common.go +++ b/cmd/tableprinters/common.go @@ -6,6 +6,7 @@ import ( "strings" "time" + 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/metal-lib/pkg/genericcli/printers" @@ -22,13 +23,18 @@ const ( ) type TablePrinter struct { - t *printers.TablePrinter + t *printers.TablePrinter + lastEventErrorThreshold time.Duration } func New() *TablePrinter { return &TablePrinter{} } +func (t *TablePrinter) SetLastEventErrorThreshold(threshold time.Duration) { + t.lastEventErrorThreshold = threshold +} + func (t *TablePrinter) SetPrinter(printer *printers.TablePrinter) { t.t = printer } @@ -49,6 +55,16 @@ func (t *TablePrinter) ToHeaderAndRows(data any, wide bool) ([]string, [][]strin case []*apiv2.Image: return t.ImageTable(d, wide) + case *apiv2.Size: + return t.SizeTable(pointer.WrapInSlice(d), wide) + case []*apiv2.Size: + return t.SizeTable(d, wide) + + case *apiv2.Machine: + return t.MachineTable(pointer.WrapInSlice(d), wide) + case []*apiv2.Machine: + return t.MachineTable(d, wide) + case *apiv2.Network: return t.NetworkTable(pointer.WrapInSlice(d), wide) case []*apiv2.Network: @@ -67,11 +83,21 @@ func (t *TablePrinter) ToHeaderAndRows(data any, wide bool) ([]string, [][]strin case []*apiv2.ProjectMember: return t.ProjectMemberTable(d, wide) + case *adminv2.TaskInfo: + return t.TaskTable(pointer.WrapInSlice(d), wide) + case []*adminv2.TaskInfo: + return t.TaskTable(d, wide) + case *apiv2.Token: return t.TokenTable(pointer.WrapInSlice(d), wide) case []*apiv2.Token: return t.TokenTable(d, wide) + case *apiv2.VPNNode: + return t.VPNTable(pointer.WrapInSlice(d), wide) + case []*apiv2.VPNNode: + return t.VPNTable(d, wide) + case *apiv2.Tenant: return t.TenantTable(pointer.WrapInSlice(d), wide) case []*apiv2.Tenant: diff --git a/cmd/tableprinters/machine.go b/cmd/tableprinters/machine.go new file mode 100644 index 0000000..569cbf1 --- /dev/null +++ b/cmd/tableprinters/machine.go @@ -0,0 +1,165 @@ +package tableprinters + +import ( + "fmt" + "strings" + "time" + + "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/cli/pkg/helpers" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/pointer" +) + +func (t *TablePrinter) MachineTable(data []*apiv2.Machine, wide bool) ([]string, [][]string, error) { + + var ( + rows [][]string + header = []string{"ID", "", "Last Event", "When", "Age", "Hostname", "Project", "Size", "Image", "Partition", "Rack"} + ) + + if wide { + header = []string{"ID", "Last Event", "When", "Age", "Description", "Name", "Hostname", "Project", "IPs", "Size", "Image", "Partition", "Rack", "Started", "Tags", "Lock/Reserve"} + } + for _, machine := range data { + machineID := machine.Uuid + + if machine.Status != nil && machine.Status.LedState != nil && machine.Status.LedState.Value == "LED-ON" { + blue := color.New(color.FgBlue).SprintFunc() + machineID = blue(machineID) + } + + alloc := pointer.SafeDeref(machine.Allocation) + sizeID := pointer.SafeDeref(machine.Size).Id + partitionID := pointer.SafeDeref(machine.Partition).Id + project := alloc.Project + name := alloc.Name + desc := alloc.Description + hostname := alloc.Hostname + image := pointer.SafeDeref(pointer.SafeDeref(alloc.Image).Name) + + rack := machine.Rack + + truncatedHostname := genericcli.TruncateEnd(hostname, 30) + + var nwIPs []string + for _, nw := range alloc.Networks { + nwIPs = append(nwIPs, nw.Ips...) + } + ips := strings.Join(nwIPs, "\n") + + started := "" + age := "" + + if alloc.Meta != nil && alloc.Meta.CreatedAt != nil && !alloc.Meta.CreatedAt.AsTime().IsZero() { + started = alloc.Meta.CreatedAt.AsTime().Format(time.RFC3339) + age = humanizeDuration(time.Since(alloc.Meta.CreatedAt.AsTime())) + } + tags := "" + if machine.Meta.Labels != nil && len(machine.Meta.Labels.Labels) > 0 { + var labels []string + for k, v := range machine.Meta.Labels.Labels { + labels = append(labels, k+"="+v) + } + tags = strings.Join(labels, ",") + } + + reserved := "" + if machine.Status.Condition != nil { + stateString, err := enum.GetStringValue(machine.Status.Condition.State) + if err != nil { + return nil, nil, err + } + reserved = fmt.Sprintf("%s:%s", *stateString, machine.Status.Condition.Description) + } + + lastEvent := "" + when := "" + if len(machine.RecentProvisioningEvents.Events) > 0 { + since := time.Since(machine.RecentProvisioningEvents.LastEventTime.AsTime()) + when = humanizeDuration(since) + lastEventString, err := enum.GetStringValue(machine.RecentProvisioningEvents.Events[0].Event) + if err != nil { + return nil, nil, err + } + lastEvent = *lastEventString + } + + emojis, _ := t.getMachineStatusEmojis(machine.Status.Liveliness, machine.RecentProvisioningEvents, machine.Status.Condition.State, alloc.Vpn) + + if wide { + rows = append(rows, []string{machineID, lastEvent, when, age, desc, name, hostname, project, ips, sizeID, image, partitionID, rack, started, tags, reserved}) + } else { + rows = append(rows, []string{machineID, emojis, lastEvent, when, age, truncatedHostname, project, sizeID, image, partitionID, rack}) + } + } + + t.t.DisableAutoWrap(false) + + return header, rows, nil +} + +func (t *TablePrinter) getMachineStatusEmojis(liveliness apiv2.MachineLiveliness, events *apiv2.MachineRecentProvisioningEvents, state apiv2.MachineState, vpn *apiv2.MachineVPN) (string, string) { + var ( + emojis []string + wide []string + livelinessString *string + err error + ) + livelinessString, err = enum.GetStringValue(liveliness) + if err != nil { + livelinessString = pointer.Pointer("unknown") + } + + switch l := liveliness; l { + case apiv2.MachineLiveliness_MACHINE_LIVELINESS_ALIVE: + // noop + case apiv2.MachineLiveliness_MACHINE_LIVELINESS_DEAD: + emojis = append(emojis, helpers.Skull) + wide = append(wide, *livelinessString) + case apiv2.MachineLiveliness_MACHINE_LIVELINESS_UNKNOWN: + emojis = append(emojis, helpers.Question) + wide = append(wide, *livelinessString) + default: + emojis = append(emojis, helpers.Question) + wide = append(wide, *livelinessString) + } + + switch state { + case apiv2.MachineState_MACHINE_STATE_AVAILABLE: + // noop + case apiv2.MachineState_MACHINE_STATE_LOCKED: + emojis = append(emojis, helpers.Lock) + wide = append(wide, "Locked") + case apiv2.MachineState_MACHINE_STATE_RESERVED: + emojis = append(emojis, helpers.Bark) + wide = append(wide, "Reserved") + } + + if events != nil { + switch events.State { + case apiv2.MachineProvisioningEventState_MACHINE_PROVISIONING_EVENT_STATE_FAILED_RECLAIM: + emojis = append(emojis, helpers.Ambulance) + wide = append(wide, "FailedReclaim") + case apiv2.MachineProvisioningEventState_MACHINE_PROVISIONING_EVENT_STATE_CRASHLOOP: + emojis = append(emojis, helpers.Loop) + wide = append(wide, "CrashLoop") + + } + + if events.LastErrorEvent != nil && time.Since(events.LastErrorEvent.Time.AsTime()) < t.lastEventErrorThreshold { + emojis = append(emojis, helpers.Exclamation) + wide = append(wide, "LastEventErrors") + } + + } + + if vpn != nil && vpn.Connected { + emojis = append(emojis, helpers.VPN) + wide = append(wide, "VPN") + } + + return strings.Join(emojis, nbr), strings.Join(wide, ", ") +} diff --git a/cmd/tableprinters/size.go b/cmd/tableprinters/size.go new file mode 100644 index 0000000..65f72c2 --- /dev/null +++ b/cmd/tableprinters/size.go @@ -0,0 +1,45 @@ +package tableprinters + +import ( + "fmt" + + "github.com/dustin/go-humanize" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/metal-lib/pkg/pointer" +) + +func (t *TablePrinter) SizeTable(data []*apiv2.Size, wide bool) ([]string, [][]string, error) { + var ( + rows [][]string + header = []string{"ID", "Name", "Description", "CPU Range", "Memory Range", "Storage Range", "GPU Range"} + ) + + for _, size := range data { + var ( + cpu string + memory string + storage string + gpu string + ) + + for _, c := range size.Constraints { + switch c.Type { + case apiv2.SizeConstraintType_SIZE_CONSTRAINT_TYPE_CORES: + cpu = fmt.Sprintf("%d - %d", c.Min, c.Max) + case apiv2.SizeConstraintType_SIZE_CONSTRAINT_TYPE_MEMORY: + memory = fmt.Sprintf("%s - %s", humanize.Bytes(uint64(c.Min)), humanize.Bytes(uint64(c.Max))) //nolint:gosec + case apiv2.SizeConstraintType_SIZE_CONSTRAINT_TYPE_STORAGE: + storage = fmt.Sprintf("%s - %s", humanize.Bytes(uint64(c.Min)), humanize.Bytes(uint64(c.Max))) //nolint:gosec + case apiv2.SizeConstraintType_SIZE_CONSTRAINT_TYPE_GPU: + gpu = fmt.Sprintf("%s: %d - %d", pointer.SafeDeref(c.Identifier), c.Min, c.Max) + } + + } + + rows = append(rows, []string{size.Id, pointer.SafeDeref(size.Name), pointer.SafeDeref(size.Description), cpu, memory, storage, gpu}) + } + + t.t.DisableAutoWrap(false) + + return header, rows, nil +} diff --git a/cmd/tableprinters/task.go b/cmd/tableprinters/task.go new file mode 100644 index 0000000..f4d4ed1 --- /dev/null +++ b/cmd/tableprinters/task.go @@ -0,0 +1,51 @@ +package tableprinters + +import ( + "time" + + "github.com/google/uuid" + "github.com/metal-stack/api/go/enum" + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + "github.com/metal-stack/metal-lib/pkg/pointer" +) + +func (t *TablePrinter) TaskTable(data []*adminv2.TaskInfo, wide bool) ([]string, [][]string, error) { + var ( + rows [][]string + ) + header := []string{"ID", "Queue", "When", "Type", "State"} + + if wide { + header = []string{"ID", "Queue", "When", "Type", "State", "Issued At", "Payload", "Result"} + } + for _, task := range data { + id := task.Id + queue := task.Queue + typeString := task.Type + state, err := enum.GetStringValue(task.State) + if err != nil { + state = pointer.Pointer("unknown") + } + payload := string(task.Payload) + result := string(task.Result) + + parsed, err := uuid.Parse(id) + if err != nil { + return nil, nil, err + } + + sec, nano := parsed.Time().UnixTime() + issuedAt := time.Unix(sec, nano) + when := humanizeDuration(time.Since(issuedAt)) + + if wide { + rows = append(rows, []string{id, queue, when, typeString, *state, issuedAt.String(), payload, result}) + } else { + rows = append(rows, []string{id, queue, when, typeString, *state}) + } + } + + t.t.DisableAutoWrap(false) + + return header, rows, nil +} diff --git a/cmd/tableprinters/vpn.go b/cmd/tableprinters/vpn.go new file mode 100644 index 0000000..0de30db --- /dev/null +++ b/cmd/tableprinters/vpn.go @@ -0,0 +1,35 @@ +package tableprinters + +import ( + "fmt" + "strings" + "time" + + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" +) + +func (t *TablePrinter) VPNTable(data []*apiv2.VPNNode, _ bool) ([]string, [][]string, error) { + var ( + rows [][]string + ) + header := []string{"ID", "Name", "Project", "IPs", "Last Seed"} + + for _, node := range data { + lastSeen := node.LastSeen.AsTime().Format(time.DateTime + " MST") + ips := strings.Join(node.IpAddresses, ",") + + row := []string{ + fmt.Sprintf("%d", node.Id), + node.Name, + node.Project, + ips, + lastSeen, + } + + rows = append(rows, row) + } + + t.t.DisableAutoWrap(false) + + return header, rows, nil +} diff --git a/docs/metalctlv2.md b/docs/metalctlv2.md index 965a721..ecd1f5b 100644 --- a/docs/metalctlv2.md +++ b/docs/metalctlv2.md @@ -26,9 +26,11 @@ cli for managing entities in metal-stack * [metalctlv2 ip](metalctlv2_ip.md) - manage ip entities * [metalctlv2 login](metalctlv2_login.md) - login * [metalctlv2 logout](metalctlv2_logout.md) - logout +* [metalctlv2 machine](metalctlv2_machine.md) - manage machine entities * [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 size](metalctlv2_size.md) - manage size entities * [metalctlv2 tenant](metalctlv2_tenant.md) - manage tenant entities * [metalctlv2 token](metalctlv2_token.md) - manage token entities * [metalctlv2 user](metalctlv2_user.md) - manage user entities diff --git a/docs/metalctlv2_ip.md b/docs/metalctlv2_ip.md index 401c938..39ddc20 100644 --- a/docs/metalctlv2_ip.md +++ b/docs/metalctlv2_ip.md @@ -4,7 +4,7 @@ manage ip entities ### Synopsis -an ip address of metal-stack.io +manage ip addresses ### Options diff --git a/docs/metalctlv2_login.md b/docs/metalctlv2_login.md index 7315df6..28e50a8 100644 --- a/docs/metalctlv2_login.md +++ b/docs/metalctlv2_login.md @@ -11,7 +11,7 @@ metalctlv2 login [flags] ``` --context string the context into which the token gets injected, if not specified it uses the current context or creates a context named default in case there is no current context set -h, --help help for login - --provider string the provider used to login with (default "oidc") + --provider string the provider used to login with (default "openid-connect") ``` ### Options inherited from parent commands diff --git a/docs/metalctlv2_logout.md b/docs/metalctlv2_logout.md index ee3a40b..bc7f032 100644 --- a/docs/metalctlv2_logout.md +++ b/docs/metalctlv2_logout.md @@ -11,7 +11,7 @@ metalctlv2 logout [flags] ``` --context-name string the context into which the token gets injected, if not specified it uses the current context or creates a context named default in case there is no current context set -h, --help help for logout - --provider string the provider used to logout with (default "oidc") + --provider string the provider used to logout with (default "openid-connect") ``` ### Options inherited from parent commands diff --git a/docs/metalctlv2_machine.md b/docs/metalctlv2_machine.md new file mode 100644 index 0000000..cf12864 --- /dev/null +++ b/docs/metalctlv2_machine.md @@ -0,0 +1,38 @@ +## metalctlv2 machine + +manage machine entities + +### Synopsis + +an machine of metal-stack.io + +### Options + +``` + -h, --help help for machine +``` + +### 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 machine apply](metalctlv2_machine_apply.md) - applies one or more machines from a given file +* [metalctlv2 machine create](metalctlv2_machine_create.md) - creates the machine +* [metalctlv2 machine delete](metalctlv2_machine_delete.md) - deletes the machine +* [metalctlv2 machine describe](metalctlv2_machine_describe.md) - describes the machine +* [metalctlv2 machine edit](metalctlv2_machine_edit.md) - edit the machine through an editor and update +* [metalctlv2 machine list](metalctlv2_machine_list.md) - list all machines +* [metalctlv2 machine update](metalctlv2_machine_update.md) - updates the machine + diff --git a/docs/metalctlv2_machine_apply.md b/docs/metalctlv2_machine_apply.md new file mode 100644 index 0000000..040e94d --- /dev/null +++ b/docs/metalctlv2_machine_apply.md @@ -0,0 +1,46 @@ +## metalctlv2 machine apply + +applies one or more machines from a given file + +``` +metalctlv2 machine 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 machine describe machine-1 -o yaml > machine.yaml + $ vi machine.yaml + $ # either via stdin + $ cat machine.yaml | metalctlv2 machine apply -f - + $ # or via file + $ metalctlv2 machine apply -f machine.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 machine](metalctlv2_machine.md) - manage machine entities + diff --git a/docs/metalctlv2_machine_create.md b/docs/metalctlv2_machine_create.md new file mode 100644 index 0000000..8381bf4 --- /dev/null +++ b/docs/metalctlv2_machine_create.md @@ -0,0 +1,46 @@ +## metalctlv2 machine create + +creates the machine + +``` +metalctlv2 machine create [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 machine describe machine-1 -o yaml > machine.yaml + $ vi machine.yaml + $ # either via stdin + $ cat machine.yaml | metalctlv2 machine create -f - + $ # or via file + $ metalctlv2 machine create -f machine.yaml + + the file can also contain multiple documents and perform a bulk operation. + + -h, --help help for create + --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 machine](metalctlv2_machine.md) - manage machine entities + diff --git a/docs/metalctlv2_machine_delete.md b/docs/metalctlv2_machine_delete.md new file mode 100644 index 0000000..b9aa286 --- /dev/null +++ b/docs/metalctlv2_machine_delete.md @@ -0,0 +1,46 @@ +## metalctlv2 machine delete + +deletes the machine + +``` +metalctlv2 machine 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 machine describe machine-1 -o yaml > machine.yaml + $ vi machine.yaml + $ # either via stdin + $ cat machine.yaml | metalctlv2 machine delete -f - + $ # or via file + $ metalctlv2 machine delete -f machine.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 machine](metalctlv2_machine.md) - manage machine entities + diff --git a/docs/metalctlv2_machine_describe.md b/docs/metalctlv2_machine_describe.md new file mode 100644 index 0000000..e590112 --- /dev/null +++ b/docs/metalctlv2_machine_describe.md @@ -0,0 +1,32 @@ +## metalctlv2 machine describe + +describes the machine + +``` +metalctlv2 machine describe [flags] +``` + +### Options + +``` + -h, --help help for describe + -p, --project string project of the machine +``` + +### 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 machine](metalctlv2_machine.md) - manage machine entities + diff --git a/docs/metalctlv2_machine_edit.md b/docs/metalctlv2_machine_edit.md new file mode 100644 index 0000000..08d19fe --- /dev/null +++ b/docs/metalctlv2_machine_edit.md @@ -0,0 +1,31 @@ +## metalctlv2 machine edit + +edit the machine through an editor and update + +``` +metalctlv2 machine 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 machine](metalctlv2_machine.md) - manage machine entities + diff --git a/docs/metalctlv2_machine_list.md b/docs/metalctlv2_machine_list.md new file mode 100644 index 0000000..6ec184d --- /dev/null +++ b/docs/metalctlv2_machine_list.md @@ -0,0 +1,33 @@ +## metalctlv2 machine list + +list all machines + +``` +metalctlv2 machine list [flags] +``` + +### Options + +``` + -h, --help help for list + -p, --project string project from where machines should be listed + --sort-by strings sort by (comma separated) column(s), sort direction can be changed by appending :asc or :desc behind the column identifier. possible values: age|image|partition|project|rack|size|uuid +``` + +### 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 machine](metalctlv2_machine.md) - manage machine entities + diff --git a/docs/metalctlv2_machine_update.md b/docs/metalctlv2_machine_update.md new file mode 100644 index 0000000..0d23675 --- /dev/null +++ b/docs/metalctlv2_machine_update.md @@ -0,0 +1,46 @@ +## metalctlv2 machine update + +updates the machine + +``` +metalctlv2 machine 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. + -f, --file string filename of the create or update request in yaml format, or - for stdin. + + Example: + $ metalctlv2 machine describe machine-1 -o yaml > machine.yaml + $ vi machine.yaml + $ # either via stdin + $ cat machine.yaml | metalctlv2 machine update -f - + $ # or via file + $ metalctlv2 machine update -f machine.yaml + + the file can also contain multiple documents and perform a bulk operation. + + -h, --help help for update + --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 machine](metalctlv2_machine.md) - manage machine entities + diff --git a/docs/metalctlv2_size.md b/docs/metalctlv2_size.md new file mode 100644 index 0000000..a5f4c21 --- /dev/null +++ b/docs/metalctlv2_size.md @@ -0,0 +1,33 @@ +## metalctlv2 size + +manage size entities + +### Synopsis + +manage sizes which defines the cpu, gpu, memory and storage properties of machines + +### Options + +``` + -h, --help help for size +``` + +### 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 size describe](metalctlv2_size_describe.md) - describes the size +* [metalctlv2 size list](metalctlv2_size_list.md) - list all sizes + diff --git a/docs/metalctlv2_size_describe.md b/docs/metalctlv2_size_describe.md new file mode 100644 index 0000000..98d7d0f --- /dev/null +++ b/docs/metalctlv2_size_describe.md @@ -0,0 +1,31 @@ +## metalctlv2 size describe + +describes the size + +``` +metalctlv2 size 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 size](metalctlv2_size.md) - manage size entities + diff --git a/docs/metalctlv2_size_list.md b/docs/metalctlv2_size_list.md new file mode 100644 index 0000000..cd5d5cd --- /dev/null +++ b/docs/metalctlv2_size_list.md @@ -0,0 +1,34 @@ +## metalctlv2 size list + +list all sizes + +``` +metalctlv2 size list [flags] +``` + +### Options + +``` + --description string size description to filter for + -h, --help help for list + --id string size id to filter for + --name string size name to filter for +``` + +### 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 size](metalctlv2_size.md) - manage size entities + diff --git a/docs/metalctlv2_tenant_create.md b/docs/metalctlv2_tenant_create.md index 48b7a0e..86f7da7 100644 --- a/docs/metalctlv2_tenant_create.md +++ b/docs/metalctlv2_tenant_create.md @@ -27,7 +27,6 @@ metalctlv2 tenant create [flags] -h, --help help for create --name string the name of the tenant to create - --phone string the phone number of the tenant to create --skip-security-prompts skips security prompt for bulk operations --timestamps when used with --file (bulk operation): prints timestamps in-between the operations ``` diff --git a/docs/metalctlv2_token.md b/docs/metalctlv2_token.md index 01394f8..2ebe76a 100644 --- a/docs/metalctlv2_token.md +++ b/docs/metalctlv2_token.md @@ -4,7 +4,7 @@ manage token entities ### Synopsis -manage api tokens for accessing the metal-stack.io api +manage api tokens ### Options diff --git a/go.mod b/go.mod index 1e25ad5..6c2d7b3 100644 --- a/go.mod +++ b/go.mod @@ -1,68 +1,68 @@ module github.com/metal-stack/cli -go 1.25 +go 1.25.0 require ( - bou.ke/monkey v1.0.2 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.31 + github.com/google/uuid v1.6.0 + github.com/metal-stack/api v0.0.45 github.com/metal-stack/metal-lib v0.23.5 github.com/metal-stack/v v1.0.3 github.com/spf13/afero v1.15.0 - github.com/spf13/cobra v1.10.1 + github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 - golang.org/x/net v0.46.0 - google.golang.org/grpc v1.76.0 - google.golang.org/protobuf v1.36.10 + google.golang.org/grpc v1.78.0 + google.golang.org/protobuf v1.36.11 sigs.k8s.io/yaml v1.6.0 ) require ( - buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.10-20250912141014-52f32327d4b0.1 // indirect + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20251209175733-2a1774d88802.1 // indirect connectrpc.com/connect v1.19.1 // indirect - github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/clipperhouse/uax29/v2 v2.2.0 // indirect + github.com/clipperhouse/displaywidth v0.9.0 // indirect + github.com/clipperhouse/stringish v0.1.1 // indirect + github.com/clipperhouse/uax29/v2 v2.5.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/go-openapi/errors v0.22.3 // indirect - github.com/go-openapi/strfmt v0.24.0 // indirect + github.com/go-openapi/errors v0.22.6 // indirect + github.com/go-openapi/strfmt v0.25.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/go-viper/mapstructure/v2 v2.4.0 // indirect - github.com/goccy/go-yaml v1.18.0 // indirect - github.com/golang-jwt/jwt/v5 v5.3.0 // indirect - github.com/google/uuid v1.6.0 // indirect + github.com/go-viper/mapstructure/v2 v2.5.0 // indirect + github.com/goccy/go-yaml v1.19.2 // indirect + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/klauspost/compress v1.18.0 // indirect - github.com/klauspost/connect-compress/v2 v2.0.0 // indirect + github.com/klauspost/compress v1.18.3 // indirect + github.com/klauspost/connect-compress/v2 v2.1.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.19 // indirect + github.com/minio/minlz v1.0.1 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect - github.com/olekukonko/errors v1.1.0 // indirect - github.com/olekukonko/ll v0.1.2 // indirect - github.com/olekukonko/tablewriter v1.1.0 // indirect + github.com/olekukonko/errors v1.2.0 // indirect + github.com/olekukonko/ll v0.1.4 // indirect + github.com/olekukonko/tablewriter v1.1.3 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.12.0 // indirect github.com/spf13/cast v1.10.0 // indirect github.com/stretchr/objx v0.5.3 // indirect github.com/subosito/gotenv v1.6.0 // indirect - go.mongodb.org/mongo-driver v1.17.4 // indirect + go.mongodb.org/mongo-driver v1.17.8 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/sys v0.37.0 // indirect - golang.org/x/text v0.30.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apimachinery v0.34.1 // indirect + k8s.io/apimachinery v0.35.0 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect ) diff --git a/go.sum b/go.sum index 14826f0..1c08196 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,15 @@ -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.10-20250912141014-52f32327d4b0.1 h1:31on4W/yPcV4nZHL4+UCiCvLPsMqe/vJcNg8Rci0scc= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.10-20250912141014-52f32327d4b0.1/go.mod h1:fUl8CEN/6ZAMk6bP8ahBJPUJw7rbp+j4x+wCcYi2IG4= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20251209175733-2a1774d88802.1 h1:j9yeqTWEFrtimt8Nng2MIeRrpoCvQzM9/g25XTvqUGg= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20251209175733-2a1774d88802.1/go.mod h1:tvtbpgaVXZX4g6Pn+AnzFycuRK3MOz5HJfEGeEllXYM= connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14= connectrpc.com/connect v1.19.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w= -github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= -github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= -github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY= -github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= +github.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA= +github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA= +github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= +github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= +github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U= +github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -23,18 +23,20 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/go-openapi/errors v0.22.3 h1:k6Hxa5Jg1TUyZnOwV2Lh81j8ayNw5VVYLvKrp4zFKFs= -github.com/go-openapi/errors v0.22.3/go.mod h1:+WvbaBBULWCOna//9B9TbLNGSFOfF8lY9dw4hGiEiKQ= -github.com/go-openapi/strfmt v0.24.0 h1:dDsopqbI3wrrlIzeXRbqMihRNnjzGC+ez4NQaAAJLuc= -github.com/go-openapi/strfmt v0.24.0/go.mod h1:Lnn1Bk9rZjXxU9VMADbEEOo7D7CDyKGLsSKekhFr7s4= +github.com/go-openapi/errors v0.22.6 h1:eDxcf89O8odEnohIXwEjY1IB4ph5vmbUsBMsFNwXWPo= +github.com/go-openapi/errors v0.22.6/go.mod h1:z9S8ASTUqx7+CP1Q8dD8ewGH/1JWFFLX/2PmAYNQLgk= +github.com/go-openapi/strfmt v0.25.0 h1:7R0RX7mbKLa9EYCTHRcCuIPcaqlyQiWNPTXwClK0saQ= +github.com/go-openapi/strfmt v0.25.0/go.mod h1:nNXct7OzbwrMY9+5tLX4I21pzcmE6ccMGXl3jFdPfn8= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= 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.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= -github.com/go-viper/mapstructure/v2 v2.4.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-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= -github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= +github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= +github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= 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.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -43,10 +45,10 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= -github.com/klauspost/connect-compress/v2 v2.0.0 h1:L7TVsLa6Oo9Hkkb6r3DwSrhBbcWlXjneqBj7fCRXviU= -github.com/klauspost/connect-compress/v2 v2.0.0/go.mod h1:604CD9JSAjGqtVzCM4SRgM/9TFTkWBcp+2wlQfGyJ6c= +github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw= +github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/klauspost/connect-compress/v2 v2.1.1 h1:ycZNp4rWOZBodVE2Ls5AzK4aHkyK+GteEfzRZgKNs+c= +github.com/klauspost/connect-compress/v2 v2.1.1/go.mod h1:9oilsPHJMzGKkjafSBk9J7iVo4mO+dw0G0KSdVpnlVE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -57,30 +59,30 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= -github.com/metal-stack/api v0.0.31 h1:ivgaqYuWFrGO4IZS/9ViwkuV1AzytZlji6zgDnANo+s= -github.com/metal-stack/api v0.0.31/go.mod h1:LcJkrJPJRdLPFaeCiWy5tL0dJXU74kq+vFe5uc/Cm9U= +github.com/metal-stack/api v0.0.45 h1:Jein21w+8jEuPt98arB/lbUXzPc+RXEGZPc1HlM1KFw= +github.com/metal-stack/api v0.0.45/go.mod h1:kqAqRS5/5pKOKw4yTbqF/u3h2WCrtvYhKp/Bt2ExZJM= github.com/metal-stack/metal-lib v0.23.5 h1:ozrkB3DNr3Cqn8nkBvmzc/KKpYqC1j1mv2OVOj8i7Ac= github.com/metal-stack/metal-lib v0.23.5/go.mod h1:7uyHIrE19dkLwCZyeh2jmd7IEq5pEpzrzUGLoMN1eqY= 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/minio/minlz v1.0.1 h1:OUZUzXcib8diiX+JYxyRLIdomyZYzHct6EShOKtQY2A= +github.com/minio/minlz v1.0.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0= -github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM= -github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= -github.com/olekukonko/ll v0.1.2 h1:lkg/k/9mlsy0SxO5aC+WEpbdT5K83ddnNhAepz7TQc0= -github.com/olekukonko/ll v0.1.2/go.mod h1:b52bVQRRPObe+yyBl0TxNfhesL0nedD4Cht0/zx55Ew= -github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY= -github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo= +github.com/olekukonko/errors v1.2.0 h1:10Zcn4GeV59t/EGqJc8fUjtFT/FuUh5bTMzZ1XwmCRo= +github.com/olekukonko/errors v1.2.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= +github.com/olekukonko/ll v0.1.4 h1:QcDaO9quz213xqHZr0gElOcYeOSnFeq7HTQ9Wu4O1wE= +github.com/olekukonko/ll v0.1.4/go.mod h1:b52bVQRRPObe+yyBl0TxNfhesL0nedD4Cht0/zx55Ew= +github.com/olekukonko/tablewriter v1.1.3 h1:VSHhghXxrP0JHl+0NnKid7WoEmd9/urKRJLysb70nnA= +github.com/olekukonko/tablewriter v1.1.3/go.mod h1:9VU0knjhmMkXjnMKrZ3+L2JhhtsQ/L38BbL3CRNE8tM= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= @@ -89,8 +91,8 @@ github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= -github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= -github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -102,25 +104,25 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 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.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw= -go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= +go.mongodb.org/mongo-driver v1.17.8 h1:BDP3+U3Y8K0vTrpqDJIRaXNhb/bKyoVeg6tIJsW5EhM= +go.mongodb.org/mongo-driver v1.17.8/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= -golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= -golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f h1:1FTH6cpXFsENbPR5Bu8NQddPSaUUE6NA2XdZdDSAJK4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= -google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= -google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= +google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -128,8 +130,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.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= -k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8= +k8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= diff --git a/pkg/helpers/emoji.go b/pkg/helpers/emoji.go new file mode 100644 index 0000000..63e9c56 --- /dev/null +++ b/pkg/helpers/emoji.go @@ -0,0 +1,27 @@ +package helpers + +const ( + Ambulance = "🚑" + Exclamation = "❗" + Bark = "🚧" + Loop = "⭕" + Lock = "🔒" + Question = "❓" + Skull = "💀" + VPN = "🛡" +) + +func EmojiHelpText() string { + return ` +Meaning of the emojis: + +🚧 Machine is reserved. Reserved machines are not considered for random allocation until the reservation flag is removed. +🔒 Machine is locked. Locked machines can not be deleted until the lock is removed. +💀 Machine is dead. The metal-api does not receive any events from this machine. +❗ Machine has a last event error. The machine has recently encountered an error during the provisioning lifecycle. +❓ Machine is in unknown condition. The metal-api does not receive phoned home events anymore or has never booted successfully. +⭕ Machine is in a provisioning crash loop. Flag can be reset through an API-triggered reboot or when the machine reaches the phoned home state. +🚑 Machine reclaim has failed. The machine was deleted but it is not going back into the available machine pool. +🛡 Machine is connected to our VPN, ssh access only possible via this VPN. +` +}