Skip to content

Commit 930173a

Browse files
authored
Merge pull request #5969 from thaJeztah/simplify_auth_fixed
cli/command: Reapply "remove uses of GetAuthConfigKey, ParseRepositoryInfo" and add test
2 parents 3fe40e5 + 0e32baf commit 930173a

2 files changed

Lines changed: 143 additions & 10 deletions

File tree

cli/command/registry.go

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import (
1515
"github.com/docker/cli/cli/streams"
1616
"github.com/docker/cli/internal/tui"
1717
registrytypes "github.com/docker/docker/api/types/registry"
18-
"github.com/docker/docker/registry"
1918
"github.com/morikuni/aec"
2019
"github.com/pkg/errors"
2120
)
@@ -28,16 +27,22 @@ const (
2827
"for organizations using SSO. Learn more at https://docs.docker.com/go/access-tokens/"
2928
)
3029

30+
// authConfigKey is the key used to store credentials for Docker Hub. It is
31+
// a copy of [registry.IndexServer].
32+
//
33+
// [registry.IndexServer]: https://pkg.go.dev/github.com/docker/docker/registry#IndexServer
34+
const authConfigKey = "https://index.docker.io/v1/"
35+
3136
// RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
3237
// for the given command.
3338
func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInfo, cmdName string) registrytypes.RequestAuthConfig {
39+
configKey := getAuthConfigKey(index.Name)
40+
isDefaultRegistry := configKey == authConfigKey || index.Official
3441
return func(ctx context.Context) (string, error) {
3542
_, _ = fmt.Fprintf(cli.Out(), "\nLogin prior to %s:\n", cmdName)
36-
indexServer := registry.GetAuthConfigKey(index)
37-
isDefaultRegistry := indexServer == registry.IndexServer
38-
authConfig, err := GetDefaultAuthConfig(cli.ConfigFile(), true, indexServer, isDefaultRegistry)
43+
authConfig, err := GetDefaultAuthConfig(cli.ConfigFile(), true, configKey, isDefaultRegistry)
3944
if err != nil {
40-
_, _ = fmt.Fprintf(cli.Err(), "Unable to retrieve stored credentials for %s, error: %s.\n", indexServer, err)
45+
_, _ = fmt.Fprintf(cli.Err(), "Unable to retrieve stored credentials for %s, error: %s.\n", authConfigKey, err)
4146
}
4247

4348
select {
@@ -46,7 +51,7 @@ func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInf
4651
default:
4752
}
4853

49-
authConfig, err = PromptUserForCredentials(ctx, cli, "", "", authConfig.Username, indexServer)
54+
authConfig, err = PromptUserForCredentials(ctx, cli, "", "", authConfig.Username, authConfigKey)
5055
if err != nil {
5156
return "", err
5257
}
@@ -63,7 +68,7 @@ func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInf
6368
func ResolveAuthConfig(cfg *configfile.ConfigFile, index *registrytypes.IndexInfo) registrytypes.AuthConfig {
6469
configKey := index.Name
6570
if index.Official {
66-
configKey = registry.IndexServer
71+
configKey = authConfigKey
6772
}
6873

6974
a, _ := cfg.GetAuthConfig(configKey)
@@ -132,7 +137,7 @@ func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword
132137

133138
argUser = strings.TrimSpace(argUser)
134139
if argUser == "" {
135-
if serverAddress == registry.IndexServer {
140+
if serverAddress == authConfigKey {
136141
// When signing in to the default (Docker Hub) registry, we display
137142
// hints for creating an account, and (if hints are enabled), using
138143
// a token instead of a password.
@@ -225,9 +230,25 @@ func resolveAuthConfigFromImage(cfg *configfile.ConfigFile, image string) (regis
225230
if err != nil {
226231
return registrytypes.AuthConfig{}, err
227232
}
228-
repoInfo, err := registry.ParseRepositoryInfo(registryRef)
233+
configKey := getAuthConfigKey(reference.Domain(registryRef))
234+
a, err := cfg.GetAuthConfig(configKey)
229235
if err != nil {
230236
return registrytypes.AuthConfig{}, err
231237
}
232-
return ResolveAuthConfig(cfg, repoInfo.Index), nil
238+
return registrytypes.AuthConfig(a), nil
239+
}
240+
241+
// getAuthConfigKey special-cases using the full index address of the official
242+
// index as the AuthConfig key, and uses the (host)name[:port] for private indexes.
243+
//
244+
// It is similar to [registry.GetAuthConfigKey], but does not require on
245+
// [registrytypes.IndexInfo] as intermediate.
246+
//
247+
// [registry.GetAuthConfigKey]: https://pkg.go.dev/github.com/docker/docker/registry#GetAuthConfigKey
248+
// [registrytypes.IndexInfo]:https://pkg.go.dev/github.com/docker/docker/api/types/registry#IndexInfo
249+
func getAuthConfigKey(domainName string) string {
250+
if domainName == "docker.io" || domainName == "index.docker.io" {
251+
return authConfigKey
252+
}
253+
return domainName
233254
}

cli/command/registry_test.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package command_test
22

33
import (
4+
"bytes"
5+
"path"
46
"testing"
57

68
"github.com/docker/cli/cli/command"
@@ -80,3 +82,113 @@ func TestGetDefaultAuthConfig_HelperError(t *testing.T) {
8082
assert.Check(t, is.DeepEqual(expectedAuthConfig, authconfig))
8183
assert.Check(t, is.ErrorContains(err, "docker-credential-fake-does-not-exist"))
8284
}
85+
86+
func TestRetrieveAuthTokenFromImage(t *testing.T) {
87+
// configFileContent contains a plain-text "username:password", as stored by
88+
// the plain-text store;
89+
// https://github.com/docker/cli/blob/v28.0.4/cli/config/configfile/file.go#L218-L229
90+
const configFileContent = `{"auths": {
91+
"https://index.docker.io/v1/": {"auth": "dXNlcm5hbWU6cGFzc3dvcmQ="},
92+
"[::1]": {"auth": "dXNlcm5hbWU6cGFzc3dvcmQ="},
93+
"[::1]:5000": {"auth": "dXNlcm5hbWU6cGFzc3dvcmQ="},
94+
"127.0.0.1": {"auth": "dXNlcm5hbWU6cGFzc3dvcmQ="},
95+
"127.0.0.1:5000": {"auth": "dXNlcm5hbWU6cGFzc3dvcmQ="},
96+
"localhost": {"auth": "dXNlcm5hbWU6cGFzc3dvcmQ="},
97+
"localhost:5000": {"auth": "dXNlcm5hbWU6cGFzc3dvcmQ="},
98+
"registry-1.docker.io": {"auth": "dXNlcm5hbWU6cGFzc3dvcmQ="},
99+
"registry.hub.docker.com": {"auth": "dXNlcm5hbWU6cGFzc3dvcmQ="}
100+
}
101+
}`
102+
cfg := configfile.ConfigFile{}
103+
err := cfg.LoadFromReader(bytes.NewReader([]byte(configFileContent)))
104+
assert.NilError(t, err)
105+
106+
remoteRefs := []string{
107+
"ubuntu",
108+
"ubuntu:latest",
109+
"ubuntu:latest@sha256:72297848456d5d37d1262630108ab308d3e9ec7ed1c3286a32fe09856619a782",
110+
"ubuntu@sha256:72297848456d5d37d1262630108ab308d3e9ec7ed1c3286a32fe09856619a782",
111+
"library/ubuntu",
112+
"library/ubuntu:latest",
113+
"library/ubuntu:latest@sha256:72297848456d5d37d1262630108ab308d3e9ec7ed1c3286a32fe09856619a782",
114+
"library/ubuntu@sha256:72297848456d5d37d1262630108ab308d3e9ec7ed1c3286a32fe09856619a782",
115+
}
116+
117+
tests := []struct {
118+
prefix string
119+
expectedAddress string
120+
expectedAuthCfg registry.AuthConfig
121+
}{
122+
{
123+
prefix: "",
124+
expectedAddress: "https://index.docker.io/v1/",
125+
expectedAuthCfg: registry.AuthConfig{Username: "username", Password: "password", ServerAddress: "https://index.docker.io/v1/"},
126+
},
127+
{
128+
prefix: "docker.io",
129+
expectedAddress: "https://index.docker.io/v1/",
130+
expectedAuthCfg: registry.AuthConfig{Username: "username", Password: "password", ServerAddress: "https://index.docker.io/v1/"},
131+
},
132+
{
133+
prefix: "index.docker.io",
134+
expectedAddress: "https://index.docker.io/v1/",
135+
expectedAuthCfg: registry.AuthConfig{Username: "username", Password: "password", ServerAddress: "https://index.docker.io/v1/"},
136+
},
137+
{
138+
// FIXME(thaJeztah): registry-1.docker.io (the actual registry) is the odd one out, and is stored separate from other URLs used for docker hub's registry
139+
prefix: "registry-1.docker.io",
140+
expectedAuthCfg: registry.AuthConfig{Username: "username", Password: "password", ServerAddress: "registry-1.docker.io"},
141+
},
142+
{
143+
// FIXME(thaJeztah): registry.hub.docker.com is stored separate from other URLs used for docker hub's registry
144+
prefix: "registry.hub.docker.com",
145+
expectedAuthCfg: registry.AuthConfig{Username: "username", Password: "password", ServerAddress: "registry.hub.docker.com"},
146+
},
147+
{
148+
prefix: "[::1]",
149+
expectedAddress: "[::1]",
150+
expectedAuthCfg: registry.AuthConfig{Username: "username", Password: "password", ServerAddress: "[::1]"},
151+
},
152+
{
153+
prefix: "[::1]:5000",
154+
expectedAddress: "[::1]:5000",
155+
expectedAuthCfg: registry.AuthConfig{Username: "username", Password: "password", ServerAddress: "[::1]:5000"},
156+
},
157+
{
158+
prefix: "127.0.0.1",
159+
expectedAddress: "127.0.0.1",
160+
expectedAuthCfg: registry.AuthConfig{Username: "username", Password: "password", ServerAddress: "127.0.0.1"},
161+
},
162+
{
163+
prefix: "localhost",
164+
expectedAddress: "localhost",
165+
expectedAuthCfg: registry.AuthConfig{Username: "username", Password: "password", ServerAddress: "localhost"},
166+
},
167+
{
168+
prefix: "localhost:5000",
169+
expectedAddress: "localhost:5000",
170+
expectedAuthCfg: registry.AuthConfig{Username: "username", Password: "password", ServerAddress: "localhost:5000"},
171+
},
172+
{
173+
prefix: "no-auth.example.com",
174+
expectedAuthCfg: registry.AuthConfig{},
175+
},
176+
}
177+
178+
for _, tc := range tests {
179+
tcName := tc.prefix
180+
if tc.prefix == "" {
181+
tcName = "no-prefix"
182+
}
183+
t.Run(tcName, func(t *testing.T) {
184+
for _, remoteRef := range remoteRefs {
185+
imageRef := path.Join(tc.prefix, remoteRef)
186+
actual, err := command.RetrieveAuthTokenFromImage(&cfg, imageRef)
187+
assert.NilError(t, err)
188+
ac, err := registry.DecodeAuthConfig(actual)
189+
assert.NilError(t, err)
190+
assert.Check(t, is.DeepEqual(*ac, tc.expectedAuthCfg))
191+
}
192+
})
193+
}
194+
}

0 commit comments

Comments
 (0)