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