Skip to content

Commit

Permalink
cli: new daemon command and new cli package
Browse files Browse the repository at this point in the history
This patch creates a new cli package that allows to combine both client
and daemon commands (there is only one daemon command: docker daemon).

The `-d` and `--daemon` top-level flags are deprecated and a special
message is added to prompt the user to use `docker daemon`.

Providing top-level daemon-specific flags for client commands result
in an error message prompting the user to use `docker daemon`.

This patch does not break any old but correct usages.

This also makes `-d` and `--daemon` flags, as well as the `daemon`
command illegal in client-only binaries.

Signed-off-by: Tibor Vass <[email protected]>
  • Loading branch information
Tibor Vass committed Jul 23, 2015
1 parent 490e78a commit 96ce3a1
Show file tree
Hide file tree
Showing 67 changed files with 905 additions and 606 deletions.
6 changes: 4 additions & 2 deletions api/client/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/Sirupsen/logrus"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/signal"
)
Expand All @@ -16,9 +17,10 @@ import (
//
// Usage: docker attach [OPTIONS] CONTAINER
func (cli *DockerCli) CmdAttach(args ...string) error {
cmd := cli.Subcmd("attach", []string{"CONTAINER"}, "Attach to a running container", true)
cmd := Cli.Subcmd("attach", []string{"CONTAINER"}, "Attach to a running container", true)
noStdin := cmd.Bool([]string{"#nostdin", "-no-stdin"}, false, "Do not attach STDIN")
proxy := cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy all received signals to the process")

cmd.Require(flag.Exact, 1)

cmd.ParseFlags(args, true)
Expand Down Expand Up @@ -75,7 +77,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
return err
}
if status != 0 {
return StatusError{StatusCode: status}
return Cli.StatusError{StatusCode: status}
}

return nil
Expand Down
7 changes: 4 additions & 3 deletions api/client/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"strings"

"github.com/docker/docker/api"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/graph/tags"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/archive"
Expand Down Expand Up @@ -46,7 +47,7 @@ const (
//
// Usage: docker build [OPTIONS] PATH | URL | -
func (cli *DockerCli) CmdBuild(args ...string) error {
cmd := cli.Subcmd("build", []string{"PATH | URL | -"}, "Build a new image from the source code at PATH", true)
cmd := Cli.Subcmd("build", []string{"PATH | URL | -"}, "Build a new image from the source code at PATH", true)
tag := cmd.String([]string{"t", "-tag"}, "", "Repository name (and optionally a tag) for the image")
suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the verbose output generated by the containers")
noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image")
Expand All @@ -64,7 +65,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
flCgroupParent := cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")

ulimits := make(map[string]*ulimit.Ulimit)
flUlimits := opts.NewUlimitOpt(ulimits)
flUlimits := opts.NewUlimitOpt(&ulimits)
cmd.Var(flUlimits, []string{"-ulimit"}, "Ulimit options")

cmd.Require(flag.Exact, 1)
Expand Down Expand Up @@ -325,7 +326,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
if jerr.Code == 0 {
jerr.Code = 1
}
return StatusError{Status: jerr.Message, StatusCode: jerr.Code}
return Cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
}
return err
}
242 changes: 77 additions & 165 deletions api/client/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,28 @@ package client

import (
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"reflect"
"os"
"strings"
"text/template"

"github.com/docker/docker/cli"
"github.com/docker/docker/cliconfig"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/sockets"
"github.com/docker/docker/pkg/term"
"github.com/docker/docker/pkg/tlsconfig"
)

// DockerCli represents the docker command line client.
// Instances of the client can be returned from NewDockerCli.
type DockerCli struct {
// initializing closure
init func() error

// proto holds the client protocol i.e. unix.
proto string
// addr holds the client address.
Expand Down Expand Up @@ -55,116 +58,11 @@ type DockerCli struct {
transport *http.Transport
}

var funcMap = template.FuncMap{
"json": func(v interface{}) string {
a, _ := json.Marshal(v)
return string(a)
},
}

func (cli *DockerCli) Out() io.Writer {
return cli.out
}

func (cli *DockerCli) Err() io.Writer {
return cli.err
}

func (cli *DockerCli) getMethod(args ...string) (func(...string) error, bool) {
camelArgs := make([]string, len(args))
for i, s := range args {
if len(s) == 0 {
return nil, false
}
camelArgs[i] = strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
}
methodName := "Cmd" + strings.Join(camelArgs, "")
method := reflect.ValueOf(cli).MethodByName(methodName)
if !method.IsValid() {
return nil, false
}
return method.Interface().(func(...string) error), true
}

// Cmd executes the specified command.
func (cli *DockerCli) Cmd(args ...string) error {
if len(args) > 1 {
method, exists := cli.getMethod(args[:2]...)
if exists {
return method(args[2:]...)
}
}
if len(args) > 0 {
method, exists := cli.getMethod(args[0])
if !exists {
return fmt.Errorf("docker: '%s' is not a docker command.\nSee 'docker --help'.", args[0])
}
return method(args[1:]...)
func (cli *DockerCli) Initialize() error {
if cli.init == nil {
return nil
}
return cli.CmdHelp()
}

// Subcmd is a subcommand of the main "docker" command.
// A subcommand represents an action that can be performed
// from the Docker command line client.
//
// Multiple subcommand synopses may be provided with one 'Usage' line being
// printed for each in the following way:
//
// Usage: docker <subcmd-name> [OPTIONS] <synopsis 0>
// docker <subcmd-name> [OPTIONS] <synopsis 1>
// ...
//
// If no undeprecated flags are added to the returned FlagSet, "[OPTIONS]" will
// not be included on the usage synopsis lines. If no synopses are given, only
// one usage synopsis line will be printed with nothing following the
// "[OPTIONS]" section
//
// To see all available subcommands, run "docker --help".
func (cli *DockerCli) 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() {
options := ""
if flags.FlagCountUndeprecated() > 0 {
options = " [OPTIONS]"
}

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%s", lead, name, options, synopsis)
}

fmt.Fprintf(flags.Out(), "\n\n%s\n", description)
}

return flags
return cli.init()
}

// CheckTtyInput checks if we are trying to attach to a container tty
Expand All @@ -187,64 +85,78 @@ func (cli *DockerCli) PsFormat() string {
// 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, keyFile string, proto, addr string, tlsConfig *tls.Config) *DockerCli {
var (
inFd uintptr
outFd uintptr
isTerminalIn = false
isTerminalOut = false
scheme = "http"
basePath = ""
)

if tlsConfig != nil {
scheme = "https"
}
if in != nil {
inFd, isTerminalIn = term.GetFdInfo(in)
func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cli.ClientFlags) *DockerCli {
cli := &DockerCli{
in: in,
out: out,
err: err,
keyFile: clientFlags.Common.TrustKey,
}

if out != nil {
outFd, isTerminalOut = term.GetFdInfo(out)
}
cli.init = func() error {
clientFlags.PostParse()

if err == nil {
err = out
}
hosts := clientFlags.Common.Hosts

// The transport is created here for reuse during the client session.
tr := &http.Transport{
TLSClientConfig: tlsConfig,
}
sockets.ConfigureTCPTransport(tr, proto, addr)
switch len(hosts) {
case 0:
defaultHost := os.Getenv("DOCKER_HOST")
if defaultHost == "" {
defaultHost = opts.DefaultHost
}
defaultHost, err := opts.ValidateHost(defaultHost)
if err != nil {
return err
}
hosts = []string{defaultHost}
case 1:
// only accept one host to talk to
default:
return errors.New("Please specify only one -H")
}

configFile, e := cliconfig.Load(cliconfig.ConfigDir())
if e != nil {
fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e)
}
protoAddrParts := strings.SplitN(hosts[0], "://", 2)
cli.proto, cli.addr = protoAddrParts[0], protoAddrParts[1]

if proto == "tcp" {
// error is checked in pkg/parsers already
parsed, _ := url.Parse("tcp://" + addr)
addr = parsed.Host
basePath = parsed.Path
}
if cli.proto == "tcp" {
// error is checked in pkg/parsers already
parsed, _ := url.Parse("tcp://" + cli.addr)
cli.addr = parsed.Host
cli.basePath = parsed.Path
}

return &DockerCli{
proto: proto,
addr: addr,
basePath: basePath,
configFile: configFile,
in: in,
out: out,
err: err,
keyFile: keyFile,
inFd: inFd,
outFd: outFd,
isTerminalIn: isTerminalIn,
isTerminalOut: isTerminalOut,
tlsConfig: tlsConfig,
scheme: scheme,
transport: tr,
if clientFlags.Common.TLSOptions != nil {
cli.scheme = "https"
var e error
cli.tlsConfig, e = tlsconfig.Client(*clientFlags.Common.TLSOptions)
if e != nil {
return e
}
} else {
cli.scheme = "http"
}

if cli.in != nil {
cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in)
}
if cli.out != nil {
cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out)
}

// The transport is created here for reuse during the client session.
cli.transport = &http.Transport{
TLSClientConfig: cli.tlsConfig,
}
sockets.ConfigureTCPTransport(cli.transport, cli.proto, cli.addr)

configFile, e := cliconfig.Load(cliconfig.ConfigDir())
if e != nil {
fmt.Fprintf(cli.err, "WARNING: Error loading config file:%v\n", e)
}
cli.configFile = configFile

return nil
}

return cli
}
12 changes: 0 additions & 12 deletions api/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,3 @@
// Run "docker help SUBCOMMAND" or "docker SUBCOMMAND --help" to see more information on any Docker subcommand, including the full list of options supported for the subcommand.
// See https://docs.docker.com/installation/ for instructions on installing Docker.
package client

import "fmt"

// An 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)
}
3 changes: 2 additions & 1 deletion api/client/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/url"

