Merge pull request #25354 from dnephin/remove-mflag-from-dockerd
Convert docker and dockerd commands to spf13/cobra
This commit is contained in:
commit
25587906d1
69 changed files with 1080 additions and 3410 deletions
|
@ -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,23 +147,12 @@ func (cli *DockerCli) restoreTerminal(in io.Closer) error {
|
|||
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,
|
||||
}
|
||||
// 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)
|
||||
|
||||
cli.init = func() error {
|
||||
clientFlags.PostParse()
|
||||
cli.configFile = LoadDefaultConfigFile(err)
|
||||
|
||||
client, err := NewAPIClientFromFlags(clientFlags, cli.configFile)
|
||||
client, err := NewAPIClientFromFlags(opts.Common, cli.configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -185,10 +166,22 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cliflags.Cl
|
|||
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
|
||||
}
|
||||
|
||||
return cli
|
||||
// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
|
||||
func NewDockerCli(in io.ReadCloser, out, err io.Writer) *DockerCli {
|
||||
return &DockerCli{
|
||||
in: in,
|
||||
out: out,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
|
|
71
api/client/command/commands.go
Normal file
71
api/client/command/commands.go
Normal 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)
|
||||
}
|
|
@ -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]
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -38,7 +38,6 @@ func NewCommitCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|||
return runCommit(dockerCli, &opts)
|
||||
},
|
||||
}
|
||||
cmd.SetFlagErrorFunc(flagErrorFunc)
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.SetInterspersed(false)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -34,8 +34,6 @@ func NewPortCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|||
return runPort(dockerCli, &opts)
|
||||
},
|
||||
}
|
||||
cmd.SetFlagErrorFunc(flagErrorFunc)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
|
|
@ -30,8 +30,6 @@ func NewRenameCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|||
return runRename(dockerCli, &opts)
|
||||
},
|
||||
}
|
||||
cmd.SetFlagErrorFunc(flagErrorFunc)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -32,7 +32,6 @@ func NewTopCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|||
return runTop(dockerCli, &opts)
|
||||
},
|
||||
}
|
||||
cmd.SetFlagErrorFunc(flagErrorFunc)
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.SetInterspersed(false)
|
||||
|
|
|
@ -28,8 +28,6 @@ func NewUnpauseCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|||
return runUnpause(dockerCli, &opts)
|
||||
},
|
||||
}
|
||||
cmd.SetFlagErrorFunc(flagErrorFunc)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
|
|
@ -28,8 +28,6 @@ func NewWaitCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|||
return runWait(dockerCli, &opts)
|
||||
},
|
||||
}
|
||||
cmd.SetFlagErrorFunc(flagErrorFunc)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
|
191
cli/cli.go
191
cli/cli.go
|
@ -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
57
cli/cobra.go
Normal 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}}`
|
|
@ -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}}`
|
15
cli/error.go
15
cli/error.go
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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()}
|
||||
}
|
||||
|
|
|
@ -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 = ""
|
||||
}
|
||||
|
|
19
cli/usage.go
19
cli/usage.go
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
}
|
|
@ -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))
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 opts.ConfigDir != "" {
|
||||
cliconfig.SetConfigDir(opts.ConfigDir)
|
||||
}
|
||||
|
||||
if clientFlags.Common.TrustKey == "" {
|
||||
clientFlags.Common.TrustKey = filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile)
|
||||
}
|
||||
|
||||
if clientFlags.Common.Debug {
|
||||
if opts.Common.Debug {
|
||||
utils.EnableDebug()
|
||||
}
|
||||
}
|
||||
return clientFlags
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
)
|
||||
|
||||
func main() {
|
||||
if reexec.Init() {
|
||||
return
|
||||
type daemonOptions struct {
|
||||
version bool
|
||||
configFile string
|
||||
daemonConfig *daemon.Config
|
||||
common *cliflags.CommonOptions
|
||||
flags *pflag.FlagSet
|
||||
}
|
||||
|
||||
// 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")
|
||||
func newDaemonCommand() *cobra.Command {
|
||||
opts := daemonOptions{
|
||||
daemonConfig: daemon.NewConfig(),
|
||||
common: cliflags.NewCommonOptions(),
|
||||
}
|
||||
|
||||
if err := flag.CommandLine.ParseFlags(os.Args[1:], false); err != nil {
|
||||
os.Exit(1)
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
if *flVersion {
|
||||
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()
|
||||
if stop {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = daemonCli.start(opts)
|
||||
notifyShutdown(err)
|
||||
if err != nil {
|
||||
logrus.Fatal(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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
|
|
|
@ -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
|
||||
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")
|
||||
|
@ -227,6 +235,7 @@ func initService() (bool, error) {
|
|||
h := &handler{
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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{})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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.
|
|
@ -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.
|
|
@ -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())
|
||||
}
|
||||
}
|
1280
pkg/mflag/flag.go
1280
pkg/mflag/flag.go
File diff suppressed because it is too large
Load diff
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
36
pkg/testutil/tempfile/tempfile.go
Normal file
36
pkg/testutil/tempfile/tempfile.go
Normal 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())
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
12
vendor/src/github.com/spf13/cobra/.gitignore
vendored
12
vendor/src/github.com/spf13/cobra/.gitignore
vendored
|
@ -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
|
||||
|
|
|
@ -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=("${must_have_one_noun[@]}")
|
||||
else
|
||||
completions=("${commands[@]}")
|
||||
if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then
|
||||
completions=("${must_have_one_noun[@]}")
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
4
vendor/src/github.com/spf13/cobra/cobra.go
vendored
4
vendor/src/github.com/spf13/cobra/cobra.go
vendored
|
@ -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{}) {
|
||||
|
|
150
vendor/src/github.com/spf13/cobra/command.go
vendored
150
vendor/src/github.com/spf13/cobra/command.go
vendored
|
@ -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
|
||||
// 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")
|
||||
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) {
|
||||
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
|
||||
|
||||
cmd := c.findNext(nextSubCmd)
|
||||
if cmd != nil {
|
||||
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]))
|
||||
}
|
||||
|
||||
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()
|
||||
|
|
Loading…
Reference in a new issue