Browse code

Merge pull request #18587 from calavera/daemon_configuration_file

Allow to set daemon and server configurations in a file.

Sebastiaan van Stijn authored on 2016/01/15 09:44:58
Showing 32 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,30 @@
0
+package server
1
+
2
+import (
3
+	"net/http"
4
+	"sync"
5
+
6
+	"github.com/gorilla/mux"
7
+)
8
+
9
+// routerSwapper is an http.Handler that allow you to swap
10
+// mux routers.
11
+type routerSwapper struct {
12
+	mu     sync.Mutex
13
+	router *mux.Router
14
+}
15
+
16
+// Swap changes the old router with the new one.
17
+func (rs *routerSwapper) Swap(newRouter *mux.Router) {
18
+	rs.mu.Lock()
19
+	rs.router = newRouter
20
+	rs.mu.Unlock()
21
+}
22
+
23
+// ServeHTTP makes the routerSwapper to implement the http.Handler interface.
24
+func (rs *routerSwapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
25
+	rs.mu.Lock()
26
+	router := rs.router
27
+	rs.mu.Unlock()
28
+	router.ServeHTTP(w, r)
29
+}
... ...
@@ -4,7 +4,6 @@ import (
4 4
 	"crypto/tls"
5 5
 	"net"
6 6
 	"net/http"
7
-	"os"
8 7
 	"strings"
9 8
 
10 9
 	"github.com/Sirupsen/logrus"
... ...
@@ -42,10 +41,11 @@ type Config struct {
42 42
 
43 43
 // Server contains instance details for the server
44 44
 type Server struct {
45
-	cfg          *Config
46
-	servers      []*HTTPServer
47
-	routers      []router.Router
48
-	authZPlugins []authorization.Plugin
45
+	cfg           *Config
46
+	servers       []*HTTPServer
47
+	routers       []router.Router
48
+	authZPlugins  []authorization.Plugin
49
+	routerSwapper *routerSwapper
49 50
 }
50 51
 
51 52
 // Addr contains string representation of address and its protocol (tcp, unix...).
... ...
@@ -80,12 +80,14 @@ func (s *Server) Close() {
80 80
 	}
81 81
 }
82 82
 
83
-// ServeAPI loops through all initialized servers and spawns goroutine
84
-// with Server method for each. It sets CreateMux() as Handler also.
85
-func (s *Server) ServeAPI() error {
83
+// serveAPI loops through all initialized servers and spawns goroutine
84
+// with Server method for each. It sets createMux() as Handler also.
85
+func (s *Server) serveAPI() error {
86
+	s.initRouterSwapper()
87
+
86 88
 	var chErrors = make(chan error, len(s.servers))
87 89
 	for _, srv := range s.servers {
88
-		srv.srv.Handler = s.CreateMux()
90
+		srv.srv.Handler = s.routerSwapper
89 91
 		go func(srv *HTTPServer) {
90 92
 			var err error
91 93
 			logrus.Infof("API listen on %s", srv.l.Addr())
... ...
@@ -186,11 +188,11 @@ func (s *Server) addRouter(r router.Router) {
186 186
 	s.routers = append(s.routers, r)
187 187
 }
188 188
 
189
-// CreateMux initializes the main router the server uses.
189
+// createMux initializes the main router the server uses.
190 190
 // we keep enableCors just for legacy usage, need to be removed in the future
191
-func (s *Server) CreateMux() *mux.Router {
191
+func (s *Server) createMux() *mux.Router {
192 192
 	m := mux.NewRouter()
193
-	if os.Getenv("DEBUG") != "" {
193
+	if utils.IsDebugEnabled() {
194 194
 		profilerSetup(m, "/debug/")
195 195
 	}
196 196
 
... ...
@@ -207,3 +209,36 @@ func (s *Server) CreateMux() *mux.Router {
207 207
 
208 208
 	return m
209 209
 }
210
+
211
+// Wait blocks the server goroutine until it exits.
212
+// It sends an error message if there is any error during
213
+// the API execution.
214
+func (s *Server) Wait(waitChan chan error) {
215
+	if err := s.serveAPI(); err != nil {
216
+		logrus.Errorf("ServeAPI error: %v", err)
217
+		waitChan <- err
218
+		return
219
+	}
220
+	waitChan <- nil
221
+}
222
+
223
+func (s *Server) initRouterSwapper() {
224
+	s.routerSwapper = &routerSwapper{
225
+		router: s.createMux(),
226
+	}
227
+}
228
+
229
+// Reload reads configuration changes and modifies the
230
+// server according to those changes.
231
+// Currently, only the --debug configuration is taken into account.
232
+func (s *Server) Reload(config *daemon.Config) {
233
+	debugEnabled := utils.IsDebugEnabled()
234
+	switch {
235
+	case debugEnabled && !config.Debug: // disable debug
236
+		utils.DisableDebug()
237
+		s.routerSwapper.Swap(s.createMux())
238
+	case config.Debug && !debugEnabled: // enable debug
239
+		utils.EnableDebug()
240
+		s.routerSwapper.Swap(s.createMux())
241
+	}
242
+}
... ...
@@ -1,9 +1,19 @@
1 1
 package daemon
2 2
 
3 3
 import (
4
+	"bytes"
5
+	"encoding/json"
6
+	"fmt"
7
+	"io"
8
+	"io/ioutil"
9
+	"strings"
10
+	"sync"
11
+
12
+	"github.com/Sirupsen/logrus"
4 13
 	"github.com/docker/docker/opts"
14
+	"github.com/docker/docker/pkg/discovery"
5 15
 	flag "github.com/docker/docker/pkg/mflag"
6
-	"github.com/docker/engine-api/types/container"
16
+	"github.com/imdario/mergo"
7 17
 )
8 18
 
9 19
 const (
... ...
@@ -11,42 +21,69 @@ const (
11 11
 	disableNetworkBridge = "none"
12 12
 )
13 13
 
14
+// LogConfig represents the default log configuration.
15
+// It includes json tags to deserialize configuration from a file
16
+// using the same names that the flags in the command line uses.
17
+type LogConfig struct {
18
+	Type   string            `json:"log-driver,omitempty"`
19
+	Config map[string]string `json:"log-opts,omitempty"`
20
+}
21
+
22
+// CommonTLSOptions defines TLS configuration for the daemon server.
23
+// It includes json tags to deserialize configuration from a file
24
+// using the same names that the flags in the command line uses.
25
+type CommonTLSOptions struct {
26
+	CAFile   string `json:"tlscacert,omitempty"`
27
+	CertFile string `json:"tlscert,omitempty"`
28
+	KeyFile  string `json:"tlskey,omitempty"`
29
+}
30
+
14 31
 // CommonConfig defines the configuration of a docker daemon which are
15 32
 // common across platforms.
33
+// It includes json tags to deserialize configuration from a file
34
+// using the same names that the flags in the command line uses.
16 35
 type CommonConfig struct {
17
-	AuthorizationPlugins []string // AuthorizationPlugins holds list of authorization plugins
18
-	AutoRestart          bool
19
-	Bridge               bridgeConfig // Bridge holds bridge network specific configuration.
20
-	Context              map[string][]string
21
-	DisableBridge        bool
22
-	DNS                  []string
23
-	DNSOptions           []string
24
-	DNSSearch            []string
25
-	ExecOptions          []string
26
-	ExecRoot             string
27
-	GraphDriver          string
28
-	GraphOptions         []string
29
-	Labels               []string
30
-	LogConfig            container.LogConfig
31
-	Mtu                  int
32
-	Pidfile              string
33
-	RemappedRoot         string
34
-	Root                 string
35
-	TrustKeyPath         string
36
+	AuthorizationPlugins []string            `json:"authorization-plugins,omitempty"` // AuthorizationPlugins holds list of authorization plugins
37
+	AutoRestart          bool                `json:"-"`
38
+	Bridge               bridgeConfig        `json:"-"` // Bridge holds bridge network specific configuration.
39
+	Context              map[string][]string `json:"-"`
40
+	DisableBridge        bool                `json:"-"`
41
+	DNS                  []string            `json:"dns,omitempty"`
42
+	DNSOptions           []string            `json:"dns-opts,omitempty"`
43
+	DNSSearch            []string            `json:"dns-search,omitempty"`
44
+	ExecOptions          []string            `json:"exec-opts,omitempty"`
45
+	ExecRoot             string              `json:"exec-root,omitempty"`
46
+	GraphDriver          string              `json:"storage-driver,omitempty"`
47
+	GraphOptions         []string            `json:"storage-opts,omitempty"`
48
+	Labels               []string            `json:"labels,omitempty"`
49
+	LogConfig            LogConfig           `json:"log-config,omitempty"`
50
+	Mtu                  int                 `json:"mtu,omitempty"`
51
+	Pidfile              string              `json:"pidfile,omitempty"`
52
+	Root                 string              `json:"graph,omitempty"`
53
+	TrustKeyPath         string              `json:"-"`
36 54
 
37 55
 	// ClusterStore is the storage backend used for the cluster information. It is used by both
38 56
 	// multihost networking (to store networks and endpoints information) and by the node discovery
39 57
 	// mechanism.
40
-	ClusterStore string
58
+	ClusterStore string `json:"cluster-store,omitempty"`
41 59
 
42 60
 	// ClusterOpts is used to pass options to the discovery package for tuning libkv settings, such
43 61
 	// as TLS configuration settings.
44
-	ClusterOpts map[string]string
62
+	ClusterOpts map[string]string `json:"cluster-store-opts,omitempty"`
45 63
 
46 64
 	// ClusterAdvertise is the network endpoint that the Engine advertises for the purpose of node
47 65
 	// discovery. This should be a 'host:port' combination on which that daemon instance is
48 66
 	// reachable by other hosts.
49
-	ClusterAdvertise string
67
+	ClusterAdvertise string `json:"cluster-advertise,omitempty"`
68
+
69
+	Debug      bool             `json:"debug,omitempty"`
70
+	Hosts      []string         `json:"hosts,omitempty"`
71
+	LogLevel   string           `json:"log-level,omitempty"`
72
+	TLS        bool             `json:"tls,omitempty"`
73
+	TLSVerify  bool             `json:"tls-verify,omitempty"`
74
+	TLSOptions CommonTLSOptions `json:"tls-opts,omitempty"`
75
+
76
+	reloadLock sync.Mutex
50 77
 }
51 78
 
52 79
 // InstallCommonFlags adds command-line options to the top-level flag parser for
... ...
@@ -54,9 +91,9 @@ type CommonConfig struct {
54 54
 // Subsequent calls to `flag.Parse` will populate config with values parsed
55 55
 // from the command-line.
56 56
 func (config *Config) InstallCommonFlags(cmd *flag.FlagSet, usageFn func(string) string) {
57
-	cmd.Var(opts.NewListOptsRef(&config.GraphOptions, nil), []string{"-storage-opt"}, usageFn("Set storage driver options"))
58
-	cmd.Var(opts.NewListOptsRef(&config.AuthorizationPlugins, nil), []string{"-authorization-plugin"}, usageFn("List authorization plugins in order from first evaluator to last"))
59
-	cmd.Var(opts.NewListOptsRef(&config.ExecOptions, nil), []string{"-exec-opt"}, usageFn("Set exec driver options"))
57
+	cmd.Var(opts.NewNamedListOptsRef("storage-opts", &config.GraphOptions, nil), []string{"-storage-opt"}, usageFn("Set storage driver options"))
58
+	cmd.Var(opts.NewNamedListOptsRef("authorization-plugins", &config.AuthorizationPlugins, nil), []string{"-authorization-plugin"}, usageFn("List authorization plugins in order from first evaluator to last"))
59
+	cmd.Var(opts.NewNamedListOptsRef("exec-opts", &config.ExecOptions, nil), []string{"-exec-opt"}, usageFn("Set exec driver options"))
60 60
 	cmd.StringVar(&config.Pidfile, []string{"p", "-pidfile"}, defaultPidFile, usageFn("Path to use for daemon PID file"))
61 61
 	cmd.StringVar(&config.Root, []string{"g", "-graph"}, defaultGraph, usageFn("Root of the Docker runtime"))
62 62
 	cmd.StringVar(&config.ExecRoot, []string{"-exec-root"}, "/var/run/docker", usageFn("Root of the Docker execdriver"))
... ...
@@ -65,12 +102,131 @@ func (config *Config) InstallCommonFlags(cmd *flag.FlagSet, usageFn func(string)
65 65
 	cmd.IntVar(&config.Mtu, []string{"#mtu", "-mtu"}, 0, usageFn("Set the containers network MTU"))
66 66
 	// FIXME: why the inconsistency between "hosts" and "sockets"?
67 67
 	cmd.Var(opts.NewListOptsRef(&config.DNS, opts.ValidateIPAddress), []string{"#dns", "-dns"}, usageFn("DNS server to use"))
68
-	cmd.Var(opts.NewListOptsRef(&config.DNSOptions, nil), []string{"-dns-opt"}, usageFn("DNS options to use"))
68
+	cmd.Var(opts.NewNamedListOptsRef("dns-opts", &config.DNSOptions, nil), []string{"-dns-opt"}, usageFn("DNS options to use"))
69 69
 	cmd.Var(opts.NewListOptsRef(&config.DNSSearch, opts.ValidateDNSSearch), []string{"-dns-search"}, usageFn("DNS search domains to use"))
70
-	cmd.Var(opts.NewListOptsRef(&config.Labels, opts.ValidateLabel), []string{"-label"}, usageFn("Set key=value labels to the daemon"))
70
+	cmd.Var(opts.NewNamedListOptsRef("labels", &config.Labels, opts.ValidateLabel), []string{"-label"}, usageFn("Set key=value labels to the daemon"))
71 71
 	cmd.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", usageFn("Default driver for container logs"))
72
-	cmd.Var(opts.NewMapOpts(config.LogConfig.Config, nil), []string{"-log-opt"}, usageFn("Set log driver options"))
72
+	cmd.Var(opts.NewNamedMapOpts("log-opts", config.LogConfig.Config, nil), []string{"-log-opt"}, usageFn("Set log driver options"))
73 73
 	cmd.StringVar(&config.ClusterAdvertise, []string{"-cluster-advertise"}, "", usageFn("Address or interface name to advertise"))
74 74
 	cmd.StringVar(&config.ClusterStore, []string{"-cluster-store"}, "", usageFn("Set the cluster store"))
75
-	cmd.Var(opts.NewMapOpts(config.ClusterOpts, nil), []string{"-cluster-store-opt"}, usageFn("Set cluster store options"))
75
+	cmd.Var(opts.NewNamedMapOpts("cluster-store-opts", config.ClusterOpts, nil), []string{"-cluster-store-opt"}, usageFn("Set cluster store options"))
76
+}
77
+
78
+func parseClusterAdvertiseSettings(clusterStore, clusterAdvertise string) (string, error) {
79
+	if clusterAdvertise == "" {
80
+		return "", errDiscoveryDisabled
81
+	}
82
+	if clusterStore == "" {
83
+		return "", fmt.Errorf("invalid cluster configuration. --cluster-advertise must be accompanied by --cluster-store configuration")
84
+	}
85
+
86
+	advertise, err := discovery.ParseAdvertise(clusterAdvertise)
87
+	if err != nil {
88
+		return "", fmt.Errorf("discovery advertise parsing failed (%v)", err)
89
+	}
90
+	return advertise, nil
91
+}
92
+
93
+// ReloadConfiguration reads the configuration in the host and reloads the daemon and server.
94
+func ReloadConfiguration(configFile string, flags *flag.FlagSet, reload func(*Config)) {
95
+	logrus.Infof("Got signal to reload configuration, reloading from: %s", configFile)
96
+	newConfig, err := getConflictFreeConfiguration(configFile, flags)
97
+	if err != nil {
98
+		logrus.Error(err)
99
+	} else {
100
+		reload(newConfig)
101
+	}
102
+}
103
+
104
+// MergeDaemonConfigurations reads a configuration file,
105
+// loads the file configuration in an isolated structure,
106
+// and merges the configuration provided from flags on top
107
+// if there are no conflicts.
108
+func MergeDaemonConfigurations(flagsConfig *Config, flags *flag.FlagSet, configFile string) (*Config, error) {
109
+	fileConfig, err := getConflictFreeConfiguration(configFile, flags)
110
+	if err != nil {
111
+		return nil, err
112
+	}
113
+
114
+	// merge flags configuration on top of the file configuration
115
+	if err := mergo.Merge(fileConfig, flagsConfig); err != nil {
116
+		return nil, err
117
+	}
118
+
119
+	return fileConfig, nil
120
+}
121
+
122
+// getConflictFreeConfiguration loads the configuration from a JSON file.
123
+// It compares that configuration with the one provided by the flags,
124
+// and returns an error if there are conflicts.
125
+func getConflictFreeConfiguration(configFile string, flags *flag.FlagSet) (*Config, error) {
126
+	b, err := ioutil.ReadFile(configFile)
127
+	if err != nil {
128
+		return nil, err
129
+	}
130
+
131
+	var reader io.Reader
132
+	if flags != nil {
133
+		var jsonConfig map[string]interface{}
134
+		reader = bytes.NewReader(b)
135
+		if err := json.NewDecoder(reader).Decode(&jsonConfig); err != nil {
136
+			return nil, err
137
+		}
138
+
139
+		if err := findConfigurationConflicts(jsonConfig, flags); err != nil {
140
+			return nil, err
141
+		}
142
+	}
143
+
144
+	var config Config
145
+	reader = bytes.NewReader(b)
146
+	err = json.NewDecoder(reader).Decode(&config)
147
+	return &config, err
148
+}
149
+
150
+// findConfigurationConflicts iterates over the provided flags searching for
151
+// duplicated configurations. It returns an error with all the conflicts if
152
+// it finds any.
153
+func findConfigurationConflicts(config map[string]interface{}, flags *flag.FlagSet) error {
154
+	var conflicts []string
155
+	flatten := make(map[string]interface{})
156
+	for k, v := range config {
157
+		if m, ok := v.(map[string]interface{}); ok {
158
+			for km, vm := range m {
159
+				flatten[km] = vm
160
+			}
161
+		} else {
162
+			flatten[k] = v
163
+		}
164
+	}
165
+
166
+	printConflict := func(name string, flagValue, fileValue interface{}) string {
167
+		return fmt.Sprintf("%s: (from flag: %v, from file: %v)", name, flagValue, fileValue)
168
+	}
169
+
170
+	collectConflicts := func(f *flag.Flag) {
171
+		// search option name in the json configuration payload if the value is a named option
172
+		if namedOption, ok := f.Value.(opts.NamedOption); ok {
173
+			if optsValue, ok := flatten[namedOption.Name()]; ok {
174
+				conflicts = append(conflicts, printConflict(namedOption.Name(), f.Value.String(), optsValue))
175
+			}
176
+		} else {
177
+			// search flag name in the json configuration payload without trailing dashes
178
+			for _, name := range f.Names {
179
+				name = strings.TrimLeft(name, "-")
180
+
181
+				if value, ok := flatten[name]; ok {
182
+					conflicts = append(conflicts, printConflict(name, f.Value.String(), value))
183
+					break
184
+				}
185
+			}
186
+		}
187
+	}
188
+
189
+	flags.Visit(collectConflicts)
190
+
191
+	if len(conflicts) > 0 {
192
+		return fmt.Errorf("the following directives are specified both as a flag and in the configuration file: %s", strings.Join(conflicts, ", "))
193
+	}
194
+	return nil
76 195
 }
77 196
new file mode 100644
... ...
@@ -0,0 +1,177 @@
0
+package daemon
1
+
2
+import (
3
+	"io/ioutil"
4
+	"os"
5
+	"strings"
6
+	"testing"
7
+
8
+	"github.com/docker/docker/opts"
9
+	"github.com/docker/docker/pkg/mflag"
10
+)
11
+
12
+func TestDaemonConfigurationMerge(t *testing.T) {
13
+	f, err := ioutil.TempFile("", "docker-config-")
14
+	if err != nil {
15
+		t.Fatal(err)
16
+	}
17
+
18
+	configFile := f.Name()
19
+	f.Write([]byte(`{"debug": true}`))
20
+	f.Close()
21
+
22
+	c := &Config{
23
+		CommonConfig: CommonConfig{
24
+			AutoRestart: true,
25
+			LogConfig: LogConfig{
26
+				Type:   "syslog",
27
+				Config: map[string]string{"tag": "test"},
28
+			},
29
+		},
30
+	}
31
+
32
+	cc, err := MergeDaemonConfigurations(c, nil, configFile)
33
+	if err != nil {
34
+		t.Fatal(err)
35
+	}
36
+	if !cc.Debug {
37
+		t.Fatalf("expected %v, got %v\n", true, cc.Debug)
38
+	}
39
+	if !cc.AutoRestart {
40
+		t.Fatalf("expected %v, got %v\n", true, cc.AutoRestart)
41
+	}
42
+	if cc.LogConfig.Type != "syslog" {
43
+		t.Fatalf("expected syslog config, got %q\n", cc.LogConfig)
44
+	}
45
+}
46
+
47
+func TestDaemonConfigurationNotFound(t *testing.T) {
48
+	_, err := MergeDaemonConfigurations(&Config{}, nil, "/tmp/foo-bar-baz-docker")
49
+	if err == nil || !os.IsNotExist(err) {
50
+		t.Fatalf("expected does not exist error, got %v", err)
51
+	}
52
+}
53
+
54
+func TestDaemonBrokenConfiguration(t *testing.T) {
55
+	f, err := ioutil.TempFile("", "docker-config-")
56
+	if err != nil {
57
+		t.Fatal(err)
58
+	}
59
+
60
+	configFile := f.Name()
61
+	f.Write([]byte(`{"Debug": tru`))
62
+	f.Close()
63
+
64
+	_, err = MergeDaemonConfigurations(&Config{}, nil, configFile)
65
+	if err == nil {
66
+		t.Fatalf("expected error, got %v", err)
67
+	}
68
+}
69
+
70
+func TestParseClusterAdvertiseSettings(t *testing.T) {
71
+	_, err := parseClusterAdvertiseSettings("something", "")
72
+	if err != errDiscoveryDisabled {
73
+		t.Fatalf("expected discovery disabled error, got %v\n", err)
74
+	}
75
+
76
+	_, err = parseClusterAdvertiseSettings("", "something")
77
+	if err == nil {
78
+		t.Fatalf("expected discovery store error, got %v\n", err)
79
+	}
80
+
81
+	_, err = parseClusterAdvertiseSettings("etcd", "127.0.0.1:8080")
82
+	if err != nil {
83
+		t.Fatal(err)
84
+	}
85
+}
86
+
87
+func TestFindConfigurationConflicts(t *testing.T) {
88
+	config := map[string]interface{}{"authorization-plugins": "foobar"}
89
+	flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
90
+
91
+	err := findConfigurationConflicts(config, flags)
92
+	if err != nil {
93
+		t.Fatal(err)
94
+	}
95
+
96
+	flags.String([]string{"authorization-plugins"}, "", "")
97
+	if err := flags.Set("authorization-plugins", "asdf"); err != nil {
98
+		t.Fatal(err)
99
+	}
100
+
101
+	err = findConfigurationConflicts(config, flags)
102
+	if err == nil {
103
+		t.Fatal("expected error, got nil")
104
+	}
105
+	if !strings.Contains(err.Error(), "authorization-plugins") {
106
+		t.Fatalf("expected authorization-plugins conflict, got %v", err)
107
+	}
108
+}
109
+
110
+func TestFindConfigurationConflictsWithNamedOptions(t *testing.T) {
111
+	config := map[string]interface{}{"hosts": []string{"qwer"}}
112
+	flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
113
+
114
+	var hosts []string
115
+	flags.Var(opts.NewNamedListOptsRef("hosts", &hosts, opts.ValidateHost), []string{"H", "-host"}, "Daemon socket(s) to connect to")
116
+	if err := flags.Set("-host", "tcp://127.0.0.1:4444"); err != nil {
117
+		t.Fatal(err)
118
+	}
119
+	if err := flags.Set("H", "unix:///var/run/docker.sock"); err != nil {
120
+		t.Fatal(err)
121
+	}
122
+
123
+	err := findConfigurationConflicts(config, flags)
124
+	if err == nil {
125
+		t.Fatal("expected error, got nil")
126
+	}
127
+	if !strings.Contains(err.Error(), "hosts") {
128
+		t.Fatalf("expected hosts conflict, got %v", err)
129
+	}
130
+}
131
+
132
+func TestDaemonConfigurationMergeConflicts(t *testing.T) {
133
+	f, err := ioutil.TempFile("", "docker-config-")
134
+	if err != nil {
135
+		t.Fatal(err)
136
+	}
137
+
138
+	configFile := f.Name()
139
+	f.Write([]byte(`{"debug": true}`))
140
+	f.Close()
141
+
142
+	flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
143
+	flags.Bool([]string{"debug"}, false, "")
144
+	flags.Set("debug", "false")
145
+
146
+	_, err = MergeDaemonConfigurations(&Config{}, flags, configFile)
147
+	if err == nil {
148
+		t.Fatal("expected error, got nil")
149
+	}
150
+	if !strings.Contains(err.Error(), "debug") {
151
+		t.Fatalf("expected debug conflict, got %v", err)
152
+	}
153
+}
154
+
155
+func TestDaemonConfigurationMergeConflictsWithInnerStructs(t *testing.T) {
156
+	f, err := ioutil.TempFile("", "docker-config-")
157
+	if err != nil {
158
+		t.Fatal(err)
159
+	}
160
+
161
+	configFile := f.Name()
162
+	f.Write([]byte(`{"tlscacert": "/etc/certificates/ca.pem"}`))
163
+	f.Close()
164
+
165
+	flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
166
+	flags.String([]string{"tlscacert"}, "", "")
167
+	flags.Set("tlscacert", "~/.docker/ca.pem")
168
+
169
+	_, err = MergeDaemonConfigurations(&Config{}, flags, configFile)
170
+	if err == nil {
171
+		t.Fatal("expected error, got nil")
172
+	}
173
+	if !strings.Contains(err.Error(), "tlscacert") {
174
+		t.Fatalf("expected tlscacert conflict, got %v", err)
175
+	}
176
+}
... ...
@@ -18,18 +18,20 @@ var (
18 18
 )
19 19
 
20 20
 // Config defines the configuration of a docker daemon.
21
+// It includes json tags to deserialize configuration from a file
22
+// using the same names that the flags in the command line uses.
21 23
 type Config struct {
22 24
 	CommonConfig
23 25
 
24 26
 	// Fields below here are platform specific.
25 27
 
26
-	CorsHeaders          string
27
-	EnableCors           bool
28
-	EnableSelinuxSupport bool
29
-	RemappedRoot         string
30
-	SocketGroup          string
31
-	CgroupParent         string
32
-	Ulimits              map[string]*units.Ulimit
28
+	CorsHeaders          string                   `json:"api-cors-headers,omitempty"`
29
+	EnableCors           bool                     `json:"api-enable-cors,omitempty"`
30
+	EnableSelinuxSupport bool                     `json:"selinux-enabled,omitempty"`
31
+	RemappedRoot         string                   `json:"userns-remap,omitempty"`
32
+	SocketGroup          string                   `json:"group,omitempty"`
33
+	CgroupParent         string                   `json:"cgroup-parent,omitempty"`
34
+	Ulimits              map[string]*units.Ulimit `json:"default-ulimits,omitempty"`
33 35
 }
34 36
 
35 37
 // bridgeConfig stores all the bridge driver specific
... ...
@@ -46,7 +46,6 @@ import (
46 46
 	"github.com/docker/docker/layer"
47 47
 	"github.com/docker/docker/migrate/v1"
48 48
 	"github.com/docker/docker/pkg/archive"
49
-	"github.com/docker/docker/pkg/discovery"
50 49
 	"github.com/docker/docker/pkg/fileutils"
51 50
 	"github.com/docker/docker/pkg/graphdb"
52 51
 	"github.com/docker/docker/pkg/idtools"
... ...
@@ -155,7 +154,7 @@ type Daemon struct {
155 155
 	EventsService             *events.Events
156 156
 	netController             libnetwork.NetworkController
157 157
 	volumes                   *store.VolumeStore
158
-	discoveryWatcher          discovery.Watcher
158
+	discoveryWatcher          discoveryReloader
159 159
 	root                      string
160 160
 	seccompEnabled            bool
161 161
 	shutdown                  bool
... ...
@@ -292,7 +291,7 @@ func (daemon *Daemon) Register(container *container.Container) error {
292 292
 
293 293
 func (daemon *Daemon) restore() error {
294 294
 	var (
295
-		debug         = os.Getenv("DEBUG") != ""
295
+		debug         = utils.IsDebugEnabled()
296 296
 		currentDriver = daemon.GraphDriverName()
297 297
 		containers    = make(map[string]*container.Container)
298 298
 	)
... ...
@@ -772,19 +771,8 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
772 772
 
773 773
 	// Discovery is only enabled when the daemon is launched with an address to advertise.  When
774 774
 	// initialized, the daemon is registered and we can store the discovery backend as its read-only
775
-	// DiscoveryWatcher version.
776
-	if config.ClusterStore != "" && config.ClusterAdvertise != "" {
777
-		advertise, err := discovery.ParseAdvertise(config.ClusterStore, config.ClusterAdvertise)
778
-		if err != nil {
779
-			return nil, fmt.Errorf("discovery advertise parsing failed (%v)", err)
780
-		}
781
-		config.ClusterAdvertise = advertise
782
-		d.discoveryWatcher, err = initDiscovery(config.ClusterStore, config.ClusterAdvertise, config.ClusterOpts)
783
-		if err != nil {
784
-			return nil, fmt.Errorf("discovery initialization failed (%v)", err)
785
-		}
786
-	} else if config.ClusterAdvertise != "" {
787
-		return nil, fmt.Errorf("invalid cluster configuration. --cluster-advertise must be accompanied by --cluster-store configuration")
775
+	if err := d.initDiscovery(config); err != nil {
776
+		return nil, err
788 777
 	}
789 778
 
790 779
 	d.netController, err = d.initNetworkController(config)
... ...
@@ -815,7 +803,10 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
815 815
 	d.configStore = config
816 816
 	d.execDriver = ed
817 817
 	d.statsCollector = d.newStatsCollector(1 * time.Second)
818
-	d.defaultLogConfig = config.LogConfig
818
+	d.defaultLogConfig = containertypes.LogConfig{
819
+		Type:   config.LogConfig.Type,
820
+		Config: config.LogConfig.Config,
821
+	}
819 822
 	d.RegistryService = registryService
820 823
 	d.EventsService = eventsService
821 824
 	d.volumes = volStore
... ...
@@ -1521,6 +1512,76 @@ func (daemon *Daemon) newBaseContainer(id string) *container.Container {
1521 1521
 	return container.NewBaseContainer(id, daemon.containerRoot(id))
1522 1522
 }
1523 1523
 
1524
+// initDiscovery initializes the discovery watcher for this daemon.
1525
+func (daemon *Daemon) initDiscovery(config *Config) error {
1526
+	advertise, err := parseClusterAdvertiseSettings(config.ClusterStore, config.ClusterAdvertise)
1527
+	if err != nil {
1528
+		if err == errDiscoveryDisabled {
1529
+			return nil
1530
+		}
1531
+		return err
1532
+	}
1533
+
1534
+	config.ClusterAdvertise = advertise
1535
+	discoveryWatcher, err := initDiscovery(config.ClusterStore, config.ClusterAdvertise, config.ClusterOpts)
1536
+	if err != nil {
1537
+		return fmt.Errorf("discovery initialization failed (%v)", err)
1538
+	}
1539
+
1540
+	daemon.discoveryWatcher = discoveryWatcher
1541
+	return nil
1542
+}
1543
+
1544
+// Reload reads configuration changes and modifies the
1545
+// daemon according to those changes.
1546
+// This are the settings that Reload changes:
1547
+// - Daemon labels.
1548
+// - Cluster discovery (reconfigure and restart).
1549
+func (daemon *Daemon) Reload(config *Config) error {
1550
+	daemon.configStore.reloadLock.Lock()
1551
+	defer daemon.configStore.reloadLock.Unlock()
1552
+
1553
+	daemon.configStore.Labels = config.Labels
1554
+	return daemon.reloadClusterDiscovery(config)
1555
+}
1556
+
1557
+func (daemon *Daemon) reloadClusterDiscovery(config *Config) error {
1558
+	newAdvertise, err := parseClusterAdvertiseSettings(config.ClusterStore, config.ClusterAdvertise)
1559
+	if err != nil && err != errDiscoveryDisabled {
1560
+		return err
1561
+	}
1562
+
1563
+	// check discovery modifications
1564
+	if !modifiedDiscoverySettings(daemon.configStore, newAdvertise, config.ClusterStore, config.ClusterOpts) {
1565
+		return nil
1566
+	}
1567
+
1568
+	// enable discovery for the first time if it was not previously enabled
1569
+	if daemon.discoveryWatcher == nil {
1570
+		discoveryWatcher, err := initDiscovery(config.ClusterStore, newAdvertise, config.ClusterOpts)
1571
+		if err != nil {
1572
+			return fmt.Errorf("discovery initialization failed (%v)", err)
1573
+		}
1574
+		daemon.discoveryWatcher = discoveryWatcher
1575
+	} else {
1576
+		if err == errDiscoveryDisabled {
1577
+			// disable discovery if it was previously enabled and it's disabled now
1578
+			daemon.discoveryWatcher.Stop()
1579
+		} else {
1580
+			// reload discovery
1581
+			if err = daemon.discoveryWatcher.Reload(config.ClusterStore, newAdvertise, config.ClusterOpts); err != nil {
1582
+				return err
1583
+			}
1584
+		}
1585
+	}
1586
+
1587
+	daemon.configStore.ClusterStore = config.ClusterStore
1588
+	daemon.configStore.ClusterOpts = config.ClusterOpts
1589
+	daemon.configStore.ClusterAdvertise = newAdvertise
1590
+
1591
+	return nil
1592
+}
1593
+
1524 1594
 func convertLnNetworkStats(name string, stats *lntypes.InterfaceStatistics) *libcontainer.NetworkInterface {
1525 1595
 	n := &libcontainer.NetworkInterface{Name: name}
1526 1596
 	n.RxBytes = stats.RxBytes
... ...
@@ -4,9 +4,13 @@ import (
4 4
 	"io/ioutil"
5 5
 	"os"
6 6
 	"path/filepath"
7
+	"reflect"
7 8
 	"testing"
9
+	"time"
8 10
 
9 11
 	"github.com/docker/docker/container"
12
+	"github.com/docker/docker/pkg/discovery"
13
+	_ "github.com/docker/docker/pkg/discovery/memory"
10 14
 	"github.com/docker/docker/pkg/registrar"
11 15
 	"github.com/docker/docker/pkg/truncindex"
12 16
 	"github.com/docker/docker/volume"
... ...
@@ -371,3 +375,118 @@ func TestMerge(t *testing.T) {
371 371
 		}
372 372
 	}
373 373
 }
374
+
375
+func TestDaemonReloadLabels(t *testing.T) {
376
+	daemon := &Daemon{}
377
+	daemon.configStore = &Config{
378
+		CommonConfig: CommonConfig{
379
+			Labels: []string{"foo:bar"},
380
+		},
381
+	}
382
+
383
+	newConfig := &Config{
384
+		CommonConfig: CommonConfig{
385
+			Labels: []string{"foo:baz"},
386
+		},
387
+	}
388
+
389
+	daemon.Reload(newConfig)
390
+	label := daemon.configStore.Labels[0]
391
+	if label != "foo:baz" {
392
+		t.Fatalf("Expected daemon label `foo:baz`, got %s", label)
393
+	}
394
+}
395
+
396
+func TestDaemonDiscoveryReload(t *testing.T) {
397
+	daemon := &Daemon{}
398
+	daemon.configStore = &Config{
399
+		CommonConfig: CommonConfig{
400
+			ClusterStore:     "memory://127.0.0.1",
401
+			ClusterAdvertise: "127.0.0.1:3333",
402
+		},
403
+	}
404
+
405
+	if err := daemon.initDiscovery(daemon.configStore); err != nil {
406
+		t.Fatal(err)
407
+	}
408
+
409
+	expected := discovery.Entries{
410
+		&discovery.Entry{Host: "127.0.0.1", Port: "3333"},
411
+	}
412
+
413
+	stopCh := make(chan struct{})
414
+	defer close(stopCh)
415
+	ch, errCh := daemon.discoveryWatcher.Watch(stopCh)
416
+
417
+	select {
418
+	case <-time.After(1 * time.Second):
419
+		t.Fatal("failed to get discovery advertisements in time")
420
+	case e := <-ch:
421
+		if !reflect.DeepEqual(e, expected) {
422
+			t.Fatalf("expected %v, got %v\n", expected, e)
423
+		}
424
+	case e := <-errCh:
425
+		t.Fatal(e)
426
+	}
427
+
428
+	newConfig := &Config{
429
+		CommonConfig: CommonConfig{
430
+			ClusterStore:     "memory://127.0.0.1:2222",
431
+			ClusterAdvertise: "127.0.0.1:5555",
432
+		},
433
+	}
434
+
435
+	expected = discovery.Entries{
436
+		&discovery.Entry{Host: "127.0.0.1", Port: "5555"},
437
+	}
438
+
439
+	if err := daemon.Reload(newConfig); err != nil {
440
+		t.Fatal(err)
441
+	}
442
+	ch, errCh = daemon.discoveryWatcher.Watch(stopCh)
443
+
444
+	select {
445
+	case <-time.After(1 * time.Second):
446
+		t.Fatal("failed to get discovery advertisements in time")
447
+	case e := <-ch:
448
+		if !reflect.DeepEqual(e, expected) {
449
+			t.Fatalf("expected %v, got %v\n", expected, e)
450
+		}
451
+	case e := <-errCh:
452
+		t.Fatal(e)
453
+	}
454
+}
455
+
456
+func TestDaemonDiscoveryReloadFromEmptyDiscovery(t *testing.T) {
457
+	daemon := &Daemon{}
458
+	daemon.configStore = &Config{}
459
+
460
+	newConfig := &Config{
461
+		CommonConfig: CommonConfig{
462
+			ClusterStore:     "memory://127.0.0.1:2222",
463
+			ClusterAdvertise: "127.0.0.1:5555",
464
+		},
465
+	}
466
+
467
+	expected := discovery.Entries{
468
+		&discovery.Entry{Host: "127.0.0.1", Port: "5555"},
469
+	}
470
+
471
+	if err := daemon.Reload(newConfig); err != nil {
472
+		t.Fatal(err)
473
+	}
474
+	stopCh := make(chan struct{})
475
+	defer close(stopCh)
476
+	ch, errCh := daemon.discoveryWatcher.Watch(stopCh)
477
+
478
+	select {
479
+	case <-time.After(1 * time.Second):
480
+		t.Fatal("failed to get discovery advertisements in time")
481
+	case e := <-ch:
482
+		if !reflect.DeepEqual(e, expected) {
483
+			t.Fatalf("expected %v, got %v\n", expected, e)
484
+		}
485
+	case e := <-errCh:
486
+		t.Fatal(e)
487
+	}
488
+}
... ...
@@ -1,7 +1,9 @@
1 1
 package daemon
2 2
 
3 3
 import (
4
+	"errors"
4 5
 	"fmt"
6
+	"reflect"
5 7
 	"strconv"
6 8
 	"time"
7 9
 
... ...
@@ -19,6 +21,24 @@ const (
19 19
 	defaultDiscoveryTTLFactor = 3
20 20
 )
21 21
 
22
+var errDiscoveryDisabled = errors.New("discovery is disabled")
23
+
24
+type discoveryReloader interface {
25
+	discovery.Watcher
26
+	Stop()
27
+	Reload(backend, address string, clusterOpts map[string]string) error
28
+}
29
+
30
+type daemonDiscoveryReloader struct {
31
+	backend discovery.Backend
32
+	ticker  *time.Ticker
33
+	term    chan bool
34
+}
35
+
36
+func (d *daemonDiscoveryReloader) Watch(stopCh <-chan struct{}) (<-chan discovery.Entries, <-chan error) {
37
+	return d.backend.Watch(stopCh)
38
+}
39
+
22 40
 func discoveryOpts(clusterOpts map[string]string) (time.Duration, time.Duration, error) {
23 41
 	var (
24 42
 		heartbeat = defaultDiscoveryHeartbeat
... ...
@@ -57,36 +77,94 @@ func discoveryOpts(clusterOpts map[string]string) (time.Duration, time.Duration,
57 57
 
58 58
 // initDiscovery initialized the nodes discovery subsystem by connecting to the specified backend
59 59
 // and start a registration loop to advertise the current node under the specified address.
60
-func initDiscovery(backend, address string, clusterOpts map[string]string) (discovery.Backend, error) {
61
-
62
-	heartbeat, ttl, err := discoveryOpts(clusterOpts)
60
+func initDiscovery(backendAddress, advertiseAddress string, clusterOpts map[string]string) (discoveryReloader, error) {
61
+	heartbeat, backend, err := parseDiscoveryOptions(backendAddress, clusterOpts)
63 62
 	if err != nil {
64 63
 		return nil, err
65 64
 	}
66 65
 
67
-	discoveryBackend, err := discovery.New(backend, heartbeat, ttl, clusterOpts)
68
-	if err != nil {
69
-		return nil, err
66
+	reloader := &daemonDiscoveryReloader{
67
+		backend: backend,
68
+		ticker:  time.NewTicker(heartbeat),
69
+		term:    make(chan bool),
70 70
 	}
71
-
72 71
 	// We call Register() on the discovery backend in a loop for the whole lifetime of the daemon,
73 72
 	// but we never actually Watch() for nodes appearing and disappearing for the moment.
74
-	go registrationLoop(discoveryBackend, address, heartbeat)
75
-	return discoveryBackend, nil
73
+	reloader.advertise(advertiseAddress)
74
+	return reloader, nil
76 75
 }
77 76
 
78
-func registerAddr(backend discovery.Backend, addr string) {
79
-	if err := backend.Register(addr); err != nil {
77
+func (d *daemonDiscoveryReloader) advertise(address string) {
78
+	d.registerAddr(address)
79
+	go d.advertiseHeartbeat(address)
80
+}
81
+
82
+func (d *daemonDiscoveryReloader) registerAddr(addr string) {
83
+	if err := d.backend.Register(addr); err != nil {
80 84
 		log.Warnf("Registering as %q in discovery failed: %v", addr, err)
81 85
 	}
82 86
 }
83 87
 
84
-// registrationLoop registers the current node against the discovery backend using the specified
88
+// advertiseHeartbeat registers the current node against the discovery backend using the specified
85 89
 // address. The function never returns, as registration against the backend comes with a TTL and
86 90
 // requires regular heartbeats.
87
-func registrationLoop(discoveryBackend discovery.Backend, address string, heartbeat time.Duration) {
88
-	registerAddr(discoveryBackend, address)
89
-	for range time.Tick(heartbeat) {
90
-		registerAddr(discoveryBackend, address)
91
+func (d *daemonDiscoveryReloader) advertiseHeartbeat(address string) {
92
+	for {
93
+		select {
94
+		case <-d.ticker.C:
95
+			d.registerAddr(address)
96
+		case <-d.term:
97
+			return
98
+		}
99
+	}
100
+}
101
+
102
+// Reload makes the watcher to stop advertising and reconfigures it to advertise in a new address.
103
+func (d *daemonDiscoveryReloader) Reload(backendAddress, advertiseAddress string, clusterOpts map[string]string) error {
104
+	d.Stop()
105
+
106
+	heartbeat, backend, err := parseDiscoveryOptions(backendAddress, clusterOpts)
107
+	if err != nil {
108
+		return err
109
+	}
110
+
111
+	d.backend = backend
112
+	d.ticker = time.NewTicker(heartbeat)
113
+
114
+	d.advertise(advertiseAddress)
115
+	return nil
116
+}
117
+
118
+// Stop terminates the discovery advertising.
119
+func (d *daemonDiscoveryReloader) Stop() {
120
+	d.ticker.Stop()
121
+	d.term <- true
122
+}
123
+
124
+func parseDiscoveryOptions(backendAddress string, clusterOpts map[string]string) (time.Duration, discovery.Backend, error) {
125
+	heartbeat, ttl, err := discoveryOpts(clusterOpts)
126
+	if err != nil {
127
+		return 0, nil, err
91 128
 	}
129
+
130
+	backend, err := discovery.New(backendAddress, heartbeat, ttl, clusterOpts)
131
+	if err != nil {
132
+		return 0, nil, err
133
+	}
134
+	return heartbeat, backend, nil
135
+}
136
+
137
+// modifiedDiscoverySettings returns whether the discovery configuration has been modified or not.
138
+func modifiedDiscoverySettings(config *Config, backendType, advertise string, clusterOpts map[string]string) bool {
139
+	if config.ClusterStore != backendType || config.ClusterAdvertise != advertise {
140
+		return true
141
+	}
142
+
143
+	if (config.ClusterOpts == nil && clusterOpts == nil) ||
144
+		(config.ClusterOpts == nil && len(clusterOpts) == 0) ||
145
+		(len(config.ClusterOpts) == 0 && clusterOpts == nil) {
146
+		return false
147
+	}
148
+
149
+	return !reflect.DeepEqual(config.ClusterOpts, clusterOpts)
92 150
 }
... ...
@@ -89,3 +89,64 @@ func TestDiscoveryOpts(t *testing.T) {
89 89
 		t.Fatalf("TTL - Expected : %v, Actual : %v", expected, ttl)
90 90
 	}
91 91
 }
92
+
93
+func TestModifiedDiscoverySettings(t *testing.T) {
94
+	cases := []struct {
95
+		current  *Config
96
+		modified *Config
97
+		expected bool
98
+	}{
99
+		{
100
+			current:  discoveryConfig("foo", "bar", map[string]string{}),
101
+			modified: discoveryConfig("foo", "bar", map[string]string{}),
102
+			expected: false,
103
+		},
104
+		{
105
+			current:  discoveryConfig("foo", "bar", map[string]string{"foo": "bar"}),
106
+			modified: discoveryConfig("foo", "bar", map[string]string{"foo": "bar"}),
107
+			expected: false,
108
+		},
109
+		{
110
+			current:  discoveryConfig("foo", "bar", map[string]string{}),
111
+			modified: discoveryConfig("foo", "bar", nil),
112
+			expected: false,
113
+		},
114
+		{
115
+			current:  discoveryConfig("foo", "bar", nil),
116
+			modified: discoveryConfig("foo", "bar", map[string]string{}),
117
+			expected: false,
118
+		},
119
+		{
120
+			current:  discoveryConfig("foo", "bar", nil),
121
+			modified: discoveryConfig("baz", "bar", nil),
122
+			expected: true,
123
+		},
124
+		{
125
+			current:  discoveryConfig("foo", "bar", nil),
126
+			modified: discoveryConfig("foo", "baz", nil),
127
+			expected: true,
128
+		},
129
+		{
130
+			current:  discoveryConfig("foo", "bar", nil),
131
+			modified: discoveryConfig("foo", "bar", map[string]string{"foo": "bar"}),
132
+			expected: true,
133
+		},
134
+	}
135
+
136
+	for _, c := range cases {
137
+		got := modifiedDiscoverySettings(c.current, c.modified.ClusterStore, c.modified.ClusterAdvertise, c.modified.ClusterOpts)
138
+		if c.expected != got {
139
+			t.Fatalf("expected %v, got %v: current config %q, new config %q", c.expected, got, c.current, c.modified)
140
+		}
141
+	}
142
+}
143
+
144
+func discoveryConfig(backendAddr, advertiseAddr string, opts map[string]string) *Config {
145
+	return &Config{
146
+		CommonConfig: CommonConfig{
147
+			ClusterStore:     backendAddr,
148
+			ClusterAdvertise: advertiseAddr,
149
+			ClusterOpts:      opts,
150
+		},
151
+	}
152
+}
... ...
@@ -79,7 +79,7 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) {
79 79
 		IPv4Forwarding:     !sysInfo.IPv4ForwardingDisabled,
80 80
 		BridgeNfIptables:   !sysInfo.BridgeNfCallIptablesDisabled,
81 81
 		BridgeNfIP6tables:  !sysInfo.BridgeNfCallIP6tablesDisabled,
82
-		Debug:              os.Getenv("DEBUG") != "",
82
+		Debug:              utils.IsDebugEnabled(),
83 83
 		NFd:                fileutils.GetTotalUsedFds(),
84 84
 		NGoroutines:        runtime.NumGoroutine(),
85 85
 		SystemTime:         time.Now().Format(time.RFC3339Nano),
... ...
@@ -21,7 +21,6 @@ const (
21 21
 )
22 22
 
23 23
 var (
24
-	daemonFlags *flag.FlagSet
25 24
 	commonFlags = &cli.CommonFlags{FlagSet: new(flag.FlagSet)}
26 25
 
27 26
 	dockerCertPath  = os.Getenv("DOCKER_CERT_PATH")
... ...
@@ -50,7 +49,7 @@ func init() {
50 50
 	cmd.StringVar(&tlsOptions.CertFile, []string{"-tlscert"}, filepath.Join(dockerCertPath, defaultCertFile), "Path to TLS certificate file")
51 51
 	cmd.StringVar(&tlsOptions.KeyFile, []string{"-tlskey"}, filepath.Join(dockerCertPath, defaultKeyFile), "Path to TLS key file")
52 52
 
53
-	cmd.Var(opts.NewListOptsRef(&commonFlags.Hosts, opts.ValidateHost), []string{"H", "-host"}, "Daemon socket(s) to connect to")
53
+	cmd.Var(opts.NewNamedListOptsRef("hosts", &commonFlags.Hosts, opts.ValidateHost), []string{"H", "-host"}, "Daemon socket(s) to connect to")
54 54
 }
55 55
 
56 56
 func postParseCommon() {
... ...
@@ -67,11 +66,6 @@ func postParseCommon() {
67 67
 		logrus.SetLevel(logrus.InfoLevel)
68 68
 	}
69 69
 
70
-	if commonFlags.Debug {
71
-		os.Setenv("DEBUG", "1")
72
-		logrus.SetLevel(logrus.DebugLevel)
73
-	}
74
-
75 70
 	// Regardless of whether the user sets it to true or false, if they
76 71
 	// specify --tlsverify at all then we need to turn on tls
77 72
 	// TLSVerify can be true even if not set due to DOCKER_TLS_VERIFY env var, so we need to check that here as well
... ...
@@ -30,23 +30,34 @@ import (
30 30
 	"github.com/docker/go-connections/tlsconfig"
31 31
 )
32 32
 
33
-const daemonUsage = "       docker daemon [ --help | ... ]\n"
33
+const (
34
+	daemonUsage          = "       docker daemon [ --help | ... ]\n"
35
+	daemonConfigFileFlag = "-config-file"
36
+)
34 37
 
35 38
 var (
36 39
 	daemonCli cli.Handler = NewDaemonCli()
37 40
 )
38 41
 
42
+// DaemonCli represents the daemon CLI.
43
+type DaemonCli struct {
44
+	*daemon.Config
45
+	registryOptions *registry.Options
46
+	flags           *flag.FlagSet
47
+}
48
+
39 49
 func presentInHelp(usage string) string { return usage }
40 50
 func absentFromHelp(string) string      { return "" }
41 51
 
42 52
 // NewDaemonCli returns a pre-configured daemon CLI
43 53
 func NewDaemonCli() *DaemonCli {
44
-	daemonFlags = cli.Subcmd("daemon", nil, "Enable daemon mode", true)
54
+	daemonFlags := cli.Subcmd("daemon", nil, "Enable daemon mode", true)
45 55
 
46 56
 	// TODO(tiborvass): remove InstallFlags?
47 57
 	daemonConfig := new(daemon.Config)
48 58
 	daemonConfig.LogConfig.Config = make(map[string]string)
49 59
 	daemonConfig.ClusterOpts = make(map[string]string)
60
+
50 61
 	daemonConfig.InstallFlags(daemonFlags, presentInHelp)
51 62
 	daemonConfig.InstallFlags(flag.CommandLine, absentFromHelp)
52 63
 	registryOptions := new(registry.Options)
... ...
@@ -57,6 +68,7 @@ func NewDaemonCli() *DaemonCli {
57 57
 	return &DaemonCli{
58 58
 		Config:          daemonConfig,
59 59
 		registryOptions: registryOptions,
60
+		flags:           daemonFlags,
60 61
 	}
61 62
 }
62 63
 
... ...
@@ -101,12 +113,6 @@ func migrateKey() (err error) {
101 101
 	return nil
102 102
 }
103 103
 
104
-// DaemonCli represents the daemon CLI.
105
-type DaemonCli struct {
106
-	*daemon.Config
107
-	registryOptions *registry.Options
108
-}
109
-
110 104
 func getGlobalFlag() (globalFlag *flag.Flag) {
111 105
 	defer func() {
112 106
 		if x := recover(); x != nil {
... ...
@@ -136,15 +142,27 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
136 136
 		os.Exit(1)
137 137
 	} else {
138 138
 		// allow new form `docker daemon -D`
139
-		flag.Merge(daemonFlags, commonFlags.FlagSet)
139
+		flag.Merge(cli.flags, commonFlags.FlagSet)
140 140
 	}
141 141
 
142
-	daemonFlags.ParseFlags(args, true)
142
+	configFile := cli.flags.String([]string{daemonConfigFileFlag}, defaultDaemonConfigFile, "Daemon configuration file")
143
+
144
+	cli.flags.ParseFlags(args, true)
143 145
 	commonFlags.PostParse()
144 146
 
145 147
 	if commonFlags.TrustKey == "" {
146 148
 		commonFlags.TrustKey = filepath.Join(getDaemonConfDir(), defaultTrustKeyFile)
147 149
 	}
150
+	cliConfig, err := loadDaemonCliConfig(cli.Config, cli.flags, commonFlags, *configFile)
151
+	if err != nil {
152
+		fmt.Fprint(os.Stderr, err)
153
+		os.Exit(1)
154
+	}
155
+	cli.Config = cliConfig
156
+
157
+	if cli.Config.Debug {
158
+		utils.EnableDebug()
159
+	}
148 160
 
149 161
 	if utils.ExperimentalBuild() {
150 162
 		logrus.Warn("Running experimental build")
... ...
@@ -184,12 +202,18 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
184 184
 	serverConfig = setPlatformServerConfig(serverConfig, cli.Config)
185 185
 
186 186
 	defaultHost := opts.DefaultHost
187
-	if commonFlags.TLSOptions != nil {
188
-		if !commonFlags.TLSOptions.InsecureSkipVerify {
187
+	if cli.Config.TLS {
188
+		tlsOptions := tlsconfig.Options{
189
+			CAFile:   cli.Config.TLSOptions.CAFile,
190
+			CertFile: cli.Config.TLSOptions.CertFile,
191
+			KeyFile:  cli.Config.TLSOptions.KeyFile,
192
+		}
193
+
194
+		if cli.Config.TLSVerify {
189 195
 			// server requires and verifies client's certificate
190
-			commonFlags.TLSOptions.ClientAuth = tls.RequireAndVerifyClientCert
196
+			tlsOptions.ClientAuth = tls.RequireAndVerifyClientCert
191 197
 		}
192
-		tlsConfig, err := tlsconfig.Server(*commonFlags.TLSOptions)
198
+		tlsConfig, err := tlsconfig.Server(tlsOptions)
193 199
 		if err != nil {
194 200
 			logrus.Fatal(err)
195 201
 		}
... ...
@@ -197,22 +221,23 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
197 197
 		defaultHost = opts.DefaultTLSHost
198 198
 	}
199 199
 
200
-	if len(commonFlags.Hosts) == 0 {
201
-		commonFlags.Hosts = make([]string, 1)
200
+	if len(cli.Config.Hosts) == 0 {
201
+		cli.Config.Hosts = make([]string, 1)
202 202
 	}
203
-	for i := 0; i < len(commonFlags.Hosts); i++ {
203
+	for i := 0; i < len(cli.Config.Hosts); i++ {
204 204
 		var err error
205
-		if commonFlags.Hosts[i], err = opts.ParseHost(defaultHost, commonFlags.Hosts[i]); err != nil {
206
-			logrus.Fatalf("error parsing -H %s : %v", commonFlags.Hosts[i], err)
205
+		if cli.Config.Hosts[i], err = opts.ParseHost(defaultHost, cli.Config.Hosts[i]); err != nil {
206
+			logrus.Fatalf("error parsing -H %s : %v", cli.Config.Hosts[i], err)
207 207
 		}
208
-	}
209
-	for _, protoAddr := range commonFlags.Hosts {
208
+
209
+		protoAddr := cli.Config.Hosts[i]
210 210
 		protoAddrParts := strings.SplitN(protoAddr, "://", 2)
211 211
 		if len(protoAddrParts) != 2 {
212 212
 			logrus.Fatalf("bad format %s, expected PROTO://ADDR", protoAddr)
213 213
 		}
214 214
 		serverConfig.Addrs = append(serverConfig.Addrs, apiserver.Addr{Proto: protoAddrParts[0], Addr: protoAddrParts[1]})
215 215
 	}
216
+
216 217
 	api, err := apiserver.New(serverConfig)
217 218
 	if err != nil {
218 219
 		logrus.Fatal(err)
... ...
@@ -245,18 +270,21 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
245 245
 
246 246
 	api.InitRouters(d)
247 247
 
248
+	reload := func(config *daemon.Config) {
249
+		if err := d.Reload(config); err != nil {
250
+			logrus.Errorf("Error reconfiguring the daemon: %v", err)
251
+			return
252
+		}
253
+		api.Reload(config)
254
+	}
255
+
256
+	setupConfigReloadTrap(*configFile, cli.flags, reload)
257
+
248 258
 	// The serve API routine never exits unless an error occurs
249 259
 	// We need to start it as a goroutine and wait on it so
250 260
 	// daemon doesn't exit
251 261
 	serveAPIWait := make(chan error)
252
-	go func() {
253
-		if err := api.ServeAPI(); err != nil {
254
-			logrus.Errorf("ServeAPI error: %v", err)
255
-			serveAPIWait <- err
256
-			return
257
-		}
258
-		serveAPIWait <- nil
259
-	}()
262
+	go api.Wait(serveAPIWait)
260 263
 
261 264
 	signal.Trap(func() {
262 265
 		api.Close()
... ...
@@ -303,3 +331,34 @@ func shutdownDaemon(d *daemon.Daemon, timeout time.Duration) {
303 303
 		logrus.Error("Force shutdown daemon")
304 304
 	}
305 305
 }
306
+
307
+func loadDaemonCliConfig(config *daemon.Config, daemonFlags *flag.FlagSet, commonConfig *cli.CommonFlags, configFile string) (*daemon.Config, error) {
308
+	config.Debug = commonConfig.Debug
309
+	config.Hosts = commonConfig.Hosts
310
+	config.LogLevel = commonConfig.LogLevel
311
+	config.TLS = commonConfig.TLS
312
+	config.TLSVerify = commonConfig.TLSVerify
313
+	config.TLSOptions = daemon.CommonTLSOptions{}
314
+
315
+	if commonConfig.TLSOptions != nil {
316
+		config.TLSOptions.CAFile = commonConfig.TLSOptions.CAFile
317
+		config.TLSOptions.CertFile = commonConfig.TLSOptions.CertFile
318
+		config.TLSOptions.KeyFile = commonConfig.TLSOptions.KeyFile
319
+	}
320
+
321
+	if configFile != "" {
322
+		c, err := daemon.MergeDaemonConfigurations(config, daemonFlags, configFile)
323
+		if err != nil {
324
+			if daemonFlags.IsSet(daemonConfigFileFlag) || !os.IsNotExist(err) {
325
+				return nil, fmt.Errorf("unable to configure the Docker daemon with file %s: %v\n", configFile, err)
326
+			}
327
+		}
328
+		// the merged configuration can be nil if the config file didn't exist.
329
+		// leave the current configuration as it is if when that happens.
330
+		if c != nil {
331
+			config = c
332
+		}
333
+	}
334
+
335
+	return config, nil
336
+}
306 337
new file mode 100644
... ...
@@ -0,0 +1,91 @@
0
+// +build daemon
1
+
2
+package main
3
+
4
+import (
5
+	"io/ioutil"
6
+	"strings"
7
+	"testing"
8
+
9
+	"github.com/docker/docker/cli"
10
+	"github.com/docker/docker/daemon"
11
+	"github.com/docker/docker/opts"
12
+	"github.com/docker/docker/pkg/mflag"
13
+	"github.com/docker/go-connections/tlsconfig"
14
+)
15
+
16
+func TestLoadDaemonCliConfigWithoutOverriding(t *testing.T) {
17
+	c := &daemon.Config{}
18
+	common := &cli.CommonFlags{
19
+		Debug: true,
20
+	}
21
+
22
+	flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
23
+	loadedConfig, err := loadDaemonCliConfig(c, flags, common, "/tmp/fooobarbaz")
24
+	if err != nil {
25
+		t.Fatal(err)
26
+	}
27
+	if loadedConfig == nil {
28
+		t.Fatalf("expected configuration %v, got nil", c)
29
+	}
30
+	if !loadedConfig.Debug {
31
+		t.Fatalf("expected debug to be copied from the common flags, got false")
32
+	}
33
+}
34
+
35
+func TestLoadDaemonCliConfigWithTLS(t *testing.T) {
36
+	c := &daemon.Config{}
37
+	common := &cli.CommonFlags{
38
+		TLS: true,
39
+		TLSOptions: &tlsconfig.Options{
40
+			CAFile: "/tmp/ca.pem",
41
+		},
42
+	}
43
+
44
+	flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
45
+	loadedConfig, err := loadDaemonCliConfig(c, flags, common, "/tmp/fooobarbaz")
46
+	if err != nil {
47
+		t.Fatal(err)
48
+	}
49
+	if loadedConfig == nil {
50
+		t.Fatalf("expected configuration %v, got nil", c)
51
+	}
52
+	if loadedConfig.TLSOptions.CAFile != "/tmp/ca.pem" {
53
+		t.Fatalf("expected /tmp/ca.pem, got %s: %q", loadedConfig.TLSOptions.CAFile, loadedConfig)
54
+	}
55
+}
56
+
57
+func TestLoadDaemonCliConfigWithConflicts(t *testing.T) {
58
+	c := &daemon.Config{}
59
+	common := &cli.CommonFlags{}
60
+	f, err := ioutil.TempFile("", "docker-config-")
61
+	if err != nil {
62
+		t.Fatal(err)
63
+	}
64
+
65
+	configFile := f.Name()
66
+	f.Write([]byte(`{"labels": ["l3=foo"]}`))
67
+	f.Close()
68
+
69
+	var labels []string
70
+
71
+	flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
72
+	flags.String([]string{daemonConfigFileFlag}, "", "")
73
+	flags.Var(opts.NewNamedListOptsRef("labels", &labels, opts.ValidateLabel), []string{"-label"}, "")
74
+
75
+	flags.Set(daemonConfigFileFlag, configFile)
76
+	if err := flags.Set("-label", "l1=bar"); err != nil {
77
+		t.Fatal(err)
78
+	}
79
+	if err := flags.Set("-label", "l2=baz"); err != nil {
80
+		t.Fatal(err)
81
+	}
82
+
83
+	_, err = loadDaemonCliConfig(c, flags, common, configFile)
84
+	if err == nil {
85
+		t.Fatalf("expected configuration error, got nil")
86
+	}
87
+	if !strings.Contains(err.Error(), "labels") {
88
+		t.Fatalf("expected labels conflict, got %v", err)
89
+	}
90
+}
... ...
@@ -5,15 +5,19 @@ package main
5 5
 import (
6 6
 	"fmt"
7 7
 	"os"
8
+	"os/signal"
8 9
 	"syscall"
9 10
 
10 11
 	apiserver "github.com/docker/docker/api/server"
11 12
 	"github.com/docker/docker/daemon"
13
+	"github.com/docker/docker/pkg/mflag"
12 14
 	"github.com/docker/docker/pkg/system"
13 15
 
14 16
 	_ "github.com/docker/docker/daemon/execdriver/native"
15 17
 )
16 18
 
19
+const defaultDaemonConfigFile = "/etc/docker/daemon.json"
20
+
17 21
 func setPlatformServerConfig(serverConfig *apiserver.Config, daemonCfg *daemon.Config) *apiserver.Config {
18 22
 	serverConfig.SocketGroup = daemonCfg.SocketGroup
19 23
 	serverConfig.EnableCors = daemonCfg.EnableCors
... ...
@@ -48,3 +52,14 @@ func setDefaultUmask() error {
48 48
 func getDaemonConfDir() string {
49 49
 	return "/etc/docker"
50 50
 }
51
+
52
+// setupConfigReloadTrap configures the USR2 signal to reload the configuration.
53
+func setupConfigReloadTrap(configFile string, flags *mflag.FlagSet, reload func(*daemon.Config)) {
54
+	c := make(chan os.Signal, 1)
55
+	signal.Notify(c, syscall.SIGHUP)
56
+	go func() {
57
+		for range c {
58
+			daemon.ReloadConfiguration(configFile, flags, reload)
59
+		}
60
+	}()
61
+}
... ...
@@ -3,12 +3,19 @@
3 3
 package main
4 4
 
5 5
 import (
6
+	"fmt"
6 7
 	"os"
8
+	"syscall"
7 9
 
10
+	"github.com/Sirupsen/logrus"
8 11
 	apiserver "github.com/docker/docker/api/server"
9 12
 	"github.com/docker/docker/daemon"
13
+	"github.com/docker/docker/pkg/mflag"
14
+	"github.com/docker/docker/pkg/system"
10 15
 )
11 16
 
17
+var defaultDaemonConfigFile = os.Getenv("programdata") + string(os.PathSeparator) + "docker" + string(os.PathSeparator) + "config" + string(os.PathSeparator) + "daemon.json"
18
+
12 19
 func setPlatformServerConfig(serverConfig *apiserver.Config, daemonCfg *daemon.Config) *apiserver.Config {
13 20
 	return serverConfig
14 21
 }
... ...
@@ -31,3 +38,20 @@ func getDaemonConfDir() string {
31 31
 // notifySystem sends a message to the host when the server is ready to be used
32 32
 func notifySystem() {
33 33
 }
34
+
35
+// setupConfigReloadTrap configures a Win32 event to reload the configuration.
36
+func setupConfigReloadTrap(configFile string, flags *mflag.FlagSet, reload func(*daemon.Config)) {
37
+	go func() {
38
+		sa := syscall.SecurityAttributes{
39
+			Length: 0,
40
+		}
41
+		ev := "Global\\docker-daemon-config-" + fmt.Sprint(os.Getpid())
42
+		if h, _ := system.CreateEvent(&sa, false, false, ev); h != 0 {
43
+			logrus.Debugf("Config reload - waiting signal at %s", ev)
44
+			for {
45
+				syscall.WaitForSingleObject(h, syscall.INFINITE)
46
+				daemon.ReloadConfiguration(configFile, flags, reload)
47
+			}
48
+		}
49
+	}()
50
+}
... ...
@@ -27,6 +27,7 @@ weight = -1
27 27
       --cluster-store=""                     URL of the distributed storage backend
28 28
       --cluster-advertise=""                 Address of the daemon instance on the cluster
29 29
       --cluster-store-opt=map[]              Set cluster options
30
+      --config-file=/etc/docker/daemon.json  Daemon configuration file
30 31
       --dns=[]                               DNS server to use
31 32
       --dns-opt=[]                           DNS options to use
32 33
       --dns-search=[]                        DNS search domains to use
... ...
@@ -788,7 +789,7 @@ set like this:
788 788
     /usr/local/bin/docker daemon -D -g /var/lib/docker -H unix:// > /var/lib/docker-machine/docker.log 2>&1
789 789
 
790 790
 
791
-# Default cgroup parent
791
+## Default cgroup parent
792 792
 
793 793
 The `--cgroup-parent` option allows you to set the default cgroup parent
794 794
 to use for containers. If this option is not set, it defaults to `/docker` for
... ...
@@ -806,3 +807,79 @@ creates the cgroup in `/sys/fs/cgroup/memory/daemoncgroup/foobar`
806 806
 This setting can also be set per container, using the `--cgroup-parent`
807 807
 option on `docker create` and `docker run`, and takes precedence over
808 808
 the `--cgroup-parent` option on the daemon.
809
+
810
+## Daemon configuration file
811
+
812
+The `--config-file` option allows you to set any configuration option
813
+for the daemon in a JSON format. This file uses the same flag names as keys,
814
+except for flags that allow several entries, where it uses the plural
815
+of the flag name, e.g., `labels` for the `label` flag. By default,
816
+docker tries to load a configuration file from `/etc/docker/daemon.json`
817
+on Linux and `%programdata%\docker\config\daemon.json` on Windows.
818
+
819
+The options set in the configuration file must not conflict with options set
820
+via flags. The docker daemon fails to start if an option is duplicated between
821
+the file and the flags, regardless their value. We do this to avoid
822
+silently ignore changes introduced in configuration reloads.
823
+For example, the daemon fails to start if you set daemon labels
824
+in the configuration file and also set daemon labels via the `--label` flag.
825
+
826
+Options that are not present in the file are ignored when the daemon starts.
827
+This is a full example of the allowed configuration options in the file:
828
+
829
+```json
830
+{
831
+	"authorization-plugins": [],
832
+	"dns": [],
833
+	"dns-opts": [],
834
+	"dns-search": [],
835
+	"exec-opts": [],
836
+	"exec-root": "",
837
+	"storage-driver": "",
838
+	"storage-opts": "",
839
+	"labels": [],
840
+	"log-config": {
841
+		"log-driver": "",
842
+		"log-opts": []
843
+	},
844
+	"mtu": 0,
845
+	"pidfile": "",
846
+	"graph": "",
847
+	"cluster-store": "",
848
+	"cluster-store-opts": [],
849
+	"cluster-advertise": "",
850
+	"debug": true,
851
+	"hosts": [],
852
+	"log-level": "",
853
+	"tls": true,
854
+	"tls-verify": true,
855
+	"tls-opts": {
856
+		"tlscacert": "",
857
+		"tlscert": "",
858
+		"tlskey": ""
859
+	},
860
+	"api-cors-headers": "",
861
+	"selinux-enabled": false,
862
+	"userns-remap": "",
863
+	"group": "",
864
+	"cgroup-parent": "",
865
+	"default-ulimits": {}
866
+}
867
+```
868
+
869
+### Configuration reloading
870
+
871
+Some options can be reconfigured when the daemon is running without requiring
872
+to restart the process. We use the `SIGHUP` signal in Linux to reload, and a global event
873
+in Windows with the key `Global\docker-daemon-config-$PID`. The options can
874
+be modified in the configuration file but still will check for conflicts with
875
+the provided flags. The daemon fails to reconfigure itself
876
+if there are conflicts, but it won't stop execution.
877
+
878
+The list of currently supported options that can be reconfigured is this:
879
+
880
+- `debug`: it changes the daemon to debug mode when set to true.
881
+- `label`: it replaces the daemon labels with a new set of labels.
882
+- `cluster-store`: it reloads the discovery store with the new address.
883
+- `cluster-store-opts`: it uses the new options to reload the discovery store.
884
+- `cluster-advertise`: it modifies the address advertised after reloading.
... ...
@@ -24,6 +24,7 @@ clone git github.com/docker/go-units 651fc226e7441360384da338d0fd37f2440ffbe3
24 24
 clone git github.com/docker/go-connections v0.1.2
25 25
 clone git github.com/docker/engine-api v0.2.2
26 26
 clone git github.com/RackSec/srslog 6eb773f331e46fbba8eecb8e794e635e75fc04de
27
+clone git github.com/imdario/mergo 0.2.1
27 28
 
28 29
 #get libnetwork packages
29 30
 clone git github.com/docker/libnetwork v0.5.5
... ...
@@ -133,7 +133,7 @@ func (s *DockerSuite) TestHelpTextVerify(c *check.C) {
133 133
 			// Check each line for lots of stuff
134 134
 			lines := strings.Split(out, "\n")
135 135
 			for _, line := range lines {
136
-				c.Assert(len(line), checker.LessOrEqualThan, 103, check.Commentf("Help for %q is too long:\n%s", cmd, line))
136
+				c.Assert(len(line), checker.LessOrEqualThan, 107, check.Commentf("Help for %q is too long:\n%s", cmd, line))
137 137
 
138 138
 				if scanForHome && strings.Contains(line, `"`+home) {
139 139
 					c.Fatalf("Help for %q should use ~ instead of %q on:\n%s",
... ...
@@ -14,6 +14,7 @@ docker-daemon - Enable daemon mode
14 14
 [**--cluster-store**[=*[]*]]
15 15
 [**--cluster-advertise**[=*[]*]]
16 16
 [**--cluster-store-opt**[=*map[]*]]
17
+[**--config-file**[=*/etc/docker/daemon.json*]]
17 18
 [**-D**|**--debug**]
18 19
 [**--default-gateway**[=*DEFAULT-GATEWAY*]]
19 20
 [**--default-gateway-v6**[=*DEFAULT-GATEWAY-V6*]]
... ...
@@ -96,6 +97,9 @@ format.
96 96
 **--cluster-store-opt**=""
97 97
   Specifies options for the Key/Value store.
98 98
 
99
+**--config-file**="/etc/docker/daemon.json"
100
+  Specifies the JSON file path to load the configuration from.
101
+
99 102
 **-D**, **--debug**=*true*|*false*
100 103
   Enable debug mode. Default is false.
101 104
 
... ...
@@ -100,6 +100,35 @@ func (opts *ListOpts) Len() int {
100 100
 	return len((*opts.values))
101 101
 }
102 102
 
103
+// NamedOption is an interface that list and map options
104
+// with names implement.
105
+type NamedOption interface {
106
+	Name() string
107
+}
108
+
109
+// NamedListOpts is a ListOpts with a configuration name.
110
+// This struct is useful to keep reference to the assigned
111
+// field name in the internal configuration struct.
112
+type NamedListOpts struct {
113
+	name string
114
+	ListOpts
115
+}
116
+
117
+var _ NamedOption = &NamedListOpts{}
118
+
119
+// NewNamedListOptsRef creates a reference to a new NamedListOpts struct.
120
+func NewNamedListOptsRef(name string, values *[]string, validator ValidatorFctType) *NamedListOpts {
121
+	return &NamedListOpts{
122
+		name:     name,
123
+		ListOpts: *NewListOptsRef(values, validator),
124
+	}
125
+}
126
+
127
+// Name returns the name of the NamedListOpts in the configuration.
128
+func (o *NamedListOpts) Name() string {
129
+	return o.name
130
+}
131
+
103 132
 //MapOpts holds a map of values and a validation function.
104 133
 type MapOpts struct {
105 134
 	values    map[string]string
... ...
@@ -145,6 +174,29 @@ func NewMapOpts(values map[string]string, validator ValidatorFctType) *MapOpts {
145 145
 	}
146 146
 }
147 147
 
148
+// NamedMapOpts is a MapOpts struct with a configuration name.
149
+// This struct is useful to keep reference to the assigned
150
+// field name in the internal configuration struct.
151
+type NamedMapOpts struct {
152
+	name string
153
+	MapOpts
154
+}
155
+
156
+var _ NamedOption = &NamedMapOpts{}
157
+
158
+// NewNamedMapOpts creates a reference to a new NamedMapOpts struct.
159
+func NewNamedMapOpts(name string, values map[string]string, validator ValidatorFctType) *NamedMapOpts {
160
+	return &NamedMapOpts{
161
+		name:    name,
162
+		MapOpts: *NewMapOpts(values, validator),
163
+	}
164
+}
165
+
166
+// Name returns the name of the NamedMapOpts in the configuration.
167
+func (o *NamedMapOpts) Name() string {
168
+	return o.name
169
+}
170
+
148 171
 // ValidatorFctType defines a validator function that returns a validated string and/or an error.
149 172
 type ValidatorFctType func(val string) (string, error)
150 173
 
... ...
@@ -198,3 +198,35 @@ func logOptsValidator(val string) (string, error) {
198 198
 	}
199 199
 	return "", fmt.Errorf("invalid key %s", vals[0])
200 200
 }
201
+
202
+func TestNamedListOpts(t *testing.T) {
203
+	var v []string
204
+	o := NewNamedListOptsRef("foo-name", &v, nil)
205
+
206
+	o.Set("foo")
207
+	if o.String() != "[foo]" {
208
+		t.Errorf("%s != [foo]", o.String())
209
+	}
210
+	if o.Name() != "foo-name" {
211
+		t.Errorf("%s != foo-name", o.Name())
212
+	}
213
+	if len(v) != 1 {
214
+		t.Errorf("expected foo to be in the values, got %v", v)
215
+	}
216
+}
217
+
218
+func TestNamedMapOpts(t *testing.T) {
219
+	tmpMap := make(map[string]string)
220
+	o := NewNamedMapOpts("max-name", tmpMap, nil)
221
+
222
+	o.Set("max-size=1")
223
+	if o.String() != "map[max-size:1]" {
224
+		t.Errorf("%s != [map[max-size:1]", o.String())
225
+	}
226
+	if o.Name() != "max-name" {
227
+		t.Errorf("%s != max-name", o.Name())
228
+	}
229
+	if _, exist := tmpMap["max-size"]; !exist {
230
+		t.Errorf("expected map-size to be in the values, got %v", tmpMap)
231
+	}
232
+}
... ...
@@ -12,12 +12,8 @@ import (
12 12
 var (
13 13
 	// Backends is a global map of discovery backends indexed by their
14 14
 	// associated scheme.
15
-	backends map[string]Backend
16
-)
17
-
18
-func init() {
19 15
 	backends = make(map[string]Backend)
20
-}
16
+)
21 17
 
22 18
 // Register makes a discovery backend available by the provided scheme.
23 19
 // If Register is called twice with the same scheme an error is returned.
... ...
@@ -42,7 +38,7 @@ func parse(rawurl string) (string, string) {
42 42
 
43 43
 // ParseAdvertise parses the --cluster-advertise daemon config which accepts
44 44
 // <ip-address>:<port> or <interface-name>:<port>
45
-func ParseAdvertise(store, advertise string) (string, error) {
45
+func ParseAdvertise(advertise string) (string, error) {
46 46
 	var (
47 47
 		iface *net.Interface
48 48
 		addrs []net.Addr
49 49
new file mode 100644
... ...
@@ -0,0 +1,83 @@
0
+package memory
1
+
2
+import (
3
+	"time"
4
+
5
+	"github.com/docker/docker/pkg/discovery"
6
+)
7
+
8
+// Discovery implements a descovery backend that keeps
9
+// data in memory.
10
+type Discovery struct {
11
+	heartbeat time.Duration
12
+	values    []string
13
+}
14
+
15
+func init() {
16
+	Init()
17
+}
18
+
19
+// Init registers the memory backend on demand.
20
+func Init() {
21
+	discovery.Register("memory", &Discovery{})
22
+}
23
+
24
+// Initialize sets the heartbeat for the memory backend.
25
+func (s *Discovery) Initialize(_ string, heartbeat time.Duration, _ time.Duration, _ map[string]string) error {
26
+	s.heartbeat = heartbeat
27
+	s.values = make([]string, 0)
28
+	return nil
29
+}
30
+
31
+// Watch sends periodic discovery updates to a channel.
32
+func (s *Discovery) Watch(stopCh <-chan struct{}) (<-chan discovery.Entries, <-chan error) {
33
+	ch := make(chan discovery.Entries)
34
+	errCh := make(chan error)
35
+	ticker := time.NewTicker(s.heartbeat)
36
+
37
+	go func() {
38
+		defer close(errCh)
39
+		defer close(ch)
40
+
41
+		// Send the initial entries if available.
42
+		var currentEntries discovery.Entries
43
+		if len(s.values) > 0 {
44
+			var err error
45
+			currentEntries, err = discovery.CreateEntries(s.values)
46
+			if err != nil {
47
+				errCh <- err
48
+			} else {
49
+				ch <- currentEntries
50
+			}
51
+		}
52
+
53
+		// Periodically send updates.
54
+		for {
55
+			select {
56
+			case <-ticker.C:
57
+				newEntries, err := discovery.CreateEntries(s.values)
58
+				if err != nil {
59
+					errCh <- err
60
+					continue
61
+				}
62
+
63
+				// Check if the file has really changed.
64
+				if !newEntries.Equals(currentEntries) {
65
+					ch <- newEntries
66
+				}
67
+				currentEntries = newEntries
68
+			case <-stopCh:
69
+				ticker.Stop()
70
+				return
71
+			}
72
+		}
73
+	}()
74
+
75
+	return ch, errCh
76
+}
77
+
78
+// Register adds a new address to the discovery.
79
+func (s *Discovery) Register(addr string) error {
80
+	s.values = append(s.values, addr)
81
+	return nil
82
+}
0 83
new file mode 100644
... ...
@@ -0,0 +1,48 @@
0
+package memory
1
+
2
+import (
3
+	"testing"
4
+
5
+	"github.com/docker/docker/pkg/discovery"
6
+	"github.com/go-check/check"
7
+)
8
+
9
+// Hook up gocheck into the "go test" runner.
10
+func Test(t *testing.T) { check.TestingT(t) }
11
+
12
+type discoverySuite struct{}
13
+
14
+var _ = check.Suite(&discoverySuite{})
15
+
16
+func (s *discoverySuite) TestWatch(c *check.C) {
17
+	d := &Discovery{}
18
+	d.Initialize("foo", 1000, 0, nil)
19
+	stopCh := make(chan struct{})
20
+	ch, errCh := d.Watch(stopCh)
21
+
22
+	// We have to drain the error channel otherwise Watch will get stuck.
23
+	go func() {
24
+		for range errCh {
25
+		}
26
+	}()
27
+
28
+	expected := discovery.Entries{
29
+		&discovery.Entry{Host: "1.1.1.1", Port: "1111"},
30
+	}
31
+
32
+	c.Assert(d.Register("1.1.1.1:1111"), check.IsNil)
33
+	c.Assert(<-ch, check.DeepEquals, expected)
34
+
35
+	expected = discovery.Entries{
36
+		&discovery.Entry{Host: "1.1.1.1", Port: "1111"},
37
+		&discovery.Entry{Host: "2.2.2.2", Port: "2222"},
38
+	}
39
+
40
+	c.Assert(d.Register("2.2.2.2:2222"), check.IsNil)
41
+	c.Assert(<-ch, check.DeepEquals, expected)
42
+
43
+	// Stop and make sure it closes all channels.
44
+	close(stopCh)
45
+	c.Assert(<-ch, check.IsNil)
46
+	c.Assert(<-errCh, check.IsNil)
47
+}
0 48
new file mode 100644
... ...
@@ -0,0 +1,26 @@
0
+package utils
1
+
2
+import (
3
+	"os"
4
+
5
+	"github.com/Sirupsen/logrus"
6
+)
7
+
8
+// EnableDebug sets the DEBUG env var to true
9
+// and makes the logger to log at debug level.
10
+func EnableDebug() {
11
+	os.Setenv("DEBUG", "1")
12
+	logrus.SetLevel(logrus.DebugLevel)
13
+}
14
+
15
+// DisableDebug sets the DEBUG env var to false
16
+// and makes the logger to log at info level.
17
+func DisableDebug() {
18
+	os.Setenv("DEBUG", "")
19
+	logrus.SetLevel(logrus.InfoLevel)
20
+}
21
+
22
+// IsDebugEnabled checks whether the debug flag is set or not.
23
+func IsDebugEnabled() bool {
24
+	return os.Getenv("DEBUG") != ""
25
+}
0 26
new file mode 100644
... ...
@@ -0,0 +1,2 @@
0
+language: go
1
+install: go get -t
0 2
new file mode 100644
... ...
@@ -0,0 +1,28 @@
0
+Copyright (c) 2013 Dario Castañé. All rights reserved.
1
+Copyright (c) 2012 The Go Authors. All rights reserved.
2
+
3
+Redistribution and use in source and binary forms, with or without
4
+modification, are permitted provided that the following conditions are
5
+met:
6
+
7
+   * Redistributions of source code must retain the above copyright
8
+notice, this list of conditions and the following disclaimer.
9
+   * Redistributions in binary form must reproduce the above
10
+copyright notice, this list of conditions and the following disclaimer
11
+in the documentation and/or other materials provided with the
12
+distribution.
13
+   * Neither the name of Google Inc. nor the names of its
14
+contributors may be used to endorse or promote products derived from
15
+this software without specific prior written permission.
16
+
17
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0 28
new file mode 100644
... ...
@@ -0,0 +1,122 @@
0
+# Mergo
1
+
2
+A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements.
3
+
4
+Also a lovely [comune](http://en.wikipedia.org/wiki/Mergo) (municipality) in the Province of Ancona in the Italian region Marche.
5
+
6
+![Mergo dall'alto](http://www.comune.mergo.an.it/Siti/Mergo/Immagini/Foto/mergo_dall_alto.jpg)
7
+
8
+## Status
9
+
10
+It is ready for production use. It works fine after extensive use in the wild.
11
+
12
+[![Build Status][1]][2]
13
+[![GoDoc](https://godoc.org/github.com/imdario/mergo?status.svg)](https://godoc.org/github.com/imdario/mergo)
14
+
15
+[1]: https://travis-ci.org/imdario/mergo.png
16
+[2]: https://travis-ci.org/imdario/mergo
17
+
18
+### Important note
19
+
20
+Mergo is intended to assign **only** zero value fields on destination with source value. Since April 6th it works like this. Before it didn't work properly, causing some random overwrites. After some issues and PRs I found it didn't merge as I designed it. Thanks to [imdario/mergo#8](https://github.com/imdario/mergo/pull/8) overwriting functions were added and the wrong behavior was clearly detected.
21
+
22
+If you were using Mergo **before** April 6th 2015, please check your project works as intended after updating your local copy with ```go get -u github.com/imdario/mergo```. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause (I hope it won't!) in existing projects after the change (release 0.2.0).
23
+
24
+### Mergo in the wild
25
+
26
+- [imdario/zas](https://github.com/imdario/zas)
27
+- [GoogleCloudPlatform/kubernetes](https://github.com/GoogleCloudPlatform/kubernetes)
28
+- [soniah/dnsmadeeasy](https://github.com/soniah/dnsmadeeasy)
29
+- [EagerIO/Stout](https://github.com/EagerIO/Stout)
30
+- [lynndylanhurley/defsynth-api](https://github.com/lynndylanhurley/defsynth-api)
31
+- [russross/canvasassignments](https://github.com/russross/canvasassignments)
32
+- [rdegges/cryptly-api](https://github.com/rdegges/cryptly-api)
33
+- [casualjim/exeggutor](https://github.com/casualjim/exeggutor)
34
+- [divshot/gitling](https://github.com/divshot/gitling)
35
+- [RWJMurphy/gorl](https://github.com/RWJMurphy/gorl)
36
+- [andrerocker/deploy42](https://github.com/andrerocker/deploy42)
37
+- [elwinar/rambler](https://github.com/elwinar/rambler)
38
+- [tmaiaroto/gopartman](https://github.com/tmaiaroto/gopartman)
39
+- [jfbus/impressionist](https://github.com/jfbus/impressionist)
40
+- [Jmeyering/zealot](https://github.com/Jmeyering/zealot)
41
+- [godep-migrator/rigger-host](https://github.com/godep-migrator/rigger-host)
42
+- [Dronevery/MultiwaySwitch-Go](https://github.com/Dronevery/MultiwaySwitch-Go)
43
+- [thoas/picfit](https://github.com/thoas/picfit)
44
+- [mantasmatelis/whooplist-server](https://github.com/mantasmatelis/whooplist-server)
45
+- [jnuthong/item_search](https://github.com/jnuthong/item_search)
46
+
47
+## Installation
48
+
49
+    go get github.com/imdario/mergo
50
+
51
+    // use in your .go code
52
+    import (
53
+        "github.com/imdario/mergo"
54
+    )
55
+
56
+## Usage
57
+
58
+You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. Also maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection).
59
+
60
+    if err := mergo.Merge(&dst, src); err != nil {
61
+        // ...
62
+    }
63
+
64
+Additionally, you can map a map[string]interface{} to a struct (and otherwise, from struct to map), following the same restrictions as in Merge(). Keys are capitalized to find each corresponding exported field.
65
+
66
+    if err := mergo.Map(&dst, srcMap); err != nil {
67
+        // ...
68
+    }
69
+
70
+Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as map[string]interface{}. They will be just assigned as values.
71
+
72
+More information and examples in [godoc documentation](http://godoc.org/github.com/imdario/mergo).
73
+
74
+### Nice example
75
+
76
+```go
77
+package main
78
+
79
+import (
80
+	"fmt"
81
+	"github.com/imdario/mergo"
82
+)
83
+
84
+type Foo struct {
85
+	A string
86
+	B int64
87
+}
88
+
89
+func main() {
90
+	src := Foo{
91
+		A: "one",
92
+	}
93
+
94
+	dest := Foo{
95
+		A: "two",
96
+		B: 2,
97
+	}
98
+
99
+	mergo.Merge(&dest, src)
100
+
101
+	fmt.Println(dest)
102
+	// Will print
103
+	// {two 2}
104
+}
105
+```
106
+
107
+Note: if test are failing due missing package, please execute:
108
+
109
+    go get gopkg.in/yaml.v1
110
+
111
+## Contact me
112
+
113
+If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): [@im_dario](https://twitter.com/im_dario)
114
+
115
+## About
116
+
117
+Written by [Dario Castañé](http://dario.im).
118
+
119
+## License
120
+
121
+[BSD 3-Clause](http://opensource.org/licenses/BSD-3-Clause) license, as [Go language](http://golang.org/LICENSE).
0 122
new file mode 100644
... ...
@@ -0,0 +1,44 @@
0
+// Copyright 2013 Dario Castañé. All rights reserved.
1
+// Copyright 2009 The Go Authors. All rights reserved.
2
+// Use of this source code is governed by a BSD-style
3
+// license that can be found in the LICENSE file.
4
+
5
+/*
6
+Package mergo merges same-type structs and maps by setting default values in zero-value fields.
7
+
8
+Mergo won't merge unexported (private) fields but will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection).
9
+
10
+Usage
11
+
12
+From my own work-in-progress project:
13
+
14
+	type networkConfig struct {
15
+		Protocol string
16
+		Address string
17
+		ServerType string `json: "server_type"`
18
+		Port uint16
19
+	}
20
+
21
+	type FssnConfig struct {
22
+		Network networkConfig
23
+	}
24
+
25
+	var fssnDefault = FssnConfig {
26
+		networkConfig {
27
+			"tcp",
28
+			"127.0.0.1",
29
+			"http",
30
+			31560,
31
+		},
32
+	}
33
+
34
+	// Inside a function [...]
35
+
36
+	if err := mergo.Merge(&config, fssnDefault); err != nil {
37
+		log.Fatal(err)
38
+	}
39
+
40
+	// More code [...]
41
+
42
+*/
43
+package mergo
0 44
new file mode 100644
... ...
@@ -0,0 +1,154 @@
0
+// Copyright 2014 Dario Castañé. All rights reserved.
1
+// Copyright 2009 The Go Authors. All rights reserved.
2
+// Use of this source code is governed by a BSD-style
3
+// license that can be found in the LICENSE file.
4
+
5
+// Based on src/pkg/reflect/deepequal.go from official
6
+// golang's stdlib.
7
+
8
+package mergo
9
+
10
+import (
11
+	"fmt"
12
+	"reflect"
13
+	"unicode"
14
+	"unicode/utf8"
15
+)
16
+
17
+func changeInitialCase(s string, mapper func(rune) rune) string {
18
+	if s == "" {
19
+		return s
20
+	}
21
+	r, n := utf8.DecodeRuneInString(s)
22
+	return string(mapper(r)) + s[n:]
23
+}
24
+
25
+func isExported(field reflect.StructField) bool {
26
+	r, _ := utf8.DecodeRuneInString(field.Name)
27
+	return r >= 'A' && r <= 'Z'
28
+}
29
+
30
+// Traverses recursively both values, assigning src's fields values to dst.
31
+// The map argument tracks comparisons that have already been seen, which allows
32
+// short circuiting on recursive types.
33
+func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, overwrite bool) (err error) {
34
+	if dst.CanAddr() {
35
+		addr := dst.UnsafeAddr()
36
+		h := 17 * addr
37
+		seen := visited[h]
38
+		typ := dst.Type()
39
+		for p := seen; p != nil; p = p.next {
40
+			if p.ptr == addr && p.typ == typ {
41
+				return nil
42
+			}
43
+		}
44
+		// Remember, remember...
45
+		visited[h] = &visit{addr, typ, seen}
46
+	}
47
+	zeroValue := reflect.Value{}
48
+	switch dst.Kind() {
49
+	case reflect.Map:
50
+		dstMap := dst.Interface().(map[string]interface{})
51
+		for i, n := 0, src.NumField(); i < n; i++ {
52
+			srcType := src.Type()
53
+			field := srcType.Field(i)
54
+			if !isExported(field) {
55
+				continue
56
+			}
57
+			fieldName := field.Name
58
+			fieldName = changeInitialCase(fieldName, unicode.ToLower)
59
+			if v, ok := dstMap[fieldName]; !ok || (isEmptyValue(reflect.ValueOf(v)) || overwrite) {
60
+				dstMap[fieldName] = src.Field(i).Interface()
61
+			}
62
+		}
63
+	case reflect.Struct:
64
+		srcMap := src.Interface().(map[string]interface{})
65
+		for key := range srcMap {
66
+			srcValue := srcMap[key]
67
+			fieldName := changeInitialCase(key, unicode.ToUpper)
68
+			dstElement := dst.FieldByName(fieldName)
69
+			if dstElement == zeroValue {
70
+				// We discard it because the field doesn't exist.
71
+				continue
72
+			}
73
+			srcElement := reflect.ValueOf(srcValue)
74
+			dstKind := dstElement.Kind()
75
+			srcKind := srcElement.Kind()
76
+			if srcKind == reflect.Ptr && dstKind != reflect.Ptr {
77
+				srcElement = srcElement.Elem()
78
+				srcKind = reflect.TypeOf(srcElement.Interface()).Kind()
79
+			} else if dstKind == reflect.Ptr {
80
+				// Can this work? I guess it can't.
81
+				if srcKind != reflect.Ptr && srcElement.CanAddr() {
82
+					srcPtr := srcElement.Addr()
83
+					srcElement = reflect.ValueOf(srcPtr)
84
+					srcKind = reflect.Ptr
85
+				}
86
+			}
87
+			if !srcElement.IsValid() {
88
+				continue
89
+			}
90
+			if srcKind == dstKind {
91
+				if err = deepMerge(dstElement, srcElement, visited, depth+1, overwrite); err != nil {
92
+					return
93
+				}
94
+			} else {
95
+				if srcKind == reflect.Map {
96
+					if err = deepMap(dstElement, srcElement, visited, depth+1, overwrite); err != nil {
97
+						return
98
+					}
99
+				} else {
100
+					return fmt.Errorf("type mismatch on %s field: found %v, expected %v", fieldName, srcKind, dstKind)
101
+				}
102
+			}
103
+		}
104
+	}
105
+	return
106
+}
107
+
108
+// Map sets fields' values in dst from src.
109
+// src can be a map with string keys or a struct. dst must be the opposite:
110
+// if src is a map, dst must be a valid pointer to struct. If src is a struct,
111
+// dst must be map[string]interface{}.
112
+// It won't merge unexported (private) fields and will do recursively
113
+// any exported field.
114
+// If dst is a map, keys will be src fields' names in lower camel case.
115
+// Missing key in src that doesn't match a field in dst will be skipped. This
116
+// doesn't apply if dst is a map.
117
+// This is separated method from Merge because it is cleaner and it keeps sane
118
+// semantics: merging equal types, mapping different (restricted) types.
119
+func Map(dst, src interface{}) error {
120
+	return _map(dst, src, false)
121
+}
122
+
123
+func MapWithOverwrite(dst, src interface{}) error {
124
+	return _map(dst, src, true)
125
+}
126
+
127
+func _map(dst, src interface{}, overwrite bool) error {
128
+	var (
129
+		vDst, vSrc reflect.Value
130
+		err        error
131
+	)
132
+	if vDst, vSrc, err = resolveValues(dst, src); err != nil {
133
+		return err
134
+	}
135
+	// To be friction-less, we redirect equal-type arguments
136
+	// to deepMerge. Only because arguments can be anything.
137
+	if vSrc.Kind() == vDst.Kind() {
138
+		return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, overwrite)
139
+	}
140
+	switch vSrc.Kind() {
141
+	case reflect.Struct:
142
+		if vDst.Kind() != reflect.Map {
143
+			return ErrExpectedMapAsDestination
144
+		}
145
+	case reflect.Map:
146
+		if vDst.Kind() != reflect.Struct {
147
+			return ErrExpectedStructAsDestination
148
+		}
149
+	default:
150
+		return ErrNotSupported
151
+	}
152
+	return deepMap(vDst, vSrc, make(map[uintptr]*visit), 0, overwrite)
153
+}
0 154
new file mode 100644
... ...
@@ -0,0 +1,120 @@
0
+// Copyright 2013 Dario Castañé. All rights reserved.
1
+// Copyright 2009 The Go Authors. All rights reserved.
2
+// Use of this source code is governed by a BSD-style
3
+// license that can be found in the LICENSE file.
4
+
5
+// Based on src/pkg/reflect/deepequal.go from official
6
+// golang's stdlib.
7
+
8
+package mergo
9
+
10
+import (
11
+	"reflect"
12
+)
13
+
14
+// Traverses recursively both values, assigning src's fields values to dst.
15
+// The map argument tracks comparisons that have already been seen, which allows
16
+// short circuiting on recursive types.
17
+func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, overwrite bool) (err error) {
18
+	if !src.IsValid() {
19
+		return
20
+	}
21
+	if dst.CanAddr() {
22
+		addr := dst.UnsafeAddr()
23
+		h := 17 * addr
24
+		seen := visited[h]
25
+		typ := dst.Type()
26
+		for p := seen; p != nil; p = p.next {
27
+			if p.ptr == addr && p.typ == typ {
28
+				return nil
29
+			}
30
+		}
31
+		// Remember, remember...
32
+		visited[h] = &visit{addr, typ, seen}
33
+	}
34
+	switch dst.Kind() {
35
+	case reflect.Struct:
36
+		for i, n := 0, dst.NumField(); i < n; i++ {
37
+			if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1, overwrite); err != nil {
38
+				return
39
+			}
40
+		}
41
+	case reflect.Map:
42
+		for _, key := range src.MapKeys() {
43
+			srcElement := src.MapIndex(key)
44
+			if !srcElement.IsValid() {
45
+				continue
46
+			}
47
+			dstElement := dst.MapIndex(key)
48
+			switch srcElement.Kind() {
49
+			case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Slice:
50
+				if srcElement.IsNil() {
51
+					continue
52
+				}
53
+				fallthrough
54
+			default:
55
+				switch reflect.TypeOf(srcElement.Interface()).Kind() {
56
+				case reflect.Struct:
57
+					fallthrough
58
+				case reflect.Ptr:
59
+					fallthrough
60
+				case reflect.Map:
61
+					if err = deepMerge(dstElement, srcElement, visited, depth+1, overwrite); err != nil {
62
+						return
63
+					}
64
+				}
65
+			}
66
+			if !isEmptyValue(srcElement) && (overwrite || (!dstElement.IsValid() || isEmptyValue(dst))) {
67
+				if dst.IsNil() {
68
+					dst.Set(reflect.MakeMap(dst.Type()))
69
+				}
70
+				dst.SetMapIndex(key, srcElement)
71
+			}
72
+		}
73
+	case reflect.Ptr:
74
+		fallthrough
75
+	case reflect.Interface:
76
+		if src.IsNil() {
77
+			break
78
+		} else if dst.IsNil() {
79
+			if dst.CanSet() && (overwrite || isEmptyValue(dst)) {
80
+				dst.Set(src)
81
+			}
82
+		} else if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, overwrite); err != nil {
83
+			return
84
+		}
85
+	default:
86
+		if dst.CanSet() && !isEmptyValue(src) && (overwrite || isEmptyValue(dst)) {
87
+			dst.Set(src)
88
+		}
89
+	}
90
+	return
91
+}
92
+
93
+// Merge sets fields' values in dst from src if they have a zero
94
+// value of their type.
95
+// dst and src must be valid same-type structs and dst must be
96
+// a pointer to struct.
97
+// It won't merge unexported (private) fields and will do recursively
98
+// any exported field.
99
+func Merge(dst, src interface{}) error {
100
+	return merge(dst, src, false)
101
+}
102
+
103
+func MergeWithOverwrite(dst, src interface{}) error {
104
+	return merge(dst, src, true)
105
+}
106
+
107
+func merge(dst, src interface{}, overwrite bool) error {
108
+	var (
109
+		vDst, vSrc reflect.Value
110
+		err        error
111
+	)
112
+	if vDst, vSrc, err = resolveValues(dst, src); err != nil {
113
+		return err
114
+	}
115
+	if vDst.Type() != vSrc.Type() {
116
+		return ErrDifferentArgumentsTypes
117
+	}
118
+	return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, overwrite)
119
+}
0 120
new file mode 100644
... ...
@@ -0,0 +1,90 @@
0
+// Copyright 2013 Dario Castañé. All rights reserved.
1
+// Copyright 2009 The Go Authors. All rights reserved.
2
+// Use of this source code is governed by a BSD-style
3
+// license that can be found in the LICENSE file.
4
+
5
+// Based on src/pkg/reflect/deepequal.go from official
6
+// golang's stdlib.
7
+
8
+package mergo
9
+
10
+import (
11
+	"errors"
12
+	"reflect"
13
+)
14
+
15
+// Errors reported by Mergo when it finds invalid arguments.
16
+var (
17
+	ErrNilArguments                = errors.New("src and dst must not be nil")
18
+	ErrDifferentArgumentsTypes     = errors.New("src and dst must be of same type")
19
+	ErrNotSupported                = errors.New("only structs and maps are supported")
20
+	ErrExpectedMapAsDestination    = errors.New("dst was expected to be a map")
21
+	ErrExpectedStructAsDestination = errors.New("dst was expected to be a struct")
22
+)
23
+
24
+// During deepMerge, must keep track of checks that are
25
+// in progress.  The comparison algorithm assumes that all
26
+// checks in progress are true when it reencounters them.
27
+// Visited are stored in a map indexed by 17 * a1 + a2;
28
+type visit struct {
29
+	ptr  uintptr
30
+	typ  reflect.Type
31
+	next *visit
32
+}
33
+
34
+// From src/pkg/encoding/json.
35
+func isEmptyValue(v reflect.Value) bool {
36
+	switch v.Kind() {
37
+	case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
38
+		return v.Len() == 0
39
+	case reflect.Bool:
40
+		return !v.Bool()
41
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
42
+		return v.Int() == 0
43
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
44
+		return v.Uint() == 0
45
+	case reflect.Float32, reflect.Float64:
46
+		return v.Float() == 0
47
+	case reflect.Interface, reflect.Ptr:
48
+		return v.IsNil()
49
+	}
50
+	return false
51
+}
52
+
53
+func resolveValues(dst, src interface{}) (vDst, vSrc reflect.Value, err error) {
54
+	if dst == nil || src == nil {
55
+		err = ErrNilArguments
56
+		return
57
+	}
58
+	vDst = reflect.ValueOf(dst).Elem()
59
+	if vDst.Kind() != reflect.Struct && vDst.Kind() != reflect.Map {
60
+		err = ErrNotSupported
61
+		return
62
+	}
63
+	vSrc = reflect.ValueOf(src)
64
+	// We check if vSrc is a pointer to dereference it.
65
+	if vSrc.Kind() == reflect.Ptr {
66
+		vSrc = vSrc.Elem()
67
+	}
68
+	return
69
+}
70
+
71
+// Traverses recursively both values, assigning src's fields values to dst.
72
+// The map argument tracks comparisons that have already been seen, which allows
73
+// short circuiting on recursive types.
74
+func deeper(dst, src reflect.Value, visited map[uintptr]*visit, depth int) (err error) {
75
+	if dst.CanAddr() {
76
+		addr := dst.UnsafeAddr()
77
+		h := 17 * addr
78
+		seen := visited[h]
79
+		typ := dst.Type()
80
+		for p := seen; p != nil; p = p.next {
81
+			if p.ptr == addr && p.typ == typ {
82
+				return nil
83
+			}
84
+		}
85
+		// Remember, remember...
86
+		visited[h] = &visit{addr, typ, seen}
87
+	}
88
+	return // TODO refactor
89
+}