diff --git a/cli/command/registry/logout.go b/cli/command/registry/logout.go index 16218f67d9a7..8ed29e2c7324 100644 --- a/cli/command/registry/logout.go +++ b/cli/command/registry/logout.go @@ -60,6 +60,13 @@ func runLogout(ctx context.Context, dockerCLI command.Cli, serverAddress string) // the tries below are kept for backward compatibility where a user could have // saved the registry in one of the following format. regsToLogout = append(regsToLogout, hostnameAddress, "http://"+hostnameAddress, "https://"+hostnameAddress) + // Credentials for the default registry are stored under the full index + // address, which is the key "docker login docker.io" writes to. The + // hostnames below are the ones that getAuthConfigKey in + // cli/config/configfile maps to that key. + if hostnameAddress == registry.DefaultNamespace || hostnameAddress == registry.IndexHostname { + regsToLogout = append(regsToLogout, registry.IndexServer) + } } if isDefaultRegistry { diff --git a/cli/command/registry/logout_test.go b/cli/command/registry/logout_test.go new file mode 100644 index 000000000000..e7212d09d6d4 --- /dev/null +++ b/cli/command/registry/logout_test.go @@ -0,0 +1,50 @@ +package registry + +import ( + "context" + "path/filepath" + "testing" + + "github.com/docker/cli/cli/config/configfile" + "github.com/docker/cli/internal/test" + "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" +) + +// TestLogoutRemovesCredentialsStoredByLogin verifies that logging out with the +// same argument that was used to log in removes the stored credentials. +// +// Credentials for the default registry are stored under +// [registry.IndexServer] regardless of the spelling passed to "docker login", +// so logout has to look for that key as well to find them again. +func TestLogoutRemovesCredentialsStoredByLogin(t *testing.T) { + for _, serverAddress := range []string{ + "", + "docker.io", + "index.docker.io", + "https://index.docker.io/v1/", + "myreg.example.com", + } { + name := serverAddress + if name == "" { + name = "no server address" + } + t.Run(name, func(t *testing.T) { + configFile := configfile.New(filepath.Join(t.TempDir(), "config.json")) + cli := test.NewFakeCli(&fakeClient{}) + cli.SetConfigFile(configFile) + + err := runLogin(context.Background(), cli, loginOptions{ + serverAddress: serverAddress, + user: "my-username", + password: "my-password", + }) + assert.NilError(t, err) + assert.Assert(t, is.Len(configFile.AuthConfigs, 1), "login did not store credentials") + + err = runLogout(context.Background(), cli, serverAddress) + assert.NilError(t, err) + assert.Check(t, is.Len(configFile.AuthConfigs, 0)) + }) + } +}