Allow to set daemon and server configurations in a file.
Read configuration after flags making this the priority: 1- Apply configuration from file. 2- Apply configuration from flags. Reload configuration when a signal is received, USR2 in Linux: - Reload router if the debug configuration changes. - Reload daemon labels. - Reload cluster discovery. Signed-off-by: David Calavera <david.calavera@gmail.com>
This commit is contained in:
parent
22a81a2c58
commit
677a6b3506
|
@ -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)
|
||||||
|
}
|
|
@ -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"
|
||||||
|
@ -46,6 +45,7 @@ type Server struct {
|
||||||
servers []*HTTPServer
|
servers []*HTTPServer
|
||||||
routers []router.Router
|
routers []router.Router
|
||||||
authZPlugins []authorization.Plugin
|
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
|
// serveAPI loops through all initialized servers and spawns goroutine
|
||||||
// with Server method for each. It sets CreateMux() as Handler also.
|
// with Server method for each. It sets createMux() as Handler also.
|
||||||
func (s *Server) ServeAPI() error {
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
216
daemon/config.go
216
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
|
AuthorizationPlugins []string `json:"authorization-plugins,omitempty"` // AuthorizationPlugins holds list of authorization plugins
|
||||||
AutoRestart bool
|
AutoRestart bool `json:"-"`
|
||||||
Bridge bridgeConfig // Bridge holds bridge network specific configuration.
|
Bridge bridgeConfig `json:"-"` // Bridge holds bridge network specific configuration.
|
||||||
Context map[string][]string
|
Context map[string][]string `json:"-"`
|
||||||
DisableBridge bool
|
DisableBridge bool `json:"-"`
|
||||||
DNS []string
|
DNS []string `json:"dns,omitempty"`
|
||||||
DNSOptions []string
|
DNSOptions []string `json:"dns-opts,omitempty"`
|
||||||
DNSSearch []string
|
DNSSearch []string `json:"dns-search,omitempty"`
|
||||||
ExecOptions []string
|
ExecOptions []string `json:"exec-opts,omitempty"`
|
||||||
ExecRoot string
|
ExecRoot string `json:"exec-root,omitempty"`
|
||||||
GraphDriver string
|
GraphDriver string `json:"storage-driver,omitempty"`
|
||||||
GraphOptions []string
|
GraphOptions []string `json:"storage-opts,omitempty"`
|
||||||
Labels []string
|
Labels []string `json:"labels,omitempty"`
|
||||||
LogConfig container.LogConfig
|
LogConfig LogConfig `json:"log-config,omitempty"`
|
||||||
Mtu int
|
Mtu int `json:"mtu,omitempty"`
|
||||||
Pidfile string
|
Pidfile string `json:"pidfile,omitempty"`
|
||||||
RemappedRoot string
|
Root string `json:"graph,omitempty"`
|
||||||
Root string
|
TrustKeyPath string `json:"-"`
|
||||||
TrustKeyPath string
|
|
||||||
|
|
||||||
// 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.NewNamedListOptsRef("storage-opts", &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.NewNamedListOptsRef("authorization-plugins", &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("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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
CorsHeaders string `json:"api-cors-headers,omitempty"`
|
||||||
EnableCors bool
|
EnableCors bool `json:"api-enable-cors,omitempty"`
|
||||||
EnableSelinuxSupport bool
|
EnableSelinuxSupport bool `json:"selinux-enabled,omitempty"`
|
||||||
RemappedRoot string
|
RemappedRoot string `json:"userns-remap,omitempty"`
|
||||||
SocketGroup string
|
SocketGroup string `json:"group,omitempty"`
|
||||||
CgroupParent string
|
CgroupParent string `json:"cgroup-parent,omitempty"`
|
||||||
Ulimits map[string]*units.Ulimit
|
Ulimits map[string]*units.Ulimit `json:"default-ulimits,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// bridgeConfig stores all the bridge driver specific
|
// bridgeConfig stores all the bridge driver specific
|
||||||
|
|
|
@ -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 err := d.initDiscovery(config); err != nil {
|
||||||
if config.ClusterStore != "" && config.ClusterAdvertise != "" {
|
return nil, err
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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) {
|
func initDiscovery(backendAddress, advertiseAddress string, clusterOpts map[string]string) (discoveryReloader, error) {
|
||||||
|
heartbeat, backend, err := parseDiscoveryOptions(backendAddress, clusterOpts)
|
||||||
heartbeat, ttl, err := discoveryOpts(clusterOpts)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
discoveryBackend, err := discovery.New(backend, heartbeat, ttl, clusterOpts)
|
reloader := &daemonDiscoveryReloader{
|
||||||
if err != nil {
|
backend: backend,
|
||||||
return nil, err
|
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)
|
reloader.advertise(advertiseAddress)
|
||||||
return discoveryBackend, nil
|
return reloader, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerAddr(backend discovery.Backend, addr string) {
|
func (d *daemonDiscoveryReloader) advertise(address string) {
|
||||||
if err := backend.Register(addr); err != nil {
|
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) {
|
func (d *daemonDiscoveryReloader) advertiseHeartbeat(address string) {
|
||||||
registerAddr(discoveryBackend, address)
|
for {
|
||||||
for range time.Tick(heartbeat) {
|
select {
|
||||||
registerAddr(discoveryBackend, address)
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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
|
||||||
|
|
119
docker/daemon.go
119
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 cli.Config.TLS {
|
||||||
if !commonFlags.TLSOptions.InsecureSkipVerify {
|
tlsOptions := tlsconfig.Options{
|
||||||
// server requires and verifies client's certificate
|
CAFile: cli.Config.TLSOptions.CAFile,
|
||||||
commonFlags.TLSOptions.ClientAuth = tls.RequireAndVerifyClientCert
|
CertFile: cli.Config.TLSOptions.CertFile,
|
||||||
|
KeyFile: cli.Config.TLSOptions.KeyFile,
|
||||||
}
|
}
|
||||||
tlsConfig, err := tlsconfig.Server(*commonFlags.TLSOptions)
|
|
||||||
|
if cli.Config.TLSVerify {
|
||||||
|
// server requires and verifies client's certificate
|
||||||
|
tlsOptions.ClientAuth = tls.RequireAndVerifyClientCert
|
||||||
|
}
|
||||||
|
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 {
|
if len(cli.Config.Hosts) == 0 {
|
||||||
commonFlags.Hosts = make([]string, 1)
|
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 {
|
if cli.Config.Hosts[i], err = opts.ParseHost(defaultHost, cli.Config.Hosts[i]); err != nil {
|
||||||
logrus.Fatalf("error parsing -H %s : %v", commonFlags.Hosts[i], err)
|
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() {
|
go api.Wait(serveAPIWait)
|
||||||
if err := api.ServeAPI(); err != nil {
|
|
||||||
logrus.Errorf("ServeAPI error: %v", err)
|
|
||||||
serveAPIWait <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
serveAPIWait <- nil
|
|
||||||
}()
|
|
||||||
|
|
||||||
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
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
@ -776,7 +777,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
|
||||||
|
@ -794,3 +795,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.
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
opts/opts.go
52
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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -25,6 +25,7 @@ func Init() {
|
||||||
// Initialize sets the heartbeat for the memory backend.
|
// Initialize sets the heartbeat for the memory backend.
|
||||||
func (s *Discovery) Initialize(_ string, heartbeat time.Duration, _ time.Duration, _ map[string]string) error {
|
func (s *Discovery) Initialize(_ string, heartbeat time.Duration, _ time.Duration, _ map[string]string) error {
|
||||||
s.heartbeat = heartbeat
|
s.heartbeat = heartbeat
|
||||||
|
s.values = make([]string, 0)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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") != ""
|
||||||
|
}
|
Loading…
Reference in New Issue