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