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/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/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 new file mode 100644 index 0000000..ce206ab --- /dev/null +++ b/cmd/admin/v2/network.go @@ -0,0 +1,437 @@ +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.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)) + } + ) + + 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.NetworkAdmin, + 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", "", "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]") + 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.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.Network)) + }, + 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.ProjectList)) + }, + } + + 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 + 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") { + nt, err := enum.GetEnum[apiv2.NATType](viper.GetString("nat-type")) + if err != nil { + return nil, err + } + + natType = &nt + } + + 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"), + UpdateMeta: &apiv2.UpdateMeta{ + LockingStrategy: apiv2.OptimisticLockingStrategy_OPTIMISTIC_LOCKING_STRATEGY_SERVER, + }, + } + ) + + return ur, nil +} 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/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/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 new file mode 100644 index 0000000..1b68ed2 --- /dev/null +++ b/cmd/api/v2/network.go @@ -0,0 +1,356 @@ +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.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]{ + 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.Network, + 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", "", "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]") + 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.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.Network)) + }, + 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.ProjectList)) + }, + DeleteCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("project", "", "project of this network.") + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectList)) + }, + } + + 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 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 + + 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() + + 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 + } + + 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 helpers.EncodeProject(r.Id, pointer.SafeDeref(r.Project)), 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/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 new file mode 100644 index 0000000..f36c50e --- /dev/null +++ b/cmd/completion/network.go @@ -0,0 +1,85 @@ +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) 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, + }) + 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) 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 { + names = append(names, *e) + } + } + return names, cobra.ShellCompDirectiveNoFileComp +} + +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.NATType(val)); err == nil { + names = append(names, *e) + } + } + return names, cobra.ShellCompDirectiveNoFileComp +} + +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, + 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) 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 + } + + var names []string + for _, s := range networks.Networks { + names = append(names, s.Id+"\t"+pointer.SafeDeref(s.Name)) + } + + return names, cobra.ShellCompDirectiveNoFileComp +} diff --git a/cmd/completion/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 } 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..8b31e78 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: diff --git a/cmd/tableprinters/network.go b/cmd/tableprinters/network.go new file mode 100644 index 0000000..b6b6306 --- /dev/null +++ b/cmd/tableprinters/network.go @@ -0,0 +1,181 @@ +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 := renderNetworkRow("", 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 := renderNetworkRow(prefix, c, wide) + if err != nil { + return nil, nil, err + } + + rows = append(rows, row) + } + } + + return header, rows, nil +} + +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, ",") + 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 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 +} + +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/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 { 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..3f642d7 --- /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 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] + --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..a7973f5 --- /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 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 +``` + +### 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..231471f --- /dev/null +++ b/docs/metalctlv2_network_delete.md @@ -0,0 +1,47 @@ +## 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 + --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 +``` + +### 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 +} diff --git a/tests/e2e/admin/network_test.go b/tests/e2e/admin/network_test.go new file mode 100644 index 0000000..d0e82f9 --- /dev/null +++ b/tests/e2e/admin/network_test.go @@ -0,0 +1,392 @@ +package admin_e2e + +import ( + "strconv" + "strings" + "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/metal-stack/metal-lib/pkg/pointer" + "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", + "--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{ + 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{ + 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, + "--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"}, + 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{ + 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..e7bc07e --- /dev/null +++ b/tests/e2e/api/network_test.go @@ -0,0 +1,438 @@ +package api_e2e + +import ( + "fmt" + "strconv" + "strings" + "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/metal-stack/metal-lib/pkg/pointer" + "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, + "--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, + 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 { + 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..b8c4c92 --- /dev/null +++ b/tests/e2e/testresources/networks.go @@ -0,0 +1,53 @@ +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)), + ParentNetwork: &Network1().Id, + Meta: &apiv2.Meta{ + CreatedAt: timestamppb.New(e2e.TimeBubbleStartTime()), + Labels: &apiv2.Labels{ + Labels: map[string]string{ + "a": "b", + }, + }, + }, + } + } +)