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