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