Merge pull request #25354 from dnephin/remove-mflag-from-dockerd

Convert docker and dockerd commands to spf13/cobra
This commit is contained in:
Daniel Nephin 2016-08-25 17:19:19 -04:00 committed by GitHub
commit 25587906d1
69 changed files with 1080 additions and 3410 deletions

View file

@ -6,6 +6,7 @@ import (
"io"
"net/http"
"os"
"path/filepath"
"runtime"
"github.com/docker/docker/api"
@ -14,7 +15,7 @@ import (
"github.com/docker/docker/cliconfig/configfile"
"github.com/docker/docker/cliconfig/credentials"
"github.com/docker/docker/dockerversion"
"github.com/docker/docker/opts"
dopts "github.com/docker/docker/opts"
"github.com/docker/docker/pkg/term"
"github.com/docker/engine-api/client"
"github.com/docker/go-connections/sockets"
@ -53,15 +54,6 @@ type DockerCli struct {
outState *term.State
}
// Initialize calls the init function that will setup the configuration for the client
// such as the TLS, tcp and other parameters used to run the client.
func (cli *DockerCli) Initialize() error {
if cli.init == nil {
return nil
}
return cli.init()
}
// Client returns the APIClient
func (cli *DockerCli) Client() client.APIClient {
return cli.client
@ -155,40 +147,41 @@ func (cli *DockerCli) restoreTerminal(in io.Closer) error {
return nil
}
// Initialize the dockerCli runs initialization that must happen after command
// line flags are parsed.
func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
cli.configFile = LoadDefaultConfigFile(cli.err)
client, err := NewAPIClientFromFlags(opts.Common, cli.configFile)
if err != nil {
return err
}
cli.client = client
if cli.in != nil {
cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in)
}
if cli.out != nil {
cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out)
}
if opts.Common.TrustKey == "" {
cli.keyFile = filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile)
} else {
cli.keyFile = opts.Common.TrustKey
}
return nil
}
// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
// The key file, protocol (i.e. unix) and address are passed in as strings, along with the tls.Config. If the tls.Config
// is set the client scheme will be set to https.
// The client will be given a 32-second timeout (see https://github.com/docker/docker/pull/8035).
func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cliflags.ClientFlags) *DockerCli {
cli := &DockerCli{
in: in,
out: out,
err: err,
keyFile: clientFlags.Common.TrustKey,
func NewDockerCli(in io.ReadCloser, out, err io.Writer) *DockerCli {
return &DockerCli{
in: in,
out: out,
err: err,
}
cli.init = func() error {
clientFlags.PostParse()
cli.configFile = LoadDefaultConfigFile(err)
client, err := NewAPIClientFromFlags(clientFlags, cli.configFile)
if err != nil {
return err
}
cli.client = client
if cli.in != nil {
cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in)
}
if cli.out != nil {
cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out)
}
return nil
}
return cli
}
// LoadDefaultConfigFile attempts to load the default config file and returns
@ -205,8 +198,8 @@ func LoadDefaultConfigFile(err io.Writer) *configfile.ConfigFile {
}
// NewAPIClientFromFlags creates a new APIClient from command line flags
func NewAPIClientFromFlags(clientFlags *cliflags.ClientFlags, configFile *configfile.ConfigFile) (client.APIClient, error) {
host, err := getServerHost(clientFlags.Common.Hosts, clientFlags.Common.TLSOptions)
func NewAPIClientFromFlags(opts *cliflags.CommonOptions, configFile *configfile.ConfigFile) (client.APIClient, error) {
host, err := getServerHost(opts.Hosts, opts.TLSOptions)
if err != nil {
return &client.Client{}, err
}
@ -222,7 +215,7 @@ func NewAPIClientFromFlags(clientFlags *cliflags.ClientFlags, configFile *config
verStr = tmpStr
}
httpClient, err := newHTTPClient(host, clientFlags.Common.TLSOptions)
httpClient, err := newHTTPClient(host, opts.TLSOptions)
if err != nil {
return &client.Client{}, err
}
@ -240,7 +233,7 @@ func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (host string,
return "", errors.New("Please specify only one -H")
}
host, err = opts.ParseHost(tlsOptions != nil, host)
host, err = dopts.ParseHost(tlsOptions != nil, host)
return
}

View file

@ -0,0 +1,71 @@
package command
import (
"github.com/docker/docker/api/client"
"github.com/docker/docker/api/client/container"
"github.com/docker/docker/api/client/image"
"github.com/docker/docker/api/client/network"
"github.com/docker/docker/api/client/node"
"github.com/docker/docker/api/client/plugin"
"github.com/docker/docker/api/client/registry"
"github.com/docker/docker/api/client/service"
"github.com/docker/docker/api/client/stack"
"github.com/docker/docker/api/client/swarm"
"github.com/docker/docker/api/client/system"
"github.com/docker/docker/api/client/volume"
"github.com/spf13/cobra"
)
// AddCommands adds all the commands from api/client to the root command
func AddCommands(cmd *cobra.Command, dockerCli *client.DockerCli) {
cmd.AddCommand(
node.NewNodeCommand(dockerCli),
service.NewServiceCommand(dockerCli),
stack.NewStackCommand(dockerCli),
stack.NewTopLevelDeployCommand(dockerCli),
swarm.NewSwarmCommand(dockerCli),
container.NewAttachCommand(dockerCli),
container.NewCommitCommand(dockerCli),
container.NewCopyCommand(dockerCli),
container.NewCreateCommand(dockerCli),
container.NewDiffCommand(dockerCli),
container.NewExecCommand(dockerCli),
container.NewExportCommand(dockerCli),
container.NewKillCommand(dockerCli),
container.NewLogsCommand(dockerCli),
container.NewPauseCommand(dockerCli),
container.NewPortCommand(dockerCli),
container.NewPsCommand(dockerCli),
container.NewRenameCommand(dockerCli),
container.NewRestartCommand(dockerCli),
container.NewRmCommand(dockerCli),
container.NewRunCommand(dockerCli),
container.NewStartCommand(dockerCli),
container.NewStatsCommand(dockerCli),
container.NewStopCommand(dockerCli),
container.NewTopCommand(dockerCli),
container.NewUnpauseCommand(dockerCli),
container.NewUpdateCommand(dockerCli),
container.NewWaitCommand(dockerCli),
image.NewBuildCommand(dockerCli),
image.NewHistoryCommand(dockerCli),
image.NewImagesCommand(dockerCli),
image.NewLoadCommand(dockerCli),
image.NewRemoveCommand(dockerCli),
image.NewSaveCommand(dockerCli),
image.NewPullCommand(dockerCli),
image.NewPushCommand(dockerCli),
image.NewSearchCommand(dockerCli),
image.NewImportCommand(dockerCli),
image.NewTagCommand(dockerCli),
network.NewNetworkCommand(dockerCli),
system.NewEventsCommand(dockerCli),
system.NewInspectCommand(dockerCli),
registry.NewLoginCommand(dockerCli),
registry.NewLogoutCommand(dockerCli),
system.NewVersionCommand(dockerCli),
volume.NewVolumeCommand(dockerCli),
system.NewInfoCommand(dockerCli),
)
plugin.NewPluginCommand(cmd, dockerCli)
}

View file

@ -1,6 +0,0 @@
package client
// Command returns a cli command handler if one exists
func (cli *DockerCli) Command(name string) func(...string) error {
return map[string]func(...string) error{}[name]
}

View file

@ -36,7 +36,6 @@ func NewAttachCommand(dockerCli *client.DockerCli) *cobra.Command {
return runAttach(dockerCli, &opts)
},
}
cmd.SetFlagErrorFunc(flagErrorFunc)
flags := cmd.Flags()
flags.BoolVar(&opts.noStdin, "no-stdin", false, "Do not attach STDIN")

View file

@ -38,7 +38,6 @@ func NewCommitCommand(dockerCli *client.DockerCli) *cobra.Command {
return runCommit(dockerCli, &opts)
},
}
cmd.SetFlagErrorFunc(flagErrorFunc)
flags := cmd.Flags()
flags.SetInterspersed(false)

View file

@ -43,7 +43,6 @@ func NewCreateCommand(dockerCli *client.DockerCli) *cobra.Command {
return runCreate(dockerCli, cmd.Flags(), &opts, copts)
},
}
cmd.SetFlagErrorFunc(flagErrorFunc)
flags := cmd.Flags()
flags.SetInterspersed(false)

View file

@ -19,7 +19,7 @@ type diffOptions struct {
func NewDiffCommand(dockerCli *client.DockerCli) *cobra.Command {
var opts diffOptions
cmd := &cobra.Command{
return &cobra.Command{
Use: "diff CONTAINER",
Short: "Inspect changes on a container's filesystem",
Args: cli.ExactArgs(1),
@ -28,9 +28,6 @@ func NewDiffCommand(dockerCli *client.DockerCli) *cobra.Command {
return runDiff(dockerCli, &opts)
},
}
cmd.SetFlagErrorFunc(flagErrorFunc)
return cmd
}
func runDiff(dockerCli *client.DockerCli, opts *diffOptions) error {

View file

@ -41,7 +41,6 @@ func NewLogsCommand(dockerCli *client.DockerCli) *cobra.Command {
return runLogs(dockerCli, &opts)
},
}
cmd.SetFlagErrorFunc(flagErrorFunc)
flags := cmd.Flags()
flags.BoolVarP(&opts.follow, "follow", "f", false, "Follow log output")

View file

@ -19,7 +19,7 @@ type pauseOptions struct {
func NewPauseCommand(dockerCli *client.DockerCli) *cobra.Command {
var opts pauseOptions
cmd := &cobra.Command{
return &cobra.Command{
Use: "pause CONTAINER [CONTAINER...]",
Short: "Pause all processes within one or more containers",
Args: cli.RequiresMinArgs(1),
@ -28,9 +28,6 @@ func NewPauseCommand(dockerCli *client.DockerCli) *cobra.Command {
return runPause(dockerCli, &opts)
},
}
cmd.SetFlagErrorFunc(flagErrorFunc)
return cmd
}
func runPause(dockerCli *client.DockerCli, opts *pauseOptions) error {

View file

@ -34,8 +34,6 @@ func NewPortCommand(dockerCli *client.DockerCli) *cobra.Command {
return runPort(dockerCli, &opts)
},
}
cmd.SetFlagErrorFunc(flagErrorFunc)
return cmd
}

View file

@ -30,8 +30,6 @@ func NewRenameCommand(dockerCli *client.DockerCli) *cobra.Command {
return runRename(dockerCli, &opts)
},
}
cmd.SetFlagErrorFunc(flagErrorFunc)
return cmd
}

View file

@ -48,7 +48,6 @@ func NewRunCommand(dockerCli *client.DockerCli) *cobra.Command {
return runRun(dockerCli, cmd.Flags(), &opts, copts)
},
}
cmd.SetFlagErrorFunc(flagErrorFunc)
flags := cmd.Flags()
flags.SetInterspersed(false)

View file

@ -37,7 +37,6 @@ func NewStartCommand(dockerCli *client.DockerCli) *cobra.Command {
return runStart(dockerCli, &opts)
},
}
cmd.SetFlagErrorFunc(flagErrorFunc)
flags := cmd.Flags()
flags.BoolVarP(&opts.attach, "attach", "a", false, "Attach STDOUT/STDERR and forward signals")

View file

@ -31,7 +31,6 @@ func NewStopCommand(dockerCli *client.DockerCli) *cobra.Command {
return runStop(dockerCli, &opts)
},
}
cmd.SetFlagErrorFunc(flagErrorFunc)
flags := cmd.Flags()
flags.IntVarP(&opts.time, "time", "t", 10, "Seconds to wait for stop before killing it")

View file

@ -32,7 +32,6 @@ func NewTopCommand(dockerCli *client.DockerCli) *cobra.Command {
return runTop(dockerCli, &opts)
},
}
cmd.SetFlagErrorFunc(flagErrorFunc)
flags := cmd.Flags()
flags.SetInterspersed(false)

View file

@ -28,8 +28,6 @@ func NewUnpauseCommand(dockerCli *client.DockerCli) *cobra.Command {
return runUnpause(dockerCli, &opts)
},
}
cmd.SetFlagErrorFunc(flagErrorFunc)
return cmd
}

View file

@ -28,8 +28,6 @@ func NewWaitCommand(dockerCli *client.DockerCli) *cobra.Command {
return runWait(dockerCli, &opts)
},
}
cmd.SetFlagErrorFunc(flagErrorFunc)
return cmd
}

View file

@ -1,191 +0,0 @@
package cli
import (
"errors"
"fmt"
"io"
"os"
"strings"
flag "github.com/docker/docker/pkg/mflag"
)
// Cli represents a command line interface.
type Cli struct {
Stderr io.Writer
handlers []Handler
Usage func()
}
// Handler holds the different commands Cli will call
// It should have methods with names starting with `Cmd` like:
// func (h myHandler) CmdFoo(args ...string) error
type Handler interface {
Command(name string) func(...string) error
}
// Initializer can be optionally implemented by a Handler to
// initialize before each call to one of its commands.
type Initializer interface {
Initialize() error
}
// New instantiates a ready-to-use Cli.
func New(handlers ...Handler) *Cli {
// make the generic Cli object the first cli handler
// in order to handle `docker help` appropriately
cli := new(Cli)
cli.handlers = append([]Handler{cli}, handlers...)
return cli
}
var errCommandNotFound = errors.New("command not found")
func (cli *Cli) command(args ...string) (func(...string) error, error) {
for _, c := range cli.handlers {
if c == nil {
continue
}
if cmd := c.Command(strings.Join(args, " ")); cmd != nil {
if ci, ok := c.(Initializer); ok {
if err := ci.Initialize(); err != nil {
return nil, err
}
}
return cmd, nil
}
}
return nil, errCommandNotFound
}
// Run executes the specified command.
func (cli *Cli) Run(args ...string) error {
if len(args) > 1 {
command, err := cli.command(args[:2]...)
if err == nil {
return command(args[2:]...)
}
if err != errCommandNotFound {
return err
}
}
if len(args) > 0 {
command, err := cli.command(args[0])
if err != nil {
if err == errCommandNotFound {
cli.noSuchCommand(args[0])
return nil
}
return err
}
return command(args[1:]...)
}
return cli.CmdHelp()
}
func (cli *Cli) noSuchCommand(command string) {
if cli.Stderr == nil {
cli.Stderr = os.Stderr
}
fmt.Fprintf(cli.Stderr, "docker: '%s' is not a docker command.\nSee 'docker --help'.\n", command)
os.Exit(1)
}
// Command returns a command handler, or nil if the command does not exist
func (cli *Cli) Command(name string) func(...string) error {
return map[string]func(...string) error{
"help": cli.CmdHelp,
}[name]
}
// CmdHelp displays information on a Docker command.
//
// If more than one command is specified, information is only shown for the first command.
//
// Usage: docker help COMMAND or docker COMMAND --help
func (cli *Cli) CmdHelp(args ...string) error {
if len(args) > 1 {
command, err := cli.command(args[:2]...)
if err == nil {
command("--help")
return nil
}
if err != errCommandNotFound {
return err
}
}
if len(args) > 0 {
command, err := cli.command(args[0])
if err != nil {
if err == errCommandNotFound {
cli.noSuchCommand(args[0])
return nil
}
return err
}
command("--help")
return nil
}
if cli.Usage == nil {
flag.Usage()
} else {
cli.Usage()
}
return nil
}
// Subcmd is a subcommand of the main "docker" command.
// A subcommand represents an action that can be performed
// from the Docker command line client.
//
// To see all available subcommands, run "docker --help".
func Subcmd(name string, synopses []string, description string, exitOnError bool) *flag.FlagSet {
var errorHandling flag.ErrorHandling
if exitOnError {
errorHandling = flag.ExitOnError
} else {
errorHandling = flag.ContinueOnError
}
flags := flag.NewFlagSet(name, errorHandling)
flags.Usage = func() {
flags.ShortUsage()
flags.PrintDefaults()
}
flags.ShortUsage = func() {
if len(synopses) == 0 {
synopses = []string{""}
}
// Allow for multiple command usage synopses.
for i, synopsis := range synopses {
lead := "\t"
if i == 0 {
// First line needs the word 'Usage'.
lead = "Usage:\t"
}
if synopsis != "" {
synopsis = " " + synopsis
}
fmt.Fprintf(flags.Out(), "\n%sdocker %s%s", lead, name, synopsis)
}
fmt.Fprintf(flags.Out(), "\n\n%s\n", description)
}
return flags
}
// StatusError reports an unsuccessful exit by a command.
type StatusError struct {
Status string
StatusCode int
}
func (e StatusError) Error() string {
return fmt.Sprintf("Status: %s, Code: %d", e.Status, e.StatusCode)
}

