Skip to content

Commit 0452ff5

Browse files
committed
Convert docker root command to use pflag and cobra
Fix the daemon proxy for cobra commands. Signed-off-by: Daniel Nephin <[email protected]>
1 parent fb83394 commit 0452ff5

8 files changed

Lines changed: 146 additions & 205 deletions

File tree

api/client/cli.go

Lines changed: 32 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
"github.com/docker/docker/cliconfig/configfile"
1515
"github.com/docker/docker/cliconfig/credentials"
1616
"github.com/docker/docker/dockerversion"
17-
"github.com/docker/docker/opts"
17+
dopts "github.com/docker/docker/opts"
1818
"github.com/docker/docker/pkg/term"
1919
"github.com/docker/engine-api/client"
2020
"github.com/docker/go-connections/sockets"
@@ -53,15 +53,6 @@ type DockerCli struct {
5353
outState *term.State
5454
}
5555

56-
// Initialize calls the init function that will setup the configuration for the client
57-
// such as the TLS, tcp and other parameters used to run the client.
58-
func (cli *DockerCli) Initialize() error {
59-
if cli.init == nil {
60-
return nil
61-
}
62-
return cli.init()
63-
}
64-
6556
// Client returns the APIClient
6657
func (cli *DockerCli) Client() client.APIClient {
6758
return cli.client
@@ -155,39 +146,39 @@ func (cli *DockerCli) restoreTerminal(in io.Closer) error {
155146
return nil
156147
}
157148

149+
// Initialize the dockerCli runs initialization that must happen after command
150+
// line flags are parsed.
151+
func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
152+
cli.configFile = LoadDefaultConfigFile(cli.err)
153+
154+
client, err := NewAPIClientFromFlags(opts.Common, cli.configFile)
155+
if err != nil {
156+
return err
157+
}
158+
159+
cli.client = client
160+
161+
if cli.in != nil {
162+
cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in)
163+
}
164+
if cli.out != nil {
165+
cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out)
166+
}
167+
return nil
168+
}
169+
158170
// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
159171
// The key file, protocol (i.e. unix) and address are passed in as strings, along with the tls.Config. If the tls.Config
160172
// is set the client scheme will be set to https.
161173
// The client will be given a 32-second timeout (see https://github.com/docker/docker/pull/8035).
162-
func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cliflags.ClientFlags) *DockerCli {
174+
func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientOpts *cliflags.ClientOptions) *DockerCli {
163175
cli := &DockerCli{
164-
in: in,
165-
out: out,
166-
err: err,
167-
keyFile: clientFlags.Common.TrustKey,
176+
in: in,
177+
out: out,
178+
err: err,
179+
// TODO: just pass trustKey, not the entire opts struct
180+
keyFile: clientOpts.Common.TrustKey,
168181
}
169-
170-
cli.init = func() error {
171-
clientFlags.PostParse()
172-
cli.configFile = LoadDefaultConfigFile(err)
173-
174-
client, err := NewAPIClientFromFlags(clientFlags, cli.configFile)
175-
if err != nil {
176-
return err
177-
}
178-
179-
cli.client = client
180-
181-
if cli.in != nil {
182-
cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in)
183-
}
184-
if cli.out != nil {
185-
cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out)
186-
}
187-
188-
return nil
189-
}
190-
191182
return cli
192183
}
193184

@@ -205,8 +196,8 @@ func LoadDefaultConfigFile(err io.Writer) *configfile.ConfigFile {
205196
}
206197

