github.com/devdivbcp/moby@v17.12.0-ce-rc1.0.20200726071732-2d4bfdc789ad+incompatible/daemon/config/config.go (about) 1 package config // import "github.com/docker/docker/daemon/config" 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "os" 10 "reflect" 11 "strings" 12 "sync" 13 14 daemondiscovery "github.com/docker/docker/daemon/discovery" 15 "github.com/docker/docker/opts" 16 "github.com/docker/docker/pkg/authorization" 17 "github.com/docker/docker/pkg/discovery" 18 "github.com/docker/docker/registry" 19 "github.com/imdario/mergo" 20 "github.com/pkg/errors" 21 "github.com/sirupsen/logrus" 22 "github.com/spf13/pflag" 23 ) 24 25 const ( 26 // DefaultMaxConcurrentDownloads is the default value for 27 // maximum number of downloads that 28 // may take place at a time for each pull. 29 DefaultMaxConcurrentDownloads = 3 30 // DefaultMaxConcurrentUploads is the default value for 31 // maximum number of uploads that 32 // may take place at a time for each push. 33 DefaultMaxConcurrentUploads = 5 34 // StockRuntimeName is the reserved name/alias used to represent the 35 // OCI runtime being shipped with the docker daemon package. 36 StockRuntimeName = "runc" 37 // DefaultShmSize is the default value for container's shm size 38 DefaultShmSize = int64(67108864) 39 // DefaultNetworkMtu is the default value for network MTU 40 DefaultNetworkMtu = 1500 41 // DisableNetworkBridge is the default value of the option to disable network bridge 42 DisableNetworkBridge = "none" 43 // DefaultInitBinary is the name of the default init binary 44 DefaultInitBinary = "docker-init" 45 ) 46 47 // flatOptions contains configuration keys 48 // that MUST NOT be parsed as deep structures. 49 // Use this to differentiate these options 50 // with others like the ones in CommonTLSOptions. 51 var flatOptions = map[string]bool{ 52 "cluster-store-opts": true, 53 "log-opts": true, 54 "runtimes": true, 55 "default-ulimits": true, 56 "features": true, 57 "builder": true, 58 } 59 60 // skipValidateOptions contains configuration keys 61 // that will be skipped from findConfigurationConflicts 62 // for unknown flag validation. 63 var skipValidateOptions = map[string]bool{ 64 "features": true, 65 "builder": true, 66 // Corresponding flag has been removed because it was already unusable 67 "deprecated-key-path": true, 68 } 69 70 // skipDuplicates contains configuration keys that 71 // will be skipped when checking duplicated 72 // configuration field defined in both daemon 73 // config file and from dockerd cli flags. 74 // This allows some configurations to be merged 75 // during the parsing. 76 var skipDuplicates = map[string]bool{ 77 "runtimes": true, 78 } 79 80 // LogConfig represents the default log configuration. 81 // It includes json tags to deserialize configuration from a file 82 // using the same names that the flags in the command line use. 83 type LogConfig struct { 84 Type string `json:"log-driver,omitempty"` 85 Config map[string]string `json:"log-opts,omitempty"` 86 } 87 88 // commonBridgeConfig stores all the platform-common bridge driver specific 89 // configuration. 90 type commonBridgeConfig struct { 91 Iface string `json:"bridge,omitempty"` 92 FixedCIDR string `json:"fixed-cidr,omitempty"` 93 } 94 95 // NetworkConfig stores the daemon-wide networking configurations 96 type NetworkConfig struct { 97 // Default address pools for docker networks 98 DefaultAddressPools opts.PoolsOpt `json:"default-address-pools,omitempty"` 99 // NetworkControlPlaneMTU allows to specify the control plane MTU, this will allow to optimize the network use in some components 100 NetworkControlPlaneMTU int `json:"network-control-plane-mtu,omitempty"` 101 } 102 103 // CommonTLSOptions defines TLS configuration for the daemon server. 104 // It includes json tags to deserialize configuration from a file 105 // using the same names that the flags in the command line use. 106 type CommonTLSOptions struct { 107 CAFile string `json:"tlscacert,omitempty"` 108 CertFile string `json:"tlscert,omitempty"` 109 KeyFile string `json:"tlskey,omitempty"` 110 } 111 112 // DNSConfig defines the DNS configurations. 113 type DNSConfig struct { 114 DNS []string `json:"dns,omitempty"` 115 DNSOptions []string `json:"dns-opts,omitempty"` 116 DNSSearch []string `json:"dns-search,omitempty"` 117 } 118 119 // CommonConfig defines the configuration of a docker daemon which is 120 // common across platforms. 121 // It includes json tags to deserialize configuration from a file 122 // using the same names that the flags in the command line use. 123 type CommonConfig struct { 124 AuthzMiddleware *authorization.Middleware `json:"-"` 125 AuthorizationPlugins []string `json:"authorization-plugins,omitempty"` // AuthorizationPlugins holds list of authorization plugins 126 AutoRestart bool `json:"-"` 127 Context map[string][]string `json:"-"` 128 DisableBridge bool `json:"-"` 129 ExecOptions []string `json:"exec-opts,omitempty"` 130 GraphDriver string `json:"storage-driver,omitempty"` 131 GraphOptions []string `json:"storage-opts,omitempty"` 132 Labels []string `json:"labels,omitempty"` 133 Mtu int `json:"mtu,omitempty"` 134 NetworkDiagnosticPort int `json:"network-diagnostic-port,omitempty"` 135 Pidfile string `json:"pidfile,omitempty"` 136 RawLogs bool `json:"raw-logs,omitempty"` 137 RootDeprecated string `json:"graph,omitempty"` 138 Root string `json:"data-root,omitempty"` 139 ExecRoot string `json:"exec-root,omitempty"` 140 SocketGroup string `json:"group,omitempty"` 141 CorsHeaders string `json:"api-cors-header,omitempty"` 142 143 // TrustKeyPath is used to generate the daemon ID and for signing schema 1 manifests 144 // when pushing to a registry which does not support schema 2. This field is marked as 145 // deprecated because schema 1 manifests are deprecated in favor of schema 2 and the 146 // daemon ID will use a dedicated identifier not shared with exported signatures. 147 TrustKeyPath string `json:"deprecated-key-path,omitempty"` 148 149 // LiveRestoreEnabled determines whether we should keep containers 150 // alive upon daemon shutdown/start 151 LiveRestoreEnabled bool `json:"live-restore,omitempty"` 152 153 // ClusterStore is the storage backend used for the cluster information. It is used by both 154 // multihost networking (to store networks and endpoints information) and by the node discovery 155 // mechanism. 156 ClusterStore string `json:"cluster-store,omitempty"` 157 158 // ClusterOpts is used to pass options to the discovery package for tuning libkv settings, such 159 // as TLS configuration settings. 160 ClusterOpts map[string]string `json:"cluster-store-opts,omitempty"` 161 162 // ClusterAdvertise is the network endpoint that the Engine advertises for the purpose of node 163 // discovery. This should be a 'host:port' combination on which that daemon instance is 164 // reachable by other hosts. 165 ClusterAdvertise string `json:"cluster-advertise,omitempty"` 166 167 // MaxConcurrentDownloads is the maximum number of downloads that 168 // may take place at a time for each pull. 169 MaxConcurrentDownloads *int `json:"max-concurrent-downloads,omitempty"` 170 171 // MaxConcurrentUploads is the maximum number of uploads that 172 // may take place at a time for each push. 173 MaxConcurrentUploads *int `json:"max-concurrent-uploads,omitempty"` 174 175 // ShutdownTimeout is the timeout value (in seconds) the daemon will wait for the container 176 // to stop when daemon is being shutdown 177 ShutdownTimeout int `json:"shutdown-timeout,omitempty"` 178 179 Debug bool `json:"debug,omitempty"` 180 Hosts []string `json:"hosts,omitempty"` 181 LogLevel string `json:"log-level,omitempty"` 182 TLS bool `json:"tls,omitempty"` 183 TLSVerify bool `json:"tlsverify,omitempty"` 184 185 // Embedded structs that allow config 186 // deserialization without the full struct. 187 CommonTLSOptions 188 189 // SwarmDefaultAdvertiseAddr is the default host/IP or network interface 190 // to use if a wildcard address is specified in the ListenAddr value 191 // given to the /swarm/init endpoint and no advertise address is 192 // specified. 193 SwarmDefaultAdvertiseAddr string `json:"swarm-default-advertise-addr"` 194 195 // SwarmRaftHeartbeatTick is the number of ticks in time for swarm mode raft quorum heartbeat 196 // Typical value is 1 197 SwarmRaftHeartbeatTick uint32 `json:"swarm-raft-heartbeat-tick"` 198 199 // SwarmRaftElectionTick is the number of ticks to elapse before followers in the quorum can propose 200 // a new round of leader election. Default, recommended value is at least 10X that of Heartbeat tick. 201 // Higher values can make the quorum less sensitive to transient faults in the environment, but this also 202 // means it takes longer for the managers to detect a down leader. 203 SwarmRaftElectionTick uint32 `json:"swarm-raft-election-tick"` 204 205 MetricsAddress string `json:"metrics-addr"` 206 207 DNSConfig 208 LogConfig 209 BridgeConfig // bridgeConfig holds bridge network specific configuration. 210 NetworkConfig 211 registry.ServiceOptions 212 213 sync.Mutex 214 // FIXME(vdemeester) This part is not that clear and is mainly dependent on cli flags 215 // It should probably be handled outside this package. 216 ValuesSet map[string]interface{} `json:"-"` 217 218 Experimental bool `json:"experimental"` // Experimental indicates whether experimental features should be exposed or not 219 220 // Exposed node Generic Resources 221 // e.g: ["orange=red", "orange=green", "orange=blue", "apple=3"] 222 NodeGenericResources []string `json:"node-generic-resources,omitempty"` 223 224 // ContainerAddr is the address used to connect to containerd if we're 225 // not starting it ourselves 226 ContainerdAddr string `json:"containerd,omitempty"` 227 228 // CriContainerd determines whether a supervised containerd instance 229 // should be configured with the CRI plugin enabled. This allows using 230 // Docker's containerd instance directly with a Kubernetes kubelet. 231 CriContainerd bool `json:"cri-containerd,omitempty"` 232 233 // Features contains a list of feature key value pairs indicating what features are enabled or disabled. 234 // If a certain feature doesn't appear in this list then it's unset (i.e. neither true nor false). 235 Features map[string]bool `json:"features,omitempty"` 236 237 Builder BuilderConfig `json:"builder,omitempty"` 238 239 ContainerdNamespace string `json:"containerd-namespace,omitempty"` 240 ContainerdPluginNamespace string `json:"containerd-plugin-namespace,omitempty"` 241 } 242 243 // IsValueSet returns true if a configuration value 244 // was explicitly set in the configuration file. 245 func (conf *Config) IsValueSet(name string) bool { 246 if conf.ValuesSet == nil { 247 return false 248 } 249 _, ok := conf.ValuesSet[name] 250 return ok 251 } 252 253 // New returns a new fully initialized Config struct 254 func New() *Config { 255 config := Config{} 256 config.LogConfig.Config = make(map[string]string) 257 config.ClusterOpts = make(map[string]string) 258 return &config 259 } 260 261 // ParseClusterAdvertiseSettings parses the specified advertise settings 262 func ParseClusterAdvertiseSettings(clusterStore, clusterAdvertise string) (string, error) { 263 if clusterAdvertise == "" { 264 return "", daemondiscovery.ErrDiscoveryDisabled 265 } 266 if clusterStore == "" { 267 return "", errors.New("invalid cluster configuration. --cluster-advertise must be accompanied by --cluster-store configuration") 268 } 269 270 advertise, err := discovery.ParseAdvertise(clusterAdvertise) 271 if err != nil { 272 return "", errors.Wrap(err, "discovery advertise parsing failed") 273 } 274 return advertise, nil 275 } 276 277 // GetConflictFreeLabels validates Labels for conflict 278 // In swarm the duplicates for labels are removed 279 // so we only take same values here, no conflict values 280 // If the key-value is the same we will only take the last label 281 func GetConflictFreeLabels(labels []string) ([]string, error) { 282 labelMap := map[string]string{} 283 for _, label := range labels { 284 stringSlice := strings.SplitN(label, "=", 2) 285 if len(stringSlice) > 1 { 286 // If there is a conflict we will return an error 287 if v, ok := labelMap[stringSlice[0]]; ok && v != stringSlice[1] { 288 return nil, fmt.Errorf("conflict labels for %s=%s and %s=%s", stringSlice[0], stringSlice[1], stringSlice[0], v) 289 } 290 labelMap[stringSlice[0]] = stringSlice[1] 291 } 292 } 293 294 newLabels := []string{} 295 for k, v := range labelMap { 296 newLabels = append(newLabels, fmt.Sprintf("%s=%s", k, v)) 297 } 298 return newLabels, nil 299 } 300 301 // ValidateReservedNamespaceLabels errors if the reserved namespaces com.docker.*, 302 // io.docker.*, org.dockerproject.* are used in a configured engine label. 303 // 304 // TODO: This is a separate function because we need to warn users first of the 305 // deprecation. When we return an error, this logic can be added to Validate 306 // or GetConflictFreeLabels instead of being here. 307 func ValidateReservedNamespaceLabels(labels []string) error { 308 for _, label := range labels { 309 lowered := strings.ToLower(label) 310 if strings.HasPrefix(lowered, "com.docker.") || strings.HasPrefix(lowered, "io.docker.") || 311 strings.HasPrefix(lowered, "org.dockerproject.") { 312 return fmt.Errorf( 313 "label %s not allowed: the namespaces com.docker.*, io.docker.*, and org.dockerproject.* are reserved for Docker's internal use", 314 label) 315 } 316 } 317 return nil 318 } 319 320 // Reload reads the configuration in the host and reloads the daemon and server. 321 func Reload(configFile string, flags *pflag.FlagSet, reload func(*Config)) error { 322 logrus.Infof("Got signal to reload configuration, reloading from: %s", configFile) 323 newConfig, err := getConflictFreeConfiguration(configFile, flags) 324 if err != nil { 325 if flags.Changed("config-file") || !os.IsNotExist(err) { 326 return errors.Wrapf(err, "unable to configure the Docker daemon with file %s", configFile) 327 } 328 newConfig = New() 329 } 330 331 if err := Validate(newConfig); err != nil { 332 return errors.Wrap(err, "file configuration validation failed") 333 } 334 335 // Check if duplicate label-keys with different values are found 336 newLabels, err := GetConflictFreeLabels(newConfig.Labels) 337 if err != nil { 338 return err 339 } 340 newConfig.Labels = newLabels 341 342 reload(newConfig) 343 return nil 344 } 345 346 // boolValue is an interface that boolean value flags implement 347 // to tell the command line how to make -name equivalent to -name=true. 348 type boolValue interface { 349 IsBoolFlag() bool 350 } 351 352 // MergeDaemonConfigurations reads a configuration file, 353 // loads the file configuration in an isolated structure, 354 // and merges the configuration provided from flags on top 355 // if there are no conflicts. 356 func MergeDaemonConfigurations(flagsConfig *Config, flags *pflag.FlagSet, configFile string) (*Config, error) { 357 fileConfig, err := getConflictFreeConfiguration(configFile, flags) 358 if err != nil { 359 return nil, err 360 } 361 362 if err := Validate(fileConfig); err != nil { 363 return nil, errors.Wrap(err, "configuration validation from file failed") 364 } 365 366 // merge flags configuration on top of the file configuration 367 if err := mergo.Merge(fileConfig, flagsConfig); err != nil { 368 return nil, err 369 } 370 371 // We need to validate again once both fileConfig and flagsConfig 372 // have been merged 373 if err := Validate(fileConfig); err != nil { 374 return nil, errors.Wrap(err, "merged configuration validation from file and command line flags failed") 375 } 376 377 return fileConfig, nil 378 } 379 380 // getConflictFreeConfiguration loads the configuration from a JSON file. 381 // It compares that configuration with the one provided by the flags, 382 // and returns an error if there are conflicts. 383 func getConflictFreeConfiguration(configFile string, flags *pflag.FlagSet) (*Config, error) { 384 b, err := ioutil.ReadFile(configFile) 385 if err != nil { 386 return nil, err 387 } 388 389 var config Config 390 var reader io.Reader 391 if flags != nil { 392 var jsonConfig map[string]interface{} 393 reader = bytes.NewReader(b) 394 if err := json.NewDecoder(reader).Decode(&jsonConfig); err != nil { 395 return nil, err 396 } 397 398 configSet := configValuesSet(jsonConfig) 399 400 if err := findConfigurationConflicts(configSet, flags); err != nil { 401 return nil, err 402 } 403 404 // Override flag values to make sure the values set in the config file with nullable values, like `false`, 405 // are not overridden by default truthy values from the flags that were not explicitly set. 406 // See https://github.com/docker/docker/issues/20289 for an example. 407 // 408 // TODO: Rewrite configuration logic to avoid same issue with other nullable values, like numbers. 409 namedOptions := make(map[string]interface{}) 410 for key, value := range configSet { 411 f := flags.Lookup(key) 412 if f == nil { // ignore named flags that don't match 413 namedOptions[key] = value 414 continue 415 } 416 417 if _, ok := f.Value.(boolValue); ok { 418 f.Value.Set(fmt.Sprintf("%v", value)) 419 } 420 } 421 if len(namedOptions) > 0 { 422 // set also default for mergeVal flags that are boolValue at the same time. 423 flags.VisitAll(func(f *pflag.Flag) { 424 if opt, named := f.Value.(opts.NamedOption); named { 425 v, set := namedOptions[opt.Name()] 426 _, boolean := f.Value.(boolValue) 427 if set && boolean { 428 f.Value.Set(fmt.Sprintf("%v", v)) 429 } 430 } 431 }) 432 } 433 434 config.ValuesSet = configSet 435 } 436 437 reader = bytes.NewReader(b) 438 if err := json.NewDecoder(reader).Decode(&config); err != nil { 439 return nil, err 440 } 441 442 if config.RootDeprecated != "" { 443 logrus.Warn(`The "graph" config file option is deprecated. Please use "data-root" instead.`) 444 445 if config.Root != "" { 446 return nil, errors.New(`cannot specify both "graph" and "data-root" config file options`) 447 } 448 449 config.Root = config.RootDeprecated 450 } 451 452 return &config, nil 453 } 454 455 // configValuesSet returns the configuration values explicitly set in the file. 456 func configValuesSet(config map[string]interface{}) map[string]interface{} { 457 flatten := make(map[string]interface{}) 458 for k, v := range config { 459 if m, isMap := v.(map[string]interface{}); isMap && !flatOptions[k] { 460 for km, vm := range m { 461 flatten[km] = vm 462 } 463 continue 464 } 465 466 flatten[k] = v 467 } 468 return flatten 469 } 470 471 // findConfigurationConflicts iterates over the provided flags searching for 472 // duplicated configurations and unknown keys. It returns an error with all the conflicts if 473 // it finds any. 474 func findConfigurationConflicts(config map[string]interface{}, flags *pflag.FlagSet) error { 475 // 1. Search keys from the file that we don't recognize as flags. 476 unknownKeys := make(map[string]interface{}) 477 for key, value := range config { 478 if flag := flags.Lookup(key); flag == nil && !skipValidateOptions[key] { 479 unknownKeys[key] = value 480 } 481 } 482 483 // 2. Discard values that implement NamedOption. 484 // Their configuration name differs from their flag name, like `labels` and `label`. 485 if len(unknownKeys) > 0 { 486 unknownNamedConflicts := func(f *pflag.Flag) { 487 if namedOption, ok := f.Value.(opts.NamedOption); ok { 488 if _, valid := unknownKeys[namedOption.Name()]; valid { 489 delete(unknownKeys, namedOption.Name()) 490 } 491 } 492 } 493 flags.VisitAll(unknownNamedConflicts) 494 } 495 496 if len(unknownKeys) > 0 { 497 var unknown []string 498 for key := range unknownKeys { 499 unknown = append(unknown, key) 500 } 501 return fmt.Errorf("the following directives don't match any configuration option: %s", strings.Join(unknown, ", ")) 502 } 503 504 var conflicts []string 505 printConflict := func(name string, flagValue, fileValue interface{}) string { 506 return fmt.Sprintf("%s: (from flag: %v, from file: %v)", name, flagValue, fileValue) 507 } 508 509 // 3. Search keys that are present as a flag and as a file option. 510 duplicatedConflicts := func(f *pflag.Flag) { 511 // search option name in the json configuration payload if the value is a named option 512 if namedOption, ok := f.Value.(opts.NamedOption); ok { 513 if optsValue, ok := config[namedOption.Name()]; ok && !skipDuplicates[namedOption.Name()] { 514 conflicts = append(conflicts, printConflict(namedOption.Name(), f.Value.String(), optsValue)) 515 } 516 } else { 517 // search flag name in the json configuration payload 518 for _, name := range []string{f.Name, f.Shorthand} { 519 if value, ok := config[name]; ok && !skipDuplicates[name] { 520 conflicts = append(conflicts, printConflict(name, f.Value.String(), value)) 521 break 522 } 523 } 524 } 525 } 526 527 flags.Visit(duplicatedConflicts) 528 529 if len(conflicts) > 0 { 530 return fmt.Errorf("the following directives are specified both as a flag and in the configuration file: %s", strings.Join(conflicts, ", ")) 531 } 532 return nil 533 } 534 535 // Validate validates some specific configs. 536 // such as config.DNS, config.Labels, config.DNSSearch, 537 // as well as config.MaxConcurrentDownloads, config.MaxConcurrentUploads. 538 func Validate(config *Config) error { 539 // validate DNS 540 for _, dns := range config.DNS { 541 if _, err := opts.ValidateIPAddress(dns); err != nil { 542 return err 543 } 544 } 545 546 // validate DNSSearch 547 for _, dnsSearch := range config.DNSSearch { 548 if _, err := opts.ValidateDNSSearch(dnsSearch); err != nil { 549 return err 550 } 551 } 552 553 // validate Labels 554 for _, label := range config.Labels { 555 if _, err := opts.ValidateLabel(label); err != nil { 556 return err 557 } 558 } 559 // validate MaxConcurrentDownloads 560 if config.MaxConcurrentDownloads != nil && *config.MaxConcurrentDownloads < 0 { 561 return fmt.Errorf("invalid max concurrent downloads: %d", *config.MaxConcurrentDownloads) 562 } 563 // validate MaxConcurrentUploads 564 if config.MaxConcurrentUploads != nil && *config.MaxConcurrentUploads < 0 { 565 return fmt.Errorf("invalid max concurrent uploads: %d", *config.MaxConcurrentUploads) 566 } 567 568 // validate that "default" runtime is not reset 569 if runtimes := config.GetAllRuntimes(); len(runtimes) > 0 { 570 if _, ok := runtimes[StockRuntimeName]; ok { 571 return fmt.Errorf("runtime name '%s' is reserved", StockRuntimeName) 572 } 573 } 574 575 if _, err := ParseGenericResources(config.NodeGenericResources); err != nil { 576 return err 577 } 578 579 if defaultRuntime := config.GetDefaultRuntimeName(); defaultRuntime != "" && defaultRuntime != StockRuntimeName { 580 runtimes := config.GetAllRuntimes() 581 if _, ok := runtimes[defaultRuntime]; !ok { 582 return fmt.Errorf("specified default runtime '%s' does not exist", defaultRuntime) 583 } 584 } 585 586 // validate platform-specific settings 587 return config.ValidatePlatformConfig() 588 } 589 590 // ModifiedDiscoverySettings returns whether the discovery configuration has been modified or not. 591 func ModifiedDiscoverySettings(config *Config, backendType, advertise string, clusterOpts map[string]string) bool { 592 if config.ClusterStore != backendType || config.ClusterAdvertise != advertise { 593 return true 594 } 595 596 if (config.ClusterOpts == nil && clusterOpts == nil) || 597 (config.ClusterOpts == nil && len(clusterOpts) == 0) || 598 (len(config.ClusterOpts) == 0 && clusterOpts == nil) { 599 return false 600 } 601 602 return !reflect.DeepEqual(config.ClusterOpts, clusterOpts) 603 }