github.com/justincormack/cli@v0.0.0-20201215022714-831ebeae9675/cli/config/config_test.go (about)

     1  package config
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"runtime"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/docker/cli/cli/config/configfile"
    14  	"github.com/docker/cli/cli/config/credentials"
    15  	"gotest.tools/v3/assert"
    16  	is "gotest.tools/v3/assert/cmp"
    17  	"gotest.tools/v3/env"
    18  )
    19  
    20  var homeKey = "HOME"
    21  
    22  func init() {
    23  	if runtime.GOOS == "windows" {
    24  		homeKey = "USERPROFILE"
    25  	}
    26  }
    27  
    28  func setupConfigDir(t *testing.T) (string, func()) {
    29  	tmpdir, err := ioutil.TempDir("", "config-test")
    30  	assert.NilError(t, err)
    31  	oldDir := Dir()
    32  	SetDir(tmpdir)
    33  
    34  	return tmpdir, func() {
    35  		SetDir(oldDir)
    36  		os.RemoveAll(tmpdir)
    37  	}
    38  }
    39  
    40  func TestEmptyConfigDir(t *testing.T) {
    41  	tmpHome, cleanup := setupConfigDir(t)
    42  	defer cleanup()
    43  
    44  	config, err := Load("")
    45  	assert.NilError(t, err)
    46  
    47  	expectedConfigFilename := filepath.Join(tmpHome, ConfigFileName)
    48  	assert.Check(t, is.Equal(expectedConfigFilename, config.Filename))
    49  
    50  	// Now save it and make sure it shows up in new form
    51  	saveConfigAndValidateNewFormat(t, config, tmpHome)
    52  }
    53  
    54  func TestMissingFile(t *testing.T) {
    55  	tmpHome, err := ioutil.TempDir("", "config-test")
    56  	assert.NilError(t, err)
    57  	defer os.RemoveAll(tmpHome)
    58  
    59  	config, err := Load(tmpHome)
    60  	assert.NilError(t, err)
    61  
    62  	// Now save it and make sure it shows up in new form
    63  	saveConfigAndValidateNewFormat(t, config, tmpHome)
    64  }
    65  
    66  func TestSaveFileToDirs(t *testing.T) {
    67  	tmpHome, err := ioutil.TempDir("", "config-test")
    68  	assert.NilError(t, err)
    69  	defer os.RemoveAll(tmpHome)
    70  
    71  	tmpHome += "/.docker"
    72  
    73  	config, err := Load(tmpHome)
    74  	assert.NilError(t, err)
    75  
    76  	// Now save it and make sure it shows up in new form
    77  	saveConfigAndValidateNewFormat(t, config, tmpHome)
    78  }
    79  
    80  func TestEmptyFile(t *testing.T) {
    81  	tmpHome, err := ioutil.TempDir("", "config-test")
    82  	assert.NilError(t, err)
    83  	defer os.RemoveAll(tmpHome)
    84  
    85  	fn := filepath.Join(tmpHome, ConfigFileName)
    86  	err = ioutil.WriteFile(fn, []byte(""), 0600)
    87  	assert.NilError(t, err)
    88  
    89  	_, err = Load(tmpHome)
    90  	assert.NilError(t, err)
    91  }
    92  
    93  func TestEmptyJSON(t *testing.T) {
    94  	tmpHome, err := ioutil.TempDir("", "config-test")
    95  	assert.NilError(t, err)
    96  	defer os.RemoveAll(tmpHome)
    97  
    98  	fn := filepath.Join(tmpHome, ConfigFileName)
    99  	err = ioutil.WriteFile(fn, []byte("{}"), 0600)
   100  	assert.NilError(t, err)
   101  
   102  	config, err := Load(tmpHome)
   103  	assert.NilError(t, err)
   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  	assert.NilError(t, err)
   120  	defer os.RemoveAll(tmpHome)
   121  	defer env.Patch(t, homeKey, tmpHome)()
   122  
   123  	for content, expectedError := range invalids {
   124  		fn := filepath.Join(tmpHome, oldConfigfile)
   125  		err := ioutil.WriteFile(fn, []byte(content), 0600)
   126  		assert.NilError(t, err)
   127  
   128  		_, err = Load(tmpHome)
   129  		assert.ErrorContains(t, err, expectedError)
   130  	}
   131  }
   132  
   133  func TestOldValidAuth(t *testing.T) {
   134  	tmpHome, err := ioutil.TempDir("", "config-test")
   135  	assert.NilError(t, err)
   136  	defer os.RemoveAll(tmpHome)
   137  	defer env.Patch(t, homeKey, tmpHome)()
   138  
   139  	fn := filepath.Join(tmpHome, oldConfigfile)
   140  	js := `username = am9lam9lOmhlbGxv
   141  	email = user@example.com`
   142  	err = ioutil.WriteFile(fn, []byte(js), 0600)
   143  	assert.NilError(t, err)
   144  
   145  	config, err := Load(tmpHome)
   146  	assert.NilError(t, err)
   147  
   148  	// defaultIndexserver is https://index.docker.io/v1/
   149  	ac := config.AuthConfigs["https://index.docker.io/v1/"]
   150  	assert.Equal(t, ac.Username, "joejoe")
   151  	assert.Equal(t, ac.Password, "hello")
   152  
   153  	// Now save it and make sure it shows up in new form
   154  	configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
   155  
   156  	expConfStr := `{
   157  	"auths": {
   158  		"https://index.docker.io/v1/": {
   159  			"auth": "am9lam9lOmhlbGxv"
   160  		}
   161  	}
   162  }`
   163  
   164  	assert.Check(t, is.Equal(expConfStr, configStr))
   165  }
   166  
   167  func TestOldJSONInvalid(t *testing.T) {
   168  	tmpHome, err := ioutil.TempDir("", "config-test")
   169  	assert.NilError(t, err)
   170  	defer os.RemoveAll(tmpHome)
   171  	defer env.Patch(t, homeKey, tmpHome)()
   172  
   173  	fn := filepath.Join(tmpHome, oldConfigfile)
   174  	js := `{"https://index.docker.io/v1/":{"auth":"test","email":"user@example.com"}}`
   175  	if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
   176  		t.Fatal(err)
   177  	}
   178  
   179  	config, err := Load(tmpHome)
   180  	// Use Contains instead of == since the file name will change each time
   181  	if err == nil || !strings.Contains(err.Error(), "Invalid auth configuration file") {
   182  		t.Fatalf("Expected an error got : %v, %v", config, err)
   183  	}
   184  }
   185  
   186  func TestOldJSON(t *testing.T) {
   187  	tmpHome, err := ioutil.TempDir("", "config-test")
   188  	assert.NilError(t, err)
   189  	defer os.RemoveAll(tmpHome)
   190  	defer env.Patch(t, homeKey, tmpHome)()
   191  
   192  	fn := filepath.Join(tmpHome, oldConfigfile)
   193  	js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
   194  	if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
   195  		t.Fatal(err)
   196  	}
   197  
   198  	config, err := Load(tmpHome)
   199  	assert.NilError(t, err)
   200  
   201  	ac := config.AuthConfigs["https://index.docker.io/v1/"]
   202  	assert.Equal(t, ac.Username, "joejoe")
   203  	assert.Equal(t, ac.Password, "hello")
   204  
   205  	// Now save it and make sure it shows up in new form
   206  	configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
   207  
   208  	expConfStr := `{
   209  	"auths": {
   210  		"https://index.docker.io/v1/": {
   211  			"auth": "am9lam9lOmhlbGxv",
   212  			"email": "user@example.com"
   213  		}
   214  	}
   215  }`
   216  
   217  	if configStr != expConfStr {
   218  		t.Fatalf("Should have save in new form: \n'%s'\n not \n'%s'\n", configStr, expConfStr)
   219  	}
   220  }
   221  
   222  func TestNewJSON(t *testing.T) {
   223  	tmpHome, err := ioutil.TempDir("", "config-test")
   224  	assert.NilError(t, err)
   225  	defer os.RemoveAll(tmpHome)
   226  
   227  	fn := filepath.Join(tmpHome, ConfigFileName)
   228  	js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } } }`
   229  	if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
   230  		t.Fatal(err)
   231  	}
   232  
   233  	config, err := Load(tmpHome)
   234  	assert.NilError(t, err)
   235  
   236  	ac := config.AuthConfigs["https://index.docker.io/v1/"]
   237  	assert.Equal(t, ac.Username, "joejoe")
   238  	assert.Equal(t, ac.Password, "hello")
   239  
   240  	// Now save it and make sure it shows up in new form
   241  	configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
   242  
   243  	expConfStr := `{
   244  	"auths": {
   245  		"https://index.docker.io/v1/": {
   246  			"auth": "am9lam9lOmhlbGxv"
   247  		}
   248  	}
   249  }`
   250  
   251  	if configStr != expConfStr {
   252  		t.Fatalf("Should have save in new form: \n%s\n not \n%s", configStr, expConfStr)
   253  	}
   254  }
   255  
   256  func TestNewJSONNoEmail(t *testing.T) {
   257  	tmpHome, err := ioutil.TempDir("", "config-test")
   258  	assert.NilError(t, err)
   259  	defer os.RemoveAll(tmpHome)
   260  
   261  	fn := filepath.Join(tmpHome, ConfigFileName)
   262  	js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } } }`
   263  	if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
   264  		t.Fatal(err)
   265  	}
   266  
   267  	config, err := Load(tmpHome)
   268  	assert.NilError(t, err)
   269  
   270  	ac := config.AuthConfigs["https://index.docker.io/v1/"]
   271  	assert.Equal(t, ac.Username, "joejoe")
   272  	assert.Equal(t, ac.Password, "hello")
   273  
   274  	// Now save it and make sure it shows up in new form
   275  	configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
   276  
   277  	expConfStr := `{
   278  	"auths": {
   279  		"https://index.docker.io/v1/": {
   280  			"auth": "am9lam9lOmhlbGxv"
   281  		}
   282  	}
   283  }`
   284  
   285  	if configStr != expConfStr {
   286  		t.Fatalf("Should have save in new form: \n%s\n not \n%s", configStr, expConfStr)
   287  	}
   288  }
   289  
   290  func TestJSONWithPsFormat(t *testing.T) {
   291  	tmpHome, err := ioutil.TempDir("", "config-test")
   292  	assert.NilError(t, err)
   293  	defer os.RemoveAll(tmpHome)
   294  
   295  	fn := filepath.Join(tmpHome, ConfigFileName)
   296  	js := `{
   297  		"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
   298  		"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
   299  }`
   300  	if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
   301  		t.Fatal(err)
   302  	}
   303  
   304  	config, err := Load(tmpHome)
   305  	assert.NilError(t, err)
   306  
   307  	if config.PsFormat != `table {{.ID}}\t{{.Label "com.docker.label.cpu"}}` {
   308  		t.Fatalf("Unknown ps format: %s\n", config.PsFormat)
   309  	}
   310  
   311  	// Now save it and make sure it shows up in new form
   312  	configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
   313  	if !strings.Contains(configStr, `"psFormat":`) ||
   314  		!strings.Contains(configStr, "{{.ID}}") {
   315  		t.Fatalf("Should have save in new form: %s", configStr)
   316  	}
   317  }
   318  
   319  func TestJSONWithCredentialStore(t *testing.T) {
   320  	tmpHome, err := ioutil.TempDir("", "config-test")
   321  	assert.NilError(t, err)
   322  	defer os.RemoveAll(tmpHome)
   323  
   324  	fn := filepath.Join(tmpHome, ConfigFileName)
   325  	js := `{
   326  		"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
   327  		"credsStore": "crazy-secure-storage"
   328  }`
   329  	if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
   330  		t.Fatal(err)
   331  	}
   332  
   333  	config, err := Load(tmpHome)
   334  	assert.NilError(t, err)
   335  
   336  	if config.CredentialsStore != "crazy-secure-storage" {
   337  		t.Fatalf("Unknown credential store: %s\n", config.CredentialsStore)
   338  	}
   339  
   340  	// Now save it and make sure it shows up in new form
   341  	configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
   342  	if !strings.Contains(configStr, `"credsStore":`) ||
   343  		!strings.Contains(configStr, "crazy-secure-storage") {
   344  		t.Fatalf("Should have save in new form: %s", configStr)
   345  	}
   346  }
   347  
   348  func TestJSONWithCredentialHelpers(t *testing.T) {
   349  	tmpHome, err := ioutil.TempDir("", "config-test")
   350  	assert.NilError(t, err)
   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  		"credHelpers": { "images.io": "images-io", "containers.com": "crazy-secure-storage" }
   357  }`
   358  	if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
   359  		t.Fatal(err)
   360  	}
   361  
   362  	config, err := Load(tmpHome)
   363  	assert.NilError(t, err)
   364  
   365  	if config.CredentialHelpers == nil {
   366  		t.Fatal("config.CredentialHelpers was nil")
   367  	} else if config.CredentialHelpers["images.io"] != "images-io" ||
   368  		config.CredentialHelpers["containers.com"] != "crazy-secure-storage" {
   369  		t.Fatalf("Credential helpers not deserialized properly: %v\n", config.CredentialHelpers)
   370  	}
   371  
   372  	// Now save it and make sure it shows up in new form
   373  	configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
   374  	if !strings.Contains(configStr, `"credHelpers":`) ||
   375  		!strings.Contains(configStr, "images.io") ||
   376  		!strings.Contains(configStr, "images-io") ||
   377  		!strings.Contains(configStr, "containers.com") ||
   378  		!strings.Contains(configStr, "crazy-secure-storage") {
   379  		t.Fatalf("Should have save in new form: %s", configStr)
   380  	}
   381  }
   382  
   383  // Save it and make sure it shows up in new form
   384  func saveConfigAndValidateNewFormat(t *testing.T, config *configfile.ConfigFile, configDir string) string {
   385  	t.Helper()
   386  	assert.NilError(t, config.Save())
   387  
   388  	buf, err := ioutil.ReadFile(filepath.Join(configDir, ConfigFileName))
   389  	assert.NilError(t, err)
   390  	assert.Check(t, is.Contains(string(buf), `"auths":`))
   391  	return string(buf)
   392  }
   393  
   394  func TestConfigDir(t *testing.T) {
   395  	tmpHome, err := ioutil.TempDir("", "config-test")
   396  	assert.NilError(t, err)
   397  	defer os.RemoveAll(tmpHome)
   398  
   399  	if Dir() == tmpHome {
   400  		t.Fatalf("Expected ConfigDir to be different than %s by default, but was the same", tmpHome)
   401  	}
   402  
   403  	// Update configDir
   404  	SetDir(tmpHome)
   405  
   406  	if Dir() != tmpHome {
   407  		t.Fatalf("Expected ConfigDir to %s, but was %s", tmpHome, Dir())
   408  	}
   409  }
   410  
   411  func TestJSONReaderNoFile(t *testing.T) {
   412  	js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } } }`
   413  
   414  	config, err := LoadFromReader(strings.NewReader(js))
   415  	assert.NilError(t, err)
   416  
   417  	ac := config.AuthConfigs["https://index.docker.io/v1/"]
   418  	assert.Equal(t, ac.Username, "joejoe")
   419  	assert.Equal(t, ac.Password, "hello")
   420  }
   421  
   422  func TestOldJSONReaderNoFile(t *testing.T) {
   423  	js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
   424  
   425  	config, err := LegacyLoadFromReader(strings.NewReader(js))
   426  	assert.NilError(t, err)
   427  
   428  	ac := config.AuthConfigs["https://index.docker.io/v1/"]
   429  	assert.Equal(t, ac.Username, "joejoe")
   430  	assert.Equal(t, ac.Password, "hello")
   431  }
   432  
   433  func TestJSONWithPsFormatNoFile(t *testing.T) {
   434  	js := `{
   435  		"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
   436  		"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
   437  }`
   438  	config, err := LoadFromReader(strings.NewReader(js))
   439  	assert.NilError(t, err)
   440  
   441  	if config.PsFormat != `table {{.ID}}\t{{.Label "com.docker.label.cpu"}}` {
   442  		t.Fatalf("Unknown ps format: %s\n", config.PsFormat)
   443  	}
   444  }
   445  
   446  func TestJSONSaveWithNoFile(t *testing.T) {
   447  	js := `{
   448  		"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } },
   449  		"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
   450  }`
   451  	config, err := LoadFromReader(strings.NewReader(js))
   452  	assert.NilError(t, err)
   453  	err = config.Save()
   454  	assert.ErrorContains(t, err, "with empty filename")
   455  
   456  	tmpHome, err := ioutil.TempDir("", "config-test")
   457  	assert.NilError(t, err)
   458  	defer os.RemoveAll(tmpHome)
   459  
   460  	fn := filepath.Join(tmpHome, ConfigFileName)
   461  	f, _ := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
   462  	defer f.Close()
   463  
   464  	assert.NilError(t, config.SaveToWriter(f))
   465  	buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName))
   466  	assert.NilError(t, err)
   467  	expConfStr := `{
   468  	"auths": {
   469  		"https://index.docker.io/v1/": {
   470  			"auth": "am9lam9lOmhlbGxv"
   471  		}
   472  	},
   473  	"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
   474  }`
   475  	if string(buf) != expConfStr {
   476  		t.Fatalf("Should have save in new form: \n%s\nnot \n%s", string(buf), expConfStr)
   477  	}
   478  }
   479  
   480  func TestLegacyJSONSaveWithNoFile(t *testing.T) {
   481  	js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
   482  	config, err := LegacyLoadFromReader(strings.NewReader(js))
   483  	assert.NilError(t, err)
   484  	err = config.Save()
   485  	assert.ErrorContains(t, err, "with empty filename")
   486  
   487  	tmpHome, err := ioutil.TempDir("", "config-test")
   488  	assert.NilError(t, err)
   489  	defer os.RemoveAll(tmpHome)
   490  
   491  	fn := filepath.Join(tmpHome, ConfigFileName)
   492  	f, _ := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
   493  	defer f.Close()
   494  
   495  	assert.NilError(t, config.SaveToWriter(f))
   496  	buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName))
   497  	assert.NilError(t, err)
   498  
   499  	expConfStr := `{
   500  	"auths": {
   501  		"https://index.docker.io/v1/": {
   502  			"auth": "am9lam9lOmhlbGxv",
   503  			"email": "user@example.com"
   504  		}
   505  	}
   506  }`
   507  
   508  	if string(buf) != expConfStr {
   509  		t.Fatalf("Should have save in new form: \n%s\n not \n%s", string(buf), expConfStr)
   510  	}
   511  }
   512  
   513  func TestLoadDefaultConfigFile(t *testing.T) {
   514  	dir, cleanup := setupConfigDir(t)
   515  	defer cleanup()
   516  	buffer := new(bytes.Buffer)
   517  
   518  	filename := filepath.Join(dir, ConfigFileName)
   519  	content := []byte(`{"PsFormat": "format"}`)
   520  	err := ioutil.WriteFile(filename, content, 0644)
   521  	assert.NilError(t, err)
   522  
   523  	configFile := LoadDefaultConfigFile(buffer)
   524  	credStore := credentials.DetectDefaultStore("")
   525  	expected := configfile.New(filename)
   526  	expected.CredentialsStore = credStore
   527  	expected.PsFormat = "format"
   528  
   529  	assert.Check(t, is.DeepEqual(expected, configFile))
   530  }
   531  
   532  func TestConfigPath(t *testing.T) {
   533  	oldDir := Dir()
   534  
   535  	for _, tc := range []struct {
   536  		name        string
   537  		dir         string
   538  		path        []string
   539  		expected    string
   540  		expectedErr string
   541  	}{
   542  		{
   543  			name:     "valid_path",
   544  			dir:      "dummy",
   545  			path:     []string{"a", "b"},
   546  			expected: filepath.Join("dummy", "a", "b"),
   547  		},
   548  		{
   549  			name:     "valid_path_absolute_dir",
   550  			dir:      "/dummy",
   551  			path:     []string{"a", "b"},
   552  			expected: filepath.Join("/dummy", "a", "b"),
   553  		},
   554  		{
   555  			name:        "invalid_relative_path",
   556  			dir:         "dummy",
   557  			path:        []string{"e", "..", "..", "f"},
   558  			expectedErr: fmt.Sprintf("is outside of root config directory %q", "dummy"),
   559  		},
   560  		{
   561  			name:        "invalid_absolute_path",
   562  			dir:         "dummy",
   563  			path:        []string{"/a", "..", ".."},
   564  			expectedErr: fmt.Sprintf("is outside of root config directory %q", "dummy"),
   565  		},
   566  	} {
   567  		tc := tc
   568  		t.Run(tc.name, func(t *testing.T) {
   569  			SetDir(tc.dir)
   570  			f, err := Path(tc.path...)
   571  			assert.Equal(t, f, tc.expected)
   572  			if tc.expectedErr == "" {
   573  				assert.NilError(t, err)
   574  			} else {
   575  				assert.ErrorContains(t, err, tc.expectedErr)
   576  			}
   577  		})
   578  	}
   579  
   580  	SetDir(oldDir)
   581  }