57
cli/cobra.go Normal file
View file

@ -0,0 +1,57 @@
package cli
import (
"fmt"
"github.com/spf13/cobra"
)
// SetupRootCommand sets default usage, help, and error handling for the
// root command.
func SetupRootCommand(rootCmd *cobra.Command) {
rootCmd.SetUsageTemplate(usageTemplate)
rootCmd.SetHelpTemplate(helpTemplate)
rootCmd.SetFlagErrorFunc(FlagErrorFunc)
rootCmd.PersistentFlags().BoolP("help", "h", false, "Print usage")
rootCmd.PersistentFlags().MarkShorthandDeprecated("help", "please use --help")
}
// FlagErrorFunc prints an error messages which matches the format of the
// docker/docker/cli error messages
func FlagErrorFunc(cmd *cobra.Command, err error) error {
if err == nil {
return err
}
usage := ""
if cmd.HasSubCommands() {
usage = "\n\n" + cmd.UsageString()
}
return StatusError{
Status: fmt.Sprintf("%s\nSee '%s --help'.%s", err, cmd.CommandPath(), usage),
StatusCode: 125,
}
}
var usageTemplate = `Usage: {{if not .HasSubCommands}}{{.UseLine}}{{end}}{{if .HasSubCommands}}{{ .CommandPath}} COMMAND{{end}}
{{ .Short | trim }}{{if gt .Aliases 0}}
Aliases:
{{.NameAndAliases}}{{end}}{{if .HasExample}}
Examples:
{{ .Example }}{{end}}{{if .HasFlags}}
Options:
{{.Flags.FlagUsages | trimRightSpace}}{{end}}{{ if .HasAvailableSubCommands}}
Commands:{{range .Commands}}{{if .IsAvailableCommand}}
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasSubCommands }}
Run '{{.CommandPath}} COMMAND --help' for more information on a command.{{end}}
`
var helpTemplate = `
{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`

View file

@ -1,163 +0,0 @@
package cobraadaptor
import (
"github.com/docker/docker/api/client"
"github.com/docker/docker/api/client/container"
"github.com/docker/docker/api/client/image"
"github.com/docker/docker/api/client/network"
"github.com/docker/docker/api/client/node"
"github.com/docker/docker/api/client/plugin"
"github.com/docker/docker/api/client/registry"
"github.com/docker/docker/api/client/service"
"github.com/docker/docker/api/client/stack"
"github.com/docker/docker/api/client/swarm"
"github.com/docker/docker/api/client/system"
"github.com/docker/docker/api/client/volume"
"github.com/docker/docker/cli"
cliflags "github.com/docker/docker/cli/flags"
"github.com/docker/docker/pkg/term"
"github.com/spf13/cobra"
)
// CobraAdaptor is an adaptor for supporting spf13/cobra commands in the
// docker/cli framework
type CobraAdaptor struct {
rootCmd *cobra.Command
dockerCli *client.DockerCli
}
// NewCobraAdaptor returns a new handler
func NewCobraAdaptor(clientFlags *cliflags.ClientFlags) CobraAdaptor {
stdin, stdout, stderr := term.StdStreams()
dockerCli := client.NewDockerCli(stdin, stdout, stderr, clientFlags)
var rootCmd = &cobra.Command{
Use: "docker [OPTIONS]",
Short: "A self-sufficient runtime for containers",
SilenceUsage: true,
SilenceErrors: true,
}
rootCmd.SetUsageTemplate(usageTemplate)
rootCmd.SetHelpTemplate(helpTemplate)
rootCmd.SetFlagErrorFunc(cli.FlagErrorFunc)
rootCmd.SetOutput(stdout)
rootCmd.AddCommand(
node.NewNodeCommand(dockerCli),
service.NewServiceCommand(dockerCli),
stack.NewStackCommand(dockerCli),
stack.NewTopLevelDeployCommand(dockerCli),
swarm.NewSwarmCommand(dockerCli),
container.NewAttachCommand(dockerCli),
container.NewCommitCommand(dockerCli),
container.NewCopyCommand(dockerCli),
container.NewCreateCommand(dockerCli),
container.NewDiffCommand(dockerCli),
container.NewExecCommand(dockerCli),
container.NewExportCommand(dockerCli),
container.NewKillCommand(dockerCli),
container.NewLogsCommand(dockerCli),
container.NewPauseCommand(dockerCli),
container.NewPortCommand(dockerCli),
container.NewPsCommand(dockerCli),
container.NewRenameCommand(dockerCli),
container.NewRestartCommand(dockerCli),
container.NewRmCommand(dockerCli),
container.NewRunCommand(dockerCli),
container.NewStartCommand(dockerCli),
container.NewStatsCommand(dockerCli),
container.NewStopCommand(dockerCli),
container.NewTopCommand(dockerCli),
container.NewUnpauseCommand(dockerCli),
container.NewUpdateCommand(dockerCli),
container.NewWaitCommand(dockerCli),
image.NewBuildCommand(dockerCli),
image.NewHistoryCommand(dockerCli),
image.NewImagesCommand(dockerCli),
image.NewLoadCommand(dockerCli),
image.NewRemoveCommand(dockerCli),
image.NewSaveCommand(dockerCli),
image.NewPullCommand(dockerCli),
image.NewPushCommand(dockerCli),
image.NewSearchCommand(dockerCli),
image.NewImportCommand(dockerCli),
image.NewTagCommand(dockerCli),
network.NewNetworkCommand(dockerCli),
system.NewEventsCommand(dockerCli),
system.NewInspectCommand(dockerCli),
registry.NewLoginCommand(dockerCli),
registry.NewLogoutCommand(dockerCli),
system.NewVersionCommand(dockerCli),
volume.NewVolumeCommand(dockerCli),
system.NewInfoCommand(dockerCli),
)
plugin.NewPluginCommand(rootCmd, dockerCli)
rootCmd.PersistentFlags().BoolP("help", "h", false, "Print usage")
rootCmd.PersistentFlags().MarkShorthandDeprecated("help", "please use --help")
return CobraAdaptor{
rootCmd: rootCmd,
dockerCli: dockerCli,
}
}
// Usage returns the list of commands and their short usage string for
// all top level cobra commands.
func (c CobraAdaptor) Usage() []cli.Command {
cmds := []cli.Command{}
for _, cmd := range c.rootCmd.Commands() {
if cmd.Name() != "" {
cmds = append(cmds, cli.Command{Name: cmd.Name(), Description: cmd.Short})
}
}
return cmds
}
func (c CobraAdaptor) run(cmd string, args []string) error {
if err := c.dockerCli.Initialize(); err != nil {
return err
}
// Prepend the command name to support normal cobra command delegation
c.rootCmd.SetArgs(append([]string{cmd}, args...))
return c.rootCmd.Execute()
}
// Command returns a cli command handler if one exists
func (c CobraAdaptor) Command(name string) func(...string) error {
for _, cmd := range c.rootCmd.Commands() {
if cmd.Name() == name {
return func(args ...string) error {
return c.run(name, args)
}
}
}
return nil
}
// GetRootCommand returns the root command. Required to generate the man pages
// and reference docs from a script outside this package.
func (c CobraAdaptor) GetRootCommand() *cobra.Command {
return c.rootCmd
}
var usageTemplate = `Usage: {{if not .HasSubCommands}}{{.UseLine}}{{end}}{{if .HasSubCommands}}{{ .CommandPath}} COMMAND{{end}}
{{ .Short | trim }}{{if gt .Aliases 0}}
Aliases:
{{.NameAndAliases}}{{end}}{{if .HasExample}}
Examples:
{{ .Example }}{{end}}{{if .HasFlags}}
Options:
{{.Flags.FlagUsages | trimRightSpace}}{{end}}{{ if .HasAvailableSubCommands}}
Commands:{{range .Commands}}{{if .IsAvailableCommand}}
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasSubCommands }}
Run '{{.CommandPath}} COMMAND --help' for more information on a command.{{end}}
`
var helpTemplate = `
{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`

View file

@ -1,6 +1,9 @@
package cli
import "strings"
import (
"fmt"
"strings"
)
// Errors is a list of errors.
// Useful in a loop if you don't want to return the error right away and you want to display after the loop,
@ -18,3 +21,13 @@ func (errList Errors) Error() string {
}
return strings.Join(out, ", ")
}
// StatusError reports an unsuccessful exit by a command.
type StatusError struct {
Status string
StatusCode int
}
func (e StatusError) Error() string {
return fmt.Sprintf("Status: %s, Code: %d", e.Status, e.StatusCode)
}

View file

@ -1,21 +0,0 @@
package cli
import (
"fmt"
"github.com/spf13/cobra"
)
// FlagErrorFunc prints an error messages which matches the format of the
// docker/docker/cli error messages
func FlagErrorFunc(cmd *cobra.Command, err error) error {
if err == nil {
return err
}
usage := ""
if cmd.HasSubCommands() {
usage = "\n\n" + cmd.UsageString()
}
return fmt.Errorf("%s\nSee '%s --help'.%s", err, cmd.CommandPath(), usage)
}

View file

@ -1,12 +1,13 @@
package flags
import flag "github.com/docker/docker/pkg/mflag"
// ClientFlags represents flags for the docker client.
type ClientFlags struct {
FlagSet *flag.FlagSet
Common *CommonFlags
PostParse func()
// ClientOptions are the options used to configure the client cli
type ClientOptions struct {
Common *CommonOptions
ConfigDir string
Version bool
}
// NewClientOptions returns a new ClientOptions
func NewClientOptions() *ClientOptions {
return &ClientOptions{Common: NewCommonOptions()}
}

View file

@ -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,59 @@ 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{}
}
// 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")
commonOpts.TLSOptions = &tlsconfig.Options{}
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 = ""
}

View file

@ -1,19 +0,0 @@
package cli
// Command is the struct containing the command name and description
type Command struct {
Name string
Description string
}
// DockerCommandUsage lists the top level docker commands and their short usage
var DockerCommandUsage = []Command{}
// DockerCommands stores all the docker command
var DockerCommands = make(map[string]Command)
func init() {
for _, cmd := range DockerCommandUsage {
DockerCommands[cmd.Name] = cmd
}
}

View file

@ -1,18 +0,0 @@
package main
const daemonBinary = "dockerd"
// DaemonProxy acts as a cli.Handler to proxy calls to the daemon binary
type DaemonProxy struct{}
// NewDaemonProxy returns a new handler
func NewDaemonProxy() DaemonProxy {
return DaemonProxy{}
}
// Command returns a cli command handler if one exists
func (p DaemonProxy) Command(name string) func(...string) error {
return map[string]func(...string) error{
"daemon": p.CmdDaemon,
}[name]
}

View file

