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