docker cli ConfigFile()源码分析
tags: container,源码分析
docker cli ConfigFile()
源码分析
本文编写时,最新release为v20.10.7, 因此代码均为v20.10.7分支的代码
在执行很多命令时,都需要读取docker cli的配置,即调用ConfigFile()
,理解这个过程是有必要的。
https://github.com/docker/cli/blob/v20.10.7/cli/command/cli.go#L127
func (cli *DockerCli) ConfigFile() *configfile.ConfigFile {
if cli.configFile == nil {
cli.loadConfigFile()
}
return cli.configFile
}
func (cli *DockerCli) loadConfigFile() {
cli.configFile = cliconfig.LoadDefaultConfigFile(cli.err)
}
LoadDefaultConfigFile
实现如下
https://github.com/docker/cli/blob/v20.10.7/cli/config/config.go#L151
func LoadDefaultConfigFile(stderr io.Writer) *configfile.ConfigFile {
configFile, err := Load(Dir())
if err != nil {
fmt.Fprintf(stderr, "WARNING: Error loading config file: %v\n", err)
}
if printLegacyFileWarning {
_, _ = fmt.Fprintln(stderr, "WARNING: Support for the legacy ~/.dockercfg configuration file and file-format is deprecated and will be removed in an upcoming release")
}
if !configFile.ContainsAuth() {
configFile.CredentialsStore = credentials.DetectDefaultStore(configFile.CredentialsStore)
}
return configFile
}
Config目录优先从环境变量DOCKER_CONFIG
获取,如果未设置环境变量,则默认为$HOME/.docker
https://github.com/docker/cli/blob/v20.10.7/cli/config/config.go#L63
const (
...
configFileDir = ".docker"
...
)
func resetConfigDir() {
configDir = ""
initConfigDir = new(sync.Once)
}
func setConfigDir() {
if configDir != "" {
return
}
configDir = os.Getenv("DOCKER_CONFIG")
if configDir == "" {
configDir = filepath.Join(getHomeDir(), configFileDir)
}
}
// Dir returns the directory the configuration file is stored in
func Dir() string {
initConfigDir.Do(setConfigDir)
return configDir
}
优先从config.json
文件读取配置,如果不存在,则从.dockercfg
文件读取
https://github.com/docker/cli/blob/v20.10.7/cli/config/config.go#L113
const (
ConfigFileName = "config.json"
...
oldConfigfile = ".dockercfg"
)
func Load(configDir string) (*configfile.ConfigFile, error) {
printLegacyFileWarning = false
if configDir == "" {
configDir = Dir()
}
filename := filepath.Join(configDir, ConfigFileName)
configFile := configfile.New(filename)
// Try happy path first - latest config file
if file, err := os.Open(filename); err == nil {
defer file.Close()
err = configFile.LoadFromReader(file)
if err != nil {
err = errors.Wrap(err, filename)
}
return configFile, err
} else if !os.IsNotExist(err) {
// if file is there but we can't stat it for any reason other
// than it doesn't exist then stop
return configFile, errors.Wrap(err, filename)
}
// Can't find latest config file so check for the old one
filename = filepath.Join(getHomeDir(), oldConfigfile)
if file, err := os.Open(filename); err == nil {
printLegacyFileWarning = true
defer file.Close()
if err := configFile.LegacyLoadFromReader(file); err != nil {
return configFile, errors.Wrap(err, filename)
}
}
return configFile, nil
}
config.json
文件解析过程如下,先使用json反序列化到ConfigFile结构体,如果有Auth字段,则使用base64解码,解析得到username, password
https://github.com/docker/cli/blob/v20.10.7/cli/config/configfile/file.go#L121
func (configFile *ConfigFile) LoadFromReader(configData io.Reader) error {
if err := json.NewDecoder(configData).Decode(&configFile); err != nil && !errors.Is(err, io.EOF) {
return err
}
var err error
for addr, ac := range configFile.AuthConfigs {
if ac.Auth != "" {
ac.Username, ac.Password, err = decodeAuth(ac.Auth)
if err != nil {
return err
}
}
ac.Auth = ""
ac.ServerAddress = addr
configFile.AuthConfigs[addr] = ac
}
return checkKubernetesConfiguration(configFile.Kubernetes)
}
// decodeAuth decodes a base64 encoded string and returns username and password
func decodeAuth(authStr string) (string, string, error) {
if authStr == "" {
return "", "", nil
}
decLen := base64.StdEncoding.DecodedLen(len(authStr))
decoded := make([]byte, decLen)
authByte := []byte(authStr)
n, err := base64.StdEncoding.Decode(decoded, authByte)
if err != nil {
return "", "", err
}
if n > decLen {
return "", "", errors.Errorf("Something went wrong decoding auth config")
}
arr := strings.SplitN(string(decoded), ":", 2)
if len(arr) != 2 {
return "", "", errors.Errorf("Invalid auth configuration file")
}
password := strings.Trim(arr[1], "\x00")
return arr[0], password, nil
}
.dockercfg
文件的解析过程类似, 略
如果不存在认证相关的配置,则检测默认的CredentialsStore
func LoadDefaultConfigFile(stderr io.Writer) *configfile.ConfigFile {
...
if !configFile.ContainsAuth() {
configFile.CredentialsStore = credentials.DetectDefaultStore(configFile.CredentialsStore)
}
...