@ -6,10 +6,21 @@ import (
"fmt"
"runtime"
"strings"
"github.com/spf13/cobra"
)
// CmdDaemon reports on an error on windows, because there is no exec
func (p DaemonProxy) CmdDaemon(args ...string) error {
func newDaemonCommand() *cobra.Command {
return &cobra.Command{
Use: "daemon",
Hidden: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runDaemon()
},
}
}
func runDaemon() error {
return fmt.Errorf(
"`docker daemon` is not supported on %s. Please run `dockerd` directly",
strings.Title(runtime.GOOS))

View file

@ -3,23 +3,37 @@
package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"syscall"
"github.com/spf13/cobra"
)
// CmdDaemon execs dockerd with the same flags
func (p DaemonProxy) CmdDaemon(args ...string) error {
// Special case for handling `docker help daemon`. When pkg/mflag is removed
// we can support this on the daemon side, but that is not possible with
// pkg/mflag because it uses os.Exit(1) instead of returning an error on
// unexpected args.
if len(args) == 0 || args[0] != "--help" {
// Use os.Args[1:] so that "global" args are passed to dockerd
args = stripDaemonArg(os.Args[1:])
}
const daemonBinary = "dockerd"
func newDaemonCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "daemon",
Hidden: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runDaemon()
},
}
cmd.SetHelpFunc(helpFunc)
return cmd
}
// CmdDaemon execs dockerd with the same flags
func runDaemon() error {
// Use os.Args[1:] so that "global" args are passed to dockerd
return execDaemon(stripDaemonArg(os.Args[1:]))
}
func execDaemon(args []string) error {
binaryPath, err := findDaemonBinary()
if err != nil {
return err
@ -31,6 +45,12 @@ func (p DaemonProxy) CmdDaemon(args ...string) error {
os.Environ())
}
func helpFunc(cmd *cobra.Command, args []string) {
if err := execDaemon([]string{"--help"}); err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
}
}
// findDaemonBinary looks for the path to the dockerd binary starting with
// the directory of the current executable (if one exists) and followed by $PATH
func findDaemonBinary() (string, error) {

View file

@ -3,73 +3,77 @@ package main
import (
"fmt"
"os"
"path/filepath"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/api/client"
"github.com/docker/docker/api/client/command"
"github.com/docker/docker/cli"
"github.com/docker/docker/cli/cobraadaptor"
cliflags "github.com/docker/docker/cli/flags"
"github.com/docker/docker/cliconfig"
"github.com/docker/docker/dockerversion"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/term"
"github.com/docker/docker/utils"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
var (
commonFlags = cliflags.InitCommonFlags()
clientFlags = initClientFlags(commonFlags)
flHelp = flag.Bool([]string{"h", "-help"}, false, "Print usage")
flVersion = flag.Bool([]string{"v", "-version"}, false, "Print version information and quit")
)
func newDockerCommand(dockerCli *client.DockerCli) *cobra.Command {
opts := cliflags.NewClientOptions()
var flags *pflag.FlagSet
cmd := &cobra.Command{
Use: "docker [OPTIONS] COMMAND [arg...]",
Short: "A self-sufficient runtime for containers.",
SilenceUsage: true,
SilenceErrors: true,
TraverseChildren: true,
Args: noArgs,
RunE: func(cmd *cobra.Command, args []string) error {
if opts.Version {
showVersion()
return nil
}
fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
return nil
},
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
// flags must be the top-level command flags, not cmd.Flags()
opts.Common.SetDefaultOptions(flags)
dockerPreRun(opts)
return dockerCli.Initialize(opts)
},
}
cli.SetupRootCommand(cmd)
flags = cmd.Flags()
flags.BoolVarP(&opts.Version, "version", "v", false, "Print version information and quit")
flags.StringVar(&opts.ConfigDir, "config", cliconfig.ConfigDir(), "Location of client config files")
opts.Common.InstallFlags(flags)
cmd.SetOutput(dockerCli.Out())
cmd.AddCommand(newDaemonCommand())
command.AddCommands(cmd, dockerCli)
return cmd
}
func noArgs(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return nil
}
return fmt.Errorf(
"docker: '%s' is not a docker command.\nSee 'docker --help'%s", args[0], ".")
}
func main() {
// Set terminal emulation based on platform as required.
stdin, stdout, stderr := term.StdStreams()
logrus.SetOutput(stderr)
flag.Merge(flag.CommandLine, clientFlags.FlagSet, commonFlags.FlagSet)
dockerCli := client.NewDockerCli(stdin, stdout, stderr)
cmd := newDockerCommand(dockerCli)
cobraAdaptor := cobraadaptor.NewCobraAdaptor(clientFlags)
flag.Usage = func() {
fmt.Fprint(stdout, "Usage: docker [OPTIONS] COMMAND [arg...]\n docker [ --help | -v | --version ]\n\n")
fmt.Fprint(stdout, "A self-sufficient runtime for containers.\n\nOptions:\n")
flag.CommandLine.SetOutput(stdout)
flag.PrintDefaults()
help := "\nCommands:\n"
dockerCommands := append(cli.DockerCommandUsage, cobraAdaptor.Usage()...)
for _, cmd := range sortCommands(dockerCommands) {
help += fmt.Sprintf(" %-10.10s%s\n", cmd.Name, cmd.Description)
}
help += "\nRun 'docker COMMAND --help' for more information on a command."
fmt.Fprintf(stdout, "%s\n", help)
}
flag.Parse()
if *flVersion {
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
}
clientCli := client.NewDockerCli(stdin, stdout, stderr, clientFlags)
c := cli.New(clientCli, NewDaemonProxy(), cobraAdaptor)
if err := c.Run(flag.Args()...); err != nil {
if err := cmd.Execute(); err != nil {
if sterr, ok := err.(cli.StatusError); ok {
if sterr.Status != "" {
fmt.Fprintln(stderr, sterr.Status)
@ -94,25 +98,14 @@ func showVersion() {
}
}
func initClientFlags(commonFlags *cliflags.CommonFlags) *cliflags.ClientFlags {
clientFlags := &cliflags.ClientFlags{FlagSet: new(flag.FlagSet), Common: commonFlags}
client := clientFlags.FlagSet
client.StringVar(&clientFlags.ConfigDir, []string{"-config"}, cliconfig.ConfigDir(), "Location of client config files")
func dockerPreRun(opts *cliflags.ClientOptions) {
cliflags.SetDaemonLogLevel(opts.Common.LogLevel)
clientFlags.PostParse = func() {
clientFlags.Common.PostParse()
if clientFlags.ConfigDir != "" {
cliconfig.SetConfigDir(clientFlags.ConfigDir)
}
if clientFlags.Common.TrustKey == "" {
clientFlags.Common.TrustKey = filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile)
}
if clientFlags.Common.Debug {
utils.EnableDebug()
}
if opts.ConfigDir != "" {
cliconfig.SetConfigDir(opts.ConfigDir)
}
if opts.Common.Debug {
utils.EnableDebug()
}
return clientFlags
}

View file

@ -6,13 +6,19 @@ import (
"github.com/Sirupsen/logrus"
"github.com/docker/docker/utils"
"github.com/docker/docker/api/client"
)
func TestClientDebugEnabled(t *testing.T) {
defer utils.DisableDebug()
clientFlags.Common.FlagSet.Parse([]string{"-D"})
clientFlags.PostParse()
cmd := newDockerCommand(&client.DockerCli{})
cmd.Flags().Set("debug", "true")
if err := cmd.PersistentPreRunE(cmd, []string{}); err != nil {
t.Fatalf("Unexpected error: %s", err.Error())
}
if os.Getenv("DEBUG") != "1" {
t.Fatal("expected debug enabled, got false")

View file

@ -1,22 +0,0 @@
package main
import (
"sort"
"github.com/docker/docker/cli"
)
type byName []cli.Command
func (a byName) Len() int { return len(a) }
func (a byName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byName) Less(i, j int) bool { return a[i].Name < a[j].Name }
// TODO(tiborvass): do not show 'daemon' on client-only binaries
func sortCommands(commands []cli.Command) []cli.Command {
dockerCommands := make([]cli.Command, len(commands))
copy(dockerCommands, commands)
sort.Sort(byName(dockerCommands))
return dockerCommands
}

View file

@ -1,15 +0,0 @@
package main
import (
"sort"
"testing"
"github.com/docker/docker/cli"
)
// Tests if the subcommands of docker are sorted
func TestDockerSubcommandsAreSorted(t *testing.T) {
if !sort.IsSorted(byName(cli.DockerCommandUsage)) {
t.Fatal("Docker subcommands are not in sorted order")
}
}

View file

@ -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
}

View file

@ -1,290 +1,145 @@
package main
import (
"io/ioutil"
"os"
"strings"
"testing"
"github.com/Sirupsen/logrus"
cliflags "github.com/docker/docker/cli/flags"
"github.com/docker/docker/daemon"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/mflag"
"github.com/docker/go-connections/tlsconfig"
"github.com/docker/docker/pkg/testutil/assert"
"github.com/docker/docker/pkg/testutil/tempfile"
"github.com/spf13/pflag"
)
func TestLoadDaemonCliConfigWithoutOverriding(t *testing.T) {
c := &daemon.Config{}
common := &cliflags.CommonFlags{
Debug: true,
func defaultOptions(configFile string) daemonOptions {
opts := daemonOptions{
daemonConfig: &daemon.Config{},
flags: &pflag.FlagSet{},
common: cliflags.NewCommonOptions(),
}
opts.common.InstallFlags(opts.flags)
opts.daemonConfig.InstallFlags(opts.flags)
opts.flags.StringVar(&opts.configFile, flagDaemonConfigFile, defaultDaemonConfigFile, "")
opts.configFile = configFile
return opts
}
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)
}
func TestLoadDaemonCliConfigWithoutOverriding(t *testing.T) {
opts := defaultOptions("")
opts.common.Debug = true
loadedConfig, err := loadDaemonCliConfig(opts)
assert.NilError(t, err)
assert.NotNil(t, loadedConfig)
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 := &cliflags.CommonFlags{
TLS: true,
TLSOptions: &tlsconfig.Options{
CAFile: "/tmp/ca.pem",
},
}
opts := defaultOptions("")
opts.common.TLSOptions.CAFile = "/tmp/ca.pem"
opts.common.TLS = 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.CommonTLSOptions.CAFile != "/tmp/ca.pem" {
t.Fatalf("expected /tmp/ca.pem, got %s: %q", loadedConfig.CommonTLSOptions.CAFile, loadedConfig)
}
loadedConfig, err := loadDaemonCliConfig(opts)
assert.NilError(t, err)
assert.NotNil(t, loadedConfig)
assert.Equal(t, loadedConfig.CommonTLSOptions.CAFile, "/tmp/ca.pem")
}
func TestLoadDaemonCliConfigWithConflicts(t *testing.T) {
c := &daemon.Config{}
common := &cliflags.CommonFlags{}
f, err := ioutil.TempFile("", "docker-config-")
if err != nil {
t.Fatal(err)
}
configFile := f.Name()
defer os.Remove(configFile)
tempFile := tempfile.NewTempFile(t, "config", `{"labels": ["l3=foo"]}`)
defer tempFile.Remove()
configFile := tempFile.Name()
f.Write([]byte(`{"labels": ["l3=foo"]}`))
f.Close()
opts := defaultOptions(configFile)
flags := opts.flags
var labels []string
assert.NilError(t, flags.Set(flagDaemonConfigFile, configFile))
assert.NilError(t, flags.Set("label", "l1=bar"))
assert.NilError(t, flags.Set("label", "l2=baz"))
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)
}
_, err := loadDaemonCliConfig(opts)
assert.Error(t, err, "as a flag and in the configuration file: labels")
}
func TestLoadDaemonCliConfigWithTLSVerify(t *testing.T) {
c := &daemon.Config{}
common := &cliflags.CommonFlags{
TLSOptions: &tlsconfig.Options{
CAFile: "/tmp/ca.pem",
},
}
tempFile := tempfile.NewTempFile(t, "config", `{"tlsverify": true}`)
defer tempFile.Remove()
f, err := ioutil.TempFile("", "docker-config-")
if err != nil {
t.Fatal(err)
}
configFile := f.Name()
defer os.Remove(configFile)
opts := defaultOptions(tempFile.Name())
opts.common.TLSOptions.CAFile = "/tmp/ca.pem"
f.Write([]byte(`{"tlsverify": true}`))
f.Close()
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
flags.Bool([]string{"-tlsverify"}, false, "")
loadedConfig, err := loadDaemonCliConfig(c, flags, common, configFile)
if err != nil {
t.Fatal(err)
}
if loadedConfig == nil {
t.Fatalf("expected configuration %v, got nil", c)
}
if !loadedConfig.TLS {
t.Fatalf("expected TLS enabled, got %q", loadedConfig)
}
loadedConfig, err := loadDaemonCliConfig(opts)
assert.NilError(t, err)
assert.NotNil(t, loadedConfig)
assert.Equal(t, loadedConfig.TLS, true)
}
func TestLoadDaemonCliConfigWithExplicitTLSVerifyFalse(t *testing.T) {
c := &daemon.Config{}
common := &cliflags.CommonFlags{
TLSOptions: &tlsconfig.Options{
CAFile: "/tmp/ca.pem",
},
}
tempFile := tempfile.NewTempFile(t, "config", `{"tlsverify": false}`)
defer tempFile.Remove()
f, err := ioutil.TempFile("", "docker-config-")
if err != nil {
t.Fatal(err)
}
configFile := f.Name()
defer os.Remove(configFile)
opts := defaultOptions(tempFile.Name())
opts.common.TLSOptions.CAFile = "/tmp/ca.pem"
f.Write([]byte(`{"tlsverify": false}`))
f.Close()
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
flags.Bool([]string{"-tlsverify"}, false, "")
loadedConfig, err := loadDaemonCliConfig(c, flags, common, configFile)
if err != nil {
t.Fatal(err)
}
if loadedConfig == nil {
t.Fatalf("expected configuration %v, got nil", c)
}
if !loadedConfig.TLS {
t.Fatalf("expected TLS enabled, got %q", loadedConfig)
}
loadedConfig, err := loadDaemonCliConfig(opts)
assert.NilError(t, err)
assert.NotNil(t, loadedConfig)
assert.Equal(t, loadedConfig.TLS, true)
}
func TestLoadDaemonCliConfigWithoutTLSVerify(t *testing.T) {
c := &daemon.Config{}
common := &cliflags.CommonFlags{
TLSOptions: &tlsconfig.Options{
CAFile: "/tmp/ca.pem",
},
}
tempFile := tempfile.NewTempFile(t, "config", `{}`)
defer tempFile.Remove()
f, err := ioutil.TempFile("", "docker-config-")
if err != nil {
t.Fatal(err)
}
configFile := f.Name()
defer os.Remove(configFile)
opts := defaultOptions(tempFile.Name())
opts.common.TLSOptions.CAFile = "/tmp/ca.pem"
f.Write([]byte(`{}`))
f.Close()
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
loadedConfig, err := loadDaemonCliConfig(c, flags, common, configFile)
if err != nil {
t.Fatal(err)
}
if loadedConfig == nil {
t.Fatalf("expected configuration %v, got nil", c)
}
if loadedConfig.TLS {
t.Fatalf("expected TLS disabled, got %q", loadedConfig)
}
loadedConfig, err := loadDaemonCliConfig(opts)
assert.NilError(t, err)
assert.NotNil(t, loadedConfig)
assert.Equal(t, loadedConfig.TLS, false)
}
func TestLoadDaemonCliConfigWithLogLevel(t *testing.T) {
c := &daemon.Config{}
common := &cliflags.CommonFlags{}
tempFile := tempfile.NewTempFile(t, "config", `{"log-level": "warn"}`)
defer tempFile.Remove()
f, err := ioutil.TempFile("", "docker-config-")
if err != nil {
t.Fatal(err)
}
configFile := f.Name()
defer os.Remove(configFile)
f.Write([]byte(`{"log-level": "warn"}`))
f.Close()
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
flags.String([]string{"-log-level"}, "", "")
loadedConfig, err := loadDaemonCliConfig(c, flags, common, configFile)
if err != nil {
t.Fatal(err)
}
if loadedConfig == nil {
t.Fatalf("expected configuration %v, got nil", c)
}
if loadedConfig.LogLevel != "warn" {
t.Fatalf("expected warn log level, got %v", loadedConfig.LogLevel)
}
if logrus.GetLevel() != logrus.WarnLevel {
t.Fatalf("expected warn log level, got %v", logrus.GetLevel())
}
opts := defaultOptions(tempFile.Name())
loadedConfig, err := loadDaemonCliConfig(opts)
assert.NilError(t, err)
assert.NotNil(t, loadedConfig)
assert.Equal(t, loadedConfig.LogLevel, "warn")
assert.Equal(t, logrus.GetLevel(), logrus.WarnLevel)
}
func TestLoadDaemonConfigWithEmbeddedOptions(t *testing.T) {
c := &daemon.Config{}
common := &cliflags.CommonFlags{}
content := `{"tlscacert": "/etc/certs/ca.pem", "log-driver": "syslog"}`
tempFile := tempfile.NewTempFile(t, "config", content)
defer tempFile.Remove()
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
flags.String([]string{"-tlscacert"}, "", "")
flags.String([]string{"-log-driver"}, "", "")
f, err := ioutil.TempFile("", "docker-config-")
if err != nil {
t.Fatal(err)
}
configFile := f.Name()
defer os.Remove(configFile)
f.Write([]byte(`{"tlscacert": "/etc/certs/ca.pem", "log-driver": "syslog"}`))
f.Close()
loadedConfig, err := loadDaemonCliConfig(c, flags, common, configFile)
if err != nil {
t.Fatal(err)
}
if loadedConfig == nil {
t.Fatal("expected configuration, got nil")
}
if loadedConfig.CommonTLSOptions.CAFile != "/etc/certs/ca.pem" {
t.Fatalf("expected CA file path /etc/certs/ca.pem, got %v", loadedConfig.CommonTLSOptions.CAFile)
}
if loadedConfig.LogConfig.Type != "syslog" {
t.Fatalf("expected LogConfig type syslog, got %v", loadedConfig.LogConfig.Type)
}
opts := defaultOptions(tempFile.Name())
loadedConfig, err := loadDaemonCliConfig(opts)
assert.NilError(t, err)
assert.NotNil(t, loadedConfig)
assert.Equal(t, loadedConfig.CommonTLSOptions.CAFile, "/etc/certs/ca.pem")
assert.Equal(t, loadedConfig.LogConfig.Type, "syslog")
}
func TestLoadDaemonConfigWithRegistryOptions(t *testing.T) {
c := &daemon.Config{}
common := &cliflags.CommonFlags{}
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
c.ServiceOptions.InstallCliFlags(flags, absentFromHelp)
content := `{
"registry-mirrors": ["https://mirrors.docker.com"],
"insecure-registries": ["https://insecure.docker.com"]
}`
tempFile := tempfile.NewTempFile(t, "config", content)
defer tempFile.Remove()
f, err := ioutil.TempFile("", "docker-config-")
if err != nil {
t.Fatal(err)
}
configFile := f.Name()
defer os.Remove(configFile)
opts := defaultOptions(tempFile.Name())
loadedConfig, err := loadDaemonCliConfig(opts)
assert.NilError(t, err)
assert.NotNil(t, loadedConfig)
f.Write([]byte(`{"registry-mirrors": ["https://mirrors.docker.com"], "insecure-registries": ["https://insecure.docker.com"]}`))
f.Close()
loadedConfig, err := loadDaemonCliConfig(c, flags, common, configFile)
if err != nil {
t.Fatal(err)
}
if loadedConfig == nil {
t.Fatal("expected configuration, got nil")
}
m := loadedConfig.Mirrors
if len(m) != 1 {
t.Fatalf("expected 1 mirror, got %d", len(m))
}
r := loadedConfig.InsecureRegistries
if len(r) != 1 {
t.Fatalf("expected 1 insecure registries, got %d", len(r))
}
assert.Equal(t, len(loadedConfig.Mirrors), 1)
assert.Equal(t, len(loadedConfig.InsecureRegistries), 1)
}

View file

@ -3,240 +3,110 @@
package main
import (
"io/ioutil"
"os"
"testing"
cliflags "github.com/docker/docker/cli/flags"
"github.com/docker/docker/daemon"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/testutil/assert"
"github.com/docker/docker/pkg/testutil/tempfile"
)
func TestLoadDaemonCliConfigWithDaemonFlags(t *testing.T) {
c := &daemon.Config{}
common := &cliflags.CommonFlags{
Debug: true,
LogLevel: "info",
}
content := `{"log-opts": {"max-size": "1k"}}`
tempFile := tempfile.NewTempFile(t, "config", content)
defer tempFile.Remove()
f, err := ioutil.TempFile("", "docker-config-")
if err != nil {
t.Fatal(err)
}
opts := defaultOptions(tempFile.Name())
opts.common.Debug = true
opts.common.LogLevel = "info"
assert.NilError(t, opts.flags.Set("selinux-enabled", "true"))
configFile := f.Name()
f.Write([]byte(`{"log-opts": {"max-size": "1k"}}`))
f.Close()
loadedConfig, err := loadDaemonCliConfig(opts)
assert.NilError(t, err)
assert.NotNil(t, loadedConfig)
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
flags.String([]string{daemonConfigFileFlag}, "", "")
flags.BoolVar(&c.EnableSelinuxSupport, []string{"-selinux-enabled"}, true, "")
flags.StringVar(&c.LogConfig.Type, []string{"-log-driver"}, "json-file", "")
flags.Var(opts.NewNamedMapOpts("log-opts", c.LogConfig.Config, nil), []string{"-log-opt"}, "")
flags.Set(daemonConfigFileFlag, configFile)
loadedConfig, err := loadDaemonCliConfig(c, flags, common, configFile)
if err != nil {
t.Fatal(err)
}
if loadedConfig == nil {
t.Fatalf("expected configuration %v, got nil", c)
}
if !loadedConfig.Debug {
t.Fatalf("expected debug mode, got false")
}
if loadedConfig.LogLevel != "info" {
t.Fatalf("expected info log level, got %v", loadedConfig.LogLevel)
}
if !loadedConfig.EnableSelinuxSupport {
t.Fatalf("expected enabled selinux support, got disabled")
}
if loadedConfig.LogConfig.Type != "json-file" {
t.Fatalf("expected LogConfig type json-file, got %v", loadedConfig.LogConfig.Type)
}
if maxSize := loadedConfig.LogConfig.Config["max-size"]; maxSize != "1k" {
t.Fatalf("expected log max-size `1k`, got %s", maxSize)
}
assert.Equal(t, loadedConfig.Debug, true)
assert.Equal(t, loadedConfig.LogLevel, "info")
assert.Equal(t, loadedConfig.EnableSelinuxSupport, true)
assert.Equal(t, loadedConfig.LogConfig.Type, "json-file")
assert.Equal(t, loadedConfig.LogConfig.Config["max-size"], "1k")
}
func TestLoadDaemonConfigWithNetwork(t *testing.T) {
c := &daemon.Config{}
common := &cliflags.CommonFlags{}
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
flags.String([]string{"-bip"}, "", "")
flags.String([]string{"-ip"}, "", "")
content := `{"bip": "127.0.0.2", "ip": "127.0.0.1"}`
tempFile := tempfile.NewTempFile(t, "config", content)
defer tempFile.Remove()
f, err := ioutil.TempFile("", "docker-config-")
if err != nil {
t.Fatal(err)
}
opts := defaultOptions(tempFile.Name())
loadedConfig, err := loadDaemonCliConfig(opts)
assert.NilError(t, err)
assert.NotNil(t, loadedConfig)
configFile := f.Name()
f.Write([]byte(`{"bip": "127.0.0.2", "ip": "127.0.0.1"}`))
f.Close()
loadedConfig, err := loadDaemonCliConfig(c, flags, common, configFile)
if err != nil {
t.Fatal(err)
}
if loadedConfig == nil {
t.Fatalf("expected configuration %v, got nil", c)
}
if loadedConfig.IP != "127.0.0.2" {
t.Fatalf("expected IP 127.0.0.2, got %v", loadedConfig.IP)
}
if loadedConfig.DefaultIP.String() != "127.0.0.1" {
t.Fatalf("expected DefaultIP 127.0.0.1, got %s", loadedConfig.DefaultIP)
}
assert.Equal(t, loadedConfig.IP, "127.0.0.2")
assert.Equal(t, loadedConfig.DefaultIP.String(), "127.0.0.1")
}
func TestLoadDaemonConfigWithMapOptions(t *testing.T) {
c := &daemon.Config{}
common := &cliflags.CommonFlags{}
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
flags.Var(opts.NewNamedMapOpts("cluster-store-opts", c.ClusterOpts, nil), []string{"-cluster-store-opt"}, "")
flags.Var(opts.NewNamedMapOpts("log-opts", c.LogConfig.Config, nil), []string{"-log-opt"}, "")
f, err := ioutil.TempFile("", "docker-config-")
if err != nil {
t.Fatal(err)
}
configFile := f.Name()
f.Write([]byte(`{
content := `{
"cluster-store-opts": {"kv.cacertfile": "/var/lib/docker/discovery_certs/ca.pem"},
"log-opts": {"tag": "test"}
}`))
f.Close()
}`
tempFile := tempfile.NewTempFile(t, "config", content)
defer tempFile.Remove()
loadedConfig, err := loadDaemonCliConfig(c, flags, common, configFile)
if err != nil {
t.Fatal(err)
}
if loadedConfig == nil {
t.Fatal("expected configuration, got nil")
}
if loadedConfig.ClusterOpts == nil {
t.Fatal("expected cluster options, got nil")
}
opts := defaultOptions(tempFile.Name())
loadedConfig, err := loadDaemonCliConfig(opts)
assert.NilError(t, err)
assert.NotNil(t, loadedConfig)
assert.NotNil(t, loadedConfig.ClusterOpts)
expectedPath := "/var/lib/docker/discovery_certs/ca.pem"
if caPath := loadedConfig.ClusterOpts["kv.cacertfile"]; caPath != expectedPath {
t.Fatalf("expected %s, got %s", expectedPath, caPath)
}
if loadedConfig.LogConfig.Config == nil {
t.Fatal("expected log config options, got nil")
}
if tag := loadedConfig.LogConfig.Config["tag"]; tag != "test" {
t.Fatalf("expected log tag `test`, got %s", tag)
}
assert.Equal(t, loadedConfig.ClusterOpts["kv.cacertfile"], expectedPath)
assert.NotNil(t, loadedConfig.LogConfig.Config)
assert.Equal(t, loadedConfig.LogConfig.Config["tag"], "test")
}
func TestLoadDaemonConfigWithTrueDefaultValues(t *testing.T) {
c := &daemon.Config{}
common := &cliflags.CommonFlags{}
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
flags.BoolVar(&c.EnableUserlandProxy, []string{"-userland-proxy"}, true, "")
content := `{ "userland-proxy": false }`
tempFile := tempfile.NewTempFile(t, "config", content)
defer tempFile.Remove()
f, err := ioutil.TempFile("", "docker-config-")
if err != nil {
t.Fatal(err)
}
opts := defaultOptions(tempFile.Name())
loadedConfig, err := loadDaemonCliConfig(opts)
assert.NilError(t, err)
assert.NotNil(t, loadedConfig)
assert.NotNil(t, loadedConfig.ClusterOpts)
if err := flags.ParseFlags([]string{}, false); err != nil {
t.Fatal(err)
}
configFile := f.Name()
f.Write([]byte(`{
"userland-proxy": false
}`))
f.Close()
loadedConfig, err := loadDaemonCliConfig(c, flags, common, configFile)
if err != nil {
t.Fatal(err)
}
if loadedConfig == nil {
t.Fatal("expected configuration, got nil")
}
if loadedConfig.EnableUserlandProxy {
t.Fatal("expected userland proxy to be disabled, got enabled")
}
assert.Equal(t, loadedConfig.EnableUserlandProxy, false)
// make sure reloading doesn't generate configuration
// conflicts after normalizing boolean values.
err = daemon.ReloadConfiguration(configFile, flags, func(reloadedConfig *daemon.Config) {
if reloadedConfig.EnableUserlandProxy {
t.Fatal("expected userland proxy to be disabled, got enabled")
}
})
if err != nil {
t.Fatal(err)
reload := func(reloadedConfig *daemon.Config) {
assert.Equal(t, reloadedConfig.EnableUserlandProxy, false)
}
assert.NilError(t, daemon.ReloadConfiguration(opts.configFile, opts.flags, reload))
}
func TestLoadDaemonConfigWithTrueDefaultValuesLeaveDefaults(t *testing.T) {
c := &daemon.Config{}
common := &cliflags.CommonFlags{}
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
flags.BoolVar(&c.EnableUserlandProxy, []string{"-userland-proxy"}, true, "")
tempFile := tempfile.NewTempFile(t, "config", `{}`)
defer tempFile.Remove()
f, err := ioutil.TempFile("", "docker-config-")
if err != nil {
t.Fatal(err)
}
opts := defaultOptions(tempFile.Name())
loadedConfig, err := loadDaemonCliConfig(opts)
assert.NilError(t, err)
assert.NotNil(t, loadedConfig)
assert.NotNil(t, loadedConfig.ClusterOpts)
if err := flags.ParseFlags([]string{}, false); err != nil {
t.Fatal(err)
}
configFile := f.Name()
f.Write([]byte(`{}`))
f.Close()
loadedConfig, err := loadDaemonCliConfig(c, flags, common, configFile)
if err != nil {
t.Fatal(err)
}
if loadedConfig == nil {
t.Fatal("expected configuration, got nil")
}
if !loadedConfig.EnableUserlandProxy {
t.Fatal("expected userland proxy to be enabled, got disabled")
}
assert.Equal(t, loadedConfig.EnableUserlandProxy, true)
}
func TestLoadDaemonConfigWithLegacyRegistryOptions(t *testing.T) {
c := &daemon.Config{}
common := &cliflags.CommonFlags{}
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
c.ServiceOptions.InstallCliFlags(flags, absentFromHelp)
content := `{"disable-legacy-registry": true}`
tempFile := tempfile.NewTempFile(t, "config", content)
defer tempFile.Remove()
f, err := ioutil.TempFile("", "docker-config-")
if err != nil {
t.Fatal(err)
}
configFile := f.Name()
defer os.Remove(configFile)
f.Write([]byte(`{"disable-legacy-registry": true}`))
f.Close()
loadedConfig, err := loadDaemonCliConfig(c, flags, common, configFile)
if err != nil {
t.Fatal(err)
}
if loadedConfig == nil {
t.Fatal("expected configuration, got nil")
}
if !loadedConfig.V2Only {
t.Fatal("expected disable-legacy-registry to be true, got false")
}
opts := defaultOptions(tempFile.Name())
loadedConfig, err := loadDaemonCliConfig(opts)
assert.NilError(t, err)
assert.NotNil(t, loadedConfig)
assert.Equal(t, loadedConfig.V2Only, true)
}

View file

@ -5,72 +5,76 @@ import (
"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)
},
}
cli.SetupRootCommand(cmd)
if err := flag.CommandLine.ParseFlags(os.Args[1:], false); err != nil {
os.Exit(1)
}
flags := cmd.Flags()
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)
installServiceFlags(flags)
if *flVersion {
return cmd
}
func runDaemon(opts daemonOptions) error {
if opts.version {
showVersion()
return
return nil
}
if *flHelp {
// if global flag --help is present, regardless of what other options and commands there are,
// just print the usage.
flag.Usage()
return
}
daemonCli := NewDaemonCli()
// On Windows, this may be launching as a service or with an option to
// register the service.
stop, err := initService()
stop, err := initService(daemonCli)
if err != nil {
logrus.Fatal(err)
}
if !stop {
err = daemonCli.start()
notifyShutdown(err)
if err != nil {
logrus.Fatal(err)
}
if stop {
return nil
}
err = daemonCli.start(opts)
notifyShutdown(err)
return err
}
func showVersion() {
@ -80,3 +84,20 @@ 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 {
fmt.Fprintf(stderr, "%s\n", err)
os.Exit(1)
}
}

View file

@ -2,6 +2,13 @@
package main
func initService() (bool, error) {
import (
"github.com/spf13/pflag"
)
func initService(daemonCli *DaemonCli) (bool, error) {
return false, nil
}
func installServiceFlags(flags *pflag.FlagSet) {
}

View file

@ -11,7 +11,7 @@ import (
"syscall"
"github.com/Sirupsen/logrus"
flag "github.com/docker/docker/pkg/mflag"
"github.com/spf13/pflag"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/debug"
@ -20,10 +20,10 @@ import (
)
var (
flServiceName = flag.String([]string{"-service-name"}, "docker", "Set the Windows service name")
flRegisterService = flag.Bool([]string{"-register-service"}, false, "Register the service and exit")
flUnregisterService = flag.Bool([]string{"-unregister-service"}, false, "Unregister the service and exit")
flRunService = flag.Bool([]string{"-run-service"}, false, "")
flServiceName *string
flRegisterService *bool
flUnregisterService *bool
flRunService *bool
setStdHandle = syscall.NewLazyDLL("kernel32.dll").NewProc("SetStdHandle")
oldStderr syscall.Handle
@ -44,9 +44,17 @@ const (
eventExtraOffset = 10 // Add this to any event to get a string that supports extended data
)
func installServiceFlags(flags *pflag.FlagSet) {
flServiceName = flags.String("service-name", "docker", "Set the Windows service name")
flRegisterService = flags.Bool("register-service", false, "Register the service and exit")
flUnregisterService = flags.Bool("unregister-service", false, "Unregister the service and exit")
flRunService = flags.Bool("run-service", false, "")
}
type handler struct {
tosvc chan bool
fromsvc chan error
tosvc chan bool
fromsvc chan error
daemonCli *DaemonCli
}
type etwHook struct {
@ -203,7 +211,7 @@ func unregisterService() error {
return nil
}
func initService() (bool, error) {
func initService(daemonCli *DaemonCli) (bool, error) {
if *flUnregisterService {
if *flRegisterService {
return true, errors.New("--register-service and --unregister-service cannot be used together")
@ -225,8 +233,9 @@ func initService() (bool, error) {
}
h := &handler{
tosvc: make(chan bool),
fromsvc: make(chan error),
tosvc: make(chan bool),
fromsvc: make(chan error),
daemonCli: daemonCli,
}
var log *eventlog.Log
@ -261,7 +270,7 @@ func initService() (bool, error) {
func (h *handler) started() error {
// This must be delayed until daemonCli initializes Config.Root
err := initPanicFile(filepath.Join(daemonCli.Config.Root, "panic.log"))
err := initPanicFile(filepath.Join(h.daemonCli.Config.Root, "panic.log"))
if err != nil {
return err
}
@ -298,12 +307,12 @@ Loop:
case c := <-r:
switch c.Cmd {
case svc.Cmd(windows.SERVICE_CONTROL_PARAMCHANGE):
daemonCli.reloadConfig()
h.daemonCli.reloadConfig()
case svc.Interrogate:
s <- c.CurrentStatus
case svc.Stop, svc.Shutdown:
s <- svc.Status{State: svc.StopPending, Accepts: 0}
daemonCli.stop()
h.daemonCli.stop()
}
}
}

View file

@ -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,39 +146,37 @@ 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 docker 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"))
flags.StringVar(&config.SwarmDefaultAdvertiseAddr, "swarm-default-advertise-addr", "", "Set default address or interface for swarm advertised address")
config.MaxConcurrentDownloads = &maxConcurrentDownloads
config.MaxConcurrentUploads = &maxConcurrentUploads
@ -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
@ -289,7 +300,7 @@ func getConflictFreeConfiguration(configFile string, flags *flag.FlagSet) (*Conf
// 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)
f := flags.Lookup(key)
if f == nil { // ignore named flags that don't match
namedOptions[key] = value
continue
@ -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,12 +350,11 @@ 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 {
flagName := "-" + key
if flag := flags.Lookup(flagName); flag == nil {
if flag := flags.Lookup(key); flag == nil {
unknownKeys[key] = value
}
}
@ -352,7 +362,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,17 +386,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 {
name = strings.TrimLeft(name, "-")
// search flag name in the json configuration payload
for _, name := range []string{f.Name, f.Shorthand} {
if value, ok := config[name]; ok {
conflicts = append(conflicts, printConflict(name, f.Value.String(), value))
break

View file

@ -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) {
}

View file

@ -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

View file

@ -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) {
}

View file

@ -7,7 +7,8 @@ import (
"testing"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/testutil/assert"
"github.com/spf13/pflag"
)
func TestDaemonConfigurationMerge(t *testing.T) {
@ -87,42 +88,26 @@ func TestParseClusterAdvertiseSettings(t *testing.T) {
func TestFindConfigurationConflicts(t *testing.T) {
config := map[string]interface{}{"authorization-plugins": "foobar"}
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
flags.String([]string{"-authorization-plugins"}, "", "")
if err := flags.Set("-authorization-plugins", "asdf"); err != nil {
t.Fatal(err)
}
flags.String("authorization-plugins", "", "")
assert.NilError(t, flags.Set("authorization-plugins", "asdf"))
err := findConfigurationConflicts(config, flags)
if err == nil {
t.Fatal("expected error, got nil")
}
if !strings.Contains(err.Error(), "authorization-plugins: (from flag: asdf, from file: foobar)") {
t.Fatalf("expected authorization-plugins conflict, got %v", err)
}
assert.Error(t,
findConfigurationConflicts(config, flags),
"authorization-plugins: (from flag: asdf, from file: foobar)")
}
func TestFindConfigurationConflictsWithNamedOptions(t *testing.T) {
config := map[string]interface{}{"hosts": []string{"qwer"}}
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
flags := pflag.NewFlagSet("test", pflag.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)
}
flags.VarP(opts.NewNamedListOptsRef("hosts", &hosts, opts.ValidateHost), "host", "H", "Daemon socket(s) to connect to")
assert.NilError(t, flags.Set("host", "tcp://127.0.0.1:4444"))
assert.NilError(t, flags.Set("host", "unix:///var/run/docker.sock"))
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)
}
assert.Error(t, findConfigurationConflicts(config, flags), "hosts")
}
func TestDaemonConfigurationMergeConflicts(t *testing.T) {
@ -135,8 +120,8 @@ func TestDaemonConfigurationMergeConflicts(t *testing.T) {
f.Write([]byte(`{"debug": true}`))
f.Close()
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
flags.Bool([]string{"debug"}, false, "")
flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
flags.Bool("debug", false, "")
flags.Set("debug", "false")
_, err = MergeDaemonConfigurations(&Config{}, flags, configFile)
@ -158,8 +143,8 @@ func TestDaemonConfigurationMergeConflictsWithInnerStructs(t *testing.T) {
f.Write([]byte(`{"tlscacert": "/etc/certificates/ca.pem"}`))
f.Close()
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
flags.String([]string{"tlscacert"}, "", "")
flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
flags.String("tlscacert", "", "")
flags.Set("tlscacert", "~/.docker/ca.pem")
_, err = MergeDaemonConfigurations(&Config{}, flags, configFile)
@ -173,9 +158,9 @@ func TestDaemonConfigurationMergeConflictsWithInnerStructs(t *testing.T) {
func TestFindConfigurationConflictsWithUnknownKeys(t *testing.T) {
config := map[string]interface{}{"tls-verify": "true"}
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
flags.Bool([]string{"-tlsverify"}, false, "")
flags.Bool("tlsverify", false, "")
err := findConfigurationConflicts(config, flags)
if err == nil {
t.Fatal("expected error, got nil")
@ -188,18 +173,15 @@ func TestFindConfigurationConflictsWithUnknownKeys(t *testing.T) {
func TestFindConfigurationConflictsWithMergedValues(t *testing.T) {
var hosts []string
config := map[string]interface{}{"hosts": "tcp://127.0.0.1:2345"}
base := mflag.NewFlagSet("base", mflag.ContinueOnError)
base.Var(opts.NewNamedListOptsRef("hosts", &hosts, nil), []string{"H", "-host"}, "")
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
mflag.Merge(flags, base)
flags := pflag.NewFlagSet("base", pflag.ContinueOnError)
flags.VarP(opts.NewNamedListOptsRef("hosts", &hosts, nil), "host", "H", "")
err := findConfigurationConflicts(config, flags)
if err != nil {
t.Fatal(err)
}
flags.Set("-host", "unix:///var/run/docker.sock")
flags.Set("host", "unix:///var/run/docker.sock")
err = findConfigurationConflicts(config, flags)
if err == nil {
t.Fatal("expected error, got nil")

View file

@ -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,43 @@ 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.StringVarP(&config.SocketGroup, "group", "G", "docker", "Group for the unix socket")
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

View file

@ -3,8 +3,8 @@ package daemon
import (
"os"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/engine-api/types"
"github.com/spf13/pflag"
)
var (
@ -28,18 +28,15 @@ type Config struct {
// for the Windows daemon.)
}
// 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)
// Then platform-specific install flags.
cmd.StringVar(&config.bridgeConfig.FixedCIDR, []string{"-fixed-cidr"}, "", usageFn("IPv4 subnet for fixed IPs"))
cmd.StringVar(&config.bridgeConfig.Iface, []string{"b", "-bridge"}, "", "Attach containers to a virtual switch")
cmd.StringVar(&config.SocketGroup, []string{"G", "-group"}, "", usageFn("Users or groups that can access the named pipe"))
flags.StringVar(&config.bridgeConfig.FixedCIDR, "fixed-cidr", "", "IPv4 subnet for fixed IPs")
flags.StringVarP(&config.bridgeConfig.Iface, "bridge", "b", "", "Attach containers to a virtual switch")
flags.StringVarP(&config.SocketGroup, "group", "G", "", "Users or groups that can access the named pipe")
}
// GetRuntime returns the runtime path and arguments for a given

View file

@ -162,7 +162,7 @@ clone git github.com/matttproud/golang_protobuf_extensions fc2b8d3a73c4867e51861
clone git github.com/pkg/errors 01fa4104b9c248c8945d14d9f128454d5b28d595
# cli
clone git github.com/spf13/cobra 75205f23b3ea70dc7ae5e900d074e010c23c37e9 https://github.com/dnephin/cobra.git
clone git github.com/spf13/cobra v1.4.1 https://github.com/dnephin/cobra.git
clone git github.com/spf13/pflag cb88ea77998c3f024757528e3305022ab50b43be
clone git github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
clone git github.com/flynn-archive/go-shlex 3f9db97f856818214da2e1057f8ad84803971cff

View file

@ -1555,12 +1555,12 @@ func (s *DockerSuite) TestBuildWithInaccessibleFilesInContext(c *check.C) {
c.Fatalf("failed to chmod file to 700: %s", err)
}
buildCmd := exec.Command("su", "unprivilegeduser", "-c", fmt.Sprintf("%s build -t %s .", dockerBinary, name))
buildCmd.Dir = ctx.Dir
if out, _, err := runCommandWithOutput(buildCmd); err != nil {
c.Fatalf("build should have worked: %s %s", err, out)
}
result := icmd.RunCmd(icmd.Cmd{
Dir: ctx.Dir,
Command: []string{"su", "unprivilegeduser", "-c",
fmt.Sprintf("%s build -t %s .", dockerBinary, name)},
})
result.Assert(c, icmd.Expected{})
}
}

View file

@ -11,6 +11,7 @@ import (
"strings"
"github.com/docker/docker/pkg/integration/checker"
icmd "github.com/docker/docker/pkg/integration/cmd"
"github.com/go-check/check"
)
@ -399,10 +400,9 @@ func (s *DockerSuite) TestCpUnprivilegedUser(c *check.C) {
c.Assert(os.Chmod(tmpdir, 0777), checker.IsNil)
path := cpTestName
_, _, err = runCommandWithOutput(exec.Command("su", "unprivilegeduser", "-c", dockerBinary+" cp "+containerID+":"+path+" "+tmpdir))
c.Assert(err, checker.IsNil, check.Commentf("couldn't copy with unprivileged user: %s:%s", containerID, path))
result := icmd.RunCommand("su", "unprivilegeduser", "-c",
fmt.Sprintf("%s cp %s:%s %s", dockerBinary, containerID, cpTestName, tmpdir))
result.Assert(c, icmd.Expected{})
}
func (s *DockerSuite) TestCpSpecialFiles(c *check.C) {

View file

@ -23,6 +23,7 @@ import (
"github.com/docker/docker/pkg/integration/checker"
icmd "github.com/docker/docker/pkg/integration/cmd"
"github.com/docker/docker/pkg/mount"
"github.com/docker/docker/pkg/testutil/tempfile"
"github.com/docker/go-units"
"github.com/docker/libnetwork/iptables"
"github.com/docker/libtrust"
@ -352,10 +353,8 @@ func (s *DockerDaemonSuite) TestDaemonIptablesCreate(c *check.C) {
func (s *DockerSuite) TestDaemonIPv6Enabled(c *check.C) {
testRequires(c, IPv6)
if err := setupV6(); err != nil {
c.Fatal("Could not set up host for IPv6 tests")
}
setupV6(c)
defer teardownV6(c)
d := NewDaemon(c)
if err := d.StartWithBusybox("--ipv6"); err != nil {
@ -412,11 +411,6 @@ func (s *DockerSuite) TestDaemonIPv6Enabled(c *check.C) {
if ip := net.ParseIP(out); ip != nil {
c.Fatalf("Container should not have a global IPv6 address: %v", out)
}
if err := teardownV6(); err != nil {
c.Fatal("Could not perform teardown for IPv6 tests")
}
}
// TestDaemonIPv6FixedCIDR checks that when the daemon is started with --ipv6=true and a fixed CIDR
@ -424,16 +418,16 @@ func (s *DockerSuite) TestDaemonIPv6Enabled(c *check.C) {
func (s *DockerDaemonSuite) TestDaemonIPv6FixedCIDR(c *check.C) {
// IPv6 setup is messing with local bridge address.
testRequires(c, SameHostDaemon)
err := setupV6()
c.Assert(err, checker.IsNil, check.Commentf("Could not set up host for IPv6 tests"))
setupV6(c)
defer teardownV6(c)
err = s.d.StartWithBusybox("--ipv6", "--fixed-cidr-v6='2001:db8:2::/64'", "--default-gateway-v6='2001:db8:2::100'")
err := s.d.StartWithBusybox("--ipv6", "--fixed-cidr-v6=2001:db8:2::/64", "--default-gateway-v6=2001:db8:2::100")
c.Assert(err, checker.IsNil, check.Commentf("Could not start daemon with busybox: %v", err))
out, err := s.d.Cmd("run", "-itd", "--name=ipv6test", "busybox:latest")
c.Assert(err, checker.IsNil, check.Commentf("Could not run container: %s, %v", out, err))
out, err = s.d.Cmd("inspect", "--format", "'{{.NetworkSettings.Networks.bridge.GlobalIPv6Address}}'", "ipv6test")
out, err = s.d.Cmd("inspect", "--format", "{{.NetworkSettings.Networks.bridge.GlobalIPv6Address}}", "ipv6test")
out = strings.Trim(out, " \r\n'")
c.Assert(err, checker.IsNil, check.Commentf(out))
@ -441,13 +435,10 @@ func (s *DockerDaemonSuite) TestDaemonIPv6FixedCIDR(c *check.C) {
ip := net.ParseIP(out)
c.Assert(ip, checker.NotNil, check.Commentf("Container should have a global IPv6 address"))
out, err = s.d.Cmd("inspect", "--format", "'{{.NetworkSettings.Networks.bridge.IPv6Gateway}}'", "ipv6test")
out, err = s.d.Cmd("inspect", "--format", "{{.NetworkSettings.Networks.bridge.IPv6Gateway}}", "ipv6test")
c.Assert(err, checker.IsNil, check.Commentf(out))
c.Assert(strings.Trim(out, " \r\n'"), checker.Equals, "2001:db8:2::100", check.Commentf("Container should have a global IPv6 gateway"))
err = teardownV6()
c.Assert(err, checker.IsNil, check.Commentf("Could not perform teardown for IPv6 tests"))
}
// TestDaemonIPv6FixedCIDRAndMac checks that when the daemon is started with ipv6 fixed CIDR
@ -455,21 +446,18 @@ func (s *DockerDaemonSuite) TestDaemonIPv6FixedCIDR(c *check.C) {
func (s *DockerDaemonSuite) TestDaemonIPv6FixedCIDRAndMac(c *check.C) {
// IPv6 setup is messing with local bridge address.
testRequires(c, SameHostDaemon)
err := setupV6()
c.Assert(err, checker.IsNil)
setupV6(c)
defer teardownV6(c)
err = s.d.StartWithBusybox("--ipv6", "--fixed-cidr-v6='2001:db8:1::/64'")
err := s.d.StartWithBusybox("--ipv6", "--fixed-cidr-v6=2001:db8:1::/64")
c.Assert(err, checker.IsNil)
out, err := s.d.Cmd("run", "-itd", "--name=ipv6test", "--mac-address", "AA:BB:CC:DD:EE:FF", "busybox")
c.Assert(err, checker.IsNil)
out, err = s.d.Cmd("inspect", "--format", "'{{.NetworkSettings.Networks.bridge.GlobalIPv6Address}}'", "ipv6test")
out, err = s.d.Cmd("inspect", "--format", "{{.NetworkSettings.Networks.bridge.GlobalIPv6Address}}", "ipv6test")
c.Assert(err, checker.IsNil)
c.Assert(strings.Trim(out, " \r\n'"), checker.Equals, "2001:db8:1::aabb:ccdd:eeff")
err = teardownV6()
c.Assert(err, checker.IsNil)
}
func (s *DockerDaemonSuite) TestDaemonLogLevelWrong(c *check.C) {
@ -1724,13 +1712,15 @@ func (s *DockerDaemonSuite) TestDaemonNoTlsCliTlsVerifyWithEnv(c *check.C) {
}
func setupV6() error {
func setupV6(c *check.C) {
// Hack to get the right IPv6 address on docker0, which has already been created
return exec.Command("ip", "addr", "add", "fe80::1/64", "dev", "docker0").Run()
result := icmd.RunCommand("ip", "addr", "add", "fe80::1/64", "dev", "docker0")
result.Assert(c, icmd.Expected{})
}
func teardownV6() error {
return exec.Command("ip", "addr", "del", "fe80::1/64", "dev", "docker0").Run()
func teardownV6(c *check.C) {
result := icmd.RunCommand("ip", "addr", "del", "fe80::1/64", "dev", "docker0")
result.Assert(c, icmd.Expected{})
}
func (s *DockerDaemonSuite) TestDaemonRestartWithContainerWithRestartPolicyAlways(c *check.C) {
@ -2362,31 +2352,26 @@ func (s *DockerSuite) TestDaemonDiscoveryBackendConfigReload(c *check.C) {
testRequires(c, SameHostDaemon, DaemonIsLinux)
// daemon config file
daemonConfig := `{ "debug" : false }`
configFilePath := "test.json"
configFile, err := os.Create(configFilePath)
c.Assert(err, checker.IsNil)
fmt.Fprintf(configFile, "%s", daemonConfig)
tmpfile := tempfile.NewTempFile(c, "config-test", `{ "debug" : false }`)
defer tmpfile.Remove()
d := NewDaemon(c)
err = d.Start(fmt.Sprintf("--config-file=%s", configFilePath))
// --log-level needs to be set so that d.Start() doesn't add --debug causing
// a conflict with the config
err := d.Start("--config-file", tmpfile.Name(), "--log-level=info")
c.Assert(err, checker.IsNil)
defer d.Stop()
// daemon config file
daemonConfig = `{
daemonConfig := `{
"cluster-store": "consul://consuladdr:consulport/some/path",
"cluster-advertise": "192.168.56.100:0",
"debug" : false
}`
configFile.Close()
os.Remove(configFilePath)
configFile, err = os.Create(configFilePath)
os.Remove(tmpfile.Name())
configFile, err := os.Create(tmpfile.Name())
c.Assert(err, checker.IsNil)
defer os.Remove(configFilePath)
fmt.Fprintf(configFile, "%s", daemonConfig)
configFile.Close()
@ -2396,6 +2381,7 @@ func (s *DockerSuite) TestDaemonDiscoveryBackendConfigReload(c *check.C) {
out, err := d.Cmd("info")
c.Assert(err, checker.IsNil)
c.Assert(out, checker.Contains, fmt.Sprintf("Cluster Store: consul://consuladdr:consulport/some/path"))
c.Assert(out, checker.Contains, fmt.Sprintf("Cluster Advertise: 192.168.56.100:0"))
}

View file

@ -9,6 +9,7 @@ import (
"github.com/docker/docker/pkg/homedir"
"github.com/docker/docker/pkg/integration/checker"
icmd "github.com/docker/docker/pkg/integration/cmd"
"github.com/go-check/check"
)
@ -56,12 +57,7 @@ func (s *DockerSuite) TestHelpTextVerify(c *check.C) {
out, _, err := runCommandWithOutput(helpCmd)
c.Assert(err, checker.IsNil, check.Commentf(out))
lines := strings.Split(out, "\n")
foundTooLongLine := false
for _, line := range lines {
if !foundTooLongLine && len(line) > 80 {
c.Logf("Line is too long:\n%s", line)
foundTooLongLine = true
}
// All lines should not end with a space
c.Assert(line, checker.Not(checker.HasSuffix), " ", check.Commentf("Line should not end with a space"))
@ -229,14 +225,6 @@ func testCommand(cmd string, newEnvs []string, scanForHome bool, home string) er
// Check each line for lots of stuff
lines := strings.Split(out, "\n")
for _, line := range lines {
if len(line) > 107 {
return fmt.Errorf("Help for %q is too long:\n%s\n", cmd, line)
}
if scanForHome && strings.Contains(line, `"`+home) {
return fmt.Errorf("Help for %q should use ~ instead of %q on:\n%s\n",
cmd, home, line)
}
i := strings.Index(line, "~")
if i >= 0 && i != len(line)-1 && line[i+1] != '/' {
return fmt.Errorf("Help for %q should not have used ~:\n%s", cmd, line)
@ -290,10 +278,6 @@ func testCommand(cmd string, newEnvs []string, scanForHome bool, home string) er
}
if _, ok := noShortUsage[cmd]; !ok {
// For each command run it w/o any args. It will either return
// valid output or print a short-usage
var dCmd *exec.Cmd
// skipNoArgs are ones that we don't want to try w/o
// any args. Either because it'll hang the test or
// lead to incorrect test result (like false negative).
@ -305,33 +289,31 @@ func testCommand(cmd string, newEnvs []string, scanForHome bool, home string) er
"load": {},
}
ec := 0
var result *icmd.Result
if _, ok := skipNoArgs[cmd]; !ok {
args = strings.Split(cmd, " ")
dCmd = exec.Command(dockerBinary, args...)
out, stderr, ec, err = runCommandWithStdoutStderr(dCmd)
result = dockerCmdWithResult(strings.Split(cmd, " ")...)
}
// If its ok w/o any args then try again with an arg
if ec == 0 {
args = strings.Split(cmd+" badArg", " ")
dCmd = exec.Command(dockerBinary, args...)
out, stderr, ec, err = runCommandWithStdoutStderr(dCmd)
if result == nil || result.ExitCode == 0 {
result = dockerCmdWithResult(strings.Split(cmd+" badArg", " ")...)
}
if len(out) != 0 || len(stderr) == 0 || ec == 0 || err == nil {
return fmt.Errorf("Bad output from %q\nstdout:%q\nstderr:%q\nec:%d\nerr:%q\n", args, out, stderr, ec, err)
if err := result.Compare(icmd.Expected{
Out: icmd.None,
Err: "\nUsage:",
ExitCode: 1,
}); err != nil {
return err
}
// Should have just short usage
if !strings.Contains(stderr, "\nUsage:") {
return fmt.Errorf("Missing short usage on %q\n:%#v", args, stderr)
}
// But shouldn't have full usage
stderr := result.Stderr()
// Shouldn't have full usage
if strings.Contains(stderr, "--help=false") {
return fmt.Errorf("Should not have full usage on %q\n", args)
return fmt.Errorf("Should not have full usage on %q:%v", result.Cmd.Args, stderr)
}
if strings.HasSuffix(stderr, "\n\n") {
return fmt.Errorf("Should not have a blank line on %q\n%v", args, stderr)
return fmt.Errorf("Should not have a blank line on %q\n%v", result.Cmd.Args, stderr)
}
}

View file

@ -334,11 +334,8 @@ func (s *DockerSuite) TestUserDefinedNetworkAlias(c *check.C) {
// Issue 9677.
func (s *DockerSuite) TestRunWithDaemonFlags(c *check.C) {
out, _, err := dockerCmdWithError("--exec-opt", "foo=bar", "run", "-i", "busybox", "true")
if err != nil {
if !strings.Contains(out, "flag provided but not defined: --exec-opt") { // no daemon (client-only)
c.Fatal(err, out)
}
}
c.Assert(err, checker.NotNil)
c.Assert(out, checker.Contains, "unknown flag: --exec-opt")
}
// Regression test for #4979
@ -2663,15 +2660,11 @@ func (s *DockerSuite) TestRunTLSverify(c *check.C) {
// Regardless of whether we specify true or false we need to
// test to make sure tls is turned on if --tlsverify is specified at all
out, code, err := dockerCmdWithError("--tlsverify=false", "ps")
if err == nil || code == 0 || !strings.Contains(out, "trying to connect") {
c.Fatalf("Should have failed: \net:%v\nout:%v\nerr:%v", code, out, err)
}
result := dockerCmdWithResult("--tlsverify=false", "ps")
result.Assert(c, icmd.Expected{ExitCode: 1, Err: "trying to connect"})
out, code, err = dockerCmdWithError("--tlsverify=true", "ps")
if err == nil || code == 0 || !strings.Contains(out, "cert") {
c.Fatalf("Should have failed: \net:%v\nout:%v\nerr:%v", code, out, err)
}
result = dockerCmdWithResult("--tlsverify=true", "ps")
result.Assert(c, icmd.Expected{ExitCode: 1, Err: "cert"})
}
func (s *DockerSuite) TestRunPortFromDockerRangeInUse(c *check.C) {

View file

@ -4,8 +4,10 @@ import (
"fmt"
"os"
"github.com/docker/docker/cli/cobraadaptor"
cliflags "github.com/docker/docker/cli/flags"
"github.com/docker/docker/api/client"
"github.com/docker/docker/api/client/command"
"github.com/docker/docker/pkg/term"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
)
@ -15,10 +17,12 @@ func generateManPages(path string) error {
Section: "1",
Source: "Docker Community",
}
flags := &cliflags.ClientFlags{
Common: cliflags.InitCommonFlags(),
}
cmd := cobraadaptor.NewCobraAdaptor(flags).GetRootCommand()
stdin, stdout, stderr := term.StdStreams()
dockerCli := client.NewDockerCli(stdin, stdout, stderr)
cmd := &cobra.Command{Use: "docker"}
command.AddCommands(cmd, dockerCli)
cmd.DisableAutoGenTag = true
return doc.GenManTreeFromOpts(cmd, doc.GenManTreeOptions{
Header: header,

View file

@ -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"
}

View file

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

View file

@ -1,40 +0,0 @@
Package mflag (aka multiple-flag) implements command-line flag parsing.
It's a **hacky** fork of the [official golang package](http://golang.org/pkg/flag/)
It adds:
* both short and long flag version
`./example -s red` `./example --string blue`
* multiple names for the same option
```
$>./example -h
Usage of example:
-s, --string="": a simple string
```
___
It is very flexible on purpose, so you can do things like:
```
$>./example -h
Usage of example:
-s, -string, --string="": a simple string
```
Or:
```
$>./example -h
Usage of example:
-oldflag, --newflag="": a simple string
```
You can also hide some flags from the usage, so if we want only `--newflag`:
```
$>./example -h
Usage of example:
--newflag="": a simple string
$>./example -oldflag str
str
```
See [example.go](example/example.go) for more details.

View file

@ -1,37 +0,0 @@
package main
import (
"fmt"
flag "github.com/docker/docker/pkg/mflag"
)
var (
i int
str string
b, b2, h bool
)
func init() {
flag.Bool([]string{"#hp", "#-halp"}, false, "display the halp")
flag.BoolVar(&b, []string{"b", "#bal", "#bol", "-bal"}, false, "a simple bool")
flag.BoolVar(&b, []string{"g", "#gil"}, false, "a simple bool")
flag.BoolVar(&b2, []string{"#-bool"}, false, "a simple bool")
flag.IntVar(&i, []string{"-integer", "-number"}, -1, "a simple integer")
flag.StringVar(&str, []string{"s", "#hidden", "-string"}, "", "a simple string") //-s -hidden and --string will work, but -hidden won't be in the usage
flag.BoolVar(&h, []string{"h", "#help", "-help"}, false, "display the help")
flag.StringVar(&str, []string{"mode"}, "mode1", "set the mode\nmode1: use the mode1\nmode2: use the mode2\nmode3: use the mode3")
flag.Parse()
}
func main() {
if h {
flag.PrintDefaults()
} else {
fmt.Printf("s/#hidden/-string: %s\n", str)
fmt.Printf("b: %t\n", b)
fmt.Printf("-bool: %t\n", b2)
fmt.Printf("-integer/-number: %d\n", i)
fmt.Printf("s/#hidden/-string(via lookup): %s\n", flag.Lookup("s").Value.String())
fmt.Printf("ARGS: %v\n", flag.Args())
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,527 +0,0 @@
// Copyright 2014-2016 The Docker & Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package mflag
import (
"bytes"
"fmt"
"os"
"sort"
"strings"
"testing"
"time"
)
// ResetForTesting clears all flag state and sets the usage function as directed.
// After calling ResetForTesting, parse errors in flag handling will not
// exit the program.
func ResetForTesting(usage func()) {
CommandLine = NewFlagSet(os.Args[0], ContinueOnError)
Usage = usage
}
func boolString(s string) string {
if s == "0" {
return "false"
}
return "true"
}
func TestEverything(t *testing.T) {
ResetForTesting(nil)
Bool([]string{"test_bool"}, false, "bool value")
Int([]string{"test_int"}, 0, "int value")
Int64([]string{"test_int64"}, 0, "int64 value")
Uint([]string{"test_uint"}, 0, "uint value")
Uint64([]string{"test_uint64"}, 0, "uint64 value")
String([]string{"test_string"}, "0", "string value")
Float64([]string{"test_float64"}, 0, "float64 value")
Duration([]string{"test_duration"}, 0, "time.Duration value")
m := make(map[string]*Flag)
desired := "0"
visitor := func(f *Flag) {
for _, name := range f.Names {
if len(name) > 5 && name[0:5] == "test_" {
m[name] = f
ok := false
switch {
case f.Value.String() == desired:
ok = true
case name == "test_bool" && f.Value.String() == boolString(desired):
ok = true
case name == "test_duration" && f.Value.String() == desired+"s":
ok = true
}
if !ok {
t.Error("Visit: bad value", f.Value.String(), "for", name)
}
}
}
}
VisitAll(visitor)
if len(m) != 8 {
t.Error("VisitAll misses some flags")
for k, v := range m {
t.Log(k, *v)
}
}
m = make(map[string]*Flag)
Visit(visitor)
if len(m) != 0 {
t.Errorf("Visit sees unset flags")
for k, v := range m {
t.Log(k, *v)
}
}
// Now set all flags
Set("test_bool", "true")
Set("test_int", "1")
Set("test_int64", "1")
Set("test_uint", "1")
Set("test_uint64", "1")
Set("test_string", "1")
Set("test_float64", "1")
Set("test_duration", "1s")
desired = "1"
Visit(visitor)
if len(m) != 8 {
t.Error("Visit fails after set")
for k, v := range m {
t.Log(k, *v)
}
}
// Now test they're visited in sort order.
var flagNames []string
Visit(func(f *Flag) {
for _, name := range f.Names {
flagNames = append(flagNames, name)
}
})
if !sort.StringsAreSorted(flagNames) {
t.Errorf("flag names not sorted: %v", flagNames)
}
}
func TestGet(t *testing.T) {
ResetForTesting(nil)
Bool([]string{"test_bool"}, true, "bool value")
Int([]string{"test_int"}, 1, "int value")
Int64([]string{"test_int64"}, 2, "int64 value")
Uint([]string{"test_uint"}, 3, "uint value")
Uint64([]string{"test_uint64"}, 4, "uint64 value")
String([]string{"test_string"}, "5", "string value")
Float64([]string{"test_float64"}, 6, "float64 value")
Duration([]string{"test_duration"}, 7, "time.Duration value")
visitor := func(f *Flag) {
for _, name := range f.Names {
if len(name) > 5 && name[0:5] == "test_" {
g, ok := f.Value.(Getter)
if !ok {
t.Errorf("Visit: value does not satisfy Getter: %T", f.Value)
return
}
switch name {
case "test_bool":
ok = g.Get() == true
case "test_int":
ok = g.Get() == int(1)
case "test_int64":
ok = g.Get() == int64(2)
case "test_uint":
ok = g.Get() == uint(3)
case "test_uint64":
ok = g.Get() == uint64(4)
case "test_string":
ok = g.Get() == "5"
case "test_float64":
ok = g.Get() == float64(6)
case "test_duration":
ok = g.Get() == time.Duration(7)
}
if !ok {
t.Errorf("Visit: bad value %T(%v) for %s", g.Get(), g.Get(), name)
}
}
}
}
VisitAll(visitor)
}
func testParse(f *FlagSet, t *testing.T) {
if f.Parsed() {
t.Error("f.Parse() = true before Parse")
}
boolFlag := f.Bool([]string{"bool"}, false, "bool value")
bool2Flag := f.Bool([]string{"bool2"}, false, "bool2 value")
f.Bool([]string{"bool3"}, false, "bool3 value")
bool4Flag := f.Bool([]string{"bool4"}, false, "bool4 value")
intFlag := f.Int([]string{"-int"}, 0, "int value")
int64Flag := f.Int64([]string{"-int64"}, 0, "int64 value")
uintFlag := f.Uint([]string{"uint"}, 0, "uint value")
uint64Flag := f.Uint64([]string{"-uint64"}, 0, "uint64 value")
stringFlag := f.String([]string{"string"}, "0", "string value")
f.String([]string{"string2"}, "0", "string2 value")
singleQuoteFlag := f.String([]string{"squote"}, "", "single quoted value")
doubleQuoteFlag := f.String([]string{"dquote"}, "", "double quoted value")
mixedQuoteFlag := f.String([]string{"mquote"}, "", "mixed quoted value")
mixed2QuoteFlag := f.String([]string{"mquote2"}, "", "mixed2 quoted value")
nestedQuoteFlag := f.String([]string{"nquote"}, "", "nested quoted value")
nested2QuoteFlag := f.String([]string{"nquote2"}, "", "nested2 quoted value")
float64Flag := f.Float64([]string{"float64"}, 0, "float64 value")
durationFlag := f.Duration([]string{"duration"}, 5*time.Second, "time.Duration value")
extra := "one-extra-argument"
args := []string{
"-bool",
"-bool2=true",
"-bool4=false",
"--int", "22",
"--int64", "0x23",
"-uint", "24",
"--uint64", "25",
"-string", "hello",
"-squote='single'",
`-dquote="double"`,
`-mquote='mixed"`,
`-mquote2="mixed2'`,
`-nquote="'single nested'"`,
`-nquote2='"double nested"'`,
"-float64", "2718e28",
"-duration", "2m",
extra,
}
if err := f.Parse(args); err != nil {
t.Fatal(err)
}
if !f.Parsed() {
t.Error("f.Parse() = false after Parse")
}
if *boolFlag != true {
t.Error("bool flag should be true, is ", *boolFlag)
}
if *bool2Flag != true {
t.Error("bool2 flag should be true, is ", *bool2Flag)
}
if !f.IsSet("bool2") {
t.Error("bool2 should be marked as set")
}
if f.IsSet("bool3") {
t.Error("bool3 should not be marked as set")
}
if !f.IsSet("bool4") {
t.Error("bool4 should be marked as set")
}
if *bool4Flag != false {
t.Error("bool4 flag should be false, is ", *bool4Flag)
}
if *intFlag != 22 {
t.Error("int flag should be 22, is ", *intFlag)
}
if *int64Flag != 0x23 {
t.Error("int64 flag should be 0x23, is ", *int64Flag)
}
if *uintFlag != 24 {
t.Error("uint flag should be 24, is ", *uintFlag)
}
if *uint64Flag != 25 {
t.Error("uint64 flag should be 25, is ", *uint64Flag)
}
if *stringFlag != "hello" {
t.Error("string flag should be `hello`, is ", *stringFlag)
}
if !f.IsSet("string") {
t.Error("string flag should be marked as set")
}
if f.IsSet("string2") {
t.Error("string2 flag should not be marked as set")
}
if *singleQuoteFlag != "single" {
t.Error("single quote string flag should be `single`, is ", *singleQuoteFlag)
}
if *doubleQuoteFlag != "double" {
t.Error("double quote string flag should be `double`, is ", *doubleQuoteFlag)
}
if *mixedQuoteFlag != `'mixed"` {
t.Error("mixed quote string flag should be `'mixed\"`, is ", *mixedQuoteFlag)
}
if *mixed2QuoteFlag != `"mixed2'` {
t.Error("mixed2 quote string flag should be `\"mixed2'`, is ", *mixed2QuoteFlag)
}
if *nestedQuoteFlag != "'single nested'" {
t.Error("nested quote string flag should be `'single nested'`, is ", *nestedQuoteFlag)
}
if *nested2QuoteFlag != `"double nested"` {
t.Error("double quote string flag should be `\"double nested\"`, is ", *nested2QuoteFlag)
}
if *float64Flag != 2718e28 {
t.Error("float64 flag should be 2718e28, is ", *float64Flag)
}
if *durationFlag != 2*time.Minute {
t.Error("duration flag should be 2m, is ", *durationFlag)
}
if len(f.Args()) != 1 {
t.Error("expected one argument, got", len(f.Args()))
} else if f.Args()[0] != extra {
t.Errorf("expected argument %q got %q", extra, f.Args()[0])
}
}
func testPanic(f *FlagSet, t *testing.T) {
f.Int([]string{"-int"}, 0, "int value")
if f.Parsed() {
t.Error("f.Parse() = true before Parse")
}
args := []string{
"-int", "21",
}
f.Parse(args)
}
func TestParsePanic(t *testing.T) {
ResetForTesting(func() {})
testPanic(CommandLine, t)
}
func TestParse(t *testing.T) {
ResetForTesting(func() { t.Error("bad parse") })
testParse(CommandLine, t)
}
func TestFlagSetParse(t *testing.T) {
testParse(NewFlagSet("test", ContinueOnError), t)
}
// Declare a user-defined flag type.
type flagVar []string
func (f *flagVar) String() string {
return fmt.Sprint([]string(*f))
}
func (f *flagVar) Set(value string) error {
*f = append(*f, value)
return nil
}
func TestUserDefined(t *testing.T) {
var flags FlagSet
flags.Init("test", ContinueOnError)
var v flagVar
flags.Var(&v, []string{"v"}, "usage")
if err := flags.Parse([]string{"-v", "1", "-v", "2", "-v=3"}); err != nil {
t.Error(err)
}
if len(v) != 3 {
t.Fatal("expected 3 args; got ", len(v))
}
expect := "[1 2 3]"
if v.String() != expect {
t.Errorf("expected value %q got %q", expect, v.String())
}
}
// Declare a user-defined boolean flag type.
type boolFlagVar struct {
count int
}
func (b *boolFlagVar) String() string {
return fmt.Sprintf("%d", b.count)
}
func (b *boolFlagVar) Set(value string) error {
if value == "true" {
b.count++
}
return nil
}
func (b *boolFlagVar) IsBoolFlag() bool {
return b.count < 4
}
func TestUserDefinedBool(t *testing.T) {
var flags FlagSet
flags.Init("test", ContinueOnError)
var b boolFlagVar
var err error
flags.Var(&b, []string{"b"}, "usage")
if err = flags.Parse([]string{"-b", "-b", "-b", "-b=true", "-b=false", "-b", "barg", "-b"}); err != nil {
if b.count < 4 {
t.Error(err)
}
}
if b.count != 4 {
t.Errorf("want: %d; got: %d", 4, b.count)
}
if err == nil {
t.Error("expected error; got none")
}
}
func TestSetOutput(t *testing.T) {
var flags FlagSet
var buf bytes.Buffer
flags.SetOutput(&buf)
flags.Init("test", ContinueOnError)
flags.Parse([]string{"-unknown"})
if out := buf.String(); !strings.Contains(out, "-unknown") {
t.Logf("expected output mentioning unknown; got %q", out)
}
}
// This tests that one can reset the flags. This still works but not well, and is
// superseded by FlagSet.
func TestChangingArgs(t *testing.T) {
ResetForTesting(func() { t.Fatal("bad parse") })
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
os.Args = []string{"cmd", "-before", "subcmd", "-after", "args"}
before := Bool([]string{"before"}, false, "")
if err := CommandLine.Parse(os.Args[1:]); err != nil {
t.Fatal(err)
}
cmd := Arg(0)
os.Args = Args()
after := Bool([]string{"after"}, false, "")
Parse()
args := Args()
if !*before || cmd != "subcmd" || !*after || len(args) != 1 || args[0] != "args" {
t.Fatalf("expected true subcmd true [args] got %v %v %v %v", *before, cmd, *after, args)
}
}
// Test that -help invokes the usage message and returns ErrHelp.
func TestHelp(t *testing.T) {
var helpCalled = false
fs := NewFlagSet("help test", ContinueOnError)
fs.Usage = func() { helpCalled = true }
var flag bool
fs.BoolVar(&flag, []string{"flag"}, false, "regular flag")
// Regular flag invocation should work
err := fs.Parse([]string{"-flag=true"})
if err != nil {
t.Fatal("expected no error; got ", err)
}
if !flag {
t.Error("flag was not set by -flag")
}
if helpCalled {
t.Error("help called for regular flag")
helpCalled = false // reset for next test
}
// Help flag should work as expected.
err = fs.Parse([]string{"-help"})
if err == nil {
t.Fatal("error expected")
}
if err != ErrHelp {
t.Fatal("expected ErrHelp; got ", err)
}
if !helpCalled {
t.Fatal("help was not called")
}
// If we define a help flag, that should override.
var help bool
fs.BoolVar(&help, []string{"help"}, false, "help flag")
helpCalled = false
err = fs.Parse([]string{"-help"})
if err != nil {
t.Fatal("expected no error for defined -help; got ", err)
}
if helpCalled {
t.Fatal("help was called; should not have been for defined help flag")
}
}
// Test the flag count functions.
func TestFlagCounts(t *testing.T) {
fs := NewFlagSet("help test", ContinueOnError)
var flag bool
fs.BoolVar(&flag, []string{"flag1"}, false, "regular flag")
fs.BoolVar(&flag, []string{"#deprecated1"}, false, "regular flag")
fs.BoolVar(&flag, []string{"f", "flag2"}, false, "regular flag")
fs.BoolVar(&flag, []string{"#d", "#deprecated2"}, false, "regular flag")
fs.BoolVar(&flag, []string{"flag3"}, false, "regular flag")
fs.BoolVar(&flag, []string{"g", "#flag4", "-flag4"}, false, "regular flag")
if fs.FlagCount() != 6 {
t.Fatal("FlagCount wrong. ", fs.FlagCount())
}
if fs.FlagCountUndeprecated() != 4 {
t.Fatal("FlagCountUndeprecated wrong. ", fs.FlagCountUndeprecated())
}
if fs.NFlag() != 0 {
t.Fatal("NFlag wrong. ", fs.NFlag())
}
err := fs.Parse([]string{"-fd", "-g", "-flag4"})
if err != nil {
t.Fatal("expected no error for defined -help; got ", err)
}
if fs.NFlag() != 4 {
t.Fatal("NFlag wrong. ", fs.NFlag())
}
}
// Show up bug in sortFlags
func TestSortFlags(t *testing.T) {
fs := NewFlagSet("help TestSortFlags", ContinueOnError)
var err error
var b bool
fs.BoolVar(&b, []string{"b", "-banana"}, false, "usage")
err = fs.Parse([]string{"--banana=true"})
if err != nil {
t.Fatal("expected no error; got ", err)
}
count := 0
fs.VisitAll(func(flag *Flag) {
count++
if flag == nil {
t.Fatal("VisitAll should not return a nil flag")
}
})
flagcount := fs.FlagCount()
if flagcount != count {
t.Fatalf("FlagCount (%d) != number (%d) of elements visited", flagcount, count)
}
// Make sure its idempotent
if flagcount != fs.FlagCount() {
t.Fatalf("FlagCount (%d) != fs.FlagCount() (%d) of elements visited", flagcount, fs.FlagCount())
}
count = 0
fs.Visit(func(flag *Flag) {
count++
if flag == nil {
t.Fatal("Visit should not return a nil flag")
}
})
nflag := fs.NFlag()
if nflag != count {
t.Fatalf("NFlag (%d) != number (%d) of elements visited", nflag, count)
}
if nflag != fs.NFlag() {
t.Fatalf("NFlag (%d) != fs.NFlag() (%d) of elements visited", nflag, fs.NFlag())
}
}
func TestMergeFlags(t *testing.T) {
base := NewFlagSet("base", ContinueOnError)
base.String([]string{"f"}, "", "")
fs := NewFlagSet("test", ContinueOnError)
Merge(fs, base)
if len(fs.formal) != 1 {
t.Fatalf("FlagCount (%d) != number (1) of elements merged", len(fs.formal))
}
}

View file

@ -18,7 +18,7 @@ type TestingT interface {
// they are not equal.
func Equal(t TestingT, actual, expected interface{}) {
if expected != actual {
fatal(t, fmt.Sprintf("Expected '%v' (%T) got '%v' (%T)", expected, expected, actual, actual))
fatal(t, "Expected '%v' (%T) got '%v' (%T)", expected, expected, actual, actual)
}
}
@ -26,12 +26,12 @@ func Equal(t TestingT, actual, expected interface{}) {
// the same items.
func EqualStringSlice(t TestingT, actual, expected []string) {
if len(actual) != len(expected) {
t.Fatalf("Expected (length %d): %q\nActual (length %d): %q",
fatal(t, "Expected (length %d): %q\nActual (length %d): %q",
len(expected), expected, len(actual), actual)
}
for i, item := range actual {
if item != expected[i] {
t.Fatalf("Slices differ at element %d, expected %q got %q",
fatal(t, "Slices differ at element %d, expected %q got %q",
i, expected[i], item)
}
}
@ -40,7 +40,7 @@ func EqualStringSlice(t TestingT, actual, expected []string) {
// NilError asserts that the error is nil, otherwise it fails the test.
func NilError(t TestingT, err error) {
if err != nil {
fatal(t, fmt.Sprintf("Expected no error, got: %s", err.Error()))
fatal(t, "Expected no error, got: %s", err.Error())
}
}
@ -52,7 +52,7 @@ func Error(t TestingT, err error, contains string) {
}
if !strings.Contains(err.Error(), contains) {
fatal(t, fmt.Sprintf("Expected error to contain '%s', got '%s'", contains, err.Error()))
fatal(t, "Expected error to contain '%s', got '%s'", contains, err.Error())
}
}
@ -60,11 +60,26 @@ func Error(t TestingT, err error, contains string) {
// test.
func Contains(t TestingT, actual, contains string) {
if !strings.Contains(actual, contains) {
fatal(t, fmt.Sprintf("Expected '%s' to contain '%s'", actual, contains))
fatal(t, "Expected '%s' to contain '%s'", actual, contains)
}
}
func fatal(t TestingT, msg string) {
_, file, line, _ := runtime.Caller(2)
t.Fatalf("%s:%d: %s", filepath.Base(file), line, msg)
// NotNil fails the test if the object is nil
func NotNil(t TestingT, obj interface{}) {
if obj == nil {
fatal(t, "Expected non-nil value.")
}
}
func fatal(t TestingT, format string, args ...interface{}) {
t.Fatalf(errorSource()+format, args...)
}
// See testing.decorate()
func errorSource() string {
_, filename, line, ok := runtime.Caller(3)
if !ok {
return ""
}
return fmt.Sprintf("%s:%d: ", filepath.Base(filename), line)
}

View file

@ -0,0 +1,36 @@
package tempfile
import (
"io/ioutil"
"os"
"github.com/docker/docker/pkg/testutil/assert"
)
// TempFile is a temporary file that can be used with unit tests. TempFile
// reduces the boilerplate setup required in each test case by handling
// setup errors.
type TempFile struct {
File *os.File
}
// NewTempFile returns a new temp file with contents
func NewTempFile(t assert.TestingT, prefix string, content string) *TempFile {
file, err := ioutil.TempFile("", prefix+"-")
assert.NilError(t, err)
_, err = file.Write([]byte(content))
assert.NilError(t, err)
file.Close()
return &TempFile{File: file}
}
// Name returns the filename
func (f *TempFile) Name() string {
return f.File.Name()
}
// Remove removes the file
func (f *TempFile) Remove() {
os.Remove(f.Name())
}

View file

@ -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

View file

@ -3,7 +3,7 @@
package registry
import (
flag "github.com/docker/docker/pkg/mflag"
"github.com/spf13/pflag"
)
var (
@ -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 *pflag.FlagSet) {
flags.BoolVar(&options.V2Only, "disable-legacy-registry", false, "Disable contacting legacy registries")
}

View file

@ -5,7 +5,7 @@ import (
"path/filepath"
"strings"
flag "github.com/docker/docker/pkg/mflag"
"github.com/spf13/pflag"
)
// CertsDir is the directory where certificates are stored
@ -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 *pflag.FlagSet) {
// No Windows specific flags.
}

View file

@ -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"
}

View file

@ -19,6 +19,18 @@ _cgo_export.*
_testmain.go
# Vim files https://github.com/github/gitignore/blob/master/Global/Vim.gitignore
# swap
[._]*.s[a-w][a-z]
[._]s[a-w][a-z]
# session
Session.vim
# temporary
.netrwhist
*~
# auto-generated tag files
tags
*.exe
cobra.test

View file

@ -116,12 +116,12 @@ __handle_reply()
fi
local completions
if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then
completions=("${must_have_one_flag[@]}")
elif [[ ${#must_have_one_noun[@]} -ne 0 ]]; then
completions=("${commands[@]}")
if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then
completions=("${must_have_one_noun[@]}")
else
completions=("${commands[@]}")
fi
if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then
completions+=("${must_have_one_flag[@]}")
fi
COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") )
@ -167,6 +167,11 @@ __handle_flag()
must_have_one_flag=()
fi
# if you set a flag which only applies to this command, don't show subcommands
if __contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then
commands=()
fi
# keep flag value with flagname as flaghash
if [ -n "${flagvalue}" ] ; then
flaghash[${flagname}]=${flagvalue}
@ -263,6 +268,7 @@ func postscript(w io.Writer, name string) error {
local c=0
local flags=()
local two_word_flags=()
local local_nonpersistent_flags=()
local flags_with_completion=()
local flags_completion=()
local commands=("%s")
@ -360,7 +366,7 @@ func writeFlagHandler(name string, annotations map[string][]string, w io.Writer)
}
func writeShortFlag(flag *pflag.Flag, w io.Writer) error {
b := (flag.Value.Type() == "bool")
b := (len(flag.NoOptDefVal) > 0)
name := flag.Shorthand
format := " "
if !b {
@ -374,7 +380,7 @@ func writeShortFlag(flag *pflag.Flag, w io.Writer) error {
}
func writeFlag(flag *pflag.Flag, w io.Writer) error {
b := (flag.Value.Type() == "bool")
b := (len(flag.NoOptDefVal) > 0)
name := flag.Name
format := " flags+=(\"--%s"
if !b {
@ -387,9 +393,24 @@ func writeFlag(flag *pflag.Flag, w io.Writer) error {
return writeFlagHandler("--"+name, flag.Annotations, w)
}
func writeLocalNonPersistentFlag(flag *pflag.Flag, w io.Writer) error {
b := (len(flag.NoOptDefVal) > 0)
name := flag.Name
format := " local_nonpersistent_flags+=(\"--%s"
if !b {
format += "="
}
format += "\")\n"
if _, err := fmt.Fprintf(w, format, name); err != nil {
return err
}
return nil
}
func writeFlags(cmd *Command, w io.Writer) error {
_, err := fmt.Fprintf(w, ` flags=()
two_word_flags=()
local_nonpersistent_flags=()
flags_with_completion=()
flags_completion=()
@ -397,6 +418,7 @@ func writeFlags(cmd *Command, w io.Writer) error {
if err != nil {
return err
}
localNonPersistentFlags := cmd.LocalNonPersistentFlags()
var visitErr error
cmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
if err := writeFlag(flag, w); err != nil {
@ -409,6 +431,12 @@ func writeFlags(cmd *Command, w io.Writer) error {
return
}
}
if localNonPersistentFlags.Lookup(flag.Name) != nil {
if err := writeLocalNonPersistentFlag(flag, w); err != nil {
visitErr = err
return
}
}
})
if visitErr != nil {
return visitErr

View file

@ -117,7 +117,7 @@ cmd := &cobra.Command{
```
The aliases are not shown to the user on tab completion, but they are accepted as valid nouns by
the completion aglorithm if entered manually, e.g. in:
the completion algorithm if entered manually, e.g. in:
```bash
# kubectl get rc [tab][tab]
@ -175,7 +175,7 @@ So while there are many other files in the CWD it only shows me subdirs and thos
# Specifiy custom flag completion
Similar to the filename completion and filtering usingn cobra.BashCompFilenameExt, you can specifiy
Similar to the filename completion and filtering using cobra.BashCompFilenameExt, you can specifiy
a custom flag completion function with cobra.BashCompCustom:
```go

View file

@ -41,6 +41,10 @@ var initializers []func()
// Set this to true to enable it
var EnablePrefixMatching = false
//EnableCommandSorting controls sorting of the slice of commands, which is turned on by default.
//To disable sorting, set it to false.
var EnableCommandSorting = true
//AddTemplateFunc adds a template function that's available to Usage and Help
//template generation.
func AddTemplateFunc(name string, tmplFunc interface{}) {

View file

@ -21,6 +21,7 @@ import (
"io"
"os"
"path/filepath"
"sort"
"strings"
flag "github.com/spf13/pflag"
@ -105,6 +106,8 @@ type Command struct {
commandsMaxUseLen int
commandsMaxCommandPathLen int
commandsMaxNameLen int
// is commands slice are sorted or not
commandsAreSorted bool
flagErrorBuf *bytes.Buffer
@ -126,6 +129,9 @@ type Command struct {
// Disable the flag parsing. If this is true all flags will be passed to the command as arguments.
DisableFlagParsing bool
// TraverseChildren parses flags on all parents before executing child command
TraverseChildren bool
}
// os.Args[1:] by default, if desired, can be overridden
@ -409,13 +415,14 @@ func argsMinusFirstX(args []string, x string) []string {
return args
}
// find the target command given the args and command tree
func isFlagArg(arg string) bool {
return ((len(arg) >= 3 && arg[1] == '-') ||
(len(arg) >= 2 && arg[0] == '-' && arg[1] != '-'))
}
// Find the target command given the args and command tree
// Meant to be run on the highest node. Only searches down.
func (c *Command) Find(args []string) (*Command, []string, error) {
if c == nil {
return nil, nil, fmt.Errorf("Called find() on a nil Command")
}
var innerfind func(*Command, []string) (*Command, []string)
innerfind = func(c *Command, innerArgs []string) (*Command, []string) {
@ -424,28 +431,11 @@ func (c *Command) Find(args []string) (*Command, []string, error) {
return c, innerArgs
}
nextSubCmd := argsWOflags[0]
matches := make([]*Command, 0)
for _, cmd := range c.commands {
if cmd.Name() == nextSubCmd || cmd.HasAlias(nextSubCmd) { // exact name or alias match
return innerfind(cmd, argsMinusFirstX(innerArgs, nextSubCmd))
}
if EnablePrefixMatching {
if strings.HasPrefix(cmd.Name(), nextSubCmd) { // prefix match
matches = append(matches, cmd)
}
for _, x := range cmd.Aliases {
if strings.HasPrefix(x, nextSubCmd) {
matches = append(matches, cmd)
}
}
}
}
// only accept a single prefix match - multiple matches would be ambiguous
if len(matches) == 1 {
return innerfind(matches[0], argsMinusFirstX(innerArgs, argsWOflags[0]))
cmd := c.findNext(nextSubCmd)
if cmd != nil {
return innerfind(cmd, argsMinusFirstX(innerArgs, nextSubCmd))
}
return c, innerArgs
}
@ -456,6 +446,66 @@ func (c *Command) Find(args []string) (*Command, []string, error) {
return commandFound, a, nil
}
func (c *Command) findNext(next string) *Command {
matches := make([]*Command, 0)
for _, cmd := range c.commands {
if cmd.Name() == next || cmd.HasAlias(next) {
return cmd
}
if EnablePrefixMatching && cmd.HasNameOrAliasPrefix(next) {
matches = append(matches, cmd)
}
}
if len(matches) == 1 {
return matches[0]
}
return nil
}
// Traverse the command tree to find the command, and parse args for
// each parent.
func (c *Command) Traverse(args []string) (*Command, []string, error) {
flags := []string{}
inFlag := false
for i, arg := range args {
switch {
// A long flag with a space separated value
case strings.HasPrefix(arg, "--") && !strings.Contains(arg, "="):
// TODO: this isn't quite right, we should really check ahead for 'true' or 'false'
inFlag = !isBooleanFlag(arg[2:], c.Flags())
flags = append(flags, arg)
continue
// A short flag with a space separated value
case strings.HasPrefix(arg, "-") && !strings.Contains(arg, "=") && len(arg) == 2 && !isBooleanShortFlag(arg[1:], c.Flags()):
inFlag = true
flags = append(flags, arg)
continue
// The value for a flag
case inFlag:
inFlag = false
flags = append(flags, arg)
continue
// A flag without a value, or with an `=` separated value
case isFlagArg(arg):
flags = append(flags, arg)
continue
}
cmd := c.findNext(arg)
if cmd == nil {
return c, args, nil
}
if err := c.ParseFlags(flags); err != nil {
return nil, args, err
}
return cmd.Traverse(args[i+1:])
}
return c, args, nil
}
func (c *Command) findSuggestions(arg string) string {
if c.DisableSuggestions {
return ""
@ -668,7 +718,12 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
args = c.args
}
cmd, flags, err := c.Find(args)
var flags []string
if c.TraverseChildren {
cmd, flags, err = c.Traverse(args)
} else {
cmd, flags, err = c.Find(args)
}
if err != nil {
// If found parse to a subcommand and then failed, talk about the subcommand
if cmd != nil {
@ -680,6 +735,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
}
return c, err
}
err = cmd.execute(flags)
if err != nil {
// Always show help if requested, even if SilenceErrors is in
@ -754,8 +810,20 @@ func (c *Command) ResetCommands() {
c.helpCommand = nil
}
//Commands returns a slice of child commands.
// Sorts commands by their names
type commandSorterByName []*Command
func (c commandSorterByName) Len() int { return len(c) }
func (c commandSorterByName) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
func (c commandSorterByName) Less(i, j int) bool { return c[i].Name() < c[j].Name() }
// Commands returns a sorted slice of child commands.
func (c *Command) Commands() []*Command {
// do not sort commands if it already sorted or sorting was disabled
if EnableCommandSorting && !c.commandsAreSorted{
sort.Sort(commandSorterByName(c.commands))
c.commandsAreSorted = true
}
return c.commands
}
@ -784,6 +852,7 @@ func (c *Command) AddCommand(cmds ...*Command) {
x.SetGlobalNormalizationFunc(c.globNormFunc)
}
c.commands = append(c.commands, x)
c.commandsAreSorted = false
}
}
@ -953,6 +1022,20 @@ func (c *Command) HasAlias(s string) bool {
return false
}
// HasNameOrAliasPrefix returns true if the Name or any of aliases start
// with prefix
func (c *Command) HasNameOrAliasPrefix(prefix string) bool {
if strings.HasPrefix(c.Name(), prefix) {
return true
}
for _, alias := range c.Aliases {
if strings.HasPrefix(alias, prefix) {
return true
}
}
return false
}
func (c *Command) NameAndAliases() string {
return strings.Join(append([]string{c.Name()}, c.Aliases...), ", ")
}
@ -1065,6 +1148,19 @@ func (c *Command) Flags() *flag.FlagSet {
return c.flags
}
// LocalNonPersistentFlags are flags specific to this command which will NOT persist to subcommands
func (c *Command) LocalNonPersistentFlags() *flag.FlagSet {
persistentFlags := c.PersistentFlags()
out := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
c.LocalFlags().VisitAll(func(f *flag.Flag) {
if persistentFlags.Lookup(f.Name) == nil {
out.AddFlag(f)
}
})
return out
}
// Get the local FlagSet specifically set in the current command
func (c *Command) LocalFlags() *flag.FlagSet {
c.mergePersistentFlags()