From 2517e9b0410e5fb443465ddf85623a5a9f8507d3 Mon Sep 17 00:00:00 2001 From: Gerrit Date: Wed, 29 Jul 2026 15:29:08 +0200 Subject: [PATCH 01/13] Implement network comands. --- cmd/admin/v2/commands.go | 1 + cmd/admin/v2/network.go | 432 ++++++++++++++++++ cmd/api/v2/commands.go | 1 + cmd/api/v2/network.go | 333 ++++++++++++++ cmd/completion/network.go | 95 ++++ cmd/sorters/network.go | 27 ++ cmd/tableprinters/common.go | 22 +- cmd/tableprinters/network.go | 170 +++++++ docs/admin/metalctlv2_admin.md | 1 + docs/admin/metalctlv2_admin_network.md | 38 ++ docs/admin/metalctlv2_admin_network_apply.md | 46 ++ docs/admin/metalctlv2_admin_network_create.md | 66 +++ docs/admin/metalctlv2_admin_network_delete.md | 46 ++ .../metalctlv2_admin_network_describe.md | 31 ++ docs/admin/metalctlv2_admin_network_edit.md | 31 ++ docs/admin/metalctlv2_admin_network_list.md | 45 ++ docs/admin/metalctlv2_admin_network_update.md | 52 +++ docs/metalctlv2.md | 1 + docs/metalctlv2_network.md | 39 ++ 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-base-networks.md | 42 ++ docs/metalctlv2_network_list.md | 44 ++ docs/metalctlv2_network_update.md | 52 +++ pkg/helpers/network.go | 70 +++ 28 files changed, 1892 insertions(+), 2 deletions(-) create mode 100644 cmd/admin/v2/network.go create mode 100644 cmd/api/v2/network.go create mode 100644 cmd/completion/network.go create mode 100644 cmd/sorters/network.go create mode 100644 cmd/tableprinters/network.go create mode 100644 docs/admin/metalctlv2_admin_network.md create mode 100644 docs/admin/metalctlv2_admin_network_apply.md create mode 100644 docs/admin/metalctlv2_admin_network_create.md create mode 100644 docs/admin/metalctlv2_admin_network_delete.md create mode 100644 docs/admin/metalctlv2_admin_network_describe.md create mode 100644 docs/admin/metalctlv2_admin_network_edit.md create mode 100644 docs/admin/metalctlv2_admin_network_list.md create mode 100644 docs/admin/metalctlv2_admin_network_update.md 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-base-networks.md create mode 100644 docs/metalctlv2_network_list.md create mode 100644 docs/metalctlv2_network_update.md create mode 100644 pkg/helpers/network.go diff --git a/cmd/admin/v2/commands.go b/cmd/admin/v2/commands.go index e3ea879..38a1947 100644 --- a/cmd/admin/v2/commands.go +++ b/cmd/admin/v2/commands.go @@ -17,6 +17,7 @@ func AddCmds(cmd *cobra.Command, c *config.Config) { adminCmd.AddCommand(newAuditCmd(c)) adminCmd.AddCommand(newComponentCmd(c)) adminCmd.AddCommand(newImageCmd(c)) + adminCmd.AddCommand(newNetworkCmd(c)) adminCmd.AddCommand(newPartitionCmd(c)) adminCmd.AddCommand(newProjectCmd(c)) adminCmd.AddCommand(newSizeCmd(c)) diff --git a/cmd/admin/v2/network.go b/cmd/admin/v2/network.go new file mode 100644 index 0000000..e6782cc --- /dev/null +++ b/cmd/admin/v2/network.go @@ -0,0 +1,432 @@ +package v2 + +import ( + "github.com/metal-stack/api/go/enum" + "github.com/metal-stack/api/go/errorutil" + 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/helpers" + "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 networkCmd struct { + c *config.Config +} + +func newNetworkCmd(c *config.Config) *cobra.Command { + var ( + w = &networkCmd{ + 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]") + cmd.Flags().StringP("type", "t", "", "type of the network. [optional]") + cmd.Flags().String("nat-type", "", "nat 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.NetworkAddressFamilyCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("type", c.Completion.NetworkTypeCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("nat-type", c.Completion.NetworkTypeCompletion)) + } + ) + + 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.NetworkAdminListCompletion, + 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). [optional]") + cmd.Flags().String("parent-network", "", "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]") + 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.NetworkAddressFamilyCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("type", c.Completion.NetworkTypeCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("nat-type", c.Completion.NetworkNatTypeCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("parent-network", c.Completion.NetworkListCompletion)) + }, + ListCmdMutateFn: func(cmd *cobra.Command) { + listFlags(cmd) + cmd.Flags().String("parent-network", "", "parent network to filter [optional]") + genericcli.Must(cmd.RegisterFlagCompletionFunc("parent-network", c.Completion.NetworkListCompletion)) + }, + 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().String("project", "", "project to filter [optional]") + cmd.Flags().StringSlice("labels", nil, "labels to replace for the network") + cmd.Flags().StringSlice("add-labels", nil, "labels to add to the network") + cmd.Flags().StringSlice("remove-labels", nil, "labels to remove to the network") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + }, + } + + return genericcli.NewCmds(cmdsConfig) +} + +func (c *networkCmd) Get(id string) (*apiv2.Network, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().Network().Get(ctx, &adminv2.NetworkServiceGetRequest{ + Id: id, + }) + + if err != nil { + return nil, err + } + + return resp.Network, nil +} + +func (c *networkCmd) List() ([]*apiv2.Network, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + var ( + nwType *apiv2.NetworkType + natType *apiv2.NATType + ) + + if viper.IsSet("type") { + nt, err := enum.GetEnum[apiv2.NetworkType](viper.GetString("type")) + if err != nil { + return nil, err + } + + nwType = &nt + } + + if viper.IsSet("nat-type") { + nat, err := enum.GetEnum[apiv2.NATType](viper.GetString("nat-type")) + if err != nil { + return nil, err + } + + natType = &nat + } + + req := &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")), + ParentNetwork: pointer.PointerOrNil(viper.GetString("parent-network")), + AddressFamily: helpers.NetworkAddressFamilyToType(viper.GetString("addressfamily")), + Type: nwType, + NatType: natType, + }, + } + + if labelSlice := viper.GetStringSlice("labels"); len(labelSlice) > 0 { + var err error + + req.Query.Labels, err = helpers.LabelsFromSlice(labelSlice) + if err != nil { + return nil, err + } + } + + resp, err := c.c.Client.Adminv2().Network().List(ctx, req) + if err != nil { + return nil, err + } + + return resp.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, &adminv2.NetworkServiceDeleteRequest{ + Id: id, + }) + if err != nil { + return nil, err + } + + return resp.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, rq) + if err != nil { + if errorutil.IsConflict(err) { + return nil, genericcli.AlreadyExistsError() + } + return nil, err + } + + return resp.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, rq) + if err != nil { + return nil, err + } + + return resp.Network, nil +} + +func (c *networkCmd) Convert(r *apiv2.Network) (string, *adminv2.NetworkServiceCreateRequest, *adminv2.NetworkServiceUpdateRequest, error) { + addressFamily, err := helpers.AddressFamilyFromPrefixes(r.Prefixes...) + if err != nil { + return "", nil, nil, err + } + + return r.Id, networkResponseToCreate(r, addressFamily), networkResponseToUpdate(r), nil +} + +func networkResponseToCreate(r *apiv2.Network, addressFamily *apiv2.NetworkAddressFamily) *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, + }, + ParentNetwork: r.ParentNetwork, + Id: &r.Id, + Type: r.Type, + Prefixes: r.Prefixes, + DestinationPrefixes: r.DestinationPrefixes, + DefaultChildPrefixLength: r.DefaultChildPrefixLength, + MinChildPrefixLength: r.MinChildPrefixLength, + NatType: &r.NatType, + Vrf: r.Vrf, + AdditionalAnnouncableCidrs: r.AdditionalAnnouncableCidrs, + // TODO + // Length: r.Length, + AddressFamily: addressFamily, + } +} + +func networkResponseToUpdate(r *apiv2.Network) *adminv2.NetworkServiceUpdateRequest { + return &adminv2.NetworkServiceUpdateRequest{ + UpdateMeta: helpers.UpdateMetaFromMeta(r.Meta), + Labels: helpers.UpdateLabelsFromMeta(r.Meta), + Id: r.Id, + Name: r.Name, + Description: r.Description, + Prefixes: r.Prefixes, + DestinationPrefixes: r.DestinationPrefixes, + DefaultChildPrefixLength: r.DefaultChildPrefixLength, + MinChildPrefixLength: r.MinChildPrefixLength, + NatType: &r.NatType, + AdditionalAnnouncableCidrs: r.AdditionalAnnouncableCidrs, + Force: viper.GetBool("FORCE_NETWORK"), + } +} + +func (c *networkCmd) createRequestFromCLI() (*adminv2.NetworkServiceCreateRequest, error) { + labels, err := helpers.LabelsFromSlice(viper.GetStringSlice("labels")) + if err != nil { + return nil, err + } + + var ( + natType = apiv2.NATType_NAT_TYPE_NONE + defaultCPL *apiv2.ChildPrefixLength + minCPL *apiv2.ChildPrefixLength + length *apiv2.ChildPrefixLength + ) + + if viper.IsSet("default-ipv4-prefix-length") { + defaultCPL = &apiv2.ChildPrefixLength{ + Ipv4: new(viper.GetUint32("default-ipv4-prefix-length")), + } + } + if viper.IsSet("default-ipv6-prefix-length") { + if defaultCPL == nil { + defaultCPL = &apiv2.ChildPrefixLength{} + } + + defaultCPL.Ipv6 = new(viper.GetUint32("default-ipv6-prefix-length")) + } + + if viper.IsSet("min-ipv4-prefix-length") { + minCPL = &apiv2.ChildPrefixLength{ + Ipv4: new(viper.GetUint32("min-ipv4-prefix-length")), + } + } + if viper.IsSet("min-ipv6-prefix-length") { + if minCPL == nil { + minCPL = &apiv2.ChildPrefixLength{} + } + + minCPL.Ipv6 = new(viper.GetUint32("min-ipv6-prefix-length")) + } + + if viper.IsSet("ipv4-prefix-length") { + length = &apiv2.ChildPrefixLength{ + Ipv4: new(viper.GetUint32("ipv4-prefix-length")), + } + } + if viper.IsSet("ipv6-prefix-length") { + if length == nil { + length = &apiv2.ChildPrefixLength{} + } + + length.Ipv6 = new(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 = new(viper.GetUint32("vrf")) + } + + return &adminv2.NetworkServiceCreateRequest{ + Description: pointer.PointerOrNil(viper.GetString("description")), + Name: pointer.PointerOrNil(viper.GetString("name")), + Project: pointer.PointerOrNil(viper.GetString("project")), + Partition: pointer.PointerOrNil(viper.GetString("partition")), + Labels: labels, + ParentNetwork: pointer.PointerOrNil(viper.GetString("parent-network")), + AddressFamily: helpers.NetworkAddressFamilyToType(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 +} + +func (c *networkCmd) updateRequestFromCLI(args []string) (*adminv2.NetworkServiceUpdateRequest, error) { + id, err := genericcli.GetExactlyOneArg(args) + if err != nil { + return nil, err + } + + updateLabels, err := helpers.UpdateLabelsFromCLI() + if err != nil { + return nil, err + } + + var ( + natType = apiv2.NATType_NAT_TYPE_NONE + defaultCPL *apiv2.ChildPrefixLength + minCPL *apiv2.ChildPrefixLength + ) + if viper.IsSet("default-ipv4-prefix-length") { + defaultCPL = &apiv2.ChildPrefixLength{ + Ipv4: new(viper.GetUint32("default-ipv4-prefix-length")), + } + } + if viper.IsSet("default-ipv6-prefix-length") { + if defaultCPL == nil { + defaultCPL = &apiv2.ChildPrefixLength{} + } + defaultCPL.Ipv6 = new(viper.GetUint32("default-ipv6-prefix-length")) + } + if viper.IsSet("min-ipv4-prefix-length") { + minCPL = &apiv2.ChildPrefixLength{ + Ipv4: new(viper.GetUint32("min-ipv4-prefix-length")), + } + } + if viper.IsSet("min-ipv6-prefix-length") { + if minCPL == nil { + minCPL = &apiv2.ChildPrefixLength{} + } + minCPL.Ipv6 = new(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, + Description: pointer.PointerOrNil(viper.GetString("description")), + Name: pointer.PointerOrNil(viper.GetString("name")), + Labels: updateLabels, + 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"), + } + ) + + return ur, nil +} diff --git a/cmd/api/v2/commands.go b/cmd/api/v2/commands.go index 5e72d47..32d8182 100644 --- a/cmd/api/v2/commands.go +++ b/cmd/api/v2/commands.go @@ -11,6 +11,7 @@ func AddCmds(cmd *cobra.Command, c *config.Config) { cmd.AddCommand(newImageCmd(c)) cmd.AddCommand(newIPCmd(c)) cmd.AddCommand(newMethodsCmd(c)) + cmd.AddCommand(newNetworkCmd(c)) cmd.AddCommand(newPartitionCmd(c)) cmd.AddCommand(newProjectCmd(c)) cmd.AddCommand(newSizeCmd(c)) diff --git a/cmd/api/v2/network.go b/cmd/api/v2/network.go new file mode 100644 index 0000000..3aad6f5 --- /dev/null +++ b/cmd/api/v2/network.go @@ -0,0 +1,333 @@ +package v2 + +import ( + "github.com/metal-stack/api/go/enum" + "github.com/metal-stack/api/go/errorutil" + 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/metal-stack/metal-lib/pkg/pointer" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +type networkCmd struct { + c *config.Config +} + +func newNetworkCmd(c *config.Config) *cobra.Command { + w := &networkCmd{ + 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]") + cmd.Flags().StringP("type", "t", "", "type of the network. [optional]") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("id", c.Completion.NetworkListCompletion)) + 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("type", c.Completion.NetworkTypeCompletion)) + } + + 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.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). [optional]") + cmd.Flags().String("parent-network", "", "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.NetworkAddressFamilyCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("parent-network", c.Completion.NetworkListCompletion)) + }, + ListCmdMutateFn: func(cmd *cobra.Command) { + listFlags(cmd) + cmd.Flags().String("parent-network", "", "parent network to filter [optional]") + genericcli.Must(cmd.RegisterFlagCompletionFunc("parent-network", c.Completion.NetworkListCompletion)) + }, + 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().String("project", "", "project to filter [optional]") + cmd.Flags().StringSlice("labels", nil, "labels to replace for the network") + cmd.Flags().StringSlice("add-labels", nil, "labels to add to the network") + cmd.Flags().StringSlice("remove-labels", nil, "labels to remove to the network") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + }, + } + + 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() + }, + } + listFlags(listBaseNetworksCmd) + + return genericcli.NewCmds(cmdsConfig, listBaseNetworksCmd) +} + +func (c *networkCmd) Get(id string) (*apiv2.Network, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Apiv2().Network().Get(ctx, &apiv2.NetworkServiceGetRequest{ + Id: id, + Project: c.c.GetProject(), + }) + if err != nil { + return nil, err + } + + return resp.Network, nil +} + +func (c *networkCmd) List() ([]*apiv2.Network, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &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(c.c.GetProject()), + Prefixes: viper.GetStringSlice("prefixes"), + DestinationPrefixes: viper.GetStringSlice("destination-prefixes"), + Vrf: pointer.PointerOrNil(viper.GetUint32("vrf")), + ParentNetwork: pointer.PointerOrNil(viper.GetString("parent-network")), + AddressFamily: helpers.NetworkAddressFamilyToType(viper.GetString("addressfamily")), + }, + } + + if labelSlice := viper.GetStringSlice("labels"); len(labelSlice) > 0 { + var err error + + req.Query.Labels, err = helpers.LabelsFromSlice(labelSlice) + if err != nil { + return nil, err + } + } + + resp, err := c.c.Client.Apiv2().Network().List(ctx, req) + + if err != nil { + return nil, err + } + + return resp.Networks, nil +} + +func (c *networkCmd) Delete(id string) (*apiv2.Network, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Apiv2().Network().Delete(ctx, &apiv2.NetworkServiceDeleteRequest{ + Id: id, + Project: c.c.GetProject(), + }) + if err != nil { + return nil, err + } + + return resp.Network, 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, rq) + if err != nil { + if errorutil.IsConflict(err) { + return nil, genericcli.AlreadyExistsError() + } + return nil, err + } + + return resp.Network, 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, rq) + if err != nil { + return nil, err + } + + return resp.Network, nil +} + +func (c *networkCmd) Convert(r *apiv2.Network) (string, *apiv2.NetworkServiceCreateRequest, *apiv2.NetworkServiceUpdateRequest, error) { + addressFamily, err := helpers.AddressFamilyFromPrefixes(r.Prefixes...) + if err != nil { + return "", nil, nil, err + } + + return r.Id, networkResponseToCreate(r, addressFamily), networkResponseToUpdate(r), nil +} + +func networkResponseToCreate(r *apiv2.Network, addressFamily *apiv2.NetworkAddressFamily) *apiv2.NetworkServiceCreateRequest { + var ( + meta = pointer.SafeDeref(r.Meta) + ) + + return &apiv2.NetworkServiceCreateRequest{ + Project: pointer.SafeDeref(r.Project), + Name: r.Name, + Description: r.Description, + Partition: r.Partition, + Labels: &apiv2.Labels{ + Labels: pointer.SafeDeref(meta.Labels).Labels, + }, + ParentNetwork: r.ParentNetwork, // TODO: allow defining length + AddressFamily: addressFamily, + } +} + +func networkResponseToUpdate(r *apiv2.Network) *apiv2.NetworkServiceUpdateRequest { + return &apiv2.NetworkServiceUpdateRequest{ + Id: r.Id, + Project: pointer.SafeDeref(r.Project), + Name: r.Name, + Description: r.Description, + UpdateMeta: helpers.UpdateMetaFromMeta(r.Meta), + Labels: helpers.UpdateLabelsFromMeta(r.Meta), + } +} + +func (c *networkCmd) createRequestFromCLI() (*apiv2.NetworkServiceCreateRequest, error) { + var ( + cpl = &apiv2.ChildPrefixLength{} + ) + if viper.IsSet("ipv4-prefix-length") { + cpl.Ipv4 = new(viper.GetUint32("ipv4-prefix-length")) + } + if viper.IsSet("ipv6-prefix-length") { + cpl.Ipv6 = new(viper.GetUint32("ipv6-prefix-length")) + } + + labels, err := helpers.LabelsFromSlice(viper.GetStringSlice("labels")) + if err != nil { + return nil, err + } + + 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: labels, + ParentNetwork: pointer.PointerOrNil(viper.GetString("parent-network")), + Length: cpl, + AddressFamily: helpers.NetworkAddressFamilyToType(viper.GetString("addressfamily")), + }, nil +} + +func (c *networkCmd) updateRequestFromCLI(args []string) (*apiv2.NetworkServiceUpdateRequest, error) { + id, err := genericcli.GetExactlyOneArg(args) + if err != nil { + return nil, err + } + + updateLabels, err := helpers.UpdateLabelsFromCLI() + if err != nil { + return nil, err + } + + var ( + ur = &apiv2.NetworkServiceUpdateRequest{ + Id: id, + Project: c.c.GetProject(), + Description: pointer.PointerOrNil(viper.GetString("description")), + Name: pointer.PointerOrNil(viper.GetString("name")), + Labels: updateLabels, + UpdateMeta: &apiv2.UpdateMeta{ + LockingStrategy: *apiv2.OptimisticLockingStrategy_OPTIMISTIC_LOCKING_STRATEGY_SERVER.Enum(), + }, + } + ) + + return ur, nil +} + +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 + } + + labels, err := helpers.LabelsFromSlice(viper.GetStringSlice("labels")) + if err != nil { + return err + } + + resp, err := c.c.Client.Apiv2().Network().ListBaseNetworks(ctx, &apiv2.NetworkServiceListBaseNetworksRequest{ + 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")), + AddressFamily: helpers.NetworkAddressFamilyToType(viper.GetString("addressfamily")), + Labels: labels, + Type: nwType, + }, + }) + + if err != nil { + return err + } + + return c.c.ListPrinter.Print(resp.Networks) +} diff --git a/cmd/completion/network.go b/cmd/completion/network.go new file mode 100644 index 0000000..1eb7578 --- /dev/null +++ b/cmd/completion/network.go @@ -0,0 +1,95 @@ +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" +) + +func (c *Completion) NetworkListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ownNetworks, err := c.Client.Apiv2().Network().List(c.Ctx, &apiv2.NetworkServiceListRequest{ + Project: c.Project, + }) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + baseNetworks, err := c.Client.Apiv2().Network().ListBaseNetworks(c.Ctx, &apiv2.NetworkServiceListBaseNetworksRequest{ + Project: c.Project, + }) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + var names []string + for _, s := range baseNetworks.Networks { + names = append(names, s.Id+"\t"+pointer.SafeDeref(s.Name)) + } + for _, s := range ownNetworks.Networks { + names = append(names, s.Id+"\t"+pointer.SafeDeref(s.Name)) + } + + 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) NatTypeCompletion(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.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 +} + +func (c *Completion) NetworkAddressFamilyCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + 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 +} + +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/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 58c1c73..0ce63c2 100644 --- a/cmd/tableprinters/common.go +++ b/cmd/tableprinters/common.go @@ -14,8 +14,10 @@ import ( ) const ( - dot = "●" - nbr = " " + dot = "●" + nbr = " " + halfpie = "◒" + threequarterpie = "◕" ambulance = "🚑" exclamation = "❗" @@ -60,6 +62,11 @@ func (t *TablePrinter) ToHeaderAndRows(data any, wide bool) ([]string, [][]strin case []*apiv2.Component: return t.ComponentTable(d, wide) + case *apiv2.Network: + return t.NetworkTable(pointer.WrapInSlice(d), wide) + case []*apiv2.Network: + return t.NetworkTable(d, wide) + case *apiv2.IP: return t.IPTable(pointer.WrapInSlice(d), wide) case []*apiv2.IP: @@ -188,3 +195,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..b8db0b5 --- /dev/null +++ b/cmd/tableprinters/network.go @@ -0,0 +1,170 @@ +package tableprinters + +import ( + "fmt" + "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/genericcli" + "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", "Type", "Project", "Partition", "Nat", "Prefixes", "Prefix Usage", "IP Usage"} + if wide { + header = []string{"ID", "Description", "Name", "Type", "Project", "Partition", "Nat", "Prefixes", "Annotations"} + } + + nn := &networks{} + for _, n := range data { + if n.ParentNetwork == nil { + *nn = append(*nn, &network{parent: n}) + } + } + for _, n := range data { + if n.ParentNetwork != nil { + if !nn.appendChild(*n.ParentNetwork, n) { + *nn = append(*nn, &network{parent: n}) + } + } + } + for _, n := range *nn { + row, err := addNetwork("", n.parent, wide) + if err != nil { + return nil, nil, err + } + + rows = append(rows, row) + + for i, c := range n.children { + prefix := "├" + if i == len(n.children)-1 { + prefix = "└" + } + prefix += "─╴" + + row, err := addNetwork(prefix, c, wide) + if err != nil { + return nil, nil, err + } + + rows = append(rows, row) + } + } + + return header, rows, nil +} + +func addNetwork(prefix string, n *apiv2.Network, wide bool) ([]string, error) { + var ( + 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 = genericcli.TruncateEnd(pointer.SafeDeref(n.Name), 40) + project = pointer.SafeDeref(n.Project) + partition = pointer.SafeDeref(n.Partition) + natType = n.NatType.String() + ) + + if t, err := enum.GetStringValue(n.NatType); err == nil { + natType = *t + } else { + return nil, err + } + + max := getMaxLineCount(description, name, project, partition, natType, prefixes, shortIPUsage) + for range max - 1 { + 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") + + 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}, nil + } else { + return []string{id, name, networkType, project, partition, natType, prefixes, shortPrefixUsage, shortIPUsage}, nil + } +} + +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/docs/admin/metalctlv2_admin.md b/docs/admin/metalctlv2_admin.md index ac589e9..ebad65f 100644 --- a/docs/admin/metalctlv2_admin.md +++ b/docs/admin/metalctlv2_admin.md @@ -31,6 +31,7 @@ these commands utilize the admin api, which can only be accessed by metal-stack * [metalctlv2 admin audit](metalctlv2_admin_audit.md) - manage audit entities * [metalctlv2 admin component](metalctlv2_admin_component.md) - manage component entities * [metalctlv2 admin image](metalctlv2_admin_image.md) - manage image entities +* [metalctlv2 admin network](metalctlv2_admin_network.md) - manage network entities * [metalctlv2 admin partition](metalctlv2_admin_partition.md) - manage partition entities * [metalctlv2 admin project](metalctlv2_admin_project.md) - manage project entities * [metalctlv2 admin size](metalctlv2_admin_size.md) - manage size entities diff --git a/docs/admin/metalctlv2_admin_network.md b/docs/admin/metalctlv2_admin_network.md new file mode 100644 index 0000000..70a5409 --- /dev/null +++ b/docs/admin/metalctlv2_admin_network.md @@ -0,0 +1,38 @@ +## metalctlv2 admin 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 + -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), wide is a table with more columns. (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 admin](metalctlv2_admin.md) - admin commands +* [metalctlv2 admin network apply](metalctlv2_admin_network_apply.md) - applies one or more networks from a given file +* [metalctlv2 admin network create](metalctlv2_admin_network_create.md) - creates the network +* [metalctlv2 admin network delete](metalctlv2_admin_network_delete.md) - deletes the network +* [metalctlv2 admin network describe](metalctlv2_admin_network_describe.md) - describes the network +* [metalctlv2 admin network edit](metalctlv2_admin_network_edit.md) - edit the network through an editor and update +* [metalctlv2 admin network list](metalctlv2_admin_network_list.md) - list all networks +* [metalctlv2 admin network update](metalctlv2_admin_network_update.md) - updates the network + diff --git a/docs/admin/metalctlv2_admin_network_apply.md b/docs/admin/metalctlv2_admin_network_apply.md new file mode 100644 index 0000000..e82ad05 --- /dev/null +++ b/docs/admin/metalctlv2_admin_network_apply.md @@ -0,0 +1,46 @@ +## metalctlv2 admin network apply + +applies one or more networks from a given file + +``` +metalctlv2 admin 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 + -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), wide is a table with more columns. (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 admin network](metalctlv2_admin_network.md) - manage network entities + diff --git a/docs/admin/metalctlv2_admin_network_create.md b/docs/admin/metalctlv2_admin_network_create.md new file mode 100644 index 0000000..0b6ff1d --- /dev/null +++ b/docs/admin/metalctlv2_admin_network_create.md @@ -0,0 +1,66 @@ +## metalctlv2 admin network create + +creates the network + +``` +metalctlv2 admin network create [flags] +``` + +### Options + +``` + --additional-announcable-cidrs strings additional-announcable-cidrs for this network. [optional] + --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. + --default-ipv4-prefix-length uint32 default ipv4 prefix bit length of the network to create. [optional] + --default-ipv6-prefix-length uint32 default ipv6 prefix bit length of the network to create. [optional] + --description string description of the network to create. [optional] + --destination-prefixes strings destination-prefixes for this 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 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 + --id string id of the network to create, defaults to a random uuid if not provided. [optional] + --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] + --min-ipv4-prefix-length uint32 min ipv4 prefix bit length of the network to create. [optional] + --min-ipv6-prefix-length uint32 min ipv6 prefix bit length of the network to create. [optional] + --name string name of the network to create. [required] + --nat-type string nat-type of the network. [required] + --parent-network string the parent of the network (alternative to partition). [optional] + --partition string partition where this network should exist. [required] + --prefixes strings prefixes for this network. [optional] + --project string partition where this network should exist (alternative to parent-network). [optional] + --skip-security-prompts skips security prompt for bulk operations + --timestamps when used with --file (bulk operation): prints timestamps in-between the operations + -t, --type string type of the network. [required] + --vrf uint32 the vrf of the network to create. [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 + -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), wide is a table with more columns. (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 admin network](metalctlv2_admin_network.md) - manage network entities + diff --git a/docs/admin/metalctlv2_admin_network_delete.md b/docs/admin/metalctlv2_admin_network_delete.md new file mode 100644 index 0000000..6012c57 --- /dev/null +++ b/docs/admin/metalctlv2_admin_network_delete.md @@ -0,0 +1,46 @@ +## metalctlv2 admin network delete + +deletes the network + +``` +metalctlv2 admin 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 + -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), wide is a table with more columns. (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 admin network](metalctlv2_admin_network.md) - manage network entities + diff --git a/docs/admin/metalctlv2_admin_network_describe.md b/docs/admin/metalctlv2_admin_network_describe.md new file mode 100644 index 0000000..6cf119b --- /dev/null +++ b/docs/admin/metalctlv2_admin_network_describe.md @@ -0,0 +1,31 @@ +## metalctlv2 admin network describe + +describes the network + +``` +metalctlv2 admin 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 + -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), wide is a table with more columns. (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 admin network](metalctlv2_admin_network.md) - manage network entities + diff --git a/docs/admin/metalctlv2_admin_network_edit.md b/docs/admin/metalctlv2_admin_network_edit.md new file mode 100644 index 0000000..6586cc3 --- /dev/null +++ b/docs/admin/metalctlv2_admin_network_edit.md @@ -0,0 +1,31 @@ +## metalctlv2 admin network edit + +edit the network through an editor and update + +``` +metalctlv2 admin 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 + -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), wide is a table with more columns. (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 admin network](metalctlv2_admin_network.md) - manage network entities + diff --git a/docs/admin/metalctlv2_admin_network_list.md b/docs/admin/metalctlv2_admin_network_list.md new file mode 100644 index 0000000..1d6d8fd --- /dev/null +++ b/docs/admin/metalctlv2_admin_network_list.md @@ -0,0 +1,45 @@ +## metalctlv2 admin network list + +list all networks + +``` +metalctlv2 admin 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 + -h, --help help for list + --id string ID to filter [optional] + --labels strings labels to filter [optional] + --name string name to filter [optional] + --nat-type string nat type of the network. [optional] + --parent-network string parent network to filter [optional] + --partition string partition to filter [optional] + --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] +``` + +### 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 + -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), wide is a table with more columns. (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 admin network](metalctlv2_admin_network.md) - manage network entities + diff --git a/docs/admin/metalctlv2_admin_network_update.md b/docs/admin/metalctlv2_admin_network_update.md new file mode 100644 index 0000000..e25a27d --- /dev/null +++ b/docs/admin/metalctlv2_admin_network_update.md @@ -0,0 +1,52 @@ +## metalctlv2 admin network update + +updates the network + +``` +metalctlv2 admin network update [flags] +``` + +### Options + +``` + --add-labels strings labels to add to the network + --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 labels to replace for the network + --name string the name of the network [optional] + --project string project to filter [optional] + --remove-labels strings labels to remove to the network + --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 + -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), wide is a table with more columns. (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 admin network](metalctlv2_admin_network.md) - manage network entities + diff --git a/docs/metalctlv2.md b/docs/metalctlv2.md index 9ade541..b1579d5 100644 --- a/docs/metalctlv2.md +++ b/docs/metalctlv2.md @@ -28,6 +28,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 partition](metalctlv2_partition.md) - manage partition entities * [metalctlv2 project](metalctlv2_project.md) - manage project entities * [metalctlv2 size](metalctlv2_size.md) - manage size entities diff --git a/docs/metalctlv2_network.md b/docs/metalctlv2_network.md new file mode 100644 index 0000000..090443c --- /dev/null +++ b/docs/metalctlv2_network.md @@ -0,0 +1,39 @@ +## 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 + -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), wide is a table with more columns. (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 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_apply.md b/docs/metalctlv2_network_apply.md new file mode 100644 index 0000000..195dc3c --- /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 + -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), wide is a table with more columns. (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..c9a669b --- /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. + --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] + --name string name of the network to create. [required] + --parent-network 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). [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 + -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), wide is a table with more columns. (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..c88f9b9 --- /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 + -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), wide is a table with more columns. (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..3d320ab --- /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 + -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), wide is a table with more columns. (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..c694c41 --- /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 + -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), wide is a table with more columns. (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-base-networks.md b/docs/metalctlv2_network_list-base-networks.md new file mode 100644 index 0000000..0969999 --- /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 + -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), wide is a table with more columns. (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..75a7cb6 --- /dev/null +++ b/docs/metalctlv2_network_list.md @@ -0,0 +1,44 @@ +## 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 + -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 string parent network to filter [optional] + --partition string partition to filter [optional] + --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] +``` + +### 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 + -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), wide is a table with more columns. (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..dfb42bc --- /dev/null +++ b/docs/metalctlv2_network_update.md @@ -0,0 +1,52 @@ +## metalctlv2 network update + +updates the network + +``` +metalctlv2 network update [flags] +``` + +### Options + +``` + --add-labels strings labels to add to the network + --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 labels to replace for the network + --name string the name of the network [optional] + --project string project to filter [optional] + --remove-labels strings labels to remove to the network + --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 + -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), wide is a table with more columns. (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/pkg/helpers/network.go b/pkg/helpers/network.go new file mode 100644 index 0000000..9f064d3 --- /dev/null +++ b/pkg/helpers/network.go @@ -0,0 +1,70 @@ +package helpers + +import ( + "net/netip" + + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" +) + +func IPAddressFamilyToType(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() + } +} + +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() + } +} + +func AddressFamilyFromPrefixes(prefixes ...string) (*apiv2.NetworkAddressFamily, error) { + var ( + addressFamily *apiv2.NetworkAddressFamily + isIPv4 bool + isIPv6 bool + ) + + for _, pfx := range prefixes { + p, err := netip.ParsePrefix(pfx) + if err != nil { + return nil, err + } + + if p.Addr().Is4() { + isIPv4 = true + } + if p.Addr().Is6() { + isIPv6 = true + } + } + + switch { + case isIPv4 && isIPv6: + addressFamily = apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_DUAL_STACK.Enum() + case isIPv4: + addressFamily = apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_V4.Enum() + case isIPv6: + addressFamily = apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_V6.Enum() + default: + // noop + } + + return addressFamily, nil +} From aaa823db63ac55cb7a216a853e133bd403ec300e Mon Sep 17 00:00:00 2001 From: Gerrit Date: Wed, 29 Jul 2026 16:06:25 +0200 Subject: [PATCH 02/13] Some tests. --- cmd/admin/v2/network.go | 13 +- cmd/api/v2/network.go | 22 +- tests/e2e/admin/network_test.go | 320 ++++++++++++++++++++++ tests/e2e/api/network_test.go | 405 ++++++++++++++++++++++++++++ tests/e2e/testresources/networks.go | 52 ++++ 5 files changed, 804 insertions(+), 8 deletions(-) create mode 100644 tests/e2e/admin/network_test.go create mode 100644 tests/e2e/api/network_test.go create mode 100644 tests/e2e/testresources/networks.go diff --git a/cmd/admin/v2/network.go b/cmd/admin/v2/network.go index e6782cc..38befda 100644 --- a/cmd/admin/v2/network.go +++ b/cmd/admin/v2/network.go @@ -64,7 +64,7 @@ func newNetworkCmd(c *config.Config) *cobra.Command { 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). [optional]") + cmd.Flags().String("project", "", "project of this network. [optional]") cmd.Flags().String("parent-network", "", "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]") @@ -378,7 +378,7 @@ func (c *networkCmd) updateRequestFromCLI(args []string) (*adminv2.NetworkServic } var ( - natType = apiv2.NATType_NAT_TYPE_NONE + natType *apiv2.NATType defaultCPL *apiv2.ChildPrefixLength minCPL *apiv2.ChildPrefixLength ) @@ -406,10 +406,12 @@ func (c *networkCmd) updateRequestFromCLI(args []string) (*adminv2.NetworkServic } if viper.IsSet("nat-type") { - natType, err = enum.GetEnum[apiv2.NATType](viper.GetString("nat-type")) + nt, err := enum.GetEnum[apiv2.NATType](viper.GetString("nat-type")) if err != nil { return nil, err } + + natType = &nt } var ( @@ -422,9 +424,12 @@ func (c *networkCmd) updateRequestFromCLI(args []string) (*adminv2.NetworkServic DestinationPrefixes: viper.GetStringSlice("destination-prefixes"), DefaultChildPrefixLength: defaultCPL, MinChildPrefixLength: minCPL, - NatType: &natType, + NatType: natType, AdditionalAnnouncableCidrs: viper.GetStringSlice("additional-announcable-cidrs"), Force: viper.GetBool("force"), + UpdateMeta: &apiv2.UpdateMeta{ + LockingStrategy: apiv2.OptimisticLockingStrategy_OPTIMISTIC_LOCKING_STRATEGY_SERVER, + }, } ) diff --git a/cmd/api/v2/network.go b/cmd/api/v2/network.go index 3aad6f5..02d376f 100644 --- a/cmd/api/v2/network.go +++ b/cmd/api/v2/network.go @@ -58,7 +58,7 @@ func newNetworkCmd(c *config.Config) *cobra.Command { 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). [optional]") + cmd.Flags().String("project", "", "project of this network. [optional]") cmd.Flags().String("parent-network", "", "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]") @@ -86,6 +86,10 @@ func newNetworkCmd(c *config.Config) *cobra.Command { genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) }, + DeleteCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("project", "", "project of this network.") + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + }, } listBaseNetworksCmd := &cobra.Command{ @@ -157,10 +161,20 @@ func (c *networkCmd) Delete(id string) (*apiv2.Network, error) { ctx, cancel := c.c.NewRequestContext() defer cancel() - resp, err := c.c.Client.Apiv2().Network().Delete(ctx, &apiv2.NetworkServiceDeleteRequest{ + req := &apiv2.NetworkServiceDeleteRequest{ Id: id, Project: c.c.GetProject(), - }) + } + + if viper.IsSet("file") { + var err error + req.Id, req.Project, err = helpers.DecodeProject(id) + if err != nil { + return nil, err + } + } + + resp, err := c.c.Client.Apiv2().Network().Delete(ctx, req) if err != nil { return nil, err } @@ -201,7 +215,7 @@ func (c *networkCmd) Convert(r *apiv2.Network) (string, *apiv2.NetworkServiceCre return "", nil, nil, err } - return r.Id, networkResponseToCreate(r, addressFamily), networkResponseToUpdate(r), nil + return helpers.EncodeProject(r.Id, pointer.SafeDeref(r.Project)), networkResponseToCreate(r, addressFamily), networkResponseToUpdate(r), nil } func networkResponseToCreate(r *apiv2.Network, addressFamily *apiv2.NetworkAddressFamily) *apiv2.NetworkServiceCreateRequest { diff --git a/tests/e2e/admin/network_test.go b/tests/e2e/admin/network_test.go new file mode 100644 index 0000000..861908d --- /dev/null +++ b/tests/e2e/admin/network_test.go @@ -0,0 +1,320 @@ +package admin_e2e + +import ( + "testing" + + "connectrpc.com/connect" + "github.com/metal-stack/api/go/client" + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + e2erootcmd "github.com/metal-stack/cli/testing/e2e" + "github.com/metal-stack/cli/tests/e2e/testresources" + e2e "github.com/metal-stack/metal-lib/pkg/genericcli/e2e" + "github.com/spf13/afero" + "github.com/stretchr/testify/require" +) + +func Test_AdminNetworkCmd_List(t *testing.T) { + tests := []*e2e.Test[adminv2.NetworkServiceListResponse, apiv2.Network]{ + { + Name: "list", + CmdArgs: []string{"admin", "network", "list"}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.NetworkServiceListRequest{ + Query: &apiv2.NetworkQuery{}, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.NetworkServiceListResponse{ + Networks: []*apiv2.Network{ + testresources.Network2(), + testresources.Network1(), + }, + }) + }, + }, + }, + }), + WantTable: new(` + ID NAME TYPE PROJECT PARTITION NAT PREFIXES PREFIX USAGE IP USAGE + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet external 0d81bca7-73f6-4da3-8397-4a8c52a0c583 partition-1 none 10.0.0.0/16,2001:db8::/32 + d83ffb0a-7aa6-4a66-8e03-0b5ee8b718a0 private child f3b4e6a1-2c8d-4e5f-a7b9-1d3e5f7a9b0c partition-1 ipv4-masq 192.168.1.0/24 + `), + WantWideTable: new(` + ID DESCRIPTION NAME TYPE PROJECT PARTITION NAT PREFIXES ANNOTATIONS + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet network internet external 0d81bca7-73f6-4da3-8397-4a8c52a0c583 partition-1 none 10.0.0.0/16,2001:db8::/32 cluster.metal-stack.io/id/namespace/service=/default/ingress-nginx + d83ffb0a-7aa6-4a66-8e03-0b5ee8b718a0 private network private child f3b4e6a1-2c8d-4e5f-a7b9-1d3e5f7a9b0c partition-1 ipv4-masq 192.168.1.0/24 a=b + `), + Template: new("{{ .id }} {{ .name }}"), + WantTemplate: new(` +6988ebb0-9531-4f9b-a893-d7868258e2ef internet +d83ffb0a-7aa6-4a66-8e03-0b5ee8b718a0 private + `), + WantMarkdown: new(` + | ID | NAME | TYPE | PROJECT | PARTITION | NAT | PREFIXES | PREFIX USAGE | IP USAGE | + |--------------------------------------|----------|----------|--------------------------------------|-------------|-----------|---------------------------|--------------|----------| + | 6988ebb0-9531-4f9b-a893-d7868258e2ef | internet | external | 0d81bca7-73f6-4da3-8397-4a8c52a0c583 | partition-1 | none | 10.0.0.0/16,2001:db8::/32 |   |   | + | d83ffb0a-7aa6-4a66-8e03-0b5ee8b718a0 | private | child | f3b4e6a1-2c8d-4e5f-a7b9-1d3e5f7a9b0c | partition-1 | ipv4-masq | 192.168.1.0/24 |   |   | + `), + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} + +func Test_AdminNetworkCmd_Describe(t *testing.T) { + tests := []*e2e.Test[adminv2.NetworkServiceGetResponse, *apiv2.Network]{ + { + Name: "describe", + CmdArgs: []string{"admin", "network", "describe", testresources.Network1().Id}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.NetworkServiceGetRequest{ + Id: testresources.Network1().Id, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.NetworkServiceGetResponse{ + Network: testresources.Network1(), + }) + }, + }, + }, + }), + WantProtoObject: testresources.Network1(), + WantTable: new(` + ID NAME TYPE PROJECT PARTITION NAT PREFIXES PREFIX USAGE IP USAGE + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet external 0d81bca7-73f6-4da3-8397-4a8c52a0c583 partition-1 none 10.0.0.0/16,2001:db8::/32 + `), + WantWideTable: new(` + ID DESCRIPTION NAME TYPE PROJECT PARTITION NAT PREFIXES ANNOTATIONS + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet network internet external 0d81bca7-73f6-4da3-8397-4a8c52a0c583 partition-1 none 10.0.0.0/16,2001:db8::/32 cluster.metal-stack.io/id/namespace/service=/default/ingress-nginx + `), + Template: new("{{ .id }} {{ .name }}"), + WantTemplate: new(` + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet + `), + WantMarkdown: new(` + | ID | NAME | TYPE | PROJECT | PARTITION | NAT | PREFIXES | PREFIX USAGE | IP USAGE | + |--------------------------------------|----------|----------|--------------------------------------|-------------|------|---------------------------|--------------|----------| + | 6988ebb0-9531-4f9b-a893-d7868258e2ef | internet | external | 0d81bca7-73f6-4da3-8397-4a8c52a0c583 | partition-1 | none | 10.0.0.0/16,2001:db8::/32 |   |   | + `), + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} + +func Test_AdminNetworkCmd_Create(t *testing.T) { + tests := []*e2e.Test[adminv2.NetworkServiceCreateResponse, *apiv2.Network]{ + { + Name: "create", + CmdArgs: []string{"admin", "network", "create", + "--id", testresources.Network1().Id, + "--name", *testresources.Network1().Name, + "--type", "external", + "--nat-type", "none", + "--partition", *testresources.Network1().Partition, + "--project", *testresources.Network1().Project, + "--prefixes", "10.0.0.0/16,2001:db8::/32", + }, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.NetworkServiceCreateRequest{ + Id: &testresources.Network1().Id, + Name: testresources.Network1().Name, + Partition: testresources.Network1().Partition, + Project: testresources.Network1().Project, + Type: apiv2.NetworkType_NETWORK_TYPE_EXTERNAL, + NatType: apiv2.NATType_NAT_TYPE_NONE.Enum(), + Prefixes: []string{"10.0.0.0/16", "2001:db8::/32"}, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.NetworkServiceCreateResponse{ + Network: testresources.Network1(), + }) + }, + }, + }, + }), + WantProtoObject: testresources.Network1(), + }, + { + Name: "create from file", + CmdArgs: append([]string{"admin", "network", "create"}, e2e.AppendFromFileCommonArgs()...), + NewRootCmd: e2erootcmd.NewRootCmd(t, + &e2erootcmd.TestConfig{ + FsMocks: func(fs *afero.Afero) { + require.NoError(t, fs.WriteFile(e2e.InputFilePath, e2e.MustMarshal(t, testresources.Network1()), 0755)) + }, + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.NetworkServiceCreateRequest{ + Id: &testresources.Network1().Id, + Name: testresources.Network1().Name, + Description: testresources.Network1().Description, + Partition: testresources.Network1().Partition, + Project: testresources.Network1().Project, + Type: apiv2.NetworkType_NETWORK_TYPE_EXTERNAL, + Prefixes: testresources.Network1().Prefixes, + NatType: apiv2.NATType_NAT_TYPE_NONE.Enum(), + ParentNetwork: testresources.Network1().ParentNetwork, + AdditionalAnnouncableCidrs: testresources.Network1().AdditionalAnnouncableCidrs, + Labels: testresources.Network1().Meta.Labels, + AddressFamily: apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_DUAL_STACK.Enum(), + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.NetworkServiceCreateResponse{ + Network: testresources.Network1(), + }) + }, + }, + }, + }), + WantTable: new(` + ID NAME TYPE PROJECT PARTITION NAT PREFIXES PREFIX USAGE IP USAGE + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet external 0d81bca7-73f6-4da3-8397-4a8c52a0c583 partition-1 none 10.0.0.0/16,2001:db8::/32 + `), + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} + +func Test_AdminNetworkCmd_Delete(t *testing.T) { + tests := []*e2e.Test[adminv2.NetworkServiceDeleteResponse, *apiv2.Network]{ + { + Name: "delete", + CmdArgs: []string{"admin", "network", "delete", testresources.Network1().Id}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.NetworkServiceDeleteRequest{ + Id: testresources.Network1().Id, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.NetworkServiceDeleteResponse{ + Network: testresources.Network1(), + }) + }, + }, + }, + }), + WantProtoObject: testresources.Network1(), + WantTable: new(` + ID NAME TYPE PROJECT PARTITION NAT PREFIXES PREFIX USAGE IP USAGE + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet external 0d81bca7-73f6-4da3-8397-4a8c52a0c583 partition-1 none 10.0.0.0/16,2001:db8::/32 + `), + }, + { + Name: "delete from file", + CmdArgs: append([]string{"admin", "network", "delete"}, e2e.AppendFromFileCommonArgs()...), + NewRootCmd: e2erootcmd.NewRootCmd(t, + &e2erootcmd.TestConfig{ + FsMocks: func(fs *afero.Afero) { + require.NoError(t, fs.WriteFile(e2e.InputFilePath, e2e.MustMarshal(t, testresources.Network1()), 0755)) + }, + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.NetworkServiceDeleteRequest{ + Id: testresources.Network1().Id, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.NetworkServiceDeleteResponse{ + Network: testresources.Network1(), + }) + }, + }, + }, + }), + WantTable: new(` + ID NAME TYPE PROJECT PARTITION NAT PREFIXES PREFIX USAGE IP USAGE + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet external 0d81bca7-73f6-4da3-8397-4a8c52a0c583 partition-1 none 10.0.0.0/16,2001:db8::/32 + `), + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} + +func Test_AdminNetworkCmd_Update(t *testing.T) { + tests := []*e2e.Test[adminv2.NetworkServiceUpdateResponse, *apiv2.Network]{ + { + Name: "update", + CmdArgs: []string{"admin", "network", "update", testresources.Network1().Id, "--name", "foo"}, + NewRootCmd: e2erootcmd.NewRootCmd(t, + &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.NetworkServiceUpdateRequest{ + Id: testresources.Network1().Id, + Name: new("foo"), + UpdateMeta: &apiv2.UpdateMeta{ + LockingStrategy: apiv2.OptimisticLockingStrategy_OPTIMISTIC_LOCKING_STRATEGY_SERVER, + }, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.NetworkServiceUpdateResponse{ + Network: testresources.Network1(), + }) + }, + }, + }, + }), + WantProtoObject: testresources.Network1(), + WantTable: new(` + ID NAME TYPE PROJECT PARTITION NAT PREFIXES PREFIX USAGE IP USAGE + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet external 0d81bca7-73f6-4da3-8397-4a8c52a0c583 partition-1 none 10.0.0.0/16,2001:db8::/32 + `), + }, + { + Name: "update from file", + CmdArgs: append([]string{"admin", "network", "update"}, e2e.AppendFromFileCommonArgs()...), + NewRootCmd: e2erootcmd.NewRootCmd(t, + &e2erootcmd.TestConfig{ + FsMocks: func(fs *afero.Afero) { + require.NoError(t, fs.WriteFile(e2e.InputFilePath, e2e.MustMarshal(t, testresources.Network1()), 0755)) + }, + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.NetworkServiceUpdateRequest{ + Id: testresources.Network1().Id, + Description: testresources.Network1().Description, + Name: testresources.Network1().Name, + Labels: &apiv2.UpdateLabels{ + Strategy: &apiv2.UpdateLabels_Replace{ + Replace: &apiv2.Labels{ + Labels: testresources.Network1().Meta.Labels.Labels, + }, + }, + }, + NatType: &testresources.Network1().NatType, + Prefixes: testresources.Network1().Prefixes, + UpdateMeta: &apiv2.UpdateMeta{ + LockingStrategy: apiv2.OptimisticLockingStrategy_OPTIMISTIC_LOCKING_STRATEGY_SERVER, + }, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.NetworkServiceUpdateResponse{ + Network: testresources.Network1(), + }) + }, + }, + }, + }), + WantTable: new(` + ID NAME TYPE PROJECT PARTITION NAT PREFIXES PREFIX USAGE IP USAGE + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet external 0d81bca7-73f6-4da3-8397-4a8c52a0c583 partition-1 none 10.0.0.0/16,2001:db8::/32 + `), + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} diff --git a/tests/e2e/api/network_test.go b/tests/e2e/api/network_test.go new file mode 100644 index 0000000..47849f5 --- /dev/null +++ b/tests/e2e/api/network_test.go @@ -0,0 +1,405 @@ +package api_e2e + +import ( + "fmt" + "testing" + + "connectrpc.com/connect" + "github.com/metal-stack/api/go/client" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + e2erootcmd "github.com/metal-stack/cli/testing/e2e" + "github.com/metal-stack/cli/tests/e2e/testresources" + "github.com/metal-stack/metal-lib/pkg/genericcli/e2e" + "github.com/spf13/afero" + "github.com/stretchr/testify/require" +) + +func Test_NetworkCmd_List(t *testing.T) { + tests := []*e2e.Test[apiv2.NetworkServiceListResponse, apiv2.Network]{ + { + Name: "list", + CmdArgs: []string{"network", "list", "--project", *testresources.Network1().Project}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &apiv2.NetworkServiceListRequest{ + Project: *testresources.Network1().Project, + Query: &apiv2.NetworkQuery{ + Project: testresources.Network1().Project, + }, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&apiv2.NetworkServiceListResponse{ + Networks: []*apiv2.Network{ + testresources.Network1(), + testresources.Network2(), + }, + }) + }, + }, + }, + }), + WantTable: new(` + ID NAME TYPE PROJECT PARTITION NAT PREFIXES PREFIX USAGE IP USAGE + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet external 0d81bca7-73f6-4da3-8397-4a8c52a0c583 partition-1 none 10.0.0.0/16,2001:db8::/32 + d83ffb0a-7aa6-4a66-8e03-0b5ee8b718a0 private child f3b4e6a1-2c8d-4e5f-a7b9-1d3e5f7a9b0c partition-1 ipv4-masq 192.168.1.0/24 + `), + WantWideTable: new(` + ID DESCRIPTION NAME TYPE PROJECT PARTITION NAT PREFIXES ANNOTATIONS + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet network internet external 0d81bca7-73f6-4da3-8397-4a8c52a0c583 partition-1 none 10.0.0.0/16,2001:db8::/32 cluster.metal-stack.io/id/namespace/service=/default/ingress-nginx + d83ffb0a-7aa6-4a66-8e03-0b5ee8b718a0 private network private child f3b4e6a1-2c8d-4e5f-a7b9-1d3e5f7a9b0c partition-1 ipv4-masq 192.168.1.0/24 a=b + `), + Template: new("{{ .id }} {{ .name }}"), + WantTemplate: new(` +6988ebb0-9531-4f9b-a893-d7868258e2ef internet +d83ffb0a-7aa6-4a66-8e03-0b5ee8b718a0 private + `), + WantMarkdown: new(` + | ID | NAME | TYPE | PROJECT | PARTITION | NAT | PREFIXES | PREFIX USAGE | IP USAGE | + |--------------------------------------|----------|----------|--------------------------------------|-------------|-----------|---------------------------|--------------|----------| + | 6988ebb0-9531-4f9b-a893-d7868258e2ef | internet | external | 0d81bca7-73f6-4da3-8397-4a8c52a0c583 | partition-1 | none | 10.0.0.0/16,2001:db8::/32 |   |   | + | d83ffb0a-7aa6-4a66-8e03-0b5ee8b718a0 | private | child | f3b4e6a1-2c8d-4e5f-a7b9-1d3e5f7a9b0c | partition-1 | ipv4-masq | 192.168.1.0/24 |   |   | + `), + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} + +func Test_NetworkCmd_Describe(t *testing.T) { + tests := []*e2e.Test[apiv2.NetworkServiceGetResponse, *apiv2.Network]{ + { + Name: "describe", + CmdArgs: []string{"network", "describe", testresources.Network1().Id}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &apiv2.NetworkServiceGetRequest{ + Id: testresources.Network1().Id, + Project: "", + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&apiv2.NetworkServiceGetResponse{ + Network: testresources.Network1(), + }) + }, + }, + }, + }), + WantProtoObject: testresources.Network1(), + WantTable: new(` + ID NAME TYPE PROJECT PARTITION NAT PREFIXES PREFIX USAGE IP USAGE + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet external 0d81bca7-73f6-4da3-8397-4a8c52a0c583 partition-1 none 10.0.0.0/16,2001:db8::/32 + `), + WantWideTable: new(` + ID DESCRIPTION NAME TYPE PROJECT PARTITION NAT PREFIXES ANNOTATIONS + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet network internet external 0d81bca7-73f6-4da3-8397-4a8c52a0c583 partition-1 none 10.0.0.0/16,2001:db8::/32 cluster.metal-stack.io/id/namespace/service=/default/ingress-nginx + `), + Template: new("{{ .id }} {{ .name }}"), + WantTemplate: new(` + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet + `), + WantMarkdown: new(` + | ID | NAME | TYPE | PROJECT | PARTITION | NAT | PREFIXES | PREFIX USAGE | IP USAGE | + |--------------------------------------|----------|----------|--------------------------------------|-------------|------|---------------------------|--------------|----------| + | 6988ebb0-9531-4f9b-a893-d7868258e2ef | internet | external | 0d81bca7-73f6-4da3-8397-4a8c52a0c583 | partition-1 | none | 10.0.0.0/16,2001:db8::/32 |   |   | + `), + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} + +func Test_NetworkCmd_Create(t *testing.T) { + tests := []*e2e.Test[apiv2.NetworkServiceCreateResponse, *apiv2.Network]{ + { + Name: "create", + CmdArgs: []string{"network", "create", + "--project", *testresources.Network1().Project, + "--name", *testresources.Network1().Name, + "--partition", *testresources.Network1().Partition, + }, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &apiv2.NetworkServiceCreateRequest{ + Project: *testresources.Network1().Project, + Name: testresources.Network1().Name, + Partition: testresources.Network1().Partition, + Length: &apiv2.ChildPrefixLength{}, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&apiv2.NetworkServiceCreateResponse{ + Network: testresources.Network1(), + }) + }, + }, + }, + }), + WantProtoObject: testresources.Network1(), + }, + { + Name: "create from file", + CmdArgs: append([]string{"network", "create"}, e2e.AppendFromFileCommonArgs()...), + NewRootCmd: e2erootcmd.NewRootCmd(t, + &e2erootcmd.TestConfig{ + FsMocks: func(fs *afero.Afero) { + require.NoError(t, fs.WriteFile(e2e.InputFilePath, e2e.MustMarshal(t, testresources.Network1()), 0755)) + }, + ClientCalls: []client.ClientCall{ + { + WantRequest: &apiv2.NetworkServiceCreateRequest{ + Project: *testresources.Network1().Project, + Name: testresources.Network1().Name, + Description: testresources.Network1().Description, + Partition: testresources.Network1().Partition, + Labels: testresources.Network1().Meta.Labels, + ParentNetwork: testresources.Network1().ParentNetwork, + AddressFamily: apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_DUAL_STACK.Enum(), + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&apiv2.NetworkServiceCreateResponse{ + Network: testresources.Network1(), + }) + }, + }, + }, + }), + WantTable: new(` + ID NAME TYPE PROJECT PARTITION NAT PREFIXES PREFIX USAGE IP USAGE + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet external 0d81bca7-73f6-4da3-8397-4a8c52a0c583 partition-1 none 10.0.0.0/16,2001:db8::/32 + `), + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} + +func Test_NetworkCmd_Delete(t *testing.T) { + tests := []*e2e.Test[apiv2.NetworkServiceDeleteResponse, *apiv2.Network]{ + { + Name: "delete", + CmdArgs: []string{"network", "delete", testresources.Network1().Id, "--project", *testresources.Network1().Project}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &apiv2.NetworkServiceDeleteRequest{ + Id: testresources.Network1().Id, + Project: *testresources.Network1().Project, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&apiv2.NetworkServiceDeleteResponse{ + Network: testresources.Network1(), + }) + }, + }, + }, + }), + WantProtoObject: testresources.Network1(), + }, + { + Name: "delete from file", + CmdArgs: append([]string{"network", "delete"}, e2e.AppendFromFileCommonArgs()...), + NewRootCmd: e2erootcmd.NewRootCmd(t, + &e2erootcmd.TestConfig{ + FsMocks: func(fs *afero.Afero) { + require.NoError(t, fs.WriteFile(e2e.InputFilePath, e2e.MustMarshal(t, testresources.Network1()), 0755)) + }, + ClientCalls: []client.ClientCall{ + { + WantRequest: &apiv2.NetworkServiceDeleteRequest{ + Id: testresources.Network1().Id, + Project: *testresources.Network1().Project, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&apiv2.NetworkServiceDeleteResponse{ + Network: testresources.Network1(), + }) + }, + }, + }, + }), + WantTable: new(` + ID NAME TYPE PROJECT PARTITION NAT PREFIXES PREFIX USAGE IP USAGE + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet external 0d81bca7-73f6-4da3-8397-4a8c52a0c583 partition-1 none 10.0.0.0/16,2001:db8::/32 + `), + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} + +func Test_NetworkCmd_Update(t *testing.T) { + tests := []*e2e.Test[apiv2.NetworkServiceUpdateResponse, *apiv2.Network]{ + { + Name: "update", + CmdArgs: []string{"network", "update", testresources.Network1().Id, "--project", *testresources.Network1().Project, "--name", "foo"}, + NewRootCmd: e2erootcmd.NewRootCmd(t, + &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &apiv2.NetworkServiceUpdateRequest{ + Id: testresources.Network1().Id, + Project: *testresources.Network1().Project, + Name: new("foo"), + UpdateMeta: &apiv2.UpdateMeta{ + LockingStrategy: apiv2.OptimisticLockingStrategy_OPTIMISTIC_LOCKING_STRATEGY_SERVER, + }, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&apiv2.NetworkServiceUpdateResponse{ + Network: testresources.Network1(), + }) + }, + }, + }, + }), + WantProtoObject: testresources.Network1(), + WantTable: new(` + ID NAME TYPE PROJECT PARTITION NAT PREFIXES PREFIX USAGE IP USAGE + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet external 0d81bca7-73f6-4da3-8397-4a8c52a0c583 partition-1 none 10.0.0.0/16,2001:db8::/32 + `), + WantWideTable: new(` + ID DESCRIPTION NAME TYPE PROJECT PARTITION NAT PREFIXES ANNOTATIONS + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet network internet external 0d81bca7-73f6-4da3-8397-4a8c52a0c583 partition-1 none 10.0.0.0/16,2001:db8::/32 cluster.metal-stack.io/id/namespace/service=/default/ingress-nginx + `), + }, + { + Name: "update from file", + CmdArgs: append([]string{"network", "update"}, e2e.AppendFromFileCommonArgs()...), + NewRootCmd: e2erootcmd.NewRootCmd(t, + &e2erootcmd.TestConfig{ + FsMocks: func(fs *afero.Afero) { + require.NoError(t, fs.WriteFile(e2e.InputFilePath, e2e.MustMarshal(t, testresources.Network1()), 0755)) + }, + ClientCalls: []client.ClientCall{ + { + WantRequest: &apiv2.NetworkServiceUpdateRequest{ + Id: testresources.Network1().Id, + Project: *testresources.Network1().Project, + Description: testresources.Network1().Description, + Name: testresources.Network1().Name, + Labels: &apiv2.UpdateLabels{ + Strategy: &apiv2.UpdateLabels_Replace{ + Replace: &apiv2.Labels{ + Labels: testresources.Network1().Meta.Labels.Labels, + }, + }, + }, + UpdateMeta: &apiv2.UpdateMeta{ + LockingStrategy: apiv2.OptimisticLockingStrategy_OPTIMISTIC_LOCKING_STRATEGY_SERVER, + }, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&apiv2.NetworkServiceUpdateResponse{ + Network: testresources.Network1(), + }) + }, + }, + }, + }), + WantTable: new(` + ID NAME TYPE PROJECT PARTITION NAT PREFIXES PREFIX USAGE IP USAGE + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet external 0d81bca7-73f6-4da3-8397-4a8c52a0c583 partition-1 none 10.0.0.0/16,2001:db8::/32 + `), + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} + +func Test_NetworkCmd_Apply(t *testing.T) { + tests := []*e2e.Test[apiv2.NetworkServiceCreateResponse, *apiv2.Network]{ + { + Name: "apply", + CmdArgs: append([]string{"network", "apply"}, e2e.AppendFromFileCommonArgs()...), + NewRootCmd: e2erootcmd.NewRootCmd(t, + &e2erootcmd.TestConfig{ + FsMocks: func(fs *afero.Afero) { + require.NoError(t, fs.WriteFile(e2e.InputFilePath, e2e.MustMarshal(t, testresources.Network1()), 0755)) + }, + ClientCalls: []client.ClientCall{ + { + WantRequest: &apiv2.NetworkServiceCreateRequest{ + Project: *testresources.Network1().Project, + Name: testresources.Network1().Name, + Description: testresources.Network1().Description, + Partition: testresources.Network1().Partition, + Labels: testresources.Network1().Meta.Labels, + ParentNetwork: testresources.Network1().ParentNetwork, + AddressFamily: apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_DUAL_STACK.Enum(), + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&apiv2.NetworkServiceCreateResponse{ + Network: testresources.Network1(), + }) + }, + }, + }, + }), + WantTable: new(` + ID NAME TYPE PROJECT PARTITION NAT PREFIXES PREFIX USAGE IP USAGE + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet external 0d81bca7-73f6-4da3-8397-4a8c52a0c583 partition-1 none 10.0.0.0/16,2001:db8::/32 + `), + }, + { + Name: "apply already exists", + CmdArgs: append([]string{"network", "apply"}, e2e.AppendFromFileCommonArgs()...), + NewRootCmd: e2erootcmd.NewRootCmd(t, + &e2erootcmd.TestConfig{ + FsMocks: func(fs *afero.Afero) { + require.NoError(t, fs.WriteFile(e2e.InputFilePath, e2e.MustMarshal(t, testresources.Network1()), 0755)) + }, + ClientCalls: []client.ClientCall{ + { + WantRequest: &apiv2.NetworkServiceCreateRequest{ + Project: *testresources.Network1().Project, + Name: testresources.Network1().Name, + Description: testresources.Network1().Description, + Partition: testresources.Network1().Partition, + Labels: testresources.Network1().Meta.Labels, + ParentNetwork: testresources.Network1().ParentNetwork, + AddressFamily: apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_DUAL_STACK.Enum(), + }, + WantError: connect.NewError(connect.CodeAlreadyExists, fmt.Errorf("already exists")), + }, + { + WantRequest: &apiv2.NetworkServiceUpdateRequest{ + Id: testresources.Network1().Id, + Project: *testresources.Network1().Project, + Description: testresources.Network1().Description, + Name: testresources.Network1().Name, + Labels: &apiv2.UpdateLabels{ + Strategy: &apiv2.UpdateLabels_Replace{ + Replace: &apiv2.Labels{ + Labels: testresources.Network1().Meta.Labels.Labels, + }, + }, + }, + UpdateMeta: &apiv2.UpdateMeta{ + LockingStrategy: apiv2.OptimisticLockingStrategy_OPTIMISTIC_LOCKING_STRATEGY_SERVER, + }, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&apiv2.NetworkServiceUpdateResponse{ + Network: testresources.Network1(), + }) + }, + }, + }, + }), + WantTable: new(` + ID NAME TYPE PROJECT PARTITION NAT PREFIXES PREFIX USAGE IP USAGE + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet external 0d81bca7-73f6-4da3-8397-4a8c52a0c583 partition-1 none 10.0.0.0/16,2001:db8::/32 + `), + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} diff --git a/tests/e2e/testresources/networks.go b/tests/e2e/testresources/networks.go new file mode 100644 index 0000000..9fd525f --- /dev/null +++ b/tests/e2e/testresources/networks.go @@ -0,0 +1,52 @@ +package testresources + +import ( + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + e2e "github.com/metal-stack/metal-lib/pkg/genericcli/e2e" + tag "github.com/metal-stack/metal-lib/pkg/tag" + "google.golang.org/protobuf/types/known/timestamppb" +) + +var ( + Network1 = func() *apiv2.Network { + return &apiv2.Network{ + Id: "6988ebb0-9531-4f9b-a893-d7868258e2ef", + Name: new("internet"), + Description: new("internet network"), + Partition: new(Partition1().Id), + Project: new(Project1().Uuid), + Type: apiv2.NetworkType_NETWORK_TYPE_EXTERNAL, + NatType: apiv2.NATType_NAT_TYPE_NONE, + Prefixes: []string{"10.0.0.0/16", "2001:db8::/32"}, + Meta: &apiv2.Meta{ + CreatedAt: timestamppb.New(e2e.TimeBubbleStartTime()), + Labels: &apiv2.Labels{ + Labels: map[string]string{ + tag.ClusterServiceFQN: "/default/ingress-nginx", + }, + }, + }, + } + } + Network2 = func() *apiv2.Network { + return &apiv2.Network{ + Id: "d83ffb0a-7aa6-4a66-8e03-0b5ee8b718a0", + Name: new("private"), + Description: new("private network"), + Partition: new(Partition1().Id), + Project: new(Project2().Uuid), + Type: apiv2.NetworkType_NETWORK_TYPE_CHILD, + NatType: apiv2.NATType_NAT_TYPE_IPV4_MASQUERADE, + Prefixes: []string{"192.168.1.0/24"}, + Vrf: new(uint32(100)), + Meta: &apiv2.Meta{ + CreatedAt: timestamppb.New(e2e.TimeBubbleStartTime()), + Labels: &apiv2.Labels{ + Labels: map[string]string{ + "a": "b", + }, + }, + }, + } + } +) From 830e1a37cd13fb3b264aa09ff2f5563715ce055a Mon Sep 17 00:00:00 2001 From: Gerrit Date: Wed, 29 Jul 2026 16:29:39 +0200 Subject: [PATCH 03/13] Fixes. --- cmd/api/v2/network.go | 9 +++ docs/admin/metalctlv2_admin_network_create.md | 2 +- docs/metalctlv2_network_create.md | 2 +- docs/metalctlv2_network_delete.md | 1 + tests/e2e/admin/network_test.go | 20 +++---- tests/e2e/api/network_test.go | 59 +++++++++++++++---- tests/e2e/testresources/networks.go | 19 +++--- 7 files changed, 78 insertions(+), 34 deletions(-) diff --git a/cmd/api/v2/network.go b/cmd/api/v2/network.go index 02d376f..94ccbcc 100644 --- a/cmd/api/v2/network.go +++ b/cmd/api/v2/network.go @@ -139,6 +139,15 @@ func (c *networkCmd) List() ([]*apiv2.Network, error) { }, } + if viper.IsSet("type") { + nwType, err := enum.GetEnum[apiv2.NetworkType](viper.GetString("type")) + if err != nil { + return nil, err + } + + req.Query.Type = &nwType + } + if labelSlice := viper.GetStringSlice("labels"); len(labelSlice) > 0 { var err error diff --git a/docs/admin/metalctlv2_admin_network_create.md b/docs/admin/metalctlv2_admin_network_create.md index 0b6ff1d..3f642d7 100644 --- a/docs/admin/metalctlv2_admin_network_create.md +++ b/docs/admin/metalctlv2_admin_network_create.md @@ -40,7 +40,7 @@ metalctlv2 admin network create [flags] --parent-network string the parent of the network (alternative to partition). [optional] --partition string partition where this network should exist. [required] --prefixes strings prefixes for this network. [optional] - --project string partition where this network should exist (alternative to parent-network). [optional] + --project string project of this network. [optional] --skip-security-prompts skips security prompt for bulk operations --timestamps when used with --file (bulk operation): prints timestamps in-between the operations -t, --type string type of the network. [required] diff --git a/docs/metalctlv2_network_create.md b/docs/metalctlv2_network_create.md index c9a669b..a7973f5 100644 --- a/docs/metalctlv2_network_create.md +++ b/docs/metalctlv2_network_create.md @@ -31,7 +31,7 @@ metalctlv2 network create [flags] --name string name of the network to create. [required] --parent-network 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). [optional] + --project string project of this network. [optional] --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_network_delete.md b/docs/metalctlv2_network_delete.md index c88f9b9..231471f 100644 --- a/docs/metalctlv2_network_delete.md +++ b/docs/metalctlv2_network_delete.md @@ -23,6 +23,7 @@ metalctlv2 network delete [flags] the file can also contain multiple documents and perform a bulk operation. -h, --help help for delete + --project string project of this network. --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/tests/e2e/admin/network_test.go b/tests/e2e/admin/network_test.go index 861908d..7b3205c 100644 --- a/tests/e2e/admin/network_test.go +++ b/tests/e2e/admin/network_test.go @@ -37,14 +37,14 @@ func Test_AdminNetworkCmd_List(t *testing.T) { }, }), WantTable: new(` - ID NAME TYPE PROJECT PARTITION NAT PREFIXES PREFIX USAGE IP USAGE - 6988ebb0-9531-4f9b-a893-d7868258e2ef internet external 0d81bca7-73f6-4da3-8397-4a8c52a0c583 partition-1 none 10.0.0.0/16,2001:db8::/32 - d83ffb0a-7aa6-4a66-8e03-0b5ee8b718a0 private child f3b4e6a1-2c8d-4e5f-a7b9-1d3e5f7a9b0c partition-1 ipv4-masq 192.168.1.0/24 + ID NAME TYPE PROJECT PARTITION NAT PREFIXES PREFIX USAGE IP USAGE + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet external 0d81bca7-73f6-4da3-8397-4a8c52a0c583 partition-1 none 10.0.0.0/16,2001:db8::/32 + └─╴d83ffb0a-7aa6-4a66-8e03-0b5ee8b718a0 private child f3b4e6a1-2c8d-4e5f-a7b9-1d3e5f7a9b0c partition-1 ipv4-masq 192.168.1.0/24 `), WantWideTable: new(` - ID DESCRIPTION NAME TYPE PROJECT PARTITION NAT PREFIXES ANNOTATIONS - 6988ebb0-9531-4f9b-a893-d7868258e2ef internet network internet external 0d81bca7-73f6-4da3-8397-4a8c52a0c583 partition-1 none 10.0.0.0/16,2001:db8::/32 cluster.metal-stack.io/id/namespace/service=/default/ingress-nginx - d83ffb0a-7aa6-4a66-8e03-0b5ee8b718a0 private network private child f3b4e6a1-2c8d-4e5f-a7b9-1d3e5f7a9b0c partition-1 ipv4-masq 192.168.1.0/24 a=b + ID DESCRIPTION NAME TYPE PROJECT PARTITION NAT PREFIXES ANNOTATIONS + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet network internet external 0d81bca7-73f6-4da3-8397-4a8c52a0c583 partition-1 none 10.0.0.0/16,2001:db8::/32 cluster.metal-stack.io/id/namespace/service=/default/ingress-nginx + └─╴d83ffb0a-7aa6-4a66-8e03-0b5ee8b718a0 private network private child f3b4e6a1-2c8d-4e5f-a7b9-1d3e5f7a9b0c partition-1 ipv4-masq 192.168.1.0/24 a=b `), Template: new("{{ .id }} {{ .name }}"), WantTemplate: new(` @@ -52,10 +52,10 @@ func Test_AdminNetworkCmd_List(t *testing.T) { d83ffb0a-7aa6-4a66-8e03-0b5ee8b718a0 private `), WantMarkdown: new(` - | ID | NAME | TYPE | PROJECT | PARTITION | NAT | PREFIXES | PREFIX USAGE | IP USAGE | - |--------------------------------------|----------|----------|--------------------------------------|-------------|-----------|---------------------------|--------------|----------| - | 6988ebb0-9531-4f9b-a893-d7868258e2ef | internet | external | 0d81bca7-73f6-4da3-8397-4a8c52a0c583 | partition-1 | none | 10.0.0.0/16,2001:db8::/32 |   |   | - | d83ffb0a-7aa6-4a66-8e03-0b5ee8b718a0 | private | child | f3b4e6a1-2c8d-4e5f-a7b9-1d3e5f7a9b0c | partition-1 | ipv4-masq | 192.168.1.0/24 |   |   | + | ID | NAME | TYPE | PROJECT | PARTITION | NAT | PREFIXES | PREFIX USAGE | IP USAGE | + |-----------------------------------------|----------|----------|--------------------------------------|-------------|-----------|---------------------------|--------------|----------| + | 6988ebb0-9531-4f9b-a893-d7868258e2ef | internet | external | 0d81bca7-73f6-4da3-8397-4a8c52a0c583 | partition-1 | none | 10.0.0.0/16,2001:db8::/32 |   |   | + | └─╴d83ffb0a-7aa6-4a66-8e03-0b5ee8b718a0 | private | child | f3b4e6a1-2c8d-4e5f-a7b9-1d3e5f7a9b0c | partition-1 | ipv4-masq | 192.168.1.0/24 |   |   | `), }, } diff --git a/tests/e2e/api/network_test.go b/tests/e2e/api/network_test.go index 47849f5..e7bc07e 100644 --- a/tests/e2e/api/network_test.go +++ b/tests/e2e/api/network_test.go @@ -2,6 +2,8 @@ package api_e2e import ( "fmt" + "strconv" + "strings" "testing" "connectrpc.com/connect" @@ -10,6 +12,7 @@ import ( e2erootcmd "github.com/metal-stack/cli/testing/e2e" "github.com/metal-stack/cli/tests/e2e/testresources" "github.com/metal-stack/metal-lib/pkg/genericcli/e2e" + "github.com/metal-stack/metal-lib/pkg/pointer" "github.com/spf13/afero" "github.com/stretchr/testify/require" ) @@ -17,15 +20,45 @@ import ( func Test_NetworkCmd_List(t *testing.T) { tests := []*e2e.Test[apiv2.NetworkServiceListResponse, apiv2.Network]{ { - Name: "list", - CmdArgs: []string{"network", "list", "--project", *testresources.Network1().Project}, + Name: "list", + CmdArgs: []string{"network", "list", + "--project", *testresources.Network1().Project, + "--addressfamily", "dual-stack", + "--description", *testresources.Network1().Description, + "--destination-prefixes", strings.Join(testresources.Network1().DestinationPrefixes, ","), + "--prefixes", strings.Join(testresources.Network1().Prefixes, ","), + "--id", testresources.Network1().Id, + "--vrf", strconv.Itoa(int(pointer.SafeDeref(testresources.Network1().Vrf))), + "--type", "external", + "--labels", "a=b", + "--name", *testresources.Network1().Name, + "--partition", *testresources.Network1().Partition, + "--parent-network", *testresources.Network2().ParentNetwork, + }, + AssertExhaustiveArgs: true, + AssertExhaustiveExcludes: []string{"sort-by"}, NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ ClientCalls: []client.ClientCall{ { WantRequest: &apiv2.NetworkServiceListRequest{ Project: *testresources.Network1().Project, Query: &apiv2.NetworkQuery{ - Project: testresources.Network1().Project, + Project: testresources.Network1().Project, + Id: &testresources.Network1().Id, + Name: testresources.Network1().Name, + Description: testresources.Network1().Description, + Partition: testresources.Network1().Partition, + // Namespace: new(string), + // NatType: , + Prefixes: testresources.Network1().Prefixes, + DestinationPrefixes: testresources.Network1().DestinationPrefixes, + Vrf: testresources.Network1().Vrf, + ParentNetwork: testresources.Network2().ParentNetwork, + AddressFamily: apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_DUAL_STACK.Enum(), + Type: &testresources.Network1().Type, + Labels: &apiv2.Labels{ + Labels: map[string]string{"a": "b"}, + }, }, }, WantResponse: func() connect.AnyResponse { @@ -40,14 +73,14 @@ func Test_NetworkCmd_List(t *testing.T) { }, }), WantTable: new(` - ID NAME TYPE PROJECT PARTITION NAT PREFIXES PREFIX USAGE IP USAGE - 6988ebb0-9531-4f9b-a893-d7868258e2ef internet external 0d81bca7-73f6-4da3-8397-4a8c52a0c583 partition-1 none 10.0.0.0/16,2001:db8::/32 - d83ffb0a-7aa6-4a66-8e03-0b5ee8b718a0 private child f3b4e6a1-2c8d-4e5f-a7b9-1d3e5f7a9b0c partition-1 ipv4-masq 192.168.1.0/24 + ID NAME TYPE PROJECT PARTITION NAT PREFIXES PREFIX USAGE IP USAGE + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet external 0d81bca7-73f6-4da3-8397-4a8c52a0c583 partition-1 none 10.0.0.0/16,2001:db8::/32 + └─╴d83ffb0a-7aa6-4a66-8e03-0b5ee8b718a0 private child f3b4e6a1-2c8d-4e5f-a7b9-1d3e5f7a9b0c partition-1 ipv4-masq 192.168.1.0/24 `), WantWideTable: new(` - ID DESCRIPTION NAME TYPE PROJECT PARTITION NAT PREFIXES ANNOTATIONS - 6988ebb0-9531-4f9b-a893-d7868258e2ef internet network internet external 0d81bca7-73f6-4da3-8397-4a8c52a0c583 partition-1 none 10.0.0.0/16,2001:db8::/32 cluster.metal-stack.io/id/namespace/service=/default/ingress-nginx - d83ffb0a-7aa6-4a66-8e03-0b5ee8b718a0 private network private child f3b4e6a1-2c8d-4e5f-a7b9-1d3e5f7a9b0c partition-1 ipv4-masq 192.168.1.0/24 a=b + ID DESCRIPTION NAME TYPE PROJECT PARTITION NAT PREFIXES ANNOTATIONS + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet network internet external 0d81bca7-73f6-4da3-8397-4a8c52a0c583 partition-1 none 10.0.0.0/16,2001:db8::/32 cluster.metal-stack.io/id/namespace/service=/default/ingress-nginx + └─╴d83ffb0a-7aa6-4a66-8e03-0b5ee8b718a0 private network private child f3b4e6a1-2c8d-4e5f-a7b9-1d3e5f7a9b0c partition-1 ipv4-masq 192.168.1.0/24 a=b `), Template: new("{{ .id }} {{ .name }}"), WantTemplate: new(` @@ -55,10 +88,10 @@ func Test_NetworkCmd_List(t *testing.T) { d83ffb0a-7aa6-4a66-8e03-0b5ee8b718a0 private `), WantMarkdown: new(` - | ID | NAME | TYPE | PROJECT | PARTITION | NAT | PREFIXES | PREFIX USAGE | IP USAGE | - |--------------------------------------|----------|----------|--------------------------------------|-------------|-----------|---------------------------|--------------|----------| - | 6988ebb0-9531-4f9b-a893-d7868258e2ef | internet | external | 0d81bca7-73f6-4da3-8397-4a8c52a0c583 | partition-1 | none | 10.0.0.0/16,2001:db8::/32 |   |   | - | d83ffb0a-7aa6-4a66-8e03-0b5ee8b718a0 | private | child | f3b4e6a1-2c8d-4e5f-a7b9-1d3e5f7a9b0c | partition-1 | ipv4-masq | 192.168.1.0/24 |   |   | + | ID | NAME | TYPE | PROJECT | PARTITION | NAT | PREFIXES | PREFIX USAGE | IP USAGE | + |-----------------------------------------|----------|----------|--------------------------------------|-------------|-----------|---------------------------|--------------|----------| + | 6988ebb0-9531-4f9b-a893-d7868258e2ef | internet | external | 0d81bca7-73f6-4da3-8397-4a8c52a0c583 | partition-1 | none | 10.0.0.0/16,2001:db8::/32 |   |   | + | └─╴d83ffb0a-7aa6-4a66-8e03-0b5ee8b718a0 | private | child | f3b4e6a1-2c8d-4e5f-a7b9-1d3e5f7a9b0c | partition-1 | ipv4-masq | 192.168.1.0/24 |   |   | `), }, } diff --git a/tests/e2e/testresources/networks.go b/tests/e2e/testresources/networks.go index 9fd525f..b8c4c92 100644 --- a/tests/e2e/testresources/networks.go +++ b/tests/e2e/testresources/networks.go @@ -30,15 +30,16 @@ var ( } Network2 = func() *apiv2.Network { return &apiv2.Network{ - Id: "d83ffb0a-7aa6-4a66-8e03-0b5ee8b718a0", - Name: new("private"), - Description: new("private network"), - Partition: new(Partition1().Id), - Project: new(Project2().Uuid), - Type: apiv2.NetworkType_NETWORK_TYPE_CHILD, - NatType: apiv2.NATType_NAT_TYPE_IPV4_MASQUERADE, - Prefixes: []string{"192.168.1.0/24"}, - Vrf: new(uint32(100)), + Id: "d83ffb0a-7aa6-4a66-8e03-0b5ee8b718a0", + Name: new("private"), + Description: new("private network"), + Partition: new(Partition1().Id), + Project: new(Project2().Uuid), + Type: apiv2.NetworkType_NETWORK_TYPE_CHILD, + NatType: apiv2.NATType_NAT_TYPE_IPV4_MASQUERADE, + Prefixes: []string{"192.168.1.0/24"}, + Vrf: new(uint32(100)), + ParentNetwork: &Network1().Id, Meta: &apiv2.Meta{ CreatedAt: timestamppb.New(e2e.TimeBubbleStartTime()), Labels: &apiv2.Labels{ From f11537219c353bc34fc28875c1987bd3fbeb97f4 Mon Sep 17 00:00:00 2001 From: Gerrit Date: Wed, 29 Jul 2026 16:40:57 +0200 Subject: [PATCH 04/13] More exhaustiveness. --- tests/e2e/admin/network_test.go | 92 +++++++++++++++++++++++++++++---- 1 file changed, 82 insertions(+), 10 deletions(-) diff --git a/tests/e2e/admin/network_test.go b/tests/e2e/admin/network_test.go index 7b3205c..d0e82f9 100644 --- a/tests/e2e/admin/network_test.go +++ b/tests/e2e/admin/network_test.go @@ -1,6 +1,8 @@ package admin_e2e import ( + "strconv" + "strings" "testing" "connectrpc.com/connect" @@ -10,6 +12,7 @@ import ( e2erootcmd "github.com/metal-stack/cli/testing/e2e" "github.com/metal-stack/cli/tests/e2e/testresources" e2e "github.com/metal-stack/metal-lib/pkg/genericcli/e2e" + "github.com/metal-stack/metal-lib/pkg/pointer" "github.com/spf13/afero" "github.com/stretchr/testify/require" ) @@ -17,13 +20,46 @@ import ( func Test_AdminNetworkCmd_List(t *testing.T) { tests := []*e2e.Test[adminv2.NetworkServiceListResponse, apiv2.Network]{ { - Name: "list", - CmdArgs: []string{"admin", "network", "list"}, + Name: "list", + CmdArgs: []string{"admin", "network", "list", + "--project", *testresources.Network1().Project, + "--addressfamily", "dual-stack", + "--description", *testresources.Network1().Description, + "--destination-prefixes", strings.Join(testresources.Network1().DestinationPrefixes, ","), + "--prefixes", strings.Join(testresources.Network1().Prefixes, ","), + "--id", testresources.Network1().Id, + "--vrf", strconv.Itoa(int(pointer.SafeDeref(testresources.Network1().Vrf))), + "--type", "external", + "--nat-type", "none", + "--labels", "a=b", + "--name", *testresources.Network1().Name, + "--partition", *testresources.Network1().Partition, + "--parent-network", *testresources.Network2().ParentNetwork, + }, + AssertExhaustiveArgs: true, + AssertExhaustiveExcludes: []string{"sort-by"}, NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ ClientCalls: []client.ClientCall{ { WantRequest: &adminv2.NetworkServiceListRequest{ - Query: &apiv2.NetworkQuery{}, + Query: &apiv2.NetworkQuery{ + Project: testresources.Network1().Project, + Id: &testresources.Network1().Id, + Name: testresources.Network1().Name, + Description: testresources.Network1().Description, + Partition: testresources.Network1().Partition, + // Namespace: new(string), + NatType: apiv2.NATType_NAT_TYPE_NONE.Enum(), + Prefixes: testresources.Network1().Prefixes, + DestinationPrefixes: testresources.Network1().DestinationPrefixes, + Vrf: testresources.Network1().Vrf, + ParentNetwork: testresources.Network2().ParentNetwork, + AddressFamily: apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_DUAL_STACK.Enum(), + Type: &testresources.Network1().Type, + Labels: &apiv2.Labels{ + Labels: map[string]string{"a": "b"}, + }, + }, }, WantResponse: func() connect.AnyResponse { return connect.NewResponse(&adminv2.NetworkServiceListResponse{ @@ -115,23 +151,59 @@ func Test_AdminNetworkCmd_Create(t *testing.T) { CmdArgs: []string{"admin", "network", "create", "--id", testresources.Network1().Id, "--name", *testresources.Network1().Name, + "--description", *testresources.Network1().Description, "--type", "external", "--nat-type", "none", + "--parent-network", *testresources.Network2().ParentNetwork, "--partition", *testresources.Network1().Partition, "--project", *testresources.Network1().Project, "--prefixes", "10.0.0.0/16,2001:db8::/32", + "--destination-prefixes", strings.Join(testresources.Network1().DestinationPrefixes, ","), + "--additional-announcable-cidrs", "1.2.3.4/32", + "--addressfamily", "ipv4", + "--default-ipv4-prefix-length", "17", + "--default-ipv6-prefix-length", "64", + "--ipv4-prefix-length", "18", + "--ipv6-prefix-length", "65", + "--labels", "a=b", + "--min-ipv4-prefix-length", "14", + "--min-ipv6-prefix-length", "60", + "--vrf", strconv.Itoa(int(pointer.SafeDeref(testresources.Network1().Vrf))), }, + AssertExhaustiveArgs: true, + AssertExhaustiveExcludes: e2e.CommonExcludedFileArgs(), NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ ClientCalls: []client.ClientCall{ { WantRequest: &adminv2.NetworkServiceCreateRequest{ - Id: &testresources.Network1().Id, - Name: testresources.Network1().Name, - Partition: testresources.Network1().Partition, - Project: testresources.Network1().Project, - Type: apiv2.NetworkType_NETWORK_TYPE_EXTERNAL, - NatType: apiv2.NATType_NAT_TYPE_NONE.Enum(), - Prefixes: []string{"10.0.0.0/16", "2001:db8::/32"}, + Id: &testresources.Network1().Id, + Name: testresources.Network1().Name, + Partition: testresources.Network1().Partition, + Project: testresources.Network1().Project, + Type: apiv2.NetworkType_NETWORK_TYPE_EXTERNAL, + NatType: apiv2.NATType_NAT_TYPE_NONE.Enum(), + Prefixes: []string{"10.0.0.0/16", "2001:db8::/32"}, + AdditionalAnnouncableCidrs: []string{"1.2.3.4/32"}, + Description: testresources.Network1().Description, + DestinationPrefixes: testresources.Network1().DestinationPrefixes, + Vrf: new(uint32(0)), + ParentNetwork: testresources.Network2().ParentNetwork, + AddressFamily: apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_V4.Enum(), + Labels: &apiv2.Labels{ + Labels: map[string]string{"a": "b"}, + }, + DefaultChildPrefixLength: &apiv2.ChildPrefixLength{ + Ipv4: new(uint32(17)), + Ipv6: new(uint32(64)), + }, + MinChildPrefixLength: &apiv2.ChildPrefixLength{ + Ipv4: new(uint32(14)), + Ipv6: new(uint32(60)), + }, + Length: &apiv2.ChildPrefixLength{ + Ipv4: new(uint32(18)), + Ipv6: new(uint32(65)), + }, }, WantResponse: func() connect.AnyResponse { return connect.NewResponse(&adminv2.NetworkServiceCreateResponse{ From 28ffce997401ebfafa3374771db269c5aad2261f Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Thu, 30 Jul 2026 07:54:22 +0200 Subject: [PATCH 05/13] Fixes --- cmd/admin/v2/network.go | 2 +- cmd/completion/network.go | 10 ---------- cmd/tableprinters/network.go | 2 +- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/cmd/admin/v2/network.go b/cmd/admin/v2/network.go index 38befda..ddf7506 100644 --- a/cmd/admin/v2/network.go +++ b/cmd/admin/v2/network.go @@ -83,7 +83,7 @@ func newNetworkCmd(c *config.Config) *cobra.Command { genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.PartitionListCompletion)) 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)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("nat-type", c.Completion.NatTypeCompletion)) genericcli.Must(cmd.RegisterFlagCompletionFunc("parent-network", c.Completion.NetworkListCompletion)) }, ListCmdMutateFn: func(cmd *cobra.Command) { diff --git a/cmd/completion/network.go b/cmd/completion/network.go index 1eb7578..f303e66 100644 --- a/cmd/completion/network.go +++ b/cmd/completion/network.go @@ -54,16 +54,6 @@ func (c *Completion) NatTypeCompletion(cmd *cobra.Command, args []string, toComp 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 -} - func (c *Completion) NetworkAddressFamilyCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { var afs []string for _, af := range []apiv2.NetworkAddressFamily{ diff --git a/cmd/tableprinters/network.go b/cmd/tableprinters/network.go index b8db0b5..1f7f312 100644 --- a/cmd/tableprinters/network.go +++ b/cmd/tableprinters/network.go @@ -121,7 +121,7 @@ func addNetwork(prefix string, n *apiv2.Network, wide bool) ([]string, error) { name = genericcli.TruncateEnd(pointer.SafeDeref(n.Name), 40) project = pointer.SafeDeref(n.Project) partition = pointer.SafeDeref(n.Partition) - natType = n.NatType.String() + natType string ) if t, err := enum.GetStringValue(n.NatType); err == nil { From 622203d4c0866431b8ce7a71a54ef0014d445c11 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Thu, 30 Jul 2026 08:02:18 +0200 Subject: [PATCH 06/13] No repeated completions --- cmd/admin/v2/audit.go | 12 +++++------ cmd/admin/v2/image.go | 12 +++++------ cmd/admin/v2/network.go | 28 ++++++++++++------------- cmd/admin/v2/partition.go | 8 ++++---- cmd/admin/v2/size.go | 2 +- cmd/admin/v2/switch.go | 40 ++++++++++++++++++------------------ cmd/admin/v2/tenant.go | 8 ++++---- cmd/admin/v2/token.go | 16 +++++++-------- cmd/admin/v2/vpn.go | 8 ++++---- cmd/api/v2/audit.go | 12 +++++------ cmd/api/v2/image.go | 4 ++-- cmd/api/v2/ip.go | 12 +++++------ cmd/api/v2/network.go | 26 +++++++++++------------ cmd/api/v2/project.go | 24 +++++++++++----------- cmd/api/v2/size.go | 2 +- cmd/api/v2/tenant.go | 22 ++++++++++---------- cmd/api/v2/token.go | 10 ++++----- cmd/completion/audit.go | 4 ++-- cmd/completion/completion.go | 2 +- cmd/completion/image.go | 4 ++-- cmd/completion/ip.go | 2 +- cmd/completion/network.go | 10 ++++----- cmd/completion/partition.go | 2 +- cmd/completion/project.go | 8 ++++---- cmd/completion/size.go | 2 +- cmd/completion/switch.go | 12 +++++------ cmd/completion/tenant.go | 10 ++++----- cmd/completion/token.go | 14 ++++++------- cmd/context.go | 4 ++-- cmd/login.go | 2 +- 30 files changed, 161 insertions(+), 161 deletions(-) diff --git a/cmd/admin/v2/audit.go b/cmd/admin/v2/audit.go index 96f2cee..3385f18 100644 --- a/cmd/admin/v2/audit.go +++ b/cmd/admin/v2/audit.go @@ -56,10 +56,10 @@ func newAuditCmd(c *config.Config) *cobra.Command { cmd.Flags().Bool("prettify-body", true, "attempts to interpret the body as json and prettifies it.") - genericcli.Must(cmd.RegisterFlagCompletionFunc("phase", c.Completion.AuditPhaseListCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant", c.Completion.TenantListCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("result-code", c.Completion.AuditStatusCodesCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("phase", c.Completion.AuditPhase)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant", c.Completion.Tenant)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("result-code", c.Completion.AuditStatusCodes)) }, DescribeCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().String("tenant", "", "tenant of the audit trace.") @@ -68,8 +68,8 @@ func newAuditCmd(c *config.Config) *cobra.Command { cmd.Flags().Bool("prettify-body", true, "attempts to interpret the body as json and prettifies it.") - genericcli.Must(cmd.RegisterFlagCompletionFunc("phase", c.Completion.AuditPhaseListCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant", c.Completion.TenantListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("phase", c.Completion.AuditPhase)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant", c.Completion.Tenant)) }, } diff --git a/cmd/admin/v2/image.go b/cmd/admin/v2/image.go index 11cf3e6..e405026 100644 --- a/cmd/admin/v2/image.go +++ b/cmd/admin/v2/image.go @@ -36,8 +36,8 @@ func newImageCmd(c *config.Config) *cobra.Command { cmd.Flags().String("feature", "", "image feature to filter for, can be either machine|firewall") cmd.Flags().String("classification", "", "image classification to filter for") - genericcli.Must(cmd.RegisterFlagCompletionFunc("feature", c.Completion.ImageFeaturesCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("classification", c.Completion.ImageClassificationCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("feature", c.Completion.ImageFeatures)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("classification", c.Completion.ImageClassification)) } ) @@ -60,8 +60,8 @@ func newImageCmd(c *config.Config) *cobra.Command { cmd.Flags().StringSlice("features", nil, "image features can be machine and/or firewall") cmd.Flags().StringSlice("labels", nil, "labels to add to the image") - genericcli.Must(cmd.RegisterFlagCompletionFunc("features", c.Completion.ImageFeaturesCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("classification", c.Completion.ImageClassificationCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("features", c.Completion.ImageFeatures)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("classification", c.Completion.ImageClassification)) }, CreateRequestFromCLI: w.createFromCLI, @@ -77,8 +77,8 @@ func newImageCmd(c *config.Config) *cobra.Command { cmd.Flags().StringSlice("add-labels", nil, "labels to add to the image") cmd.Flags().StringSlice("remove-labels", nil, "labels to remove to the image") - genericcli.Must(cmd.RegisterFlagCompletionFunc("features", c.Completion.ImageFeaturesCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("classification", c.Completion.ImageClassificationCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("features", c.Completion.ImageFeatures)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("classification", c.Completion.ImageClassification)) }, UpdateRequestFromCLI: w.updateFromCLI, } diff --git a/cmd/admin/v2/network.go b/cmd/admin/v2/network.go index ddf7506..ce206ab 100644 --- a/cmd/admin/v2/network.go +++ b/cmd/admin/v2/network.go @@ -38,11 +38,11 @@ func newNetworkCmd(c *config.Config) *cobra.Command { cmd.Flags().StringP("type", "t", "", "type of the network. [optional]") cmd.Flags().String("nat-type", "", "nat 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.NetworkAddressFamilyCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("type", c.Completion.NetworkTypeCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("nat-type", c.Completion.NetworkTypeCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.Partition)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.NetworkAddressFamily)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("type", c.Completion.NetworkType)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("nat-type", c.Completion.NetworkType)) } ) @@ -55,7 +55,7 @@ func newNetworkCmd(c *config.Config) *cobra.Command { CreateRequestFromCLI: w.createRequestFromCLI, UpdateRequestFromCLI: w.updateRequestFromCLI, Sorter: sorters.NetworkSorter(), - ValidArgsFn: c.Completion.NetworkAdminListCompletion, + ValidArgsFn: c.Completion.NetworkAdmin, DescribePrinter: func() printers.Printer { return c.DescribePrinter }, ListPrinter: func() printers.Printer { return c.ListPrinter }, CreateCmdMutateFn: func(cmd *cobra.Command) { @@ -79,17 +79,17 @@ 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)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("type", c.Completion.NetworkTypeCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("nat-type", c.Completion.NatTypeCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("parent-network", c.Completion.NetworkListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.Partition)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.NetworkAddressFamily)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("type", c.Completion.NetworkType)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("nat-type", c.Completion.NatType)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("parent-network", c.Completion.Network)) }, ListCmdMutateFn: func(cmd *cobra.Command) { listFlags(cmd) cmd.Flags().String("parent-network", "", "parent network to filter [optional]") - genericcli.Must(cmd.RegisterFlagCompletionFunc("parent-network", c.Completion.NetworkListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("parent-network", c.Completion.Network)) }, UpdateCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().String("name", "", "the name of the network [optional]") @@ -99,7 +99,7 @@ func newNetworkCmd(c *config.Config) *cobra.Command { cmd.Flags().StringSlice("add-labels", nil, "labels to add to the network") cmd.Flags().StringSlice("remove-labels", nil, "labels to remove to the network") - genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) }, } diff --git a/cmd/admin/v2/partition.go b/cmd/admin/v2/partition.go index 842c073..e069a61 100644 --- a/cmd/admin/v2/partition.go +++ b/cmd/admin/v2/partition.go @@ -46,7 +46,7 @@ func newPartitionCmd(c *config.Config) *cobra.Command { Description: "manage partitions", DescribePrinter: func() printers.Printer { return c.DescribePrinter }, ListPrinter: func() printers.Printer { return c.ListPrinter }, - ValidArgsFn: c.Completion.PartitionListCompletion, + ValidArgsFn: c.Completion.Partition, Sorter: sorters.PartitionSorter(), OnlyCmds: genericcli.OnlyCmds( genericcli.DescribeCmd, @@ -93,9 +93,9 @@ func newPartitionCmd(c *config.Config) *cobra.Command { capacityCmd.Flags().StringP("size", "", "", "filter on size id.") capacityCmd.Flags().StringP("project", "", "", "consider project-specific counts, e.g. size reservations.") capacityCmd.Flags().StringSlice("sort-by", []string{}, fmt.Sprintf("order by (comma separated) column(s), sort direction can be changed by appending :asc or :desc behind the column identifier. possible values: %s", strings.Join(sorters.PartitionCapacitySorter().AvailableKeys(), "|"))) - genericcli.Must(capacityCmd.RegisterFlagCompletionFunc("id", c.Completion.PartitionListCompletion)) - genericcli.Must(capacityCmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) - genericcli.Must(capacityCmd.RegisterFlagCompletionFunc("size", c.Completion.SizeListCompletion)) + genericcli.Must(capacityCmd.RegisterFlagCompletionFunc("id", c.Completion.Partition)) + genericcli.Must(capacityCmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + genericcli.Must(capacityCmd.RegisterFlagCompletionFunc("size", c.Completion.Size)) genericcli.Must(capacityCmd.RegisterFlagCompletionFunc("sort-by", cobra.FixedCompletions(sorters.PartitionCapacitySorter().AvailableKeys(), cobra.ShellCompDirectiveNoFileComp))) return genericcli.NewCmds(cmdsConfig, capacityCmd) diff --git a/cmd/admin/v2/size.go b/cmd/admin/v2/size.go index f390108..dc1cd7c 100644 --- a/cmd/admin/v2/size.go +++ b/cmd/admin/v2/size.go @@ -34,7 +34,7 @@ func newSizeCmd(c *config.Config) *cobra.Command { Sorter: sorters.SizeSorter(), DescribePrinter: func() printers.Printer { return c.DescribePrinter }, ListPrinter: func() printers.Printer { return c.ListPrinter }, - ValidArgsFn: c.Completion.SizeListCompletion, + ValidArgsFn: c.Completion.Size, ListCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().StringP("id", "", "", "size id to filter for") cmd.Flags().StringP("name", "", "", "size name to filter for") diff --git a/cmd/admin/v2/switch.go b/cmd/admin/v2/switch.go index 3f13d3c..bf04db1 100644 --- a/cmd/admin/v2/switch.go +++ b/cmd/admin/v2/switch.go @@ -46,7 +46,7 @@ func newSwitchCmd(c *config.Config) *cobra.Command { DescribePrinter: func() printers.Printer { return c.DescribePrinter }, ListPrinter: func() printers.Printer { return c.ListPrinter }, Sorter: sorters.SwitchSorter(), - ValidArgsFn: c.Completion.SwitchListCompletion, + ValidArgsFn: c.Completion.Switch, ListCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().String("id", "", "ID of the switch.") cmd.Flags().String("os-vendor", "", "OS vendor of this switch.") @@ -54,11 +54,11 @@ func newSwitchCmd(c *config.Config) *cobra.Command { cmd.Flags().String("partition", "", "Partition of this switch.") cmd.Flags().String("rack", "", "Rack of this switch.") - genericcli.Must(cmd.RegisterFlagCompletionFunc("id", c.Completion.SwitchListCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.SwitchPartitionListCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("rack", c.Completion.SwitchRackListCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("os-vendor", c.Completion.SwitchOSVendorListCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("os-version", c.Completion.SwitchOSVersionListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("id", c.Completion.Switch)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.SwitchPartition)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("rack", c.Completion.SwitchRack)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("os-vendor", c.Completion.SwitchOSVendor)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("os-version", c.Completion.SwitchOSVersion)) }, DeleteCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().Bool("force", false, "forcefully delete the switch accepting the risk that it still has machines connected to it") @@ -83,9 +83,9 @@ func newSwitchCmd(c *config.Config) *cobra.Command { // switchMachinesCmd.Flags().String("size", "", "Size of the connected machines.") // switchMachinesCmd.Flags().String("machine-id", "", "The id of the connected machine, ignores size flag if set.") - genericcli.Must(switchConnectedMachinesCmd.RegisterFlagCompletionFunc("id", c.Completion.SwitchListCompletion)) - genericcli.Must(switchConnectedMachinesCmd.RegisterFlagCompletionFunc("partition", c.Completion.SwitchPartitionListCompletion)) - genericcli.Must(switchConnectedMachinesCmd.RegisterFlagCompletionFunc("rack", c.Completion.SwitchRackListCompletion)) + genericcli.Must(switchConnectedMachinesCmd.RegisterFlagCompletionFunc("id", c.Completion.Switch)) + genericcli.Must(switchConnectedMachinesCmd.RegisterFlagCompletionFunc("partition", c.Completion.SwitchPartition)) + genericcli.Must(switchConnectedMachinesCmd.RegisterFlagCompletionFunc("rack", c.Completion.SwitchRack)) // TODO: add once size and machine completion are implemented // genericcli.Must(switchMachinesCmd.RegisterFlagCompletionFunc("size", c.Completion.SizeListCompletion)) @@ -98,7 +98,7 @@ func newSwitchCmd(c *config.Config) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return sw.switchConsole(args) }, - ValidArgsFunction: c.Completion.SwitchListCompletion, + ValidArgsFunction: c.Completion.Switch, } switchDetailCmd := &cobra.Command{ @@ -107,7 +107,7 @@ func newSwitchCmd(c *config.Config) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return sw.switchDetail() }, - ValidArgsFunction: c.Completion.SwitchListCompletion, + ValidArgsFunction: c.Completion.Switch, } switchDetailCmd.Flags().String("id", "", "ID of the switch.") @@ -116,14 +116,14 @@ func newSwitchCmd(c *config.Config) *cobra.Command { switchDetailCmd.Flags().String("partition", "", "Partition of this switch.") switchDetailCmd.Flags().String("rack", "", "Rack of this switch.") - genericcli.Must(switchDetailCmd.RegisterFlagCompletionFunc("id", c.Completion.SwitchListCompletion)) - genericcli.Must(switchDetailCmd.RegisterFlagCompletionFunc("partition", c.Completion.SwitchPartitionListCompletion)) - genericcli.Must(switchDetailCmd.RegisterFlagCompletionFunc("rack", c.Completion.SwitchRackListCompletion)) + genericcli.Must(switchDetailCmd.RegisterFlagCompletionFunc("id", c.Completion.Switch)) + genericcli.Must(switchDetailCmd.RegisterFlagCompletionFunc("partition", c.Completion.SwitchPartition)) + genericcli.Must(switchDetailCmd.RegisterFlagCompletionFunc("rack", c.Completion.SwitchRack)) switchMigrateCmd := &cobra.Command{ Use: "migrate ", Short: "migrate machine connections and other configuration from one switch to another", - ValidArgsFunction: c.Completion.SwitchListCompletion, + ValidArgsFunction: c.Completion.Switch, RunE: func(cmd *cobra.Command, args []string) error { return sw.switchMigrate(args) }, @@ -134,7 +134,7 @@ func newSwitchCmd(c *config.Config) *cobra.Command { Short: "sets the given switch port state up or down", } switchPortCmd.PersistentFlags().String("port", "", "the port to be changed.") - genericcli.Must(switchPortCmd.RegisterFlagCompletionFunc("port", c.Completion.SwitchListPorts)) + genericcli.Must(switchPortCmd.RegisterFlagCompletionFunc("port", c.Completion.SwitchPorts)) switchPortUpCmd := &cobra.Command{ Use: "up ", @@ -143,7 +143,7 @@ func newSwitchCmd(c *config.Config) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return sw.port(args, apiv2.SwitchPortStatus_SWITCH_PORT_STATUS_UP) }, - ValidArgsFunction: c.Completion.SwitchListCompletion, + ValidArgsFunction: c.Completion.Switch, } switchPortDownCmd := &cobra.Command{ @@ -153,7 +153,7 @@ func newSwitchCmd(c *config.Config) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return sw.port(args, apiv2.SwitchPortStatus_SWITCH_PORT_STATUS_DOWN) }, - ValidArgsFunction: c.Completion.SwitchListCompletion, + ValidArgsFunction: c.Completion.Switch, } switchPortCmd.AddCommand(switchPortUpCmd, switchPortDownCmd) @@ -176,7 +176,7 @@ Operational steps to replace a switch: RunE: func(cmd *cobra.Command, args []string) error { return sw.switchReplace(args) }, - ValidArgsFunction: c.Completion.SwitchListCompletion, + ValidArgsFunction: c.Completion.Switch, } switchSSHCmd := &cobra.Command{ @@ -186,7 +186,7 @@ Operational steps to replace a switch: RunE: func(cmd *cobra.Command, args []string) error { return sw.switchSSH(args) }, - ValidArgsFunction: c.Completion.SwitchListCompletion, + ValidArgsFunction: c.Completion.Switch, } return genericcli.NewCmds(cmdsConfig, switchConnectedMachinesCmd, switchConsoleCmd, switchDetailCmd, switchMigrateCmd, switchPortCmd, switchReplaceCmd, switchSSHCmd) diff --git a/cmd/admin/v2/tenant.go b/cmd/admin/v2/tenant.go index f32afb2..e8948c3 100644 --- a/cmd/admin/v2/tenant.go +++ b/cmd/admin/v2/tenant.go @@ -55,7 +55,7 @@ func newTenantCmd(c *config.Config) *cobra.Command { }, nil }, OnlyCmds: genericcli.OnlyCmds(genericcli.ListCmd, genericcli.CreateCmd), - ValidArgsFn: w.c.Completion.AdminTenantListCompletion, + ValidArgsFn: w.c.Completion.AdminTenant, } return genericcli.NewCmds(cmdsConfig, newAddMemberCmd(c)) @@ -160,9 +160,9 @@ func newAddMemberCmd(c *config.Config) *cobra.Command { genericcli.Must(cmd.MarkFlagRequired("member-id")) genericcli.Must(cmd.MarkFlagRequired("role")) - genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant-id", c.Completion.AdminTenantListCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("member-id", c.Completion.AdminTenantListCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("role", c.Completion.TenantRoleCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant-id", c.Completion.AdminTenant)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("member-id", c.Completion.AdminTenant)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("role", c.Completion.TenantRole)) return cmd } diff --git a/cmd/admin/v2/token.go b/cmd/admin/v2/token.go index ed64b01..183dbcf 100644 --- a/cmd/admin/v2/token.go +++ b/cmd/admin/v2/token.go @@ -119,16 +119,16 @@ func newTokenCmd(c *config.Config) *cobra.Command { cmd.Flags().String("infra-role", "", "the infra role to associate with the api token") cmd.Flags().Duration("expires", 8*time.Hour, "the duration how long the api token is valid") - genericcli.Must(cmd.RegisterFlagCompletionFunc("user", c.Completion.TenantListCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("permissions", c.Completion.TokenPermissionsCompletionfunc)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("project-roles", c.Completion.TokenProjectRolesCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant-roles", c.Completion.TokenTenantRolesCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("admin-role", c.Completion.TokenAdminRoleCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("infra-role", c.Completion.TokenInfraRoleCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("machine-roles", c.Completion.TokenMachineRolesCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("user", c.Completion.Tenant)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("permissions", c.Completion.TokenPermissions)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project-roles", c.Completion.TokenProjectRoles)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant-roles", c.Completion.TokenTenantRoles)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("admin-role", c.Completion.TokenAdminRole)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("infra-role", c.Completion.TokenInfraRole)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("machine-roles", c.Completion.TokenMachineRoles)) }, - ValidArgsFn: w.c.Completion.TokenListCompletion, + ValidArgsFn: w.c.Completion.Token, } return genericcli.NewCmds(cmdsConfig) } diff --git a/cmd/admin/v2/vpn.go b/cmd/admin/v2/vpn.go index b49d9de..c012c3c 100644 --- a/cmd/admin/v2/vpn.go +++ b/cmd/admin/v2/vpn.go @@ -37,10 +37,10 @@ func newVPNCmd(c *config.Config) *cobra.Command { 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") - genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) }, OnlyCmds: genericcli.OnlyCmds(genericcli.ListCmd), - ValidArgsFn: w.c.Completion.ProjectListCompletion, + ValidArgsFn: w.c.Completion.ProjectList, } authKeyCmd := &cobra.Command{ @@ -49,14 +49,14 @@ func newVPNCmd(c *config.Config) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return w.authKey() }, - ValidArgsFunction: c.Completion.ProjectListCompletion, + ValidArgsFunction: c.Completion.ProjectList, } authKeyCmd.Flags().String("project", "", "the project for which the authkey should be generated") authKeyCmd.Flags().String("reason", "", "the reason why 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")) - genericcli.Must(authKeyCmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + genericcli.Must(authKeyCmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) return genericcli.NewCmds(cmdsConfig, authKeyCmd) } diff --git a/cmd/api/v2/audit.go b/cmd/api/v2/audit.go index 89ee600..de87ace 100644 --- a/cmd/api/v2/audit.go +++ b/cmd/api/v2/audit.go @@ -40,8 +40,8 @@ func newAuditCmd(c *config.Config) *cobra.Command { cmd.Flags().Bool("prettify-body", true, "attempts to interpret the body as json and prettifies it.") - genericcli.Must(cmd.RegisterFlagCompletionFunc("phase", c.Completion.AuditPhaseListCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant", c.Completion.TenantListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("phase", c.Completion.AuditPhase)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant", c.Completion.Tenant)) }, ListCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().String("request-id", "", "request id of the audit trace.") @@ -65,10 +65,10 @@ func newAuditCmd(c *config.Config) *cobra.Command { cmd.Flags().Bool("prettify-body", true, "attempts to interpret the body as json and prettifies it.") - genericcli.Must(cmd.RegisterFlagCompletionFunc("phase", c.Completion.AuditPhaseListCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant", c.Completion.TenantListCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("result-code", c.Completion.AuditStatusCodesCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("phase", c.Completion.AuditPhase)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant", c.Completion.Tenant)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("result-code", c.Completion.AuditStatusCodes)) }, } diff --git a/cmd/api/v2/image.go b/cmd/api/v2/image.go index 8a85f8d..ba34c91 100644 --- a/cmd/api/v2/image.go +++ b/cmd/api/v2/image.go @@ -42,8 +42,8 @@ func newImageCmd(c *config.Config) *cobra.Command { cmd.Flags().StringP("feature", "", "", "image feature to filter for, can be either machine|firewall") cmd.Flags().String("classification", "", "image classification") - genericcli.Must(cmd.RegisterFlagCompletionFunc("feature", c.Completion.ImageFeaturesCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("classification", c.Completion.ImageClassificationCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("feature", c.Completion.ImageFeatures)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("classification", c.Completion.ImageClassification)) }, } diff --git a/cmd/api/v2/ip.go b/cmd/api/v2/ip.go index 851983f..f1e4f94 100644 --- a/cmd/api/v2/ip.go +++ b/cmd/api/v2/ip.go @@ -34,7 +34,7 @@ func newIPCmd(c *config.Config) *cobra.Command { ListCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().StringP("project", "p", "", "project from where ips should be listed") - genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) }, CreateCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().StringP("project", "p", "", "project of the ip") @@ -45,7 +45,7 @@ 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("project", c.Completion.ProjectListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) }, UpdateCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().StringP("project", "p", "", "project of the ip") @@ -55,21 +55,21 @@ func newIPCmd(c *config.Config) *cobra.Command { cmd.Flags().StringArray("remove-labels", nil, "removes the volume labels with the given key") cmd.Flags().Bool("static", false, "make this ip static") - genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) }, DescribeCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().StringP("project", "p", "", "project of the ip") - genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) }, DeleteCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().StringP("project", "p", "", "project of the ip") - genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) }, CreateRequestFromCLI: w.createFromCLI, UpdateRequestFromCLI: w.updateFromCLI, - ValidArgsFn: c.Completion.IpListCompletion, + ValidArgsFn: c.Completion.Ip, } return genericcli.NewCmds(cmdsConfig) diff --git a/cmd/api/v2/network.go b/cmd/api/v2/network.go index 94ccbcc..1b68ed2 100644 --- a/cmd/api/v2/network.go +++ b/cmd/api/v2/network.go @@ -36,11 +36,11 @@ func newNetworkCmd(c *config.Config) *cobra.Command { cmd.Flags().StringSlice("labels", nil, "labels to filter [optional]") cmd.Flags().StringP("type", "t", "", "type of the network. [optional]") - genericcli.Must(cmd.RegisterFlagCompletionFunc("id", c.Completion.NetworkListCompletion)) - 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("type", c.Completion.NetworkTypeCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("id", c.Completion.Network)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.Partition)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.NetworkAddressFamily)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("type", c.Completion.NetworkType)) } cmdsConfig := &genericcli.CmdsConfig[*apiv2.NetworkServiceCreateRequest, *apiv2.NetworkServiceUpdateRequest, *apiv2.Network]{ @@ -52,7 +52,7 @@ func newNetworkCmd(c *config.Config) *cobra.Command { CreateRequestFromCLI: w.createRequestFromCLI, UpdateRequestFromCLI: w.updateRequestFromCLI, Sorter: sorters.NetworkSorter(), - ValidArgsFn: c.Completion.NetworkListCompletion, + ValidArgsFn: c.Completion.Network, DescribePrinter: func() printers.Printer { return c.DescribePrinter }, ListPrinter: func() printers.Printer { return c.ListPrinter }, CreateCmdMutateFn: func(cmd *cobra.Command) { @@ -66,15 +66,15 @@ func newNetworkCmd(c *config.Config) *cobra.Command { 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.NetworkAddressFamilyCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("parent-network", c.Completion.NetworkListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.Partition)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.NetworkAddressFamily)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("parent-network", c.Completion.Network)) }, ListCmdMutateFn: func(cmd *cobra.Command) { listFlags(cmd) cmd.Flags().String("parent-network", "", "parent network to filter [optional]") - genericcli.Must(cmd.RegisterFlagCompletionFunc("parent-network", c.Completion.NetworkListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("parent-network", c.Completion.Network)) }, UpdateCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().String("name", "", "the name of the network [optional]") @@ -84,11 +84,11 @@ func newNetworkCmd(c *config.Config) *cobra.Command { cmd.Flags().StringSlice("add-labels", nil, "labels to add to the network") cmd.Flags().StringSlice("remove-labels", nil, "labels to remove to the network") - genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) }, DeleteCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().String("project", "", "project of this network.") - genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) }, } diff --git a/cmd/api/v2/project.go b/cmd/api/v2/project.go index 3e938e3..74565d6 100644 --- a/cmd/api/v2/project.go +++ b/cmd/api/v2/project.go @@ -45,7 +45,7 @@ func newProjectCmd(c *config.Config) *cobra.Command { cmd.Flags().String("description", "", "the description of the project to create") cmd.Flags().String("tenant", "", "the tenant of this project, defaults to tenant of the default project") - genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant", c.Completion.TenantListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant", c.Completion.Tenant)) }, CreateRequestFromCLI: w.createRequestFromCLI, UpdateCmdMutateFn: func(cmd *cobra.Command) { @@ -53,7 +53,7 @@ func newProjectCmd(c *config.Config) *cobra.Command { cmd.Flags().String("description", "", "the description of the project to update") }, UpdateRequestFromCLI: w.updateRequestFromCLI, - ValidArgsFn: w.c.Completion.ProjectListCompletion, + ValidArgsFn: w.c.Completion.ProjectList, } inviteCmd := &cobra.Command{ @@ -72,8 +72,8 @@ func newProjectCmd(c *config.Config) *cobra.Command { generateInviteCmd.Flags().StringP("project", "p", "", "the project for which to generate the invite") generateInviteCmd.Flags().String("role", apiv2.ProjectRole_PROJECT_ROLE_VIEWER.String(), "the role that the new member will assume when joining through the invite secret") - genericcli.Must(generateInviteCmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) - genericcli.Must(generateInviteCmd.RegisterFlagCompletionFunc("role", c.Completion.ProjectRoleCompletion)) + genericcli.Must(generateInviteCmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + genericcli.Must(generateInviteCmd.RegisterFlagCompletionFunc("role", c.Completion.ProjectRole)) deleteInviteCmd := &cobra.Command{ Use: "delete ", @@ -82,12 +82,12 @@ func newProjectCmd(c *config.Config) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return w.deleteInvite(args) }, - ValidArgsFunction: c.Completion.ProjectInviteListCompletion, + ValidArgsFunction: c.Completion.ProjectInvite, } deleteInviteCmd.Flags().StringP("project", "p", "", "the project in which to delete the invite") - genericcli.Must(deleteInviteCmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + genericcli.Must(deleteInviteCmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) listInvitesCmd := &cobra.Command{ Use: "list", @@ -102,7 +102,7 @@ func newProjectCmd(c *config.Config) *cobra.Command { genericcli.AddSortFlag(listInvitesCmd, sorters.ProjectInviteSorter()) - genericcli.Must(listInvitesCmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + genericcli.Must(listInvitesCmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) joinProjectCmd := &cobra.Command{ Use: "join ", @@ -140,12 +140,12 @@ func newProjectCmd(c *config.Config) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return w.removeMember(args) }, - ValidArgsFunction: c.Completion.ProjectMemberListCompletion, + ValidArgsFunction: c.Completion.ProjectMember, } removeMemberCmd.Flags().StringP("project", "p", "", "the project in which to remove the member") - genericcli.Must(removeMemberCmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + genericcli.Must(removeMemberCmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) updateMemberCmd := &cobra.Command{ Use: "update ", @@ -153,14 +153,14 @@ func newProjectCmd(c *config.Config) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return w.updateMember(args) }, - ValidArgsFunction: c.Completion.ProjectMemberListCompletion, + ValidArgsFunction: c.Completion.ProjectMember, } updateMemberCmd.Flags().StringP("project", "p", "", "the project in which to remove the member") updateMemberCmd.Flags().String("role", "", "the role of the member") - genericcli.Must(updateMemberCmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) - genericcli.Must(updateMemberCmd.RegisterFlagCompletionFunc("role", c.Completion.ProjectRoleCompletion)) + genericcli.Must(updateMemberCmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + genericcli.Must(updateMemberCmd.RegisterFlagCompletionFunc("role", c.Completion.ProjectRole)) memberCmd.AddCommand(removeMemberCmd, updateMemberCmd, listMembersCmd) diff --git a/cmd/api/v2/size.go b/cmd/api/v2/size.go index be7e7c8..8610f72 100644 --- a/cmd/api/v2/size.go +++ b/cmd/api/v2/size.go @@ -33,7 +33,7 @@ func newSizeCmd(c *config.Config) *cobra.Command { Sorter: sorters.SizeSorter(), DescribePrinter: func() printers.Printer { return c.DescribePrinter }, ListPrinter: func() printers.Printer { return c.ListPrinter }, - ValidArgsFn: c.Completion.SizeListCompletion, + ValidArgsFn: c.Completion.Size, OnlyCmds: genericcli.OnlyCmds(genericcli.DescribeCmd, genericcli.ListCmd), ListCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().StringP("id", "", "", "size id to filter for") diff --git a/cmd/api/v2/tenant.go b/cmd/api/v2/tenant.go index 74e70a6..2ffbef4 100644 --- a/cmd/api/v2/tenant.go +++ b/cmd/api/v2/tenant.go @@ -52,7 +52,7 @@ func newTenantCmd(c *config.Config) *cobra.Command { cmd.Flags().String("description", "", "the description of the tenant to update") }, UpdateRequestFromCLI: w.updateRequestFromCLI, - ValidArgsFn: w.c.Completion.TenantListCompletion, + ValidArgsFn: w.c.Completion.Tenant, } inviteCmd := &cobra.Command{ @@ -71,8 +71,8 @@ func newTenantCmd(c *config.Config) *cobra.Command { generateInviteCmd.Flags().String("tenant", "", "the tenant for which to generate the invite") generateInviteCmd.Flags().String("role", apiv2.TenantRole_TENANT_ROLE_VIEWER.String(), "the role that the new member will assume when joining through the invite secret") - genericcli.Must(generateInviteCmd.RegisterFlagCompletionFunc("tenant", c.Completion.TenantListCompletion)) - genericcli.Must(generateInviteCmd.RegisterFlagCompletionFunc("role", c.Completion.TenantRoleCompletion)) + genericcli.Must(generateInviteCmd.RegisterFlagCompletionFunc("tenant", c.Completion.Tenant)) + genericcli.Must(generateInviteCmd.RegisterFlagCompletionFunc("role", c.Completion.TenantRole)) deleteInviteCmd := &cobra.Command{ Use: "delete ", @@ -81,12 +81,12 @@ func newTenantCmd(c *config.Config) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return w.deleteInvite(args) }, - ValidArgsFunction: c.Completion.TenantInviteListCompletion, + ValidArgsFunction: c.Completion.TenantInvite, } deleteInviteCmd.Flags().String("tenant", "", "the tenant in which to delete the invite") - genericcli.Must(deleteInviteCmd.RegisterFlagCompletionFunc("tenant", c.Completion.TenantListCompletion)) + genericcli.Must(deleteInviteCmd.RegisterFlagCompletionFunc("tenant", c.Completion.Tenant)) listInvitesCmd := &cobra.Command{ Use: "list", @@ -101,7 +101,7 @@ func newTenantCmd(c *config.Config) *cobra.Command { genericcli.AddSortFlag(listInvitesCmd, sorters.TenantInviteSorter()) - genericcli.Must(listInvitesCmd.RegisterFlagCompletionFunc("tenant", c.Completion.TenantListCompletion)) + genericcli.Must(listInvitesCmd.RegisterFlagCompletionFunc("tenant", c.Completion.Tenant)) joinTenantCmd := &cobra.Command{ Use: "join ", @@ -137,12 +137,12 @@ func newTenantCmd(c *config.Config) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return w.removeMember(args) }, - ValidArgsFunction: c.Completion.TenantMemberListCompletion, + ValidArgsFunction: c.Completion.TenantMember, } removeMemberCmd.Flags().String("tenant", "", "the tenant in which to remove the member") - genericcli.Must(removeMemberCmd.RegisterFlagCompletionFunc("tenant", c.Completion.TenantListCompletion)) + genericcli.Must(removeMemberCmd.RegisterFlagCompletionFunc("tenant", c.Completion.Tenant)) updateMemberCmd := &cobra.Command{ Use: "update ", @@ -150,14 +150,14 @@ func newTenantCmd(c *config.Config) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return w.updateMember(args) }, - ValidArgsFunction: c.Completion.TenantMemberListCompletion, + ValidArgsFunction: c.Completion.TenantMember, } updateMemberCmd.Flags().String("tenant", "", "the tenant in which to remove the member") updateMemberCmd.Flags().String("role", "", "the role of the member") - genericcli.Must(updateMemberCmd.RegisterFlagCompletionFunc("tenant", c.Completion.TenantListCompletion)) - genericcli.Must(updateMemberCmd.RegisterFlagCompletionFunc("role", c.Completion.TenantRoleCompletion)) + genericcli.Must(updateMemberCmd.RegisterFlagCompletionFunc("tenant", c.Completion.Tenant)) + genericcli.Must(updateMemberCmd.RegisterFlagCompletionFunc("role", c.Completion.TenantRole)) memberCmd.AddCommand(removeMemberCmd, updateMemberCmd, listMembersCmd) diff --git a/cmd/api/v2/token.go b/cmd/api/v2/token.go index 85eafac..8e70ba7 100644 --- a/cmd/api/v2/token.go +++ b/cmd/api/v2/token.go @@ -86,15 +86,15 @@ func newTokenCmd(c *config.Config) *cobra.Command { cmd.Flags().String("admin-role", "", "the admin role to associate with the api token") cmd.Flags().Duration("expires", 8*time.Hour, "the duration how long the api token is valid") - genericcli.Must(cmd.RegisterFlagCompletionFunc("permissions", c.Completion.TokenPermissionsCompletionfunc)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("project-roles", c.Completion.TokenProjectRolesCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant-roles", c.Completion.TokenTenantRolesCompletion)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("admin-role", c.Completion.TokenAdminRoleCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("permissions", c.Completion.TokenPermissions)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project-roles", c.Completion.TokenProjectRoles)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant-roles", c.Completion.TokenTenantRoles)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("admin-role", c.Completion.TokenAdminRole)) }, DeleteCmdMutateFn: func(cmd *cobra.Command) { cmd.Aliases = append(cmd.Aliases, "revoke") }, - ValidArgsFn: w.c.Completion.TokenListCompletion, + ValidArgsFn: w.c.Completion.Token, } return genericcli.NewCmds(cmdsConfig) } diff --git a/cmd/completion/audit.go b/cmd/completion/audit.go index af20571..ff1f066 100644 --- a/cmd/completion/audit.go +++ b/cmd/completion/audit.go @@ -8,11 +8,11 @@ import ( "google.golang.org/grpc/codes" ) -func (c *Completion) AuditPhaseListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) AuditPhase(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return []string{apiv2.AuditPhase_AUDIT_PHASE_REQUEST.String(), apiv2.AuditPhase_AUDIT_PHASE_RESPONSE.String()}, cobra.ShellCompDirectiveNoFileComp } -func (c *Completion) AuditStatusCodesCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) AuditStatusCodes(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { var result []string for i := range 16 { diff --git a/cmd/completion/completion.go b/cmd/completion/completion.go index 0c5a3d3..78cd273 100644 --- a/cmd/completion/completion.go +++ b/cmd/completion/completion.go @@ -13,6 +13,6 @@ type Completion struct { Ctx context.Context } -func OutputFormatListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func OutputFormat(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return []string{"table", "wide", "markdown", "json", "yaml", "template"}, cobra.ShellCompDirectiveNoFileComp } diff --git a/cmd/completion/image.go b/cmd/completion/image.go index 237f8dc..3c2021b 100644 --- a/cmd/completion/image.go +++ b/cmd/completion/image.go @@ -6,7 +6,7 @@ import ( "github.com/spf13/cobra" ) -func (c *Completion) ImageFeaturesCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) ImageFeatures(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { var names []string for i := range apiv2.ImageFeature_name { @@ -25,7 +25,7 @@ func (c *Completion) ImageFeaturesCompletion(cmd *cobra.Command, args []string, return names, cobra.ShellCompDirectiveNoFileComp } -func (c *Completion) ImageClassificationCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) ImageClassification(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { var names []string for i := range apiv2.ImageClassification_name { diff --git a/cmd/completion/ip.go b/cmd/completion/ip.go index b433db5..09b24d6 100644 --- a/cmd/completion/ip.go +++ b/cmd/completion/ip.go @@ -5,7 +5,7 @@ import ( "github.com/spf13/cobra" ) -func (c *Completion) IpListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) Ip(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { req := &apiv2.IPServiceListRequest{ Project: c.Project, } diff --git a/cmd/completion/network.go b/cmd/completion/network.go index f303e66..8911856 100644 --- a/cmd/completion/network.go +++ b/cmd/completion/network.go @@ -8,7 +8,7 @@ import ( "github.com/spf13/cobra" ) -func (c *Completion) NetworkListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) Network(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ownNetworks, err := c.Client.Apiv2().Network().List(c.Ctx, &apiv2.NetworkServiceListRequest{ Project: c.Project, }) @@ -34,7 +34,7 @@ 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) { +func (c *Completion) NetworkType(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 { @@ -44,7 +44,7 @@ func (c *Completion) NetworkTypeCompletion(cmd *cobra.Command, args []string, to return names, cobra.ShellCompDirectiveNoFileComp } -func (c *Completion) NatTypeCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) NatType(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.NetworkType(val)); err == nil { @@ -54,7 +54,7 @@ func (c *Completion) NatTypeCompletion(cmd *cobra.Command, args []string, toComp return names, cobra.ShellCompDirectiveNoFileComp } -func (c *Completion) NetworkAddressFamilyCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) NetworkAddressFamily(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { var afs []string for _, af := range []apiv2.NetworkAddressFamily{ apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_DUAL_STACK, @@ -70,7 +70,7 @@ 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) { +func (c *Completion) NetworkAdmin(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 diff --git a/cmd/completion/partition.go b/cmd/completion/partition.go index 0e71613..b40babd 100644 --- a/cmd/completion/partition.go +++ b/cmd/completion/partition.go @@ -5,7 +5,7 @@ import ( "github.com/spf13/cobra" ) -func (c *Completion) PartitionListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) Partition(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { req := &apiv2.PartitionServiceListRequest{} resp, err := c.Client.Apiv2().Partition().List(c.Ctx, req) if err != nil { diff --git a/cmd/completion/project.go b/cmd/completion/project.go index 6acb591..5cc7684 100644 --- a/cmd/completion/project.go +++ b/cmd/completion/project.go @@ -6,7 +6,7 @@ import ( apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" ) -func (c *Completion) ProjectListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) ProjectList(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { req := &apiv2.ProjectServiceListRequest{} resp, err := c.Client.Apiv2().Project().List(c.Ctx, req) if err != nil { @@ -21,7 +21,7 @@ func (c *Completion) ProjectListCompletion(cmd *cobra.Command, args []string, to return names, cobra.ShellCompDirectiveNoFileComp } -func (c *Completion) ProjectRoleCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) ProjectRole(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { var names []string for value, name := range apiv2.ProjectRole_name { @@ -35,7 +35,7 @@ func (c *Completion) ProjectRoleCompletion(cmd *cobra.Command, args []string, to return names, cobra.ShellCompDirectiveNoFileComp } -func (c *Completion) ProjectInviteListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) ProjectInvite(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { resp, err := c.Client.Apiv2().Project().InvitesList(c.Ctx, &apiv2.ProjectServiceInvitesListRequest{ Project: c.Project, }) @@ -52,7 +52,7 @@ func (c *Completion) ProjectInviteListCompletion(cmd *cobra.Command, args []stri return names, cobra.ShellCompDirectiveNoFileComp } -func (c *Completion) ProjectMemberListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) ProjectMember(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { resp, err := c.Client.Apiv2().Project().Get(c.Ctx, &apiv2.ProjectServiceGetRequest{ Project: c.Project, }) diff --git a/cmd/completion/size.go b/cmd/completion/size.go index 8ebd814..5f42fbe 100644 --- a/cmd/completion/size.go +++ b/cmd/completion/size.go @@ -5,7 +5,7 @@ import ( "github.com/spf13/cobra" ) -func (c *Completion) SizeListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) Size(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 { diff --git a/cmd/completion/switch.go b/cmd/completion/switch.go index a07069c..f0b4d20 100644 --- a/cmd/completion/switch.go +++ b/cmd/completion/switch.go @@ -6,7 +6,7 @@ import ( "github.com/spf13/cobra" ) -func (c *Completion) SwitchListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) Switch(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { resp, err := c.Client.Adminv2().Switch().List(c.Ctx, &adminv2.SwitchServiceListRequest{}) if err != nil { return nil, cobra.ShellCompDirectiveError @@ -20,7 +20,7 @@ func (c *Completion) SwitchListCompletion(cmd *cobra.Command, args []string, toC return ids, cobra.ShellCompDirectiveNoFileComp } -func (c *Completion) SwitchPartitionListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) SwitchPartition(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { resp, err := c.Client.Adminv2().Switch().List(c.Ctx, &adminv2.SwitchServiceListRequest{}) if err != nil { return nil, cobra.ShellCompDirectiveError @@ -34,7 +34,7 @@ func (c *Completion) SwitchPartitionListCompletion(cmd *cobra.Command, args []st return partitions, cobra.ShellCompDirectiveNoFileComp } -func (c *Completion) SwitchRackListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) SwitchRack(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { resp, err := c.Client.Adminv2().Switch().List(c.Ctx, &adminv2.SwitchServiceListRequest{}) if err != nil { return nil, cobra.ShellCompDirectiveError @@ -48,7 +48,7 @@ func (c *Completion) SwitchRackListCompletion(cmd *cobra.Command, args []string, return racks, cobra.ShellCompDirectiveNoFileComp } -func (c *Completion) SwitchOSVendorListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) SwitchOSVendor(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { resp, err := c.Client.Adminv2().Switch().List(c.Ctx, &adminv2.SwitchServiceListRequest{}) if err != nil { return nil, cobra.ShellCompDirectiveError @@ -62,7 +62,7 @@ func (c *Completion) SwitchOSVendorListCompletion(cmd *cobra.Command, args []str return oss, cobra.ShellCompDirectiveNoFileComp } -func (c *Completion) SwitchOSVersionListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) SwitchOSVersion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { resp, err := c.Client.Adminv2().Switch().List(c.Ctx, &adminv2.SwitchServiceListRequest{}) if err != nil { return nil, cobra.ShellCompDirectiveError @@ -76,7 +76,7 @@ func (c *Completion) SwitchOSVersionListCompletion(cmd *cobra.Command, args []st return osVersions, cobra.ShellCompDirectiveNoFileComp } -func (c *Completion) SwitchListPorts(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) SwitchPorts(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { // there is no switch selected so we cannot get the list of ports return nil, cobra.ShellCompDirectiveNoFileComp diff --git a/cmd/completion/tenant.go b/cmd/completion/tenant.go index fb4e9eb..bb848e5 100644 --- a/cmd/completion/tenant.go +++ b/cmd/completion/tenant.go @@ -6,7 +6,7 @@ import ( "github.com/spf13/cobra" ) -func (c *Completion) TenantListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) Tenant(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { req := &apiv2.TenantServiceListRequest{} resp, err := c.Client.Apiv2().Tenant().List(c.Ctx, req) if err != nil { @@ -20,7 +20,7 @@ func (c *Completion) TenantListCompletion(cmd *cobra.Command, args []string, toC return names, cobra.ShellCompDirectiveNoFileComp } -func (c *Completion) TenantRoleCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) TenantRole(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { var names []string for value, name := range apiv2.TenantRole_name { @@ -34,7 +34,7 @@ func (c *Completion) TenantRoleCompletion(cmd *cobra.Command, args []string, toC return names, cobra.ShellCompDirectiveNoFileComp } -func (c *Completion) TenantInviteListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) TenantInvite(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { projectResp, err := c.Client.Apiv2().Project().Get(c.Ctx, &apiv2.ProjectServiceGetRequest{ Project: c.Project, }) @@ -58,7 +58,7 @@ func (c *Completion) TenantInviteListCompletion(cmd *cobra.Command, args []strin return names, cobra.ShellCompDirectiveNoFileComp } -func (c *Completion) TenantMemberListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) TenantMember(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { projectResp, err := c.Client.Apiv2().Project().Get(c.Ctx, &apiv2.ProjectServiceGetRequest{ Project: c.Project, }) @@ -82,7 +82,7 @@ func (c *Completion) TenantMemberListCompletion(cmd *cobra.Command, args []strin return names, cobra.ShellCompDirectiveNoFileComp } -func (c *Completion) AdminTenantListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) AdminTenant(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { req := &adminv2.TenantServiceListRequest{} resp, err := c.Client.Adminv2().Tenant().List(c.Ctx, req) if err != nil { diff --git a/cmd/completion/token.go b/cmd/completion/token.go index 774296c..a4be2b8 100644 --- a/cmd/completion/token.go +++ b/cmd/completion/token.go @@ -8,7 +8,7 @@ import ( apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" ) -func (c *Completion) TokenListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) Token(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { req := &apiv2.TokenServiceListRequest{} resp, err := c.Client.Apiv2().Token().List(c.Ctx, req) if err != nil { @@ -23,7 +23,7 @@ func (c *Completion) TokenListCompletion(cmd *cobra.Command, args []string, toCo return names, cobra.ShellCompDirectiveNoFileComp } -func (c *Completion) TokenProjectRolesCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) TokenProjectRoles(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 @@ -41,7 +41,7 @@ func (c *Completion) TokenProjectRolesCompletion(cmd *cobra.Command, args []stri return roles, cobra.ShellCompDirectiveNoFileComp } -func (c *Completion) TokenTenantRolesCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) TokenTenantRoles(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 @@ -59,7 +59,7 @@ func (c *Completion) TokenTenantRolesCompletion(cmd *cobra.Command, args []strin return roles, cobra.ShellCompDirectiveNoFileComp } -func (c *Completion) TokenMachineRolesCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) TokenMachineRoles(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 @@ -77,7 +77,7 @@ func (c *Completion) TokenMachineRolesCompletion(cmd *cobra.Command, args []stri return roles, cobra.ShellCompDirectiveNoFileComp } -func (c *Completion) TokenAdminRoleCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) TokenAdminRole(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { var roles []string for i, role := range apiv2.AdminRole_name { @@ -90,7 +90,7 @@ func (c *Completion) TokenAdminRoleCompletion(cmd *cobra.Command, args []string, return roles, cobra.ShellCompDirectiveNoFileComp } -func (c *Completion) TokenInfraRoleCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) TokenInfraRole(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { var roles []string for i, role := range apiv2.InfraRole_name { @@ -103,7 +103,7 @@ func (c *Completion) TokenInfraRoleCompletion(cmd *cobra.Command, args []string, return roles, cobra.ShellCompDirectiveNoFileComp } -func (c *Completion) TokenPermissionsCompletionfunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) TokenPermissions(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { methods, err := c.Client.Apiv2().Method().List(c.Ctx, &apiv2.MethodServiceListRequest{}) if err != nil { return nil, cobra.ShellCompDirectiveError diff --git a/cmd/context.go b/cmd/context.go index 99d654f..8e8a77f 100644 --- a/cmd/context.go +++ b/cmd/context.go @@ -66,7 +66,7 @@ func newContextCmd(c *config.Config) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return w.setProject(args) }, - ValidArgsFunction: c.Completion.ProjectListCompletion, + ValidArgsFunction: c.Completion.ProjectList, } contextRemoveCmd := &cobra.Command{ Use: "remove ", @@ -110,7 +110,7 @@ func newContextCmd(c *config.Config) *cobra.Command { contextUpdateCmd.Flags().Bool("activate", false, "immediately switches to the new context") contextUpdateCmd.Flags().String("provider", "", "sets the login provider for this context") - genericcli.Must(contextUpdateCmd.RegisterFlagCompletionFunc("default-project", c.Completion.ProjectListCompletion)) + genericcli.Must(contextUpdateCmd.RegisterFlagCompletionFunc("default-project", c.Completion.ProjectList)) contextCmd.AddCommand( contextListCmd, diff --git a/cmd/login.go b/cmd/login.go index 7e14376..9466f39 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -41,7 +41,7 @@ func newLoginCmd(c *config.Config) *cobra.Command { genericcli.Must(loginCmd.Flags().MarkHidden("admin-role")) genericcli.Must(loginCmd.RegisterFlagCompletionFunc("provider", cobra.FixedCompletions([]string{"openid-connect"}, cobra.ShellCompDirectiveNoFileComp))) - genericcli.Must(loginCmd.RegisterFlagCompletionFunc("admin-role", c.Completion.TokenAdminRoleCompletion)) + genericcli.Must(loginCmd.RegisterFlagCompletionFunc("admin-role", c.Completion.TokenAdminRole)) return loginCmd } From c0355f2289a60d49f8e0dbcbe8f736695b190d3f Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Thu, 30 Jul 2026 08:04:38 +0200 Subject: [PATCH 07/13] Only used here --- cmd/tableprinters/common.go | 10 ---------- cmd/tableprinters/network.go | 11 +++++++++++ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/cmd/tableprinters/common.go b/cmd/tableprinters/common.go index 0ce63c2..8c6add4 100644 --- a/cmd/tableprinters/common.go +++ b/cmd/tableprinters/common.go @@ -196,13 +196,3 @@ 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 index 1f7f312..677cc1e 100644 --- a/cmd/tableprinters/network.go +++ b/cmd/tableprinters/network.go @@ -168,3 +168,14 @@ func (nn *networks) appendChild(parentID string, child *apiv2.Network) bool { } return false } + +func getMaxLineCount(ss ...string) int { + max := 0 + for _, s := range ss { + c := strings.Count(s, "\n") + if c > max { + max = c + } + } + return max +} From a7d52ebc329767d17a3e80ae68777cc4e2f318ed Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Thu, 30 Jul 2026 08:10:18 +0200 Subject: [PATCH 08/13] Ouch --- cmd/completion/network.go | 2 +- cmd/tableprinters/network.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/completion/network.go b/cmd/completion/network.go index 8911856..f36c50e 100644 --- a/cmd/completion/network.go +++ b/cmd/completion/network.go @@ -47,7 +47,7 @@ func (c *Completion) NetworkType(cmd *cobra.Command, args []string, toComplete s func (c *Completion) NatType(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.NetworkType(val)); err == nil { + if e, err := enum.GetStringValue(apiv2.NATType(val)); err == nil { names = append(names, *e) } } diff --git a/cmd/tableprinters/network.go b/cmd/tableprinters/network.go index 677cc1e..b6b6306 100644 --- a/cmd/tableprinters/network.go +++ b/cmd/tableprinters/network.go @@ -42,7 +42,7 @@ func (t *TablePrinter) NetworkTable(data []*apiv2.Network, wide bool) ([]string, } } for _, n := range *nn { - row, err := addNetwork("", n.parent, wide) + row, err := renderNetworkRow("", n.parent, wide) if err != nil { return nil, nil, err } @@ -56,7 +56,7 @@ func (t *TablePrinter) NetworkTable(data []*apiv2.Network, wide bool) ([]string, } prefix += "─╴" - row, err := addNetwork(prefix, c, wide) + row, err := renderNetworkRow(prefix, c, wide) if err != nil { return nil, nil, err } @@ -68,7 +68,7 @@ func (t *TablePrinter) NetworkTable(data []*apiv2.Network, wide bool) ([]string, return header, rows, nil } -func addNetwork(prefix string, n *apiv2.Network, wide bool) ([]string, error) { +func renderNetworkRow(prefix string, n *apiv2.Network, wide bool) ([]string, error) { var ( id = fmt.Sprintf("%s%s", prefix, n.Id) prefixes = strings.Join(n.Prefixes, ",") From a6e4f0f902e260190fc2bcdc4b45ffd0726c5a51 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Thu, 30 Jul 2026 08:18:18 +0200 Subject: [PATCH 09/13] Linter --- cmd/tableprinters/common.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/tableprinters/common.go b/cmd/tableprinters/common.go index 8c6add4..8b31e78 100644 --- a/cmd/tableprinters/common.go +++ b/cmd/tableprinters/common.go @@ -195,4 +195,3 @@ func humanizeDuration(duration time.Duration) string { } return strings.Join(parts, " ") } - From a1e6b2c79aa5a345c3aad5afbe1c02c7c4b189fc Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Thu, 30 Jul 2026 08:46:16 +0200 Subject: [PATCH 10/13] cleanup completions --- cmd/admin/v2/audit.go | 2 +- cmd/admin/v2/network.go | 6 +++--- cmd/admin/v2/partition.go | 2 +- cmd/admin/v2/vpn.go | 8 ++++---- cmd/api/v2/audit.go | 2 +- cmd/api/v2/ip.go | 10 +++++----- cmd/api/v2/network.go | 8 ++++---- cmd/api/v2/project.go | 12 ++++++------ cmd/completion/completion.go | 14 +++++++++----- cmd/completion/ip.go | 4 ++-- cmd/completion/network.go | 10 +++++----- cmd/completion/partition.go | 2 +- cmd/completion/project.go | 12 ++++++------ cmd/completion/size.go | 2 +- cmd/completion/switch.go | 12 ++++++------ cmd/completion/tenant.go | 16 ++++++++-------- cmd/completion/token.go | 10 +++++----- cmd/context.go | 4 ++-- cmd/root.go | 15 +++++---------- testing/e2e/test_cmd.go | 14 ++++++-------- 20 files changed, 81 insertions(+), 84 deletions(-) diff --git a/cmd/admin/v2/audit.go b/cmd/admin/v2/audit.go index 3385f18..de6bc6f 100644 --- a/cmd/admin/v2/audit.go +++ b/cmd/admin/v2/audit.go @@ -57,7 +57,7 @@ func newAuditCmd(c *config.Config) *cobra.Command { cmd.Flags().Bool("prettify-body", true, "attempts to interpret the body as json and prettifies it.") genericcli.Must(cmd.RegisterFlagCompletionFunc("phase", c.Completion.AuditPhase)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant", c.Completion.Tenant)) genericcli.Must(cmd.RegisterFlagCompletionFunc("result-code", c.Completion.AuditStatusCodes)) }, diff --git a/cmd/admin/v2/network.go b/cmd/admin/v2/network.go index ce206ab..e924374 100644 --- a/cmd/admin/v2/network.go +++ b/cmd/admin/v2/network.go @@ -38,7 +38,7 @@ func newNetworkCmd(c *config.Config) *cobra.Command { cmd.Flags().StringP("type", "t", "", "type of the network. [optional]") cmd.Flags().String("nat-type", "", "nat type of the network. [optional]") - genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.Partition)) genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.NetworkAddressFamily)) genericcli.Must(cmd.RegisterFlagCompletionFunc("type", c.Completion.NetworkType)) @@ -79,7 +79,7 @@ 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.ProjectList)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.Partition)) genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.NetworkAddressFamily)) genericcli.Must(cmd.RegisterFlagCompletionFunc("type", c.Completion.NetworkType)) @@ -99,7 +99,7 @@ func newNetworkCmd(c *config.Config) *cobra.Command { cmd.Flags().StringSlice("add-labels", nil, "labels to add to the network") cmd.Flags().StringSlice("remove-labels", nil, "labels to remove to the network") - genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) }, } diff --git a/cmd/admin/v2/partition.go b/cmd/admin/v2/partition.go index e069a61..1fa3a1a 100644 --- a/cmd/admin/v2/partition.go +++ b/cmd/admin/v2/partition.go @@ -94,7 +94,7 @@ func newPartitionCmd(c *config.Config) *cobra.Command { capacityCmd.Flags().StringP("project", "", "", "consider project-specific counts, e.g. size reservations.") capacityCmd.Flags().StringSlice("sort-by", []string{}, fmt.Sprintf("order by (comma separated) column(s), sort direction can be changed by appending :asc or :desc behind the column identifier. possible values: %s", strings.Join(sorters.PartitionCapacitySorter().AvailableKeys(), "|"))) genericcli.Must(capacityCmd.RegisterFlagCompletionFunc("id", c.Completion.Partition)) - genericcli.Must(capacityCmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + genericcli.Must(capacityCmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) genericcli.Must(capacityCmd.RegisterFlagCompletionFunc("size", c.Completion.Size)) genericcli.Must(capacityCmd.RegisterFlagCompletionFunc("sort-by", cobra.FixedCompletions(sorters.PartitionCapacitySorter().AvailableKeys(), cobra.ShellCompDirectiveNoFileComp))) diff --git a/cmd/admin/v2/vpn.go b/cmd/admin/v2/vpn.go index c012c3c..1970fc2 100644 --- a/cmd/admin/v2/vpn.go +++ b/cmd/admin/v2/vpn.go @@ -37,10 +37,10 @@ func newVPNCmd(c *config.Config) *cobra.Command { 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") - genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) }, OnlyCmds: genericcli.OnlyCmds(genericcli.ListCmd), - ValidArgsFn: w.c.Completion.ProjectList, + ValidArgsFn: w.c.Completion.Project, } authKeyCmd := &cobra.Command{ @@ -49,14 +49,14 @@ func newVPNCmd(c *config.Config) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return w.authKey() }, - ValidArgsFunction: c.Completion.ProjectList, + ValidArgsFunction: c.Completion.Project, } authKeyCmd.Flags().String("project", "", "the project for which the authkey should be generated") authKeyCmd.Flags().String("reason", "", "the reason why 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")) - genericcli.Must(authKeyCmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + genericcli.Must(authKeyCmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) return genericcli.NewCmds(cmdsConfig, authKeyCmd) } diff --git a/cmd/api/v2/audit.go b/cmd/api/v2/audit.go index de87ace..de2617a 100644 --- a/cmd/api/v2/audit.go +++ b/cmd/api/v2/audit.go @@ -66,7 +66,7 @@ func newAuditCmd(c *config.Config) *cobra.Command { cmd.Flags().Bool("prettify-body", true, "attempts to interpret the body as json and prettifies it.") genericcli.Must(cmd.RegisterFlagCompletionFunc("phase", c.Completion.AuditPhase)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant", c.Completion.Tenant)) genericcli.Must(cmd.RegisterFlagCompletionFunc("result-code", c.Completion.AuditStatusCodes)) }, diff --git a/cmd/api/v2/ip.go b/cmd/api/v2/ip.go index f1e4f94..866a7a6 100644 --- a/cmd/api/v2/ip.go +++ b/cmd/api/v2/ip.go @@ -34,7 +34,7 @@ func newIPCmd(c *config.Config) *cobra.Command { ListCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().StringP("project", "p", "", "project from where ips should be listed") - genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) }, CreateCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().StringP("project", "p", "", "project of the ip") @@ -45,7 +45,7 @@ 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("project", c.Completion.ProjectList)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) }, UpdateCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().StringP("project", "p", "", "project of the ip") @@ -55,17 +55,17 @@ func newIPCmd(c *config.Config) *cobra.Command { cmd.Flags().StringArray("remove-labels", nil, "removes the volume labels with the given key") cmd.Flags().Bool("static", false, "make this ip static") - genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) }, DescribeCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().StringP("project", "p", "", "project of the ip") - genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) }, DeleteCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().StringP("project", "p", "", "project of the ip") - genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) }, CreateRequestFromCLI: w.createFromCLI, UpdateRequestFromCLI: w.updateFromCLI, diff --git a/cmd/api/v2/network.go b/cmd/api/v2/network.go index 1b68ed2..392145d 100644 --- a/cmd/api/v2/network.go +++ b/cmd/api/v2/network.go @@ -37,7 +37,7 @@ func newNetworkCmd(c *config.Config) *cobra.Command { cmd.Flags().StringP("type", "t", "", "type of the network. [optional]") genericcli.Must(cmd.RegisterFlagCompletionFunc("id", c.Completion.Network)) - genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.Partition)) genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.NetworkAddressFamily)) genericcli.Must(cmd.RegisterFlagCompletionFunc("type", c.Completion.NetworkType)) @@ -66,7 +66,7 @@ func newNetworkCmd(c *config.Config) *cobra.Command { 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.ProjectList)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.Partition)) genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.NetworkAddressFamily)) genericcli.Must(cmd.RegisterFlagCompletionFunc("parent-network", c.Completion.Network)) @@ -84,11 +84,11 @@ func newNetworkCmd(c *config.Config) *cobra.Command { cmd.Flags().StringSlice("add-labels", nil, "labels to add to the network") cmd.Flags().StringSlice("remove-labels", nil, "labels to remove to the network") - genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) }, DeleteCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().String("project", "", "project of this network.") - genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) }, } diff --git a/cmd/api/v2/project.go b/cmd/api/v2/project.go index 74565d6..6c14735 100644 --- a/cmd/api/v2/project.go +++ b/cmd/api/v2/project.go @@ -53,7 +53,7 @@ func newProjectCmd(c *config.Config) *cobra.Command { cmd.Flags().String("description", "", "the description of the project to update") }, UpdateRequestFromCLI: w.updateRequestFromCLI, - ValidArgsFn: w.c.Completion.ProjectList, + ValidArgsFn: w.c.Completion.Project, } inviteCmd := &cobra.Command{ @@ -72,7 +72,7 @@ func newProjectCmd(c *config.Config) *cobra.Command { generateInviteCmd.Flags().StringP("project", "p", "", "the project for which to generate the invite") generateInviteCmd.Flags().String("role", apiv2.ProjectRole_PROJECT_ROLE_VIEWER.String(), "the role that the new member will assume when joining through the invite secret") - genericcli.Must(generateInviteCmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + genericcli.Must(generateInviteCmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) genericcli.Must(generateInviteCmd.RegisterFlagCompletionFunc("role", c.Completion.ProjectRole)) deleteInviteCmd := &cobra.Command{ @@ -87,7 +87,7 @@ func newProjectCmd(c *config.Config) *cobra.Command { deleteInviteCmd.Flags().StringP("project", "p", "", "the project in which to delete the invite") - genericcli.Must(deleteInviteCmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + genericcli.Must(deleteInviteCmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) listInvitesCmd := &cobra.Command{ Use: "list", @@ -102,7 +102,7 @@ func newProjectCmd(c *config.Config) *cobra.Command { genericcli.AddSortFlag(listInvitesCmd, sorters.ProjectInviteSorter()) - genericcli.Must(listInvitesCmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + genericcli.Must(listInvitesCmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) joinProjectCmd := &cobra.Command{ Use: "join ", @@ -145,7 +145,7 @@ func newProjectCmd(c *config.Config) *cobra.Command { removeMemberCmd.Flags().StringP("project", "p", "", "the project in which to remove the member") - genericcli.Must(removeMemberCmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + genericcli.Must(removeMemberCmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) updateMemberCmd := &cobra.Command{ Use: "update ", @@ -159,7 +159,7 @@ func newProjectCmd(c *config.Config) *cobra.Command { updateMemberCmd.Flags().StringP("project", "p", "", "the project in which to remove the member") updateMemberCmd.Flags().String("role", "", "the role of the member") - genericcli.Must(updateMemberCmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + genericcli.Must(updateMemberCmd.RegisterFlagCompletionFunc("project", c.Completion.Project)) genericcli.Must(updateMemberCmd.RegisterFlagCompletionFunc("role", c.Completion.ProjectRole)) memberCmd.AddCommand(removeMemberCmd, updateMemberCmd, listMembersCmd) diff --git a/cmd/completion/completion.go b/cmd/completion/completion.go index 78cd273..90d554d 100644 --- a/cmd/completion/completion.go +++ b/cmd/completion/completion.go @@ -1,16 +1,20 @@ package completion import ( - "context" - "github.com/metal-stack/api/go/client" "github.com/spf13/cobra" ) type Completion struct { - Client client.Client - Project string - Ctx context.Context + client client.Client + project string +} + +func New(c client.Client, project string) *Completion { + return &Completion{ + client: c, + project: project, + } } func OutputFormat(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { diff --git a/cmd/completion/ip.go b/cmd/completion/ip.go index 09b24d6..c8f4b24 100644 --- a/cmd/completion/ip.go +++ b/cmd/completion/ip.go @@ -7,9 +7,9 @@ import ( func (c *Completion) Ip(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { req := &apiv2.IPServiceListRequest{ - Project: c.Project, + Project: c.project, } - resp, err := c.Client.Apiv2().IP().List(c.Ctx, req) + resp, err := c.client.Apiv2().IP().List(cmd.Context(), req) if err != nil { return nil, cobra.ShellCompDirectiveError } diff --git a/cmd/completion/network.go b/cmd/completion/network.go index f36c50e..91feaee 100644 --- a/cmd/completion/network.go +++ b/cmd/completion/network.go @@ -9,15 +9,15 @@ import ( ) func (c *Completion) Network(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - ownNetworks, err := c.Client.Apiv2().Network().List(c.Ctx, &apiv2.NetworkServiceListRequest{ - Project: c.Project, + ownNetworks, err := c.client.Apiv2().Network().List(cmd.Context(), &apiv2.NetworkServiceListRequest{ + Project: c.project, }) if err != nil { return nil, cobra.ShellCompDirectiveError } - baseNetworks, err := c.Client.Apiv2().Network().ListBaseNetworks(c.Ctx, &apiv2.NetworkServiceListBaseNetworksRequest{ - Project: c.Project, + baseNetworks, err := c.client.Apiv2().Network().ListBaseNetworks(cmd.Context(), &apiv2.NetworkServiceListBaseNetworksRequest{ + Project: c.project, }) if err != nil { return nil, cobra.ShellCompDirectiveError @@ -71,7 +71,7 @@ func (c *Completion) NetworkAddressFamily(cmd *cobra.Command, args []string, toC } func (c *Completion) NetworkAdmin(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - networks, err := c.Client.Adminv2().Network().List(c.Ctx, &adminv2.NetworkServiceListRequest{}) + networks, err := c.client.Adminv2().Network().List(cmd.Context(), &adminv2.NetworkServiceListRequest{}) if err != nil { return nil, cobra.ShellCompDirectiveError } diff --git a/cmd/completion/partition.go b/cmd/completion/partition.go index b40babd..af22118 100644 --- a/cmd/completion/partition.go +++ b/cmd/completion/partition.go @@ -7,7 +7,7 @@ import ( func (c *Completion) Partition(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { req := &apiv2.PartitionServiceListRequest{} - resp, err := c.Client.Apiv2().Partition().List(c.Ctx, req) + resp, err := c.client.Apiv2().Partition().List(cmd.Context(), req) if err != nil { return nil, cobra.ShellCompDirectiveError } diff --git a/cmd/completion/project.go b/cmd/completion/project.go index 5cc7684..f5cf734 100644 --- a/cmd/completion/project.go +++ b/cmd/completion/project.go @@ -6,9 +6,9 @@ import ( apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" ) -func (c *Completion) ProjectList(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func (c *Completion) Project(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { req := &apiv2.ProjectServiceListRequest{} - resp, err := c.Client.Apiv2().Project().List(c.Ctx, req) + resp, err := c.client.Apiv2().Project().List(cmd.Context(), req) if err != nil { return nil, cobra.ShellCompDirectiveError } @@ -36,8 +36,8 @@ func (c *Completion) ProjectRole(cmd *cobra.Command, args []string, toComplete s } func (c *Completion) ProjectInvite(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - resp, err := c.Client.Apiv2().Project().InvitesList(c.Ctx, &apiv2.ProjectServiceInvitesListRequest{ - Project: c.Project, + resp, err := c.client.Apiv2().Project().InvitesList(cmd.Context(), &apiv2.ProjectServiceInvitesListRequest{ + Project: c.project, }) if err != nil { return nil, cobra.ShellCompDirectiveError @@ -53,8 +53,8 @@ func (c *Completion) ProjectInvite(cmd *cobra.Command, args []string, toComplete } func (c *Completion) ProjectMember(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - resp, err := c.Client.Apiv2().Project().Get(c.Ctx, &apiv2.ProjectServiceGetRequest{ - Project: c.Project, + resp, err := c.client.Apiv2().Project().Get(cmd.Context(), &apiv2.ProjectServiceGetRequest{ + Project: c.project, }) if err != nil { return nil, cobra.ShellCompDirectiveError diff --git a/cmd/completion/size.go b/cmd/completion/size.go index 5f42fbe..204f97f 100644 --- a/cmd/completion/size.go +++ b/cmd/completion/size.go @@ -7,7 +7,7 @@ import ( func (c *Completion) Size(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { req := &apiv2.SizeServiceListRequest{} - resp, err := c.Client.Apiv2().Size().List(c.Ctx, req) + resp, err := c.client.Apiv2().Size().List(cmd.Context(), req) if err != nil { return nil, cobra.ShellCompDirectiveError } diff --git a/cmd/completion/switch.go b/cmd/completion/switch.go index f0b4d20..9889946 100644 --- a/cmd/completion/switch.go +++ b/cmd/completion/switch.go @@ -7,7 +7,7 @@ import ( ) func (c *Completion) Switch(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - resp, err := c.Client.Adminv2().Switch().List(c.Ctx, &adminv2.SwitchServiceListRequest{}) + resp, err := c.client.Adminv2().Switch().List(cmd.Context(), &adminv2.SwitchServiceListRequest{}) if err != nil { return nil, cobra.ShellCompDirectiveError } @@ -21,7 +21,7 @@ func (c *Completion) Switch(cmd *cobra.Command, args []string, toComplete string } func (c *Completion) SwitchPartition(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - resp, err := c.Client.Adminv2().Switch().List(c.Ctx, &adminv2.SwitchServiceListRequest{}) + resp, err := c.client.Adminv2().Switch().List(cmd.Context(), &adminv2.SwitchServiceListRequest{}) if err != nil { return nil, cobra.ShellCompDirectiveError } @@ -35,7 +35,7 @@ func (c *Completion) SwitchPartition(cmd *cobra.Command, args []string, toComple } func (c *Completion) SwitchRack(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - resp, err := c.Client.Adminv2().Switch().List(c.Ctx, &adminv2.SwitchServiceListRequest{}) + resp, err := c.client.Adminv2().Switch().List(cmd.Context(), &adminv2.SwitchServiceListRequest{}) if err != nil { return nil, cobra.ShellCompDirectiveError } @@ -49,7 +49,7 @@ func (c *Completion) SwitchRack(cmd *cobra.Command, args []string, toComplete st } func (c *Completion) SwitchOSVendor(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - resp, err := c.Client.Adminv2().Switch().List(c.Ctx, &adminv2.SwitchServiceListRequest{}) + resp, err := c.client.Adminv2().Switch().List(cmd.Context(), &adminv2.SwitchServiceListRequest{}) if err != nil { return nil, cobra.ShellCompDirectiveError } @@ -63,7 +63,7 @@ func (c *Completion) SwitchOSVendor(cmd *cobra.Command, args []string, toComplet } func (c *Completion) SwitchOSVersion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - resp, err := c.Client.Adminv2().Switch().List(c.Ctx, &adminv2.SwitchServiceListRequest{}) + resp, err := c.client.Adminv2().Switch().List(cmd.Context(), &adminv2.SwitchServiceListRequest{}) if err != nil { return nil, cobra.ShellCompDirectiveError } @@ -82,7 +82,7 @@ func (c *Completion) SwitchPorts(cmd *cobra.Command, args []string, toComplete s return nil, cobra.ShellCompDirectiveNoFileComp } - resp, err := c.Client.Adminv2().Switch().Get(c.Ctx, &adminv2.SwitchServiceGetRequest{ + resp, err := c.client.Adminv2().Switch().Get(cmd.Context(), &adminv2.SwitchServiceGetRequest{ Id: args[0], }) if err != nil { diff --git a/cmd/completion/tenant.go b/cmd/completion/tenant.go index bb848e5..e6e8d13 100644 --- a/cmd/completion/tenant.go +++ b/cmd/completion/tenant.go @@ -8,7 +8,7 @@ import ( func (c *Completion) Tenant(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { req := &apiv2.TenantServiceListRequest{} - resp, err := c.Client.Apiv2().Tenant().List(c.Ctx, req) + resp, err := c.client.Apiv2().Tenant().List(cmd.Context(), req) if err != nil { return nil, cobra.ShellCompDirectiveError } @@ -35,14 +35,14 @@ func (c *Completion) TenantRole(cmd *cobra.Command, args []string, toComplete st } func (c *Completion) TenantInvite(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - projectResp, err := c.Client.Apiv2().Project().Get(c.Ctx, &apiv2.ProjectServiceGetRequest{ - Project: c.Project, + projectResp, err := c.client.Apiv2().Project().Get(cmd.Context(), &apiv2.ProjectServiceGetRequest{ + Project: c.project, }) if err != nil { return nil, cobra.ShellCompDirectiveError } - resp, err := c.Client.Apiv2().Tenant().InvitesList(c.Ctx, &apiv2.TenantServiceInvitesListRequest{ + resp, err := c.client.Apiv2().Tenant().InvitesList(cmd.Context(), &apiv2.TenantServiceInvitesListRequest{ Login: projectResp.Project.Tenant, }) if err != nil { @@ -59,14 +59,14 @@ func (c *Completion) TenantInvite(cmd *cobra.Command, args []string, toComplete } func (c *Completion) TenantMember(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - projectResp, err := c.Client.Apiv2().Project().Get(c.Ctx, &apiv2.ProjectServiceGetRequest{ - Project: c.Project, + projectResp, err := c.client.Apiv2().Project().Get(cmd.Context(), &apiv2.ProjectServiceGetRequest{ + Project: c.project, }) if err != nil { return nil, cobra.ShellCompDirectiveError } - resp, err := c.Client.Apiv2().Tenant().Get(c.Ctx, &apiv2.TenantServiceGetRequest{ + resp, err := c.client.Apiv2().Tenant().Get(cmd.Context(), &apiv2.TenantServiceGetRequest{ Login: projectResp.Project.Tenant, }) if err != nil { @@ -84,7 +84,7 @@ func (c *Completion) TenantMember(cmd *cobra.Command, args []string, toComplete func (c *Completion) AdminTenant(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { req := &adminv2.TenantServiceListRequest{} - resp, err := c.Client.Adminv2().Tenant().List(c.Ctx, req) + resp, err := c.client.Adminv2().Tenant().List(cmd.Context(), req) if err != nil { return nil, cobra.ShellCompDirectiveError } diff --git a/cmd/completion/token.go b/cmd/completion/token.go index a4be2b8..9aa49b3 100644 --- a/cmd/completion/token.go +++ b/cmd/completion/token.go @@ -10,7 +10,7 @@ import ( func (c *Completion) Token(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { req := &apiv2.TokenServiceListRequest{} - resp, err := c.Client.Apiv2().Token().List(c.Ctx, req) + resp, err := c.client.Apiv2().Token().List(cmd.Context(), req) if err != nil { return nil, cobra.ShellCompDirectiveError } @@ -24,7 +24,7 @@ func (c *Completion) Token(cmd *cobra.Command, args []string, toComplete string) } func (c *Completion) TokenProjectRoles(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - methods, err := c.Client.Apiv2().Method().TokenScopedList(c.Ctx, &apiv2.MethodServiceTokenScopedListRequest{}) + methods, err := c.client.Apiv2().Method().TokenScopedList(cmd.Context(), &apiv2.MethodServiceTokenScopedListRequest{}) if err != nil { return nil, cobra.ShellCompDirectiveError } @@ -42,7 +42,7 @@ func (c *Completion) TokenProjectRoles(cmd *cobra.Command, args []string, toComp } func (c *Completion) TokenTenantRoles(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - methods, err := c.Client.Apiv2().Method().TokenScopedList(c.Ctx, &apiv2.MethodServiceTokenScopedListRequest{}) + methods, err := c.client.Apiv2().Method().TokenScopedList(cmd.Context(), &apiv2.MethodServiceTokenScopedListRequest{}) if err != nil { return nil, cobra.ShellCompDirectiveError } @@ -60,7 +60,7 @@ func (c *Completion) TokenTenantRoles(cmd *cobra.Command, args []string, toCompl } func (c *Completion) TokenMachineRoles(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - methods, err := c.Client.Apiv2().Method().TokenScopedList(c.Ctx, &apiv2.MethodServiceTokenScopedListRequest{}) + methods, err := c.client.Apiv2().Method().TokenScopedList(cmd.Context(), &apiv2.MethodServiceTokenScopedListRequest{}) if err != nil { return nil, cobra.ShellCompDirectiveError } @@ -104,7 +104,7 @@ func (c *Completion) TokenInfraRole(cmd *cobra.Command, args []string, toComplet } func (c *Completion) TokenPermissions(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - methods, err := c.Client.Apiv2().Method().List(c.Ctx, &apiv2.MethodServiceListRequest{}) + methods, err := c.client.Apiv2().Method().List(cmd.Context(), &apiv2.MethodServiceListRequest{}) if err != nil { return nil, cobra.ShellCompDirectiveError } diff --git a/cmd/context.go b/cmd/context.go index 8e8a77f..228e901 100644 --- a/cmd/context.go +++ b/cmd/context.go @@ -66,7 +66,7 @@ func newContextCmd(c *config.Config) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return w.setProject(args) }, - ValidArgsFunction: c.Completion.ProjectList, + ValidArgsFunction: c.Completion.Project, } contextRemoveCmd := &cobra.Command{ Use: "remove ", @@ -110,7 +110,7 @@ func newContextCmd(c *config.Config) *cobra.Command { contextUpdateCmd.Flags().Bool("activate", false, "immediately switches to the new context") contextUpdateCmd.Flags().String("provider", "", "sets the login provider for this context") - genericcli.Must(contextUpdateCmd.RegisterFlagCompletionFunc("default-project", c.Completion.ProjectList)) + genericcli.Must(contextUpdateCmd.RegisterFlagCompletionFunc("default-project", c.Completion.Project)) contextCmd.AddCommand( contextListCmd, diff --git a/cmd/root.go b/cmd/root.go index 395c943..8041aae 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,7 +1,6 @@ package cmd import ( - "context" "log/slog" "os" @@ -21,11 +20,10 @@ import ( func Execute() { cfg := &config.Config{ - Fs: afero.NewOsFs(), - Out: os.Stdout, - PromptOut: os.Stdout, - In: os.Stdin, - Completion: &completion.Completion{}, + Fs: afero.NewOsFs(), + Out: os.Stdout, + PromptOut: os.Stdout, + In: os.Stdin, } cmd := NewRootCmd(cfg) @@ -129,10 +127,7 @@ func initConfigWithViperCtx(c *config.Config) error { } c.Client = mc - c.Completion.Client = mc - c.Completion.Ctx = context.Background() - c.Completion.Project = c.GetProject() - + c.Completion = completion.New(mc, c.GetProject()) return nil } diff --git a/testing/e2e/test_cmd.go b/testing/e2e/test_cmd.go index ba36594..405464f 100644 --- a/testing/e2e/test_cmd.go +++ b/testing/e2e/test_cmd.go @@ -57,14 +57,12 @@ func NewRootCmd(t *testing.T, c *TestConfig) e2e_test.NewRootCmdFunc { viper.Reset() return cmd.NewRootCmd(&config.Config{ - Fs: fs, - Out: &out, - In: in, - PromptOut: io.Discard, - Completion: &completion.Completion{ - Client: cl, - }, - Client: cl, + Fs: fs, + Out: &out, + In: in, + PromptOut: io.Discard, + Completion: completion.New(cl, ""), + Client: cl, }), &out } } From ebe1fb7d01b5b7b402c1422cd7e6c3456e4dab38 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Fri, 31 Jul 2026 08:51:19 +0200 Subject: [PATCH 11/13] Typo --- cmd/tableprinters/vpn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/tableprinters/vpn.go b/cmd/tableprinters/vpn.go index d772229..a739b3e 100644 --- a/cmd/tableprinters/vpn.go +++ b/cmd/tableprinters/vpn.go @@ -11,7 +11,7 @@ import ( func (t *TablePrinter) VPNTable(data []*apiv2.VPNNode, _ bool) ([]string, [][]string, error) { var ( rows [][]string - header = []string{"ID", "Name", "Project", "IPS", "Last Seed"} + header = []string{"ID", "Name", "Project", "IPS", "Last Seen"} ) for _, node := range data { From 99c8dd49d09f4cda6b35d68200223c5cb1330610 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Fri, 31 Jul 2026 13:52:11 +0200 Subject: [PATCH 12/13] Test fix --- tests/e2e/admin/vpn_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/admin/vpn_test.go b/tests/e2e/admin/vpn_test.go index c0ea94c..5fdec98 100644 --- a/tests/e2e/admin/vpn_test.go +++ b/tests/e2e/admin/vpn_test.go @@ -41,12 +41,12 @@ func Test_AdminVpnCmd_List(t *testing.T) { 2 f3b4e6a1-2c8d-4e5f-a7b9-1d3e5f7a9b0c `), WantTable: new(` - ID NAME PROJECT IPS LAST SEED + ID NAME PROJECT IPS LAST SEEN 1 node-1 0d81bca7-73f6-4da3-8397-4a8c52a0c583 1.2.3.4,1.2.3.5 2000-01-01 00:00:00 2 node-2 f3b4e6a1-2c8d-4e5f-a7b9-1d3e5f7a9b0c 2.3.4.5 2000-01-01 00:01:00 `), WantMarkdown: new(` - | ID | NAME | PROJECT | IPS | LAST SEED | + | ID | NAME | PROJECT | IPS | LAST SEEN | |----|--------|--------------------------------------|-----------------|---------------------| | 1 | node-1 | 0d81bca7-73f6-4da3-8397-4a8c52a0c583 | 1.2.3.4,1.2.3.5 | 2000-01-01 00:00:00 | | 2 | node-2 | f3b4e6a1-2c8d-4e5f-a7b9-1d3e5f7a9b0c | 2.3.4.5 | 2000-01-01 00:01:00 | From 9bbbced9d7ccc017978eb8d1b855b7b8af27b97a Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Fri, 31 Jul 2026 14:08:38 +0200 Subject: [PATCH 13/13] Test fix --- tests/e2e/admin/vpn_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/admin/vpn_test.go b/tests/e2e/admin/vpn_test.go index c0ea94c..5fdec98 100644 --- a/tests/e2e/admin/vpn_test.go +++ b/tests/e2e/admin/vpn_test.go @@ -41,12 +41,12 @@ func Test_AdminVpnCmd_List(t *testing.T) { 2 f3b4e6a1-2c8d-4e5f-a7b9-1d3e5f7a9b0c `), WantTable: new(` - ID NAME PROJECT IPS LAST SEED + ID NAME PROJECT IPS LAST SEEN 1 node-1 0d81bca7-73f6-4da3-8397-4a8c52a0c583 1.2.3.4,1.2.3.5 2000-01-01 00:00:00 2 node-2 f3b4e6a1-2c8d-4e5f-a7b9-1d3e5f7a9b0c 2.3.4.5 2000-01-01 00:01:00 `), WantMarkdown: new(` - | ID | NAME | PROJECT | IPS | LAST SEED | + | ID | NAME | PROJECT | IPS | LAST SEEN | |----|--------|--------------------------------------|-----------------|---------------------| | 1 | node-1 | 0d81bca7-73f6-4da3-8397-4a8c52a0c583 | 1.2.3.4,1.2.3.5 | 2000-01-01 00:00:00 | | 2 | node-2 | f3b4e6a1-2c8d-4e5f-a7b9-1d3e5f7a9b0c | 2.3.4.5 | 2000-01-01 00:01:00 |