Browse code

Added support for creating a cliconfig.ConfigFile via io.Reader

Signed-off-by: Kevin Shi <kshi@andrew.cmu.edu>

Kevin Shi authored on 2015/07/29 10:28:14
Showing 2 changed files
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"encoding/base64"
5 5
 	"encoding/json"
6 6
 	"fmt"
7
+	"io"
7 8
 	"io/ioutil"
8 9
 	"os"
9 10
 	"path/filepath"
... ...
@@ -70,6 +71,88 @@ func NewConfigFile(fn string) *ConfigFile {
70 70
 	}
71 71
 }
72 72
 
73
+// LegacyLoadFromReader reads the non-nested configuration data given and sets up the
74
+// auth config information with given directory and populates the receiver object
75
+func (configFile *ConfigFile) LegacyLoadFromReader(configData io.Reader) error {
76
+	b, err := ioutil.ReadAll(configData)
77
+	if err != nil {
78
+		return err
79
+	}
80
+
81
+	if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil {
82
+		arr := strings.Split(string(b), "\n")
83
+		if len(arr) < 2 {
84
+			return fmt.Errorf("The Auth config file is empty")
85
+		}
86
+		authConfig := AuthConfig{}
87
+		origAuth := strings.Split(arr[0], " = ")
88
+		if len(origAuth) != 2 {
89
+			return fmt.Errorf("Invalid Auth config file")
90
+		}
91
+		authConfig.Username, authConfig.Password, err = DecodeAuth(origAuth[1])
92
+		if err != nil {
93
+			return err
94
+		}
95
+		origEmail := strings.Split(arr[1], " = ")
96
+		if len(origEmail) != 2 {
97
+			return fmt.Errorf("Invalid Auth config file")
98
+		}
99
+		authConfig.Email = origEmail[1]
100
+		authConfig.ServerAddress = defaultIndexserver
101
+		configFile.AuthConfigs[defaultIndexserver] = authConfig
102
+	} else {
103
+		for k, authConfig := range configFile.AuthConfigs {
104
+			authConfig.Username, authConfig.Password, err = DecodeAuth(authConfig.Auth)
105
+			if err != nil {
106
+				return err
107
+			}
108
+			authConfig.Auth = ""
109
+			authConfig.ServerAddress = k
110
+			configFile.AuthConfigs[k] = authConfig
111
+		}
112
+	}
113
+	return nil
114
+}
115
+
116
+// LoadFromReader reads the configuration data given and sets up the auth config
117
+// information with given directory and populates the receiver object
118
+func (configFile *ConfigFile) LoadFromReader(configData io.Reader) error {
119
+	if err := json.NewDecoder(configData).Decode(&configFile); err != nil {
120
+		return err
121
+	}
122
+	var err error
123
+	for addr, ac := range configFile.AuthConfigs {
124
+		ac.Username, ac.Password, err = DecodeAuth(ac.Auth)
125
+		if err != nil {
126
+			return err
127
+		}
128
+		ac.Auth = ""
129
+		ac.ServerAddress = addr
130
+		configFile.AuthConfigs[addr] = ac
131
+	}
132
+	return nil
133
+}
134
+
135
+// LegacyLoadFromReader is a convenience function that creates a ConfigFile object from
136
+// a non-nested reader
137
+func LegacyLoadFromReader(configData io.Reader) (*ConfigFile, error) {
138
+	configFile := ConfigFile{
139
+		AuthConfigs: make(map[string]AuthConfig),
140
+	}
141
+	err := configFile.LegacyLoadFromReader(configData)
142
+	return &configFile, err
143
+}
144
+
145
+// LoadFromReader is a convenience function that creates a ConfigFile object from
146
+// a reader
147
+func LoadFromReader(configData io.Reader) (*ConfigFile, error) {
148
+	configFile := ConfigFile{
149
+		AuthConfigs: make(map[string]AuthConfig),
150
+	}
151
+	err := configFile.LoadFromReader(configData)
152
+	return &configFile, err
153
+}
154
+
73 155
 // Load reads the configuration files in the given directory, and sets up
74 156
 // the auth config information and return values.
75 157
 // FIXME: use the internal golang config parser
... ...
@@ -90,22 +173,8 @@ func Load(configDir string) (*ConfigFile, error) {
90 90
 			return &configFile, err
91 91
 		}