"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/opts"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/parsers"
Expand All @@ -17,7 +18,7 @@ import (
//
// Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
func (cli *DockerCli) CmdCommit(args ...string) error {
cmd := cli.Subcmd("commit", []string{"CONTAINER [REPOSITORY[:TAG]]"}, "Create a new image from a container's changes", true)
cmd := Cli.Subcmd("commit", []string{"CONTAINER [REPOSITORY[:TAG]]"}, "Create a new image from a container's changes", true)
flPause := cmd.Bool([]string{"p", "-pause"}, true, "Pause container during commit")
flComment := cmd.String([]string{"m", "-message"}, "", "Commit message")
flAuthor := cmd.String([]string{"a", "#author", "-author"}, "", "Author (e.g., \"John Hannibal Smith <[email protected]>\")")
Expand Down
3 changes: 2 additions & 1 deletion api/client/cp.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"strings"

"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/pkg/archive"
flag "github.com/docker/docker/pkg/mflag"
)
Expand All @@ -37,7 +38,7 @@ const (
// docker cp CONTAINER:PATH LOCALPATH|-
// docker cp LOCALPATH|- CONTAINER:PATH
func (cli *DockerCli) CmdCp(args ...string) error {
cmd := cli.Subcmd(
cmd := Cli.Subcmd(
"cp",
[]string{"CONTAINER:PATH LOCALPATH|-", "LOCALPATH|- CONTAINER:PATH"},
strings.Join([]string{
Expand Down
Loading

0 comments on commit 96ce3a1

Please sign in to comment.