From ce20c409916c5376745b2f500f7abd3fb22375c3 Mon Sep 17 00:00:00 2001 From: Brandon Mitchell Date: Thu, 20 Feb 2025 15:09:59 -0500 Subject: [PATCH] Feat: Adding a whoami command Signed-off-by: Brandon Mitchell --- cmd/regctl/registry.go | 59 +++++++++++++++++++++++++++++++++---- cmd/regctl/registry_test.go | 20 +++++++++++++ types/errs/error.go | 2 ++ 3 files changed, 75 insertions(+), 6 deletions(-) diff --git a/cmd/regctl/registry.go b/cmd/regctl/registry.go index ef70f0f..6dc3ac5 100644 --- a/cmd/regctl/registry.go +++ b/cmd/regctl/registry.go @@ -16,6 +16,7 @@ import ( "github.com/regclient/regclient" "github.com/regclient/regclient/config" "github.com/regclient/regclient/pkg/template" + "github.com/regclient/regclient/types/errs" "github.com/regclient/regclient/types/ref" ) @@ -122,6 +123,20 @@ regctl registry set quay.io --req-per-sec 10`, ValidArgsFunction: registryArgListReg, RunE: registryOpts.runRegistrySet, } + var registryWhoamiCmd = &cobra.Command{ + Use: "whoami [registry]", + Short: "show current login for a registry", + Long: `Displays the username for a given registry.`, + Example: ` +# show the login on Docker Hub +regctl registry whoami + +# show the login on another registry +regctl registry whoami registry.example.org`, + Args: cobra.RangeArgs(0, 1), + ValidArgsFunction: registryArgListReg, + RunE: registryOpts.runRegistryWhoami, + } registryConfigCmd.Flags().StringVar(®istryOpts.formatConf, "format", "{{jsonPretty .}}", "Format output with go template syntax") @@ -173,6 +188,7 @@ regctl registry set quay.io --req-per-sec 10`, registryTopCmd.AddCommand(registryLoginCmd) registryTopCmd.AddCommand(registryLogoutCmd) registryTopCmd.AddCommand(registrySetCmd) + registryTopCmd.AddCommand(registryWhoamiCmd) return registryTopCmd } @@ -195,12 +211,6 @@ func (registryOpts *registryCmd) runRegistryConfig(cmd *cobra.Command, args []st if err != nil { return err } - // empty out the password fields, do not print them - for i := range c.Hosts { - c.Hosts[i].Pass = "" - c.Hosts[i].Token = "" - c.Hosts[i].ClientKey = "" - } if len(args) > 0 { h, ok := c.Hosts[args[0]] if !ok { @@ -208,8 +218,21 @@ func (registryOpts *registryCmd) runRegistryConfig(cmd *cobra.Command, args []st slog.String("registry", args[0])) return nil } + // load the username from a credential helper + cred := h.GetCred() + h.User = cred.User + // do not output secrets + h.Pass = "" + h.Token = "" + h.ClientKey = "" return template.Writer(cmd.OutOrStdout(), registryOpts.formatConf, h) } else { + // do not output secrets + for i := range c.Hosts { + c.Hosts[i].Pass = "" + c.Hosts[i].Token = "" + c.Hosts[i].ClientKey = "" + } return template.Writer(cmd.OutOrStdout(), registryOpts.formatConf, c) } } @@ -468,3 +491,27 @@ func (registryOpts *registryCmd) runRegistrySet(cmd *cobra.Command, args []strin slog.String("name", h.Name)) return nil } + +func (registryOpts *registryCmd) runRegistryWhoami(cmd *cobra.Command, args []string) error { + c, err := ConfigLoadDefault() + if err != nil { + return err + } + if len(args) == 0 { + args = []string{regclient.DockerRegistry} + } + h, ok := c.Hosts[args[0]] + if !ok { + return fmt.Errorf("no login found for %s%.0w", args[0], errs.ErrNoLogin) + } + cred := h.GetCred() + if cred.User == "" && cred.Token != "" { + cred.User = "" + } + if cred.User == "" { + return fmt.Errorf("no login found for %s%.0w", args[0], errs.ErrNoLogin) + } + // output the user + _, err = fmt.Fprintf(cmd.OutOrStdout(), "%s\n", cred.User) + return err +} diff --git a/cmd/regctl/registry_test.go b/cmd/regctl/registry_test.go index 229f068..faf9584 100644 --- a/cmd/regctl/registry_test.go +++ b/cmd/regctl/registry_test.go @@ -52,6 +52,11 @@ func TestRegistry(t *testing.T) { expectOut string outContains bool }{ + { + name: "whoami to an unknown server", + args: []string{"registry", "whoami", tsGoodHost}, + expectErr: errs.ErrNoLogin, + }, // disable tls { name: "set no TLS", @@ -77,6 +82,11 @@ func TestRegistry(t *testing.T) { expectOut: `"tls": "disabled",`, outContains: true, }, + { + name: "whoami to an known server without logging in", + args: []string{"registry", "whoami", tsGoodHost}, + expectErr: errs.ErrNoLogin, + }, { name: "query unauth host", args: []string{"registry", "config", tsUnauthHost}, @@ -113,6 +123,11 @@ func TestRegistry(t *testing.T) { args: []string{"registry", "config", tsGoodHost, "--format", "{{.User}}"}, expectOut: `testgooduser`, }, + { + name: "whoami to an known server", + args: []string{"registry", "whoami", tsGoodHost}, + expectOut: "testgooduser", + }, { name: "query unauth host", args: []string{"registry", "config", tsUnauthHost, "--format", "{{.User}}"}, @@ -148,6 +163,11 @@ func TestRegistry(t *testing.T) { args: []string{"registry", "config", tsGoodHost, "--format", "{{.User}}"}, expectOut: ``, }, + { + name: "whoami to an known server after logout", + args: []string{"registry", "whoami", tsGoodHost}, + expectErr: errs.ErrNoLogin, + }, { name: "check logout on unauth host", args: []string{"registry", "config", tsUnauthHost, "--format", "{{.User}}"}, diff --git a/types/errs/error.go b/types/errs/error.go index 03f1842..1821553 100644 --- a/types/errs/error.go +++ b/types/errs/error.go @@ -50,6 +50,8 @@ var ( ErrMismatch = errors.New("content does not match") // ErrMountReturnedLocation when a blob mount fails but a location header is received ErrMountReturnedLocation = errors.New("blob mount returned a location to upload") + // ErrNoLogin indicates there is no user login defined for a registry + ErrNoLogin = errors.New("no login found") // ErrNoNewChallenge indicates a challenge update did not result in any change ErrNoNewChallenge = errors.New("no new challenge") // ErrNotFound isn't there, search for your value elsewhere