I felt it made more sence 👼
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
| ... | ... |
@@ -12,10 +12,10 @@ import ( |
| 12 | 12 |
"github.com/docker/docker/api" |
| 13 | 13 |
"github.com/docker/docker/api/types" |
| 14 | 14 |
"github.com/docker/docker/api/types/versions" |
| 15 |
+ cliconfig "github.com/docker/docker/cli/config" |
|
| 16 |
+ "github.com/docker/docker/cli/config/configfile" |
|
| 17 |
+ "github.com/docker/docker/cli/config/credentials" |
|
| 15 | 18 |
cliflags "github.com/docker/docker/cli/flags" |
| 16 |
- "github.com/docker/docker/cliconfig" |
|
| 17 |
- "github.com/docker/docker/cliconfig/configfile" |
|
| 18 |
- "github.com/docker/docker/cliconfig/credentials" |
|
| 19 | 19 |
"github.com/docker/docker/client" |
| 20 | 20 |
"github.com/docker/docker/dockerversion" |
| 21 | 21 |
dopts "github.com/docker/docker/opts" |
| ... | ... |
@@ -150,7 +150,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
|
| 150 | 150 |
cli.defaultVersion = cli.client.ClientVersion() |
| 151 | 151 |
|
| 152 | 152 |
if opts.Common.TrustKey == "" {
|
| 153 |
- cli.keyFile = filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile) |
|
| 153 |
+ cli.keyFile = filepath.Join(cliconfig.Dir(), cliflags.DefaultTrustKeyFile) |
|
| 154 | 154 |
} else {
|
| 155 | 155 |
cli.keyFile = opts.Common.TrustKey |
| 156 | 156 |
} |
| ... | ... |
@@ -179,7 +179,7 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer) *DockerCli {
|
| 179 | 179 |
// LoadDefaultConfigFile attempts to load the default config file and returns |
| 180 | 180 |
// an initialized ConfigFile struct if none is found. |
| 181 | 181 |
func LoadDefaultConfigFile(err io.Writer) *configfile.ConfigFile {
|
| 182 |
- configFile, e := cliconfig.Load(cliconfig.ConfigDir()) |
|
| 182 |
+ configFile, e := cliconfig.Load(cliconfig.Dir()) |
|
| 183 | 183 |
if e != nil {
|
| 184 | 184 |
fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e) |
| 185 | 185 |
} |
| 186 | 186 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,120 @@ |
| 0 |
+package config |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "io" |
|
| 5 |
+ "os" |
|
| 6 |
+ "path/filepath" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/docker/docker/api/types" |
|
| 9 |
+ "github.com/docker/docker/cli/config/configfile" |
|
| 10 |
+ "github.com/docker/docker/pkg/homedir" |
|
| 11 |
+) |
|
| 12 |
+ |
|
| 13 |
+const ( |
|
| 14 |
+ // ConfigFileName is the name of config file |
|
| 15 |
+ ConfigFileName = "config.json" |
|
| 16 |
+ configFileDir = ".docker" |
|
| 17 |
+ oldConfigfile = ".dockercfg" |
|
| 18 |
+) |
|
| 19 |
+ |
|
| 20 |
+var ( |
|
| 21 |
+ configDir = os.Getenv("DOCKER_CONFIG")
|
|
| 22 |
+) |
|
| 23 |
+ |
|
| 24 |
+func init() {
|
|
| 25 |
+ if configDir == "" {
|
|
| 26 |
+ configDir = filepath.Join(homedir.Get(), configFileDir) |
|
| 27 |
+ } |
|
| 28 |
+} |
|
| 29 |
+ |
|
| 30 |
+// Dir returns the directory the configuration file is stored in |
|
| 31 |
+func Dir() string {
|
|
| 32 |
+ return configDir |
|
| 33 |
+} |
|
| 34 |
+ |
|
| 35 |
+// SetDir sets the directory the configuration file is stored in |
|
| 36 |
+func SetDir(dir string) {
|
|
| 37 |
+ configDir = dir |
|
| 38 |
+} |
|
| 39 |
+ |
|
| 40 |
+// NewConfigFile initializes an empty configuration file for the given filename 'fn' |
|
| 41 |
+func NewConfigFile(fn string) *configfile.ConfigFile {
|
|
| 42 |
+ return &configfile.ConfigFile{
|
|
| 43 |
+ AuthConfigs: make(map[string]types.AuthConfig), |
|
| 44 |
+ HTTPHeaders: make(map[string]string), |
|
| 45 |
+ Filename: fn, |
|
| 46 |
+ } |
|
| 47 |
+} |
|
| 48 |
+ |
|
| 49 |
+// LegacyLoadFromReader is a convenience function that creates a ConfigFile object from |
|
| 50 |
+// a non-nested reader |
|
| 51 |
+func LegacyLoadFromReader(configData io.Reader) (*configfile.ConfigFile, error) {
|
|
| 52 |
+ configFile := configfile.ConfigFile{
|
|
| 53 |
+ AuthConfigs: make(map[string]types.AuthConfig), |
|
| 54 |
+ } |
|
| 55 |
+ err := configFile.LegacyLoadFromReader(configData) |
|
| 56 |
+ return &configFile, err |
|
| 57 |
+} |
|
| 58 |
+ |
|
| 59 |
+// LoadFromReader is a convenience function that creates a ConfigFile object from |
|
| 60 |
+// a reader |
|
| 61 |
+func LoadFromReader(configData io.Reader) (*configfile.ConfigFile, error) {
|
|
| 62 |
+ configFile := configfile.ConfigFile{
|
|
| 63 |
+ AuthConfigs: make(map[string]types.AuthConfig), |
|
| 64 |
+ } |
|
| 65 |
+ err := configFile.LoadFromReader(configData) |
|
| 66 |
+ return &configFile, err |
|
| 67 |
+} |
|
| 68 |
+ |
|
| 69 |
+// Load reads the configuration files in the given directory, and sets up |
|
| 70 |
+// the auth config information and returns values. |
|
| 71 |
+// FIXME: use the internal golang config parser |
|
| 72 |
+func Load(configDir string) (*configfile.ConfigFile, error) {
|
|
| 73 |
+ if configDir == "" {
|
|
| 74 |
+ configDir = Dir() |
|
| 75 |
+ } |
|
| 76 |
+ |
|
| 77 |
+ configFile := configfile.ConfigFile{
|
|
| 78 |
+ AuthConfigs: make(map[string]types.AuthConfig), |
|
| 79 |
+ Filename: filepath.Join(configDir, ConfigFileName), |
|
| 80 |
+ } |
|
| 81 |
+ |
|
| 82 |
+ // Try happy path first - latest config file |
|
| 83 |
+ if _, err := os.Stat(configFile.Filename); err == nil {
|
|
| 84 |
+ file, err := os.Open(configFile.Filename) |
|
| 85 |
+ if err != nil {
|
|
| 86 |
+ return &configFile, fmt.Errorf("%s - %v", configFile.Filename, err)
|
|
| 87 |
+ } |
|
| 88 |
+ defer file.Close() |
|
| 89 |
+ err = configFile.LoadFromReader(file) |
|
| 90 |
+ if err != nil {
|
|
| 91 |
+ err = fmt.Errorf("%s - %v", configFile.Filename, err)
|
|
| 92 |
+ } |
|
| 93 |
+ return &configFile, err |
|
| 94 |
+ } else if !os.IsNotExist(err) {
|
|
| 95 |
+ // if file is there but we can't stat it for any reason other |
|
| 96 |
+ // than it doesn't exist then stop |
|
| 97 |
+ return &configFile, fmt.Errorf("%s - %v", configFile.Filename, err)
|
|
| 98 |
+ } |
|
| 99 |
+ |
|
| 100 |
+ // Can't find latest config file so check for the old one |
|
| 101 |
+ confFile := filepath.Join(homedir.Get(), oldConfigfile) |
|
| 102 |
+ if _, err := os.Stat(confFile); err != nil {
|
|
| 103 |
+ return &configFile, nil //missing file is not an error |
|
| 104 |
+ } |
|
| 105 |
+ file, err := os.Open(confFile) |
|
| 106 |
+ if err != nil {
|
|
| 107 |
+ return &configFile, fmt.Errorf("%s - %v", confFile, err)
|
|
| 108 |
+ } |
|
| 109 |
+ defer file.Close() |
|
| 110 |
+ err = configFile.LegacyLoadFromReader(file) |
|
| 111 |
+ if err != nil {
|
|
| 112 |
+ return &configFile, fmt.Errorf("%s - %v", confFile, err)
|
|
| 113 |
+ } |
|
| 114 |
+ |
|
| 115 |
+ if configFile.HTTPHeaders == nil {
|
|
| 116 |
+ configFile.HTTPHeaders = map[string]string{}
|
|
| 117 |
+ } |
|
| 118 |
+ return &configFile, nil |
|
| 119 |
+} |
| 0 | 120 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,621 @@ |
| 0 |
+package config |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "io/ioutil" |
|
| 4 |
+ "os" |
|
| 5 |
+ "path/filepath" |
|
| 6 |
+ "strings" |
|
| 7 |
+ "testing" |
|
| 8 |
+ |
|
| 9 |
+ "github.com/docker/docker/cli/config/configfile" |
|
| 10 |
+ "github.com/docker/docker/pkg/homedir" |
|
| 11 |
+) |
|
| 12 |
+ |
|
| 13 |
+func TestEmptyConfigDir(t *testing.T) {
|
|
| 14 |
+ tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 15 |
+ if err != nil {
|
|
| 16 |
+ t.Fatal(err) |
|
| 17 |
+ } |
|
| 18 |
+ defer os.RemoveAll(tmpHome) |
|
| 19 |
+ |
|
| 20 |
+ SetDir(tmpHome) |
|
| 21 |
+ |
|
| 22 |
+ config, err := Load("")
|
|
| 23 |
+ if err != nil {
|
|
| 24 |
+ t.Fatalf("Failed loading on empty config dir: %q", err)
|
|
| 25 |
+ } |
|
| 26 |
+ |
|
| 27 |
+ expectedConfigFilename := filepath.Join(tmpHome, ConfigFileName) |
|
| 28 |
+ if config.Filename != expectedConfigFilename {
|
|
| 29 |
+ t.Fatalf("Expected config filename %s, got %s", expectedConfigFilename, config.Filename)
|
|
| 30 |
+ } |
|
| 31 |
+ |
|
| 32 |
+ // Now save it and make sure it shows up in new form |
|
| 33 |
+ saveConfigAndValidateNewFormat(t, config, tmpHome) |
|
| 34 |
+} |
|
| 35 |
+ |
|
| 36 |
+func TestMissingFile(t *testing.T) {
|
|
| 37 |
+ tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 38 |
+ if err != nil {
|
|
| 39 |
+ t.Fatal(err) |
|
| 40 |
+ } |
|
| 41 |
+ defer os.RemoveAll(tmpHome) |
|
| 42 |
+ |
|
| 43 |
+ config, err := Load(tmpHome) |
|
| 44 |
+ if err != nil {
|
|
| 45 |
+ t.Fatalf("Failed loading on missing file: %q", err)
|
|
| 46 |
+ } |
|
| 47 |
+ |
|
| 48 |
+ // Now save it and make sure it shows up in new form |
|
| 49 |
+ saveConfigAndValidateNewFormat(t, config, tmpHome) |
|
| 50 |
+} |
|
| 51 |
+ |
|
| 52 |
+func TestSaveFileToDirs(t *testing.T) {
|
|
| 53 |
+ tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 54 |
+ if err != nil {
|
|
| 55 |
+ t.Fatal(err) |
|
| 56 |
+ } |
|
| 57 |
+ defer os.RemoveAll(tmpHome) |
|
| 58 |
+ |
|
| 59 |
+ tmpHome += "/.docker" |
|
| 60 |
+ |
|
| 61 |
+ config, err := Load(tmpHome) |
|
| 62 |
+ if err != nil {
|
|
| 63 |
+ t.Fatalf("Failed loading on missing file: %q", err)
|
|
| 64 |
+ } |
|
| 65 |
+ |
|
| 66 |
+ // Now save it and make sure it shows up in new form |
|
| 67 |
+ saveConfigAndValidateNewFormat(t, config, tmpHome) |
|
| 68 |
+} |
|
| 69 |
+ |
|
| 70 |
+func TestEmptyFile(t *testing.T) {
|
|
| 71 |
+ tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 72 |
+ if err != nil {
|
|
| 73 |
+ t.Fatal(err) |
|
| 74 |
+ } |
|
| 75 |
+ defer os.RemoveAll(tmpHome) |
|
| 76 |
+ |
|
| 77 |
+ fn := filepath.Join(tmpHome, ConfigFileName) |
|
| 78 |
+ if err := ioutil.WriteFile(fn, []byte(""), 0600); err != nil {
|
|
| 79 |
+ t.Fatal(err) |
|
| 80 |
+ } |
|
| 81 |
+ |
|
| 82 |
+ _, err = Load(tmpHome) |
|
| 83 |
+ if err == nil {
|
|
| 84 |
+ t.Fatalf("Was supposed to fail")
|
|
| 85 |
+ } |
|
| 86 |
+} |
|
| 87 |
+ |
|
| 88 |
+func TestEmptyJSON(t *testing.T) {
|
|
| 89 |
+ tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 90 |
+ if err != nil {
|
|
| 91 |
+ t.Fatal(err) |
|
| 92 |
+ } |
|
| 93 |
+ defer os.RemoveAll(tmpHome) |
|
| 94 |
+ |
|
| 95 |
+ fn := filepath.Join(tmpHome, ConfigFileName) |
|
| 96 |
+ if err := ioutil.WriteFile(fn, []byte("{}"), 0600); err != nil {
|
|
| 97 |
+ t.Fatal(err) |
|
| 98 |
+ } |
|
| 99 |
+ |
|
| 100 |
+ config, err := Load(tmpHome) |
|
| 101 |
+ if err != nil {
|
|
| 102 |
+ t.Fatalf("Failed loading on empty json file: %q", err)
|
|
| 103 |
+ } |
|
| 104 |
+ |
|
| 105 |
+ // Now save it and make sure it shows up in new form |
|
| 106 |
+ saveConfigAndValidateNewFormat(t, config, tmpHome) |
|
| 107 |
+} |
|
| 108 |
+ |
|
| 109 |
+func TestOldInvalidsAuth(t *testing.T) {
|
|
| 110 |
+ invalids := map[string]string{
|
|
| 111 |
+ `username = test`: "The Auth config file is empty", |
|
| 112 |
+ `username |
|
| 113 |
+password`: "Invalid Auth config file", |
|
| 114 |
+ `username = test |
|
| 115 |
+email`: "Invalid auth configuration file", |
|
| 116 |
+ } |
|
| 117 |
+ |
|
| 118 |
+ tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 119 |
+ if err != nil {
|
|
| 120 |
+ t.Fatal(err) |
|
| 121 |
+ } |
|
| 122 |
+ defer os.RemoveAll(tmpHome) |
|
| 123 |
+ |
|
| 124 |
+ homeKey := homedir.Key() |
|
| 125 |
+ homeVal := homedir.Get() |
|
| 126 |
+ |
|
| 127 |
+ defer func() { os.Setenv(homeKey, homeVal) }()
|
|
| 128 |
+ os.Setenv(homeKey, tmpHome) |
|
| 129 |
+ |
|
| 130 |
+ for content, expectedError := range invalids {
|
|
| 131 |
+ fn := filepath.Join(tmpHome, oldConfigfile) |
|
| 132 |
+ if err := ioutil.WriteFile(fn, []byte(content), 0600); err != nil {
|
|
| 133 |
+ t.Fatal(err) |
|
| 134 |
+ } |
|
| 135 |
+ |
|
| 136 |
+ config, err := Load(tmpHome) |
|
| 137 |
+ // Use Contains instead of == since the file name will change each time |
|
| 138 |
+ if err == nil || !strings.Contains(err.Error(), expectedError) {
|
|
| 139 |
+ t.Fatalf("Should have failed\nConfig: %v\nGot: %v\nExpected: %v", config, err, expectedError)
|
|
| 140 |
+ } |
|
| 141 |
+ |
|
| 142 |
+ } |
|
| 143 |
+} |
|
| 144 |
+ |
|
| 145 |
+func TestOldValidAuth(t *testing.T) {
|
|
| 146 |
+ tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 147 |
+ if err != nil {
|
|
| 148 |
+ t.Fatal(err) |
|
| 149 |
+ } |
|
| 150 |
+ if err != nil {
|
|
| 151 |
+ t.Fatal(err) |
|
| 152 |
+ } |
|
| 153 |
+ defer os.RemoveAll(tmpHome) |
|
| 154 |
+ |
|
| 155 |
+ homeKey := homedir.Key() |
|
| 156 |
+ homeVal := homedir.Get() |
|
| 157 |
+ |
|
| 158 |
+ defer func() { os.Setenv(homeKey, homeVal) }()
|
|
| 159 |
+ os.Setenv(homeKey, tmpHome) |
|
| 160 |
+ |
|
| 161 |
+ fn := filepath.Join(tmpHome, oldConfigfile) |
|
| 162 |
+ js := `username = am9lam9lOmhlbGxv |
|
| 163 |
+ email = user@example.com` |
|
| 164 |
+ if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
|
|
| 165 |
+ t.Fatal(err) |
|
| 166 |
+ } |
|
| 167 |
+ |
|
| 168 |
+ config, err := Load(tmpHome) |
|
| 169 |
+ if err != nil {
|
|
| 170 |
+ t.Fatal(err) |
|
| 171 |
+ } |
|
| 172 |
+ |
|
| 173 |
+ // defaultIndexserver is https://index.docker.io/v1/ |
|
| 174 |
+ ac := config.AuthConfigs["https://index.docker.io/v1/"] |
|
| 175 |
+ if ac.Username != "joejoe" || ac.Password != "hello" {
|
|
| 176 |
+ t.Fatalf("Missing data from parsing:\n%q", config)
|
|
| 177 |
+ } |
|
| 178 |
+ |
|
| 179 |
+ // Now save it and make sure it shows up in new form |
|
| 180 |
+ configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) |
|
| 181 |
+ |
|
| 182 |
+ expConfStr := `{
|
|
| 183 |
+ "auths": {
|
|
| 184 |
+ "https://index.docker.io/v1/": {
|
|
| 185 |
+ "auth": "am9lam9lOmhlbGxv" |
|
| 186 |
+ } |
|
| 187 |
+ } |
|
| 188 |
+}` |
|
| 189 |
+ |
|
| 190 |
+ if configStr != expConfStr {
|
|
| 191 |
+ t.Fatalf("Should have save in new form: \n%s\n not \n%s", configStr, expConfStr)
|
|
| 192 |
+ } |
|
| 193 |
+} |
|
| 194 |
+ |
|
| 195 |
+func TestOldJSONInvalid(t *testing.T) {
|
|
| 196 |
+ tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 197 |
+ if err != nil {
|
|
| 198 |
+ t.Fatal(err) |
|
| 199 |
+ } |
|
| 200 |
+ defer os.RemoveAll(tmpHome) |
|
| 201 |
+ |
|
| 202 |
+ homeKey := homedir.Key() |
|
| 203 |
+ homeVal := homedir.Get() |
|
| 204 |
+ |
|
| 205 |
+ defer func() { os.Setenv(homeKey, homeVal) }()
|
|
| 206 |
+ os.Setenv(homeKey, tmpHome) |
|
| 207 |
+ |
|
| 208 |
+ fn := filepath.Join(tmpHome, oldConfigfile) |
|
| 209 |
+ js := `{"https://index.docker.io/v1/":{"auth":"test","email":"user@example.com"}}`
|
|
| 210 |
+ if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
|
|
| 211 |
+ t.Fatal(err) |
|
| 212 |
+ } |
|
| 213 |
+ |
|
| 214 |
+ config, err := Load(tmpHome) |
|
| 215 |
+ // Use Contains instead of == since the file name will change each time |
|
| 216 |
+ if err == nil || !strings.Contains(err.Error(), "Invalid auth configuration file") {
|
|
| 217 |
+ t.Fatalf("Expected an error got : %v, %v", config, err)
|
|
| 218 |
+ } |
|
| 219 |
+} |
|
| 220 |
+ |
|
| 221 |
+func TestOldJSON(t *testing.T) {
|
|
| 222 |
+ tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 223 |
+ if err != nil {
|
|
| 224 |
+ t.Fatal(err) |
|
| 225 |
+ } |
|
| 226 |
+ defer os.RemoveAll(tmpHome) |
|
| 227 |
+ |
|
| 228 |
+ homeKey := homedir.Key() |
|
| 229 |
+ homeVal := homedir.Get() |
|
| 230 |
+ |
|
| 231 |
+ defer func() { os.Setenv(homeKey, homeVal) }()
|
|
| 232 |
+ os.Setenv(homeKey, tmpHome) |
|
| 233 |
+ |
|
| 234 |
+ fn := filepath.Join(tmpHome, oldConfigfile) |
|
| 235 |
+ js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
|
|
| 236 |
+ if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
|
|
| 237 |
+ t.Fatal(err) |
|
| 238 |
+ } |
|
| 239 |
+ |
|
| 240 |
+ config, err := Load(tmpHome) |
|
| 241 |
+ if err != nil {
|
|
| 242 |
+ t.Fatalf("Failed loading on empty json file: %q", err)
|
|
| 243 |
+ } |
|
| 244 |
+ |
|
| 245 |
+ ac := config.AuthConfigs["https://index.docker.io/v1/"] |
|
| 246 |
+ if ac.Username != "joejoe" || ac.Password != "hello" {
|
|
| 247 |
+ t.Fatalf("Missing data from parsing:\n%q", config)
|
|
| 248 |
+ } |
|
| 249 |
+ |
|
| 250 |
+ // Now save it and make sure it shows up in new form |
|
| 251 |
+ configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) |
|
| 252 |
+ |
|
| 253 |
+ expConfStr := `{
|
|
| 254 |
+ "auths": {
|
|
| 255 |
+ "https://index.docker.io/v1/": {
|
|
| 256 |
+ "auth": "am9lam9lOmhlbGxv", |
|
| 257 |
+ "email": "user@example.com" |
|
| 258 |
+ } |
|
| 259 |
+ } |
|
| 260 |
+}` |
|
| 261 |
+ |
|
| 262 |
+ if configStr != expConfStr {
|
|
| 263 |
+ t.Fatalf("Should have save in new form: \n'%s'\n not \n'%s'\n", configStr, expConfStr)
|
|
| 264 |
+ } |
|
| 265 |
+} |
|
| 266 |
+ |
|
| 267 |
+func TestNewJSON(t *testing.T) {
|
|
| 268 |
+ tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 269 |
+ if err != nil {
|
|
| 270 |
+ t.Fatal(err) |
|
| 271 |
+ } |
|
| 272 |
+ defer os.RemoveAll(tmpHome) |
|
| 273 |
+ |
|
| 274 |
+ fn := filepath.Join(tmpHome, ConfigFileName) |
|
| 275 |
+ js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } } }`
|
|
| 276 |
+ if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
|
|
| 277 |
+ t.Fatal(err) |
|
| 278 |
+ } |
|
| 279 |
+ |
|
| 280 |
+ config, err := Load(tmpHome) |
|
| 281 |
+ if err != nil {
|
|
| 282 |
+ t.Fatalf("Failed loading on empty json file: %q", err)
|
|
| 283 |
+ } |
|
| 284 |
+ |
|
| 285 |
+ ac := config.AuthConfigs["https://index.docker.io/v1/"] |
|
| 286 |
+ if ac.Username != "joejoe" || ac.Password != "hello" {
|
|
| 287 |
+ t.Fatalf("Missing data from parsing:\n%q", config)
|
|
| 288 |
+ } |
|
| 289 |
+ |
|
| 290 |
+ // Now save it and make sure it shows up in new form |
|
| 291 |
+ configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) |
|
| 292 |
+ |
|
| 293 |
+ expConfStr := `{
|
|
| 294 |
+ "auths": {
|
|
| 295 |
+ "https://index.docker.io/v1/": {
|
|
| 296 |
+ "auth": "am9lam9lOmhlbGxv" |
|
| 297 |
+ } |
|
| 298 |
+ } |
|
| 299 |
+}` |
|
| 300 |
+ |
|
| 301 |
+ if configStr != expConfStr {
|
|
| 302 |
+ t.Fatalf("Should have save in new form: \n%s\n not \n%s", configStr, expConfStr)
|
|
| 303 |
+ } |
|
| 304 |
+} |
|
| 305 |
+ |
|
| 306 |
+func TestNewJSONNoEmail(t *testing.T) {
|
|
| 307 |
+ tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 308 |
+ if err != nil {
|
|
| 309 |
+ t.Fatal(err) |
|
| 310 |
+ } |
|
| 311 |
+ defer os.RemoveAll(tmpHome) |
|
| 312 |
+ |
|
| 313 |
+ fn := filepath.Join(tmpHome, ConfigFileName) |
|
| 314 |
+ js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } } }`
|
|
| 315 |
+ if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
|
|
| 316 |
+ t.Fatal(err) |
|
| 317 |
+ } |
|
| 318 |
+ |
|
| 319 |
+ config, err := Load(tmpHome) |
|
| 320 |
+ if err != nil {
|
|
| 321 |
+ t.Fatalf("Failed loading on empty json file: %q", err)
|
|
| 322 |
+ } |
|
| 323 |
+ |
|
| 324 |
+ ac := config.AuthConfigs["https://index.docker.io/v1/"] |
|
| 325 |
+ if ac.Username != "joejoe" || ac.Password != "hello" {
|
|
| 326 |
+ t.Fatalf("Missing data from parsing:\n%q", config)
|
|
| 327 |
+ } |
|
| 328 |
+ |
|
| 329 |
+ // Now save it and make sure it shows up in new form |
|
| 330 |
+ configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) |
|
| 331 |
+ |
|
| 332 |
+ expConfStr := `{
|
|
| 333 |
+ "auths": {
|
|
| 334 |
+ "https://index.docker.io/v1/": {
|
|
| 335 |
+ "auth": "am9lam9lOmhlbGxv" |
|
| 336 |
+ } |
|
| 337 |
+ } |
|
| 338 |
+}` |
|
| 339 |
+ |
|
| 340 |
+ if configStr != expConfStr {
|
|
| 341 |
+ t.Fatalf("Should have save in new form: \n%s\n not \n%s", configStr, expConfStr)
|
|
| 342 |
+ } |
|
| 343 |
+} |
|
| 344 |
+ |
|
| 345 |
+func TestJSONWithPsFormat(t *testing.T) {
|
|
| 346 |
+ tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 347 |
+ if err != nil {
|
|
| 348 |
+ t.Fatal(err) |
|
| 349 |
+ } |
|
| 350 |
+ defer os.RemoveAll(tmpHome) |
|
| 351 |
+ |
|
| 352 |
+ fn := filepath.Join(tmpHome, ConfigFileName) |
|
| 353 |
+ js := `{
|
|
| 354 |
+ "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
|
|
| 355 |
+ "psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
|
|
| 356 |
+}` |
|
| 357 |
+ if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
|
|
| 358 |
+ t.Fatal(err) |
|
| 359 |
+ } |
|
| 360 |
+ |
|
| 361 |
+ config, err := Load(tmpHome) |
|
| 362 |
+ if err != nil {
|
|
| 363 |
+ t.Fatalf("Failed loading on empty json file: %q", err)
|
|
| 364 |
+ } |
|
| 365 |
+ |
|
| 366 |
+ if config.PsFormat != `table {{.ID}}\t{{.Label "com.docker.label.cpu"}}` {
|
|
| 367 |
+ t.Fatalf("Unknown ps format: %s\n", config.PsFormat)
|
|
| 368 |
+ } |
|
| 369 |
+ |
|
| 370 |
+ // Now save it and make sure it shows up in new form |
|
| 371 |
+ configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) |
|
| 372 |
+ if !strings.Contains(configStr, `"psFormat":`) || |
|
| 373 |
+ !strings.Contains(configStr, "{{.ID}}") {
|
|
| 374 |
+ t.Fatalf("Should have save in new form: %s", configStr)
|
|
| 375 |
+ } |
|
| 376 |
+} |
|
| 377 |
+ |
|
| 378 |
+func TestJSONWithCredentialStore(t *testing.T) {
|
|
| 379 |
+ tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 380 |
+ if err != nil {
|
|
| 381 |
+ t.Fatal(err) |
|
| 382 |
+ } |
|
| 383 |
+ defer os.RemoveAll(tmpHome) |
|
| 384 |
+ |
|
| 385 |
+ fn := filepath.Join(tmpHome, ConfigFileName) |
|
| 386 |
+ js := `{
|
|
| 387 |
+ "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
|
|
| 388 |
+ "credsStore": "crazy-secure-storage" |
|
| 389 |
+}` |
|
| 390 |
+ if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
|
|
| 391 |
+ t.Fatal(err) |
|
| 392 |
+ } |
|
| 393 |
+ |
|
| 394 |
+ config, err := Load(tmpHome) |
|
| 395 |
+ if err != nil {
|
|
| 396 |
+ t.Fatalf("Failed loading on empty json file: %q", err)
|
|
| 397 |
+ } |
|
| 398 |
+ |
|
| 399 |
+ if config.CredentialsStore != "crazy-secure-storage" {
|
|
| 400 |
+ t.Fatalf("Unknown credential store: %s\n", config.CredentialsStore)
|
|
| 401 |
+ } |
|
| 402 |
+ |
|
| 403 |
+ // Now save it and make sure it shows up in new form |
|
| 404 |
+ configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) |
|
| 405 |
+ if !strings.Contains(configStr, `"credsStore":`) || |
|
| 406 |
+ !strings.Contains(configStr, "crazy-secure-storage") {
|
|
| 407 |
+ t.Fatalf("Should have save in new form: %s", configStr)
|
|
| 408 |
+ } |
|
| 409 |
+} |
|
| 410 |
+ |
|
| 411 |
+func TestJSONWithCredentialHelpers(t *testing.T) {
|
|
| 412 |
+ tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 413 |
+ if err != nil {
|
|
| 414 |
+ t.Fatal(err) |
|
| 415 |
+ } |
|
| 416 |
+ defer os.RemoveAll(tmpHome) |
|
| 417 |
+ |
|
| 418 |
+ fn := filepath.Join(tmpHome, ConfigFileName) |
|
| 419 |
+ js := `{
|
|
| 420 |
+ "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
|
|
| 421 |
+ "credHelpers": { "images.io": "images-io", "containers.com": "crazy-secure-storage" }
|
|
| 422 |
+}` |
|
| 423 |
+ if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
|
|
| 424 |
+ t.Fatal(err) |
|
| 425 |
+ } |
|
| 426 |
+ |
|
| 427 |
+ config, err := Load(tmpHome) |
|
| 428 |
+ if err != nil {
|
|
| 429 |
+ t.Fatalf("Failed loading on empty json file: %q", err)
|
|
| 430 |
+ } |
|
| 431 |
+ |
|
| 432 |
+ if config.CredentialHelpers == nil {
|
|
| 433 |
+ t.Fatal("config.CredentialHelpers was nil")
|
|
| 434 |
+ } else if config.CredentialHelpers["images.io"] != "images-io" || |
|
| 435 |
+ config.CredentialHelpers["containers.com"] != "crazy-secure-storage" {
|
|
| 436 |
+ t.Fatalf("Credential helpers not deserialized properly: %v\n", config.CredentialHelpers)
|
|
| 437 |
+ } |
|
| 438 |
+ |
|
| 439 |
+ // Now save it and make sure it shows up in new form |
|
| 440 |
+ configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) |
|
| 441 |
+ if !strings.Contains(configStr, `"credHelpers":`) || |
|
| 442 |
+ !strings.Contains(configStr, "images.io") || |
|
| 443 |
+ !strings.Contains(configStr, "images-io") || |
|
| 444 |
+ !strings.Contains(configStr, "containers.com") || |
|
| 445 |
+ !strings.Contains(configStr, "crazy-secure-storage") {
|
|
| 446 |
+ t.Fatalf("Should have save in new form: %s", configStr)
|
|
| 447 |
+ } |
|
| 448 |
+} |
|
| 449 |
+ |
|
| 450 |
+// Save it and make sure it shows up in new form |
|
| 451 |
+func saveConfigAndValidateNewFormat(t *testing.T, config *configfile.ConfigFile, homeFolder string) string {
|
|
| 452 |
+ if err := config.Save(); err != nil {
|
|
| 453 |
+ t.Fatalf("Failed to save: %q", err)
|
|
| 454 |
+ } |
|
| 455 |
+ |
|
| 456 |
+ buf, err := ioutil.ReadFile(filepath.Join(homeFolder, ConfigFileName)) |
|
| 457 |
+ if err != nil {
|
|
| 458 |
+ t.Fatal(err) |
|
| 459 |
+ } |
|
| 460 |
+ if !strings.Contains(string(buf), `"auths":`) {
|
|
| 461 |
+ t.Fatalf("Should have save in new form: %s", string(buf))
|
|
| 462 |
+ } |
|
| 463 |
+ return string(buf) |
|
| 464 |
+} |
|
| 465 |
+ |
|
| 466 |
+func TestConfigDir(t *testing.T) {
|
|
| 467 |
+ tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 468 |
+ if err != nil {
|
|
| 469 |
+ t.Fatal(err) |
|
| 470 |
+ } |
|
| 471 |
+ defer os.RemoveAll(tmpHome) |
|
| 472 |
+ |
|
| 473 |
+ if Dir() == tmpHome {
|
|
| 474 |
+ t.Fatalf("Expected ConfigDir to be different than %s by default, but was the same", tmpHome)
|
|
| 475 |
+ } |
|
| 476 |
+ |
|
| 477 |
+ // Update configDir |
|
| 478 |
+ SetDir(tmpHome) |
|
| 479 |
+ |
|
| 480 |
+ if Dir() != tmpHome {
|
|
| 481 |
+ t.Fatalf("Expected ConfigDir to %s, but was %s", tmpHome, Dir())
|
|
| 482 |
+ } |
|
| 483 |
+} |
|
| 484 |
+ |
|
| 485 |
+func TestConfigFile(t *testing.T) {
|
|
| 486 |
+ configFilename := "configFilename" |
|
| 487 |
+ configFile := NewConfigFile(configFilename) |
|
| 488 |
+ |
|
| 489 |
+ if configFile.Filename != configFilename {
|
|
| 490 |
+ t.Fatalf("Expected %s, got %s", configFilename, configFile.Filename)
|
|
| 491 |
+ } |
|
| 492 |
+} |
|
| 493 |
+ |
|
| 494 |
+func TestJSONReaderNoFile(t *testing.T) {
|
|
| 495 |
+ js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } } }`
|
|
| 496 |
+ |
|
| 497 |
+ config, err := LoadFromReader(strings.NewReader(js)) |
|
| 498 |
+ if err != nil {
|
|
| 499 |
+ t.Fatalf("Failed loading on empty json file: %q", err)
|
|
| 500 |
+ } |
|
| 501 |
+ |
|
| 502 |
+ ac := config.AuthConfigs["https://index.docker.io/v1/"] |
|
| 503 |
+ if ac.Username != "joejoe" || ac.Password != "hello" {
|
|
| 504 |
+ t.Fatalf("Missing data from parsing:\n%q", config)
|
|
| 505 |
+ } |
|
| 506 |
+ |
|
| 507 |
+} |
|
| 508 |
+ |
|
| 509 |
+func TestOldJSONReaderNoFile(t *testing.T) {
|
|
| 510 |
+ js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
|
|
| 511 |
+ |
|
| 512 |
+ config, err := LegacyLoadFromReader(strings.NewReader(js)) |
|
| 513 |
+ if err != nil {
|
|
| 514 |
+ t.Fatalf("Failed loading on empty json file: %q", err)
|
|
| 515 |
+ } |
|
| 516 |
+ |
|
| 517 |
+ ac := config.AuthConfigs["https://index.docker.io/v1/"] |
|
| 518 |
+ if ac.Username != "joejoe" || ac.Password != "hello" {
|
|
| 519 |
+ t.Fatalf("Missing data from parsing:\n%q", config)
|
|
| 520 |
+ } |
|
| 521 |
+} |
|
| 522 |
+ |
|
| 523 |
+func TestJSONWithPsFormatNoFile(t *testing.T) {
|
|
| 524 |
+ js := `{
|
|
| 525 |
+ "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
|
|
| 526 |
+ "psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
|
|
| 527 |
+}` |
|
| 528 |
+ config, err := LoadFromReader(strings.NewReader(js)) |
|
| 529 |
+ if err != nil {
|
|
| 530 |
+ t.Fatalf("Failed loading on empty json file: %q", err)
|
|
| 531 |
+ } |
|
| 532 |
+ |
|
| 533 |
+ if config.PsFormat != `table {{.ID}}\t{{.Label "com.docker.label.cpu"}}` {
|
|
| 534 |
+ t.Fatalf("Unknown ps format: %s\n", config.PsFormat)
|
|
| 535 |
+ } |
|
| 536 |
+ |
|
| 537 |
+} |
|
| 538 |
+ |
|
| 539 |
+func TestJSONSaveWithNoFile(t *testing.T) {
|
|
| 540 |
+ js := `{
|
|
| 541 |
+ "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } },
|
|
| 542 |
+ "psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
|
|
| 543 |
+}` |
|
| 544 |
+ config, err := LoadFromReader(strings.NewReader(js)) |
|
| 545 |
+ err = config.Save() |
|
| 546 |
+ if err == nil {
|
|
| 547 |
+ t.Fatalf("Expected error. File should not have been able to save with no file name.")
|
|
| 548 |
+ } |
|
| 549 |
+ |
|
| 550 |
+ tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 551 |
+ if err != nil {
|
|
| 552 |
+ t.Fatalf("Failed to create a temp dir: %q", err)
|
|
| 553 |
+ } |
|
| 554 |
+ defer os.RemoveAll(tmpHome) |
|
| 555 |
+ |
|
| 556 |
+ fn := filepath.Join(tmpHome, ConfigFileName) |
|
| 557 |
+ f, _ := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) |
|
| 558 |
+ defer f.Close() |
|
| 559 |
+ |
|
| 560 |
+ err = config.SaveToWriter(f) |
|
| 561 |
+ if err != nil {
|
|
| 562 |
+ t.Fatalf("Failed saving to file: %q", err)
|
|
| 563 |
+ } |
|
| 564 |
+ buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName)) |
|
| 565 |
+ if err != nil {
|
|
| 566 |
+ t.Fatal(err) |
|
| 567 |
+ } |
|
| 568 |
+ expConfStr := `{
|
|
| 569 |
+ "auths": {
|
|
| 570 |
+ "https://index.docker.io/v1/": {
|
|
| 571 |
+ "auth": "am9lam9lOmhlbGxv" |
|
| 572 |
+ } |
|
| 573 |
+ }, |
|
| 574 |
+ "psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
|
|
| 575 |
+}` |
|
| 576 |
+ if string(buf) != expConfStr {
|
|
| 577 |
+ t.Fatalf("Should have save in new form: \n%s\nnot \n%s", string(buf), expConfStr)
|
|
| 578 |
+ } |
|
| 579 |
+} |
|
| 580 |
+ |
|
| 581 |
+func TestLegacyJSONSaveWithNoFile(t *testing.T) {
|
|
| 582 |
+ |
|
| 583 |
+ js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
|
|
| 584 |
+ config, err := LegacyLoadFromReader(strings.NewReader(js)) |
|
| 585 |
+ err = config.Save() |
|
| 586 |
+ if err == nil {
|
|
| 587 |
+ t.Fatalf("Expected error. File should not have been able to save with no file name.")
|
|
| 588 |
+ } |
|
| 589 |
+ |
|
| 590 |
+ tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 591 |
+ if err != nil {
|
|
| 592 |
+ t.Fatalf("Failed to create a temp dir: %q", err)
|
|
| 593 |
+ } |
|
| 594 |
+ defer os.RemoveAll(tmpHome) |
|
| 595 |
+ |
|
| 596 |
+ fn := filepath.Join(tmpHome, ConfigFileName) |
|
| 597 |
+ f, _ := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) |
|
| 598 |
+ defer f.Close() |
|
| 599 |
+ |
|
| 600 |
+ if err = config.SaveToWriter(f); err != nil {
|
|
| 601 |
+ t.Fatalf("Failed saving to file: %q", err)
|
|
| 602 |
+ } |
|
| 603 |
+ buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName)) |
|
| 604 |
+ if err != nil {
|
|
| 605 |
+ t.Fatal(err) |
|
| 606 |
+ } |
|
| 607 |
+ |
|
| 608 |
+ expConfStr := `{
|
|
| 609 |
+ "auths": {
|
|
| 610 |
+ "https://index.docker.io/v1/": {
|
|
| 611 |
+ "auth": "am9lam9lOmhlbGxv", |
|
| 612 |
+ "email": "user@example.com" |
|
| 613 |
+ } |
|
| 614 |
+ } |
|
| 615 |
+}` |
|
| 616 |
+ |
|
| 617 |
+ if string(buf) != expConfStr {
|
|
| 618 |
+ t.Fatalf("Should have save in new form: \n%s\n not \n%s", string(buf), expConfStr)
|
|
| 619 |
+ } |
|
| 620 |
+} |
| 0 | 621 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,183 @@ |
| 0 |
+package configfile |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "encoding/base64" |
|
| 4 |
+ "encoding/json" |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "io" |
|
| 7 |
+ "io/ioutil" |
|
| 8 |
+ "os" |
|
| 9 |
+ "path/filepath" |
|
| 10 |
+ "strings" |
|
| 11 |
+ |
|
| 12 |
+ "github.com/docker/docker/api/types" |
|
| 13 |
+) |
|
| 14 |
+ |
|
| 15 |
+const ( |
|
| 16 |
+ // This constant is only used for really old config files when the |
|
| 17 |
+ // URL wasn't saved as part of the config file and it was just |
|
| 18 |
+ // assumed to be this value. |
|
| 19 |
+ defaultIndexserver = "https://index.docker.io/v1/" |
|
| 20 |
+) |
|
| 21 |
+ |
|
| 22 |
+// ConfigFile ~/.docker/config.json file info |
|
| 23 |
+type ConfigFile struct {
|
|
| 24 |
+ AuthConfigs map[string]types.AuthConfig `json:"auths"` |
|
| 25 |
+ HTTPHeaders map[string]string `json:"HttpHeaders,omitempty"` |
|
| 26 |
+ PsFormat string `json:"psFormat,omitempty"` |
|
| 27 |
+ ImagesFormat string `json:"imagesFormat,omitempty"` |
|
| 28 |
+ NetworksFormat string `json:"networksFormat,omitempty"` |
|
| 29 |
+ VolumesFormat string `json:"volumesFormat,omitempty"` |
|
| 30 |
+ StatsFormat string `json:"statsFormat,omitempty"` |
|
| 31 |
+ DetachKeys string `json:"detachKeys,omitempty"` |
|
| 32 |
+ CredentialsStore string `json:"credsStore,omitempty"` |
|
| 33 |
+ CredentialHelpers map[string]string `json:"credHelpers,omitempty"` |
|
| 34 |
+ Filename string `json:"-"` // Note: for internal use only |
|
| 35 |
+ ServiceInspectFormat string `json:"serviceInspectFormat,omitempty"` |
|
| 36 |
+} |
|
| 37 |
+ |
|
| 38 |
+// LegacyLoadFromReader reads the non-nested configuration data given and sets up the |
|
| 39 |
+// auth config information with given directory and populates the receiver object |
|
| 40 |
+func (configFile *ConfigFile) LegacyLoadFromReader(configData io.Reader) error {
|
|
| 41 |
+ b, err := ioutil.ReadAll(configData) |
|
| 42 |
+ if err != nil {
|
|
| 43 |
+ return err |
|
| 44 |
+ } |
|
| 45 |
+ |
|
| 46 |
+ if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil {
|
|
| 47 |
+ arr := strings.Split(string(b), "\n") |
|
| 48 |
+ if len(arr) < 2 {
|
|
| 49 |
+ return fmt.Errorf("The Auth config file is empty")
|
|
| 50 |
+ } |
|
| 51 |
+ authConfig := types.AuthConfig{}
|
|
| 52 |
+ origAuth := strings.Split(arr[0], " = ") |
|
| 53 |
+ if len(origAuth) != 2 {
|
|
| 54 |
+ return fmt.Errorf("Invalid Auth config file")
|
|
| 55 |
+ } |
|
| 56 |
+ authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1]) |
|
| 57 |
+ if err != nil {
|
|
| 58 |
+ return err |
|
| 59 |
+ } |
|
| 60 |
+ authConfig.ServerAddress = defaultIndexserver |
|
| 61 |
+ configFile.AuthConfigs[defaultIndexserver] = authConfig |
|
| 62 |
+ } else {
|
|
| 63 |
+ for k, authConfig := range configFile.AuthConfigs {
|
|
| 64 |
+ authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth) |
|
| 65 |
+ if err != nil {
|
|
| 66 |
+ return err |
|
| 67 |
+ } |
|
| 68 |
+ authConfig.Auth = "" |
|
| 69 |
+ authConfig.ServerAddress = k |
|
| 70 |
+ configFile.AuthConfigs[k] = authConfig |
|
| 71 |
+ } |
|
| 72 |
+ } |
|
| 73 |
+ return nil |
|
| 74 |
+} |
|
| 75 |
+ |
|
| 76 |
+// LoadFromReader reads the configuration data given and sets up the auth config |
|
| 77 |
+// information with given directory and populates the receiver object |
|
| 78 |
+func (configFile *ConfigFile) LoadFromReader(configData io.Reader) error {
|
|
| 79 |
+ if err := json.NewDecoder(configData).Decode(&configFile); err != nil {
|
|
| 80 |
+ return err |
|
| 81 |
+ } |
|
| 82 |
+ var err error |
|
| 83 |
+ for addr, ac := range configFile.AuthConfigs {
|
|
| 84 |
+ ac.Username, ac.Password, err = decodeAuth(ac.Auth) |
|
| 85 |
+ if err != nil {
|
|
| 86 |
+ return err |
|
| 87 |
+ } |
|
| 88 |
+ ac.Auth = "" |
|
| 89 |
+ ac.ServerAddress = addr |
|
| 90 |
+ configFile.AuthConfigs[addr] = ac |
|
| 91 |
+ } |
|
| 92 |
+ return nil |
|
| 93 |
+} |
|
| 94 |
+ |
|
| 95 |
+// ContainsAuth returns whether there is authentication configured |
|
| 96 |
+// in this file or not. |
|
| 97 |
+func (configFile *ConfigFile) ContainsAuth() bool {
|
|
| 98 |
+ return configFile.CredentialsStore != "" || |
|
| 99 |
+ len(configFile.CredentialHelpers) > 0 || |
|
| 100 |
+ len(configFile.AuthConfigs) > 0 |
|
| 101 |
+} |
|
| 102 |
+ |
|
| 103 |
+// SaveToWriter encodes and writes out all the authorization information to |
|
| 104 |
+// the given writer |
|
| 105 |
+func (configFile *ConfigFile) SaveToWriter(writer io.Writer) error {
|
|
| 106 |
+ // Encode sensitive data into a new/temp struct |
|
| 107 |
+ tmpAuthConfigs := make(map[string]types.AuthConfig, len(configFile.AuthConfigs)) |
|
| 108 |
+ for k, authConfig := range configFile.AuthConfigs {
|
|
| 109 |
+ authCopy := authConfig |
|
| 110 |
+ // encode and save the authstring, while blanking out the original fields |
|
| 111 |
+ authCopy.Auth = encodeAuth(&authCopy) |
|
| 112 |
+ authCopy.Username = "" |
|
| 113 |
+ authCopy.Password = "" |
|
| 114 |
+ authCopy.ServerAddress = "" |
|
| 115 |
+ tmpAuthConfigs[k] = authCopy |
|
| 116 |
+ } |
|
| 117 |
+ |
|
| 118 |
+ saveAuthConfigs := configFile.AuthConfigs |
|
| 119 |
+ configFile.AuthConfigs = tmpAuthConfigs |
|
| 120 |
+ defer func() { configFile.AuthConfigs = saveAuthConfigs }()
|
|
| 121 |
+ |
|
| 122 |
+ data, err := json.MarshalIndent(configFile, "", "\t") |
|
| 123 |
+ if err != nil {
|
|
| 124 |
+ return err |
|
| 125 |
+ } |
|
| 126 |
+ _, err = writer.Write(data) |
|
| 127 |
+ return err |
|
| 128 |
+} |
|
| 129 |
+ |
|
| 130 |
+// Save encodes and writes out all the authorization information |
|
| 131 |
+func (configFile *ConfigFile) Save() error {
|
|
| 132 |
+ if configFile.Filename == "" {
|
|
| 133 |
+ return fmt.Errorf("Can't save config with empty filename")
|
|
| 134 |
+ } |
|
| 135 |
+ |
|
| 136 |
+ if err := os.MkdirAll(filepath.Dir(configFile.Filename), 0700); err != nil {
|
|
| 137 |
+ return err |
|
| 138 |
+ } |
|
| 139 |
+ f, err := os.OpenFile(configFile.Filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) |
|
| 140 |
+ if err != nil {
|
|
| 141 |
+ return err |
|
| 142 |
+ } |
|
| 143 |
+ defer f.Close() |
|
| 144 |
+ return configFile.SaveToWriter(f) |
|
| 145 |
+} |
|
| 146 |
+ |
|
| 147 |
+// encodeAuth creates a base64 encoded string to containing authorization information |
|
| 148 |
+func encodeAuth(authConfig *types.AuthConfig) string {
|
|
| 149 |
+ if authConfig.Username == "" && authConfig.Password == "" {
|
|
| 150 |
+ return "" |
|
| 151 |
+ } |
|
| 152 |
+ |
|
| 153 |
+ authStr := authConfig.Username + ":" + authConfig.Password |
|
| 154 |
+ msg := []byte(authStr) |
|
| 155 |
+ encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg))) |
|
| 156 |
+ base64.StdEncoding.Encode(encoded, msg) |
|
| 157 |
+ return string(encoded) |
|
| 158 |
+} |
|
| 159 |
+ |
|
| 160 |
+// decodeAuth decodes a base64 encoded string and returns username and password |
|
| 161 |
+func decodeAuth(authStr string) (string, string, error) {
|
|
| 162 |
+ if authStr == "" {
|
|
| 163 |
+ return "", "", nil |
|
| 164 |
+ } |
|
| 165 |
+ |
|
| 166 |
+ decLen := base64.StdEncoding.DecodedLen(len(authStr)) |
|
| 167 |
+ decoded := make([]byte, decLen) |
|
| 168 |
+ authByte := []byte(authStr) |
|
| 169 |
+ n, err := base64.StdEncoding.Decode(decoded, authByte) |
|
| 170 |
+ if err != nil {
|
|
| 171 |
+ return "", "", err |
|
| 172 |
+ } |
|
| 173 |
+ if n > decLen {
|
|
| 174 |
+ return "", "", fmt.Errorf("Something went wrong decoding auth config")
|
|
| 175 |
+ } |
|
| 176 |
+ arr := strings.SplitN(string(decoded), ":", 2) |
|
| 177 |
+ if len(arr) != 2 {
|
|
| 178 |
+ return "", "", fmt.Errorf("Invalid auth configuration file")
|
|
| 179 |
+ } |
|
| 180 |
+ password := strings.Trim(arr[1], "\x00") |
|
| 181 |
+ return arr[0], password, nil |
|
| 182 |
+} |
| 0 | 183 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,27 @@ |
| 0 |
+package configfile |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "testing" |
|
| 4 |
+ |
|
| 5 |
+ "github.com/docker/docker/api/types" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+func TestEncodeAuth(t *testing.T) {
|
|
| 9 |
+ newAuthConfig := &types.AuthConfig{Username: "ken", Password: "test"}
|
|
| 10 |
+ authStr := encodeAuth(newAuthConfig) |
|
| 11 |
+ decAuthConfig := &types.AuthConfig{}
|
|
| 12 |
+ var err error |
|
| 13 |
+ decAuthConfig.Username, decAuthConfig.Password, err = decodeAuth(authStr) |
|
| 14 |
+ if err != nil {
|
|
| 15 |
+ t.Fatal(err) |
|
| 16 |
+ } |
|
| 17 |
+ if newAuthConfig.Username != decAuthConfig.Username {
|
|
| 18 |
+ t.Fatal("Encode Username doesn't match decoded Username")
|
|
| 19 |
+ } |
|
| 20 |
+ if newAuthConfig.Password != decAuthConfig.Password {
|
|
| 21 |
+ t.Fatal("Encode Password doesn't match decoded Password")
|
|
| 22 |
+ } |
|
| 23 |
+ if authStr != "a2VuOnRlc3Q=" {
|
|
| 24 |
+ t.Fatal("AuthString encoding isn't correct.")
|
|
| 25 |
+ } |
|
| 26 |
+} |
| 0 | 27 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,17 @@ |
| 0 |
+package credentials |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "github.com/docker/docker/api/types" |
|
| 4 |
+) |
|
| 5 |
+ |
|
| 6 |
+// Store is the interface that any credentials store must implement. |
|
| 7 |
+type Store interface {
|
|
| 8 |
+ // Erase removes credentials from the store for a given server. |
|
| 9 |
+ Erase(serverAddress string) error |
|
| 10 |
+ // Get retrieves credentials from the store for a given server. |
|
| 11 |
+ Get(serverAddress string) (types.AuthConfig, error) |
|
| 12 |
+ // GetAll retrieves all the credentials from the store. |
|
| 13 |
+ GetAll() (map[string]types.AuthConfig, error) |
|
| 14 |
+ // Store saves credentials in the store. |
|
| 15 |
+ Store(authConfig types.AuthConfig) error |
|
| 16 |
+} |
| 0 | 17 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,22 @@ |
| 0 |
+package credentials |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "os/exec" |
|
| 4 |
+ |
|
| 5 |
+ "github.com/docker/docker/cli/config/configfile" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+// DetectDefaultStore sets the default credentials store |
|
| 9 |
+// if the host includes the default store helper program. |
|
| 10 |
+func DetectDefaultStore(c *configfile.ConfigFile) {
|
|
| 11 |
+ if c.CredentialsStore != "" {
|
|
| 12 |
+ // user defined |
|
| 13 |
+ return |
|
| 14 |
+ } |
|
| 15 |
+ |
|
| 16 |
+ if defaultCredentialsStore != "" {
|
|
| 17 |
+ if _, err := exec.LookPath(remoteCredentialsPrefix + defaultCredentialsStore); err == nil {
|
|
| 18 |
+ c.CredentialsStore = defaultCredentialsStore |
|
| 19 |
+ } |
|
| 20 |
+ } |
|
| 21 |
+} |
| 0 | 3 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,53 @@ |
| 0 |
+package credentials |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "github.com/docker/docker/api/types" |
|
| 4 |
+ "github.com/docker/docker/cli/config/configfile" |
|
| 5 |
+ "github.com/docker/docker/registry" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+// fileStore implements a credentials store using |
|
| 9 |
+// the docker configuration file to keep the credentials in plain text. |
|
| 10 |
+type fileStore struct {
|
|
| 11 |
+ file *configfile.ConfigFile |
|
| 12 |
+} |
|
| 13 |
+ |
|
| 14 |
+// NewFileStore creates a new file credentials store. |
|
| 15 |
+func NewFileStore(file *configfile.ConfigFile) Store {
|
|
| 16 |
+ return &fileStore{
|
|
| 17 |
+ file: file, |
|
| 18 |
+ } |
|
| 19 |
+} |
|
| 20 |
+ |
|
| 21 |
+// Erase removes the given credentials from the file store. |
|
| 22 |
+func (c *fileStore) Erase(serverAddress string) error {
|
|
| 23 |
+ delete(c.file.AuthConfigs, serverAddress) |
|
| 24 |
+ return c.file.Save() |
|
| 25 |
+} |
|
| 26 |
+ |
|
| 27 |
+// Get retrieves credentials for a specific server from the file store. |
|
| 28 |
+func (c *fileStore) Get(serverAddress string) (types.AuthConfig, error) {
|
|
| 29 |
+ authConfig, ok := c.file.AuthConfigs[serverAddress] |
|
| 30 |
+ if !ok {
|
|
| 31 |
+ // Maybe they have a legacy config file, we will iterate the keys converting |
|
| 32 |
+ // them to the new format and testing |
|
| 33 |
+ for r, ac := range c.file.AuthConfigs {
|
|
| 34 |
+ if serverAddress == registry.ConvertToHostname(r) {
|
|
| 35 |
+ return ac, nil |
|
| 36 |
+ } |
|
| 37 |
+ } |
|
| 38 |
+ |
|
| 39 |
+ authConfig = types.AuthConfig{}
|
|
| 40 |
+ } |
|
| 41 |
+ return authConfig, nil |
|
| 42 |
+} |
|
| 43 |
+ |
|
| 44 |
+func (c *fileStore) GetAll() (map[string]types.AuthConfig, error) {
|
|
| 45 |
+ return c.file.AuthConfigs, nil |
|
| 46 |
+} |
|
| 47 |
+ |
|
| 48 |
+// Store saves the given credentials in the file store. |
|
| 49 |
+func (c *fileStore) Store(authConfig types.AuthConfig) error {
|
|
| 50 |
+ c.file.AuthConfigs[authConfig.ServerAddress] = authConfig |
|
| 51 |
+ return c.file.Save() |
|
| 52 |
+} |
| 0 | 53 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,139 @@ |
| 0 |
+package credentials |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "io/ioutil" |
|
| 4 |
+ "testing" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/docker/docker/api/types" |
|
| 7 |
+ cliconfig "github.com/docker/docker/cli/config" |
|
| 8 |
+ "github.com/docker/docker/cli/config/configfile" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+func newConfigFile(auths map[string]types.AuthConfig) *configfile.ConfigFile {
|
|
| 12 |
+ tmp, _ := ioutil.TempFile("", "docker-test")
|
|
| 13 |
+ name := tmp.Name() |
|
| 14 |
+ tmp.Close() |
|
| 15 |
+ |
|
| 16 |
+ c := cliconfig.NewConfigFile(name) |
|
| 17 |
+ c.AuthConfigs = auths |
|
| 18 |
+ return c |
|
| 19 |
+} |
|
| 20 |
+ |
|
| 21 |
+func TestFileStoreAddCredentials(t *testing.T) {
|
|
| 22 |
+ f := newConfigFile(make(map[string]types.AuthConfig)) |
|
| 23 |
+ |
|
| 24 |
+ s := NewFileStore(f) |
|
| 25 |
+ err := s.Store(types.AuthConfig{
|
|
| 26 |
+ Auth: "super_secret_token", |
|
| 27 |
+ Email: "foo@example.com", |
|
| 28 |
+ ServerAddress: "https://example.com", |
|
| 29 |
+ }) |
|
| 30 |
+ |
|
| 31 |
+ if err != nil {
|
|
| 32 |
+ t.Fatal(err) |
|
| 33 |
+ } |
|
| 34 |
+ |
|
| 35 |
+ if len(f.AuthConfigs) != 1 {
|
|
| 36 |
+ t.Fatalf("expected 1 auth config, got %d", len(f.AuthConfigs))
|
|
| 37 |
+ } |
|
| 38 |
+ |
|
| 39 |
+ a, ok := f.AuthConfigs["https://example.com"] |
|
| 40 |
+ if !ok {
|
|
| 41 |
+ t.Fatalf("expected auth for https://example.com, got %v", f.AuthConfigs)
|
|
| 42 |
+ } |
|
| 43 |
+ if a.Auth != "super_secret_token" {
|
|
| 44 |
+ t.Fatalf("expected auth `super_secret_token`, got %s", a.Auth)
|
|
| 45 |
+ } |
|
| 46 |
+ if a.Email != "foo@example.com" {
|
|
| 47 |
+ t.Fatalf("expected email `foo@example.com`, got %s", a.Email)
|
|
| 48 |
+ } |
|
| 49 |
+} |
|
| 50 |
+ |
|
| 51 |
+func TestFileStoreGet(t *testing.T) {
|
|
| 52 |
+ f := newConfigFile(map[string]types.AuthConfig{
|
|
| 53 |
+ "https://example.com": {
|
|
| 54 |
+ Auth: "super_secret_token", |
|
| 55 |
+ Email: "foo@example.com", |
|
| 56 |
+ ServerAddress: "https://example.com", |
|
| 57 |
+ }, |
|
| 58 |
+ }) |
|
| 59 |
+ |
|
| 60 |
+ s := NewFileStore(f) |
|
| 61 |
+ a, err := s.Get("https://example.com")
|
|
| 62 |
+ if err != nil {
|
|
| 63 |
+ t.Fatal(err) |
|
| 64 |
+ } |
|
| 65 |
+ if a.Auth != "super_secret_token" {
|
|
| 66 |
+ t.Fatalf("expected auth `super_secret_token`, got %s", a.Auth)
|
|
| 67 |
+ } |
|
| 68 |
+ if a.Email != "foo@example.com" {
|
|
| 69 |
+ t.Fatalf("expected email `foo@example.com`, got %s", a.Email)
|
|
| 70 |
+ } |
|
| 71 |
+} |
|
| 72 |
+ |
|
| 73 |
+func TestFileStoreGetAll(t *testing.T) {
|
|
| 74 |
+ s1 := "https://example.com" |
|
| 75 |
+ s2 := "https://example2.com" |
|
| 76 |
+ f := newConfigFile(map[string]types.AuthConfig{
|
|
| 77 |
+ s1: {
|
|
| 78 |
+ Auth: "super_secret_token", |
|
| 79 |
+ Email: "foo@example.com", |
|
| 80 |
+ ServerAddress: "https://example.com", |
|
| 81 |
+ }, |
|
| 82 |
+ s2: {
|
|
| 83 |
+ Auth: "super_secret_token2", |
|
| 84 |
+ Email: "foo@example2.com", |
|
| 85 |
+ ServerAddress: "https://example2.com", |
|
| 86 |
+ }, |
|
| 87 |
+ }) |
|
| 88 |
+ |
|
| 89 |
+ s := NewFileStore(f) |
|
| 90 |
+ as, err := s.GetAll() |
|
| 91 |
+ if err != nil {
|
|
| 92 |
+ t.Fatal(err) |
|
| 93 |
+ } |
|
| 94 |
+ if len(as) != 2 {
|
|
| 95 |
+ t.Fatalf("wanted 2, got %d", len(as))
|
|
| 96 |
+ } |
|
| 97 |
+ if as[s1].Auth != "super_secret_token" {
|
|
| 98 |
+ t.Fatalf("expected auth `super_secret_token`, got %s", as[s1].Auth)
|
|
| 99 |
+ } |
|
| 100 |
+ if as[s1].Email != "foo@example.com" {
|
|
| 101 |
+ t.Fatalf("expected email `foo@example.com`, got %s", as[s1].Email)
|
|
| 102 |
+ } |
|
| 103 |
+ if as[s2].Auth != "super_secret_token2" {
|
|
| 104 |
+ t.Fatalf("expected auth `super_secret_token2`, got %s", as[s2].Auth)
|
|
| 105 |
+ } |
|
| 106 |
+ if as[s2].Email != "foo@example2.com" {
|
|
| 107 |
+ t.Fatalf("expected email `foo@example2.com`, got %s", as[s2].Email)
|
|
| 108 |
+ } |
|
| 109 |
+} |
|
| 110 |
+ |
|
| 111 |
+func TestFileStoreErase(t *testing.T) {
|
|
| 112 |
+ f := newConfigFile(map[string]types.AuthConfig{
|
|
| 113 |
+ "https://example.com": {
|
|
| 114 |
+ Auth: "super_secret_token", |
|
| 115 |
+ Email: "foo@example.com", |
|
| 116 |
+ ServerAddress: "https://example.com", |
|
| 117 |
+ }, |
|
| 118 |
+ }) |
|
| 119 |
+ |
|
| 120 |
+ s := NewFileStore(f) |
|
| 121 |
+ err := s.Erase("https://example.com")
|
|
| 122 |
+ if err != nil {
|
|
| 123 |
+ t.Fatal(err) |
|
| 124 |
+ } |
|
| 125 |
+ |
|
| 126 |
+ // file store never returns errors, check that the auth config is empty |
|
| 127 |
+ a, err := s.Get("https://example.com")
|
|
| 128 |
+ if err != nil {
|
|
| 129 |
+ t.Fatal(err) |
|
| 130 |
+ } |
|
| 131 |
+ |
|
| 132 |
+ if a.Auth != "" {
|
|
| 133 |
+ t.Fatalf("expected empty auth token, got %s", a.Auth)
|
|
| 134 |
+ } |
|
| 135 |
+ if a.Email != "" {
|
|
| 136 |
+ t.Fatalf("expected empty email, got %s", a.Email)
|
|
| 137 |
+ } |
|
| 138 |
+} |
| 0 | 139 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,144 @@ |
| 0 |
+package credentials |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "github.com/docker/docker-credential-helpers/client" |
|
| 4 |
+ "github.com/docker/docker-credential-helpers/credentials" |
|
| 5 |
+ "github.com/docker/docker/api/types" |
|
| 6 |
+ "github.com/docker/docker/cli/config/configfile" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+const ( |
|
| 10 |
+ remoteCredentialsPrefix = "docker-credential-" |
|
| 11 |
+ tokenUsername = "<token>" |
|
| 12 |
+) |
|
| 13 |
+ |
|
| 14 |
+// nativeStore implements a credentials store |
|
| 15 |
+// using native keychain to keep credentials secure. |
|
| 16 |
+// It piggybacks into a file store to keep users' emails. |
|
| 17 |
+type nativeStore struct {
|
|
| 18 |
+ programFunc client.ProgramFunc |
|
| 19 |
+ fileStore Store |
|
| 20 |
+} |
|
| 21 |
+ |
|
| 22 |
+// NewNativeStore creates a new native store that |
|
| 23 |
+// uses a remote helper program to manage credentials. |
|
| 24 |
+func NewNativeStore(file *configfile.ConfigFile, helperSuffix string) Store {
|
|
| 25 |
+ name := remoteCredentialsPrefix + helperSuffix |
|
| 26 |
+ return &nativeStore{
|
|
| 27 |
+ programFunc: client.NewShellProgramFunc(name), |
|
| 28 |
+ fileStore: NewFileStore(file), |
|
| 29 |
+ } |
|
| 30 |
+} |
|
| 31 |
+ |
|
| 32 |
+// Erase removes the given credentials from the native store. |
|
| 33 |
+func (c *nativeStore) Erase(serverAddress string) error {
|
|
| 34 |
+ if err := client.Erase(c.programFunc, serverAddress); err != nil {
|
|
| 35 |
+ return err |
|
| 36 |
+ } |
|
| 37 |
+ |
|
| 38 |
+ // Fallback to plain text store to remove email |
|
| 39 |
+ return c.fileStore.Erase(serverAddress) |
|
| 40 |
+} |
|
| 41 |
+ |
|
| 42 |
+// Get retrieves credentials for a specific server from the native store. |
|
| 43 |
+func (c *nativeStore) Get(serverAddress string) (types.AuthConfig, error) {
|
|
| 44 |
+ // load user email if it exist or an empty auth config. |
|
| 45 |
+ auth, _ := c.fileStore.Get(serverAddress) |
|
| 46 |
+ |
|
| 47 |
+ creds, err := c.getCredentialsFromStore(serverAddress) |
|
| 48 |
+ if err != nil {
|
|
| 49 |
+ return auth, err |
|
| 50 |
+ } |
|
| 51 |
+ auth.Username = creds.Username |
|
| 52 |
+ auth.IdentityToken = creds.IdentityToken |
|
| 53 |
+ auth.Password = creds.Password |
|
| 54 |
+ |
|
| 55 |
+ return auth, nil |
|
| 56 |
+} |
|
| 57 |
+ |
|
| 58 |
+// GetAll retrieves all the credentials from the native store. |
|
| 59 |
+func (c *nativeStore) GetAll() (map[string]types.AuthConfig, error) {
|
|
| 60 |
+ auths, err := c.listCredentialsInStore() |
|
| 61 |
+ if err != nil {
|
|
| 62 |
+ return nil, err |
|
| 63 |
+ } |
|
| 64 |
+ |
|
| 65 |
+ // Emails are only stored in the file store. |
|
| 66 |
+ // This call can be safely eliminated when emails are removed. |
|
| 67 |
+ fileConfigs, _ := c.fileStore.GetAll() |
|
| 68 |
+ |
|
| 69 |
+ authConfigs := make(map[string]types.AuthConfig) |
|
| 70 |
+ for registry := range auths {
|
|
| 71 |
+ creds, err := c.getCredentialsFromStore(registry) |
|
| 72 |
+ if err != nil {
|
|
| 73 |
+ return nil, err |
|
| 74 |
+ } |
|
| 75 |
+ ac, _ := fileConfigs[registry] // might contain Email |
|
| 76 |
+ ac.Username = creds.Username |
|
| 77 |
+ ac.Password = creds.Password |
|
| 78 |
+ ac.IdentityToken = creds.IdentityToken |
|
| 79 |
+ authConfigs[registry] = ac |
|
| 80 |
+ } |
|
| 81 |
+ |
|
| 82 |
+ return authConfigs, nil |
|
| 83 |
+} |
|
| 84 |
+ |
|
| 85 |
+// Store saves the given credentials in the file store. |
|
| 86 |
+func (c *nativeStore) Store(authConfig types.AuthConfig) error {
|
|
| 87 |
+ if err := c.storeCredentialsInStore(authConfig); err != nil {
|
|
| 88 |
+ return err |
|
| 89 |
+ } |
|
| 90 |
+ authConfig.Username = "" |
|
| 91 |
+ authConfig.Password = "" |
|
| 92 |
+ authConfig.IdentityToken = "" |
|
| 93 |
+ |
|
| 94 |
+ // Fallback to old credential in plain text to save only the email |
|
| 95 |
+ return c.fileStore.Store(authConfig) |
|
| 96 |
+} |
|
| 97 |
+ |
|
| 98 |
+// storeCredentialsInStore executes the command to store the credentials in the native store. |
|
| 99 |
+func (c *nativeStore) storeCredentialsInStore(config types.AuthConfig) error {
|
|
| 100 |
+ creds := &credentials.Credentials{
|
|
| 101 |
+ ServerURL: config.ServerAddress, |
|
| 102 |
+ Username: config.Username, |
|
| 103 |
+ Secret: config.Password, |
|
| 104 |
+ } |
|
| 105 |
+ |
|
| 106 |
+ if config.IdentityToken != "" {
|
|
| 107 |
+ creds.Username = tokenUsername |
|
| 108 |
+ creds.Secret = config.IdentityToken |
|
| 109 |
+ } |
|
| 110 |
+ |
|
| 111 |
+ return client.Store(c.programFunc, creds) |
|
| 112 |
+} |
|
| 113 |
+ |
|
| 114 |
+// getCredentialsFromStore executes the command to get the credentials from the native store. |
|
| 115 |
+func (c *nativeStore) getCredentialsFromStore(serverAddress string) (types.AuthConfig, error) {
|
|
| 116 |
+ var ret types.AuthConfig |
|
| 117 |
+ |
|
| 118 |
+ creds, err := client.Get(c.programFunc, serverAddress) |
|
| 119 |
+ if err != nil {
|
|
| 120 |
+ if credentials.IsErrCredentialsNotFound(err) {
|
|
| 121 |
+ // do not return an error if the credentials are not |
|
| 122 |
+ // in the keyckain. Let docker ask for new credentials. |
|
| 123 |
+ return ret, nil |
|
| 124 |
+ } |
|
| 125 |
+ return ret, err |
|
| 126 |
+ } |
|
| 127 |
+ |
|
| 128 |
+ if creds.Username == tokenUsername {
|
|
| 129 |
+ ret.IdentityToken = creds.Secret |
|
| 130 |
+ } else {
|
|
| 131 |
+ ret.Password = creds.Secret |
|
| 132 |
+ ret.Username = creds.Username |
|
| 133 |
+ } |
|
| 134 |
+ |
|
| 135 |
+ ret.ServerAddress = serverAddress |
|
| 136 |
+ return ret, nil |
|
| 137 |
+} |
|
| 138 |
+ |
|
| 139 |
+// listCredentialsInStore returns a listing of stored credentials as a map of |
|
| 140 |
+// URL -> username. |
|
| 141 |
+func (c *nativeStore) listCredentialsInStore() (map[string]string, error) {
|
|
| 142 |
+ return client.List(c.programFunc) |
|
| 143 |
+} |
| 0 | 144 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,355 @@ |
| 0 |
+package credentials |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "encoding/json" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "io" |
|
| 6 |
+ "io/ioutil" |
|
| 7 |
+ "strings" |
|
| 8 |
+ "testing" |
|
| 9 |
+ |
|
| 10 |
+ "github.com/docker/docker-credential-helpers/client" |
|
| 11 |
+ "github.com/docker/docker-credential-helpers/credentials" |
|
| 12 |
+ "github.com/docker/docker/api/types" |
|
| 13 |
+) |
|
| 14 |
+ |
|
| 15 |
+const ( |
|
| 16 |
+ validServerAddress = "https://index.docker.io/v1" |
|
| 17 |
+ validServerAddress2 = "https://example.com:5002" |
|
| 18 |
+ invalidServerAddress = "https://foobar.example.com" |
|
| 19 |
+ missingCredsAddress = "https://missing.docker.io/v1" |
|
| 20 |
+) |
|
| 21 |
+ |
|
| 22 |
+var errCommandExited = fmt.Errorf("exited 1")
|
|
| 23 |
+ |
|
| 24 |
+// mockCommand simulates interactions between the docker client and a remote |
|
| 25 |
+// credentials helper. |
|
| 26 |
+// Unit tests inject this mocked command into the remote to control execution. |
|
| 27 |
+type mockCommand struct {
|
|
| 28 |
+ arg string |
|
| 29 |
+ input io.Reader |
|
| 30 |
+} |
|
| 31 |
+ |
|
| 32 |
+// Output returns responses from the remote credentials helper. |
|
| 33 |
+// It mocks those responses based in the input in the mock. |
|
| 34 |
+func (m *mockCommand) Output() ([]byte, error) {
|
|
| 35 |
+ in, err := ioutil.ReadAll(m.input) |
|
| 36 |
+ if err != nil {
|
|
| 37 |
+ return nil, err |
|
| 38 |
+ } |
|
| 39 |
+ inS := string(in) |
|
| 40 |
+ |
|
| 41 |
+ switch m.arg {
|
|
| 42 |
+ case "erase": |
|
| 43 |
+ switch inS {
|
|
| 44 |
+ case validServerAddress: |
|
| 45 |
+ return nil, nil |
|
| 46 |
+ default: |
|
| 47 |
+ return []byte("program failed"), errCommandExited
|
|
| 48 |
+ } |
|
| 49 |
+ case "get": |
|
| 50 |
+ switch inS {
|
|
| 51 |
+ case validServerAddress: |
|
| 52 |
+ return []byte(`{"Username": "foo", "Secret": "bar"}`), nil
|
|
| 53 |
+ case validServerAddress2: |
|
| 54 |
+ return []byte(`{"Username": "<token>", "Secret": "abcd1234"}`), nil
|
|
| 55 |
+ case missingCredsAddress: |
|
| 56 |
+ return []byte(credentials.NewErrCredentialsNotFound().Error()), errCommandExited |
|
| 57 |
+ case invalidServerAddress: |
|
| 58 |
+ return []byte("program failed"), errCommandExited
|
|
| 59 |
+ } |
|
| 60 |
+ case "store": |
|
| 61 |
+ var c credentials.Credentials |
|
| 62 |
+ err := json.NewDecoder(strings.NewReader(inS)).Decode(&c) |
|
| 63 |
+ if err != nil {
|
|
| 64 |
+ return []byte("program failed"), errCommandExited
|
|
| 65 |
+ } |
|
| 66 |
+ switch c.ServerURL {
|
|
| 67 |
+ case validServerAddress: |
|
| 68 |
+ return nil, nil |
|
| 69 |
+ default: |
|
| 70 |
+ return []byte("program failed"), errCommandExited
|
|
| 71 |
+ } |
|
| 72 |
+ case "list": |
|
| 73 |
+ return []byte(fmt.Sprintf(`{"%s": "%s", "%s": "%s"}`, validServerAddress, "foo", validServerAddress2, "<token>")), nil
|
|
| 74 |
+ } |
|
| 75 |
+ |
|
| 76 |
+ return []byte(fmt.Sprintf("unknown argument %q with %q", m.arg, inS)), errCommandExited
|
|
| 77 |
+} |
|
| 78 |
+ |
|
| 79 |
+// Input sets the input to send to a remote credentials helper. |
|
| 80 |
+func (m *mockCommand) Input(in io.Reader) {
|
|
| 81 |
+ m.input = in |
|
| 82 |
+} |
|
| 83 |
+ |
|
| 84 |
+func mockCommandFn(args ...string) client.Program {
|
|
| 85 |
+ return &mockCommand{
|
|
| 86 |
+ arg: args[0], |
|
| 87 |
+ } |
|
| 88 |
+} |
|
| 89 |
+ |
|
| 90 |
+func TestNativeStoreAddCredentials(t *testing.T) {
|
|
| 91 |
+ f := newConfigFile(make(map[string]types.AuthConfig)) |
|
| 92 |
+ f.CredentialsStore = "mock" |
|
| 93 |
+ |
|
| 94 |
+ s := &nativeStore{
|
|
| 95 |
+ programFunc: mockCommandFn, |
|
| 96 |
+ fileStore: NewFileStore(f), |
|
| 97 |
+ } |
|
| 98 |
+ err := s.Store(types.AuthConfig{
|
|
| 99 |
+ Username: "foo", |
|
| 100 |
+ Password: "bar", |
|
| 101 |
+ Email: "foo@example.com", |
|
| 102 |
+ ServerAddress: validServerAddress, |
|
| 103 |
+ }) |
|
| 104 |
+ |
|
| 105 |
+ if err != nil {
|
|
| 106 |
+ t.Fatal(err) |
|
| 107 |
+ } |
|
| 108 |
+ |
|
| 109 |
+ if len(f.AuthConfigs) != 1 {
|
|
| 110 |
+ t.Fatalf("expected 1 auth config, got %d", len(f.AuthConfigs))
|
|
| 111 |
+ } |
|
| 112 |
+ |
|
| 113 |
+ a, ok := f.AuthConfigs[validServerAddress] |
|
| 114 |
+ if !ok {
|
|
| 115 |
+ t.Fatalf("expected auth for %s, got %v", validServerAddress, f.AuthConfigs)
|
|
| 116 |
+ } |
|
| 117 |
+ if a.Auth != "" {
|
|
| 118 |
+ t.Fatalf("expected auth to be empty, got %s", a.Auth)
|
|
| 119 |
+ } |
|
| 120 |
+ if a.Username != "" {
|
|
| 121 |
+ t.Fatalf("expected username to be empty, got %s", a.Username)
|
|
| 122 |
+ } |
|
| 123 |
+ if a.Password != "" {
|
|
| 124 |
+ t.Fatalf("expected password to be empty, got %s", a.Password)
|
|
| 125 |
+ } |
|
| 126 |
+ if a.IdentityToken != "" {
|
|
| 127 |
+ t.Fatalf("expected identity token to be empty, got %s", a.IdentityToken)
|
|
| 128 |
+ } |
|
| 129 |
+ if a.Email != "foo@example.com" {
|
|
| 130 |
+ t.Fatalf("expected email `foo@example.com`, got %s", a.Email)
|
|
| 131 |
+ } |
|
| 132 |
+} |
|
| 133 |
+ |
|
| 134 |
+func TestNativeStoreAddInvalidCredentials(t *testing.T) {
|
|
| 135 |
+ f := newConfigFile(make(map[string]types.AuthConfig)) |
|
| 136 |
+ f.CredentialsStore = "mock" |
|
| 137 |
+ |
|
| 138 |
+ s := &nativeStore{
|
|
| 139 |
+ programFunc: mockCommandFn, |
|
| 140 |
+ fileStore: NewFileStore(f), |
|
| 141 |
+ } |
|
| 142 |
+ err := s.Store(types.AuthConfig{
|
|
| 143 |
+ Username: "foo", |
|
| 144 |
+ Password: "bar", |
|
| 145 |
+ Email: "foo@example.com", |
|
| 146 |
+ ServerAddress: invalidServerAddress, |
|
| 147 |
+ }) |
|
| 148 |
+ |
|
| 149 |
+ if err == nil {
|
|
| 150 |
+ t.Fatal("expected error, got nil")
|
|
| 151 |
+ } |
|
| 152 |
+ |
|
| 153 |
+ if !strings.Contains(err.Error(), "program failed") {
|
|
| 154 |
+ t.Fatalf("expected `program failed`, got %v", err)
|
|
| 155 |
+ } |
|
| 156 |
+ |
|
| 157 |
+ if len(f.AuthConfigs) != 0 {
|
|
| 158 |
+ t.Fatalf("expected 0 auth config, got %d", len(f.AuthConfigs))
|
|
| 159 |
+ } |
|
| 160 |
+} |
|
| 161 |
+ |
|
| 162 |
+func TestNativeStoreGet(t *testing.T) {
|
|
| 163 |
+ f := newConfigFile(map[string]types.AuthConfig{
|
|
| 164 |
+ validServerAddress: {
|
|
| 165 |
+ Email: "foo@example.com", |
|
| 166 |
+ }, |
|
| 167 |
+ }) |
|
| 168 |
+ f.CredentialsStore = "mock" |
|
| 169 |
+ |
|
| 170 |
+ s := &nativeStore{
|
|
| 171 |
+ programFunc: mockCommandFn, |
|
| 172 |
+ fileStore: NewFileStore(f), |
|
| 173 |
+ } |
|
| 174 |
+ a, err := s.Get(validServerAddress) |
|
| 175 |
+ if err != nil {
|
|
| 176 |
+ t.Fatal(err) |
|
| 177 |
+ } |
|
| 178 |
+ |
|
| 179 |
+ if a.Username != "foo" {
|
|
| 180 |
+ t.Fatalf("expected username `foo`, got %s", a.Username)
|
|
| 181 |
+ } |
|
| 182 |
+ if a.Password != "bar" {
|
|
| 183 |
+ t.Fatalf("expected password `bar`, got %s", a.Password)
|
|
| 184 |
+ } |
|
| 185 |
+ if a.IdentityToken != "" {
|
|
| 186 |
+ t.Fatalf("expected identity token to be empty, got %s", a.IdentityToken)
|
|
| 187 |
+ } |
|
| 188 |
+ if a.Email != "foo@example.com" {
|
|
| 189 |
+ t.Fatalf("expected email `foo@example.com`, got %s", a.Email)
|
|
| 190 |
+ } |
|
| 191 |
+} |
|
| 192 |
+ |
|
| 193 |
+func TestNativeStoreGetIdentityToken(t *testing.T) {
|
|
| 194 |
+ f := newConfigFile(map[string]types.AuthConfig{
|
|
| 195 |
+ validServerAddress2: {
|
|
| 196 |
+ Email: "foo@example2.com", |
|
| 197 |
+ }, |
|
| 198 |
+ }) |
|
| 199 |
+ f.CredentialsStore = "mock" |
|
| 200 |
+ |
|
| 201 |
+ s := &nativeStore{
|
|
| 202 |
+ programFunc: mockCommandFn, |
|
| 203 |
+ fileStore: NewFileStore(f), |
|
| 204 |
+ } |
|
| 205 |
+ a, err := s.Get(validServerAddress2) |
|
| 206 |
+ if err != nil {
|
|
| 207 |
+ t.Fatal(err) |
|
| 208 |
+ } |
|
| 209 |
+ |
|
| 210 |
+ if a.Username != "" {
|
|
| 211 |
+ t.Fatalf("expected username to be empty, got %s", a.Username)
|
|
| 212 |
+ } |
|
| 213 |
+ if a.Password != "" {
|
|
| 214 |
+ t.Fatalf("expected password to be empty, got %s", a.Password)
|
|
| 215 |
+ } |
|
| 216 |
+ if a.IdentityToken != "abcd1234" {
|
|
| 217 |
+ t.Fatalf("expected identity token `abcd1234`, got %s", a.IdentityToken)
|
|
| 218 |
+ } |
|
| 219 |
+ if a.Email != "foo@example2.com" {
|
|
| 220 |
+ t.Fatalf("expected email `foo@example2.com`, got %s", a.Email)
|
|
| 221 |
+ } |
|
| 222 |
+} |
|
| 223 |
+ |
|
| 224 |
+func TestNativeStoreGetAll(t *testing.T) {
|
|
| 225 |
+ f := newConfigFile(map[string]types.AuthConfig{
|
|
| 226 |
+ validServerAddress: {
|
|
| 227 |
+ Email: "foo@example.com", |
|
| 228 |
+ }, |
|
| 229 |
+ }) |
|
| 230 |
+ f.CredentialsStore = "mock" |
|
| 231 |
+ |
|
| 232 |
+ s := &nativeStore{
|
|
| 233 |
+ programFunc: mockCommandFn, |
|
| 234 |
+ fileStore: NewFileStore(f), |
|
| 235 |
+ } |
|
| 236 |
+ as, err := s.GetAll() |
|
| 237 |
+ if err != nil {
|
|
| 238 |
+ t.Fatal(err) |
|
| 239 |
+ } |
|
| 240 |
+ |
|
| 241 |
+ if len(as) != 2 {
|
|
| 242 |
+ t.Fatalf("wanted 2, got %d", len(as))
|
|
| 243 |
+ } |
|
| 244 |
+ |
|
| 245 |
+ if as[validServerAddress].Username != "foo" {
|
|
| 246 |
+ t.Fatalf("expected username `foo` for %s, got %s", validServerAddress, as[validServerAddress].Username)
|
|
| 247 |
+ } |
|
| 248 |
+ if as[validServerAddress].Password != "bar" {
|
|
| 249 |
+ t.Fatalf("expected password `bar` for %s, got %s", validServerAddress, as[validServerAddress].Password)
|
|
| 250 |
+ } |
|
| 251 |
+ if as[validServerAddress].IdentityToken != "" {
|
|
| 252 |
+ t.Fatalf("expected identity to be empty for %s, got %s", validServerAddress, as[validServerAddress].IdentityToken)
|
|
| 253 |
+ } |
|
| 254 |
+ if as[validServerAddress].Email != "foo@example.com" {
|
|
| 255 |
+ t.Fatalf("expected email `foo@example.com` for %s, got %s", validServerAddress, as[validServerAddress].Email)
|
|
| 256 |
+ } |
|
| 257 |
+ if as[validServerAddress2].Username != "" {
|
|
| 258 |
+ t.Fatalf("expected username to be empty for %s, got %s", validServerAddress2, as[validServerAddress2].Username)
|
|
| 259 |
+ } |
|
| 260 |
+ if as[validServerAddress2].Password != "" {
|
|
| 261 |
+ t.Fatalf("expected password to be empty for %s, got %s", validServerAddress2, as[validServerAddress2].Password)
|
|
| 262 |
+ } |
|
| 263 |
+ if as[validServerAddress2].IdentityToken != "abcd1234" {
|
|
| 264 |
+ t.Fatalf("expected identity token `abcd1324` for %s, got %s", validServerAddress2, as[validServerAddress2].IdentityToken)
|
|
| 265 |
+ } |
|
| 266 |
+ if as[validServerAddress2].Email != "" {
|
|
| 267 |
+ t.Fatalf("expected no email for %s, got %s", validServerAddress2, as[validServerAddress2].Email)
|
|
| 268 |
+ } |
|
| 269 |
+} |
|
| 270 |
+ |
|
| 271 |
+func TestNativeStoreGetMissingCredentials(t *testing.T) {
|
|
| 272 |
+ f := newConfigFile(map[string]types.AuthConfig{
|
|
| 273 |
+ validServerAddress: {
|
|
| 274 |
+ Email: "foo@example.com", |
|
| 275 |
+ }, |
|
| 276 |
+ }) |
|
| 277 |
+ f.CredentialsStore = "mock" |
|
| 278 |
+ |
|
| 279 |
+ s := &nativeStore{
|
|
| 280 |
+ programFunc: mockCommandFn, |
|
| 281 |
+ fileStore: NewFileStore(f), |
|
| 282 |
+ } |
|
| 283 |
+ _, err := s.Get(missingCredsAddress) |
|
| 284 |
+ if err != nil {
|
|
| 285 |
+ // missing credentials do not produce an error |
|
| 286 |
+ t.Fatal(err) |
|
| 287 |
+ } |
|
| 288 |
+} |
|
| 289 |
+ |
|
| 290 |
+func TestNativeStoreGetInvalidAddress(t *testing.T) {
|
|
| 291 |
+ f := newConfigFile(map[string]types.AuthConfig{
|
|
| 292 |
+ validServerAddress: {
|
|
| 293 |
+ Email: "foo@example.com", |
|
| 294 |
+ }, |
|
| 295 |
+ }) |
|
| 296 |
+ f.CredentialsStore = "mock" |
|
| 297 |
+ |
|
| 298 |
+ s := &nativeStore{
|
|
| 299 |
+ programFunc: mockCommandFn, |
|
| 300 |
+ fileStore: NewFileStore(f), |
|
| 301 |
+ } |
|
| 302 |
+ _, err := s.Get(invalidServerAddress) |
|
| 303 |
+ if err == nil {
|
|
| 304 |
+ t.Fatal("expected error, got nil")
|
|
| 305 |
+ } |
|
| 306 |
+ |
|
| 307 |
+ if !strings.Contains(err.Error(), "program failed") {
|
|
| 308 |
+ t.Fatalf("expected `program failed`, got %v", err)
|
|
| 309 |
+ } |
|
| 310 |
+} |
|
| 311 |
+ |
|
| 312 |
+func TestNativeStoreErase(t *testing.T) {
|
|
| 313 |
+ f := newConfigFile(map[string]types.AuthConfig{
|
|
| 314 |
+ validServerAddress: {
|
|
| 315 |
+ Email: "foo@example.com", |
|
| 316 |
+ }, |
|
| 317 |
+ }) |
|
| 318 |
+ f.CredentialsStore = "mock" |
|
| 319 |
+ |
|
| 320 |
+ s := &nativeStore{
|
|
| 321 |
+ programFunc: mockCommandFn, |
|
| 322 |
+ fileStore: NewFileStore(f), |
|
| 323 |
+ } |
|
| 324 |
+ err := s.Erase(validServerAddress) |
|
| 325 |
+ if err != nil {
|
|
| 326 |
+ t.Fatal(err) |
|
| 327 |
+ } |
|
| 328 |
+ |
|
| 329 |
+ if len(f.AuthConfigs) != 0 {
|
|
| 330 |
+ t.Fatalf("expected 0 auth configs, got %d", len(f.AuthConfigs))
|
|
| 331 |
+ } |
|
| 332 |
+} |
|
| 333 |
+ |
|
| 334 |
+func TestNativeStoreEraseInvalidAddress(t *testing.T) {
|
|
| 335 |
+ f := newConfigFile(map[string]types.AuthConfig{
|
|
| 336 |
+ validServerAddress: {
|
|
| 337 |
+ Email: "foo@example.com", |
|
| 338 |
+ }, |
|
| 339 |
+ }) |
|
| 340 |
+ f.CredentialsStore = "mock" |
|
| 341 |
+ |
|
| 342 |
+ s := &nativeStore{
|
|
| 343 |
+ programFunc: mockCommandFn, |
|
| 344 |
+ fileStore: NewFileStore(f), |
|
| 345 |
+ } |
|
| 346 |
+ err := s.Erase(invalidServerAddress) |
|
| 347 |
+ if err == nil {
|
|
| 348 |
+ t.Fatal("expected error, got nil")
|
|
| 349 |
+ } |
|
| 350 |
+ |
|
| 351 |
+ if !strings.Contains(err.Error(), "program failed") {
|
|
| 352 |
+ t.Fatalf("expected `program failed`, got %v", err)
|
|
| 353 |
+ } |
|
| 354 |
+} |
| ... | ... |
@@ -6,7 +6,7 @@ import ( |
| 6 | 6 |
"path/filepath" |
| 7 | 7 |
|
| 8 | 8 |
"github.com/Sirupsen/logrus" |
| 9 |
- "github.com/docker/docker/cliconfig" |
|
| 9 |
+ cliconfig "github.com/docker/docker/cli/config" |
|
| 10 | 10 |
"github.com/docker/docker/opts" |
| 11 | 11 |
"github.com/docker/go-connections/tlsconfig" |
| 12 | 12 |
"github.com/spf13/pflag" |
| ... | ... |
@@ -49,7 +49,7 @@ func NewCommonOptions() *CommonOptions {
|
| 49 | 49 |
// InstallFlags adds flags for the common options on the FlagSet |
| 50 | 50 |
func (commonOpts *CommonOptions) InstallFlags(flags *pflag.FlagSet) {
|
| 51 | 51 |
if dockerCertPath == "" {
|
| 52 |
- dockerCertPath = cliconfig.ConfigDir() |
|
| 52 |
+ dockerCertPath = cliconfig.Dir() |
|
| 53 | 53 |
} |
| 54 | 54 |
|
| 55 | 55 |
flags.BoolVarP(&commonOpts.Debug, "debug", "D", false, "Enable debug mode") |
| ... | ... |
@@ -18,7 +18,7 @@ import ( |
| 18 | 18 |
"github.com/docker/docker/api/types" |
| 19 | 19 |
registrytypes "github.com/docker/docker/api/types/registry" |
| 20 | 20 |
"github.com/docker/docker/cli/command" |
| 21 |
- "github.com/docker/docker/cliconfig" |
|
| 21 |
+ cliconfig "github.com/docker/docker/cli/config" |
|
| 22 | 22 |
"github.com/docker/docker/registry" |
| 23 | 23 |
"github.com/docker/go-connections/tlsconfig" |
| 24 | 24 |
"github.com/docker/notary" |
| ... | ... |
@@ -37,7 +37,7 @@ var ( |
| 37 | 37 |
) |
| 38 | 38 |
|
| 39 | 39 |
func trustDirectory() string {
|
| 40 |
- return filepath.Join(cliconfig.ConfigDir(), "trust") |
|
| 40 |
+ return filepath.Join(cliconfig.Dir(), "trust") |
|
| 41 | 41 |
} |
| 42 | 42 |
|
| 43 | 43 |
// certificateDirectory returns the directory containing |
| ... | ... |
@@ -49,7 +49,7 @@ func certificateDirectory(server string) (string, error) {
|
| 49 | 49 |
return "", err |
| 50 | 50 |
} |
| 51 | 51 |
|
| 52 |
- return filepath.Join(cliconfig.ConfigDir(), "tls", u.Host), nil |
|
| 52 |
+ return filepath.Join(cliconfig.Dir(), "tls", u.Host), nil |
|
| 53 | 53 |
} |
| 54 | 54 |
|
| 55 | 55 |
// Server returns the base URL for the trust server. |
| 56 | 56 |
deleted file mode 100644 |
| ... | ... |
@@ -1,120 +0,0 @@ |
| 1 |
-package cliconfig |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- "io" |
|
| 6 |
- "os" |
|
| 7 |
- "path/filepath" |
|
| 8 |
- |
|
| 9 |
- "github.com/docker/docker/api/types" |
|
| 10 |
- "github.com/docker/docker/cliconfig/configfile" |
|
| 11 |
- "github.com/docker/docker/pkg/homedir" |
|
| 12 |
-) |
|
| 13 |
- |
|
| 14 |
-const ( |
|
| 15 |
- // ConfigFileName is the name of config file |
|
| 16 |
- ConfigFileName = "config.json" |
|
| 17 |
- configFileDir = ".docker" |
|
| 18 |
- oldConfigfile = ".dockercfg" |
|
| 19 |
-) |
|
| 20 |
- |
|
| 21 |
-var ( |
|
| 22 |
- configDir = os.Getenv("DOCKER_CONFIG")
|
|
| 23 |
-) |
|
| 24 |
- |
|
| 25 |
-func init() {
|
|
| 26 |
- if configDir == "" {
|
|
| 27 |
- configDir = filepath.Join(homedir.Get(), configFileDir) |
|
| 28 |
- } |
|
| 29 |
-} |
|
| 30 |
- |
|
| 31 |
-// ConfigDir returns the directory the configuration file is stored in |
|
| 32 |
-func ConfigDir() string {
|
|
| 33 |
- return configDir |
|
| 34 |
-} |
|
| 35 |
- |
|
| 36 |
-// SetConfigDir sets the directory the configuration file is stored in |
|
| 37 |
-func SetConfigDir(dir string) {
|
|
| 38 |
- configDir = dir |
|
| 39 |
-} |
|
| 40 |
- |
|
| 41 |
-// NewConfigFile initializes an empty configuration file for the given filename 'fn' |
|
| 42 |
-func NewConfigFile(fn string) *configfile.ConfigFile {
|
|
| 43 |
- return &configfile.ConfigFile{
|
|
| 44 |
- AuthConfigs: make(map[string]types.AuthConfig), |
|
| 45 |
- HTTPHeaders: make(map[string]string), |
|
| 46 |
- Filename: fn, |
|
| 47 |
- } |
|
| 48 |
-} |
|
| 49 |
- |
|
| 50 |
-// LegacyLoadFromReader is a convenience function that creates a ConfigFile object from |
|
| 51 |
-// a non-nested reader |
|
| 52 |
-func LegacyLoadFromReader(configData io.Reader) (*configfile.ConfigFile, error) {
|
|
| 53 |
- configFile := configfile.ConfigFile{
|
|
| 54 |
- AuthConfigs: make(map[string]types.AuthConfig), |
|
| 55 |
- } |
|
| 56 |
- err := configFile.LegacyLoadFromReader(configData) |
|
| 57 |
- return &configFile, err |
|
| 58 |
-} |
|
| 59 |
- |
|
| 60 |
-// LoadFromReader is a convenience function that creates a ConfigFile object from |
|
| 61 |
-// a reader |
|
| 62 |
-func LoadFromReader(configData io.Reader) (*configfile.ConfigFile, error) {
|
|
| 63 |
- configFile := configfile.ConfigFile{
|
|
| 64 |
- AuthConfigs: make(map[string]types.AuthConfig), |
|
| 65 |
- } |
|
| 66 |
- err := configFile.LoadFromReader(configData) |
|
| 67 |
- return &configFile, err |
|
| 68 |
-} |
|
| 69 |
- |
|
| 70 |
-// Load reads the configuration files in the given directory, and sets up |
|
| 71 |
-// the auth config information and returns values. |
|
| 72 |
-// FIXME: use the internal golang config parser |
|
| 73 |
-func Load(configDir string) (*configfile.ConfigFile, error) {
|
|
| 74 |
- if configDir == "" {
|
|
| 75 |
- configDir = ConfigDir() |
|
| 76 |
- } |
|
| 77 |
- |
|
| 78 |
- configFile := configfile.ConfigFile{
|
|
| 79 |
- AuthConfigs: make(map[string]types.AuthConfig), |
|
| 80 |
- Filename: filepath.Join(configDir, ConfigFileName), |
|
| 81 |
- } |
|
| 82 |
- |
|
| 83 |
- // Try happy path first - latest config file |
|
| 84 |
- if _, err := os.Stat(configFile.Filename); err == nil {
|
|
| 85 |
- file, err := os.Open(configFile.Filename) |
|
| 86 |
- if err != nil {
|
|
| 87 |
- return &configFile, fmt.Errorf("%s - %v", configFile.Filename, err)
|
|
| 88 |
- } |
|
| 89 |
- defer file.Close() |
|
| 90 |
- err = configFile.LoadFromReader(file) |
|
| 91 |
- if err != nil {
|
|
| 92 |
- err = fmt.Errorf("%s - %v", configFile.Filename, err)
|
|
| 93 |
- } |
|
| 94 |
- return &configFile, err |
|
| 95 |
- } else if !os.IsNotExist(err) {
|
|
| 96 |
- // if file is there but we can't stat it for any reason other |
|
| 97 |
- // than it doesn't exist then stop |
|
| 98 |
- return &configFile, fmt.Errorf("%s - %v", configFile.Filename, err)
|
|
| 99 |
- } |
|
| 100 |
- |
|
| 101 |
- // Can't find latest config file so check for the old one |
|
| 102 |
- confFile := filepath.Join(homedir.Get(), oldConfigfile) |
|
| 103 |
- if _, err := os.Stat(confFile); err != nil {
|
|
| 104 |
- return &configFile, nil //missing file is not an error |
|
| 105 |
- } |
|
| 106 |
- file, err := os.Open(confFile) |
|
| 107 |
- if err != nil {
|
|
| 108 |
- return &configFile, fmt.Errorf("%s - %v", confFile, err)
|
|
| 109 |
- } |
|
| 110 |
- defer file.Close() |
|
| 111 |
- err = configFile.LegacyLoadFromReader(file) |
|
| 112 |
- if err != nil {
|
|
| 113 |
- return &configFile, fmt.Errorf("%s - %v", confFile, err)
|
|
| 114 |
- } |
|
| 115 |
- |
|
| 116 |
- if configFile.HTTPHeaders == nil {
|
|
| 117 |
- configFile.HTTPHeaders = map[string]string{}
|
|
| 118 |
- } |
|
| 119 |
- return &configFile, nil |
|
| 120 |
-} |
| 121 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,621 +0,0 @@ |
| 1 |
-package cliconfig |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "io/ioutil" |
|
| 5 |
- "os" |
|
| 6 |
- "path/filepath" |
|
| 7 |
- "strings" |
|
| 8 |
- "testing" |
|
| 9 |
- |
|
| 10 |
- "github.com/docker/docker/cliconfig/configfile" |
|
| 11 |
- "github.com/docker/docker/pkg/homedir" |
|
| 12 |
-) |
|
| 13 |
- |
|
| 14 |
-func TestEmptyConfigDir(t *testing.T) {
|
|
| 15 |
- tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 16 |
- if err != nil {
|
|
| 17 |
- t.Fatal(err) |
|
| 18 |
- } |
|
| 19 |
- defer os.RemoveAll(tmpHome) |
|
| 20 |
- |
|
| 21 |
- SetConfigDir(tmpHome) |
|
| 22 |
- |
|
| 23 |
- config, err := Load("")
|
|
| 24 |
- if err != nil {
|
|
| 25 |
- t.Fatalf("Failed loading on empty config dir: %q", err)
|
|
| 26 |
- } |
|
| 27 |
- |
|
| 28 |
- expectedConfigFilename := filepath.Join(tmpHome, ConfigFileName) |
|
| 29 |
- if config.Filename != expectedConfigFilename {
|
|
| 30 |
- t.Fatalf("Expected config filename %s, got %s", expectedConfigFilename, config.Filename)
|
|
| 31 |
- } |
|
| 32 |
- |
|
| 33 |
- // Now save it and make sure it shows up in new form |
|
| 34 |
- saveConfigAndValidateNewFormat(t, config, tmpHome) |
|
| 35 |
-} |
|
| 36 |
- |
|
| 37 |
-func TestMissingFile(t *testing.T) {
|
|
| 38 |
- tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 39 |
- if err != nil {
|
|
| 40 |
- t.Fatal(err) |
|
| 41 |
- } |
|
| 42 |
- defer os.RemoveAll(tmpHome) |
|
| 43 |
- |
|
| 44 |
- config, err := Load(tmpHome) |
|
| 45 |
- if err != nil {
|
|
| 46 |
- t.Fatalf("Failed loading on missing file: %q", err)
|
|
| 47 |
- } |
|
| 48 |
- |
|
| 49 |
- // Now save it and make sure it shows up in new form |
|
| 50 |
- saveConfigAndValidateNewFormat(t, config, tmpHome) |
|
| 51 |
-} |
|
| 52 |
- |
|
| 53 |
-func TestSaveFileToDirs(t *testing.T) {
|
|
| 54 |
- tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 55 |
- if err != nil {
|
|
| 56 |
- t.Fatal(err) |
|
| 57 |
- } |
|
| 58 |
- defer os.RemoveAll(tmpHome) |
|
| 59 |
- |
|
| 60 |
- tmpHome += "/.docker" |
|
| 61 |
- |
|
| 62 |
- config, err := Load(tmpHome) |
|
| 63 |
- if err != nil {
|
|
| 64 |
- t.Fatalf("Failed loading on missing file: %q", err)
|
|
| 65 |
- } |
|
| 66 |
- |
|
| 67 |
- // Now save it and make sure it shows up in new form |
|
| 68 |
- saveConfigAndValidateNewFormat(t, config, tmpHome) |
|
| 69 |
-} |
|
| 70 |
- |
|
| 71 |
-func TestEmptyFile(t *testing.T) {
|
|
| 72 |
- tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 73 |
- if err != nil {
|
|
| 74 |
- t.Fatal(err) |
|
| 75 |
- } |
|
| 76 |
- defer os.RemoveAll(tmpHome) |
|
| 77 |
- |
|
| 78 |
- fn := filepath.Join(tmpHome, ConfigFileName) |
|
| 79 |
- if err := ioutil.WriteFile(fn, []byte(""), 0600); err != nil {
|
|
| 80 |
- t.Fatal(err) |
|
| 81 |
- } |
|
| 82 |
- |
|
| 83 |
- _, err = Load(tmpHome) |
|
| 84 |
- if err == nil {
|
|
| 85 |
- t.Fatalf("Was supposed to fail")
|
|
| 86 |
- } |
|
| 87 |
-} |
|
| 88 |
- |
|
| 89 |
-func TestEmptyJSON(t *testing.T) {
|
|
| 90 |
- tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 91 |
- if err != nil {
|
|
| 92 |
- t.Fatal(err) |
|
| 93 |
- } |
|
| 94 |
- defer os.RemoveAll(tmpHome) |
|
| 95 |
- |
|
| 96 |
- fn := filepath.Join(tmpHome, ConfigFileName) |
|
| 97 |
- if err := ioutil.WriteFile(fn, []byte("{}"), 0600); err != nil {
|
|
| 98 |
- t.Fatal(err) |
|
| 99 |
- } |
|
| 100 |
- |
|
| 101 |
- config, err := Load(tmpHome) |
|
| 102 |
- if err != nil {
|
|
| 103 |
- t.Fatalf("Failed loading on empty json file: %q", err)
|
|
| 104 |
- } |
|
| 105 |
- |
|
| 106 |
- // Now save it and make sure it shows up in new form |
|
| 107 |
- saveConfigAndValidateNewFormat(t, config, tmpHome) |
|
| 108 |
-} |
|
| 109 |
- |
|
| 110 |
-func TestOldInvalidsAuth(t *testing.T) {
|
|
| 111 |
- invalids := map[string]string{
|
|
| 112 |
- `username = test`: "The Auth config file is empty", |
|
| 113 |
- `username |
|
| 114 |
-password`: "Invalid Auth config file", |
|
| 115 |
- `username = test |
|
| 116 |
-email`: "Invalid auth configuration file", |
|
| 117 |
- } |
|
| 118 |
- |
|
| 119 |
- tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 120 |
- if err != nil {
|
|
| 121 |
- t.Fatal(err) |
|
| 122 |
- } |
|
| 123 |
- defer os.RemoveAll(tmpHome) |
|
| 124 |
- |
|
| 125 |
- homeKey := homedir.Key() |
|
| 126 |
- homeVal := homedir.Get() |
|
| 127 |
- |
|
| 128 |
- defer func() { os.Setenv(homeKey, homeVal) }()
|
|
| 129 |
- os.Setenv(homeKey, tmpHome) |
|
| 130 |
- |
|
| 131 |
- for content, expectedError := range invalids {
|
|
| 132 |
- fn := filepath.Join(tmpHome, oldConfigfile) |
|
| 133 |
- if err := ioutil.WriteFile(fn, []byte(content), 0600); err != nil {
|
|
| 134 |
- t.Fatal(err) |
|
| 135 |
- } |
|
| 136 |
- |
|
| 137 |
- config, err := Load(tmpHome) |
|
| 138 |
- // Use Contains instead of == since the file name will change each time |
|
| 139 |
- if err == nil || !strings.Contains(err.Error(), expectedError) {
|
|
| 140 |
- t.Fatalf("Should have failed\nConfig: %v\nGot: %v\nExpected: %v", config, err, expectedError)
|
|
| 141 |
- } |
|
| 142 |
- |
|
| 143 |
- } |
|
| 144 |
-} |
|
| 145 |
- |
|
| 146 |
-func TestOldValidAuth(t *testing.T) {
|
|
| 147 |
- tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 148 |
- if err != nil {
|
|
| 149 |
- t.Fatal(err) |
|
| 150 |
- } |
|
| 151 |
- if err != nil {
|
|
| 152 |
- t.Fatal(err) |
|
| 153 |
- } |
|
| 154 |
- defer os.RemoveAll(tmpHome) |
|
| 155 |
- |
|
| 156 |
- homeKey := homedir.Key() |
|
| 157 |
- homeVal := homedir.Get() |
|
| 158 |
- |
|
| 159 |
- defer func() { os.Setenv(homeKey, homeVal) }()
|
|
| 160 |
- os.Setenv(homeKey, tmpHome) |
|
| 161 |
- |
|
| 162 |
- fn := filepath.Join(tmpHome, oldConfigfile) |
|
| 163 |
- js := `username = am9lam9lOmhlbGxv |
|
| 164 |
- email = user@example.com` |
|
| 165 |
- if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
|
|
| 166 |
- t.Fatal(err) |
|
| 167 |
- } |
|
| 168 |
- |
|
| 169 |
- config, err := Load(tmpHome) |
|
| 170 |
- if err != nil {
|
|
| 171 |
- t.Fatal(err) |
|
| 172 |
- } |
|
| 173 |
- |
|
| 174 |
- // defaultIndexserver is https://index.docker.io/v1/ |
|
| 175 |
- ac := config.AuthConfigs["https://index.docker.io/v1/"] |
|
| 176 |
- if ac.Username != "joejoe" || ac.Password != "hello" {
|
|
| 177 |
- t.Fatalf("Missing data from parsing:\n%q", config)
|
|
| 178 |
- } |
|
| 179 |
- |
|
| 180 |
- // Now save it and make sure it shows up in new form |
|
| 181 |
- configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) |
|
| 182 |
- |
|
| 183 |
- expConfStr := `{
|
|
| 184 |
- "auths": {
|
|
| 185 |
- "https://index.docker.io/v1/": {
|
|
| 186 |
- "auth": "am9lam9lOmhlbGxv" |
|
| 187 |
- } |
|
| 188 |
- } |
|
| 189 |
-}` |
|
| 190 |
- |
|
| 191 |
- if configStr != expConfStr {
|
|
| 192 |
- t.Fatalf("Should have save in new form: \n%s\n not \n%s", configStr, expConfStr)
|
|
| 193 |
- } |
|
| 194 |
-} |
|
| 195 |
- |
|
| 196 |
-func TestOldJSONInvalid(t *testing.T) {
|
|
| 197 |
- tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 198 |
- if err != nil {
|
|
| 199 |
- t.Fatal(err) |
|
| 200 |
- } |
|
| 201 |
- defer os.RemoveAll(tmpHome) |
|
| 202 |
- |
|
| 203 |
- homeKey := homedir.Key() |
|
| 204 |
- homeVal := homedir.Get() |
|
| 205 |
- |
|
| 206 |
- defer func() { os.Setenv(homeKey, homeVal) }()
|
|
| 207 |
- os.Setenv(homeKey, tmpHome) |
|
| 208 |
- |
|
| 209 |
- fn := filepath.Join(tmpHome, oldConfigfile) |
|
| 210 |
- js := `{"https://index.docker.io/v1/":{"auth":"test","email":"user@example.com"}}`
|
|
| 211 |
- if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
|
|
| 212 |
- t.Fatal(err) |
|
| 213 |
- } |
|
| 214 |
- |
|
| 215 |
- config, err := Load(tmpHome) |
|
| 216 |
- // Use Contains instead of == since the file name will change each time |
|
| 217 |
- if err == nil || !strings.Contains(err.Error(), "Invalid auth configuration file") {
|
|
| 218 |
- t.Fatalf("Expected an error got : %v, %v", config, err)
|
|
| 219 |
- } |
|
| 220 |
-} |
|
| 221 |
- |
|
| 222 |
-func TestOldJSON(t *testing.T) {
|
|
| 223 |
- tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 224 |
- if err != nil {
|
|
| 225 |
- t.Fatal(err) |
|
| 226 |
- } |
|
| 227 |
- defer os.RemoveAll(tmpHome) |
|
| 228 |
- |
|
| 229 |
- homeKey := homedir.Key() |
|
| 230 |
- homeVal := homedir.Get() |
|
| 231 |
- |
|
| 232 |
- defer func() { os.Setenv(homeKey, homeVal) }()
|
|
| 233 |
- os.Setenv(homeKey, tmpHome) |
|
| 234 |
- |
|
| 235 |
- fn := filepath.Join(tmpHome, oldConfigfile) |
|
| 236 |
- js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
|
|
| 237 |
- if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
|
|
| 238 |
- t.Fatal(err) |
|
| 239 |
- } |
|
| 240 |
- |
|
| 241 |
- config, err := Load(tmpHome) |
|
| 242 |
- if err != nil {
|
|
| 243 |
- t.Fatalf("Failed loading on empty json file: %q", err)
|
|
| 244 |
- } |
|
| 245 |
- |
|
| 246 |
- ac := config.AuthConfigs["https://index.docker.io/v1/"] |
|
| 247 |
- if ac.Username != "joejoe" || ac.Password != "hello" {
|
|
| 248 |
- t.Fatalf("Missing data from parsing:\n%q", config)
|
|
| 249 |
- } |
|
| 250 |
- |
|
| 251 |
- // Now save it and make sure it shows up in new form |
|
| 252 |
- configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) |
|
| 253 |
- |
|
| 254 |
- expConfStr := `{
|
|
| 255 |
- "auths": {
|
|
| 256 |
- "https://index.docker.io/v1/": {
|
|
| 257 |
- "auth": "am9lam9lOmhlbGxv", |
|
| 258 |
- "email": "user@example.com" |
|
| 259 |
- } |
|
| 260 |
- } |
|
| 261 |
-}` |
|
| 262 |
- |
|
| 263 |
- if configStr != expConfStr {
|
|
| 264 |
- t.Fatalf("Should have save in new form: \n'%s'\n not \n'%s'\n", configStr, expConfStr)
|
|
| 265 |
- } |
|
| 266 |
-} |
|
| 267 |
- |
|
| 268 |
-func TestNewJSON(t *testing.T) {
|
|
| 269 |
- tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 270 |
- if err != nil {
|
|
| 271 |
- t.Fatal(err) |
|
| 272 |
- } |
|
| 273 |
- defer os.RemoveAll(tmpHome) |
|
| 274 |
- |
|
| 275 |
- fn := filepath.Join(tmpHome, ConfigFileName) |
|
| 276 |
- js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } } }`
|
|
| 277 |
- if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
|
|
| 278 |
- t.Fatal(err) |
|
| 279 |
- } |
|
| 280 |
- |
|
| 281 |
- config, err := Load(tmpHome) |
|
| 282 |
- if err != nil {
|
|
| 283 |
- t.Fatalf("Failed loading on empty json file: %q", err)
|
|
| 284 |
- } |
|
| 285 |
- |
|
| 286 |
- ac := config.AuthConfigs["https://index.docker.io/v1/"] |
|
| 287 |
- if ac.Username != "joejoe" || ac.Password != "hello" {
|
|
| 288 |
- t.Fatalf("Missing data from parsing:\n%q", config)
|
|
| 289 |
- } |
|
| 290 |
- |
|
| 291 |
- // Now save it and make sure it shows up in new form |
|
| 292 |
- configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) |
|
| 293 |
- |
|
| 294 |
- expConfStr := `{
|
|
| 295 |
- "auths": {
|
|
| 296 |
- "https://index.docker.io/v1/": {
|
|
| 297 |
- "auth": "am9lam9lOmhlbGxv" |
|
| 298 |
- } |
|
| 299 |
- } |
|
| 300 |
-}` |
|
| 301 |
- |
|
| 302 |
- if configStr != expConfStr {
|
|
| 303 |
- t.Fatalf("Should have save in new form: \n%s\n not \n%s", configStr, expConfStr)
|
|
| 304 |
- } |
|
| 305 |
-} |
|
| 306 |
- |
|
| 307 |
-func TestNewJSONNoEmail(t *testing.T) {
|
|
| 308 |
- tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 309 |
- if err != nil {
|
|
| 310 |
- t.Fatal(err) |
|
| 311 |
- } |
|
| 312 |
- defer os.RemoveAll(tmpHome) |
|
| 313 |
- |
|
| 314 |
- fn := filepath.Join(tmpHome, ConfigFileName) |
|
| 315 |
- js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } } }`
|
|
| 316 |
- if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
|
|
| 317 |
- t.Fatal(err) |
|
| 318 |
- } |
|
| 319 |
- |
|
| 320 |
- config, err := Load(tmpHome) |
|
| 321 |
- if err != nil {
|
|
| 322 |
- t.Fatalf("Failed loading on empty json file: %q", err)
|
|
| 323 |
- } |
|
| 324 |
- |
|
| 325 |
- ac := config.AuthConfigs["https://index.docker.io/v1/"] |
|
| 326 |
- if ac.Username != "joejoe" || ac.Password != "hello" {
|
|
| 327 |
- t.Fatalf("Missing data from parsing:\n%q", config)
|
|
| 328 |
- } |
|
| 329 |
- |
|
| 330 |
- // Now save it and make sure it shows up in new form |
|
| 331 |
- configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) |
|
| 332 |
- |
|
| 333 |
- expConfStr := `{
|
|
| 334 |
- "auths": {
|
|
| 335 |
- "https://index.docker.io/v1/": {
|
|
| 336 |
- "auth": "am9lam9lOmhlbGxv" |
|
| 337 |
- } |
|
| 338 |
- } |
|
| 339 |
-}` |
|
| 340 |
- |
|
| 341 |
- if configStr != expConfStr {
|
|
| 342 |
- t.Fatalf("Should have save in new form: \n%s\n not \n%s", configStr, expConfStr)
|
|
| 343 |
- } |
|
| 344 |
-} |
|
| 345 |
- |
|
| 346 |
-func TestJSONWithPsFormat(t *testing.T) {
|
|
| 347 |
- tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 348 |
- if err != nil {
|
|
| 349 |
- t.Fatal(err) |
|
| 350 |
- } |
|
| 351 |
- defer os.RemoveAll(tmpHome) |
|
| 352 |
- |
|
| 353 |
- fn := filepath.Join(tmpHome, ConfigFileName) |
|
| 354 |
- js := `{
|
|
| 355 |
- "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
|
|
| 356 |
- "psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
|
|
| 357 |
-}` |
|
| 358 |
- if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
|
|
| 359 |
- t.Fatal(err) |
|
| 360 |
- } |
|
| 361 |
- |
|
| 362 |
- config, err := Load(tmpHome) |
|
| 363 |
- if err != nil {
|
|
| 364 |
- t.Fatalf("Failed loading on empty json file: %q", err)
|
|
| 365 |
- } |
|
| 366 |
- |
|
| 367 |
- if config.PsFormat != `table {{.ID}}\t{{.Label "com.docker.label.cpu"}}` {
|
|
| 368 |
- t.Fatalf("Unknown ps format: %s\n", config.PsFormat)
|
|
| 369 |
- } |
|
| 370 |
- |
|
| 371 |
- // Now save it and make sure it shows up in new form |
|
| 372 |
- configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) |
|
| 373 |
- if !strings.Contains(configStr, `"psFormat":`) || |
|
| 374 |
- !strings.Contains(configStr, "{{.ID}}") {
|
|
| 375 |
- t.Fatalf("Should have save in new form: %s", configStr)
|
|
| 376 |
- } |
|
| 377 |
-} |
|
| 378 |
- |
|
| 379 |
-func TestJSONWithCredentialStore(t *testing.T) {
|
|
| 380 |
- tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 381 |
- if err != nil {
|
|
| 382 |
- t.Fatal(err) |
|
| 383 |
- } |
|
| 384 |
- defer os.RemoveAll(tmpHome) |
|
| 385 |
- |
|
| 386 |
- fn := filepath.Join(tmpHome, ConfigFileName) |
|
| 387 |
- js := `{
|
|
| 388 |
- "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
|
|
| 389 |
- "credsStore": "crazy-secure-storage" |
|
| 390 |
-}` |
|
| 391 |
- if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
|
|
| 392 |
- t.Fatal(err) |
|
| 393 |
- } |
|
| 394 |
- |
|
| 395 |
- config, err := Load(tmpHome) |
|
| 396 |
- if err != nil {
|
|
| 397 |
- t.Fatalf("Failed loading on empty json file: %q", err)
|
|
| 398 |
- } |
|
| 399 |
- |
|
| 400 |
- if config.CredentialsStore != "crazy-secure-storage" {
|
|
| 401 |
- t.Fatalf("Unknown credential store: %s\n", config.CredentialsStore)
|
|
| 402 |
- } |
|
| 403 |
- |
|
| 404 |
- // Now save it and make sure it shows up in new form |
|
| 405 |
- configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) |
|
| 406 |
- if !strings.Contains(configStr, `"credsStore":`) || |
|
| 407 |
- !strings.Contains(configStr, "crazy-secure-storage") {
|
|
| 408 |
- t.Fatalf("Should have save in new form: %s", configStr)
|
|
| 409 |
- } |
|
| 410 |
-} |
|
| 411 |
- |
|
| 412 |
-func TestJSONWithCredentialHelpers(t *testing.T) {
|
|
| 413 |
- tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 414 |
- if err != nil {
|
|
| 415 |
- t.Fatal(err) |
|
| 416 |
- } |
|
| 417 |
- defer os.RemoveAll(tmpHome) |
|
| 418 |
- |
|
| 419 |
- fn := filepath.Join(tmpHome, ConfigFileName) |
|
| 420 |
- js := `{
|
|
| 421 |
- "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
|
|
| 422 |
- "credHelpers": { "images.io": "images-io", "containers.com": "crazy-secure-storage" }
|
|
| 423 |
-}` |
|
| 424 |
- if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
|
|
| 425 |
- t.Fatal(err) |
|
| 426 |
- } |
|
| 427 |
- |
|
| 428 |
- config, err := Load(tmpHome) |
|
| 429 |
- if err != nil {
|
|
| 430 |
- t.Fatalf("Failed loading on empty json file: %q", err)
|
|
| 431 |
- } |
|
| 432 |
- |
|
| 433 |
- if config.CredentialHelpers == nil {
|
|
| 434 |
- t.Fatal("config.CredentialHelpers was nil")
|
|
| 435 |
- } else if config.CredentialHelpers["images.io"] != "images-io" || |
|
| 436 |
- config.CredentialHelpers["containers.com"] != "crazy-secure-storage" {
|
|
| 437 |
- t.Fatalf("Credential helpers not deserialized properly: %v\n", config.CredentialHelpers)
|
|
| 438 |
- } |
|
| 439 |
- |
|
| 440 |
- // Now save it and make sure it shows up in new form |
|
| 441 |
- configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) |
|
| 442 |
- if !strings.Contains(configStr, `"credHelpers":`) || |
|
| 443 |
- !strings.Contains(configStr, "images.io") || |
|
| 444 |
- !strings.Contains(configStr, "images-io") || |
|
| 445 |
- !strings.Contains(configStr, "containers.com") || |
|
| 446 |
- !strings.Contains(configStr, "crazy-secure-storage") {
|
|
| 447 |
- t.Fatalf("Should have save in new form: %s", configStr)
|
|
| 448 |
- } |
|
| 449 |
-} |
|
| 450 |
- |
|
| 451 |
-// Save it and make sure it shows up in new form |
|
| 452 |
-func saveConfigAndValidateNewFormat(t *testing.T, config *configfile.ConfigFile, homeFolder string) string {
|
|
| 453 |
- if err := config.Save(); err != nil {
|
|
| 454 |
- t.Fatalf("Failed to save: %q", err)
|
|
| 455 |
- } |
|
| 456 |
- |
|
| 457 |
- buf, err := ioutil.ReadFile(filepath.Join(homeFolder, ConfigFileName)) |
|
| 458 |
- if err != nil {
|
|
| 459 |
- t.Fatal(err) |
|
| 460 |
- } |
|
| 461 |
- if !strings.Contains(string(buf), `"auths":`) {
|
|
| 462 |
- t.Fatalf("Should have save in new form: %s", string(buf))
|
|
| 463 |
- } |
|
| 464 |
- return string(buf) |
|
| 465 |
-} |
|
| 466 |
- |
|
| 467 |
-func TestConfigDir(t *testing.T) {
|
|
| 468 |
- tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 469 |
- if err != nil {
|
|
| 470 |
- t.Fatal(err) |
|
| 471 |
- } |
|
| 472 |
- defer os.RemoveAll(tmpHome) |
|
| 473 |
- |
|
| 474 |
- if ConfigDir() == tmpHome {
|
|
| 475 |
- t.Fatalf("Expected ConfigDir to be different than %s by default, but was the same", tmpHome)
|
|
| 476 |
- } |
|
| 477 |
- |
|
| 478 |
- // Update configDir |
|
| 479 |
- SetConfigDir(tmpHome) |
|
| 480 |
- |
|
| 481 |
- if ConfigDir() != tmpHome {
|
|
| 482 |
- t.Fatalf("Expected ConfigDir to %s, but was %s", tmpHome, ConfigDir())
|
|
| 483 |
- } |
|
| 484 |
-} |
|
| 485 |
- |
|
| 486 |
-func TestConfigFile(t *testing.T) {
|
|
| 487 |
- configFilename := "configFilename" |
|
| 488 |
- configFile := NewConfigFile(configFilename) |
|
| 489 |
- |
|
| 490 |
- if configFile.Filename != configFilename {
|
|
| 491 |
- t.Fatalf("Expected %s, got %s", configFilename, configFile.Filename)
|
|
| 492 |
- } |
|
| 493 |
-} |
|
| 494 |
- |
|
| 495 |
-func TestJSONReaderNoFile(t *testing.T) {
|
|
| 496 |
- js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } } }`
|
|
| 497 |
- |
|
| 498 |
- config, err := LoadFromReader(strings.NewReader(js)) |
|
| 499 |
- if err != nil {
|
|
| 500 |
- t.Fatalf("Failed loading on empty json file: %q", err)
|
|
| 501 |
- } |
|
| 502 |
- |
|
| 503 |
- ac := config.AuthConfigs["https://index.docker.io/v1/"] |
|
| 504 |
- if ac.Username != "joejoe" || ac.Password != "hello" {
|
|
| 505 |
- t.Fatalf("Missing data from parsing:\n%q", config)
|
|
| 506 |
- } |
|
| 507 |
- |
|
| 508 |
-} |
|
| 509 |
- |
|
| 510 |
-func TestOldJSONReaderNoFile(t *testing.T) {
|
|
| 511 |
- js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
|
|
| 512 |
- |
|
| 513 |
- config, err := LegacyLoadFromReader(strings.NewReader(js)) |
|
| 514 |
- if err != nil {
|
|
| 515 |
- t.Fatalf("Failed loading on empty json file: %q", err)
|
|
| 516 |
- } |
|
| 517 |
- |
|
| 518 |
- ac := config.AuthConfigs["https://index.docker.io/v1/"] |
|
| 519 |
- if ac.Username != "joejoe" || ac.Password != "hello" {
|
|
| 520 |
- t.Fatalf("Missing data from parsing:\n%q", config)
|
|
| 521 |
- } |
|
| 522 |
-} |
|
| 523 |
- |
|
| 524 |
-func TestJSONWithPsFormatNoFile(t *testing.T) {
|
|
| 525 |
- js := `{
|
|
| 526 |
- "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
|
|
| 527 |
- "psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
|
|
| 528 |
-}` |
|
| 529 |
- config, err := LoadFromReader(strings.NewReader(js)) |
|
| 530 |
- if err != nil {
|
|
| 531 |
- t.Fatalf("Failed loading on empty json file: %q", err)
|
|
| 532 |
- } |
|
| 533 |
- |
|
| 534 |
- if config.PsFormat != `table {{.ID}}\t{{.Label "com.docker.label.cpu"}}` {
|
|
| 535 |
- t.Fatalf("Unknown ps format: %s\n", config.PsFormat)
|
|
| 536 |
- } |
|
| 537 |
- |
|
| 538 |
-} |
|
| 539 |
- |
|
| 540 |
-func TestJSONSaveWithNoFile(t *testing.T) {
|
|
| 541 |
- js := `{
|
|
| 542 |
- "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } },
|
|
| 543 |
- "psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
|
|
| 544 |
-}` |
|
| 545 |
- config, err := LoadFromReader(strings.NewReader(js)) |
|
| 546 |
- err = config.Save() |
|
| 547 |
- if err == nil {
|
|
| 548 |
- t.Fatalf("Expected error. File should not have been able to save with no file name.")
|
|
| 549 |
- } |
|
| 550 |
- |
|
| 551 |
- tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 552 |
- if err != nil {
|
|
| 553 |
- t.Fatalf("Failed to create a temp dir: %q", err)
|
|
| 554 |
- } |
|
| 555 |
- defer os.RemoveAll(tmpHome) |
|
| 556 |
- |
|
| 557 |
- fn := filepath.Join(tmpHome, ConfigFileName) |
|
| 558 |
- f, _ := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) |
|
| 559 |
- defer f.Close() |
|
| 560 |
- |
|
| 561 |
- err = config.SaveToWriter(f) |
|
| 562 |
- if err != nil {
|
|
| 563 |
- t.Fatalf("Failed saving to file: %q", err)
|
|
| 564 |
- } |
|
| 565 |
- buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName)) |
|
| 566 |
- if err != nil {
|
|
| 567 |
- t.Fatal(err) |
|
| 568 |
- } |
|
| 569 |
- expConfStr := `{
|
|
| 570 |
- "auths": {
|
|
| 571 |
- "https://index.docker.io/v1/": {
|
|
| 572 |
- "auth": "am9lam9lOmhlbGxv" |
|
| 573 |
- } |
|
| 574 |
- }, |
|
| 575 |
- "psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
|
|
| 576 |
-}` |
|
| 577 |
- if string(buf) != expConfStr {
|
|
| 578 |
- t.Fatalf("Should have save in new form: \n%s\nnot \n%s", string(buf), expConfStr)
|
|
| 579 |
- } |
|
| 580 |
-} |
|
| 581 |
- |
|
| 582 |
-func TestLegacyJSONSaveWithNoFile(t *testing.T) {
|
|
| 583 |
- |
|
| 584 |
- js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
|
|
| 585 |
- config, err := LegacyLoadFromReader(strings.NewReader(js)) |
|
| 586 |
- err = config.Save() |
|
| 587 |
- if err == nil {
|
|
| 588 |
- t.Fatalf("Expected error. File should not have been able to save with no file name.")
|
|
| 589 |
- } |
|
| 590 |
- |
|
| 591 |
- tmpHome, err := ioutil.TempDir("", "config-test")
|
|
| 592 |
- if err != nil {
|
|
| 593 |
- t.Fatalf("Failed to create a temp dir: %q", err)
|
|
| 594 |
- } |
|
| 595 |
- defer os.RemoveAll(tmpHome) |
|
| 596 |
- |
|
| 597 |
- fn := filepath.Join(tmpHome, ConfigFileName) |
|
| 598 |
- f, _ := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) |
|
| 599 |
- defer f.Close() |
|
| 600 |
- |
|
| 601 |
- if err = config.SaveToWriter(f); err != nil {
|
|
| 602 |
- t.Fatalf("Failed saving to file: %q", err)
|
|
| 603 |
- } |
|
| 604 |
- buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName)) |
|
| 605 |
- if err != nil {
|
|
| 606 |
- t.Fatal(err) |
|
| 607 |
- } |
|
| 608 |
- |
|
| 609 |
- expConfStr := `{
|
|
| 610 |
- "auths": {
|
|
| 611 |
- "https://index.docker.io/v1/": {
|
|
| 612 |
- "auth": "am9lam9lOmhlbGxv", |
|
| 613 |
- "email": "user@example.com" |
|
| 614 |
- } |
|
| 615 |
- } |
|
| 616 |
-}` |
|
| 617 |
- |
|
| 618 |
- if string(buf) != expConfStr {
|
|
| 619 |
- t.Fatalf("Should have save in new form: \n%s\n not \n%s", string(buf), expConfStr)
|
|
| 620 |
- } |
|
| 621 |
-} |
| 622 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,183 +0,0 @@ |
| 1 |
-package configfile |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "encoding/base64" |
|
| 5 |
- "encoding/json" |
|
| 6 |
- "fmt" |
|
| 7 |
- "io" |
|
| 8 |
- "io/ioutil" |
|
| 9 |
- "os" |
|
| 10 |
- "path/filepath" |
|
| 11 |
- "strings" |
|
| 12 |
- |
|
| 13 |
- "github.com/docker/docker/api/types" |
|
| 14 |
-) |
|
| 15 |
- |
|
| 16 |
-const ( |
|
| 17 |
- // This constant is only used for really old config files when the |
|
| 18 |
- // URL wasn't saved as part of the config file and it was just |
|
| 19 |
- // assumed to be this value. |
|
| 20 |
- defaultIndexserver = "https://index.docker.io/v1/" |
|
| 21 |
-) |
|
| 22 |
- |
|
| 23 |
-// ConfigFile ~/.docker/config.json file info |
|
| 24 |
-type ConfigFile struct {
|
|
| 25 |
- AuthConfigs map[string]types.AuthConfig `json:"auths"` |
|
| 26 |
- HTTPHeaders map[string]string `json:"HttpHeaders,omitempty"` |
|
| 27 |
- PsFormat string `json:"psFormat,omitempty"` |
|
| 28 |
- ImagesFormat string `json:"imagesFormat,omitempty"` |
|
| 29 |
- NetworksFormat string `json:"networksFormat,omitempty"` |
|
| 30 |
- VolumesFormat string `json:"volumesFormat,omitempty"` |
|
| 31 |
- StatsFormat string `json:"statsFormat,omitempty"` |
|
| 32 |
- DetachKeys string `json:"detachKeys,omitempty"` |
|
| 33 |
- CredentialsStore string `json:"credsStore,omitempty"` |
|
| 34 |
- CredentialHelpers map[string]string `json:"credHelpers,omitempty"` |
|
| 35 |
- Filename string `json:"-"` // Note: for internal use only |
|
| 36 |
- ServiceInspectFormat string `json:"serviceInspectFormat,omitempty"` |
|
| 37 |
-} |
|
| 38 |
- |
|
| 39 |
-// LegacyLoadFromReader reads the non-nested configuration data given and sets up the |
|
| 40 |
-// auth config information with given directory and populates the receiver object |
|
| 41 |
-func (configFile *ConfigFile) LegacyLoadFromReader(configData io.Reader) error {
|
|
| 42 |
- b, err := ioutil.ReadAll(configData) |
|
| 43 |
- if err != nil {
|
|
| 44 |
- return err |
|
| 45 |
- } |
|
| 46 |
- |
|
| 47 |
- if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil {
|
|
| 48 |
- arr := strings.Split(string(b), "\n") |
|
| 49 |
- if len(arr) < 2 {
|
|
| 50 |
- return fmt.Errorf("The Auth config file is empty")
|
|
| 51 |
- } |
|
| 52 |
- authConfig := types.AuthConfig{}
|
|
| 53 |
- origAuth := strings.Split(arr[0], " = ") |
|
| 54 |
- if len(origAuth) != 2 {
|
|
| 55 |
- return fmt.Errorf("Invalid Auth config file")
|
|
| 56 |
- } |
|
| 57 |
- authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1]) |
|
| 58 |
- if err != nil {
|
|
| 59 |
- return err |
|
| 60 |
- } |
|
| 61 |
- authConfig.ServerAddress = defaultIndexserver |
|
| 62 |
- configFile.AuthConfigs[defaultIndexserver] = authConfig |
|
| 63 |
- } else {
|
|
| 64 |
- for k, authConfig := range configFile.AuthConfigs {
|
|
| 65 |
- authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth) |
|
| 66 |
- if err != nil {
|
|
| 67 |
- return err |
|
| 68 |
- } |
|
| 69 |
- authConfig.Auth = "" |
|
| 70 |
- authConfig.ServerAddress = k |
|
| 71 |
- configFile.AuthConfigs[k] = authConfig |
|
| 72 |
- } |
|
| 73 |
- } |
|
| 74 |
- return nil |
|
| 75 |
-} |
|
| 76 |
- |
|
| 77 |
-// LoadFromReader reads the configuration data given and sets up the auth config |
|
| 78 |
-// information with given directory and populates the receiver object |
|
| 79 |
-func (configFile *ConfigFile) LoadFromReader(configData io.Reader) error {
|
|
| 80 |
- if err := json.NewDecoder(configData).Decode(&configFile); err != nil {
|
|
| 81 |
- return err |
|
| 82 |
- } |
|
| 83 |
- var err error |
|
| 84 |
- for addr, ac := range configFile.AuthConfigs {
|
|
| 85 |
- ac.Username, ac.Password, err = decodeAuth(ac.Auth) |
|
| 86 |
- if err != nil {
|
|
| 87 |
- return err |
|
| 88 |
- } |
|
| 89 |
- ac.Auth = "" |
|
| 90 |
- ac.ServerAddress = addr |
|
| 91 |
- configFile.AuthConfigs[addr] = ac |
|
| 92 |
- } |
|
| 93 |
- return nil |
|
| 94 |
-} |
|
| 95 |
- |
|
| 96 |
-// ContainsAuth returns whether there is authentication configured |
|
| 97 |
-// in this file or not. |
|
| 98 |
-func (configFile *ConfigFile) ContainsAuth() bool {
|
|
| 99 |
- return configFile.CredentialsStore != "" || |
|
| 100 |
- len(configFile.CredentialHelpers) > 0 || |
|
| 101 |
- len(configFile.AuthConfigs) > 0 |
|
| 102 |
-} |
|
| 103 |
- |
|
| 104 |
-// SaveToWriter encodes and writes out all the authorization information to |
|
| 105 |
-// the given writer |
|
| 106 |
-func (configFile *ConfigFile) SaveToWriter(writer io.Writer) error {
|
|
| 107 |
- // Encode sensitive data into a new/temp struct |
|
| 108 |
- tmpAuthConfigs := make(map[string]types.AuthConfig, len(configFile.AuthConfigs)) |
|
| 109 |
- for k, authConfig := range configFile.AuthConfigs {
|
|
| 110 |
- authCopy := authConfig |
|
| 111 |
- // encode and save the authstring, while blanking out the original fields |
|
| 112 |
- authCopy.Auth = encodeAuth(&authCopy) |
|
| 113 |
- authCopy.Username = "" |
|
| 114 |
- authCopy.Password = "" |
|
| 115 |
- authCopy.ServerAddress = "" |
|
| 116 |
- tmpAuthConfigs[k] = authCopy |
|
| 117 |
- } |
|
| 118 |
- |
|
| 119 |
- saveAuthConfigs := configFile.AuthConfigs |
|
| 120 |
- configFile.AuthConfigs = tmpAuthConfigs |
|
| 121 |
- defer func() { configFile.AuthConfigs = saveAuthConfigs }()
|
|
| 122 |
- |
|
| 123 |
- data, err := json.MarshalIndent(configFile, "", "\t") |
|
| 124 |
- if err != nil {
|
|
| 125 |
- return err |
|
| 126 |
- } |
|
| 127 |
- _, err = writer.Write(data) |
|
| 128 |
- return err |
|
| 129 |
-} |
|
| 130 |
- |
|
| 131 |
-// Save encodes and writes out all the authorization information |
|
| 132 |
-func (configFile *ConfigFile) Save() error {
|
|
| 133 |
- if configFile.Filename == "" {
|
|
| 134 |
- return fmt.Errorf("Can't save config with empty filename")
|
|
| 135 |
- } |
|
| 136 |
- |
|
| 137 |
- if err := os.MkdirAll(filepath.Dir(configFile.Filename), 0700); err != nil {
|
|
| 138 |
- return err |
|
| 139 |
- } |
|
| 140 |
- f, err := os.OpenFile(configFile.Filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) |
|
| 141 |
- if err != nil {
|
|
| 142 |
- return err |
|
| 143 |
- } |
|
| 144 |
- defer f.Close() |
|
| 145 |
- return configFile.SaveToWriter(f) |
|
| 146 |
-} |
|
| 147 |
- |
|
| 148 |
-// encodeAuth creates a base64 encoded string to containing authorization information |
|
| 149 |
-func encodeAuth(authConfig *types.AuthConfig) string {
|
|
| 150 |
- if authConfig.Username == "" && authConfig.Password == "" {
|
|
| 151 |
- return "" |
|
| 152 |
- } |
|
| 153 |
- |
|
| 154 |
- authStr := authConfig.Username + ":" + authConfig.Password |
|
| 155 |
- msg := []byte(authStr) |
|
| 156 |
- encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg))) |
|
| 157 |
- base64.StdEncoding.Encode(encoded, msg) |
|
| 158 |
- return string(encoded) |
|
| 159 |
-} |
|
| 160 |
- |
|
| 161 |
-// decodeAuth decodes a base64 encoded string and returns username and password |
|
| 162 |
-func decodeAuth(authStr string) (string, string, error) {
|
|
| 163 |
- if authStr == "" {
|
|
| 164 |
- return "", "", nil |
|
| 165 |
- } |
|
| 166 |
- |
|
| 167 |
- decLen := base64.StdEncoding.DecodedLen(len(authStr)) |
|
| 168 |
- decoded := make([]byte, decLen) |
|
| 169 |
- authByte := []byte(authStr) |
|
| 170 |
- n, err := base64.StdEncoding.Decode(decoded, authByte) |
|
| 171 |
- if err != nil {
|
|
| 172 |
- return "", "", err |
|
| 173 |
- } |
|
| 174 |
- if n > decLen {
|
|
| 175 |
- return "", "", fmt.Errorf("Something went wrong decoding auth config")
|
|
| 176 |
- } |
|
| 177 |
- arr := strings.SplitN(string(decoded), ":", 2) |
|
| 178 |
- if len(arr) != 2 {
|
|
| 179 |
- return "", "", fmt.Errorf("Invalid auth configuration file")
|
|
| 180 |
- } |
|
| 181 |
- password := strings.Trim(arr[1], "\x00") |
|
| 182 |
- return arr[0], password, nil |
|
| 183 |
-} |
| 184 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,27 +0,0 @@ |
| 1 |
-package configfile |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "testing" |
|
| 5 |
- |
|
| 6 |
- "github.com/docker/docker/api/types" |
|
| 7 |
-) |
|
| 8 |
- |
|
| 9 |
-func TestEncodeAuth(t *testing.T) {
|
|
| 10 |
- newAuthConfig := &types.AuthConfig{Username: "ken", Password: "test"}
|
|
| 11 |
- authStr := encodeAuth(newAuthConfig) |
|
| 12 |
- decAuthConfig := &types.AuthConfig{}
|
|
| 13 |
- var err error |
|
| 14 |
- decAuthConfig.Username, decAuthConfig.Password, err = decodeAuth(authStr) |
|
| 15 |
- if err != nil {
|
|
| 16 |
- t.Fatal(err) |
|
| 17 |
- } |
|
| 18 |
- if newAuthConfig.Username != decAuthConfig.Username {
|
|
| 19 |
- t.Fatal("Encode Username doesn't match decoded Username")
|
|
| 20 |
- } |
|
| 21 |
- if newAuthConfig.Password != decAuthConfig.Password {
|
|
| 22 |
- t.Fatal("Encode Password doesn't match decoded Password")
|
|
| 23 |
- } |
|
| 24 |
- if authStr != "a2VuOnRlc3Q=" {
|
|
| 25 |
- t.Fatal("AuthString encoding isn't correct.")
|
|
| 26 |
- } |
|
| 27 |
-} |
| 28 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,17 +0,0 @@ |
| 1 |
-package credentials |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "github.com/docker/docker/api/types" |
|
| 5 |
-) |
|
| 6 |
- |
|
| 7 |
-// Store is the interface that any credentials store must implement. |
|
| 8 |
-type Store interface {
|
|
| 9 |
- // Erase removes credentials from the store for a given server. |
|
| 10 |
- Erase(serverAddress string) error |
|
| 11 |
- // Get retrieves credentials from the store for a given server. |
|
| 12 |
- Get(serverAddress string) (types.AuthConfig, error) |
|
| 13 |
- // GetAll retrieves all the credentials from the store. |
|
| 14 |
- GetAll() (map[string]types.AuthConfig, error) |
|
| 15 |
- // Store saves credentials in the store. |
|
| 16 |
- Store(authConfig types.AuthConfig) error |
|
| 17 |
-} |
| 18 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,22 +0,0 @@ |
| 1 |
-package credentials |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "os/exec" |
|
| 5 |
- |
|
| 6 |
- "github.com/docker/docker/cliconfig/configfile" |
|
| 7 |
-) |
|
| 8 |
- |
|
| 9 |
-// DetectDefaultStore sets the default credentials store |
|
| 10 |
-// if the host includes the default store helper program. |
|
| 11 |
-func DetectDefaultStore(c *configfile.ConfigFile) {
|
|
| 12 |
- if c.CredentialsStore != "" {
|
|
| 13 |
- // user defined |
|
| 14 |
- return |
|
| 15 |
- } |
|
| 16 |
- |
|
| 17 |
- if defaultCredentialsStore != "" {
|
|
| 18 |
- if _, err := exec.LookPath(remoteCredentialsPrefix + defaultCredentialsStore); err == nil {
|
|
| 19 |
- c.CredentialsStore = defaultCredentialsStore |
|
| 20 |
- } |
|
| 21 |
- } |
|
| 22 |
-} |
| 4 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,53 +0,0 @@ |
| 1 |
-package credentials |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "github.com/docker/docker/api/types" |
|
| 5 |
- "github.com/docker/docker/cliconfig/configfile" |
|
| 6 |
- "github.com/docker/docker/registry" |
|
| 7 |
-) |
|
| 8 |
- |
|
| 9 |
-// fileStore implements a credentials store using |
|
| 10 |
-// the docker configuration file to keep the credentials in plain text. |
|
| 11 |
-type fileStore struct {
|
|
| 12 |
- file *configfile.ConfigFile |
|
| 13 |
-} |
|
| 14 |
- |
|
| 15 |
-// NewFileStore creates a new file credentials store. |
|
| 16 |
-func NewFileStore(file *configfile.ConfigFile) Store {
|
|
| 17 |
- return &fileStore{
|
|
| 18 |
- file: file, |
|
| 19 |
- } |
|
| 20 |
-} |
|
| 21 |
- |
|
| 22 |
-// Erase removes the given credentials from the file store. |
|
| 23 |
-func (c *fileStore) Erase(serverAddress string) error {
|
|
| 24 |
- delete(c.file.AuthConfigs, serverAddress) |
|
| 25 |
- return c.file.Save() |
|
| 26 |
-} |
|
| 27 |
- |
|
| 28 |
-// Get retrieves credentials for a specific server from the file store. |
|
| 29 |
-func (c *fileStore) Get(serverAddress string) (types.AuthConfig, error) {
|
|
| 30 |
- authConfig, ok := c.file.AuthConfigs[serverAddress] |
|
| 31 |
- if !ok {
|
|
| 32 |
- // Maybe they have a legacy config file, we will iterate the keys converting |
|
| 33 |
- // them to the new format and testing |
|
| 34 |
- for r, ac := range c.file.AuthConfigs {
|
|
| 35 |
- if serverAddress == registry.ConvertToHostname(r) {
|
|
| 36 |
- return ac, nil |
|
| 37 |
- } |
|
| 38 |
- } |
|
| 39 |
- |
|
| 40 |
- authConfig = types.AuthConfig{}
|
|
| 41 |
- } |
|
| 42 |
- return authConfig, nil |
|
| 43 |
-} |
|
| 44 |
- |
|
| 45 |
-func (c *fileStore) GetAll() (map[string]types.AuthConfig, error) {
|
|
| 46 |
- return c.file.AuthConfigs, nil |
|
| 47 |
-} |
|
| 48 |
- |
|
| 49 |
-// Store saves the given credentials in the file store. |
|
| 50 |
-func (c *fileStore) Store(authConfig types.AuthConfig) error {
|
|
| 51 |
- c.file.AuthConfigs[authConfig.ServerAddress] = authConfig |
|
| 52 |
- return c.file.Save() |
|
| 53 |
-} |
| 54 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,139 +0,0 @@ |
| 1 |
-package credentials |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "io/ioutil" |
|
| 5 |
- "testing" |
|
| 6 |
- |
|
| 7 |
- "github.com/docker/docker/api/types" |
|
| 8 |
- "github.com/docker/docker/cliconfig" |
|
| 9 |
- "github.com/docker/docker/cliconfig/configfile" |
|
| 10 |
-) |
|
| 11 |
- |
|
| 12 |
-func newConfigFile(auths map[string]types.AuthConfig) *configfile.ConfigFile {
|
|
| 13 |
- tmp, _ := ioutil.TempFile("", "docker-test")
|
|
| 14 |
- name := tmp.Name() |
|
| 15 |
- tmp.Close() |
|
| 16 |
- |
|
| 17 |
- c := cliconfig.NewConfigFile(name) |
|
| 18 |
- c.AuthConfigs = auths |
|
| 19 |
- return c |
|
| 20 |
-} |
|
| 21 |
- |
|
| 22 |
-func TestFileStoreAddCredentials(t *testing.T) {
|
|
| 23 |
- f := newConfigFile(make(map[string]types.AuthConfig)) |
|
| 24 |
- |
|
| 25 |
- s := NewFileStore(f) |
|
| 26 |
- err := s.Store(types.AuthConfig{
|
|
| 27 |
- Auth: "super_secret_token", |
|
| 28 |
- Email: "foo@example.com", |
|
| 29 |
- ServerAddress: "https://example.com", |
|
| 30 |
- }) |
|
| 31 |
- |
|
| 32 |
- if err != nil {
|
|
| 33 |
- t.Fatal(err) |
|
| 34 |
- } |
|
| 35 |
- |
|
| 36 |
- if len(f.AuthConfigs) != 1 {
|
|
| 37 |
- t.Fatalf("expected 1 auth config, got %d", len(f.AuthConfigs))
|
|
| 38 |
- } |
|
| 39 |
- |
|
| 40 |
- a, ok := f.AuthConfigs["https://example.com"] |
|
| 41 |
- if !ok {
|
|
| 42 |
- t.Fatalf("expected auth for https://example.com, got %v", f.AuthConfigs)
|
|
| 43 |
- } |
|
| 44 |
- if a.Auth != "super_secret_token" {
|
|
| 45 |
- t.Fatalf("expected auth `super_secret_token`, got %s", a.Auth)
|
|
| 46 |
- } |
|
| 47 |
- if a.Email != "foo@example.com" {
|
|
| 48 |
- t.Fatalf("expected email `foo@example.com`, got %s", a.Email)
|
|
| 49 |
- } |
|
| 50 |
-} |
|
| 51 |
- |
|
| 52 |
-func TestFileStoreGet(t *testing.T) {
|
|
| 53 |
- f := newConfigFile(map[string]types.AuthConfig{
|
|
| 54 |
- "https://example.com": {
|
|
| 55 |
- Auth: "super_secret_token", |
|
| 56 |
- Email: "foo@example.com", |
|
| 57 |
- ServerAddress: "https://example.com", |
|
| 58 |
- }, |
|
| 59 |
- }) |
|
| 60 |
- |
|
| 61 |
- s := NewFileStore(f) |
|
| 62 |
- a, err := s.Get("https://example.com")
|
|
| 63 |
- if err != nil {
|
|
| 64 |
- t.Fatal(err) |
|
| 65 |
- } |
|
| 66 |
- if a.Auth != "super_secret_token" {
|
|
| 67 |
- t.Fatalf("expected auth `super_secret_token`, got %s", a.Auth)
|
|
| 68 |
- } |
|
| 69 |
- if a.Email != "foo@example.com" {
|
|
| 70 |
- t.Fatalf("expected email `foo@example.com`, got %s", a.Email)
|
|
| 71 |
- } |
|
| 72 |
-} |
|
| 73 |
- |
|
| 74 |
-func TestFileStoreGetAll(t *testing.T) {
|
|
| 75 |
- s1 := "https://example.com" |
|
| 76 |
- s2 := "https://example2.com" |
|
| 77 |
- f := newConfigFile(map[string]types.AuthConfig{
|
|
| 78 |
- s1: {
|
|
| 79 |
- Auth: "super_secret_token", |
|
| 80 |
- Email: "foo@example.com", |
|
| 81 |
- ServerAddress: "https://example.com", |
|
| 82 |
- }, |
|
| 83 |
- s2: {
|
|
| 84 |
- Auth: "super_secret_token2", |
|
| 85 |
- Email: "foo@example2.com", |
|
| 86 |
- ServerAddress: "https://example2.com", |
|
| 87 |
- }, |
|
| 88 |
- }) |
|
| 89 |
- |
|
| 90 |
- s := NewFileStore(f) |
|
| 91 |
- as, err := s.GetAll() |
|
| 92 |
- if err != nil {
|
|
| 93 |
- t.Fatal(err) |
|
| 94 |
- } |
|
| 95 |
- if len(as) != 2 {
|
|
| 96 |
- t.Fatalf("wanted 2, got %d", len(as))
|
|
| 97 |
- } |
|
| 98 |
- if as[s1].Auth != "super_secret_token" {
|
|
| 99 |
- t.Fatalf("expected auth `super_secret_token`, got %s", as[s1].Auth)
|
|
| 100 |
- } |
|
| 101 |
- if as[s1].Email != "foo@example.com" {
|
|
| 102 |
- t.Fatalf("expected email `foo@example.com`, got %s", as[s1].Email)
|
|
| 103 |
- } |
|
| 104 |
- if as[s2].Auth != "super_secret_token2" {
|
|
| 105 |
- t.Fatalf("expected auth `super_secret_token2`, got %s", as[s2].Auth)
|
|
| 106 |
- } |
|
| 107 |
- if as[s2].Email != "foo@example2.com" {
|
|
| 108 |
- t.Fatalf("expected email `foo@example2.com`, got %s", as[s2].Email)
|
|
| 109 |
- } |
|
| 110 |
-} |
|
| 111 |
- |
|
| 112 |
-func TestFileStoreErase(t *testing.T) {
|
|
| 113 |
- f := newConfigFile(map[string]types.AuthConfig{
|
|
| 114 |
- "https://example.com": {
|
|
| 115 |
- Auth: "super_secret_token", |
|
| 116 |
- Email: "foo@example.com", |
|
| 117 |
- ServerAddress: "https://example.com", |
|
| 118 |
- }, |
|
| 119 |
- }) |
|
| 120 |
- |
|
| 121 |
- s := NewFileStore(f) |
|
| 122 |
- err := s.Erase("https://example.com")
|
|
| 123 |
- if err != nil {
|
|
| 124 |
- t.Fatal(err) |
|
| 125 |
- } |
|
| 126 |
- |
|
| 127 |
- // file store never returns errors, check that the auth config is empty |
|
| 128 |
- a, err := s.Get("https://example.com")
|
|
| 129 |
- if err != nil {
|
|
| 130 |
- t.Fatal(err) |
|
| 131 |
- } |
|
| 132 |
- |
|
| 133 |
- if a.Auth != "" {
|
|
| 134 |
- t.Fatalf("expected empty auth token, got %s", a.Auth)
|
|
| 135 |
- } |
|
| 136 |
- if a.Email != "" {
|
|
| 137 |
- t.Fatalf("expected empty email, got %s", a.Email)
|
|
| 138 |
- } |
|
| 139 |
-} |
| 140 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,144 +0,0 @@ |
| 1 |
-package credentials |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "github.com/docker/docker-credential-helpers/client" |
|
| 5 |
- "github.com/docker/docker-credential-helpers/credentials" |
|
| 6 |
- "github.com/docker/docker/api/types" |
|
| 7 |
- "github.com/docker/docker/cliconfig/configfile" |
|
| 8 |
-) |
|
| 9 |
- |
|
| 10 |
-const ( |
|
| 11 |
- remoteCredentialsPrefix = "docker-credential-" |
|
| 12 |
- tokenUsername = "<token>" |
|
| 13 |
-) |
|
| 14 |
- |
|
| 15 |
-// nativeStore implements a credentials store |
|
| 16 |
-// using native keychain to keep credentials secure. |
|
| 17 |
-// It piggybacks into a file store to keep users' emails. |
|
| 18 |
-type nativeStore struct {
|
|
| 19 |
- programFunc client.ProgramFunc |
|
| 20 |
- fileStore Store |
|
| 21 |
-} |
|
| 22 |
- |
|
| 23 |
-// NewNativeStore creates a new native store that |
|
| 24 |
-// uses a remote helper program to manage credentials. |
|
| 25 |
-func NewNativeStore(file *configfile.ConfigFile, helperSuffix string) Store {
|
|
| 26 |
- name := remoteCredentialsPrefix + helperSuffix |
|
| 27 |
- return &nativeStore{
|
|
| 28 |
- programFunc: client.NewShellProgramFunc(name), |
|
| 29 |
- fileStore: NewFileStore(file), |
|
| 30 |
- } |
|
| 31 |
-} |
|
| 32 |
- |
|
| 33 |
-// Erase removes the given credentials from the native store. |
|
| 34 |
-func (c *nativeStore) Erase(serverAddress string) error {
|
|
| 35 |
- if err := client.Erase(c.programFunc, serverAddress); err != nil {
|
|
| 36 |
- return err |
|
| 37 |
- } |
|
| 38 |
- |
|
| 39 |
- // Fallback to plain text store to remove email |
|
| 40 |
- return c.fileStore.Erase(serverAddress) |
|
| 41 |
-} |
|
| 42 |
- |
|
| 43 |
-// Get retrieves credentials for a specific server from the native store. |
|
| 44 |
-func (c *nativeStore) Get(serverAddress string) (types.AuthConfig, error) {
|
|
| 45 |
- // load user email if it exist or an empty auth config. |
|
| 46 |
- auth, _ := c.fileStore.Get(serverAddress) |
|
| 47 |
- |
|
| 48 |
- creds, err := c.getCredentialsFromStore(serverAddress) |
|
| 49 |
- if err != nil {
|
|
| 50 |
- return auth, err |
|
| 51 |
- } |
|
| 52 |
- auth.Username = creds.Username |
|
| 53 |
- auth.IdentityToken = creds.IdentityToken |
|
| 54 |
- auth.Password = creds.Password |
|
| 55 |
- |
|
| 56 |
- return auth, nil |
|
| 57 |
-} |
|
| 58 |
- |
|
| 59 |
-// GetAll retrieves all the credentials from the native store. |
|
| 60 |
-func (c *nativeStore) GetAll() (map[string]types.AuthConfig, error) {
|
|
| 61 |
- auths, err := c.listCredentialsInStore() |
|
| 62 |
- if err != nil {
|
|
| 63 |
- return nil, err |
|
| 64 |
- } |
|
| 65 |
- |
|
| 66 |
- // Emails are only stored in the file store. |
|
| 67 |
- // This call can be safely eliminated when emails are removed. |
|
| 68 |
- fileConfigs, _ := c.fileStore.GetAll() |
|
| 69 |
- |
|
| 70 |
- authConfigs := make(map[string]types.AuthConfig) |
|
| 71 |
- for registry := range auths {
|
|
| 72 |
- creds, err := c.getCredentialsFromStore(registry) |
|
| 73 |
- if err != nil {
|
|
| 74 |
- return nil, err |
|
| 75 |
- } |
|
| 76 |
- ac, _ := fileConfigs[registry] // might contain Email |
|
| 77 |
- ac.Username = creds.Username |
|
| 78 |
- ac.Password = creds.Password |
|
| 79 |
- ac.IdentityToken = creds.IdentityToken |
|
| 80 |
- authConfigs[registry] = ac |
|
| 81 |
- } |
|
| 82 |
- |
|
| 83 |
- return authConfigs, nil |
|
| 84 |
-} |
|
| 85 |
- |
|
| 86 |
-// Store saves the given credentials in the file store. |
|
| 87 |
-func (c *nativeStore) Store(authConfig types.AuthConfig) error {
|
|
| 88 |
- if err := c.storeCredentialsInStore(authConfig); err != nil {
|
|
| 89 |
- return err |
|
| 90 |
- } |
|
| 91 |
- authConfig.Username = "" |
|
| 92 |
- authConfig.Password = "" |
|
| 93 |
- authConfig.IdentityToken = "" |
|
| 94 |
- |
|
| 95 |
- // Fallback to old credential in plain text to save only the email |
|
| 96 |
- return c.fileStore.Store(authConfig) |
|
| 97 |
-} |
|
| 98 |
- |
|
| 99 |
-// storeCredentialsInStore executes the command to store the credentials in the native store. |
|
| 100 |
-func (c *nativeStore) storeCredentialsInStore(config types.AuthConfig) error {
|
|
| 101 |
- creds := &credentials.Credentials{
|
|
| 102 |
- ServerURL: config.ServerAddress, |
|
| 103 |
- Username: config.Username, |
|
| 104 |
- Secret: config.Password, |
|
| 105 |
- } |
|
| 106 |
- |
|
| 107 |
- if config.IdentityToken != "" {
|
|
| 108 |
- creds.Username = tokenUsername |
|
| 109 |
- creds.Secret = config.IdentityToken |
|
| 110 |
- } |
|
| 111 |
- |
|
| 112 |
- return client.Store(c.programFunc, creds) |
|
| 113 |
-} |
|
| 114 |
- |
|
| 115 |
-// getCredentialsFromStore executes the command to get the credentials from the native store. |
|
| 116 |
-func (c *nativeStore) getCredentialsFromStore(serverAddress string) (types.AuthConfig, error) {
|
|
| 117 |
- var ret types.AuthConfig |
|
| 118 |
- |
|
| 119 |
- creds, err := client.Get(c.programFunc, serverAddress) |
|
| 120 |
- if err != nil {
|
|
| 121 |
- if credentials.IsErrCredentialsNotFound(err) {
|
|
| 122 |
- // do not return an error if the credentials are not |
|
| 123 |
- // in the keyckain. Let docker ask for new credentials. |
|
| 124 |
- return ret, nil |
|
| 125 |
- } |
|
| 126 |
- return ret, err |
|
| 127 |
- } |
|
| 128 |
- |
|
| 129 |
- if creds.Username == tokenUsername {
|
|
| 130 |
- ret.IdentityToken = creds.Secret |
|
| 131 |
- } else {
|
|
| 132 |
- ret.Password = creds.Secret |
|
| 133 |
- ret.Username = creds.Username |
|
| 134 |
- } |
|
| 135 |
- |
|
| 136 |
- ret.ServerAddress = serverAddress |
|
| 137 |
- return ret, nil |
|
| 138 |
-} |
|
| 139 |
- |
|
| 140 |
-// listCredentialsInStore returns a listing of stored credentials as a map of |
|
| 141 |
-// URL -> username. |
|
| 142 |
-func (c *nativeStore) listCredentialsInStore() (map[string]string, error) {
|
|
| 143 |
- return client.List(c.programFunc) |
|
| 144 |
-} |
| 145 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,355 +0,0 @@ |
| 1 |
-package credentials |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "encoding/json" |
|
| 5 |
- "fmt" |
|
| 6 |
- "io" |
|
| 7 |
- "io/ioutil" |
|
| 8 |
- "strings" |
|
| 9 |
- "testing" |
|
| 10 |
- |
|
| 11 |
- "github.com/docker/docker-credential-helpers/client" |
|
| 12 |
- "github.com/docker/docker-credential-helpers/credentials" |
|
| 13 |
- "github.com/docker/docker/api/types" |
|
| 14 |
-) |
|
| 15 |
- |
|
| 16 |
-const ( |
|
| 17 |
- validServerAddress = "https://index.docker.io/v1" |
|
| 18 |
- validServerAddress2 = "https://example.com:5002" |
|
| 19 |
- invalidServerAddress = "https://foobar.example.com" |
|
| 20 |
- missingCredsAddress = "https://missing.docker.io/v1" |
|
| 21 |
-) |
|
| 22 |
- |
|
| 23 |
-var errCommandExited = fmt.Errorf("exited 1")
|
|
| 24 |
- |
|
| 25 |
-// mockCommand simulates interactions between the docker client and a remote |
|
| 26 |
-// credentials helper. |
|
| 27 |
-// Unit tests inject this mocked command into the remote to control execution. |
|
| 28 |
-type mockCommand struct {
|
|
| 29 |
- arg string |
|
| 30 |
- input io.Reader |
|
| 31 |
-} |
|
| 32 |
- |
|
| 33 |
-// Output returns responses from the remote credentials helper. |
|
| 34 |
-// It mocks those responses based in the input in the mock. |
|
| 35 |
-func (m *mockCommand) Output() ([]byte, error) {
|
|
| 36 |
- in, err := ioutil.ReadAll(m.input) |
|
| 37 |
- if err != nil {
|
|
| 38 |
- return nil, err |
|
| 39 |
- } |
|
| 40 |
- inS := string(in) |
|
| 41 |
- |
|
| 42 |
- switch m.arg {
|
|
| 43 |
- case "erase": |
|
| 44 |
- switch inS {
|
|
| 45 |
- case validServerAddress: |
|
| 46 |
- return nil, nil |
|
| 47 |
- default: |
|
| 48 |
- return []byte("program failed"), errCommandExited
|
|
| 49 |
- } |
|
| 50 |
- case "get": |
|
| 51 |
- switch inS {
|
|
| 52 |
- case validServerAddress: |
|
| 53 |
- return []byte(`{"Username": "foo", "Secret": "bar"}`), nil
|
|
| 54 |
- case validServerAddress2: |
|
| 55 |
- return []byte(`{"Username": "<token>", "Secret": "abcd1234"}`), nil
|
|
| 56 |
- case missingCredsAddress: |
|
| 57 |
- return []byte(credentials.NewErrCredentialsNotFound().Error()), errCommandExited |
|
| 58 |
- case invalidServerAddress: |
|
| 59 |
- return []byte("program failed"), errCommandExited
|
|
| 60 |
- } |
|
| 61 |
- case "store": |
|
| 62 |
- var c credentials.Credentials |
|
| 63 |
- err := json.NewDecoder(strings.NewReader(inS)).Decode(&c) |
|
| 64 |
- if err != nil {
|
|
| 65 |
- return []byte("program failed"), errCommandExited
|
|
| 66 |
- } |
|
| 67 |
- switch c.ServerURL {
|
|
| 68 |
- case validServerAddress: |
|
| 69 |
- return nil, nil |
|
| 70 |
- default: |
|
| 71 |
- return []byte("program failed"), errCommandExited
|
|
| 72 |
- } |
|
| 73 |
- case "list": |
|
| 74 |
- return []byte(fmt.Sprintf(`{"%s": "%s", "%s": "%s"}`, validServerAddress, "foo", validServerAddress2, "<token>")), nil
|
|
| 75 |
- } |
|
| 76 |
- |
|
| 77 |
- return []byte(fmt.Sprintf("unknown argument %q with %q", m.arg, inS)), errCommandExited
|
|
| 78 |
-} |
|
| 79 |
- |
|
| 80 |
-// Input sets the input to send to a remote credentials helper. |
|
| 81 |
-func (m *mockCommand) Input(in io.Reader) {
|
|
| 82 |
- m.input = in |
|
| 83 |
-} |
|
| 84 |
- |
|
| 85 |
-func mockCommandFn(args ...string) client.Program {
|
|
| 86 |
- return &mockCommand{
|
|
| 87 |
- arg: args[0], |
|
| 88 |
- } |
|
| 89 |
-} |
|
| 90 |
- |
|
| 91 |
-func TestNativeStoreAddCredentials(t *testing.T) {
|
|
| 92 |
- f := newConfigFile(make(map[string]types.AuthConfig)) |
|
| 93 |
- f.CredentialsStore = "mock" |
|
| 94 |
- |
|
| 95 |
- s := &nativeStore{
|
|
| 96 |
- programFunc: mockCommandFn, |
|
| 97 |
- fileStore: NewFileStore(f), |
|
| 98 |
- } |
|
| 99 |
- err := s.Store(types.AuthConfig{
|
|
| 100 |
- Username: "foo", |
|
| 101 |
- Password: "bar", |
|
| 102 |
- Email: "foo@example.com", |
|
| 103 |
- ServerAddress: validServerAddress, |
|
| 104 |
- }) |
|
| 105 |
- |
|
| 106 |
- if err != nil {
|
|
| 107 |
- t.Fatal(err) |
|
| 108 |
- } |
|
| 109 |
- |
|
| 110 |
- if len(f.AuthConfigs) != 1 {
|
|
| 111 |
- t.Fatalf("expected 1 auth config, got %d", len(f.AuthConfigs))
|
|
| 112 |
- } |
|
| 113 |
- |
|
| 114 |
- a, ok := f.AuthConfigs[validServerAddress] |
|
| 115 |
- if !ok {
|
|
| 116 |
- t.Fatalf("expected auth for %s, got %v", validServerAddress, f.AuthConfigs)
|
|
| 117 |
- } |
|
| 118 |
- if a.Auth != "" {
|
|
| 119 |
- t.Fatalf("expected auth to be empty, got %s", a.Auth)
|
|
| 120 |
- } |
|
| 121 |
- if a.Username != "" {
|
|
| 122 |
- t.Fatalf("expected username to be empty, got %s", a.Username)
|
|
| 123 |
- } |
|
| 124 |
- if a.Password != "" {
|
|
| 125 |
- t.Fatalf("expected password to be empty, got %s", a.Password)
|
|
| 126 |
- } |
|
| 127 |
- if a.IdentityToken != "" {
|
|
| 128 |
- t.Fatalf("expected identity token to be empty, got %s", a.IdentityToken)
|
|
| 129 |
- } |
|
| 130 |
- if a.Email != "foo@example.com" {
|
|
| 131 |
- t.Fatalf("expected email `foo@example.com`, got %s", a.Email)
|
|
| 132 |
- } |
|
| 133 |
-} |
|
| 134 |
- |
|
| 135 |
-func TestNativeStoreAddInvalidCredentials(t *testing.T) {
|
|
| 136 |
- f := newConfigFile(make(map[string]types.AuthConfig)) |
|
| 137 |
- f.CredentialsStore = "mock" |
|
| 138 |
- |
|
| 139 |
- s := &nativeStore{
|
|
| 140 |
- programFunc: mockCommandFn, |
|
| 141 |
- fileStore: NewFileStore(f), |
|
| 142 |
- } |
|
| 143 |
- err := s.Store(types.AuthConfig{
|
|
| 144 |
- Username: "foo", |
|
| 145 |
- Password: "bar", |
|
| 146 |
- Email: "foo@example.com", |
|
| 147 |
- ServerAddress: invalidServerAddress, |
|
| 148 |
- }) |
|
| 149 |
- |
|
| 150 |
- if err == nil {
|
|
| 151 |
- t.Fatal("expected error, got nil")
|
|
| 152 |
- } |
|
| 153 |
- |
|
| 154 |
- if !strings.Contains(err.Error(), "program failed") {
|
|
| 155 |
- t.Fatalf("expected `program failed`, got %v", err)
|
|
| 156 |
- } |
|
| 157 |
- |
|
| 158 |
- if len(f.AuthConfigs) != 0 {
|
|
| 159 |
- t.Fatalf("expected 0 auth config, got %d", len(f.AuthConfigs))
|
|
| 160 |
- } |
|
| 161 |
-} |
|
| 162 |
- |
|
| 163 |
-func TestNativeStoreGet(t *testing.T) {
|
|
| 164 |
- f := newConfigFile(map[string]types.AuthConfig{
|
|
| 165 |
- validServerAddress: {
|
|
| 166 |
- Email: "foo@example.com", |
|
| 167 |
- }, |
|
| 168 |
- }) |
|
| 169 |
- f.CredentialsStore = "mock" |
|
| 170 |
- |
|
| 171 |
- s := &nativeStore{
|
|
| 172 |
- programFunc: mockCommandFn, |
|
| 173 |
- fileStore: NewFileStore(f), |
|
| 174 |
- } |
|
| 175 |
- a, err := s.Get(validServerAddress) |
|
| 176 |
- if err != nil {
|
|
| 177 |
- t.Fatal(err) |
|
| 178 |
- } |
|
| 179 |
- |
|
| 180 |
- if a.Username != "foo" {
|
|
| 181 |
- t.Fatalf("expected username `foo`, got %s", a.Username)
|
|
| 182 |
- } |
|
| 183 |
- if a.Password != "bar" {
|
|
| 184 |
- t.Fatalf("expected password `bar`, got %s", a.Password)
|
|
| 185 |
- } |
|
| 186 |
- if a.IdentityToken != "" {
|
|
| 187 |
- t.Fatalf("expected identity token to be empty, got %s", a.IdentityToken)
|
|
| 188 |
- } |
|
| 189 |
- if a.Email != "foo@example.com" {
|
|
| 190 |
- t.Fatalf("expected email `foo@example.com`, got %s", a.Email)
|
|
| 191 |
- } |
|
| 192 |
-} |
|
| 193 |
- |
|
| 194 |
-func TestNativeStoreGetIdentityToken(t *testing.T) {
|
|
| 195 |
- f := newConfigFile(map[string]types.AuthConfig{
|
|
| 196 |
- validServerAddress2: {
|
|
| 197 |
- Email: "foo@example2.com", |
|
| 198 |
- }, |
|
| 199 |
- }) |
|
| 200 |
- f.CredentialsStore = "mock" |
|
| 201 |
- |
|
| 202 |
- s := &nativeStore{
|
|
| 203 |
- programFunc: mockCommandFn, |
|
| 204 |
- fileStore: NewFileStore(f), |
|
| 205 |
- } |
|
| 206 |
- a, err := s.Get(validServerAddress2) |
|
| 207 |
- if err != nil {
|
|
| 208 |
- t.Fatal(err) |
|
| 209 |
- } |
|
| 210 |
- |
|
| 211 |
- if a.Username != "" {
|
|
| 212 |
- t.Fatalf("expected username to be empty, got %s", a.Username)
|
|
| 213 |
- } |
|
| 214 |
- if a.Password != "" {
|
|
| 215 |
- t.Fatalf("expected password to be empty, got %s", a.Password)
|
|
| 216 |
- } |
|
| 217 |
- if a.IdentityToken != "abcd1234" {
|
|
| 218 |
- t.Fatalf("expected identity token `abcd1234`, got %s", a.IdentityToken)
|
|
| 219 |
- } |
|
| 220 |
- if a.Email != "foo@example2.com" {
|
|
| 221 |
- t.Fatalf("expected email `foo@example2.com`, got %s", a.Email)
|
|
| 222 |
- } |
|
| 223 |
-} |
|
| 224 |
- |
|
| 225 |
-func TestNativeStoreGetAll(t *testing.T) {
|
|
| 226 |
- f := newConfigFile(map[string]types.AuthConfig{
|
|
| 227 |
- validServerAddress: {
|
|
| 228 |
- Email: "foo@example.com", |
|
| 229 |
- }, |
|
| 230 |
- }) |
|
| 231 |
- f.CredentialsStore = "mock" |
|
| 232 |
- |
|
| 233 |
- s := &nativeStore{
|
|
| 234 |
- programFunc: mockCommandFn, |
|
| 235 |
- fileStore: NewFileStore(f), |
|
| 236 |
- } |
|
| 237 |
- as, err := s.GetAll() |
|
| 238 |
- if err != nil {
|
|
| 239 |
- t.Fatal(err) |
|
| 240 |
- } |
|
| 241 |
- |
|
| 242 |
- if len(as) != 2 {
|
|
| 243 |
- t.Fatalf("wanted 2, got %d", len(as))
|
|
| 244 |
- } |
|
| 245 |
- |
|
| 246 |
- if as[validServerAddress].Username != "foo" {
|
|
| 247 |
- t.Fatalf("expected username `foo` for %s, got %s", validServerAddress, as[validServerAddress].Username)
|
|
| 248 |
- } |
|
| 249 |
- if as[validServerAddress].Password != "bar" {
|
|
| 250 |
- t.Fatalf("expected password `bar` for %s, got %s", validServerAddress, as[validServerAddress].Password)
|
|
| 251 |
- } |
|
| 252 |
- if as[validServerAddress].IdentityToken != "" {
|
|
| 253 |
- t.Fatalf("expected identity to be empty for %s, got %s", validServerAddress, as[validServerAddress].IdentityToken)
|
|
| 254 |
- } |
|
| 255 |
- if as[validServerAddress].Email != "foo@example.com" {
|
|
| 256 |
- t.Fatalf("expected email `foo@example.com` for %s, got %s", validServerAddress, as[validServerAddress].Email)
|
|
| 257 |
- } |
|
| 258 |
- if as[validServerAddress2].Username != "" {
|
|
| 259 |
- t.Fatalf("expected username to be empty for %s, got %s", validServerAddress2, as[validServerAddress2].Username)
|
|
| 260 |
- } |
|
| 261 |
- if as[validServerAddress2].Password != "" {
|
|
| 262 |
- t.Fatalf("expected password to be empty for %s, got %s", validServerAddress2, as[validServerAddress2].Password)
|
|
| 263 |
- } |
|
| 264 |
- if as[validServerAddress2].IdentityToken != "abcd1234" {
|
|
| 265 |
- t.Fatalf("expected identity token `abcd1324` for %s, got %s", validServerAddress2, as[validServerAddress2].IdentityToken)
|
|
| 266 |
- } |
|
| 267 |
- if as[validServerAddress2].Email != "" {
|
|
| 268 |
- t.Fatalf("expected no email for %s, got %s", validServerAddress2, as[validServerAddress2].Email)
|
|
| 269 |
- } |
|
| 270 |
-} |
|
| 271 |
- |
|
| 272 |
-func TestNativeStoreGetMissingCredentials(t *testing.T) {
|
|
| 273 |
- f := newConfigFile(map[string]types.AuthConfig{
|
|
| 274 |
- validServerAddress: {
|
|
| 275 |
- Email: "foo@example.com", |
|
| 276 |
- }, |
|
| 277 |
- }) |
|
| 278 |
- f.CredentialsStore = "mock" |
|
| 279 |
- |
|
| 280 |
- s := &nativeStore{
|
|
| 281 |
- programFunc: mockCommandFn, |
|
| 282 |
- fileStore: NewFileStore(f), |
|
| 283 |
- } |
|
| 284 |
- _, err := s.Get(missingCredsAddress) |
|
| 285 |
- if err != nil {
|
|
| 286 |
- // missing credentials do not produce an error |
|
| 287 |
- t.Fatal(err) |
|
| 288 |
- } |
|
| 289 |
-} |
|
| 290 |
- |
|
| 291 |
-func TestNativeStoreGetInvalidAddress(t *testing.T) {
|
|
| 292 |
- f := newConfigFile(map[string]types.AuthConfig{
|
|
| 293 |
- validServerAddress: {
|
|
| 294 |
- Email: "foo@example.com", |
|
| 295 |
- }, |
|
| 296 |
- }) |
|
| 297 |
- f.CredentialsStore = "mock" |
|
| 298 |
- |
|
| 299 |
- s := &nativeStore{
|
|
| 300 |
- programFunc: mockCommandFn, |
|
| 301 |
- fileStore: NewFileStore(f), |
|
| 302 |
- } |
|
| 303 |
- _, err := s.Get(invalidServerAddress) |
|
| 304 |
- if err == nil {
|
|
| 305 |
- t.Fatal("expected error, got nil")
|
|
| 306 |
- } |
|
| 307 |
- |
|
| 308 |
- if !strings.Contains(err.Error(), "program failed") {
|
|
| 309 |
- t.Fatalf("expected `program failed`, got %v", err)
|
|
| 310 |
- } |
|
| 311 |
-} |
|
| 312 |
- |
|
| 313 |
-func TestNativeStoreErase(t *testing.T) {
|
|
| 314 |
- f := newConfigFile(map[string]types.AuthConfig{
|
|
| 315 |
- validServerAddress: {
|
|
| 316 |
- Email: "foo@example.com", |
|
| 317 |
- }, |
|
| 318 |
- }) |
|
| 319 |
- f.CredentialsStore = "mock" |
|
| 320 |
- |
|
| 321 |
- s := &nativeStore{
|
|
| 322 |
- programFunc: mockCommandFn, |
|
| 323 |
- fileStore: NewFileStore(f), |
|
| 324 |
- } |
|
| 325 |
- err := s.Erase(validServerAddress) |
|
| 326 |
- if err != nil {
|
|
| 327 |
- t.Fatal(err) |
|
| 328 |
- } |
|
| 329 |
- |
|
| 330 |
- if len(f.AuthConfigs) != 0 {
|
|
| 331 |
- t.Fatalf("expected 0 auth configs, got %d", len(f.AuthConfigs))
|
|
| 332 |
- } |
|
| 333 |
-} |
|
| 334 |
- |
|
| 335 |
-func TestNativeStoreEraseInvalidAddress(t *testing.T) {
|
|
| 336 |
- f := newConfigFile(map[string]types.AuthConfig{
|
|
| 337 |
- validServerAddress: {
|
|
| 338 |
- Email: "foo@example.com", |
|
| 339 |
- }, |
|
| 340 |
- }) |
|
| 341 |
- f.CredentialsStore = "mock" |
|
| 342 |
- |
|
| 343 |
- s := &nativeStore{
|
|
| 344 |
- programFunc: mockCommandFn, |
|
| 345 |
- fileStore: NewFileStore(f), |
|
| 346 |
- } |
|
| 347 |
- err := s.Erase(invalidServerAddress) |
|
| 348 |
- if err == nil {
|
|
| 349 |
- t.Fatal("expected error, got nil")
|
|
| 350 |
- } |
|
| 351 |
- |
|
| 352 |
- if !strings.Contains(err.Error(), "program failed") {
|
|
| 353 |
- t.Fatalf("expected `program failed`, got %v", err)
|
|
| 354 |
- } |
|
| 355 |
-} |
| ... | ... |
@@ -10,9 +10,9 @@ import ( |
| 10 | 10 |
"github.com/docker/docker/cli" |
| 11 | 11 |
"github.com/docker/docker/cli/command" |
| 12 | 12 |
"github.com/docker/docker/cli/command/commands" |
| 13 |
+ cliconfig "github.com/docker/docker/cli/config" |
|
| 13 | 14 |
"github.com/docker/docker/cli/debug" |
| 14 | 15 |
cliflags "github.com/docker/docker/cli/flags" |
| 15 |
- "github.com/docker/docker/cliconfig" |
|
| 16 | 16 |
"github.com/docker/docker/dockerversion" |
| 17 | 17 |
"github.com/docker/docker/pkg/term" |
| 18 | 18 |
"github.com/spf13/cobra" |
| ... | ... |
@@ -75,7 +75,7 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 75 | 75 |
|
| 76 | 76 |
flags = cmd.Flags() |
| 77 | 77 |
flags.BoolVarP(&opts.Version, "version", "v", false, "Print version information and quit") |
| 78 |
- flags.StringVar(&opts.ConfigDir, "config", cliconfig.ConfigDir(), "Location of client config files") |
|
| 78 |
+ flags.StringVar(&opts.ConfigDir, "config", cliconfig.Dir(), "Location of client config files") |
|
| 79 | 79 |
opts.Common.InstallFlags(flags) |
| 80 | 80 |
|
| 81 | 81 |
cmd.SetOutput(dockerCli.Out()) |
| ... | ... |
@@ -126,7 +126,7 @@ func dockerPreRun(opts *cliflags.ClientOptions) {
|
| 126 | 126 |
cliflags.SetLogLevel(opts.Common.LogLevel) |
| 127 | 127 |
|
| 128 | 128 |
if opts.ConfigDir != "" {
|
| 129 |
- cliconfig.SetConfigDir(opts.ConfigDir) |
|
| 129 |
+ cliconfig.SetDir(opts.ConfigDir) |
|
| 130 | 130 |
} |
| 131 | 131 |
|
| 132 | 132 |
if opts.Common.Debug {
|
| ... | ... |
@@ -26,9 +26,9 @@ import ( |
| 26 | 26 |
systemrouter "github.com/docker/docker/api/server/router/system" |
| 27 | 27 |
"github.com/docker/docker/api/server/router/volume" |
| 28 | 28 |
"github.com/docker/docker/builder/dockerfile" |
| 29 |
+ cliconfig "github.com/docker/docker/cli/config" |
|
| 29 | 30 |
"github.com/docker/docker/cli/debug" |
| 30 | 31 |
cliflags "github.com/docker/docker/cli/flags" |
| 31 |
- "github.com/docker/docker/cliconfig" |
|
| 32 | 32 |
"github.com/docker/docker/daemon" |
| 33 | 33 |
"github.com/docker/docker/daemon/cluster" |
| 34 | 34 |
"github.com/docker/docker/daemon/logger" |
| ... | ... |
@@ -75,7 +75,7 @@ func migrateKey(config *daemon.Config) (err error) {
|
| 75 | 75 |
} |
| 76 | 76 |
|
| 77 | 77 |
// Migrate trust key if exists at ~/.docker/key.json and owned by current user |
| 78 |
- oldPath := filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile) |
|
| 78 |
+ oldPath := filepath.Join(cliconfig.Dir(), cliflags.DefaultTrustKeyFile) |
|
| 79 | 79 |
newPath := filepath.Join(getDaemonConfDir(config.Root), cliflags.DefaultTrustKeyFile) |
| 80 | 80 |
if _, statErr := os.Stat(newPath); os.IsNotExist(statErr) && currentUserIsOwner(oldPath) {
|
| 81 | 81 |
defer func() {
|
| ... | ... |
@@ -10,7 +10,7 @@ import ( |
| 10 | 10 |
"testing" |
| 11 | 11 |
|
| 12 | 12 |
"github.com/docker/docker/api/types/swarm" |
| 13 |
- "github.com/docker/docker/cliconfig" |
|
| 13 |
+ cliconfig "github.com/docker/docker/cli/config" |
|
| 14 | 14 |
"github.com/docker/docker/integration-cli/daemon" |
| 15 | 15 |
"github.com/docker/docker/pkg/reexec" |
| 16 | 16 |
"github.com/go-check/check" |
| ... | ... |
@@ -359,7 +359,7 @@ func (s *DockerTrustSuite) TearDownTest(c *check.C) {
|
| 359 | 359 |
} |
| 360 | 360 |
|
| 361 | 361 |
// Remove trusted keys and metadata after test |
| 362 |
- os.RemoveAll(filepath.Join(cliconfig.ConfigDir(), "trust")) |
|
| 362 |
+ os.RemoveAll(filepath.Join(cliconfig.Dir(), "trust")) |
|
| 363 | 363 |
s.ds.TearDownTest(c) |
| 364 | 364 |
} |
| 365 | 365 |
|
| ... | ... |
@@ -14,7 +14,7 @@ import ( |
| 14 | 14 |
"time" |
| 15 | 15 |
|
| 16 | 16 |
"github.com/docker/distribution/reference" |
| 17 |
- "github.com/docker/docker/cliconfig" |
|
| 17 |
+ cliconfig "github.com/docker/docker/cli/config" |
|
| 18 | 18 |
"github.com/docker/docker/pkg/integration/checker" |
| 19 | 19 |
"github.com/go-check/check" |
| 20 | 20 |
) |
| ... | ... |
@@ -300,7 +300,7 @@ func (s *DockerTrustSuite) TestTrustedPush(c *check.C) {
|
| 300 | 300 |
c.Assert(string(out), checker.Contains, "Status: Image is up to date", check.Commentf(out)) |
| 301 | 301 |
|
| 302 | 302 |
// Assert that we rotated the snapshot key to the server by checking our local keystore |
| 303 |
- contents, err := ioutil.ReadDir(filepath.Join(cliconfig.ConfigDir(), "trust/private/tuf_keys", privateRegistryURL, "dockerclitrusted/pushtest")) |
|
| 303 |
+ contents, err := ioutil.ReadDir(filepath.Join(cliconfig.Dir(), "trust/private/tuf_keys", privateRegistryURL, "dockerclitrusted/pushtest")) |
|
| 304 | 304 |
c.Assert(err, check.IsNil, check.Commentf("Unable to read local tuf key files"))
|
| 305 | 305 |
// Check that we only have 1 key (targets key) |
| 306 | 306 |
c.Assert(contents, checker.HasLen, 1) |
| ... | ... |
@@ -496,7 +496,7 @@ func (s *DockerTrustSuite) TestTrustedPushWithReleasesDelegationOnly(c *check.C) |
| 496 | 496 |
s.assertTargetNotInRoles(c, repoName, "latest", "targets") |
| 497 | 497 |
|
| 498 | 498 |
// Try pull after push |
| 499 |
- os.RemoveAll(filepath.Join(cliconfig.ConfigDir(), "trust")) |
|
| 499 |
+ os.RemoveAll(filepath.Join(cliconfig.Dir(), "trust")) |
|
| 500 | 500 |
|
| 501 | 501 |
pullCmd := exec.Command(dockerBinary, "pull", targetName) |
| 502 | 502 |
s.trustedCmd(pullCmd) |
| ... | ... |
@@ -539,7 +539,7 @@ func (s *DockerTrustSuite) TestTrustedPushSignsAllFirstLevelRolesWeHaveKeysFor(c |
| 539 | 539 |
s.assertTargetNotInRoles(c, repoName, "latest", "targets") |
| 540 | 540 |
|
| 541 | 541 |
// Try pull after push |
| 542 |
- os.RemoveAll(filepath.Join(cliconfig.ConfigDir(), "trust")) |
|
| 542 |
+ os.RemoveAll(filepath.Join(cliconfig.Dir(), "trust")) |
|
| 543 | 543 |
|
| 544 | 544 |
// pull should fail because none of these are the releases role |
| 545 | 545 |
pullCmd := exec.Command(dockerBinary, "pull", targetName) |
| ... | ... |
@@ -580,7 +580,7 @@ func (s *DockerTrustSuite) TestTrustedPushSignsForRolesWithKeysAndValidPaths(c * |
| 580 | 580 |
s.assertTargetNotInRoles(c, repoName, "latest", "targets") |
| 581 | 581 |
|
| 582 | 582 |
// Try pull after push |
| 583 |
- os.RemoveAll(filepath.Join(cliconfig.ConfigDir(), "trust")) |
|
| 583 |
+ os.RemoveAll(filepath.Join(cliconfig.Dir(), "trust")) |
|
| 584 | 584 |
|
| 585 | 585 |
// pull should fail because none of these are the releases role |
| 586 | 586 |
pullCmd := exec.Command(dockerBinary, "pull", targetName) |
| ... | ... |
@@ -11,7 +11,7 @@ import ( |
| 11 | 11 |
"strings" |
| 12 | 12 |
"time" |
| 13 | 13 |
|
| 14 |
- "github.com/docker/docker/cliconfig" |
|
| 14 |
+ cliconfig "github.com/docker/docker/cli/config" |
|
| 15 | 15 |
"github.com/docker/docker/pkg/integration/checker" |
| 16 | 16 |
"github.com/docker/go-connections/tlsconfig" |
| 17 | 17 |
"github.com/go-check/check" |
| ... | ... |
@@ -90,7 +90,7 @@ func newTestNotary(c *check.C) (*testNotary, error) {
|
| 90 | 90 |
"skipTLSVerify": true |
| 91 | 91 |
} |
| 92 | 92 |
}` |
| 93 |
- if _, err = fmt.Fprintf(clientConfig, template, filepath.Join(cliconfig.ConfigDir(), "trust"), notaryURL); err != nil {
|
|
| 93 |
+ if _, err = fmt.Fprintf(clientConfig, template, filepath.Join(cliconfig.Dir(), "trust"), notaryURL); err != nil {
|
|
| 94 | 94 |
os.RemoveAll(tmp) |
| 95 | 95 |
return nil, err |
| 96 | 96 |
} |