@@ -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