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