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