Repository: regclient/regclient
Pull request #912 (2025-02-20 20:13:07)

Feat: Adding a whoami command
<!--

Commits must be signed indicating your agreement to the [DCO](https://developercertificate.org/).
See [DCO missing](https://github.com/src-d/guide/blob/master/developer-community/fix-DCO.md) for steps to fix a missing signoff.

-->

### Fixes issue

<!-- If this is a bug fix, include "fixes #xxxx", or "closes #xxxx" -->

### Describe the change

This adds the command:

```shell
regctl registry whoami
```

which returns the current logged in user, if configured. It also updates `regctl registry config` to call the credential helper to set the username in `regctl registry config`.
<!-- Include the type of change: bug fix, new feature, breaking change, documentation update -->
<!-- Describe what was changed, why the change was made, and how it was implemented -->

### How to verify it

```shell
regctl registry whoami
regctl registry whoami ghcr.io
```
<!-- Include steps that can be taken to verify the change -->

### Changelog text

- Feat: Adding a whoami command.
<!-- If the release changelog should have an entry for this, include it here -->

### Please verify and check that the pull request fulfills the following requirements

<!-- Mark the following with an [X] to verify they are included -->

- [X] Tests have been added or not applicable
- [ ] Documentation has been added, updated, or not applicable
- [X] Changes have been rebased to main
- [X] Multiple commits to the same code have been squashed

<!-- markdownlint-disable-file MD041 -->



Patch:
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(&registryOpts.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 = "<token>"
+	}
+	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/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


Test patch:
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}}"},