github.com/brahmaroutu/docker@v1.2.1-0.20160809185609-eb28dde01f16/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  	// defaultMaxConcurrentDownloads is the default value for
    22  	// maximum number of downloads that
    23  	// may take place at a time for each pull.
    24  	defaultMaxConcurrentDownloads = 3
    25  	// defaultMaxConcurrentUploads is the default value for
    26  	// maximum number of uploads that
    27  	// may take place at a time for each push.
    28  	defaultMaxConcurrentUploads = 5
    29  	// stockRuntimeName is the reserved name/alias used to represent the
    30  	// OCI runtime being shipped with the docker daemon package.
    31  	stockRuntimeName = "runc"
    32  )
    33  
    34  const (
    35  	defaultNetworkMtu    = 1500
    36  	disableNetworkBridge = "none"
    37  )
    38  
    39  // flatOptions contains configuration keys
    40  // that MUST NOT be parsed as deep structures.
    41  // Use this to differentiate these options
    42  // with others like the ones in CommonTLSOptions.
    43  var flatOptions = map[string]bool{
    44  	"cluster-store-opts": true,
    45  	"log-opts":           true,
    46  	"runtimes":           true,
    47  }
    48  
    49  // LogConfig represents the default log configuration.
    50  // It includes json tags to deserialize configuration from a file
    51  // using the same names that the flags in the command line use.
    52  type LogConfig struct {
    53  	Type   string            `json:"log-driver,omitempty"`
    54  	Config map[string]string `json:"log-opts,omitempty"`
    55  }
    56  
    57  // commonBridgeConfig stores all the platform-common bridge driver specific
    58  // configuration.
    59  type commonBridgeConfig struct {
    60  	Iface     string `json:"bridge,omitempty"`
    61  	FixedCIDR string `json:"fixed-cidr,omitempty"`
    62  }
    63  
    64  // CommonTLSOptions defines TLS configuration for the daemon server.
    65  // It includes json tags to deserialize configuration from a file
    66  // using the same names that the flags in the command line use.
    67  type CommonTLSOptions struct {
    68  	CAFile   string `json:"tlscacert,omitempty"`
    69  	CertFile string `json:"tlscert,omitempty"`
    70  	KeyFile  string `json:"tlskey,omitempty"`
    71  }
    72  
    73  // CommonConfig defines the configuration of a docker daemon which is
    74  // common across platforms.
    75  // It includes json tags to deserialize configuration from a file
    76  // using the same names that the flags in the command line use.
    77  type CommonConfig struct {
    78  	AuthorizationPlugins []string            `json:"authorization-plugins,omitempty"` // AuthorizationPlugins holds list of authorization plugins
    79  	AutoRestart          bool                `json:"-"`
    80  	Context              map[string][]string `json:"-"`
    81  	DisableBridge        bool                `json:"-"`
    82  	DNS                  []string            `json:"dns,omitempty"`
    83  	DNSOptions           []string            `json:"dns-opts,omitempty"`
    84  	DNSSearch            []string            `json:"dns-search,omitempty"`
    85  	ExecOptions          []string            `json:"exec-opts,omitempty"`
    86  	GraphDriver          string              `json:"storage-driver,omitempty"`
    87  	GraphOptions         []string            `json:"storage-opts,omitempty"`
    88  	Labels               []string            `json:"labels,omitempty"`
    89  	Mtu                  int                 `json:"mtu,omitempty"`
    90  	Pidfile              string              `json:"pidfile,omitempty"`
    91  	RawLogs              bool                `json:"raw-logs,omitempty"`
    92  	Root                 string              `json:"graph,omitempty"`
    93  	SocketGroup          string              `json:"group,omitempty"`
    94  	TrustKeyPath         string              `json:"-"`
    95  	CorsHeaders          string              `json:"api-cors-header,omitempty"`
    96  	EnableCors           bool                `json:"api-enable-cors,omitempty"`
    97  
    98  	// LiveRestoreEnabled determines whether we should keep containers
    99  	// alive upon daemon shutdown/start
   100  	LiveRestoreEnabled bool `json:"live-restore,omitempty"`
   101  
   102  	// ClusterStore is the storage backend used for the cluster information. It is used by both
   103  	// multihost networking (to store networks and endpoints information) and by the node discovery
   104  	// mechanism.
   105  	ClusterStore string `json:"cluster-store,omitempty"`
   106  
   107  	// ClusterOpts is used to pass options to the discovery package for tuning libkv settings, such
   108  	// as TLS configuration settings.
   109  	ClusterOpts map[string]string `json:"cluster-store-opts,omitempty"`
   110  
   111  	// ClusterAdvertise is the network endpoint that the Engine advertises for the purpose of node
   112  	// discovery. This should be a 'host:port' combination on which that daemon instance is
   113  	// reachable by other hosts.
   114  	ClusterAdvertise string `json:"cluster-advertise,omitempty"`
   115  
   116  	// MaxConcurrentDownloads is the maximum number of downloads that
   117  	// may take place at a time for each pull.
   118  	MaxConcurrentDownloads *int `json:"max-concurrent-downloads,omitempty"`
   119  
   120  	// MaxConcurrentUploads is the maximum number of uploads that
   121  	// may take place at a time for each push.
   122  	MaxConcurrentUploads *int `json:"max-concurrent-uploads,omitempty"`
   123  
   124  	Debug     bool     `json:"debug,omitempty"`
   125  	Hosts     []string `json:"hosts,omitempty"`
   126  	LogLevel  string   `json:"log-level,omitempty"`
   127  	TLS       bool     `json:"tls,omitempty"`
   128  	TLSVerify bool     `json:"tlsverify,omitempty"`
   129  
   130  	// Embedded structs that allow config
   131  	// deserialization without the full struct.
   132  	CommonTLSOptions
   133  
   134  	// SwarmDefaultAdvertiseAddr is the default host/IP or network interface
   135  	// to use if a wildcard address is specified in the ListenAddr value
   136  	// given to the /swarm/init endpoint and no advertise address is
   137  	// specified.
   138  	SwarmDefaultAdvertiseAddr string `json:"swarm-default-advertise-addr"`
   139  
   140  	LogConfig
   141  	bridgeConfig // bridgeConfig holds bridge network specific configuration.
   142  	registry.ServiceOptions
   143  
   144  	reloadLock sync.Mutex
   145  	valuesSet  map[string]interface{}
   146  }
   147  
   148  // InstallCommonFlags adds command-line options to the top-level flag parser for
   149  // the current process.
   150  // Subsequent calls to `flag.Parse` will populate config with values parsed
   151  // from the command-line.
   152  func (config *Config) InstallCommonFlags(cmd *flag.FlagSet, usageFn func(string) string) {
   153  	var maxConcurrentDownloads, maxConcurrentUploads int
   154  
   155  	config.ServiceOptions.InstallCliFlags(cmd, usageFn)
   156  
   157  	cmd.Var(opts.NewNamedListOptsRef("storage-opts", &config.GraphOptions, nil), []string{"-storage-opt"}, usageFn("Storage driver options"))
   158  	cmd.Var(opts.NewNamedListOptsRef("authorization-plugins", &config.AuthorizationPlugins, nil), []string{"-authorization-plugin"}, usageFn("Authorization plugins to load"))
   159  	cmd.Var(opts.NewNamedListOptsRef("exec-opts", &config.ExecOptions, nil), []string{"-exec-opt"}, usageFn("Runtime execution options"))
   160  	cmd.StringVar(&config.Pidfile, []string{"p", "-pidfile"}, defaultPidFile, usageFn("Path to use for daemon PID file"))
   161  	cmd.StringVar(&config.Root, []string{"g", "-graph"}, defaultGraph, usageFn("Root of the Docker runtime"))
   162  	cmd.BoolVar(&config.AutoRestart, []string{"#r", "#-restart"}, true, usageFn("--restart on the daemon has been deprecated in favor of --restart policies on docker run"))
   163  	cmd.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", usageFn("Storage driver to use"))
   164  	cmd.IntVar(&config.Mtu, []string{"#mtu", "-mtu"}, 0, usageFn("Set the containers network MTU"))
   165  	cmd.BoolVar(&config.RawLogs, []string{"-raw-logs"}, false, usageFn("Full timestamps without ANSI coloring"))
   166  	// FIXME: why the inconsistency between "hosts" and "sockets"?
   167  	cmd.Var(opts.NewListOptsRef(&config.DNS, opts.ValidateIPAddress), []string{"#dns", "-dns"}, usageFn("DNS server to use"))
   168  	cmd.Var(opts.NewNamedListOptsRef("dns-opts", &config.DNSOptions, nil), []string{"-dns-opt"}, usageFn("DNS options to use"))
   169  	cmd.Var(opts.NewListOptsRef(&config.DNSSearch, opts.ValidateDNSSearch), []string{"-dns-search"}, usageFn("DNS search domains to use"))
   170  	cmd.Var(opts.NewNamedListOptsRef("labels", &config.Labels, opts.ValidateLabel), []string{"-label"}, usageFn("Set key=value labels to the daemon"))
   171  	cmd.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", usageFn("Default driver for container logs"))
   172  	cmd.Var(opts.NewNamedMapOpts("log-opts", config.LogConfig.Config, nil), []string{"-log-opt"}, usageFn("Default log driver options for containers"))
   173  	cmd.StringVar(&config.ClusterAdvertise, []string{"-cluster-advertise"}, "", usageFn("Address or interface name to advertise"))
   174  	cmd.StringVar(&config.ClusterStore, []string{"-cluster-store"}, "", usageFn("URL of the distributed storage backend"))
   175  	cmd.Var(opts.NewNamedMapOpts("cluster-store-opts", config.ClusterOpts, nil), []string{"-cluster-store-opt"}, usageFn("Set cluster store options"))
   176  	cmd.StringVar(&config.CorsHeaders, []string{"-api-cors-header"}, "", usageFn("Set CORS headers in the remote API"))
   177  	cmd.IntVar(&maxConcurrentDownloads, []string{"-max-concurrent-downloads"}, defaultMaxConcurrentDownloads, usageFn("Set the max concurrent downloads for each pull"))
   178  	cmd.IntVar(&maxConcurrentUploads, []string{"-max-concurrent-uploads"}, defaultMaxConcurrentUploads, usageFn("Set the max concurrent uploads for each push"))
   179  
   180  	cmd.StringVar(&config.SwarmDefaultAdvertiseAddr, []string{"-swarm-default-advertise-addr"}, "", usageFn("Set default address or interface for swarm advertised address"))
   181  
   182  	config.MaxConcurrentDownloads = &maxConcurrentDownloads
   183  	config.MaxConcurrentUploads = &maxConcurrentUploads
   184  }
   185  
   186  // IsValueSet returns true if a configuration value
   187  // was explicitly set in the configuration file.
   188  func (config *Config) IsValueSet(name string) bool {
   189  	if config.valuesSet == nil {
   190  		return false
   191  	}
   192  	_, ok := config.valuesSet[name]
   193  	return ok
   194  }
   195  
   196  func parseClusterAdvertiseSettings(clusterStore, clusterAdvertise string) (string, error) {
   197  	if clusterAdvertise == "" {
   198  		return "", errDiscoveryDisabled
   199  	}
   200  	if clusterStore == "" {
   201  		return "", fmt.Errorf("invalid cluster configuration. --cluster-advertise must be accompanied by --cluster-store configuration")
   202  	}
   203  
   204  	advertise, err := discovery.ParseAdvertise(clusterAdvertise)
   205  	if err != nil {
   206  		return "", fmt.Errorf("discovery advertise parsing failed (%v)", err)
   207  	}
   208  	return advertise, nil
   209  }
   210  
   211  // ReloadConfiguration reads the configuration in the host and reloads the daemon and server.
   212  func ReloadConfiguration(configFile string, flags *flag.FlagSet, reload func(*Config)) error {
   213  	logrus.Infof("Got signal to reload configuration, reloading from: %s", configFile)
   214  	newConfig, err := getConflictFreeConfiguration(configFile, flags)
   215  	if err != nil {
   216  		return err
   217  	}
   218  
   219  	if err := ValidateConfiguration(newConfig); err != nil {
   220  		return fmt.Errorf("file configuration validation failed (%v)", err)
   221  	}
   222  
   223  	reload(newConfig)
   224  	return nil
   225  }
   226  
   227  // boolValue is an interface that boolean value flags implement
   228  // to tell the command line how to make -name equivalent to -name=true.
   229  type boolValue interface {
   230  	IsBoolFlag() bool
   231  }
   232  
   233  // MergeDaemonConfigurations reads a configuration file,
   234  // loads the file configuration in an isolated structure,
   235  // and merges the configuration provided from flags on top
   236  // if there are no conflicts.
   237  func MergeDaemonConfigurations(flagsConfig *Config, flags *flag.FlagSet, configFile string) (*Config, error) {
   238  	fileConfig, err := getConflictFreeConfiguration(configFile, flags)
   239  	if err != nil {
   240  		return nil, err
   241  	}
   242  
   243  	if err := ValidateConfiguration(fileConfig); err != nil {
   244  		return nil, fmt.Errorf("file configuration validation failed (%v)", err)
   245  	}
   246  
   247  	// merge flags configuration on top of the file configuration
   248  	if err := mergo.Merge(fileConfig, flagsConfig); err != nil {
   249  		return nil, err
   250  	}
   251  
   252  	// We need to validate again once both fileConfig and flagsConfig
   253  	// have been merged
   254  	if err := ValidateConfiguration(fileConfig); err != nil {
   255  		return nil, fmt.Errorf("file configuration validation failed (%v)", err)
   256  	}
   257  
   258  	return fileConfig, nil
   259  }
   260  
   261  // getConflictFreeConfiguration loads the configuration from a JSON file.
   262  // It compares that configuration with the one provided by the flags,
   263  // and returns an error if there are conflicts.
   264  func getConflictFreeConfiguration(configFile string, flags *flag.FlagSet) (*Config, error) {
   265  	b, err := ioutil.ReadFile(configFile)
   266  	if err != nil {
   267  		return nil, err
   268  	}
   269  
   270  	var config Config
   271  	var reader io.Reader
   272  	if flags != nil {
   273  		var jsonConfig map[string]interface{}
   274  		reader = bytes.NewReader(b)
   275  		if err := json.NewDecoder(reader).Decode(&jsonConfig); err != nil {
   276  			return nil, err
   277  		}
   278  
   279  		configSet := configValuesSet(jsonConfig)
   280  
   281  		if err := findConfigurationConflicts(configSet, flags); err != nil {
   282  			return nil, err
   283  		}
   284  
   285  		// Override flag values to make sure the values set in the config file with nullable values, like `false`,
   286  		// are not overridden by default truthy values from the flags that were not explicitly set.
   287  		// See https://github.com/docker/docker/issues/20289 for an example.
   288  		//
   289  		// TODO: Rewrite configuration logic to avoid same issue with other nullable values, like numbers.
   290  		namedOptions := make(map[string]interface{})
   291  		for key, value := range configSet {
   292  			f := flags.Lookup("-" + key)
   293  			if f == nil { // ignore named flags that don't match
   294  				namedOptions[key] = value
   295  				continue
   296  			}
   297  
   298  			if _, ok := f.Value.(boolValue); ok {
   299  				f.Value.Set(fmt.Sprintf("%v", value))
   300  			}
   301  		}
   302  		if len(namedOptions) > 0 {
   303  			// set also default for mergeVal flags that are boolValue at the same time.
   304  			flags.VisitAll(func(f *flag.Flag) {
   305  				if opt, named := f.Value.(opts.NamedOption); named {
   306  					v, set := namedOptions[opt.Name()]
   307  					_, boolean := f.Value.(boolValue)
   308  					if set && boolean {
   309  						f.Value.Set(fmt.Sprintf("%v", v))
   310  					}
   311  				}
   312  			})
   313  		}
   314  
   315  		config.valuesSet = configSet
   316  	}
   317  
   318  	reader = bytes.NewReader(b)
   319  	err = json.NewDecoder(reader).Decode(&config)
   320  	return &config, err
   321  }
   322  
   323  // configValuesSet returns the configuration values explicitly set in the file.
   324  func configValuesSet(config map[string]interface{}) map[string]interface{} {
   325  	flatten := make(map[string]interface{})
   326  	for k, v := range config {
   327  		if m, isMap := v.(map[string]interface{}); isMap && !flatOptions[k] {
   328  			for km, vm := range m {
   329  				flatten[km] = vm
   330  			}
   331  			continue
   332  		}
   333  
   334  		flatten[k] = v
   335  	}
   336  	return flatten
   337  }
   338  
   339  // findConfigurationConflicts iterates over the provided flags searching for
   340  // duplicated configurations and unknown keys. It returns an error with all the conflicts if
   341  // it finds any.
   342  func findConfigurationConflicts(config map[string]interface{}, flags *flag.FlagSet) error {
   343  	// 1. Search keys from the file that we don't recognize as flags.
   344  	unknownKeys := make(map[string]interface{})
   345  	for key, value := range config {
   346  		flagName := "-" + key
   347  		if flag := flags.Lookup(flagName); flag == nil {
   348  			unknownKeys[key] = value
   349  		}
   350  	}
   351  
   352  	// 2. Discard values that implement NamedOption.
   353  	// Their configuration name differs from their flag name, like `labels` and `label`.
   354  	if len(unknownKeys) > 0 {
   355  		unknownNamedConflicts := func(f *flag.Flag) {
   356  			if namedOption, ok := f.Value.(opts.NamedOption); ok {
   357  				if _, valid := unknownKeys[namedOption.Name()]; valid {
   358  					delete(unknownKeys, namedOption.Name())
   359  				}
   360  			}
   361  		}
   362  		flags.VisitAll(unknownNamedConflicts)
   363  	}
   364  
   365  	if len(unknownKeys) > 0 {
   366  		var unknown []string
   367  		for key := range unknownKeys {
   368  			unknown = append(unknown, key)
   369  		}
   370  		return fmt.Errorf("the following directives don't match any configuration option: %s", strings.Join(unknown, ", "))
   371  	}
   372  
   373  	var conflicts []string
   374  	printConflict := func(name string, flagValue, fileValue interface{}) string {
   375  		return fmt.Sprintf("%s: (from flag: %v, from file: %v)", name, flagValue, fileValue)
   376  	}
   377  
   378  	// 3. Search keys that are present as a flag and as a file option.
   379  	duplicatedConflicts := func(f *flag.Flag) {
   380  		// search option name in the json configuration payload if the value is a named option
   381  		if namedOption, ok := f.Value.(opts.NamedOption); ok {
   382  			if optsValue, ok := config[namedOption.Name()]; ok {
   383  				conflicts = append(conflicts, printConflict(namedOption.Name(), f.Value.String(), optsValue))
   384  			}
   385  		} else {
   386  			// search flag name in the json configuration payload without trailing dashes
   387  			for _, name := range f.Names {
   388  				name = strings.TrimLeft(name, "-")
   389  
   390  				if value, ok := config[name]; ok {
   391  					conflicts = append(conflicts, printConflict(name, f.Value.String(), value))
   392  					break
   393  				}
   394  			}
   395  		}
   396  	}
   397  
   398  	flags.Visit(duplicatedConflicts)
   399  
   400  	if len(conflicts) > 0 {
   401  		return fmt.Errorf("the following directives are specified both as a flag and in the configuration file: %s", strings.Join(conflicts, ", "))
   402  	}
   403  	return nil
   404  }
   405  
   406  // ValidateConfiguration validates some specific configs.
   407  // such as config.DNS, config.Labels, config.DNSSearch,
   408  // as well as config.MaxConcurrentDownloads, config.MaxConcurrentUploads.
   409  func ValidateConfiguration(config *Config) error {
   410  	// validate DNS
   411  	for _, dns := range config.DNS {
   412  		if _, err := opts.ValidateIPAddress(dns); err != nil {
   413  			return err
   414  		}
   415  	}
   416  
   417  	// validate DNSSearch
   418  	for _, dnsSearch := range config.DNSSearch {
   419  		if _, err := opts.ValidateDNSSearch(dnsSearch); err != nil {
   420  			return err
   421  		}
   422  	}
   423  
   424  	// validate Labels
   425  	for _, label := range config.Labels {
   426  		if _, err := opts.ValidateLabel(label); err != nil {
   427  			return err
   428  		}
   429  	}
   430  
   431  	// validate MaxConcurrentDownloads
   432  	if config.IsValueSet("max-concurrent-downloads") && config.MaxConcurrentDownloads != nil && *config.MaxConcurrentDownloads < 0 {
   433  		return fmt.Errorf("invalid max concurrent downloads: %d", *config.MaxConcurrentDownloads)
   434  	}
   435  
   436  	// validate MaxConcurrentUploads
   437  	if config.IsValueSet("max-concurrent-uploads") && config.MaxConcurrentUploads != nil && *config.MaxConcurrentUploads < 0 {
   438  		return fmt.Errorf("invalid max concurrent uploads: %d", *config.MaxConcurrentUploads)
   439  	}
   440  
   441  	// validate that "default" runtime is not reset
   442  	if runtimes := config.GetAllRuntimes(); len(runtimes) > 0 {
   443  		if _, ok := runtimes[stockRuntimeName]; ok {
   444  			return fmt.Errorf("runtime name '%s' is reserved", stockRuntimeName)
   445  		}
   446  	}
   447  
   448  	if defaultRuntime := config.GetDefaultRuntimeName(); defaultRuntime != "" && defaultRuntime != stockRuntimeName {
   449  		runtimes := config.GetAllRuntimes()
   450  		if _, ok := runtimes[defaultRuntime]; !ok {
   451  			return fmt.Errorf("specified default runtime '%s' does not exist", defaultRuntime)
   452  		}
   453  	}
   454  
   455  	return nil
   456  }