92 92
 		defer file.Close()
93
-
94
-		if err := json.NewDecoder(file).Decode(&configFile); err != nil {
95
-			return &configFile, err
96
-		}
97
-
98
-		for addr, ac := range configFile.AuthConfigs {
99
-			ac.Username, ac.Password, err = DecodeAuth(ac.Auth)
100
-			if err != nil {
101
-				return &configFile, err
102
-			}
103
-			ac.Auth = ""
104
-			ac.ServerAddress = addr
105
-			configFile.AuthConfigs[addr] = ac
106
-		}
107
-
108
-		return &configFile, nil
93
+		err = configFile.LoadFromReader(file)
94
+		return &configFile, err
109 95
 	} else if !os.IsNotExist(err) {
110 96
 		// if file is there but we can't stat it for any reason other
111 97
 		// than it doesn't exist then stop
... ...
@@ -117,49 +186,18 @@ func Load(configDir string) (*ConfigFile, error) {
117 117
 	if _, err := os.Stat(confFile); err != nil {
118 118
 		return &configFile, nil //missing file is not an error
119 119
 	}
120
-
121
-	b, err := ioutil.ReadFile(confFile)
120
+	file, err := os.Open(confFile)
122 121
 	if err != nil {
123 122
 		return &configFile, err
124 123
 	}
125
-
126
-	if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil {
127
-		arr := strings.Split(string(b), "\n")
128
-		if len(arr) < 2 {
129
-			return &configFile, fmt.Errorf("The Auth config file is empty")
130
-		}
131
-		authConfig := AuthConfig{}
132
-		origAuth := strings.Split(arr[0], " = ")
133
-		if len(origAuth) != 2 {
134
-			return &configFile, fmt.Errorf("Invalid Auth config file")
135
-		}
136
-		authConfig.Username, authConfig.Password, err = DecodeAuth(origAuth[1])
137
-		if err != nil {
138
-			return &configFile, err
139
-		}
140
-		origEmail := strings.Split(arr[1], " = ")
141
-		if len(origEmail) != 2 {
142
-			return &configFile, fmt.Errorf("Invalid Auth config file")
143
-		}
144
-		authConfig.Email = origEmail[1]
145
-		authConfig.ServerAddress = defaultIndexserver
146
-		configFile.AuthConfigs[defaultIndexserver] = authConfig
147
-	} else {
148
-		for k, authConfig := range configFile.AuthConfigs {
149
-			authConfig.Username, authConfig.Password, err = DecodeAuth(authConfig.Auth)
150
-			if err != nil {
151
-				return &configFile, err
152
-			}
153
-			authConfig.Auth = ""
154
-			authConfig.ServerAddress = k
155
-			configFile.AuthConfigs[k] = authConfig
156
-		}
157
-	}
158
-	return &configFile, nil
124
+	defer file.Close()
125
+	err = configFile.LegacyLoadFromReader(file)
126
+	return &configFile, err
159 127
 }
160 128
 
161
-// Save encodes and writes out all the authorization information
162
-func (configFile *ConfigFile) Save() error {
129
+// SaveToWriter encodes and writes out all the authorization information to
130
+// the given writer
131
+func (configFile *ConfigFile) SaveToWriter(writer io.Writer) error {
163 132
 	// Encode sensitive data into a new/temp struct
164 133
 	tmpAuthConfigs := make(map[string]AuthConfig, len(configFile.AuthConfigs))
165 134
 	for k, authConfig := range configFile.AuthConfigs {
... ...
@@ -180,16 +218,25 @@ func (configFile *ConfigFile) Save() error {
180 180
 	if err != nil {
181 181
 		return err
182 182
 	}
183
+	_, err = writer.Write(data)
184
+	return err
185
+}
186
+
187
+// Save encodes and writes out all the authorization information
188
+func (configFile *ConfigFile) Save() error {
189
+	if configFile.Filename() == "" {
190
+		return fmt.Errorf("Can't save config with empty filename")
191
+	}
183 192
 
184 193
 	if err := system.MkdirAll(filepath.Dir(configFile.filename), 0700); err != nil {
185 194
 		return err
186 195
 	}
187
-
188
-	if err := ioutil.WriteFile(configFile.filename, data, 0600); err != nil {
196
+	f, err := os.OpenFile(configFile.filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
197
+	if err != nil {
189 198
 		return err
190 199
 	}
191
-
192
-	return nil
200
+	defer f.Close()
201
+	return configFile.SaveToWriter(f)
193 202
 }
194 203
 
195 204
 // Filename returns the name of the configuration file
... ...
@@ -353,3 +353,96 @@ func TestConfigFile(t *testing.T) {
353 353
 		t.Fatalf("Expected %s, got %s", configFilename, configFile.Filename())
354 354
 	}
355 355
 }
356
+
357
+func TestJsonReaderNoFile(t *testing.T) {
358
+	js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } } }`
359
+
360
+	config, err := LoadFromReader(strings.NewReader(js))
361
+	if err != nil {
362
+		t.Fatalf("Failed loading on empty json file: %q", err)
363
+	}
364
+
365
+	ac := config.AuthConfigs["https://index.docker.io/v1/"]
366
+	if ac.Email != "user@example.com" || ac.Username != "joejoe" || ac.Password != "hello" {
367
+		t.Fatalf("Missing data from parsing:\n%q", config)
368
+	}
369
+
370
+}
371
+
372
+func TestOldJsonReaderNoFile(t *testing.T) {
373
+	js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
374
+
375
+	config, err := LegacyLoadFromReader(strings.NewReader(js))
376
+	if err != nil {
377
+		t.Fatalf("Failed loading on empty json file: %q", err)
378
+	}
379
+
380
+	ac := config.AuthConfigs["https://index.docker.io/v1/"]
381
+	if ac.Email != "user@example.com" || ac.Username != "joejoe" || ac.Password != "hello" {
382
+		t.Fatalf("Missing data from parsing:\n%q", config)
383
+	}
384
+}
385
+
386
+func TestJsonWithPsFormatNoFile(t *testing.T) {
387
+	js := `{
388
+		"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
389
+		"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
390
+}`
391
+	config, err := LoadFromReader(strings.NewReader(js))
392
+	if err != nil {
393
+		t.Fatalf("Failed loading on empty json file: %q", err)
394
+	}
395
+
396
+	if config.PsFormat != `table {{.ID}}\t{{.Label "com.docker.label.cpu"}}` {
397
+		t.Fatalf("Unknown ps format: %s\n", config.PsFormat)
398
+	}
399
+
400
+}
401
+
402
+func TestJsonSaveWithNoFile(t *testing.T) {
403
+	js := `{
404
+		"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
405
+		"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
406
+}`
407
+	config, err := LoadFromReader(strings.NewReader(js))
408
+	err = config.Save()
409
+	if err == nil {
410
+		t.Fatalf("Expected error. File should not have been able to save with no file name.")
411
+	}
412
+
413
+	tmpHome, _ := ioutil.TempDir("", "config-test")
414
+	fn := filepath.Join(tmpHome, ConfigFileName)
415
+	f, _ := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
416
+	err = config.SaveToWriter(f)
417
+	if err != nil {
418
+		t.Fatalf("Failed saving to file: %q", err)
419
+	}
420
+	buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName))
421
+	if !strings.Contains(string(buf), `"auths":`) ||
422
+		!strings.Contains(string(buf), "user@example.com") {
423
+		t.Fatalf("Should have save in new form: %s", string(buf))
424
+	}
425
+
426
+}
427
+func TestLegacyJsonSaveWithNoFile(t *testing.T) {
428
+
429
+	js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
430
+	config, err := LegacyLoadFromReader(strings.NewReader(js))
431
+	err = config.Save()
432
+	if err == nil {
433
+		t.Fatalf("Expected error. File should not have been able to save with no file name.")
434
+	}
435
+
436
+	tmpHome, _ := ioutil.TempDir("", "config-test")
437
+	fn := filepath.Join(tmpHome, ConfigFileName)
438
+	f, _ := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
439
+	err = config.SaveToWriter(f)
440
+	if err != nil {
441
+		t.Fatalf("Failed saving to file: %q", err)
442
+	}
443
+	buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName))
444
+	if !strings.Contains(string(buf), `"auths":`) ||
445
+		!strings.Contains(string(buf), "user@example.com") {
446
+		t.Fatalf("Should have save in new form: %s", string(buf))
447
+	}
448
+}