github.com/moby/docker@v26.1.3+incompatible/daemon/config/config_test.go (about)

     1  package config // import "github.com/docker/docker/daemon/config"
     2  
     3  import (
     4  	"encoding/json"
     5  	"os"
     6  	"path/filepath"
     7  	"reflect"
     8  	"strings"
     9  	"testing"
    10  
    11  	"dario.cat/mergo"
    12  	"github.com/docker/docker/api"
    13  	"github.com/docker/docker/libnetwork/ipamutils"
    14  	"github.com/docker/docker/opts"
    15  	"github.com/google/go-cmp/cmp"
    16  	"github.com/google/go-cmp/cmp/cmpopts"
    17  	"github.com/spf13/pflag"
    18  	"golang.org/x/text/encoding"
    19  	"golang.org/x/text/encoding/unicode"
    20  	"gotest.tools/v3/assert"
    21  	is "gotest.tools/v3/assert/cmp"
    22  	"gotest.tools/v3/skip"
    23  )
    24  
    25  func makeConfigFile(t *testing.T, content string) string {
    26  	t.Helper()
    27  	name := filepath.Join(t.TempDir(), "daemon.json")
    28  	err := os.WriteFile(name, []byte(content), 0o666)
    29  	assert.NilError(t, err)
    30  	return name
    31  }
    32  
    33  func TestDaemonConfigurationNotFound(t *testing.T) {
    34  	_, err := MergeDaemonConfigurations(&Config{}, nil, "/tmp/foo-bar-baz-docker")
    35  	assert.Check(t, os.IsNotExist(err), "got: %[1]T: %[1]v", err)
    36  }
    37  
    38  func TestDaemonBrokenConfiguration(t *testing.T) {
    39  	configFile := makeConfigFile(t, `{"Debug": tru`)
    40  
    41  	_, err := MergeDaemonConfigurations(&Config{}, nil, configFile)
    42  	assert.ErrorContains(t, err, `invalid character ' ' in literal true`)
    43  }
    44  
    45  // TestDaemonConfigurationUnicodeVariations feeds various variations of Unicode into the JSON parser, ensuring that we
    46  // respect a BOM and otherwise default to UTF-8.
    47  func TestDaemonConfigurationUnicodeVariations(t *testing.T) {
    48  	jsonData := `{"debug": true}`
    49  
    50  	testCases := []struct {
    51  		name     string
    52  		encoding encoding.Encoding
    53  	}{
    54  		{
    55  			name:     "UTF-8",
    56  			encoding: unicode.UTF8,
    57  		},
    58  		{
    59  			name:     "UTF-8 (with BOM)",
    60  			encoding: unicode.UTF8BOM,
    61  		},
    62  		{
    63  			name:     "UTF-16 (BE with BOM)",
    64  			encoding: unicode.UTF16(unicode.BigEndian, unicode.UseBOM),
    65  		},
    66  		{
    67  			name:     "UTF-16 (LE with BOM)",
    68  			encoding: unicode.UTF16(unicode.LittleEndian, unicode.UseBOM),
    69  		},
    70  	}
    71  	for _, tc := range testCases {
    72  		t.Run(tc.name, func(t *testing.T) {
    73  			encodedJson, err := tc.encoding.NewEncoder().String(jsonData)
    74  			assert.NilError(t, err)
    75  			configFile := makeConfigFile(t, encodedJson)
    76  			_, err = MergeDaemonConfigurations(&Config{}, nil, configFile)
    77  			assert.NilError(t, err)
    78  		})
    79  	}
    80  }
    81  
    82  // TestDaemonConfigurationInvalidUnicode ensures that the JSON parser returns a useful error message if malformed UTF-8
    83  // is provided.
    84  func TestDaemonConfigurationInvalidUnicode(t *testing.T) {
    85  	configFileBOM := makeConfigFile(t, "\xef\xbb\xbf{\"debug\": true}\xff")
    86  	_, err := MergeDaemonConfigurations(&Config{}, nil, configFileBOM)
    87  	assert.ErrorIs(t, err, encoding.ErrInvalidUTF8)
    88  
    89  	configFileNoBOM := makeConfigFile(t, "{\"debug\": true}\xff")
    90  	_, err = MergeDaemonConfigurations(&Config{}, nil, configFileNoBOM)
    91  	assert.ErrorIs(t, err, encoding.ErrInvalidUTF8)
    92  }
    93  
    94  func TestFindConfigurationConflicts(t *testing.T) {
    95  	config := map[string]interface{}{"authorization-plugins": "foobar"}
    96  	flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
    97  
    98  	flags.String("authorization-plugins", "", "")
    99  	assert.Check(t, flags.Set("authorization-plugins", "asdf"))
   100  	assert.Check(t, is.ErrorContains(findConfigurationConflicts(config, flags), "authorization-plugins: (from flag: asdf, from file: foobar)"))
   101  }
   102  
   103  func TestFindConfigurationConflictsWithNamedOptions(t *testing.T) {
   104  	config := map[string]interface{}{"hosts": []string{"qwer"}}
   105  	flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
   106  
   107  	var hosts []string
   108  	flags.VarP(opts.NewNamedListOptsRef("hosts", &hosts, opts.ValidateHost), "host", "H", "Daemon socket(s) to connect to")
   109  	assert.Check(t, flags.Set("host", "tcp://127.0.0.1:4444"))
   110  	assert.Check(t, flags.Set("host", "unix:///var/run/docker.sock"))
   111  	assert.Check(t, is.ErrorContains(findConfigurationConflicts(config, flags), "hosts"))
   112  }
   113  
   114  func TestDaemonConfigurationMergeConflicts(t *testing.T) {
   115  	configFile := makeConfigFile(t, `{"debug": true}`)
   116  
   117  	flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
   118  	flags.Bool("debug", false, "")
   119  	assert.Check(t, flags.Set("debug", "false"))
   120  
   121  	_, err := MergeDaemonConfigurations(&Config{}, flags, configFile)
   122  	if err == nil {
   123  		t.Fatal("expected error, got nil")
   124  	}
   125  	if !strings.Contains(err.Error(), "debug") {
   126  		t.Fatalf("expected debug conflict, got %v", err)
   127  	}
   128  }
   129  
   130  func TestDaemonConfigurationMergeConcurrent(t *testing.T) {
   131  	configFile := makeConfigFile(t, `{"max-concurrent-downloads": 1}`)
   132  
   133  	_, err := MergeDaemonConfigurations(&Config{}, nil, configFile)
   134  	assert.NilError(t, err)
   135  }
   136  
   137  func TestDaemonConfigurationMergeConcurrentError(t *testing.T) {
   138  	configFile := makeConfigFile(t, `{"max-concurrent-downloads": -1}`)
   139  
   140  	_, err := MergeDaemonConfigurations(&Config{}, nil, configFile)
   141  	assert.ErrorContains(t, err, `invalid max concurrent downloads: -1`)
   142  }
   143  
   144  func TestDaemonConfigurationMergeConflictsWithInnerStructs(t *testing.T) {
   145  	configFile := makeConfigFile(t, `{"tlscacert": "/etc/certificates/ca.pem"}`)
   146  
   147  	flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
   148  	flags.String("tlscacert", "", "")
   149  	assert.Check(t, flags.Set("tlscacert", "~/.docker/ca.pem"))
   150  
   151  	_, err := MergeDaemonConfigurations(&Config{}, flags, configFile)
   152  	assert.ErrorContains(t, err, `the following directives are specified both as a flag and in the configuration file: tlscacert`)
   153  }
   154  
   155  // TestDaemonConfigurationMergeDefaultAddressPools is a regression test for #40711.
   156  func TestDaemonConfigurationMergeDefaultAddressPools(t *testing.T) {
   157  	emptyConfigFile := makeConfigFile(t, `{}`)
   158  	configFile := makeConfigFile(t, `{"default-address-pools":[{"base": "10.123.0.0/16", "size": 24 }]}`)
   159  
   160  	expected := []*ipamutils.NetworkToSplit{{Base: "10.123.0.0/16", Size: 24}}
   161  
   162  	t.Run("empty config file", func(t *testing.T) {
   163  		conf := Config{}
   164  		flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
   165  		flags.Var(&conf.NetworkConfig.DefaultAddressPools, "default-address-pool", "")
   166  		assert.Check(t, flags.Set("default-address-pool", "base=10.123.0.0/16,size=24"))
   167  
   168  		config, err := MergeDaemonConfigurations(&conf, flags, emptyConfigFile)
   169  		assert.NilError(t, err)
   170  		assert.DeepEqual(t, config.DefaultAddressPools.Value(), expected)
   171  	})
   172  
   173  	t.Run("config file", func(t *testing.T) {
   174  		conf := Config{}
   175  		flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
   176  		flags.Var(&conf.NetworkConfig.DefaultAddressPools, "default-address-pool", "")
   177  
   178  		config, err := MergeDaemonConfigurations(&conf, flags, configFile)
   179  		assert.NilError(t, err)
   180  		assert.DeepEqual(t, config.DefaultAddressPools.Value(), expected)
   181  	})
   182  
   183  	t.Run("with conflicting options", func(t *testing.T) {
   184  		conf := Config{}
   185  		flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
   186  		flags.Var(&conf.NetworkConfig.DefaultAddressPools, "default-address-pool", "")
   187  		assert.Check(t, flags.Set("default-address-pool", "base=10.123.0.0/16,size=24"))
   188  
   189  		_, err := MergeDaemonConfigurations(&conf, flags, configFile)
   190  		assert.ErrorContains(t, err, "the following directives are specified both as a flag and in the configuration file")
   191  		assert.ErrorContains(t, err, "default-address-pools")
   192  	})
   193  }
   194  
   195  func TestFindConfigurationConflictsWithUnknownKeys(t *testing.T) {
   196  	config := map[string]interface{}{"tls-verify": "true"}
   197  	flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
   198  
   199  	flags.Bool("tlsverify", false, "")
   200  	err := findConfigurationConflicts(config, flags)
   201  	assert.ErrorContains(t, err, "the following directives don't match any configuration option: tls-verify")
   202  }
   203  
   204  func TestFindConfigurationConflictsWithMergedValues(t *testing.T) {
   205  	var hosts []string
   206  	config := map[string]interface{}{"hosts": "tcp://127.0.0.1:2345"}
   207  	flags := pflag.NewFlagSet("base", pflag.ContinueOnError)
   208  	flags.VarP(opts.NewNamedListOptsRef("hosts", &hosts, nil), "host", "H", "")
   209  
   210  	err := findConfigurationConflicts(config, flags)
   211  	assert.NilError(t, err)
   212  
   213  	assert.Check(t, flags.Set("host", "unix:///var/run/docker.sock"))
   214  	err = findConfigurationConflicts(config, flags)
   215  	assert.ErrorContains(t, err, "hosts: (from flag: [unix:///var/run/docker.sock], from file: tcp://127.0.0.1:2345)")
   216  }
   217  
   218  func TestValidateConfigurationErrors(t *testing.T) {
   219  	testCases := []struct {
   220  		name        string
   221  		field       string
   222  		config      *Config
   223  		expectedErr string
   224  	}{
   225  		{
   226  			name: "single label without value",
   227  			config: &Config{
   228  				CommonConfig: CommonConfig{
   229  					Labels: []string{"one"},
   230  				},
   231  			},
   232  			expectedErr: "bad attribute format: one",
   233  		},
   234  		{
   235  			name: "multiple label without value",
   236  			config: &Config{
   237  				CommonConfig: CommonConfig{
   238  					Labels: []string{"foo=bar", "one"},
   239  				},
   240  			},
   241  			expectedErr: "bad attribute format: one",
   242  		},
   243  		{
   244  			name: "single DNSSearch",
   245  			config: &Config{
   246  				CommonConfig: CommonConfig{
   247  					DNSConfig: DNSConfig{
   248  						DNSSearch: []string{"123456"},
   249  					},
   250  				},
   251  			},
   252  			expectedErr: "123456 is not a valid domain",
   253  		},
   254  		{
   255  			name: "multiple DNSSearch",
   256  			config: &Config{
   257  				CommonConfig: CommonConfig{
   258  					DNSConfig: DNSConfig{
   259  						DNSSearch: []string{"a.b.c", "123456"},
   260  					},
   261  				},
   262  			},
   263  			expectedErr: "123456 is not a valid domain",
   264  		},
   265  		{
   266  			name: "negative MTU",
   267  			config: &Config{
   268  				CommonConfig: CommonConfig{
   269  					BridgeConfig: BridgeConfig{
   270  						DefaultBridgeConfig: DefaultBridgeConfig{
   271  							MTU: -10,
   272  						},
   273  					},
   274  				},
   275  			},
   276  			expectedErr: "invalid default MTU: -10",
   277  		},
   278  		{
   279  			name: "negative max-concurrent-downloads",
   280  			config: &Config{
   281  				CommonConfig: CommonConfig{
   282  					MaxConcurrentDownloads: -10,
   283  				},
   284  			},
   285  			expectedErr: "invalid max concurrent downloads: -10",
   286  		},
   287  		{
   288  			name: "negative max-concurrent-uploads",
   289  			config: &Config{
   290  				CommonConfig: CommonConfig{
   291  					MaxConcurrentUploads: -10,
   292  				},
   293  			},
   294  			expectedErr: "invalid max concurrent uploads: -10",
   295  		},
   296  		{
   297  			name: "negative max-download-attempts",
   298  			config: &Config{
   299  				CommonConfig: CommonConfig{
   300  					MaxDownloadAttempts: -10,
   301  				},
   302  			},
   303  			expectedErr: "invalid max download attempts: -10",
   304  		},
   305  		// TODO(thaJeztah) temporarily excluding this test as it assumes defaults are set before validating and applying updated configs
   306  		/*
   307  			{
   308  				name:  "zero max-download-attempts",
   309  				field: "MaxDownloadAttempts",
   310  				config: &Config{
   311  					CommonConfig: CommonConfig{
   312  						MaxDownloadAttempts: 0,
   313  					},
   314  				},
   315  				expectedErr: "invalid max download attempts: 0",
   316  			},
   317  		*/
   318  		{
   319  			name: "generic resource without =",
   320  			config: &Config{
   321  				CommonConfig: CommonConfig{
   322  					NodeGenericResources: []string{"foo"},
   323  				},
   324  			},
   325  			expectedErr: "could not parse GenericResource: incorrect term foo, missing '=' or malformed expression",
   326  		},
   327  		{
   328  			name: "generic resource mixed named and discrete",
   329  			config: &Config{
   330  				CommonConfig: CommonConfig{
   331  					NodeGenericResources: []string{"foo=bar", "foo=1"},
   332  				},
   333  			},
   334  			expectedErr: "could not parse GenericResource: mixed discrete and named resources in expression 'foo=[bar 1]'",
   335  		},
   336  		{
   337  			name: "with invalid hosts",
   338  			config: &Config{
   339  				CommonConfig: CommonConfig{
   340  					Hosts: []string{"127.0.0.1:2375/path"},
   341  				},
   342  			},
   343  			expectedErr: "invalid bind address (127.0.0.1:2375/path): should not contain a path element",
   344  		},
   345  		{
   346  			name: "with invalid log-level",
   347  			config: &Config{
   348  				CommonConfig: CommonConfig{
   349  					LogLevel: "foobar",
   350  				},
   351  			},
   352  			expectedErr: "invalid logging level: foobar",
   353  		},
   354  	}
   355  	for _, tc := range testCases {
   356  		t.Run(tc.name, func(t *testing.T) {
   357  			cfg, err := New()
   358  			assert.NilError(t, err)
   359  			if tc.field != "" {
   360  				assert.Check(t, mergo.Merge(cfg, tc.config, mergo.WithOverride, withForceOverwrite(tc.field)))
   361  			} else {
   362  				assert.Check(t, mergo.Merge(cfg, tc.config, mergo.WithOverride))
   363  			}
   364  			err = Validate(cfg)
   365  			assert.Error(t, err, tc.expectedErr)
   366  		})
   367  	}
   368  }
   369  
   370  func withForceOverwrite(fieldName string) func(config *mergo.Config) {
   371  	return mergo.WithTransformers(overwriteTransformer{fieldName: fieldName})
   372  }
   373  
   374  type overwriteTransformer struct {
   375  	fieldName string
   376  }
   377  
   378  func (tf overwriteTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error {
   379  	if typ == reflect.TypeOf(CommonConfig{}) {
   380  		return func(dst, src reflect.Value) error {
   381  			dst.FieldByName(tf.fieldName).Set(src.FieldByName(tf.fieldName))
   382  			return nil
   383  		}
   384  	}
   385  	return nil
   386  }
   387  
   388  func TestValidateConfiguration(t *testing.T) {
   389  	testCases := []struct {
   390  		name   string
   391  		field  string
   392  		config *Config
   393  	}{
   394  		{
   395  			name:  "with label",
   396  			field: "Labels",
   397  			config: &Config{
   398  				CommonConfig: CommonConfig{
   399  					Labels: []string{"one=two"},
   400  				},
   401  			},
   402  		},
   403  		{
   404  			name:  "with dns-search",
   405  			field: "DNSConfig",
   406  			config: &Config{
   407  				CommonConfig: CommonConfig{
   408  					DNSConfig: DNSConfig{
   409  						DNSSearch: []string{"a.b.c"},
   410  					},
   411  				},
   412  			},
   413  		},
   414  		{
   415  			name:  "with mtu",
   416  			field: "MTU",
   417  			config: &Config{
   418  				CommonConfig: CommonConfig{
   419  					BridgeConfig: BridgeConfig{
   420  						DefaultBridgeConfig: DefaultBridgeConfig{
   421  							MTU: 1234,
   422  						},
   423  					},
   424  				},
   425  			},
   426  		},
   427  		{
   428  			name:  "with max-concurrent-downloads",
   429  			field: "MaxConcurrentDownloads",
   430  			config: &Config{
   431  				CommonConfig: CommonConfig{
   432  					MaxConcurrentDownloads: 4,
   433  				},
   434  			},
   435  		},
   436  		{
   437  			name:  "with max-concurrent-uploads",
   438  			field: "MaxConcurrentUploads",
   439  			config: &Config{
   440  				CommonConfig: CommonConfig{
   441  					MaxConcurrentUploads: 4,
   442  				},
   443  			},
   444  		},
   445  		{
   446  			name:  "with max-download-attempts",
   447  			field: "MaxDownloadAttempts",
   448  			config: &Config{
   449  				CommonConfig: CommonConfig{
   450  					MaxDownloadAttempts: 4,
   451  				},
   452  			},
   453  		},
   454  		{
   455  			name:  "with multiple node generic resources",
   456  			field: "NodeGenericResources",
   457  			config: &Config{
   458  				CommonConfig: CommonConfig{
   459  					NodeGenericResources: []string{"foo=bar", "foo=baz"},
   460  				},
   461  			},
   462  		},
   463  		{
   464  			name:  "with node generic resources",
   465  			field: "NodeGenericResources",
   466  			config: &Config{
   467  				CommonConfig: CommonConfig{
   468  					NodeGenericResources: []string{"foo=1"},
   469  				},
   470  			},
   471  		},
   472  		{
   473  			name:  "with hosts",
   474  			field: "Hosts",
   475  			config: &Config{
   476  				CommonConfig: CommonConfig{
   477  					Hosts: []string{"tcp://127.0.0.1:2375"},
   478  				},
   479  			},
   480  		},
   481  		{
   482  			name:  "with log-level warn",
   483  			field: "LogLevel",
   484  			config: &Config{
   485  				CommonConfig: CommonConfig{
   486  					LogLevel: "warn",
   487  				},
   488  			},
   489  		},
   490  	}
   491  	for _, tc := range testCases {
   492  		t.Run(tc.name, func(t *testing.T) {
   493  			// Start with a config with all defaults set, so that we only
   494  			cfg, err := New()
   495  			assert.NilError(t, err)
   496  			assert.Check(t, mergo.Merge(cfg, tc.config, mergo.WithOverride))
   497  
   498  			// Check that the override happened :)
   499  			assert.Check(t, is.DeepEqual(cfg, tc.config, field(tc.field)))
   500  			err = Validate(cfg)
   501  			assert.NilError(t, err)
   502  		})
   503  	}
   504  }
   505  
   506  func TestValidateMinAPIVersion(t *testing.T) {
   507  	t.Parallel()
   508  	tests := []struct {
   509  		doc         string
   510  		input       string
   511  		expectedErr string
   512  	}{
   513  		{
   514  			doc:         "empty",
   515  			expectedErr: "value is empty",
   516  		},
   517  		{
   518  			doc:         "with prefix",
   519  			input:       "v1.43",
   520  			expectedErr: `API version must be provided without "v" prefix`,
   521  		},
   522  		{
   523  			doc:         "major only",
   524  			input:       "1",
   525  			expectedErr: `minimum supported API version is`,
   526  		},
   527  		{
   528  			doc:         "too low",
   529  			input:       "1.0",
   530  			expectedErr: `minimum supported API version is`,
   531  		},
   532  		{
   533  			doc:         "minor too high",
   534  			input:       "1.99",
   535  			expectedErr: `maximum supported API version is`,
   536  		},
   537  		{
   538  			doc:         "major too high",
   539  			input:       "9.0",
   540  			expectedErr: `maximum supported API version is`,
   541  		},
   542  		{
   543  			doc:   "current version",
   544  			input: api.DefaultVersion,
   545  		},
   546  	}
   547  
   548  	for _, tc := range tests {
   549  		tc := tc
   550  		t.Run(tc.doc, func(t *testing.T) {
   551  			err := ValidateMinAPIVersion(tc.input)
   552  			if tc.expectedErr != "" {
   553  				assert.Check(t, is.ErrorContains(err, tc.expectedErr))
   554  			} else {
   555  				assert.Check(t, err)
   556  			}
   557  		})
   558  	}
   559  
   560  }
   561  
   562  func TestConfigInvalidDNS(t *testing.T) {
   563  	tests := []struct {
   564  		doc         string
   565  		input       string
   566  		expectedErr string
   567  	}{
   568  		{
   569  			doc:         "single DNS, invalid IP-address",
   570  			input:       `{"dns": ["1.1.1.1o"]}`,
   571  			expectedErr: `invalid IP address: 1.1.1.1o`,
   572  		},
   573  		{
   574  			doc:         "multiple DNS, invalid IP-address",
   575  			input:       `{"dns": ["2.2.2.2", "1.1.1.1o"]}`,
   576  			expectedErr: `invalid IP address: 1.1.1.1o`,
   577  		},
   578  	}
   579  
   580  	for _, tc := range tests {
   581  		tc := tc
   582  		t.Run(tc.doc, func(t *testing.T) {
   583  			var cfg Config
   584  			err := json.Unmarshal([]byte(tc.input), &cfg)
   585  			assert.Check(t, is.Error(err, tc.expectedErr))
   586  		})
   587  	}
   588  }
   589  
   590  func field(field string) cmp.Option {
   591  	tmp := reflect.TypeOf(Config{})
   592  	ignoreFields := make([]string, 0, tmp.NumField())
   593  	for i := 0; i < tmp.NumField(); i++ {
   594  		if tmp.Field(i).Name != field {
   595  			ignoreFields = append(ignoreFields, tmp.Field(i).Name)
   596  		}
   597  	}
   598  	return cmpopts.IgnoreFields(Config{}, ignoreFields...)
   599  }
   600  
   601  // TestReloadSetConfigFileNotExist tests that when `--config-file` is set, and it doesn't exist the `Reload` function
   602  // returns an error.
   603  func TestReloadSetConfigFileNotExist(t *testing.T) {
   604  	configFile := "/tmp/blabla/not/exists/config.json"
   605  	flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
   606  	flags.String("config-file", "", "")
   607  	assert.Check(t, flags.Set("config-file", configFile))
   608  
   609  	err := Reload(configFile, flags, func(c *Config) {})
   610  	assert.Check(t, is.ErrorContains(err, "unable to configure the Docker daemon with file"))
   611  }
   612  
   613  // TestReloadDefaultConfigNotExist tests that if the default configuration file doesn't exist the daemon still will
   614  // still be reloaded.
   615  func TestReloadDefaultConfigNotExist(t *testing.T) {
   616  	skip.If(t, os.Getuid() != 0, "skipping test that requires root")
   617  	defaultConfigFile := "/tmp/blabla/not/exists/daemon.json"
   618  	flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
   619  	flags.String("config-file", defaultConfigFile, "")
   620  	reloaded := false
   621  	err := Reload(defaultConfigFile, flags, func(c *Config) {
   622  		reloaded = true
   623  	})
   624  	assert.Check(t, err)
   625  	assert.Check(t, reloaded)
   626  }
   627  
   628  // TestReloadBadDefaultConfig tests that when `--config-file` is not set and the default configuration file exists and
   629  // is bad, an error is returned.
   630  func TestReloadBadDefaultConfig(t *testing.T) {
   631  	configFile := makeConfigFile(t, `{wrong: "configuration"}`)
   632  
   633  	flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
   634  	flags.String("config-file", configFile, "")
   635  	reloaded := false
   636  	err := Reload(configFile, flags, func(c *Config) {
   637  		reloaded = true
   638  	})
   639  	assert.Check(t, is.ErrorContains(err, "unable to configure the Docker daemon with file"))
   640  	assert.Check(t, reloaded == false)
   641  }
   642  
   643  func TestReloadWithConflictingLabels(t *testing.T) {
   644  	configFile := makeConfigFile(t, `{"labels": ["foo=bar", "foo=baz"]}`)
   645  
   646  	var lbls []string
   647  	flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
   648  	flags.String("config-file", configFile, "")
   649  	flags.StringSlice("labels", lbls, "")
   650  	reloaded := false
   651  	err := Reload(configFile, flags, func(c *Config) {
   652  		reloaded = true
   653  	})
   654  	assert.Check(t, is.ErrorContains(err, "conflict labels for foo=baz and foo=bar"))
   655  	assert.Check(t, reloaded == false)
   656  }
   657  
   658  func TestReloadWithDuplicateLabels(t *testing.T) {
   659  	configFile := makeConfigFile(t, `{"labels": ["foo=the-same", "foo=the-same"]}`)
   660  
   661  	var lbls []string
   662  	flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
   663  	flags.String("config-file", configFile, "")
   664  	flags.StringSlice("labels", lbls, "")
   665  	reloaded := false
   666  	err := Reload(configFile, flags, func(c *Config) {
   667  		reloaded = true
   668  		assert.Check(t, is.DeepEqual(c.Labels, []string{"foo=the-same"}))
   669  	})
   670  	assert.Check(t, err)
   671  	assert.Check(t, reloaded)
   672  }
   673  
   674  func TestMaskURLCredentials(t *testing.T) {
   675  	tests := []struct {
   676  		rawURL    string
   677  		maskedURL string
   678  	}{
   679  		{
   680  			rawURL:    "",
   681  			maskedURL: "",
   682  		}, {
   683  			rawURL:    "invalidURL",
   684  			maskedURL: "invalidURL",
   685  		}, {
   686  			rawURL:    "http://proxy.example.com:80/",
   687  			maskedURL: "http://proxy.example.com:80/",
   688  		}, {
   689  			rawURL:    "http://USER:PASSWORD@proxy.example.com:80/",
   690  			maskedURL: "http://xxxxx:xxxxx@proxy.example.com:80/",
   691  		}, {
   692  			rawURL:    "http://PASSWORD:PASSWORD@proxy.example.com:80/",
   693  			maskedURL: "http://xxxxx:xxxxx@proxy.example.com:80/",
   694  		}, {
   695  			rawURL:    "http://USER:@proxy.example.com:80/",
   696  			maskedURL: "http://xxxxx:xxxxx@proxy.example.com:80/",
   697  		}, {
   698  			rawURL:    "http://:PASSWORD@proxy.example.com:80/",
   699  			maskedURL: "http://xxxxx:xxxxx@proxy.example.com:80/",
   700  		}, {
   701  			rawURL:    "http://USER@docker:password@proxy.example.com:80/",
   702  			maskedURL: "http://xxxxx:xxxxx@proxy.example.com:80/",
   703  		}, {
   704  			rawURL:    "http://USER%40docker:password@proxy.example.com:80/",
   705  			maskedURL: "http://xxxxx:xxxxx@proxy.example.com:80/",
   706  		}, {
   707  			rawURL:    "http://USER%40docker:pa%3Fsword@proxy.example.com:80/",
   708  			maskedURL: "http://xxxxx:xxxxx@proxy.example.com:80/",
   709  		}, {
   710  			rawURL:    "http://USER%40docker:pa%3Fsword@proxy.example.com:80/hello%20world",
   711  			maskedURL: "http://xxxxx:xxxxx@proxy.example.com:80/hello%20world",
   712  		},
   713  	}
   714  	for _, test := range tests {
   715  		maskedURL := MaskCredentials(test.rawURL)
   716  		assert.Equal(t, maskedURL, test.maskedURL)
   717  	}
   718  }