config.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  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. "github.com/Sirupsen/logrus"
  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/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. EnableCors bool `json:"api-enable-cors,omitempty"`
  99. // TrustKeyPath is used to generate the daemon ID and for signing schema 1 manifests
  100. // when pushing to a registry which does not support schema 2. This field is marked as
  101. // deprecated because schema 1 manifests are deprecated in favor of schema 2 and the
  102. // daemon ID will use a dedicated identifier not shared with exported signatures.
  103. TrustKeyPath string `json:"deprecated-key-path,omitempty"`
  104. // LiveRestoreEnabled determines whether we should keep containers
  105. // alive upon daemon shutdown/start
  106. LiveRestoreEnabled bool `json:"live-restore,omitempty"`
  107. // ClusterStore is the storage backend used for the cluster information. It is used by both
  108. // multihost networking (to store networks and endpoints information) and by the node discovery
  109. // mechanism.
  110. ClusterStore string `json:"cluster-store,omitempty"`
  111. // ClusterOpts is used to pass options to the discovery package for tuning libkv settings, such
  112. // as TLS configuration settings.
  113. ClusterOpts map[string]string `json:"cluster-store-opts,omitempty"`
  114. // ClusterAdvertise is the network endpoint that the Engine advertises for the purpose of node
  115. // discovery. This should be a 'host:port' combination on which that daemon instance is
  116. // reachable by other hosts.
  117. ClusterAdvertise string `json:"cluster-advertise,omitempty"`
  118. // MaxConcurrentDownloads is the maximum number of downloads that
  119. // may take place at a time for each pull.
  120. MaxConcurrentDownloads *int `json:"max-concurrent-downloads,omitempty"`
  121. // MaxConcurrentUploads is the maximum number of uploads that
  122. // may take place at a time for each push.
  123. MaxConcurrentUploads *int `json:"max-concurrent-uploads,omitempty"`
  124. // ShutdownTimeout is the timeout value (in seconds) the daemon will wait for the container
  125. // to stop when daemon is being shutdown
  126. ShutdownTimeout int `json:"shutdown-timeout,omitempty"`
  127. Debug bool `json:"debug,omitempty"`
  128. Hosts []string `json:"hosts,omitempty"`
  129. LogLevel string `json:"log-level,omitempty"`
  130. TLS bool `json:"tls,omitempty"`
  131. TLSVerify bool `json:"tlsverify,omitempty"`
  132. // Embedded structs that allow config
  133. // deserialization without the full struct.
  134. CommonTLSOptions
  135. // SwarmDefaultAdvertiseAddr is the default host/IP or network interface
  136. // to use if a wildcard address is specified in the ListenAddr value
  137. // given to the /swarm/init endpoint and no advertise address is
  138. // specified.
  139. SwarmDefaultAdvertiseAddr string `json:"swarm-default-advertise-addr"`
  140. MetricsAddress string `json:"metrics-addr"`
  141. LogConfig
  142. BridgeConfig // bridgeConfig holds bridge network specific configuration.
  143. registry.ServiceOptions
  144. sync.Mutex
  145. // FIXME(vdemeester) This part is not that clear and is mainly dependent on cli flags
  146. // It should probably be handled outside this package.
  147. ValuesSet map[string]interface{}
  148. Experimental bool `json:"experimental"` // Experimental indicates whether experimental features should be exposed or not
  149. }
  150. // IsValueSet returns true if a configuration value
  151. // was explicitly set in the configuration file.
  152. func (conf *Config) IsValueSet(name string) bool {
  153. if conf.ValuesSet == nil {
  154. return false
  155. }
  156. _, ok := conf.ValuesSet[name]
  157. return ok
  158. }
  159. // New returns a new fully initialized Config struct
  160. func New() *Config {
  161. config := Config{}
  162. config.LogConfig.Config = make(map[string]string)
  163. config.ClusterOpts = make(map[string]string)
  164. if runtime.GOOS != "linux" {
  165. config.V2Only = true
  166. }
  167. return &config
  168. }
  169. // ParseClusterAdvertiseSettings parses the specified advertise settings
  170. func ParseClusterAdvertiseSettings(clusterStore, clusterAdvertise string) (string, error) {
  171. if runtime.GOOS == "solaris" && (clusterAdvertise != "" || clusterStore != "") {
  172. return "", errors.New("Cluster Advertise Settings not supported on Solaris")
  173. }
  174. if clusterAdvertise == "" {
  175. return "", daemondiscovery.ErrDiscoveryDisabled
  176. }
  177. if clusterStore == "" {
  178. return "", errors.New("invalid cluster configuration. --cluster-advertise must be accompanied by --cluster-store configuration")
  179. }
  180. advertise, err := discovery.ParseAdvertise(clusterAdvertise)
  181. if err != nil {
  182. return "", fmt.Errorf("discovery advertise parsing failed (%v)", err)
  183. }
  184. return advertise, nil
  185. }
  186. // GetConflictFreeLabels validates Labels for conflict
  187. // In swarm the duplicates for labels are removed
  188. // so we only take same values here, no conflict values
  189. // If the key-value is the same we will only take the last label
  190. func GetConflictFreeLabels(labels []string) ([]string, error) {
  191. labelMap := map[string]string{}
  192. for _, label := range labels {
  193. stringSlice := strings.SplitN(label, "=", 2)
  194. if len(stringSlice) > 1 {
  195. // If there is a conflict we will return an error
  196. if v, ok := labelMap[stringSlice[0]]; ok && v != stringSlice[1] {
  197. return nil, fmt.Errorf("conflict labels for %s=%s and %s=%s", stringSlice[0], stringSlice[1], stringSlice[0], v)
  198. }
  199. labelMap[stringSlice[0]] = stringSlice[1]
  200. }
  201. }
  202. newLabels := []string{}
  203. for k, v := range labelMap {
  204. newLabels = append(newLabels, fmt.Sprintf("%s=%s", k, v))
  205. }
  206. return newLabels, nil
  207. }
  208. // Reload reads the configuration in the host and reloads the daemon and server.
  209. func Reload(configFile string, flags *pflag.FlagSet, reload func(*Config)) error {
  210. logrus.Infof("Got signal to reload configuration, reloading from: %s", configFile)
  211. newConfig, err := getConflictFreeConfiguration(configFile, flags)
  212. if err != nil {
  213. return err
  214. }
  215. if err := Validate(newConfig); err != nil {
  216. return fmt.Errorf("file configuration validation failed (%v)", err)
  217. }
  218. // Labels of the docker engine used to allow multiple values associated with the same key.
  219. // This is deprecated in 1.13, and, be removed after 3 release cycles.
  220. // The following will check the conflict of labels, and report a warning for deprecation.
  221. //
  222. // TODO: After 3 release cycles (17.12) an error will be returned, and labels will be
  223. // sanitized to consolidate duplicate key-value pairs (config.Labels = newLabels):
  224. //
  225. // newLabels, err := GetConflictFreeLabels(newConfig.Labels)
  226. // if err != nil {
  227. // return err
  228. // }
  229. // newConfig.Labels = newLabels
  230. //
  231. if _, err := GetConflictFreeLabels(newConfig.Labels); err != nil {
  232. logrus.Warnf("Engine labels with duplicate keys and conflicting values have been deprecated: %s", err)
  233. }
  234. reload(newConfig)
  235. return nil
  236. }
  237. // boolValue is an interface that boolean value flags implement
  238. // to tell the command line how to make -name equivalent to -name=true.
  239. type boolValue interface {
  240. IsBoolFlag() bool
  241. }
  242. // MergeDaemonConfigurations reads a configuration file,
  243. // loads the file configuration in an isolated structure,
  244. // and merges the configuration provided from flags on top
  245. // if there are no conflicts.
  246. func MergeDaemonConfigurations(flagsConfig *Config, flags *pflag.FlagSet, configFile string) (*Config, error) {
  247. fileConfig, err := getConflictFreeConfiguration(configFile, flags)
  248. if err != nil {
  249. return nil, err
  250. }
  251. if err := Validate(fileConfig); err != nil {
  252. return nil, fmt.Errorf("configuration validation from file failed (%v)", err)
  253. }
  254. // merge flags configuration on top of the file configuration
  255. if err := mergo.Merge(fileConfig, flagsConfig); err != nil {
  256. return nil, err
  257. }
  258. // We need to validate again once both fileConfig and flagsConfig
  259. // have been merged
  260. if err := Validate(fileConfig); err != nil {
  261. return nil, fmt.Errorf("merged configuration validation from file and command line flags failed (%v)", err)
  262. }
  263. return fileConfig, nil
  264. }
  265. // getConflictFreeConfiguration loads the configuration from a JSON file.
  266. // It compares that configuration with the one provided by the flags,
  267. // and returns an error if there are conflicts.
  268. func getConflictFreeConfiguration(configFile string, flags *pflag.FlagSet) (*Config, error) {
  269. b, err := ioutil.ReadFile(configFile)
  270. if err != nil {
  271. return nil, err
  272. }
  273. var config Config
  274. var reader io.Reader
  275. if flags != nil {
  276. var jsonConfig map[string]interface{}
  277. reader = bytes.NewReader(b)
  278. if err := json.NewDecoder(reader).Decode(&jsonConfig); err != nil {
  279. return nil, err
  280. }
  281. configSet := configValuesSet(jsonConfig)
  282. if err := findConfigurationConflicts(configSet, flags); err != nil {
  283. return nil, err
  284. }
  285. // Override flag values to make sure the values set in the config file with nullable values, like `false`,
  286. // are not overridden by default truthy values from the flags that were not explicitly set.
  287. // See https://github.com/docker/docker/issues/20289 for an example.
  288. //
  289. // TODO: Rewrite configuration logic to avoid same issue with other nullable values, like numbers.
  290. namedOptions := make(map[string]interface{})
  291. for key, value := range configSet {
  292. f := flags.Lookup(key)
  293. if f == nil { // ignore named flags that don't match
  294. namedOptions[key] = value
  295. continue
  296. }
  297. if _, ok := f.Value.(boolValue); ok {
  298. f.Value.Set(fmt.Sprintf("%v", value))
  299. }
  300. }
  301. if len(namedOptions) > 0 {
  302. // set also default for mergeVal flags that are boolValue at the same time.
  303. flags.VisitAll(func(f *pflag.Flag) {
  304. if opt, named := f.Value.(opts.NamedOption); named {
  305. v, set := namedOptions[opt.Name()]
  306. _, boolean := f.Value.(boolValue)
  307. if set && boolean {
  308. f.Value.Set(fmt.Sprintf("%v", v))
  309. }
  310. }
  311. })
  312. }
  313. config.ValuesSet = configSet
  314. }
  315. reader = bytes.NewReader(b)
  316. if err := json.NewDecoder(reader).Decode(&config); err != nil {
  317. return nil, err
  318. }
  319. if config.RootDeprecated != "" {
  320. logrus.Warn(`The "graph" config file option is deprecated. Please use "data-root" instead.`)
  321. if config.Root != "" {
  322. return nil, fmt.Errorf(`cannot specify both "graph" and "data-root" config file options`)
  323. }
  324. config.Root = config.RootDeprecated
  325. }
  326. return &config, nil
  327. }
  328. // configValuesSet returns the configuration values explicitly set in the file.
  329. func configValuesSet(config map[string]interface{}) map[string]interface{} {
  330. flatten := make(map[string]interface{})
  331. for k, v := range config {
  332. if m, isMap := v.(map[string]interface{}); isMap && !flatOptions[k] {
  333. for km, vm := range m {
  334. flatten[km] = vm
  335. }
  336. continue
  337. }
  338. flatten[k] = v
  339. }
  340. return flatten
  341. }
  342. // findConfigurationConflicts iterates over the provided flags searching for
  343. // duplicated configurations and unknown keys. It returns an error with all the conflicts if
  344. // it finds any.
  345. func findConfigurationConflicts(config map[string]interface{}, flags *pflag.FlagSet) error {
  346. // 1. Search keys from the file that we don't recognize as flags.
  347. unknownKeys := make(map[string]interface{})
  348. for key, value := range config {
  349. if flag := flags.Lookup(key); flag == nil {
  350. unknownKeys[key] = value
  351. }
  352. }
  353. // 2. Discard values that implement NamedOption.
  354. // Their configuration name differs from their flag name, like `labels` and `label`.
  355. if len(unknownKeys) > 0 {
  356. unknownNamedConflicts := func(f *pflag.Flag) {
  357. if namedOption, ok := f.Value.(opts.NamedOption); ok {
  358. if _, valid := unknownKeys[namedOption.Name()]; valid {
  359. delete(unknownKeys, namedOption.Name())
  360. }
  361. }
  362. }
  363. flags.VisitAll(unknownNamedConflicts)
  364. }
  365. if len(unknownKeys) > 0 {
  366. var unknown []string
  367. for key := range unknownKeys {
  368. unknown = append(unknown, key)
  369. }
  370. return fmt.Errorf("the following directives don't match any configuration option: %s", strings.Join(unknown, ", "))
  371. }
  372. var conflicts []string
  373. printConflict := func(name string, flagValue, fileValue interface{}) string {
  374. return fmt.Sprintf("%s: (from flag: %v, from file: %v)", name, flagValue, fileValue)
  375. }
  376. // 3. Search keys that are present as a flag and as a file option.
  377. duplicatedConflicts := func(f *pflag.Flag) {
  378. // search option name in the json configuration payload if the value is a named option
  379. if namedOption, ok := f.Value.(opts.NamedOption); ok {
  380. if optsValue, ok := config[namedOption.Name()]; ok {
  381. conflicts = append(conflicts, printConflict(namedOption.Name(), f.Value.String(), optsValue))
  382. }
  383. } else {
  384. // search flag name in the json configuration payload
  385. for _, name := range []string{f.Name, f.Shorthand} {
  386. if value, ok := config[name]; ok {
  387. conflicts = append(conflicts, printConflict(name, f.Value.String(), value))
  388. break
  389. }
  390. }
  391. }
  392. }
  393. flags.Visit(duplicatedConflicts)
  394. if len(conflicts) > 0 {
  395. return fmt.Errorf("the following directives are specified both as a flag and in the configuration file: %s", strings.Join(conflicts, ", "))
  396. }
  397. return nil
  398. }
  399. // Validate validates some specific configs.
  400. // such as config.DNS, config.Labels, config.DNSSearch,
  401. // as well as config.MaxConcurrentDownloads, config.MaxConcurrentUploads.
  402. func Validate(config *Config) error {
  403. // validate DNS
  404. for _, dns := range config.DNS {
  405. if _, err := opts.ValidateIPAddress(dns); err != nil {
  406. return err
  407. }
  408. }
  409. // validate DNSSearch
  410. for _, dnsSearch := range config.DNSSearch {
  411. if _, err := opts.ValidateDNSSearch(dnsSearch); err != nil {
  412. return err
  413. }
  414. }
  415. // validate Labels
  416. for _, label := range config.Labels {
  417. if _, err := opts.ValidateLabel(label); err != nil {
  418. return err
  419. }
  420. }
  421. // validate MaxConcurrentDownloads
  422. if config.MaxConcurrentDownloads != nil && *config.MaxConcurrentDownloads < 0 {
  423. return fmt.Errorf("invalid max concurrent downloads: %d", *config.MaxConcurrentDownloads)
  424. }
  425. // validate MaxConcurrentUploads
  426. if config.MaxConcurrentUploads != nil && *config.MaxConcurrentUploads < 0 {
  427. return fmt.Errorf("invalid max concurrent uploads: %d", *config.MaxConcurrentUploads)
  428. }
  429. // validate that "default" runtime is not reset
  430. if runtimes := config.GetAllRuntimes(); len(runtimes) > 0 {
  431. if _, ok := runtimes[StockRuntimeName]; ok {
  432. return fmt.Errorf("runtime name '%s' is reserved", StockRuntimeName)
  433. }
  434. }
  435. if defaultRuntime := config.GetDefaultRuntimeName(); defaultRuntime != "" && defaultRuntime != StockRuntimeName {
  436. runtimes := config.GetAllRuntimes()
  437. if _, ok := runtimes[defaultRuntime]; !ok {
  438. return fmt.Errorf("specified default runtime '%s' does not exist", defaultRuntime)
  439. }
  440. }
  441. return nil
  442. }
  443. // ModifiedDiscoverySettings returns whether the discovery configuration has been modified or not.
  444. func ModifiedDiscoverySettings(config *Config, backendType, advertise string, clusterOpts map[string]string) bool {
  445. if config.ClusterStore != backendType || config.ClusterAdvertise != advertise {
  446. return true
  447. }
  448. if (config.ClusterOpts == nil && clusterOpts == nil) ||
  449. (config.ClusterOpts == nil && len(clusterOpts) == 0) ||
  450. (len(config.ClusterOpts) == 0 && clusterOpts == nil) {
  451. return false
  452. }
  453. return !reflect.DeepEqual(config.ClusterOpts, clusterOpts)
  454. }