github.com/ojongerius/docker@v1.11.2/daemon/config.go (about)

     1  package daemon
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"strings"
    10  	"sync"
    11  
    12  	"github.com/Sirupsen/logrus"
    13  	"github.com/docker/docker/opts"
    14  	"github.com/docker/docker/pkg/discovery"
    15  	flag "github.com/docker/docker/pkg/mflag"
    16  	"github.com/docker/docker/registry"
    17  	"github.com/imdario/mergo"
    18  )
    19  
    20  const (
    21  	defaultNetworkMtu    = 1500
    22  	disableNetworkBridge = "none"
    23  )
    24  
    25  // flatOptions contains configuration keys
    26  // that MUST NOT be parsed as deep structures.
    27  // Use this to differentiate these options
    28  // with others like the ones in CommonTLSOptions.
    29  var flatOptions = map[string]bool{
    30  	"cluster-store-opts": true,
    31  	"log-opts":           true,
    32  }
    33  
    34  // LogConfig represents the default log configuration.
    35  // It includes json tags to deserialize configuration from a file
    36  // using the same names that the flags in the command line uses.
    37  type LogConfig struct {
    38  	Type   string            `json:"log-driver,omitempty"`
    39  	Config map[string]string `json:"log-opts,omitempty"`
    40  }
    41  
    42  // CommonTLSOptions defines TLS configuration for the daemon server.
    43  // It includes json tags to deserialize configuration from a file
    44  // using the same names that the flags in the command line uses.
    45  type CommonTLSOptions struct {
    46  	CAFile   string `json:"tlscacert,omitempty"`
    47  	CertFile string `json:"tlscert,omitempty"`
    48  	KeyFile  string `json:"tlskey,omitempty"`
    49  }
    50  
    51  // CommonConfig defines the configuration of a docker daemon which are
    52  // common across platforms.
    53  // It includes json tags to deserialize configuration from a file
    54  // using the same names that the flags in the command line uses.
    55  type CommonConfig struct {
    56  	AuthorizationPlugins []string            `json:"authorization-plugins,omitempty"` // AuthorizationPlugins holds list of authorization plugins
    57  	AutoRestart          bool                `json:"-"`
    58  	Context              map[string][]string `json:"-"`
    59  	DisableBridge        bool                `json:"-"`
    60  	DNS                  []string            `json:"dns,omitempty"`
    61  	DNSOptions           []string            `json:"dns-opts,omitempty"`
    62  	DNSSearch            []string            `json:"dns-search,omitempty"`
    63  	ExecOptions          []string            `json:"exec-opts,omitempty"`
    64  	ExecRoot             string              `json:"exec-root,omitempty"`
    65  	GraphDriver          string              `json:"storage-driver,omitempty"`
    66  	GraphOptions         []string            `json:"storage-opts,omitempty"`
    67  	Labels               []string            `json:"labels,omitempty"`
    68  	Mtu                  int                 `json:"mtu,omitempty"`
    69  	Pidfile              string              `json:"pidfile,omitempty"`
    70  	RawLogs              bool                `json:"raw-logs,omitempty"`
    71  	Root                 string              `json:"graph,omitempty"`
    72  	SocketGroup          string              `json:"group,omitempty"`
    73  	TrustKeyPath         string              `json:"-"`
    74  
    75  	// ClusterStore is the storage backend used for the cluster information. It is used by both
    76  	// multihost networking (to store networks and endpoints information) and by the node discovery
    77  	// mechanism.
    78  	ClusterStore string `json:"cluster-store,omitempty"`
    79  
    80  	// ClusterOpts is used to pass options to the discovery package for tuning libkv settings, such
    81  	// as TLS configuration settings.
    82  	ClusterOpts map[string]string `json:"cluster-store-opts,omitempty"`
    83  
    84  	// ClusterAdvertise is the network endpoint that the Engine advertises for the purpose of node
    85  	// discovery. This should be a 'host:port' combination on which that daemon instance is
    86  	// reachable by other hosts.
    87  	ClusterAdvertise string `json:"cluster-advertise,omitempty"`
    88  
    89  	Debug     bool     `json:"debug,omitempty"`
    90  	Hosts     []string `json:"hosts,omitempty"`
    91  	LogLevel  string   `json:"log-level,omitempty"`
    92  	TLS       bool     `json:"tls,omitempty"`
    93  	TLSVerify bool     `json:"tlsverify,omitempty"`
    94  
    95  	// Embedded structs that allow config
    96  	// deserialization without the full struct.
    97  	CommonTLSOptions
    98  	LogConfig
    99  	bridgeConfig // bridgeConfig holds bridge network specific configuration.
   100  	registry.ServiceOptions
   101  
   102  	reloadLock sync.Mutex
   103  	valuesSet  map[string]interface{}
   104  }
   105  
   106  // InstallCommonFlags adds command-line options to the top-level flag parser for
   107  // the current process.
   108  // Subsequent calls to `flag.Parse` will populate config with values parsed
   109  // from the command-line.
   110  func (config *Config) InstallCommonFlags(cmd *flag.FlagSet, usageFn func(string) string) {
   111  	config.ServiceOptions.InstallCliFlags(cmd, usageFn)
   112  
   113  	cmd.Var(opts.NewNamedListOptsRef("storage-opts", &config.GraphOptions, nil), []string{"-storage-opt"}, usageFn("Set storage driver options"))
   114  	cmd.Var(opts.NewNamedListOptsRef("authorization-plugins", &config.AuthorizationPlugins, nil), []string{"-authorization-plugin"}, usageFn("List authorization plugins in order from first evaluator to last"))
   115  	cmd.Var(opts.NewNamedListOptsRef("exec-opts", &config.ExecOptions, nil), []string{"-exec-opt"}, usageFn("Set runtime execution options"))
   116  	cmd.StringVar(&config.Pidfile, []string{"p", "-pidfile"}, defaultPidFile, usageFn("Path to use for daemon PID file"))
   117  	cmd.StringVar(&config.Root, []string{"g", "-graph"}, defaultGraph, usageFn("Root of the Docker runtime"))
   118  	cmd.StringVar(&config.ExecRoot, []string{"-exec-root"}, defaultExecRoot, usageFn("Root directory for execution state files"))
   119  	cmd.BoolVar(&config.AutoRestart, []string{"#r", "#-restart"}, true, usageFn("--restart on the daemon has been deprecated in favor of --restart policies on docker run"))
   120  	cmd.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", usageFn("Storage driver to use"))
   121  	cmd.IntVar(&config.Mtu, []string{"#mtu", "-mtu"}, 0, usageFn("Set the containers network MTU"))
   122  	cmd.BoolVar(&config.RawLogs, []string{"-raw-logs"}, false, usageFn("Full timestamps without ANSI coloring"))
   123  	// FIXME: why the inconsistency between "hosts" and "sockets"?
   124  	cmd.Var(opts.NewListOptsRef(&config.DNS, opts.ValidateIPAddress), []string{"#dns", "-dns"}, usageFn("DNS server to use"))
   125  	cmd.Var(opts.NewNamedListOptsRef("dns-opts", &config.DNSOptions, nil), []string{"-dns-opt"}, usageFn("DNS options to use"))
   126  	cmd.Var(opts.NewListOptsRef(&config.DNSSearch, opts.ValidateDNSSearch), []string{"-dns-search"}, usageFn("DNS search domains to use"))
   127  	cmd.Var(opts.NewNamedListOptsRef("labels", &config.Labels, opts.ValidateLabel), []string{"-label"}, usageFn("Set key=value labels to the daemon"))
   128  	cmd.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", usageFn("Default driver for container logs"))
   129  	cmd.Var(opts.NewNamedMapOpts("log-opts", config.LogConfig.Config, nil), []string{"-log-opt"}, usageFn("Set log driver options"))
   130  	cmd.StringVar(&config.ClusterAdvertise, []string{"-cluster-advertise"}, "", usageFn("Address or interface name to advertise"))
   131  	cmd.StringVar(&config.ClusterStore, []string{"-cluster-store"}, "", usageFn("Set the cluster store"))
   132  	cmd.Var(opts.NewNamedMapOpts("cluster-store-opts", config.ClusterOpts, nil), []string{"-cluster-store-opt"}, usageFn("Set cluster store options"))
   133  }
   134  
   135  // IsValueSet returns true if a configuration value
   136  // was explicitly set in the configuration file.
   137  func (config *Config) IsValueSet(name string) bool {
   138  	if config.valuesSet == nil {
   139  		return false
   140  	}
   141  	_, ok := config.valuesSet[name]
   142  	return ok
   143  }
   144  
   145  func parseClusterAdvertiseSettings(clusterStore, clusterAdvertise string) (string, error) {
   146  	if clusterAdvertise == "" {
   147  		return "", errDiscoveryDisabled
   148  	}
   149  	if clusterStore == "" {
   150  		return "", fmt.Errorf("invalid cluster configuration. --cluster-advertise must be accompanied by --cluster-store configuration")
   151  	}
   152  
   153  	advertise, err := discovery.ParseAdvertise(clusterAdvertise)
   154  	if err != nil {
   155  		return "", fmt.Errorf("discovery advertise parsing failed (%v)", err)
   156  	}
   157  	return advertise, nil
   158  }
   159  
   160  // ReloadConfiguration reads the configuration in the host and reloads the daemon and server.
   161  func ReloadConfiguration(configFile string, flags *flag.FlagSet, reload func(*Config)) error {
   162  	logrus.Infof("Got signal to reload configuration, reloading from: %s", configFile)
   163  	newConfig, err := getConflictFreeConfiguration(configFile, flags)
   164  	if err != nil {
   165  		return err
   166  	}
   167  
   168  	if err := validateConfiguration(newConfig); err != nil {
   169  		return fmt.Errorf("file configuration validation failed (%v)", err)
   170  	}
   171  
   172  	reload(newConfig)
   173  	return nil
   174  }
   175  
   176  // boolValue is an interface that boolean value flags implement
   177  // to tell the command line how to make -name equivalent to -name=true.
   178  type boolValue interface {
   179  	IsBoolFlag() bool
   180  }
   181  
   182  // MergeDaemonConfigurations reads a configuration file,
   183  // loads the file configuration in an isolated structure,
   184  // and merges the configuration provided from flags on top
   185  // if there are no conflicts.
   186  func MergeDaemonConfigurations(flagsConfig *Config, flags *flag.FlagSet, configFile string) (*Config, error) {
   187  	fileConfig, err := getConflictFreeConfiguration(configFile, flags)
   188  	if err != nil {
   189  		return nil, err
   190  	}
   191  
   192  	if err := validateConfiguration(fileConfig); err != nil {
   193  		return nil, fmt.Errorf("file configuration validation failed (%v)", err)
   194  	}
   195  
   196  	// merge flags configuration on top of the file configuration
   197  	if err := mergo.Merge(fileConfig, flagsConfig); err != nil {
   198  		return nil, err
   199  	}
   200  
   201  	return fileConfig, nil
   202  }
   203  
   204  // getConflictFreeConfiguration loads the configuration from a JSON file.
   205  // It compares that configuration with the one provided by the flags,
   206  // and returns an error if there are conflicts.
   207  func getConflictFreeConfiguration(configFile string, flags *flag.FlagSet) (*Config, error) {
   208  	b, err := ioutil.ReadFile(configFile)
   209  	if err != nil {
   210  		return nil, err
   211  	}
   212  
   213  	var config Config
   214  	var reader io.Reader
   215  	if flags != nil {
   216  		var jsonConfig map[string]interface{}
   217  		reader = bytes.NewReader(b)
   218  		if err := json.NewDecoder(reader).Decode(&jsonConfig); err != nil {
   219  			return nil, err
   220  		}
   221  
   222  		configSet := configValuesSet(jsonConfig)
   223  
   224  		if err := findConfigurationConflicts(configSet, flags); err != nil {
   225  			return nil, err
   226  		}
   227  
   228  		// Override flag values to make sure the values set in the config file with nullable values, like `false`,
   229  		// are not overriden by default truthy values from the flags that were not explicitly set.
   230  		// See https://github.com/docker/docker/issues/20289 for an example.
   231  		//
   232  		// TODO: Rewrite configuration logic to avoid same issue with other nullable values, like numbers.
   233  		namedOptions := make(map[string]interface{})
   234  		for key, value := range configSet {
   235  			f := flags.Lookup("-" + key)
   236  			if f == nil { // ignore named flags that don't match
   237  				namedOptions[key] = value
   238  				continue
   239  			}
   240  
   241  			if _, ok := f.Value.(boolValue); ok {
   242  				f.Value.Set(fmt.Sprintf("%v", value))
   243  			}
   244  		}
   245  		if len(namedOptions) > 0 {
   246  			// set also default for mergeVal flags that are boolValue at the same time.
   247  			flags.VisitAll(func(f *flag.Flag) {
   248  				if opt, named := f.Value.(opts.NamedOption); named {
   249  					v, set := namedOptions[opt.Name()]
   250  					_, boolean := f.Value.(boolValue)
   251  					if set && boolean {
   252  						f.Value.Set(fmt.Sprintf("%v", v))
   253  					}
   254  				}
   255  			})
   256  		}
   257  
   258  		config.valuesSet = configSet
   259  	}
   260  
   261  	reader = bytes.NewReader(b)
   262  	err = json.NewDecoder(reader).Decode(&config)
   263  	return &config, err
   264  }
   265  
   266  // configValuesSet returns the configuration values explicitly set in the file.
   267  func configValuesSet(config map[string]interface{}) map[string]interface{} {
   268  	flatten := make(map[string]interface{})
   269  	for k, v := range config {
   270  		if m, isMap := v.(map[string]interface{}); isMap && !flatOptions[k] {
   271  			for km, vm := range m {
   272  				flatten[km] = vm
   273  			}
   274  			continue
   275  		}
   276  
   277  		flatten[k] = v
   278  	}
   279  	return flatten
   280  }
   281  
   282  // findConfigurationConflicts iterates over the provided flags searching for
   283  // duplicated configurations and unknown keys. It returns an error with all the conflicts if
   284  // it finds any.
   285  func findConfigurationConflicts(config map[string]interface{}, flags *flag.FlagSet) error {
   286  	// 1. Search keys from the file that we don't recognize as flags.
   287  	unknownKeys := make(map[string]interface{})
   288  	for key, value := range config {
   289  		flagName := "-" + key
   290  		if flag := flags.Lookup(flagName); flag == nil {
   291  			unknownKeys[key] = value
   292  		}
   293  	}
   294  
   295  	// 2. Discard values that implement NamedOption.
   296  	// Their configuration name differs from their flag name, like `labels` and `label`.
   297  	if len(unknownKeys) > 0 {
   298  		unknownNamedConflicts := func(f *flag.Flag) {
   299  			if namedOption, ok := f.Value.(opts.NamedOption); ok {
   300  				if _, valid := unknownKeys[namedOption.Name()]; valid {
   301  					delete(unknownKeys, namedOption.Name())
   302  				}
   303  			}
   304  		}
   305  		flags.VisitAll(unknownNamedConflicts)
   306  	}
   307  
   308  	if len(unknownKeys) > 0 {
   309  		var unknown []string
   310  		for key := range unknownKeys {
   311  			unknown = append(unknown, key)
   312  		}
   313  		return fmt.Errorf("the following directives don't match any configuration option: %s", strings.Join(unknown, ", "))
   314  	}
   315  
   316  	var conflicts []string
   317  	printConflict := func(name string, flagValue, fileValue interface{}) string {
   318  		return fmt.Sprintf("%s: (from flag: %v, from file: %v)", name, flagValue, fileValue)
   319  	}
   320  
   321  	// 3. Search keys that are present as a flag and as a file option.
   322  	duplicatedConflicts := func(f *flag.Flag) {
   323  		// search option name in the json configuration payload if the value is a named option
   324  		if namedOption, ok := f.Value.(opts.NamedOption); ok {
   325  			if optsValue, ok := config[namedOption.Name()]; ok {
   326  				conflicts = append(conflicts, printConflict(namedOption.Name(), f.Value.String(), optsValue))
   327  			}
   328  		} else {
   329  			// search flag name in the json configuration payload without trailing dashes
   330  			for _, name := range f.Names {
   331  				name = strings.TrimLeft(name, "-")
   332  
   333  				if value, ok := config[name]; ok {
   334  					conflicts = append(conflicts, printConflict(name, f.Value.String(), value))
   335  					break
   336  				}
   337  			}
   338  		}
   339  	}
   340  
   341  	flags.Visit(duplicatedConflicts)
   342  
   343  	if len(conflicts) > 0 {
   344  		return fmt.Errorf("the following directives are specified both as a flag and in the configuration file: %s", strings.Join(conflicts, ", "))
   345  	}
   346  	return nil
   347  }
   348  
   349  // validateConfiguration validates some specific configs.
   350  // such as config.DNS, config.Labels, config.DNSSearch
   351  func validateConfiguration(config *Config) error {
   352  	// validate DNS
   353  	for _, dns := range config.DNS {
   354  		if _, err := opts.ValidateIPAddress(dns); err != nil {
   355  			return err
   356  		}
   357  	}
   358  
   359  	// validate DNSSearch
   360  	for _, dnsSearch := range config.DNSSearch {
   361  		if _, err := opts.ValidateDNSSearch(dnsSearch); err != nil {
   362  			return err
   363  		}
   364  	}
   365  
   366  	// validate Labels
   367  	for _, label := range config.Labels {
   368  		if _, err := opts.ValidateLabel(label); err != nil {
   369  			return err
   370  		}
   371  	}
   372  
   373  	return nil
   374  }