github.com/panekj/cli@v0.0.0-20230304125325-467dd2f3797e/cli/config/config_test.go (about)

     1  package config
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"runtime"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/docker/cli/cli/config/configfile"
    13  	"github.com/docker/cli/cli/config/credentials"
    14  	"github.com/docker/cli/cli/config/types"
    15  	"gotest.tools/v3/assert"
    16  	is "gotest.tools/v3/assert/cmp"
    17  	"gotest.tools/v3/env"
    18  	"gotest.tools/v3/fs"
    19  )
    20  
    21  var homeKey = "HOME"
    22  
    23  func init() {
    24  	if runtime.GOOS == "windows" {
    25  		homeKey = "USERPROFILE"
    26  	}
    27  }
    28  
    29  func setupConfigDir(t *testing.T) string {
    30  	tmpdir := t.TempDir()
    31  	oldDir := Dir()
    32  	SetDir(tmpdir)
    33  	t.Cleanup(func() {
    34  		SetDir(oldDir)
    35  	})
    36  	return tmpdir
    37  }
    38  
    39  func TestEmptyConfigDir(t *testing.T) {
    40  	tmpHome := setupConfigDir(t)
    41  
    42  	config, err := Load("")
    43  	assert.NilError(t, err)
    44  
    45  	expectedConfigFilename := filepath.Join(tmpHome, ConfigFileName)
    46  	assert.Check(t, is.Equal(expectedConfigFilename, config.Filename))
    47  
    48  	// Now save it and make sure it shows up in new form
    49  	saveConfigAndValidateNewFormat(t, config, tmpHome)
    50  }
    51  
    52  func TestMissingFile(t *testing.T) {
    53  	tmpHome := t.TempDir()
    54  
    55  	config, err := Load(tmpHome)
    56  	assert.NilError(t, err)
    57  
    58  	// Now save it and make sure it shows up in new form
    59  	saveConfigAndValidateNewFormat(t, config, tmpHome)
    60  }
    61  
    62  func TestSaveFileToDirs(t *testing.T) {
    63  	tmpHome := filepath.Join(t.TempDir(), ".docker")
    64  	config, err := Load(tmpHome)
    65  	assert.NilError(t, err)
    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 := t.TempDir()
    73  
    74  	fn := filepath.Join(tmpHome, ConfigFileName)
    75  	err := os.WriteFile(fn, []byte(""), 0o600)
    76  	assert.NilError(t, err)
    77  
    78  	_, err = Load(tmpHome)
    79  	assert.NilError(t, err)
    80  }
    81  
    82  func TestEmptyJSON(t *testing.T) {
    83  	tmpHome := t.TempDir()
    84  
    85  	fn := filepath.Join(tmpHome, ConfigFileName)
    86  	err := os.WriteFile(fn, []byte("{}"), 0o600)
    87  	assert.NilError(t, err)
    88  
    89  	config, err := Load(tmpHome)
    90  	assert.NilError(t, err)
    91  
    92  	// Now save it and make sure it shows up in new form
    93  	saveConfigAndValidateNewFormat(t, config, tmpHome)
    94  }
    95  
    96  func TestOldJSONFallbackDeprecationWarning(t *testing.T) {
    97  	js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
    98  	tmpHome := fs.NewDir(t, t.Name(), fs.WithFile(oldConfigfile, js))
    99  	defer tmpHome.Remove()
   100  	env.PatchAll(t, map[string]string{homeKey: tmpHome.Path(), "DOCKER_CONFIG": ""})
   101  
   102  	// reset the homeDir, configDir, and its sync.Once, to force them being resolved again
   103  	resetHomeDir()
   104  	resetConfigDir()
   105  
   106  	buffer := new(bytes.Buffer)
   107  	configFile := LoadDefaultConfigFile(buffer)
   108  	expected := configfile.New(tmpHome.Join(configFileDir, ConfigFileName))
   109  	expected.AuthConfigs = map[string]types.AuthConfig{}
   110  	assert.Assert(t, strings.Contains(buffer.String(), "WARNING: Support for the legacy ~/.dockercfg configuration file and file-format has been removed and the configuration file will be ignored"))
   111  	assert.Check(t, is.DeepEqual(expected, configFile))
   112  }
   113  
   114  func TestNewJSON(t *testing.T) {
   115  	tmpHome := t.TempDir()
   116  
   117  	fn := filepath.Join(tmpHome, ConfigFileName)
   118  	js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } } }`
   119  	if err := os.WriteFile(fn, []byte(js), 0o600); err != nil {
   120  		t.Fatal(err)
   121  	}
   122  
   123  	config, err := Load(tmpHome)
   124  	assert.NilError(t, err)
   125  
   126  	ac := config.AuthConfigs["https://index.docker.io/v1/"]
   127  	assert.Equal(t, ac.Username, "joejoe")
   128  	assert.Equal(t, ac.Password, "hello")
   129  
   130  	// Now save it and make sure it shows up in new form
   131  	configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
   132  
   133  	expConfStr := `{
   134  	"auths": {
   135  		"https://index.docker.io/v1/": {
   136  			"auth": "am9lam9lOmhlbGxv"
   137  		}
   138  	}
   139  }`
   140  
   141  	if configStr != expConfStr {
   142  		t.Fatalf("Should have save in new form: \n%s\n not \n%s", configStr, expConfStr)
   143  	}
   144  }
   145  
   146  func TestNewJSONNoEmail(t *testing.T) {
   147  	tmpHome := t.TempDir()
   148  
   149  	fn := filepath.Join(tmpHome, ConfigFileName)
   150  	js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } } }`
   151  	if err := os.WriteFile(fn, []byte(js), 0o600); err != nil {
   152  		t.Fatal(err)
   153  	}
   154  
   155  	config, err := Load(tmpHome)
   156  	assert.NilError(t, err)
   157  
   158  	ac := config.AuthConfigs["https://index.docker.io/v1/"]
   159  	assert.Equal(t, ac.Username, "joejoe")
   160  	assert.Equal(t, ac.Password, "hello")
   161  
   162  	// Now save it and make sure it shows up in new form
   163  	configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
   164  
   165  	expConfStr := `{
   166  	"auths": {
   167  		"https://index.docker.io/v1/": {
   168  			"auth": "am9lam9lOmhlbGxv"
   169  		}
   170  	}
   171  }`
   172  
   173  	if configStr != expConfStr {
   174  		t.Fatalf("Should have save in new form: \n%s\n not \n%s", configStr, expConfStr)
   175  	}
   176  }
   177  
   178  func TestJSONWithPsFormat(t *testing.T) {
   179  	tmpHome := t.TempDir()
   180  
   181  	fn := filepath.Join(tmpHome, ConfigFileName)
   182  	js := `{
   183  		"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
   184  		"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
   185  }`
   186  	if err := os.WriteFile(fn, []byte(js), 0o600); err != nil {
   187  		t.Fatal(err)
   188  	}
   189  
   190  	config, err := Load(tmpHome)
   191  	assert.NilError(t, err)
   192  
   193  	if config.PsFormat != `table {{.ID}}\t{{.Label "com.docker.label.cpu"}}` {
   194  		t.Fatalf("Unknown ps format: %s\n", config.PsFormat)
   195  	}
   196  
   197  	// Now save it and make sure it shows up in new form
   198  	configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
   199  	if !strings.Contains(configStr, `"psFormat":`) ||
   200  		!strings.Contains(configStr, "{{.ID}}") {
   201  		t.Fatalf("Should have save in new form: %s", configStr)
   202  	}
   203  }
   204  
   205  func TestJSONWithCredentialStore(t *testing.T) {
   206  	tmpHome := t.TempDir()
   207  
   208  	fn := filepath.Join(tmpHome, ConfigFileName)
   209  	js := `{
   210  		"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
   211  		"credsStore": "crazy-secure-storage"
   212  }`
   213  	if err := os.WriteFile(fn, []byte(js), 0o600); err != nil {
   214  		t.Fatal(err)
   215  	}
   216  
   217  	config, err := Load(tmpHome)
   218  	assert.NilError(t, err)
   219  
   220  	if config.CredentialsStore != "crazy-secure-storage" {
   221  		t.Fatalf("Unknown credential store: %s\n", config.CredentialsStore)
   222  	}
   223  
   224  	// Now save it and make sure it shows up in new form
   225  	configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
   226  	if !strings.Contains(configStr, `"credsStore":`) ||
   227  		!strings.Contains(configStr, "crazy-secure-storage") {
   228  		t.Fatalf("Should have save in new form: %s", configStr)
   229  	}
   230  }
   231  
   232  func TestJSONWithCredentialHelpers(t *testing.T) {
   233  	tmpHome := t.TempDir()
   234  
   235  	fn := filepath.Join(tmpHome, ConfigFileName)
   236  	js := `{
   237  		"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
   238  		"credHelpers": { "images.io": "images-io", "containers.com": "crazy-secure-storage" }
   239  }`
   240  	if err := os.WriteFile(fn, []byte(js), 0o600); err != nil {
   241  		t.Fatal(err)
   242  	}
   243  
   244  	config, err := Load(tmpHome)
   245  	assert.NilError(t, err)
   246  
   247  	if config.CredentialHelpers == nil {
   248  		t.Fatal("config.CredentialHelpers was nil")
   249  	} else if config.CredentialHelpers["images.io"] != "images-io" ||
   250  		config.CredentialHelpers["containers.com"] != "crazy-secure-storage" {
   251  		t.Fatalf("Credential helpers not deserialized properly: %v\n", config.CredentialHelpers)
   252  	}
   253  
   254  	// Now save it and make sure it shows up in new form
   255  	configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
   256  	if !strings.Contains(configStr, `"credHelpers":`) ||
   257  		!strings.Contains(configStr, "images.io") ||
   258  		!strings.Contains(configStr, "images-io") ||
   259  		!strings.Contains(configStr, "containers.com") ||
   260  		!strings.Contains(configStr, "crazy-secure-storage") {
   261  		t.Fatalf("Should have save in new form: %s", configStr)
   262  	}
   263  }
   264  
   265  // Save it and make sure it shows up in new form
   266  func saveConfigAndValidateNewFormat(t *testing.T, config *configfile.ConfigFile, configDir string) string {
   267  	t.Helper()
   268  	assert.NilError(t, config.Save())
   269  
   270  	buf, err := os.ReadFile(filepath.Join(configDir, ConfigFileName))
   271  	assert.NilError(t, err)
   272  	assert.Check(t, is.Contains(string(buf), `"auths":`))
   273  	return string(buf)
   274  }
   275  
   276  func TestConfigDir(t *testing.T) {
   277  	tmpHome := t.TempDir()
   278  
   279  	if Dir() == tmpHome {
   280  		t.Fatalf("Expected ConfigDir to be different than %s by default, but was the same", tmpHome)
   281  	}
   282  
   283  	// Update configDir
   284  	SetDir(tmpHome)
   285  
   286  	if Dir() != tmpHome {
   287  		t.Fatalf("Expected ConfigDir to %s, but was %s", tmpHome, Dir())
   288  	}
   289  }
   290  
   291  func TestJSONReaderNoFile(t *testing.T) {
   292  	js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } } }`
   293  
   294  	config, err := LoadFromReader(strings.NewReader(js))
   295  	assert.NilError(t, err)
   296  
   297  	ac := config.AuthConfigs["https://index.docker.io/v1/"]
   298  	assert.Equal(t, ac.Username, "joejoe")
   299  	assert.Equal(t, ac.Password, "hello")
   300  }
   301  
   302  func TestJSONWithPsFormatNoFile(t *testing.T) {
   303  	js := `{
   304  		"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
   305  		"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
   306  }`
   307  	config, err := LoadFromReader(strings.NewReader(js))
   308  	assert.NilError(t, err)
   309  
   310  	if config.PsFormat != `table {{.ID}}\t{{.Label "com.docker.label.cpu"}}` {
   311  		t.Fatalf("Unknown ps format: %s\n", config.PsFormat)
   312  	}
   313  }
   314  
   315  func TestJSONSaveWithNoFile(t *testing.T) {
   316  	js := `{
   317  		"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } },
   318  		"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
   319  }`
   320  	config, err := LoadFromReader(strings.NewReader(js))
   321  	assert.NilError(t, err)
   322  	err = config.Save()
   323  	assert.ErrorContains(t, err, "with empty filename")
   324  
   325  	tmpHome := t.TempDir()
   326  
   327  	fn := filepath.Join(tmpHome, ConfigFileName)
   328  	f, _ := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
   329  	defer f.Close()
   330  
   331  	assert.NilError(t, config.SaveToWriter(f))
   332  	buf, err := os.ReadFile(filepath.Join(tmpHome, ConfigFileName))
   333  	assert.NilError(t, err)
   334  	expConfStr := `{
   335  	"auths": {
   336  		"https://index.docker.io/v1/": {
   337  			"auth": "am9lam9lOmhlbGxv"
   338  		}
   339  	},
   340  	"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
   341  }`
   342  	if string(buf) != expConfStr {
   343  		t.Fatalf("Should have save in new form: \n%s\nnot \n%s", string(buf), expConfStr)
   344  	}
   345  }
   346  
   347  func TestLoadDefaultConfigFile(t *testing.T) {
   348  	dir := setupConfigDir(t)
   349  	buffer := new(bytes.Buffer)
   350  
   351  	filename := filepath.Join(dir, ConfigFileName)
   352  	content := []byte(`{"PsFormat": "format"}`)
   353  	err := os.WriteFile(filename, content, 0o644)
   354  	assert.NilError(t, err)
   355  
   356  	configFile := LoadDefaultConfigFile(buffer)
   357  	credStore := credentials.DetectDefaultStore("")
   358  	expected := configfile.New(filename)
   359  	expected.CredentialsStore = credStore
   360  	expected.PsFormat = "format"
   361  
   362  	assert.Check(t, is.DeepEqual(expected, configFile))
   363  }
   364  
   365  func TestConfigPath(t *testing.T) {
   366  	oldDir := Dir()
   367  
   368  	for _, tc := range []struct {
   369  		name        string
   370  		dir         string
   371  		path        []string
   372  		expected    string
   373  		expectedErr string
   374  	}{
   375  		{
   376  			name:     "valid_path",
   377  			dir:      "dummy",
   378  			path:     []string{"a", "b"},
   379  			expected: filepath.Join("dummy", "a", "b"),
   380  		},
   381  		{
   382  			name:     "valid_path_absolute_dir",
   383  			dir:      "/dummy",
   384  			path:     []string{"a", "b"},
   385  			expected: filepath.Join("/dummy", "a", "b"),
   386  		},
   387  		{
   388  			name:        "invalid_relative_path",
   389  			dir:         "dummy",
   390  			path:        []string{"e", "..", "..", "f"},
   391  			expectedErr: fmt.Sprintf("is outside of root config directory %q", "dummy"),
   392  		},
   393  		{
   394  			name:        "invalid_absolute_path",
   395  			dir:         "dummy",
   396  			path:        []string{"/a", "..", ".."},
   397  			expectedErr: fmt.Sprintf("is outside of root config directory %q", "dummy"),
   398  		},
   399  	} {
   400  		tc := tc
   401  		t.Run(tc.name, func(t *testing.T) {
   402  			SetDir(tc.dir)
   403  			f, err := Path(tc.path...)
   404  			assert.Equal(t, f, tc.expected)
   405  			if tc.expectedErr == "" {
   406  				assert.NilError(t, err)
   407  			} else {
   408  				assert.ErrorContains(t, err, tc.expectedErr)
   409  			}
   410  		})
   411  	}
   412  
   413  	SetDir(oldDir)
   414  }