New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix new docker config format for private registries #12717
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,13 @@ import ( | |
"github.com/golang/glog" | ||
) | ||
|
||
// DockerConfigJson represents ~/.docker/config.json file info | ||
// see https://github.com/docker/docker/pull/12009 | ||
type DockerConfigJson struct { | ||
Auths DockerConfig `json:"auths"` | ||
HttpHeaders map[string]string `json:"HttpHeaders,omitempty"` | ||
} | ||
|
||
// DockerConfig represents the config file used by the docker CLI. | ||
// This config that represents the credentials that should be used | ||
// when pulling images from specific image repositories. | ||
|
@@ -47,8 +54,11 @@ var ( | |
workingDirPath = "" | ||
homeDirPath = os.Getenv("HOME") | ||
rootDirPath = "/" | ||
homeJsonDirPath = filepath.Join(homeDirPath, ".docker") | ||
rootJsonDirPath = filepath.Join(rootDirPath, ".docker") | ||
|
||
configFileName = ".dockercfg" | ||
configFileName = ".dockercfg" | ||
configJsonFileName = "config.json" | ||
) | ||
|
||
func SetPreferredDockercfgPath(path string) { | ||
|
@@ -64,6 +74,32 @@ func GetPreferredDockercfgPath() string { | |
} | ||
|
||
func ReadDockerConfigFile() (cfg DockerConfig, err error) { | ||
// Try happy path first - latest config file | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How is docker handling the transition? Are the files complementary? If so, we should probably have two different provider instances to build the keyring. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://github.com/docker/docker/blob/master/cliconfig/config.go#L159 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Can you add a comment explaining the behavior and why we're doing it? It struck me as a little odd when I read it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function
The first four one are new type of secret, and the last four one are the old type. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd agree that having multiple providers, one that passes ".docker/config.json" and one that passes ".dockercfg" would be preferable because it blends the keyrings. This is what I did here: #13894 However, I didn't realize this existed when I wrote that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think having two separate providers for .docker/config.json" and ".dockercfg" makes sense. It clearly states in the issue that the format is also changed between the two so we cannot use the same secret for both providers. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new format is a strict superset of the old, so these are not mutually exclusive options. The new format adds a level of JSON around an identical structure, and at the top-level can now also include This is clearly possible via To support |
||
dockerConfigJsonLocations := []string{GetPreferredDockercfgPath(), workingDirPath, homeJsonDirPath, rootJsonDirPath} | ||
for _, configPath := range dockerConfigJsonLocations { | ||
absDockerConfigFileLocation, err := filepath.Abs(filepath.Join(configPath, configJsonFileName)) | ||
if err != nil { | ||
glog.Errorf("while trying to canonicalize %s: %v", configPath, err) | ||
continue | ||
} | ||
glog.V(4).Infof("looking for .docker/config.json at %s", absDockerConfigFileLocation) | ||
contents, err := ioutil.ReadFile(absDockerConfigFileLocation) | ||
if os.IsNotExist(err) { | ||
continue | ||
} | ||
if err != nil { | ||
glog.V(4).Infof("while trying to read %s: %v", absDockerConfigFileLocation, err) | ||
continue | ||
} | ||
cfg, err := readDockerConfigJsonFileFromBytes(contents) | ||
if err == nil { | ||
glog.V(4).Infof("found .docker/config.json at %s", absDockerConfigFileLocation) | ||
return cfg, nil | ||
} | ||
} | ||
glog.V(4).Infof("couldn't find valid .docker/config.json after checking in %v", dockerConfigJsonLocations) | ||
|
||
// Can't find latest config file so check for the old one | ||
dockerConfigFileLocations := []string{GetPreferredDockercfgPath(), workingDirPath, homeDirPath, rootDirPath} | ||
for _, configPath := range dockerConfigFileLocations { | ||
absDockerConfigFileLocation, err := filepath.Abs(filepath.Join(configPath, configFileName)) | ||
|
@@ -147,6 +183,16 @@ func readDockerConfigFileFromBytes(contents []byte) (cfg DockerConfig, err error | |
return | ||
} | ||
|
||
func readDockerConfigJsonFileFromBytes(contents []byte) (cfg DockerConfig, err error) { | ||
var cfgJson DockerConfigJson | ||
if err = json.Unmarshal(contents, &cfgJson); err != nil { | ||
glog.Errorf("while trying to parse blob %q: %v", contents, err) | ||
return nil, err | ||
} | ||
cfg = cfgJson.Auths | ||
return | ||
} | ||
|
||
// dockerConfigEntryWithAuth is used solely for deserializing the Auth field | ||
// into a dockerConfigEntry during JSON deserialization. | ||
type dockerConfigEntryWithAuth struct { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a comment about what this is for? (Based on the docs here: https://docs.docker.com/reference/commandline/cli/, it looks like a set of blindly attached headers) and an indication that we don't use or respect them (yet).
@erictune Are you willing to take the
HttpHeaders
support as a follow on item? I think we'd gain a lot of benefit even without plumbing them through.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This struct
DockerConfigJson
is used to store content of~/.docker/config.json
, it is the same structure as ConfigFile struct indocker/cliconfig
:In the case we don't use
HTTPHeaders
andPsFormat
, then we do not need that struct. And as in your comment below, a single methodfunc DockerConfigFrom(bytes []bytes) (DockerConfig, error)
which parse both new and old type of secrets will make code easier to maintain.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@deads2k Can you explain what you mean by "...take the HttpHeaders support as a follow on item... without plumbing them through.".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd drop HttpHeaders until they are plumbed through.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
HTTPHeaders
field is used by docker to add opaque headers to client requests. The new format supports this field, but actually implementing in the kubelet will take additional work. I'd prefer to avoid having that feature block this pull. This pull is very useful even if theHTTPHeaders
field doesn't work as expected.