github.com/moby/docker@v26.1.3+incompatible/daemon/config/config.go (about) 1 package config // import "github.com/docker/docker/daemon/config" 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "net" 9 "net/url" 10 "os" 11 "strings" 12 13 "dario.cat/mergo" 14 "github.com/containerd/log" 15 "github.com/docker/docker/api" 16 "github.com/docker/docker/api/types/versions" 17 "github.com/docker/docker/opts" 18 "github.com/docker/docker/registry" 19 "github.com/pkg/errors" 20 "github.com/spf13/pflag" 21 "golang.org/x/text/encoding" 22 "golang.org/x/text/encoding/unicode" 23 "golang.org/x/text/transform" 24 ) 25 26 const ( 27 // DefaultMaxConcurrentDownloads is the default value for 28 // maximum number of downloads that 29 // may take place at a time. 30 DefaultMaxConcurrentDownloads = 3 31 // DefaultMaxConcurrentUploads is the default value for 32 // maximum number of uploads that 33 // may take place at a time. 34 DefaultMaxConcurrentUploads = 5 35 // DefaultDownloadAttempts is the default value for 36 // maximum number of attempts that 37 // may take place at a time for each pull when the connection is lost. 38 DefaultDownloadAttempts = 5 39 // DefaultShmSize is the default value for container's shm size (64 MiB) 40 DefaultShmSize int64 = 64 * 1024 * 1024 41 // DefaultNetworkMtu is the default value for network MTU 42 DefaultNetworkMtu = 1500 43 // DisableNetworkBridge is the default value of the option to disable network bridge 44 DisableNetworkBridge = "none" 45 // DefaultShutdownTimeout is the default shutdown timeout (in seconds) for 46 // the daemon for containers to stop when it is shutting down. 47 DefaultShutdownTimeout = 15 48 // DefaultInitBinary is the name of the default init binary 49 DefaultInitBinary = "docker-init" 50 // DefaultRuntimeBinary is the default runtime to be used by 51 // containerd if none is specified 52 DefaultRuntimeBinary = "runc" 53 // DefaultContainersNamespace is the name of the default containerd namespace used for users containers. 54 DefaultContainersNamespace = "moby" 55 // DefaultPluginNamespace is the name of the default containerd namespace used for plugins. 56 DefaultPluginNamespace = "plugins.moby" 57 // defaultMinAPIVersion is the minimum API version supported by the API. 58 // This version can be overridden through the "DOCKER_MIN_API_VERSION" 59 // environment variable. It currently defaults to the minimum API version 60 // supported by the API server. 61 defaultMinAPIVersion = api.MinSupportedAPIVersion 62 // SeccompProfileDefault is the built-in default seccomp profile. 63 SeccompProfileDefault = "builtin" 64 // SeccompProfileUnconfined is a special profile name for seccomp to use an 65 // "unconfined" seccomp profile. 66 SeccompProfileUnconfined = "unconfined" 67 ) 68 69 // flatOptions contains configuration keys 70 // that MUST NOT be parsed as deep structures. 71 // Use this to differentiate these options 72 // with others like the ones in TLSOptions. 73 var flatOptions = map[string]bool{ 74 "cluster-store-opts": true, 75 "default-network-opts": true, 76 "log-opts": true, 77 "runtimes": true, 78 "default-ulimits": true, 79 "features": true, 80 "builder": true, 81 } 82 83 // skipValidateOptions contains configuration keys 84 // that will be skipped from findConfigurationConflicts 85 // for unknown flag validation. 86 var skipValidateOptions = map[string]bool{ 87 "features": true, 88 "builder": true, 89 // Corresponding flag has been removed because it was already unusable 90 "deprecated-key-path": true, 91 } 92 93 // skipDuplicates contains configuration keys that 94 // will be skipped when checking duplicated 95 // configuration field defined in both daemon 96 // config file and from dockerd cli flags. 97 // This allows some configurations to be merged 98 // during the parsing. 99 var skipDuplicates = map[string]bool{ 100 "runtimes": true, 101 } 102 103 // LogConfig represents the default log configuration. 104 // It includes json tags to deserialize configuration from a file 105 // using the same names that the flags in the command line use. 106 type LogConfig struct { 107 Type string `json:"log-driver,omitempty"` 108 Config map[string]string `json:"log-opts,omitempty"` 109 } 110 111 // commonBridgeConfig stores all the platform-common bridge driver specific 112 // configuration. 113 type commonBridgeConfig struct { 114 Iface string `json:"bridge,omitempty"` 115 FixedCIDR string `json:"fixed-cidr,omitempty"` 116 } 117 118 // NetworkConfig stores the daemon-wide networking configurations 119 type NetworkConfig struct { 120 // Default address pools for docker networks 121 DefaultAddressPools opts.PoolsOpt `json:"default-address-pools,omitempty"` 122 // NetworkControlPlaneMTU allows to specify the control plane MTU, this will allow to optimize the network use in some components 123 NetworkControlPlaneMTU int `json:"network-control-plane-mtu,omitempty"` 124 // Default options for newly created networks 125 DefaultNetworkOpts map[string]map[string]string `json:"default-network-opts,omitempty"` 126 } 127 128 // TLSOptions defines TLS configuration for the daemon server. 129 // It includes json tags to deserialize configuration from a file 130 // using the same names that the flags in the command line use. 131 type TLSOptions struct { 132 CAFile string `json:"tlscacert,omitempty"` 133 CertFile string `json:"tlscert,omitempty"` 134 KeyFile string `json:"tlskey,omitempty"` 135 } 136 137 // DNSConfig defines the DNS configurations. 138 type DNSConfig struct { 139 DNS []net.IP `json:"dns,omitempty"` 140 DNSOptions []string `json:"dns-opts,omitempty"` 141 DNSSearch []string `json:"dns-search,omitempty"` 142 HostGatewayIP net.IP `json:"host-gateway-ip,omitempty"` 143 } 144 145 // CommonConfig defines the configuration of a docker daemon which is 146 // common across platforms. 147 // It includes json tags to deserialize configuration from a file 148 // using the same names that the flags in the command line use. 149 type CommonConfig struct { 150 AuthorizationPlugins []string `json:"authorization-plugins,omitempty"` // AuthorizationPlugins holds list of authorization plugins 151 AutoRestart bool `json:"-"` 152 DisableBridge bool `json:"-"` 153 ExecOptions []string `json:"exec-opts,omitempty"` 154 GraphDriver string `json:"storage-driver,omitempty"` 155 GraphOptions []string `json:"storage-opts,omitempty"` 156 Labels []string `json:"labels,omitempty"` 157 NetworkDiagnosticPort int `json:"network-diagnostic-port,omitempty"` 158 Pidfile string `json:"pidfile,omitempty"` 159 RawLogs bool `json:"raw-logs,omitempty"` 160 Root string `json:"data-root,omitempty"` 161 ExecRoot string `json:"exec-root,omitempty"` 162 SocketGroup string `json:"group,omitempty"` 163 CorsHeaders string `json:"api-cors-header,omitempty"` 164 165 // Proxies holds the proxies that are configured for the daemon. 166 Proxies `json:"proxies"` 167 168 // LiveRestoreEnabled determines whether we should keep containers 169 // alive upon daemon shutdown/start 170 LiveRestoreEnabled bool `json:"live-restore,omitempty"` 171 172 // MaxConcurrentDownloads is the maximum number of downloads that 173 // may take place at a time for each pull. 174 MaxConcurrentDownloads int `json:"max-concurrent-downloads,omitempty"` 175 176 // MaxConcurrentUploads is the maximum number of uploads that 177 // may take place at a time for each push. 178 MaxConcurrentUploads int `json:"max-concurrent-uploads,omitempty"` 179 180 // MaxDownloadAttempts is the maximum number of attempts that 181 // may take place at a time for each push. 182 MaxDownloadAttempts int `json:"max-download-attempts,omitempty"` 183 184 // ShutdownTimeout is the timeout value (in seconds) the daemon will wait for the container 185 // to stop when daemon is being shutdown 186 ShutdownTimeout int `json:"shutdown-timeout,omitempty"` 187 188 Debug bool `json:"debug,omitempty"` 189 Hosts []string `json:"hosts,omitempty"` 190 LogLevel string `json:"log-level,omitempty"` 191 LogFormat log.OutputFormat `json:"log-format,omitempty"` 192 TLS *bool `json:"tls,omitempty"` 193 TLSVerify *bool `json:"tlsverify,omitempty"` 194 195 // Embedded structs that allow config 196 // deserialization without the full struct. 197 TLSOptions 198 199 // SwarmDefaultAdvertiseAddr is the default host/IP or network interface 200 // to use if a wildcard address is specified in the ListenAddr value 201 // given to the /swarm/init endpoint and no advertise address is 202 // specified. 203 SwarmDefaultAdvertiseAddr string `json:"swarm-default-advertise-addr"` 204 205 // SwarmRaftHeartbeatTick is the number of ticks in time for swarm mode raft quorum heartbeat 206 // Typical value is 1 207 SwarmRaftHeartbeatTick uint32 `json:"swarm-raft-heartbeat-tick"` 208 209 // SwarmRaftElectionTick is the number of ticks to elapse before followers in the quorum can propose 210 // a new round of leader election. Default, recommended value is at least 10X that of Heartbeat tick. 211 // Higher values can make the quorum less sensitive to transient faults in the environment, but this also 212 // means it takes longer for the managers to detect a down leader. 213 SwarmRaftElectionTick uint32 `json:"swarm-raft-election-tick"` 214 215 MetricsAddress string `json:"metrics-addr"` 216 217 DNSConfig 218 LogConfig 219 BridgeConfig // BridgeConfig holds bridge network specific configuration. 220 NetworkConfig 221 registry.ServiceOptions 222 223 // FIXME(vdemeester) This part is not that clear and is mainly dependent on cli flags 224 // It should probably be handled outside this package. 225 ValuesSet map[string]interface{} `json:"-"` 226 227 Experimental bool `json:"experimental"` // Experimental indicates whether experimental features should be exposed or not 228 229 // Exposed node Generic Resources 230 // e.g: ["orange=red", "orange=green", "orange=blue", "apple=3"] 231 NodeGenericResources []string `json:"node-generic-resources,omitempty"` 232 233 // ContainerAddr is the address used to connect to containerd if we're 234 // not starting it ourselves 235 ContainerdAddr string `json:"containerd,omitempty"` 236 237 // CriContainerd determines whether a supervised containerd instance 238 // should be configured with the CRI plugin enabled. This allows using 239 // Docker's containerd instance directly with a Kubernetes kubelet. 240 CriContainerd bool `json:"cri-containerd,omitempty"` 241 242 // Features contains a list of feature key value pairs indicating what features are enabled or disabled. 243 // If a certain feature doesn't appear in this list then it's unset (i.e. neither true nor false). 244 Features map[string]bool `json:"features,omitempty"` 245 246 Builder BuilderConfig `json:"builder,omitempty"` 247 248 ContainerdNamespace string `json:"containerd-namespace,omitempty"` 249 ContainerdPluginNamespace string `json:"containerd-plugin-namespace,omitempty"` 250 251 DefaultRuntime string `json:"default-runtime,omitempty"` 252 253 // CDISpecDirs is a list of directories in which CDI specifications can be found. 254 CDISpecDirs []string `json:"cdi-spec-dirs,omitempty"` 255 256 // The minimum API version provided by the daemon. Defaults to [defaultMinAPIVersion]. 257 // 258 // The DOCKER_MIN_API_VERSION allows overriding the minimum API version within 259 // constraints of the minimum and maximum (current) supported API versions. 260 // 261 // API versions older than [defaultMinAPIVersion] are deprecated and 262 // to be removed in a future release. The "DOCKER_MIN_API_VERSION" env 263 // var should only be used for exceptional cases, and the MinAPIVersion 264 // field is therefore not included in the JSON representation. 265 MinAPIVersion string `json:"-"` 266 } 267 268 // Proxies holds the proxies that are configured for the daemon. 269 type Proxies struct { 270 HTTPProxy string `json:"http-proxy,omitempty"` 271 HTTPSProxy string `json:"https-proxy,omitempty"` 272 NoProxy string `json:"no-proxy,omitempty"` 273 } 274 275 // IsValueSet returns true if a configuration value 276 // was explicitly set in the configuration file. 277 func (conf *Config) IsValueSet(name string) bool { 278 if conf.ValuesSet == nil { 279 return false 280 } 281 _, ok := conf.ValuesSet[name] 282 return ok 283 } 284 285 // New returns a new fully initialized Config struct with default values set. 286 func New() (*Config, error) { 287 // platform-agnostic default values for the Config. 288 cfg := &Config{ 289 CommonConfig: CommonConfig{ 290 ShutdownTimeout: DefaultShutdownTimeout, 291 LogConfig: LogConfig{ 292 Config: make(map[string]string), 293 }, 294 MaxConcurrentDownloads: DefaultMaxConcurrentDownloads, 295 MaxConcurrentUploads: DefaultMaxConcurrentUploads, 296 MaxDownloadAttempts: DefaultDownloadAttempts, 297 BridgeConfig: BridgeConfig{ 298 DefaultBridgeConfig: DefaultBridgeConfig{ 299 MTU: DefaultNetworkMtu, 300 }, 301 }, 302 NetworkConfig: NetworkConfig{ 303 NetworkControlPlaneMTU: DefaultNetworkMtu, 304 DefaultNetworkOpts: make(map[string]map[string]string), 305 }, 306 ContainerdNamespace: DefaultContainersNamespace, 307 ContainerdPluginNamespace: DefaultPluginNamespace, 308 DefaultRuntime: StockRuntimeName, 309 MinAPIVersion: defaultMinAPIVersion, 310 }, 311 } 312 313 if err := setPlatformDefaults(cfg); err != nil { 314 return nil, err 315 } 316 317 return cfg, nil 318 } 319 320 // GetConflictFreeLabels validates Labels for conflict 321 // In swarm the duplicates for labels are removed 322 // so we only take same values here, no conflict values 323 // If the key-value is the same we will only take the last label 324 func GetConflictFreeLabels(labels []string) ([]string, error) { 325 labelMap := map[string]string{} 326 for _, label := range labels { 327 key, val, ok := strings.Cut(label, "=") 328 if ok { 329 // If there is a conflict we will return an error 330 if v, ok := labelMap[key]; ok && v != val { 331 return nil, errors.Errorf("conflict labels for %s=%s and %s=%s", key, val, key, v) 332 } 333 labelMap[key] = val 334 } 335 } 336 337 newLabels := []string{} 338 for k, v := range labelMap { 339 newLabels = append(newLabels, k+"="+v) 340 } 341 return newLabels, nil 342 } 343 344 // Reload reads the configuration in the host and reloads the daemon and server. 345 func Reload(configFile string, flags *pflag.FlagSet, reload func(*Config)) error { 346 log.G(context.TODO()).Infof("Got signal to reload configuration, reloading from: %s", configFile) 347 newConfig, err := getConflictFreeConfiguration(configFile, flags) 348 if err != nil { 349 if flags.Changed("config-file") || !os.IsNotExist(err) { 350 return errors.Wrapf(err, "unable to configure the Docker daemon with file %s", configFile) 351 } 352 newConfig, err = New() 353 if err != nil { 354 return err 355 } 356 } 357 358 // Check if duplicate label-keys with different values are found 359 newLabels, err := GetConflictFreeLabels(newConfig.Labels) 360 if err != nil { 361 return err 362 } 363 newConfig.Labels = newLabels 364 365 // TODO(thaJeztah) This logic is problematic and needs a rewrite; 366 // This is validating newConfig before the "reload()" callback is executed. 367 // At this point, newConfig may be a partial configuration, to be merged 368 // with the existing configuration in the "reload()" callback. Validating 369 // this config before it's merged can result in incorrect validation errors. 370 // 371 // However, the current "reload()" callback we use is DaemonCli.reloadConfig(), 372 // which includes a call to Daemon.Reload(), which both performs "merging" 373 // and validation, as well as actually updating the daemon configuration. 374 // Calling DaemonCli.reloadConfig() *before* validation, could thus lead to 375 // a failure in that function (making the reload non-atomic). 376 // 377 // While *some* errors could always occur when applying/updating the config, 378 // we should make it more atomic, and; 379 // 380 // 1. get (a copy of) the active configuration 381 // 2. get the new configuration 382 // 3. apply the (reloadable) options from the new configuration 383 // 4. validate the merged results 384 // 5. apply the new configuration. 385 if err := Validate(newConfig); err != nil { 386 return errors.Wrap(err, "file configuration validation failed") 387 } 388 389 reload(newConfig) 390 return nil 391 } 392 393 // boolValue is an interface that boolean value flags implement 394 // to tell the command line how to make -name equivalent to -name=true. 395 type boolValue interface { 396 IsBoolFlag() bool 397 } 398 399 // MergeDaemonConfigurations reads a configuration file, 400 // loads the file configuration in an isolated structure, 401 // and merges the configuration provided from flags on top 402 // if there are no conflicts. 403 func MergeDaemonConfigurations(flagsConfig *Config, flags *pflag.FlagSet, configFile string) (*Config, error) { 404 fileConfig, err := getConflictFreeConfiguration(configFile, flags) 405 if err != nil { 406 return nil, err 407 } 408 409 // merge flags configuration on top of the file configuration 410 if err := mergo.Merge(fileConfig, flagsConfig); err != nil { 411 return nil, err 412 } 413 414 // validate the merged fileConfig and flagsConfig 415 if err := Validate(fileConfig); err != nil { 416 return nil, errors.Wrap(err, "merged configuration validation from file and command line flags failed") 417 } 418 419 return fileConfig, nil 420 } 421 422 // getConflictFreeConfiguration loads the configuration from a JSON file. 423 // It compares that configuration with the one provided by the flags, 424 // and returns an error if there are conflicts. 425 func getConflictFreeConfiguration(configFile string, flags *pflag.FlagSet) (*Config, error) { 426 b, err := os.ReadFile(configFile) 427 if err != nil { 428 return nil, err 429 } 430 431 // Decode the contents of the JSON file using a [byte order mark] if present, instead of assuming UTF-8 without BOM. 432 // The BOM, if present, will be used to determine the encoding. If no BOM is present, we will assume the default 433 // and preferred encoding for JSON as defined by [RFC 8259], UTF-8 without BOM. 434 // 435 // While JSON is normatively UTF-8 with no BOM, there are a couple of reasons to decode here: 436 // * UTF-8 with BOM is something that new implementations should avoid producing; however, [RFC 8259 Section 8.1] 437 // allows implementations to ignore the UTF-8 BOM when present for interoperability. Older versions of Notepad, 438 // the only text editor available out of the box on Windows Server, writes UTF-8 with a BOM by default. 439 // * The default encoding for [Windows PowerShell] is UTF-16 LE with BOM. While encodings in PowerShell can be a 440 // bit idiosyncratic, BOMs are still generally written. There is no support for selecting UTF-8 without a BOM as 441 // the encoding in Windows PowerShell, though some Cmdlets only write UTF-8 with no BOM. PowerShell Core 442 // introduces `utf8NoBOM` and makes it the default, but PowerShell Core is unlikely to be the implementation for 443 // a majority of Windows Server + PowerShell users. 444 // * While [RFC 8259 Section 8.1] asserts that software that is not part of a closed ecosystem or that crosses a 445 // network boundary should only support UTF-8, and should never write a BOM, it does acknowledge older versions 446 // of the standard, such as [RFC 7159 Section 8.1]. In the interest of pragmatism and easing pain for Windows 447 // users, we consider Windows tools such as Windows PowerShell and Notepad part of our ecosystem, and support 448 // the two most common encodings: UTF-16 LE with BOM, and UTF-8 with BOM, in addition to the standard UTF-8 449 // without BOM. 450 // 451 // [byte order mark]: https://www.unicode.org/faq/utf_bom.html#BOM 452 // [RFC 8259]: https://www.rfc-editor.org/rfc/rfc8259 453 // [RFC 8259 Section 8.1]: https://www.rfc-editor.org/rfc/rfc8259#section-8.1 454 // [RFC 7159 Section 8.1]: https://www.rfc-editor.org/rfc/rfc7159#section-8.1 455 // [Windows PowerShell]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_character_encoding?view=powershell-5.1 456 b, n, err := transform.Bytes(transform.Chain(unicode.BOMOverride(transform.Nop), encoding.UTF8Validator), b) 457 if err != nil { 458 return nil, errors.Wrapf(err, "failed to decode configuration JSON at offset %d", n) 459 } 460 // Trim whitespace so that an empty config can be detected for an early return. 461 b = bytes.TrimSpace(b) 462 463 var config Config 464 if len(b) == 0 { 465 return &config, nil // early return on empty config 466 } 467 468 if flags != nil { 469 var jsonConfig map[string]interface{} 470 if err := json.Unmarshal(b, &jsonConfig); err != nil { 471 return nil, err 472 } 473 474 configSet := configValuesSet(jsonConfig) 475 476 if err := findConfigurationConflicts(configSet, flags); err != nil { 477 return nil, err 478 } 479 480 // Override flag values to make sure the values set in the config file with nullable values, like `false`, 481 // are not overridden by default truthy values from the flags that were not explicitly set. 482 // See https://github.com/docker/docker/issues/20289 for an example. 483 // 484 // TODO: Rewrite configuration logic to avoid same issue with other nullable values, like numbers. 485 namedOptions := make(map[string]interface{}) 486 for key, value := range configSet { 487 f := flags.Lookup(key) 488 if f == nil { // ignore named flags that don't match 489 namedOptions[key] = value 490 continue 491 } 492 493 if _, ok := f.Value.(boolValue); ok { 494 f.Value.Set(fmt.Sprintf("%v", value)) 495 } 496 } 497 if len(namedOptions) > 0 { 498 // set also default for mergeVal flags that are boolValue at the same time. 499 flags.VisitAll(func(f *pflag.Flag) { 500 if opt, named := f.Value.(opts.NamedOption); named { 501 v, set := namedOptions[opt.Name()] 502 _, boolean := f.Value.(boolValue) 503 if set && boolean { 504 f.Value.Set(fmt.Sprintf("%v", v)) 505 } 506 } 507 }) 508 } 509 510 config.ValuesSet = configSet 511 } 512 513 if err := json.Unmarshal(b, &config); err != nil { 514 return nil, err 515 } 516 517 return &config, nil 518 } 519 520 // configValuesSet returns the configuration values explicitly set in the file. 521 func configValuesSet(config map[string]interface{}) map[string]interface{} { 522 flatten := make(map[string]interface{}) 523 for k, v := range config { 524 if m, isMap := v.(map[string]interface{}); isMap && !flatOptions[k] { 525 for km, vm := range m { 526 flatten[km] = vm 527 } 528 continue 529 } 530 531 flatten[k] = v 532 } 533 return flatten 534 } 535 536 // findConfigurationConflicts iterates over the provided flags searching for 537 // duplicated configurations and unknown keys. It returns an error with all the conflicts if 538 // it finds any. 539 func findConfigurationConflicts(config map[string]interface{}, flags *pflag.FlagSet) error { 540 // 1. Search keys from the file that we don't recognize as flags. 541 unknownKeys := make(map[string]interface{}) 542 for key, value := range config { 543 if flag := flags.Lookup(key); flag == nil && !skipValidateOptions[key] { 544 unknownKeys[key] = value 545 } 546 } 547 548 // 2. Discard values that implement NamedOption. 549 // Their configuration name differs from their flag name, like `labels` and `label`. 550 if len(unknownKeys) > 0 { 551 unknownNamedConflicts := func(f *pflag.Flag) { 552 if namedOption, ok := f.Value.(opts.NamedOption); ok { 553 delete(unknownKeys, namedOption.Name()) 554 } 555 } 556 flags.VisitAll(unknownNamedConflicts) 557 } 558 559 if len(unknownKeys) > 0 { 560 var unknown []string 561 for key := range unknownKeys { 562 unknown = append(unknown, key) 563 } 564 return errors.Errorf("the following directives don't match any configuration option: %s", strings.Join(unknown, ", ")) 565 } 566 567 var conflicts []string 568 printConflict := func(name string, flagValue, fileValue interface{}) string { 569 switch name { 570 case "http-proxy", "https-proxy": 571 flagValue = MaskCredentials(flagValue.(string)) 572 fileValue = MaskCredentials(fileValue.(string)) 573 } 574 return fmt.Sprintf("%s: (from flag: %v, from file: %v)", name, flagValue, fileValue) 575 } 576 577 // 3. Search keys that are present as a flag and as a file option. 578 duplicatedConflicts := func(f *pflag.Flag) { 579 // search option name in the json configuration payload if the value is a named option 580 if namedOption, ok := f.Value.(opts.NamedOption); ok { 581 if optsValue, ok := config[namedOption.Name()]; ok && !skipDuplicates[namedOption.Name()] { 582 conflicts = append(conflicts, printConflict(namedOption.Name(), f.Value.String(), optsValue)) 583 } 584 } else { 585 // search flag name in the json configuration payload 586 for _, name := range []string{f.Name, f.Shorthand} { 587 if value, ok := config[name]; ok && !skipDuplicates[name] { 588 conflicts = append(conflicts, printConflict(name, f.Value.String(), value)) 589 break 590 } 591 } 592 } 593 } 594 595 flags.Visit(duplicatedConflicts) 596 597 if len(conflicts) > 0 { 598 return errors.Errorf("the following directives are specified both as a flag and in the configuration file: %s", strings.Join(conflicts, ", ")) 599 } 600 return nil 601 } 602 603 // ValidateMinAPIVersion verifies if the given API version is within the 604 // range supported by the daemon. It is used to validate a custom minimum 605 // API version set through DOCKER_MIN_API_VERSION. 606 func ValidateMinAPIVersion(ver string) error { 607 if ver == "" { 608 return errors.New(`value is empty`) 609 } 610 if strings.EqualFold(ver[0:1], "v") { 611 return errors.New(`API version must be provided without "v" prefix`) 612 } 613 if versions.LessThan(ver, defaultMinAPIVersion) { 614 return errors.Errorf(`minimum supported API version is %s: %s`, defaultMinAPIVersion, ver) 615 } 616 if versions.GreaterThan(ver, api.DefaultVersion) { 617 return errors.Errorf(`maximum supported API version is %s: %s`, api.DefaultVersion, ver) 618 } 619 return nil 620 } 621 622 // Validate validates some specific configs. 623 // such as config.DNS, config.Labels, config.DNSSearch, 624 // as well as config.MaxConcurrentDownloads, config.MaxConcurrentUploads and config.MaxDownloadAttempts. 625 func Validate(config *Config) error { 626 // validate log-level 627 if config.LogLevel != "" { 628 // FIXME(thaJeztah): find a better way for this; this depends on knowledge of containerd's log package internals. 629 // Alternatively: try log.SetLevel(config.LogLevel), and restore the original level, but this also requires internal knowledge. 630 switch strings.ToLower(config.LogLevel) { 631 case "panic", "fatal", "error", "warn", "info", "debug", "trace": 632 // These are valid. See [log.SetLevel] for a list of accepted levels. 633 default: 634 return errors.Errorf("invalid logging level: %s", config.LogLevel) 635 } 636 } 637 638 // validate log-format 639 if logFormat := config.LogFormat; logFormat != "" { 640 switch logFormat { 641 case log.TextFormat, log.JSONFormat: 642 // These are valid 643 default: 644 return errors.Errorf("invalid log format: %s", logFormat) 645 } 646 } 647 648 // validate DNSSearch 649 for _, dnsSearch := range config.DNSSearch { 650 if _, err := opts.ValidateDNSSearch(dnsSearch); err != nil { 651 return err 652 } 653 } 654 655 // validate Labels 656 for _, label := range config.Labels { 657 if _, err := opts.ValidateLabel(label); err != nil { 658 return err 659 } 660 } 661 662 // TODO(thaJeztah) Validations below should not accept "0" to be valid; see Validate() for a more in-depth description of this problem 663 if config.MTU < 0 { 664 return errors.Errorf("invalid default MTU: %d", config.MTU) 665 } 666 if config.MaxConcurrentDownloads < 0 { 667 return errors.Errorf("invalid max concurrent downloads: %d", config.MaxConcurrentDownloads) 668 } 669 if config.MaxConcurrentUploads < 0 { 670 return errors.Errorf("invalid max concurrent uploads: %d", config.MaxConcurrentUploads) 671 } 672 if config.MaxDownloadAttempts < 0 { 673 return errors.Errorf("invalid max download attempts: %d", config.MaxDownloadAttempts) 674 } 675 676 if _, err := ParseGenericResources(config.NodeGenericResources); err != nil { 677 return err 678 } 679 680 for _, h := range config.Hosts { 681 if _, err := opts.ValidateHost(h); err != nil { 682 return err 683 } 684 } 685 686 // validate platform-specific settings 687 return config.ValidatePlatformConfig() 688 } 689 690 // MaskCredentials masks credentials that are in an URL. 691 func MaskCredentials(rawURL string) string { 692 parsedURL, err := url.Parse(rawURL) 693 if err != nil || parsedURL.User == nil { 694 return rawURL 695 } 696 parsedURL.User = url.UserPassword("xxxxx", "xxxxx") 697 return parsedURL.String() 698 }