@@ -48,11 +48,27 @@ type Keychain interface {
48
48
// defaultKeychain implements Keychain with the semantics of the standard Docker
49
49
// credential keychain.
50
50
type defaultKeychain struct {
51
- mu sync.Mutex
51
+ once sync.Once
52
+ cfg types.AuthConfig
53
+
54
+ configFilePath string
52
55
}
53
56
54
57
var (
55
- // DefaultKeychain implements Keychain by interpreting the docker config file.
58
+ // DefaultKeychain implements Keychain by interpreting the Docker config file.
59
+ // This matches the behavior of tools like `docker` and `podman`.
60
+ //
61
+ // This keychain looks for credentials configured in a few places, in order:
62
+ //
63
+ // 1. $HOME/.docker/config.json
64
+ // 2. $DOCKER_CONFIG/config.json
65
+ // 3. $XDG_RUNTIME_DIR/containers/auth.json (for compatibility with Podman)
66
+ //
67
+ // If a config file is found and can be parsed, Resolve will return credentials
68
+ // configured by the file for the given registry.
69
+ //
70
+ // If no config file is found, Resolve returns Anonymous.
71
+ // If a config file is found but can't be parsed, Resolve returns an error.
56
72
DefaultKeychain = RefreshingKeychain (& defaultKeychain {}, 5 * time .Minute )
57
73
)
58
74
@@ -62,11 +78,16 @@ const (
62
78
DefaultAuthKey = "https://" + name .DefaultRegistry + "/v1/"
63
79
)
64
80
65
- // Resolve implements Keychain.
66
- func (dk * defaultKeychain ) Resolve (target Resource ) (Authenticator , error ) {
67
- dk .mu .Lock ()
68
- defer dk .mu .Unlock ()
81
+ // NewConfigKeychain implements Keychain by interpreting the Docker config file
82
+ // at the specified file path.
83
+ //
84
+ // It acts like DefaultKeychain except that the exact path of the file can be specified,
85
+ // instead of being dependent on environment variables and conventional file names.
86
+ func NewConfigKeychain (filename string ) Keychain {
87
+ return & defaultKeychain {configFilePath : filename }
88
+ }
69
89
90
+ func getDefaultConfigFile () (* configfile.ConfigFile , error ) {
70
91
// Podman users may have their container registry auth configured in a
71
92
// different location, that Docker packages aren't aware of.
72
93
// If the Docker config file isn't found, we'll fallback to look where
@@ -99,39 +120,73 @@ func (dk *defaultKeychain) Resolve(target Resource) (Authenticator, error) {
99
120
} else {
100
121
f , err := os .Open (filepath .Join (os .Getenv ("XDG_RUNTIME_DIR" ), "containers/auth.json" ))
101
122
if err != nil {
102
- return Anonymous , nil
123
+ return nil , nil
103
124
}
104
125
defer f .Close ()
105
126
cf , err = config .LoadFromReader (f )
106
127
if err != nil {
107
128
return nil , err
108
129
}
109
130
}
131
+ return cf , nil
132
+ }
110
133
111
- // See:
112
- // https://github.com/google/ko/issues/90
113
- // https://github.com/moby/moby/blob/fc01c2b481097a6057bec3cd1ab2d7b4488c50c4/registry/config.go#L397-L404
114
- var cfg , empty types.AuthConfig
115
- for _ , key := range []string {
116
- target .String (),
117
- target .RegistryStr (),
118
- } {
119
- if key == name .DefaultRegistry {
120
- key = DefaultAuthKey
134
+ func (dk * defaultKeychain ) Resolve (target Resource ) (Authenticator , error ) {
135
+ var err error
136
+ var empty types.AuthConfig
137
+ dk .once .Do (func () {
138
+ var cf * configfile.ConfigFile
139
+ if dk .configFilePath == "" {
140
+ cf , err = getDefaultConfigFile ()
141
+ if err != nil {
142
+ return
143
+ }
144
+ if cf == nil {
145
+ dk .cfg = empty
146
+ return
147
+ }
148
+ } else {
149
+ var f * os.File
150
+ f , err = os .Open (dk .configFilePath )
151
+ if err != nil {
152
+ return
153
+ }
154
+ defer f .Close ()
155
+ cf , err = config .LoadFromReader (f )
156
+ if err != nil {
157
+ return
158
+ }
121
159
}
122
160
123
- cfg , err = cf .GetAuthConfig (key )
124
- if err != nil {
125
- return nil , err
126
- }
127
- // cf.GetAuthConfig automatically sets the ServerAddress attribute. Since
128
- // we don't make use of it, clear the value for a proper "is-empty" test.
129
- // See: https://github.com/google/go-containerregistry/issues/1510
130
- cfg .ServerAddress = ""
131
- if cfg != empty {
132
- break
161
+ // See:
162
+ // https://github.com/google/ko/issues/90
163
+ // https://github.com/moby/moby/blob/fc01c2b481097a6057bec3cd1ab2d7b4488c50c4/registry/config.go#L397-L404
164
+ for _ , key := range []string {
165
+ target .String (),
166
+ target .RegistryStr (),
167
+ } {
168
+ if key == name .DefaultRegistry {
169
+ key = DefaultAuthKey
170
+ }
171
+
172
+ dk .cfg , err = cf .GetAuthConfig (key )
173
+ if err != nil {
174
+ return
175
+ }
176
+ // cf.GetAuthConfig automatically sets the ServerAddress attribute. Since
177
+ // we don't make use of it, clear the value for a proper "is-empty" test.
178
+ // See: https://github.com/google/go-containerregistry/issues/1510
179
+ dk .cfg .ServerAddress = ""
180
+ if dk .cfg != empty {
181
+ break
182
+ }
133
183
}
184
+ })
185
+ if err != nil {
186
+ return nil , err
134
187
}
188
+
189
+ cfg := dk .cfg
135
190
if cfg == empty {
136
191
return Anonymous , nil
137
192
}
0 commit comments