Ver código fonte

Merge pull request #18587 from calavera/daemon_configuration_file

Allow to set daemon and server configurations in a file.
Sebastiaan van Stijn 9 anos atrás
pai
commit
e44364eae9

+ 30 - 0
api/server/router_swapper.go

@@ -0,0 +1,30 @@
+package server
+
+import (
+	"net/http"
+	"sync"
+
+	"github.com/gorilla/mux"
+)
+
+// routerSwapper is an http.Handler that allow you to swap
+// mux routers.
+type routerSwapper struct {
+	mu     sync.Mutex
+	router *mux.Router
+}
+
+// Swap changes the old router with the new one.
+func (rs *routerSwapper) Swap(newRouter *mux.Router) {
+	rs.mu.Lock()
+	rs.router = newRouter
+	rs.mu.Unlock()
+}
+
+// ServeHTTP makes the routerSwapper to implement the http.Handler interface.
+func (rs *routerSwapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	rs.mu.Lock()
+	router := rs.router
+	rs.mu.Unlock()
+	router.ServeHTTP(w, r)
+}

+ 47 - 12
api/server/server.go

@@ -4,7 +4,6 @@ import (
 	"crypto/tls"
 	"crypto/tls"
 	"net"
 	"net"
 	"net/http"
 	"net/http"
-	"os"
 	"strings"
 	"strings"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
@@ -42,10 +41,11 @@ type Config struct {
 
 
 // Server contains instance details for the server
 // Server contains instance details for the server
 type Server struct {
 type Server struct {
-	cfg          *Config
-	servers      []*HTTPServer
-	routers      []router.Router
-	authZPlugins []authorization.Plugin
+	cfg           *Config
+	servers       []*HTTPServer
+	routers       []router.Router
+	authZPlugins  []authorization.Plugin
+	routerSwapper *routerSwapper
 }
 }
 
 
 // Addr contains string representation of address and its protocol (tcp, unix...).
 // Addr contains string representation of address and its protocol (tcp, unix...).
@@ -80,12 +80,14 @@ func (s *Server) Close() {
 	}
 	}
 }
 }
 
 
