github.com/afbjorklund/moby@v20.10.5+incompatible/daemon/config/config_test.go (about)

     1  package config // import "github.com/docker/docker/daemon/config"
     2  
     3  import (
     4  	"io/ioutil"
     5  	"os"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/docker/docker/daemon/discovery"
    10  	"github.com/docker/docker/opts"
    11  	"github.com/docker/libnetwork/ipamutils"
    12  	"github.com/spf13/pflag"
    13  	"gotest.tools/v3/assert"
    14  	is "gotest.tools/v3/assert/cmp"
    15  	"gotest.tools/v3/fs"
    16  	"gotest.tools/v3/skip"
    17  )
    18  
    19  func TestDaemonConfigurationNotFound(t *testing.T) {
    20  	_, err := MergeDaemonConfigurations(&Config{}, nil, "/tmp/foo-bar-baz-docker")
    21  	if err == nil || !os.IsNotExist(err) {
    22  		t.Fatalf("expected does not exist error, got %v", err)
    23  	}
    24  }
    25  
    26  func TestDaemonBrokenConfiguration(t *testing.T) {
    27  	f, err := ioutil.TempFile("", "docker-config-")
    28  	if err != nil {
    29  		t.Fatal(err)
    30  	}
    31  
    32  	configFile := f.Name()
    33  	f.Write([]byte(`{"Debug": tru`))
    34  	f.Close()
    35  
    36  	_, err = MergeDaemonConfigurations(&Config{}, nil, configFile)
    37  	if err == nil {
    38  		t.Fatalf("expected error, got %v", err)
    39  	}
    40  }
    41  
    42  func TestParseClusterAdvertiseSettings(t *testing.T) {
    43  	_, err := ParseClusterAdvertiseSettings("something", "")
    44  	if err != discovery.ErrDiscoveryDisabled {
    45  		t.Fatalf("expected discovery disabled error, got %v\n", err)
    46  	}
    47  
    48  	_, err = ParseClusterAdvertiseSettings("", "something")
    49  	if err == nil {
    50  		t.Fatalf("expected discovery store error, got %v\n", err)
    51  	}
    52  
    53  	_, err = ParseClusterAdvertiseSettings("etcd", "127.0.0.1:8080")
    54  	if err != nil {
    55  		t.Fatal(err)
    56  	}
    57  }
    58  
    59  func TestFindConfigurationConflicts(t *testing.T) {
    60  	config := map[string]interface{}{"authorization-plugins": "foobar"}
    61  	flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
    62  
    63  	flags.String("authorization-plugins", "", "")
    64  	assert.Check(t, flags.Set("authorization-plugins", "asdf"))
    65  	assert.Check(t, is.ErrorContains(findConfigurationConflicts(config, flags), "authorization-plugins: (from flag: asdf, from file: foobar)"))
    66  }
    67  
    68  func TestFindConfigurationConflictsWithNamedOptions(t *testing.T) {
    69  	config := map[string]interface{}{"hosts": []string{"qwer"}}
    70  	flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
    71  
    72  	var hosts []string
    73  	flags.VarP(opts.NewNamedListOptsRef("hosts", &hosts, opts.ValidateHost), "host", "H", "Daemon socket(s) to connect to")
    74  	assert.Check(t, flags.Set("host", "tcp://127.0.0.1:4444"))
    75  	assert.Check(t, flags.Set("host", "unix:///var/run/docker.sock"))
    76  	assert.Check(t, is.ErrorContains(findConfigurationConflicts(config, flags), "hosts"))
    77  }
    78  
    79  func TestDaemonConfigurationMergeConflicts(t *testing.T) {
    80  	f, err := ioutil.TempFile("", "docker-config-")
    81  	if err != nil {
    82  		t.Fatal(err)
    83  	}
    84  
    85  	configFile := f.Name()
    86  	f.Write([]byte(`{"debug": true}`))
    87  	f.Close()
    88  
    89  	flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
    90  	flags.Bool("debug", false, "")
    91  	flags.Set("debug", "false")
    92  
    93  	_, err = MergeDaemonConfigurations(&Config{}, flags, configFile)
    94  	if err == nil {
    95  		t.Fatal("expected error, got nil")
    96  	}
    97  	if !strings.Contains(err.Error(), "debug") {
    98  		t.Fatalf("expected debug conflict, got %v", err)
    99  	}
   100  }
   101  
   102  func TestDaemonConfigurationMergeConcurrent(t *testing.T) {
   103  	f, err := ioutil.TempFile("", "docker-config-")
   104  	if err != nil {
   105  		t.Fatal(err)
   106  	}
   107  
   108  	configFile := f.Name()
   109  	f.Write([]byte(`{"max-concurrent-downloads": 1}`))
   110  	f.Close()
   111  
   112  	_, err = MergeDaemonConfigurations(&Config{}, nil, configFile)
   113  	if err != nil {
   114  		t.Fatal("expected error, got nil")
   115  	}
   116  }
   117  
   118  func TestDaemonConfigurationMergeConcurrentError(t *testing.T) {
   119  	f, err := ioutil.TempFile("", "docker-config-")
   120  	if err != nil {
   121  		t.Fatal(err)
   122  	}
   123  
   124  	configFile := f.Name()
   125  	f.Write([]byte(`{"max-concurrent-downloads": -1}`))
   126  	f.Close()
   127  
   128  	_, err = MergeDaemonConfigurations(&Config{}, nil, configFile)
   129  	if err == nil {
   130  		t.Fatalf("expected no error, got error %v", err)
   131  	}
   132  }
   133  
   134  func TestDaemonConfigurationMergeConflictsWithInnerStructs(t *testing.T) {
   135  	f, err := ioutil.TempFile("", "docker-config-")
   136  	if err != nil {
   137  		t.Fatal(err)
   138  	}
   139  
   140  	configFile := f.Name()
   141  	f.Write([]byte(`{"tlscacert": "/etc/certificates/ca.pem"}`))
   142  	f.Close()
   143  
   144  	flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
   145  	flags.String("tlscacert", "", "")
   146  	flags.Set("tlscacert", "~/.docker/ca.pem")
   147  
   148  	_, err = MergeDaemonConfigurations(&Config{}, flags, configFile)
   149  	if err == nil {
   150  		t.Fatal("expected error, got nil")
   151  	}
   152  	if !strings.Contains(err.Error(), "tlscacert") {
   153  		t.Fatalf("expected tlscacert conflict, got %v", err)
   154  	}
   155  }
   156  
   157  // Test for #40711
   158  func TestDaemonConfigurationMergeDefaultAddressPools(t *testing.T) {
   159  	emptyConfigFile := fs.NewFile(t, "config", fs.WithContent(`{}`))
   160  	defer emptyConfigFile.Remove()
   161  	configFile := fs.NewFile(t, "config", fs.WithContent(`{"default-address-pools":[{"base": "10.123.0.0/16", "size": 24 }]}`))
   162  	defer configFile.Remove()
   163  
   164  	expected := []*ipamutils.NetworkToSplit{{Base: "10.123.0.0/16", Size: 24}}
   165  
   166  	t.Run("empty config file", func(t *testing.T) {
   167  		var conf = Config{}
   168  		flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
   169  		flags.Var(&conf.NetworkConfig.DefaultAddressPools, "default-address-pool", "")
   170  		flags.Set("default-address-pool", "base=10.123.0.0/16,size=24")
   171  
   172  		config, err := MergeDaemonConfigurations(&conf, flags, emptyConfigFile.Path())
   173  		assert.NilError(t, err)
   174  		assert.DeepEqual(t, config.DefaultAddressPools.Value(), expected)
   175  	})
   176  
   177  	t.Run("config file", func(t *testing.T) {
   178  		var conf = Config{}
   179  		flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
   180  		flags.Var(&conf.NetworkConfig.DefaultAddressPools, "default-address-pool", "")
   181  
   182  		config, err := MergeDaemonConfigurations(&conf, flags, configFile.Path())
   183  		assert.NilError(t, err)
   184  		assert.DeepEqual(t, config.DefaultAddressPools.Value(), expected)
   185  	})
   186  
   187  	t.Run("with conflicting options", func(t *testing.T) {
   188  		var conf = Config{}
   189  		flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
   190  		flags.Var(&conf.NetworkConfig.DefaultAddressPools, "default-address-pool", "")
   191  		flags.Set("default-address-pool", "base=10.123.0.0/16,size=24")
   192  
   193  		_, err := MergeDaemonConfigurations(&conf, flags, configFile.Path())
   194  		assert.ErrorContains(t, err, "the following directives are specified both as a flag and in the configuration file")
   195  		assert.ErrorContains(t, err, "default-address-pools")
   196  	})
   197  }
   198  
   199  func TestFindConfigurationConflictsWithUnknownKeys(t *testing.T) {
   200  	config := map[string]interface{}{"tls-verify": "true"}
   201  	flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
   202  
   203  	flags.Bool("tlsverify", false, "")
   204  	err := findConfigurationConflicts(config, flags)
   205  	if err == nil {
   206  		t.Fatal("expected error, got nil")
   207  	}
   208  	if !strings.Contains(err.Error(), "the following directives don't match any configuration option: tls-verify") {
   209  		t.Fatalf("expected tls-verify conflict, got %v", err)
   210  	}
   211  }
   212  
   213  func TestFindConfigurationConflictsWithMergedValues(t *testing.T) {
   214  	var hosts []string
   215  	config := map[string]interface{}{"hosts": "tcp://127.0.0.1:2345"}
   216  	flags := pflag.NewFlagSet("base", pflag.ContinueOnError)
   217  	flags.VarP(opts.NewNamedListOptsRef("hosts", &hosts, nil), "host", "H", "")
   218  
   219  	err := findConfigurationConflicts(config, flags)
   220  	if err != nil {
   221  		t.Fatal(err)
   222  	}
   223  
   224  	flags.Set("host", "unix:///var/run/docker.sock")
   225  	err = findConfigurationConflicts(config, flags)
   226  	if err == nil {
   227  		t.Fatal("expected error, got nil")
   228  	}
   229  	if !strings.Contains(err.Error(), "hosts: (from flag: [unix:///var/run/docker.sock], from file: tcp://127.0.0.1:2345)") {
   230  		t.Fatalf("expected hosts conflict, got %v", err)
   231  	}
   232  }
   233  
   234  func TestValidateConfigurationErrors(t *testing.T) {
   235  	intPtr := func(i int) *int { return &i }
   236  
   237  	testCases := []struct {
   238  		name        string
   239  		config      *Config
   240  		expectedErr string
   241  	}{
   242  		{
   243  			name: "single label without value",
   244  			config: &Config{
   245  				CommonConfig: CommonConfig{
   246  					Labels: []string{"one"},
   247  				},
   248  			},
   249  			expectedErr: "bad attribute format: one",
   250  		},
   251  		{
   252  			name: "multiple label without value",
   253  			config: &Config{
   254  				CommonConfig: CommonConfig{
   255  					Labels: []string{"foo=bar", "one"},
   256  				},
   257  			},
   258  			expectedErr: "bad attribute format: one",
   259  		},
   260  		{
   261  			name: "single DNS, invalid IP-address",
   262  			config: &Config{
   263  				CommonConfig: CommonConfig{
   264  					DNSConfig: DNSConfig{
   265  						DNS: []string{"1.1.1.1o"},
   266  					},
   267  				},
   268  			},
   269  			expectedErr: "1.1.1.1o is not an ip address",
   270  		},
   271  		{
   272  			name: "multiple DNS, invalid IP-address",
   273  			config: &Config{
   274  				CommonConfig: CommonConfig{
   275  					DNSConfig: DNSConfig{
   276  						DNS: []string{"2.2.2.2", "1.1.1.1o"},
   277  					},
   278  				},
   279  			},
   280  			expectedErr: "1.1.1.1o is not an ip address",
   281  		},
   282  		{
   283  			name: "single DNSSearch",
   284  			config: &Config{
   285  				CommonConfig: CommonConfig{
   286  					DNSConfig: DNSConfig{
   287  						DNSSearch: []string{"123456"},
   288  					},
   289  				},
   290  			},
   291  			expectedErr: "123456 is not a valid domain",
   292  		},
   293  		{
   294  			name: "multiple DNSSearch",
   295  			config: &Config{
   296  				CommonConfig: CommonConfig{
   297  					DNSConfig: DNSConfig{
   298  						DNSSearch: []string{"a.b.c", "123456"},
   299  					},
   300  				},
   301  			},
   302  			expectedErr: "123456 is not a valid domain",
   303  		},
   304  		{
   305  			name: "negative max-concurrent-downloads",
   306  			config: &Config{
   307  				CommonConfig: CommonConfig{
   308  					MaxConcurrentDownloads: intPtr(-10),
   309  				},
   310  			},
   311  			expectedErr: "invalid max concurrent downloads: -10",
   312  		},
   313  		{
   314  			name: "negative max-concurrent-uploads",
   315  			config: &Config{
   316  				CommonConfig: CommonConfig{
   317  					MaxConcurrentUploads: intPtr(-10),
   318  				},
   319  			},
   320  			expectedErr: "invalid max concurrent uploads: -10",
   321  		},
   322  		{
   323  			name: "negative max-download-attempts",
   324  			config: &Config{
   325  				CommonConfig: CommonConfig{
   326  					MaxDownloadAttempts: intPtr(-10),
   327  				},
   328  			},
   329  			expectedErr: "invalid max download attempts: -10",
   330  		},
   331  		{
   332  			name: "zero max-download-attempts",
   333  			config: &Config{
   334  				CommonConfig: CommonConfig{
   335  					MaxDownloadAttempts: intPtr(0),
   336  				},
   337  			},
   338  			expectedErr: "invalid max download attempts: 0",
   339  		},
   340  		{
   341  			name: "generic resource without =",
   342  			config: &Config{
   343  				CommonConfig: CommonConfig{
   344  					NodeGenericResources: []string{"foo"},
   345  				},
   346  			},
   347  			expectedErr: "could not parse GenericResource: incorrect term foo, missing '=' or malformed expression",
   348  		},
   349  		{
   350  			name: "generic resource mixed named and discrete",
   351  			config: &Config{
   352  				CommonConfig: CommonConfig{
   353  					NodeGenericResources: []string{"foo=bar", "foo=1"},
   354  				},
   355  			},
   356  			expectedErr: "could not parse GenericResource: mixed discrete and named resources in expression 'foo=[bar 1]'",
   357  		},
   358  	}
   359  	for _, tc := range testCases {
   360  		t.Run(tc.name, func(t *testing.T) {
   361  			err := Validate(tc.config)
   362  			assert.Error(t, err, tc.expectedErr)
   363  		})
   364  	}
   365  }
   366  
   367  func TestValidateConfiguration(t *testing.T) {
   368  	intPtr := func(i int) *int { return &i }
   369  
   370  	testCases := []struct {
   371  		name   string
   372  		config *Config
   373  	}{
   374  		{
   375  			name: "with label",
   376  			config: &Config{
   377  				CommonConfig: CommonConfig{
   378  					Labels: []string{"one=two"},
   379  				},
   380  			},
   381  		},
   382  		{
   383  			name: "with dns",
   384  			config: &Config{
   385  				CommonConfig: CommonConfig{
   386  					DNSConfig: DNSConfig{
   387  						DNS: []string{"1.1.1.1"},
   388  					},
   389  				},
   390  			},
   391  		},
   392  		{
   393  			name: "with dns-search",
   394  			config: &Config{
   395  				CommonConfig: CommonConfig{
   396  					DNSConfig: DNSConfig{
   397  						DNSSearch: []string{"a.b.c"},
   398  					},
   399  				},
   400  			},
   401  		},
   402  		{
   403  			name: "with max-concurrent-downloads",
   404  			config: &Config{
   405  				CommonConfig: CommonConfig{
   406  					MaxConcurrentDownloads: intPtr(4),
   407  				},
   408  			},
   409  		},
   410  		{
   411  			name: "with max-concurrent-uploads",
   412  			config: &Config{
   413  				CommonConfig: CommonConfig{
   414  					MaxConcurrentUploads: intPtr(4),
   415  				},
   416  			},
   417  		},
   418  		{
   419  			name: "with max-download-attempts",
   420  			config: &Config{
   421  				CommonConfig: CommonConfig{
   422  					MaxDownloadAttempts: intPtr(4),
   423  				},
   424  			},
   425  		},
   426  		{
   427  			name: "with multiple node generic resources",
   428  			config: &Config{
   429  				CommonConfig: CommonConfig{
   430  					NodeGenericResources: []string{"foo=bar", "foo=baz"},
   431  				},
   432  			},
   433  		},
   434  		{
   435  			name: "with node generic resources",
   436  			config: &Config{
   437  				CommonConfig: CommonConfig{
   438  					NodeGenericResources: []string{"foo=1"},
   439  				},
   440  			},
   441  		},
   442  	}
   443  	for _, tc := range testCases {
   444  		t.Run(tc.name, func(t *testing.T) {
   445  			err := Validate(tc.config)
   446  			assert.NilError(t, err)
   447  		})
   448  	}
   449  }
   450  
   451  func TestModifiedDiscoverySettings(t *testing.T) {
   452  	cases := []struct {
   453  		current  *Config
   454  		modified *Config
   455  		expected bool
   456  	}{
   457  		{
   458  			current:  discoveryConfig("foo", "bar", map[string]string{}),
   459  			modified: discoveryConfig("foo", "bar", map[string]string{}),
   460  			expected: false,
   461  		},
   462  		{
   463  			current:  discoveryConfig("foo", "bar", map[string]string{"foo": "bar"}),
   464  			modified: discoveryConfig("foo", "bar", map[string]string{"foo": "bar"}),
   465  			expected: false,
   466  		},
   467  		{
   468  			current:  discoveryConfig("foo", "bar", map[string]string{}),
   469  			modified: discoveryConfig("foo", "bar", nil),
   470  			expected: false,
   471  		},
   472  		{
   473  			current:  discoveryConfig("foo", "bar", nil),
   474  			modified: discoveryConfig("foo", "bar", map[string]string{}),
   475  			expected: false,
   476  		},
   477  		{
   478  			current:  discoveryConfig("foo", "bar", nil),
   479  			modified: discoveryConfig("baz", "bar", nil),
   480  			expected: true,
   481  		},
   482  		{
   483  			current:  discoveryConfig("foo", "bar", nil),
   484  			modified: discoveryConfig("foo", "baz", nil),
   485  			expected: true,
   486  		},
   487  		{
   488  			current:  discoveryConfig("foo", "bar", nil),
   489  			modified: discoveryConfig("foo", "bar", map[string]string{"foo": "bar"}),
   490  			expected: true,
   491  		},
   492  	}
   493  
   494  	for _, c := range cases {
   495  		got := ModifiedDiscoverySettings(c.current, c.modified.ClusterStore, c.modified.ClusterAdvertise, c.modified.ClusterOpts)
   496  		if c.expected != got {
   497  			t.Fatalf("expected %v, got %v: current config %v, new config %v", c.expected, got, c.current, c.modified)
   498  		}
   499  	}
   500  }
   501  
   502  func discoveryConfig(backendAddr, advertiseAddr string, opts map[string]string) *Config {
   503  	return &Config{
   504  		CommonConfig: CommonConfig{
   505  			ClusterStore:     backendAddr,
   506  			ClusterAdvertise: advertiseAddr,
   507  			ClusterOpts:      opts,
   508  		},
   509  	}
   510  }
   511  
   512  // TestReloadSetConfigFileNotExist tests that when `--config-file` is set
   513  // and it doesn't exist the `Reload` function returns an error.
   514  func TestReloadSetConfigFileNotExist(t *testing.T) {
   515  	configFile := "/tmp/blabla/not/exists/config.json"
   516  	flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
   517  	flags.String("config-file", "", "")
   518  	flags.Set("config-file", configFile)
   519  
   520  	err := Reload(configFile, flags, func(c *Config) {})
   521  	assert.Check(t, is.ErrorContains(err, "unable to configure the Docker daemon with file"))
   522  }
   523  
   524  // TestReloadDefaultConfigNotExist tests that if the default configuration file
   525  // doesn't exist the daemon still will be reloaded.
   526  func TestReloadDefaultConfigNotExist(t *testing.T) {
   527  	skip.If(t, os.Getuid() != 0, "skipping test that requires root")
   528  	reloaded := false
   529  	configFile := "/etc/docker/daemon.json"
   530  	flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
   531  	flags.String("config-file", configFile, "")
   532  	err := Reload(configFile, flags, func(c *Config) {
   533  		reloaded = true
   534  	})
   535  	assert.Check(t, err)
   536  	assert.Check(t, reloaded)
   537  }
   538  
   539  // TestReloadBadDefaultConfig tests that when `--config-file` is not set
   540  // and the default configuration file exists and is bad return an error
   541  func TestReloadBadDefaultConfig(t *testing.T) {
   542  	f, err := ioutil.TempFile("", "docker-config-")
   543  	if err != nil {
   544  		t.Fatal(err)
   545  	}
   546  
   547  	configFile := f.Name()
   548  	f.Write([]byte(`{wrong: "configuration"}`))
   549  	f.Close()
   550  
   551  	flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
   552  	flags.String("config-file", configFile, "")
   553  	err = Reload(configFile, flags, func(c *Config) {})
   554  	assert.Check(t, is.ErrorContains(err, "unable to configure the Docker daemon with file"))
   555  }
   556  
   557  func TestReloadWithConflictingLabels(t *testing.T) {
   558  	tempFile := fs.NewFile(t, "config", fs.WithContent(`{"labels":["foo=bar","foo=baz"]}`))
   559  	defer tempFile.Remove()
   560  	configFile := tempFile.Path()
   561  
   562  	var lbls []string
   563  	flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
   564  	flags.String("config-file", configFile, "")
   565  	flags.StringSlice("labels", lbls, "")
   566  	err := Reload(configFile, flags, func(c *Config) {})
   567  	assert.Check(t, is.ErrorContains(err, "conflict labels for foo=baz and foo=bar"))
   568  }
   569  
   570  func TestReloadWithDuplicateLabels(t *testing.T) {
   571  	tempFile := fs.NewFile(t, "config", fs.WithContent(`{"labels":["foo=the-same","foo=the-same"]}`))
   572  	defer tempFile.Remove()
   573  	configFile := tempFile.Path()
   574  
   575  	var lbls []string
   576  	flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
   577  	flags.String("config-file", configFile, "")
   578  	flags.StringSlice("labels", lbls, "")
   579  	err := Reload(configFile, flags, func(c *Config) {})
   580  	assert.Check(t, err)
   581  }