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