From 7d94638a9b7c8004b7e50a24cc025752b3d52067 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Mon, 23 Feb 2026 11:02:28 +0100 Subject: [PATCH 1/2] Component Service --- cmd/admin/v2/commands.go | 3 +- cmd/admin/v2/component.go | 109 +++++++++++++++++++++++++++++++++ cmd/tableprinters/common.go | 5 ++ cmd/tableprinters/component.go | 33 ++++++++++ 4 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 cmd/admin/v2/component.go create mode 100644 cmd/tableprinters/component.go diff --git a/cmd/admin/v2/commands.go b/cmd/admin/v2/commands.go index 881a7be..6ab7358 100644 --- a/cmd/admin/v2/commands.go +++ b/cmd/admin/v2/commands.go @@ -14,10 +14,11 @@ func AddCmds(cmd *cobra.Command, c *config.Config) { Hidden: true, } + adminCmd.AddCommand(newComponentCmd(c)) adminCmd.AddCommand(newImageCmd(c)) + adminCmd.AddCommand(newProjectCmd(c)) adminCmd.AddCommand(newTenantCmd(c)) adminCmd.AddCommand(newTokenCmd(c)) - adminCmd.AddCommand(newProjectCmd(c)) cmd.AddCommand(adminCmd) } diff --git a/cmd/admin/v2/component.go b/cmd/admin/v2/component.go new file mode 100644 index 0000000..5910dd8 --- /dev/null +++ b/cmd/admin/v2/component.go @@ -0,0 +1,109 @@ +package v2 + +import ( + "fmt" + + "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/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 component struct { + c *config.Config +} + +func newComponentCmd(c *config.Config) *cobra.Command { + w := &component{ + c: c, + } + gcli := genericcli.NewGenericCLI(w).WithFS(c.Fs) + + cmdsConfig := &genericcli.CmdsConfig[any, any, *apiv2.Component]{ + BinaryName: config.BinaryName, + GenericCLI: gcli, + Singular: "component", + Plural: "components", + Description: "list status of components, e.g. microservices connected to the metal-apiserver", + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + OnlyCmds: genericcli.OnlyCmds(genericcli.DescribeCmd, genericcli.ListCmd, genericcli.DeleteCmd), + ListCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("uuid", "", "lists only component with this uuid") + cmd.Flags().String("type", "", "lists only component of this type") + cmd.Flags().String("identifier", "", "lists only component with this identifier") + }, + } + + return genericcli.NewCmds(cmdsConfig) +} + +func (c *component) Get(id string) (*apiv2.Component, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &adminv2.ComponentServiceGetRequest{Uuid: id} + + resp, err := c.c.Client.Adminv2().Component().Get(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get component: %w", err) + } + + return resp.Component, nil +} + +func (c *component) Create(rq any) (*apiv2.Component, error) { + panic("unimplemented") +} + +func (c *component) Delete(id string) (*apiv2.Component, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + req := &adminv2.ComponentServiceDeleteRequest{Uuid: id} + + resp, err := c.c.Client.Adminv2().Component().Delete(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to delete component: %w", err) + } + + return resp.Component, nil +} +func (c *component) List() ([]*apiv2.Component, error) { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + query := &apiv2.ComponentQuery{ + Uuid: pointer.PointerOrNil(viper.GetString("uuid")), + Identifier: pointer.PointerOrNil(viper.GetString("identifier")), + } + + if viper.IsSet("type") { + t, err := enum.GetEnum[apiv2.ComponentType](viper.GetString("type")) + if err != nil { + return nil, fmt.Errorf("unable to get component type of string %q %w", viper.GetString("type"), err) + } + query.Type = &t + } + + req := &adminv2.ComponentServiceListRequest{Query: query} + + resp, err := c.c.Client.Adminv2().Component().List(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get components: %w", err) + } + + return resp.Components, nil +} +func (c *component) Convert(r *apiv2.Component) (string, any, any, error) { + panic("unimplemented") +} + +func (c *component) Update(rq any) (*apiv2.Component, error) { + panic("unimplemented") +} diff --git a/cmd/tableprinters/common.go b/cmd/tableprinters/common.go index 19c30e7..78fad32 100644 --- a/cmd/tableprinters/common.go +++ b/cmd/tableprinters/common.go @@ -30,6 +30,11 @@ func (t *TablePrinter) ToHeaderAndRows(data any, wide bool) ([]string, [][]strin case *config.Contexts: return t.ContextTable(d, wide) + case *apiv2.Component: + return t.ComponentTable(pointer.WrapInSlice(d), wide) + case []*apiv2.Component: + return t.ComponentTable(d, wide) + case *apiv2.IP: return t.IPTable(pointer.WrapInSlice(d), wide) case []*apiv2.IP: diff --git a/cmd/tableprinters/component.go b/cmd/tableprinters/component.go new file mode 100644 index 0000000..6e19583 --- /dev/null +++ b/cmd/tableprinters/component.go @@ -0,0 +1,33 @@ +package tableprinters + +import ( + "time" + + "github.com/metal-stack/api/go/enum" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" +) + +func (t *TablePrinter) ComponentTable(data []*apiv2.Component, wide bool) ([]string, [][]string, error) { + var ( + rows [][]string + header = []string{"ID", "Type", "Identifier", "Started", "Age", "Version", "Token", "Token Expires In"} + ) + + for _, c := range data { + typeString, err := enum.GetStringValue(c.Type) + if err != nil { + return nil, nil, err + } + + started := humanizeDuration(time.Since(c.StartedAt.AsTime())) + age := humanizeDuration(time.Since(c.ReportedAt.AsTime())) + + tokenExpiresIn := humanizeDuration(time.Until(c.Token.Expires.AsTime())) + + rows = append(rows, []string{c.Uuid, *typeString, c.Identifier, started, age, c.Version.Version, c.Token.Uuid, tokenExpiresIn}) + } + + t.t.DisableAutoWrap(false) + + return header, rows, nil +} From ed849d6f92d0e9f4d84d23565ca11e972a530a77 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Mon, 23 Feb 2026 14:50:01 +0100 Subject: [PATCH 2/2] Colored hints --- cmd/tableprinters/component.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/cmd/tableprinters/component.go b/cmd/tableprinters/component.go index 6e19583..fddb4c2 100644 --- a/cmd/tableprinters/component.go +++ b/cmd/tableprinters/component.go @@ -3,6 +3,7 @@ package tableprinters import ( "time" + "github.com/fatih/color" "github.com/metal-stack/api/go/enum" apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" ) @@ -20,9 +21,21 @@ func (t *TablePrinter) ComponentTable(data []*apiv2.Component, wide bool) ([]str } started := humanizeDuration(time.Since(c.StartedAt.AsTime())) - age := humanizeDuration(time.Since(c.ReportedAt.AsTime())) + ageAsDuration := time.Since(c.ReportedAt.AsTime()) + age := humanizeDuration(ageAsDuration) - tokenExpiresIn := humanizeDuration(time.Until(c.Token.Expires.AsTime())) + if c.Interval != nil && c.Interval.AsDuration() < ageAsDuration { + age = color.RedString(age) + } + + tokenExpirationAsDuration := time.Until(c.Token.Expires.AsTime()) + tokenExpiresIn := humanizeDuration(tokenExpirationAsDuration) + + if tokenExpirationAsDuration < time.Hour { + tokenExpiresIn = color.YellowString(tokenExpiresIn) + } else if tokenExpirationAsDuration < 0 { + tokenExpiresIn = color.RedString(tokenExpiresIn) + } rows = append(rows, []string{c.Uuid, *typeString, c.Identifier, started, age, c.Version.Version, c.Token.Uuid, tokenExpiresIn}) }