-// ServeAPI loops through all initialized servers and spawns goroutine
-// with Server method for each. It sets CreateMux() as Handler also.
-func (s *Server) ServeAPI() error {
+// serveAPI loops through all initialized servers and spawns goroutine
+// with Server method for each. It sets createMux() as Handler also.
+func (s *Server) serveAPI() error {
+	s.initRouterSwapper()
+
 	var chErrors = make(chan error, len(s.servers))
 	var chErrors = make(chan error, len(s.servers))
 	for _, srv := range s.servers {
 	for _, srv := range s.servers {
-		srv.srv.Handler = s.CreateMux()
+		srv.srv.Handler = s.routerSwapper
 		go func(srv *HTTPServer) {
 		go func(srv *HTTPServer) {
 			var err error
 			var err error
 			logrus.Infof("API listen on %s", srv.l.Addr())
 			logrus.Infof("API listen on %s", srv.l.Addr())
@@ -186,11 +188,11 @@ func (s *Server) addRouter(r router.Router) {
 	s.routers = append(s.routers, r)
 	s.routers = append(s.routers, r)
 }
 }
 
 
-// CreateMux initializes the main router the server uses.
+// createMux initializes the main router the server uses.
 // we keep enableCors just for legacy usage, need to be removed in the future
 // we keep enableCors just for legacy usage, need to be removed in the future
-func (s *Server) CreateMux() *mux.Router {
+func (s *Server) createMux() *mux.Router {
 	m := mux.NewRouter()
 	m := mux.NewRouter()
-	if os.Getenv("DEBUG") != "" {
+	if utils.IsDebugEnabled() {
 		profilerSetup(m, "/debug/")
 		profilerSetup(m, "/debug/")
 	}
 	}
 
 
@@ -207,3 +209,36 @@ func (s *Server) CreateMux() *mux.Router {
 
 
 	return m
 	return m
 }
 }
+
+// Wait blocks the server goroutine until it exits.
+// It sends an error message if there is any error during
+// the API execution.
+func (s *Server) Wait(waitChan chan error) {
+	if err := s.serveAPI(); err != nil {
+		logrus.Errorf("ServeAPI error: %v", err)
+		waitChan <- err
+		return
+	}
+	waitChan <- nil
+}
+
+func (s *Server) initRouterSwapper() {
+	s.routerSwapper = &routerSwapper{
+		router: s.createMux(),
+	}
+}
+
+// Reload reads configuration changes and modifies the
+// server according to those changes.
+// Currently, only the --debug configuration is taken into account.
+func (s *Server) Reload(config *daemon.Config) {
+	debugEnabled := utils.IsDebugEnabled()
+	switch {
+	case debugEnabled && !config.Debug: // disable debug
+		utils.DisableDebug()
+		s.routerSwapper.Swap(s.createMux())
+	case config.Debug && !debugEnabled: // enable debug
+		utils.EnableDebug()
+		s.routerSwapper.Swap(s.createMux())
+	}
+}

+ 186 - 30
daemon/config.go

@@ -1,9 +1,19 @@
 package daemon
 package daemon
 
 
 import (
 import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"strings"
+	"sync"
+
+	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/opts"
 	"github.com/docker/docker/opts"
+	"github.com/docker/docker/pkg/discovery"
 	flag "github.com/docker/docker/pkg/mflag"
 	flag "github.com/docker/docker/pkg/mflag"
-	"github.com/docker/engine-api/types/container"
+	"github.com/imdario/mergo"
 )
 )
 
 
 const (
 const (
@@ -11,42 +21,69 @@ const (
 	disableNetworkBridge = "none"
 	disableNetworkBridge = "none"
 )
 )
 
 
+// LogConfig represents the default log configuration.
+// It includes json tags to deserialize configuration from a file
+// using the same names that the flags in the command line uses.
+type LogConfig struct {
+	Type   string            `json:"log-driver,omitempty"`
+	Config map[string]string `json:"log-opts,omitempty"`
+}
+
+// CommonTLSOptions defines TLS configuration for the daemon server.
+// It includes json tags to deserialize configuration from a file
+// using the same names that the flags in the command line uses.
+type CommonTLSOptions struct {
+	CAFile   string `json:"tlscacert,omitempty"`
+	CertFile string `json:"tlscert,omitempty"`
+	KeyFile  string `json:"tlskey,omitempty"`
+}
+
 // CommonConfig defines the configuration of a docker daemon which are
 // CommonConfig defines the configuration of a docker daemon which are
 // common across platforms.
 // common across platforms.
+// It includes json tags to deserialize configuration from a file
+// using the same names that the flags in the command line uses.
 type CommonConfig struct {
 type CommonConfig struct {
-	AuthorizationPlugins []string // AuthorizationPlugins holds list of authorization plugins
-	AutoRestart          bool
-	Bridge               bridgeConfig // Bridge holds bridge network specific configuration.
-	Context              map[string][]string
-	DisableBridge        bool
-	DNS                  []string
-	DNSOptions           []string
-	DNSSearch            []string
-	ExecOptions          []string
-	ExecRoot             string
-	GraphDriver          string
-	GraphOptions         []string
-	Labels               []string
-	LogConfig            container.LogConfig
-	Mtu                  int
-	Pidfile              string
-	RemappedRoot         string
-	Root                 string
-	TrustKeyPath         string
+	AuthorizationPlugins []string            `json:"authorization-plugins,omitempty"` // AuthorizationPlugins holds list of authorization plugins
+	AutoRestart          bool                `json:"-"`
+	Bridge               bridgeConfig        `json:"-"` // Bridge holds bridge network specific configuration.
+	Context              map[string][]string `json:"-"`
+	DisableBridge        bool                `json:"-"`
+	DNS                  []string            `json:"dns,omitempty"`
+	DNSOptions           []string            `json:"dns-opts,omitempty"`
+	DNSSearch            []string            `json:"dns-search,omitempty"`
+	ExecOptions          []string            `json:"exec-opts,omitempty"`
+	ExecRoot             string              `json:"exec-root,omitempty"`
+	GraphDriver          string              `json:"storage-driver,omitempty"`
+	GraphOptions         []string            `json:"storage-opts,omitempty"`
+	Labels               []string            `json:"labels,omitempty"`
+	LogConfig            LogConfig           `json:"log-config,omitempty"`
+	Mtu                  int                 `json:"mtu,omitempty"`
+	Pidfile              string              `json:"pidfile,omitempty"`
+	Root                 string              `json:"graph,omitempty"`
+	TrustKeyPath         string              `json:"-"`
 
 
 	// ClusterStore is the storage backend used for the cluster information. It is used by both
 	// ClusterStore is the storage backend used for the cluster information. It is used by both
 	// multihost networking (to store networks and endpoints information) and by the node discovery
 	// multihost networking (to store networks and endpoints information) and by the node discovery
 	// mechanism.
 	// mechanism.
-	ClusterStore string
+	ClusterStore string `json:"cluster-store,omitempty"`
 
 
 	// ClusterOpts is used to pass options to the discovery package for tuning libkv settings, such
 	// ClusterOpts is used to pass options to the discovery package for tuning libkv settings, such
 	// as TLS configuration settings.
 	// as TLS configuration settings.
-	ClusterOpts map[string]string
+	ClusterOpts map[string]string `json:"cluster-store-opts,omitempty"`
 
 
 	// ClusterAdvertise is the network endpoint that the Engine advertises for the purpose of node
 	// ClusterAdvertise is the network endpoint that the Engine advertises for the purpose of node
 	// discovery. This should be a 'host:port' combination on which that daemon instance is
 	// discovery. This should be a 'host:port' combination on which that daemon instance is
 	// reachable by other hosts.
 	// reachable by other hosts.
-	ClusterAdvertise string
+	ClusterAdvertise string `json:"cluster-advertise,omitempty"`
+
+	Debug      bool             `json:"debug,omitempty"`
+	Hosts      []string         `json:"hosts,omitempty"`
+	LogLevel   string           `json:"log-level,omitempty"`
+	TLS        bool             `json:"tls,omitempty"`
+	TLSVerify  bool             `json:"tls-verify,omitempty"`
+	TLSOptions CommonTLSOptions `json:"tls-opts,omitempty"`
+
+	reloadLock sync.Mutex
 }
 }
 
 
 // InstallCommonFlags adds command-line options to the top-level flag parser for
 // InstallCommonFlags adds command-line options to the top-level flag parser for
@@ -54,9 +91,9 @@ type CommonConfig struct {
 // Subsequent calls to `flag.Parse` will populate config with values parsed
 // Subsequent calls to `flag.Parse` will populate config with values parsed
 // from the command-line.
 // from the command-line.
 func (config *Config) InstallCommonFlags(cmd *flag.FlagSet, usageFn func(string) string) {
 func (config *Config) InstallCommonFlags(cmd *flag.FlagSet, usageFn func(string) string) {
-	cmd.Var(opts.NewListOptsRef(&config.GraphOptions, nil), []string{"-storage-opt"}, usageFn("Set storage driver options"))
-	cmd.Var(opts.NewListOptsRef(&config.AuthorizationPlugins, nil), []string{"-authorization-plugin"}, usageFn("List authorization plugins in order from first evaluator to last"))
-	cmd.Var(opts.NewListOptsRef(&config.ExecOptions, nil), []string{"-exec-opt"}, usageFn("Set exec driver options"))
+	cmd.Var(opts.NewNamedListOptsRef("storage-opts", &config.GraphOptions, nil), []string{"-storage-opt"}, usageFn("Set storage driver options"))
+	cmd.Var(opts.NewNamedListOptsRef("authorization-plugins", &config.AuthorizationPlugins, nil), []string{"-authorization-plugin"}, usageFn("List authorization plugins in order from first evaluator to last"))
+	cmd.Var(opts.NewNamedListOptsRef("exec-opts", &config.ExecOptions, nil), []string{"-exec-opt"}, usageFn("Set exec driver options"))
 	cmd.StringVar(&config.Pidfile, []string{"p", "-pidfile"}, defaultPidFile, usageFn("Path to use for daemon PID file"))
 	cmd.StringVar(&config.Pidfile, []string{"p", "-pidfile"}, defaultPidFile, usageFn("Path to use for daemon PID file"))
 	cmd.StringVar(&config.Root, []string{"g", "-graph"}, defaultGraph, usageFn("Root of the Docker runtime"))
 	cmd.StringVar(&config.Root, []string{"g", "-graph"}, defaultGraph, usageFn("Root of the Docker runtime"))
 	cmd.StringVar(&config.ExecRoot, []string{"-exec-root"}, "/var/run/docker", usageFn("Root of the Docker execdriver"))
 	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)
 	cmd.IntVar(&config.Mtu, []string{"#mtu", "-mtu"}, 0, usageFn("Set the containers network MTU"))
 	cmd.IntVar(&config.Mtu, []string{"#mtu", "-mtu"}, 0, usageFn("Set the containers network MTU"))
 	// FIXME: why the inconsistency between "hosts" and "sockets"?
 	// FIXME: why the inconsistency between "hosts" and "sockets"?
 	cmd.Var(opts.NewListOptsRef(&config.DNS, opts.ValidateIPAddress), []string{"#dns", "-dns"}, usageFn("DNS server to use"))
 	cmd.Var(opts.NewListOptsRef(&config.DNS, opts.ValidateIPAddress), []string{"#dns", "-dns"}, usageFn("DNS server to use"))
-	cmd.Var(opts.NewListOptsRef(&config.DNSOptions, nil), []string{"-dns-opt"}, usageFn("DNS options to use"))
+	cmd.Var(opts.NewNamedListOptsRef("dns-opts", &config.DNSOptions, nil), []string{"-dns-opt"}, usageFn("DNS options to use"))
 	cmd.Var(opts.NewListOptsRef(&config.DNSSearch, opts.ValidateDNSSearch), []string{"-dns-search"}, usageFn("DNS search domains to use"))
 	cmd.Var(opts.NewListOptsRef(&config.DNSSearch, opts.ValidateDNSSearch), []string{"-dns-search"}, usageFn("DNS search domains to use"))
-	cmd.Var(opts.NewListOptsRef(&config.Labels, opts.ValidateLabel), []string{"-label"}, usageFn("Set key=value labels to the daemon"))
+	cmd.Var(opts.NewNamedListOptsRef("labels", &config.Labels, opts.ValidateLabel), []string{"-label"}, usageFn("Set key=value labels to the daemon"))
 	cmd.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", usageFn("Default driver for container logs"))
 	cmd.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", usageFn("Default driver for container logs"))
-	cmd.Var(opts.NewMapOpts(config.LogConfig.Config, nil), []string{"-log-opt"}, usageFn("Set log driver options"))
+	cmd.Var(opts.NewNamedMapOpts("log-opts", config.LogConfig.Config, nil), []string{"-log-opt"}, usageFn("Set log driver options"))
 	cmd.StringVar(&config.ClusterAdvertise, []string{"-cluster-advertise"}, "", usageFn("Address or interface name to advertise"))
 	cmd.StringVar(&config.ClusterAdvertise, []string{"-cluster-advertise"}, "", usageFn("Address or interface name to advertise"))
 	cmd.StringVar(&config.ClusterStore, []string{"-cluster-store"}, "", usageFn("Set the cluster store"))
 	cmd.StringVar(&config.ClusterStore, []string{"-cluster-store"}, "", usageFn("Set the cluster store"))
-	cmd.Var(opts.NewMapOpts(config.ClusterOpts, nil), []string{"-cluster-store-opt"}, usageFn("Set cluster store options"))
+	cmd.Var(opts.NewNamedMapOpts("cluster-store-opts", config.ClusterOpts, nil), []string{"-cluster-store-opt"}, usageFn("Set cluster store options"))
+}
+
+func parseClusterAdvertiseSettings(clusterStore, clusterAdvertise string) (string, error) {
+	if clusterAdvertise == "" {
+		return "", errDiscoveryDisabled
+	}
+	if clusterStore == "" {
+		return "", fmt.Errorf("invalid cluster configuration. --cluster-advertise must be accompanied by --cluster-store configuration")
+	}
+
+	advertise, err := discovery.ParseAdvertise(clusterAdvertise)
+	if err != nil {
+		return "", fmt.Errorf("discovery advertise parsing failed (%v)", err)
+	}
+	return advertise, nil
+}
+
+// ReloadConfiguration reads the configuration in the host and reloads the daemon and server.
+func ReloadConfiguration(configFile string, flags *flag.FlagSet, reload func(*Config)) {
+	logrus.Infof("Got signal to reload configuration, reloading from: %s", configFile)
+	newConfig, err := getConflictFreeConfiguration(configFile, flags)
+	if err != nil {
+		logrus.Error(err)
+	} else {
+		reload(newConfig)
+	}
+}
+
+// MergeDaemonConfigurations reads a configuration file,
+// loads the file configuration in an isolated structure,
+// and merges the configuration provided from flags on top
+// if there are no conflicts.
+func MergeDaemonConfigurations(flagsConfig *Config, flags *flag.FlagSet, configFile string) (*Config, error) {
+	fileConfig, err := getConflictFreeConfiguration(configFile, flags)
+	if err != nil {
+		return nil, err
+	}
+
+	// merge flags configuration on top of the file configuration
+	if err := mergo.Merge(fileConfig, flagsConfig); err != nil {
+		return nil, err
+	}
+
+	return fileConfig, nil
+}
+
+// getConflictFreeConfiguration loads the configuration from a JSON file.
+// It compares that configuration with the one provided by the flags,
+// and returns an error if there are conflicts.
+func getConflictFreeConfiguration(configFile string, flags *flag.FlagSet) (*Config, error) {
+	b, err := ioutil.ReadFile(configFile)
+	if err != nil {
+		return nil, err
+	}
+
+	var reader io.Reader
+	if flags != nil {
+		var jsonConfig map[string]interface{}
+		reader = bytes.NewReader(b)
+		if err := json.NewDecoder(reader).Decode(&jsonConfig); err != nil {
+			return nil, err
+		}
+
+		if err := findConfigurationConflicts(jsonConfig, flags); err != nil {
+			return nil, err
+		}
+	}
+
+	var config Config
+	reader = bytes.NewReader(b)
+	err = json.NewDecoder(reader).Decode(&config)
+	return &config, err
+}
+
+// findConfigurationConflicts iterates over the provided flags searching for
+// duplicated configurations. It returns an error with all the conflicts if
+// it finds any.
+func findConfigurationConflicts(config map[string]interface{}, flags *flag.FlagSet) error {
+	var conflicts []string
+	flatten := make(map[string]interface{})
+	for k, v := range config {
+		if m, ok := v.(map[string]interface{}); ok {
+			for km, vm := range m {
+				flatten[km] = vm
+			}
+		} else {
+			flatten[k] = v
+		}
+	}
+
+	printConflict := func(name string, flagValue, fileValue interface{}) string {
+		return fmt.Sprintf("%s: (from flag: %v, from file: %v)", name, flagValue, fileValue)
+	}
+
+	collectConflicts := func(f *flag.Flag) {
+		// search option name in the json configuration payload if the value is a named option
+		if namedOption, ok := f.Value.(opts.NamedOption); ok {
+			if optsValue, ok := flatten[namedOption.Name()]; ok {
+				conflicts = append(conflicts, printConflict(namedOption.Name(), f.Value.String(), optsValue))
+			}
+		} else {
+			// search flag name in the json configuration payload without trailing dashes
+			for _, name := range f.Names {
+				name = strings.TrimLeft(name, "-")
+
+				if value, ok := flatten[name]; ok {
+					conflicts = append(conflicts, printConflict(name, f.Value.String(), value))
+					break
+				}
+			}
+		}
+	}
+
+	flags.Visit(collectConflicts)
+
+	if len(conflicts) > 0 {
+		return fmt.Errorf("the following directives are specified both as a flag and in the configuration file: %s", strings.Join(conflicts, ", "))
+	}
+	return nil
 }
 }

+ 177 - 0
daemon/config_test.go

@@ -0,0 +1,177 @@
+package daemon
+
+import (
+	"io/ioutil"
+	"os"
+	"strings"
+	"testing"
+
+	"github.com/docker/docker/opts"
+	"github.com/docker/docker/pkg/mflag"
+)
+
+func TestDaemonConfigurationMerge(t *testing.T) {
+	f, err := ioutil.TempFile("", "docker-config-")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	configFile := f.Name()
+	f.Write([]byte(`{"debug": true}`))
+	f.Close()
+
+	c := &Config{
+		CommonConfig: CommonConfig{
+			AutoRestart: true,
+			LogConfig: LogConfig{
+				Type:   "syslog",
+				Config: map[string]string{"tag": "test"},
+			},
+		},
+	}
+
+	cc, err := MergeDaemonConfigurations(c, nil, configFile)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !cc.Debug {
+		t.Fatalf("expected %v, got %v\n", true, cc.Debug)
+	}
+	if !cc.AutoRestart {
+		t.Fatalf("expected %v, got %v\n", true, cc.AutoRestart)
+	}
+	if cc.LogConfig.Type != "syslog" {
+		t.Fatalf("expected syslog config, got %q\n", cc.LogConfig)
+	}
+}
+
+func TestDaemonConfigurationNotFound(t *testing.T) {
+	_, err := MergeDaemonConfigurations(&Config{}, nil, "/tmp/foo-bar-baz-docker")
+	if err == nil || !os.IsNotExist(err) {
+		t.Fatalf("expected does not exist error, got %v", err)
+	}
+}
+
+func TestDaemonBrokenConfiguration(t *testing.T) {
+	f, err := ioutil.TempFile("", "docker-config-")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	configFile := f.Name()
+	f.Write([]byte(`{"Debug": tru`))
+	f.Close()
+
+	_, err = MergeDaemonConfigurations(&Config{}, nil, configFile)
+	if err == nil {
+		t.Fatalf("expected error, got %v", err)
+	}
+}
+
+func TestParseClusterAdvertiseSettings(t *testing.T) {
+	_, err := parseClusterAdvertiseSettings("something", "")
+	if err != errDiscoveryDisabled {
+		t.Fatalf("expected discovery disabled error, got %v\n", err)
+	}
+
+	_, err = parseClusterAdvertiseSettings("", "something")
+	if err == nil {
+		t.Fatalf("expected discovery store error, got %v\n", err)
+	}
+
+	_, err = parseClusterAdvertiseSettings("etcd", "127.0.0.1:8080")
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestFindConfigurationConflicts(t *testing.T) {
+	config := map[string]interface{}{"authorization-plugins": "foobar"}
+	flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
+
+	err := findConfigurationConflicts(config, flags)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	flags.String([]string{"authorization-plugins"}, "", "")
+	if err := flags.Set("authorization-plugins", "asdf"); err != nil {
+		t.Fatal(err)
+	}
+
+	err = findConfigurationConflicts(config, flags)
+	if err == nil {
+		t.Fatal("expected error, got nil")
+	}
+	if !strings.Contains(err.Error(), "authorization-plugins") {
+		t.Fatalf("expected authorization-plugins conflict, got %v", err)
+	}
+}
+
+func TestFindConfigurationConflictsWithNamedOptions(t *testing.T) {
+	config := map[string]interface{}{"hosts": []string{"qwer"}}
+	flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
+
+	var hosts []string
+	flags.Var(opts.NewNamedListOptsRef("hosts", &hosts, opts.ValidateHost), []string{"H", "-host"}, "Daemon socket(s) to connect to")
+	if err := flags.Set("-host", "tcp://127.0.0.1:4444"); err != nil {
+		t.Fatal(err)
+	}
+	if err := flags.Set("H", "unix:///var/run/docker.sock"); err != nil {
+		t.Fatal(err)
+	}
+
+	err := findConfigurationConflicts(config, flags)
+	if err == nil {
+		t.Fatal("expected error, got nil")
+	}
+	if !strings.Contains(err.Error(), "hosts") {
+		t.Fatalf("expected hosts conflict, got %v", err)
+	}
+}
+
+func TestDaemonConfigurationMergeConflicts(t *testing.T) {
+	f, err := ioutil.TempFile("", "docker-config-")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	configFile := f.Name()
+	f.Write([]byte(`{"debug": true}`))
+	f.Close()
+
+	flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
+	flags.Bool([]string{"debug"}, false, "")
+	flags.Set("debug", "false")
+
+	_, err = MergeDaemonConfigurations(&Config{}, flags, configFile)
+	if err == nil {
+		t.Fatal("expected error, got nil")
+	}
+	if !strings.Contains(err.Error(), "debug") {
+		t.Fatalf("expected debug conflict, got %v", err)
+	}
+}
+
+func TestDaemonConfigurationMergeConflictsWithInnerStructs(t *testing.T) {
+	f, err := ioutil.TempFile("", "docker-config-")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	configFile := f.Name()
+	f.Write([]byte(`{"tlscacert": "/etc/certificates/ca.pem"}`))
+	f.Close()
+
+	flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
+	flags.String([]string{"tlscacert"}, "", "")
+	flags.Set("tlscacert", "~/.docker/ca.pem")
+
+	_, err = MergeDaemonConfigurations(&Config{}, flags, configFile)
+	if err == nil {
+		t.Fatal("expected error, got nil")
+	}
+	if !strings.Contains(err.Error(), "tlscacert") {
+		t.Fatalf("expected tlscacert conflict, got %v", err)
+	}
+}

+ 9 - 7
daemon/config_unix.go

@@ -18,18 +18,20 @@ var (
 )
 )
 
 
 // Config defines the configuration of a docker daemon.
 // Config defines the configuration of a docker daemon.
+// It includes json tags to deserialize configuration from a file
+// using the same names that the flags in the command line uses.
 type Config struct {
 type Config struct {
 	CommonConfig
 	CommonConfig
 
 
 	// Fields below here are platform specific.
 	// Fields below here are platform specific.
 
 
-	CorsHeaders          string
-	EnableCors           bool
-	EnableSelinuxSupport bool
-	RemappedRoot         string
-	SocketGroup          string
-	CgroupParent         string
-	Ulimits              map[string]*units.Ulimit
+	CorsHeaders          string                   `json:"api-cors-headers,omitempty"`
+	EnableCors           bool                     `json:"api-enable-cors,omitempty"`
+	EnableSelinuxSupport bool                     `json:"selinux-enabled,omitempty"`
+	RemappedRoot         string                   `json:"userns-remap,omitempty"`
+	SocketGroup          string                   `json:"group,omitempty"`
+	CgroupParent         string                   `json:"cgroup-parent,omitempty"`
+	Ulimits              map[string]*units.Ulimit `json:"default-ulimits,omitempty"`
 }
 }
 
 
 // bridgeConfig stores all the bridge driver specific
 // bridgeConfig stores all the bridge driver specific

+ 78 - 17
daemon/daemon.go

@@ -46,7 +46,6 @@ import (
 	"github.com/docker/docker/layer"
 	"github.com/docker/docker/layer"
 	"github.com/docker/docker/migrate/v1"
 	"github.com/docker/docker/migrate/v1"
 	"github.com/docker/docker/pkg/archive"
 	"github.com/docker/docker/pkg/archive"
-	"github.com/docker/docker/pkg/discovery"
 	"github.com/docker/docker/pkg/fileutils"
 	"github.com/docker/docker/pkg/fileutils"
 	"github.com/docker/docker/pkg/graphdb"
 	"github.com/docker/docker/pkg/graphdb"
 	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/idtools"
@@ -155,7 +154,7 @@ type Daemon struct {
 	EventsService             *events.Events
 	EventsService             *events.Events
 	netController             libnetwork.NetworkController
 	netController             libnetwork.NetworkController
 	volumes                   *store.VolumeStore
 	volumes                   *store.VolumeStore
-	discoveryWatcher          discovery.Watcher
+	discoveryWatcher          discoveryReloader
 	root                      string
 	root                      string
 	seccompEnabled            bool
 	seccompEnabled            bool
 	shutdown                  bool
 	shutdown                  bool
@@ -292,7 +291,7 @@ func (daemon *Daemon) Register(container *container.Container) error {
 
 
 func (daemon *Daemon) restore() error {
 func (daemon *Daemon) restore() error {
 	var (
 	var (
-		debug         = os.Getenv("DEBUG") != ""
+		debug         = utils.IsDebugEnabled()
 		currentDriver = daemon.GraphDriverName()
 		currentDriver = daemon.GraphDriverName()
 		containers    = make(map[string]*container.Container)
 		containers    = make(map[string]*container.Container)
 	)
 	)
@@ -772,19 +771,8 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
 
 
 	// Discovery is only enabled when the daemon is launched with an address to advertise.  When
 	// Discovery is only enabled when the daemon is launched with an address to advertise.  When
 	// initialized, the daemon is registered and we can store the discovery backend as its read-only
 	// initialized, the daemon is registered and we can store the discovery backend as its read-only
-	// DiscoveryWatcher version.
-	if config.ClusterStore != "" && config.ClusterAdvertise != "" {
-		advertise, err := discovery.ParseAdvertise(config.ClusterStore, config.ClusterAdvertise)
-		if err != nil {
-			return nil, fmt.Errorf("discovery advertise parsing failed (%v)", err)
-		}
-		config.ClusterAdvertise = advertise
-		d.discoveryWatcher, err = initDiscovery(config.ClusterStore, config.ClusterAdvertise, config.ClusterOpts)
-		if err != nil {
-			return nil, fmt.Errorf("discovery initialization failed (%v)", err)
-		}
-	} else if config.ClusterAdvertise != "" {
-		return nil, fmt.Errorf("invalid cluster configuration. --cluster-advertise must be accompanied by --cluster-store configuration")
+	if err := d.initDiscovery(config); err != nil {
+		return nil, err
 	}
 	}
 
 
 	d.netController, err = d.initNetworkController(config)
 	d.netController, err = d.initNetworkController(config)
@@ -815,7 +803,10 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
 	d.configStore = config
 	d.configStore = config
 	d.execDriver = ed
 	d.execDriver = ed
 	d.statsCollector = d.newStatsCollector(1 * time.Second)
 	d.statsCollector = d.newStatsCollector(1 * time.Second)
-	d.defaultLogConfig = config.LogConfig
+	d.defaultLogConfig = containertypes.LogConfig{
+		Type:   config.LogConfig.Type,
+		Config: config.LogConfig.Config,
+	}
 	d.RegistryService = registryService
 	d.RegistryService = registryService
 	d.EventsService = eventsService
 	d.EventsService = eventsService
 	d.volumes = volStore
 	d.volumes = volStore
@@ -1521,6 +1512,76 @@ func (daemon *Daemon) newBaseContainer(id string) *container.Container {
 	return container.NewBaseContainer(id, daemon.containerRoot(id))
 	return container.NewBaseContainer(id, daemon.containerRoot(id))
 }
 }
 
 
+// initDiscovery initializes the discovery watcher for this daemon.
+func (daemon *Daemon) initDiscovery(config *Config) error {
+	advertise, err := parseClusterAdvertiseSettings(config.ClusterStore, config.ClusterAdvertise)
+	if err != nil {
+		if err == errDiscoveryDisabled {
+			return nil
+		}
+		return err
+	}
+
+	config.ClusterAdvertise = advertise
+	discoveryWatcher, err := initDiscovery(config.ClusterStore, config.ClusterAdvertise, config.ClusterOpts)
+	if err != nil {
+		return fmt.Errorf("discovery initialization failed (%v)", err)
+	}
+
+	daemon.discoveryWatcher = discoveryWatcher
+	return nil
+}
+
+// Reload reads configuration changes and modifies the
+// daemon according to those changes.
+// This are the settings that Reload changes:
+// - Daemon labels.
+// - Cluster discovery (reconfigure and restart).
+func (daemon *Daemon) Reload(config *Config) error {
+	daemon.configStore.reloadLock.Lock()
+	defer daemon.configStore.reloadLock.Unlock()
+
+	daemon.configStore.Labels = config.Labels
+	return daemon.reloadClusterDiscovery(config)
+}
+
+func (daemon *Daemon) reloadClusterDiscovery(config *Config) error {
+	newAdvertise, err := parseClusterAdvertiseSettings(config.ClusterStore, config.ClusterAdvertise)
+	if err != nil && err != errDiscoveryDisabled {
+		return err
+	}
+
+	// check discovery modifications
+	if !modifiedDiscoverySettings(daemon.configStore, newAdvertise, config.ClusterStore, config.ClusterOpts) {
+		return nil
+	}
+
+	// enable discovery for the first time if it was not previously enabled
+	if daemon.discoveryWatcher == nil {
+		discoveryWatcher, err := initDiscovery(config.ClusterStore, newAdvertise, config.ClusterOpts)
+		if err != nil {
+			return fmt.Errorf("discovery initialization failed (%v)", err)
+		}
+		daemon.discoveryWatcher = discoveryWatcher
+	} else {
+		if err == errDiscoveryDisabled {
+			// disable discovery if it was previously enabled and it's disabled now
+			daemon.discoveryWatcher.Stop()
+		} else {
+			// reload discovery
+			if err = daemon.discoveryWatcher.Reload(config.ClusterStore, newAdvertise, config.ClusterOpts); err != nil {
+				return err
+			}
+		}
+	}
+
+	daemon.configStore.ClusterStore = config.ClusterStore
+	daemon.configStore.ClusterOpts = config.ClusterOpts
+	daemon.configStore.ClusterAdvertise = newAdvertise
+
+	return nil
+}
+
 func convertLnNetworkStats(name string, stats *lntypes.InterfaceStatistics) *libcontainer.NetworkInterface {
 func convertLnNetworkStats(name string, stats *lntypes.InterfaceStatistics) *libcontainer.NetworkInterface {
 	n := &libcontainer.NetworkInterface{Name: name}
 	n := &libcontainer.NetworkInterface{Name: name}
 	n.RxBytes = stats.RxBytes
 	n.RxBytes = stats.RxBytes

+ 119 - 0
daemon/daemon_test.go

@@ -4,9 +4,13 @@ import (
 	"io/ioutil"
 	"io/ioutil"
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
+	"reflect"
 	"testing"
 	"testing"
+	"time"
 
 
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/container"
+	"github.com/docker/docker/pkg/discovery"
+	_ "github.com/docker/docker/pkg/discovery/memory"
 	"github.com/docker/docker/pkg/registrar"
 	"github.com/docker/docker/pkg/registrar"
 	"github.com/docker/docker/pkg/truncindex"
 	"github.com/docker/docker/pkg/truncindex"
 	"github.com/docker/docker/volume"
 	"github.com/docker/docker/volume"
@@ -371,3 +375,118 @@ func TestMerge(t *testing.T) {
 		}
 		}
 	}
 	}
 }
 }
+
+func TestDaemonReloadLabels(t *testing.T) {
+	daemon := &Daemon{}
+	daemon.configStore = &Config{
+		CommonConfig: CommonConfig{
+			Labels: []string{"foo:bar"},
+		},
+	}
+
+	newConfig := &Config{
+		CommonConfig: CommonConfig{
+			Labels: []string{"foo:baz"},
+		},
+	}
+
+	daemon.Reload(newConfig)
+	label := daemon.configStore.Labels[0]
+	if label != "foo:baz" {
+		t.Fatalf("Expected daemon label `foo:baz`, got %s", label)
+	}
+}
+
+func TestDaemonDiscoveryReload(t *testing.T) {
+	daemon := &Daemon{}
+	daemon.configStore = &Config{
+		CommonConfig: CommonConfig{
+			ClusterStore:     "memory://127.0.0.1",
+			ClusterAdvertise: "127.0.0.1:3333",
+		},
+	}
+
+	if err := daemon.initDiscovery(daemon.configStore); err != nil {
+		t.Fatal(err)
+	}
+
+	expected := discovery.Entries{
+		&discovery.Entry{Host: "127.0.0.1", Port: "3333"},
+	}
+
+	stopCh := make(chan struct{})
+	defer close(stopCh)
+	ch, errCh := daemon.discoveryWatcher.Watch(stopCh)
+
+	select {
+	case <-time.After(1 * time.Second):
+		t.Fatal("failed to get discovery advertisements in time")
+	case e := <-ch:
+		if !reflect.DeepEqual(e, expected) {
+			t.Fatalf("expected %v, got %v\n", expected, e)
+		}
+	case e := <-errCh:
+		t.Fatal(e)
+	}
+
+	newConfig := &Config{
+		CommonConfig: CommonConfig{
+			ClusterStore:     "memory://127.0.0.1:2222",
+			ClusterAdvertise: "127.0.0.1:5555",
+		},
+	}
+
+	expected = discovery.Entries{
+		&discovery.Entry{Host: "127.0.0.1", Port: "5555"},
+	}
+
+	if err := daemon.Reload(newConfig); err != nil {
+		t.Fatal(err)
+	}
+	ch, errCh = daemon.discoveryWatcher.Watch(stopCh)
+
+	select {
+	case <-time.After(1 * time.Second):
+		t.Fatal("failed to get discovery advertisements in time")
+	case e := <-ch:
+		if !reflect.DeepEqual(e, expected) {
+			t.Fatalf("expected %v, got %v\n", expected, e)
+		}
+	case e := <-errCh:
+		t.Fatal(e)
+	}
+}
+
+func TestDaemonDiscoveryReloadFromEmptyDiscovery(t *testing.T) {
+	daemon := &Daemon{}
+	daemon.configStore = &Config{}
+
+	newConfig := &Config{
+		CommonConfig: CommonConfig{
+			ClusterStore:     "memory://127.0.0.1:2222",
+			ClusterAdvertise: "127.0.0.1:5555",
+		},
+	}
+
+	expected := discovery.Entries{
+		&discovery.Entry{Host: "127.0.0.1", Port: "5555"},
+	}
+
+	if err := daemon.Reload(newConfig); err != nil {
+		t.Fatal(err)
+	}
+	stopCh := make(chan struct{})
+	defer close(stopCh)
+	ch, errCh := daemon.discoveryWatcher.Watch(stopCh)
+
+	select {
+	case <-time.After(1 * time.Second):
+		t.Fatal("failed to get discovery advertisements in time")
+	case e := <-ch:
+		if !reflect.DeepEqual(e, expected) {
+			t.Fatalf("expected %v, got %v\n", expected, e)
+		}
+	case e := <-errCh:
+		t.Fatal(e)
+	}
+}

+ 94 - 16
daemon/discovery.go

@@ -1,7 +1,9 @@
 package daemon
 package daemon
 
 
 import (
 import (
+	"errors"
 	"fmt"
 	"fmt"
+	"reflect"
 	"strconv"
 	"strconv"
 	"time"
 	"time"
 
 
@@ -19,6 +21,24 @@ const (
 	defaultDiscoveryTTLFactor = 3
 	defaultDiscoveryTTLFactor = 3
 )
 )
 
 
+var errDiscoveryDisabled = errors.New("discovery is disabled")
+
+type discoveryReloader interface {
+	discovery.Watcher
+	Stop()
+	Reload(backend, address string, clusterOpts map[string]string) error
+}
+
+type daemonDiscoveryReloader struct {
+	backend discovery.Backend
+	ticker  *time.Ticker
+	term    chan bool
+}
+
+func (d *daemonDiscoveryReloader) Watch(stopCh <-chan struct{}) (<-chan discovery.Entries, <-chan error) {
+	return d.backend.Watch(stopCh)
+}
+
 func discoveryOpts(clusterOpts map[string]string) (time.Duration, time.Duration, error) {
 func discoveryOpts(clusterOpts map[string]string) (time.Duration, time.Duration, error) {
 	var (
 	var (
 		heartbeat = defaultDiscoveryHeartbeat
 		heartbeat = defaultDiscoveryHeartbeat
@@ -57,36 +77,94 @@ func discoveryOpts(clusterOpts map[string]string) (time.Duration, time.Duration,
 
 
 // initDiscovery initialized the nodes discovery subsystem by connecting to the specified backend
 // initDiscovery initialized the nodes discovery subsystem by connecting to the specified backend
 // and start a registration loop to advertise the current node under the specified address.
 // and start a registration loop to advertise the current node under the specified address.
-func initDiscovery(backend, address string, clusterOpts map[string]string) (discovery.Backend, error) {
-
-	heartbeat, ttl, err := discoveryOpts(clusterOpts)
+func initDiscovery(backendAddress, advertiseAddress string, clusterOpts map[string]string) (discoveryReloader, error) {
+	heartbeat, backend, err := parseDiscoveryOptions(backendAddress, clusterOpts)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	discoveryBackend, err := discovery.New(backend, heartbeat, ttl, clusterOpts)
-	if err != nil {
-		return nil, err
+	reloader := &daemonDiscoveryReloader{
+		backend: backend,
+		ticker:  time.NewTicker(heartbeat),
+		term:    make(chan bool),
 	}
 	}
-
 	// We call Register() on the discovery backend in a loop for the whole lifetime of the daemon,
 	// We call Register() on the discovery backend in a loop for the whole lifetime of the daemon,
 	// but we never actually Watch() for nodes appearing and disappearing for the moment.
 	// but we never actually Watch() for nodes appearing and disappearing for the moment.
-	go registrationLoop(discoveryBackend, address, heartbeat)
-	return discoveryBackend, nil
+	reloader.advertise(advertiseAddress)
+	return reloader, nil
 }
 }
 
 
-func registerAddr(backend discovery.Backend, addr string) {
-	if err := backend.Register(addr); err != nil {
+func (d *daemonDiscoveryReloader) advertise(address string) {
+	d.registerAddr(address)
+	go d.advertiseHeartbeat(address)
+}
+
+func (d *daemonDiscoveryReloader) registerAddr(addr string) {
+	if err := d.backend.Register(addr); err != nil {
 		log.Warnf("Registering as %q in discovery failed: %v", addr, err)
 		log.Warnf("Registering as %q in discovery failed: %v", addr, err)
 	}
 	}
 }
 }
 
 
-// registrationLoop registers the current node against the discovery backend using the specified
+// advertiseHeartbeat registers the current node against the discovery backend using the specified
 // address. The function never returns, as registration against the backend comes with a TTL and
 // address. The function never returns, as registration against the backend comes with a TTL and
 // requires regular heartbeats.
 // requires regular heartbeats.
-func registrationLoop(discoveryBackend discovery.Backend, address string, heartbeat time.Duration) {
-	registerAddr(discoveryBackend, address)
-	for range time.Tick(heartbeat) {
-		registerAddr(discoveryBackend, address)
+func (d *daemonDiscoveryReloader) advertiseHeartbeat(address string) {
+	for {
+		select {
+		case <-d.ticker.C:
+			d.registerAddr(address)
+		case <-d.term:
+			return
+		}
+	}
+}
+
+// Reload makes the watcher to stop advertising and reconfigures it to advertise in a new address.
+func (d *daemonDiscoveryReloader) Reload(backendAddress, advertiseAddress string, clusterOpts map[string]string) error {
+	d.Stop()
+
+	heartbeat, backend, err := parseDiscoveryOptions(backendAddress, clusterOpts)
+	if err != nil {
+		return err
+	}
+
+	d.backend = backend
+	d.ticker = time.NewTicker(heartbeat)
+
+	d.advertise(advertiseAddress)
+	return nil
+}
+
+// Stop terminates the discovery advertising.
+func (d *daemonDiscoveryReloader) Stop() {
+	d.ticker.Stop()
+	d.term <- true
+}
+
+func parseDiscoveryOptions(backendAddress string, clusterOpts map[string]string) (time.Duration, discovery.Backend, error) {
+	heartbeat, ttl, err := discoveryOpts(clusterOpts)
+	if err != nil {
+		return 0, nil, err
 	}
 	}
+
+	backend, err := discovery.New(backendAddress, heartbeat, ttl, clusterOpts)
+	if err != nil {
+		return 0, nil, err
+	}
+	return heartbeat, backend, nil
+}
+
+// modifiedDiscoverySettings returns whether the discovery configuration has been modified or not.
+func modifiedDiscoverySettings(config *Config, backendType, advertise string, clusterOpts map[string]string) bool {
+	if config.ClusterStore != backendType || config.ClusterAdvertise != advertise {
+		return true
+	}
+
+	if (config.ClusterOpts == nil && clusterOpts == nil) ||
+		(config.ClusterOpts == nil && len(clusterOpts) == 0) ||
+		(len(config.ClusterOpts) == 0 && clusterOpts == nil) {
+		return false
+	}
+
+	return !reflect.DeepEqual(config.ClusterOpts, clusterOpts)
 }
 }

+ 61 - 0
daemon/discovery_test.go

@@ -89,3 +89,64 @@ func TestDiscoveryOpts(t *testing.T) {
 		t.Fatalf("TTL - Expected : %v, Actual : %v", expected, ttl)
 		t.Fatalf("TTL - Expected : %v, Actual : %v", expected, ttl)
 	}
 	}
 }
 }
+
+func TestModifiedDiscoverySettings(t *testing.T) {
+	cases := []struct {
+		current  *Config
+		modified *Config
+		expected bool
+	}{
+		{
+			current:  discoveryConfig("foo", "bar", map[string]string{}),
+			modified: discoveryConfig("foo", "bar", map[string]string{}),
+			expected: false,
+		},
+		{
+			current:  discoveryConfig("foo", "bar", map[string]string{"foo": "bar"}),
+			modified: discoveryConfig("foo", "bar", map[string]string{"foo": "bar"}),
+			expected: false,
+		},
+		{
+			current:  discoveryConfig("foo", "bar", map[string]string{}),
+			modified: discoveryConfig("foo", "bar", nil),
+			expected: false,
+		},
+		{
+			current:  discoveryConfig("foo", "bar", nil),
+			modified: discoveryConfig("foo", "bar", map[string]string{}),
+			expected: false,
+		},
+		{
+			current:  discoveryConfig("foo", "bar", nil),
+			modified: discoveryConfig("baz", "bar", nil),
+			expected: true,
+		},
+		{
+			current:  discoveryConfig("foo", "bar", nil),
+			modified: discoveryConfig("foo", "baz", nil),
+			expected: true,
+		},
+		{
+			current:  discoveryConfig("foo", "bar", nil),
+			modified: discoveryConfig("foo", "bar", map[string]string{"foo": "bar"}),
+			expected: true,
+		},
+	}
+
+	for _, c := range cases {
+		got := modifiedDiscoverySettings(c.current, c.modified.ClusterStore, c.modified.ClusterAdvertise, c.modified.ClusterOpts)
+		if c.expected != got {
+			t.Fatalf("expected %v, got %v: current config %q, new config %q", c.expected, got, c.current, c.modified)
+		}
+	}
+}
+
+func discoveryConfig(backendAddr, advertiseAddr string, opts map[string]string) *Config {
+	return &Config{
+		CommonConfig: CommonConfig{
+			ClusterStore:     backendAddr,
+			ClusterAdvertise: advertiseAddr,
+			ClusterOpts:      opts,
+		},
+	}
+}

+ 1 - 1
daemon/info.go

@@ -79,7 +79,7 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) {
 		IPv4Forwarding:     !sysInfo.IPv4ForwardingDisabled,
 		IPv4Forwarding:     !sysInfo.IPv4ForwardingDisabled,
 		BridgeNfIptables:   !sysInfo.BridgeNfCallIptablesDisabled,
 		BridgeNfIptables:   !sysInfo.BridgeNfCallIptablesDisabled,
 		BridgeNfIP6tables:  !sysInfo.BridgeNfCallIP6tablesDisabled,
 		BridgeNfIP6tables:  !sysInfo.BridgeNfCallIP6tablesDisabled,
-		Debug:              os.Getenv("DEBUG") != "",
+		Debug:              utils.IsDebugEnabled(),
 		NFd:                fileutils.GetTotalUsedFds(),
 		NFd:                fileutils.GetTotalUsedFds(),
 		NGoroutines:        runtime.NumGoroutine(),
 		NGoroutines:        runtime.NumGoroutine(),
 		SystemTime:         time.Now().Format(time.RFC3339Nano),
 		SystemTime:         time.Now().Format(time.RFC3339Nano),

+ 1 - 7
docker/common.go

@@ -21,7 +21,6 @@ const (
 )
 )
 
 
 var (
 var (
-	daemonFlags *flag.FlagSet
 	commonFlags = &cli.CommonFlags{FlagSet: new(flag.FlagSet)}
 	commonFlags = &cli.CommonFlags{FlagSet: new(flag.FlagSet)}
 
 
 	dockerCertPath  = os.Getenv("DOCKER_CERT_PATH")
 	dockerCertPath  = os.Getenv("DOCKER_CERT_PATH")
@@ -50,7 +49,7 @@ func init() {
 	cmd.StringVar(&tlsOptions.CertFile, []string{"-tlscert"}, filepath.Join(dockerCertPath, defaultCertFile), "Path to TLS certificate file")
 	cmd.StringVar(&tlsOptions.CertFile, []string{"-tlscert"}, filepath.Join(dockerCertPath, defaultCertFile), "Path to TLS certificate file")
 	cmd.StringVar(&tlsOptions.KeyFile, []string{"-tlskey"}, filepath.Join(dockerCertPath, defaultKeyFile), "Path to TLS key file")
 	cmd.StringVar(&tlsOptions.KeyFile, []string{"-tlskey"}, filepath.Join(dockerCertPath, defaultKeyFile), "Path to TLS key file")
 
 
-	cmd.Var(opts.NewListOptsRef(&commonFlags.Hosts, opts.ValidateHost), []string{"H", "-host"}, "Daemon socket(s) to connect to")
+	cmd.Var(opts.NewNamedListOptsRef("hosts", &commonFlags.Hosts, opts.ValidateHost), []string{"H", "-host"}, "Daemon socket(s) to connect to")
 }
 }
 
 
 func postParseCommon() {
 func postParseCommon() {
@@ -67,11 +66,6 @@ func postParseCommon() {
 		logrus.SetLevel(logrus.InfoLevel)
 		logrus.SetLevel(logrus.InfoLevel)
 	}
 	}
 
 
-	if commonFlags.Debug {
-		os.Setenv("DEBUG", "1")
-		logrus.SetLevel(logrus.DebugLevel)
-	}
-
 	// Regardless of whether the user sets it to true or false, if they
 	// Regardless of whether the user sets it to true or false, if they
 	// specify --tlsverify at all then we need to turn on tls
 	// specify --tlsverify at all then we need to turn on tls
 	// TLSVerify can be true even if not set due to DOCKER_TLS_VERIFY env var, so we need to check that here as well
 	// TLSVerify can be true even if not set due to DOCKER_TLS_VERIFY env var, so we need to check that here as well

+ 88 - 29
docker/daemon.go

@@ -30,23 +30,34 @@ import (
 	"github.com/docker/go-connections/tlsconfig"
 	"github.com/docker/go-connections/tlsconfig"
 )
 )
 
 
-const daemonUsage = "       docker daemon [ --help | ... ]\n"
+const (
+	daemonUsage          = "       docker daemon [ --help | ... ]\n"
+	daemonConfigFileFlag = "-config-file"
+)
 
 
 var (
 var (
 	daemonCli cli.Handler = NewDaemonCli()
 	daemonCli cli.Handler = NewDaemonCli()
 )
 )
 
 
+// DaemonCli represents the daemon CLI.
+type DaemonCli struct {
+	*daemon.Config
+	registryOptions *registry.Options
+	flags           *flag.FlagSet
+}
+
 func presentInHelp(usage string) string { return usage }
 func presentInHelp(usage string) string { return usage }
 func absentFromHelp(string) string      { return "" }
 func absentFromHelp(string) string      { return "" }
 
 
 // NewDaemonCli returns a pre-configured daemon CLI
 // NewDaemonCli returns a pre-configured daemon CLI
 func NewDaemonCli() *DaemonCli {
 func NewDaemonCli() *DaemonCli {
-	daemonFlags = cli.Subcmd("daemon", nil, "Enable daemon mode", true)
+	daemonFlags := cli.Subcmd("daemon", nil, "Enable daemon mode", true)
 
 
 	// TODO(tiborvass): remove InstallFlags?
 	// TODO(tiborvass): remove InstallFlags?
 	daemonConfig := new(daemon.Config)
 	daemonConfig := new(daemon.Config)
 	daemonConfig.LogConfig.Config = make(map[string]string)
 	daemonConfig.LogConfig.Config = make(map[string]string)
 	daemonConfig.ClusterOpts = make(map[string]string)
 	daemonConfig.ClusterOpts = make(map[string]string)
+
 	daemonConfig.InstallFlags(daemonFlags, presentInHelp)
 	daemonConfig.InstallFlags(daemonFlags, presentInHelp)
 	daemonConfig.InstallFlags(flag.CommandLine, absentFromHelp)
 	daemonConfig.InstallFlags(flag.CommandLine, absentFromHelp)
 	registryOptions := new(registry.Options)
 	registryOptions := new(registry.Options)
@@ -57,6 +68,7 @@ func NewDaemonCli() *DaemonCli {
 	return &DaemonCli{
 	return &DaemonCli{
 		Config:          daemonConfig,
 		Config:          daemonConfig,
 		registryOptions: registryOptions,
 		registryOptions: registryOptions,
+		flags:           daemonFlags,
 	}
 	}
 }
 }
 
 
@@ -101,12 +113,6 @@ func migrateKey() (err error) {
 	return nil
 	return nil
 }
 }
 
 
-// DaemonCli represents the daemon CLI.
-type DaemonCli struct {
-	*daemon.Config
-	registryOptions *registry.Options
-}
-
 func getGlobalFlag() (globalFlag *flag.Flag) {
 func getGlobalFlag() (globalFlag *flag.Flag) {
 	defer func() {
 	defer func() {
 		if x := recover(); x != nil {
 		if x := recover(); x != nil {
@@ -136,15 +142,27 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
 		os.Exit(1)
 		os.Exit(1)
 	} else {
 	} else {
 		// allow new form `docker daemon -D`
 		// allow new form `docker daemon -D`
-		flag.Merge(daemonFlags, commonFlags.FlagSet)
+		flag.Merge(cli.flags, commonFlags.FlagSet)
 	}
 	}
 
 
-	daemonFlags.ParseFlags(args, true)
+	configFile := cli.flags.String([]string{daemonConfigFileFlag}, defaultDaemonConfigFile, "Daemon configuration file")
+
+	cli.flags.ParseFlags(args, true)
 	commonFlags.PostParse()
 	commonFlags.PostParse()
 
 
 	if commonFlags.TrustKey == "" {
 	if commonFlags.TrustKey == "" {
 		commonFlags.TrustKey = filepath.Join(getDaemonConfDir(), defaultTrustKeyFile)
 		commonFlags.TrustKey = filepath.Join(getDaemonConfDir(), defaultTrustKeyFile)
 	}
 	}
+	cliConfig, err := loadDaemonCliConfig(cli.Config, cli.flags, commonFlags, *configFile)
+	if err != nil {
+		fmt.Fprint(os.Stderr, err)
+		os.Exit(1)
+	}
+	cli.Config = cliConfig
+
+	if cli.Config.Debug {
+		utils.EnableDebug()
+	}
 
 
 	if utils.ExperimentalBuild() {
 	if utils.ExperimentalBuild() {
 		logrus.Warn("Running experimental build")
 		logrus.Warn("Running experimental build")
@@ -184,12 +202,18 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
 	serverConfig = setPlatformServerConfig(serverConfig, cli.Config)
 	serverConfig = setPlatformServerConfig(serverConfig, cli.Config)
 
 
 	defaultHost := opts.DefaultHost
 	defaultHost := opts.DefaultHost
-	if commonFlags.TLSOptions != nil {
-		if !commonFlags.TLSOptions.InsecureSkipVerify {
+	if cli.Config.TLS {
+		tlsOptions := tlsconfig.Options{
+			CAFile:   cli.Config.TLSOptions.CAFile,
+			CertFile: cli.Config.TLSOptions.CertFile,
+			KeyFile:  cli.Config.TLSOptions.KeyFile,
+		}
+
+		if cli.Config.TLSVerify {
 			// server requires and verifies client's certificate
 			// server requires and verifies client's certificate
-			commonFlags.TLSOptions.ClientAuth = tls.RequireAndVerifyClientCert
+			tlsOptions.ClientAuth = tls.RequireAndVerifyClientCert
 		}
 		}
-		tlsConfig, err := tlsconfig.Server(*commonFlags.TLSOptions)
+		tlsConfig, err := tlsconfig.Server(tlsOptions)
 		if err != nil {
 		if err != nil {
 			logrus.Fatal(err)
 			logrus.Fatal(err)
 		}
 		}
@@ -197,22 +221,23 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
 		defaultHost = opts.DefaultTLSHost
 		defaultHost = opts.DefaultTLSHost
 	}
 	}
 
 
-	if len(commonFlags.Hosts) == 0 {
-		commonFlags.Hosts = make([]string, 1)
+	if len(cli.Config.Hosts) == 0 {
+		cli.Config.Hosts = make([]string, 1)
 	}
 	}
-	for i := 0; i < len(commonFlags.Hosts); i++ {
+	for i := 0; i < len(cli.Config.Hosts); i++ {
 		var err error
 		var err error
-		if commonFlags.Hosts[i], err = opts.ParseHost(defaultHost, commonFlags.Hosts[i]); err != nil {
-			logrus.Fatalf("error parsing -H %s : %v", commonFlags.Hosts[i], err)
+		if cli.Config.Hosts[i], err = opts.ParseHost(defaultHost, cli.Config.Hosts[i]); err != nil {
+			logrus.Fatalf("error parsing -H %s : %v", cli.Config.Hosts[i], err)
 		}
 		}
-	}
-	for _, protoAddr := range commonFlags.Hosts {
+
+		protoAddr := cli.Config.Hosts[i]
 		protoAddrParts := strings.SplitN(protoAddr, "://", 2)
 		protoAddrParts := strings.SplitN(protoAddr, "://", 2)
 		if len(protoAddrParts) != 2 {
 		if len(protoAddrParts) != 2 {
 			logrus.Fatalf("bad format %s, expected PROTO://ADDR", protoAddr)
 			logrus.Fatalf("bad format %s, expected PROTO://ADDR", protoAddr)
 		}
 		}
 		serverConfig.Addrs = append(serverConfig.Addrs, apiserver.Addr{Proto: protoAddrParts[0], Addr: protoAddrParts[1]})
 		serverConfig.Addrs = append(serverConfig.Addrs, apiserver.Addr{Proto: protoAddrParts[0], Addr: protoAddrParts[1]})
 	}
 	}
+
 	api, err := apiserver.New(serverConfig)
 	api, err := apiserver.New(serverConfig)
 	if err != nil {
 	if err != nil {
 		logrus.Fatal(err)
 		logrus.Fatal(err)
@@ -245,18 +270,21 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
 
 
 	api.InitRouters(d)
 	api.InitRouters(d)
 
 
+	reload := func(config *daemon.Config) {
+		if err := d.Reload(config); err != nil {
+			logrus.Errorf("Error reconfiguring the daemon: %v", err)
+			return
+		}
+		api.Reload(config)
+	}
+
+	setupConfigReloadTrap(*configFile, cli.flags, reload)
+
 	// The serve API routine never exits unless an error occurs
 	// The serve API routine never exits unless an error occurs
 	// We need to start it as a goroutine and wait on it so
 	// We need to start it as a goroutine and wait on it so
 	// daemon doesn't exit
 	// daemon doesn't exit
 	serveAPIWait := make(chan error)
 	serveAPIWait := make(chan error)
-	go func() {
-		if err := api.ServeAPI(); err != nil {
-			logrus.Errorf("ServeAPI error: %v", err)
-			serveAPIWait <- err
-			return
-		}
-		serveAPIWait <- nil
-	}()
+	go api.Wait(serveAPIWait)
 
 
 	signal.Trap(func() {
 	signal.Trap(func() {
 		api.Close()
 		api.Close()
@@ -303,3 +331,34 @@ func shutdownDaemon(d *daemon.Daemon, timeout time.Duration) {
 		logrus.Error("Force shutdown daemon")
 		logrus.Error("Force shutdown daemon")
 	}
 	}
 }
 }
+
+func loadDaemonCliConfig(config *daemon.Config, daemonFlags *flag.FlagSet, commonConfig *cli.CommonFlags, configFile string) (*daemon.Config, error) {
+	config.Debug = commonConfig.Debug
+	config.Hosts = commonConfig.Hosts
+	config.LogLevel = commonConfig.LogLevel
+	config.TLS = commonConfig.TLS
+	config.TLSVerify = commonConfig.TLSVerify
+	config.TLSOptions = daemon.CommonTLSOptions{}
+
+	if commonConfig.TLSOptions != nil {
+		config.TLSOptions.CAFile = commonConfig.TLSOptions.CAFile
+		config.TLSOptions.CertFile = commonConfig.TLSOptions.CertFile
+		config.TLSOptions.KeyFile = commonConfig.TLSOptions.KeyFile
+	}
+
+	if configFile != "" {
+		c, err := daemon.MergeDaemonConfigurations(config, daemonFlags, configFile)
+		if err != nil {
+			if daemonFlags.IsSet(daemonConfigFileFlag) || !os.IsNotExist(err) {
+				return nil, fmt.Errorf("unable to configure the Docker daemon with file %s: %v\n", configFile, err)
+			}
+		}
+		// the merged configuration can be nil if the config file didn't exist.
+		// leave the current configuration as it is if when that happens.
+		if c != nil {
+			config = c
+		}
+	}
+
+	return config, nil
+}

+ 91 - 0
docker/daemon_test.go

@@ -0,0 +1,91 @@
+// +build daemon
+
+package main
+
+import (
+	"io/ioutil"
+	"strings"
+	"testing"
+
+	"github.com/docker/docker/cli"
+	"github.com/docker/docker/daemon"
+	"github.com/docker/docker/opts"
+	"github.com/docker/docker/pkg/mflag"
+	"github.com/docker/go-connections/tlsconfig"
+)
+
+func TestLoadDaemonCliConfigWithoutOverriding(t *testing.T) {
+	c := &daemon.Config{}
+	common := &cli.CommonFlags{
+		Debug: true,
+	}
+
+	flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
+	loadedConfig, err := loadDaemonCliConfig(c, flags, common, "/tmp/fooobarbaz")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if loadedConfig == nil {
+		t.Fatalf("expected configuration %v, got nil", c)
+	}
+	if !loadedConfig.Debug {
+		t.Fatalf("expected debug to be copied from the common flags, got false")
+	}
+}
+
+func TestLoadDaemonCliConfigWithTLS(t *testing.T) {
+	c := &daemon.Config{}
+	common := &cli.CommonFlags{
+		TLS: true,
+		TLSOptions: &tlsconfig.Options{
+			CAFile: "/tmp/ca.pem",
+		},
+	}
+
+	flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
+	loadedConfig, err := loadDaemonCliConfig(c, flags, common, "/tmp/fooobarbaz")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if loadedConfig == nil {
+		t.Fatalf("expected configuration %v, got nil", c)
+	}
+	if loadedConfig.TLSOptions.CAFile != "/tmp/ca.pem" {
+		t.Fatalf("expected /tmp/ca.pem, got %s: %q", loadedConfig.TLSOptions.CAFile, loadedConfig)
+	}
+}
+
+func TestLoadDaemonCliConfigWithConflicts(t *testing.T) {
+	c := &daemon.Config{}
+	common := &cli.CommonFlags{}
+	f, err := ioutil.TempFile("", "docker-config-")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	configFile := f.Name()
+	f.Write([]byte(`{"labels": ["l3=foo"]}`))
+	f.Close()
+
+	var labels []string
+
+	flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
+	flags.String([]string{daemonConfigFileFlag}, "", "")
+	flags.Var(opts.NewNamedListOptsRef("labels", &labels, opts.ValidateLabel), []string{"-label"}, "")
+
+	flags.Set(daemonConfigFileFlag, configFile)
+	if err := flags.Set("-label", "l1=bar"); err != nil {
+		t.Fatal(err)
+	}
+	if err := flags.Set("-label", "l2=baz"); err != nil {
+		t.Fatal(err)
+	}
+
+	_, err = loadDaemonCliConfig(c, flags, common, configFile)
+	if err == nil {
+		t.Fatalf("expected configuration error, got nil")
+	}
+	if !strings.Contains(err.Error(), "labels") {
+		t.Fatalf("expected labels conflict, got %v", err)
+	}
+}

+ 15 - 0
docker/daemon_unix.go

@@ -5,15 +5,19 @@ package main
 import (
 import (
 	"fmt"
 	"fmt"
 	"os"
 	"os"
+	"os/signal"
 	"syscall"
 	"syscall"
 
 
 	apiserver "github.com/docker/docker/api/server"
 	apiserver "github.com/docker/docker/api/server"
 	"github.com/docker/docker/daemon"
 	"github.com/docker/docker/daemon"
+	"github.com/docker/docker/pkg/mflag"
 	"github.com/docker/docker/pkg/system"
 	"github.com/docker/docker/pkg/system"
 
 
 	_ "github.com/docker/docker/daemon/execdriver/native"
 	_ "github.com/docker/docker/daemon/execdriver/native"
 )
 )
 
 
+const defaultDaemonConfigFile = "/etc/docker/daemon.json"
+
 func setPlatformServerConfig(serverConfig *apiserver.Config, daemonCfg *daemon.Config) *apiserver.Config {
 func setPlatformServerConfig(serverConfig *apiserver.Config, daemonCfg *daemon.Config) *apiserver.Config {
 	serverConfig.SocketGroup = daemonCfg.SocketGroup
 	serverConfig.SocketGroup = daemonCfg.SocketGroup
 	serverConfig.EnableCors = daemonCfg.EnableCors
 	serverConfig.EnableCors = daemonCfg.EnableCors
@@ -48,3 +52,14 @@ func setDefaultUmask() error {
 func getDaemonConfDir() string {
 func getDaemonConfDir() string {
 	return "/etc/docker"
 	return "/etc/docker"
 }
 }
+
+// setupConfigReloadTrap configures the USR2 signal to reload the configuration.
+func setupConfigReloadTrap(configFile string, flags *mflag.FlagSet, reload func(*daemon.Config)) {
+	c := make(chan os.Signal, 1)
+	signal.Notify(c, syscall.SIGHUP)
+	go func() {
+		for range c {
+			daemon.ReloadConfiguration(configFile, flags, reload)
+		}
+	}()
+}

+ 24 - 0
docker/daemon_windows.go

@@ -3,12 +3,19 @@
 package main
 package main
 
 
 import (
 import (
+	"fmt"
 	"os"
 	"os"
+	"syscall"
 
 
+	"github.com/Sirupsen/logrus"
 	apiserver "github.com/docker/docker/api/server"
 	apiserver "github.com/docker/docker/api/server"
 	"github.com/docker/docker/daemon"
 	"github.com/docker/docker/daemon"
+	"github.com/docker/docker/pkg/mflag"
+	"github.com/docker/docker/pkg/system"
 )
 )
 
 
+var defaultDaemonConfigFile = os.Getenv("programdata") + string(os.PathSeparator) + "docker" + string(os.PathSeparator) + "config" + string(os.PathSeparator) + "daemon.json"
+
 func setPlatformServerConfig(serverConfig *apiserver.Config, daemonCfg *daemon.Config) *apiserver.Config {
 func setPlatformServerConfig(serverConfig *apiserver.Config, daemonCfg *daemon.Config) *apiserver.Config {
 	return serverConfig
 	return serverConfig
 }
 }
@@ -31,3 +38,20 @@ func getDaemonConfDir() string {
 // notifySystem sends a message to the host when the server is ready to be used
 // notifySystem sends a message to the host when the server is ready to be used
 func notifySystem() {
 func notifySystem() {
 }
 }
+
+// setupConfigReloadTrap configures a Win32 event to reload the configuration.
+func setupConfigReloadTrap(configFile string, flags *mflag.FlagSet, reload func(*daemon.Config)) {
+	go func() {
+		sa := syscall.SecurityAttributes{
+			Length: 0,
+		}
+		ev := "Global\\docker-daemon-config-" + fmt.Sprint(os.Getpid())
+		if h, _ := system.CreateEvent(&sa, false, false, ev); h != 0 {
+			logrus.Debugf("Config reload - waiting signal at %s", ev)
+			for {
+				syscall.WaitForSingleObject(h, syscall.INFINITE)
+				daemon.ReloadConfiguration(configFile, flags, reload)
+			}
+		}
+	}()
+}

+ 78 - 1
docs/reference/commandline/daemon.md

@@ -27,6 +27,7 @@ weight = -1
       --cluster-store=""                     URL of the distributed storage backend
       --cluster-store=""                     URL of the distributed storage backend
       --cluster-advertise=""                 Address of the daemon instance on the cluster
       --cluster-advertise=""                 Address of the daemon instance on the cluster
       --cluster-store-opt=map[]              Set cluster options
       --cluster-store-opt=map[]              Set cluster options
+      --config-file=/etc/docker/daemon.json  Daemon configuration file
       --dns=[]                               DNS server to use
       --dns=[]                               DNS server to use
       --dns-opt=[]                           DNS options to use
       --dns-opt=[]                           DNS options to use
       --dns-search=[]                        DNS search domains to use
       --dns-search=[]                        DNS search domains to use
@@ -788,7 +789,7 @@ set like this:
     /usr/local/bin/docker daemon -D -g /var/lib/docker -H unix:// > /var/lib/docker-machine/docker.log 2>&1
     /usr/local/bin/docker daemon -D -g /var/lib/docker -H unix:// > /var/lib/docker-machine/docker.log 2>&1
 
 
 
 
-# Default cgroup parent
+## Default cgroup parent
 
 
 The `--cgroup-parent` option allows you to set the default cgroup parent
 The `--cgroup-parent` option allows you to set the default cgroup parent
 to use for containers. If this option is not set, it defaults to `/docker` for
 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`
 This setting can also be set per container, using the `--cgroup-parent`
 This setting can also be set per container, using the `--cgroup-parent`
 option on `docker create` and `docker run`, and takes precedence over
 option on `docker create` and `docker run`, and takes precedence over
 the `--cgroup-parent` option on the daemon.
 the `--cgroup-parent` option on the daemon.
+
+## Daemon configuration file
+
+The `--config-file` option allows you to set any configuration option
+for the daemon in a JSON format. This file uses the same flag names as keys,
+except for flags that allow several entries, where it uses the plural
+of the flag name, e.g., `labels` for the `label` flag. By default,
+docker tries to load a configuration file from `/etc/docker/daemon.json`
+on Linux and `%programdata%\docker\config\daemon.json` on Windows.
+
+The options set in the configuration file must not conflict with options set
+via flags. The docker daemon fails to start if an option is duplicated between
+the file and the flags, regardless their value. We do this to avoid
+silently ignore changes introduced in configuration reloads.
+For example, the daemon fails to start if you set daemon labels
+in the configuration file and also set daemon labels via the `--label` flag.
+
+Options that are not present in the file are ignored when the daemon starts.
+This is a full example of the allowed configuration options in the file:
+
+```json
+{
+	"authorization-plugins": [],
+	"dns": [],
+	"dns-opts": [],
+	"dns-search": [],
+	"exec-opts": [],
+	"exec-root": "",
+	"storage-driver": "",
+	"storage-opts": "",
+	"labels": [],
+	"log-config": {
+		"log-driver": "",
+		"log-opts": []
+	},
+	"mtu": 0,
+	"pidfile": "",
+	"graph": "",
+	"cluster-store": "",
+	"cluster-store-opts": [],
+	"cluster-advertise": "",
+	"debug": true,
+	"hosts": [],
+	"log-level": "",
+	"tls": true,
+	"tls-verify": true,
+	"tls-opts": {
+		"tlscacert": "",
+		"tlscert": "",
+		"tlskey": ""
+	},
+	"api-cors-headers": "",
+	"selinux-enabled": false,
+	"userns-remap": "",
+	"group": "",
+	"cgroup-parent": "",
+	"default-ulimits": {}
+}
+```
+
+### Configuration reloading
+
+Some options can be reconfigured when the daemon is running without requiring
+to restart the process. We use the `SIGHUP` signal in Linux to reload, and a global event
+in Windows with the key `Global\docker-daemon-config-$PID`. The options can
+be modified in the configuration file but still will check for conflicts with
+the provided flags. The daemon fails to reconfigure itself
+if there are conflicts, but it won't stop execution.
+
+The list of currently supported options that can be reconfigured is this:
+
+- `debug`: it changes the daemon to debug mode when set to true.
+- `label`: it replaces the daemon labels with a new set of labels.
+- `cluster-store`: it reloads the discovery store with the new address.
+- `cluster-store-opts`: it uses the new options to reload the discovery store.
+- `cluster-advertise`: it modifies the address advertised after reloading.

+ 1 - 0
hack/vendor.sh

@@ -24,6 +24,7 @@ clone git github.com/docker/go-units 651fc226e7441360384da338d0fd37f2440ffbe3
 clone git github.com/docker/go-connections v0.1.2
 clone git github.com/docker/go-connections v0.1.2
 clone git github.com/docker/engine-api v0.2.2
 clone git github.com/docker/engine-api v0.2.2
 clone git github.com/RackSec/srslog 6eb773f331e46fbba8eecb8e794e635e75fc04de
 clone git github.com/RackSec/srslog 6eb773f331e46fbba8eecb8e794e635e75fc04de
+clone git github.com/imdario/mergo 0.2.1
 
 
 #get libnetwork packages
 #get libnetwork packages
 clone git github.com/docker/libnetwork v0.5.5
 clone git github.com/docker/libnetwork v0.5.5

+ 1 - 1
integration-cli/docker_cli_help_test.go

@@ -133,7 +133,7 @@ func (s *DockerSuite) TestHelpTextVerify(c *check.C) {
 			// Check each line for lots of stuff
 			// Check each line for lots of stuff
 			lines := strings.Split(out, "\n")
 			lines := strings.Split(out, "\n")
 			for _, line := range lines {
 			for _, line := range lines {
-				c.Assert(len(line), checker.LessOrEqualThan, 103, check.Commentf("Help for %q is too long:\n%s", cmd, line))
+				c.Assert(len(line), checker.LessOrEqualThan, 107, check.Commentf("Help for %q is too long:\n%s", cmd, line))
 
 
 				if scanForHome && strings.Contains(line, `"`+home) {
 				if scanForHome && strings.Contains(line, `"`+home) {
 					c.Fatalf("Help for %q should use ~ instead of %q on:\n%s",
 					c.Fatalf("Help for %q should use ~ instead of %q on:\n%s",

+ 4 - 0
man/docker-daemon.8.md

@@ -14,6 +14,7 @@ docker-daemon - Enable daemon mode
 [**--cluster-store**[=*[]*]]
 [**--cluster-store**[=*[]*]]
 [**--cluster-advertise**[=*[]*]]
 [**--cluster-advertise**[=*[]*]]
 [**--cluster-store-opt**[=*map[]*]]
 [**--cluster-store-opt**[=*map[]*]]
+[**--config-file**[=*/etc/docker/daemon.json*]]
 [**-D**|**--debug**]
 [**-D**|**--debug**]
 [**--default-gateway**[=*DEFAULT-GATEWAY*]]
 [**--default-gateway**[=*DEFAULT-GATEWAY*]]
 [**--default-gateway-v6**[=*DEFAULT-GATEWAY-V6*]]
 [**--default-gateway-v6**[=*DEFAULT-GATEWAY-V6*]]
@@ -96,6 +97,9 @@ format.
 **--cluster-store-opt**=""
 **--cluster-store-opt**=""
   Specifies options for the Key/Value store.
   Specifies options for the Key/Value store.
 
 
+**--config-file**="/etc/docker/daemon.json"
+  Specifies the JSON file path to load the configuration from.
+
 **-D**, **--debug**=*true*|*false*
 **-D**, **--debug**=*true*|*false*
   Enable debug mode. Default is false.
   Enable debug mode. Default is false.
 
 

+ 52 - 0
opts/opts.go

@@ -100,6 +100,35 @@ func (opts *ListOpts) Len() int {
 	return len((*opts.values))
 	return len((*opts.values))
 }
 }
 
 
+// NamedOption is an interface that list and map options
+// with names implement.
+type NamedOption interface {
+	Name() string
+}
+
+// NamedListOpts is a ListOpts with a configuration name.
+// This struct is useful to keep reference to the assigned
+// field name in the internal configuration struct.
+type NamedListOpts struct {
+	name string
+	ListOpts
+}
+
+var _ NamedOption = &NamedListOpts{}
+
+// NewNamedListOptsRef creates a reference to a new NamedListOpts struct.
+func NewNamedListOptsRef(name string, values *[]string, validator ValidatorFctType) *NamedListOpts {
+	return &NamedListOpts{
+		name:     name,
+		ListOpts: *NewListOptsRef(values, validator),
+	}
+}
+
+// Name returns the name of the NamedListOpts in the configuration.
+func (o *NamedListOpts) Name() string {
+	return o.name
+}
+
 //MapOpts holds a map of values and a validation function.
 //MapOpts holds a map of values and a validation function.
 type MapOpts struct {
 type MapOpts struct {
 	values    map[string]string
 	values    map[string]string
@@ -145,6 +174,29 @@ func NewMapOpts(values map[string]string, validator ValidatorFctType) *MapOpts {
 	}
 	}
 }
 }
 
 
+// NamedMapOpts is a MapOpts struct with a configuration name.
+// This struct is useful to keep reference to the assigned
+// field name in the internal configuration struct.
+type NamedMapOpts struct {
+	name string
+	MapOpts
+}
+
+var _ NamedOption = &NamedMapOpts{}
+
+// NewNamedMapOpts creates a reference to a new NamedMapOpts struct.
+func NewNamedMapOpts(name string, values map[string]string, validator ValidatorFctType) *NamedMapOpts {
+	return &NamedMapOpts{
+		name:    name,
+		MapOpts: *NewMapOpts(values, validator),
+	}
+}
+
+// Name returns the name of the NamedMapOpts in the configuration.
+func (o *NamedMapOpts) Name() string {
+	return o.name
+}
+
 // ValidatorFctType defines a validator function that returns a validated string and/or an error.
 // ValidatorFctType defines a validator function that returns a validated string and/or an error.
 type ValidatorFctType func(val string) (string, error)
 type ValidatorFctType func(val string) (string, error)
 
 

+ 32 - 0
opts/opts_test.go

@@ -198,3 +198,35 @@ func logOptsValidator(val string) (string, error) {
 	}
 	}
 	return "", fmt.Errorf("invalid key %s", vals[0])
 	return "", fmt.Errorf("invalid key %s", vals[0])
 }
 }
+
+func TestNamedListOpts(t *testing.T) {
+	var v []string
+	o := NewNamedListOptsRef("foo-name", &v, nil)
+
+	o.Set("foo")
+	if o.String() != "[foo]" {
+		t.Errorf("%s != [foo]", o.String())
+	}
+	if o.Name() != "foo-name" {
+		t.Errorf("%s != foo-name", o.Name())
+	}
+	if len(v) != 1 {
+		t.Errorf("expected foo to be in the values, got %v", v)
+	}
+}
+
+func TestNamedMapOpts(t *testing.T) {
+	tmpMap := make(map[string]string)
+	o := NewNamedMapOpts("max-name", tmpMap, nil)
+
+	o.Set("max-size=1")
+	if o.String() != "map[max-size:1]" {
+		t.Errorf("%s != [map[max-size:1]", o.String())
+	}
+	if o.Name() != "max-name" {
+		t.Errorf("%s != max-name", o.Name())
+	}
+	if _, exist := tmpMap["max-size"]; !exist {
+		t.Errorf("expected map-size to be in the values, got %v", tmpMap)
+	}
+}

+ 2 - 6
pkg/discovery/backends.go

@@ -12,12 +12,8 @@ import (
 var (
 var (
 	// Backends is a global map of discovery backends indexed by their
 	// Backends is a global map of discovery backends indexed by their
 	// associated scheme.
 	// associated scheme.
-	backends map[string]Backend
-)
-
-func init() {
 	backends = make(map[string]Backend)
 	backends = make(map[string]Backend)
-}
+)
 
 
 // Register makes a discovery backend available by the provided scheme.
 // Register makes a discovery backend available by the provided scheme.
 // If Register is called twice with the same scheme an error is returned.
 // If Register is called twice with the same scheme an error is returned.
@@ -42,7 +38,7 @@ func parse(rawurl string) (string, string) {
 
 
 // ParseAdvertise parses the --cluster-advertise daemon config which accepts
 // ParseAdvertise parses the --cluster-advertise daemon config which accepts
 // <ip-address>:<port> or <interface-name>:<port>
 // <ip-address>:<port> or <interface-name>:<port>
-func ParseAdvertise(store, advertise string) (string, error) {
+func ParseAdvertise(advertise string) (string, error) {
 	var (
 	var (
 		iface *net.Interface
 		iface *net.Interface
 		addrs []net.Addr
 		addrs []net.Addr

+ 83 - 0
pkg/discovery/memory/memory.go

@@ -0,0 +1,83 @@
+package memory
+
+import (
+	"time"
+
+	"github.com/docker/docker/pkg/discovery"
+)
+
+// Discovery implements a descovery backend that keeps
+// data in memory.
+type Discovery struct {
+	heartbeat time.Duration
+	values    []string
+}
+
+func init() {
+	Init()
+}
+
+// Init registers the memory backend on demand.
+func Init() {
+	discovery.Register("memory", &Discovery{})
+}
+
+// Initialize sets the heartbeat for the memory backend.
+func (s *Discovery) Initialize(_ string, heartbeat time.Duration, _ time.Duration, _ map[string]string) error {
+	s.heartbeat = heartbeat
+	s.values = make([]string, 0)
+	return nil
+}
+
+// Watch sends periodic discovery updates to a channel.
+func (s *Discovery) Watch(stopCh <-chan struct{}) (<-chan discovery.Entries, <-chan error) {
+	ch := make(chan discovery.Entries)
+	errCh := make(chan error)
+	ticker := time.NewTicker(s.heartbeat)
+
+	go func() {
+		defer close(errCh)
+		defer close(ch)
+
+		// Send the initial entries if available.
+		var currentEntries discovery.Entries
+		if len(s.values) > 0 {
+			var err error
+			currentEntries, err = discovery.CreateEntries(s.values)
+			if err != nil {
+				errCh <- err
+			} else {
+				ch <- currentEntries
+			}
+		}
+
+		// Periodically send updates.
+		for {
+			select {
+			case <-ticker.C:
+				newEntries, err := discovery.CreateEntries(s.values)
+				if err != nil {
+					errCh <- err
+					continue
+				}
+
+				// Check if the file has really changed.
+				if !newEntries.Equals(currentEntries) {
+					ch <- newEntries
+				}
+				currentEntries = newEntries
+			case <-stopCh:
+				ticker.Stop()
+				return
+			}
+		}
+	}()
+
+	return ch, errCh
+}
+
+// Register adds a new address to the discovery.
+func (s *Discovery) Register(addr string) error {
+	s.values = append(s.values, addr)
+	return nil
+}

+ 48 - 0
pkg/discovery/memory/memory_test.go

@@ -0,0 +1,48 @@
+package memory
+
+import (
+	"testing"
+
+	"github.com/docker/docker/pkg/discovery"
+	"github.com/go-check/check"
+)
+
+// Hook up gocheck into the "go test" runner.
+func Test(t *testing.T) { check.TestingT(t) }
+
+type discoverySuite struct{}
+
+var _ = check.Suite(&discoverySuite{})
+
+func (s *discoverySuite) TestWatch(c *check.C) {
+	d := &Discovery{}
+	d.Initialize("foo", 1000, 0, nil)
+	stopCh := make(chan struct{})
+	ch, errCh := d.Watch(stopCh)
+
+	// We have to drain the error channel otherwise Watch will get stuck.
+	go func() {
+		for range errCh {
+		}
+	}()
+
+	expected := discovery.Entries{
+		&discovery.Entry{Host: "1.1.1.1", Port: "1111"},
+	}
+
+	c.Assert(d.Register("1.1.1.1:1111"), check.IsNil)
+	c.Assert(<-ch, check.DeepEquals, expected)
+
+	expected = discovery.Entries{
+		&discovery.Entry{Host: "1.1.1.1", Port: "1111"},
+		&discovery.Entry{Host: "2.2.2.2", Port: "2222"},
+	}
+
+	c.Assert(d.Register("2.2.2.2:2222"), check.IsNil)
+	c.Assert(<-ch, check.DeepEquals, expected)
+
+	// Stop and make sure it closes all channels.
+	close(stopCh)
+	c.Assert(<-ch, check.IsNil)
+	c.Assert(<-errCh, check.IsNil)
+}

+ 26 - 0
utils/debug.go

@@ -0,0 +1,26 @@
+package utils
+
+import (
+	"os"
+
+	"github.com/Sirupsen/logrus"
+)
+
+// EnableDebug sets the DEBUG env var to true
+// and makes the logger to log at debug level.
+func EnableDebug() {
+	os.Setenv("DEBUG", "1")
+	logrus.SetLevel(logrus.DebugLevel)
+}
+
+// DisableDebug sets the DEBUG env var to false
+// and makes the logger to log at info level.
+func DisableDebug() {
+	os.Setenv("DEBUG", "")
+	logrus.SetLevel(logrus.InfoLevel)
+}
+
+// IsDebugEnabled checks whether the debug flag is set or not.
+func IsDebugEnabled() bool {
+	return os.Getenv("DEBUG") != ""
+}

+ 2 - 0
vendor/src/github.com/imdario/mergo/.travis.yml

@@ -0,0 +1,2 @@
+language: go
+install: go get -t

+ 28 - 0
vendor/src/github.com/imdario/mergo/LICENSE

@@ -0,0 +1,28 @@
+Copyright (c) 2013 Dario Castañé. All rights reserved.
+Copyright (c) 2012 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 122 - 0
vendor/src/github.com/imdario/mergo/README.md

@@ -0,0 +1,122 @@
+# Mergo
+
+A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements.
+
+Also a lovely [comune](http://en.wikipedia.org/wiki/Mergo) (municipality) in the Province of Ancona in the Italian region Marche.
+
+![Mergo dall'alto](http://www.comune.mergo.an.it/Siti/Mergo/Immagini/Foto/mergo_dall_alto.jpg)
+
+## Status
+
+It is ready for production use. It works fine after extensive use in the wild.
+
+[![Build Status][1]][2]
+[![GoDoc](https://godoc.org/github.com/imdario/mergo?status.svg)](https://godoc.org/github.com/imdario/mergo)
+
+[1]: https://travis-ci.org/imdario/mergo.png
+[2]: https://travis-ci.org/imdario/mergo
+
+### Important note
+
+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.
+
+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).
+
+### Mergo in the wild
+
+- [imdario/zas](https://github.com/imdario/zas)
+- [GoogleCloudPlatform/kubernetes](https://github.com/GoogleCloudPlatform/kubernetes)
+- [soniah/dnsmadeeasy](https://github.com/soniah/dnsmadeeasy)
+- [EagerIO/Stout](https://github.com/EagerIO/Stout)
+- [lynndylanhurley/defsynth-api](https://github.com/lynndylanhurley/defsynth-api)
+- [russross/canvasassignments](https://github.com/russross/canvasassignments)
+- [rdegges/cryptly-api](https://github.com/rdegges/cryptly-api)
+- [casualjim/exeggutor](https://github.com/casualjim/exeggutor)
+- [divshot/gitling](https://github.com/divshot/gitling)
+- [RWJMurphy/gorl](https://github.com/RWJMurphy/gorl)
+- [andrerocker/deploy42](https://github.com/andrerocker/deploy42)
+- [elwinar/rambler](https://github.com/elwinar/rambler)
+- [tmaiaroto/gopartman](https://github.com/tmaiaroto/gopartman)
+- [jfbus/impressionist](https://github.com/jfbus/impressionist)
+- [Jmeyering/zealot](https://github.com/Jmeyering/zealot)
+- [godep-migrator/rigger-host](https://github.com/godep-migrator/rigger-host)
+- [Dronevery/MultiwaySwitch-Go](https://github.com/Dronevery/MultiwaySwitch-Go)
+- [thoas/picfit](https://github.com/thoas/picfit)
+- [mantasmatelis/whooplist-server](https://github.com/mantasmatelis/whooplist-server)
+- [jnuthong/item_search](https://github.com/jnuthong/item_search)
+
+## Installation
+
+    go get github.com/imdario/mergo
+
+    // use in your .go code
+    import (
+        "github.com/imdario/mergo"
+    )
+
+## Usage
+
+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).
+
+    if err := mergo.Merge(&dst, src); err != nil {
+        // ...
+    }
+
+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.
+
+    if err := mergo.Map(&dst, srcMap); err != nil {
+        // ...
+    }
+
+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.
+
+More information and examples in [godoc documentation](http://godoc.org/github.com/imdario/mergo).
+
+### Nice example
+
+```go
+package main
+
+import (
+	"fmt"
+	"github.com/imdario/mergo"
+)
+
+type Foo struct {
+	A string
+	B int64
+}
+
+func main() {
+	src := Foo{
+		A: "one",
+	}
+
+	dest := Foo{
+		A: "two",
+		B: 2,
+	}
+
+	mergo.Merge(&dest, src)
+
+	fmt.Println(dest)
+	// Will print
+	// {two 2}
+}
+```
+
+Note: if test are failing due missing package, please execute:
+
+    go get gopkg.in/yaml.v1
+
+## Contact me
+
+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)
+
+## About
+
+Written by [Dario Castañé](http://dario.im).
+
+## License
+
+[BSD 3-Clause](http://opensource.org/licenses/BSD-3-Clause) license, as [Go language](http://golang.org/LICENSE).

+ 44 - 0
vendor/src/github.com/imdario/mergo/doc.go

@@ -0,0 +1,44 @@
+// Copyright 2013 Dario Castañé. All rights reserved.
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+Package mergo merges same-type structs and maps by setting default values in zero-value fields.
+
+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).
+
+Usage
+
+From my own work-in-progress project:
+
+	type networkConfig struct {
+		Protocol string
+		Address string
+		ServerType string `json: "server_type"`
+		Port uint16
+	}
+
+	type FssnConfig struct {
+		Network networkConfig
+	}
+
+	var fssnDefault = FssnConfig {
+		networkConfig {
+			"tcp",
+			"127.0.0.1",
+			"http",
+			31560,
+		},
+	}
+
+	// Inside a function [...]
+
+	if err := mergo.Merge(&config, fssnDefault); err != nil {
+		log.Fatal(err)
+	}
+
+	// More code [...]
+
+*/
+package mergo

+ 154 - 0
vendor/src/github.com/imdario/mergo/map.go

@@ -0,0 +1,154 @@
+// Copyright 2014 Dario Castañé. All rights reserved.
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Based on src/pkg/reflect/deepequal.go from official
+// golang's stdlib.
+
+package mergo
+
+import (
+	"fmt"
+	"reflect"
+	"unicode"
+	"unicode/utf8"
+)
+
+func changeInitialCase(s string, mapper func(rune) rune) string {
+	if s == "" {
+		return s
+	}
+	r, n := utf8.DecodeRuneInString(s)
+	return string(mapper(r)) + s[n:]
+}
+
+func isExported(field reflect.StructField) bool {
+	r, _ := utf8.DecodeRuneInString(field.Name)
+	return r >= 'A' && r <= 'Z'
+}
+
+// Traverses recursively both values, assigning src's fields values to dst.
+// The map argument tracks comparisons that have already been seen, which allows
+// short circuiting on recursive types.
+func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, overwrite bool) (err error) {
+	if dst.CanAddr() {
+		addr := dst.UnsafeAddr()
+		h := 17 * addr
+		seen := visited[h]
+		typ := dst.Type()
+		for p := seen; p != nil; p = p.next {
+			if p.ptr == addr && p.typ == typ {
+				return nil
+			}
+		}
+		// Remember, remember...
+		visited[h] = &visit{addr, typ, seen}
+	}
+	zeroValue := reflect.Value{}
+	switch dst.Kind() {
+	case reflect.Map:
+		dstMap := dst.Interface().(map[string]interface{})
+		for i, n := 0, src.NumField(); i < n; i++ {
+			srcType := src.Type()
+			field := srcType.Field(i)
+			if !isExported(field) {
+				continue
+			}
+			fieldName := field.Name
+			fieldName = changeInitialCase(fieldName, unicode.ToLower)
+			if v, ok := dstMap[fieldName]; !ok || (isEmptyValue(reflect.ValueOf(v)) || overwrite) {
+				dstMap[fieldName] = src.Field(i).Interface()
+			}
+		}
+	case reflect.Struct:
+		srcMap := src.Interface().(map[string]interface{})
+		for key := range srcMap {
+			srcValue := srcMap[key]
+			fieldName := changeInitialCase(key, unicode.ToUpper)
+			dstElement := dst.FieldByName(fieldName)
+			if dstElement == zeroValue {
+				// We discard it because the field doesn't exist.
+				continue
+			}
+			srcElement := reflect.ValueOf(srcValue)
+			dstKind := dstElement.Kind()
+			srcKind := srcElement.Kind()
+			if srcKind == reflect.Ptr && dstKind != reflect.Ptr {
+				srcElement = srcElement.Elem()
+				srcKind = reflect.TypeOf(srcElement.Interface()).Kind()
+			} else if dstKind == reflect.Ptr {
+				// Can this work? I guess it can't.
+				if srcKind != reflect.Ptr && srcElement.CanAddr() {
+					srcPtr := srcElement.Addr()
+					srcElement = reflect.ValueOf(srcPtr)
+					srcKind = reflect.Ptr
+				}
+			}
+			if !srcElement.IsValid() {
+				continue
+			}
+			if srcKind == dstKind {
+				if err = deepMerge(dstElement, srcElement, visited, depth+1, overwrite); err != nil {
+					return
+				}
+			} else {
+				if srcKind == reflect.Map {
+					if err = deepMap(dstElement, srcElement, visited, depth+1, overwrite); err != nil {
+						return
+					}
+				} else {
+					return fmt.Errorf("type mismatch on %s field: found %v, expected %v", fieldName, srcKind, dstKind)
+				}
+			}
+		}
+	}
+	return
+}
+
+// Map sets fields' values in dst from src.
+// src can be a map with string keys or a struct. dst must be the opposite:
+// if src is a map, dst must be a valid pointer to struct. If src is a struct,
+// dst must be map[string]interface{}.
+// It won't merge unexported (private) fields and will do recursively
+// any exported field.
+// If dst is a map, keys will be src fields' names in lower camel case.
+// Missing key in src that doesn't match a field in dst will be skipped. This
+// doesn't apply if dst is a map.
+// This is separated method from Merge because it is cleaner and it keeps sane
+// semantics: merging equal types, mapping different (restricted) types.
+func Map(dst, src interface{}) error {
+	return _map(dst, src, false)
+}
+
+func MapWithOverwrite(dst, src interface{}) error {
+	return _map(dst, src, true)
+}
+
+func _map(dst, src interface{}, overwrite bool) error {
+	var (
+		vDst, vSrc reflect.Value
+		err        error
+	)
+	if vDst, vSrc, err = resolveValues(dst, src); err != nil {
+		return err
+	}
+	// To be friction-less, we redirect equal-type arguments
+	// to deepMerge. Only because arguments can be anything.
+	if vSrc.Kind() == vDst.Kind() {
+		return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, overwrite)
+	}
+	switch vSrc.Kind() {
+	case reflect.Struct:
+		if vDst.Kind() != reflect.Map {
+			return ErrExpectedMapAsDestination
+		}
+	case reflect.Map:
+		if vDst.Kind() != reflect.Struct {
+			return ErrExpectedStructAsDestination
+		}
+	default:
+		return ErrNotSupported
+	}
+	return deepMap(vDst, vSrc, make(map[uintptr]*visit), 0, overwrite)
+}

+ 120 - 0
vendor/src/github.com/imdario/mergo/merge.go

@@ -0,0 +1,120 @@
+// Copyright 2013 Dario Castañé. All rights reserved.
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Based on src/pkg/reflect/deepequal.go from official
+// golang's stdlib.
+
+package mergo
+
+import (
+	"reflect"
+)
+
+// Traverses recursively both values, assigning src's fields values to dst.
+// The map argument tracks comparisons that have already been seen, which allows
+// short circuiting on recursive types.
+func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, overwrite bool) (err error) {
+	if !src.IsValid() {
+		return
+	}
+	if dst.CanAddr() {
+		addr := dst.UnsafeAddr()
+		h := 17 * addr
+		seen := visited[h]
+		typ := dst.Type()
+		for p := seen; p != nil; p = p.next {
+			if p.ptr == addr && p.typ == typ {
+				return nil
+			}
+		}
+		// Remember, remember...
+		visited[h] = &visit{addr, typ, seen}
+	}
+	switch dst.Kind() {
+	case reflect.Struct:
+		for i, n := 0, dst.NumField(); i < n; i++ {
+			if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1, overwrite); err != nil {
+				return
+			}
+		}
+	case reflect.Map:
+		for _, key := range src.MapKeys() {
+			srcElement := src.MapIndex(key)
+			if !srcElement.IsValid() {
+				continue
+			}
+			dstElement := dst.MapIndex(key)
+			switch srcElement.Kind() {
+			case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Slice:
+				if srcElement.IsNil() {
+					continue
+				}
+				fallthrough
+			default:
+				switch reflect.TypeOf(srcElement.Interface()).Kind() {
+				case reflect.Struct:
+					fallthrough
+				case reflect.Ptr:
+					fallthrough
+				case reflect.Map:
+					if err = deepMerge(dstElement, srcElement, visited, depth+1, overwrite); err != nil {
+						return
+					}
+				}
+			}
+			if !isEmptyValue(srcElement) && (overwrite || (!dstElement.IsValid() || isEmptyValue(dst))) {
+				if dst.IsNil() {
+					dst.Set(reflect.MakeMap(dst.Type()))
+				}
+				dst.SetMapIndex(key, srcElement)
+			}
+		}
+	case reflect.Ptr:
+		fallthrough
+	case reflect.Interface:
+		if src.IsNil() {
+			break
+		} else if dst.IsNil() {
+			if dst.CanSet() && (overwrite || isEmptyValue(dst)) {
+				dst.Set(src)
+			}
+		} else if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, overwrite); err != nil {
+			return
+		}
+	default:
+		if dst.CanSet() && !isEmptyValue(src) && (overwrite || isEmptyValue(dst)) {
+			dst.Set(src)
+		}
+	}
+	return
+}
+
+// Merge sets fields' values in dst from src if they have a zero
+// value of their type.
+// dst and src must be valid same-type structs and dst must be
+// a pointer to struct.
+// It won't merge unexported (private) fields and will do recursively
+// any exported field.
+func Merge(dst, src interface{}) error {
+	return merge(dst, src, false)
+}
+
+func MergeWithOverwrite(dst, src interface{}) error {
+	return merge(dst, src, true)
+}
+
+func merge(dst, src interface{}, overwrite bool) error {
+	var (
+		vDst, vSrc reflect.Value
+		err        error
+	)
+	if vDst, vSrc, err = resolveValues(dst, src); err != nil {
+		return err
+	}
+	if vDst.Type() != vSrc.Type() {
+		return ErrDifferentArgumentsTypes
+	}
+	return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, overwrite)
+}

+ 90 - 0
vendor/src/github.com/imdario/mergo/mergo.go

@@ -0,0 +1,90 @@
+// Copyright 2013 Dario Castañé. All rights reserved.
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Based on src/pkg/reflect/deepequal.go from official
+// golang's stdlib.
+
+package mergo
+
+import (
+	"errors"
+	"reflect"
+)
+
+// Errors reported by Mergo when it finds invalid arguments.
+var (
+	ErrNilArguments                = errors.New("src and dst must not be nil")
+	ErrDifferentArgumentsTypes     = errors.New("src and dst must be of same type")
+	ErrNotSupported                = errors.New("only structs and maps are supported")
+	ErrExpectedMapAsDestination    = errors.New("dst was expected to be a map")
+	ErrExpectedStructAsDestination = errors.New("dst was expected to be a struct")
+)
+
+// During deepMerge, must keep track of checks that are
+// in progress.  The comparison algorithm assumes that all
+// checks in progress are true when it reencounters them.
+// Visited are stored in a map indexed by 17 * a1 + a2;
+type visit struct {
+	ptr  uintptr
+	typ  reflect.Type
+	next *visit
+}
+
+// From src/pkg/encoding/json.
+func isEmptyValue(v reflect.Value) bool {
+	switch v.Kind() {
+	case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
+		return v.Len() == 0
+	case reflect.Bool:
+		return !v.Bool()
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return v.Int() == 0
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		return v.Uint() == 0
+	case reflect.Float32, reflect.Float64:
+		return v.Float() == 0
+	case reflect.Interface, reflect.Ptr:
+		return v.IsNil()
+	}
+	return false
+}
+
+func resolveValues(dst, src interface{}) (vDst, vSrc reflect.Value, err error) {
+	if dst == nil || src == nil {
+		err = ErrNilArguments
+		return
+	}
+	vDst = reflect.ValueOf(dst).Elem()
+	if vDst.Kind() != reflect.Struct && vDst.Kind() != reflect.Map {
+		err = ErrNotSupported
+		return
+	}
+	vSrc = reflect.ValueOf(src)
+	// We check if vSrc is a pointer to dereference it.
+	if vSrc.Kind() == reflect.Ptr {
+		vSrc = vSrc.Elem()
+	}
+	return
+}
+
+// Traverses recursively both values, assigning src's fields values to dst.
+// The map argument tracks comparisons that have already been seen, which allows
+// short circuiting on recursive types.
+func deeper(dst, src reflect.Value, visited map[uintptr]*visit, depth int) (err error) {
+	if dst.CanAddr() {
+		addr := dst.UnsafeAddr()
+		h := 17 * addr
+		seen := visited[h]
+		typ := dst.Type()
+		for p := seen; p != nil; p = p.next {
+			if p.ptr == addr && p.typ == typ {
+				return nil
+			}
+		}
+		// Remember, remember...
+		visited[h] = &visit{addr, typ, seen}
+	}
+	return // TODO refactor
+}