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