From fb83394714a9797f8ca5a08023a89560ce6c4aa3 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Tue, 21 Jun 2016 16:42:47 -0400 Subject: [PATCH] Convert dockerd to use cobra and pflag Signed-off-by: Daniel Nephin --- cli/flags/client.go | 8 ++- cli/flags/common.go | 75 +++++++++++------------- cmd/docker/docker.go | 1 + cmd/dockerd/daemon.go | 91 ++++++++++++----------------- cmd/dockerd/docker.go | 106 ++++++++++++++++++++-------------- daemon/config.go | 85 +++++++++++++++------------ daemon/config_experimental.go | 6 +- daemon/config_solaris.go | 10 ++-- daemon/config_stub.go | 6 +- daemon/config_unix.go | 70 +++++++++++----------- opts/ip.go | 5 ++ registry/config.go | 12 ++-- registry/config_unix.go | 4 +- registry/config_windows.go | 2 +- runconfig/opts/runtime.go | 5 ++ 15 files changed, 253 insertions(+), 233 deletions(-) diff --git a/cli/flags/client.go b/cli/flags/client.go index cc7309db4b..eadbc143b7 100644 --- a/cli/flags/client.go +++ b/cli/flags/client.go @@ -1,11 +1,13 @@ package flags -import flag "github.com/docker/docker/pkg/mflag" +import ( + "github.com/spf13/pflag" +) // ClientFlags represents flags for the docker client. type ClientFlags struct { - FlagSet *flag.FlagSet - Common *CommonFlags + FlagSet *pflag.FlagSet + Common *CommonOptions PostParse func() ConfigDir string diff --git a/cli/flags/common.go b/cli/flags/common.go index 4726b04f2a..a3579bff6d 100644 --- a/cli/flags/common.go +++ b/cli/flags/common.go @@ -8,8 +8,8 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/cliconfig" "github.com/docker/docker/opts" - flag "github.com/docker/docker/pkg/mflag" "github.com/docker/go-connections/tlsconfig" + "github.com/spf13/pflag" ) const ( @@ -21,8 +21,8 @@ const ( DefaultKeyFile = "key.pem" // DefaultCertFile is the default filename for the cert pem file DefaultCertFile = "cert.pem" - // TLSVerifyKey is the default flag name for the tls verification option - TLSVerifyKey = "tlsverify" + // FlagTLSVerify is the flag name for the tls verification option + FlagTLSVerify = "tlsverify" ) var ( @@ -30,11 +30,8 @@ var ( dockerTLSVerify = os.Getenv("DOCKER_TLS_VERIFY") != "" ) -// CommonFlags are flags common to both the client and the daemon. -type CommonFlags struct { - FlagSet *flag.FlagSet - PostParse func() - +// CommonOptions are options common to both the client and the daemon. +type CommonOptions struct { Debug bool Hosts []string LogLevel string @@ -44,62 +41,60 @@ type CommonFlags struct { TrustKey string } -// InitCommonFlags initializes flags common to both client and daemon -func InitCommonFlags() *CommonFlags { - var commonFlags = &CommonFlags{FlagSet: new(flag.FlagSet)} +// NewCommonOptions returns a new CommonOptions +func NewCommonOptions() *CommonOptions { + return &CommonOptions{ + TLSOptions: &tlsconfig.Options{}, + } +} +// InstallFlags adds flags for the common options on the FlagSet +func (commonOpts *CommonOptions) InstallFlags(flags *pflag.FlagSet) { if dockerCertPath == "" { dockerCertPath = cliconfig.ConfigDir() } - commonFlags.PostParse = func() { postParseCommon(commonFlags) } + flags.BoolVarP(&commonOpts.Debug, "debug", "D", false, "Enable debug mode") + flags.StringVarP(&commonOpts.LogLevel, "log-level", "l", "info", "Set the logging level") + flags.BoolVar(&commonOpts.TLS, "tls", false, "Use TLS; implied by --tlsverify") + flags.BoolVar(&commonOpts.TLSVerify, FlagTLSVerify, dockerTLSVerify, "Use TLS and verify the remote") - cmd := commonFlags.FlagSet + // TODO use flag flags.String("identity"}, "i", "", "Path to libtrust key file") - cmd.BoolVar(&commonFlags.Debug, []string{"D", "-debug"}, false, "Enable debug mode") - cmd.StringVar(&commonFlags.LogLevel, []string{"l", "-log-level"}, "info", "Set the logging level") - cmd.BoolVar(&commonFlags.TLS, []string{"-tls"}, false, "Use TLS; implied by --tlsverify") - cmd.BoolVar(&commonFlags.TLSVerify, []string{"-tlsverify"}, dockerTLSVerify, "Use TLS and verify the remote") + tlsOptions := commonOpts.TLSOptions + flags.StringVar(&tlsOptions.CAFile, "tlscacert", filepath.Join(dockerCertPath, DefaultCaFile), "Trust certs signed only by this CA") + flags.StringVar(&tlsOptions.CertFile, "tlscert", filepath.Join(dockerCertPath, DefaultCertFile), "Path to TLS certificate file") + flags.StringVar(&tlsOptions.KeyFile, "tlskey", filepath.Join(dockerCertPath, DefaultKeyFile), "Path to TLS key file") - // TODO use flag flag.String([]string{"i", "-identity"}, "", "Path to libtrust key file") - - var tlsOptions tlsconfig.Options - commonFlags.TLSOptions = &tlsOptions - cmd.StringVar(&tlsOptions.CAFile, []string{"-tlscacert"}, filepath.Join(dockerCertPath, DefaultCaFile), "Trust certs signed only by this CA") - 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.Var(opts.NewNamedListOptsRef("hosts", &commonFlags.Hosts, opts.ValidateHost), []string{"H", "-host"}, "Daemon socket(s) to connect to") - return commonFlags + hostOpt := opts.NewNamedListOptsRef("hosts", &commonOpts.Hosts, opts.ValidateHost) + flags.VarP(hostOpt, "-host", "H", "Daemon socket(s) to connect to") } -func postParseCommon(commonFlags *CommonFlags) { - cmd := commonFlags.FlagSet - - SetDaemonLogLevel(commonFlags.LogLevel) - +// SetDefaultOptions sets default values for options after flag parsing is +// complete +func (commonOpts *CommonOptions) SetDefaultOptions(flags *pflag.FlagSet) { // Regardless of whether the user sets it to true or false, if they // 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 - if cmd.IsSet("-"+TLSVerifyKey) || commonFlags.TLSVerify { - commonFlags.TLS = true + if flags.Changed(FlagTLSVerify) || commonOpts.TLSVerify { + commonOpts.TLS = true } - if !commonFlags.TLS { - commonFlags.TLSOptions = nil + if !commonOpts.TLS { + commonOpts.TLSOptions = nil } else { - tlsOptions := commonFlags.TLSOptions - tlsOptions.InsecureSkipVerify = !commonFlags.TLSVerify + tlsOptions := commonOpts.TLSOptions + tlsOptions.InsecureSkipVerify = !commonOpts.TLSVerify // Reset CertFile and KeyFile to empty string if the user did not specify // the respective flags and the respective default files were not found. - if !cmd.IsSet("-tlscert") { + if !flags.Changed("tlscert") { if _, err := os.Stat(tlsOptions.CertFile); os.IsNotExist(err) { tlsOptions.CertFile = "" } } - if !cmd.IsSet("-tlskey") { + if !flags.Changed("tlskey") { if _, err := os.Stat(tlsOptions.KeyFile); os.IsNotExist(err) { tlsOptions.KeyFile = "" } diff --git a/cmd/docker/docker.go b/cmd/docker/docker.go index 0c727e32c9..7346d913ef 100644 --- a/cmd/docker/docker.go +++ b/cmd/docker/docker.go @@ -101,6 +101,7 @@ func initClientFlags(commonFlags *cliflags.CommonFlags) *cliflags.ClientFlags { clientFlags.PostParse = func() { clientFlags.Common.PostParse() + cliflags.SetDaemonLogLevel(commonOpts.LogLevel) if clientFlags.ConfigDir != "" { cliconfig.SetConfigDir(clientFlags.ConfigDir) diff --git a/cmd/dockerd/daemon.go b/cmd/dockerd/daemon.go index 53e7e61dfb..fb6524277f 100644 --- a/cmd/dockerd/daemon.go +++ b/cmd/dockerd/daemon.go @@ -6,7 +6,6 @@ import ( "io" "os" "path/filepath" - "runtime" "strings" "time" @@ -31,11 +30,10 @@ import ( "github.com/docker/docker/daemon/logger" "github.com/docker/docker/dockerversion" "github.com/docker/docker/libcontainerd" - "github.com/docker/docker/opts" + dopts "github.com/docker/docker/opts" "github.com/docker/docker/pkg/authorization" "github.com/docker/docker/pkg/jsonlog" "github.com/docker/docker/pkg/listeners" - flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/pidfile" "github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/system" @@ -43,46 +41,27 @@ import ( "github.com/docker/docker/runconfig" "github.com/docker/docker/utils" "github.com/docker/go-connections/tlsconfig" + "github.com/spf13/pflag" ) const ( - daemonConfigFileFlag = "-config-file" + flagDaemonConfigFile = "config-file" ) // DaemonCli represents the daemon CLI. type DaemonCli struct { *daemon.Config - commonFlags *cliflags.CommonFlags - configFile *string + configFile *string + flags *pflag.FlagSet api *apiserver.Server d *daemon.Daemon authzMiddleware *authorization.Middleware // authzMiddleware enables to dynamically reload the authorization plugins } -func presentInHelp(usage string) string { return usage } -func absentFromHelp(string) string { return "" } - -// NewDaemonCli returns a pre-configured daemon CLI +// NewDaemonCli returns a daemon CLI func NewDaemonCli() *DaemonCli { - // TODO(tiborvass): remove InstallFlags? - daemonConfig := new(daemon.Config) - daemonConfig.LogConfig.Config = make(map[string]string) - daemonConfig.ClusterOpts = make(map[string]string) - - daemonConfig.InstallFlags(flag.CommandLine, presentInHelp) - configFile := flag.CommandLine.String([]string{daemonConfigFileFlag}, defaultDaemonConfigFile, "Daemon configuration file") - flag.CommandLine.Require(flag.Exact, 0) - - if runtime.GOOS != "linux" { - daemonConfig.V2Only = true - } - - return &DaemonCli{ - Config: daemonConfig, - commonFlags: cliflags.InitCommonFlags(), - configFile: configFile, - } + return &DaemonCli{} } func migrateKey() (err error) { @@ -126,24 +105,25 @@ func migrateKey() (err error) { return nil } -func (cli *DaemonCli) start() (err error) { +func (cli *DaemonCli) start(opts daemonOptions) (err error) { stopc := make(chan bool) defer close(stopc) // warn from uuid package when running the daemon uuid.Loggerf = logrus.Warnf - flags := flag.CommandLine - cli.commonFlags.PostParse() + opts.common.SetDefaultOptions(opts.flags) - if cli.commonFlags.TrustKey == "" { - cli.commonFlags.TrustKey = filepath.Join(getDaemonConfDir(), cliflags.DefaultTrustKeyFile) + if opts.common.TrustKey == "" { + opts.common.TrustKey = filepath.Join( + getDaemonConfDir(), + cliflags.DefaultTrustKeyFile) } - cliConfig, err := loadDaemonCliConfig(cli.Config, flags, cli.commonFlags, *cli.configFile) - if err != nil { + if cli.Config, err = loadDaemonCliConfig(opts); err != nil { return err } - cli.Config = cliConfig + cli.configFile = &opts.configFile + cli.flags = opts.flags if cli.Config.Debug { utils.EnableDebug() @@ -215,7 +195,7 @@ func (cli *DaemonCli) start() (err error) { for i := 0; i < len(cli.Config.Hosts); i++ { var err error - if cli.Config.Hosts[i], err = opts.ParseHost(cli.Config.TLS, cli.Config.Hosts[i]); err != nil { + if cli.Config.Hosts[i], err = dopts.ParseHost(cli.Config.TLS, cli.Config.Hosts[i]); err != nil { return fmt.Errorf("error parsing -H %s : %v", cli.Config.Hosts[i], err) } @@ -250,7 +230,8 @@ func (cli *DaemonCli) start() (err error) { if err := migrateKey(); err != nil { return err } - cli.TrustKeyPath = cli.commonFlags.TrustKey + // FIXME: why is this down here instead of with the other TrustKey logic above? + cli.TrustKeyPath = opts.common.TrustKey registryService := registry.NewService(cli.Config.ServiceOptions) containerdRemote, err := libcontainerd.New(cli.getLibcontainerdRoot(), cli.getPlatformRemoteOptions()...) @@ -341,7 +322,7 @@ func (cli *DaemonCli) reloadConfig() { } } - if err := daemon.ReloadConfiguration(*cli.configFile, flag.CommandLine, reload); err != nil { + if err := daemon.ReloadConfiguration(*cli.configFile, cli.flags, reload); err != nil { logrus.Error(err) } } @@ -367,25 +348,27 @@ func shutdownDaemon(d *daemon.Daemon, timeout time.Duration) { } } -func loadDaemonCliConfig(config *daemon.Config, flags *flag.FlagSet, commonConfig *cliflags.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 +func loadDaemonCliConfig(opts daemonOptions) (*daemon.Config, error) { + config := opts.daemonConfig + flags := opts.flags + config.Debug = opts.common.Debug + config.Hosts = opts.common.Hosts + config.LogLevel = opts.common.LogLevel + config.TLS = opts.common.TLS + config.TLSVerify = opts.common.TLSVerify config.CommonTLSOptions = daemon.CommonTLSOptions{} - if commonConfig.TLSOptions != nil { - config.CommonTLSOptions.CAFile = commonConfig.TLSOptions.CAFile - config.CommonTLSOptions.CertFile = commonConfig.TLSOptions.CertFile - config.CommonTLSOptions.KeyFile = commonConfig.TLSOptions.KeyFile + if opts.common.TLSOptions != nil { + config.CommonTLSOptions.CAFile = opts.common.TLSOptions.CAFile + config.CommonTLSOptions.CertFile = opts.common.TLSOptions.CertFile + config.CommonTLSOptions.KeyFile = opts.common.TLSOptions.KeyFile } - if configFile != "" { - c, err := daemon.MergeDaemonConfigurations(config, flags, configFile) + if opts.configFile != "" { + c, err := daemon.MergeDaemonConfigurations(config, flags, opts.configFile) if err != nil { - if flags.IsSet(daemonConfigFileFlag) || !os.IsNotExist(err) { - return nil, fmt.Errorf("unable to configure the Docker daemon with file %s: %v\n", configFile, err) + if flags.Changed(flagDaemonConfigFile) || !os.IsNotExist(err) { + return nil, fmt.Errorf("unable to configure the Docker daemon with file %s: %v\n", opts.configFile, err) } } // the merged configuration can be nil if the config file didn't exist. @@ -401,7 +384,7 @@ func loadDaemonCliConfig(config *daemon.Config, flags *flag.FlagSet, commonConfi // Regardless of whether the user sets it to true or false, if they // specify TLSVerify at all then we need to turn on TLS - if config.IsValueSet(cliflags.TLSVerifyKey) { + if config.IsValueSet(cliflags.FlagTLSVerify) { config.TLS = true } diff --git a/cmd/dockerd/docker.go b/cmd/dockerd/docker.go index b1c9b803a1..cf55c0fd84 100644 --- a/cmd/dockerd/docker.go +++ b/cmd/dockerd/docker.go @@ -2,59 +2,61 @@ package main import ( "fmt" - "os" "github.com/Sirupsen/logrus" + "github.com/docker/docker/cli" + cliflags "github.com/docker/docker/cli/flags" + "github.com/docker/docker/daemon" "github.com/docker/docker/dockerversion" - flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/reexec" "github.com/docker/docker/pkg/term" "github.com/docker/docker/utils" + "github.com/spf13/cobra" + "github.com/spf13/pflag" ) -var ( - daemonCli = NewDaemonCli() - flHelp = flag.Bool([]string{"h", "-help"}, false, "Print usage") - flVersion = flag.Bool([]string{"v", "-version"}, false, "Print version information and quit") -) +type daemonOptions struct { + version bool + configFile string + daemonConfig *daemon.Config + common *cliflags.CommonOptions + flags *pflag.FlagSet +} -func main() { - if reexec.Init() { - return +func newDaemonCommand() *cobra.Command { + opts := daemonOptions{ + daemonConfig: daemon.NewConfig(), + common: cliflags.NewCommonOptions(), } - // Set terminal emulation based on platform as required. - _, stdout, stderr := term.StdStreams() - - logrus.SetOutput(stderr) - - flag.Merge(flag.CommandLine, daemonCli.commonFlags.FlagSet) - - flag.Usage = func() { - fmt.Fprint(stdout, "Usage: dockerd [OPTIONS]\n\n") - fmt.Fprint(stdout, "A self-sufficient runtime for containers.\n\nOptions:\n") - - flag.CommandLine.SetOutput(stdout) - flag.PrintDefaults() - } - flag.CommandLine.ShortUsage = func() { - fmt.Fprint(stderr, "\nUsage:\tdockerd [OPTIONS]\n") + cmd := &cobra.Command{ + Use: "dockerd [OPTIONS]", + Short: "A self-sufficient runtime for containers.", + SilenceUsage: true, + SilenceErrors: true, + Args: cli.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + opts.flags = cmd.Flags() + return runDaemon(opts) + }, } + // TODO: SetUsageTemplate, SetHelpTemplate, SetFlagErrorFunc - if err := flag.CommandLine.ParseFlags(os.Args[1:], false); err != nil { - os.Exit(1) - } + flags := cmd.Flags() + flags.BoolP("help", "h", false, "Print usage") + flags.MarkShorthandDeprecated("help", "please use --help") + flags.BoolVarP(&opts.version, "version", "v", false, "Print version information and quit") + flags.StringVar(&opts.configFile, flagDaemonConfigFile, defaultDaemonConfigFile, "Daemon configuration file") + opts.common.InstallFlags(flags) + opts.daemonConfig.InstallFlags(flags) - if *flVersion { + return cmd +} + +func runDaemon(opts daemonOptions) error { + if opts.version { showVersion() - return - } - - if *flHelp { - // if global flag --help is present, regardless of what other options and commands there are, - // just print the usage. - flag.Usage() - return + return nil } // On Windows, this may be launching as a service or with an option to @@ -64,13 +66,13 @@ func main() { logrus.Fatal(err) } - if !stop { - err = daemonCli.start() - notifyShutdown(err) - if err != nil { - logrus.Fatal(err) - } + if stop { + return nil } + + err = NewDaemonCli().start(opts) + notifyShutdown(err) + return err } func showVersion() { @@ -80,3 +82,19 @@ func showVersion() { fmt.Printf("Docker version %s, build %s\n", dockerversion.Version, dockerversion.GitCommit) } } + +func main() { + if reexec.Init() { + return + } + + // Set terminal emulation based on platform as required. + _, stdout, stderr := term.StdStreams() + logrus.SetOutput(stderr) + + cmd := newDaemonCommand() + cmd.SetOutput(stdout) + if err := cmd.Execute(); err != nil { + logrus.Fatal(err) + } +} diff --git a/daemon/config.go b/daemon/config.go index a00f338e44..2f41cabb58 100644 --- a/daemon/config.go +++ b/daemon/config.go @@ -6,15 +6,16 @@ import ( "fmt" "io" "io/ioutil" + "runtime" "strings" "sync" "github.com/Sirupsen/logrus" "github.com/docker/docker/opts" "github.com/docker/docker/pkg/discovery" - flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/registry" "github.com/imdario/mergo" + "github.com/spf13/pflag" ) const ( @@ -145,37 +146,35 @@ type CommonConfig struct { valuesSet map[string]interface{} } -// InstallCommonFlags adds command-line options to the top-level flag parser for -// the current process. -// Subsequent calls to `flag.Parse` will populate config with values parsed -// from the command-line. -func (config *Config) InstallCommonFlags(cmd *flag.FlagSet, usageFn func(string) string) { +// InstallCommonFlags adds flags to the pflag.FlagSet to configure the daemon +func (config *Config) InstallCommonFlags(flags *pflag.FlagSet) { var maxConcurrentDownloads, maxConcurrentUploads int - config.ServiceOptions.InstallCliFlags(cmd, usageFn) + config.ServiceOptions.InstallCliFlags(flags) - cmd.Var(opts.NewNamedListOptsRef("storage-opts", &config.GraphOptions, nil), []string{"-storage-opt"}, usageFn("Storage driver options")) - cmd.Var(opts.NewNamedListOptsRef("authorization-plugins", &config.AuthorizationPlugins, nil), []string{"-authorization-plugin"}, usageFn("Authorization plugins to load")) - cmd.Var(opts.NewNamedListOptsRef("exec-opts", &config.ExecOptions, nil), []string{"-exec-opt"}, usageFn("Runtime execution options")) - 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.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")) - cmd.BoolVar(&config.RawLogs, []string{"-raw-logs"}, false, usageFn("Full timestamps without ANSI coloring")) + flags.Var(opts.NewNamedListOptsRef("storage-opts", &config.GraphOptions, nil), "storage-opt", "Storage driver options") + flags.Var(opts.NewNamedListOptsRef("authorization-plugins", &config.AuthorizationPlugins, nil), "authorization-plugin", "Authorization plugins to load") + flags.Var(opts.NewNamedListOptsRef("exec-opts", &config.ExecOptions, nil), "exec-opt", "Runtime execution options") + flags.StringVarP(&config.Pidfile, "pidfile", "p", defaultPidFile, "Path to use for daemon PID file") + flags.StringVarP(&config.Root, "graph", "g", defaultGraph, "Root of the Docker runtime") + flags.BoolVarP(&config.AutoRestart, "restart", "r", true, "--restart on the daemon has been deprecated in favor of --restart policies on docker run") + flags.MarkDeprecated("restart", "Please use a restart policy on ducker run") + flags.StringVarP(&config.GraphDriver, "storage-driver", "s", "", "Storage driver to use") + flags.IntVar(&config.Mtu, "mtu", 0, "Set the containers network MTU") + flags.BoolVar(&config.RawLogs, "raw-logs", false, "Full timestamps without ANSI coloring") // 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.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.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.Var(opts.NewNamedMapOpts("log-opts", config.LogConfig.Config, nil), []string{"-log-opt"}, usageFn("Default log driver options for containers")) - cmd.StringVar(&config.ClusterAdvertise, []string{"-cluster-advertise"}, "", usageFn("Address or interface name to advertise")) - cmd.StringVar(&config.ClusterStore, []string{"-cluster-store"}, "", usageFn("URL of the distributed storage backend")) - cmd.Var(opts.NewNamedMapOpts("cluster-store-opts", config.ClusterOpts, nil), []string{"-cluster-store-opt"}, usageFn("Set cluster store options")) - cmd.StringVar(&config.CorsHeaders, []string{"-api-cors-header"}, "", usageFn("Set CORS headers in the remote API")) - cmd.IntVar(&maxConcurrentDownloads, []string{"-max-concurrent-downloads"}, defaultMaxConcurrentDownloads, usageFn("Set the max concurrent downloads for each pull")) - cmd.IntVar(&maxConcurrentUploads, []string{"-max-concurrent-uploads"}, defaultMaxConcurrentUploads, usageFn("Set the max concurrent uploads for each push")) + flags.Var(opts.NewListOptsRef(&config.DNS, opts.ValidateIPAddress), "dns", "DNS server to use") + flags.Var(opts.NewNamedListOptsRef("dns-opts", &config.DNSOptions, nil), "dns-opt", "DNS options to use") + flags.Var(opts.NewListOptsRef(&config.DNSSearch, opts.ValidateDNSSearch), "dns-search", "DNS search domains to use") + flags.Var(opts.NewNamedListOptsRef("labels", &config.Labels, opts.ValidateLabel), "label", "Set key=value labels to the daemon") + flags.StringVar(&config.LogConfig.Type, "log-driver", "json-file", "Default driver for container logs") + flags.Var(opts.NewNamedMapOpts("log-opts", config.LogConfig.Config, nil), "log-opt", "Default log driver options for containers") + flags.StringVar(&config.ClusterAdvertise, "cluster-advertise", "", "Address or interface name to advertise") + flags.StringVar(&config.ClusterStore, "cluster-store", "", "URL of the distributed storage backend") + flags.Var(opts.NewNamedMapOpts("cluster-store-opts", config.ClusterOpts, nil), "cluster-store-opt", "Set cluster store options") + flags.StringVar(&config.CorsHeaders, "api-cors-header", "", "Set CORS headers in the remote API") + flags.IntVar(&maxConcurrentDownloads, "max-concurrent-downloads", defaultMaxConcurrentDownloads, "Set the max concurrent downloads for each pull") + flags.IntVar(&maxConcurrentUploads, "max-concurrent-uploads", defaultMaxConcurrentUploads, "Set the max concurrent uploads for each push") cmd.StringVar(&config.SwarmDefaultAdvertiseAddr, []string{"-swarm-default-advertise-addr"}, "", usageFn("Set default address or interface for swarm advertised address")) @@ -193,6 +192,18 @@ func (config *Config) IsValueSet(name string) bool { return ok } +// NewConfig returns a new fully initialized Config struct +func NewConfig() *Config { + config := Config{} + config.LogConfig.Config = make(map[string]string) + config.ClusterOpts = make(map[string]string) + + if runtime.GOOS != "linux" { + config.V2Only = true + } + return &config +} + func parseClusterAdvertiseSettings(clusterStore, clusterAdvertise string) (string, error) { if clusterAdvertise == "" { return "", errDiscoveryDisabled @@ -209,7 +220,7 @@ func parseClusterAdvertiseSettings(clusterStore, clusterAdvertise string) (strin } // ReloadConfiguration reads the configuration in the host and reloads the daemon and server. -func ReloadConfiguration(configFile string, flags *flag.FlagSet, reload func(*Config)) error { +func ReloadConfiguration(configFile string, flags *pflag.FlagSet, reload func(*Config)) error { logrus.Infof("Got signal to reload configuration, reloading from: %s", configFile) newConfig, err := getConflictFreeConfiguration(configFile, flags) if err != nil { @@ -234,7 +245,7 @@ type boolValue interface { // 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) { +func MergeDaemonConfigurations(flagsConfig *Config, flags *pflag.FlagSet, configFile string) (*Config, error) { fileConfig, err := getConflictFreeConfiguration(configFile, flags) if err != nil { return nil, err @@ -261,7 +272,7 @@ func MergeDaemonConfigurations(flagsConfig *Config, flags *flag.FlagSet, configF // 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) { +func getConflictFreeConfiguration(configFile string, flags *pflag.FlagSet) (*Config, error) { b, err := ioutil.ReadFile(configFile) if err != nil { return nil, err @@ -301,7 +312,7 @@ func getConflictFreeConfiguration(configFile string, flags *flag.FlagSet) (*Conf } if len(namedOptions) > 0 { // set also default for mergeVal flags that are boolValue at the same time. - flags.VisitAll(func(f *flag.Flag) { + flags.VisitAll(func(f *pflag.Flag) { if opt, named := f.Value.(opts.NamedOption); named { v, set := namedOptions[opt.Name()] _, boolean := f.Value.(boolValue) @@ -339,7 +350,7 @@ func configValuesSet(config map[string]interface{}) map[string]interface{} { // findConfigurationConflicts iterates over the provided flags searching for // duplicated configurations and unknown keys. It returns an error with all the conflicts if // it finds any. -func findConfigurationConflicts(config map[string]interface{}, flags *flag.FlagSet) error { +func findConfigurationConflicts(config map[string]interface{}, flags *pflag.FlagSet) error { // 1. Search keys from the file that we don't recognize as flags. unknownKeys := make(map[string]interface{}) for key, value := range config { @@ -352,7 +363,7 @@ func findConfigurationConflicts(config map[string]interface{}, flags *flag.FlagS // 2. Discard values that implement NamedOption. // Their configuration name differs from their flag name, like `labels` and `label`. if len(unknownKeys) > 0 { - unknownNamedConflicts := func(f *flag.Flag) { + unknownNamedConflicts := func(f *pflag.Flag) { if namedOption, ok := f.Value.(opts.NamedOption); ok { if _, valid := unknownKeys[namedOption.Name()]; valid { delete(unknownKeys, namedOption.Name()) @@ -376,15 +387,15 @@ func findConfigurationConflicts(config map[string]interface{}, flags *flag.FlagS } // 3. Search keys that are present as a flag and as a file option. - duplicatedConflicts := func(f *flag.Flag) { + duplicatedConflicts := func(f *pflag.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 := config[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 { + // search flag name in the json configuration payload + for _, name := range []string{f.Name, f.Shorthand} { name = strings.TrimLeft(name, "-") if value, ok := config[name]; ok { diff --git a/daemon/config_experimental.go b/daemon/config_experimental.go index ceb7c38225..55e87986b1 100644 --- a/daemon/config_experimental.go +++ b/daemon/config_experimental.go @@ -2,7 +2,9 @@ package daemon -import flag "github.com/docker/docker/pkg/mflag" +import ( + "github.com/spf13/pflag" +) -func (config *Config) attachExperimentalFlags(cmd *flag.FlagSet, usageFn func(string) string) { +func (config *Config) attachExperimentalFlags(cmd *pflag.FlagSet) { } diff --git a/daemon/config_solaris.go b/daemon/config_solaris.go index e59d0514df..e99d1bbf11 100644 --- a/daemon/config_solaris.go +++ b/daemon/config_solaris.go @@ -1,7 +1,7 @@ package daemon import ( - flag "github.com/docker/docker/pkg/mflag" + "github.com/spf13/pflag" ) var ( @@ -28,14 +28,12 @@ type bridgeConfig struct { // InstallFlags adds command-line options to the top-level flag parser for // the current process. -// Subsequent calls to `flag.Parse` will populate config with values parsed -// from the command-line. -func (config *Config) InstallFlags(cmd *flag.FlagSet, usageFn func(string) string) { +func (config *Config) InstallFlags(flags *pflag.FlagSet) { // First handle install flags which are consistent cross-platform - config.InstallCommonFlags(cmd, usageFn) + config.InstallCommonFlags(flags) // Then platform-specific install flags - config.attachExperimentalFlags(cmd, usageFn) + config.attachExperimentalFlags(flags) } // GetExecRoot returns the user configured Exec-root diff --git a/daemon/config_stub.go b/daemon/config_stub.go index 796e6b6e4e..fb64813e23 100644 --- a/daemon/config_stub.go +++ b/daemon/config_stub.go @@ -2,7 +2,9 @@ package daemon -import flag "github.com/docker/docker/pkg/mflag" +import ( + "github.com/spf13/pflag" +) -func (config *Config) attachExperimentalFlags(cmd *flag.FlagSet, usageFn func(string) string) { +func (config *Config) attachExperimentalFlags(cmd *pflag.FlagSet) { } diff --git a/daemon/config_unix.go b/daemon/config_unix.go index 24548a428e..69049de2b7 100644 --- a/daemon/config_unix.go +++ b/daemon/config_unix.go @@ -7,10 +7,10 @@ import ( "net" "github.com/docker/docker/opts" - flag "github.com/docker/docker/pkg/mflag" runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/docker/engine-api/types" - "github.com/docker/go-units" + units "github.com/docker/go-units" + "github.com/spf13/pflag" ) var ( @@ -56,44 +56,42 @@ type bridgeConfig struct { InterContainerCommunication bool `json:"icc,omitempty"` } -// InstallFlags adds command-line options to the top-level flag parser for -// the current process. -// Subsequent calls to `flag.Parse` will populate config with values parsed -// from the command-line. -func (config *Config) InstallFlags(cmd *flag.FlagSet, usageFn func(string) string) { +// InstallFlags adds flags to the pflag.FlagSet to configure the daemon +func (config *Config) InstallFlags(flags *pflag.FlagSet) { // First handle install flags which are consistent cross-platform - config.InstallCommonFlags(cmd, usageFn) + config.InstallCommonFlags(flags) + + config.Ulimits = make(map[string]*units.Ulimit) + config.Runtimes = make(map[string]types.Runtime) // Then platform-specific install flags - cmd.BoolVar(&config.EnableSelinuxSupport, []string{"-selinux-enabled"}, false, usageFn("Enable selinux support")) - cmd.StringVar(&config.SocketGroup, []string{"G", "-group"}, "docker", usageFn("Group for the unix socket")) - config.Ulimits = make(map[string]*units.Ulimit) - cmd.Var(runconfigopts.NewUlimitOpt(&config.Ulimits), []string{"-default-ulimit"}, usageFn("Default ulimits for containers")) - cmd.BoolVar(&config.bridgeConfig.EnableIPTables, []string{"#iptables", "-iptables"}, true, usageFn("Enable addition of iptables rules")) - cmd.BoolVar(&config.bridgeConfig.EnableIPForward, []string{"#ip-forward", "-ip-forward"}, true, usageFn("Enable net.ipv4.ip_forward")) - cmd.BoolVar(&config.bridgeConfig.EnableIPMasq, []string{"-ip-masq"}, true, usageFn("Enable IP masquerading")) - cmd.BoolVar(&config.bridgeConfig.EnableIPv6, []string{"-ipv6"}, false, usageFn("Enable IPv6 networking")) - cmd.StringVar(&config.ExecRoot, []string{"-exec-root"}, defaultExecRoot, usageFn("Root directory for execution state files")) - cmd.StringVar(&config.bridgeConfig.IP, []string{"#bip", "-bip"}, "", usageFn("Specify network bridge IP")) - cmd.StringVar(&config.bridgeConfig.Iface, []string{"b", "-bridge"}, "", usageFn("Attach containers to a network bridge")) - cmd.StringVar(&config.bridgeConfig.FixedCIDR, []string{"-fixed-cidr"}, "", usageFn("IPv4 subnet for fixed IPs")) - cmd.StringVar(&config.bridgeConfig.FixedCIDRv6, []string{"-fixed-cidr-v6"}, "", usageFn("IPv6 subnet for fixed IPs")) - cmd.Var(opts.NewIPOpt(&config.bridgeConfig.DefaultGatewayIPv4, ""), []string{"-default-gateway"}, usageFn("Container default gateway IPv4 address")) - cmd.Var(opts.NewIPOpt(&config.bridgeConfig.DefaultGatewayIPv6, ""), []string{"-default-gateway-v6"}, usageFn("Container default gateway IPv6 address")) - cmd.BoolVar(&config.bridgeConfig.InterContainerCommunication, []string{"#icc", "-icc"}, true, usageFn("Enable inter-container communication")) - cmd.Var(opts.NewIPOpt(&config.bridgeConfig.DefaultIP, "0.0.0.0"), []string{"#ip", "-ip"}, usageFn("Default IP when binding container ports")) - cmd.BoolVar(&config.bridgeConfig.EnableUserlandProxy, []string{"-userland-proxy"}, true, usageFn("Use userland proxy for loopback traffic")) - cmd.BoolVar(&config.EnableCors, []string{"#api-enable-cors", "#-api-enable-cors"}, false, usageFn("Enable CORS headers in the remote API, this is deprecated by --api-cors-header")) - cmd.StringVar(&config.CgroupParent, []string{"-cgroup-parent"}, "", usageFn("Set parent cgroup for all containers")) - cmd.StringVar(&config.RemappedRoot, []string{"-userns-remap"}, "", usageFn("User/Group setting for user namespaces")) - cmd.StringVar(&config.ContainerdAddr, []string{"-containerd"}, "", usageFn("Path to containerd socket")) - cmd.BoolVar(&config.LiveRestoreEnabled, []string{"-live-restore"}, false, usageFn("Enable live restore of docker when containers are still running")) - config.Runtimes = make(map[string]types.Runtime) - cmd.Var(runconfigopts.NewNamedRuntimeOpt("runtimes", &config.Runtimes, stockRuntimeName), []string{"-add-runtime"}, usageFn("Register an additional OCI compatible runtime")) - cmd.StringVar(&config.DefaultRuntime, []string{"-default-runtime"}, stockRuntimeName, usageFn("Default OCI runtime for containers")) - cmd.IntVar(&config.OOMScoreAdjust, []string{"-oom-score-adjust"}, -500, usageFn("Set the oom_score_adj for the daemon")) + flags.BoolVar(&config.EnableSelinuxSupport, "selinux-enabled", false, "Enable selinux support") + flags.Var(runconfigopts.NewUlimitOpt(&config.Ulimits), "default-ulimit", "Default ulimits for containers") + flags.BoolVar(&config.bridgeConfig.EnableIPTables, "iptables", true, "Enable addition of iptables rules") + flags.BoolVar(&config.bridgeConfig.EnableIPForward, "ip-forward", true, "Enable net.ipv4.ip_forward") + flags.BoolVar(&config.bridgeConfig.EnableIPMasq, "ip-masq", true, "Enable IP masquerading") + flags.BoolVar(&config.bridgeConfig.EnableIPv6, "ipv6", false, "Enable IPv6 networking") + flags.StringVar(&config.ExecRoot, "exec-root", defaultExecRoot, "Root directory for execution state files") + flags.StringVar(&config.bridgeConfig.IP, "bip", "", "Specify network bridge IP") + flags.StringVarP(&config.bridgeConfig.Iface, "bridge", "b", "", "Attach containers to a network bridge") + flags.StringVar(&config.bridgeConfig.FixedCIDR, "fixed-cidr", "", "IPv4 subnet for fixed IPs") + flags.StringVar(&config.bridgeConfig.FixedCIDRv6, "fixed-cidr-v6", "", "IPv6 subnet for fixed IPs") + flags.Var(opts.NewIPOpt(&config.bridgeConfig.DefaultGatewayIPv4, ""), "default-gateway", "Container default gateway IPv4 address") + flags.Var(opts.NewIPOpt(&config.bridgeConfig.DefaultGatewayIPv6, ""), "default-gateway-v6", "Container default gateway IPv6 address") + flags.BoolVar(&config.bridgeConfig.InterContainerCommunication, "icc", true, "Enable inter-container communication") + flags.Var(opts.NewIPOpt(&config.bridgeConfig.DefaultIP, "0.0.0.0"), "ip", "Default IP when binding container ports") + flags.BoolVar(&config.bridgeConfig.EnableUserlandProxy, "userland-proxy", true, "Use userland proxy for loopback traffic") + flags.BoolVar(&config.EnableCors, "api-enable-cors", false, "Enable CORS headers in the remote API, this is deprecated by --api-cors-header") + flags.MarkDeprecated("api-enable-cors", "Please use --api-cors-header") + flags.StringVar(&config.CgroupParent, "cgroup-parent", "", "Set parent cgroup for all containers") + flags.StringVar(&config.RemappedRoot, "userns-remap", "", "User/Group setting for user namespaces") + flags.StringVar(&config.ContainerdAddr, "containerd", "", "Path to containerd socket") + flags.BoolVar(&config.LiveRestoreEnabled, "live-restore", false, "Enable live restore of docker when containers are still running") + flags.Var(runconfigopts.NewNamedRuntimeOpt("runtimes", &config.Runtimes, stockRuntimeName), "add-runtime", "Register an additional OCI compatible runtime") + flags.StringVar(&config.DefaultRuntime, "default-runtime", stockRuntimeName, "Default OCI runtime for containers") + flags.IntVar(&config.OOMScoreAdjust, "oom-score-adjust", -500, "Set the oom_score_adj for the daemon") - config.attachExperimentalFlags(cmd, usageFn) + config.attachExperimentalFlags(flags) } // GetRuntime returns the runtime path and arguments for a given diff --git a/opts/ip.go b/opts/ip.go index c7b0dc9947..fb03b50111 100644 --- a/opts/ip.go +++ b/opts/ip.go @@ -40,3 +40,8 @@ func (o *IPOpt) String() string { } return o.IP.String() } + +// Type returns the type of the option +func (o *IPOpt) Type() string { + return "ip" +} diff --git a/registry/config.go b/registry/config.go index 90723bfd51..0adcc9c122 100644 --- a/registry/config.go +++ b/registry/config.go @@ -8,9 +8,9 @@ import ( "strings" "github.com/docker/docker/opts" - flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/reference" registrytypes "github.com/docker/engine-api/types/registry" + "github.com/spf13/pflag" ) // ServiceOptions holds command line options. @@ -70,14 +70,14 @@ var lookupIP = net.LookupIP // InstallCliFlags adds command-line options to the top-level flag parser for // the current process. -func (options *ServiceOptions) InstallCliFlags(cmd *flag.FlagSet, usageFn func(string) string) { +func (options *ServiceOptions) InstallCliFlags(flags *pflag.FlagSet) { mirrors := opts.NewNamedListOptsRef("registry-mirrors", &options.Mirrors, ValidateMirror) - cmd.Var(mirrors, []string{"-registry-mirror"}, usageFn("Preferred Docker registry mirror")) - insecureRegistries := opts.NewNamedListOptsRef("insecure-registries", &options.InsecureRegistries, ValidateIndexName) - cmd.Var(insecureRegistries, []string{"-insecure-registry"}, usageFn("Enable insecure registry communication")) - options.installCliPlatformFlags(cmd, usageFn) + flags.Var(mirrors, "registry-mirror", "Preferred Docker registry mirror") + flags.Var(insecureRegistries, "insecure-registry", "Enable insecure registry communication") + + options.installCliPlatformFlags(flags) } // newServiceConfig returns a new instance of ServiceConfig diff --git a/registry/config_unix.go b/registry/config_unix.go index bae33970cc..047ddce3ba 100644 --- a/registry/config_unix.go +++ b/registry/config_unix.go @@ -20,6 +20,6 @@ func cleanPath(s string) string { } // installCliPlatformFlags handles any platform specific flags for the service. -func (options *ServiceOptions) installCliPlatformFlags(cmd *flag.FlagSet, usageFn func(string) string) { - cmd.BoolVar(&options.V2Only, []string{"-disable-legacy-registry"}, false, usageFn("Disable contacting legacy registries")) +func (options *ServiceOptions) installCliPlatformFlags(flags *flag.FlagSet) string) { + flags.BoolVar(&options.V2Only, "disable-legacy-registry", false, "Disable contacting legacy registries") } diff --git a/registry/config_windows.go b/registry/config_windows.go index d8b7f7eae7..f8da94badc 100644 --- a/registry/config_windows.go +++ b/registry/config_windows.go @@ -20,6 +20,6 @@ func cleanPath(s string) string { } // installCliPlatformFlags handles any platform specific flags for the service. -func (options *ServiceOptions) installCliPlatformFlags(cmd *flag.FlagSet, usageFn func(string) string) { +func (options *ServiceOptions) installCliPlatformFlags(flags *flag.FlagSet) string) { // No Windows specific flags. } diff --git a/runconfig/opts/runtime.go b/runconfig/opts/runtime.go index 1fc099834e..541fda017a 100644 --- a/runconfig/opts/runtime.go +++ b/runconfig/opts/runtime.go @@ -72,3 +72,8 @@ func (o *RuntimeOpt) GetMap() map[string]types.Runtime { return map[string]types.Runtime{} } + +// Type returns the type of the option +func (o *RuntimeOpt) Type() string { + return "runtime" +}