github.com/ld86/docker@v1.7.1-rc3/cliconfig/config.go (about)

     1  package cliconfig
     2  
     3  import (
     4  	"encoding/base64"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  
    13  	"github.com/docker/docker/pkg/homedir"
    14  )
    15  
    16  const (
    17  	// Where we store the config file
    18  	CONFIGFILE     = "config.json"
    19  	OLD_CONFIGFILE = ".dockercfg"
    20  
    21  	// This constant is only used for really old config files when the
    22  	// URL wasn't saved as part of the config file and it was just
    23  	// assumed to be this value.
    24  	DEFAULT_INDEXSERVER = "https://index.docker.io/v1/"
    25  )
    26  
    27  var (
    28  	ErrConfigFileMissing = errors.New("The Auth config file is missing")
    29  )
    30  
    31  // Registry Auth Info
    32  type AuthConfig struct {
    33  	Username      string `json:"username,omitempty"`
    34  	Password      string `json:"password,omitempty"`
    35  	Auth          string `json:"auth"`
    36  	Email         string `json:"email"`
    37  	ServerAddress string `json:"serveraddress,omitempty"`
    38  }
    39  
    40  // ~/.docker/config.json file info
    41  type ConfigFile struct {
    42  	AuthConfigs map[string]AuthConfig `json:"auths"`
    43  	HttpHeaders map[string]string     `json:"HttpHeaders,omitempty"`
    44  	filename    string                // Note: not serialized - for internal use only
    45  }
    46  
    47  func NewConfigFile(fn string) *ConfigFile {
    48  	return &ConfigFile{
    49  		AuthConfigs: make(map[string]AuthConfig),
    50  		HttpHeaders: make(map[string]string),
    51  		filename:    fn,
    52  	}
    53  }
    54  
    55  // load up the auth config information and return values
    56  // FIXME: use the internal golang config parser
    57  func Load(configDir string) (*ConfigFile, error) {
    58  	if configDir == "" {
    59  		configDir = filepath.Join(homedir.Get(), ".docker")
    60  	}
    61  
    62  	configFile := ConfigFile{
    63  		AuthConfigs: make(map[string]AuthConfig),
    64  		filename:    filepath.Join(configDir, CONFIGFILE),
    65  	}
    66  
    67  	// Try happy path first - latest config file
    68  	if _, err := os.Stat(configFile.filename); err == nil {
    69  		file, err := os.Open(configFile.filename)
    70  		if err != nil {
    71  			return &configFile, err
    72  		}
    73  		defer file.Close()
    74  
    75  		if err := json.NewDecoder(file).Decode(&configFile); err != nil {
    76  			return &configFile, err
    77  		}
    78  
    79  		for addr, ac := range configFile.AuthConfigs {
    80  			ac.Username, ac.Password, err = DecodeAuth(ac.Auth)
    81  			if err != nil {
    82  				return &configFile, err
    83  			}
    84  			ac.Auth = ""
    85  			ac.ServerAddress = addr
    86  			configFile.AuthConfigs[addr] = ac
    87  		}
    88  
    89  		return &configFile, nil
    90  	} else if !os.IsNotExist(err) {
    91  		// if file is there but we can't stat it for any reason other
    92  		// than it doesn't exist then stop
    93  		return &configFile, err
    94  	}
    95  
    96  	// Can't find latest config file so check for the old one
    97  	confFile := filepath.Join(homedir.Get(), OLD_CONFIGFILE)
    98  
    99  	if _, err := os.Stat(confFile); err != nil {
   100  		return &configFile, nil //missing file is not an error
   101  	}
   102  
   103  	b, err := ioutil.ReadFile(confFile)
   104  	if err != nil {
   105  		return &configFile, err
   106  	}
   107  
   108  	if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil {
   109  		arr := strings.Split(string(b), "\n")
   110  		if len(arr) < 2 {
   111  			return &configFile, fmt.Errorf("The Auth config file is empty")
   112  		}
   113  		authConfig := AuthConfig{}
   114  		origAuth := strings.Split(arr[0], " = ")
   115  		if len(origAuth) != 2 {
   116  			return &configFile, fmt.Errorf("Invalid Auth config file")
   117  		}
   118  		authConfig.Username, authConfig.Password, err = DecodeAuth(origAuth[1])
   119  		if err != nil {
   120  			return &configFile, err
   121  		}
   122  		origEmail := strings.Split(arr[1], " = ")
   123  		if len(origEmail) != 2 {
   124  			return &configFile, fmt.Errorf("Invalid Auth config file")
   125  		}
   126  		authConfig.Email = origEmail[1]
   127  		authConfig.ServerAddress = DEFAULT_INDEXSERVER
   128  		configFile.AuthConfigs[DEFAULT_INDEXSERVER] = authConfig
   129  	} else {
   130  		for k, authConfig := range configFile.AuthConfigs {
   131  			authConfig.Username, authConfig.Password, err = DecodeAuth(authConfig.Auth)
   132  			if err != nil {
   133  				return &configFile, err
   134  			}
   135  			authConfig.Auth = ""
   136  			authConfig.ServerAddress = k
   137  			configFile.AuthConfigs[k] = authConfig
   138  		}
   139  	}
   140  	return &configFile, nil
   141  }
   142  
   143  func (configFile *ConfigFile) Save() error {
   144  	// Encode sensitive data into a new/temp struct
   145  	tmpAuthConfigs := make(map[string]AuthConfig, len(configFile.AuthConfigs))
   146  	for k, authConfig := range configFile.AuthConfigs {
   147  		authCopy := authConfig
   148  
   149  		authCopy.Auth = EncodeAuth(&authCopy)
   150  		authCopy.Username = ""
   151  		authCopy.Password = ""
   152  		authCopy.ServerAddress = ""
   153  		tmpAuthConfigs[k] = authCopy
   154  	}
   155  
   156  	saveAuthConfigs := configFile.AuthConfigs
   157  	configFile.AuthConfigs = tmpAuthConfigs
   158  	defer func() { configFile.AuthConfigs = saveAuthConfigs }()
   159  
   160  	data, err := json.MarshalIndent(configFile, "", "\t")
   161  	if err != nil {
   162  		return err
   163  	}
   164  
   165  	if err := os.MkdirAll(filepath.Dir(configFile.filename), 0700); err != nil {
   166  		return err
   167  	}
   168  
   169  	if err := ioutil.WriteFile(configFile.filename, data, 0600); err != nil {
   170  		return err
   171  	}
   172  
   173  	return nil
   174  }
   175  
   176  func (config *ConfigFile) Filename() string {
   177  	return config.filename
   178  }
   179  
   180  // create a base64 encoded auth string to store in config
   181  func EncodeAuth(authConfig *AuthConfig) string {
   182  	authStr := authConfig.Username + ":" + authConfig.Password
   183  	msg := []byte(authStr)
   184  	encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg)))
   185  	base64.StdEncoding.Encode(encoded, msg)
   186  	return string(encoded)
   187  }
   188  
   189  // decode the auth string
   190  func DecodeAuth(authStr string) (string, string, error) {
   191  	decLen := base64.StdEncoding.DecodedLen(len(authStr))
   192  	decoded := make([]byte, decLen)
   193  	authByte := []byte(authStr)
   194  	n, err := base64.StdEncoding.Decode(decoded, authByte)
   195  	if err != nil {
   196  		return "", "", err
   197  	}
   198  	if n > decLen {
   199  		return "", "", fmt.Errorf("Something went wrong decoding auth config")
   200  	}
   201  	arr := strings.SplitN(string(decoded), ":", 2)
   202  	if len(arr) != 2 {
   203  		return "", "", fmt.Errorf("Invalid auth configuration file")
   204  	}
   205  	password := strings.Trim(arr[1], "\x00")
   206  	return arr[0], password, nil
   207  }