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