diff --git a/.codegraph/.gitignore b/.codegraph/.gitignore new file mode 100644 index 0000000..d20c0fe --- /dev/null +++ b/.codegraph/.gitignore @@ -0,0 +1,5 @@ +# CodeGraph data files — local to each machine, not for committing. +# Ignore everything in .codegraph/ except this file itself, so transient +# files (the database, daemon.pid, sockets, logs) never show up in git. +* +!.gitignore diff --git a/cmd/admin/v2/commands.go b/cmd/admin/v2/commands.go index ff90b69..6c21de6 100644 --- a/cmd/admin/v2/commands.go +++ b/cmd/admin/v2/commands.go @@ -14,6 +14,15 @@ func AddCmds(cmd *cobra.Command, c *config.Config) { Hidden: true, } + adminCmd.AddCommand(newAdminFilesystemLayoutCmd(c)) + adminCmd.AddCommand(newAdminImageUsageCmd(c)) + adminCmd.AddCommand(newAdminMachineCmd(c)) + adminCmd.AddCommand(newAdminNetworkCmd(c)) + adminCmd.AddCommand(newAdminPartitionCmd(c)) + adminCmd.AddCommand(newAdminSizeImageConstraintCmd(c)) + adminCmd.AddCommand(newAdminSizeReservationCmd(c)) + adminCmd.AddCommand(newAdminTokenCreateCmd(c)) + adminCmd.AddCommand(newAdminVPNCmd(c)) adminCmd.AddCommand(newAuditCmd(c)) adminCmd.AddCommand(newComponentCmd(c)) adminCmd.AddCommand(newImageCmd(c)) diff --git a/cmd/admin/v2/filesystem.go b/cmd/admin/v2/filesystem.go new file mode 100644 index 0000000..c4ee171 --- /dev/null +++ b/cmd/admin/v2/filesystem.go @@ -0,0 +1,101 @@ +package v2 + +import ( + "fmt" + + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/spf13/cobra" +) + +type adminFilesystemLayout struct { + c *config.Config +} + +func newAdminFilesystemLayoutCmd(c *config.Config) *cobra.Command { + w := &adminFilesystemLayout{ + c: c, + } + + cmdsConfig := &genericcli.CmdsConfig[any, *adminv2.FilesystemServiceUpdateRequest, *apiv2.FilesystemLayout]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), + Singular: "filesystem-layout", + Plural: "filesystem-layouts", + Description: "manage filesystem layouts", + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + OnlyCmds: genericcli.OnlyCmds(genericcli.DescribeCmd, genericcli.ListCmd, genericcli.DeleteCmd), + } + + return genericcli.NewCmds(cmdsConfig) +} + +func (c *adminFilesystemLayout) Get(id string) (*apiv2.FilesystemLayout, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &apiv2.FilesystemServiceGetRequest{Id: id} + + resp, err := c.c.Client.Apiv2().Filesystem().Get(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get filesystem layout: %w", err) + } + + return resp.FilesystemLayout, nil +} + +func (c *adminFilesystemLayout) List() ([]*apiv2.FilesystemLayout, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &apiv2.FilesystemServiceListRequest{} + + resp, err := c.c.Client.Apiv2().Filesystem().List(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to list filesystem layouts: %w", err) + } + + return resp.FilesystemLayouts, nil +} + +func (c *adminFilesystemLayout) Create(rq any) (*apiv2.FilesystemLayout, error) { + panic("unimplemented") +} + +func (c *adminFilesystemLayout) Delete(id string) (*apiv2.FilesystemLayout, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().Filesystem().Delete(ctx, &adminv2.FilesystemServiceDeleteRequest{Id: id}) + if err != nil { + return nil, fmt.Errorf("failed to delete filesystem layout: %w", err) + } + + return resp.FilesystemLayout, nil +} + +func (c *adminFilesystemLayout) Convert(r *apiv2.FilesystemLayout) (string, any, *adminv2.FilesystemServiceUpdateRequest, error) { + return r.Id, nil, &adminv2.FilesystemServiceUpdateRequest{ + Id: r.Id, + Name: r.Name, + Description: r.Description, + Disks: r.Disks, + Constraints: r.Constraints, + }, nil +} + +func (c *adminFilesystemLayout) Update(rq *adminv2.FilesystemServiceUpdateRequest) (*apiv2.FilesystemLayout, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().Filesystem().Update(ctx, rq) + if err != nil { + return nil, fmt.Errorf("failed to update filesystem layout: %w", err) + } + + return resp.FilesystemLayout, nil +} diff --git a/cmd/admin/v2/image_usage.go b/cmd/admin/v2/image_usage.go new file mode 100644 index 0000000..f2a247f --- /dev/null +++ b/cmd/admin/v2/image_usage.go @@ -0,0 +1,54 @@ +package v2 + +import ( + "fmt" + + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/metal-lib/pkg/pointer" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +type adminImageUsage struct { + c *config.Config +} + +func newAdminImageUsageCmd(c *config.Config) *cobra.Command { + w := &adminImageUsage{ + c: c, + } + + usageCmd := &cobra.Command{ + Use: "image-usage", + Short: "show image usage information", + RunE: func(cmd *cobra.Command, args []string) error { + return w.imageUsage() + }, + } + + usageCmd.Flags().String("id", "", "image id to query usage for") + + return usageCmd +} + +func (c *adminImageUsage) imageUsage() error { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &adminv2.ImageServiceUsageRequest{ + Query: &apiv2.ImageQuery{}, + } + + if viper.IsSet("id") { + req.Query.Id = pointer.PointerOrNil(viper.GetString("id")) + } + + resp, err := c.c.Client.Adminv2().Image().Usage(ctx, req) + if err != nil { + return fmt.Errorf("failed to get image usage: %w", err) + } + + return c.c.ListPrinter.Print(resp.ImageUsage) +} diff --git a/cmd/admin/v2/machine.go b/cmd/admin/v2/machine.go new file mode 100644 index 0000000..52c6dce --- /dev/null +++ b/cmd/admin/v2/machine.go @@ -0,0 +1,257 @@ +package v2 + +import ( + "fmt" + + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/cli/cmd/sorters" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/metal-stack/metal-lib/pkg/pointer" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +type adminMachine struct { + c *config.Config +} + +func newAdminMachineCmd(c *config.Config) *cobra.Command { + w := &adminMachine{ + c: c, + } + + cmdsConfig := &genericcli.CmdsConfig[any, any, *apiv2.Machine]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), + Singular: "machine", + Plural: "machines", + Description: "manage machines", + Sorter: sorters.MachineSorter(), + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + ValidArgsFn: c.Completion.MachineListCompletion, + OnlyCmds: genericcli.OnlyCmds(genericcli.DescribeCmd, genericcli.ListCmd, genericcli.DeleteCmd), + ListCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().StringP("partition", "", "", "partition to filter for") + cmd.Flags().String("id", "", "machine id to filter for") + cmd.Flags().String("size", "", "size to filter for") + cmd.Flags().String("rack", "", "rack to filter for") + }, + } + + setStateCmd := &cobra.Command{ + Use: "set-state ", + Short: "set the state of a machine", + RunE: func(cmd *cobra.Command, args []string) error { + return w.setState(args) + }, + ValidArgsFunction: c.Completion.MachineListCompletion, + } + + setStateCmd.Flags().String("state", "", "the state to set (e.g. AVAILABLE, LOCKED, TAINTED)") + setStateCmd.Flags().String("description", "", "description why this machine state was set") + + consolePasswordCmd := &cobra.Command{ + Use: "console-password ", + Short: "get the console password of a machine", + RunE: func(cmd *cobra.Command, args []string) error { + return w.consolePassword(args) + }, + ValidArgsFunction: c.Completion.MachineListCompletion, + } + + issuesCmd := &cobra.Command{ + Use: "issues", + Short: "list machines with issues", + RunE: func(cmd *cobra.Command, args []string) error { + return w.issues() + }, + } + + issuesCmd.Flags().String("partition", "", "partition to filter for") + issuesCmd.Flags().String("machine-id", "", "machine id to filter for") + + bmcCmd := &cobra.Command{ + Use: "bmc", + Short: "manage machine BMC", + } + + bmcGetCmd := &cobra.Command{ + Use: "get ", + Short: "get BMC details of a machine", + RunE: func(cmd *cobra.Command, args []string) error { + return w.bmcGet(args) + }, + ValidArgsFunction: c.Completion.MachineListCompletion, + } + + bmcListCmd := &cobra.Command{ + Use: "list", + Short: "list BMC details of many machines", + RunE: func(cmd *cobra.Command, args []string) error { + return w.bmcList() + }, + } + + bmcCmd.AddCommand(bmcGetCmd, bmcListCmd) + + return genericcli.NewCmds(cmdsConfig, setStateCmd, consolePasswordCmd, issuesCmd, bmcCmd) +} + +func (c *adminMachine) Get(id string) (*apiv2.Machine, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &adminv2.MachineServiceGetRequest{Uuid: id} + + resp, err := c.c.Client.Adminv2().Machine().Get(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get machine: %w", err) + } + + return resp.Machine, nil +} + +func (c *adminMachine) List() ([]*apiv2.Machine, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &adminv2.MachineServiceListRequest{ + Query: &apiv2.MachineQuery{ + Uuid: pointer.PointerOrNil(viper.GetString("id")), + Partition: pointer.PointerOrNil(viper.GetString("partition")), + Size: pointer.PointerOrNil(viper.GetString("size")), + Rack: pointer.PointerOrNil(viper.GetString("rack")), + }, + } + + resp, err := c.c.Client.Adminv2().Machine().List(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to list machines: %w", err) + } + + return resp.Machines, nil +} + +func (c *adminMachine) Delete(id string) (*apiv2.Machine, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().Machine().Delete(ctx, &adminv2.MachineServiceDeleteRequest{Uuid: id}) + if err != nil { + return nil, fmt.Errorf("failed to delete machine: %w", err) + } + + return resp.Machine, nil +} + +func (c *adminMachine) setState(args []string) error { + id, err := genericcli.GetExactlyOneArg(args) + if err != nil { + return err + } + + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + stateStr := viper.GetString("state") + state, ok := apiv2.MachineState_value[stateStr] + if !ok { + return fmt.Errorf("invalid machine state: %s", stateStr) + } + + _, err = c.c.Client.Adminv2().Machine().SetState(ctx, &adminv2.MachineServiceSetStateRequest{ + Uuid: id, + State: apiv2.MachineState(state), + Description: viper.GetString("description"), + }) + if err != nil { + return fmt.Errorf("failed to set machine state: %w", err) + } + + return nil +} + +func (c *adminMachine) consolePassword(args []string) error { + id, err := genericcli.GetExactlyOneArg(args) + if err != nil { + return err + } + + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().Machine().ConsolePassword(ctx, &adminv2.MachineServiceConsolePasswordRequest{ + Uuid: id, + }) + if err != nil { + return fmt.Errorf("failed to get console password: %w", err) + } + + return c.c.DescribePrinter.Print(resp) +} + +func (c *adminMachine) issues() error { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().Machine().Issues(ctx, &adminv2.MachineServiceIssuesRequest{ + Query: &apiv2.MachineIssuesQuery{ + MachineQuery: &apiv2.MachineQuery{ + Uuid: pointer.PointerOrNil(viper.GetString("machine-id")), + Partition: pointer.PointerOrNil(viper.GetString("partition")), + }, + }, + }) + if err != nil { + return fmt.Errorf("failed to list machine issues: %w", err) + } + + return c.c.ListPrinter.Print(resp.Issues) +} + +func (c *adminMachine) bmcGet(args []string) error { + id, err := genericcli.GetExactlyOneArg(args) + if err != nil { + return err + } + + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().Machine().GetBMC(ctx, &adminv2.MachineServiceGetBMCRequest{ + Uuid: id, + }) + if err != nil { + return fmt.Errorf("failed to get BMC: %w", err) + } + + return c.c.DescribePrinter.Print(resp) +} + +func (c *adminMachine) bmcList() error { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().Machine().ListBMC(ctx, &adminv2.MachineServiceListBMCRequest{}) + if err != nil { + return fmt.Errorf("failed to list BMC: %w", err) + } + + return c.c.DescribePrinter.Print(resp) +} + +func (c *adminMachine) Create(rq any) (*apiv2.Machine, error) { + panic("unimplemented") +} + +func (c *adminMachine) Convert(r *apiv2.Machine) (string, any, any, error) { + panic("unimplemented") +} + +func (c *adminMachine) Update(rq any) (*apiv2.Machine, error) { + panic("unimplemented") +} diff --git a/cmd/admin/v2/network.go b/cmd/admin/v2/network.go new file mode 100644 index 0000000..0048da9 --- /dev/null +++ b/cmd/admin/v2/network.go @@ -0,0 +1,141 @@ +package v2 + +import ( + "fmt" + + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/cli/cmd/sorters" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/spf13/cobra" +) + +type adminNetwork struct { + c *config.Config +} + +func newAdminNetworkCmd(c *config.Config) *cobra.Command { + w := &adminNetwork{ + c: c, + } + + cmdsConfig := &genericcli.CmdsConfig[*adminv2.NetworkServiceCreateRequest, *adminv2.NetworkServiceUpdateRequest, *apiv2.Network]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), + Singular: "network", + Plural: "networks", + Description: "manage networks", + Sorter: sorters.NetworkSorter(), + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + ValidArgsFn: c.Completion.NetworkListCompletion, + } + + return genericcli.NewCmds(cmdsConfig) +} + +func (c *adminNetwork) Get(id string) (*apiv2.Network, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &adminv2.NetworkServiceGetRequest{Id: id} + + resp, err := c.c.Client.Adminv2().Network().Get(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get network: %w", err) + } + + return resp.Network, nil +} + +func (c *adminNetwork) List() ([]*apiv2.Network, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &adminv2.NetworkServiceListRequest{} + + resp, err := c.c.Client.Adminv2().Network().List(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to list networks: %w", err) + } + + return resp.Networks, nil +} + +func (c *adminNetwork) 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 { + return nil, fmt.Errorf("failed to create network: %w", err) + } + + return resp.Network, nil +} + +func (c *adminNetwork) 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, fmt.Errorf("failed to delete network: %w", err) + } + + return resp.Network, nil +} + +func (c *adminNetwork) Convert(r *apiv2.Network) (string, *adminv2.NetworkServiceCreateRequest, *adminv2.NetworkServiceUpdateRequest, error) { + return r.Id, &adminv2.NetworkServiceCreateRequest{ + Id: &r.Id, + Name: r.Name, + Description: r.Description, + Partition: r.Partition, + Project: r.Project, + Type: r.Type, + Prefixes: r.Prefixes, + DestinationPrefixes: r.DestinationPrefixes, + DefaultChildPrefixLength: r.DefaultChildPrefixLength, + MinChildPrefixLength: r.MinChildPrefixLength, + Labels: r.Meta.Labels, + NatType: r.NatType.Enum(), + Vrf: r.Vrf, + ParentNetwork: r.ParentNetwork, + AdditionalAnnouncableCidrs: r.AdditionalAnnouncableCidrs, + Length: r.DefaultChildPrefixLength, + }, &adminv2.NetworkServiceUpdateRequest{ + Id: r.Id, + Name: r.Name, + Description: r.Description, + Prefixes: r.Prefixes, + DestinationPrefixes: r.DestinationPrefixes, + DefaultChildPrefixLength: r.DefaultChildPrefixLength, + MinChildPrefixLength: r.MinChildPrefixLength, + NatType: r.NatType.Enum(), + AdditionalAnnouncableCidrs: r.AdditionalAnnouncableCidrs, + Labels: &apiv2.UpdateLabels{ + Strategy: &apiv2.UpdateLabels_Replace{ + Replace: r.Meta.Labels, + }, + }, + UpdateMeta: &apiv2.UpdateMeta{ + LockingStrategy: apiv2.OptimisticLockingStrategy_OPTIMISTIC_LOCKING_STRATEGY_CLIENT, + UpdatedAt: r.Meta.UpdatedAt, + }, + }, nil +} + +func (c *adminNetwork) 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, fmt.Errorf("failed to update network: %w", err) + } + + return resp.Network, nil +} diff --git a/cmd/admin/v2/partition.go b/cmd/admin/v2/partition.go new file mode 100644 index 0000000..1e8d794 --- /dev/null +++ b/cmd/admin/v2/partition.go @@ -0,0 +1,142 @@ +package v2 + +import ( + "fmt" + + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/cli/cmd/sorters" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/metal-stack/metal-lib/pkg/pointer" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +type adminPartition struct { + c *config.Config +} + +func newAdminPartitionCmd(c *config.Config) *cobra.Command { + w := &adminPartition{ + c: c, + } + + cmdsConfig := &genericcli.CmdsConfig[any, *adminv2.PartitionServiceUpdateRequest, *apiv2.Partition]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), + Singular: "partition", + Plural: "partitions", + Description: "manage partitions (failure domains)", + Sorter: sorters.PartitionSorter(), + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + ValidArgsFn: c.Completion.PartitionListCompletion, + OnlyCmds: genericcli.OnlyCmds(genericcli.DescribeCmd, genericcli.ListCmd, genericcli.DeleteCmd), + ListCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().StringP("id", "", "", "partition id to filter for") + }, + } + + capacityCmd := &cobra.Command{ + Use: "capacity", + Short: "show partition capacity", + RunE: func(cmd *cobra.Command, args []string) error { + return w.capacity() + }, + } + + capacityCmd.Flags().String("id", "", "partition id to filter for") + capacityCmd.Flags().String("size", "", "size to filter for") + capacityCmd.Flags().String("project", "", "project to filter for") + + return genericcli.NewCmds(cmdsConfig, capacityCmd) +} + +func (c *adminPartition) capacity() error { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &adminv2.PartitionServiceCapacityRequest{ + Id: pointer.PointerOrNil(viper.GetString("id")), + Size: pointer.PointerOrNil(viper.GetString("size")), + Project: pointer.PointerOrNil(viper.GetString("project")), + } + + resp, err := c.c.Client.Adminv2().Partition().Capacity(ctx, req) + if err != nil { + return fmt.Errorf("failed to get partition capacity: %w", err) + } + + return c.c.ListPrinter.Print(resp.PartitionCapacity) +} + +func (c *adminPartition) Get(id string) (*apiv2.Partition, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &apiv2.PartitionServiceGetRequest{Id: id} + + resp, err := c.c.Client.Apiv2().Partition().Get(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get partition: %w", err) + } + + return resp.Partition, nil +} + +func (c *adminPartition) List() ([]*apiv2.Partition, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &apiv2.PartitionServiceListRequest{Query: &apiv2.PartitionQuery{ + Id: pointer.PointerOrNil(viper.GetString("id")), + }} + + resp, err := c.c.Client.Apiv2().Partition().List(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get partitions: %w", err) + } + + return resp.Partitions, nil +} + +func (c *adminPartition) Create(rq any) (*apiv2.Partition, error) { + panic("unimplemented") +} + +func (c *adminPartition) Delete(id string) (*apiv2.Partition, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().Partition().Delete(ctx, &adminv2.PartitionServiceDeleteRequest{Id: id}) + if err != nil { + return nil, fmt.Errorf("failed to delete partition: %w", err) + } + + return resp.Partition, nil +} + +func (c *adminPartition) Convert(r *apiv2.Partition) (string, any, *adminv2.PartitionServiceUpdateRequest, error) { + return r.Id, nil, &adminv2.PartitionServiceUpdateRequest{ + Id: r.Id, + Description: &r.Description, + BootConfiguration: r.BootConfiguration, + DnsServers: r.DnsServers, + NtpServers: r.NtpServers, + MgmtServiceAddresses: r.MgmtServiceAddresses, + }, nil +} + +func (c *adminPartition) Update(rq *adminv2.PartitionServiceUpdateRequest) (*apiv2.Partition, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().Partition().Update(ctx, rq) + if err != nil { + return nil, fmt.Errorf("failed to update partition: %w", err) + } + + return resp.Partition, nil +} diff --git a/cmd/admin/v2/size_image_constraint.go b/cmd/admin/v2/size_image_constraint.go new file mode 100644 index 0000000..0e3175b --- /dev/null +++ b/cmd/admin/v2/size_image_constraint.go @@ -0,0 +1,102 @@ +package v2 + +import ( + "fmt" + + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/cli/cmd/sorters" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/spf13/cobra" +) + +type adminSizeImageConstraint struct { + c *config.Config +} + +func newAdminSizeImageConstraintCmd(c *config.Config) *cobra.Command { + w := &adminSizeImageConstraint{ + c: c, + } + + cmdsConfig := &genericcli.CmdsConfig[any, *adminv2.SizeImageConstraintServiceUpdateRequest, *apiv2.SizeImageConstraint]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), + Singular: "size-image-constraint", + Plural: "size-image-constraints", + Description: "manage size image constraints", + Sorter: sorters.SizeImageConstraintSorter(), + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + OnlyCmds: genericcli.OnlyCmds(genericcli.DescribeCmd, genericcli.ListCmd, genericcli.DeleteCmd), + } + + return genericcli.NewCmds(cmdsConfig) +} + +func (c *adminSizeImageConstraint) Get(id string) (*apiv2.SizeImageConstraint, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &adminv2.SizeImageConstraintServiceGetRequest{Size: id} + + resp, err := c.c.Client.Adminv2().SizeImageConstraint().Get(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get size image constraint: %w", err) + } + + return resp.SizeImageConstraint, nil +} + +func (c *adminSizeImageConstraint) List() ([]*apiv2.SizeImageConstraint, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &adminv2.SizeImageConstraintServiceListRequest{} + + resp, err := c.c.Client.Adminv2().SizeImageConstraint().List(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to list size image constraints: %w", err) + } + + return resp.SizeImageConstraints, nil +} + +func (c *adminSizeImageConstraint) Create(rq any) (*apiv2.SizeImageConstraint, error) { + panic("unimplemented") +} + +func (c *adminSizeImageConstraint) Delete(id string) (*apiv2.SizeImageConstraint, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().SizeImageConstraint().Delete(ctx, &adminv2.SizeImageConstraintServiceDeleteRequest{Size: id}) + if err != nil { + return nil, fmt.Errorf("failed to delete size image constraint: %w", err) + } + + return resp.SizeImageConstraint, nil +} + +func (c *adminSizeImageConstraint) Convert(r *apiv2.SizeImageConstraint) (string, any, *adminv2.SizeImageConstraintServiceUpdateRequest, error) { + return r.Size, nil, &adminv2.SizeImageConstraintServiceUpdateRequest{ + Size: r.Size, + ImageConstraints: r.ImageConstraints, + Name: r.Name, + Description: r.Description, + }, nil +} + +func (c *adminSizeImageConstraint) Update(rq *adminv2.SizeImageConstraintServiceUpdateRequest) (*apiv2.SizeImageConstraint, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().SizeImageConstraint().Update(ctx, rq) + if err != nil { + return nil, fmt.Errorf("failed to update size image constraint: %w", err) + } + + return resp.SizeImageConstraint, nil +} diff --git a/cmd/admin/v2/size_reservation.go b/cmd/admin/v2/size_reservation.go new file mode 100644 index 0000000..c9653a7 --- /dev/null +++ b/cmd/admin/v2/size_reservation.go @@ -0,0 +1,104 @@ +package v2 + +import ( + "fmt" + + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/cli/cmd/sorters" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/spf13/cobra" +) + +type adminSizeReservation struct { + c *config.Config +} + +func newAdminSizeReservationCmd(c *config.Config) *cobra.Command { + w := &adminSizeReservation{ + c: c, + } + + cmdsConfig := &genericcli.CmdsConfig[any, *adminv2.SizeReservationServiceUpdateRequest, *apiv2.SizeReservation]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), + Singular: "size-reservation", + Plural: "size-reservations", + Description: "manage size reservations", + Sorter: sorters.SizeReservationSorter(), + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + ValidArgsFn: c.Completion.AdminSizeReservationListCompletion, + OnlyCmds: genericcli.OnlyCmds(genericcli.DescribeCmd, genericcli.ListCmd, genericcli.DeleteCmd), + } + + return genericcli.NewCmds(cmdsConfig) +} + +func (c *adminSizeReservation) Get(id string) (*apiv2.SizeReservation, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &apiv2.SizeReservationServiceGetRequest{Id: id} + + resp, err := c.c.Client.Apiv2().SizeReservation().Get(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get size reservation: %w", err) + } + + return resp.SizeReservation, nil +} + +func (c *adminSizeReservation) List() ([]*apiv2.SizeReservation, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &adminv2.SizeReservationServiceListRequest{} + + resp, err := c.c.Client.Adminv2().SizeReservation().List(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to list size reservations: %w", err) + } + + return resp.SizeReservations, nil +} + +func (c *adminSizeReservation) Create(rq any) (*apiv2.SizeReservation, error) { + panic("unimplemented") +} + +func (c *adminSizeReservation) Delete(id string) (*apiv2.SizeReservation, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().SizeReservation().Delete(ctx, &adminv2.SizeReservationServiceDeleteRequest{Id: id}) + if err != nil { + return nil, fmt.Errorf("failed to delete size reservation: %w", err) + } + + return resp.SizeReservation, nil +} + +func (c *adminSizeReservation) Convert(r *apiv2.SizeReservation) (string, any, *adminv2.SizeReservationServiceUpdateRequest, error) { + return r.Id, nil, &adminv2.SizeReservationServiceUpdateRequest{ + Id: r.Id, + Name: &r.Name, + Description: &r.Description, + Partitions: r.Partitions, + Amount: &r.Amount, + }, nil +} + +func (c *adminSizeReservation) Update(rq *adminv2.SizeReservationServiceUpdateRequest) (*apiv2.SizeReservation, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().SizeReservation().Update(ctx, rq) + if err != nil { + return nil, fmt.Errorf("failed to update size reservation: %w", err) + } + + return resp.SizeReservation, nil +} diff --git a/cmd/admin/v2/token_create.go b/cmd/admin/v2/token_create.go new file mode 100644 index 0000000..902a9f7 --- /dev/null +++ b/cmd/admin/v2/token_create.go @@ -0,0 +1,62 @@ +package v2 + +import ( + "fmt" + "time" + + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/metal-lib/pkg/pointer" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "google.golang.org/protobuf/types/known/durationpb" +) + +type adminToken struct { + c *config.Config +} + +func newAdminTokenCreateCmd(c *config.Config) *cobra.Command { + w := &adminToken{ + c: c, + } + + createCmd := &cobra.Command{ + Use: "token-create", + Short: "create a token for any user (admin only)", + RunE: func(cmd *cobra.Command, args []string) error { + return w.create() + }, + } + + createCmd.Flags().String("user", "", "the user to create the token for") + createCmd.Flags().String("description", "", "a short description for the intention to use this token for") + createCmd.Flags().Duration("expires", 8*time.Hour, "the duration how long the api token is valid") + + return createCmd +} + +func (c *adminToken) create() error { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &adminv2.TokenServiceCreateRequest{ + User: pointer.PointerOrNil(viper.GetString("user")), + TokenCreateRequest: &apiv2.TokenServiceCreateRequest{ + Description: viper.GetString("description"), + Expires: durationpb.New(viper.GetDuration("expires")), + }, + } + + resp, err := c.c.Client.Adminv2().Token().Create(ctx, req) + if err != nil { + return fmt.Errorf("failed to create token: %w", err) + } + + _, _ = fmt.Fprintf(c.c.Out, "Make sure to copy your personal access token now as you will not be able to see this again.\n\n") + _, _ = fmt.Fprintln(c.c.Out, resp.GetSecret()) + _, _ = fmt.Fprintln(c.c.Out) + + return c.c.DescribePrinter.Print(resp.Token) +} diff --git a/cmd/admin/v2/vpn.go b/cmd/admin/v2/vpn.go new file mode 100644 index 0000000..b0a1809 --- /dev/null +++ b/cmd/admin/v2/vpn.go @@ -0,0 +1,87 @@ +package v2 + +import ( + "fmt" + + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "google.golang.org/protobuf/types/known/durationpb" +) + +type adminVPN struct { + c *config.Config +} + +func newAdminVPNCmd(c *config.Config) *cobra.Command { + w := &adminVPN{ + c: c, + } + + adminVPNCmd := &cobra.Command{ + Use: "vpn", + Short: "manage VPN", + } + + authKeyCmd := &cobra.Command{ + Use: "auth-key", + Short: "generate a VPN authentication key for a project", + RunE: func(cmd *cobra.Command, args []string) error { + return w.authKey() + }, + } + + authKeyCmd.Flags().String("project", "", "project for which to generate the VPN auth key") + genericcli.Must(authKeyCmd.MarkFlagRequired("project")) + authKeyCmd.Flags().Bool("ephemeral", false, "whether the auth key should be ephemeral") + authKeyCmd.Flags().Duration("expires", 0, "duration after which the auth key expires") + authKeyCmd.Flags().String("reason", "", "reason for requesting VPN access") + + listNodesCmd := &cobra.Command{ + Use: "list-nodes", + Short: "list VPN connected machines", + RunE: func(cmd *cobra.Command, args []string) error { + return w.listNodes() + }, + } + + adminVPNCmd.AddCommand(authKeyCmd, listNodesCmd) + + return adminVPNCmd +} + +func (c *adminVPN) authKey() error { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &adminv2.VPNServiceAuthKeyRequest{ + Project: viper.GetString("project"), + Ephemeral: viper.GetBool("ephemeral"), + Reason: viper.GetString("reason"), + } + + if viper.IsSet("expires") { + req.Expires = durationpb.New(viper.GetDuration("expires")) + } + + resp, err := c.c.Client.Adminv2().VPN().AuthKey(ctx, req) + if err != nil { + return fmt.Errorf("failed to generate VPN auth key: %w", err) + } + + return c.c.DescribePrinter.Print(resp) +} + +func (c *adminVPN) listNodes() error { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().VPN().ListNodes(ctx, &adminv2.VPNServiceListNodesRequest{}) + if err != nil { + return fmt.Errorf("failed to list VPN nodes: %w", err) + } + + return c.c.ListPrinter.Print(resp.Nodes) +} diff --git a/cmd/api/v2/commands.go b/cmd/api/v2/commands.go index 3f4278a..ca41ef7 100644 --- a/cmd/api/v2/commands.go +++ b/cmd/api/v2/commands.go @@ -7,12 +7,17 @@ import ( func AddCmds(cmd *cobra.Command, c *config.Config) { cmd.AddCommand(newAuditCmd(c)) + cmd.AddCommand(newFilesystemLayoutCmd(c)) cmd.AddCommand(newHealthCmd(c)) cmd.AddCommand(newImageCmd(c)) cmd.AddCommand(newIPCmd(c)) + cmd.AddCommand(newMachineCmd(c)) cmd.AddCommand(newMethodsCmd(c)) + cmd.AddCommand(newNetworkCmd(c)) + cmd.AddCommand(newPartitionCmd(c)) cmd.AddCommand(newProjectCmd(c)) cmd.AddCommand(newSizeCmd(c)) + cmd.AddCommand(newSizeReservationCmd(c)) cmd.AddCommand(newTenantCmd(c)) cmd.AddCommand(newTokenCmd(c)) cmd.AddCommand(newUserCmd(c)) diff --git a/cmd/api/v2/filesystem.go b/cmd/api/v2/filesystem.go new file mode 100644 index 0000000..5ce6465 --- /dev/null +++ b/cmd/api/v2/filesystem.go @@ -0,0 +1,81 @@ +package v2 + +import ( + "fmt" + + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/cli/cmd/sorters" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/spf13/cobra" +) + +type filesystemLayout struct { + c *config.Config +} + +func newFilesystemLayoutCmd(c *config.Config) *cobra.Command { + w := &filesystemLayout{ + c: c, + } + + cmdsConfig := &genericcli.CmdsConfig[any, any, *apiv2.FilesystemLayout]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), + Singular: "filesystem-layout", + Plural: "filesystem-layouts", + Description: "read filesystem layouts for machine disk partitioning", + Sorter: sorters.FilesystemLayoutSorter(), + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + ValidArgsFn: c.Completion.FilesystemLayoutListCompletion, + OnlyCmds: genericcli.OnlyCmds(genericcli.DescribeCmd, genericcli.ListCmd), + } + + return genericcli.NewCmds(cmdsConfig) +} + +func (c *filesystemLayout) Get(id string) (*apiv2.FilesystemLayout, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &apiv2.FilesystemServiceGetRequest{Id: id} + + resp, err := c.c.Client.Apiv2().Filesystem().Get(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get filesystem layout: %w", err) + } + + return resp.FilesystemLayout, nil +} + +func (c *filesystemLayout) List() ([]*apiv2.FilesystemLayout, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &apiv2.FilesystemServiceListRequest{} + + resp, err := c.c.Client.Apiv2().Filesystem().List(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to list filesystem layouts: %w", err) + } + + return resp.FilesystemLayouts, nil +} + +func (c *filesystemLayout) Create(rq any) (*apiv2.FilesystemLayout, error) { + panic("unimplemented") +} + +func (c *filesystemLayout) Delete(id string) (*apiv2.FilesystemLayout, error) { + panic("unimplemented") +} + +func (c *filesystemLayout) Convert(r *apiv2.FilesystemLayout) (string, any, any, error) { + panic("unimplemented") +} + +func (c *filesystemLayout) Update(rq any) (*apiv2.FilesystemLayout, error) { + panic("unimplemented") +} diff --git a/cmd/api/v2/machine.go b/cmd/api/v2/machine.go new file mode 100644 index 0000000..93e493f --- /dev/null +++ b/cmd/api/v2/machine.go @@ -0,0 +1,96 @@ +package v2 + +import ( + "fmt" + + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/cli/cmd/sorters" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/metal-stack/metal-lib/pkg/pointer" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +type machine struct { + c *config.Config +} + +func newMachineCmd(c *config.Config) *cobra.Command { + w := &machine{ + c: c, + } + + cmdsConfig := &genericcli.CmdsConfig[any, any, *apiv2.Machine]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), + Singular: "machine", + Plural: "machines", + Description: "read machines of the metal cloud", + Sorter: sorters.MachineSorter(), + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + ValidArgsFn: c.Completion.MachineListCompletion, + OnlyCmds: genericcli.OnlyCmds(genericcli.DescribeCmd, genericcli.ListCmd), + ListCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().StringP("project", "p", "", "project to list machines for") + cmd.Flags().StringP("partition", "", "", "partition to filter for") + cmd.Flags().StringP("size", "", "", "size to filter for") + }, + } + + return genericcli.NewCmds(cmdsConfig) +} + +func (c *machine) Get(id string) (*apiv2.Machine, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &apiv2.MachineServiceGetRequest{ + Uuid: id, + Project: c.c.GetProject(), + } + + resp, err := c.c.Client.Apiv2().Machine().Get(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get machine: %w", err) + } + + return resp.Machine, nil +} + +func (c *machine) List() ([]*apiv2.Machine, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &apiv2.MachineServiceListRequest{ + Query: &apiv2.MachineQuery{ + Partition: pointer.PointerOrNil(viper.GetString("partition")), + Size: pointer.PointerOrNil(viper.GetString("size")), + }, + } + + resp, err := c.c.Client.Apiv2().Machine().List(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to list machines: %w", err) + } + + return resp.Machines, nil +} + +func (c *machine) Create(rq any) (*apiv2.Machine, error) { + panic("unimplemented") +} + +func (c *machine) Delete(id string) (*apiv2.Machine, error) { + panic("unimplemented") +} + +func (c *machine) Convert(r *apiv2.Machine) (string, any, any, error) { + panic("unimplemented") +} + +func (c *machine) Update(rq any) (*apiv2.Machine, error) { + panic("unimplemented") +} diff --git a/cmd/api/v2/network.go b/cmd/api/v2/network.go new file mode 100644 index 0000000..8991c92 --- /dev/null +++ b/cmd/api/v2/network.go @@ -0,0 +1,89 @@ +package v2 + +import ( + "fmt" + + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/cli/cmd/sorters" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/spf13/cobra" +) + +type network struct { + c *config.Config +} + +func newNetworkCmd(c *config.Config) *cobra.Command { + w := &network{ + c: c, + } + + cmdsConfig := &genericcli.CmdsConfig[any, any, *apiv2.Network]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), + Singular: "network", + Plural: "networks", + Description: "read project networks", + Sorter: sorters.NetworkSorter(), + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + ValidArgsFn: c.Completion.NetworkListCompletion, + OnlyCmds: genericcli.OnlyCmds(genericcli.DescribeCmd, genericcli.ListCmd), + ListCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().StringP("project", "p", "", "project to list networks for") + }, + } + + return genericcli.NewCmds(cmdsConfig) +} + +func (c *network) Get(id string) (*apiv2.Network, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &apiv2.NetworkServiceGetRequest{ + Id: id, + Project: c.c.GetProject(), + } + + resp, err := c.c.Client.Apiv2().Network().Get(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get network: %w", err) + } + + return resp.Network, nil +} + +func (c *network) List() ([]*apiv2.Network, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &apiv2.NetworkServiceListRequest{ + Project: c.c.GetProject(), + } + + resp, err := c.c.Client.Apiv2().Network().List(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to list networks: %w", err) + } + + return resp.Networks, nil +} + +func (c *network) Create(rq any) (*apiv2.Network, error) { + panic("unimplemented") +} + +func (c *network) Delete(id string) (*apiv2.Network, error) { + panic("unimplemented") +} + +func (c *network) Convert(r *apiv2.Network) (string, any, any, error) { + panic("unimplemented") +} + +func (c *network) Update(rq any) (*apiv2.Network, error) { + panic("unimplemented") +} diff --git a/cmd/api/v2/partition.go b/cmd/api/v2/partition.go new file mode 100644 index 0000000..d84c88e --- /dev/null +++ b/cmd/api/v2/partition.go @@ -0,0 +1,88 @@ +package v2 + +import ( + "fmt" + + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/cli/cmd/sorters" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/metal-stack/metal-lib/pkg/pointer" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +type partition struct { + c *config.Config +} + +func newPartitionCmd(c *config.Config) *cobra.Command { + w := &partition{ + c: c, + } + + cmdsConfig := &genericcli.CmdsConfig[any, any, *apiv2.Partition]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), + Singular: "partition", + Plural: "partitions", + Description: "read partitions which represent a failure domain of the metal cloud", + Sorter: sorters.PartitionSorter(), + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + ValidArgsFn: c.Completion.PartitionListCompletion, + OnlyCmds: genericcli.OnlyCmds(genericcli.DescribeCmd, genericcli.ListCmd), + ListCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().StringP("id", "", "", "partition id to filter for") + }, + } + + return genericcli.NewCmds(cmdsConfig) +} + +func (c *partition) Get(id string) (*apiv2.Partition, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &apiv2.PartitionServiceGetRequest{Id: id} + + resp, err := c.c.Client.Apiv2().Partition().Get(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get partition: %w", err) + } + + return resp.Partition, nil +} + +func (c *partition) List() ([]*apiv2.Partition, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &apiv2.PartitionServiceListRequest{Query: &apiv2.PartitionQuery{ + Id: pointer.PointerOrNil(viper.GetString("id")), + }} + + resp, err := c.c.Client.Apiv2().Partition().List(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get partitions: %w", err) + } + + return resp.Partitions, nil +} + +func (c *partition) Create(rq any) (*apiv2.Partition, error) { + panic("unimplemented") +} + +func (c *partition) Delete(id string) (*apiv2.Partition, error) { + panic("unimplemented") +} + +func (c *partition) Convert(r *apiv2.Partition) (string, any, any, error) { + panic("unimplemented") +} + +func (c *partition) Update(rq any) (*apiv2.Partition, error) { + panic("unimplemented") +} diff --git a/cmd/api/v2/size_reservation.go b/cmd/api/v2/size_reservation.go new file mode 100644 index 0000000..8f4d01d --- /dev/null +++ b/cmd/api/v2/size_reservation.go @@ -0,0 +1,81 @@ +package v2 + +import ( + "fmt" + + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/cli/cmd/sorters" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/spf13/cobra" +) + +type sizeReservation struct { + c *config.Config +} + +func newSizeReservationCmd(c *config.Config) *cobra.Command { + w := &sizeReservation{ + c: c, + } + + cmdsConfig := &genericcli.CmdsConfig[any, any, *apiv2.SizeReservation]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), + Singular: "size-reservation", + Plural: "size-reservations", + Description: "read size reservations which allow to reserve machine capacity", + Sorter: sorters.SizeReservationSorter(), + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + ValidArgsFn: c.Completion.SizeReservationListCompletion, + OnlyCmds: genericcli.OnlyCmds(genericcli.DescribeCmd, genericcli.ListCmd), + } + + return genericcli.NewCmds(cmdsConfig) +} + +func (c *sizeReservation) Get(id string) (*apiv2.SizeReservation, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &apiv2.SizeReservationServiceGetRequest{Id: id} + + resp, err := c.c.Client.Apiv2().SizeReservation().Get(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get size reservation: %w", err) + } + + return resp.SizeReservation, nil +} + +func (c *sizeReservation) List() ([]*apiv2.SizeReservation, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &apiv2.SizeReservationServiceListRequest{} + + resp, err := c.c.Client.Apiv2().SizeReservation().List(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to list size reservations: %w", err) + } + + return resp.SizeReservations, nil +} + +func (c *sizeReservation) Create(rq any) (*apiv2.SizeReservation, error) { + panic("unimplemented") +} + +func (c *sizeReservation) Delete(id string) (*apiv2.SizeReservation, error) { + panic("unimplemented") +} + +func (c *sizeReservation) Convert(r *apiv2.SizeReservation) (string, any, any, error) { + panic("unimplemented") +} + +func (c *sizeReservation) Update(rq any) (*apiv2.SizeReservation, error) { + panic("unimplemented") +} diff --git a/cmd/completion/network.go b/cmd/completion/network.go new file mode 100644 index 0000000..1c69f30 --- /dev/null +++ b/cmd/completion/network.go @@ -0,0 +1,125 @@ +package completion + +import ( + "github.com/spf13/cobra" + + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" +) + +func (c *Completion) PartitionListCompletion(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 { + return nil, cobra.ShellCompDirectiveError + } + + var names []string + for _, s := range resp.GetPartitions() { + names = append(names, s.Id+"\t"+s.Description) + } + + return names, cobra.ShellCompDirectiveNoFileComp +} + +func (c *Completion) NetworkListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + req := &apiv2.NetworkServiceListRequest{} + resp, err := c.Client.Apiv2().Network().List(c.Ctx, req) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + var names []string + for _, s := range resp.GetNetworks() { + name := "" + if s.Name != nil { + name = *s.Name + } + names = append(names, s.Id+"\t"+name) + } + + return names, cobra.ShellCompDirectiveNoFileComp +} + +func (c *Completion) MachineListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + req := &apiv2.MachineServiceListRequest{} + resp, err := c.Client.Apiv2().Machine().List(c.Ctx, req) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + var names []string + for _, m := range resp.GetMachines() { + partition := "" + if m.Partition != nil { + partition = m.Partition.Id + } + names = append(names, m.Uuid+"\t"+partition) + } + + return names, cobra.ShellCompDirectiveNoFileComp +} + +func (c *Completion) FilesystemLayoutListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + req := &apiv2.FilesystemServiceListRequest{} + resp, err := c.Client.Apiv2().Filesystem().List(c.Ctx, req) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + var names []string + for _, s := range resp.GetFilesystemLayouts() { + name := "" + if s.Name != nil { + name = *s.Name + } + names = append(names, s.Id+"\t"+name) + } + + return names, cobra.ShellCompDirectiveNoFileComp +} + +func (c *Completion) SizeReservationListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + req := &apiv2.SizeReservationServiceListRequest{} + resp, err := c.Client.Apiv2().SizeReservation().List(c.Ctx, req) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + var names []string + for _, s := range resp.GetSizeReservations() { + names = append(names, s.Id+"\t"+s.Name) + } + + return names, cobra.ShellCompDirectiveNoFileComp +} + +func (c *Completion) AdminSizeReservationListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + req := &adminv2.SizeReservationServiceListRequest{} + resp, err := c.Client.Adminv2().SizeReservation().List(c.Ctx, req) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + var names []string + for _, s := range resp.GetSizeReservations() { + names = append(names, s.Id+"\t"+s.Name) + } + + return names, cobra.ShellCompDirectiveNoFileComp +} + +func (c *Completion) SizeImageConstraintListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + req := &adminv2.SizeImageConstraintServiceListRequest{} + resp, err := c.Client.Adminv2().SizeImageConstraint().List(c.Ctx, req) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + var names []string + for _, s := range resp.GetSizeImageConstraints() { + names = append(names, s.Size+"\t"+s.String()) + } + + return names, cobra.ShellCompDirectiveNoFileComp +} diff --git a/cmd/sorters/partition.go b/cmd/sorters/partition.go new file mode 100644 index 0000000..82a0f31 --- /dev/null +++ b/cmd/sorters/partition.go @@ -0,0 +1,113 @@ +package sorters + +import ( + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/metal-lib/pkg/multisort" +) + +func PartitionSorter() *multisort.Sorter[*apiv2.Partition] { + return multisort.New(multisort.FieldMap[*apiv2.Partition]{ + "id": func(a, b *apiv2.Partition, descending bool) multisort.CompareResult { + return multisort.Compare(a.Id, b.Id, descending) + }, + "description": func(a, b *apiv2.Partition, descending bool) multisort.CompareResult { + return multisort.Compare(a.Description, b.Description, descending) + }, + }, multisort.Keys{{ID: "id"}}) +} + +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(*a.Name, *b.Name, descending) + }, + "type": func(a, b *apiv2.Network, descending bool) multisort.CompareResult { + return multisort.Compare(a.Type, b.Type, descending) + }, + }, multisort.Keys{{ID: "id"}}) +} + +func MachineSorter() *multisort.Sorter[*apiv2.Machine] { + return multisort.New(multisort.FieldMap[*apiv2.Machine]{ + "id": func(a, b *apiv2.Machine, descending bool) multisort.CompareResult { + return multisort.Compare(a.Uuid, b.Uuid, descending) + }, + "partition": func(a, b *apiv2.Machine, descending bool) multisort.CompareResult { + ap := "" + if a.Partition != nil { + ap = a.Partition.Id + } + bp := "" + if b.Partition != nil { + bp = b.Partition.Id + } + return multisort.Compare(ap, bp, descending) + }, + "liveliness": func(a, b *apiv2.Machine, descending bool) multisort.CompareResult { + al := int32(0) + bl := int32(0) + if a.Status != nil { + al = int32(a.Status.Liveliness) + } + if b.Status != nil { + bl = int32(b.Status.Liveliness) + } + return multisort.Compare(al, bl, descending) + }, + }, multisort.Keys{{ID: "id"}}) +} + +func FilesystemLayoutSorter() *multisort.Sorter[*apiv2.FilesystemLayout] { + return multisort.New(multisort.FieldMap[*apiv2.FilesystemLayout]{ + "id": func(a, b *apiv2.FilesystemLayout, descending bool) multisort.CompareResult { + return multisort.Compare(a.Id, b.Id, descending) + }, + }, multisort.Keys{{ID: "id"}}) +} + +func SizeReservationSorter() *multisort.Sorter[*apiv2.SizeReservation] { + return multisort.New(multisort.FieldMap[*apiv2.SizeReservation]{ + "id": func(a, b *apiv2.SizeReservation, descending bool) multisort.CompareResult { + return multisort.Compare(a.Id, b.Id, descending) + }, + "name": func(a, b *apiv2.SizeReservation, descending bool) multisort.CompareResult { + return multisort.Compare(a.Name, b.Name, descending) + }, + "size": func(a, b *apiv2.SizeReservation, descending bool) multisort.CompareResult { + return multisort.Compare(a.Size, b.Size, descending) + }, + }, multisort.Keys{{ID: "id"}}) +} + +func SizeImageConstraintSorter() *multisort.Sorter[*apiv2.SizeImageConstraint] { + return multisort.New(multisort.FieldMap[*apiv2.SizeImageConstraint]{ + "size": func(a, b *apiv2.SizeImageConstraint, descending bool) multisort.CompareResult { + return multisort.Compare(a.Size, b.Size, descending) + }, + }, multisort.Keys{{ID: "size"}}) +} + +func VpnNodeSorter() *multisort.Sorter[*apiv2.VPNNode] { + return multisort.New(multisort.FieldMap[*apiv2.VPNNode]{ + "name": func(a, b *apiv2.VPNNode, descending bool) multisort.CompareResult { + return multisort.Compare(a.Name, b.Name, descending) + }, + "project": func(a, b *apiv2.VPNNode, descending bool) multisort.CompareResult { + return multisort.Compare(a.Project, b.Project, descending) + }, + "online": func(a, b *apiv2.VPNNode, descending bool) multisort.CompareResult { + ai := int32(0) + if a.Online { + ai = 1 + } + bi := int32(0) + if b.Online { + bi = 1 + } + return multisort.Compare(ai, bi, descending) + }, + }, multisort.Keys{{ID: "name"}}) +} diff --git a/cmd/tableprinters/common.go b/cmd/tableprinters/common.go index ead65ec..0d7c21f 100644 --- a/cmd/tableprinters/common.go +++ b/cmd/tableprinters/common.go @@ -113,6 +113,56 @@ func (t *TablePrinter) ToHeaderAndRows(data any, wide bool) ([]string, [][]strin case []*apiv2.Health: return t.HealthTable(d, wide) + case *apiv2.Machine: + return t.MachineTable(pointer.WrapInSlice(d), wide) + case []*apiv2.Machine: + return t.MachineTable(d, wide) + + case *apiv2.Network: + return t.NetworkTable(pointer.WrapInSlice(d), wide) + case []*apiv2.Network: + return t.NetworkTable(d, wide) + + case *apiv2.Partition: + return t.PartitionTable(pointer.WrapInSlice(d), wide) + case []*apiv2.Partition: + return t.PartitionTable(d, wide) + + case *apiv2.SizeImageConstraint: + return t.SizeImageConstraintTable(pointer.WrapInSlice(d), wide) + case []*apiv2.SizeImageConstraint: + return t.SizeImageConstraintTable(d, wide) + + case *apiv2.SizeReservation: + return t.SizeReservationTable(pointer.WrapInSlice(d), wide) + case []*apiv2.SizeReservation: + return t.SizeReservationTable(d, wide) + + case *apiv2.FilesystemLayout: + return t.FilesystemLayoutTable(pointer.WrapInSlice(d), wide) + case []*apiv2.FilesystemLayout: + return t.FilesystemLayoutTable(d, wide) + + case *apiv2.VPNNode: + return t.VPNNodeTable(pointer.WrapInSlice(d), wide) + case []*apiv2.VPNNode: + return t.VPNNodeTable(d, wide) + + case *apiv2.ImageUsage: + return t.ImageUsageTable(pointer.WrapInSlice(d), wide) + case []*apiv2.ImageUsage: + return t.ImageUsageTable(d, wide) + + case *adminv2.VPNServiceAuthKeyResponse: + return t.VPNServiceAuthKeyResponseTable(pointer.WrapInSlice(d), wide) + case []*adminv2.VPNServiceAuthKeyResponse: + return t.VPNServiceAuthKeyResponseTable(d, wide) + + case *adminv2.PartitionCapacity: + return t.PartitionCapacityTable(pointer.WrapInSlice(d), wide) + case []*adminv2.PartitionCapacity: + return t.PartitionCapacityTable(d, wide) + case *apiv2.Size: return t.SizeTable(pointer.WrapInSlice(d), wide) case []*apiv2.Size: diff --git a/cmd/tableprinters/filesystem.go b/cmd/tableprinters/filesystem.go new file mode 100644 index 0000000..2c4b3b5 --- /dev/null +++ b/cmd/tableprinters/filesystem.go @@ -0,0 +1,99 @@ +package tableprinters + +import ( + "time" + + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" +) + +func (t *TablePrinter) FilesystemLayoutTable(data []*apiv2.FilesystemLayout, wide bool) ([]string, [][]string, error) { + var ( + rows [][]string + header = []string{"ID", "Name", "Description"} + ) + + for _, f := range data { + name := "" + if f.Name != nil { + name = *f.Name + } + desc := "" + if f.Description != nil { + desc = *f.Description + } + + rows = append(rows, []string{f.Id, name, desc}) + } + + t.t.DisableAutoWrap(false) + + return header, rows, nil +} + +func (t *TablePrinter) SizeReservationTable(data []*apiv2.SizeReservation, wide bool) ([]string, [][]string, error) { + var ( + rows [][]string + header = []string{"ID", "Name", "Size", "Project", "Amount", "Partitions"} + ) + + for _, r := range data { + rows = append(rows, []string{r.Id, r.Name, r.Size, r.Project, formatInt32(r.Amount), joinOrEmpty(r.Partitions)}) + } + + t.t.DisableAutoWrap(false) + + return header, rows, nil +} + +func (t *TablePrinter) SizeImageConstraintTable(data []*apiv2.SizeImageConstraint, wide bool) ([]string, [][]string, error) { + var ( + rows [][]string + header = []string{"Size", "Name", "Description", "Image Constraints"} + ) + + for _, c := range data { + name := "" + if c.Name != nil { + name = *c.Name + } + desc := "" + if c.Description != nil { + desc = *c.Description + } + + constraints := []string{} + for _, ic := range c.ImageConstraints { + constraints = append(constraints, ic.String()) + } + + rows = append(rows, []string{c.Size, name, desc, joinOrEmpty(constraints)}) + } + + t.t.DisableAutoWrap(false) + + return header, rows, nil +} + +func (t *TablePrinter) VPNNodeTable(data []*apiv2.VPNNode, wide bool) ([]string, [][]string, error) { + var ( + rows [][]string + header = []string{"ID", "Name", "Project", "Online", "Last Seen", "IPs"} + ) + + for _, n := range data { + online := "no" + if n.Online { + online = "yes" + } + lastSeen := "" + if n.LastSeen != nil { + lastSeen = humanizeDuration(time.Since(n.LastSeen.AsTime())) + " ago" + } + + rows = append(rows, []string{formatUint64(n.Id), n.Name, n.Project, online, lastSeen, joinOrEmpty(n.IpAddresses)}) + } + + t.t.DisableAutoWrap(false) + + return header, rows, nil +} diff --git a/cmd/tableprinters/machine.go b/cmd/tableprinters/machine.go new file mode 100644 index 0000000..bd91a15 --- /dev/null +++ b/cmd/tableprinters/machine.go @@ -0,0 +1,59 @@ +package tableprinters + +import ( + "time" + + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" +) + +func (t *TablePrinter) MachineTable(data []*apiv2.Machine, wide bool) ([]string, [][]string, error) { + var ( + rows [][]string + header = []string{"ID", "Partition", "Size", "Hostname", "Liveliness"} + ) + + if wide { + header = []string{"ID", "Partition", "Size", "Hostname", "Project", "Liveliness", "State", "Created"} + } + + for _, m := range data { + size := "" + if m.Size != nil { + size = m.Size.Id + } + hostname := "" + if m.Allocation != nil { + hostname = m.Allocation.Hostname + } + liveliness := "" + state := "" + if m.Status != nil { + liveliness = m.Status.Liveliness.String() + if m.Status.Condition != nil { + state = m.Status.Condition.State.String() + } + } + partition := "" + if m.Partition != nil { + partition = m.Partition.Id + } + project := "" + if m.Allocation != nil { + project = m.Allocation.Project + } + created := "" + if m.Meta != nil { + created = humanizeDuration(time.Since(m.Meta.CreatedAt.AsTime())) + " ago" + } + + if wide { + rows = append(rows, []string{m.Uuid, partition, size, hostname, project, liveliness, state, created}) + } else { + rows = append(rows, []string{m.Uuid, partition, size, hostname, liveliness}) + } + } + + t.t.DisableAutoWrap(false) + + return header, rows, nil +} diff --git a/cmd/tableprinters/network.go b/cmd/tableprinters/network.go new file mode 100644 index 0000000..78d8699 --- /dev/null +++ b/cmd/tableprinters/network.go @@ -0,0 +1,50 @@ +package tableprinters + +import ( + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" +) + +func (t *TablePrinter) NetworkTable(data []*apiv2.Network, wide bool) ([]string, [][]string, error) { + var ( + rows [][]string + header = []string{"ID", "Name", "Type", "Partition", "Project", "Prefixes"} + ) + + if wide { + header = []string{"ID", "Name", "Type", "Partition", "Project", "Prefixes", "Dest. Prefixes", "VRF"} + } + + for _, n := range data { + name := "" + if n.Name != nil { + name = *n.Name + } + partition := "" + if n.Partition != nil { + partition = *n.Partition + } + project := "" + if n.Project != nil { + project = *n.Project + } + vrf := "" + if n.Vrf != nil { + vrf = formatUint32(*n.Vrf) + } + + prefixes := n.Prefixes + if len(prefixes) > 3 { + prefixes = append(prefixes[:3], "...") + } + + if wide { + rows = append(rows, []string{n.Id, name, n.Type.String(), partition, project, joinOrEmpty(n.Prefixes), joinOrEmpty(n.DestinationPrefixes), vrf}) + } else { + rows = append(rows, []string{n.Id, name, n.Type.String(), partition, project, joinOrEmpty(prefixes)}) + } + } + + t.t.DisableAutoWrap(false) + + return header, rows, nil +} diff --git a/cmd/tableprinters/partition.go b/cmd/tableprinters/partition.go new file mode 100644 index 0000000..7f1bae1 --- /dev/null +++ b/cmd/tableprinters/partition.go @@ -0,0 +1,51 @@ +package tableprinters + +import ( + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" +) + +func (t *TablePrinter) PartitionTable(data []*apiv2.Partition, wide bool) ([]string, [][]string, error) { + var ( + rows [][]string + header = []string{"ID", "Description", "Mgmt Service Address"} + ) + + for _, p := range data { + mgmtAddress := "" + if len(p.MgmtServiceAddresses) > 0 { + mgmtAddress = p.MgmtServiceAddresses[0] + } + + rows = append(rows, []string{p.Id, p.Description, mgmtAddress}) + } + + t.t.DisableAutoWrap(false) + + return header, rows, nil +} + +func (t *TablePrinter) PartitionCapacityTable(data []*adminv2.PartitionCapacity, wide bool) ([]string, [][]string, error) { + var ( + rows [][]string + header = []string{"Partition", "Size", "Free", "Total", "Allocated", "Faulty", "Reserved"} + ) + + for _, pc := range data { + for _, sc := range pc.MachineSizeCapacities { + rows = append(rows, []string{ + pc.Partition, + sc.Size, + formatInt64(sc.Free), + formatInt64(sc.Total), + formatInt64(sc.Allocated), + formatInt64(sc.Faulty), + formatInt64(sc.Other), + }) + } + } + + t.t.DisableAutoWrap(false) + + return header, rows, nil +} diff --git a/cmd/tableprinters/vpn.go b/cmd/tableprinters/vpn.go new file mode 100644 index 0000000..879641a --- /dev/null +++ b/cmd/tableprinters/vpn.go @@ -0,0 +1,77 @@ +package tableprinters + +import ( + "fmt" + "strconv" + "strings" + "time" + + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" +) + +func (t *TablePrinter) VPNServiceAuthKeyResponseTable(data []*adminv2.VPNServiceAuthKeyResponse, wide bool) ([]string, [][]string, error) { + var ( + rows [][]string + header = []string{"Address", "AuthKey", "Ephemeral", "Expires At", "Created At"} + ) + + for _, r := range data { + expiresAt := "" + if r.ExpiresAt != nil { + expiresAt = r.ExpiresAt.AsTime().Format(time.RFC3339) + } + createdAt := "" + if r.CreatedAt != nil { + createdAt = r.CreatedAt.AsTime().Format(time.RFC3339) + } + + rows = append(rows, []string{r.Address, r.AuthKey, fmt.Sprintf("%v", r.Ephemeral), expiresAt, createdAt}) + } + + t.t.DisableAutoWrap(false) + + return header, rows, nil +} + +func (t *TablePrinter) ImageUsageTable(data []*apiv2.ImageUsage, wide bool) ([]string, [][]string, error) { + var ( + rows [][]string + header = []string{"Image", "Used By"} + ) + + for _, u := range data { + imageID := "" + if u.Image != nil { + imageID = u.Image.Id + } + rows = append(rows, []string{imageID, joinOrEmpty(u.UsedBy)}) + } + + t.t.DisableAutoWrap(false) + + return header, rows, nil +} + +func formatInt64(v int64) string { + return strconv.FormatInt(v, 10) +} + +func formatInt32(v int32) string { + return strconv.FormatInt(int64(v), 10) +} + +func formatUint32(v uint32) string { + return strconv.FormatUint(uint64(v), 10) +} + +func formatUint64(v uint64) string { + return strconv.FormatUint(v, 10) +} + +func joinOrEmpty(items []string) string { + if len(items) == 0 { + return "" + } + return strings.Join(items, ", ") +} diff --git a/docs/metalctlv2.md b/docs/metalctlv2.md index df68a11..616d739 100644 --- a/docs/metalctlv2.md +++ b/docs/metalctlv2.md @@ -22,14 +22,19 @@ cli for managing entities in metal-stack * [metalctlv2 audit](metalctlv2_audit.md) - manage audit entities * [metalctlv2 completion](metalctlv2_completion.md) - Generate the autocompletion script for the specified shell * [metalctlv2 context](metalctlv2_context.md) - manage cli contexts +* [metalctlv2 filesystem-layout](metalctlv2_filesystem-layout.md) - manage filesystem-layout entities * [metalctlv2 health](metalctlv2_health.md) - print the client and server health information * [metalctlv2 image](metalctlv2_image.md) - manage image entities * [metalctlv2 ip](metalctlv2_ip.md) - manage ip entities * [metalctlv2 login](metalctlv2_login.md) - login * [metalctlv2 logout](metalctlv2_logout.md) - logout +* [metalctlv2 machine](metalctlv2_machine.md) - manage machine entities * [metalctlv2 markdown](metalctlv2_markdown.md) - create markdown documentation +* [metalctlv2 network](metalctlv2_network.md) - manage network entities +* [metalctlv2 partition](metalctlv2_partition.md) - manage partition entities * [metalctlv2 project](metalctlv2_project.md) - manage project entities * [metalctlv2 size](metalctlv2_size.md) - manage size entities +* [metalctlv2 size-reservation](metalctlv2_size-reservation.md) - manage size-reservation entities * [metalctlv2 tenant](metalctlv2_tenant.md) - manage tenant entities * [metalctlv2 token](metalctlv2_token.md) - manage token entities * [metalctlv2 user](metalctlv2_user.md) - manage user entities diff --git a/docs/metalctlv2_filesystem-layout.md b/docs/metalctlv2_filesystem-layout.md new file mode 100644 index 0000000..66bf30e --- /dev/null +++ b/docs/metalctlv2_filesystem-layout.md @@ -0,0 +1,33 @@ +## metalctlv2 filesystem-layout + +manage filesystem-layout entities + +### Synopsis + +read filesystem layouts for machine disk partitioning + +### Options + +``` + -h, --help help for filesystem-layout +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact (for apply, create, update, delete commands from file the raw output formatters must be used). (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 filesystem-layout describe](metalctlv2_filesystem-layout_describe.md) - describes the filesystem-layout +* [metalctlv2 filesystem-layout list](metalctlv2_filesystem-layout_list.md) - list all filesystem-layouts + diff --git a/docs/metalctlv2_filesystem-layout_describe.md b/docs/metalctlv2_filesystem-layout_describe.md new file mode 100644 index 0000000..a4f86e2 --- /dev/null +++ b/docs/metalctlv2_filesystem-layout_describe.md @@ -0,0 +1,31 @@ +## metalctlv2 filesystem-layout describe + +describes the filesystem-layout + +``` +metalctlv2 filesystem-layout describe [flags] +``` + +### Options + +``` + -h, --help help for describe +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact (for apply, create, update, delete commands from file the raw output formatters must be used). (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 filesystem-layout](metalctlv2_filesystem-layout.md) - manage filesystem-layout entities + diff --git a/docs/metalctlv2_filesystem-layout_list.md b/docs/metalctlv2_filesystem-layout_list.md new file mode 100644 index 0000000..b162066 --- /dev/null +++ b/docs/metalctlv2_filesystem-layout_list.md @@ -0,0 +1,32 @@ +## metalctlv2 filesystem-layout list + +list all filesystem-layouts + +``` +metalctlv2 filesystem-layout list [flags] +``` + +### Options + +``` + -h, --help help for list + --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: id +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact (for apply, create, update, delete commands from file the raw output formatters must be used). (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 filesystem-layout](metalctlv2_filesystem-layout.md) - manage filesystem-layout entities + diff --git a/docs/metalctlv2_machine.md b/docs/metalctlv2_machine.md new file mode 100644 index 0000000..7dc3f81 --- /dev/null +++ b/docs/metalctlv2_machine.md @@ -0,0 +1,33 @@ +## metalctlv2 machine + +manage machine entities + +### Synopsis + +read machines of the metal cloud + +### Options + +``` + -h, --help help for machine +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact (for apply, create, update, delete commands from file the raw output formatters must be used). (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2](metalctlv2.md) - cli for managing entities in metal-stack +* [metalctlv2 machine describe](metalctlv2_machine_describe.md) - describes the machine +* [metalctlv2 machine list](metalctlv2_machine_list.md) - list all machines + diff --git a/docs/metalctlv2_machine_describe.md b/docs/metalctlv2_machine_describe.md new file mode 100644 index 0000000..c9c400f --- /dev/null +++ b/docs/metalctlv2_machine_describe.md @@ -0,0 +1,31 @@ +## metalctlv2 machine describe + +describes the machine + +``` +metalctlv2 machine describe [flags] +``` + +### Options + +``` + -h, --help help for describe +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact (for apply, create, update, delete commands from file the raw output formatters must be used). (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 machine](metalctlv2_machine.md) - manage machine entities + diff --git a/docs/metalctlv2_machine_list.md b/docs/metalctlv2_machine_list.md new file mode 100644 index 0000000..fad0cad --- /dev/null +++ b/docs/metalctlv2_machine_list.md @@ -0,0 +1,35 @@ +## metalctlv2 machine list + +list all machines + +``` +metalctlv2 machine list [flags] +``` + +### Options + +``` + -h, --help help for list + --partition string partition to filter for + -p, --project string project to list machines for + --size string size to filter for + --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: id|liveliness|partition +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact (for apply, create, update, delete commands from file the raw output formatters must be used). (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 machine](metalctlv2_machine.md) - manage machine entities + diff --git a/docs/metalctlv2_network.md b/docs/metalctlv2_network.md new file mode 100644 index 0000000..6d5fe4e --- /dev/null +++ b/docs/metalctlv2_network.md @@ -0,0 +1,33 @@ +## metalctlv2 network + +manage network entities + +### Synopsis + +read project networks + +### Options + +``` + -h, --help help for network +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact (for apply, create, update, delete commands from file the raw output formatters must be used). (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 describe](metalctlv2_network_describe.md) - describes the network +* [metalctlv2 network list](metalctlv2_network_list.md) - list all networks + diff --git a/docs/metalctlv2_network_describe.md b/docs/metalctlv2_network_describe.md new file mode 100644 index 0000000..a1e49e1 --- /dev/null +++ b/docs/metalctlv2_network_describe.md @@ -0,0 +1,31 @@ +## metalctlv2 network describe + +describes the network + +``` +metalctlv2 network describe [flags] +``` + +### Options + +``` + -h, --help help for describe +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact (for apply, create, update, delete commands from file the raw output formatters must be used). (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..9298bf2 --- /dev/null +++ b/docs/metalctlv2_network_list.md @@ -0,0 +1,33 @@ +## metalctlv2 network list + +list all networks + +``` +metalctlv2 network list [flags] +``` + +### Options + +``` + -h, --help help for list + -p, --project string project to list networks for + --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: id|name|type +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact (for apply, create, update, delete commands from file the raw output formatters must be used). (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_partition.md b/docs/metalctlv2_partition.md new file mode 100644 index 0000000..33492d2 --- /dev/null +++ b/docs/metalctlv2_partition.md @@ -0,0 +1,33 @@ +## metalctlv2 partition + +manage partition entities + +### Synopsis + +read partitions which represent a failure domain of the metal cloud + +### Options + +``` + -h, --help help for partition +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact (for apply, create, update, delete commands from file the raw output formatters must be used). (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 partition describe](metalctlv2_partition_describe.md) - describes the partition +* [metalctlv2 partition list](metalctlv2_partition_list.md) - list all partitions + diff --git a/docs/metalctlv2_partition_describe.md b/docs/metalctlv2_partition_describe.md new file mode 100644 index 0000000..1d694d1 --- /dev/null +++ b/docs/metalctlv2_partition_describe.md @@ -0,0 +1,31 @@ +## metalctlv2 partition describe + +describes the partition + +``` +metalctlv2 partition describe [flags] +``` + +### Options + +``` + -h, --help help for describe +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact (for apply, create, update, delete commands from file the raw output formatters must be used). (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 partition](metalctlv2_partition.md) - manage partition entities + diff --git a/docs/metalctlv2_partition_list.md b/docs/metalctlv2_partition_list.md new file mode 100644 index 0000000..70e4179 --- /dev/null +++ b/docs/metalctlv2_partition_list.md @@ -0,0 +1,33 @@ +## metalctlv2 partition list + +list all partitions + +``` +metalctlv2 partition list [flags] +``` + +### Options + +``` + -h, --help help for list + --id string partition id to filter for + --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 +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact (for apply, create, update, delete commands from file the raw output formatters must be used). (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 partition](metalctlv2_partition.md) - manage partition entities + diff --git a/docs/metalctlv2_size-reservation.md b/docs/metalctlv2_size-reservation.md new file mode 100644 index 0000000..faa4053 --- /dev/null +++ b/docs/metalctlv2_size-reservation.md @@ -0,0 +1,33 @@ +## metalctlv2 size-reservation + +manage size-reservation entities + +### Synopsis + +read size reservations which allow to reserve machine capacity + +### Options + +``` + -h, --help help for size-reservation +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact (for apply, create, update, delete commands from file the raw output formatters must be used). (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2](metalctlv2.md) - cli for managing entities in metal-stack +* [metalctlv2 size-reservation describe](metalctlv2_size-reservation_describe.md) - describes the size-reservation +* [metalctlv2 size-reservation list](metalctlv2_size-reservation_list.md) - list all size-reservations + diff --git a/docs/metalctlv2_size-reservation_describe.md b/docs/metalctlv2_size-reservation_describe.md new file mode 100644 index 0000000..905045d --- /dev/null +++ b/docs/metalctlv2_size-reservation_describe.md @@ -0,0 +1,31 @@ +## metalctlv2 size-reservation describe + +describes the size-reservation + +``` +metalctlv2 size-reservation describe [flags] +``` + +### Options + +``` + -h, --help help for describe +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact (for apply, create, update, delete commands from file the raw output formatters must be used). (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 size-reservation](metalctlv2_size-reservation.md) - manage size-reservation entities + diff --git a/docs/metalctlv2_size-reservation_list.md b/docs/metalctlv2_size-reservation_list.md new file mode 100644 index 0000000..8b594b6 --- /dev/null +++ b/docs/metalctlv2_size-reservation_list.md @@ -0,0 +1,32 @@ +## metalctlv2 size-reservation list + +list all size-reservations + +``` +metalctlv2 size-reservation list [flags] +``` + +### Options + +``` + -h, --help help for list + --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: id|name|size +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact (for apply, create, update, delete commands from file the raw output formatters must be used). (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 size-reservation](metalctlv2_size-reservation.md) - manage size-reservation entities + diff --git a/go.mod b/go.mod index 61b8dd4..9db2ddb 100644 --- a/go.mod +++ b/go.mod @@ -9,51 +9,51 @@ require ( github.com/fatih/color v1.19.0 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 - github.com/metal-stack/api v0.2.3 + github.com/metal-stack/api v0.3.1 github.com/metal-stack/metal-lib v0.25.2 github.com/metal-stack/v v1.0.3 github.com/spf13/afero v1.15.0 github.com/spf13/cobra v1.10.2 github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 - google.golang.org/grpc v1.81.1 + google.golang.org/grpc v1.82.1 google.golang.org/protobuf v1.36.11 sigs.k8s.io/yaml v1.6.0 ) require ( - buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260415201107-50325440f8f2.1 // indirect + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260709200747-435963d16310.1 // indirect buf.build/go/protovalidate v1.2.0 // indirect buf.build/go/protoyaml v0.7.0 // indirect cel.dev/expr v0.25.2 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/clipperhouse/displaywidth v0.10.0 // indirect - github.com/clipperhouse/uax29/v2 v2.6.0 // indirect + github.com/clipperhouse/displaywidth v0.11.0 // indirect + github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.10.1 // indirect github.com/go-openapi/errors v0.22.8 // indirect - github.com/go-openapi/strfmt v0.26.3 // indirect + github.com/go-openapi/strfmt v0.27.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/goccy/go-json v0.10.6 // indirect github.com/goccy/go-yaml v1.19.2 // indirect github.com/golang-jwt/jwt/v5 v5.3.1 // indirect - github.com/google/cel-go v0.28.1 // indirect + github.com/google/cel-go v0.29.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/klauspost/compress v1.18.6 // indirect + github.com/klauspost/compress v1.19.0 // indirect github.com/klauspost/connect-compress/v2 v2.1.1 // indirect github.com/mattn/go-colorable v0.1.15 // indirect - github.com/mattn/go-isatty v0.0.22 // indirect + github.com/mattn/go-isatty v0.0.23 // indirect github.com/mattn/go-runewidth v0.0.24 // indirect - github.com/minio/minlz v1.1.1 // indirect + github.com/minio/minlz v1.2.0 // indirect github.com/oklog/ulid/v2 v2.1.1 // indirect github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect github.com/olekukonko/errors v1.3.0 // indirect github.com/olekukonko/ll v0.1.8 // indirect github.com/olekukonko/tablewriter v1.1.4 // indirect - github.com/pelletier/go-toml/v2 v2.4.2 // indirect + github.com/pelletier/go-toml/v2 v2.4.3 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.12.0 // indirect @@ -63,12 +63,12 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/exp v0.0.0-20260611194520-c48552f49976 // indirect - golang.org/x/net v0.56.0 // indirect - golang.org/x/sys v0.46.0 // indirect - golang.org/x/text v0.38.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260622175928-b703f567277d // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260622175928-b703f567277d // indirect + golang.org/x/exp v0.0.0-20260718201538-764159d718ef // indirect + golang.org/x/net v0.57.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/text v0.40.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260715232425-e75dac1f907d // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260715232425-e75dac1f907d // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apimachinery v0.35.1 // indirect diff --git a/go.sum b/go.sum index b3f02bf..91a73d2 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260415201107-50325440f8f2.1 h1:s6hzCXtND/ICdGPTMGk7C+/BFlr2Jg5GyH0NKf4XGXg= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260415201107-50325440f8f2.1/go.mod h1:tvtbpgaVXZX4g6Pn+AnzFycuRK3MOz5HJfEGeEllXYM= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260709200747-435963d16310.1 h1:fXh8CsdNpjRr8R5vFdqtIxPt/Lno2IIJlYOdZBIZn0w= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260709200747-435963d16310.1/go.mod h1:tvtbpgaVXZX4g6Pn+AnzFycuRK3MOz5HJfEGeEllXYM= buf.build/go/protovalidate v1.2.0 h1:DQVrUWkmGTBij+kOYv/x2LLxwcLaGKMdzShj1/6/3H0= buf.build/go/protovalidate v1.2.0/go.mod h1:7rYiQEhqvAipoazpVNBBH2S2f8bjG4huMVy1V2Yofn4= buf.build/go/protoyaml v0.7.0 h1:z4oVoFicbpPefhT7WAykxUdfp0yEQlhMQ2mCZOY5V38= @@ -16,10 +16,10 @@ github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/clipperhouse/displaywidth v0.10.0 h1:GhBG8WuerxjFQQYeuZAeVTuyxuX+UraiZGD4HJQ3Y8g= -github.com/clipperhouse/displaywidth v0.10.0/go.mod h1:XqJajYsaiEwkxOj4bowCTMcT1SgvHo9flfF3jQasdbs= -github.com/clipperhouse/uax29/v2 v2.6.0 h1:z0cDbUV+aPASdFb2/ndFnS9ts/WNXgTNNGFoKXuhpos= -github.com/clipperhouse/uax29/v2 v2.6.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= +github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8= +github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= +github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= +github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -35,10 +35,10 @@ github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx5 github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= github.com/go-openapi/errors v0.22.8 h1:oP7sW7TWc3wFFjrzzj0nI83H2qMBkNjNfSd+XRejk/I= github.com/go-openapi/errors v0.22.8/go.mod h1:BuUoHcYrU6E7V9gfj1I5wLQqgtIHnup/alXZ8KdgQ0w= -github.com/go-openapi/strfmt v0.26.3 h1:rzmslHarJgBbf2qfGge+X3htclQfmXqBZMm0Too0HhU= -github.com/go-openapi/strfmt v0.26.3/go.mod h1:a5nsUw0oRpQzZeOwx8bi6cKbzFZslpbCKt1LEot+KnQ= -github.com/go-openapi/testify/v2 v2.5.1 h1:TMdhCaw8fUNraVSf3Omoob1dO/AzBfhtFAPW0an6sBo= -github.com/go-openapi/testify/v2 v2.5.1/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= +github.com/go-openapi/strfmt v0.27.0 h1:kbcTeaD9TXuXD0hhMXzuYa1sdTo6+dWGvwjW93E80IM= +github.com/go-openapi/strfmt v0.27.0/go.mod h1:s/qhDqfY72irigXUGJmtgid2Rm+3tnz3k8hZaRmvWYc= +github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug= +github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= @@ -49,16 +49,16 @@ github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= -github.com/google/cel-go v0.28.1 h1:YWIwi77J4xIsYUwAF/iIuS6haffzIHS8yWI8glSbLWM= -github.com/google/cel-go v0.28.1/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= +github.com/google/cel-go v0.29.2 h1:ZtDxkeiMmz0mxbKDYiNkE5Lk7V5edMRcaaDf2jX002k= +github.com/google/cel-go v0.29.2/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= -github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/compress v1.19.0 h1:sXLILfc9jV2QYWkzFOPWStmcUVH2RHEB1JCdY2oVvCQ= +github.com/klauspost/compress v1.19.0/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/klauspost/connect-compress/v2 v2.1.1 h1:ycZNp4rWOZBodVE2Ls5AzK4aHkyK+GteEfzRZgKNs+c= github.com/klauspost/connect-compress/v2 v2.1.1/go.mod h1:9oilsPHJMzGKkjafSBk9J7iVo4mO+dw0G0KSdVpnlVE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -67,18 +67,18 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY= github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= -github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= +github.com/mattn/go-isatty v0.0.23 h1:cYwCQTQf3HB6xUC+BtyCLZNr7IzbOmoZbmssVNzSyiQ= +github.com/mattn/go-isatty v0.0.23/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU= github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= -github.com/metal-stack/api v0.2.3 h1:+WxXfcsd3R9Hr8BzOa0ybXv8rlarDNJDSlVgetwAYns= -github.com/metal-stack/api v0.2.3/go.mod h1:+WrGqA7QpQ2O60vakm3tjDbo70Q4c92pdco4R5MRcL8= +github.com/metal-stack/api v0.3.1 h1:jdhc82XitGLy//J1yaFHbl4u/ltekuhu7gbhbsryecQ= +github.com/metal-stack/api v0.3.1/go.mod h1:ZlpPEGuUQ/cmD8GZ6o7bgerihYz4/A9KVXUwqvK9ojw= github.com/metal-stack/metal-lib v0.25.2 h1:OW8y6PtlV5lv3bXSDU1MaNGvtbroiDYjuhSxqKHKAgw= github.com/metal-stack/metal-lib v0.25.2/go.mod h1:tnx4MM5oml10EMN6Nq6oFSbjOKB9B27WUZQUyZ4MywA= github.com/metal-stack/v v1.0.3 h1:Sh2oBlnxrCUD+mVpzfC8HiqL045YWkxs0gpTvkjppqs= github.com/metal-stack/v v1.0.3/go.mod h1:YTahEu7/ishwpYKnp/VaW/7nf8+PInogkfGwLcGPdXg= -github.com/minio/minlz v1.1.1 h1:OGmft1V6AnI/Wme332U6bhG54nxEan+VFgkD7lat4KM= -github.com/minio/minlz v1.1.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec= +github.com/minio/minlz v1.2.0 h1:6IOBuiHg04QxvbFfgFLT/9sMaO/UhL7S+ApW1mK8q5A= +github.com/minio/minlz v1.2.0/go.mod h1:Ls9H7nlkASeCcdl5thjVD5Eraj6z+zGa7xtq57jIKD4= github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s= github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc= @@ -90,8 +90,8 @@ github.com/olekukonko/ll v0.1.8/go.mod h1:RPRC6UcscfFZgjo1nulkfMH5IM0QAYim0LfnMv github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I= github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= -github.com/pelletier/go-toml/v2 v2.4.2 h1:M2fKKbmyvI+hGId/D0W64qDBMVhJnNR10O5gIbMc//Q= -github.com/pelletier/go-toml/v2 v2.4.2/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY= +github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= @@ -123,20 +123,20 @@ go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/exp v0.0.0-20260611194520-c48552f49976 h1:X8Hz2ImujgbmetVuW+w2YkyZChE3cBpZi2P158rTG9M= -golang.org/x/exp v0.0.0-20260611194520-c48552f49976/go.mod h1:vnf4pv9iKZXY58sQE1L86zmNWJ4159e1RkcWiLCkeEY= -golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= -golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= -golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= -golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= -golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= -google.golang.org/genproto/googleapis/api v0.0.0-20260622175928-b703f567277d h1:xr2lwHI91bn3UiXcnyzRMQjp2LRiM8wEHzwUaE0YhTs= -google.golang.org/genproto/googleapis/api v0.0.0-20260622175928-b703f567277d/go.mod h1:O0ZOWSrfWfJ+Z5HbwZ+wNtHsg/vk1k2C/w67eww8PfQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260622175928-b703f567277d h1:mpAgMyM9vQHxycBlDq50y1VHpfSfVwzXvrQKtYbXuUY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260622175928-b703f567277d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= -google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= +golang.org/x/exp v0.0.0-20260718201538-764159d718ef h1:LkZ48HFgy/TvhTI0bcWkjgFkgLyKUwcTbDjS0DUjw+A= +golang.org/x/exp v0.0.0-20260718201538-764159d718ef/go.mod h1:EdfpwwqSu+0Li0mzskwHU6FWDV3t9Q+RZDo3QMUtL3Q= +golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= +google.golang.org/genproto/googleapis/api v0.0.0-20260715232425-e75dac1f907d h1:QwnJwPte4XXAkhPu26LTDIahnsMSUV0kK8HkxbC+Pc4= +google.golang.org/genproto/googleapis/api v0.0.0-20260715232425-e75dac1f907d/go.mod h1:WRrQ7/7N19PypuT0fxLOL5Lq0waoiRri4FbtHDEKrGE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260715232425-e75dac1f907d h1:Jkpk39hlTZOIp3RbfvNX9R8Hv+Sw0X89nlU/xFOErsc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260715232425-e75dac1f907d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE= +google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/tests/e2e/admin/filesystem_layout_test.go b/tests/e2e/admin/filesystem_layout_test.go new file mode 100644 index 0000000..e7f5248 --- /dev/null +++ b/tests/e2e/admin/filesystem_layout_test.go @@ -0,0 +1,67 @@ +package admin_e2e + +import ( + "testing" + + "connectrpc.com/connect" + "github.com/metal-stack/api/go/client" + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + e2erootcmd "github.com/metal-stack/cli/testing/e2e" + "github.com/metal-stack/cli/tests/e2e/testresources" + e2e "github.com/metal-stack/metal-lib/pkg/genericcli/e2e" +) + +func Test_AdminFilesystemLayoutCmd_List(t *testing.T) { + tests := []*e2e.Test[apiv2.FilesystemServiceListResponse, []*apiv2.FilesystemLayout]{ + { + Name: "list", + CmdArgs: []string{"admin", "filesystem-layout", "list"}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &apiv2.FilesystemServiceListRequest{}, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&apiv2.FilesystemServiceListResponse{ + FilesystemLayouts: []*apiv2.FilesystemLayout{ + testresources.FilesystemLayout1(), + }, + }) + }, + }, + }, + }), + WantObject: []*apiv2.FilesystemLayout{testresources.FilesystemLayout1()}, + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} + +func Test_AdminFilesystemLayoutCmd_Delete(t *testing.T) { + tests := []*e2e.Test[adminv2.FilesystemServiceDeleteResponse, *apiv2.FilesystemLayout]{ + { + Name: "delete", + CmdArgs: []string{"admin", "filesystem-layout", "delete", testresources.FilesystemLayout1().Id}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.FilesystemServiceDeleteRequest{ + Id: testresources.FilesystemLayout1().Id, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.FilesystemServiceDeleteResponse{ + FilesystemLayout: testresources.FilesystemLayout1(), + }) + }, + }, + }, + }), + WantObject: testresources.FilesystemLayout1(), + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} diff --git a/tests/e2e/admin/image_usage_test.go b/tests/e2e/admin/image_usage_test.go new file mode 100644 index 0000000..4737d1d --- /dev/null +++ b/tests/e2e/admin/image_usage_test.go @@ -0,0 +1,53 @@ +package admin_e2e + +import ( + "testing" + + "connectrpc.com/connect" + "github.com/metal-stack/api/go/client" + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + e2erootcmd "github.com/metal-stack/cli/testing/e2e" + e2e "github.com/metal-stack/metal-lib/pkg/genericcli/e2e" +) + +func Test_AdminImageUsageCmd(t *testing.T) { + tests := []*e2e.Test[adminv2.ImageServiceUsageResponse, []*apiv2.ImageUsage]{ + { + Name: "usage", + CmdArgs: []string{"admin", "image-usage"}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.ImageServiceUsageRequest{ + Query: &apiv2.ImageQuery{}, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.ImageServiceUsageResponse{ + ImageUsage: []*apiv2.ImageUsage{ + { + Image: &apiv2.Image{ + Id: "ubuntu-22.04", + }, + UsedBy: []string{"machine-1", "machine-2"}, + }, + }, + }) + }, + }, + }, + }), + WantObject: []*apiv2.ImageUsage{ + { + Image: &apiv2.Image{ + Id: "ubuntu-22.04", + }, + UsedBy: []string{"machine-1", "machine-2"}, + }, + }, + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} diff --git a/tests/e2e/admin/machine_test.go b/tests/e2e/admin/machine_test.go new file mode 100644 index 0000000..78733dc --- /dev/null +++ b/tests/e2e/admin/machine_test.go @@ -0,0 +1,127 @@ +package admin_e2e + +import ( + "testing" + + "connectrpc.com/connect" + "github.com/metal-stack/api/go/client" + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + e2erootcmd "github.com/metal-stack/cli/testing/e2e" + "github.com/metal-stack/cli/tests/e2e/testresources" + e2e "github.com/metal-stack/metal-lib/pkg/genericcli/e2e" +) + +func Test_AdminMachineCmd_List(t *testing.T) { + tests := []*e2e.Test[adminv2.MachineServiceListResponse, []*apiv2.Machine]{ + { + Name: "list", + CmdArgs: []string{"admin", "machine", "list"}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.MachineServiceListRequest{ + Query: &apiv2.MachineQuery{}, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.MachineServiceListResponse{ + Machines: []*apiv2.Machine{ + testresources.Machine1(), + }, + }) + }, + }, + }, + }), + WantObject: []*apiv2.Machine{testresources.Machine1()}, + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} + +func Test_AdminMachineCmd_Delete(t *testing.T) { + tests := []*e2e.Test[adminv2.MachineServiceDeleteResponse, *apiv2.Machine]{ + { + Name: "delete", + CmdArgs: []string{"admin", "machine", "delete", testresources.Machine1().Uuid}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.MachineServiceDeleteRequest{ + Uuid: testresources.Machine1().Uuid, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.MachineServiceDeleteResponse{ + Machine: testresources.Machine1(), + }) + }, + }, + }, + }), + WantObject: testresources.Machine1(), + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} + +func Test_AdminMachineCmd_SetState(t *testing.T) { + tests := []*e2e.Test[adminv2.MachineServiceSetStateResponse, any]{ + { + Name: "set state", + CmdArgs: []string{"admin", "machine", "set-state", testresources.Machine1().Uuid, "--state", "MACHINE_STATE_LOCKED", "--description", "maintenance"}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.MachineServiceSetStateRequest{ + Uuid: testresources.Machine1().Uuid, + State: apiv2.MachineState_MACHINE_STATE_LOCKED, + Description: "maintenance", + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.MachineServiceSetStateResponse{}) + }, + }, + }, + }), + WantDefault: new(""), + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} + +func Test_AdminMachineCmd_ConsolePassword(t *testing.T) { + tests := []*e2e.Test[adminv2.MachineServiceConsolePasswordResponse, *adminv2.MachineServiceConsolePasswordResponse]{ + { + Name: "console password", + CmdArgs: []string{"admin", "machine", "console-password", testresources.Machine1().Uuid}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.MachineServiceConsolePasswordRequest{ + Uuid: testresources.Machine1().Uuid, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.MachineServiceConsolePasswordResponse{ + Uuid: testresources.Machine1().Uuid, + Password: "secret123", + }) + }, + }, + }, + }), + WantObject: &adminv2.MachineServiceConsolePasswordResponse{ + Uuid: testresources.Machine1().Uuid, + Password: "secret123", + }, + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} diff --git a/tests/e2e/admin/network_test.go b/tests/e2e/admin/network_test.go new file mode 100644 index 0000000..b25fa79 --- /dev/null +++ b/tests/e2e/admin/network_test.go @@ -0,0 +1,67 @@ +package admin_e2e + +import ( + "testing" + + "connectrpc.com/connect" + "github.com/metal-stack/api/go/client" + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + e2erootcmd "github.com/metal-stack/cli/testing/e2e" + "github.com/metal-stack/cli/tests/e2e/testresources" + e2e "github.com/metal-stack/metal-lib/pkg/genericcli/e2e" +) + +func Test_AdminNetworkCmd_List(t *testing.T) { + tests := []*e2e.Test[adminv2.NetworkServiceListResponse, []*apiv2.Network]{ + { + Name: "list", + CmdArgs: []string{"admin", "network", "list"}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.NetworkServiceListRequest{}, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.NetworkServiceListResponse{ + Networks: []*apiv2.Network{ + testresources.Network1(), + }, + }) + }, + }, + }, + }), + WantObject: []*apiv2.Network{testresources.Network1()}, + }, + } + 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(), + }) + }, + }, + }, + }), + WantObject: testresources.Network1(), + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} diff --git a/tests/e2e/admin/partition_test.go b/tests/e2e/admin/partition_test.go new file mode 100644 index 0000000..b8a58ba --- /dev/null +++ b/tests/e2e/admin/partition_test.go @@ -0,0 +1,122 @@ +package admin_e2e + +import ( + "testing" + + "connectrpc.com/connect" + "github.com/metal-stack/api/go/client" + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + e2erootcmd "github.com/metal-stack/cli/testing/e2e" + "github.com/metal-stack/cli/tests/e2e/testresources" + e2e "github.com/metal-stack/metal-lib/pkg/genericcli/e2e" +) + +func Test_AdminPartitionCmd_List(t *testing.T) { + tests := []*e2e.Test[apiv2.PartitionServiceListResponse, []*apiv2.Partition]{ + { + Name: "list", + CmdArgs: []string{"admin", "partition", "list"}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &apiv2.PartitionServiceListRequest{ + Query: &apiv2.PartitionQuery{}, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&apiv2.PartitionServiceListResponse{ + Partitions: []*apiv2.Partition{ + testresources.Partition1(), + }, + }) + }, + }, + }, + }), + WantObject: []*apiv2.Partition{testresources.Partition1()}, + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} + +func Test_AdminPartitionCmd_Capacity(t *testing.T) { + tests := []*e2e.Test[adminv2.PartitionServiceCapacityResponse, []*adminv2.PartitionCapacity]{ + { + Name: "capacity", + CmdArgs: []string{"admin", "partition", "capacity"}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.PartitionServiceCapacityRequest{}, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.PartitionServiceCapacityResponse{ + PartitionCapacity: []*adminv2.PartitionCapacity{ + { + Partition: "fra-equ01", + MachineSizeCapacities: []*adminv2.MachineSizeCapacity{ + { + Size: "v1-medium-x86", + Total: 100, + Free: 50, + Allocated: 30, + Faulty: 5, + Other: 15, + }, + }, + }, + }, + }) + }, + }, + }, + }), + WantObject: []*adminv2.PartitionCapacity{ + { + Partition: "fra-equ01", + MachineSizeCapacities: []*adminv2.MachineSizeCapacity{ + { + Size: "v1-medium-x86", + Total: 100, + Free: 50, + Allocated: 30, + Faulty: 5, + Other: 15, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} + +func Test_AdminPartitionCmd_Delete(t *testing.T) { + tests := []*e2e.Test[adminv2.PartitionServiceDeleteResponse, *apiv2.Partition]{ + { + Name: "delete", + CmdArgs: []string{"admin", "partition", "delete", testresources.Partition1().Id}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.PartitionServiceDeleteRequest{ + Id: testresources.Partition1().Id, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.PartitionServiceDeleteResponse{ + Partition: testresources.Partition1(), + }) + }, + }, + }, + }), + WantObject: testresources.Partition1(), + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} diff --git a/tests/e2e/admin/size_image_constraint_test.go b/tests/e2e/admin/size_image_constraint_test.go new file mode 100644 index 0000000..efaf528 --- /dev/null +++ b/tests/e2e/admin/size_image_constraint_test.go @@ -0,0 +1,80 @@ +package admin_e2e + +import ( + "testing" + + "connectrpc.com/connect" + "github.com/metal-stack/api/go/client" + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + e2erootcmd "github.com/metal-stack/cli/testing/e2e" + e2e "github.com/metal-stack/metal-lib/pkg/genericcli/e2e" +) + +func Test_AdminSizeImageConstraintCmd_List(t *testing.T) { + tests := []*e2e.Test[adminv2.SizeImageConstraintServiceListResponse, []*apiv2.SizeImageConstraint]{ + { + Name: "list", + CmdArgs: []string{"admin", "size-image-constraint", "list"}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.SizeImageConstraintServiceListRequest{}, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.SizeImageConstraintServiceListResponse{ + SizeImageConstraints: []*apiv2.SizeImageConstraint{ + { + Size: "v1-medium-x86", + Name: new("constraint-1"), + Description: new("Must use Ubuntu 22.04"), + Meta: &apiv2.Meta{}, + }, + }, + }) + }, + }, + }, + }), + WantObject: []*apiv2.SizeImageConstraint{ + { + Size: "v1-medium-x86", + Name: new("constraint-1"), + Description: new("Must use Ubuntu 22.04"), + Meta: &apiv2.Meta{}, + }, + }, + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} + +func Test_AdminSizeImageConstraintCmd_Delete(t *testing.T) { + tests := []*e2e.Test[adminv2.SizeImageConstraintServiceDeleteResponse, *apiv2.SizeImageConstraint]{ + { + Name: "delete", + CmdArgs: []string{"admin", "size-image-constraint", "delete", "v1-medium-x86"}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.SizeImageConstraintServiceDeleteRequest{ + Size: "v1-medium-x86", + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.SizeImageConstraintServiceDeleteResponse{ + SizeImageConstraint: &apiv2.SizeImageConstraint{ + Size: "v1-medium-x86", + }, + }) + }, + }, + }, + }), + WantObject: &apiv2.SizeImageConstraint{Size: "v1-medium-x86"}, + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} diff --git a/tests/e2e/admin/size_reservation_test.go b/tests/e2e/admin/size_reservation_test.go new file mode 100644 index 0000000..367f3b2 --- /dev/null +++ b/tests/e2e/admin/size_reservation_test.go @@ -0,0 +1,67 @@ +package admin_e2e + +import ( + "testing" + + "connectrpc.com/connect" + "github.com/metal-stack/api/go/client" + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + e2erootcmd "github.com/metal-stack/cli/testing/e2e" + "github.com/metal-stack/cli/tests/e2e/testresources" + e2e "github.com/metal-stack/metal-lib/pkg/genericcli/e2e" +) + +func Test_AdminSizeReservationCmd_List(t *testing.T) { + tests := []*e2e.Test[adminv2.SizeReservationServiceListResponse, []*apiv2.SizeReservation]{ + { + Name: "list", + CmdArgs: []string{"admin", "size-reservation", "list"}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.SizeReservationServiceListRequest{}, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.SizeReservationServiceListResponse{ + SizeReservations: []*apiv2.SizeReservation{ + testresources.SizeReservation1(), + }, + }) + }, + }, + }, + }), + WantObject: []*apiv2.SizeReservation{testresources.SizeReservation1()}, + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} + +func Test_AdminSizeReservationCmd_Delete(t *testing.T) { + tests := []*e2e.Test[adminv2.SizeReservationServiceDeleteResponse, *apiv2.SizeReservation]{ + { + Name: "delete", + CmdArgs: []string{"admin", "size-reservation", "delete", testresources.SizeReservation1().Id}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.SizeReservationServiceDeleteRequest{ + Id: testresources.SizeReservation1().Id, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.SizeReservationServiceDeleteResponse{ + SizeReservation: testresources.SizeReservation1(), + }) + }, + }, + }, + }), + WantObject: testresources.SizeReservation1(), + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} diff --git a/tests/e2e/admin/token_create_test.go b/tests/e2e/admin/token_create_test.go new file mode 100644 index 0000000..ab65adc --- /dev/null +++ b/tests/e2e/admin/token_create_test.go @@ -0,0 +1,56 @@ +package admin_e2e + +import ( + "testing" + "time" + + "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" + e2e "github.com/metal-stack/metal-lib/pkg/genericcli/e2e" + "google.golang.org/protobuf/types/known/durationpb" +) + +func Test_AdminTokenCreateCmd(t *testing.T) { + user := "user-1" + tests := []*e2e.Test[adminv2.TokenServiceCreateResponse, string]{ + { + Name: "create", + CmdArgs: []string{"admin", "token-create", "--user", "user-1", "--description", "admin token", "--expires", "24h"}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.TokenServiceCreateRequest{ + User: &user, + TokenCreateRequest: &apiv2.TokenServiceCreateRequest{ + Description: "admin token", + Expires: durationpb.New(24 * time.Hour), + }, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.TokenServiceCreateResponse{ + Token: &apiv2.Token{ + Uuid: "token-1", + Description: "admin token", + }, + Secret: "secret-value", + }) + }, + }, + }, + }), + WantDefault: new(`Make sure to copy your personal access token now as you will not be able to see this again. + +secret-value + +description: admin token +uuid: token-1 +`), + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} diff --git a/tests/e2e/admin/vpn_test.go b/tests/e2e/admin/vpn_test.go new file mode 100644 index 0000000..8d883b7 --- /dev/null +++ b/tests/e2e/admin/vpn_test.go @@ -0,0 +1,98 @@ +package admin_e2e + +import ( + "testing" + "time" + + "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" + e2e "github.com/metal-stack/metal-lib/pkg/genericcli/e2e" + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func Test_AdminVPNCmd_AuthKey(t *testing.T) { + tests := []*e2e.Test[adminv2.VPNServiceAuthKeyResponse, *adminv2.VPNServiceAuthKeyResponse]{ + { + Name: "auth key", + CmdArgs: []string{"admin", "vpn", "auth-key", "--project", "project-1", "--ephemeral", "--expires", "1h", "--reason", "debugging"}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.VPNServiceAuthKeyRequest{ + Project: "project-1", + Ephemeral: true, + Expires: durationpb.New(1 * time.Hour), + Reason: "debugging", + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.VPNServiceAuthKeyResponse{ + Address: "vpn.example.com:443", + AuthKey: "key-12345", + Ephemeral: true, + ExpiresAt: timestamppb.New(e2e.TimeBubbleStartTime().Add(1 * time.Hour)), + CreatedAt: timestamppb.New(e2e.TimeBubbleStartTime()), + }) + }, + }, + }, + }), + WantObject: &adminv2.VPNServiceAuthKeyResponse{ + Address: "vpn.example.com:443", + AuthKey: "key-12345", + Ephemeral: true, + ExpiresAt: timestamppb.New(e2e.TimeBubbleStartTime().Add(1 * time.Hour)), + CreatedAt: timestamppb.New(e2e.TimeBubbleStartTime()), + }, + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} + +func Test_AdminVPNCmd_ListNodes(t *testing.T) { + tests := []*e2e.Test[adminv2.VPNServiceListNodesResponse, []*apiv2.VPNNode]{ + { + Name: "list nodes", + CmdArgs: []string{"admin", "vpn", "list-nodes"}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.VPNServiceListNodesRequest{}, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.VPNServiceListNodesResponse{ + Nodes: []*apiv2.VPNNode{ + { + Id: 1, + Name: "node-1", + Project: "project-1", + Online: true, + LastSeen: timestamppb.New(e2e.TimeBubbleStartTime()), + IpAddresses: []string{"10.0.0.1"}, + }, + }, + }) + }, + }, + }, + }), + WantObject: []*apiv2.VPNNode{ + { + Id: 1, + Name: "node-1", + Project: "project-1", + Online: true, + LastSeen: timestamppb.New(e2e.TimeBubbleStartTime()), + IpAddresses: []string{"10.0.0.1"}, + }, + }, + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} diff --git a/tests/e2e/api/filesystem_layout_test.go b/tests/e2e/api/filesystem_layout_test.go new file mode 100644 index 0000000..0dbbb9f --- /dev/null +++ b/tests/e2e/api/filesystem_layout_test.go @@ -0,0 +1,71 @@ +package api_e2e + +import ( + "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" + e2e "github.com/metal-stack/metal-lib/pkg/genericcli/e2e" +) + +func Test_FilesystemLayoutCmd_List(t *testing.T) { + tests := []*e2e.Test[apiv2.FilesystemServiceListResponse, []*apiv2.FilesystemLayout]{ + { + Name: "list", + CmdArgs: []string{"filesystem-layout", "list"}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &apiv2.FilesystemServiceListRequest{}, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&apiv2.FilesystemServiceListResponse{ + FilesystemLayouts: []*apiv2.FilesystemLayout{ + testresources.FilesystemLayout1(), + testresources.FilesystemLayout2(), + }, + }) + }, + }, + }, + }), + WantObject: []*apiv2.FilesystemLayout{ + testresources.FilesystemLayout1(), + testresources.FilesystemLayout2(), + }, + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} + +func Test_FilesystemLayoutCmd_Describe(t *testing.T) { + fsl1 := testresources.FilesystemLayout1() + tests := []*e2e.Test[apiv2.FilesystemServiceGetResponse, *apiv2.FilesystemLayout]{ + { + Name: "describe", + CmdArgs: []string{"filesystem-layout", "describe", fsl1.Id}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &apiv2.FilesystemServiceGetRequest{ + Id: fsl1.Id, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&apiv2.FilesystemServiceGetResponse{ + FilesystemLayout: fsl1, + }) + }, + }, + }, + }), + WantObject: fsl1, + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} diff --git a/tests/e2e/api/machine_test.go b/tests/e2e/api/machine_test.go new file mode 100644 index 0000000..7256584 --- /dev/null +++ b/tests/e2e/api/machine_test.go @@ -0,0 +1,74 @@ +package api_e2e + +import ( + "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" + e2e "github.com/metal-stack/metal-lib/pkg/genericcli/e2e" +) + +func Test_MachineCmd_List(t *testing.T) { + tests := []*e2e.Test[apiv2.MachineServiceListResponse, []*apiv2.Machine]{ + { + Name: "list", + CmdArgs: []string{"machine", "list"}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &apiv2.MachineServiceListRequest{ + Query: &apiv2.MachineQuery{}, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&apiv2.MachineServiceListResponse{ + Machines: []*apiv2.Machine{ + testresources.Machine1(), + testresources.Machine2(), + }, + }) + }, + }, + }, + }), + WantObject: []*apiv2.Machine{ + testresources.Machine1(), + testresources.Machine2(), + }, + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} + +func Test_MachineCmd_Describe(t *testing.T) { + m1 := testresources.Machine1() + tests := []*e2e.Test[apiv2.MachineServiceGetResponse, *apiv2.Machine]{ + { + Name: "describe", + CmdArgs: []string{"machine", "describe", m1.Uuid}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &apiv2.MachineServiceGetRequest{ + Uuid: m1.Uuid, + Project: "", + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&apiv2.MachineServiceGetResponse{ + Machine: m1, + }) + }, + }, + }, + }), + WantObject: m1, + }, + } + 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..81adf7b --- /dev/null +++ b/tests/e2e/api/network_test.go @@ -0,0 +1,74 @@ +package api_e2e + +import ( + "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" + e2e "github.com/metal-stack/metal-lib/pkg/genericcli/e2e" +) + +func Test_NetworkCmd_List(t *testing.T) { + tests := []*e2e.Test[apiv2.NetworkServiceListResponse, []*apiv2.Network]{ + { + Name: "list", + CmdArgs: []string{"network", "list"}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &apiv2.NetworkServiceListRequest{ + Project: "", + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&apiv2.NetworkServiceListResponse{ + Networks: []*apiv2.Network{ + testresources.Network1(), + testresources.Network2(), + }, + }) + }, + }, + }, + }), + WantObject: []*apiv2.Network{ + testresources.Network1(), + testresources.Network2(), + }, + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} + +func Test_NetworkCmd_Describe(t *testing.T) { + n1 := testresources.Network1() + tests := []*e2e.Test[apiv2.NetworkServiceGetResponse, *apiv2.Network]{ + { + Name: "describe", + CmdArgs: []string{"network", "describe", n1.Id}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &apiv2.NetworkServiceGetRequest{ + Id: n1.Id, + Project: "", + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&apiv2.NetworkServiceGetResponse{ + Network: n1, + }) + }, + }, + }, + }), + WantObject: n1, + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} diff --git a/tests/e2e/api/partition_test.go b/tests/e2e/api/partition_test.go new file mode 100644 index 0000000..bbf6951 --- /dev/null +++ b/tests/e2e/api/partition_test.go @@ -0,0 +1,72 @@ +package api_e2e + +import ( + "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" + e2e "github.com/metal-stack/metal-lib/pkg/genericcli/e2e" +) + +func Test_PartitionCmd_List(t *testing.T) { + tests := []*e2e.Test[apiv2.PartitionServiceListResponse, []*apiv2.Partition]{ + { + Name: "list", + CmdArgs: []string{"partition", "list"}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &apiv2.PartitionServiceListRequest{ + Query: &apiv2.PartitionQuery{}, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&apiv2.PartitionServiceListResponse{ + Partitions: []*apiv2.Partition{ + testresources.Partition1(), + testresources.Partition2(), + }, + }) + }, + }, + }, + }), + WantObject: []*apiv2.Partition{ + testresources.Partition1(), + testresources.Partition2(), + }, + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} + +func Test_PartitionCmd_Describe(t *testing.T) { + tests := []*e2e.Test[apiv2.PartitionServiceGetResponse, *apiv2.Partition]{ + { + Name: "describe", + CmdArgs: []string{"partition", "describe", testresources.Partition1().Id}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &apiv2.PartitionServiceGetRequest{ + Id: testresources.Partition1().Id, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&apiv2.PartitionServiceGetResponse{ + Partition: testresources.Partition1(), + }) + }, + }, + }, + }), + WantObject: testresources.Partition1(), + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} diff --git a/tests/e2e/api/size_reservation_test.go b/tests/e2e/api/size_reservation_test.go new file mode 100644 index 0000000..a061581 --- /dev/null +++ b/tests/e2e/api/size_reservation_test.go @@ -0,0 +1,71 @@ +package api_e2e + +import ( + "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" + e2e "github.com/metal-stack/metal-lib/pkg/genericcli/e2e" +) + +func Test_SizeReservationCmd_List(t *testing.T) { + tests := []*e2e.Test[apiv2.SizeReservationServiceListResponse, []*apiv2.SizeReservation]{ + { + Name: "list", + CmdArgs: []string{"size-reservation", "list"}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &apiv2.SizeReservationServiceListRequest{}, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&apiv2.SizeReservationServiceListResponse{ + SizeReservations: []*apiv2.SizeReservation{ + testresources.SizeReservation1(), + testresources.SizeReservation2(), + }, + }) + }, + }, + }, + }), + WantObject: []*apiv2.SizeReservation{ + testresources.SizeReservation1(), + testresources.SizeReservation2(), + }, + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} + +func Test_SizeReservationCmd_Describe(t *testing.T) { + sr1 := testresources.SizeReservation1() + tests := []*e2e.Test[apiv2.SizeReservationServiceGetResponse, *apiv2.SizeReservation]{ + { + Name: "describe", + CmdArgs: []string{"size-reservation", "describe", sr1.Id}, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &apiv2.SizeReservationServiceGetRequest{ + Id: sr1.Id, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&apiv2.SizeReservationServiceGetResponse{ + SizeReservation: sr1, + }) + }, + }, + }, + }), + WantObject: sr1, + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} diff --git a/tests/e2e/testresources/partition.go b/tests/e2e/testresources/partition.go new file mode 100644 index 0000000..3aefd24 --- /dev/null +++ b/tests/e2e/testresources/partition.go @@ -0,0 +1,167 @@ +package testresources + +import ( + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/metal-lib/pkg/genericcli/e2e" + "google.golang.org/protobuf/types/known/timestamppb" +) + +var ( + Partition1 = func() *apiv2.Partition { + return &apiv2.Partition{ + Id: "fra-equ01", + Description: "Frankfurt Equinix 1", + Meta: &apiv2.Meta{ + CreatedAt: timestamppb.New(e2e.TimeBubbleStartTime()), + Labels: &apiv2.Labels{ + Labels: map[string]string{ + "location": "fra", + }, + }, + }, + MgmtServiceAddresses: []string{"10.0.0.1:8080", "10.0.0.2:8080"}, + } + } + Partition2 = func() *apiv2.Partition { + return &apiv2.Partition{ + Id: "fra-equ02", + Description: "Frankfurt Equinix 2", + Meta: &apiv2.Meta{ + CreatedAt: timestamppb.New(e2e.TimeBubbleStartTime()), + }, + MgmtServiceAddresses: []string{"10.0.1.1:8080"}, + } + } + Network1 = func() *apiv2.Network { + return &apiv2.Network{ + Id: "n-1", + Name: new("internal-net"), + Description: new("Internal network"), + Project: new("project-1"), + Partition: new("fra-equ01"), + Type: apiv2.NetworkType_NETWORK_TYPE_CHILD, + Prefixes: []string{"10.0.0.0/16", "10.1.0.0/16"}, + Meta: &apiv2.Meta{ + CreatedAt: timestamppb.New(e2e.TimeBubbleStartTime()), + }, + NatType: apiv2.NATType_NAT_TYPE_NONE, + Vrf: new(uint32(100)), + } + } + Network2 = func() *apiv2.Network { + return &apiv2.Network{ + Id: "n-2", + Name: new("external-net"), + Description: new("External network"), + Project: new("project-2"), + Partition: new("fra-equ02"), + Type: apiv2.NetworkType_NETWORK_TYPE_EXTERNAL, + Prefixes: []string{"172.16.0.0/12"}, + Meta: &apiv2.Meta{ + CreatedAt: timestamppb.New(e2e.TimeBubbleStartTime()), + }, + NatType: apiv2.NATType_NAT_TYPE_IPV4_MASQUERADE, + } + } + Machine1 = func() *apiv2.Machine { + return &apiv2.Machine{ + Uuid: "m-1", + Partition: &apiv2.Partition{Id: "fra-equ01"}, + Rack: "rack-1", + Size: &apiv2.Size{Id: "v1-medium-x86"}, + Meta: &apiv2.Meta{ + CreatedAt: timestamppb.New(e2e.TimeBubbleStartTime()), + }, + Allocation: &apiv2.MachineAllocation{ + Hostname: "node-1", + Project: "project-1", + }, + Status: &apiv2.MachineStatus{ + Liveliness: apiv2.MachineLiveliness_MACHINE_LIVELINESS_ALIVE, + Condition: &apiv2.MachineCondition{ + State: apiv2.MachineState_MACHINE_STATE_AVAILABLE, + }, + }, + } + } + Machine2 = func() *apiv2.Machine { + return &apiv2.Machine{ + Uuid: "m-2", + Partition: &apiv2.Partition{Id: "fra-equ02"}, + Rack: "rack-2", + Size: &apiv2.Size{Id: "g1-medium-x86"}, + Meta: &apiv2.Meta{ + CreatedAt: timestamppb.New(e2e.TimeBubbleStartTime()), + }, + Allocation: &apiv2.MachineAllocation{ + Hostname: "node-2", + Project: "project-2", + }, + Status: &apiv2.MachineStatus{ + Liveliness: apiv2.MachineLiveliness_MACHINE_LIVELINESS_ALIVE, + Condition: &apiv2.MachineCondition{ + State: apiv2.MachineState_MACHINE_STATE_AVAILABLE, + }, + }, + } + } + SizeReservation1 = func() *apiv2.SizeReservation { + return &apiv2.SizeReservation{ + Id: "sr-1", + Name: "reservation-1", + Description: "Reservation for project-1", + Project: "project-1", + Size: "v1-medium-x86", + Partitions: []string{"fra-equ01"}, + Amount: 5, + Meta: &apiv2.Meta{ + CreatedAt: timestamppb.New(e2e.TimeBubbleStartTime()), + }, + } + } + SizeReservation2 = func() *apiv2.SizeReservation { + return &apiv2.SizeReservation{ + Id: "sr-2", + Name: "reservation-2", + Description: "Reservation for project-2", + Project: "project-2", + Size: "g1-medium-x86", + Partitions: []string{"fra-equ02"}, + Amount: 3, + Meta: &apiv2.Meta{ + CreatedAt: timestamppb.New(e2e.TimeBubbleStartTime()), + }, + } + } + FilesystemLayout1 = func() *apiv2.FilesystemLayout { + return &apiv2.FilesystemLayout{ + Id: "fsl-1", + Name: new("default-ext4"), + Description: new("Default ext4 layout"), + Meta: &apiv2.Meta{ + CreatedAt: timestamppb.New(e2e.TimeBubbleStartTime()), + }, + Disks: []*apiv2.Disk{ + { + Device: "/dev/sda", + Partitions: []*apiv2.DiskPartition{ + { + Number: 1, + Size: 100000000000, + }, + }, + }, + }, + } + } + FilesystemLayout2 = func() *apiv2.FilesystemLayout { + return &apiv2.FilesystemLayout{ + Id: "fsl-2", + Name: new("default-xfs"), + Description: new("Default xfs layout"), + Meta: &apiv2.Meta{ + CreatedAt: timestamppb.New(e2e.TimeBubbleStartTime()), + }, + } + } +)