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