config.go 23 KB

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