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