diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index 189060f..c02ff18 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -21,7 +21,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Go uses: actions/setup-go@v6 @@ -30,7 +30,7 @@ jobs: cache: false - name: Lint - uses: golangci/golangci-lint-action@v8 + uses: golangci/golangci-lint-action@v9 with: args: --build-tags integration --timeout=5m @@ -58,7 +58,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Go uses: actions/setup-go@v6 diff --git a/cmd/admin/v2/commands.go b/cmd/admin/v2/commands.go index 881a7be..0b461b6 100644 --- a/cmd/admin/v2/commands.go +++ b/cmd/admin/v2/commands.go @@ -15,9 +15,15 @@ func AddCmds(cmd *cobra.Command, c *config.Config) { } adminCmd.AddCommand(newImageCmd(c)) + adminCmd.AddCommand(newIPCmd(c)) + adminCmd.AddCommand(newMachineCmd(c)) + adminCmd.AddCommand(newNetworkCmd(c)) + adminCmd.AddCommand(newProjectCmd(c)) + adminCmd.AddCommand(newSizeCmd(c)) + adminCmd.AddCommand(newTaskCmd(c)) adminCmd.AddCommand(newTenantCmd(c)) adminCmd.AddCommand(newTokenCmd(c)) - adminCmd.AddCommand(newProjectCmd(c)) + adminCmd.AddCommand(newVPNCmd(c)) cmd.AddCommand(adminCmd) } diff --git a/cmd/admin/v2/ip.go b/cmd/admin/v2/ip.go new file mode 100644 index 0000000..637080d --- /dev/null +++ b/cmd/admin/v2/ip.go @@ -0,0 +1,74 @@ +package v2 + +import ( + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/cli/cmd/sorters" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/spf13/cobra" +) + +type ip struct { + c *config.Config +} + +func newIPCmd(c *config.Config) *cobra.Command { + w := &ip{ + c: c, + } + + cmdsConfig := &genericcli.CmdsConfig[any, any, *apiv2.IP]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), + Singular: "ip", + Plural: "ips", + Description: "manage ip addresses", + Sorter: sorters.IPSorter(), + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + OnlyCmds: genericcli.OnlyCmds(genericcli.ListCmd), + ListCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("ip", "", "ipaddress to filter [optional]") + cmd.Flags().String("name", "", "name to filter [optional]") + cmd.Flags().String("network", "", "network to filter [optional]") + cmd.Flags().String("description", "", "description to filter [optional]") + genericcli.Must(cmd.RegisterFlagCompletionFunc("network", c.Completion.NetworkListCompletion)) + }, + ValidArgsFn: c.Completion.IpListCompletion, + } + + return genericcli.NewCmds(cmdsConfig) +} + +func (c *ip) List() ([]*apiv2.IP, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().IP().List(ctx, &adminv2.IPServiceListRequest{ + Query: &apiv2.IPQuery{}, + }) + if err != nil { + return nil, err + } + + return resp.Ips, nil +} + +func (t *ip) Get(id string) (*apiv2.IP, error) { + panic("unimplemented") +} +func (c *ip) Delete(id string) (*apiv2.IP, error) { + panic("unimplemented") +} +func (t *ip) Create(rq any) (*apiv2.IP, error) { + panic("unimplemented") +} +func (t *ip) Convert(r *apiv2.IP) (string, any, any, error) { + panic("unimplemented") +} + +func (t *ip) Update(rq any) (*apiv2.IP, error) { + panic("unimplemented") +} diff --git a/cmd/admin/v2/machine.go b/cmd/admin/v2/machine.go new file mode 100644 index 0000000..8a0f492 --- /dev/null +++ b/cmd/admin/v2/machine.go @@ -0,0 +1,191 @@ +package v2 + +import ( + "fmt" + + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/cli/cmd/sorters" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/metal-stack/metal-lib/pkg/pointer" + "github.com/metal-stack/metal-lib/pkg/tag" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +type machine struct { + c *config.Config +} + +func newMachineCmd(c *config.Config) *cobra.Command { + w := &machine{ + c: c, + } + + cmdsConfig := &genericcli.CmdsConfig[any, any, *apiv2.Machine]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), + Singular: "machine", + Plural: "machines", + Description: "manage machines", + Sorter: sorters.MachineSorter(), + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + ListCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().StringP("project", "p", "", "project from where machines should be listed") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + }, + DescribeCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().StringP("project", "p", "", "project of the machine") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + }, + ValidArgsFn: c.Completion.MachineListCompletion, + } + + bmcCommandCmd := &cobra.Command{ + Use: "bmc-command", + Short: "send a command to the bmc of a machine", + RunE: func(cmd *cobra.Command, args []string) error { + return w.bmcCommand() + }, + } + bmcCommandCmd.Flags().String("id", "", "id of the machine where the command should be sent to") + bmcCommandCmd.Flags().String("command", "", "the actual command to send to the machine") + bmcCommandCmd.RegisterFlagCompletionFunc("id", c.Completion.MachineListCompletion) + bmcCommandCmd.RegisterFlagCompletionFunc("command", c.Completion.BMCCommandListCompletion) + genericcli.Must(bmcCommandCmd.MarkFlagRequired("id")) + genericcli.Must(bmcCommandCmd.MarkFlagRequired("command")) + + return genericcli.NewCmds(cmdsConfig, bmcCommandCmd) +} + +func (c *machine) bmcCommand() error { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + commandString := viper.GetString("command") + + cmd, ok := apiv2.MachineBMCCommand_value[commandString] + if !ok { + return fmt.Errorf("unknown command: %s", commandString) + } + _, err := c.c.Client.Adminv2().Machine().BMCCommand(ctx, &adminv2.MachineServiceBMCCommandRequest{ + Uuid: viper.GetString("id"), + Command: apiv2.MachineBMCCommand(cmd), + }) + if err != nil { + return err + } + return err +} + +func (c *machine) updateFromCLI(args []string) (any, error) { + panic("unimplemented") +} + +func (c *machine) Create(rq any) (*apiv2.Machine, error) { + panic("unimplemented") +} + +func (c *machine) Delete(id string) (*apiv2.Machine, error) { + panic("unimplemented") +} + +func (c *machine) Get(id string) (*apiv2.Machine, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().Machine().Get(ctx, &adminv2.MachineServiceGetRequest{ + Uuid: id, + }) + if err != nil { + return nil, err + } + + return resp.Machine, nil +} + +func (c *machine) List() ([]*apiv2.Machine, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().Machine().List(ctx, &adminv2.MachineServiceListRequest{ + Query: &apiv2.MachineQuery{ + Uuid: pointer.PointerOrNil(viper.GetString("id")), + Name: pointer.PointerOrNil(viper.GetString("name")), + Partition: pointer.PointerOrNil(viper.GetString("partition")), + Size: pointer.PointerOrNil(viper.GetString("size")), + Rack: pointer.PointerOrNil(viper.GetString("rack")), + Labels: &apiv2.Labels{ + Labels: tag.NewTagMap(viper.GetStringSlice("labels")), + }, + Allocation: &apiv2.MachineAllocationQuery{ + Uuid: pointer.PointerOrNil(viper.GetString("allocation-uuid")), + Name: pointer.PointerOrNil(viper.GetString("allocation-name")), + Project: pointer.PointerOrNil(viper.GetString("project")), + Image: pointer.PointerOrNil(viper.GetString("image")), + FilesystemLayout: pointer.PointerOrNil(viper.GetString("file-system-layout-id")), + Hostname: pointer.PointerOrNil(viper.GetString("hostname")), + // AllocationType: &0, + Labels: &apiv2.Labels{ + Labels: tag.NewTagMap(viper.GetStringSlice("allocation-labels")), + }, + // Vpn: &apiv2.MachineVPN{}, these query fields are no pointers and somehow seem wrong? how to search for vpn key? + }, + Network: &apiv2.MachineNetworkQuery{}, + Nic: &apiv2.MachineNicQuery{}, + Disk: &apiv2.MachineDiskQuery{ + Names: viper.GetStringSlice("disk-names"), + // Sizes: + }, + Bmc: &apiv2.MachineBMCQuery{ + Address: pointer.PointerOrNil(viper.GetString("bmc-address")), + Mac: pointer.PointerOrNil(viper.GetString("bmc-mac")), + User: pointer.PointerOrNil(viper.GetString("bmc-user")), + Interface: pointer.PointerOrNil(viper.GetString("bmc-interface")), + }, + Fru: &apiv2.MachineFRUQuery{ + ChassisPartNumber: pointer.PointerOrNil(viper.GetString("chassis-part-number")), + ChassisPartSerial: pointer.PointerOrNil(viper.GetString("chassis-part-serial")), + BoardMfg: pointer.PointerOrNil(viper.GetString("board-mfg")), + BoardSerial: pointer.PointerOrNil(viper.GetString("board-serial")), + BoardPartNumber: pointer.PointerOrNil(viper.GetString("board-part-number")), + ProductManufacturer: pointer.PointerOrNil(viper.GetString("product-manufacturer")), + ProductPartNumber: pointer.PointerOrNil(viper.GetString("product-part-number")), + ProductSerial: pointer.PointerOrNil(viper.GetString("product-serial")), + }, + Hardware: &apiv2.MachineHardwareQuery{ + Memory: pointer.PointerOrNil(viper.GetUint64("memory")), + CpuCores: pointer.PointerOrNil(viper.GetUint32("cpu-cores")), + }, + // State: &0, + }, + Partition: nil, // again partition? + }) + if err != nil { + return nil, err + } + + return resp.Machines, nil +} + +func (c *machine) Update(rq any) (*apiv2.Machine, error) { + panic("unimplemented") +} + +func (c *machine) Convert(r *apiv2.Machine) (string, any, any, error) { + panic("unimplemented") + +} + +func (c *machine) MachineResponseToCreate(r *apiv2.Machine) any { + panic("unimplemented") +} + +func (c *machine) MachineResponseToUpdate(desired *apiv2.Machine) (any, error) { + panic("unimplemented") +} diff --git a/cmd/admin/v2/network.go b/cmd/admin/v2/network.go new file mode 100644 index 0000000..f6ef299 --- /dev/null +++ b/cmd/admin/v2/network.go @@ -0,0 +1,397 @@ +package v2 + +import ( + "github.com/metal-stack/api/go/enum" + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/cli/cmd/sorters" + "github.com/metal-stack/cli/pkg/common" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/metal-stack/metal-lib/pkg/pointer" + "github.com/metal-stack/metal-lib/pkg/tag" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type networkCmd struct { + c *config.Config +} + +func newNetworkCmd(c *config.Config) *cobra.Command { + w := &networkCmd{ + c: c, + } + + // TODO: move to common? + listFlags := func(cmd *cobra.Command) { + cmd.Flags().String("id", "", "ID to filter [optional]") + cmd.Flags().String("name", "", "name to filter [optional]") + cmd.Flags().String("description", "", "description to filter [optional]") + cmd.Flags().String("partition", "", "partition to filter [optional]") + cmd.Flags().String("project", "", "project to filter [optional]") + cmd.Flags().StringSlice("prefixes", []string{}, "prefixes to filter") + cmd.Flags().StringSlice("destination-prefixes", []string{}, "destination prefixes to filter") + cmd.Flags().String("addressfamily", "", "addressfamily to filter, either ipv4 or ipv6 [optional]") + cmd.Flags().Uint32("vrf", 0, "vrf to filter [optional]") + cmd.Flags().StringSlice("labels", nil, "labels to filter [optional]") + cmd.Flags().StringP("type", "t", "", "type of the network. [optional]") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.PartitionListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.NetworkAddressFamilyCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("type", c.Completion.NetworkTypeCompletion)) + } + + cmdsConfig := &genericcli.CmdsConfig[*adminv2.NetworkServiceCreateRequest, *adminv2.NetworkServiceUpdateRequest, *apiv2.Network]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), + Singular: "network", + Plural: "networks", + Description: "networks can be attached to a machine or firewall such that they can communicate with each other.", + CreateRequestFromCLI: w.createRequestFromCLI, + UpdateRequestFromCLI: w.updateRequestFromCLI, + Sorter: sorters.NetworkSorter(), + ValidArgsFn: c.Completion.NetworkAdminListCompletion, + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + CreateCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("id", "", "id of the network to create, defaults to a random uuid if not provided. [optional]") + cmd.Flags().String("name", "", "name of the network to create. [required]") + cmd.Flags().StringP("type", "t", "", "type of the network. [required]") + cmd.Flags().String("nat-type", "", "nat-type of the network. [required]") + cmd.Flags().String("partition", "", "partition where this network should exist. [required]") + cmd.Flags().String("project", "", "partition where this network should exist (alternative to parent-network-id). [optional]") + cmd.Flags().String("parent-network-id", "", "the parent of the network (alternative to partition). [optional]") + cmd.Flags().String("description", "", "description of the network to create. [optional]") + cmd.Flags().StringSlice("labels", nil, "labels for this network. [optional]") + cmd.Flags().String("addressfamily", "", "addressfamily of the network to acquire, if not specified the network inherits the address families from the parent [optional]") + cmd.Flags().Uint32("ipv4-prefix-length", 0, "ipv4 prefix bit length of the network to create, defaults to default child prefix length of the parent network. [optional]") + cmd.Flags().Uint32("ipv6-prefix-length", 0, "ipv6 prefix bit length of the network to create, defaults to default child prefix length of the parent network. [optional]") + cmd.Flags().Uint32("default-ipv4-prefix-length", 0, "default ipv4 prefix bit length of the network to create. [optional]") + cmd.Flags().Uint32("default-ipv6-prefix-length", 0, "default ipv6 prefix bit length of the network to create. [optional]") + cmd.Flags().Uint32("min-ipv4-prefix-length", 0, "min ipv4 prefix bit length of the network to create. [optional]") + cmd.Flags().Uint32("min-ipv6-prefix-length", 0, "min ipv6 prefix bit length of the network to create. [optional]") + cmd.Flags().StringSlice("prefixes", nil, "prefixes for this network. [optional]") + cmd.Flags().StringSlice("destination-prefixes", nil, "destination-prefixes for this network. [optional]") + cmd.Flags().StringSlice("additional-announcable-cidrs", nil, "additional-announcable-cidrs for this network. [optional]") + cmd.Flags().Uint32("vrf", 0, "the vrf of the network to create. [optional]") + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.PartitionListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.NetworkAddressFamilyCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("type", c.Completion.NetworkTypeCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("nat-type", c.Completion.NetworkNatTypeCompletion)) + }, + ListCmdMutateFn: func(cmd *cobra.Command) { + listFlags(cmd) + cmd.Flags().String("parent-network-id", "", "parent network to filter [optional]") + }, + UpdateCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("name", "", "the name of the network [optional]") + cmd.Flags().String("description", "", "the description of the network [optional]") + cmd.Flags().StringSlice("labels", nil, "the labels of the network, must be in the form of key=value, use it like: --labels \"key1=value1,key2=value2\". [optional]") + cmd.Flags().String("project", "", "project to filter [optional]") + }, + } + + return genericcli.NewCmds(cmdsConfig) +} + +func (c *networkCmd) Get(id string) (*apiv2.Network, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().Network().Get(ctx, &adminv2.NetworkServiceGetRequest{ + Id: id, + }) + + if err != nil { + return nil, err + } + + return resp.Network, nil +} + +func (c *networkCmd) List() ([]*apiv2.Network, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + var nwType *apiv2.NetworkType + if viper.IsSet("type") { + nt, err := enum.GetEnum[apiv2.NetworkType](viper.GetString("type")) + if err != nil { + return nil, err + } + nwType = &nt + } + + resp, err := c.c.Client.Adminv2().Network().List(ctx, &adminv2.NetworkServiceListRequest{ + Query: &apiv2.NetworkQuery{ + Id: pointer.PointerOrNil(viper.GetString("id")), + Name: pointer.PointerOrNil(viper.GetString("name")), + Description: pointer.PointerOrNil(viper.GetString("description")), + Partition: pointer.PointerOrNil(viper.GetString("partition")), + Project: pointer.PointerOrNil(viper.GetString("project")), + Prefixes: viper.GetStringSlice("prefixes"), + DestinationPrefixes: viper.GetStringSlice("destination-prefixes"), + Vrf: pointer.PointerOrNil(viper.GetUint32("vrf")), + ParentNetwork: pointer.PointerOrNil(viper.GetString("parent-network-id")), + AddressFamily: common.NetworkAddressFamilyToType(viper.GetString("addressfamily")), + Labels: &apiv2.Labels{ + Labels: tag.NewTagMap(viper.GetStringSlice("labels")), + }, + Type: nwType, + // NatType: (*apiv2.NATType)(nwType), + }, + }) + + if err != nil { + return nil, err + } + + return resp.Networks, nil +} + +func (c *networkCmd) Delete(id string) (*apiv2.Network, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().Network().Delete(ctx, &adminv2.NetworkServiceDeleteRequest{ + Id: id, + }) + if err != nil { + return nil, err + } + + return resp.Network, nil +} + +func (c *networkCmd) Create(rq *adminv2.NetworkServiceCreateRequest) (*apiv2.Network, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().Network().Create(ctx, rq) + if err != nil { + if s, ok := status.FromError(err); ok && s.Code() == codes.AlreadyExists { + return nil, genericcli.AlreadyExistsError() + } + return nil, err + } + + return resp.Network, nil +} + +func (c *networkCmd) Update(rq *adminv2.NetworkServiceUpdateRequest) (*apiv2.Network, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().Network().Update(ctx, rq) + if err != nil { + return nil, err + } + + return resp.Network, nil +} + +func (c *networkCmd) Convert(r *apiv2.Network) (string, *adminv2.NetworkServiceCreateRequest, *adminv2.NetworkServiceUpdateRequest, error) { + return r.Id, networkResponseToCreate(r), networkResponseToUpdate(r), nil +} + +func networkResponseToCreate(r *apiv2.Network) *adminv2.NetworkServiceCreateRequest { + meta := pointer.SafeDeref(r.Meta) + + return &adminv2.NetworkServiceCreateRequest{ + Project: r.Project, + Name: r.Name, + Description: r.Description, + Partition: r.Partition, + Labels: &apiv2.Labels{ + Labels: pointer.SafeDeref(meta.Labels).Labels, + }, + ParentNetwork: r.ParentNetwork, + // TODO: allow defining length and addressfamilies somehow? + } +} + +func networkResponseToUpdate(r *apiv2.Network) *adminv2.NetworkServiceUpdateRequest { + meta := pointer.SafeDeref(r.Meta) + + return &adminv2.NetworkServiceUpdateRequest{ + Id: r.Id, + Name: r.Name, + Description: r.Description, + Labels: &apiv2.UpdateLabels{ + Update: meta.Labels, // TODO: this only ensures that the labels are present but it does not cleanup old one's, which would require fetching the current state and calculating the diff + }, + Prefixes: r.Prefixes, + DestinationPrefixes: r.DestinationPrefixes, + DefaultChildPrefixLength: r.DefaultChildPrefixLength, + MinChildPrefixLength: r.MinChildPrefixLength, + // NatType: &0, + AdditionalAnnouncableCidrs: r.AdditionalAnnouncableCidrs, + Force: false, + } +} + +func (c *networkCmd) createRequestFromCLI() (*adminv2.NetworkServiceCreateRequest, error) { + labels, err := genericcli.LabelsToMap(viper.GetStringSlice("labels")) + if err != nil { + return nil, err + } + + var ( + natType = apiv2.NATType_NAT_TYPE_NONE + defaultCPL *apiv2.ChildPrefixLength + minCPL *apiv2.ChildPrefixLength + length *apiv2.ChildPrefixLength + ) + if viper.IsSet("default-ipv4-prefix-length") { + defaultCPL = &apiv2.ChildPrefixLength{ + Ipv4: pointer.Pointer(viper.GetUint32("default-ipv4-prefix-length")), + } + } + if viper.IsSet("default-ipv6-prefix-length") { + if defaultCPL == nil { + defaultCPL = &apiv2.ChildPrefixLength{} + } + defaultCPL.Ipv6 = pointer.Pointer(viper.GetUint32("default-ipv6-prefix-length")) + } + if viper.IsSet("min-ipv4-prefix-length") { + minCPL = &apiv2.ChildPrefixLength{ + Ipv4: pointer.Pointer(viper.GetUint32("min-ipv4-prefix-length")), + } + } + if viper.IsSet("min-ipv6-prefix-length") { + if minCPL == nil { + minCPL = &apiv2.ChildPrefixLength{} + } + minCPL.Ipv6 = pointer.Pointer(viper.GetUint32("min-ipv6-prefix-length")) + } + if viper.IsSet("ipv4-prefix-length") { + length = &apiv2.ChildPrefixLength{ + Ipv4: pointer.Pointer(viper.GetUint32("ipv4-prefix-length")), + } + } + if viper.IsSet("ipv6-prefix-length") { + if length == nil { + length = &apiv2.ChildPrefixLength{} + } + length.Ipv6 = pointer.Pointer(viper.GetUint32("ipv6-prefix-length")) + } + + nwType, err := enum.GetEnum[apiv2.NetworkType](viper.GetString("type")) + if err != nil { + return nil, err + } + + if viper.IsSet("nat-type") { + natType, err = enum.GetEnum[apiv2.NATType](viper.GetString("nat-type")) + if err != nil { + return nil, err + } + } + + var vrf *uint32 + if viper.IsSet("vrf") { + vrf = pointer.Pointer(viper.GetUint32("vrf")) + } + + return &adminv2.NetworkServiceCreateRequest{ + Description: pointer.PointerOrNil(viper.GetString("description")), + Name: pointer.PointerOrNil(viper.GetString("name")), + Project: pointer.PointerOrNil(viper.GetString("project")), + Partition: pointer.PointerOrNil(viper.GetString("partition")), + Labels: &apiv2.Labels{ + Labels: labels, + }, + ParentNetwork: pointer.PointerOrNil(viper.GetString("parent-network-id")), + AddressFamily: common.NetworkAddressFamilyToType(viper.GetString("addressfamily")), + Id: pointer.PointerOrNil(viper.GetString("id")), + Type: nwType, + Prefixes: viper.GetStringSlice("prefixes"), + DestinationPrefixes: viper.GetStringSlice("destination-prefixes"), + DefaultChildPrefixLength: defaultCPL, + MinChildPrefixLength: minCPL, + NatType: &natType, + Vrf: vrf, + AdditionalAnnouncableCidrs: viper.GetStringSlice("additional-announcable-cidrs"), + Length: length, + }, nil +} + +func (c *networkCmd) updateRequestFromCLI(args []string) (*adminv2.NetworkServiceUpdateRequest, error) { + id, err := genericcli.GetExactlyOneArg(args) + if err != nil { + return nil, err + } + + var labels *apiv2.UpdateLabels + if viper.IsSet("labels") { + lbls, err := genericcli.LabelsToMap(viper.GetStringSlice("labels")) + if err != nil { + return nil, err + } + + labels = &apiv2.UpdateLabels{ + Update: &apiv2.Labels{ + Labels: lbls, + }, + } + } + + var ( + natType = apiv2.NATType_NAT_TYPE_NONE + defaultCPL *apiv2.ChildPrefixLength + minCPL *apiv2.ChildPrefixLength + ) + if viper.IsSet("default-ipv4-prefix-length") { + defaultCPL = &apiv2.ChildPrefixLength{ + Ipv4: pointer.Pointer(viper.GetUint32("default-ipv4-prefix-length")), + } + } + if viper.IsSet("default-ipv6-prefix-length") { + if defaultCPL == nil { + defaultCPL = &apiv2.ChildPrefixLength{} + } + defaultCPL.Ipv6 = pointer.Pointer(viper.GetUint32("default-ipv6-prefix-length")) + } + if viper.IsSet("min-ipv4-prefix-length") { + minCPL = &apiv2.ChildPrefixLength{ + Ipv4: pointer.Pointer(viper.GetUint32("min-ipv4-prefix-length")), + } + } + if viper.IsSet("min-ipv6-prefix-length") { + if minCPL == nil { + minCPL = &apiv2.ChildPrefixLength{} + } + minCPL.Ipv6 = pointer.Pointer(viper.GetUint32("min-ipv6-prefix-length")) + } + + if viper.IsSet("nat-type") { + natType, err = enum.GetEnum[apiv2.NATType](viper.GetString("nat-type")) + if err != nil { + return nil, err + } + } + + var ( + ur = &adminv2.NetworkServiceUpdateRequest{ + Id: id, + Description: pointer.PointerOrNil(viper.GetString("description")), + Name: pointer.PointerOrNil(viper.GetString("name")), + Labels: labels, + Prefixes: viper.GetStringSlice("prefixes"), + DestinationPrefixes: viper.GetStringSlice("destination-prefixes"), + DefaultChildPrefixLength: defaultCPL, + MinChildPrefixLength: minCPL, + NatType: &natType, + AdditionalAnnouncableCidrs: viper.GetStringSlice("additional-announcable-cidrs"), + Force: viper.GetBool("force"), + } + ) + + return ur, nil +} diff --git a/cmd/admin/v2/size.go b/cmd/admin/v2/size.go new file mode 100644 index 0000000..962ad71 --- /dev/null +++ b/cmd/admin/v2/size.go @@ -0,0 +1,123 @@ +package v2 + +import ( + "fmt" + + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/metal-stack/metal-lib/pkg/pointer" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +type size struct { + c *config.Config +} + +func newSizeCmd(c *config.Config) *cobra.Command { + w := &size{ + c: c, + } + gcli := genericcli.NewGenericCLI(w).WithFS(c.Fs) + + cmdsConfig := &genericcli.CmdsConfig[*adminv2.SizeServiceCreateRequest, *adminv2.SizeServiceUpdateRequest, *apiv2.Size]{ + BinaryName: config.BinaryName, + GenericCLI: gcli, + Singular: "size", + Plural: "sizes", + Description: "manage sizes which defines the cpu, gpu, memory and storage properties of machines", + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + ValidArgsFn: c.Completion.SizeListCompletion, + OnlyCmds: genericcli.OnlyCmds(genericcli.CreateCmd, genericcli.UpdateCmd, genericcli.DeleteCmd, genericcli.EditCmd), + } + + return genericcli.NewCmds(cmdsConfig) +} + +func (c *size) Get(id string) (*apiv2.Size, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &apiv2.SizeServiceGetRequest{Id: id} + + resp, err := c.c.Client.Apiv2().Size().Get(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get size: %w", err) + } + + return resp.Size, nil +} + +func (c *size) Create(rq *adminv2.SizeServiceCreateRequest) (*apiv2.Size, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Adminv2().Size().Create(ctx, rq) + if err != nil { + return nil, fmt.Errorf("failed to get size: %w", err) + } + + return resp.Size, nil +} + +func (c *size) Delete(id string) (*apiv2.Size, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &adminv2.SizeServiceDeleteRequest{Id: id} + + resp, err := c.c.Client.Adminv2().Size().Delete(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to delete size: %w", err) + } + + return resp.Size, nil +} +func (c *size) List() ([]*apiv2.Size, error) { + panic("unimplemented") + +} +func (c *size) Convert(r *apiv2.Size) (string, *adminv2.SizeServiceCreateRequest, *adminv2.SizeServiceUpdateRequest, error) { + + return r.Id, &adminv2.SizeServiceCreateRequest{ + Size: &apiv2.Size{ + Id: r.Id, + Name: r.Name, + Description: r.Description, + Meta: r.Meta, + Constraints: r.Constraints, + }, + }, &adminv2.SizeServiceUpdateRequest{ + Id: r.Id, + Name: r.Name, + Description: r.Description, + Constraints: r.Constraints, + // FIXME + Labels: &apiv2.UpdateLabels{}, + }, nil + +} + +func (c *size) Update(rq *adminv2.SizeServiceUpdateRequest) (*apiv2.Size, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &adminv2.SizeServiceUpdateRequest{ + Id: viper.GetString("id"), + Name: pointer.PointerOrNil(viper.GetString("name")), + Description: pointer.PointerOrNil(viper.GetString("description")), + Constraints: rq.Constraints, + Labels: &apiv2.UpdateLabels{}, // FIXME + } + + resp, err := c.c.Client.Adminv2().Size().Update(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get size: %w", err) + } + + return resp.Size, nil +} diff --git a/cmd/admin/v2/task.go b/cmd/admin/v2/task.go new file mode 100644 index 0000000..2826138 --- /dev/null +++ b/cmd/admin/v2/task.go @@ -0,0 +1,126 @@ +package v2 + +import ( + "fmt" + "strings" + + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/metal-stack/metal-lib/pkg/pointer" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +type task struct { + c *config.Config +} + +func newTaskCmd(c *config.Config) *cobra.Command { + w := &task{ + c: c, + } + + cmdsConfig := &genericcli.CmdsConfig[any, any, *adminv2.TaskInfo]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), + Singular: "task", + Plural: "tasks", + Description: "get task insights", + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + DescribeCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("queue", "default", "the queue for which tasks should be described") + }, + ListCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("queue", "", "the queue for which tasks should be listed") + }, + DeleteCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("queue", "default", "the queue of the task which should be delete") + }, + OnlyCmds: genericcli.OnlyCmds(genericcli.ListCmd, genericcli.DescribeCmd, genericcli.DeleteCmd), + } + + queueCmd := &cobra.Command{ + Use: "queues", + Short: "list all queues", + RunE: func(cmd *cobra.Command, args []string) error { + return w.queues() + }, + } + + return genericcli.NewCmds(cmdsConfig, queueCmd) +} + +func (t *task) queues() error { + ctx, cancel := t.c.NewRequestContext() + defer cancel() + + req := &adminv2.TaskServiceQueuesRequest{} + + resp, err := t.c.Client.Adminv2().Task().Queues(ctx, req) + if err != nil { + return fmt.Errorf("failed to get task queues: %w", err) + } + + _, _ = fmt.Fprint(t.c.Out, strings.Join(resp.Queues, "\n")) + _, _ = fmt.Fprint(t.c.Out, "\n") + return err +} + +func (t *task) Get(id string) (*adminv2.TaskInfo, error) { + ctx, cancel := t.c.NewRequestContext() + defer cancel() + + req := &adminv2.TaskServiceGetRequest{TaskId: id, Queue: viper.GetString("queue")} + + resp, err := t.c.Client.Adminv2().Task().Get(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get task: %w", err) + } + + return resp.Task, nil +} +func (t *task) List() ([]*adminv2.TaskInfo, error) { + ctx, cancel := t.c.NewRequestContext() + defer cancel() + + req := &adminv2.TaskServiceListRequest{} + if viper.IsSet("queue") { + req.Queue = pointer.Pointer(viper.GetString("queue")) + } + + resp, err := t.c.Client.Adminv2().Task().List(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to list tasks: %w", err) + } + + return resp.Tasks, nil +} + +func (t *task) Create(rq any) (*adminv2.TaskInfo, error) { + panic("unimplemented") +} + +func (t *task) Delete(id string) (*adminv2.TaskInfo, error) { + ctx, cancel := t.c.NewRequestContext() + defer cancel() + + req := &adminv2.TaskServiceDeleteRequest{TaskId: id, Queue: viper.GetString("queue")} + + _, err := t.c.Client.Adminv2().Task().Delete(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get task: %w", err) + } + + return nil, nil +} + +func (t *task) Convert(r *adminv2.TaskInfo) (string, any, any, error) { + panic("unimplemented") +} + +func (t *task) Update(rq any) (*adminv2.TaskInfo, error) { + panic("unimplemented") +} diff --git a/cmd/admin/v2/token.go b/cmd/admin/v2/token.go index 67b015a..c835a12 100644 --- a/cmd/admin/v2/token.go +++ b/cmd/admin/v2/token.go @@ -28,7 +28,7 @@ func newTokenCmd(c *config.Config) *cobra.Command { GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), Singular: "token", Plural: "tokens", - Description: "manage api tokens for accessing the metal-stack.io api", + Description: "manage api tokens", Sorter: sorters.TokenSorter(), DescribePrinter: func() printers.Printer { return c.DescribePrinter }, ListPrinter: func() printers.Printer { return c.ListPrinter }, diff --git a/cmd/admin/v2/vpn.go b/cmd/admin/v2/vpn.go new file mode 100644 index 0000000..07cb1f4 --- /dev/null +++ b/cmd/admin/v2/vpn.go @@ -0,0 +1,115 @@ +package v2 + +import ( + "fmt" + "time" + + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/metal-stack/metal-lib/pkg/pointer" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "google.golang.org/protobuf/types/known/durationpb" +) + +type vpn struct { + c *config.Config +} + +func newVPNCmd(c *config.Config) *cobra.Command { + w := &vpn{ + c: c, + } + + cmdsConfig := &genericcli.CmdsConfig[any, any, *apiv2.VPNNode]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), + Singular: "vpn", + Plural: "vpn", + Description: "manage vpn keys and list nodes connected", + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + ListCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("project", "", "the project for which vpn nodes should be listed") + }, + OnlyCmds: genericcli.OnlyCmds(genericcli.ListCmd), + ValidArgsFn: w.c.Completion.ProjectListCompletion, + } + + authKeyCmd := &cobra.Command{ + Use: "authkey", + Short: "generate a authkey to connect to the vpn", + RunE: func(cmd *cobra.Command, args []string) error { + return w.authKey() + }, + ValidArgsFunction: c.Completion.ProjectListCompletion, + } + authKeyCmd.Flags().String("project", "", "the project for which the authkey should be generated") + authKeyCmd.Flags().Bool("ephemeral", true, "ephemeral defines if the key can only be used once") + authKeyCmd.Flags().Duration("expires", 1*time.Hour, "the duration after the generated key is not valid anymore") + genericcli.Must(authKeyCmd.MarkFlagRequired("project")) + + return genericcli.NewCmds(cmdsConfig, authKeyCmd) +} + +func (v *vpn) authKey() error { + ctx, cancel := v.c.NewRequestContext() + defer cancel() + + req := &adminv2.VPNServiceAuthKeyRequest{ + Project: viper.GetString("project"), + Ephemeral: viper.GetBool("ephemeral"), + Expires: durationpb.New(viper.GetDuration("expires")), + } + + resp, err := v.c.Client.Adminv2().VPN().AuthKey(ctx, req) + if err != nil { + return err + } + + _, _ = fmt.Fprintf(v.c.Out, "authkey: %s ephemeral:%t created at:%s expires at:%s\n", resp.AuthKey, resp.Ephemeral, resp.CreatedAt, resp.ExpiresAt) + _, _ = fmt.Fprintf(v.c.Out, "vpn endpoint: %s\n", resp.Address) + + return nil +} + +func (v *vpn) Get(id string) (*apiv2.VPNNode, error) { + panic("unimplemented") +} + +func (v *vpn) List() ([]*apiv2.VPNNode, error) { + ctx, cancel := v.c.NewRequestContext() + defer cancel() + + req := &adminv2.VPNServiceListNodesRequest{} + + if viper.IsSet("project") { + req.Project = pointer.Pointer(viper.GetString("project")) + } + + resp, err := v.c.Client.Adminv2().VPN().ListNodes(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to list vpn nodes: %w", err) + } + + return resp.Nodes, nil +} + +func (v *vpn) Create(rq any) (*apiv2.VPNNode, error) { + panic("unimplemented") +} + +func (v *vpn) Delete(id string) (*apiv2.VPNNode, error) { + panic("unimplemented") +} + +func (v *vpn) Convert(r *apiv2.VPNNode) (string, any, any, error) { + panic("unimplemented") +} + +func (v *vpn) Update(rq any) (*apiv2.VPNNode, error) { + panic("unimplemented") +} diff --git a/cmd/api/v2/commands.go b/cmd/api/v2/commands.go index be489cc..e80bbef 100644 --- a/cmd/api/v2/commands.go +++ b/cmd/api/v2/commands.go @@ -6,13 +6,16 @@ import ( ) func AddCmds(cmd *cobra.Command, c *config.Config) { - cmd.AddCommand(newVersionCmd(c)) cmd.AddCommand(newHealthCmd(c)) - cmd.AddCommand(newTokenCmd(c)) - cmd.AddCommand(newIPCmd(c)) cmd.AddCommand(newImageCmd(c)) + cmd.AddCommand(newIPCmd(c)) + cmd.AddCommand(newMethodsCmd(c)) + cmd.AddCommand(newNetworkCmd(c)) cmd.AddCommand(newProjectCmd(c)) + cmd.AddCommand(newSizeCmd(c)) cmd.AddCommand(newTenantCmd(c)) - cmd.AddCommand(newMethodsCmd(c)) + cmd.AddCommand(newTokenCmd(c)) cmd.AddCommand(newUserCmd(c)) + cmd.AddCommand(newVersionCmd(c)) + cmd.AddCommand(newMachineCmd(c)) } diff --git a/cmd/api/v2/ip.go b/cmd/api/v2/ip.go index 2274e94..0454e86 100644 --- a/cmd/api/v2/ip.go +++ b/cmd/api/v2/ip.go @@ -6,6 +6,7 @@ import ( apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" "github.com/metal-stack/cli/cmd/config" "github.com/metal-stack/cli/cmd/sorters" + "github.com/metal-stack/cli/pkg/common" "github.com/metal-stack/cli/pkg/helpers" "github.com/metal-stack/metal-lib/pkg/genericcli" "github.com/metal-stack/metal-lib/pkg/genericcli/printers" @@ -28,7 +29,7 @@ func newIPCmd(c *config.Config) *cobra.Command { GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), Singular: "ip", Plural: "ips", - Description: "an ip address of metal-stack.io", + Description: "manage ip addresses", Sorter: sorters.IPSorter(), DescribePrinter: func() printers.Printer { return c.DescribePrinter }, ListPrinter: func() printers.Printer { return c.ListPrinter }, @@ -46,7 +47,10 @@ func newIPCmd(c *config.Config) *cobra.Command { cmd.Flags().BoolP("static", "", false, "make this ip static") cmd.Flags().StringP("addressfamily", "", "", "addressfamily, can be either IPv4|IPv6, defaults to IPv4 (optional)") + genericcli.Must(cmd.RegisterFlagCompletionFunc("network", c.Completion.NetworkListCompletion)) genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.IpAddressFamilyCompletion)) + }, UpdateCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().StringP("project", "p", "", "project of the ip") @@ -59,6 +63,7 @@ func newIPCmd(c *config.Config) *cobra.Command { }, DescribeCmdMutateFn: func(cmd *cobra.Command) { cmd.Flags().StringP("project", "p", "", "project of the ip") + cmd.Flags().StringP("namespace", "n", "", "namespace of the ip") genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) }, @@ -74,8 +79,8 @@ func newIPCmd(c *config.Config) *cobra.Command { Description: pointer.Pointer(viper.GetString("description")), Network: viper.GetString("network"), // Labels: viper.GetStringSlice("tags"), // FIXME implement - Type: pointer.Pointer(ipStaticToType(viper.GetBool("static"))), - AddressFamily: addressFamilyToType(viper.GetString("addressfamily")), + Type: pointer.Pointer(common.IpStaticToType(viper.GetBool("static"))), + AddressFamily: common.IPAddressFamilyToType(viper.GetString("addressfamily")), }, nil }, UpdateRequestFromCLI: w.updateFromCLI, @@ -103,7 +108,7 @@ func (c *ip) updateFromCLI(args []string) (*apiv2.IPServiceUpdateRequest, error) ipToUpdate.Description = viper.GetString("description") } if viper.IsSet("static") { - ipToUpdate.Type = ipStaticToType(viper.GetBool("static")) + ipToUpdate.Type = common.IpStaticToType(viper.GetBool("static")) } // if viper.IsSet("tags") { // if ipToUpdate.Meta == nil { @@ -161,9 +166,17 @@ func (c *ip) Get(id string) (*apiv2.IP, error) { ctx, cancel := c.c.NewRequestContext() defer cancel() + var ( + namespace *string + ) + if viper.IsSet("namespace") { + namespace = pointer.Pointer(viper.GetString("namespace")) + } + resp, err := c.c.Client.Apiv2().IP().Get(ctx, &apiv2.IPServiceGetRequest{ - Project: c.c.GetProject(), - Ip: id, + Project: c.c.GetProject(), + Ip: id, + Namespace: namespace, }) if err != nil { return nil, err diff --git a/cmd/api/v2/machine.go b/cmd/api/v2/machine.go new file mode 100644 index 0000000..b7b9cbe --- /dev/null +++ b/cmd/api/v2/machine.go @@ -0,0 +1,108 @@ +package v2 + +import ( + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/cli/cmd/sorters" + "github.com/metal-stack/cli/pkg/helpers" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/spf13/cobra" +) + +type machine struct { + c *config.Config +} + +func newMachineCmd(c *config.Config) *cobra.Command { + w := &machine{ + c: c, + } + + cmdsConfig := &genericcli.CmdsConfig[*apiv2.MachineServiceCreateRequest, *apiv2.MachineServiceUpdateRequest, *apiv2.Machine]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), + Singular: "machine", + Plural: "machines", + Description: "an machine of metal-stack.io", + Sorter: sorters.MachineSorter(), + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + ListCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().StringP("project", "p", "", "project from where machines should be listed") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + }, + DescribeCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().StringP("project", "p", "", "project of the machine") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + }, + ValidArgsFn: c.Completion.MachineListCompletion, + } + + return genericcli.NewCmds(cmdsConfig) +} + +func (c *machine) updateFromCLI(args []string) (*apiv2.MachineServiceUpdateRequest, error) { + panic("unimplemented") +} + +func (c *machine) Create(rq *apiv2.MachineServiceCreateRequest) (*apiv2.Machine, error) { + panic("unimplemented") +} + +func (c *machine) Delete(id string) (*apiv2.Machine, error) { + panic("unimplemented") +} + +func (c *machine) Get(id string) (*apiv2.Machine, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Apiv2().Machine().Get(ctx, &apiv2.MachineServiceGetRequest{ + Project: c.c.GetProject(), + Uuid: id, + }) + if err != nil { + return nil, err + } + + return resp.Machine, nil +} + +func (c *machine) List() ([]*apiv2.Machine, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Apiv2().Machine().List(ctx, &apiv2.MachineServiceListRequest{ + Project: c.c.GetProject(), + Query: &apiv2.MachineQuery{ + // FIXME implement + }, + }) + if err != nil { + return nil, err + } + + return resp.Machines, nil +} + +func (c *machine) Update(rq *apiv2.MachineServiceUpdateRequest) (*apiv2.Machine, error) { + panic("unimplemented") +} + +func (c *machine) Convert(r *apiv2.Machine) (string, *apiv2.MachineServiceCreateRequest, *apiv2.MachineServiceUpdateRequest, error) { + responseToUpdate, err := c.MachineResponseToUpdate(r) + return helpers.EncodeProject(r.Uuid, r.Allocation.Project), c.MachineResponseToCreate(r), responseToUpdate, err +} + +func (c *machine) MachineResponseToCreate(r *apiv2.Machine) *apiv2.MachineServiceCreateRequest { + return &apiv2.MachineServiceCreateRequest{ + // FIXME + } +} + +func (c *machine) MachineResponseToUpdate(desired *apiv2.Machine) (*apiv2.MachineServiceUpdateRequest, error) { + panic("unimplemented") +} diff --git a/cmd/api/v2/network.go b/cmd/api/v2/network.go new file mode 100644 index 0000000..15082a6 --- /dev/null +++ b/cmd/api/v2/network.go @@ -0,0 +1,320 @@ +package v2 + +import ( + "github.com/metal-stack/api/go/enum" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/cli/cmd/sorters" + "github.com/metal-stack/cli/pkg/common" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/metal-stack/metal-lib/pkg/pointer" + "github.com/metal-stack/metal-lib/pkg/tag" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type networkCmd struct { + c *config.Config +} + +func newNetworkCmd(c *config.Config) *cobra.Command { + w := &networkCmd{ + c: c, + } + + listFlags := func(cmd *cobra.Command) { + cmd.Flags().String("id", "", "ID to filter [optional]") + cmd.Flags().String("name", "", "name to filter [optional]") + cmd.Flags().String("description", "", "description to filter [optional]") + cmd.Flags().String("partition", "", "partition to filter [optional]") + cmd.Flags().String("project", "", "project to filter [optional]") + cmd.Flags().StringSlice("prefixes", []string{}, "prefixes to filter") + cmd.Flags().StringSlice("destination-prefixes", []string{}, "destination prefixes to filter") + cmd.Flags().String("addressfamily", "", "addressfamily to filter, either ipv4 or ipv6 [optional]") + cmd.Flags().Uint32("vrf", 0, "vrf to filter [optional]") + cmd.Flags().StringSlice("labels", nil, "labels to filter [optional]") + cmd.Flags().StringP("type", "t", "", "type of the network. [optional]") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.PartitionListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.NetworkAddressFamilyCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("type", c.Completion.NetworkTypeCompletion)) + } + + cmdsConfig := &genericcli.CmdsConfig[*apiv2.NetworkServiceCreateRequest, *apiv2.NetworkServiceUpdateRequest, *apiv2.Network]{ + BinaryName: config.BinaryName, + GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), + Singular: "network", + Plural: "networks", + Description: "networks can be attached to a machine or firewall such that they can communicate with each other.", + CreateRequestFromCLI: w.createRequestFromCLI, + UpdateRequestFromCLI: w.updateRequestFromCLI, + Sorter: sorters.NetworkSorter(), + ValidArgsFn: c.Completion.NetworkListCompletion, + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + CreateCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("name", "", "name of the network to create. [required]") + cmd.Flags().String("partition", "", "partition where this network should exist. [required]") + cmd.Flags().String("project", "", "partition where this network should exist (alternative to parent-network-id). [optional]") + cmd.Flags().String("parent-network-id", "", "the parent of the network (alternative to partition). [optional]") + cmd.Flags().String("description", "", "description of the network to create. [optional]") + cmd.Flags().StringSlice("labels", nil, "labels for this network. [optional]") + cmd.Flags().String("addressfamily", "", "addressfamily of the network to acquire, if not specified the network inherits the address families from the parent [optional]") + cmd.Flags().Uint32("ipv4-prefix-length", 0, "ipv4 prefix bit length of the network to create, defaults to default child prefix length of the parent network. [optional]") + cmd.Flags().Uint32("ipv6-prefix-length", 0, "ipv6 prefix bit length of the network to create, defaults to default child prefix length of the parent network. [optional]") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.PartitionListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("addressfamily", c.Completion.NetworkAddressFamilyCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("parent-network-id", c.Completion.NetworkListCompletion)) + }, + ListCmdMutateFn: func(cmd *cobra.Command) { + listFlags(cmd) + cmd.Flags().String("parent-network-id", "", "parent network to filter [optional]") + }, + UpdateCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("name", "", "the name of the network [optional]") + cmd.Flags().String("description", "", "the description of the network [optional]") + cmd.Flags().StringSlice("labels", nil, "the labels of the network, must be in the form of key=value, use it like: --labels \"key1=value1,key2=value2\". [optional]") + cmd.Flags().String("project", "", "project to filter [optional]") + }, + } + + listBaseNetworksCmd := &cobra.Command{ + Use: "list-base-networks", + Short: "lists base networks that can be used for network creation", + RunE: func(cmd *cobra.Command, _ []string) error { + return w.listBaseNetworks() + }, + ValidArgsFunction: c.Completion.TenantMemberListCompletion, + } + listFlags(listBaseNetworksCmd) + + return genericcli.NewCmds(cmdsConfig, listBaseNetworksCmd) +} + +func (c *networkCmd) Get(id string) (*apiv2.Network, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Apiv2().Network().Get(ctx, &apiv2.NetworkServiceGetRequest{ + Id: id, + Project: c.c.GetProject(), + }) + if err != nil { + return nil, err + } + + return resp.Network, nil +} + +func (c *networkCmd) List() ([]*apiv2.Network, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Apiv2().Network().List(ctx, &apiv2.NetworkServiceListRequest{ + Project: c.c.GetProject(), + Query: &apiv2.NetworkQuery{ + Id: pointer.PointerOrNil(viper.GetString("id")), + Name: pointer.PointerOrNil(viper.GetString("name")), + Description: pointer.PointerOrNil(viper.GetString("description")), + Partition: pointer.PointerOrNil(viper.GetString("partition")), + Project: pointer.Pointer(c.c.GetProject()), + Prefixes: viper.GetStringSlice("prefixes"), + DestinationPrefixes: viper.GetStringSlice("destination-prefixes"), + Vrf: pointer.PointerOrNil(viper.GetUint32("vrf")), + ParentNetwork: pointer.PointerOrNil(viper.GetString("parent-network-id")), + AddressFamily: common.NetworkAddressFamilyToType(viper.GetString("addressfamily")), + Labels: &apiv2.Labels{ + Labels: tag.NewTagMap(viper.GetStringSlice("labels")), + }, + }, + }) + + if err != nil { + return nil, err + } + + return resp.Networks, nil +} + +func (c *networkCmd) Delete(id string) (*apiv2.Network, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Apiv2().Network().Delete(ctx, &apiv2.NetworkServiceDeleteRequest{ + Id: id, + Project: c.c.GetProject(), + }) + if err != nil { + return nil, err + } + + return resp.Network, nil +} + +func (c *networkCmd) Create(rq *apiv2.NetworkServiceCreateRequest) (*apiv2.Network, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Apiv2().Network().Create(ctx, rq) + if err != nil { + if s, ok := status.FromError(err); ok && s.Code() == codes.AlreadyExists { + return nil, genericcli.AlreadyExistsError() + } + return nil, err + } + + return resp.Network, nil +} + +func (c *networkCmd) Update(rq *apiv2.NetworkServiceUpdateRequest) (*apiv2.Network, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + resp, err := c.c.Client.Apiv2().Network().Update(ctx, rq) + if err != nil { + return nil, err + } + + return resp.Network, nil +} + +func (c *networkCmd) Convert(r *apiv2.Network) (string, *apiv2.NetworkServiceCreateRequest, *apiv2.NetworkServiceUpdateRequest, error) { + return r.Id, networkResponseToCreate(r), networkResponseToUpdate(r), nil +} + +func networkResponseToCreate(r *apiv2.Network) *apiv2.NetworkServiceCreateRequest { + meta := pointer.SafeDeref(r.Meta) + + return &apiv2.NetworkServiceCreateRequest{ + Project: pointer.SafeDeref(r.Project), + Name: r.Name, + Description: r.Description, + Partition: r.Partition, + Labels: &apiv2.Labels{ + Labels: pointer.SafeDeref(meta.Labels).Labels, + }, + ParentNetwork: r.ParentNetwork, + // TODO: allow defining length and addressfamilies somehow? + } +} + +func networkResponseToUpdate(r *apiv2.Network) *apiv2.NetworkServiceUpdateRequest { + meta := pointer.SafeDeref(r.Meta) + + return &apiv2.NetworkServiceUpdateRequest{ + Id: r.Id, + Project: pointer.SafeDeref(r.Project), + Name: r.Name, + Description: r.Description, + Labels: &apiv2.UpdateLabels{ + Update: meta.Labels, // TODO: this only ensures that the labels are present but it does not cleanup old one's, which would require fetching the current state and calculating the diff + }} +} + +func (c *networkCmd) createRequestFromCLI() (*apiv2.NetworkServiceCreateRequest, error) { + labels, err := genericcli.LabelsToMap(viper.GetStringSlice("labels")) + if err != nil { + return nil, err + } + + var ( + cpl = &apiv2.ChildPrefixLength{} + ) + if viper.IsSet("ipv4-prefix-length") { + cpl.Ipv4 = pointer.Pointer(viper.GetUint32("ipv4-prefix-length")) + } + if viper.IsSet("ipv6-prefix-length") { + cpl.Ipv6 = pointer.Pointer(viper.GetUint32("ipv6-prefix-length")) + } + + return &apiv2.NetworkServiceCreateRequest{ + Description: pointer.PointerOrNil(viper.GetString("description")), + Name: pointer.PointerOrNil(viper.GetString("name")), + Project: c.c.GetProject(), + Partition: pointer.PointerOrNil(viper.GetString("partition")), + Labels: &apiv2.Labels{ + Labels: labels, + }, + ParentNetwork: pointer.PointerOrNil(viper.GetString("parent-network-id")), + Length: cpl, + AddressFamily: common.NetworkAddressFamilyToType(viper.GetString("addressfamily")), + }, nil +} + +func (c *networkCmd) updateRequestFromCLI(args []string) (*apiv2.NetworkServiceUpdateRequest, error) { + id, err := genericcli.GetExactlyOneArg(args) + if err != nil { + return nil, err + } + + var labels *apiv2.UpdateLabels + if viper.IsSet("labels") { + lbls, err := genericcli.LabelsToMap(viper.GetStringSlice("labels")) + if err != nil { + return nil, err + } + + labels = &apiv2.UpdateLabels{ + Update: &apiv2.Labels{ + Labels: lbls, + }, + } + } + + var ( + ur = &apiv2.NetworkServiceUpdateRequest{ + Id: id, + Project: c.c.GetProject(), + Description: pointer.PointerOrNil(viper.GetString("description")), + Name: pointer.PointerOrNil(viper.GetString("name")), + Labels: labels, + } + ) + + return ur, nil +} + +func (c *networkCmd) listBaseNetworks() error { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + var nwType *apiv2.NetworkType + if viper.IsSet("type") { + nt, err := enum.GetEnum[apiv2.NetworkType](viper.GetString("type")) + if err != nil { + return err + } + nwType = &nt + } + + resp, err := c.c.Client.Apiv2().Network().ListBaseNetworks(ctx, &apiv2.NetworkServiceListBaseNetworksRequest{ + Project: c.c.GetProject(), + Query: &apiv2.NetworkQuery{ + Id: pointer.PointerOrNil(viper.GetString("id")), + Name: pointer.PointerOrNil(viper.GetString("name")), + Description: pointer.PointerOrNil(viper.GetString("description")), + Partition: pointer.PointerOrNil(viper.GetString("partition")), + Project: pointer.PointerOrNil(viper.GetString("project")), + Prefixes: viper.GetStringSlice("prefixes"), + DestinationPrefixes: viper.GetStringSlice("destination-prefixes"), + Vrf: pointer.PointerOrNil(viper.GetUint32("vrf")), + AddressFamily: common.NetworkAddressFamilyToType(viper.GetString("addressfamily")), + Labels: &apiv2.Labels{ + Labels: tag.NewTagMap(viper.GetStringSlice("labels")), + }, + Type: nwType, + }, + }) + + if err != nil { + return err + } + + return c.c.ListPrinter.Print(resp.Networks) +} diff --git a/cmd/api/v2/size.go b/cmd/api/v2/size.go new file mode 100644 index 0000000..dc6cd13 --- /dev/null +++ b/cmd/api/v2/size.go @@ -0,0 +1,92 @@ +package v2 + +import ( + "fmt" + + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/metal-stack/metal-lib/pkg/pointer" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +type size struct { + c *config.Config +} + +func newSizeCmd(c *config.Config) *cobra.Command { + w := &size{ + c: c, + } + + gcli := genericcli.NewGenericCLI(w).WithFS(c.Fs) + + cmdsConfig := &genericcli.CmdsConfig[any, any, *apiv2.Size]{ + BinaryName: config.BinaryName, + GenericCLI: gcli, + Singular: "size", + Plural: "sizes", + Description: "manage sizes which defines the cpu, gpu, memory and storage properties of machines", + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + ValidArgsFn: c.Completion.SizeListCompletion, + OnlyCmds: genericcli.OnlyCmds(genericcli.DescribeCmd, genericcli.ListCmd), + ListCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().StringP("id", "", "", "size id to filter for") + cmd.Flags().StringP("name", "", "", "size name to filter for") + cmd.Flags().StringP("description", "", "", "size description to filter for") + }, + } + + return genericcli.NewCmds(cmdsConfig) +} + +func (c *size) Get(id string) (*apiv2.Size, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &apiv2.SizeServiceGetRequest{Id: id} + + resp, err := c.c.Client.Apiv2().Size().Get(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get size: %w", err) + } + + return resp.Size, nil +} + +func (c *size) List() ([]*apiv2.Size, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &apiv2.SizeServiceListRequest{Query: &apiv2.SizeQuery{ + Id: pointer.PointerOrNil(viper.GetString("id")), + Name: pointer.PointerOrNil(viper.GetString("name")), + Description: pointer.PointerOrNil(viper.GetString("description")), + }} + + resp, err := c.c.Client.Apiv2().Size().List(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get sizes: %w", err) + } + + return resp.Sizes, nil +} + +func (c *size) Create(rq any) (*apiv2.Size, error) { + panic("unimplemented") +} + +func (c *size) Delete(id string) (*apiv2.Size, error) { + panic("unimplemented") +} + +func (t *size) Convert(r *apiv2.Size) (string, any, any, error) { + panic("unimplemented") +} + +func (t *size) Update(rq any) (*apiv2.Size, error) { + panic("unimplemented") +} diff --git a/cmd/api/v2/token.go b/cmd/api/v2/token.go index 425a8f8..da097c8 100644 --- a/cmd/api/v2/token.go +++ b/cmd/api/v2/token.go @@ -30,7 +30,7 @@ func newTokenCmd(c *config.Config) *cobra.Command { GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs), Singular: "token", Plural: "tokens", - Description: "manage api tokens for accessing the metal-stack.io api", + Description: "manage api tokens", Sorter: sorters.TokenSorter(), DescribePrinter: func() printers.Printer { return c.DescribePrinter }, ListPrinter: func() printers.Printer { return c.ListPrinter }, diff --git a/cmd/completion/ip.go b/cmd/completion/ip.go index b433db5..795f7eb 100644 --- a/cmd/completion/ip.go +++ b/cmd/completion/ip.go @@ -1,6 +1,7 @@ package completion import ( + "github.com/metal-stack/api/go/enum" apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" "github.com/spf13/cobra" ) @@ -19,3 +20,18 @@ func (c *Completion) IpListCompletion(cmd *cobra.Command, args []string, toCompl } return names, cobra.ShellCompDirectiveNoFileComp } + +func (c *Completion) IpAddressFamilyCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + var afs []string + for _, af := range []apiv2.IPAddressFamily{ + apiv2.IPAddressFamily_IP_ADDRESS_FAMILY_V4, + apiv2.IPAddressFamily_IP_ADDRESS_FAMILY_V6} { + stringValue, err := enum.GetStringValue(af) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + afs = append(afs, *stringValue) + } + + return afs, cobra.ShellCompDirectiveNoFileComp +} diff --git a/cmd/completion/machine.go b/cmd/completion/machine.go new file mode 100644 index 0000000..d54f93d --- /dev/null +++ b/cmd/completion/machine.go @@ -0,0 +1,35 @@ +package completion + +import ( + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/spf13/cobra" +) + +func (c *Completion) MachineListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + req := &apiv2.MachineServiceListRequest{ + Project: c.Project, + } + resp, err := c.Client.Apiv2().Machine().List(c.Ctx, req) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + var names []string + for _, s := range resp.Machines { + names = append(names, s.Uuid) + } + return names, cobra.ShellCompDirectiveNoFileComp +} + +func (c *Completion) BMCCommandListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{ + apiv2.MachineBMCCommand_MACHINE_BMC_COMMAND_ON.String(), + apiv2.MachineBMCCommand_MACHINE_BMC_COMMAND_OFF.String(), + apiv2.MachineBMCCommand_MACHINE_BMC_COMMAND_RESET.String(), + apiv2.MachineBMCCommand_MACHINE_BMC_COMMAND_BOOT_FROM_DISK.String(), + apiv2.MachineBMCCommand_MACHINE_BMC_COMMAND_BOOT_FROM_PXE.String(), + apiv2.MachineBMCCommand_MACHINE_BMC_COMMAND_BOOT_TO_BIOS.String(), + apiv2.MachineBMCCommand_MACHINE_BMC_COMMAND_CYCLE.String(), + apiv2.MachineBMCCommand_MACHINE_BMC_COMMAND_IDENTIFY_LED_OFF.String(), + apiv2.MachineBMCCommand_MACHINE_BMC_COMMAND_IDENTIFY_LED_ON.String(), + }, cobra.ShellCompDirectiveNoFileComp +} diff --git a/cmd/completion/network.go b/cmd/completion/network.go new file mode 100644 index 0000000..962ce64 --- /dev/null +++ b/cmd/completion/network.go @@ -0,0 +1,85 @@ +package completion + +import ( + "github.com/metal-stack/api/go/enum" + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/metal-lib/pkg/pointer" + "github.com/spf13/cobra" +) + +func (c *Completion) NetworkListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ownNetworks, err := c.Client.Apiv2().Network().List(c.Ctx, &apiv2.NetworkServiceListRequest{ + Project: c.Project, + }) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + baseNetworks, err := c.Client.Apiv2().Network().ListBaseNetworks(c.Ctx, &apiv2.NetworkServiceListBaseNetworksRequest{ + Project: c.Project, + }) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + var names []string + for _, s := range baseNetworks.Networks { + names = append(names, s.Id+"\t"+pointer.SafeDeref(s.Name)) + } + for _, s := range ownNetworks.Networks { + names = append(names, s.Id+"\t"+pointer.SafeDeref(s.Name)) + } + + return names, cobra.ShellCompDirectiveNoFileComp +} + +func (c *Completion) NetworkTypeCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + var names []string + for _, val := range apiv2.NetworkType_value { + if e, err := enum.GetStringValue(apiv2.NetworkType(val)); err == nil { + names = append(names, *e) + } + } + return names, cobra.ShellCompDirectiveNoFileComp +} + +func (c *Completion) NetworkNatTypeCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + var names []string + for _, val := range apiv2.NATType_value { + if e, err := enum.GetStringValue(apiv2.NATType(val)); err == nil { + names = append(names, *e) + } + } + return names, cobra.ShellCompDirectiveNoFileComp +} + +func (c *Completion) NetworkAddressFamilyCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + var afs []string + for _, af := range []apiv2.NetworkAddressFamily{ + apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_DUAL_STACK, + apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_V4, + apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_V6} { + stringValue, err := enum.GetStringValue(af) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + afs = append(afs, *stringValue) + } + + return afs, cobra.ShellCompDirectiveNoFileComp +} + +func (c *Completion) NetworkAdminListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + networks, err := c.Client.Adminv2().Network().List(c.Ctx, &adminv2.NetworkServiceListRequest{}) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + var names []string + for _, s := range networks.Networks { + names = append(names, s.Id+"\t"+pointer.SafeDeref(s.Name)) + } + + return names, cobra.ShellCompDirectiveNoFileComp +} diff --git a/cmd/completion/partition.go b/cmd/completion/partition.go new file mode 100644 index 0000000..f3b0c26 --- /dev/null +++ b/cmd/completion/partition.go @@ -0,0 +1,20 @@ +package completion + +import ( + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/spf13/cobra" +) + +func (c *Completion) PartitionListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + resp, err := c.Client.Apiv2().Partition().List(c.Ctx, &apiv2.PartitionServiceListRequest{}) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + var names []string + for _, s := range resp.Partitions { + names = append(names, s.Id+"\t"+s.Description) + } + + return names, cobra.ShellCompDirectiveNoFileComp +} diff --git a/cmd/completion/size.go b/cmd/completion/size.go new file mode 100644 index 0000000..8ebd814 --- /dev/null +++ b/cmd/completion/size.go @@ -0,0 +1,19 @@ +package completion + +import ( + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/spf13/cobra" +) + +func (c *Completion) SizeListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + req := &apiv2.SizeServiceListRequest{} + resp, err := c.Client.Apiv2().Size().List(c.Ctx, req) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + var names []string + for _, s := range resp.Sizes { + names = append(names, s.Id) + } + return names, cobra.ShellCompDirectiveNoFileComp +} diff --git a/cmd/completion/token.go b/cmd/completion/token.go index b7498f5..70cc895 100644 --- a/cmd/completion/token.go +++ b/cmd/completion/token.go @@ -7,6 +7,7 @@ import ( "github.com/spf13/cobra" apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/api/go/permissions" ) func (c *Completion) TokenListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { @@ -66,11 +67,8 @@ func (c *Completion) TokenAdminRoleCompletion(cmd *cobra.Command, args []string, } func (c *Completion) TokenPermissionsCompletionfunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - methods, err := c.Client.Apiv2().Method().TokenScopedList(c.Ctx, &apiv2.MethodServiceTokenScopedListRequest{}) - if err != nil { - return nil, cobra.ShellCompDirectiveError - } + methods := permissions.GetServicePermissions().Methods subject := "" if s, _, ok := strings.Cut(toComplete, "="); ok { subject = s @@ -79,19 +77,17 @@ func (c *Completion) TokenPermissionsCompletionfunc(cmd *cobra.Command, args []s if subject == "" { var perms []string - for _, p := range methods.Permissions { - perms = append(perms, p.Subject) + for p := range methods { + perms = append(perms, p) } return perms, cobra.ShellCompDirectiveNoFileComp } - // FIXME: completion does not work at this point, investigate why - var perms []string - for _, p := range methods.Permissions { - perms = append(perms, p.Methods...) + for p := range methods { + perms = append(perms, p) } return perms, cobra.ShellCompDirectiveDefault diff --git a/cmd/printers.go b/cmd/printers.go index 3668f2c..2e27033 100644 --- a/cmd/printers.go +++ b/cmd/printers.go @@ -32,6 +32,7 @@ func newPrinterFromCLI(out io.Writer) (printers.Printer, error) { } tablePrinter := printers.NewTablePrinter(cfg).WithOut(out) tp.SetPrinter(tablePrinter) + tp.SetLastEventErrorThreshold(viper.GetDuration("last-event-error-threshold")) printer = tablePrinter case "template": printer = printers.NewTemplatePrinter(viper.GetString("template")).WithOut(out) diff --git a/cmd/root.go b/cmd/root.go index 5692737..f561d97 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,6 +1,7 @@ package cmd import ( + "context" "log/slog" "os" @@ -16,7 +17,6 @@ import ( "github.com/spf13/cobra" "github.com/spf13/cobra/doc" "github.com/spf13/viper" - "golang.org/x/net/context" ) func Execute() { diff --git a/cmd/sorters/machine.go b/cmd/sorters/machine.go new file mode 100644 index 0000000..e1ba415 --- /dev/null +++ b/cmd/sorters/machine.go @@ -0,0 +1,33 @@ +package sorters + +import ( + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/metal-lib/pkg/multisort" + "github.com/metal-stack/metal-lib/pkg/pointer" +) + +func MachineSorter() *multisort.Sorter[*apiv2.Machine] { + return multisort.New(multisort.FieldMap[*apiv2.Machine]{ + "partition": func(a, b *apiv2.Machine, descending bool) multisort.CompareResult { + return multisort.Compare(a.Partition.Id, b.Partition.Id, descending) + }, + "size": func(a, b *apiv2.Machine, descending bool) multisort.CompareResult { + return multisort.Compare(a.Size.Id, b.Size.Id, descending) + }, + "uuid": func(a, b *apiv2.Machine, descending bool) multisort.CompareResult { + return multisort.Compare(a.Uuid, b.Uuid, descending) + }, + "image": func(a, b *apiv2.Machine, descending bool) multisort.CompareResult { + return multisort.Compare(pointer.SafeDeref(a.Allocation).Image.Id, pointer.SafeDeref(b.Allocation).Image.Id, descending) + }, + "rack": func(a, b *apiv2.Machine, descending bool) multisort.CompareResult { + return multisort.Compare(a.Rack, b.Rack, descending) + }, + "project": func(a, b *apiv2.Machine, descending bool) multisort.CompareResult { + return multisort.Compare(pointer.SafeDeref(a.Allocation).Project, pointer.SafeDeref(b.Allocation).Project, descending) + }, + "age": func(a, b *apiv2.Machine, descending bool) multisort.CompareResult { + return multisort.Compare(pointer.SafeDeref(a.Allocation).Meta.CreatedAt.AsTime().Unix(), pointer.SafeDeref(b.Allocation).Meta.CreatedAt.AsTime().Unix(), descending) + }, + }, multisort.Keys{{ID: "uuid"}, {ID: "size"}, {ID: "partition"}}) +} diff --git a/cmd/sorters/network.go b/cmd/sorters/network.go new file mode 100644 index 0000000..a490f7b --- /dev/null +++ b/cmd/sorters/network.go @@ -0,0 +1,27 @@ +package sorters + +import ( + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/metal-lib/pkg/multisort" + "github.com/metal-stack/metal-lib/pkg/pointer" +) + +func NetworkSorter() *multisort.Sorter[*apiv2.Network] { + return multisort.New(multisort.FieldMap[*apiv2.Network]{ + "id": func(a, b *apiv2.Network, descending bool) multisort.CompareResult { + return multisort.Compare(a.Id, b.Id, descending) + }, + "name": func(a, b *apiv2.Network, descending bool) multisort.CompareResult { + return multisort.Compare(pointer.SafeDeref(a.Name), pointer.SafeDeref(b.Name), descending) + }, + "description": func(a, b *apiv2.Network, descending bool) multisort.CompareResult { + return multisort.Compare(pointer.SafeDeref(a.Description), pointer.SafeDeref(b.Description), descending) + }, + "partition": func(a, b *apiv2.Network, descending bool) multisort.CompareResult { + return multisort.Compare(pointer.SafeDeref(a.Partition), pointer.SafeDeref(b.Partition), descending) + }, + "project": func(a, b *apiv2.Network, descending bool) multisort.CompareResult { + return multisort.Compare(pointer.SafeDeref(a.Project), pointer.SafeDeref(b.Project), descending) + }, + }, multisort.Keys{{ID: "partition"}, {ID: "id"}}) +} diff --git a/cmd/tableprinters/common.go b/cmd/tableprinters/common.go index 19c30e7..77b97cc 100644 --- a/cmd/tableprinters/common.go +++ b/cmd/tableprinters/common.go @@ -6,20 +6,35 @@ import ( "strings" "time" + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" "github.com/metal-stack/cli/cmd/config" "github.com/metal-stack/metal-lib/pkg/genericcli/printers" "github.com/metal-stack/metal-lib/pkg/pointer" ) +const ( + dot = "●" + halfpie = "◒" + threequarterpie = "◕" + nbr = " " + poweron = "⏻" + powersleep = "⏾" +) + type TablePrinter struct { - t *printers.TablePrinter + t *printers.TablePrinter + lastEventErrorThreshold time.Duration } func New() *TablePrinter { return &TablePrinter{} } +func (t *TablePrinter) SetLastEventErrorThreshold(threshold time.Duration) { + t.lastEventErrorThreshold = threshold +} + func (t *TablePrinter) SetPrinter(printer *printers.TablePrinter) { t.t = printer } @@ -40,6 +55,21 @@ func (t *TablePrinter) ToHeaderAndRows(data any, wide bool) ([]string, [][]strin case []*apiv2.Image: return t.ImageTable(d, wide) + case *apiv2.Size: + return t.SizeTable(pointer.WrapInSlice(d), wide) + case []*apiv2.Size: + return t.SizeTable(d, wide) + + case *apiv2.Machine: + return t.MachineTable(pointer.WrapInSlice(d), wide) + case []*apiv2.Machine: + return t.MachineTable(d, wide) + + case *apiv2.Network: + return t.NetworkTable(pointer.WrapInSlice(d), wide) + case []*apiv2.Network: + return t.NetworkTable(d, wide) + case *apiv2.Project: return t.ProjectTable(pointer.WrapInSlice(d), wide) case []*apiv2.Project: @@ -53,11 +83,21 @@ func (t *TablePrinter) ToHeaderAndRows(data any, wide bool) ([]string, [][]strin case []*apiv2.ProjectMember: return t.ProjectMemberTable(d, wide) + case *adminv2.TaskInfo: + return t.TaskTable(pointer.WrapInSlice(d), wide) + case []*adminv2.TaskInfo: + return t.TaskTable(d, wide) + case *apiv2.Token: return t.TokenTable(pointer.WrapInSlice(d), wide) case []*apiv2.Token: return t.TokenTable(d, wide) + case *apiv2.VPNNode: + return t.VPNTable(pointer.WrapInSlice(d), wide) + case []*apiv2.VPNNode: + return t.VPNTable(d, wide) + case *apiv2.Tenant: return t.TenantTable(pointer.WrapInSlice(d), wide) case []*apiv2.Tenant: @@ -116,3 +156,14 @@ func humanizeDuration(duration time.Duration) string { } return strings.Join(parts, " ") } + +func getMaxLineCount(ss ...string) int { + max := 0 + for _, s := range ss { + c := strings.Count(s, "\n") + if c > max { + max = c + } + } + return max +} diff --git a/cmd/tableprinters/ip.go b/cmd/tableprinters/ip.go index fdefa24..696ccae 100644 --- a/cmd/tableprinters/ip.go +++ b/cmd/tableprinters/ip.go @@ -5,16 +5,17 @@ import ( "strings" apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/metal-lib/pkg/pointer" ) func (t *TablePrinter) IPTable(data []*apiv2.IP, wide bool) ([]string, [][]string, error) { var ( rows [][]string - header = []string{"IP", "Project", "ID", "Type", "Name", "Attached Service"} + header = []string{"IP", "NS", "Project", "ID", "Type", "Name", "Attached Service"} ) if wide { - header = []string{"IP", "Project", "ID", "Type", "Name", "Description", "Labels"} + header = []string{"IP", "NS", "Project", "ID", "Type", "Name", "Description", "Labels"} } for _, ip := range data { @@ -41,11 +42,12 @@ func (t *TablePrinter) IPTable(data []*apiv2.IP, wide bool) ([]string, [][]strin labels = append(labels, fmt.Sprintf("%s=%s", k, v)) } } + ns := pointer.SafeDeref(ip.Namespace) if wide { - rows = append(rows, []string{ip.Ip, ip.Project, ip.Uuid, t, ip.Name, ip.Description, strings.Join(labels, "\n")}) + rows = append(rows, []string{ip.Ip, ns, ip.Project, ip.Uuid, t, ip.Name, ip.Description, strings.Join(labels, "\n")}) } else { - rows = append(rows, []string{ip.Ip, ip.Project, ip.Uuid, t, ip.Name, attachedService}) + rows = append(rows, []string{ip.Ip, ns, ip.Project, ip.Uuid, t, ip.Name, attachedService}) } } diff --git a/cmd/tableprinters/machine.go b/cmd/tableprinters/machine.go new file mode 100644 index 0000000..569cbf1 --- /dev/null +++ b/cmd/tableprinters/machine.go @@ -0,0 +1,165 @@ +package tableprinters + +import ( + "fmt" + "strings" + "time" + + "github.com/fatih/color" + "github.com/metal-stack/api/go/enum" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/cli/pkg/helpers" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/pointer" +) + +func (t *TablePrinter) MachineTable(data []*apiv2.Machine, wide bool) ([]string, [][]string, error) { + + var ( + rows [][]string + header = []string{"ID", "", "Last Event", "When", "Age", "Hostname", "Project", "Size", "Image", "Partition", "Rack"} + ) + + if wide { + header = []string{"ID", "Last Event", "When", "Age", "Description", "Name", "Hostname", "Project", "IPs", "Size", "Image", "Partition", "Rack", "Started", "Tags", "Lock/Reserve"} + } + for _, machine := range data { + machineID := machine.Uuid + + if machine.Status != nil && machine.Status.LedState != nil && machine.Status.LedState.Value == "LED-ON" { + blue := color.New(color.FgBlue).SprintFunc() + machineID = blue(machineID) + } + + alloc := pointer.SafeDeref(machine.Allocation) + sizeID := pointer.SafeDeref(machine.Size).Id + partitionID := pointer.SafeDeref(machine.Partition).Id + project := alloc.Project + name := alloc.Name + desc := alloc.Description + hostname := alloc.Hostname + image := pointer.SafeDeref(pointer.SafeDeref(alloc.Image).Name) + + rack := machine.Rack + + truncatedHostname := genericcli.TruncateEnd(hostname, 30) + + var nwIPs []string + for _, nw := range alloc.Networks { + nwIPs = append(nwIPs, nw.Ips...) + } + ips := strings.Join(nwIPs, "\n") + + started := "" + age := "" + + if alloc.Meta != nil && alloc.Meta.CreatedAt != nil && !alloc.Meta.CreatedAt.AsTime().IsZero() { + started = alloc.Meta.CreatedAt.AsTime().Format(time.RFC3339) + age = humanizeDuration(time.Since(alloc.Meta.CreatedAt.AsTime())) + } + tags := "" + if machine.Meta.Labels != nil && len(machine.Meta.Labels.Labels) > 0 { + var labels []string + for k, v := range machine.Meta.Labels.Labels { + labels = append(labels, k+"="+v) + } + tags = strings.Join(labels, ",") + } + + reserved := "" + if machine.Status.Condition != nil { + stateString, err := enum.GetStringValue(machine.Status.Condition.State) + if err != nil { + return nil, nil, err + } + reserved = fmt.Sprintf("%s:%s", *stateString, machine.Status.Condition.Description) + } + + lastEvent := "" + when := "" + if len(machine.RecentProvisioningEvents.Events) > 0 { + since := time.Since(machine.RecentProvisioningEvents.LastEventTime.AsTime()) + when = humanizeDuration(since) + lastEventString, err := enum.GetStringValue(machine.RecentProvisioningEvents.Events[0].Event) + if err != nil { + return nil, nil, err + } + lastEvent = *lastEventString + } + + emojis, _ := t.getMachineStatusEmojis(machine.Status.Liveliness, machine.RecentProvisioningEvents, machine.Status.Condition.State, alloc.Vpn) + + if wide { + rows = append(rows, []string{machineID, lastEvent, when, age, desc, name, hostname, project, ips, sizeID, image, partitionID, rack, started, tags, reserved}) + } else { + rows = append(rows, []string{machineID, emojis, lastEvent, when, age, truncatedHostname, project, sizeID, image, partitionID, rack}) + } + } + + t.t.DisableAutoWrap(false) + + return header, rows, nil +} + +func (t *TablePrinter) getMachineStatusEmojis(liveliness apiv2.MachineLiveliness, events *apiv2.MachineRecentProvisioningEvents, state apiv2.MachineState, vpn *apiv2.MachineVPN) (string, string) { + var ( + emojis []string + wide []string + livelinessString *string + err error + ) + livelinessString, err = enum.GetStringValue(liveliness) + if err != nil { + livelinessString = pointer.Pointer("unknown") + } + + switch l := liveliness; l { + case apiv2.MachineLiveliness_MACHINE_LIVELINESS_ALIVE: + // noop + case apiv2.MachineLiveliness_MACHINE_LIVELINESS_DEAD: + emojis = append(emojis, helpers.Skull) + wide = append(wide, *livelinessString) + case apiv2.MachineLiveliness_MACHINE_LIVELINESS_UNKNOWN: + emojis = append(emojis, helpers.Question) + wide = append(wide, *livelinessString) + default: + emojis = append(emojis, helpers.Question) + wide = append(wide, *livelinessString) + } + + switch state { + case apiv2.MachineState_MACHINE_STATE_AVAILABLE: + // noop + case apiv2.MachineState_MACHINE_STATE_LOCKED: + emojis = append(emojis, helpers.Lock) + wide = append(wide, "Locked") + case apiv2.MachineState_MACHINE_STATE_RESERVED: + emojis = append(emojis, helpers.Bark) + wide = append(wide, "Reserved") + } + + if events != nil { + switch events.State { + case apiv2.MachineProvisioningEventState_MACHINE_PROVISIONING_EVENT_STATE_FAILED_RECLAIM: + emojis = append(emojis, helpers.Ambulance) + wide = append(wide, "FailedReclaim") + case apiv2.MachineProvisioningEventState_MACHINE_PROVISIONING_EVENT_STATE_CRASHLOOP: + emojis = append(emojis, helpers.Loop) + wide = append(wide, "CrashLoop") + + } + + if events.LastErrorEvent != nil && time.Since(events.LastErrorEvent.Time.AsTime()) < t.lastEventErrorThreshold { + emojis = append(emojis, helpers.Exclamation) + wide = append(wide, "LastEventErrors") + } + + } + + if vpn != nil && vpn.Connected { + emojis = append(emojis, helpers.VPN) + wide = append(wide, "VPN") + } + + return strings.Join(emojis, nbr), strings.Join(wide, ", ") +} diff --git a/cmd/tableprinters/network.go b/cmd/tableprinters/network.go new file mode 100644 index 0000000..2fbc391 --- /dev/null +++ b/cmd/tableprinters/network.go @@ -0,0 +1,156 @@ +package tableprinters + +import ( + "fmt" + "strings" + + "github.com/fatih/color" + "github.com/metal-stack/api/go/enum" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/metal-lib/pkg/pointer" +) + +type network struct { + parent *apiv2.Network + children []*apiv2.Network +} + +type networks []*network + +func (t *TablePrinter) NetworkTable(data []*apiv2.Network, wide bool) ([]string, [][]string, error) { + var ( + rows [][]string + ) + + header := []string{"ID", "Name", "Type", "Project", "Partition", "Nat", "Prefixes", "Prefix Usage", "IP Usage"} + if wide { + header = []string{"ID", "Description", "Name", "Type", "Project", "Partition", "Nat", "Prefixes", "Annotations"} + } + + nn := &networks{} + for _, n := range data { + if n.ParentNetwork == nil { + *nn = append(*nn, &network{parent: n}) + } + } + for _, n := range data { + if n.ParentNetwork != nil { + if !nn.appendChild(*n.ParentNetwork, n) { + *nn = append(*nn, &network{parent: n}) + } + } + } + for _, n := range *nn { + rows = append(rows, addNetwork("", n.parent, wide)) + for i, c := range n.children { + prefix := "├" + if i == len(n.children)-1 { + prefix = "└" + } + prefix += "─╴" + rows = append(rows, addNetwork(prefix, c, wide)) + } + } + + return header, rows, nil +} + +func addNetwork(prefix string, n *apiv2.Network, wide bool) []string { + id := fmt.Sprintf("%s%s", prefix, n.Id) + + prefixes := strings.Join(n.Prefixes, ",") + shortIPUsage := nbr + shortPrefixUsage := nbr + ipv4Use := 0.0 + ipv4PrefixUse := 0.0 + ipv6Use := 0.0 + ipv6PrefixUse := 0.0 + + if n.Consumption != nil { + consumption := n.Consumption + if consumption.Ipv4 != nil { + ipv4Consumption := consumption.Ipv4 + ipv4Use = float64(ipv4Consumption.UsedIps) / float64(ipv4Consumption.AvailableIps) + + if ipv4Consumption.AvailablePrefixes > 0 { + ipv4PrefixUse = float64(ipv4Consumption.UsedPrefixes) / float64(ipv4Consumption.AvailablePrefixes) + } + } + if consumption.Ipv6 != nil { + ipv6Consumption := consumption.Ipv6 + ipv6Use = float64(ipv6Consumption.UsedIps) / float64(ipv6Consumption.AvailableIps) + + if ipv6Consumption.AvailablePrefixes > 0 { + ipv6PrefixUse = float64(ipv6Consumption.UsedPrefixes) / float64(ipv6Consumption.AvailablePrefixes) + } + } + + if ipv4Use >= 0.9 || ipv6Use >= 0.9 { + shortIPUsage = color.RedString(threequarterpie) + } else if ipv4Use >= 0.7 || ipv6Use >= 0.7 { + shortIPUsage = color.YellowString(halfpie) + } else { + shortIPUsage = color.GreenString(dot) + } + + if ipv4PrefixUse >= 0.9 || ipv6PrefixUse >= 0.9 { + shortPrefixUsage = color.RedString(threequarterpie) + } else if ipv4PrefixUse >= 0.7 || ipv6PrefixUse >= 0.7 { + shortPrefixUsage = color.YellowString(halfpie) + } else { + shortPrefixUsage = color.GreenString(dot) + } + } + + var ( + description = pointer.SafeDeref(n.Description) + name = pointer.SafeDeref(n.Name) + project = pointer.SafeDeref(n.Project) + partition = pointer.SafeDeref(n.Partition) + natType = pointer.SafeDeref(n.NatType).String() + ) + + if t, err := enum.GetStringValue(pointer.SafeDeref(n.NatType)); err == nil { + natType = *t + } else { + fmt.Println(err) + } + + max := getMaxLineCount(description, name, project, partition, natType, prefixes, shortIPUsage) + for range max - 1 { + id += "\n│" + } + + var as []string + if n.Meta.Labels != nil { + for k, v := range n.Meta.Labels.Labels { + as = append(as, k+"="+v) + } + } + + annotations := strings.Join(as, "\n") + + var networkType string + nt, err := enum.GetStringValue(n.Type) + if err != nil { + networkType = "unknown" + } else { + networkType = *nt + } + + if wide { + return []string{id, description, name, networkType, project, partition, natType, prefixes, annotations} + } else { + return []string{id, name, networkType, project, partition, natType, prefixes, shortPrefixUsage, shortIPUsage} + } +} + +func (nn *networks) appendChild(parentID string, child *apiv2.Network) bool { + for _, n := range *nn { + if n.parent.Id == parentID { + n.children = append(n.children, child) + return true + } + } + return false +} diff --git a/cmd/tableprinters/size.go b/cmd/tableprinters/size.go new file mode 100644 index 0000000..65f72c2 --- /dev/null +++ b/cmd/tableprinters/size.go @@ -0,0 +1,45 @@ +package tableprinters + +import ( + "fmt" + + "github.com/dustin/go-humanize" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/metal-lib/pkg/pointer" +) + +func (t *TablePrinter) SizeTable(data []*apiv2.Size, wide bool) ([]string, [][]string, error) { + var ( + rows [][]string + header = []string{"ID", "Name", "Description", "CPU Range", "Memory Range", "Storage Range", "GPU Range"} + ) + + for _, size := range data { + var ( + cpu string + memory string + storage string + gpu string + ) + + for _, c := range size.Constraints { + switch c.Type { + case apiv2.SizeConstraintType_SIZE_CONSTRAINT_TYPE_CORES: + cpu = fmt.Sprintf("%d - %d", c.Min, c.Max) + case apiv2.SizeConstraintType_SIZE_CONSTRAINT_TYPE_MEMORY: + memory = fmt.Sprintf("%s - %s", humanize.Bytes(uint64(c.Min)), humanize.Bytes(uint64(c.Max))) //nolint:gosec + case apiv2.SizeConstraintType_SIZE_CONSTRAINT_TYPE_STORAGE: + storage = fmt.Sprintf("%s - %s", humanize.Bytes(uint64(c.Min)), humanize.Bytes(uint64(c.Max))) //nolint:gosec + case apiv2.SizeConstraintType_SIZE_CONSTRAINT_TYPE_GPU: + gpu = fmt.Sprintf("%s: %d - %d", pointer.SafeDeref(c.Identifier), c.Min, c.Max) + } + + } + + rows = append(rows, []string{size.Id, pointer.SafeDeref(size.Name), pointer.SafeDeref(size.Description), cpu, memory, storage, gpu}) + } + + t.t.DisableAutoWrap(false) + + return header, rows, nil +} diff --git a/cmd/tableprinters/task.go b/cmd/tableprinters/task.go new file mode 100644 index 0000000..f4d4ed1 --- /dev/null +++ b/cmd/tableprinters/task.go @@ -0,0 +1,51 @@ +package tableprinters + +import ( + "time" + + "github.com/google/uuid" + "github.com/metal-stack/api/go/enum" + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + "github.com/metal-stack/metal-lib/pkg/pointer" +) + +func (t *TablePrinter) TaskTable(data []*adminv2.TaskInfo, wide bool) ([]string, [][]string, error) { + var ( + rows [][]string + ) + header := []string{"ID", "Queue", "When", "Type", "State"} + + if wide { + header = []string{"ID", "Queue", "When", "Type", "State", "Issued At", "Payload", "Result"} + } + for _, task := range data { + id := task.Id + queue := task.Queue + typeString := task.Type + state, err := enum.GetStringValue(task.State) + if err != nil { + state = pointer.Pointer("unknown") + } + payload := string(task.Payload) + result := string(task.Result) + + parsed, err := uuid.Parse(id) + if err != nil { + return nil, nil, err + } + + sec, nano := parsed.Time().UnixTime() + issuedAt := time.Unix(sec, nano) + when := humanizeDuration(time.Since(issuedAt)) + + if wide { + rows = append(rows, []string{id, queue, when, typeString, *state, issuedAt.String(), payload, result}) + } else { + rows = append(rows, []string{id, queue, when, typeString, *state}) + } + } + + t.t.DisableAutoWrap(false) + + return header, rows, nil +} diff --git a/cmd/tableprinters/vpn.go b/cmd/tableprinters/vpn.go new file mode 100644 index 0000000..0de30db --- /dev/null +++ b/cmd/tableprinters/vpn.go @@ -0,0 +1,35 @@ +package tableprinters + +import ( + "fmt" + "strings" + "time" + + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" +) + +func (t *TablePrinter) VPNTable(data []*apiv2.VPNNode, _ bool) ([]string, [][]string, error) { + var ( + rows [][]string + ) + header := []string{"ID", "Name", "Project", "IPs", "Last Seed"} + + for _, node := range data { + lastSeen := node.LastSeen.AsTime().Format(time.DateTime + " MST") + ips := strings.Join(node.IpAddresses, ",") + + row := []string{ + fmt.Sprintf("%d", node.Id), + node.Name, + node.Project, + ips, + lastSeen, + } + + rows = append(rows, row) + } + + t.t.DisableAutoWrap(false) + + return header, rows, nil +} diff --git a/docs/metalctlv2.md b/docs/metalctlv2.md index 4671daf..ecd1f5b 100644 --- a/docs/metalctlv2.md +++ b/docs/metalctlv2.md @@ -26,8 +26,11 @@ cli for managing entities in metal-stack * [metalctlv2 ip](metalctlv2_ip.md) - manage ip entities * [metalctlv2 login](metalctlv2_login.md) - login * [metalctlv2 logout](metalctlv2_logout.md) - logout +* [metalctlv2 machine](metalctlv2_machine.md) - manage machine entities * [metalctlv2 markdown](metalctlv2_markdown.md) - create markdown documentation +* [metalctlv2 network](metalctlv2_network.md) - manage network entities * [metalctlv2 project](metalctlv2_project.md) - manage project entities +* [metalctlv2 size](metalctlv2_size.md) - manage size entities * [metalctlv2 tenant](metalctlv2_tenant.md) - manage tenant entities * [metalctlv2 token](metalctlv2_token.md) - manage token entities * [metalctlv2 user](metalctlv2_user.md) - manage user entities diff --git a/docs/metalctlv2_ip.md b/docs/metalctlv2_ip.md index 401c938..39ddc20 100644 --- a/docs/metalctlv2_ip.md +++ b/docs/metalctlv2_ip.md @@ -4,7 +4,7 @@ manage ip entities ### Synopsis -an ip address of metal-stack.io +manage ip addresses ### Options diff --git a/docs/metalctlv2_ip_describe.md b/docs/metalctlv2_ip_describe.md index aae77b5..984fbc5 100644 --- a/docs/metalctlv2_ip_describe.md +++ b/docs/metalctlv2_ip_describe.md @@ -9,8 +9,9 @@ metalctlv2 ip describe [flags] ### Options ``` - -h, --help help for describe - -p, --project string project of the ip + -h, --help help for describe + -n, --namespace string namespace of the ip + -p, --project string project of the ip ``` ### Options inherited from parent commands diff --git a/docs/metalctlv2_machine.md b/docs/metalctlv2_machine.md new file mode 100644 index 0000000..cf12864 --- /dev/null +++ b/docs/metalctlv2_machine.md @@ -0,0 +1,38 @@ +## metalctlv2 machine + +manage machine entities + +### Synopsis + +an machine of metal-stack.io + +### Options + +``` + -h, --help help for machine +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2](metalctlv2.md) - cli for managing entities in metal-stack +* [metalctlv2 machine apply](metalctlv2_machine_apply.md) - applies one or more machines from a given file +* [metalctlv2 machine create](metalctlv2_machine_create.md) - creates the machine +* [metalctlv2 machine delete](metalctlv2_machine_delete.md) - deletes the machine +* [metalctlv2 machine describe](metalctlv2_machine_describe.md) - describes the machine +* [metalctlv2 machine edit](metalctlv2_machine_edit.md) - edit the machine through an editor and update +* [metalctlv2 machine list](metalctlv2_machine_list.md) - list all machines +* [metalctlv2 machine update](metalctlv2_machine_update.md) - updates the machine + diff --git a/docs/metalctlv2_machine_apply.md b/docs/metalctlv2_machine_apply.md new file mode 100644 index 0000000..040e94d --- /dev/null +++ b/docs/metalctlv2_machine_apply.md @@ -0,0 +1,46 @@ +## metalctlv2 machine apply + +applies one or more machines from a given file + +``` +metalctlv2 machine apply [flags] +``` + +### Options + +``` + --bulk-output when used with --file (bulk operation): prints results at the end as a list. default is printing results intermediately during the operation, which causes single entities to be printed in a row. + -f, --file string filename of the create or update request in yaml format, or - for stdin. + + Example: + $ metalctlv2 machine describe machine-1 -o yaml > machine.yaml + $ vi machine.yaml + $ # either via stdin + $ cat machine.yaml | metalctlv2 machine apply -f - + $ # or via file + $ metalctlv2 machine apply -f machine.yaml + + the file can also contain multiple documents and perform a bulk operation. + + -h, --help help for apply + --skip-security-prompts skips security prompt for bulk operations + --timestamps when used with --file (bulk operation): prints timestamps in-between the operations +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 machine](metalctlv2_machine.md) - manage machine entities + diff --git a/docs/metalctlv2_machine_create.md b/docs/metalctlv2_machine_create.md new file mode 100644 index 0000000..8381bf4 --- /dev/null +++ b/docs/metalctlv2_machine_create.md @@ -0,0 +1,46 @@ +## metalctlv2 machine create + +creates the machine + +``` +metalctlv2 machine create [flags] +``` + +### Options + +``` + --bulk-output when used with --file (bulk operation): prints results at the end as a list. default is printing results intermediately during the operation, which causes single entities to be printed in a row. + -f, --file string filename of the create or update request in yaml format, or - for stdin. + + Example: + $ metalctlv2 machine describe machine-1 -o yaml > machine.yaml + $ vi machine.yaml + $ # either via stdin + $ cat machine.yaml | metalctlv2 machine create -f - + $ # or via file + $ metalctlv2 machine create -f machine.yaml + + the file can also contain multiple documents and perform a bulk operation. + + -h, --help help for create + --skip-security-prompts skips security prompt for bulk operations + --timestamps when used with --file (bulk operation): prints timestamps in-between the operations +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 machine](metalctlv2_machine.md) - manage machine entities + diff --git a/docs/metalctlv2_machine_delete.md b/docs/metalctlv2_machine_delete.md new file mode 100644 index 0000000..b9aa286 --- /dev/null +++ b/docs/metalctlv2_machine_delete.md @@ -0,0 +1,46 @@ +## metalctlv2 machine delete + +deletes the machine + +``` +metalctlv2 machine delete [flags] +``` + +### Options + +``` + --bulk-output when used with --file (bulk operation): prints results at the end as a list. default is printing results intermediately during the operation, which causes single entities to be printed in a row. + -f, --file string filename of the create or update request in yaml format, or - for stdin. + + Example: + $ metalctlv2 machine describe machine-1 -o yaml > machine.yaml + $ vi machine.yaml + $ # either via stdin + $ cat machine.yaml | metalctlv2 machine delete -f - + $ # or via file + $ metalctlv2 machine delete -f machine.yaml + + the file can also contain multiple documents and perform a bulk operation. + + -h, --help help for delete + --skip-security-prompts skips security prompt for bulk operations + --timestamps when used with --file (bulk operation): prints timestamps in-between the operations +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 machine](metalctlv2_machine.md) - manage machine entities + diff --git a/docs/metalctlv2_machine_describe.md b/docs/metalctlv2_machine_describe.md new file mode 100644 index 0000000..e590112 --- /dev/null +++ b/docs/metalctlv2_machine_describe.md @@ -0,0 +1,32 @@ +## metalctlv2 machine describe + +describes the machine + +``` +metalctlv2 machine describe [flags] +``` + +### Options + +``` + -h, --help help for describe + -p, --project string project of the machine +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 machine](metalctlv2_machine.md) - manage machine entities + diff --git a/docs/metalctlv2_machine_edit.md b/docs/metalctlv2_machine_edit.md new file mode 100644 index 0000000..08d19fe --- /dev/null +++ b/docs/metalctlv2_machine_edit.md @@ -0,0 +1,31 @@ +## metalctlv2 machine edit + +edit the machine through an editor and update + +``` +metalctlv2 machine edit [flags] +``` + +### Options + +``` + -h, --help help for edit +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 machine](metalctlv2_machine.md) - manage machine entities + diff --git a/docs/metalctlv2_machine_list.md b/docs/metalctlv2_machine_list.md new file mode 100644 index 0000000..6ec184d --- /dev/null +++ b/docs/metalctlv2_machine_list.md @@ -0,0 +1,33 @@ +## metalctlv2 machine list + +list all machines + +``` +metalctlv2 machine list [flags] +``` + +### Options + +``` + -h, --help help for list + -p, --project string project from where machines should be listed + --sort-by strings sort by (comma separated) column(s), sort direction can be changed by appending :asc or :desc behind the column identifier. possible values: age|image|partition|project|rack|size|uuid +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 machine](metalctlv2_machine.md) - manage machine entities + diff --git a/docs/metalctlv2_machine_update.md b/docs/metalctlv2_machine_update.md new file mode 100644 index 0000000..0d23675 --- /dev/null +++ b/docs/metalctlv2_machine_update.md @@ -0,0 +1,46 @@ +## metalctlv2 machine update + +updates the machine + +``` +metalctlv2 machine update [flags] +``` + +### Options + +``` + --bulk-output when used with --file (bulk operation): prints results at the end as a list. default is printing results intermediately during the operation, which causes single entities to be printed in a row. + -f, --file string filename of the create or update request in yaml format, or - for stdin. + + Example: + $ metalctlv2 machine describe machine-1 -o yaml > machine.yaml + $ vi machine.yaml + $ # either via stdin + $ cat machine.yaml | metalctlv2 machine update -f - + $ # or via file + $ metalctlv2 machine update -f machine.yaml + + the file can also contain multiple documents and perform a bulk operation. + + -h, --help help for update + --skip-security-prompts skips security prompt for bulk operations + --timestamps when used with --file (bulk operation): prints timestamps in-between the operations +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 machine](metalctlv2_machine.md) - manage machine entities + diff --git a/docs/metalctlv2_network.md b/docs/metalctlv2_network.md new file mode 100644 index 0000000..4581d1d --- /dev/null +++ b/docs/metalctlv2_network.md @@ -0,0 +1,39 @@ +## metalctlv2 network + +manage network entities + +### Synopsis + +networks can be attached to a machine or firewall such that they can communicate with each other. + +### Options + +``` + -h, --help help for network +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2](metalctlv2.md) - cli for managing entities in metal-stack +* [metalctlv2 network apply](metalctlv2_network_apply.md) - applies one or more networks from a given file +* [metalctlv2 network create](metalctlv2_network_create.md) - creates the network +* [metalctlv2 network delete](metalctlv2_network_delete.md) - deletes the network +* [metalctlv2 network describe](metalctlv2_network_describe.md) - describes the network +* [metalctlv2 network edit](metalctlv2_network_edit.md) - edit the network through an editor and update +* [metalctlv2 network list](metalctlv2_network_list.md) - list all networks +* [metalctlv2 network list-base-networks](metalctlv2_network_list-base-networks.md) - lists base networks that can be used for network creation +* [metalctlv2 network update](metalctlv2_network_update.md) - updates the network + diff --git a/docs/metalctlv2_network_apply.md b/docs/metalctlv2_network_apply.md new file mode 100644 index 0000000..5f479e9 --- /dev/null +++ b/docs/metalctlv2_network_apply.md @@ -0,0 +1,46 @@ +## metalctlv2 network apply + +applies one or more networks from a given file + +``` +metalctlv2 network apply [flags] +``` + +### Options + +``` + --bulk-output when used with --file (bulk operation): prints results at the end as a list. default is printing results intermediately during the operation, which causes single entities to be printed in a row. + -f, --file string filename of the create or update request in yaml format, or - for stdin. + + Example: + $ metalctlv2 network describe network-1 -o yaml > network.yaml + $ vi network.yaml + $ # either via stdin + $ cat network.yaml | metalctlv2 network apply -f - + $ # or via file + $ metalctlv2 network apply -f network.yaml + + the file can also contain multiple documents and perform a bulk operation. + + -h, --help help for apply + --skip-security-prompts skips security prompt for bulk operations + --timestamps when used with --file (bulk operation): prints timestamps in-between the operations +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 network](metalctlv2_network.md) - manage network entities + diff --git a/docs/metalctlv2_network_create.md b/docs/metalctlv2_network_create.md new file mode 100644 index 0000000..d356087 --- /dev/null +++ b/docs/metalctlv2_network_create.md @@ -0,0 +1,55 @@ +## metalctlv2 network create + +creates the network + +``` +metalctlv2 network create [flags] +``` + +### Options + +``` + --addressfamily string addressfamily of the network to acquire, if not specified the network inherits the address families from the parent [optional] + --bulk-output when used with --file (bulk operation): prints results at the end as a list. default is printing results intermediately during the operation, which causes single entities to be printed in a row. + --description string description of the network to create. [optional] + -f, --file string filename of the create or update request in yaml format, or - for stdin. + + Example: + $ metalctlv2 network describe network-1 -o yaml > network.yaml + $ vi network.yaml + $ # either via stdin + $ cat network.yaml | metalctlv2 network create -f - + $ # or via file + $ metalctlv2 network create -f network.yaml + + the file can also contain multiple documents and perform a bulk operation. + + -h, --help help for create + --ipv4-prefix-length uint32 ipv4 prefix bit length of the network to create, defaults to default child prefix length of the parent network. [optional] + --ipv6-prefix-length uint32 ipv6 prefix bit length of the network to create, defaults to default child prefix length of the parent network. [optional] + --labels strings labels for this network. [optional] + --name string name of the network to create. [required] + --parent-network-id string the parent of the network (alternative to partition). [optional] + --partition string partition where this network should exist. [required] + --project string partition where this network should exist (alternative to parent-network-id). [optional] + --skip-security-prompts skips security prompt for bulk operations + --timestamps when used with --file (bulk operation): prints timestamps in-between the operations +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 network](metalctlv2_network.md) - manage network entities + diff --git a/docs/metalctlv2_network_delete.md b/docs/metalctlv2_network_delete.md new file mode 100644 index 0000000..0e7bb2a --- /dev/null +++ b/docs/metalctlv2_network_delete.md @@ -0,0 +1,46 @@ +## metalctlv2 network delete + +deletes the network + +``` +metalctlv2 network delete [flags] +``` + +### Options + +``` + --bulk-output when used with --file (bulk operation): prints results at the end as a list. default is printing results intermediately during the operation, which causes single entities to be printed in a row. + -f, --file string filename of the create or update request in yaml format, or - for stdin. + + Example: + $ metalctlv2 network describe network-1 -o yaml > network.yaml + $ vi network.yaml + $ # either via stdin + $ cat network.yaml | metalctlv2 network delete -f - + $ # or via file + $ metalctlv2 network delete -f network.yaml + + the file can also contain multiple documents and perform a bulk operation. + + -h, --help help for delete + --skip-security-prompts skips security prompt for bulk operations + --timestamps when used with --file (bulk operation): prints timestamps in-between the operations +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 network](metalctlv2_network.md) - manage network entities + diff --git a/docs/metalctlv2_network_describe.md b/docs/metalctlv2_network_describe.md new file mode 100644 index 0000000..b7c9556 --- /dev/null +++ b/docs/metalctlv2_network_describe.md @@ -0,0 +1,31 @@ +## metalctlv2 network describe + +describes the network + +``` +metalctlv2 network describe [flags] +``` + +### Options + +``` + -h, --help help for describe +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 network](metalctlv2_network.md) - manage network entities + diff --git a/docs/metalctlv2_network_edit.md b/docs/metalctlv2_network_edit.md new file mode 100644 index 0000000..4348569 --- /dev/null +++ b/docs/metalctlv2_network_edit.md @@ -0,0 +1,31 @@ +## metalctlv2 network edit + +edit the network through an editor and update + +``` +metalctlv2 network edit [flags] +``` + +### Options + +``` + -h, --help help for edit +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 network](metalctlv2_network.md) - manage network entities + diff --git a/docs/metalctlv2_network_list-base-networks.md b/docs/metalctlv2_network_list-base-networks.md new file mode 100644 index 0000000..6626638 --- /dev/null +++ b/docs/metalctlv2_network_list-base-networks.md @@ -0,0 +1,42 @@ +## metalctlv2 network list-base-networks + +lists base networks that can be used for network creation + +``` +metalctlv2 network list-base-networks [flags] +``` + +### Options + +``` + --addressfamily string addressfamily to filter, either ipv4 or ipv6 [optional] + --description string description to filter [optional] + --destination-prefixes strings destination prefixes to filter + -h, --help help for list-base-networks + --id string ID to filter [optional] + --labels strings labels to filter [optional] + --name string name to filter [optional] + --partition string partition to filter [optional] + --prefixes strings prefixes to filter + --project string project to filter [optional] + -t, --type string type of the network. [optional] + --vrf uint32 vrf to filter [optional] +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 network](metalctlv2_network.md) - manage network entities + diff --git a/docs/metalctlv2_network_list.md b/docs/metalctlv2_network_list.md new file mode 100644 index 0000000..7af715b --- /dev/null +++ b/docs/metalctlv2_network_list.md @@ -0,0 +1,44 @@ +## metalctlv2 network list + +list all networks + +``` +metalctlv2 network list [flags] +``` + +### Options + +``` + --addressfamily string addressfamily to filter, either ipv4 or ipv6 [optional] + --description string description to filter [optional] + --destination-prefixes strings destination prefixes to filter + -h, --help help for list + --id string ID to filter [optional] + --labels strings labels to filter [optional] + --name string name to filter [optional] + --parent-network-id string parent network to filter [optional] + --partition string partition to filter [optional] + --prefixes strings prefixes to filter + --project string project to filter [optional] + --sort-by strings sort by (comma separated) column(s), sort direction can be changed by appending :asc or :desc behind the column identifier. possible values: description|id|name|partition|project + -t, --type string type of the network. [optional] + --vrf uint32 vrf to filter [optional] +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 network](metalctlv2_network.md) - manage network entities + diff --git a/docs/metalctlv2_network_update.md b/docs/metalctlv2_network_update.md new file mode 100644 index 0000000..69323b9 --- /dev/null +++ b/docs/metalctlv2_network_update.md @@ -0,0 +1,50 @@ +## metalctlv2 network update + +updates the network + +``` +metalctlv2 network update [flags] +``` + +### Options + +``` + --bulk-output when used with --file (bulk operation): prints results at the end as a list. default is printing results intermediately during the operation, which causes single entities to be printed in a row. + --description string the description of the network [optional] + -f, --file string filename of the create or update request in yaml format, or - for stdin. + + Example: + $ metalctlv2 network describe network-1 -o yaml > network.yaml + $ vi network.yaml + $ # either via stdin + $ cat network.yaml | metalctlv2 network update -f - + $ # or via file + $ metalctlv2 network update -f network.yaml + + the file can also contain multiple documents and perform a bulk operation. + + -h, --help help for update + --labels strings the labels of the network, must be in the form of key=value, use it like: --labels "key1=value1,key2=value2". [optional] + --name string the name of the network [optional] + --project string project to filter [optional] + --skip-security-prompts skips security prompt for bulk operations + --timestamps when used with --file (bulk operation): prints timestamps in-between the operations +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 network](metalctlv2_network.md) - manage network entities + diff --git a/docs/metalctlv2_size.md b/docs/metalctlv2_size.md new file mode 100644 index 0000000..a5f4c21 --- /dev/null +++ b/docs/metalctlv2_size.md @@ -0,0 +1,33 @@ +## metalctlv2 size + +manage size entities + +### Synopsis + +manage sizes which defines the cpu, gpu, memory and storage properties of machines + +### Options + +``` + -h, --help help for size +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2](metalctlv2.md) - cli for managing entities in metal-stack +* [metalctlv2 size describe](metalctlv2_size_describe.md) - describes the size +* [metalctlv2 size list](metalctlv2_size_list.md) - list all sizes + diff --git a/docs/metalctlv2_size_describe.md b/docs/metalctlv2_size_describe.md new file mode 100644 index 0000000..98d7d0f --- /dev/null +++ b/docs/metalctlv2_size_describe.md @@ -0,0 +1,31 @@ +## metalctlv2 size describe + +describes the size + +``` +metalctlv2 size describe [flags] +``` + +### Options + +``` + -h, --help help for describe +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 size](metalctlv2_size.md) - manage size entities + diff --git a/docs/metalctlv2_size_list.md b/docs/metalctlv2_size_list.md new file mode 100644 index 0000000..cd5d5cd --- /dev/null +++ b/docs/metalctlv2_size_list.md @@ -0,0 +1,34 @@ +## metalctlv2 size list + +list all sizes + +``` +metalctlv2 size list [flags] +``` + +### Options + +``` + --description string size description to filter for + -h, --help help for list + --id string size id to filter for + --name string size name to filter for +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api (default "https://api.metal-stack.io") + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template|jsonraw|yamlraw), wide is a table with more columns, jsonraw and yamlraw do not translate proto enums into string types but leave the original int32 values intact. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 size](metalctlv2_size.md) - manage size entities + diff --git a/docs/metalctlv2_token.md b/docs/metalctlv2_token.md index 01394f8..2ebe76a 100644 --- a/docs/metalctlv2_token.md +++ b/docs/metalctlv2_token.md @@ -4,7 +4,7 @@ manage token entities ### Synopsis -manage api tokens for accessing the metal-stack.io api +manage api tokens ### Options diff --git a/go.mod b/go.mod index 9399bc5..cea75b2 100644 --- a/go.mod +++ b/go.mod @@ -1,66 +1,68 @@ module github.com/metal-stack/cli -go 1.25 +go 1.25.0 require ( github.com/dustin/go-humanize v1.0.1 github.com/fatih/color v1.18.0 github.com/google/go-cmp v0.7.0 - github.com/metal-stack/api v0.0.35 + github.com/google/uuid v1.6.0 + github.com/metal-stack/api v0.0.46 github.com/metal-stack/metal-lib v0.23.5 github.com/metal-stack/v v1.0.3 github.com/spf13/afero v1.15.0 - github.com/spf13/cobra v1.10.1 + github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 - golang.org/x/net v0.46.0 - google.golang.org/protobuf v1.36.10 + google.golang.org/grpc v1.78.0 + google.golang.org/protobuf v1.36.11 sigs.k8s.io/yaml v1.6.0 ) require ( - buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.10-20250912141014-52f32327d4b0.1 // indirect + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20251209175733-2a1774d88802.1 // indirect connectrpc.com/connect v1.19.1 // indirect - github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/clipperhouse/uax29/v2 v2.2.0 // indirect + github.com/clipperhouse/displaywidth v0.9.0 // indirect + github.com/clipperhouse/stringish v0.1.1 // indirect + github.com/clipperhouse/uax29/v2 v2.5.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/go-openapi/errors v0.22.3 // indirect - github.com/go-openapi/strfmt v0.24.0 // indirect + github.com/go-openapi/errors v0.22.6 // indirect + github.com/go-openapi/strfmt v0.25.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/go-viper/mapstructure/v2 v2.4.0 // indirect - github.com/goccy/go-yaml v1.18.0 // indirect - github.com/golang-jwt/jwt/v5 v5.3.0 // indirect - github.com/google/uuid v1.6.0 // indirect + github.com/go-viper/mapstructure/v2 v2.5.0 // indirect + github.com/goccy/go-yaml v1.19.2 // indirect + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/klauspost/compress v1.18.1 // indirect - github.com/klauspost/connect-compress/v2 v2.1.0 // indirect + github.com/klauspost/compress v1.18.3 // indirect + github.com/klauspost/connect-compress/v2 v2.1.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.19 // indirect github.com/minio/minlz v1.0.1 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect - github.com/olekukonko/errors v1.1.0 // indirect - github.com/olekukonko/ll v0.1.2 // indirect - github.com/olekukonko/tablewriter v1.1.0 // indirect + github.com/olekukonko/errors v1.2.0 // indirect + github.com/olekukonko/ll v0.1.4 // indirect + github.com/olekukonko/tablewriter v1.1.3 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.12.0 // indirect github.com/spf13/cast v1.10.0 // indirect github.com/stretchr/objx v0.5.3 // indirect github.com/subosito/gotenv v1.6.0 // indirect - go.mongodb.org/mongo-driver v1.17.4 // indirect + go.mongodb.org/mongo-driver v1.17.8 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/sys v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apimachinery v0.34.1 // indirect + k8s.io/apimachinery v0.35.0 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect ) diff --git a/go.sum b/go.sum index cbca136..7ee0edb 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,15 @@ -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.10-20250912141014-52f32327d4b0.1 h1:31on4W/yPcV4nZHL4+UCiCvLPsMqe/vJcNg8Rci0scc= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.10-20250912141014-52f32327d4b0.1/go.mod h1:fUl8CEN/6ZAMk6bP8ahBJPUJw7rbp+j4x+wCcYi2IG4= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20251209175733-2a1774d88802.1 h1:j9yeqTWEFrtimt8Nng2MIeRrpoCvQzM9/g25XTvqUGg= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20251209175733-2a1774d88802.1/go.mod h1:tvtbpgaVXZX4g6Pn+AnzFycuRK3MOz5HJfEGeEllXYM= connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14= connectrpc.com/connect v1.19.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w= -github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= -github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= -github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY= -github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= +github.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA= +github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA= +github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= +github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= +github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U= +github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -21,28 +23,32 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/go-openapi/errors v0.22.3 h1:k6Hxa5Jg1TUyZnOwV2Lh81j8ayNw5VVYLvKrp4zFKFs= -github.com/go-openapi/errors v0.22.3/go.mod h1:+WvbaBBULWCOna//9B9TbLNGSFOfF8lY9dw4hGiEiKQ= -github.com/go-openapi/strfmt v0.24.0 h1:dDsopqbI3wrrlIzeXRbqMihRNnjzGC+ez4NQaAAJLuc= -github.com/go-openapi/strfmt v0.24.0/go.mod h1:Lnn1Bk9rZjXxU9VMADbEEOo7D7CDyKGLsSKekhFr7s4= +github.com/go-openapi/errors v0.22.6 h1:eDxcf89O8odEnohIXwEjY1IB4ph5vmbUsBMsFNwXWPo= +github.com/go-openapi/errors v0.22.6/go.mod h1:z9S8ASTUqx7+CP1Q8dD8ewGH/1JWFFLX/2PmAYNQLgk= +github.com/go-openapi/strfmt v0.25.0 h1:7R0RX7mbKLa9EYCTHRcCuIPcaqlyQiWNPTXwClK0saQ= +github.com/go-openapi/strfmt v0.25.0/go.mod h1:nNXct7OzbwrMY9+5tLX4I21pzcmE6ccMGXl3jFdPfn8= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= -github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= -github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= -github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= +github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= +github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 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.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co= -github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0= -github.com/klauspost/connect-compress/v2 v2.1.0 h1:8fM8QrVeHT69e5VVSh4yjDaQASYIvOp2uMZq7nVLj2U= -github.com/klauspost/connect-compress/v2 v2.1.0/go.mod h1:Ayurh2wscMMx3AwdGGVL+ylSR5316WfApREDgsqHyH8= +github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw= +github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/klauspost/connect-compress/v2 v2.1.1 h1:ycZNp4rWOZBodVE2Ls5AzK4aHkyK+GteEfzRZgKNs+c= +github.com/klauspost/connect-compress/v2 v2.1.1/go.mod h1:9oilsPHJMzGKkjafSBk9J7iVo4mO+dw0G0KSdVpnlVE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -53,10 +59,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= -github.com/metal-stack/api v0.0.35-0.20251124101516-a3076941d0f8 h1:PGBiFwASqhtmI6dGzVo0IEVQiBTIsQ2eo3pMriPViNY= -github.com/metal-stack/api v0.0.35-0.20251124101516-a3076941d0f8/go.mod h1:EBwS/oZr5tIcnV6hM7iK4aBQrw4wlU7vF5p+O1p3YIU= -github.com/metal-stack/api v0.0.35 h1:XxxYKTscSeYJg/ftL519nY3FAZ01atPeyD7+Zz/amQQ= -github.com/metal-stack/api v0.0.35/go.mod h1:EBwS/oZr5tIcnV6hM7iK4aBQrw4wlU7vF5p+O1p3YIU= +github.com/metal-stack/api v0.0.46 h1:6FxXNBXEq26KIBcvaI0bRhLfTxFHTkwGTAiKl9yOTrw= +github.com/metal-stack/api v0.0.46/go.mod h1:kqAqRS5/5pKOKw4yTbqF/u3h2WCrtvYhKp/Bt2ExZJM= github.com/metal-stack/metal-lib v0.23.5 h1:ozrkB3DNr3Cqn8nkBvmzc/KKpYqC1j1mv2OVOj8i7Ac= github.com/metal-stack/metal-lib v0.23.5/go.mod h1:7uyHIrE19dkLwCZyeh2jmd7IEq5pEpzrzUGLoMN1eqY= github.com/metal-stack/v v1.0.3 h1:Sh2oBlnxrCUD+mVpzfC8HiqL045YWkxs0gpTvkjppqs= @@ -67,20 +71,18 @@ github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0= -github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM= -github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= -github.com/olekukonko/ll v0.1.2 h1:lkg/k/9mlsy0SxO5aC+WEpbdT5K83ddnNhAepz7TQc0= -github.com/olekukonko/ll v0.1.2/go.mod h1:b52bVQRRPObe+yyBl0TxNfhesL0nedD4Cht0/zx55Ew= -github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY= -github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo= +github.com/olekukonko/errors v1.2.0 h1:10Zcn4GeV59t/EGqJc8fUjtFT/FuUh5bTMzZ1XwmCRo= +github.com/olekukonko/errors v1.2.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= +github.com/olekukonko/ll v0.1.4 h1:QcDaO9quz213xqHZr0gElOcYeOSnFeq7HTQ9Wu4O1wE= +github.com/olekukonko/ll v0.1.4/go.mod h1:b52bVQRRPObe+yyBl0TxNfhesL0nedD4Cht0/zx55Ew= +github.com/olekukonko/tablewriter v1.1.3 h1:VSHhghXxrP0JHl+0NnKid7WoEmd9/urKRJLysb70nnA= +github.com/olekukonko/tablewriter v1.1.3/go.mod h1:9VU0knjhmMkXjnMKrZ3+L2JhhtsQ/L38BbL3CRNE8tM= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= @@ -89,8 +91,8 @@ github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= -github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= -github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -102,21 +104,25 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw= -go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= +go.mongodb.org/mongo-driver v1.17.8 h1:BDP3+U3Y8K0vTrpqDJIRaXNhb/bKyoVeg6tIJsW5EhM= +go.mongodb.org/mongo-driver v1.17.8/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= -golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= +google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -124,8 +130,8 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= -k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8= +k8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= diff --git a/pkg/common/ip.go b/pkg/common/ip.go new file mode 100644 index 0000000..4fea5cb --- /dev/null +++ b/pkg/common/ip.go @@ -0,0 +1,12 @@ +package common + +import ( + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" +) + +func IpStaticToType(b bool) apiv2.IPType { + if b { + return apiv2.IPType_IP_TYPE_STATIC + } + return apiv2.IPType_IP_TYPE_EPHEMERAL +} diff --git a/pkg/common/network.go b/pkg/common/network.go new file mode 100644 index 0000000..dd6c1df --- /dev/null +++ b/pkg/common/network.go @@ -0,0 +1,33 @@ +package common + +import ( + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" +) + +func IPAddressFamilyToType(af string) *apiv2.IPAddressFamily { + switch af { + case "": + return nil + case "ipv4", "IPv4": + return apiv2.IPAddressFamily_IP_ADDRESS_FAMILY_V4.Enum() + case "ipv6", "IPv6": + return apiv2.IPAddressFamily_IP_ADDRESS_FAMILY_V6.Enum() + default: + return apiv2.IPAddressFamily_IP_ADDRESS_FAMILY_UNSPECIFIED.Enum() + } +} + +func NetworkAddressFamilyToType(af string) *apiv2.NetworkAddressFamily { + switch af { + case "": + return nil + case "ipv4", "IPv4": + return apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_V4.Enum() + case "ipv6", "IPv6": + return apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_V6.Enum() + case "dual-stack": + return apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_DUAL_STACK.Enum() + default: + return apiv2.NetworkAddressFamily_NETWORK_ADDRESS_FAMILY_UNSPECIFIED.Enum() + } +} diff --git a/pkg/helpers/emoji.go b/pkg/helpers/emoji.go new file mode 100644 index 0000000..63e9c56 --- /dev/null +++ b/pkg/helpers/emoji.go @@ -0,0 +1,27 @@ +package helpers + +const ( + Ambulance = "🚑" + Exclamation = "❗" + Bark = "🚧" + Loop = "⭕" + Lock = "🔒" + Question = "❓" + Skull = "💀" + VPN = "🛡" +) + +func EmojiHelpText() string { + return ` +Meaning of the emojis: + +🚧 Machine is reserved. Reserved machines are not considered for random allocation until the reservation flag is removed. +🔒 Machine is locked. Locked machines can not be deleted until the lock is removed. +💀 Machine is dead. The metal-api does not receive any events from this machine. +❗ Machine has a last event error. The machine has recently encountered an error during the provisioning lifecycle. +❓ Machine is in unknown condition. The metal-api does not receive phoned home events anymore or has never booted successfully. +⭕ Machine is in a provisioning crash loop. Flag can be reset through an API-triggered reboot or when the machine reaches the phoned home state. +🚑 Machine reclaim has failed. The machine was deleted but it is not going back into the available machine pool. +🛡 Machine is connected to our VPN, ssh access only possible via this VPN. +` +}