github.com/docker/engine@v22.0.0-20211208180946-d456264580cf+incompatible/daemon/config/config.go (about) 1 package config // import "github.com/docker/docker/daemon/config" 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "net" 8 "net/url" 9 "os" 10 "reflect" 11 "strings" 12 "sync" 13 14 daemondiscovery "github.com/docker/docker/daemon/discovery" 15 "github.com/docker/docker/opts" 16 "github.com/docker/docker/pkg/authorization" 17 "github.com/docker/docker/pkg/discovery" 18 "github.com/docker/docker/registry" 19 "github.com/imdario/mergo" 20 "github.com/pkg/errors" 21 "github.com/sirupsen/logrus" 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 for each pull. 29 DefaultMaxConcurrentDownloads = 3 30 // DefaultMaxConcurrentUploads is the default value for 31 // maximum number of uploads that 32 // may take place at a time for each push. 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 39 DefaultShmSize = int64(67108864) 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 // DefaultInitBinary is the name of the default init binary 45 DefaultInitBinary = "docker-init" 46 // DefaultShimBinary is the default shim to be used by containerd if none 47 // is specified 48 DefaultShimBinary = "containerd-shim" 49 // DefaultRuntimeBinary is the default runtime to be used by 50 // containerd if none is specified 51 DefaultRuntimeBinary = "runc" 52 53 // LinuxV1RuntimeName is the runtime used to specify the containerd v1 shim with the runc binary 54 // Note this is different than io.containerd.runc.v1 which would be the v1 shim using the v2 shim API. 55 // This is specifically for the v1 shim using the v1 shim API. 56 LinuxV1RuntimeName = "io.containerd.runtime.v1.linux" 57 // LinuxV2RuntimeName is the runtime used to specify the containerd v2 runc shim 58 LinuxV2RuntimeName = "io.containerd.runc.v2" 59 60 // SeccompProfileDefault is the built-in default seccomp profile. 61 SeccompProfileDefault = "builtin" 62 // SeccompProfileUnconfined is a special profile name for seccomp to use an 63 // "unconfined" seccomp profile. 64 SeccompProfileUnconfined = "unconfined" 65 ) 66 67 var builtinRuntimes = map[string]bool{ 68 StockRuntimeName: true, 69 LinuxV1RuntimeName: true, 70 LinuxV2RuntimeName: true, 71 } 72 73 // flatOptions contains configuration keys 74 // that MUST NOT be parsed as deep structures. 75 // Use this to differentiate these options 76 // with others like the ones in CommonTLSOptions. 77 var flatOptions = map[string]bool{ 78 "cluster-store-opts": true, 79 "log-opts": true, 80 "runtimes": true, 81 "default-ulimits": true, 82 "features": true, 83 "builder": true, 84 } 85 86 // skipValidateOptions contains configuration keys 87 // that will be skipped from findConfigurationConflicts 88 // for unknown flag validation. 89 var skipValidateOptions = map[string]bool{ 90 "features": true, 91 "builder": true, 92 // Corresponding flag has been removed because it was already unusable 93 "deprecated-key-path": true, 94 } 95 96 // skipDuplicates contains configuration keys that 97 // will be skipped when checking duplicated 98 // configuration field defined in both daemon 99 // config file and from dockerd cli flags. 100 // This allows some configurations to be merged 101 // during the parsing. 102 var skipDuplicates = map[string]bool{ 103 "runtimes": true, 104 } 105 106 // LogConfig represents the default log configuration. 107 // It includes json tags to deserialize configuration from a file 108 // using the same names that the flags in the command line use. 109 type LogConfig struct { 110 Type string `json:"log-driver,omitempty"` 111 Config map[string]string `json:"log-opts,omitempty"` 112 } 113 114 // commonBridgeConfig stores all the platform-common bridge driver specific 115 // configuration. 116 type commonBridgeConfig struct { 117 Iface string `json:"bridge,omitempty"` 118 FixedCIDR string `json:"fixed-cidr,omitempty"` 119 } 120 121 // NetworkConfig stores the daemon-wide networking configurations 122 type NetworkConfig struct { 123 // Default address pools for docker networks 124 DefaultAddressPools opts.PoolsOpt `json:"default-address-pools,omitempty"` 125 // NetworkControlPlaneMTU allows to specify the control plane MTU, this will allow to optimize the network use in some components 126 NetworkControlPlaneMTU int `json:"network-control-plane-mtu,omitempty"` 127 } 128 129 // CommonTLSOptions defines TLS configuration for the daemon server. 130 // It includes json tags to deserialize configuration from a file 131 // using the same names that the flags in the command line use. 132 type CommonTLSOptions struct { 133 CAFile string `json:"tlscacert,omitempty"` 134 CertFile string `json:"tlscert,omitempty"` 135 KeyFile string `json:"tlskey,omitempty"` 136 } 137 138 // DNSConfig defines the DNS configurations. 139 type DNSConfig struct { 140 DNS []string `json:"dns,omitempty"` 141 DNSOptions []string `json:"dns-opts,omitempty"` 142 DNSSearch []string `json:"dns-search,omitempty"` 143 HostGatewayIP net.IP `json:"host-gateway-ip,omitempty"` 144 } 145 146 // CommonConfig defines the configuration of a docker daemon which is 147 // common across platforms. 148 // It includes json tags to deserialize configuration from a file 149 // using the same names that the flags in the command line use. 150 type CommonConfig struct { 151 AuthzMiddleware *authorization.Middleware `json:"-"` 152 AuthorizationPlugins []string `json:"authorization-plugins,omitempty"` // AuthorizationPlugins holds list of authorization plugins 153 AutoRestart bool `json:"-"` 154 Context map[string][]string `json:"-"` 155 DisableBridge bool `json:"-"` 156 ExecOptions []string `json:"exec-opts,omitempty"` 157 GraphDriver string `json:"storage-driver,omitempty"` 158 GraphOptions []string `json:"storage-opts,omitempty"` 159 Labels []string `json:"labels,omitempty"` 160 Mtu int `json:"mtu,omitempty"` 161 NetworkDiagnosticPort int `json:"network-diagnostic-port,omitempty"` 162 Pidfile string `json:"pidfile,omitempty"` 163 RawLogs bool `json:"raw-logs,omitempty"` 164 RootDeprecated string `json:"graph,omitempty"` 165 Root string `json:"data-root,omitempty"` 166 ExecRoot string `json:"exec-root,omitempty"` 167 SocketGroup string `json:"group,omitempty"` 168 CorsHeaders string `json:"api-cors-header,omitempty"` 169 ProxyConfig 170 171 // TrustKeyPath is used to generate the daemon ID and for signing schema 1 manifests 172 // when pushing to a registry which does not support schema 2. This field is marked as 173 // deprecated because schema 1 manifests are deprecated in favor of schema 2 and the 174 // daemon ID will use a dedicated identifier not shared with exported signatures. 175 TrustKeyPath string `json:"deprecated-key-path,omitempty"` 176 177 // LiveRestoreEnabled determines whether we should keep containers 178 // alive upon daemon shutdown/start 179 LiveRestoreEnabled bool `json:"live-restore,omitempty"` 180 181 // ClusterStore is the storage backend used for the cluster information. It is used by both 182 // multihost networking (to store networks and endpoints information) and by the node discovery 183 // mechanism. 184 // Deprecated: host-discovery and overlay networks with external k/v stores are deprecated 185 ClusterStore string `json:"cluster-store,omitempty"` 186 187 // ClusterOpts is used to pass options to the discovery package for tuning libkv settings, such 188 // as TLS configuration settings. 189 // Deprecated: host-discovery and overlay networks with external k/v stores are deprecated 190 ClusterOpts map[string]string `json:"cluster-store-opts,omitempty"` 191 192 // ClusterAdvertise is the network endpoint that the Engine advertises for the purpose of node 193 // discovery. This should be a 'host:port' combination on which that daemon instance is 194 // reachable by other hosts. 195 // Deprecated: host-discovery and overlay networks with external k/v stores are deprecated 196 ClusterAdvertise string `json:"cluster-advertise,omitempty"` 197 198 // MaxConcurrentDownloads is the maximum number of downloads that 199 // may take place at a time for each pull. 200 MaxConcurrentDownloads *int `json:"max-concurrent-downloads,omitempty"` 201 202 // MaxConcurrentUploads is the maximum number of uploads that 203 // may take place at a time for each push. 204 MaxConcurrentUploads *int `json:"max-concurrent-uploads,omitempty"` 205 206 // MaxDownloadAttempts is the maximum number of attempts that 207 // may take place at a time for each push. 208 MaxDownloadAttempts *int `json:"max-download-attempts,omitempty"` 209 210 // ShutdownTimeout is the timeout value (in seconds) the daemon will wait for the container 211 // to stop when daemon is being shutdown 212 ShutdownTimeout int `json:"shutdown-timeout,omitempty"` 213 214 Debug bool `json:"debug,omitempty"` 215 Hosts []string `json:"hosts,omitempty"` 216 LogLevel string `json:"log-level,omitempty"` 217 TLS *bool `json:"tls,omitempty"` 218 TLSVerify *bool `json:"tlsverify,omitempty"` 219 220 // Embedded structs that allow config 221 // deserialization without the full struct. 222 CommonTLSOptions 223 224 // SwarmDefaultAdvertiseAddr is the default host/IP or network interface 225 // to use if a wildcard address is specified in the ListenAddr value 226 // given to the /swarm/init endpoint and no advertise address is 227 // specified. 228 SwarmDefaultAdvertiseAddr string `json:"swarm-default-advertise-addr"` 229 230 // SwarmRaftHeartbeatTick is the number of ticks in time for swarm mode raft quorum heartbeat 231 // Typical value is 1 232 SwarmRaftHeartbeatTick uint32 `json:"swarm-raft-heartbeat-tick"` 233 234 // SwarmRaftElectionTick is the number of ticks to elapse before followers in the quorum can propose 235 // a new round of leader election. Default, recommended value is at least 10X that of Heartbeat tick. 236 // Higher values can make the quorum less sensitive to transient faults in the environment, but this also 237 // means it takes longer for the managers to detect a down leader. 238 SwarmRaftElectionTick uint32 `json:"swarm-raft-election-tick"` 239 240 MetricsAddress string `json:"metrics-addr"` 241 242 DNSConfig 243 LogConfig 244 BridgeConfig // bridgeConfig holds bridge network specific configuration. 245 NetworkConfig 246 registry.ServiceOptions 247 248 sync.Mutex 249 // FIXME(vdemeester) This part is not that clear and is mainly dependent on cli flags 250 // It should probably be handled outside this package. 251 ValuesSet map[string]interface{} `json:"-"` 252 253 Experimental bool `json:"experimental"` // Experimental indicates whether experimental features should be exposed or not 254 255 // Exposed node Generic Resources 256 // e.g: ["orange=red", "orange=green", "orange=blue", "apple=3"] 257 NodeGenericResources []string `json:"node-generic-resources,omitempty"` 258 259 // ContainerAddr is the address used to connect to containerd if we're 260 // not starting it ourselves 261 ContainerdAddr string `json:"containerd,omitempty"` 262 263 // CriContainerd determines whether a supervised containerd instance 264 // should be configured with the CRI plugin enabled. This allows using 265 // Docker's containerd instance directly with a Kubernetes kubelet. 266 CriContainerd bool `json:"cri-containerd,omitempty"` 267 268 // Features contains a list of feature key value pairs indicating what features are enabled or disabled. 269 // If a certain feature doesn't appear in this list then it's unset (i.e. neither true nor false). 270 Features map[string]bool `json:"features,omitempty"` 271 272 Builder BuilderConfig `json:"builder,omitempty"` 273 274 ContainerdNamespace string `json:"containerd-namespace,omitempty"` 275 ContainerdPluginNamespace string `json:"containerd-plugin-namespace,omitempty"` 276 277 DefaultRuntime string `json:"default-runtime,omitempty"` 278 } 279 280 // ProxyConfig holds the proxy-configuration for the daemon. 281 type ProxyConfig struct { 282 HTTPProxy string `json:"http-proxy,omitempty"` 283 HTTPSProxy string `json:"https-proxy,omitempty"` 284 NoProxy string `json:"no-proxy,omitempty"` 285 } 286 287 // IsValueSet returns true if a configuration value 288 // was explicitly set in the configuration file. 289 func (conf *Config) IsValueSet(name string) bool { 290 if conf.ValuesSet == nil { 291 return false 292 } 293 _, ok := conf.ValuesSet[name] 294 return ok 295 } 296 297 // New returns a new fully initialized Config struct 298 func New() *Config { 299 return &Config{ 300 CommonConfig: CommonConfig{ 301 LogConfig: LogConfig{ 302 Config: make(map[string]string), 303 }, 304 ClusterOpts: make(map[string]string), 305 }, 306 } 307 } 308 309 // ParseClusterAdvertiseSettings parses the specified advertise settings 310 func ParseClusterAdvertiseSettings(clusterStore, clusterAdvertise string) (string, error) { 311 if clusterAdvertise == "" { 312 return "", daemondiscovery.ErrDiscoveryDisabled 313 } 314 if clusterStore == "" { 315 return "", errors.New("invalid cluster configuration. --cluster-advertise must be accompanied by --cluster-store configuration") 316 } 317 318 advertise, err := discovery.ParseAdvertise(clusterAdvertise) 319 if err != nil { 320 return "", errors.Wrap(err, "discovery advertise parsing failed") 321 } 322 return advertise, nil 323 } 324 325 // GetConflictFreeLabels validates Labels for conflict 326 // In swarm the duplicates for labels are removed 327 // so we only take same values here, no conflict values 328 // If the key-value is the same we will only take the last label 329 func GetConflictFreeLabels(labels []string) ([]string, error) { 330 labelMap := map[string]string{} 331 for _, label := range labels { 332 stringSlice := strings.SplitN(label, "=", 2) 333 if len(stringSlice) > 1 { 334 // If there is a conflict we will return an error 335 if v, ok := labelMap[stringSlice[0]]; ok && v != stringSlice[1] { 336 return nil, fmt.Errorf("conflict labels for %s=%s and %s=%s", stringSlice[0], stringSlice[1], stringSlice[0], v) 337 } 338 labelMap[stringSlice[0]] = stringSlice[1] 339 } 340 } 341 342 newLabels := []string{} 343 for k, v := range labelMap { 344 newLabels = append(newLabels, fmt.Sprintf("%s=%s", k, v)) 345 } 346 return newLabels, nil 347 } 348 349 // Reload reads the configuration in the host and reloads the daemon and server. 350 func Reload(configFile string, flags *pflag.FlagSet, reload func(*Config)) error { 351 logrus.Infof("Got signal to reload configuration, reloading from: %s", configFile) 352 newConfig, err := getConflictFreeConfiguration(configFile, flags) 353 if err != nil { 354 if flags.Changed("config-file") || !os.IsNotExist(err) { 355 return errors.Wrapf(err, "unable to configure the Docker daemon with file %s", configFile) 356 } 357 newConfig = New() 358 } 359 360 if err := Validate(newConfig); err != nil { 361 return errors.Wrap(err, "file configuration validation failed") 362 } 363 364 // Check if duplicate label-keys with different values are found 365 newLabels, err := GetConflictFreeLabels(newConfig.Labels) 366 if err != nil { 367 return err 368 } 369 newConfig.Labels = newLabels 370 371 reload(newConfig) 372 return nil 373 } 374 375 // boolValue is an interface that boolean value flags implement 376 // to tell the command line how to make -name equivalent to -name=true. 377 type boolValue interface { 378 IsBoolFlag() bool 379 } 380 381 // MergeDaemonConfigurations reads a configuration file, 382 // loads the file configuration in an isolated structure, 383 // and merges the configuration provided from flags on top 384 // if there are no conflicts. 385 func MergeDaemonConfigurations(flagsConfig *Config, flags *pflag.FlagSet, configFile string) (*Config, error) { 386 fileConfig, err := getConflictFreeConfiguration(configFile, flags) 387 if err != nil { 388 return nil, err 389 } 390 391 if err := Validate(fileConfig); err != nil { 392 return nil, errors.Wrap(err, "configuration validation from file failed") 393 } 394 395 // merge flags configuration on top of the file configuration 396 if err := mergo.Merge(fileConfig, flagsConfig); err != nil { 397 return nil, err 398 } 399 400 // We need to validate again once both fileConfig and flagsConfig 401 // have been merged 402 if err := Validate(fileConfig); err != nil { 403 return nil, errors.Wrap(err, "merged configuration validation from file and command line flags failed") 404 } 405 406 return fileConfig, nil 407 } 408 409 // getConflictFreeConfiguration loads the configuration from a JSON file. 410 // It compares that configuration with the one provided by the flags, 411 // and returns an error if there are conflicts. 412 func getConflictFreeConfiguration(configFile string, flags *pflag.FlagSet) (*Config, error) { 413 b, err := os.ReadFile(configFile) 414 if err != nil { 415 return nil, err 416 } 417 418 var config Config 419 420 b = bytes.TrimSpace(b) 421 if len(b) == 0 { 422 // empty config file 423 return &config, nil 424 } 425 426 if flags != nil { 427 var jsonConfig map[string]interface{} 428 if err := json.Unmarshal(b, &jsonConfig); err != nil { 429 return nil, err 430 } 431 432 configSet := configValuesSet(jsonConfig) 433 434 if err := findConfigurationConflicts(configSet, flags); err != nil { 435 return nil, err 436 } 437 438 // Override flag values to make sure the values set in the config file with nullable values, like `false`, 439 // are not overridden by default truthy values from the flags that were not explicitly set. 440 // See https://github.com/docker/docker/issues/20289 for an example. 441 // 442 // TODO: Rewrite configuration logic to avoid same issue with other nullable values, like numbers. 443 namedOptions := make(map[string]interface{}) 444 for key, value := range configSet { 445 f := flags.Lookup(key) 446 if f == nil { // ignore named flags that don't match 447 namedOptions[key] = value 448 continue 449 } 450 451 if _, ok := f.Value.(boolValue); ok { 452 f.Value.Set(fmt.Sprintf("%v", value)) 453 } 454 } 455 if len(namedOptions) > 0 { 456 // set also default for mergeVal flags that are boolValue at the same time. 457 flags.VisitAll(func(f *pflag.Flag) { 458 if opt, named := f.Value.(opts.NamedOption); named { 459 v, set := namedOptions[opt.Name()] 460 _, boolean := f.Value.(boolValue) 461 if set && boolean { 462 f.Value.Set(fmt.Sprintf("%v", v)) 463 } 464 } 465 }) 466 } 467 468 config.ValuesSet = configSet 469 } 470 471 if err := json.Unmarshal(b, &config); err != nil { 472 return nil, err 473 } 474 475 if config.RootDeprecated != "" { 476 logrus.Warn(`The "graph" config file option is deprecated. Please use "data-root" instead.`) 477 478 if config.Root != "" { 479 return nil, errors.New(`cannot specify both "graph" and "data-root" config file options`) 480 } 481 482 config.Root = config.RootDeprecated 483 } 484 485 return &config, nil 486 } 487 488 // configValuesSet returns the configuration values explicitly set in the file. 489 func configValuesSet(config map[string]interface{}) map[string]interface{} { 490 flatten := make(map[string]interface{}) 491 for k, v := range config { 492 if m, isMap := v.(map[string]interface{}); isMap && !flatOptions[k] { 493 for km, vm := range m { 494 flatten[km] = vm 495 } 496 continue 497 } 498 499 flatten[k] = v 500 } 501 return flatten 502 } 503 504 // findConfigurationConflicts iterates over the provided flags searching for 505 // duplicated configurations and unknown keys. It returns an error with all the conflicts if 506 // it finds any. 507 func findConfigurationConflicts(config map[string]interface{}, flags *pflag.FlagSet) error { 508 // 1. Search keys from the file that we don't recognize as flags. 509 unknownKeys := make(map[string]interface{}) 510 for key, value := range config { 511 if flag := flags.Lookup(key); flag == nil && !skipValidateOptions[key] { 512 unknownKeys[key] = value 513 } 514 } 515 516 // 2. Discard values that implement NamedOption. 517 // Their configuration name differs from their flag name, like `labels` and `label`. 518 if len(unknownKeys) > 0 { 519 unknownNamedConflicts := func(f *pflag.Flag) { 520 if namedOption, ok := f.Value.(opts.NamedOption); ok { 521 delete(unknownKeys, namedOption.Name()) 522 } 523 } 524 flags.VisitAll(unknownNamedConflicts) 525 } 526 527 if len(unknownKeys) > 0 { 528 var unknown []string 529 for key := range unknownKeys { 530 unknown = append(unknown, key) 531 } 532 return fmt.Errorf("the following directives don't match any configuration option: %s", strings.Join(unknown, ", ")) 533 } 534 535 var conflicts []string 536 printConflict := func(name string, flagValue, fileValue interface{}) string { 537 switch name { 538 case "http-proxy", "https-proxy": 539 flagValue = MaskCredentials(flagValue.(string)) 540 fileValue = MaskCredentials(fileValue.(string)) 541 } 542 return fmt.Sprintf("%s: (from flag: %v, from file: %v)", name, flagValue, fileValue) 543 } 544 545 // 3. Search keys that are present as a flag and as a file option. 546 duplicatedConflicts := func(f *pflag.Flag) { 547 // search option name in the json configuration payload if the value is a named option 548 if namedOption, ok := f.Value.(opts.NamedOption); ok { 549 if optsValue, ok := config[namedOption.Name()]; ok && !skipDuplicates[namedOption.Name()] { 550 conflicts = append(conflicts, printConflict(namedOption.Name(), f.Value.String(), optsValue)) 551 } 552 } else { 553 // search flag name in the json configuration payload 554 for _, name := range []string{f.Name, f.Shorthand} { 555 if value, ok := config[name]; ok && !skipDuplicates[name] { 556 conflicts = append(conflicts, printConflict(name, f.Value.String(), value)) 557 break 558 } 559 } 560 } 561 } 562 563 flags.Visit(duplicatedConflicts) 564 565 if len(conflicts) > 0 { 566 return fmt.Errorf("the following directives are specified both as a flag and in the configuration file: %s", strings.Join(conflicts, ", ")) 567 } 568 return nil 569 } 570 571 // Validate validates some specific configs. 572 // such as config.DNS, config.Labels, config.DNSSearch, 573 // as well as config.MaxConcurrentDownloads, config.MaxConcurrentUploads and config.MaxDownloadAttempts. 574 func Validate(config *Config) error { 575 // validate DNS 576 for _, dns := range config.DNS { 577 if _, err := opts.ValidateIPAddress(dns); err != nil { 578 return err 579 } 580 } 581 582 // validate DNSSearch 583 for _, dnsSearch := range config.DNSSearch { 584 if _, err := opts.ValidateDNSSearch(dnsSearch); err != nil { 585 return err 586 } 587 } 588 589 // validate Labels 590 for _, label := range config.Labels { 591 if _, err := opts.ValidateLabel(label); err != nil { 592 return err 593 } 594 } 595 // validate MaxConcurrentDownloads 596 if config.MaxConcurrentDownloads != nil && *config.MaxConcurrentDownloads < 0 { 597 return fmt.Errorf("invalid max concurrent downloads: %d", *config.MaxConcurrentDownloads) 598 } 599 // validate MaxConcurrentUploads 600 if config.MaxConcurrentUploads != nil && *config.MaxConcurrentUploads < 0 { 601 return fmt.Errorf("invalid max concurrent uploads: %d", *config.MaxConcurrentUploads) 602 } 603 if err := ValidateMaxDownloadAttempts(config); err != nil { 604 return err 605 } 606 607 // validate that "default" runtime is not reset 608 if runtimes := config.GetAllRuntimes(); len(runtimes) > 0 { 609 if _, ok := runtimes[StockRuntimeName]; ok { 610 return fmt.Errorf("runtime name '%s' is reserved", StockRuntimeName) 611 } 612 } 613 614 if _, err := ParseGenericResources(config.NodeGenericResources); err != nil { 615 return err 616 } 617 618 if defaultRuntime := config.GetDefaultRuntimeName(); defaultRuntime != "" { 619 if !builtinRuntimes[defaultRuntime] { 620 runtimes := config.GetAllRuntimes() 621 if _, ok := runtimes[defaultRuntime]; !ok { 622 return fmt.Errorf("specified default runtime '%s' does not exist", defaultRuntime) 623 } 624 } 625 } 626 627 // validate platform-specific settings 628 return config.ValidatePlatformConfig() 629 } 630 631 // ValidateMaxDownloadAttempts validates if the max-download-attempts is within the valid range 632 func ValidateMaxDownloadAttempts(config *Config) error { 633 if config.MaxDownloadAttempts != nil && *config.MaxDownloadAttempts <= 0 { 634 return fmt.Errorf("invalid max download attempts: %d", *config.MaxDownloadAttempts) 635 } 636 return nil 637 } 638 639 // ModifiedDiscoverySettings returns whether the discovery configuration has been modified or not. 640 func ModifiedDiscoverySettings(config *Config, backendType, advertise string, clusterOpts map[string]string) bool { 641 if config.ClusterStore != backendType || config.ClusterAdvertise != advertise { 642 return true 643 } 644 645 if (config.ClusterOpts == nil && clusterOpts == nil) || 646 (config.ClusterOpts == nil && len(clusterOpts) == 0) || 647 (len(config.ClusterOpts) == 0 && clusterOpts == nil) { 648 return false 649 } 650 651 return !reflect.DeepEqual(config.ClusterOpts, clusterOpts) 652 } 653 654 // GetDefaultRuntimeName returns the current default runtime 655 func (conf *Config) GetDefaultRuntimeName() string { 656 conf.Lock() 657 rt := conf.DefaultRuntime 658 conf.Unlock() 659 660 return rt 661 } 662 663 // MaskCredentials masks credentials that are in an URL. 664 func MaskCredentials(rawURL string) string { 665 parsedURL, err := url.Parse(rawURL) 666 if err != nil || parsedURL.User == nil { 667 return rawURL 668 } 669 parsedURL.User = url.UserPassword("xxxxx", "xxxxx") 670 return parsedURL.String() 671 }