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