config.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. package config
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "io/ioutil"
  9. "reflect"
  10. "runtime"
  11. "strings"
  12. "sync"
  13. daemondiscovery "github.com/docker/docker/daemon/discovery"
  14. "github.com/docker/docker/opts"
  15. "github.com/docker/docker/pkg/authorization"
  16. "github.com/docker/docker/pkg/discovery"
  17. "github.com/docker/docker/registry"
  18. "github.com/imdario/mergo"
  19. "github.com/sirupsen/logrus"
  20. "github.com/spf13/pflag"
  21. )
  22. const (
  23. // DefaultMaxConcurrentDownloads is the default value for
  24. // maximum number of downloads that
  25. // may take place at a time for each pull.
  26. DefaultMaxConcurrentDownloads = 3
  27. // DefaultMaxConcurrentUploads is the default value for
  28. // maximum number of uploads that
  29. // may take place at a time for each push.
  30. DefaultMaxConcurrentUploads = 5
  31. // StockRuntimeName is the reserved name/alias used to represent the
  32. // OCI runtime being shipped with the docker daemon package.
  33. StockRuntimeName = "runc"
  34. // DefaultShmSize is the default value for container's shm size
  35. DefaultShmSize = int64(67108864)
  36. // DefaultNetworkMtu is the default value for network MTU
  37. DefaultNetworkMtu = 1500
  38. // DisableNetworkBridge is the default value of the option to disable network bridge
  39. DisableNetworkBridge = "none"
  40. // DefaultInitBinary is the name of the default init binary
  41. DefaultInitBinary = "docker-init"
  42. )
  43. // flatOptions contains configuration keys
  44. // that MUST NOT be parsed as deep structures.
  45. // Use this to differentiate these options
  46. // with others like the ones in CommonTLSOptions.
  47. var flatOptions = map[string]bool{
  48. "cluster-store-opts": true,
  49. "log-opts": true,
  50. "runtimes": true,
  51. "default-ulimits": true,
  52. }
  53. // LogConfig represents the default log configuration.
  54. // It includes json tags to deserialize configuration from a file
  55. // using the same names that the flags in the command line use.
  56. type LogConfig struct {
  57. Type string `json:"log-driver,omitempty"`
  58. Config map[string]string `json:"log-opts,omitempty"`
  59. }
  60. // commonBridgeConfig stores all the platform-common bridge driver specific
  61. // configuration.
  62. type commonBridgeConfig struct {
  63. Iface string `json:"bridge,omitempty"`
  64. FixedCIDR string `json:"fixed-cidr,omitempty"`
  65. }
  66. // CommonTLSOptions defines TLS configuration for the daemon server.
  67. // It includes json tags to deserialize configuration from a file
  68. // using the same names that the flags in the command line use.
  69. type CommonTLSOptions struct {
  70. CAFile string `json:"tlscacert,omitempty"`
  71. CertFile string `json:"tlscert,omitempty"`
  72. KeyFile string `json:"tlskey,omitempty"`
  73. }
  74. // CommonConfig defines the configuration of a docker daemon which is
  75. // common across platforms.
  76. // It includes json tags to deserialize configuration from a file
  77. // using the same names that the flags in the command line use.
  78. type CommonConfig struct {
  79. AuthzMiddleware *authorization.Middleware `json:"-"`
  80. AuthorizationPlugins []string `json:"authorization-plugins,omitempty"` // AuthorizationPlugins holds list of authorization plugins
  81. AutoRestart bool `json:"-"`
  82. Context map[string][]string `json:"-"`
  83. DisableBridge bool `json:"-"`
  84. DNS []string `json:"dns,omitempty"`
  85. DNSOptions []string `json:"dns-opts,omitempty"`
  86. DNSSearch []string `json:"dns-search,omitempty"`
  87. ExecOptions []string `json:"exec-opts,omitempty"`
  88. GraphDriver string `json:"storage-driver,omitempty"`
  89. GraphOptions []string `json:"storage-opts,omitempty"`
  90. Labels []string `json:"labels,omitempty"`
  91. Mtu int `json:"mtu,omitempty"`
  92. Pidfile string `json:"pidfile,omitempty"`
  93. RawLogs bool `json:"raw-logs,omitempty"`
  94. RootDeprecated string `json:"graph,omitempty"`
  95. Root string `json:"data-root,omitempty"`
  96. SocketGroup string `json:"group,omitempty"`
  97. CorsHeaders string `json:"api-cors-header,omitempty"`
  98. // TrustKeyPath is used to generate the daemon ID and for signing schema 1 manifests
  99. // when pushing to a registry which does not support schema 2. This field is marked as
  100. // deprecated because schema 1 manifests are deprecated in favor of schema 2 and the
  101. // daemon ID will use a dedicated identifier not shared with exported signatures.
  102. TrustKeyPath string `json:"deprecated-key-path,omitempty"`
  103. // LiveRestoreEnabled determines whether we should keep containers
  104. // alive upon daemon shutdown/start
  105. LiveRestoreEnabled bool `json:"live-restore,omitempty"`
  106. // ClusterStore is the storage backend used for the cluster information. It is used by both
  107. // multihost networking (to store networks and endpoints information) and by the node discovery
  108. // mechanism.
  109. ClusterStore string `json:"cluster-store,omitempty"`
  110. // ClusterOpts is used to pass options to the discovery package for tuning libkv settings, such
  111. // as TLS configuration settings.
  112. ClusterOpts map[string]string `json:"cluster-store-opts,omitempty"`
  113. // ClusterAdvertise is the network endpoint that the Engine advertises for the purpose of node
  114. // discovery. This should be a 'host:port' combination on which that daemon instance is
  115. // reachable by other hosts.
  116. ClusterAdvertise string `json:"cluster-advertise,omitempty"`
  117. // MaxConcurrentDownloads is the maximum number of downloads that
  118. // may take place at a time for each pull.
  119. MaxConcurrentDownloads *int `json:"max-concurrent-downloads,omitempty"`
  120. // MaxConcurrentUploads is the maximum number of uploads that
  121. // may take place at a time for each push.
  122. MaxConcurrentUploads *int `json:"max-concurrent-uploads,omitempty"`
  123. // ShutdownTimeout is the timeout value (in seconds) the daemon will wait for the container
  124. // to stop when daemon is being shutdown
  125. ShutdownTimeout int `json:"shutdown-timeout,omitempty"`
  126. Debug bool `json:"debug,omitempty"`
  127. Hosts []string `json:"hosts,omitempty"`
  128. LogLevel string `json:"log-level,omitempty"`
  129. TLS bool `json:"tls,omitempty"`
  130. TLSVerify bool `json:"tlsverify,omitempty"`
  131. // Embedded structs that allow config
  132. // deserialization without the full struct.
  133. CommonTLSOptions
  134. // SwarmDefaultAdvertiseAddr is the default host/IP or network interface
  135. // to use if a wildcard address is specified in the ListenAddr value
  136. // given to the /swarm/init endpoint and no advertise address is
  137. // specified.
  138. SwarmDefaultAdvertiseAddr string `json:"swarm-default-advertise-addr"`
  139. MetricsAddress string `json:"metrics-addr"`
  140. LogConfig
  141. BridgeConfig // bridgeConfig holds bridge network specific configuration.
  142. registry.ServiceOptions
  143. sync.Mutex
  144. // FIXME(vdemeester) This part is not that clear and is mainly dependent on cli flags
  145. // It should probably be handled outside this package.
  146. ValuesSet map[string]interface{}
  147. Experimental bool `json:"experimental"` // Experimental indicates whether experimental features should be exposed or not
  148. // Exposed node Generic Resources
  149. NodeGenericResources string `json:"node-generic-resources,omitempty"`
  150. // NetworkControlPlaneMTU allows to specify the control plane MTU, this will allow to optimize the network use in some components
  151. NetworkControlPlaneMTU int `json:"network-control-plane-mtu,omitempty"`
  152. }
  153. // IsValueSet returns true if a configuration value
  154. // was explicitly set in the configuration file.
  155. func (conf *Config) IsValueSet(name string) bool {
  156. if conf.ValuesSet == nil {
  157. return false
  158. }
  159. _, ok := conf.ValuesSet[name]
  160. return ok
  161. }
  162. // New returns a new fully initialized Config struct
  163. func New() *Config {
  164. config := Config{}
  165. config.LogConfig.Config = make(map[string]string)
  166. config.ClusterOpts = make(map[string]string)
  167. if runtime.GOOS != "linux" {
  168. config.V2Only = true
  169. }
  170. return &config
  171. }
  172. // ParseClusterAdvertiseSettings parses the specified advertise settings
  173. func ParseClusterAdvertiseSettings(clusterStore, clusterAdvertise string) (string, error) {
  174. if runtime.GOOS == "solaris" && (clusterAdvertise != "" || clusterStore != "") {
  175. return "", errors.New("Cluster Advertise Settings not supported on Solaris")
  176. }
  177. if clusterAdvertise == "" {
  178. return "", daemondiscovery.ErrDiscoveryDisabled
  179. }
  180. if clusterStore == "" {
  181. return "", errors.New("invalid cluster configuration. --cluster-advertise must be accompanied by --cluster-store configuration")
  182. }
  183. advertise, err := discovery.ParseAdvertise(clusterAdvertise)
  184. if err != nil {
  185. return "", fmt.Errorf("discovery advertise parsing failed (%v)", err)
  186. }
  187. return advertise, nil
  188. }
  189. // GetConflictFreeLabels validates Labels for conflict
  190. // In swarm the duplicates for labels are removed
  191. // so we only take same values here, no conflict values
  192. // If the key-value is the same we will only take the last label
  193. func GetConflictFreeLabels(labels []string) ([]string, error) {
  194. labelMap := map[string]string{}
  195. for _, label := range labels {
  196. stringSlice := strings.SplitN(label, "=", 2)
  197. if len(stringSlice) > 1 {
  198. // If there is a conflict we will return an error
  199. if v, ok := labelMap[stringSlice[0]]; ok && v != stringSlice[1] {
  200. return nil, fmt.Errorf("conflict labels for %s=%s and %s=%s", stringSlice[0], stringSlice[1], stringSlice[0], v)
  201. }
  202. labelMap[stringSlice[0]] = stringSlice[1]
  203. }
  204. }
  205. newLabels := []string{}
  206. for k, v := range labelMap {
  207. newLabels = append(newLabels, fmt.Sprintf("%s=%s", k, v))
  208. }
  209. return newLabels, nil
  210. }
  211. // Reload reads the configuration in the host and reloads the daemon and server.
  212. func Reload(configFile string, flags *pflag.FlagSet, reload func(*Config)) error {
  213. logrus.Infof("Got signal to reload configuration, reloading from: %s", configFile)
  214. newConfig, err := getConflictFreeConfiguration(configFile, flags)
  215. if err != nil {
  216. return err
  217. }
  218. if err := Validate(newConfig); err != nil {
  219. return fmt.Errorf("file configuration validation failed (%v)", err)
  220. }
  221. // Labels of the docker engine used to allow multiple values associated with the same key.
  222. // This is deprecated in 1.13, and, be removed after 3 release cycles.
  223. // The following will check the conflict of labels, and report a warning for deprecation.
  224. //
  225. // TODO: After 3 release cycles (17.12) an error will be returned, and labels will be
  226. // sanitized to consolidate duplicate key-value pairs (config.Labels = newLabels):
  227. //
  228. // newLabels, err := GetConflictFreeLabels(newConfig.Labels)
  229. // if err != nil {
  230. // return err
  231. // }
  232. // newConfig.Labels = newLabels
  233. //
  234. if _, err := GetConflictFreeLabels(newConfig.Labels); err != nil {
  235. logrus.Warnf("Engine labels with duplicate keys and conflicting values have been deprecated: %s", err)
  236. }
  237. reload(newConfig)
  238. return nil
  239. }
  240. // boolValue is an interface that boolean value flags implement
  241. // to tell the command line how to make -name equivalent to -name=true.
  242. type boolValue interface {
  243. IsBoolFlag() bool
  244. }
  245. // MergeDaemonConfigurations reads a configuration file,
  246. // loads the file configuration in an isolated structure,
  247. // and merges the configuration provided from flags on top
  248. // if there are no conflicts.
  249. func MergeDaemonConfigurations(flagsConfig *Config, flags *pflag.FlagSet, configFile string) (*Config, error) {
  250. fileConfig, err := getConflictFreeConfiguration(configFile, flags)
  251. if err != nil {
  252. return nil, err
  253. }
  254. if err := Validate(fileConfig); err != nil {
  255. return nil, fmt.Errorf("configuration validation from file failed (%v)", err)
  256. }
  257. // merge flags configuration on top of the file configuration
  258. if err := mergo.Merge(fileConfig, flagsConfig); err != nil {
  259. return nil, err
  260. }
  261. // We need to validate again once both fileConfig and flagsConfig
  262. // have been merged
  263. if err := Validate(fileConfig); err != nil {
  264. return nil, fmt.Errorf("merged configuration validation from file and command line flags failed (%v)", err)
  265. }
  266. return fileConfig, nil
  267. }
  268. // getConflictFreeConfiguration loads the configuration from a JSON file.
  269. // It compares that configuration with the one provided by the flags,
  270. // and returns an error if there are conflicts.
  271. func getConflictFreeConfiguration(configFile string, flags *pflag.FlagSet) (*Config, error) {
  272. b, err := ioutil.ReadFile(configFile)
  273. if err != nil {
  274. return nil, err
  275. }
  276. var config Config
  277. var reader io.Reader
  278. if flags != nil {
  279. var jsonConfig map[string]interface{}
  280. reader = bytes.NewReader(b)
  281. if err := json.NewDecoder(reader).Decode(&jsonConfig); err != nil {
  282. return nil, err
  283. }
  284. configSet := configValuesSet(jsonConfig)
  285. if err := findConfigurationConflicts(configSet, flags); err != nil {
  286. return nil, err
  287. }
  288. // Override flag values to make sure the values set in the config file with nullable values, like `false`,
  289. // are not overridden by default truthy values from the flags that were not explicitly set.
  290. // See https://github.com/docker/docker/issues/20289 for an example.
  291. //
  292. // TODO: Rewrite configuration logic to avoid same issue with other nullable values, like numbers.
  293. namedOptions := make(map[string]interface{})
  294. for key, value := range configSet {
  295. f := flags.Lookup(key)
  296. if f == nil { // ignore named flags that don't match
  297. namedOptions[key] = value
  298. continue
  299. }
  300. if _, ok := f.Value.(boolValue); ok {
  301. f.Value.Set(fmt.Sprintf("%v", value))
  302. }
  303. }
  304. if len(namedOptions) > 0 {
  305. // set also default for mergeVal flags that are boolValue at the same time.
  306. flags.VisitAll(func(f *pflag.Flag) {
  307. if opt, named := f.Value.(opts.NamedOption); named {
  308. v, set := namedOptions[opt.Name()]
  309. _, boolean := f.Value.(boolValue)
  310. if set && boolean {
  311. f.Value.Set(fmt.Sprintf("%v", v))
  312. }
  313. }
  314. })
  315. }
  316. config.ValuesSet = configSet
  317. }
  318. reader = bytes.NewReader(b)
  319. if err := json.NewDecoder(reader).Decode(&config); err != nil {
  320. return nil, err
  321. }
  322. if config.RootDeprecated != "" {
  323. logrus.Warn(`The "graph" config file option is deprecated. Please use "data-root" instead.`)
  324. if config.Root != "" {
  325. return nil, fmt.Errorf(`cannot specify both "graph" and "data-root" config file options`)
  326. }
  327. config.Root = config.RootDeprecated
  328. }
  329. return &config, nil
  330. }
  331. // configValuesSet returns the configuration values explicitly set in the file.
  332. func configValuesSet(config map[string]interface{}) map[string]interface{} {
  333. flatten := make(map[string]interface{})
  334. for k, v := range config {
  335. if m, isMap := v.(map[string]interface{}); isMap && !flatOptions[k] {
  336. for km, vm := range m {
  337. flatten[km] = vm
  338. }
  339. continue
  340. }
  341. flatten[k] = v
  342. }
  343. return flatten
  344. }
  345. // findConfigurationConflicts iterates over the provided flags searching for
  346. // duplicated configurations and unknown keys. It returns an error with all the conflicts if
  347. // it finds any.
  348. func findConfigurationConflicts(config map[string]interface{}, flags *pflag.FlagSet) error {
  349. // 1. Search keys from the file that we don't recognize as flags.
  350. unknownKeys := make(map[string]interface{})
  351. for key, value := range config {
  352. if flag := flags.Lookup(key); flag == nil {
  353. unknownKeys[key] = value
  354. }
  355. }
  356. // 2. Discard values that implement NamedOption.
  357. // Their configuration name differs from their flag name, like `labels` and `label`.
  358. if len(unknownKeys) > 0 {
  359. unknownNamedConflicts := func(f *pflag.Flag) {
  360. if namedOption, ok := f.Value.(opts.NamedOption); ok {
  361. if _, valid := unknownKeys[namedOption.Name()]; valid {
  362. delete(unknownKeys, namedOption.Name())
  363. }
  364. }
  365. }
  366. flags.VisitAll(unknownNamedConflicts)
  367. }
  368. if len(unknownKeys) > 0 {
  369. var unknown []string
  370. for key := range unknownKeys {
  371. unknown = append(unknown, key)
  372. }
  373. return fmt.Errorf("the following directives don't match any configuration option: %s", strings.Join(unknown, ", "))
  374. }
  375. var conflicts []string
  376. printConflict := func(name string, flagValue, fileValue interface{}) string {
  377. return fmt.Sprintf("%s: (from flag: %v, from file: %v)", name, flagValue, fileValue)
  378. }
  379. // 3. Search keys that are present as a flag and as a file option.
  380. duplicatedConflicts := func(f *pflag.Flag) {
  381. // search option name in the json configuration payload if the value is a named option
  382. if namedOption, ok := f.Value.(opts.NamedOption); ok {
  383. if optsValue, ok := config[namedOption.Name()]; ok {
  384. conflicts = append(conflicts, printConflict(namedOption.Name(), f.Value.String(), optsValue))
  385. }
  386. } else {
  387. // search flag name in the json configuration payload
  388. for _, name := range []string{f.Name, f.Shorthand} {
  389. if value, ok := config[name]; ok {
  390. conflicts = append(conflicts, printConflict(name, f.Value.String(), value))
  391. break
  392. }
  393. }
  394. }
  395. }
  396. flags.Visit(duplicatedConflicts)
  397. if len(conflicts) > 0 {
  398. return fmt.Errorf("the following directives are specified both as a flag and in the configuration file: %s", strings.Join(conflicts, ", "))
  399. }
  400. return nil
  401. }
  402. // Validate validates some specific configs.
  403. // such as config.DNS, config.Labels, config.DNSSearch,
  404. // as well as config.MaxConcurrentDownloads, config.MaxConcurrentUploads.
  405. func Validate(config *Config) error {
  406. // validate DNS
  407. for _, dns := range config.DNS {
  408. if _, err := opts.ValidateIPAddress(dns); err != nil {
  409. return err
  410. }
  411. }
  412. // validate DNSSearch
  413. for _, dnsSearch := range config.DNSSearch {
  414. if _, err := opts.ValidateDNSSearch(dnsSearch); err != nil {
  415. return err
  416. }
  417. }
  418. // validate Labels
  419. for _, label := range config.Labels {
  420. if _, err := opts.ValidateLabel(label); err != nil {
  421. return err
  422. }
  423. }
  424. // validate MaxConcurrentDownloads
  425. if config.MaxConcurrentDownloads != nil && *config.MaxConcurrentDownloads < 0 {
  426. return fmt.Errorf("invalid max concurrent downloads: %d", *config.MaxConcurrentDownloads)
  427. }
  428. // validate MaxConcurrentUploads
  429. if config.MaxConcurrentUploads != nil && *config.MaxConcurrentUploads < 0 {
  430. return fmt.Errorf("invalid max concurrent uploads: %d", *config.MaxConcurrentUploads)
  431. }
  432. // validate that "default" runtime is not reset
  433. if runtimes := config.GetAllRuntimes(); len(runtimes) > 0 {
  434. if _, ok := runtimes[StockRuntimeName]; ok {
  435. return fmt.Errorf("runtime name '%s' is reserved", StockRuntimeName)
  436. }
  437. }
  438. if _, err := ParseGenericResources(config.NodeGenericResources); err != nil {
  439. return err
  440. }
  441. if defaultRuntime := config.GetDefaultRuntimeName(); defaultRuntime != "" && defaultRuntime != StockRuntimeName {
  442. runtimes := config.GetAllRuntimes()
  443. if _, ok := runtimes[defaultRuntime]; !ok {
  444. return fmt.Errorf("specified default runtime '%s' does not exist", defaultRuntime)
  445. }
  446. }
  447. // validate platform-specific settings
  448. if err := config.ValidatePlatformConfig(); err != nil {
  449. return err
  450. }
  451. return nil
  452. }
  453. // ModifiedDiscoverySettings returns whether the discovery configuration has been modified or not.
  454. func ModifiedDiscoverySettings(config *Config, backendType, advertise string, clusterOpts map[string]string) bool {
  455. if config.ClusterStore != backendType || config.ClusterAdvertise != advertise {
  456. return true
  457. }
  458. if (config.ClusterOpts == nil && clusterOpts == nil) ||
  459. (config.ClusterOpts == nil && len(clusterOpts) == 0) ||
  460. (len(config.ClusterOpts) == 0 && clusterOpts == nil) {
  461. return false
  462. }
  463. return !reflect.DeepEqual(config.ClusterOpts, clusterOpts)
  464. }