@@ -48,11 +48,27 @@ type Keychain interface {
4848// defaultKeychain implements Keychain with the semantics of the standard Docker
4949// credential keychain.
5050type defaultKeychain struct {
51- mu sync.Mutex
51+ once sync.Once
52+ cfg types.AuthConfig
53+
54+ configFilePath string
5255}
5356
5457var (
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.
5672 DefaultKeychain = RefreshingKeychain (& defaultKeychain {}, 5 * time .Minute )
5773)
5874
@@ -62,11 +78,16 @@ const (
6278 DefaultAuthKey = "https://" + name .DefaultRegistry + "/v1/"
6379)
6480
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+ }
6989
90+ func getDefaultConfigFile () (* configfile.ConfigFile , error ) {
7091 // Podman users may have their container registry auth configured in a
7192 // different location, that Docker packages aren't aware of.
7293 // 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) {
99120 } else {
100121 f , err := os .Open (filepath .Join (os .Getenv ("XDG_RUNTIME_DIR" ), "containers/auth.json" ))
101122 if err != nil {
102- return Anonymous , nil
123+ return nil , nil
103124 }
104125 defer f .Close ()
105126 cf , err = config .LoadFromReader (f )
106127 if err != nil {
107128 return nil , err
108129 }
109130 }
131+ return cf , nil
132+ }
110133
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+ }
121159 }
122160
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+ }
133183 }
184+ })
185+ if err != nil {
186+ return nil , err
134187 }
188+
189+ cfg := dk .cfg
135190 if cfg == empty {
136191 return Anonymous , nil
137192 }
0 commit comments