2014-08-09 18:30:27 -04:00
package daemon
2013-10-04 22:25:15 -04:00
import (
2015-12-10 18:35:10 -05:00
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"strings"
"sync"
"github.com/Sirupsen/logrus"
2014-08-09 21:18:32 -04:00
"github.com/docker/docker/opts"
2015-12-10 18:35:10 -05:00
"github.com/docker/docker/pkg/discovery"
2014-08-09 21:18:32 -04:00
flag "github.com/docker/docker/pkg/mflag"
2015-12-10 18:35:10 -05:00
"github.com/imdario/mergo"
2013-10-04 22:25:15 -04:00
)
2014-01-29 21:34:43 -05:00
const (
2014-02-01 06:38:39 -05:00
defaultNetworkMtu = 1500
2014-09-16 13:37:50 -04:00
disableNetworkBridge = "none"
2014-01-29 21:34:43 -05:00
)
2016-02-02 14:33:41 -05:00
// flatOptions contains configuration keys
// that MUST NOT be parsed as deep structures.
// Use this to differentiate these options
// with others like the ones in CommonTLSOptions.
var flatOptions = map [ string ] bool {
"cluster-store-opts" : true ,
"log-opts" : true ,
}
2015-12-10 18:35:10 -05:00
// 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" `
}
2015-04-24 18:36:11 -04:00
// CommonConfig defines the configuration of a docker daemon which are
// common across platforms.
2015-12-10 18:35:10 -05:00
// It includes json tags to deserialize configuration from a file
// using the same names that the flags in the command line uses.
2015-04-24 18:36:11 -04:00
type CommonConfig struct {
2015-12-10 18:35:10 -05:00
AuthorizationPlugins [ ] string ` json:"authorization-plugins,omitempty" ` // AuthorizationPlugins holds list of authorization plugins
AutoRestart bool ` json:"-" `
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" `
Mtu int ` json:"mtu,omitempty" `
Pidfile string ` json:"pidfile,omitempty" `
2015-12-13 05:10:41 -05:00
RawLogs bool ` json:"raw-logs,omitempty" `
2015-12-10 18:35:10 -05:00
Root string ` json:"graph,omitempty" `
2016-01-30 21:45:49 -05:00
SocketGroup string ` json:"group,omitempty" `
2015-12-10 18:35:10 -05:00
TrustKeyPath string ` json:"-" `
2015-09-10 19:12:00 -04:00
// 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
// mechanism.
2015-12-10 18:35:10 -05:00
ClusterStore string ` json:"cluster-store,omitempty" `
2015-09-10 19:12:00 -04:00
2015-09-28 19:22:57 -04:00
// ClusterOpts is used to pass options to the discovery package for tuning libkv settings, such
// as TLS configuration settings.
2015-12-10 18:35:10 -05:00
ClusterOpts map [ string ] string ` json:"cluster-store-opts,omitempty" `
2015-09-28 19:22:57 -04:00
2015-09-10 19:12:00 -04:00
// 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
// reachable by other hosts.
2015-12-10 18:35:10 -05:00
ClusterAdvertise string ` json:"cluster-advertise,omitempty" `
2016-01-22 13:14:48 -05:00
Debug bool ` json:"debug,omitempty" `
Hosts [ ] string ` json:"hosts,omitempty" `
LogLevel string ` json:"log-level,omitempty" `
TLS bool ` json:"tls,omitempty" `
TLSVerify bool ` json:"tlsverify,omitempty" `
// Embedded structs that allow config
// deserialization without the full struct.
CommonTLSOptions
LogConfig
2016-01-25 16:30:33 -05:00
bridgeConfig // bridgeConfig holds bridge network specific configuration.
2015-12-10 18:35:10 -05:00
reloadLock sync . Mutex
2016-01-19 14:16:07 -05:00
valuesSet map [ string ] interface { }
2013-10-04 22:25:15 -04:00
}
2013-10-21 12:04:42 -04:00
2015-04-24 18:36:11 -04:00
// InstallCommonFlags adds command-line options to the top-level flag parser for
2014-08-09 21:18:32 -04:00
// the current process.
// Subsequent calls to `flag.Parse` will populate config with values parsed
// from the command-line.
2015-05-05 00:18:28 -04:00
func ( config * Config ) InstallCommonFlags ( cmd * flag . FlagSet , usageFn func ( string ) string ) {
2015-12-10 18:35:10 -05:00
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" ) )
2015-05-05 00:18:28 -04:00
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 . ExecRoot , [ ] string { "-exec-root" } , "/var/run/docker" , usageFn ( "Root of the Docker execdriver" ) )
cmd . BoolVar ( & config . AutoRestart , [ ] string { "#r" , "#-restart" } , true , usageFn ( "--restart on the daemon has been deprecated in favor of --restart policies on docker run" ) )
cmd . StringVar ( & config . GraphDriver , [ ] string { "s" , "-storage-driver" } , "" , usageFn ( "Storage driver to use" ) )
cmd . IntVar ( & config . Mtu , [ ] string { "#mtu" , "-mtu" } , 0 , usageFn ( "Set the containers network MTU" ) )
2015-12-13 05:10:41 -05:00
cmd . BoolVar ( & config . RawLogs , [ ] string { "-raw-logs" } , false , usageFn ( "Full timestamps without ANSI coloring" ) )
2014-08-09 21:18:32 -04:00
// FIXME: why the inconsistency between "hosts" and "sockets"?
2015-07-30 17:01:53 -04:00
cmd . Var ( opts . NewListOptsRef ( & config . DNS , opts . ValidateIPAddress ) , [ ] string { "#dns" , "-dns" } , usageFn ( "DNS server to use" ) )
2015-12-10 18:35:10 -05:00
cmd . Var ( opts . NewNamedListOptsRef ( "dns-opts" , & config . DNSOptions , nil ) , [ ] string { "-dns-opt" } , usageFn ( "DNS options to use" ) )
2015-07-30 17:01:53 -04:00
cmd . Var ( opts . NewListOptsRef ( & config . DNSSearch , opts . ValidateDNSSearch ) , [ ] string { "-dns-search" } , usageFn ( "DNS search domains to use" ) )
2015-12-10 18:35:10 -05:00
cmd . Var ( opts . NewNamedListOptsRef ( "labels" , & config . Labels , opts . ValidateLabel ) , [ ] string { "-label" } , usageFn ( "Set key=value labels to the daemon" ) )
2015-05-05 00:18:28 -04:00
cmd . StringVar ( & config . LogConfig . Type , [ ] string { "-log-driver" } , "json-file" , usageFn ( "Default driver for container logs" ) )
2015-12-10 18:35:10 -05:00
cmd . Var ( opts . NewNamedMapOpts ( "log-opts" , config . LogConfig . Config , nil ) , [ ] string { "-log-opt" } , usageFn ( "Set log driver options" ) )
2015-10-25 20:12:22 -04:00
cmd . StringVar ( & config . ClusterAdvertise , [ ] string { "-cluster-advertise" } , "" , usageFn ( "Address or interface name to advertise" ) )
2015-09-10 19:12:00 -04:00
cmd . StringVar ( & config . ClusterStore , [ ] string { "-cluster-store" } , "" , usageFn ( "Set the cluster store" ) )
2015-12-10 18:35:10 -05:00
cmd . Var ( opts . NewNamedMapOpts ( "cluster-store-opts" , config . ClusterOpts , nil ) , [ ] string { "-cluster-store-opt" } , usageFn ( "Set cluster store options" ) )
}
2016-01-19 14:16:07 -05:00
// IsValueSet returns true if a configuration value
// was explicitly set in the configuration file.
func ( config * Config ) IsValueSet ( name string ) bool {
if config . valuesSet == nil {
return false
}
_ , ok := config . valuesSet [ name ]
return ok
}
2015-12-10 18:35:10 -05:00
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.
2016-02-18 16:55:03 -05:00
func ReloadConfiguration ( configFile string , flags * flag . FlagSet , reload func ( * Config ) ) error {
2015-12-10 18:35:10 -05:00
logrus . Infof ( "Got signal to reload configuration, reloading from: %s" , configFile )
newConfig , err := getConflictFreeConfiguration ( configFile , flags )
if err != nil {
2016-02-18 16:55:03 -05:00
return err
2015-12-10 18:35:10 -05:00
}
2016-02-18 16:55:03 -05:00
reload ( newConfig )
return nil
}
// boolValue is an interface that boolean value flags implement
// to tell the command line how to make -name equivalent to -name=true.
type boolValue interface {
IsBoolFlag ( ) bool
2015-12-10 18:35:10 -05:00
}
// 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
}
2016-01-19 14:16:07 -05:00
var config Config
2015-12-10 18:35:10 -05:00
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
}
2016-01-19 14:16:07 -05:00
configSet := configValuesSet ( jsonConfig )
if err := findConfigurationConflicts ( configSet , flags ) ; err != nil {
2015-12-10 18:35:10 -05:00
return nil , err
}
2016-01-19 14:16:07 -05:00
2016-02-18 16:55:03 -05:00
// Override flag values to make sure the values set in the config file with nullable values, like `false`,
// are not overriden by default truthy values from the flags that were not explicitly set.
// See https://github.com/docker/docker/issues/20289 for an example.
//
// TODO: Rewrite configuration logic to avoid same issue with other nullable values, like numbers.
namedOptions := make ( map [ string ] interface { } )
for key , value := range configSet {
f := flags . Lookup ( "-" + key )
if f == nil { // ignore named flags that don't match
namedOptions [ key ] = value
continue
}
if _ , ok := f . Value . ( boolValue ) ; ok {
f . Value . Set ( fmt . Sprintf ( "%v" , value ) )
}
}
if len ( namedOptions ) > 0 {
// set also default for mergeVal flags that are boolValue at the same time.
flags . VisitAll ( func ( f * flag . Flag ) {
if opt , named := f . Value . ( opts . NamedOption ) ; named {
v , set := namedOptions [ opt . Name ( ) ]
_ , boolean := f . Value . ( boolValue )
if set && boolean {
f . Value . Set ( fmt . Sprintf ( "%v" , v ) )
}
}
} )
}
2016-01-19 14:16:07 -05:00
config . valuesSet = configSet
2015-12-10 18:35:10 -05:00
}
reader = bytes . NewReader ( b )
err = json . NewDecoder ( reader ) . Decode ( & config )
return & config , err
}
2016-01-19 14:16:07 -05:00
// configValuesSet returns the configuration values explicitly set in the file.
func configValuesSet ( config map [ string ] interface { } ) map [ string ] interface { } {
2015-12-10 18:35:10 -05:00
flatten := make ( map [ string ] interface { } )
for k , v := range config {
2016-02-02 14:33:41 -05:00
if m , isMap := v . ( map [ string ] interface { } ) ; isMap && ! flatOptions [ k ] {
2015-12-10 18:35:10 -05:00
for km , vm := range m {
flatten [ km ] = vm
}
2016-02-02 14:33:41 -05:00
continue
2015-12-10 18:35:10 -05:00
}
2016-02-02 14:33:41 -05:00
flatten [ k ] = v
2015-12-10 18:35:10 -05:00
}
2016-01-19 14:16:07 -05:00
return flatten
}
// findConfigurationConflicts iterates over the provided flags searching for
2016-01-20 17:16:49 -05:00
// duplicated configurations and unknown keys. It returns an error with all the conflicts if
2016-01-19 14:16:07 -05:00
// it finds any.
func findConfigurationConflicts ( config map [ string ] interface { } , flags * flag . FlagSet ) error {
2016-01-20 17:16:49 -05:00
// 1. Search keys from the file that we don't recognize as flags.
unknownKeys := make ( map [ string ] interface { } )
for key , value := range config {
flagName := "-" + key
if flag := flags . Lookup ( flagName ) ; flag == nil {
unknownKeys [ key ] = value
}
}
2016-01-22 13:14:48 -05:00
// 2. Discard values that implement NamedOption.
// Their configuration name differs from their flag name, like `labels` and `label`.
2016-02-18 16:55:03 -05:00
if len ( unknownKeys ) > 0 {
unknownNamedConflicts := func ( f * flag . Flag ) {
if namedOption , ok := f . Value . ( opts . NamedOption ) ; ok {
if _ , valid := unknownKeys [ namedOption . Name ( ) ] ; valid {
delete ( unknownKeys , namedOption . Name ( ) )
}
2016-01-20 17:16:49 -05:00
}
}
2016-02-18 16:55:03 -05:00
flags . VisitAll ( unknownNamedConflicts )
2016-01-20 17:16:49 -05:00
}
if len ( unknownKeys ) > 0 {
var unknown [ ] string
for key := range unknownKeys {
unknown = append ( unknown , key )
}
return fmt . Errorf ( "the following directives don't match any configuration option: %s" , strings . Join ( unknown , ", " ) )
}
2015-12-10 18:35:10 -05:00
2016-01-20 17:16:49 -05:00
var conflicts [ ] string
2015-12-10 18:35:10 -05:00
printConflict := func ( name string , flagValue , fileValue interface { } ) string {
return fmt . Sprintf ( "%s: (from flag: %v, from file: %v)" , name , flagValue , fileValue )
}
2016-01-20 17:16:49 -05:00
// 3. Search keys that are present as a flag and as a file option.
duplicatedConflicts := func ( f * flag . Flag ) {
2015-12-10 18:35:10 -05:00
// search option name in the json configuration payload if the value is a named option
if namedOption , ok := f . Value . ( opts . NamedOption ) ; ok {
2016-01-19 14:16:07 -05:00
if optsValue , ok := config [ namedOption . Name ( ) ] ; ok {
2015-12-10 18:35:10 -05:00
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 , "-" )
2016-01-19 14:16:07 -05:00
if value , ok := config [ name ] ; ok {
2015-12-10 18:35:10 -05:00
conflicts = append ( conflicts , printConflict ( name , f . Value . String ( ) , value ) )
break
}
}
}
}
2016-01-20 17:16:49 -05:00
flags . Visit ( duplicatedConflicts )
2015-12-10 18:35:10 -05:00
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
2014-08-09 21:18:32 -04:00
}