207198
// NewAPIClientFromFlags creates a new APIClient from command line flags
208-
func NewAPIClientFromFlags(clientFlags *cliflags.ClientFlags, configFile *configfile.ConfigFile) (client.APIClient, error) {
209-
host, err := getServerHost(clientFlags.Common.Hosts, clientFlags.Common.TLSOptions)
199+
func NewAPIClientFromFlags(opts *cliflags.CommonOptions, configFile *configfile.ConfigFile) (client.APIClient, error) {
200+
host, err := getServerHost(opts.Hosts, opts.TLSOptions)
210201
if err != nil {
211202
return &client.Client{}, err
212203
}
@@ -222,7 +213,7 @@ func NewAPIClientFromFlags(clientFlags *cliflags.ClientFlags, configFile *config
222213
verStr = tmpStr
223214
}
224215

225-
httpClient, err := newHTTPClient(host, clientFlags.Common.TLSOptions)
216+
httpClient, err := newHTTPClient(host, opts.TLSOptions)
226217
if err != nil {
227218
return &client.Client{}, err
228219
}
@@ -240,7 +231,7 @@ func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (host string,
240231
return "", errors.New("Please specify only one -H")
241232
}
242233

243-
host, err = opts.ParseHost(tlsOptions != nil, host)
234+
host, err = dopts.ParseHost(tlsOptions != nil, host)
244235
return
245236
}
246237

cli/cobraadaptor/adaptor.go

Lines changed: 6 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -14,33 +14,18 @@ import (
1414
"github.com/docker/docker/api/client/system"
1515
"github.com/docker/docker/api/client/volume"
1616
"github.com/docker/docker/cli"
17-
cliflags "github.com/docker/docker/cli/flags"
18-
"github.com/docker/docker/pkg/term"
1917
"github.com/spf13/cobra"
2018
)
2119

22-
// CobraAdaptor is an adaptor for supporting spf13/cobra commands in the
23-
// docker/cli framework
24-
type CobraAdaptor struct {
25-
rootCmd *cobra.Command
26-
dockerCli *client.DockerCli
27-
}
28-
29-
// NewCobraAdaptor returns a new handler
30-
func NewCobraAdaptor(clientFlags *cliflags.ClientFlags) CobraAdaptor {
31-
stdin, stdout, stderr := term.StdStreams()
32-
dockerCli := client.NewDockerCli(stdin, stdout, stderr, clientFlags)
33-
34-
var rootCmd = &cobra.Command{
35-
Use: "docker [OPTIONS]",
36-
Short: "A self-sufficient runtime for containers",
37-
SilenceUsage: true,
38-
SilenceErrors: true,
39-
}
20+
// SetupRootCommand sets default usage, help, and error handling for the
21+
// root command.
22+
// TODO: move to cmd/docker/docker?
23+
// TODO: split into common setup and client setup
24+
func SetupRootCommand(rootCmd *cobra.Command, dockerCli *client.DockerCli) {
4025
rootCmd.SetUsageTemplate(usageTemplate)
4126
rootCmd.SetHelpTemplate(helpTemplate)
4227
rootCmd.SetFlagErrorFunc(cli.FlagErrorFunc)
43-
rootCmd.SetOutput(stdout)
28+
rootCmd.SetOutput(dockerCli.Out())
4429
rootCmd.AddCommand(
4530
node.NewNodeCommand(dockerCli),
4631
service.NewServiceCommand(dockerCli),
@@ -94,44 +79,6 @@ func NewCobraAdaptor(clientFlags *cliflags.ClientFlags) CobraAdaptor {
9479

9580
rootCmd.PersistentFlags().BoolP("help", "h", false, "Print usage")
9681
rootCmd.PersistentFlags().MarkShorthandDeprecated("help", "please use --help")
97-
98-
return CobraAdaptor{
99-
rootCmd: rootCmd,
100-
dockerCli: dockerCli,
101-
}
102-
}
103-
104-
// Usage returns the list of commands and their short usage string for
105-
// all top level cobra commands.
106-
func (c CobraAdaptor) Usage() []cli.Command {
107-
cmds := []cli.Command{}
108-
for _, cmd := range c.rootCmd.Commands() {
109-
if cmd.Name() != "" {
110-
cmds = append(cmds, cli.Command{Name: cmd.Name(), Description: cmd.Short})
111-
}
112-
}
113-
return cmds
114-
}
115-
116-
func (c CobraAdaptor) run(cmd string, args []string) error {
117-
if err := c.dockerCli.Initialize(); err != nil {
118-
return err
119-
}
120-
// Prepend the command name to support normal cobra command delegation
121-
c.rootCmd.SetArgs(append([]string{cmd}, args...))
122-
return c.rootCmd.Execute()
123-
}
124-
125-
// Command returns a cli command handler if one exists
126-
func (c CobraAdaptor) Command(name string) func(...string) error {
127-
for _, cmd := range c.rootCmd.Commands() {
128-
if cmd.Name() == name {
129-
return func(args ...string) error {
130-
return c.run(name, args)
131-
}
132-
}
133-
}
134-
return nil
13582
}
13683

13784
// GetRootCommand returns the root command. Required to generate the man pages

cli/flags/client.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
package flags
22

3-
import (
4-
"github.com/spf13/pflag"
5-
)
6-
7-
// ClientFlags represents flags for the docker client.
8-
type ClientFlags struct {
9-
FlagSet *pflag.FlagSet
3+
// ClientOptions are the options used to configure the client cli
4+
type ClientOptions struct {
105
Common *CommonOptions
11-
PostParse func()
12-
136
ConfigDir string
7+
Version bool
8+
}
9+
10+
// NewClientOptions returns a new ClientOptions
11+
func NewClientOptions() *ClientOptions {
12+
return &ClientOptions{Common: NewCommonOptions()}
1413
}

cmd/docker/daemon.go

Lines changed: 0 additions & 18 deletions
This file was deleted.

cmd/docker/daemon_none.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,22 @@ package main
44

55
import (
66
"fmt"
7+
"github.com/spf13/cobra"
78
"runtime"
89
"strings"
910
)
1011

11-
// CmdDaemon reports on an error on windows, because there is no exec
12-
func (p DaemonProxy) CmdDaemon(args ...string) error {
12+
func newDaemonCommand() *cobra.Command {
13+
return &cobra.Command{
14+
Use: "daemon",
15+
Hidden: true,
16+
RunE: func(cmd *cobra.Command, args []string) error {
17+
return runDaemon()
18+
},
19+
}
20+
}
21+
22+
func runDaemon() error {
1323
return fmt.Errorf(
1424
"`docker daemon` is not supported on %s. Please run `dockerd` directly",
1525
strings.Title(runtime.GOOS))

cmd/docker/daemon_unix.go

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,37 @@
33
package main
44

55
import (
6+
"fmt"
7+
68
"os"
79
"os/exec"
810
"path/filepath"
911
"syscall"
12+
13+
"github.com/spf13/cobra"
1014
)
1115

12-
// CmdDaemon execs dockerd with the same flags
13-
func (p DaemonProxy) CmdDaemon(args ...string) error {
14-
// Special case for handling `docker help daemon`. When pkg/mflag is removed
15-
// we can support this on the daemon side, but that is not possible with
16-
// pkg/mflag because it uses os.Exit(1) instead of returning an error on
17-
// unexpected args.
18-
if len(args) == 0 || args[0] != "--help" {
19-
// Use os.Args[1:] so that "global" args are passed to dockerd
20-
args = stripDaemonArg(os.Args[1:])
16+
const daemonBinary = "dockerd"
17+
18+
func newDaemonCommand() *cobra.Command {
19+
cmd := &cobra.Command{
20+
Use: "daemon",
21+
Hidden: true,
22+
RunE: func(cmd *cobra.Command, args []string) error {
23+
return runDaemon()
24+
},
2125
}
26+
cmd.SetHelpFunc(helpFunc)
27+
return cmd
28+
}
2229

30+
// CmdDaemon execs dockerd with the same flags
31+
func runDaemon() error {
32+
// Use os.Args[1:] so that "global" args are passed to dockerd
33+
return execDaemon(stripDaemonArg(os.Args[1:]))
34+
}
35+
36+
func execDaemon(args []string) error {
2337
binaryPath, err := findDaemonBinary()
2438
if err != nil {
2539
return err
@@ -31,6 +45,12 @@ func (p DaemonProxy) CmdDaemon(args ...string) error {
3145
os.Environ())
3246
}
3347

48+
func helpFunc(cmd *cobra.Command, args []string) {
49+
if err := execDaemon([]string{"--help"}); err != nil {
50+
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
51+
}
52+
}
53+
3454
// findDaemonBinary looks for the path to the dockerd binary starting with
3555
// the directory of the current executable (if one exists) and followed by $PATH
3656
func findDaemonBinary() (string, error) {

0 commit comments

Comments
 (0)