Merge pull request #19517 from calavera/validate_config_keys

Verify that the configuration keys in the file are valid.
Upstream-commit: 34a83f9f2c0516b3c5e7024393487eee54309401
Component: engine
This commit is contained in:
Phil Estes
2016-01-22 15:01:29 -05:00
8 changed files with 170 additions and 45 deletions
+42 -11
View File
@@ -56,7 +56,6 @@ type CommonConfig struct {
GraphDriver string `json:"storage-driver,omitempty"`
GraphOptions []string `json:"storage-opts,omitempty"`
Labels []string `json:"labels,omitempty"`
LogConfig LogConfig `json:"log-config,omitempty"`
Mtu int `json:"mtu,omitempty"`
Pidfile string `json:"pidfile,omitempty"`
Root string `json:"graph,omitempty"`
@@ -76,12 +75,16 @@ type CommonConfig struct {
// reachable by other hosts.
ClusterAdvertise string `json:"cluster-advertise,omitempty"`
Debug bool `json:"debug,omitempty"`
Hosts []string `json:"hosts,omitempty"`
LogLevel string `json:"log-level,omitempty"`
TLS bool `json:"tls,omitempty"`
TLSVerify bool `json:"tls-verify,omitempty"`
TLSOptions CommonTLSOptions `json:"tls-opts,omitempty"`
Debug bool `json:"debug,omitempty"`
Hosts []string `json:"hosts,omitempty"`
LogLevel string `json:"log-level,omitempty"`
TLS bool `json:"tls,omitempty"`
TLSVerify bool `json:"tlsverify,omitempty"`
// Embedded structs that allow config
// deserialization without the full struct.
CommonTLSOptions
LogConfig
reloadLock sync.Mutex
valuesSet map[string]interface{}
@@ -215,16 +218,44 @@ func configValuesSet(config map[string]interface{}) map[string]interface{} {
}
// findConfigurationConflicts iterates over the provided flags searching for
// duplicated configurations. It returns an error with all the conflicts if
// duplicated configurations and unknown keys. It returns an error with all the conflicts if
// it finds any.
func findConfigurationConflicts(config map[string]interface{}, flags *flag.FlagSet) error {
var conflicts []string
// 1. Search keys from the file that we don't recognize as flags.
unknownKeys := make(map[string]interface{})
for key, value := range config {
flagName := "-" + key
if flag := flags.Lookup(flagName); flag == nil {
unknownKeys[key] = value
}
}
// 2. Discard values that implement NamedOption.
// Their configuration name differs from their flag name, like `labels` and `label`.
unknownNamedConflicts := func(f *flag.Flag) {
if namedOption, ok := f.Value.(opts.NamedOption); ok {
if _, valid := unknownKeys[namedOption.Name()]; valid {
delete(unknownKeys, namedOption.Name())
}
}
}
flags.VisitAll(unknownNamedConflicts)
if len(unknownKeys) > 0 {
var unknown []string
for key := range unknownKeys {
unknown = append(unknown, key)
}
return fmt.Errorf("the following directives don't match any configuration option: %s", strings.Join(unknown, ", "))
}
var conflicts []string
printConflict := func(name string, flagValue, fileValue interface{}) string {
return fmt.Sprintf("%s: (from flag: %v, from file: %v)", name, flagValue, fileValue)
}
collectConflicts := func(f *flag.Flag) {
// 3. Search keys that are present as a flag and as a file option.
duplicatedConflicts := func(f *flag.Flag) {
// search option name in the json configuration payload if the value is a named option
if namedOption, ok := f.Value.(opts.NamedOption); ok {
if optsValue, ok := config[namedOption.Name()]; ok {
@@ -243,7 +274,7 @@ func findConfigurationConflicts(config map[string]interface{}, flags *flag.FlagS
}
}
flags.Visit(collectConflicts)
flags.Visit(duplicatedConflicts)
if len(conflicts) > 0 {
return fmt.Errorf("the following directives are specified both as a flag and in the configuration file: %s", strings.Join(conflicts, ", "))
+44 -11
View File
@@ -89,21 +89,16 @@ func TestFindConfigurationConflicts(t *testing.T) {
config := map[string]interface{}{"authorization-plugins": "foobar"}
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
flags.String([]string{"-authorization-plugins"}, "", "")
if err := flags.Set("-authorization-plugins", "asdf"); err != nil {
t.Fatal(err)
}
err := findConfigurationConflicts(config, flags)
if err != nil {
t.Fatal(err)
}
flags.String([]string{"authorization-plugins"}, "", "")
if err := flags.Set("authorization-plugins", "asdf"); err != nil {
t.Fatal(err)
}
err = findConfigurationConflicts(config, flags)
if err == nil {
t.Fatal("expected error, got nil")
}
if !strings.Contains(err.Error(), "authorization-plugins") {
if !strings.Contains(err.Error(), "authorization-plugins: (from flag: asdf, from file: foobar)") {
t.Fatalf("expected authorization-plugins conflict, got %v", err)
}
}
@@ -175,3 +170,41 @@ func TestDaemonConfigurationMergeConflictsWithInnerStructs(t *testing.T) {
t.Fatalf("expected tlscacert conflict, got %v", err)
}
}
func TestFindConfigurationConflictsWithUnknownKeys(t *testing.T) {
config := map[string]interface{}{"tls-verify": "true"}
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
flags.Bool([]string{"-tlsverify"}, false, "")
err := findConfigurationConflicts(config, flags)
if err == nil {
t.Fatal("expected error, got nil")
}
if !strings.Contains(err.Error(), "the following directives don't match any configuration option: tls-verify") {
t.Fatalf("expected tls-verify conflict, got %v", err)
}
}
func TestFindConfigurationConflictsWithMergedValues(t *testing.T) {
var hosts []string
config := map[string]interface{}{"hosts": "tcp://127.0.0.1:2345"}
base := mflag.NewFlagSet("base", mflag.ContinueOnError)
base.Var(opts.NewNamedListOptsRef("hosts", &hosts, nil), []string{"H", "-host"}, "")
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
mflag.Merge(flags, base)
err := findConfigurationConflicts(config, flags)
if err != nil {
t.Fatal(err)
}
flags.Set("-host", "unix:///var/run/docker.sock")
err = findConfigurationConflicts(config, flags)
if err == nil {
t.Fatal("expected error, got nil")
}
if !strings.Contains(err.Error(), "hosts: (from flag: [unix:///var/run/docker.sock], from file: tcp://127.0.0.1:2345)") {
t.Fatalf("expected hosts conflict, got %v", err)
}
}