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