github.1git.de/docker/cli@v26.1.3+incompatible/cli/config/config_test.go (about)

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