Remove some code we no longer depend on
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
This commit is contained in:
parent
15ebd84cc5
commit
c41093053e
|
|
@ -1,5 +0,0 @@
|
||||||
This directory contains code pertaining to the Docker API:
|
|
||||||
|
|
||||||
- Used by the docker client when communicating with the docker daemon
|
|
||||||
|
|
||||||
- Used by third party tools wishing to interface with the docker daemon
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
// +build experimental
|
|
||||||
|
|
||||||
package bundlefile
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Bundlefile stores the contents of a bundlefile
|
|
||||||
type Bundlefile struct {
|
|
||||||
Version string
|
|
||||||
Services map[string]Service
|
|
||||||
}
|
|
||||||
|
|
||||||
// Service is a service from a bundlefile
|
|
||||||
type Service struct {
|
|
||||||
Image string
|
|
||||||
Command []string `json:",omitempty"`
|
|
||||||
Args []string `json:",omitempty"`
|
|
||||||
Env []string `json:",omitempty"`
|
|
||||||
Labels map[string]string `json:",omitempty"`
|
|
||||||
Ports []Port `json:",omitempty"`
|
|
||||||
WorkingDir *string `json:",omitempty"`
|
|
||||||
User *string `json:",omitempty"`
|
|
||||||
Networks []string `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Port is a port as defined in a bundlefile
|
|
||||||
type Port struct {
|
|
||||||
Protocol string
|
|
||||||
Port uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadFile loads a bundlefile from a path to the file
|
|
||||||
func LoadFile(reader io.Reader) (*Bundlefile, error) {
|
|
||||||
bundlefile := &Bundlefile{}
|
|
||||||
|
|
||||||
decoder := json.NewDecoder(reader)
|
|
||||||
if err := decoder.Decode(bundlefile); err != nil {
|
|
||||||
switch jsonErr := err.(type) {
|
|
||||||
case *json.SyntaxError:
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"JSON syntax error at byte %v: %s",
|
|
||||||
jsonErr.Offset,
|
|
||||||
jsonErr.Error())
|
|
||||||
case *json.UnmarshalTypeError:
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"Unexpected type at byte %v. Expected %s but received %s.",
|
|
||||||
jsonErr.Offset,
|
|
||||||
jsonErr.Type,
|
|
||||||
jsonErr.Value)
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return bundlefile, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print writes the contents of the bundlefile to the output writer
|
|
||||||
// as human readable json
|
|
||||||
func Print(out io.Writer, bundle *Bundlefile) error {
|
|
||||||
bytes, err := json.MarshalIndent(*bundle, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = out.Write(bytes)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
// +build experimental
|
|
||||||
|
|
||||||
package bundlefile
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/testutil/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLoadFileV01Success(t *testing.T) {
|
|
||||||
reader := strings.NewReader(`{
|
|
||||||
"Version": "0.1",
|
|
||||||
"Services": {
|
|
||||||
"redis": {
|
|
||||||
"Image": "redis@sha256:4b24131101fa0117bcaa18ac37055fffd9176aa1a240392bb8ea85e0be50f2ce",
|
|
||||||
"Networks": ["default"]
|
|
||||||
},
|
|
||||||
"web": {
|
|
||||||
"Image": "dockercloud/hello-world@sha256:fe79a2cfbd17eefc344fb8419420808df95a1e22d93b7f621a7399fd1e9dca1d",
|
|
||||||
"Networks": ["default"],
|
|
||||||
"User": "web"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`)
|
|
||||||
|
|
||||||
bundle, err := LoadFile(reader)
|
|
||||||
assert.NilError(t, err)
|
|
||||||
assert.Equal(t, bundle.Version, "0.1")
|
|
||||||
assert.Equal(t, len(bundle.Services), 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoadFileSyntaxError(t *testing.T) {
|
|
||||||
reader := strings.NewReader(`{
|
|
||||||
"Version": "0.1",
|
|
||||||
"Services": unquoted string
|
|
||||||
}`)
|
|
||||||
|
|
||||||
_, err := LoadFile(reader)
|
|
||||||
assert.Error(t, err, "syntax error at byte 37: invalid character 'u'")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoadFileTypeError(t *testing.T) {
|
|
||||||
reader := strings.NewReader(`{
|
|
||||||
"Version": "0.1",
|
|
||||||
"Services": {
|
|
||||||
"web": {
|
|
||||||
"Image": "redis",
|
|
||||||
"Networks": "none"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`)
|
|
||||||
|
|
||||||
_, err := LoadFile(reader)
|
|
||||||
assert.Error(t, err, "Unexpected type at byte 94. Expected []string but received string")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPrint(t *testing.T) {
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
bundle := &Bundlefile{
|
|
||||||
Version: "0.1",
|
|
||||||
Services: map[string]Service{
|
|
||||||
"web": {
|
|
||||||
Image: "image",
|
|
||||||
Command: []string{"echo", "something"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert.NilError(t, Print(&buffer, bundle))
|
|
||||||
output := buffer.String()
|
|
||||||
assert.Contains(t, output, "\"Image\": \"image\"")
|
|
||||||
assert.Contains(t, output,
|
|
||||||
`"Command": [
|
|
||||||
"echo",
|
|
||||||
"something"
|
|
||||||
]`)
|
|
||||||
}
|
|
||||||
|
|
@ -1,267 +0,0 @@
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api"
|
|
||||||
cliflags "github.com/docker/docker/cli/flags"
|
|
||||||
"github.com/docker/docker/cliconfig"
|
|
||||||
"github.com/docker/docker/cliconfig/configfile"
|
|
||||||
"github.com/docker/docker/cliconfig/credentials"
|
|
||||||
"github.com/docker/docker/dockerversion"
|
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/docker/docker/pkg/term"
|
|
||||||
"github.com/docker/engine-api/client"
|
|
||||||
"github.com/docker/go-connections/sockets"
|
|
||||||
"github.com/docker/go-connections/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
|
|
||||||
|
|
||||||
// configFile has the client configuration file
|
|
||||||
configFile *configfile.ConfigFile
|
|
||||||
// in holds the input stream and closer (io.ReadCloser) for the client.
|
|
||||||
in io.ReadCloser
|
|
||||||
// out holds the output stream (io.Writer) for the client.
|
|
||||||
out io.Writer
|
|
||||||
// err holds the error stream (io.Writer) for the client.
|
|
||||||
err io.Writer
|
|
||||||
// keyFile holds the key file as a string.
|
|
||||||
keyFile string
|
|
||||||
// inFd holds the file descriptor of the client's STDIN (if valid).
|
|
||||||
inFd uintptr
|
|
||||||
// outFd holds file descriptor of the client's STDOUT (if valid).
|
|
||||||
outFd uintptr
|
|
||||||
// isTerminalIn indicates whether the client's STDIN is a TTY
|
|
||||||
isTerminalIn bool
|
|
||||||
// isTerminalOut indicates whether the client's STDOUT is a TTY
|
|
||||||
isTerminalOut bool
|
|
||||||
// client is the http client that performs all API operations
|
|
||||||
client client.APIClient
|
|
||||||
// state holds the terminal state
|
|
||||||
state *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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Out returns the writer used for stdout
|
|
||||||
func (cli *DockerCli) Out() io.Writer {
|
|
||||||
return cli.out
|
|
||||||
}
|
|
||||||
|
|
||||||
// Err returns the writer used for stderr
|
|
||||||
func (cli *DockerCli) Err() io.Writer {
|
|
||||||
return cli.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// In returns the reader used for stdin
|
|
||||||
func (cli *DockerCli) In() io.ReadCloser {
|
|
||||||
return cli.in
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConfigFile returns the ConfigFile
|
|
||||||
func (cli *DockerCli) ConfigFile() *configfile.ConfigFile {
|
|
||||||
return cli.configFile
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsTerminalOut returns true if the clients stdin is a TTY
|
|
||||||
func (cli *DockerCli) IsTerminalOut() bool {
|
|
||||||
return cli.isTerminalOut
|
|
||||||
}
|
|
||||||
|
|
||||||
// OutFd returns the fd for the stdout stream
|
|
||||||
func (cli *DockerCli) OutFd() uintptr {
|
|
||||||
return cli.outFd
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckTtyInput checks if we are trying to attach to a container tty
|
|
||||||
// from a non-tty client input stream, and if so, returns an error.
|
|
||||||
func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error {
|
|
||||||
// In order to attach to a container tty, input stream for the client must
|
|
||||||
// be a tty itself: redirecting or piping the client standard input is
|
|
||||||
// incompatible with `docker run -t`, `docker exec -t` or `docker attach`.
|
|
||||||
if ttyMode && attachStdin && !cli.isTerminalIn {
|
|
||||||
eText := "the input device is not a TTY"
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
return errors.New(eText + ". If you are using mintty, try prefixing the command with 'winpty'")
|
|
||||||
}
|
|
||||||
return errors.New(eText)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PsFormat returns the format string specified in the configuration.
|
|
||||||
// String contains columns and format specification, for example {{ID}}\t{{Name}}.
|
|
||||||
func (cli *DockerCli) PsFormat() string {
|
|
||||||
return cli.configFile.PsFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
// ImagesFormat returns the format string specified in the configuration.
|
|
||||||
// String contains columns and format specification, for example {{ID}}\t{{Name}}.
|
|
||||||
func (cli *DockerCli) ImagesFormat() string {
|
|
||||||
return cli.configFile.ImagesFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) setRawTerminal() error {
|
|
||||||
if cli.isTerminalIn && os.Getenv("NORAW") == "" {
|
|
||||||
state, err := term.SetRawTerminal(cli.inFd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cli.state = state
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) restoreTerminal(in io.Closer) error {
|
|
||||||
if cli.state != nil {
|
|
||||||
term.RestoreTerminal(cli.inFd, cli.state)
|
|
||||||
}
|
|
||||||
// WARNING: DO NOT REMOVE THE OS CHECK !!!
|
|
||||||
// For some reason this Close call blocks on darwin..
|
|
||||||
// As the client exists right after, simply discard the close
|
|
||||||
// until we find a better solution.
|
|
||||||
if in != nil && runtime.GOOS != "darwin" {
|
|
||||||
return in.Close()
|
|
||||||
}
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
cli.init = func() error {
|
|
||||||
clientFlags.PostParse()
|
|
||||||
cli.configFile = LoadDefaultConfigFile(err)
|
|
||||||
|
|
||||||
client, err := NewAPIClientFromFlags(clientFlags, cli.configFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cli.client = client
|
|
||||||
|
|
||||||
if cli.in != nil {
|
|
||||||
cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in)
|
|
||||||
}
|
|
||||||
if cli.out != nil {
|
|
||||||
cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return cli
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadDefaultConfigFile attempts to load the default config file and returns
|
|
||||||
// an initialized ConfigFile struct if none is found.
|
|
||||||
func LoadDefaultConfigFile(err io.Writer) *configfile.ConfigFile {
|
|
||||||
configFile, e := cliconfig.Load(cliconfig.ConfigDir())
|
|
||||||
if e != nil {
|
|
||||||
fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e)
|
|
||||||
}
|
|
||||||
if !configFile.ContainsAuth() {
|
|
||||||
credentials.DetectDefaultStore(configFile)
|
|
||||||
}
|
|
||||||
return 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)
|
|
||||||
if err != nil {
|
|
||||||
return &client.Client{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
customHeaders := configFile.HTTPHeaders
|
|
||||||
if customHeaders == nil {
|
|
||||||
customHeaders = map[string]string{}
|
|
||||||
}
|
|
||||||
customHeaders["User-Agent"] = clientUserAgent()
|
|
||||||
|
|
||||||
verStr := api.DefaultVersion
|
|
||||||
if tmpStr := os.Getenv("DOCKER_API_VERSION"); tmpStr != "" {
|
|
||||||
verStr = tmpStr
|
|
||||||
}
|
|
||||||
|
|
||||||
httpClient, err := newHTTPClient(host, clientFlags.Common.TLSOptions)
|
|
||||||
if err != nil {
|
|
||||||
return &client.Client{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return client.NewClient(host, verStr, httpClient, customHeaders)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (host string, err error) {
|
|
||||||
switch len(hosts) {
|
|
||||||
case 0:
|
|
||||||
host = os.Getenv("DOCKER_HOST")
|
|
||||||
case 1:
|
|
||||||
host = hosts[0]
|
|
||||||
default:
|
|
||||||
return "", errors.New("Please specify only one -H")
|
|
||||||
}
|
|
||||||
|
|
||||||
host, err = opts.ParseHost(tlsOptions != nil, host)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func newHTTPClient(host string, tlsOptions *tlsconfig.Options) (*http.Client, error) {
|
|
||||||
if tlsOptions == nil {
|
|
||||||
// let the api client configure the default transport.
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
config, err := tlsconfig.Client(*tlsOptions)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tr := &http.Transport{
|
|
||||||
TLSClientConfig: config,
|
|
||||||
}
|
|
||||||
proto, addr, _, err := client.ParseHost(host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sockets.ConfigureTransport(tr, proto, addr)
|
|
||||||
|
|
||||||
return &http.Client{
|
|
||||||
Transport: tr,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func clientUserAgent() string {
|
|
||||||
return "Docker-Client/" + dockerversion.Version + " (" + runtime.GOOS + ")"
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
// Package client provides a command-line interface for Docker.
|
|
||||||
//
|
|
||||||
// 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
|
|
||||||
|
|
@ -1,9 +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{
|
|
||||||
"exec": cli.CmdExec,
|
|
||||||
"inspect": cli.CmdInspect,
|
|
||||||
}[name]
|
|
||||||
}
|
|
||||||
|
|
@ -1,130 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http/httputil"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/pkg/signal"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type attachOptions struct {
|
|
||||||
noStdin bool
|
|
||||||
proxy bool
|
|
||||||
detachKeys string
|
|
||||||
|
|
||||||
container string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAttachCommand creats a new cobra.Command for `docker attach`
|
|
||||||
func NewAttachCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts attachOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "attach [OPTIONS] CONTAINER",
|
|
||||||
Short: "Attach to a running container",
|
|
||||||
Args: cli.ExactArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.container = args[0]
|
|
||||||
return runAttach(dockerCli, &opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cmd.SetFlagErrorFunc(flagErrorFunc)
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.BoolVar(&opts.noStdin, "no-stdin", false, "Do not attach STDIN")
|
|
||||||
flags.BoolVar(&opts.proxy, "sig-proxy", true, "Proxy all received signals to the process")
|
|
||||||
flags.StringVar(&opts.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container")
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runAttach(dockerCli *client.DockerCli, opts *attachOptions) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
c, err := dockerCli.Client().ContainerInspect(ctx, opts.container)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.State.Running {
|
|
||||||
return fmt.Errorf("You cannot attach to a stopped container, start it first")
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.State.Paused {
|
|
||||||
return fmt.Errorf("You cannot attach to a paused container, unpause it first")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := dockerCli.CheckTtyInput(!opts.noStdin, c.Config.Tty); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.detachKeys != "" {
|
|
||||||
dockerCli.ConfigFile().DetachKeys = opts.detachKeys
|
|
||||||
}
|
|
||||||
|
|
||||||
options := types.ContainerAttachOptions{
|
|
||||||
Stream: true,
|
|
||||||
Stdin: !opts.noStdin && c.Config.OpenStdin,
|
|
||||||
Stdout: true,
|
|
||||||
Stderr: true,
|
|
||||||
DetachKeys: dockerCli.ConfigFile().DetachKeys,
|
|
||||||
}
|
|
||||||
|
|
||||||
var in io.ReadCloser
|
|
||||||
if options.Stdin {
|
|
||||||
in = dockerCli.In()
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.proxy && !c.Config.Tty {
|
|
||||||
sigc := dockerCli.ForwardAllSignals(ctx, opts.container)
|
|
||||||
defer signal.StopCatch(sigc)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, errAttach := dockerCli.Client().ContainerAttach(ctx, opts.container, options)
|
|
||||||
if errAttach != nil && errAttach != httputil.ErrPersistEOF {
|
|
||||||
// ContainerAttach returns an ErrPersistEOF (connection closed)
|
|
||||||
// means server met an error and put it in Hijacked connection
|
|
||||||
// keep the error and read detailed error message from hijacked connection later
|
|
||||||
return errAttach
|
|
||||||
}
|
|
||||||
defer resp.Close()
|
|
||||||
|
|
||||||
if c.Config.Tty && dockerCli.IsTerminalOut() {
|
|
||||||
height, width := dockerCli.GetTtySize()
|
|
||||||
// To handle the case where a user repeatedly attaches/detaches without resizing their
|
|
||||||
// terminal, the only way to get the shell prompt to display for attaches 2+ is to artificially
|
|
||||||
// resize it, then go back to normal. Without this, every attach after the first will
|
|
||||||
// require the user to manually resize or hit enter.
|
|
||||||
dockerCli.ResizeTtyTo(ctx, opts.container, height+1, width+1, false)
|
|
||||||
|
|
||||||
// After the above resizing occurs, the call to MonitorTtySize below will handle resetting back
|
|
||||||
// to the actual size.
|
|
||||||
if err := dockerCli.MonitorTtySize(ctx, opts.container, false); err != nil {
|
|
||||||
logrus.Debugf("Error monitoring TTY size: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := dockerCli.HoldHijackedConnection(ctx, c.Config.Tty, in, dockerCli.Out(), dockerCli.Err(), resp); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if errAttach != nil {
|
|
||||||
return errAttach
|
|
||||||
}
|
|
||||||
|
|
||||||
_, status, err := getExitCode(dockerCli, ctx, opts.container)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if status != 0 {
|
|
||||||
return cli.StatusError{StatusCode: status}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
dockeropts "github.com/docker/docker/opts"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
containertypes "github.com/docker/engine-api/types/container"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type commitOptions struct {
|
|
||||||
container string
|
|
||||||
reference string
|
|
||||||
|
|
||||||
pause bool
|
|
||||||
comment string
|
|
||||||
author string
|
|
||||||
changes dockeropts.ListOpts
|
|
||||||
config string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCommitCommand creats a new cobra.Command for `docker commit`
|
|
||||||
func NewCommitCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts commitOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]",
|
|
||||||
Short: "Create a new image from a container's changes",
|
|
||||||
Args: cli.RequiresRangeArgs(1, 2),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.container = args[0]
|
|
||||||
if len(args) > 1 {
|
|
||||||
opts.reference = args[1]
|
|
||||||
}
|
|
||||||
return runCommit(dockerCli, &opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cmd.SetFlagErrorFunc(flagErrorFunc)
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.SetInterspersed(false)
|
|
||||||
|
|
||||||
flags.BoolVarP(&opts.pause, "pause", "p", true, "Pause container during commit")
|
|
||||||
flags.StringVarP(&opts.comment, "message", "m", "", "Commit message")
|
|
||||||
flags.StringVarP(&opts.author, "author", "a", "", "Author (e.g., \"John Hannibal Smith <hannibal@a-team.com>\")")
|
|
||||||
|
|
||||||
opts.changes = dockeropts.NewListOpts(nil)
|
|
||||||
flags.VarP(&opts.changes, "change", "c", "Apply Dockerfile instruction to the created image")
|
|
||||||
|
|
||||||
// FIXME: --run is deprecated, it will be replaced with inline Dockerfile commands.
|
|
||||||
flags.StringVar(&opts.config, "run", "", "This option is deprecated and will be removed in a future version in favor of inline Dockerfile-compatible commands")
|
|
||||||
flags.MarkDeprecated("run", "it will be replaced with inline Dockerfile commands.")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runCommit(dockerCli *client.DockerCli, opts *commitOptions) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
name := opts.container
|
|
||||||
reference := opts.reference
|
|
||||||
|
|
||||||
var config *containertypes.Config
|
|
||||||
if opts.config != "" {
|
|
||||||
config = &containertypes.Config{}
|
|
||||||
if err := json.Unmarshal([]byte(opts.config), config); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
options := types.ContainerCommitOptions{
|
|
||||||
Reference: reference,
|
|
||||||
Comment: opts.comment,
|
|
||||||
Author: opts.author,
|
|
||||||
Changes: opts.changes.GetAll(),
|
|
||||||
Pause: opts.pause,
|
|
||||||
Config: config,
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := dockerCli.Client().ContainerCommit(ctx, name, options)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintln(dockerCli.Out(), response.ID)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,303 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/pkg/archive"
|
|
||||||
"github.com/docker/docker/pkg/system"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type copyOptions struct {
|
|
||||||
source string
|
|
||||||
destination string
|
|
||||||
followLink bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type copyDirection int
|
|
||||||
|
|
||||||
const (
|
|
||||||
fromContainer copyDirection = (1 << iota)
|
|
||||||
toContainer
|
|
||||||
acrossContainers = fromContainer | toContainer
|
|
||||||
)
|
|
||||||
|
|
||||||
type cpConfig struct {
|
|
||||||
followLink bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCopyCommand creates a new `docker cp` command
|
|
||||||
func NewCopyCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts copyOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: `cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-
|
|
||||||
docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH`,
|
|
||||||
Short: "Copy files/folders between a container and the local filesystem",
|
|
||||||
Long: strings.Join([]string{
|
|
||||||
"Copy files/folders between a container and the local filesystem\n",
|
|
||||||
"\nUse '-' as the source to read a tar archive from stdin\n",
|
|
||||||
"and extract it to a directory destination in a container.\n",
|
|
||||||
"Use '-' as the destination to stream a tar archive of a\n",
|
|
||||||
"container source to stdout.",
|
|
||||||
}, ""),
|
|
||||||
Args: cli.ExactArgs(2),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
if args[0] == "" {
|
|
||||||
return fmt.Errorf("source can not be empty")
|
|
||||||
}
|
|
||||||
if args[1] == "" {
|
|
||||||
return fmt.Errorf("destination can not be empty")
|
|
||||||
}
|
|
||||||
opts.source = args[0]
|
|
||||||
opts.destination = args[1]
|
|
||||||
return runCopy(dockerCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
|
|
||||||
flags.BoolVarP(&opts.followLink, "follow-link", "L", false, "Always follow symbol link in SRC_PATH")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runCopy(dockerCli *client.DockerCli, opts copyOptions) error {
|
|
||||||
srcContainer, srcPath := splitCpArg(opts.source)
|
|
||||||
dstContainer, dstPath := splitCpArg(opts.destination)
|
|
||||||
|
|
||||||
var direction copyDirection
|
|
||||||
if srcContainer != "" {
|
|
||||||
direction |= fromContainer
|
|
||||||
}
|
|
||||||
if dstContainer != "" {
|
|
||||||
direction |= toContainer
|
|
||||||
}
|
|
||||||
|
|
||||||
cpParam := &cpConfig{
|
|
||||||
followLink: opts.followLink,
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
switch direction {
|
|
||||||
case fromContainer:
|
|
||||||
return copyFromContainer(ctx, dockerCli, srcContainer, srcPath, dstPath, cpParam)
|
|
||||||
case toContainer:
|
|
||||||
return copyToContainer(ctx, dockerCli, srcPath, dstContainer, dstPath, cpParam)
|
|
||||||
case acrossContainers:
|
|
||||||
// Copying between containers isn't supported.
|
|
||||||
return fmt.Errorf("copying between containers is not supported")
|
|
||||||
default:
|
|
||||||
// User didn't specify any container.
|
|
||||||
return fmt.Errorf("must specify at least one container source")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func statContainerPath(ctx context.Context, dockerCli *client.DockerCli, containerName, path string) (types.ContainerPathStat, error) {
|
|
||||||
return dockerCli.Client().ContainerStatPath(ctx, containerName, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveLocalPath(localPath string) (absPath string, err error) {
|
|
||||||
if absPath, err = filepath.Abs(localPath); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return archive.PreserveTrailingDotOrSeparator(absPath, localPath), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyFromContainer(ctx context.Context, dockerCli *client.DockerCli, srcContainer, srcPath, dstPath string, cpParam *cpConfig) (err error) {
|
|
||||||
if dstPath != "-" {
|
|
||||||
// Get an absolute destination path.
|
|
||||||
dstPath, err = resolveLocalPath(dstPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if client requests to follow symbol link, then must decide target file to be copied
|
|
||||||
var rebaseName string
|
|
||||||
if cpParam.followLink {
|
|
||||||
srcStat, err := statContainerPath(ctx, dockerCli, srcContainer, srcPath)
|
|
||||||
|
|
||||||
// If the destination is a symbolic link, we should follow it.
|
|
||||||
if err == nil && srcStat.Mode&os.ModeSymlink != 0 {
|
|
||||||
linkTarget := srcStat.LinkTarget
|
|
||||||
if !system.IsAbs(linkTarget) {
|
|
||||||
// Join with the parent directory.
|
|
||||||
srcParent, _ := archive.SplitPathDirEntry(srcPath)
|
|
||||||
linkTarget = filepath.Join(srcParent, linkTarget)
|
|
||||||
}
|
|
||||||
|
|
||||||
linkTarget, rebaseName = archive.GetRebaseName(srcPath, linkTarget)
|
|
||||||
srcPath = linkTarget
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
content, stat, err := dockerCli.Client().CopyFromContainer(ctx, srcContainer, srcPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer content.Close()
|
|
||||||
|
|
||||||
if dstPath == "-" {
|
|
||||||
// Send the response to STDOUT.
|
|
||||||
_, err = io.Copy(os.Stdout, content)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare source copy info.
|
|
||||||
srcInfo := archive.CopyInfo{
|
|
||||||
Path: srcPath,
|
|
||||||
Exists: true,
|
|
||||||
IsDir: stat.Mode.IsDir(),
|
|
||||||
RebaseName: rebaseName,
|
|
||||||
}
|
|
||||||
|
|
||||||
preArchive := content
|
|
||||||
if len(srcInfo.RebaseName) != 0 {
|
|
||||||
_, srcBase := archive.SplitPathDirEntry(srcInfo.Path)
|
|
||||||
preArchive = archive.RebaseArchiveEntries(content, srcBase, srcInfo.RebaseName)
|
|
||||||
}
|
|
||||||
// See comments in the implementation of `archive.CopyTo` for exactly what
|
|
||||||
// goes into deciding how and whether the source archive needs to be
|
|
||||||
// altered for the correct copy behavior.
|
|
||||||
return archive.CopyTo(preArchive, srcInfo, dstPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyToContainer(ctx context.Context, dockerCli *client.DockerCli, srcPath, dstContainer, dstPath string, cpParam *cpConfig) (err error) {
|
|
||||||
if srcPath != "-" {
|
|
||||||
// Get an absolute source path.
|
|
||||||
srcPath, err = resolveLocalPath(srcPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// In order to get the copy behavior right, we need to know information
|
|
||||||
// about both the source and destination. The API is a simple tar
|
|
||||||
// archive/extract API but we can use the stat info header about the
|
|
||||||
// destination to be more informed about exactly what the destination is.
|
|
||||||
|
|
||||||
// Prepare destination copy info by stat-ing the container path.
|
|
||||||
dstInfo := archive.CopyInfo{Path: dstPath}
|
|
||||||
dstStat, err := statContainerPath(ctx, dockerCli, dstContainer, dstPath)
|
|
||||||
|
|
||||||
// If the destination is a symbolic link, we should evaluate it.
|
|
||||||
if err == nil && dstStat.Mode&os.ModeSymlink != 0 {
|
|
||||||
linkTarget := dstStat.LinkTarget
|
|
||||||
if !system.IsAbs(linkTarget) {
|
|
||||||
// Join with the parent directory.
|
|
||||||
dstParent, _ := archive.SplitPathDirEntry(dstPath)
|
|
||||||
linkTarget = filepath.Join(dstParent, linkTarget)
|
|
||||||
}
|
|
||||||
|
|
||||||
dstInfo.Path = linkTarget
|
|
||||||
dstStat, err = statContainerPath(ctx, dockerCli, dstContainer, linkTarget)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore any error and assume that the parent directory of the destination
|
|
||||||
// path exists, in which case the copy may still succeed. If there is any
|
|
||||||
// type of conflict (e.g., non-directory overwriting an existing directory
|
|
||||||
// or vice versa) the extraction will fail. If the destination simply did
|
|
||||||
// not exist, but the parent directory does, the extraction will still
|
|
||||||
// succeed.
|
|
||||||
if err == nil {
|
|
||||||
dstInfo.Exists, dstInfo.IsDir = true, dstStat.Mode.IsDir()
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
content io.Reader
|
|
||||||
resolvedDstPath string
|
|
||||||
)
|
|
||||||
|
|
||||||
if srcPath == "-" {
|
|
||||||
// Use STDIN.
|
|
||||||
content = os.Stdin
|
|
||||||
resolvedDstPath = dstInfo.Path
|
|
||||||
if !dstInfo.IsDir {
|
|
||||||
return fmt.Errorf("destination %q must be a directory", fmt.Sprintf("%s:%s", dstContainer, dstPath))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Prepare source copy info.
|
|
||||||
srcInfo, err := archive.CopyInfoSourcePath(srcPath, cpParam.followLink)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
srcArchive, err := archive.TarResource(srcInfo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer srcArchive.Close()
|
|
||||||
|
|
||||||
// With the stat info about the local source as well as the
|
|
||||||
// destination, we have enough information to know whether we need to
|
|
||||||
// alter the archive that we upload so that when the server extracts
|
|
||||||
// it to the specified directory in the container we get the desired
|
|
||||||
// copy behavior.
|
|
||||||
|
|
||||||
// See comments in the implementation of `archive.PrepareArchiveCopy`
|
|
||||||
// for exactly what goes into deciding how and whether the source
|
|
||||||
// archive needs to be altered for the correct copy behavior when it is
|
|
||||||
// extracted. This function also infers from the source and destination
|
|
||||||
// info which directory to extract to, which may be the parent of the
|
|
||||||
// destination that the user specified.
|
|
||||||
dstDir, preparedArchive, err := archive.PrepareArchiveCopy(srcArchive, srcInfo, dstInfo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer preparedArchive.Close()
|
|
||||||
|
|
||||||
resolvedDstPath = dstDir
|
|
||||||
content = preparedArchive
|
|
||||||
}
|
|
||||||
|
|
||||||
options := types.CopyToContainerOptions{
|
|
||||||
AllowOverwriteDirWithFile: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
return dockerCli.Client().CopyToContainer(ctx, dstContainer, resolvedDstPath, content, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We use `:` as a delimiter between CONTAINER and PATH, but `:` could also be
|
|
||||||
// in a valid LOCALPATH, like `file:name.txt`. We can resolve this ambiguity by
|
|
||||||
// requiring a LOCALPATH with a `:` to be made explicit with a relative or
|
|
||||||
// absolute path:
|
|
||||||
// `/path/to/file:name.txt` or `./file:name.txt`
|
|
||||||
//
|
|
||||||
// This is apparently how `scp` handles this as well:
|
|
||||||
// http://www.cyberciti.biz/faq/rsync-scp-file-name-with-colon-punctuation-in-it/
|
|
||||||
//
|
|
||||||
// We can't simply check for a filepath separator because container names may
|
|
||||||
// have a separator, e.g., "host0/cname1" if container is in a Docker cluster,
|
|
||||||
// so we have to check for a `/` or `.` prefix. Also, in the case of a Windows
|
|
||||||
// client, a `:` could be part of an absolute Windows path, in which case it
|
|
||||||
// is immediately proceeded by a backslash.
|
|
||||||
func splitCpArg(arg string) (container, path string) {
|
|
||||||
if system.IsAbs(arg) {
|
|
||||||
// Explicit local absolute path, e.g., `C:\foo` or `/foo`.
|
|
||||||
return "", arg
|
|
||||||
}
|
|
||||||
|
|
||||||
parts := strings.SplitN(arg, ":", 2)
|
|
||||||
|
|
||||||
if len(parts) == 1 || strings.HasPrefix(parts[0], ".") {
|
|
||||||
// Either there's no `:` in the arg
|
|
||||||
// OR it's an explicit local relative path like `./file:name.txt`.
|
|
||||||
return "", arg
|
|
||||||
}
|
|
||||||
|
|
||||||
return parts[0], parts[1]
|
|
||||||
}
|
|
||||||
|
|
@ -1,218 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
|
||||||
// FIXME migrate to docker/distribution/reference
|
|
||||||
"github.com/docker/docker/reference"
|
|
||||||
"github.com/docker/docker/registry"
|
|
||||||
runconfigopts "github.com/docker/docker/runconfig/opts"
|
|
||||||
apiclient "github.com/docker/engine-api/client"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
"github.com/docker/engine-api/types/container"
|
|
||||||
networktypes "github.com/docker/engine-api/types/network"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/pflag"
|
|
||||||
)
|
|
||||||
|
|
||||||
type createOptions struct {
|
|
||||||
name string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCreateCommand creats a new cobra.Command for `docker create`
|
|
||||||
func NewCreateCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts createOptions
|
|
||||||
var copts *runconfigopts.ContainerOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "create [OPTIONS] IMAGE [COMMAND] [ARG...]",
|
|
||||||
Short: "Create a new container",
|
|
||||||
Args: cli.RequiresMinArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
copts.Image = args[0]
|
|
||||||
if len(args) > 1 {
|
|
||||||
copts.Args = args[1:]
|
|
||||||
}
|
|
||||||
return runCreate(dockerCli, cmd.Flags(), &opts, copts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cmd.SetFlagErrorFunc(flagErrorFunc)
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.SetInterspersed(false)
|
|
||||||
|
|
||||||
flags.StringVar(&opts.name, "name", "", "Assign a name to the container")
|
|
||||||
|
|
||||||
// Add an explicit help that doesn't have a `-h` to prevent the conflict
|
|
||||||
// with hostname
|
|
||||||
flags.Bool("help", false, "Print usage")
|
|
||||||
|
|
||||||
client.AddTrustedFlags(flags, true)
|
|
||||||
copts = runconfigopts.AddFlags(flags)
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runCreate(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *createOptions, copts *runconfigopts.ContainerOptions) error {
|
|
||||||
config, hostConfig, networkingConfig, err := runconfigopts.Parse(flags, copts)
|
|
||||||
if err != nil {
|
|
||||||
reportError(dockerCli.Err(), "create", err.Error(), true)
|
|
||||||
return cli.StatusError{StatusCode: 125}
|
|
||||||
}
|
|
||||||
response, err := createContainer(context.Background(), dockerCli, config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, opts.name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "%s\n", response.ID)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func pullImage(ctx context.Context, dockerCli *client.DockerCli, image string, out io.Writer) error {
|
|
||||||
ref, err := reference.ParseNamed(image)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve the Repository name from fqn to RepositoryInfo
|
|
||||||
repoInfo, err := registry.ParseRepositoryInfo(ref)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
authConfig := dockerCli.ResolveAuthConfig(ctx, repoInfo.Index)
|
|
||||||
encodedAuth, err := client.EncodeAuthToBase64(authConfig)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
options := types.ImageCreateOptions{
|
|
||||||
RegistryAuth: encodedAuth,
|
|
||||||
}
|
|
||||||
|
|
||||||
responseBody, err := dockerCli.Client().ImageCreate(ctx, image, options)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer responseBody.Close()
|
|
||||||
|
|
||||||
return jsonmessage.DisplayJSONMessagesStream(
|
|
||||||
responseBody,
|
|
||||||
out,
|
|
||||||
dockerCli.OutFd(),
|
|
||||||
dockerCli.IsTerminalOut(),
|
|
||||||
nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
type cidFile struct {
|
|
||||||
path string
|
|
||||||
file *os.File
|
|
||||||
written bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cid *cidFile) Close() error {
|
|
||||||
cid.file.Close()
|
|
||||||
|
|
||||||
if !cid.written {
|
|
||||||
if err := os.Remove(cid.path); err != nil {
|
|
||||||
return fmt.Errorf("failed to remove the CID file '%s': %s \n", cid.path, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cid *cidFile) Write(id string) error {
|
|
||||||
if _, err := cid.file.Write([]byte(id)); err != nil {
|
|
||||||
return fmt.Errorf("Failed to write the container ID to the file: %s", err)
|
|
||||||
}
|
|
||||||
cid.written = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newCIDFile(path string) (*cidFile, error) {
|
|
||||||
if _, err := os.Stat(path); err == nil {
|
|
||||||
return nil, fmt.Errorf("Container ID file found, make sure the other container isn't running or delete %s", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.Create(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Failed to create the container ID file: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &cidFile{path: path, file: f}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createContainer(ctx context.Context, dockerCli *client.DockerCli, config *container.Config, hostConfig *container.HostConfig, networkingConfig *networktypes.NetworkingConfig, cidfile, name string) (*types.ContainerCreateResponse, error) {
|
|
||||||
stderr := dockerCli.Err()
|
|
||||||
|
|
||||||
var containerIDFile *cidFile
|
|
||||||
if cidfile != "" {
|
|
||||||
var err error
|
|
||||||
if containerIDFile, err = newCIDFile(cidfile); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer containerIDFile.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
var trustedRef reference.Canonical
|
|
||||||
_, ref, err := reference.ParseIDOrReference(config.Image)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if ref != nil {
|
|
||||||
ref = reference.WithDefaultTag(ref)
|
|
||||||
|
|
||||||
if ref, ok := ref.(reference.NamedTagged); ok && client.IsTrusted() {
|
|
||||||
var err error
|
|
||||||
trustedRef, err = dockerCli.TrustedReference(ctx, ref)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
config.Image = trustedRef.String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//create the container
|
|
||||||
response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, name)
|
|
||||||
|
|
||||||
//if image not found try to pull it
|
|
||||||
if err != nil {
|
|
||||||
if apiclient.IsErrImageNotFound(err) && ref != nil {
|
|
||||||
fmt.Fprintf(stderr, "Unable to find image '%s' locally\n", ref.String())
|
|
||||||
|
|
||||||
// we don't want to write to stdout anything apart from container.ID
|
|
||||||
if err = pullImage(ctx, dockerCli, config.Image, stderr); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if ref, ok := ref.(reference.NamedTagged); ok && trustedRef != nil {
|
|
||||||
if err := dockerCli.TagTrusted(ctx, trustedRef, ref); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Retry
|
|
||||||
var retryErr error
|
|
||||||
response, retryErr = dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, name)
|
|
||||||
if retryErr != nil {
|
|
||||||
return nil, retryErr
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, warning := range response.Warnings {
|
|
||||||
fmt.Fprintf(stderr, "WARNING: %s\n", warning)
|
|
||||||
}
|
|
||||||
if containerIDFile != nil {
|
|
||||||
if err = containerIDFile.Write(response.ID); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &response, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/pkg/archive"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type diffOptions struct {
|
|
||||||
container string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDiffCommand creates a new cobra.Command for `docker diff`
|
|
||||||
func NewDiffCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts diffOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "diff CONTAINER",
|
|
||||||
Short: "Inspect changes on a container's filesystem",
|
|
||||||
Args: cli.ExactArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.container = args[0]
|
|
||||||
return runDiff(dockerCli, &opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cmd.SetFlagErrorFunc(flagErrorFunc)
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runDiff(dockerCli *client.DockerCli, opts *diffOptions) error {
|
|
||||||
if opts.container == "" {
|
|
||||||
return fmt.Errorf("Container name cannot be empty")
|
|
||||||
}
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
changes, err := dockerCli.Client().ContainerDiff(ctx, opts.container)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, change := range changes {
|
|
||||||
var kind string
|
|
||||||
switch change.Kind {
|
|
||||||
case archive.ChangeModify:
|
|
||||||
kind = "C"
|
|
||||||
case archive.ChangeAdd:
|
|
||||||
kind = "A"
|
|
||||||
case archive.ChangeDelete:
|
|
||||||
kind = "D"
|
|
||||||
}
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "%s %s\n", kind, change.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type exportOptions struct {
|
|
||||||
container string
|
|
||||||
output string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewExportCommand creates a new `docker export` command
|
|
||||||
func NewExportCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts exportOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "export [OPTIONS] CONTAINER",
|
|
||||||
Short: "Export a container's filesystem as a tar archive",
|
|
||||||
Args: cli.ExactArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.container = args[0]
|
|
||||||
return runExport(dockerCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
|
|
||||||
flags.StringVarP(&opts.output, "output", "o", "", "Write to a file, instead of STDOUT")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runExport(dockerCli *client.DockerCli, opts exportOptions) error {
|
|
||||||
if opts.output == "" && dockerCli.IsTerminalOut() {
|
|
||||||
return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
|
|
||||||
}
|
|
||||||
|
|
||||||
clnt := dockerCli.Client()
|
|
||||||
|
|
||||||
responseBody, err := clnt.ContainerExport(context.Background(), opts.container)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer responseBody.Close()
|
|
||||||
|
|
||||||
if opts.output == "" {
|
|
||||||
_, err := io.Copy(dockerCli.Out(), responseBody)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return client.CopyToFile(opts.output, responseBody)
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type killOptions struct {
|
|
||||||
signal string
|
|
||||||
|
|
||||||
containers []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewKillCommand creats a new cobra.Command for `docker kill`
|
|
||||||
func NewKillCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts killOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "kill [OPTIONS] CONTAINER [CONTAINER...]",
|
|
||||||
Short: "Kill one or more running container",
|
|
||||||
Args: cli.RequiresMinArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.containers = args
|
|
||||||
return runKill(dockerCli, &opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.StringVarP(&opts.signal, "signal", "s", "KILL", "Signal to send to the container")
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runKill(dockerCli *client.DockerCli, opts *killOptions) error {
|
|
||||||
var errs []string
|
|
||||||
ctx := context.Background()
|
|
||||||
for _, name := range opts.containers {
|
|
||||||
if err := dockerCli.Client().ContainerKill(ctx, name, opts.signal); err != nil {
|
|
||||||
errs = append(errs, err.Error())
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "%s\n", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(errs) > 0 {
|
|
||||||
return fmt.Errorf("%s", strings.Join(errs, "\n"))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var validDrivers = map[string]bool{
|
|
||||||
"json-file": true,
|
|
||||||
"journald": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
type logsOptions struct {
|
|
||||||
follow bool
|
|
||||||
since string
|
|
||||||
timestamps bool
|
|
||||||
details bool
|
|
||||||
tail string
|
|
||||||
|
|
||||||
container string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewLogsCommand creats a new cobra.Command for `docker logs`
|
|
||||||
func NewLogsCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts logsOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "logs [OPTIONS] CONTAINER",
|
|
||||||
Short: "Fetch the logs of a container",
|
|
||||||
Args: cli.ExactArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.container = args[0]
|
|
||||||
return runLogs(dockerCli, &opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cmd.SetFlagErrorFunc(flagErrorFunc)
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.BoolVarP(&opts.follow, "follow", "f", false, "Follow log output")
|
|
||||||
flags.StringVar(&opts.since, "since", "", "Show logs since timestamp")
|
|
||||||
flags.BoolVarP(&opts.timestamps, "timestamps", "t", false, "Show timestamps")
|
|
||||||
flags.BoolVar(&opts.details, "details", false, "Show extra details provided to logs")
|
|
||||||
flags.StringVar(&opts.tail, "tail", "all", "Number of lines to show from the end of the logs")
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runLogs(dockerCli *client.DockerCli, opts *logsOptions) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
c, err := dockerCli.Client().ContainerInspect(ctx, opts.container)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !validDrivers[c.HostConfig.LogConfig.Type] {
|
|
||||||
return fmt.Errorf("\"logs\" command is supported only for \"json-file\" and \"journald\" logging drivers (got: %s)", c.HostConfig.LogConfig.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
options := types.ContainerLogsOptions{
|
|
||||||
ShowStdout: true,
|
|
||||||
ShowStderr: true,
|
|
||||||
Since: opts.since,
|
|
||||||
Timestamps: opts.timestamps,
|
|
||||||
Follow: opts.follow,
|
|
||||||
Tail: opts.tail,
|
|
||||||
Details: opts.details,
|
|
||||||
}
|
|
||||||
responseBody, err := dockerCli.Client().ContainerLogs(ctx, opts.container, options)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer responseBody.Close()
|
|
||||||
|
|
||||||
if c.Config.Tty {
|
|
||||||
_, err = io.Copy(dockerCli.Out(), responseBody)
|
|
||||||
} else {
|
|
||||||
_, err = stdcopy.StdCopy(dockerCli.Out(), dockerCli.Err(), responseBody)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type pauseOptions struct {
|
|
||||||
containers []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPauseCommand creats a new cobra.Command for `docker pause`
|
|
||||||
func NewPauseCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts pauseOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "pause CONTAINER [CONTAINER...]",
|
|
||||||
Short: "Pause all processes within one or more containers",
|
|
||||||
Args: cli.RequiresMinArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.containers = args
|
|
||||||
return runPause(dockerCli, &opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cmd.SetFlagErrorFunc(flagErrorFunc)
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runPause(dockerCli *client.DockerCli, opts *pauseOptions) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
var errs []string
|
|
||||||
for _, container := range opts.containers {
|
|
||||||
if err := dockerCli.Client().ContainerPause(ctx, container); err != nil {
|
|
||||||
errs = append(errs, err.Error())
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "%s\n", container)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(errs) > 0 {
|
|
||||||
return fmt.Errorf("%s", strings.Join(errs, "\n"))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/go-connections/nat"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type portOptions struct {
|
|
||||||
container string
|
|
||||||
|
|
||||||
port string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPortCommand creats a new cobra.Command for `docker port`
|
|
||||||
func NewPortCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts portOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "port CONTAINER [PRIVATE_PORT[/PROTO]]",
|
|
||||||
Short: "List port mappings or a specific mapping for the container",
|
|
||||||
Args: cli.RequiresRangeArgs(1, 2),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.container = args[0]
|
|
||||||
if len(args) > 1 {
|
|
||||||
opts.port = args[1]
|
|
||||||
}
|
|
||||||
return runPort(dockerCli, &opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cmd.SetFlagErrorFunc(flagErrorFunc)
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runPort(dockerCli *client.DockerCli, opts *portOptions) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
c, err := dockerCli.Client().ContainerInspect(ctx, opts.container)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.port != "" {
|
|
||||||
port := opts.port
|
|
||||||
proto := "tcp"
|
|
||||||
parts := strings.SplitN(port, "/", 2)
|
|
||||||
|
|
||||||
if len(parts) == 2 && len(parts[1]) != 0 {
|
|
||||||
port = parts[0]
|
|
||||||
proto = parts[1]
|
|
||||||
}
|
|
||||||
natPort := port + "/" + proto
|
|
||||||
newP, err := nat.NewPort(proto, port)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if frontends, exists := c.NetworkSettings.Ports[newP]; exists && frontends != nil {
|
|
||||||
for _, frontend := range frontends {
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "%s:%s\n", frontend.HostIP, frontend.HostPort)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("Error: No public port '%s' published for %s", natPort, opts.container)
|
|
||||||
}
|
|
||||||
|
|
||||||
for from, frontends := range c.NetworkSettings.Ports {
|
|
||||||
for _, frontend := range frontends {
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "%s -> %s:%s\n", from, frontend.HostIP, frontend.HostPort)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,125 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/api/client/formatter"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
"github.com/docker/engine-api/types/filters"
|
|
||||||
|
|
||||||
"github.com/docker/docker/utils/templates"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"io/ioutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
type psOptions struct {
|
|
||||||
quiet bool
|
|
||||||
size bool
|
|
||||||
all bool
|
|
||||||
noTrunc bool
|
|
||||||
nLatest bool
|
|
||||||
last int
|
|
||||||
format string
|
|
||||||
filter []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type preProcessor struct {
|
|
||||||
opts *types.ContainerListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size sets the size option when called by a template execution.
|
|
||||||
func (p *preProcessor) Size() bool {
|
|
||||||
p.opts.Size = true
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPsCommand creates a new cobra.Command for `docker ps`
|
|
||||||
func NewPsCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts psOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "ps [OPTIONS]",
|
|
||||||
Short: "List containers",
|
|
||||||
Args: cli.ExactArgs(0),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return runPs(dockerCli, &opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
|
|
||||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display numeric IDs")
|
|
||||||
flags.BoolVarP(&opts.size, "size", "s", false, "Display total file sizes")
|
|
||||||
flags.BoolVarP(&opts.all, "all", "a", false, "Show all containers (default shows just running)")
|
|
||||||
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output")
|
|
||||||
flags.BoolVarP(&opts.nLatest, "latest", "l", false, "Show the latest created container (includes all states)")
|
|
||||||
flags.IntVarP(&opts.last, "last", "n", -1, "Show n last created containers (includes all states)")
|
|
||||||
flags.StringVarP(&opts.format, "format", "", "", "Pretty-print containers using a Go template")
|
|
||||||
flags.StringSliceVarP(&opts.filter, "filter", "f", []string{}, "Filter output based on conditions provided")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runPs(dockerCli *client.DockerCli, opts *psOptions) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
if opts.nLatest && opts.last == -1 {
|
|
||||||
opts.last = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
containerFilterArgs := filters.NewArgs()
|
|
||||||
for _, f := range opts.filter {
|
|
||||||
var err error
|
|
||||||
containerFilterArgs, err = filters.ParseFlag(f, containerFilterArgs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
options := types.ContainerListOptions{
|
|
||||||
All: opts.all,
|
|
||||||
Limit: opts.last,
|
|
||||||
Size: opts.size,
|
|
||||||
Filter: containerFilterArgs,
|
|
||||||
}
|
|
||||||
|
|
||||||
pre := &preProcessor{opts: &options}
|
|
||||||
tmpl, err := templates.Parse(opts.format)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = tmpl.Execute(ioutil.Discard, pre)
|
|
||||||
|
|
||||||
containers, err := dockerCli.Client().ContainerList(ctx, options)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
f := opts.format
|
|
||||||
if len(f) == 0 {
|
|
||||||
if len(dockerCli.PsFormat()) > 0 && !opts.quiet {
|
|
||||||
f = dockerCli.PsFormat()
|
|
||||||
} else {
|
|
||||||
f = "table"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
psCtx := formatter.ContainerContext{
|
|
||||||
Context: formatter.Context{
|
|
||||||
Output: dockerCli.Out(),
|
|
||||||
Format: f,
|
|
||||||
Quiet: opts.quiet,
|
|
||||||
Trunc: !opts.noTrunc,
|
|
||||||
},
|
|
||||||
Size: opts.size,
|
|
||||||
Containers: containers,
|
|
||||||
}
|
|
||||||
|
|
||||||
psCtx.Write()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type renameOptions struct {
|
|
||||||
oldName string
|
|
||||||
newName string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRenameCommand creats a new cobra.Command for `docker rename`
|
|
||||||
func NewRenameCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts renameOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "rename OLD_NAME NEW_NAME",
|
|
||||||
Short: "Rename a container",
|
|
||||||
Args: cli.ExactArgs(2),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.oldName = args[0]
|
|
||||||
opts.newName = args[1]
|
|
||||||
return runRename(dockerCli, &opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cmd.SetFlagErrorFunc(flagErrorFunc)
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runRename(dockerCli *client.DockerCli, opts *renameOptions) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
oldName := strings.TrimSpace(opts.oldName)
|
|
||||||
newName := strings.TrimSpace(opts.newName)
|
|
||||||
|
|
||||||
if oldName == "" || newName == "" {
|
|
||||||
return fmt.Errorf("Error: Neither old nor new names may be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := dockerCli.Client().ContainerRename(ctx, oldName, newName); err != nil {
|
|
||||||
fmt.Fprintf(dockerCli.Err(), "%s\n", err)
|
|
||||||
return fmt.Errorf("Error: failed to rename container named %s", oldName)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type restartOptions struct {
|
|
||||||
nSeconds int
|
|
||||||
|
|
||||||
containers []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRestartCommand creates a new cobra.Command for `docker restart`
|
|
||||||
func NewRestartCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts restartOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "restart [OPTIONS] CONTAINER [CONTAINER...]",
|
|
||||||
Short: "Restart a container",
|
|
||||||
Args: cli.RequiresMinArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.containers = args
|
|
||||||
return runRestart(dockerCli, &opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.IntVarP(&opts.nSeconds, "time", "t", 10, "Seconds to wait for stop before killing the container")
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runRestart(dockerCli *client.DockerCli, opts *restartOptions) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
var errs []string
|
|
||||||
for _, name := range opts.containers {
|
|
||||||
timeout := time.Duration(opts.nSeconds) * time.Second
|
|
||||||
if err := dockerCli.Client().ContainerRestart(ctx, name, &timeout); err != nil {
|
|
||||||
errs = append(errs, err.Error())
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "%s\n", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(errs) > 0 {
|
|
||||||
return fmt.Errorf("%s", strings.Join(errs, "\n"))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type rmOptions struct {
|
|
||||||
rmVolumes bool
|
|
||||||
rmLink bool
|
|
||||||
force bool
|
|
||||||
|
|
||||||
containers []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRmCommand creates a new cobra.Command for `docker rm`
|
|
||||||
func NewRmCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts rmOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "rm [OPTIONS] CONTAINER [CONTAINER...]",
|
|
||||||
Short: "Remove one or more containers",
|
|
||||||
Args: cli.RequiresMinArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.containers = args
|
|
||||||
return runRm(dockerCli, &opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.BoolVarP(&opts.rmVolumes, "volumes", "v", false, "Remove the volumes associated with the container")
|
|
||||||
flags.BoolVarP(&opts.rmLink, "link", "l", false, "Remove the specified link")
|
|
||||||
flags.BoolVarP(&opts.force, "force", "f", false, "Force the removal of a running container (uses SIGKILL)")
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runRm(dockerCli *client.DockerCli, opts *rmOptions) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
var errs []string
|
|
||||||
for _, name := range opts.containers {
|
|
||||||
if name == "" {
|
|
||||||
return fmt.Errorf("Container name cannot be empty")
|
|
||||||
}
|
|
||||||
name = strings.Trim(name, "/")
|
|
||||||
|
|
||||||
if err := removeContainer(dockerCli, ctx, name, opts.rmVolumes, opts.rmLink, opts.force); err != nil {
|
|
||||||
errs = append(errs, err.Error())
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "%s\n", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(errs) > 0 {
|
|
||||||
return fmt.Errorf("%s", strings.Join(errs, "\n"))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeContainer(dockerCli *client.DockerCli, ctx context.Context, container string, removeVolumes, removeLinks, force bool) error {
|
|
||||||
options := types.ContainerRemoveOptions{
|
|
||||||
RemoveVolumes: removeVolumes,
|
|
||||||
RemoveLinks: removeLinks,
|
|
||||||
Force: force,
|
|
||||||
}
|
|
||||||
if err := dockerCli.Client().ContainerRemove(ctx, container, options); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,329 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http/httputil"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
opttypes "github.com/docker/docker/opts"
|
|
||||||
"github.com/docker/docker/pkg/promise"
|
|
||||||
"github.com/docker/docker/pkg/signal"
|
|
||||||
runconfigopts "github.com/docker/docker/runconfig/opts"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
"github.com/docker/libnetwork/resolvconf/dns"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/pflag"
|
|
||||||
)
|
|
||||||
|
|
||||||
type runOptions struct {
|
|
||||||
autoRemove bool
|
|
||||||
detach bool
|
|
||||||
sigProxy bool
|
|
||||||
name string
|
|
||||||
detachKeys string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRunCommand create a new `docker run` command
|
|
||||||
func NewRunCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts runOptions
|
|
||||||
var copts *runconfigopts.ContainerOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "run [OPTIONS] IMAGE [COMMAND] [ARG...]",
|
|
||||||
Short: "Run a command in a new container",
|
|
||||||
Args: cli.RequiresMinArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
copts.Image = args[0]
|
|
||||||
if len(args) > 1 {
|
|
||||||
copts.Args = args[1:]
|
|
||||||
}
|
|
||||||
return runRun(dockerCli, cmd.Flags(), &opts, copts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cmd.SetFlagErrorFunc(flagErrorFunc)
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.SetInterspersed(false)
|
|
||||||
|
|
||||||
// These are flags not stored in Config/HostConfig
|
|
||||||
flags.BoolVar(&opts.autoRemove, "rm", false, "Automatically remove the container when it exits")
|
|
||||||
flags.BoolVarP(&opts.detach, "detach", "d", false, "Run container in background and print container ID")
|
|
||||||
flags.BoolVar(&opts.sigProxy, "sig-proxy", true, "Proxy received signals to the process")
|
|
||||||
flags.StringVar(&opts.name, "name", "", "Assign a name to the container")
|
|
||||||
flags.StringVar(&opts.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container")
|
|
||||||
|
|
||||||
// Add an explicit help that doesn't have a `-h` to prevent the conflict
|
|
||||||
// with hostname
|
|
||||||
flags.Bool("help", false, "Print usage")
|
|
||||||
|
|
||||||
client.AddTrustedFlags(flags, true)
|
|
||||||
copts = runconfigopts.AddFlags(flags)
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func flagErrorFunc(cmd *cobra.Command, err error) error {
|
|
||||||
return cli.StatusError{
|
|
||||||
Status: cli.FlagErrorFunc(cmd, err).Error(),
|
|
||||||
StatusCode: 125,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions, copts *runconfigopts.ContainerOptions) error {
|
|
||||||
stdout, stderr, stdin := dockerCli.Out(), dockerCli.Err(), dockerCli.In()
|
|
||||||
client := dockerCli.Client()
|
|
||||||
// TODO: pass this as an argument
|
|
||||||
cmdPath := "run"
|
|
||||||
|
|
||||||
var (
|
|
||||||
flAttach *opttypes.ListOpts
|
|
||||||
ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d")
|
|
||||||
ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm")
|
|
||||||
ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d")
|
|
||||||
)
|
|
||||||
|
|
||||||
config, hostConfig, networkingConfig, err := runconfigopts.Parse(flags, copts)
|
|
||||||
|
|
||||||
// just in case the Parse does not exit
|
|
||||||
if err != nil {
|
|
||||||
reportError(stderr, cmdPath, err.Error(), true)
|
|
||||||
return cli.StatusError{StatusCode: 125}
|
|
||||||
}
|
|
||||||
|
|
||||||
if hostConfig.OomKillDisable != nil && *hostConfig.OomKillDisable && hostConfig.Memory == 0 {
|
|
||||||
fmt.Fprintf(stderr, "WARNING: Disabling the OOM killer on containers without setting a '-m/--memory' limit may be dangerous.\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(hostConfig.DNS) > 0 {
|
|
||||||
// check the DNS settings passed via --dns against
|
|
||||||
// localhost regexp to warn if they are trying to
|
|
||||||
// set a DNS to a localhost address
|
|
||||||
for _, dnsIP := range hostConfig.DNS {
|
|
||||||
if dns.IsLocalhost(dnsIP) {
|
|
||||||
fmt.Fprintf(stderr, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
config.ArgsEscaped = false
|
|
||||||
|
|
||||||
if !opts.detach {
|
|
||||||
if err := dockerCli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if fl := flags.Lookup("attach"); fl != nil {
|
|
||||||
flAttach = fl.Value.(*opttypes.ListOpts)
|
|
||||||
if flAttach.Len() != 0 {
|
|
||||||
return ErrConflictAttachDetach
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if opts.autoRemove {
|
|
||||||
return ErrConflictDetachAutoRemove
|
|
||||||
}
|
|
||||||
|
|
||||||
config.AttachStdin = false
|
|
||||||
config.AttachStdout = false
|
|
||||||
config.AttachStderr = false
|
|
||||||
config.StdinOnce = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable sigProxy when in TTY mode
|
|
||||||
if config.Tty {
|
|
||||||
opts.sigProxy = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Telling the Windows daemon the initial size of the tty during start makes
|
|
||||||
// a far better user experience rather than relying on subsequent resizes
|
|
||||||
// to cause things to catch up.
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.GetTtySize()
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancelFun := context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
createResponse, err := createContainer(ctx, dockerCli, config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, opts.name)
|
|
||||||
if err != nil {
|
|
||||||
reportError(stderr, cmdPath, err.Error(), true)
|
|
||||||
return runStartContainerErr(err)
|
|
||||||
}
|
|
||||||
if opts.sigProxy {
|
|
||||||
sigc := dockerCli.ForwardAllSignals(ctx, createResponse.ID)
|
|
||||||
defer signal.StopCatch(sigc)
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
waitDisplayID chan struct{}
|
|
||||||
errCh chan error
|
|
||||||
)
|
|
||||||
if !config.AttachStdout && !config.AttachStderr {
|
|
||||||
// Make this asynchronous to allow the client to write to stdin before having to read the ID
|
|
||||||
waitDisplayID = make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
defer close(waitDisplayID)
|
|
||||||
fmt.Fprintf(stdout, "%s\n", createResponse.ID)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
if opts.autoRemove && (hostConfig.RestartPolicy.IsAlways() || hostConfig.RestartPolicy.IsOnFailure()) {
|
|
||||||
return ErrConflictRestartPolicyAndAutoRemove
|
|
||||||
}
|
|
||||||
attach := config.AttachStdin || config.AttachStdout || config.AttachStderr
|
|
||||||
if attach {
|
|
||||||
var (
|
|
||||||
out, cerr io.Writer
|
|
||||||
in io.ReadCloser
|
|
||||||
)
|
|
||||||
if config.AttachStdin {
|
|
||||||
in = stdin
|
|
||||||
}
|
|
||||||
if config.AttachStdout {
|
|
||||||
out = stdout
|
|
||||||
}
|
|
||||||
if config.AttachStderr {
|
|
||||||
if config.Tty {
|
|
||||||
cerr = stdout
|
|
||||||
} else {
|
|
||||||
cerr = stderr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.detachKeys != "" {
|
|
||||||
dockerCli.ConfigFile().DetachKeys = opts.detachKeys
|
|
||||||
}
|
|
||||||
|
|
||||||
options := types.ContainerAttachOptions{
|
|
||||||
Stream: true,
|
|
||||||
Stdin: config.AttachStdin,
|
|
||||||
Stdout: config.AttachStdout,
|
|
||||||
Stderr: config.AttachStderr,
|
|
||||||
DetachKeys: dockerCli.ConfigFile().DetachKeys,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, errAttach := client.ContainerAttach(ctx, createResponse.ID, options)
|
|
||||||
if errAttach != nil && errAttach != httputil.ErrPersistEOF {
|
|
||||||
// ContainerAttach returns an ErrPersistEOF (connection closed)
|
|
||||||
// means server met an error and put it in Hijacked connection
|
|
||||||
// keep the error and read detailed error message from hijacked connection later
|
|
||||||
return errAttach
|
|
||||||
}
|
|
||||||
defer resp.Close()
|
|
||||||
|
|
||||||
errCh = promise.Go(func() error {
|
|
||||||
errHijack := dockerCli.HoldHijackedConnection(ctx, config.Tty, in, out, cerr, resp)
|
|
||||||
if errHijack == nil {
|
|
||||||
return errAttach
|
|
||||||
}
|
|
||||||
return errHijack
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.autoRemove {
|
|
||||||
defer func() {
|
|
||||||
// Explicitly not sharing the context as it could be "Done" (by calling cancelFun)
|
|
||||||
// and thus the container would not be removed.
|
|
||||||
if err := removeContainer(dockerCli, context.Background(), createResponse.ID, true, false, true); err != nil {
|
|
||||||
fmt.Fprintf(stderr, "%v\n", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
//start the container
|
|
||||||
if err := client.ContainerStart(ctx, createResponse.ID, types.ContainerStartOptions{}); err != nil {
|
|
||||||
// If we have holdHijackedConnection, we should notify
|
|
||||||
// holdHijackedConnection we are going to exit and wait
|
|
||||||
// to avoid the terminal are not restored.
|
|
||||||
if attach {
|
|
||||||
cancelFun()
|
|
||||||
<-errCh
|
|
||||||
}
|
|
||||||
|
|
||||||
reportError(stderr, cmdPath, err.Error(), false)
|
|
||||||
return runStartContainerErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && dockerCli.IsTerminalOut() {
|
|
||||||
if err := dockerCli.MonitorTtySize(ctx, createResponse.ID, false); err != nil {
|
|
||||||
fmt.Fprintf(stderr, "Error monitoring TTY size: %s\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if errCh != nil {
|
|
||||||
if err := <-errCh; err != nil {
|
|
||||||
logrus.Debugf("Error hijack: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detached mode: wait for the id to be displayed and return.
|
|
||||||
if !config.AttachStdout && !config.AttachStderr {
|
|
||||||
// Detached mode
|
|
||||||
<-waitDisplayID
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var status int
|
|
||||||
|
|
||||||
// Attached mode
|
|
||||||
if opts.autoRemove {
|
|
||||||
// Autoremove: wait for the container to finish, retrieve
|
|
||||||
// the exit code and remove the container
|
|
||||||
if status, err = client.ContainerWait(ctx, createResponse.ID); err != nil {
|
|
||||||
return runStartContainerErr(err)
|
|
||||||
}
|
|
||||||
if _, status, err = getExitCode(dockerCli, ctx, createResponse.ID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// No Autoremove: Simply retrieve the exit code
|
|
||||||
if !config.Tty && hostConfig.RestartPolicy.IsNone() {
|
|
||||||
// In non-TTY mode, we can't detach, so we must wait for container exit
|
|
||||||
if status, err = client.ContainerWait(ctx, createResponse.ID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// In TTY mode, there is a race: if the process dies too slowly, the state could
|
|
||||||
// be updated after the getExitCode call and result in the wrong exit code being reported
|
|
||||||
if _, status, err = getExitCode(dockerCli, ctx, createResponse.ID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if status != 0 {
|
|
||||||
return cli.StatusError{StatusCode: status}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// reportError is a utility method that prints a user-friendly message
|
|
||||||
// containing the error that occurred during parsing and a suggestion to get help
|
|
||||||
func reportError(stderr io.Writer, name string, str string, withHelp bool) {
|
|
||||||
if withHelp {
|
|
||||||
str += ".\nSee '" + os.Args[0] + " " + name + " --help'"
|
|
||||||
}
|
|
||||||
fmt.Fprintf(stderr, "%s: %s.\n", os.Args[0], str)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if container start fails with 'not found'/'no such' error, return 127
|
|
||||||
// if container start fails with 'permission denied' error, return 126
|
|
||||||
// return 125 for generic docker daemon failures
|
|
||||||
func runStartContainerErr(err error) error {
|
|
||||||
trimmedErr := strings.TrimPrefix(err.Error(), "Error response from daemon: ")
|
|
||||||
statusError := cli.StatusError{StatusCode: 125}
|
|
||||||
if strings.Contains(trimmedErr, "executable file not found") ||
|
|
||||||
strings.Contains(trimmedErr, "no such file or directory") ||
|
|
||||||
strings.Contains(trimmedErr, "system cannot find the file specified") {
|
|
||||||
statusError = cli.StatusError{StatusCode: 127}
|
|
||||||
} else if strings.Contains(trimmedErr, syscall.EACCES.Error()) {
|
|
||||||
statusError = cli.StatusError{StatusCode: 126}
|
|
||||||
}
|
|
||||||
|
|
||||||
return statusError
|
|
||||||
}
|
|
||||||
|
|
@ -1,153 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http/httputil"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/pkg/promise"
|
|
||||||
"github.com/docker/docker/pkg/signal"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type startOptions struct {
|
|
||||||
attach bool
|
|
||||||
openStdin bool
|
|
||||||
detachKeys string
|
|
||||||
|
|
||||||
containers []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewStartCommand creats a new cobra.Command for `docker start`
|
|
||||||
func NewStartCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts startOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "start [OPTIONS] CONTAINER [CONTAINER...]",
|
|
||||||
Short: "Start one or more stopped containers",
|
|
||||||
Args: cli.RequiresMinArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.containers = args
|
|
||||||
return runStart(dockerCli, &opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cmd.SetFlagErrorFunc(flagErrorFunc)
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.BoolVarP(&opts.attach, "attach", "a", false, "Attach STDOUT/STDERR and forward signals")
|
|
||||||
flags.BoolVarP(&opts.openStdin, "interactive", "i", false, "Attach container's STDIN")
|
|
||||||
flags.StringVar(&opts.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container")
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runStart(dockerCli *client.DockerCli, opts *startOptions) error {
|
|
||||||
ctx, cancelFun := context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
if opts.attach || opts.openStdin {
|
|
||||||
// We're going to attach to a container.
|
|
||||||
// 1. Ensure we only have one container.
|
|
||||||
if len(opts.containers) > 1 {
|
|
||||||
return fmt.Errorf("You cannot start and attach multiple containers at once.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Attach to the container.
|
|
||||||
container := opts.containers[0]
|
|
||||||
c, err := dockerCli.Client().ContainerInspect(ctx, container)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// We always use c.ID instead of container to maintain consistency during `docker start`
|
|
||||||
if !c.Config.Tty {
|
|
||||||
sigc := dockerCli.ForwardAllSignals(ctx, c.ID)
|
|
||||||
defer signal.StopCatch(sigc)
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.detachKeys != "" {
|
|
||||||
dockerCli.ConfigFile().DetachKeys = opts.detachKeys
|
|
||||||
}
|
|
||||||
|
|
||||||
options := types.ContainerAttachOptions{
|
|
||||||
Stream: true,
|
|
||||||
Stdin: opts.openStdin && c.Config.OpenStdin,
|
|
||||||
Stdout: true,
|
|
||||||
Stderr: true,
|
|
||||||
DetachKeys: dockerCli.ConfigFile().DetachKeys,
|
|
||||||
}
|
|
||||||
|
|
||||||
var in io.ReadCloser
|
|
||||||
|
|
||||||
if options.Stdin {
|
|
||||||
in = dockerCli.In()
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, errAttach := dockerCli.Client().ContainerAttach(ctx, c.ID, options)
|
|
||||||
if errAttach != nil && errAttach != httputil.ErrPersistEOF {
|
|
||||||
// ContainerAttach return an ErrPersistEOF (connection closed)
|
|
||||||
// means server met an error and put it in Hijacked connection
|
|
||||||
// keep the error and read detailed error message from hijacked connection
|
|
||||||
return errAttach
|
|
||||||
}
|
|
||||||
defer resp.Close()
|
|
||||||
cErr := promise.Go(func() error {
|
|
||||||
errHijack := dockerCli.HoldHijackedConnection(ctx, c.Config.Tty, in, dockerCli.Out(), dockerCli.Err(), resp)
|
|
||||||
if errHijack == nil {
|
|
||||||
return errAttach
|
|
||||||
}
|
|
||||||
return errHijack
|
|
||||||
})
|
|
||||||
|
|
||||||
// 3. Start the container.
|
|
||||||
if err := dockerCli.Client().ContainerStart(ctx, c.ID, types.ContainerStartOptions{}); err != nil {
|
|
||||||
cancelFun()
|
|
||||||
<-cErr
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Wait for attachment to break.
|
|
||||||
if c.Config.Tty && dockerCli.IsTerminalOut() {
|
|
||||||
if err := dockerCli.MonitorTtySize(ctx, c.ID, false); err != nil {
|
|
||||||
fmt.Fprintf(dockerCli.Err(), "Error monitoring TTY size: %s\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if attchErr := <-cErr; attchErr != nil {
|
|
||||||
return attchErr
|
|
||||||
}
|
|
||||||
_, status, err := getExitCode(dockerCli, ctx, c.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if status != 0 {
|
|
||||||
return cli.StatusError{StatusCode: status}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// We're not going to attach to anything.
|
|
||||||
// Start as many containers as we want.
|
|
||||||
return startContainersWithoutAttachments(dockerCli, ctx, opts.containers)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func startContainersWithoutAttachments(dockerCli *client.DockerCli, ctx context.Context, containers []string) error {
|
|
||||||
var failedContainers []string
|
|
||||||
for _, container := range containers {
|
|
||||||
if err := dockerCli.Client().ContainerStart(ctx, container, types.ContainerStartOptions{}); err != nil {
|
|
||||||
fmt.Fprintf(dockerCli.Err(), "%s\n", err)
|
|
||||||
failedContainers = append(failedContainers, container)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "%s\n", container)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(failedContainers) > 0 {
|
|
||||||
return fmt.Errorf("Error: failed to start containers: %v", strings.Join(failedContainers, ", "))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,223 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"text/tabwriter"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/api/client/system"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
"github.com/docker/engine-api/types/events"
|
|
||||||
"github.com/docker/engine-api/types/filters"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type statsOptions struct {
|
|
||||||
all bool
|
|
||||||
noStream bool
|
|
||||||
|
|
||||||
containers []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewStatsCommand creats a new cobra.Command for `docker stats`
|
|
||||||
func NewStatsCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts statsOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "stats [OPTIONS] [CONTAINER...]",
|
|
||||||
Short: "Display a live stream of container(s) resource usage statistics",
|
|
||||||
Args: cli.RequiresMinArgs(0),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.containers = args
|
|
||||||
return runStats(dockerCli, &opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.BoolVarP(&opts.all, "all", "a", false, "Show all containers (default shows just running)")
|
|
||||||
flags.BoolVar(&opts.noStream, "no-stream", false, "Disable streaming stats and only pull the first result")
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// runStats displays a live stream of resource usage statistics for one or more containers.
|
|
||||||
// This shows real-time information on CPU usage, memory usage, and network I/O.
|
|
||||||
func runStats(dockerCli *client.DockerCli, opts *statsOptions) error {
|
|
||||||
showAll := len(opts.containers) == 0
|
|
||||||
closeChan := make(chan error)
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
// monitorContainerEvents watches for container creation and removal (only
|
|
||||||
// used when calling `docker stats` without arguments).
|
|
||||||
monitorContainerEvents := func(started chan<- struct{}, c chan events.Message) {
|
|
||||||
f := filters.NewArgs()
|
|
||||||
f.Add("type", "container")
|
|
||||||
options := types.EventsOptions{
|
|
||||||
Filters: f,
|
|
||||||
}
|
|
||||||
resBody, err := dockerCli.Client().Events(ctx, options)
|
|
||||||
// Whether we successfully subscribed to events or not, we can now
|
|
||||||
// unblock the main goroutine.
|
|
||||||
close(started)
|
|
||||||
if err != nil {
|
|
||||||
closeChan <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer resBody.Close()
|
|
||||||
|
|
||||||
system.DecodeEvents(resBody, func(event events.Message, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
closeChan <- err
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
c <- event
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// waitFirst is a WaitGroup to wait first stat data's reach for each container
|
|
||||||
waitFirst := &sync.WaitGroup{}
|
|
||||||
|
|
||||||
cStats := stats{}
|
|
||||||
// getContainerList simulates creation event for all previously existing
|
|
||||||
// containers (only used when calling `docker stats` without arguments).
|
|
||||||
getContainerList := func() {
|
|
||||||
options := types.ContainerListOptions{
|
|
||||||
All: opts.all,
|
|
||||||
}
|
|
||||||
cs, err := dockerCli.Client().ContainerList(ctx, options)
|
|
||||||
if err != nil {
|
|
||||||
closeChan <- err
|
|
||||||
}
|
|
||||||
for _, container := range cs {
|
|
||||||
s := &containerStats{Name: container.ID[:12]}
|
|
||||||
if cStats.add(s) {
|
|
||||||
waitFirst.Add(1)
|
|
||||||
go s.Collect(ctx, dockerCli.Client(), !opts.noStream, waitFirst)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if showAll {
|
|
||||||
// If no names were specified, start a long running goroutine which
|
|
||||||
// monitors container events. We make sure we're subscribed before
|
|
||||||
// retrieving the list of running containers to avoid a race where we
|
|
||||||
// would "miss" a creation.
|
|
||||||
started := make(chan struct{})
|
|
||||||
eh := system.InitEventHandler()
|
|
||||||
eh.Handle("create", func(e events.Message) {
|
|
||||||
if opts.all {
|
|
||||||
s := &containerStats{Name: e.ID[:12]}
|
|
||||||
if cStats.add(s) {
|
|
||||||
waitFirst.Add(1)
|
|
||||||
go s.Collect(ctx, dockerCli.Client(), !opts.noStream, waitFirst)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
eh.Handle("start", func(e events.Message) {
|
|
||||||
s := &containerStats{Name: e.ID[:12]}
|
|
||||||
if cStats.add(s) {
|
|
||||||
waitFirst.Add(1)
|
|
||||||
go s.Collect(ctx, dockerCli.Client(), !opts.noStream, waitFirst)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
eh.Handle("die", func(e events.Message) {
|
|
||||||
if !opts.all {
|
|
||||||
cStats.remove(e.ID[:12])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
eventChan := make(chan events.Message)
|
|
||||||
go eh.Watch(eventChan)
|
|
||||||
go monitorContainerEvents(started, eventChan)
|
|
||||||
defer close(eventChan)
|
|
||||||
<-started
|
|
||||||
|
|
||||||
// Start a short-lived goroutine to retrieve the initial list of
|
|
||||||
// containers.
|
|
||||||
getContainerList()
|
|
||||||
} else {
|
|
||||||
// Artificially send creation events for the containers we were asked to
|
|
||||||
// monitor (same code path than we use when monitoring all containers).
|
|
||||||
for _, name := range opts.containers {
|
|
||||||
s := &containerStats{Name: name}
|
|
||||||
if cStats.add(s) {
|
|
||||||
waitFirst.Add(1)
|
|
||||||
go s.Collect(ctx, dockerCli.Client(), !opts.noStream, waitFirst)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't expect any asynchronous errors: closeChan can be closed.
|
|
||||||
close(closeChan)
|
|
||||||
|
|
||||||
// Do a quick pause to detect any error with the provided list of
|
|
||||||
// container names.
|
|
||||||
time.Sleep(1500 * time.Millisecond)
|
|
||||||
var errs []string
|
|
||||||
cStats.mu.Lock()
|
|
||||||
for _, c := range cStats.cs {
|
|
||||||
c.mu.Lock()
|
|
||||||
if c.err != nil {
|
|
||||||
errs = append(errs, fmt.Sprintf("%s: %v", c.Name, c.err))
|
|
||||||
}
|
|
||||||
c.mu.Unlock()
|
|
||||||
}
|
|
||||||
cStats.mu.Unlock()
|
|
||||||
if len(errs) > 0 {
|
|
||||||
return fmt.Errorf("%s", strings.Join(errs, ", "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// before print to screen, make sure each container get at least one valid stat data
|
|
||||||
waitFirst.Wait()
|
|
||||||
|
|
||||||
w := tabwriter.NewWriter(dockerCli.Out(), 20, 1, 3, ' ', 0)
|
|
||||||
printHeader := func() {
|
|
||||||
if !opts.noStream {
|
|
||||||
fmt.Fprint(dockerCli.Out(), "\033[2J")
|
|
||||||
fmt.Fprint(dockerCli.Out(), "\033[H")
|
|
||||||
}
|
|
||||||
io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET I/O\tBLOCK I/O\tPIDS\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
for range time.Tick(500 * time.Millisecond) {
|
|
||||||
printHeader()
|
|
||||||
cStats.mu.Lock()
|
|
||||||
for _, s := range cStats.cs {
|
|
||||||
if err := s.Display(w); err != nil && !opts.noStream {
|
|
||||||
logrus.Debugf("stats: got error for %s: %v", s.Name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cStats.mu.Unlock()
|
|
||||||
w.Flush()
|
|
||||||
if opts.noStream {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case err, ok := <-closeChan:
|
|
||||||
if ok {
|
|
||||||
if err != nil {
|
|
||||||
// this is suppressing "unexpected EOF" in the cli when the
|
|
||||||
// daemon restarts so it shutdowns cleanly
|
|
||||||
if err == io.ErrUnexpectedEOF {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// just skip
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,234 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/engine-api/client"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
"github.com/docker/go-units"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
type containerStats struct {
|
|
||||||
Name string
|
|
||||||
CPUPercentage float64
|
|
||||||
Memory float64
|
|
||||||
MemoryLimit float64
|
|
||||||
MemoryPercentage float64
|
|
||||||
NetworkRx float64
|
|
||||||
NetworkTx float64
|
|
||||||
BlockRead float64
|
|
||||||
BlockWrite float64
|
|
||||||
PidsCurrent uint64
|
|
||||||
mu sync.Mutex
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
type stats struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
cs []*containerStats
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stats) add(cs *containerStats) bool {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
if _, exists := s.isKnownContainer(cs.Name); !exists {
|
|
||||||
s.cs = append(s.cs, cs)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stats) remove(id string) {
|
|
||||||
s.mu.Lock()
|
|
||||||
if i, exists := s.isKnownContainer(id); exists {
|
|
||||||
s.cs = append(s.cs[:i], s.cs[i+1:]...)
|
|
||||||
}
|
|
||||||
s.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stats) isKnownContainer(cid string) (int, bool) {
|
|
||||||
for i, c := range s.cs {
|
|
||||||
if c.Name == cid {
|
|
||||||
return i, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *containerStats) Collect(ctx context.Context, cli client.APIClient, streamStats bool, waitFirst *sync.WaitGroup) {
|
|
||||||
logrus.Debugf("collecting stats for %s", s.Name)
|
|
||||||
var (
|
|
||||||
getFirst bool
|
|
||||||
previousCPU uint64
|
|
||||||
previousSystem uint64
|
|
||||||
u = make(chan error, 1)
|
|
||||||
)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
// if error happens and we get nothing of stats, release wait group whatever
|
|
||||||
if !getFirst {
|
|
||||||
getFirst = true
|
|
||||||
waitFirst.Done()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
responseBody, err := cli.ContainerStats(ctx, s.Name, streamStats)
|
|
||||||
if err != nil {
|
|
||||||
s.mu.Lock()
|
|
||||||
s.err = err
|
|
||||||
s.mu.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer responseBody.Close()
|
|
||||||
|
|
||||||
dec := json.NewDecoder(responseBody)
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
var v *types.StatsJSON
|
|
||||||
|
|
||||||
if err := dec.Decode(&v); err != nil {
|
|
||||||
dec = json.NewDecoder(io.MultiReader(dec.Buffered(), responseBody))
|
|
||||||
u <- err
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var memPercent = 0.0
|
|
||||||
var cpuPercent = 0.0
|
|
||||||
|
|
||||||
// MemoryStats.Limit will never be 0 unless the container is not running and we haven't
|
|
||||||
// got any data from cgroup
|
|
||||||
if v.MemoryStats.Limit != 0 {
|
|
||||||
memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0
|
|
||||||
}
|
|
||||||
|
|
||||||
previousCPU = v.PreCPUStats.CPUUsage.TotalUsage
|
|
||||||
previousSystem = v.PreCPUStats.SystemUsage
|
|
||||||
cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v)
|
|
||||||
blkRead, blkWrite := calculateBlockIO(v.BlkioStats)
|
|
||||||
s.mu.Lock()
|
|
||||||
s.CPUPercentage = cpuPercent
|
|
||||||
s.Memory = float64(v.MemoryStats.Usage)
|
|
||||||
s.MemoryLimit = float64(v.MemoryStats.Limit)
|
|
||||||
s.MemoryPercentage = memPercent
|
|
||||||
s.NetworkRx, s.NetworkTx = calculateNetwork(v.Networks)
|
|
||||||
s.BlockRead = float64(blkRead)
|
|
||||||
s.BlockWrite = float64(blkWrite)
|
|
||||||
s.PidsCurrent = v.PidsStats.Current
|
|
||||||
s.mu.Unlock()
|
|
||||||
u <- nil
|
|
||||||
if !streamStats {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-time.After(2 * time.Second):
|
|
||||||
// zero out the values if we have not received an update within
|
|
||||||
// the specified duration.
|
|
||||||
s.mu.Lock()
|
|
||||||
s.CPUPercentage = 0
|
|
||||||
s.Memory = 0
|
|
||||||
s.MemoryPercentage = 0
|
|
||||||
s.MemoryLimit = 0
|
|
||||||
s.NetworkRx = 0
|
|
||||||
s.NetworkTx = 0
|
|
||||||
s.BlockRead = 0
|
|
||||||
s.BlockWrite = 0
|
|
||||||
s.PidsCurrent = 0
|
|
||||||
s.err = errors.New("timeout waiting for stats")
|
|
||||||
s.mu.Unlock()
|
|
||||||
// if this is the first stat you get, release WaitGroup
|
|
||||||
if !getFirst {
|
|
||||||
getFirst = true
|
|
||||||
waitFirst.Done()
|
|
||||||
}
|
|
||||||
case err := <-u:
|
|
||||||
if err != nil {
|
|
||||||
s.mu.Lock()
|
|
||||||
s.err = err
|
|
||||||
s.mu.Unlock()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
s.err = nil
|
|
||||||
// if this is the first stat you get, release WaitGroup
|
|
||||||
if !getFirst {
|
|
||||||
getFirst = true
|
|
||||||
waitFirst.Done()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !streamStats {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *containerStats) Display(w io.Writer) error {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
// NOTE: if you change this format, you must also change the err format below!
|
|
||||||
format := "%s\t%.2f%%\t%s / %s\t%.2f%%\t%s / %s\t%s / %s\t%d\n"
|
|
||||||
if s.err != nil {
|
|
||||||
format = "%s\t%s\t%s / %s\t%s\t%s / %s\t%s / %s\t%s\n"
|
|
||||||
errStr := "--"
|
|
||||||
fmt.Fprintf(w, format,
|
|
||||||
s.Name, errStr, errStr, errStr, errStr, errStr, errStr, errStr, errStr, errStr,
|
|
||||||
)
|
|
||||||
err := s.err
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Fprintf(w, format,
|
|
||||||
s.Name,
|
|
||||||
s.CPUPercentage,
|
|
||||||
units.BytesSize(s.Memory), units.BytesSize(s.MemoryLimit),
|
|
||||||
s.MemoryPercentage,
|
|
||||||
units.HumanSize(s.NetworkRx), units.HumanSize(s.NetworkTx),
|
|
||||||
units.HumanSize(s.BlockRead), units.HumanSize(s.BlockWrite),
|
|
||||||
s.PidsCurrent)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
|
|
||||||
var (
|
|
||||||
cpuPercent = 0.0
|
|
||||||
// calculate the change for the cpu usage of the container in between readings
|
|
||||||
cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU)
|
|
||||||
// calculate the change for the entire system between readings
|
|
||||||
systemDelta = float64(v.CPUStats.SystemUsage) - float64(previousSystem)
|
|
||||||
)
|
|
||||||
|
|
||||||
if systemDelta > 0.0 && cpuDelta > 0.0 {
|
|
||||||
cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0
|
|
||||||
}
|
|
||||||
return cpuPercent
|
|
||||||
}
|
|
||||||
|
|
||||||
func calculateBlockIO(blkio types.BlkioStats) (blkRead uint64, blkWrite uint64) {
|
|
||||||
for _, bioEntry := range blkio.IoServiceBytesRecursive {
|
|
||||||
switch strings.ToLower(bioEntry.Op) {
|
|
||||||
case "read":
|
|
||||||
blkRead = blkRead + bioEntry.Value
|
|
||||||
case "write":
|
|
||||||
blkWrite = blkWrite + bioEntry.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) {
|
|
||||||
var rx, tx float64
|
|
||||||
|
|
||||||
for _, v := range network {
|
|
||||||
rx += float64(v.RxBytes)
|
|
||||||
tx += float64(v.TxBytes)
|
|
||||||
}
|
|
||||||
return rx, tx
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDisplay(t *testing.T) {
|
|
||||||
c := &containerStats{
|
|
||||||
Name: "app",
|
|
||||||
CPUPercentage: 30.0,
|
|
||||||
Memory: 100 * 1024 * 1024.0,
|
|
||||||
MemoryLimit: 2048 * 1024 * 1024.0,
|
|
||||||
MemoryPercentage: 100.0 / 2048.0 * 100.0,
|
|
||||||
NetworkRx: 100 * 1024 * 1024,
|
|
||||||
NetworkTx: 800 * 1024 * 1024,
|
|
||||||
BlockRead: 100 * 1024 * 1024,
|
|
||||||
BlockWrite: 800 * 1024 * 1024,
|
|
||||||
PidsCurrent: 1,
|
|
||||||
}
|
|
||||||
var b bytes.Buffer
|
|
||||||
if err := c.Display(&b); err != nil {
|
|
||||||
t.Fatalf("c.Display() gave error: %s", err)
|
|
||||||
}
|
|
||||||
got := b.String()
|
|
||||||
want := "app\t30.00%\t100 MiB / 2 GiB\t4.88%\t104.9 MB / 838.9 MB\t104.9 MB / 838.9 MB\t1\n"
|
|
||||||
if got != want {
|
|
||||||
t.Fatalf("c.Display() = %q, want %q", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCalculBlockIO(t *testing.T) {
|
|
||||||
blkio := types.BlkioStats{
|
|
||||||
IoServiceBytesRecursive: []types.BlkioStatEntry{{8, 0, "read", 1234}, {8, 1, "read", 4567}, {8, 0, "write", 123}, {8, 1, "write", 456}},
|
|
||||||
}
|
|
||||||
blkRead, blkWrite := calculateBlockIO(blkio)
|
|
||||||
if blkRead != 5801 {
|
|
||||||
t.Fatalf("blkRead = %d, want 5801", blkRead)
|
|
||||||
}
|
|
||||||
if blkWrite != 579 {
|
|
||||||
t.Fatalf("blkWrite = %d, want 579", blkWrite)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stopOptions struct {
|
|
||||||
time int
|
|
||||||
|
|
||||||
containers []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewStopCommand creats a new cobra.Command for `docker stop`
|
|
||||||
func NewStopCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts stopOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "stop [OPTIONS] CONTAINER [CONTAINER...]",
|
|
||||||
Short: "Stop one or more running containers",
|
|
||||||
Args: cli.RequiresMinArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.containers = args
|
|
||||||
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")
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runStop(dockerCli *client.DockerCli, opts *stopOptions) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
var errs []string
|
|
||||||
for _, container := range opts.containers {
|
|
||||||
timeout := time.Duration(opts.time) * time.Second
|
|
||||||
if err := dockerCli.Client().ContainerStop(ctx, container, &timeout); err != nil {
|
|
||||||
errs = append(errs, err.Error())
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "%s\n", container)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(errs) > 0 {
|
|
||||||
return fmt.Errorf("%s", strings.Join(errs, "\n"))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type topOptions struct {
|
|
||||||
container string
|
|
||||||
|
|
||||||
args []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTopCommand creates a new cobra.Command for `docker top`
|
|
||||||
func NewTopCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts topOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "top CONTAINER [ps OPTIONS]",
|
|
||||||
Short: "Display the running processes of a container",
|
|
||||||
Args: cli.RequiresMinArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.container = args[0]
|
|
||||||
opts.args = args[1:]
|
|
||||||
return runTop(dockerCli, &opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cmd.SetFlagErrorFunc(flagErrorFunc)
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.SetInterspersed(false)
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runTop(dockerCli *client.DockerCli, opts *topOptions) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
procList, err := dockerCli.Client().ContainerTop(ctx, opts.container, opts.args)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w := tabwriter.NewWriter(dockerCli.Out(), 20, 1, 3, ' ', 0)
|
|
||||||
fmt.Fprintln(w, strings.Join(procList.Titles, "\t"))
|
|
||||||
|
|
||||||
for _, proc := range procList.Processes {
|
|
||||||
fmt.Fprintln(w, strings.Join(proc, "\t"))
|
|
||||||
}
|
|
||||||
w.Flush()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type unpauseOptions struct {
|
|
||||||
containers []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewUnpauseCommand creats a new cobra.Command for `docker unpause`
|
|
||||||
func NewUnpauseCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts unpauseOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "unpause CONTAINER [CONTAINER...]",
|
|
||||||
Short: "Unpause all processes within one or more containers",
|
|
||||||
Args: cli.RequiresMinArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.containers = args
|
|
||||||
return runUnpause(dockerCli, &opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cmd.SetFlagErrorFunc(flagErrorFunc)
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runUnpause(dockerCli *client.DockerCli, opts *unpauseOptions) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
var errs []string
|
|
||||||
for _, container := range opts.containers {
|
|
||||||
if err := dockerCli.Client().ContainerUnpause(ctx, container); err != nil {
|
|
||||||
errs = append(errs, err.Error())
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "%s\n", container)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(errs) > 0 {
|
|
||||||
return fmt.Errorf("%s", strings.Join(errs, "\n"))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,150 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
runconfigopts "github.com/docker/docker/runconfig/opts"
|
|
||||||
containertypes "github.com/docker/engine-api/types/container"
|
|
||||||
"github.com/docker/go-units"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type updateOptions struct {
|
|
||||||
blkioWeight uint16
|
|
||||||
cpuPeriod int64
|
|
||||||
cpuQuota int64
|
|
||||||
cpusetCpus string
|
|
||||||
cpusetMems string
|
|
||||||
cpuShares int64
|
|
||||||
memoryString string
|
|
||||||
memoryReservation string
|
|
||||||
memorySwap string
|
|
||||||
kernelMemory string
|
|
||||||
restartPolicy string
|
|
||||||
|
|
||||||
nFlag int
|
|
||||||
|
|
||||||
containers []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewUpdateCommand creats a new cobra.Command for `docker update`
|
|
||||||
func NewUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts updateOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "update [OPTIONS] CONTAINER [CONTAINER...]",
|
|
||||||
Short: "Update configuration of one or more containers",
|
|
||||||
Args: cli.RequiresMinArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.containers = args
|
|
||||||
opts.nFlag = cmd.Flags().NFlag()
|
|
||||||
return runUpdate(dockerCli, &opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cmd.SetFlagErrorFunc(flagErrorFunc)
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.Uint16Var(&opts.blkioWeight, "blkio-weight", 0, "Block IO (relative weight), between 10 and 1000")
|
|
||||||
flags.Int64Var(&opts.cpuPeriod, "cpu-period", 0, "Limit CPU CFS (Completely Fair Scheduler) period")
|
|
||||||
flags.Int64Var(&opts.cpuQuota, "cpu-quota", 0, "Limit CPU CFS (Completely Fair Scheduler) quota")
|
|
||||||
flags.StringVar(&opts.cpusetCpus, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)")
|
|
||||||
flags.StringVar(&opts.cpusetMems, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)")
|
|
||||||
flags.Int64VarP(&opts.cpuShares, "cpu-shares", "c", 0, "CPU shares (relative weight)")
|
|
||||||
flags.StringVarP(&opts.memoryString, "memory", "m", "", "Memory limit")
|
|
||||||
flags.StringVar(&opts.memoryReservation, "memory-reservation", "", "Memory soft limit")
|
|
||||||
flags.StringVar(&opts.memorySwap, "memory-swap", "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
|
|
||||||
flags.StringVar(&opts.kernelMemory, "kernel-memory", "", "Kernel memory limit")
|
|
||||||
flags.StringVar(&opts.restartPolicy, "restart", "", "Restart policy to apply when a container exits")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runUpdate(dockerCli *client.DockerCli, opts *updateOptions) error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if opts.nFlag == 0 {
|
|
||||||
return fmt.Errorf("You must provide one or more flags when using this command.")
|
|
||||||
}
|
|
||||||
|
|
||||||
var memory int64
|
|
||||||
if opts.memoryString != "" {
|
|
||||||
memory, err = units.RAMInBytes(opts.memoryString)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var memoryReservation int64
|
|
||||||
if opts.memoryReservation != "" {
|
|
||||||
memoryReservation, err = units.RAMInBytes(opts.memoryReservation)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var memorySwap int64
|
|
||||||
if opts.memorySwap != "" {
|
|
||||||
if opts.memorySwap == "-1" {
|
|
||||||
memorySwap = -1
|
|
||||||
} else {
|
|
||||||
memorySwap, err = units.RAMInBytes(opts.memorySwap)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var kernelMemory int64
|
|
||||||
if opts.kernelMemory != "" {
|
|
||||||
kernelMemory, err = units.RAMInBytes(opts.kernelMemory)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var restartPolicy containertypes.RestartPolicy
|
|
||||||
if opts.restartPolicy != "" {
|
|
||||||
restartPolicy, err = runconfigopts.ParseRestartPolicy(opts.restartPolicy)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resources := containertypes.Resources{
|
|
||||||
BlkioWeight: opts.blkioWeight,
|
|
||||||
CpusetCpus: opts.cpusetCpus,
|
|
||||||
CpusetMems: opts.cpusetMems,
|
|
||||||
CPUShares: opts.cpuShares,
|
|
||||||
Memory: memory,
|
|
||||||
MemoryReservation: memoryReservation,
|
|
||||||
MemorySwap: memorySwap,
|
|
||||||
KernelMemory: kernelMemory,
|
|
||||||
CPUPeriod: opts.cpuPeriod,
|
|
||||||
CPUQuota: opts.cpuQuota,
|
|
||||||
}
|
|
||||||
|
|
||||||
updateConfig := containertypes.UpdateConfig{
|
|
||||||
Resources: resources,
|
|
||||||
RestartPolicy: restartPolicy,
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
var errs []string
|
|
||||||
for _, container := range opts.containers {
|
|
||||||
if err := dockerCli.Client().ContainerUpdate(ctx, container, updateConfig); err != nil {
|
|
||||||
errs = append(errs, err.Error())
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "%s\n", container)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(errs) > 0 {
|
|
||||||
return fmt.Errorf("%s", strings.Join(errs, "\n"))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
clientapi "github.com/docker/engine-api/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
// getExitCode perform an inspect on the container. It returns
|
|
||||||
// the running state and the exit code.
|
|
||||||
func getExitCode(dockerCli *client.DockerCli, ctx context.Context, containerID string) (bool, int, error) {
|
|
||||||
c, err := dockerCli.Client().ContainerInspect(ctx, containerID)
|
|
||||||
if err != nil {
|
|
||||||
// If we can't connect, then the daemon probably died.
|
|
||||||
if err != clientapi.ErrConnectionFailed {
|
|
||||||
return false, -1, err
|
|
||||||
}
|
|
||||||
return false, -1, nil
|
|
||||||
}
|
|
||||||
return c.State.Running, c.State.ExitCode, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type waitOptions struct {
|
|
||||||
containers []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWaitCommand creates a new cobra.Command for `docker wait`
|
|
||||||
func NewWaitCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts waitOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "wait CONTAINER [CONTAINER...]",
|
|
||||||
Short: "Block until a container stops, then print its exit code",
|
|
||||||
Args: cli.RequiresMinArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.containers = args
|
|
||||||
return runWait(dockerCli, &opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cmd.SetFlagErrorFunc(flagErrorFunc)
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runWait(dockerCli *client.DockerCli, opts *waitOptions) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
var errs []string
|
|
||||||
for _, container := range opts.containers {
|
|
||||||
status, err := dockerCli.Client().ContainerWait(ctx, container)
|
|
||||||
if err != nil {
|
|
||||||
errs = append(errs, err.Error())
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "%d\n", status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(errs) > 0 {
|
|
||||||
return fmt.Errorf("%s", strings.Join(errs, "\n"))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/docker/docker/cliconfig/configfile"
|
|
||||||
"github.com/docker/docker/cliconfig/credentials"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetCredentials loads the user credentials from a credentials store.
|
|
||||||
// The store is determined by the config file settings.
|
|
||||||
func GetCredentials(c *configfile.ConfigFile, serverAddress string) (types.AuthConfig, error) {
|
|
||||||
s := LoadCredentialsStore(c)
|
|
||||||
return s.Get(serverAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAllCredentials loads all credentials from a credentials store.
|
|
||||||
// The store is determined by the config file settings.
|
|
||||||
func GetAllCredentials(c *configfile.ConfigFile) (map[string]types.AuthConfig, error) {
|
|
||||||
s := LoadCredentialsStore(c)
|
|
||||||
return s.GetAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreCredentials saves the user credentials in a credentials store.
|
|
||||||
// The store is determined by the config file settings.
|
|
||||||
func StoreCredentials(c *configfile.ConfigFile, auth types.AuthConfig) error {
|
|
||||||
s := LoadCredentialsStore(c)
|
|
||||||
return s.Store(auth)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EraseCredentials removes the user credentials from a credentials store.
|
|
||||||
// The store is determined by the config file settings.
|
|
||||||
func EraseCredentials(c *configfile.ConfigFile, serverAddress string) error {
|
|
||||||
s := LoadCredentialsStore(c)
|
|
||||||
return s.Erase(serverAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadCredentialsStore initializes a new credentials store based
|
|
||||||
// in the settings provided in the configuration file.
|
|
||||||
func LoadCredentialsStore(c *configfile.ConfigFile) credentials.Store {
|
|
||||||
if c.CredentialsStore != "" {
|
|
||||||
return credentials.NewNativeStore(c)
|
|
||||||
}
|
|
||||||
return credentials.NewFileStore(c)
|
|
||||||
}
|
|
||||||
|
|
@ -1,160 +0,0 @@
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
|
||||||
"github.com/docker/docker/pkg/promise"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdExec runs a command in a running container.
|
|
||||||
//
|
|
||||||
// Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
|
|
||||||
func (cli *DockerCli) CmdExec(args ...string) error {
|
|
||||||
cmd := Cli.Subcmd("exec", []string{"CONTAINER COMMAND [ARG...]"}, Cli.DockerCommands["exec"].Description, true)
|
|
||||||
detachKeys := cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container")
|
|
||||||
|
|
||||||
execConfig, err := ParseExec(cmd, args)
|
|
||||||
container := cmd.Arg(0)
|
|
||||||
// just in case the ParseExec does not exit
|
|
||||||
if container == "" || err != nil {
|
|
||||||
return Cli.StatusError{StatusCode: 1}
|
|
||||||
}
|
|
||||||
|
|
||||||
if *detachKeys != "" {
|
|
||||||
cli.configFile.DetachKeys = *detachKeys
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send client escape keys
|
|
||||||
execConfig.DetachKeys = cli.configFile.DetachKeys
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
response, err := cli.client.ContainerExecCreate(ctx, container, *execConfig)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
execID := response.ID
|
|
||||||
if execID == "" {
|
|
||||||
fmt.Fprintf(cli.out, "exec ID empty")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//Temp struct for execStart so that we don't need to transfer all the execConfig
|
|
||||||
if !execConfig.Detach {
|
|
||||||
if err := cli.CheckTtyInput(execConfig.AttachStdin, execConfig.Tty); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
execStartCheck := types.ExecStartCheck{
|
|
||||||
Detach: execConfig.Detach,
|
|
||||||
Tty: execConfig.Tty,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := cli.client.ContainerExecStart(ctx, execID, execStartCheck); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// For now don't print this - wait for when we support exec wait()
|
|
||||||
// fmt.Fprintf(cli.out, "%s\n", execID)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interactive exec requested.
|
|
||||||
var (
|
|
||||||
out, stderr io.Writer
|
|
||||||
in io.ReadCloser
|
|
||||||
errCh chan error
|
|
||||||
)
|
|
||||||
|
|
||||||
if execConfig.AttachStdin {
|
|
||||||
in = cli.in
|
|
||||||
}
|
|
||||||
if execConfig.AttachStdout {
|
|
||||||
out = cli.out
|
|
||||||
}
|
|
||||||
if execConfig.AttachStderr {
|
|
||||||
if execConfig.Tty {
|
|
||||||
stderr = cli.out
|
|
||||||
} else {
|
|
||||||
stderr = cli.err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := cli.client.ContainerExecAttach(ctx, execID, *execConfig)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Close()
|
|
||||||
errCh = promise.Go(func() error {
|
|
||||||
return cli.HoldHijackedConnection(ctx, execConfig.Tty, in, out, stderr, resp)
|
|
||||||
})
|
|
||||||
|
|
||||||
if execConfig.Tty && cli.isTerminalIn {
|
|
||||||
if err := cli.MonitorTtySize(ctx, execID, true); err != nil {
|
|
||||||
fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := <-errCh; err != nil {
|
|
||||||
logrus.Debugf("Error hijack: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var status int
|
|
||||||
if _, status, err = cli.getExecExitCode(ctx, execID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if status != 0 {
|
|
||||||
return Cli.StatusError{StatusCode: status}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseExec parses the specified args for the specified command and generates
|
|
||||||
// an ExecConfig from it.
|
|
||||||
// If the minimal number of specified args is not right or if specified args are
|
|
||||||
// not valid, it will return an error.
|
|
||||||
func ParseExec(cmd *flag.FlagSet, args []string) (*types.ExecConfig, error) {
|
|
||||||
var (
|
|
||||||
flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached")
|
|
||||||
flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY")
|
|
||||||
flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: run command in the background")
|
|
||||||
flUser = cmd.String([]string{"u", "-user"}, "", "Username or UID (format: <name|uid>[:<group|gid>])")
|
|
||||||
flPrivileged = cmd.Bool([]string{"-privileged"}, false, "Give extended privileges to the command")
|
|
||||||
execCmd []string
|
|
||||||
)
|
|
||||||
cmd.Require(flag.Min, 2)
|
|
||||||
if err := cmd.ParseFlags(args, true); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
parsedArgs := cmd.Args()
|
|
||||||
execCmd = parsedArgs[1:]
|
|
||||||
|
|
||||||
execConfig := &types.ExecConfig{
|
|
||||||
User: *flUser,
|
|
||||||
Privileged: *flPrivileged,
|
|
||||||
Tty: *flTty,
|
|
||||||
Cmd: execCmd,
|
|
||||||
Detach: *flDetach,
|
|
||||||
}
|
|
||||||
|
|
||||||
// If -d is not set, attach to everything by default
|
|
||||||
if !*flDetach {
|
|
||||||
execConfig.AttachStdout = true
|
|
||||||
execConfig.AttachStderr = true
|
|
||||||
if *flStdin {
|
|
||||||
execConfig.AttachStdin = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return execConfig, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,122 +0,0 @@
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
type arguments struct {
|
|
||||||
args []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseExec(t *testing.T) {
|
|
||||||
invalids := map[*arguments]error{
|
|
||||||
&arguments{[]string{"-unknown"}}: fmt.Errorf("flag provided but not defined: -unknown"),
|
|
||||||
&arguments{[]string{"-u"}}: fmt.Errorf("flag needs an argument: -u"),
|
|
||||||
&arguments{[]string{"--user"}}: fmt.Errorf("flag needs an argument: --user"),
|
|
||||||
}
|
|
||||||
valids := map[*arguments]*types.ExecConfig{
|
|
||||||
&arguments{
|
|
||||||
[]string{"container", "command"},
|
|
||||||
}: {
|
|
||||||
Cmd: []string{"command"},
|
|
||||||
AttachStdout: true,
|
|
||||||
AttachStderr: true,
|
|
||||||
},
|
|
||||||
&arguments{
|
|
||||||
[]string{"container", "command1", "command2"},
|
|
||||||
}: {
|
|
||||||
Cmd: []string{"command1", "command2"},
|
|
||||||
AttachStdout: true,
|
|
||||||
AttachStderr: true,
|
|
||||||
},
|
|
||||||
&arguments{
|
|
||||||
[]string{"-i", "-t", "-u", "uid", "container", "command"},
|
|
||||||
}: {
|
|
||||||
User: "uid",
|
|
||||||
AttachStdin: true,
|
|
||||||
AttachStdout: true,
|
|
||||||
AttachStderr: true,
|
|
||||||
Tty: true,
|
|
||||||
Cmd: []string{"command"},
|
|
||||||
},
|
|
||||||
&arguments{
|
|
||||||
[]string{"-d", "container", "command"},
|
|
||||||
}: {
|
|
||||||
AttachStdin: false,
|
|
||||||
AttachStdout: false,
|
|
||||||
AttachStderr: false,
|
|
||||||
Detach: true,
|
|
||||||
Cmd: []string{"command"},
|
|
||||||
},
|
|
||||||
&arguments{
|
|
||||||
[]string{"-t", "-i", "-d", "container", "command"},
|
|
||||||
}: {
|
|
||||||
AttachStdin: false,
|
|
||||||
AttachStdout: false,
|
|
||||||
AttachStderr: false,
|
|
||||||
Detach: true,
|
|
||||||
Tty: true,
|
|
||||||
Cmd: []string{"command"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for invalid, expectedError := range invalids {
|
|
||||||
cmd := flag.NewFlagSet("exec", flag.ContinueOnError)
|
|
||||||
cmd.ShortUsage = func() {}
|
|
||||||
cmd.SetOutput(ioutil.Discard)
|
|
||||||
_, err := ParseExec(cmd, invalid.args)
|
|
||||||
if err == nil || err.Error() != expectedError.Error() {
|
|
||||||
t.Fatalf("Expected an error [%v] for %v, got %v", expectedError, invalid, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
for valid, expectedExecConfig := range valids {
|
|
||||||
cmd := flag.NewFlagSet("exec", flag.ContinueOnError)
|
|
||||||
cmd.ShortUsage = func() {}
|
|
||||||
cmd.SetOutput(ioutil.Discard)
|
|
||||||
execConfig, err := ParseExec(cmd, valid.args)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if !compareExecConfig(expectedExecConfig, execConfig) {
|
|
||||||
t.Fatalf("Expected [%v] for %v, got [%v]", expectedExecConfig, valid, execConfig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func compareExecConfig(config1 *types.ExecConfig, config2 *types.ExecConfig) bool {
|
|
||||||
if config1.AttachStderr != config2.AttachStderr {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if config1.AttachStdin != config2.AttachStdin {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if config1.AttachStdout != config2.AttachStdout {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if config1.Detach != config2.Detach {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if config1.Privileged != config2.Privileged {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if config1.Tty != config2.Tty {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if config1.User != config2.User {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if len(config1.Cmd) != len(config2.Cmd) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for index, value := range config1.Cmd {
|
|
||||||
if value != config2.Cmd[index] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
@ -1,243 +0,0 @@
|
||||||
package formatter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api"
|
|
||||||
"github.com/docker/docker/pkg/stringid"
|
|
||||||
"github.com/docker/docker/pkg/stringutils"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
"github.com/docker/go-units"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
tableKey = "table"
|
|
||||||
|
|
||||||
containerIDHeader = "CONTAINER ID"
|
|
||||||
imageHeader = "IMAGE"
|
|
||||||
namesHeader = "NAMES"
|
|
||||||
commandHeader = "COMMAND"
|
|
||||||
createdSinceHeader = "CREATED"
|
|
||||||
createdAtHeader = "CREATED AT"
|
|
||||||
runningForHeader = "CREATED"
|
|
||||||
statusHeader = "STATUS"
|
|
||||||
portsHeader = "PORTS"
|
|
||||||
sizeHeader = "SIZE"
|
|
||||||
labelsHeader = "LABELS"
|
|
||||||
imageIDHeader = "IMAGE ID"
|
|
||||||
repositoryHeader = "REPOSITORY"
|
|
||||||
tagHeader = "TAG"
|
|
||||||
digestHeader = "DIGEST"
|
|
||||||
mountsHeader = "MOUNTS"
|
|
||||||
)
|
|
||||||
|
|
||||||
type containerContext struct {
|
|
||||||
baseSubContext
|
|
||||||
trunc bool
|
|
||||||
c types.Container
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *containerContext) ID() string {
|
|
||||||
c.addHeader(containerIDHeader)
|
|
||||||
if c.trunc {
|
|
||||||
return stringid.TruncateID(c.c.ID)
|
|
||||||
}
|
|
||||||
return c.c.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *containerContext) Names() string {
|
|
||||||
c.addHeader(namesHeader)
|
|
||||||
names := stripNamePrefix(c.c.Names)
|
|
||||||
if c.trunc {
|
|
||||||
for _, name := range names {
|
|
||||||
if len(strings.Split(name, "/")) == 1 {
|
|
||||||
names = []string{name}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strings.Join(names, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *containerContext) Image() string {
|
|
||||||
c.addHeader(imageHeader)
|
|
||||||
if c.c.Image == "" {
|
|
||||||
return "<no image>"
|
|
||||||
}
|
|
||||||
if c.trunc {
|
|
||||||
if trunc := stringid.TruncateID(c.c.ImageID); trunc == stringid.TruncateID(c.c.Image) {
|
|
||||||
return trunc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c.c.Image
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *containerContext) Command() string {
|
|
||||||
c.addHeader(commandHeader)
|
|
||||||
command := c.c.Command
|
|
||||||
if c.trunc {
|
|
||||||
command = stringutils.Truncate(command, 20)
|
|
||||||
}
|
|
||||||
return strconv.Quote(command)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *containerContext) CreatedAt() string {
|
|
||||||
c.addHeader(createdAtHeader)
|
|
||||||
return time.Unix(int64(c.c.Created), 0).String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *containerContext) RunningFor() string {
|
|
||||||
c.addHeader(runningForHeader)
|
|
||||||
createdAt := time.Unix(int64(c.c.Created), 0)
|
|
||||||
return units.HumanDuration(time.Now().UTC().Sub(createdAt))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *containerContext) Ports() string {
|
|
||||||
c.addHeader(portsHeader)
|
|
||||||
return api.DisplayablePorts(c.c.Ports)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *containerContext) Status() string {
|
|
||||||
c.addHeader(statusHeader)
|
|
||||||
return c.c.Status
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *containerContext) Size() string {
|
|
||||||
c.addHeader(sizeHeader)
|
|
||||||
srw := units.HumanSize(float64(c.c.SizeRw))
|
|
||||||
sv := units.HumanSize(float64(c.c.SizeRootFs))
|
|
||||||
|
|
||||||
sf := srw
|
|
||||||
if c.c.SizeRootFs > 0 {
|
|
||||||
sf = fmt.Sprintf("%s (virtual %s)", srw, sv)
|
|
||||||
}
|
|
||||||
return sf
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *containerContext) Labels() string {
|
|
||||||
c.addHeader(labelsHeader)
|
|
||||||
if c.c.Labels == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var joinLabels []string
|
|
||||||
for k, v := range c.c.Labels {
|
|
||||||
joinLabels = append(joinLabels, fmt.Sprintf("%s=%s", k, v))
|
|
||||||
}
|
|
||||||
return strings.Join(joinLabels, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *containerContext) Label(name string) string {
|
|
||||||
n := strings.Split(name, ".")
|
|
||||||
r := strings.NewReplacer("-", " ", "_", " ")
|
|
||||||
h := r.Replace(n[len(n)-1])
|
|
||||||
|
|
||||||
c.addHeader(h)
|
|
||||||
|
|
||||||
if c.c.Labels == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return c.c.Labels[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *containerContext) Mounts() string {
|
|
||||||
c.addHeader(mountsHeader)
|
|
||||||
|
|
||||||
var name string
|
|
||||||
var mounts []string
|
|
||||||
for _, m := range c.c.Mounts {
|
|
||||||
if m.Name == "" {
|
|
||||||
name = m.Source
|
|
||||||
} else {
|
|
||||||
name = m.Name
|
|
||||||
}
|
|
||||||
if c.trunc {
|
|
||||||
name = stringutils.Truncate(name, 15)
|
|
||||||
}
|
|
||||||
mounts = append(mounts, name)
|
|
||||||
}
|
|
||||||
return strings.Join(mounts, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
type imageContext struct {
|
|
||||||
baseSubContext
|
|
||||||
trunc bool
|
|
||||||
i types.Image
|
|
||||||
repo string
|
|
||||||
tag string
|
|
||||||
digest string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *imageContext) ID() string {
|
|
||||||
c.addHeader(imageIDHeader)
|
|
||||||
if c.trunc {
|
|
||||||
return stringid.TruncateID(c.i.ID)
|
|
||||||
}
|
|
||||||
return c.i.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *imageContext) Repository() string {
|
|
||||||
c.addHeader(repositoryHeader)
|
|
||||||
return c.repo
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *imageContext) Tag() string {
|
|
||||||
c.addHeader(tagHeader)
|
|
||||||
return c.tag
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *imageContext) Digest() string {
|
|
||||||
c.addHeader(digestHeader)
|
|
||||||
return c.digest
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *imageContext) CreatedSince() string {
|
|
||||||
c.addHeader(createdSinceHeader)
|
|
||||||
createdAt := time.Unix(int64(c.i.Created), 0)
|
|
||||||
return units.HumanDuration(time.Now().UTC().Sub(createdAt))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *imageContext) CreatedAt() string {
|
|
||||||
c.addHeader(createdAtHeader)
|
|
||||||
return time.Unix(int64(c.i.Created), 0).String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *imageContext) Size() string {
|
|
||||||
c.addHeader(sizeHeader)
|
|
||||||
return units.HumanSize(float64(c.i.Size))
|
|
||||||
}
|
|
||||||
|
|
||||||
type subContext interface {
|
|
||||||
fullHeader() string
|
|
||||||
addHeader(header string)
|
|
||||||
}
|
|
||||||
|
|
||||||
type baseSubContext struct {
|
|
||||||
header []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *baseSubContext) fullHeader() string {
|
|
||||||
if c.header == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return strings.Join(c.header, "\t")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *baseSubContext) addHeader(header string) {
|
|
||||||
if c.header == nil {
|
|
||||||
c.header = []string{}
|
|
||||||
}
|
|
||||||
c.header = append(c.header, strings.ToUpper(header))
|
|
||||||
}
|
|
||||||
|
|
||||||
func stripNamePrefix(ss []string) []string {
|
|
||||||
sss := make([]string, len(ss))
|
|
||||||
for i, s := range ss {
|
|
||||||
sss[i] = s[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return sss
|
|
||||||
}
|
|
||||||
|
|
@ -1,192 +0,0 @@
|
||||||
package formatter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/stringid"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestContainerPsContext(t *testing.T) {
|
|
||||||
containerID := stringid.GenerateRandomID()
|
|
||||||
unix := time.Now().Add(-65 * time.Second).Unix()
|
|
||||||
|
|
||||||
var ctx containerContext
|
|
||||||
cases := []struct {
|
|
||||||
container types.Container
|
|
||||||
trunc bool
|
|
||||||
expValue string
|
|
||||||
expHeader string
|
|
||||||
call func() string
|
|
||||||
}{
|
|
||||||
{types.Container{ID: containerID}, true, stringid.TruncateID(containerID), containerIDHeader, ctx.ID},
|
|
||||||
{types.Container{ID: containerID}, false, containerID, containerIDHeader, ctx.ID},
|
|
||||||
{types.Container{Names: []string{"/foobar_baz"}}, true, "foobar_baz", namesHeader, ctx.Names},
|
|
||||||
{types.Container{Image: "ubuntu"}, true, "ubuntu", imageHeader, ctx.Image},
|
|
||||||
{types.Container{Image: "verylongimagename"}, true, "verylongimagename", imageHeader, ctx.Image},
|
|
||||||
{types.Container{Image: "verylongimagename"}, false, "verylongimagename", imageHeader, ctx.Image},
|
|
||||||
{types.Container{
|
|
||||||
Image: "a5a665ff33eced1e0803148700880edab4",
|
|
||||||
ImageID: "a5a665ff33eced1e0803148700880edab4269067ed77e27737a708d0d293fbf5",
|
|
||||||
},
|
|
||||||
true,
|
|
||||||
"a5a665ff33ec",
|
|
||||||
imageHeader,
|
|
||||||
ctx.Image,
|
|
||||||
},
|
|
||||||
{types.Container{
|
|
||||||
Image: "a5a665ff33eced1e0803148700880edab4",
|
|
||||||
ImageID: "a5a665ff33eced1e0803148700880edab4269067ed77e27737a708d0d293fbf5",
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
"a5a665ff33eced1e0803148700880edab4",
|
|
||||||
imageHeader,
|
|
||||||
ctx.Image,
|
|
||||||
},
|
|
||||||
{types.Container{Image: ""}, true, "<no image>", imageHeader, ctx.Image},
|
|
||||||
{types.Container{Command: "sh -c 'ls -la'"}, true, `"sh -c 'ls -la'"`, commandHeader, ctx.Command},
|
|
||||||
{types.Container{Created: unix}, true, time.Unix(unix, 0).String(), createdAtHeader, ctx.CreatedAt},
|
|
||||||
{types.Container{Ports: []types.Port{{PrivatePort: 8080, PublicPort: 8080, Type: "tcp"}}}, true, "8080/tcp", portsHeader, ctx.Ports},
|
|
||||||
{types.Container{Status: "RUNNING"}, true, "RUNNING", statusHeader, ctx.Status},
|
|
||||||
{types.Container{SizeRw: 10}, true, "10 B", sizeHeader, ctx.Size},
|
|
||||||
{types.Container{SizeRw: 10, SizeRootFs: 20}, true, "10 B (virtual 20 B)", sizeHeader, ctx.Size},
|
|
||||||
{types.Container{}, true, "", labelsHeader, ctx.Labels},
|
|
||||||
{types.Container{Labels: map[string]string{"cpu": "6", "storage": "ssd"}}, true, "cpu=6,storage=ssd", labelsHeader, ctx.Labels},
|
|
||||||
{types.Container{Created: unix}, true, "About a minute", runningForHeader, ctx.RunningFor},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
ctx = containerContext{c: c.container, trunc: c.trunc}
|
|
||||||
v := c.call()
|
|
||||||
if strings.Contains(v, ",") {
|
|
||||||
compareMultipleValues(t, v, c.expValue)
|
|
||||||
} else if v != c.expValue {
|
|
||||||
t.Fatalf("Expected %s, was %s\n", c.expValue, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
h := ctx.fullHeader()
|
|
||||||
if h != c.expHeader {
|
|
||||||
t.Fatalf("Expected %s, was %s\n", c.expHeader, h)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c1 := types.Container{Labels: map[string]string{"com.docker.swarm.swarm-id": "33", "com.docker.swarm.node_name": "ubuntu"}}
|
|
||||||
ctx = containerContext{c: c1, trunc: true}
|
|
||||||
|
|
||||||
sid := ctx.Label("com.docker.swarm.swarm-id")
|
|
||||||
node := ctx.Label("com.docker.swarm.node_name")
|
|
||||||
if sid != "33" {
|
|
||||||
t.Fatalf("Expected 33, was %s\n", sid)
|
|
||||||
}
|
|
||||||
|
|
||||||
if node != "ubuntu" {
|
|
||||||
t.Fatalf("Expected ubuntu, was %s\n", node)
|
|
||||||
}
|
|
||||||
|
|
||||||
h := ctx.fullHeader()
|
|
||||||
if h != "SWARM ID\tNODE NAME" {
|
|
||||||
t.Fatalf("Expected %s, was %s\n", "SWARM ID\tNODE NAME", h)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
c2 := types.Container{}
|
|
||||||
ctx = containerContext{c: c2, trunc: true}
|
|
||||||
|
|
||||||
label := ctx.Label("anything.really")
|
|
||||||
if label != "" {
|
|
||||||
t.Fatalf("Expected an empty string, was %s", label)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx = containerContext{c: c2, trunc: true}
|
|
||||||
fullHeader := ctx.fullHeader()
|
|
||||||
if fullHeader != "" {
|
|
||||||
t.Fatalf("Expected fullHeader to be empty, was %s", fullHeader)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestImagesContext(t *testing.T) {
|
|
||||||
imageID := stringid.GenerateRandomID()
|
|
||||||
unix := time.Now().Unix()
|
|
||||||
|
|
||||||
var ctx imageContext
|
|
||||||
cases := []struct {
|
|
||||||
imageCtx imageContext
|
|
||||||
expValue string
|
|
||||||
expHeader string
|
|
||||||
call func() string
|
|
||||||
}{
|
|
||||||
{imageContext{
|
|
||||||
i: types.Image{ID: imageID},
|
|
||||||
trunc: true,
|
|
||||||
}, stringid.TruncateID(imageID), imageIDHeader, ctx.ID},
|
|
||||||
{imageContext{
|
|
||||||
i: types.Image{ID: imageID},
|
|
||||||
trunc: false,
|
|
||||||
}, imageID, imageIDHeader, ctx.ID},
|
|
||||||
{imageContext{
|
|
||||||
i: types.Image{Size: 10},
|
|
||||||
trunc: true,
|
|
||||||
}, "10 B", sizeHeader, ctx.Size},
|
|
||||||
{imageContext{
|
|
||||||
i: types.Image{Created: unix},
|
|
||||||
trunc: true,
|
|
||||||
}, time.Unix(unix, 0).String(), createdAtHeader, ctx.CreatedAt},
|
|
||||||
// FIXME
|
|
||||||
// {imageContext{
|
|
||||||
// i: types.Image{Created: unix},
|
|
||||||
// trunc: true,
|
|
||||||
// }, units.HumanDuration(time.Unix(unix, 0)), createdSinceHeader, ctx.CreatedSince},
|
|
||||||
{imageContext{
|
|
||||||
i: types.Image{},
|
|
||||||
repo: "busybox",
|
|
||||||
}, "busybox", repositoryHeader, ctx.Repository},
|
|
||||||
{imageContext{
|
|
||||||
i: types.Image{},
|
|
||||||
tag: "latest",
|
|
||||||
}, "latest", tagHeader, ctx.Tag},
|
|
||||||
{imageContext{
|
|
||||||
i: types.Image{},
|
|
||||||
digest: "sha256:d149ab53f8718e987c3a3024bb8aa0e2caadf6c0328f1d9d850b2a2a67f2819a",
|
|
||||||
}, "sha256:d149ab53f8718e987c3a3024bb8aa0e2caadf6c0328f1d9d850b2a2a67f2819a", digestHeader, ctx.Digest},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
ctx = c.imageCtx
|
|
||||||
v := c.call()
|
|
||||||
if strings.Contains(v, ",") {
|
|
||||||
compareMultipleValues(t, v, c.expValue)
|
|
||||||
} else if v != c.expValue {
|
|
||||||
t.Fatalf("Expected %s, was %s\n", c.expValue, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
h := ctx.fullHeader()
|
|
||||||
if h != c.expHeader {
|
|
||||||
t.Fatalf("Expected %s, was %s\n", c.expHeader, h)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func compareMultipleValues(t *testing.T, value, expected string) {
|
|
||||||
// comma-separated values means probably a map input, which won't
|
|
||||||
// be guaranteed to have the same order as our expected value
|
|
||||||
// We'll create maps and use reflect.DeepEquals to check instead:
|
|
||||||
entriesMap := make(map[string]string)
|
|
||||||
expMap := make(map[string]string)
|
|
||||||
entries := strings.Split(value, ",")
|
|
||||||
expectedEntries := strings.Split(expected, ",")
|
|
||||||
for _, entry := range entries {
|
|
||||||
keyval := strings.Split(entry, "=")
|
|
||||||
entriesMap[keyval[0]] = keyval[1]
|
|
||||||
}
|
|
||||||
for _, expected := range expectedEntries {
|
|
||||||
keyval := strings.Split(expected, "=")
|
|
||||||
expMap[keyval[0]] = keyval[1]
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(expMap, entriesMap) {
|
|
||||||
t.Fatalf("Expected entries: %v, got: %v", expected, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,307 +0,0 @@
|
||||||
package formatter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
"text/tabwriter"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/docker/docker/reference"
|
|
||||||
"github.com/docker/docker/utils/templates"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
tableFormatKey = "table"
|
|
||||||
rawFormatKey = "raw"
|
|
||||||
|
|
||||||
defaultContainerTableFormat = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.RunningFor}} ago\t{{.Status}}\t{{.Ports}}\t{{.Names}}"
|
|
||||||
defaultImageTableFormat = "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedSince}} ago\t{{.Size}}"
|
|
||||||
defaultImageTableFormatWithDigest = "table {{.Repository}}\t{{.Tag}}\t{{.Digest}}\t{{.ID}}\t{{.CreatedSince}} ago\t{{.Size}}"
|
|
||||||
defaultQuietFormat = "{{.ID}}"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Context contains information required by the formatter to print the output as desired.
|
|
||||||
type Context struct {
|
|
||||||
// Output is the output stream to which the formatted string is written.
|
|
||||||
Output io.Writer
|
|
||||||
// Format is used to choose raw, table or custom format for the output.
|
|
||||||
Format string
|
|
||||||
// Quiet when set to true will simply print minimal information.
|
|
||||||
Quiet bool
|
|
||||||
// Trunc when set to true will truncate the output of certain fields such as Container ID.
|
|
||||||
Trunc bool
|
|
||||||
|
|
||||||
// internal element
|
|
||||||
table bool
|
|
||||||
finalFormat string
|
|
||||||
header string
|
|
||||||
buffer *bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Context) preformat() {
|
|
||||||
c.finalFormat = c.Format
|
|
||||||
|
|
||||||
if strings.HasPrefix(c.Format, tableKey) {
|
|
||||||
c.table = true
|
|
||||||
c.finalFormat = c.finalFormat[len(tableKey):]
|
|
||||||
}
|
|
||||||
|
|
||||||
c.finalFormat = strings.Trim(c.finalFormat, " ")
|
|
||||||
r := strings.NewReplacer(`\t`, "\t", `\n`, "\n")
|
|
||||||
c.finalFormat = r.Replace(c.finalFormat)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Context) parseFormat() (*template.Template, error) {
|
|
||||||
tmpl, err := templates.Parse(c.finalFormat)
|
|
||||||
if err != nil {
|
|
||||||
c.buffer.WriteString(fmt.Sprintf("Template parsing error: %v\n", err))
|
|
||||||
c.buffer.WriteTo(c.Output)
|
|
||||||
}
|
|
||||||
return tmpl, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Context) postformat(tmpl *template.Template, subContext subContext) {
|
|
||||||
if c.table {
|
|
||||||
if len(c.header) == 0 {
|
|
||||||
// if we still don't have a header, we didn't have any containers so we need to fake it to get the right headers from the template
|
|
||||||
tmpl.Execute(bytes.NewBufferString(""), subContext)
|
|
||||||
c.header = subContext.fullHeader()
|
|
||||||
}
|
|
||||||
|
|
||||||
t := tabwriter.NewWriter(c.Output, 20, 1, 3, ' ', 0)
|
|
||||||
t.Write([]byte(c.header))
|
|
||||||
t.Write([]byte("\n"))
|
|
||||||
c.buffer.WriteTo(t)
|
|
||||||
t.Flush()
|
|
||||||
} else {
|
|
||||||
c.buffer.WriteTo(c.Output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Context) contextFormat(tmpl *template.Template, subContext subContext) error {
|
|
||||||
if err := tmpl.Execute(c.buffer, subContext); err != nil {
|
|
||||||
c.buffer = bytes.NewBufferString(fmt.Sprintf("Template parsing error: %v\n", err))
|
|
||||||
c.buffer.WriteTo(c.Output)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if c.table && len(c.header) == 0 {
|
|
||||||
c.header = subContext.fullHeader()
|
|
||||||
}
|
|
||||||
c.buffer.WriteString("\n")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContainerContext contains container specific information required by the formater, encapsulate a Context struct.
|
|
||||||
type ContainerContext struct {
|
|
||||||
Context
|
|
||||||
// Size when set to true will display the size of the output.
|
|
||||||
Size bool
|
|
||||||
// Containers
|
|
||||||
Containers []types.Container
|
|
||||||
}
|
|
||||||
|
|
||||||
// ImageContext contains image specific information required by the formater, encapsulate a Context struct.
|
|
||||||
type ImageContext struct {
|
|
||||||
Context
|
|
||||||
Digest bool
|
|
||||||
// Images
|
|
||||||
Images []types.Image
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx ContainerContext) Write() {
|
|
||||||
switch ctx.Format {
|
|
||||||
case tableFormatKey:
|
|
||||||
if ctx.Quiet {
|
|
||||||
ctx.Format = defaultQuietFormat
|
|
||||||
} else {
|
|
||||||
ctx.Format = defaultContainerTableFormat
|
|
||||||
if ctx.Size {
|
|
||||||
ctx.Format += `\t{{.Size}}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case rawFormatKey:
|
|
||||||
if ctx.Quiet {
|
|
||||||
ctx.Format = `container_id: {{.ID}}`
|
|
||||||
} else {
|
|
||||||
ctx.Format = `container_id: {{.ID}}\nimage: {{.Image}}\ncommand: {{.Command}}\ncreated_at: {{.CreatedAt}}\nstatus: {{.Status}}\nnames: {{.Names}}\nlabels: {{.Labels}}\nports: {{.Ports}}\n`
|
|
||||||
if ctx.Size {
|
|
||||||
ctx.Format += `size: {{.Size}}\n`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.buffer = bytes.NewBufferString("")
|
|
||||||
ctx.preformat()
|
|
||||||
|
|
||||||
tmpl, err := ctx.parseFormat()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, container := range ctx.Containers {
|
|
||||||
containerCtx := &containerContext{
|
|
||||||
trunc: ctx.Trunc,
|
|
||||||
c: container,
|
|
||||||
}
|
|
||||||
err = ctx.contextFormat(tmpl, containerCtx)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.postformat(tmpl, &containerContext{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func isDangling(image types.Image) bool {
|
|
||||||
return len(image.RepoTags) == 1 && image.RepoTags[0] == "<none>:<none>" && len(image.RepoDigests) == 1 && image.RepoDigests[0] == "<none>@<none>"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx ImageContext) Write() {
|
|
||||||
switch ctx.Format {
|
|
||||||
case tableFormatKey:
|
|
||||||
ctx.Format = defaultImageTableFormat
|
|
||||||
if ctx.Digest {
|
|
||||||
ctx.Format = defaultImageTableFormatWithDigest
|
|
||||||
}
|
|
||||||
if ctx.Quiet {
|
|
||||||
ctx.Format = defaultQuietFormat
|
|
||||||
}
|
|
||||||
case rawFormatKey:
|
|
||||||
if ctx.Quiet {
|
|
||||||
ctx.Format = `image_id: {{.ID}}`
|
|
||||||
} else {
|
|
||||||
if ctx.Digest {
|
|
||||||
ctx.Format = `repository: {{ .Repository }}
|
|
||||||
tag: {{.Tag}}
|
|
||||||
digest: {{.Digest}}
|
|
||||||
image_id: {{.ID}}
|
|
||||||
created_at: {{.CreatedAt}}
|
|
||||||
virtual_size: {{.Size}}
|
|
||||||
`
|
|
||||||
} else {
|
|
||||||
ctx.Format = `repository: {{ .Repository }}
|
|
||||||
tag: {{.Tag}}
|
|
||||||
image_id: {{.ID}}
|
|
||||||
created_at: {{.CreatedAt}}
|
|
||||||
virtual_size: {{.Size}}
|
|
||||||
`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.buffer = bytes.NewBufferString("")
|
|
||||||
ctx.preformat()
|
|
||||||
if ctx.table && ctx.Digest && !strings.Contains(ctx.Format, "{{.Digest}}") {
|
|
||||||
ctx.finalFormat += "\t{{.Digest}}"
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpl, err := ctx.parseFormat()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, image := range ctx.Images {
|
|
||||||
images := []*imageContext{}
|
|
||||||
if isDangling(image) {
|
|
||||||
images = append(images, &imageContext{
|
|
||||||
trunc: ctx.Trunc,
|
|
||||||
i: image,
|
|
||||||
repo: "<none>",
|
|
||||||
tag: "<none>",
|
|
||||||
digest: "<none>",
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
repoTags := map[string][]string{}
|
|
||||||
repoDigests := map[string][]string{}
|
|
||||||
|
|
||||||
for _, refString := range append(image.RepoTags) {
|
|
||||||
ref, err := reference.ParseNamed(refString)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if nt, ok := ref.(reference.NamedTagged); ok {
|
|
||||||
repoTags[ref.Name()] = append(repoTags[ref.Name()], nt.Tag())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, refString := range append(image.RepoDigests) {
|
|
||||||
ref, err := reference.ParseNamed(refString)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if c, ok := ref.(reference.Canonical); ok {
|
|
||||||
repoDigests[ref.Name()] = append(repoDigests[ref.Name()], c.Digest().String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for repo, tags := range repoTags {
|
|
||||||
digests := repoDigests[repo]
|
|
||||||
|
|
||||||
// Do not display digests as their own row
|
|
||||||
delete(repoDigests, repo)
|
|
||||||
|
|
||||||
if !ctx.Digest {
|
|
||||||
// Ignore digest references, just show tag once
|
|
||||||
digests = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tag := range tags {
|
|
||||||
if len(digests) == 0 {
|
|
||||||
images = append(images, &imageContext{
|
|
||||||
trunc: ctx.Trunc,
|
|
||||||
i: image,
|
|
||||||
repo: repo,
|
|
||||||
tag: tag,
|
|
||||||
digest: "<none>",
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Display the digests for each tag
|
|
||||||
for _, dgst := range digests {
|
|
||||||
images = append(images, &imageContext{
|
|
||||||
trunc: ctx.Trunc,
|
|
||||||
i: image,
|
|
||||||
repo: repo,
|
|
||||||
tag: tag,
|
|
||||||
digest: dgst,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show rows for remaining digest only references
|
|
||||||
for repo, digests := range repoDigests {
|
|
||||||
// If digests are displayed, show row per digest
|
|
||||||
if ctx.Digest {
|
|
||||||
for _, dgst := range digests {
|
|
||||||
images = append(images, &imageContext{
|
|
||||||
trunc: ctx.Trunc,
|
|
||||||
i: image,
|
|
||||||
repo: repo,
|
|
||||||
tag: "<none>",
|
|
||||||
digest: dgst,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
images = append(images, &imageContext{
|
|
||||||
trunc: ctx.Trunc,
|
|
||||||
i: image,
|
|
||||||
repo: repo,
|
|
||||||
tag: "<none>",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, imageCtx := range images {
|
|
||||||
err = ctx.contextFormat(tmpl, imageCtx)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.postformat(tmpl, &imageContext{})
|
|
||||||
}
|
|
||||||
|
|
@ -1,537 +0,0 @@
|
||||||
package formatter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestContainerContextWrite(t *testing.T) {
|
|
||||||
unixTime := time.Now().AddDate(0, 0, -1).Unix()
|
|
||||||
expectedTime := time.Unix(unixTime, 0).String()
|
|
||||||
|
|
||||||
contexts := []struct {
|
|
||||||
context ContainerContext
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
// Errors
|
|
||||||
{
|
|
||||||
ContainerContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "{{InvalidFunction}}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`Template parsing error: template: :1: function "InvalidFunction" not defined
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ContainerContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "{{nil}}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
// Table Format
|
|
||||||
{
|
|
||||||
ContainerContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "table",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
|
||||||
containerID1 ubuntu "" 24 hours ago foobar_baz
|
|
||||||
containerID2 ubuntu "" 24 hours ago foobar_bar
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ContainerContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "table {{.Image}}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"IMAGE\nubuntu\nubuntu\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ContainerContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "table {{.Image}}",
|
|
||||||
},
|
|
||||||
Size: true,
|
|
||||||
},
|
|
||||||
"IMAGE\nubuntu\nubuntu\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ContainerContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "table {{.Image}}",
|
|
||||||
Quiet: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"IMAGE\nubuntu\nubuntu\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ContainerContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "table",
|
|
||||||
Quiet: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"containerID1\ncontainerID2\n",
|
|
||||||
},
|
|
||||||
// Raw Format
|
|
||||||
{
|
|
||||||
ContainerContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "raw",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fmt.Sprintf(`container_id: containerID1
|
|
||||||
image: ubuntu
|
|
||||||
command: ""
|
|
||||||
created_at: %s
|
|
||||||
status:
|
|
||||||
names: foobar_baz
|
|
||||||
labels:
|
|
||||||
ports:
|
|
||||||
|
|
||||||
container_id: containerID2
|
|
||||||
image: ubuntu
|
|
||||||
command: ""
|
|
||||||
created_at: %s
|
|
||||||
status:
|
|
||||||
names: foobar_bar
|
|
||||||
labels:
|
|
||||||
ports:
|
|
||||||
|
|
||||||
`, expectedTime, expectedTime),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ContainerContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "raw",
|
|
||||||
},
|
|
||||||
Size: true,
|
|
||||||
},
|
|
||||||
fmt.Sprintf(`container_id: containerID1
|
|
||||||
image: ubuntu
|
|
||||||
command: ""
|
|
||||||
created_at: %s
|
|
||||||
status:
|
|
||||||
names: foobar_baz
|
|
||||||
labels:
|
|
||||||
ports:
|
|
||||||
size: 0 B
|
|
||||||
|
|
||||||
container_id: containerID2
|
|
||||||
image: ubuntu
|
|
||||||
command: ""
|
|
||||||
created_at: %s
|
|
||||||
status:
|
|
||||||
names: foobar_bar
|
|
||||||
labels:
|
|
||||||
ports:
|
|
||||||
size: 0 B
|
|
||||||
|
|
||||||
`, expectedTime, expectedTime),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ContainerContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "raw",
|
|
||||||
Quiet: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"container_id: containerID1\ncontainer_id: containerID2\n",
|
|
||||||
},
|
|
||||||
// Custom Format
|
|
||||||
{
|
|
||||||
ContainerContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "{{.Image}}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"ubuntu\nubuntu\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ContainerContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "{{.Image}}",
|
|
||||||
},
|
|
||||||
Size: true,
|
|
||||||
},
|
|
||||||
"ubuntu\nubuntu\n",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, context := range contexts {
|
|
||||||
containers := []types.Container{
|
|
||||||
{ID: "containerID1", Names: []string{"/foobar_baz"}, Image: "ubuntu", Created: unixTime},
|
|
||||||
{ID: "containerID2", Names: []string{"/foobar_bar"}, Image: "ubuntu", Created: unixTime},
|
|
||||||
}
|
|
||||||
out := bytes.NewBufferString("")
|
|
||||||
context.context.Output = out
|
|
||||||
context.context.Containers = containers
|
|
||||||
context.context.Write()
|
|
||||||
actual := out.String()
|
|
||||||
if actual != context.expected {
|
|
||||||
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
|
|
||||||
}
|
|
||||||
// Clean buffer
|
|
||||||
out.Reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestContainerContextWriteWithNoContainers(t *testing.T) {
|
|
||||||
out := bytes.NewBufferString("")
|
|
||||||
containers := []types.Container{}
|
|
||||||
|
|
||||||
contexts := []struct {
|
|
||||||
context ContainerContext
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
ContainerContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "{{.Image}}",
|
|
||||||
Output: out,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ContainerContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "table {{.Image}}",
|
|
||||||
Output: out,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"IMAGE\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ContainerContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "{{.Image}}",
|
|
||||||
Output: out,
|
|
||||||
},
|
|
||||||
Size: true,
|
|
||||||
},
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ContainerContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "table {{.Image}}",
|
|
||||||
Output: out,
|
|
||||||
},
|
|
||||||
Size: true,
|
|
||||||
},
|
|
||||||
"IMAGE\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ContainerContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "table {{.Image}}\t{{.Size}}",
|
|
||||||
Output: out,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"IMAGE SIZE\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ContainerContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "table {{.Image}}\t{{.Size}}",
|
|
||||||
Output: out,
|
|
||||||
},
|
|
||||||
Size: true,
|
|
||||||
},
|
|
||||||
"IMAGE SIZE\n",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, context := range contexts {
|
|
||||||
context.context.Containers = containers
|
|
||||||
context.context.Write()
|
|
||||||
actual := out.String()
|
|
||||||
if actual != context.expected {
|
|
||||||
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
|
|
||||||
}
|
|
||||||
// Clean buffer
|
|
||||||
out.Reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestImageContextWrite(t *testing.T) {
|
|
||||||
unixTime := time.Now().AddDate(0, 0, -1).Unix()
|
|
||||||
expectedTime := time.Unix(unixTime, 0).String()
|
|
||||||
|
|
||||||
contexts := []struct {
|
|
||||||
context ImageContext
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
// Errors
|
|
||||||
{
|
|
||||||
ImageContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "{{InvalidFunction}}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`Template parsing error: template: :1: function "InvalidFunction" not defined
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ImageContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "{{nil}}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
// Table Format
|
|
||||||
{
|
|
||||||
ImageContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "table",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`REPOSITORY TAG IMAGE ID CREATED SIZE
|
|
||||||
image tag1 imageID1 24 hours ago 0 B
|
|
||||||
image tag2 imageID2 24 hours ago 0 B
|
|
||||||
<none> <none> imageID3 24 hours ago 0 B
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ImageContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "table {{.Repository}}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"REPOSITORY\nimage\nimage\n<none>\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ImageContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "table {{.Repository}}",
|
|
||||||
},
|
|
||||||
Digest: true,
|
|
||||||
},
|
|
||||||
`REPOSITORY DIGEST
|
|
||||||
image sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
|
|
||||||
image <none>
|
|
||||||
<none> <none>
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ImageContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "table {{.Repository}}",
|
|
||||||
Quiet: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"REPOSITORY\nimage\nimage\n<none>\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ImageContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "table",
|
|
||||||
Quiet: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"imageID1\nimageID2\nimageID3\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ImageContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "table",
|
|
||||||
Quiet: false,
|
|
||||||
},
|
|
||||||
Digest: true,
|
|
||||||
},
|
|
||||||
`REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE
|
|
||||||
image tag1 sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf imageID1 24 hours ago 0 B
|
|
||||||
image tag2 <none> imageID2 24 hours ago 0 B
|
|
||||||
<none> <none> <none> imageID3 24 hours ago 0 B
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ImageContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "table",
|
|
||||||
Quiet: true,
|
|
||||||
},
|
|
||||||
Digest: true,
|
|
||||||
},
|
|
||||||
"imageID1\nimageID2\nimageID3\n",
|
|
||||||
},
|
|
||||||
// Raw Format
|
|
||||||
{
|
|
||||||
ImageContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "raw",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fmt.Sprintf(`repository: image
|
|
||||||
tag: tag1
|
|
||||||
image_id: imageID1
|
|
||||||
created_at: %s
|
|
||||||
virtual_size: 0 B
|
|
||||||
|
|
||||||
repository: image
|
|
||||||
tag: tag2
|
|
||||||
image_id: imageID2
|
|
||||||
created_at: %s
|
|
||||||
virtual_size: 0 B
|
|
||||||
|
|
||||||
repository: <none>
|
|
||||||
tag: <none>
|
|
||||||
image_id: imageID3
|
|
||||||
created_at: %s
|
|
||||||
virtual_size: 0 B
|
|
||||||
|
|
||||||
`, expectedTime, expectedTime, expectedTime),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ImageContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "raw",
|
|
||||||
},
|
|
||||||
Digest: true,
|
|
||||||
},
|
|
||||||
fmt.Sprintf(`repository: image
|
|
||||||
tag: tag1
|
|
||||||
digest: sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
|
|
||||||
image_id: imageID1
|
|
||||||
created_at: %s
|
|
||||||
virtual_size: 0 B
|
|
||||||
|
|
||||||
repository: image
|
|
||||||
tag: tag2
|
|
||||||
digest: <none>
|
|
||||||
image_id: imageID2
|
|
||||||
created_at: %s
|
|
||||||
virtual_size: 0 B
|
|
||||||
|
|
||||||
repository: <none>
|
|
||||||
tag: <none>
|
|
||||||
digest: <none>
|
|
||||||
image_id: imageID3
|
|
||||||
created_at: %s
|
|
||||||
virtual_size: 0 B
|
|
||||||
|
|
||||||
`, expectedTime, expectedTime, expectedTime),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ImageContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "raw",
|
|
||||||
Quiet: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`image_id: imageID1
|
|
||||||
image_id: imageID2
|
|
||||||
image_id: imageID3
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
// Custom Format
|
|
||||||
{
|
|
||||||
ImageContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "{{.Repository}}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"image\nimage\n<none>\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ImageContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "{{.Repository}}",
|
|
||||||
},
|
|
||||||
Digest: true,
|
|
||||||
},
|
|
||||||
"image\nimage\n<none>\n",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, context := range contexts {
|
|
||||||
images := []types.Image{
|
|
||||||
{ID: "imageID1", RepoTags: []string{"image:tag1"}, RepoDigests: []string{"image@sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf"}, Created: unixTime},
|
|
||||||
{ID: "imageID2", RepoTags: []string{"image:tag2"}, Created: unixTime},
|
|
||||||
{ID: "imageID3", RepoTags: []string{"<none>:<none>"}, RepoDigests: []string{"<none>@<none>"}, Created: unixTime},
|
|
||||||
}
|
|
||||||
out := bytes.NewBufferString("")
|
|
||||||
context.context.Output = out
|
|
||||||
context.context.Images = images
|
|
||||||
context.context.Write()
|
|
||||||
actual := out.String()
|
|
||||||
if actual != context.expected {
|
|
||||||
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
|
|
||||||
}
|
|
||||||
// Clean buffer
|
|
||||||
out.Reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestImageContextWriteWithNoImage(t *testing.T) {
|
|
||||||
out := bytes.NewBufferString("")
|
|
||||||
images := []types.Image{}
|
|
||||||
|
|
||||||
contexts := []struct {
|
|
||||||
context ImageContext
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
ImageContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "{{.Repository}}",
|
|
||||||
Output: out,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ImageContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "table {{.Repository}}",
|
|
||||||
Output: out,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"REPOSITORY\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ImageContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "{{.Repository}}",
|
|
||||||
Output: out,
|
|
||||||
},
|
|
||||||
Digest: true,
|
|
||||||
},
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ImageContext{
|
|
||||||
Context: Context{
|
|
||||||
Format: "table {{.Repository}}",
|
|
||||||
Output: out,
|
|
||||||
},
|
|
||||||
Digest: true,
|
|
||||||
},
|
|
||||||
"REPOSITORY DIGEST\n",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, context := range contexts {
|
|
||||||
context.context.Images = images
|
|
||||||
context.context.Write()
|
|
||||||
actual := out.String()
|
|
||||||
if actual != context.expected {
|
|
||||||
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
|
|
||||||
}
|
|
||||||
// Clean buffer
|
|
||||||
out.Reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HoldHijackedConnection handles copying input to and output from streams to the
|
|
||||||
// connection
|
|
||||||
func (cli *DockerCli) HoldHijackedConnection(ctx context.Context, tty bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
restoreOnce sync.Once
|
|
||||||
)
|
|
||||||
if inputStream != nil && tty {
|
|
||||||
if err := cli.setRawTerminal(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
restoreOnce.Do(func() {
|
|
||||||
cli.restoreTerminal(inputStream)
|
|
||||||
})
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
receiveStdout := make(chan error, 1)
|
|
||||||
if outputStream != nil || errorStream != nil {
|
|
||||||
go func() {
|
|
||||||
// When TTY is ON, use regular copy
|
|
||||||
if tty && outputStream != nil {
|
|
||||||
_, err = io.Copy(outputStream, resp.Reader)
|
|
||||||
// we should restore the terminal as soon as possible once connection end
|
|
||||||
// so any following print messages will be in normal type.
|
|
||||||
if inputStream != nil {
|
|
||||||
restoreOnce.Do(func() {
|
|
||||||
cli.restoreTerminal(inputStream)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_, err = stdcopy.StdCopy(outputStream, errorStream, resp.Reader)
|
|
||||||
}
|
|
||||||
|
|
||||||
logrus.Debug("[hijack] End of stdout")
|
|
||||||
receiveStdout <- err
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
stdinDone := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
if inputStream != nil {
|
|
||||||
io.Copy(resp.Conn, inputStream)
|
|
||||||
// we should restore the terminal as soon as possible once connection end
|
|
||||||
// so any following print messages will be in normal type.
|
|
||||||
if tty {
|
|
||||||
restoreOnce.Do(func() {
|
|
||||||
cli.restoreTerminal(inputStream)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
logrus.Debug("[hijack] End of stdin")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := resp.CloseWrite(); err != nil {
|
|
||||||
logrus.Debugf("Couldn't send EOF: %s", err)
|
|
||||||
}
|
|
||||||
close(stdinDone)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case err := <-receiveStdout:
|
|
||||||
if err != nil {
|
|
||||||
logrus.Debugf("Error receiveStdout: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case <-stdinDone:
|
|
||||||
if outputStream != nil || errorStream != nil {
|
|
||||||
select {
|
|
||||||
case err := <-receiveStdout:
|
|
||||||
if err != nil {
|
|
||||||
logrus.Debugf("Error receiveStdout: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case <-ctx.Done():
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case <-ctx.Done():
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
package idresolver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/engine-api/client"
|
|
||||||
"github.com/docker/engine-api/types/swarm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IDResolver provides ID to Name resolution.
|
|
||||||
type IDResolver struct {
|
|
||||||
client client.APIClient
|
|
||||||
noResolve bool
|
|
||||||
cache map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new IDResolver.
|
|
||||||
func New(client client.APIClient, noResolve bool) *IDResolver {
|
|
||||||
return &IDResolver{
|
|
||||||
client: client,
|
|
||||||
noResolve: noResolve,
|
|
||||||
cache: make(map[string]string),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *IDResolver) get(ctx context.Context, t interface{}, id string) (string, error) {
|
|
||||||
switch t.(type) {
|
|
||||||
case swarm.Node:
|
|
||||||
node, _, err := r.client.NodeInspectWithRaw(ctx, id)
|
|
||||||
if err != nil {
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
if node.Spec.Annotations.Name != "" {
|
|
||||||
return node.Spec.Annotations.Name, nil
|
|
||||||
}
|
|
||||||
if node.Description.Hostname != "" {
|
|
||||||
return node.Description.Hostname, nil
|
|
||||||
}
|
|
||||||
return id, nil
|
|
||||||
case swarm.Service:
|
|
||||||
service, _, err := r.client.ServiceInspectWithRaw(ctx, id)
|
|
||||||
if err != nil {
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
return service.Spec.Annotations.Name, nil
|
|
||||||
default:
|
|
||||||
return "", fmt.Errorf("unsupported type")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve will attempt to resolve an ID to a Name by querying the manager.
|
|
||||||
// Results are stored into a cache.
|
|
||||||
// If the `-n` flag is used in the command-line, resolution is disabled.
|
|
||||||
func (r *IDResolver) Resolve(ctx context.Context, t interface{}, id string) (string, error) {
|
|
||||||
if r.noResolve {
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
if name, ok := r.cache[id]; ok {
|
|
||||||
return name, nil
|
|
||||||
}
|
|
||||||
name, err := r.get(ctx, t, id)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
r.cache[id] = name
|
|
||||||
return name, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,432 +0,0 @@
|
||||||
package image
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/tar"
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api"
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/builder"
|
|
||||||
"github.com/docker/docker/builder/dockerignore"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/docker/docker/pkg/archive"
|
|
||||||
"github.com/docker/docker/pkg/fileutils"
|
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
|
||||||
"github.com/docker/docker/pkg/progress"
|
|
||||||
"github.com/docker/docker/pkg/streamformatter"
|
|
||||||
"github.com/docker/docker/pkg/urlutil"
|
|
||||||
"github.com/docker/docker/reference"
|
|
||||||
runconfigopts "github.com/docker/docker/runconfig/opts"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
"github.com/docker/engine-api/types/container"
|
|
||||||
"github.com/docker/go-units"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type buildOptions struct {
|
|
||||||
context string
|
|
||||||
dockerfileName string
|
|
||||||
tags opts.ListOpts
|
|
||||||
labels []string
|
|
||||||
buildArgs opts.ListOpts
|
|
||||||
ulimits *runconfigopts.UlimitOpt
|
|
||||||
memory string
|
|
||||||
memorySwap string
|
|
||||||
shmSize string
|
|
||||||
cpuShares int64
|
|
||||||
cpuPeriod int64
|
|
||||||
cpuQuota int64
|
|
||||||
cpuSetCpus string
|
|
||||||
cpuSetMems string
|
|
||||||
cgroupParent string
|
|
||||||
isolation string
|
|
||||||
quiet bool
|
|
||||||
noCache bool
|
|
||||||
rm bool
|
|
||||||
forceRm bool
|
|
||||||
pull bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBuildCommand creates a new `docker build` command
|
|
||||||
func NewBuildCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
ulimits := make(map[string]*units.Ulimit)
|
|
||||||
options := buildOptions{
|
|
||||||
tags: opts.NewListOpts(validateTag),
|
|
||||||
buildArgs: opts.NewListOpts(runconfigopts.ValidateEnv),
|
|
||||||
ulimits: runconfigopts.NewUlimitOpt(&ulimits),
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "build [OPTIONS] PATH | URL | -",
|
|
||||||
Short: "Build an image from a Dockerfile",
|
|
||||||
Args: cli.ExactArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
options.context = args[0]
|
|
||||||
return runBuild(dockerCli, options)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
|
|
||||||
flags.VarP(&options.tags, "tag", "t", "Name and optionally a tag in the 'name:tag' format")
|
|
||||||
flags.Var(&options.buildArgs, "build-arg", "Set build-time variables")
|
|
||||||
flags.Var(options.ulimits, "ulimit", "Ulimit options")
|
|
||||||
flags.StringVarP(&options.dockerfileName, "file", "f", "", "Name of the Dockerfile (Default is 'PATH/Dockerfile')")
|
|
||||||
flags.StringVarP(&options.memory, "memory", "m", "", "Memory limit")
|
|
||||||
flags.StringVar(&options.memorySwap, "memory-swap", "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
|
|
||||||
flags.StringVar(&options.shmSize, "shm-size", "", "Size of /dev/shm, default value is 64MB")
|
|
||||||
flags.Int64VarP(&options.cpuShares, "cpu-shares", "c", 0, "CPU shares (relative weight)")
|
|
||||||
flags.Int64Var(&options.cpuPeriod, "cpu-period", 0, "Limit the CPU CFS (Completely Fair Scheduler) period")
|
|
||||||
flags.Int64Var(&options.cpuQuota, "cpu-quota", 0, "Limit the CPU CFS (Completely Fair Scheduler) quota")
|
|
||||||
flags.StringVar(&options.cpuSetCpus, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)")
|
|
||||||
flags.StringVar(&options.cpuSetMems, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)")
|
|
||||||
flags.StringVar(&options.cgroupParent, "cgroup-parent", "", "Optional parent cgroup for the container")
|
|
||||||
flags.StringVar(&options.isolation, "isolation", "", "Container isolation technology")
|
|
||||||
flags.StringSliceVar(&options.labels, "label", []string{}, "Set metadata for an image")
|
|
||||||
flags.BoolVar(&options.noCache, "no-cache", false, "Do not use cache when building the image")
|
|
||||||
flags.BoolVar(&options.rm, "rm", true, "Remove intermediate containers after a successful build")
|
|
||||||
flags.BoolVar(&options.forceRm, "force-rm", false, "Always remove intermediate containers")
|
|
||||||
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Suppress the build output and print image ID on success")
|
|
||||||
flags.BoolVar(&options.pull, "pull", false, "Always attempt to pull a newer version of the image")
|
|
||||||
|
|
||||||
client.AddTrustedFlags(flags, true)
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runBuild(dockerCli *client.DockerCli, options buildOptions) error {
|
|
||||||
|
|
||||||
var (
|
|
||||||
buildCtx io.ReadCloser
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
specifiedContext := options.context
|
|
||||||
|
|
||||||
var (
|
|
||||||
contextDir string
|
|
||||||
tempDir string
|
|
||||||
relDockerfile string
|
|
||||||
progBuff io.Writer
|
|
||||||
buildBuff io.Writer
|
|
||||||
)
|
|
||||||
|
|
||||||
progBuff = dockerCli.Out()
|
|
||||||
buildBuff = dockerCli.Out()
|
|
||||||
if options.quiet {
|
|
||||||
progBuff = bytes.NewBuffer(nil)
|
|
||||||
buildBuff = bytes.NewBuffer(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case specifiedContext == "-":
|
|
||||||
buildCtx, relDockerfile, err = builder.GetContextFromReader(dockerCli.In(), options.dockerfileName)
|
|
||||||
case urlutil.IsGitURL(specifiedContext):
|
|
||||||
tempDir, relDockerfile, err = builder.GetContextFromGitURL(specifiedContext, options.dockerfileName)
|
|
||||||
case urlutil.IsURL(specifiedContext):
|
|
||||||
buildCtx, relDockerfile, err = builder.GetContextFromURL(progBuff, specifiedContext, options.dockerfileName)
|
|
||||||
default:
|
|
||||||
contextDir, relDockerfile, err = builder.GetContextFromLocalDir(specifiedContext, options.dockerfileName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if options.quiet && urlutil.IsURL(specifiedContext) {
|
|
||||||
fmt.Fprintln(dockerCli.Err(), progBuff)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("unable to prepare context: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tempDir != "" {
|
|
||||||
defer os.RemoveAll(tempDir)
|
|
||||||
contextDir = tempDir
|
|
||||||
}
|
|
||||||
|
|
||||||
if buildCtx == nil {
|
|
||||||
// And canonicalize dockerfile name to a platform-independent one
|
|
||||||
relDockerfile, err = archive.CanonicalTarNameForPath(relDockerfile)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("cannot canonicalize dockerfile path %s: %v", relDockerfile, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.Open(filepath.Join(contextDir, ".dockerignore"))
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var excludes []string
|
|
||||||
if err == nil {
|
|
||||||
excludes, err = dockerignore.ReadAll(f)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := builder.ValidateContextDirectory(contextDir, excludes); err != nil {
|
|
||||||
return fmt.Errorf("Error checking context: '%s'.", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If .dockerignore mentions .dockerignore or the Dockerfile
|
|
||||||
// then make sure we send both files over to the daemon
|
|
||||||
// because Dockerfile is, obviously, needed no matter what, and
|
|
||||||
// .dockerignore is needed to know if either one needs to be
|
|
||||||
// removed. The daemon will remove them for us, if needed, after it
|
|
||||||
// parses the Dockerfile. Ignore errors here, as they will have been
|
|
||||||
// caught by validateContextDirectory above.
|
|
||||||
var includes = []string{"."}
|
|
||||||
keepThem1, _ := fileutils.Matches(".dockerignore", excludes)
|
|
||||||
keepThem2, _ := fileutils.Matches(relDockerfile, excludes)
|
|
||||||
if keepThem1 || keepThem2 {
|
|
||||||
includes = append(includes, ".dockerignore", relDockerfile)
|
|
||||||
}
|
|
||||||
|
|
||||||
buildCtx, err = archive.TarWithOptions(contextDir, &archive.TarOptions{
|
|
||||||
Compression: archive.Uncompressed,
|
|
||||||
ExcludePatterns: excludes,
|
|
||||||
IncludeFiles: includes,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
var resolvedTags []*resolvedTag
|
|
||||||
if client.IsTrusted() {
|
|
||||||
// Wrap the tar archive to replace the Dockerfile entry with the rewritten
|
|
||||||
// Dockerfile which uses trusted pulls.
|
|
||||||
buildCtx = replaceDockerfileTarWrapper(ctx, buildCtx, relDockerfile, dockerCli.TrustedReference, &resolvedTags)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup an upload progress bar
|
|
||||||
progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(progBuff, true)
|
|
||||||
|
|
||||||
var body io.Reader = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
|
|
||||||
|
|
||||||
var memory int64
|
|
||||||
if options.memory != "" {
|
|
||||||
parsedMemory, err := units.RAMInBytes(options.memory)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
memory = parsedMemory
|
|
||||||
}
|
|
||||||
|
|
||||||
var memorySwap int64
|
|
||||||
if options.memorySwap != "" {
|
|
||||||
if options.memorySwap == "-1" {
|
|
||||||
memorySwap = -1
|
|
||||||
} else {
|
|
||||||
parsedMemorySwap, err := units.RAMInBytes(options.memorySwap)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
memorySwap = parsedMemorySwap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var shmSize int64
|
|
||||||
if options.shmSize != "" {
|
|
||||||
shmSize, err = units.RAMInBytes(options.shmSize)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildOptions := types.ImageBuildOptions{
|
|
||||||
Memory: memory,
|
|
||||||
MemorySwap: memorySwap,
|
|
||||||
Tags: options.tags.GetAll(),
|
|
||||||
SuppressOutput: options.quiet,
|
|
||||||
NoCache: options.noCache,
|
|
||||||
Remove: options.rm,
|
|
||||||
ForceRemove: options.forceRm,
|
|
||||||
PullParent: options.pull,
|
|
||||||
Isolation: container.Isolation(options.isolation),
|
|
||||||
CPUSetCPUs: options.cpuSetCpus,
|
|
||||||
CPUSetMems: options.cpuSetMems,
|
|
||||||
CPUShares: options.cpuShares,
|
|
||||||
CPUQuota: options.cpuQuota,
|
|
||||||
CPUPeriod: options.cpuPeriod,
|
|
||||||
CgroupParent: options.cgroupParent,
|
|
||||||
Dockerfile: relDockerfile,
|
|
||||||
ShmSize: shmSize,
|
|
||||||
Ulimits: options.ulimits.GetList(),
|
|
||||||
BuildArgs: runconfigopts.ConvertKVStringsToMap(options.buildArgs.GetAll()),
|
|
||||||
AuthConfigs: dockerCli.RetrieveAuthConfigs(),
|
|
||||||
Labels: runconfigopts.ConvertKVStringsToMap(options.labels),
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := dockerCli.Client().ImageBuild(ctx, body, buildOptions)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
|
|
||||||
err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, dockerCli.OutFd(), dockerCli.IsTerminalOut(), nil)
|
|
||||||
if err != nil {
|
|
||||||
if jerr, ok := err.(*jsonmessage.JSONError); ok {
|
|
||||||
// If no error code is set, default to 1
|
|
||||||
if jerr.Code == 0 {
|
|
||||||
jerr.Code = 1
|
|
||||||
}
|
|
||||||
if options.quiet {
|
|
||||||
fmt.Fprintf(dockerCli.Err(), "%s%s", progBuff, buildBuff)
|
|
||||||
}
|
|
||||||
return cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Windows: show error message about modified file permissions if the
|
|
||||||
// daemon isn't running Windows.
|
|
||||||
if response.OSType != "windows" && runtime.GOOS == "windows" && !options.quiet {
|
|
||||||
fmt.Fprintln(dockerCli.Err(), `SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Everything worked so if -q was provided the output from the daemon
|
|
||||||
// should be just the image ID and we'll print that to stdout.
|
|
||||||
if options.quiet {
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "%s", buildBuff)
|
|
||||||
}
|
|
||||||
|
|
||||||
if client.IsTrusted() {
|
|
||||||
// Since the build was successful, now we must tag any of the resolved
|
|
||||||
// images from the above Dockerfile rewrite.
|
|
||||||
for _, resolved := range resolvedTags {
|
|
||||||
if err := dockerCli.TagTrusted(ctx, resolved.digestRef, resolved.tagRef); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type translatorFunc func(context.Context, reference.NamedTagged) (reference.Canonical, error)
|
|
||||||
|
|
||||||
// validateTag checks if the given image name can be resolved.
|
|
||||||
func validateTag(rawRepo string) (string, error) {
|
|
||||||
_, err := reference.ParseNamed(rawRepo)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return rawRepo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var dockerfileFromLinePattern = regexp.MustCompile(`(?i)^[\s]*FROM[ \f\r\t\v]+(?P<image>[^ \f\r\t\v\n#]+)`)
|
|
||||||
|
|
||||||
// resolvedTag records the repository, tag, and resolved digest reference
|
|
||||||
// from a Dockerfile rewrite.
|
|
||||||
type resolvedTag struct {
|
|
||||||
digestRef reference.Canonical
|
|
||||||
tagRef reference.NamedTagged
|
|
||||||
}
|
|
||||||
|
|
||||||
// rewriteDockerfileFrom rewrites the given Dockerfile by resolving images in
|
|
||||||
// "FROM <image>" instructions to a digest reference. `translator` is a
|
|
||||||
// function that takes a repository name and tag reference and returns a
|
|
||||||
// trusted digest reference.
|
|
||||||
func rewriteDockerfileFrom(ctx context.Context, dockerfile io.Reader, translator translatorFunc) (newDockerfile []byte, resolvedTags []*resolvedTag, err error) {
|
|
||||||
scanner := bufio.NewScanner(dockerfile)
|
|
||||||
buf := bytes.NewBuffer(nil)
|
|
||||||
|
|
||||||
// Scan the lines of the Dockerfile, looking for a "FROM" line.
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := scanner.Text()
|
|
||||||
|
|
||||||
matches := dockerfileFromLinePattern.FindStringSubmatch(line)
|
|
||||||
if matches != nil && matches[1] != api.NoBaseImageSpecifier {
|
|
||||||
// Replace the line with a resolved "FROM repo@digest"
|
|
||||||
ref, err := reference.ParseNamed(matches[1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
ref = reference.WithDefaultTag(ref)
|
|
||||||
if ref, ok := ref.(reference.NamedTagged); ok && client.IsTrusted() {
|
|
||||||
trustedRef, err := translator(ctx, ref)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, fmt.Sprintf("FROM %s", trustedRef.String()))
|
|
||||||
resolvedTags = append(resolvedTags, &resolvedTag{
|
|
||||||
digestRef: trustedRef,
|
|
||||||
tagRef: ref,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := fmt.Fprintln(buf, line)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.Bytes(), resolvedTags, scanner.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
// replaceDockerfileTarWrapper wraps the given input tar archive stream and
|
|
||||||
// replaces the entry with the given Dockerfile name with the contents of the
|
|
||||||
// new Dockerfile. Returns a new tar archive stream with the replaced
|
|
||||||
// Dockerfile.
|
|
||||||
func replaceDockerfileTarWrapper(ctx context.Context, inputTarStream io.ReadCloser, dockerfileName string, translator translatorFunc, resolvedTags *[]*resolvedTag) io.ReadCloser {
|
|
||||||
pipeReader, pipeWriter := io.Pipe()
|
|
||||||
go func() {
|
|
||||||
tarReader := tar.NewReader(inputTarStream)
|
|
||||||
tarWriter := tar.NewWriter(pipeWriter)
|
|
||||||
|
|
||||||
defer inputTarStream.Close()
|
|
||||||
|
|
||||||
for {
|
|
||||||
hdr, err := tarReader.Next()
|
|
||||||
if err == io.EOF {
|
|
||||||
// Signals end of archive.
|
|
||||||
tarWriter.Close()
|
|
||||||
pipeWriter.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
pipeWriter.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var content io.Reader = tarReader
|
|
||||||
if hdr.Name == dockerfileName {
|
|
||||||
// This entry is the Dockerfile. Since the tar archive was
|
|
||||||
// generated from a directory on the local filesystem, the
|
|
||||||
// Dockerfile will only appear once in the archive.
|
|
||||||
var newDockerfile []byte
|
|
||||||
newDockerfile, *resolvedTags, err = rewriteDockerfileFrom(ctx, content, translator)
|
|
||||||
if err != nil {
|
|
||||||
pipeWriter.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
hdr.Size = int64(len(newDockerfile))
|
|
||||||
content = bytes.NewBuffer(newDockerfile)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tarWriter.WriteHeader(hdr); err != nil {
|
|
||||||
pipeWriter.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := io.Copy(tarWriter, content); err != nil {
|
|
||||||
pipeWriter.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return pipeReader
|
|
||||||
}
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
||||||
package image
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"text/tabwriter"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/pkg/stringid"
|
|
||||||
"github.com/docker/docker/pkg/stringutils"
|
|
||||||
"github.com/docker/go-units"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type historyOptions struct {
|
|
||||||
image string
|
|
||||||
|
|
||||||
human bool
|
|
||||||
quiet bool
|
|
||||||
noTrunc bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHistoryCommand create a new `docker history` command
|
|
||||||
func NewHistoryCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts historyOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "history [OPTIONS] IMAGE",
|
|
||||||
Short: "Show the history of an image",
|
|
||||||
Args: cli.ExactArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.image = args[0]
|
|
||||||
return runHistory(dockerCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
|
|
||||||
flags.BoolVarP(&opts.human, "human", "H", true, "Print sizes and dates in human readable format")
|
|
||||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only show numeric IDs")
|
|
||||||
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runHistory(dockerCli *client.DockerCli, opts historyOptions) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
history, err := dockerCli.Client().ImageHistory(ctx, opts.image)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w := tabwriter.NewWriter(dockerCli.Out(), 20, 1, 3, ' ', 0)
|
|
||||||
|
|
||||||
if opts.quiet {
|
|
||||||
for _, entry := range history {
|
|
||||||
if opts.noTrunc {
|
|
||||||
fmt.Fprintf(w, "%s\n", entry.ID)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(w, "%s\n", stringid.TruncateID(entry.ID))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.Flush()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var imageID string
|
|
||||||
var createdBy string
|
|
||||||
var created string
|
|
||||||
var size string
|
|
||||||
|
|
||||||
fmt.Fprintln(w, "IMAGE\tCREATED\tCREATED BY\tSIZE\tCOMMENT")
|
|
||||||
for _, entry := range history {
|
|
||||||
imageID = entry.ID
|
|
||||||
createdBy = strings.Replace(entry.CreatedBy, "\t", " ", -1)
|
|
||||||
if opts.noTrunc == false {
|
|
||||||
createdBy = stringutils.Truncate(createdBy, 45)
|
|
||||||
imageID = stringid.TruncateID(entry.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.human {
|
|
||||||
created = units.HumanDuration(time.Now().UTC().Sub(time.Unix(entry.Created, 0))) + " ago"
|
|
||||||
size = units.HumanSize(float64(entry.Size))
|
|
||||||
} else {
|
|
||||||
created = time.Unix(entry.Created, 0).Format(time.RFC3339)
|
|
||||||
size = strconv.FormatInt(entry.Size, 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", imageID, created, createdBy, size, entry.Comment)
|
|
||||||
}
|
|
||||||
w.Flush()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,103 +0,0 @@
|
||||||
package image
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/api/client/formatter"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
"github.com/docker/engine-api/types/filters"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type imagesOptions struct {
|
|
||||||
matchName string
|
|
||||||
|
|
||||||
quiet bool
|
|
||||||
all bool
|
|
||||||
noTrunc bool
|
|
||||||
showDigests bool
|
|
||||||
format string
|
|
||||||
filter []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewImagesCommand create a new `docker images` command
|
|
||||||
func NewImagesCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts imagesOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "images [OPTIONS] [REPOSITORY[:TAG]]",
|
|
||||||
Short: "List images",
|
|
||||||
Args: cli.RequiresMaxArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
if len(args) > 0 {
|
|
||||||
opts.matchName = args[0]
|
|
||||||
}
|
|
||||||
return runImages(dockerCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
|
|
||||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only show numeric IDs")
|
|
||||||
flags.BoolVarP(&opts.all, "all", "a", false, "Show all images (default hides intermediate images)")
|
|
||||||
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output")
|
|
||||||
flags.BoolVar(&opts.showDigests, "digests", false, "Show digests")
|
|
||||||
flags.StringVar(&opts.format, "format", "", "Pretty-print images using a Go template")
|
|
||||||
flags.StringSliceVarP(&opts.filter, "filter", "f", []string{}, "Filter output based on conditions provided")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runImages(dockerCli *client.DockerCli, opts imagesOptions) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
// Consolidate all filter flags, and sanity check them early.
|
|
||||||
// They'll get process in the daemon/server.
|
|
||||||
imageFilterArgs := filters.NewArgs()
|
|
||||||
for _, f := range opts.filter {
|
|
||||||
var err error
|
|
||||||
imageFilterArgs, err = filters.ParseFlag(f, imageFilterArgs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
matchName := opts.matchName
|
|
||||||
|
|
||||||
options := types.ImageListOptions{
|
|
||||||
MatchName: matchName,
|
|
||||||
All: opts.all,
|
|
||||||
Filters: imageFilterArgs,
|
|
||||||
}
|
|
||||||
|
|
||||||
images, err := dockerCli.Client().ImageList(ctx, options)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
f := opts.format
|
|
||||||
if len(f) == 0 {
|
|
||||||
if len(dockerCli.ImagesFormat()) > 0 && !opts.quiet {
|
|
||||||
f = dockerCli.ImagesFormat()
|
|
||||||
} else {
|
|
||||||
f = "table"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
imagesCtx := formatter.ImageContext{
|
|
||||||
Context: formatter.Context{
|
|
||||||
Output: dockerCli.Out(),
|
|
||||||
Format: f,
|
|
||||||
Quiet: opts.quiet,
|
|
||||||
Trunc: !opts.noTrunc,
|
|
||||||
},
|
|
||||||
Digest: opts.showDigests,
|
|
||||||
Images: images,
|
|
||||||
}
|
|
||||||
|
|
||||||
imagesCtx.Write()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
package image
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
|
||||||
"github.com/docker/docker/pkg/urlutil"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type importOptions struct {
|
|
||||||
source string
|
|
||||||
reference string
|
|
||||||
changes []string
|
|
||||||
message string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewImportCommand creates a new `docker import` command
|
|
||||||
func NewImportCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts importOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]",
|
|
||||||
Short: "Import the contents from a tarball to create a filesystem image",
|
|
||||||
Args: cli.RequiresMinArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.source = args[0]
|
|
||||||
if len(args) > 1 {
|
|
||||||
opts.reference = args[1]
|
|
||||||
}
|
|
||||||
return runImport(dockerCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
|
|
||||||
flags.StringSliceVarP(&opts.changes, "change", "c", []string{}, "Apply Dockerfile instruction to the created image")
|
|
||||||
flags.StringVarP(&opts.message, "message", "m", "", "Set commit message for imported image")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runImport(dockerCli *client.DockerCli, opts importOptions) error {
|
|
||||||
var (
|
|
||||||
in io.Reader
|
|
||||||
srcName = opts.source
|
|
||||||
)
|
|
||||||
|
|
||||||
if opts.source == "-" {
|
|
||||||
in = dockerCli.In()
|
|
||||||
} else if !urlutil.IsURL(opts.source) {
|
|
||||||
srcName = "-"
|
|
||||||
file, err := os.Open(opts.source)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
in = file
|
|
||||||
}
|
|
||||||
|
|
||||||
source := types.ImageImportSource{
|
|
||||||
Source: in,
|
|
||||||
SourceName: srcName,
|
|
||||||
}
|
|
||||||
|
|
||||||
options := types.ImageImportOptions{
|
|
||||||
Message: opts.message,
|
|
||||||
Changes: opts.changes,
|
|
||||||
}
|
|
||||||
|
|
||||||
clnt := dockerCli.Client()
|
|
||||||
|
|
||||||
responseBody, err := clnt.ImageImport(context.Background(), source, opts.reference, options)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer responseBody.Close()
|
|
||||||
|
|
||||||
return jsonmessage.DisplayJSONMessagesStream(responseBody, dockerCli.Out(), dockerCli.OutFd(), dockerCli.IsTerminalOut(), nil)
|
|
||||||
}
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
package image
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type loadOptions struct {
|
|
||||||
input string
|
|
||||||
quiet bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewLoadCommand creates a new `docker load` command
|
|
||||||
func NewLoadCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts loadOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "load [OPTIONS]",
|
|
||||||
Short: "Load an image from a tar archive or STDIN",
|
|
||||||
Args: cli.NoArgs,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return runLoad(dockerCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
|
|
||||||
flags.StringVarP(&opts.input, "input", "i", "", "Read from tar archive file, instead of STDIN")
|
|
||||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress the load output")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runLoad(dockerCli *client.DockerCli, opts loadOptions) error {
|
|
||||||
|
|
||||||
var input io.Reader = dockerCli.In()
|
|
||||||
if opts.input != "" {
|
|
||||||
file, err := os.Open(opts.input)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
input = file
|
|
||||||
}
|
|
||||||
if !dockerCli.IsTerminalOut() {
|
|
||||||
opts.quiet = true
|
|
||||||
}
|
|
||||||
response, err := dockerCli.Client().ImageLoad(context.Background(), input, opts.quiet)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
|
|
||||||
if response.Body != nil && response.JSON {
|
|
||||||
return jsonmessage.DisplayJSONMessagesStream(response.Body, dockerCli.Out(), dockerCli.OutFd(), dockerCli.IsTerminalOut(), nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.Copy(dockerCli.Out(), response.Body)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
package image
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/reference"
|
|
||||||
"github.com/docker/docker/registry"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type pullOptions struct {
|
|
||||||
remote string
|
|
||||||
all bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPullCommand creates a new `docker pull` command
|
|
||||||
func NewPullCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts pullOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "pull [OPTIONS] NAME[:TAG|@DIGEST]",
|
|
||||||
Short: "Pull an image or a repository from a registry",
|
|
||||||
Args: cli.ExactArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.remote = args[0]
|
|
||||||
return runPull(dockerCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
|
|
||||||
flags.BoolVarP(&opts.all, "all-tags", "a", false, "Download all tagged images in the repository")
|
|
||||||
client.AddTrustedFlags(flags, true)
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runPull(dockerCli *client.DockerCli, opts pullOptions) error {
|
|
||||||
distributionRef, err := reference.ParseNamed(opts.remote)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if opts.all && !reference.IsNameOnly(distributionRef) {
|
|
||||||
return errors.New("tag can't be used with --all-tags/-a")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !opts.all && reference.IsNameOnly(distributionRef) {
|
|
||||||
distributionRef = reference.WithDefaultTag(distributionRef)
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "Using default tag: %s\n", reference.DefaultTag)
|
|
||||||
}
|
|
||||||
|
|
||||||
var tag string
|
|
||||||
switch x := distributionRef.(type) {
|
|
||||||
case reference.Canonical:
|
|
||||||
tag = x.Digest().String()
|
|
||||||
case reference.NamedTagged:
|
|
||||||
tag = x.Tag()
|
|
||||||
}
|
|
||||||
|
|
||||||
registryRef := registry.ParseReference(tag)
|
|
||||||
|
|
||||||
// Resolve the Repository name from fqn to RepositoryInfo
|
|
||||||
repoInfo, err := registry.ParseRepositoryInfo(distributionRef)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
authConfig := dockerCli.ResolveAuthConfig(ctx, repoInfo.Index)
|
|
||||||
requestPrivilege := dockerCli.RegistryAuthenticationPrivilegedFunc(repoInfo.Index, "pull")
|
|
||||||
|
|
||||||
if client.IsTrusted() && !registryRef.HasDigest() {
|
|
||||||
// Check if tag is digest
|
|
||||||
return dockerCli.TrustedPull(ctx, repoInfo, registryRef, authConfig, requestPrivilege)
|
|
||||||
}
|
|
||||||
|
|
||||||
return dockerCli.ImagePullPrivileged(ctx, authConfig, distributionRef.String(), requestPrivilege, opts.all)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
package image
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
|
||||||
"github.com/docker/docker/reference"
|
|
||||||
"github.com/docker/docker/registry"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewPushCommand creates a new `docker push` command
|
|
||||||
func NewPushCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "push [OPTIONS] NAME[:TAG]",
|
|
||||||
Short: "Push an image or a repository to a registry",
|
|
||||||
Args: cli.ExactArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return runPush(dockerCli, args[0])
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
|
|
||||||
client.AddTrustedFlags(flags, true)
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runPush(dockerCli *client.DockerCli, remote string) error {
|
|
||||||
ref, err := reference.ParseNamed(remote)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve the Repository name from fqn to RepositoryInfo
|
|
||||||
repoInfo, err := registry.ParseRepositoryInfo(ref)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
// Resolve the Auth config relevant for this server
|
|
||||||
authConfig := dockerCli.ResolveAuthConfig(ctx, repoInfo.Index)
|
|
||||||
requestPrivilege := dockerCli.RegistryAuthenticationPrivilegedFunc(repoInfo.Index, "push")
|
|
||||||
|
|
||||||
if client.IsTrusted() {
|
|
||||||
return dockerCli.TrustedPush(ctx, repoInfo, ref, authConfig, requestPrivilege)
|
|
||||||
}
|
|
||||||
|
|
||||||
responseBody, err := dockerCli.ImagePushPrivileged(ctx, authConfig, ref.String(), requestPrivilege)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer responseBody.Close()
|
|
||||||
|
|
||||||
return jsonmessage.DisplayJSONMessagesStream(responseBody, dockerCli.Out(), dockerCli.OutFd(), dockerCli.IsTerminalOut(), nil)
|
|
||||||
}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
package image
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type removeOptions struct {
|
|
||||||
force bool
|
|
||||||
noPrune bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRemoveCommand create a new `docker remove` command
|
|
||||||
func NewRemoveCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts removeOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "rmi [OPTIONS] IMAGE [IMAGE...]",
|
|
||||||
Short: "Remove one or more images",
|
|
||||||
Args: cli.RequiresMinArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return runRemove(dockerCli, opts, args)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
|
|
||||||
flags.BoolVarP(&opts.force, "force", "f", false, "Force removal of the image")
|
|
||||||
flags.BoolVar(&opts.noPrune, "no-prune", false, "Do not delete untagged parents")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runRemove(dockerCli *client.DockerCli, opts removeOptions, images []string) error {
|
|
||||||
client := dockerCli.Client()
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
options := types.ImageRemoveOptions{
|
|
||||||
Force: opts.force,
|
|
||||||
PruneChildren: !opts.noPrune,
|
|
||||||
}
|
|
||||||
|
|
||||||
var errs []string
|
|
||||||
for _, image := range images {
|
|
||||||
dels, err := client.ImageRemove(ctx, image, options)
|
|
||||||
if err != nil {
|
|
||||||
errs = append(errs, err.Error())
|
|
||||||
} else {
|
|
||||||
for _, del := range dels {
|
|
||||||
if del.Deleted != "" {
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "Deleted: %s\n", del.Deleted)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "Untagged: %s\n", del.Untagged)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(errs) > 0 {
|
|
||||||
return fmt.Errorf("%s", strings.Join(errs, "\n"))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
package image
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type saveOptions struct {
|
|
||||||
images []string
|
|
||||||
output string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSaveCommand creates a new `docker save` command
|
|
||||||
func NewSaveCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts saveOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "save [OPTIONS] IMAGE [IMAGE...]",
|
|
||||||
Short: "Save one or more images to a tar archive (streamed to STDOUT by default)",
|
|
||||||
Args: cli.RequiresMinArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.images = args
|
|
||||||
return runSave(dockerCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
|
|
||||||
flags.StringVarP(&opts.output, "output", "o", "", "Write to a file, instead of STDOUT")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runSave(dockerCli *client.DockerCli, opts saveOptions) error {
|
|
||||||
if opts.output == "" && dockerCli.IsTerminalOut() {
|
|
||||||
return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
|
|
||||||
}
|
|
||||||
|
|
||||||
responseBody, err := dockerCli.Client().ImageSave(context.Background(), opts.images)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer responseBody.Close()
|
|
||||||
|
|
||||||
if opts.output == "" {
|
|
||||||
_, err := io.Copy(dockerCli.Out(), responseBody)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return client.CopyToFile(opts.output, responseBody)
|
|
||||||
}
|
|
||||||
|
|
@ -1,135 +0,0 @@
|
||||||
package image
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/pkg/stringutils"
|
|
||||||
"github.com/docker/docker/registry"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
"github.com/docker/engine-api/types/filters"
|
|
||||||
registrytypes "github.com/docker/engine-api/types/registry"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type searchOptions struct {
|
|
||||||
term string
|
|
||||||
noTrunc bool
|
|
||||||
limit int
|
|
||||||
filter []string
|
|
||||||
|
|
||||||
// Deprecated
|
|
||||||
stars uint
|
|
||||||
automated bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSearchCommand create a new `docker search` command
|
|
||||||
func NewSearchCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts searchOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "search [OPTIONS] TERM",
|
|
||||||
Short: "Search the Docker Hub for images",
|
|
||||||
Args: cli.ExactArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.term = args[0]
|
|
||||||
return runSearch(dockerCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
|
|
||||||
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output")
|
|
||||||
flags.StringSliceVarP(&opts.filter, "filter", "f", []string{}, "Filter output based on conditions provided")
|
|
||||||
flags.IntVar(&opts.limit, "limit", registry.DefaultSearchLimit, "Max number of search results")
|
|
||||||
|
|
||||||
flags.BoolVar(&opts.automated, "automated", false, "Only show automated builds")
|
|
||||||
flags.UintVarP(&opts.stars, "stars", "s", 0, "Only displays with at least x stars")
|
|
||||||
|
|
||||||
flags.MarkDeprecated("automated", "use --filter=automated=true instead")
|
|
||||||
flags.MarkDeprecated("stars", "use --filter=stars=3 instead")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runSearch(dockerCli *client.DockerCli, opts searchOptions) error {
|
|
||||||
indexInfo, err := registry.ParseSearchIndexInfo(opts.term)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
authConfig := dockerCli.ResolveAuthConfig(ctx, indexInfo)
|
|
||||||
requestPrivilege := dockerCli.RegistryAuthenticationPrivilegedFunc(indexInfo, "search")
|
|
||||||
|
|
||||||
encodedAuth, err := client.EncodeAuthToBase64(authConfig)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
searchFilters := filters.NewArgs()
|
|
||||||
for _, f := range opts.filter {
|
|
||||||
var err error
|
|
||||||
searchFilters, err = filters.ParseFlag(f, searchFilters)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
options := types.ImageSearchOptions{
|
|
||||||
RegistryAuth: encodedAuth,
|
|
||||||
PrivilegeFunc: requestPrivilege,
|
|
||||||
Filters: searchFilters,
|
|
||||||
Limit: opts.limit,
|
|
||||||
}
|
|
||||||
|
|
||||||
clnt := dockerCli.Client()
|
|
||||||
|
|
||||||
unorderedResults, err := clnt.ImageSearch(ctx, opts.term, options)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
results := searchResultsByStars(unorderedResults)
|
|
||||||
sort.Sort(results)
|
|
||||||
|
|
||||||
w := tabwriter.NewWriter(dockerCli.Out(), 10, 1, 3, ' ', 0)
|
|
||||||
fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n")
|
|
||||||
for _, res := range results {
|
|
||||||
// --automated and -s, --stars are deprecated since Docker 1.12
|
|
||||||
if (opts.automated && !res.IsAutomated) || (int(opts.stars) > res.StarCount) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
desc := strings.Replace(res.Description, "\n", " ", -1)
|
|
||||||
desc = strings.Replace(desc, "\r", " ", -1)
|
|
||||||
if !opts.noTrunc && len(desc) > 45 {
|
|
||||||
desc = stringutils.Truncate(desc, 42) + "..."
|
|
||||||
}
|
|
||||||
fmt.Fprintf(w, "%s\t%s\t%d\t", res.Name, desc, res.StarCount)
|
|
||||||
if res.IsOfficial {
|
|
||||||
fmt.Fprint(w, "[OK]")
|
|
||||||
|
|
||||||
}
|
|
||||||
fmt.Fprint(w, "\t")
|
|
||||||
if res.IsAutomated {
|
|
||||||
fmt.Fprint(w, "[OK]")
|
|
||||||
}
|
|
||||||
fmt.Fprint(w, "\n")
|
|
||||||
}
|
|
||||||
w.Flush()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SearchResultsByStars sorts search results in descending order by number of stars.
|
|
||||||
type searchResultsByStars []registrytypes.SearchResult
|
|
||||||
|
|
||||||
func (r searchResultsByStars) Len() int { return len(r) }
|
|
||||||
func (r searchResultsByStars) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
|
||||||
func (r searchResultsByStars) Less(i, j int) bool { return r[j].StarCount < r[i].StarCount }
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
package image
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type tagOptions struct {
|
|
||||||
image string
|
|
||||||
name string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTagCommand create a new `docker tag` command
|
|
||||||
func NewTagCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts tagOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "tag IMAGE[:TAG] IMAGE[:TAG]",
|
|
||||||
Short: "Tag an image into a repository",
|
|
||||||
Args: cli.ExactArgs(2),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.image = args[0]
|
|
||||||
opts.name = args[1]
|
|
||||||
return runTag(dockerCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.SetInterspersed(false)
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runTag(dockerCli *client.DockerCli, opts tagOptions) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
return dockerCli.Client().ImageTag(ctx, opts.image, opts.name)
|
|
||||||
}
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client/inspect"
|
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
|
||||||
"github.com/docker/engine-api/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdInspect displays low-level information on one or more containers, images or tasks.
|
|
||||||
//
|
|
||||||
// Usage: docker inspect [OPTIONS] CONTAINER|IMAGE|TASK [CONTAINER|IMAGE|TASK...]
|
|
||||||
func (cli *DockerCli) CmdInspect(args ...string) error {
|
|
||||||
cmd := Cli.Subcmd("inspect", []string{"CONTAINER|IMAGE|TASK [CONTAINER|IMAGE|TASK...]"}, Cli.DockerCommands["inspect"].Description, true)
|
|
||||||
tmplStr := cmd.String([]string{"f", "-format"}, "", "Format the output using the given go template")
|
|
||||||
inspectType := cmd.String([]string{"-type"}, "", "Return JSON for specified type, (e.g image, container or task)")
|
|
||||||
size := cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes if the type is container")
|
|
||||||
cmd.Require(flag.Min, 1)
|
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
|
||||||
|
|
||||||
if *inspectType != "" && *inspectType != "container" && *inspectType != "image" && *inspectType != "task" {
|
|
||||||
return fmt.Errorf("%q is not a valid value for --type", *inspectType)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
var elementSearcher inspect.GetRefFunc
|
|
||||||
switch *inspectType {
|
|
||||||
case "container":
|
|
||||||
elementSearcher = cli.inspectContainers(ctx, *size)
|
|
||||||
case "image":
|
|
||||||
elementSearcher = cli.inspectImages(ctx, *size)
|
|
||||||
case "task":
|
|
||||||
if *size {
|
|
||||||
fmt.Fprintln(cli.err, "WARNING: --size ignored for tasks")
|
|
||||||
}
|
|
||||||
elementSearcher = cli.inspectTasks(ctx)
|
|
||||||
default:
|
|
||||||
elementSearcher = cli.inspectAll(ctx, *size)
|
|
||||||
}
|
|
||||||
|
|
||||||
return inspect.Inspect(cli.out, cmd.Args(), *tmplStr, elementSearcher)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) inspectContainers(ctx context.Context, getSize bool) inspect.GetRefFunc {
|
|
||||||
return func(ref string) (interface{}, []byte, error) {
|
|
||||||
return cli.client.ContainerInspectWithRaw(ctx, ref, getSize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) inspectImages(ctx context.Context, getSize bool) inspect.GetRefFunc {
|
|
||||||
return func(ref string) (interface{}, []byte, error) {
|
|
||||||
return cli.client.ImageInspectWithRaw(ctx, ref, getSize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) inspectTasks(ctx context.Context) inspect.GetRefFunc {
|
|
||||||
return func(ref string) (interface{}, []byte, error) {
|
|
||||||
return cli.client.TaskInspectWithRaw(ctx, ref)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) inspectAll(ctx context.Context, getSize bool) inspect.GetRefFunc {
|
|
||||||
return func(ref string) (interface{}, []byte, error) {
|
|
||||||
c, rawContainer, err := cli.client.ContainerInspectWithRaw(ctx, ref, getSize)
|
|
||||||
if err != nil {
|
|
||||||
// Search for image with that id if a container doesn't exist.
|
|
||||||
if client.IsErrContainerNotFound(err) {
|
|
||||||
i, rawImage, err := cli.client.ImageInspectWithRaw(ctx, ref, getSize)
|
|
||||||
if err != nil {
|
|
||||||
if client.IsErrImageNotFound(err) {
|
|
||||||
// Search for task with that id if an image doesn't exists.
|
|
||||||
t, rawTask, err := cli.client.TaskInspectWithRaw(ctx, ref)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("Error: No such image, container or task: %s", ref)
|
|
||||||
}
|
|
||||||
if getSize {
|
|
||||||
fmt.Fprintln(cli.err, "WARNING: --size ignored for tasks")
|
|
||||||
}
|
|
||||||
return t, rawTask, nil
|
|
||||||
}
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return i, rawImage, nil
|
|
||||||
}
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return c, rawContainer, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,195 +0,0 @@
|
||||||
package inspect
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/utils/templates"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Inspector defines an interface to implement to process elements
|
|
||||||
type Inspector interface {
|
|
||||||
Inspect(typedElement interface{}, rawElement []byte) error
|
|
||||||
Flush() error
|
|
||||||
}
|
|
||||||
|
|
||||||
// TemplateInspector uses a text template to inspect elements.
|
|
||||||
type TemplateInspector struct {
|
|
||||||
outputStream io.Writer
|
|
||||||
buffer *bytes.Buffer
|
|
||||||
tmpl *template.Template
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTemplateInspector creates a new inspector with a template.
|
|
||||||
func NewTemplateInspector(outputStream io.Writer, tmpl *template.Template) Inspector {
|
|
||||||
return &TemplateInspector{
|
|
||||||
outputStream: outputStream,
|
|
||||||
buffer: new(bytes.Buffer),
|
|
||||||
tmpl: tmpl,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTemplateInspectorFromString creates a new TemplateInspector from a string
|
|
||||||
// which is compiled into a template.
|
|
||||||
func NewTemplateInspectorFromString(out io.Writer, tmplStr string) (Inspector, error) {
|
|
||||||
if tmplStr == "" {
|
|
||||||
return NewIndentedInspector(out), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpl, err := templates.Parse(tmplStr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Template parsing error: %s", err)
|
|
||||||
}
|
|
||||||
return NewTemplateInspector(out, tmpl), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRefFunc is a function which used by Inspect to fetch an object from a
|
|
||||||
// reference
|
|
||||||
type GetRefFunc func(ref string) (interface{}, []byte, error)
|
|
||||||
|
|
||||||
// Inspect fetches objects by reference using GetRefFunc and writes the json
|
|
||||||
// representation to the output writer.
|
|
||||||
func Inspect(out io.Writer, references []string, tmplStr string, getRef GetRefFunc) error {
|
|
||||||
inspector, err := NewTemplateInspectorFromString(out, tmplStr)
|
|
||||||
if err != nil {
|
|
||||||
return cli.StatusError{StatusCode: 64, Status: err.Error()}
|
|
||||||
}
|
|
||||||
|
|
||||||
var inspectErr error
|
|
||||||
for _, ref := range references {
|
|
||||||
element, raw, err := getRef(ref)
|
|
||||||
if err != nil {
|
|
||||||
inspectErr = err
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := inspector.Inspect(element, raw); err != nil {
|
|
||||||
inspectErr = err
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := inspector.Flush(); err != nil {
|
|
||||||
logrus.Errorf("%s\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if inspectErr != nil {
|
|
||||||
return cli.StatusError{StatusCode: 1, Status: inspectErr.Error()}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inspect executes the inspect template.
|
|
||||||
// It decodes the raw element into a map if the initial execution fails.
|
|
||||||
// This allows docker cli to parse inspect structs injected with Swarm fields.
|
|
||||||
func (i *TemplateInspector) Inspect(typedElement interface{}, rawElement []byte) error {
|
|
||||||
buffer := new(bytes.Buffer)
|
|
||||||
if err := i.tmpl.Execute(buffer, typedElement); err != nil {
|
|
||||||
if rawElement == nil {
|
|
||||||
return fmt.Errorf("Template parsing error: %v", err)
|
|
||||||
}
|
|
||||||
return i.tryRawInspectFallback(rawElement)
|
|
||||||
}
|
|
||||||
i.buffer.Write(buffer.Bytes())
|
|
||||||
i.buffer.WriteByte('\n')
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// tryRawInspectFallback executes the inspect template with a raw interface.
|
|
||||||
// This allows docker cli to parse inspect structs injected with Swarm fields.
|
|
||||||
func (i *TemplateInspector) tryRawInspectFallback(rawElement []byte) error {
|
|
||||||
var raw interface{}
|
|
||||||
buffer := new(bytes.Buffer)
|
|
||||||
rdr := bytes.NewReader(rawElement)
|
|
||||||
dec := json.NewDecoder(rdr)
|
|
||||||
|
|
||||||
if rawErr := dec.Decode(&raw); rawErr != nil {
|
|
||||||
return fmt.Errorf("unable to read inspect data: %v", rawErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
tmplMissingKey := i.tmpl.Option("missingkey=error")
|
|
||||||
if rawErr := tmplMissingKey.Execute(buffer, raw); rawErr != nil {
|
|
||||||
return fmt.Errorf("Template parsing error: %v", rawErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
i.buffer.Write(buffer.Bytes())
|
|
||||||
i.buffer.WriteByte('\n')
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush write the result of inspecting all elements into the output stream.
|
|
||||||
func (i *TemplateInspector) Flush() error {
|
|
||||||
if i.buffer.Len() == 0 {
|
|
||||||
_, err := io.WriteString(i.outputStream, "\n")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err := io.Copy(i.outputStream, i.buffer)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// IndentedInspector uses a buffer to stop the indented representation of an element.
|
|
||||||
type IndentedInspector struct {
|
|
||||||
outputStream io.Writer
|
|
||||||
elements []interface{}
|
|
||||||
rawElements [][]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewIndentedInspector generates a new IndentedInspector.
|
|
||||||
func NewIndentedInspector(outputStream io.Writer) Inspector {
|
|
||||||
return &IndentedInspector{
|
|
||||||
outputStream: outputStream,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inspect writes the raw element with an indented json format.
|
|
||||||
func (i *IndentedInspector) Inspect(typedElement interface{}, rawElement []byte) error {
|
|
||||||
if rawElement != nil {
|
|
||||||
i.rawElements = append(i.rawElements, rawElement)
|
|
||||||
} else {
|
|
||||||
i.elements = append(i.elements, typedElement)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush write the result of inspecting all elements into the output stream.
|
|
||||||
func (i *IndentedInspector) Flush() error {
|
|
||||||
if len(i.elements) == 0 && len(i.rawElements) == 0 {
|
|
||||||
_, err := io.WriteString(i.outputStream, "[]\n")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var buffer io.Reader
|
|
||||||
if len(i.rawElements) > 0 {
|
|
||||||
bytesBuffer := new(bytes.Buffer)
|
|
||||||
bytesBuffer.WriteString("[")
|
|
||||||
for idx, r := range i.rawElements {
|
|
||||||
bytesBuffer.Write(r)
|
|
||||||
if idx < len(i.rawElements)-1 {
|
|
||||||
bytesBuffer.WriteString(",")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bytesBuffer.WriteString("]")
|
|
||||||
indented := new(bytes.Buffer)
|
|
||||||
if err := json.Indent(indented, bytesBuffer.Bytes(), "", " "); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
buffer = indented
|
|
||||||
} else {
|
|
||||||
b, err := json.MarshalIndent(i.elements, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
buffer = bytes.NewReader(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := io.Copy(i.outputStream, buffer); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err := io.WriteString(i.outputStream, "\n")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
@ -1,221 +0,0 @@
|
||||||
package inspect
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/docker/docker/utils/templates"
|
|
||||||
)
|
|
||||||
|
|
||||||
type testElement struct {
|
|
||||||
DNS string `json:"Dns"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTemplateInspectorDefault(t *testing.T) {
|
|
||||||
b := new(bytes.Buffer)
|
|
||||||
tmpl, err := templates.Parse("{{.DNS}}")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
i := NewTemplateInspector(b, tmpl)
|
|
||||||
if err := i.Inspect(testElement{"0.0.0.0"}, nil); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := i.Flush(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if b.String() != "0.0.0.0\n" {
|
|
||||||
t.Fatalf("Expected `0.0.0.0\\n`, got `%s`", b.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTemplateInspectorEmpty(t *testing.T) {
|
|
||||||
b := new(bytes.Buffer)
|
|
||||||
tmpl, err := templates.Parse("{{.DNS}}")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
i := NewTemplateInspector(b, tmpl)
|
|
||||||
|
|
||||||
if err := i.Flush(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if b.String() != "\n" {
|
|
||||||
t.Fatalf("Expected `\\n`, got `%s`", b.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTemplateInspectorTemplateError(t *testing.T) {
|
|
||||||
b := new(bytes.Buffer)
|
|
||||||
tmpl, err := templates.Parse("{{.Foo}}")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
i := NewTemplateInspector(b, tmpl)
|
|
||||||
|
|
||||||
err = i.Inspect(testElement{"0.0.0.0"}, nil)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("Expected error got nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.HasPrefix(err.Error(), "Template parsing error") {
|
|
||||||
t.Fatalf("Expected template error, got %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTemplateInspectorRawFallback(t *testing.T) {
|
|
||||||
b := new(bytes.Buffer)
|
|
||||||
tmpl, err := templates.Parse("{{.Dns}}")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
i := NewTemplateInspector(b, tmpl)
|
|
||||||
if err := i.Inspect(testElement{"0.0.0.0"}, []byte(`{"Dns": "0.0.0.0"}`)); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := i.Flush(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if b.String() != "0.0.0.0\n" {
|
|
||||||
t.Fatalf("Expected `0.0.0.0\\n`, got `%s`", b.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTemplateInspectorRawFallbackError(t *testing.T) {
|
|
||||||
b := new(bytes.Buffer)
|
|
||||||
tmpl, err := templates.Parse("{{.Dns}}")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
i := NewTemplateInspector(b, tmpl)
|
|
||||||
err = i.Inspect(testElement{"0.0.0.0"}, []byte(`{"Foo": "0.0.0.0"}`))
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("Expected error got nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.HasPrefix(err.Error(), "Template parsing error") {
|
|
||||||
t.Fatalf("Expected template error, got %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTemplateInspectorMultiple(t *testing.T) {
|
|
||||||
b := new(bytes.Buffer)
|
|
||||||
tmpl, err := templates.Parse("{{.DNS}}")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
i := NewTemplateInspector(b, tmpl)
|
|
||||||
|
|
||||||
if err := i.Inspect(testElement{"0.0.0.0"}, nil); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := i.Inspect(testElement{"1.1.1.1"}, nil); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := i.Flush(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if b.String() != "0.0.0.0\n1.1.1.1\n" {
|
|
||||||
t.Fatalf("Expected `0.0.0.0\\n1.1.1.1\\n`, got `%s`", b.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIndentedInspectorDefault(t *testing.T) {
|
|
||||||
b := new(bytes.Buffer)
|
|
||||||
i := NewIndentedInspector(b)
|
|
||||||
if err := i.Inspect(testElement{"0.0.0.0"}, nil); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := i.Flush(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := `[
|
|
||||||
{
|
|
||||||
"Dns": "0.0.0.0"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
`
|
|
||||||
if b.String() != expected {
|
|
||||||
t.Fatalf("Expected `%s`, got `%s`", expected, b.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIndentedInspectorMultiple(t *testing.T) {
|
|
||||||
b := new(bytes.Buffer)
|
|
||||||
i := NewIndentedInspector(b)
|
|
||||||
if err := i.Inspect(testElement{"0.0.0.0"}, nil); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := i.Inspect(testElement{"1.1.1.1"}, nil); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := i.Flush(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := `[
|
|
||||||
{
|
|
||||||
"Dns": "0.0.0.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Dns": "1.1.1.1"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
`
|
|
||||||
if b.String() != expected {
|
|
||||||
t.Fatalf("Expected `%s`, got `%s`", expected, b.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIndentedInspectorEmpty(t *testing.T) {
|
|
||||||
b := new(bytes.Buffer)
|
|
||||||
i := NewIndentedInspector(b)
|
|
||||||
|
|
||||||
if err := i.Flush(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := "[]\n"
|
|
||||||
if b.String() != expected {
|
|
||||||
t.Fatalf("Expected `%s`, got `%s`", expected, b.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIndentedInspectorRawElements(t *testing.T) {
|
|
||||||
b := new(bytes.Buffer)
|
|
||||||
i := NewIndentedInspector(b)
|
|
||||||
if err := i.Inspect(testElement{"0.0.0.0"}, []byte(`{"Dns": "0.0.0.0", "Node": "0"}`)); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := i.Inspect(testElement{"1.1.1.1"}, []byte(`{"Dns": "1.1.1.1", "Node": "1"}`)); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := i.Flush(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := `[
|
|
||||||
{
|
|
||||||
"Dns": "0.0.0.0",
|
|
||||||
"Node": "0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Dns": "1.1.1.1",
|
|
||||||
"Node": "1"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
`
|
|
||||||
if b.String() != expected {
|
|
||||||
t.Fatalf("Expected `%s`, got `%s`", expected, b.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
package network
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewNetworkCommand returns a cobra command for `network` subcommands
|
|
||||||
func NewNetworkCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "network",
|
|
||||||
Short: "Manage Docker networks",
|
|
||||||
Args: cli.NoArgs,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cmd.AddCommand(
|
|
||||||
newConnectCommand(dockerCli),
|
|
||||||
newCreateCommand(dockerCli),
|
|
||||||
newDisconnectCommand(dockerCli),
|
|
||||||
newInspectCommand(dockerCli),
|
|
||||||
newListCommand(dockerCli),
|
|
||||||
newRemoveCommand(dockerCli),
|
|
||||||
)
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
package network
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
runconfigopts "github.com/docker/docker/runconfig/opts"
|
|
||||||
"github.com/docker/engine-api/types/network"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type connectOptions struct {
|
|
||||||
network string
|
|
||||||
container string
|
|
||||||
ipaddress string
|
|
||||||
ipv6address string
|
|
||||||
links opts.ListOpts
|
|
||||||
aliases []string
|
|
||||||
linklocalips []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newConnectCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
opts := connectOptions{
|
|
||||||
links: opts.NewListOpts(runconfigopts.ValidateLink),
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "connect [OPTIONS] NETWORK CONTAINER",
|
|
||||||
Short: "Connect a container to a network",
|
|
||||||
Args: cli.ExactArgs(2),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.network = args[0]
|
|
||||||
opts.container = args[1]
|
|
||||||
return runConnect(dockerCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.StringVar(&opts.ipaddress, "ip", "", "IP Address")
|
|
||||||
flags.StringVar(&opts.ipv6address, "ip6", "", "IPv6 Address")
|
|
||||||
flags.Var(&opts.links, "link", "Add link to another container")
|
|
||||||
flags.StringSliceVar(&opts.aliases, "alias", []string{}, "Add network-scoped alias for the container")
|
|
||||||
flags.StringSliceVar(&opts.linklocalips, "link-local-ip", []string{}, "Add a link-local address for the container")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runConnect(dockerCli *client.DockerCli, opts connectOptions) error {
|
|
||||||
client := dockerCli.Client()
|
|
||||||
|
|
||||||
epConfig := &network.EndpointSettings{
|
|
||||||
IPAMConfig: &network.EndpointIPAMConfig{
|
|
||||||
IPv4Address: opts.ipaddress,
|
|
||||||
IPv6Address: opts.ipv6address,
|
|
||||||
LinkLocalIPs: opts.linklocalips,
|
|
||||||
},
|
|
||||||
Links: opts.links.GetAll(),
|
|
||||||
Aliases: opts.aliases,
|
|
||||||
}
|
|
||||||
|
|
||||||
return client.NetworkConnect(context.Background(), opts.network, opts.container, epConfig)
|
|
||||||
}
|
|
||||||
|
|
@ -1,222 +0,0 @@
|
||||||
package network
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
runconfigopts "github.com/docker/docker/runconfig/opts"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
"github.com/docker/engine-api/types/network"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type createOptions struct {
|
|
||||||
name string
|
|
||||||
driver string
|
|
||||||
driverOpts opts.MapOpts
|
|
||||||
labels []string
|
|
||||||
internal bool
|
|
||||||
ipv6 bool
|
|
||||||
|
|
||||||
ipamDriver string
|
|
||||||
ipamSubnet []string
|
|
||||||
ipamIPRange []string
|
|
||||||
ipamGateway []string
|
|
||||||
ipamAux opts.MapOpts
|
|
||||||
ipamOpt opts.MapOpts
|
|
||||||
}
|
|
||||||
|
|
||||||
func newCreateCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
opts := createOptions{
|
|
||||||
driverOpts: *opts.NewMapOpts(nil, nil),
|
|
||||||
ipamAux: *opts.NewMapOpts(nil, nil),
|
|
||||||
ipamOpt: *opts.NewMapOpts(nil, nil),
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "create [OPTIONS] NETWORK",
|
|
||||||
Short: "Create a network",
|
|
||||||
Args: cli.ExactArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.name = args[0]
|
|
||||||
return runCreate(dockerCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.StringVarP(&opts.driver, "driver", "d", "bridge", "Driver to manage the Network")
|
|
||||||
flags.VarP(&opts.driverOpts, "opt", "o", "Set driver specific options")
|
|
||||||
flags.StringSliceVar(&opts.labels, "label", []string{}, "Set metadata on a network")
|
|
||||||
flags.BoolVar(&opts.internal, "internal", false, "restricts external access to the network")
|
|
||||||
flags.BoolVar(&opts.ipv6, "ipv6", false, "enable IPv6 networking")
|
|
||||||
|
|
||||||
flags.StringVar(&opts.ipamDriver, "ipam-driver", "default", "IP Address Management Driver")
|
|
||||||
flags.StringSliceVar(&opts.ipamSubnet, "subnet", []string{}, "subnet in CIDR format that represents a network segment")
|
|
||||||
flags.StringSliceVar(&opts.ipamIPRange, "ip-range", []string{}, "allocate container ip from a sub-range")
|
|
||||||
flags.StringSliceVar(&opts.ipamGateway, "gateway", []string{}, "ipv4 or ipv6 Gateway for the master subnet")
|
|
||||||
|
|
||||||
flags.Var(&opts.ipamAux, "aux-address", "auxiliary ipv4 or ipv6 addresses used by Network driver")
|
|
||||||
flags.Var(&opts.ipamOpt, "ipam-opt", "set IPAM driver specific options")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runCreate(dockerCli *client.DockerCli, opts createOptions) error {
|
|
||||||
client := dockerCli.Client()
|
|
||||||
|
|
||||||
ipamCfg, err := consolidateIpam(opts.ipamSubnet, opts.ipamIPRange, opts.ipamGateway, opts.ipamAux.GetAll())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct network create request body
|
|
||||||
nc := types.NetworkCreate{
|
|
||||||
Driver: opts.driver,
|
|
||||||
Options: opts.driverOpts.GetAll(),
|
|
||||||
IPAM: network.IPAM{
|
|
||||||
Driver: opts.ipamDriver,
|
|
||||||
Config: ipamCfg,
|
|
||||||
Options: opts.ipamOpt.GetAll(),
|
|
||||||
},
|
|
||||||
CheckDuplicate: true,
|
|
||||||
Internal: opts.internal,
|
|
||||||
EnableIPv6: opts.ipv6,
|
|
||||||
Labels: runconfigopts.ConvertKVStringsToMap(opts.labels),
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := client.NetworkCreate(context.Background(), opts.name, nc)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "%s\n", resp.ID)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consolidates the ipam configuration as a group from different related configurations
|
|
||||||
// user can configure network with multiple non-overlapping subnets and hence it is
|
|
||||||
// possible to correlate the various related parameters and consolidate them.
|
|
||||||
// consoidateIpam consolidates subnets, ip-ranges, gateways and auxiliary addresses into
|
|
||||||
// structured ipam data.
|
|
||||||
func consolidateIpam(subnets, ranges, gateways []string, auxaddrs map[string]string) ([]network.IPAMConfig, error) {
|
|
||||||
if len(subnets) < len(ranges) || len(subnets) < len(gateways) {
|
|
||||||
return nil, fmt.Errorf("every ip-range or gateway must have a corresponding subnet")
|
|
||||||
}
|
|
||||||
iData := map[string]*network.IPAMConfig{}
|
|
||||||
|
|
||||||
// Populate non-overlapping subnets into consolidation map
|
|
||||||
for _, s := range subnets {
|
|
||||||
for k := range iData {
|
|
||||||
ok1, err := subnetMatches(s, k)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ok2, err := subnetMatches(k, s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if ok1 || ok2 {
|
|
||||||
return nil, fmt.Errorf("multiple overlapping subnet configuration is not supported")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
iData[s] = &network.IPAMConfig{Subnet: s, AuxAddress: map[string]string{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate and add valid ip ranges
|
|
||||||
for _, r := range ranges {
|
|
||||||
match := false
|
|
||||||
for _, s := range subnets {
|
|
||||||
ok, err := subnetMatches(s, r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if iData[s].IPRange != "" {
|
|
||||||
return nil, fmt.Errorf("cannot configure multiple ranges (%s, %s) on the same subnet (%s)", r, iData[s].IPRange, s)
|
|
||||||
}
|
|
||||||
d := iData[s]
|
|
||||||
d.IPRange = r
|
|
||||||
match = true
|
|
||||||
}
|
|
||||||
if !match {
|
|
||||||
return nil, fmt.Errorf("no matching subnet for range %s", r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate and add valid gateways
|
|
||||||
for _, g := range gateways {
|
|
||||||
match := false
|
|
||||||
for _, s := range subnets {
|
|
||||||
ok, err := subnetMatches(s, g)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if iData[s].Gateway != "" {
|
|
||||||
return nil, fmt.Errorf("cannot configure multiple gateways (%s, %s) for the same subnet (%s)", g, iData[s].Gateway, s)
|
|
||||||
}
|
|
||||||
d := iData[s]
|
|
||||||
d.Gateway = g
|
|
||||||
match = true
|
|
||||||
}
|
|
||||||
if !match {
|
|
||||||
return nil, fmt.Errorf("no matching subnet for gateway %s", g)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate and add aux-addresses
|
|
||||||
for key, aa := range auxaddrs {
|
|
||||||
match := false
|
|
||||||
for _, s := range subnets {
|
|
||||||
ok, err := subnetMatches(s, aa)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
iData[s].AuxAddress[key] = aa
|
|
||||||
match = true
|
|
||||||
}
|
|
||||||
if !match {
|
|
||||||
return nil, fmt.Errorf("no matching subnet for aux-address %s", aa)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
idl := []network.IPAMConfig{}
|
|
||||||
for _, v := range iData {
|
|
||||||
idl = append(idl, *v)
|
|
||||||
}
|
|
||||||
return idl, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func subnetMatches(subnet, data string) (bool, error) {
|
|
||||||
var (
|
|
||||||
ip net.IP
|
|
||||||
)
|
|
||||||
|
|
||||||
_, s, err := net.ParseCIDR(subnet)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("Invalid subnet %s : %v", s, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(data, "/") {
|
|
||||||
ip, _, err = net.ParseCIDR(data)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("Invalid cidr %s : %v", data, err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ip = net.ParseIP(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.Contains(ip), nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
package network
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type disconnectOptions struct {
|
|
||||||
network string
|
|
||||||
container string
|
|
||||||
force bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newDisconnectCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
opts := disconnectOptions{}
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "disconnect [OPTIONS] NETWORK CONTAINER",
|
|
||||||
Short: "Disconnect a container from a network",
|
|
||||||
Args: cli.ExactArgs(2),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.network = args[0]
|
|
||||||
opts.container = args[1]
|
|
||||||
return runDisconnect(dockerCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.BoolVarP(&opts.force, "force", "f", false, "Force the container to disconnect from a network")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runDisconnect(dockerCli *client.DockerCli, opts disconnectOptions) error {
|
|
||||||
client := dockerCli.Client()
|
|
||||||
|
|
||||||
return client.NetworkDisconnect(context.Background(), opts.network, opts.container, opts.force)
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
package network
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/api/client/inspect"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type inspectOptions struct {
|
|
||||||
format string
|
|
||||||
names []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts inspectOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "inspect [OPTIONS] NETWORK [NETWORK...]",
|
|
||||||
Short: "Display detailed information on one or more networks",
|
|
||||||
Args: cli.RequiresMinArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.names = args
|
|
||||||
return runInspect(dockerCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.Flags().StringVarP(&opts.format, "format", "f", "", "Format the output using the given go template")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runInspect(dockerCli *client.DockerCli, opts inspectOptions) error {
|
|
||||||
client := dockerCli.Client()
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
getNetFunc := func(name string) (interface{}, []byte, error) {
|
|
||||||
return client.NetworkInspectWithRaw(ctx, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return inspect.Inspect(dockerCli.Out(), opts.names, opts.format, getNetFunc)
|
|
||||||
}
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
package network
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/pkg/stringid"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
"github.com/docker/engine-api/types/filters"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type byNetworkName []types.NetworkResource
|
|
||||||
|
|
||||||
func (r byNetworkName) Len() int { return len(r) }
|
|
||||||
func (r byNetworkName) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
|
||||||
func (r byNetworkName) Less(i, j int) bool { return r[i].Name < r[j].Name }
|
|
||||||
|
|
||||||
type listOptions struct {
|
|
||||||
quiet bool
|
|
||||||
noTrunc bool
|
|
||||||
filter []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newListCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts listOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "ls",
|
|
||||||
Aliases: []string{"list"},
|
|
||||||
Short: "List networks",
|
|
||||||
Args: cli.NoArgs,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return runList(dockerCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display volume names")
|
|
||||||
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate the output")
|
|
||||||
flags.StringSliceVarP(&opts.filter, "filter", "f", []string{}, "Provide filter values (i.e. 'dangling=true')")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runList(dockerCli *client.DockerCli, opts listOptions) error {
|
|
||||||
client := dockerCli.Client()
|
|
||||||
|
|
||||||
netFilterArgs := filters.NewArgs()
|
|
||||||
for _, f := range opts.filter {
|
|
||||||
var err error
|
|
||||||
netFilterArgs, err = filters.ParseFlag(f, netFilterArgs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
options := types.NetworkListOptions{
|
|
||||||
Filters: netFilterArgs,
|
|
||||||
}
|
|
||||||
|
|
||||||
networkResources, err := client.NetworkList(context.Background(), options)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w := tabwriter.NewWriter(dockerCli.Out(), 20, 1, 3, ' ', 0)
|
|
||||||
if !opts.quiet {
|
|
||||||
fmt.Fprintf(w, "NETWORK ID\tNAME\tDRIVER\tSCOPE")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(byNetworkName(networkResources))
|
|
||||||
for _, networkResource := range networkResources {
|
|
||||||
ID := networkResource.ID
|
|
||||||
netName := networkResource.Name
|
|
||||||
driver := networkResource.Driver
|
|
||||||
scope := networkResource.Scope
|
|
||||||
if !opts.noTrunc {
|
|
||||||
ID = stringid.TruncateID(ID)
|
|
||||||
}
|
|
||||||
if opts.quiet {
|
|
||||||
fmt.Fprintln(w, ID)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t",
|
|
||||||
ID,
|
|
||||||
netName,
|
|
||||||
driver,
|
|
||||||
scope)
|
|
||||||
fmt.Fprint(w, "\n")
|
|
||||||
}
|
|
||||||
w.Flush()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
package network
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newRemoveCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
return &cobra.Command{
|
|
||||||
Use: "rm NETWORK [NETWORK]...",
|
|
||||||
Aliases: []string{"remove"},
|
|
||||||
Short: "Remove a network",
|
|
||||||
Args: cli.RequiresMinArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return runRemove(dockerCli, args)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runRemove(dockerCli *client.DockerCli, networks []string) error {
|
|
||||||
client := dockerCli.Client()
|
|
||||||
ctx := context.Background()
|
|
||||||
status := 0
|
|
||||||
|
|
||||||
for _, name := range networks {
|
|
||||||
if err := client.NetworkRemove(ctx, name); err != nil {
|
|
||||||
fmt.Fprintf(dockerCli.Err(), "%s\n", err)
|
|
||||||
status = 1
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "%s\n", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if status != 0 {
|
|
||||||
return cli.StatusError{StatusCode: status}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
package node
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/engine-api/types/swarm"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newAcceptCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
return &cobra.Command{
|
|
||||||
Use: "accept NODE [NODE...]",
|
|
||||||
Short: "Accept a node in the swarm",
|
|
||||||
Args: cli.RequiresMinArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return runAccept(dockerCli, args)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runAccept(dockerCli *client.DockerCli, nodes []string) error {
|
|
||||||
accept := func(node *swarm.Node) {
|
|
||||||
node.Spec.Membership = swarm.NodeMembershipAccepted
|
|
||||||
}
|
|
||||||
success := func(nodeID string) {
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "Node %s accepted in the swarm.\n", nodeID)
|
|
||||||
}
|
|
||||||
return updateNodes(dockerCli, nodes, accept, success)
|
|
||||||
}
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
package node
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
apiclient "github.com/docker/engine-api/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewNodeCommand returns a cobra command for `node` subcommands
|
|
||||||
func NewNodeCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "node",
|
|
||||||
Short: "Manage Docker Swarm nodes",
|
|
||||||
Args: cli.NoArgs,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cmd.AddCommand(
|
|
||||||
newAcceptCommand(dockerCli),
|
|
||||||
newDemoteCommand(dockerCli),
|
|
||||||
newInspectCommand(dockerCli),
|
|
||||||
newListCommand(dockerCli),
|
|
||||||
newPromoteCommand(dockerCli),
|
|
||||||
newRemoveCommand(dockerCli),
|
|
||||||
newTasksCommand(dockerCli),
|
|
||||||
newUpdateCommand(dockerCli),
|
|
||||||
)
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reference returns the reference of a node. The special value "self" for a node
|
|
||||||
// reference is mapped to the current node, hence the node ID is retrieved using
|
|
||||||
// the `/info` endpoint.
|
|
||||||
func Reference(client apiclient.APIClient, ctx context.Context, ref string) (string, error) {
|
|
||||||
if ref == "self" {
|
|
||||||
info, err := client.Info(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return info.Swarm.NodeID, nil
|
|
||||||
}
|
|
||||||
return ref, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
package node
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/engine-api/types/swarm"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newDemoteCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
return &cobra.Command{
|
|
||||||
Use: "demote NODE [NODE...]",
|
|
||||||
Short: "Demote a node from manager in the swarm",
|
|
||||||
Args: cli.RequiresMinArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return runDemote(dockerCli, args)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runDemote(dockerCli *client.DockerCli, nodes []string) error {
|
|
||||||
demote := func(node *swarm.Node) {
|
|
||||||
node.Spec.Role = swarm.NodeRoleWorker
|
|
||||||
}
|
|
||||||
success := func(nodeID string) {
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "Manager %s demoted in the swarm.\n", nodeID)
|
|
||||||
}
|
|
||||||
return updateNodes(dockerCli, nodes, demote, success)
|
|
||||||
}
|
|
||||||
|
|
@ -1,141 +0,0 @@
|
||||||
package node
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/api/client/inspect"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
|
||||||
"github.com/docker/engine-api/types/swarm"
|
|
||||||
"github.com/docker/go-units"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
type inspectOptions struct {
|
|
||||||
nodeIds []string
|
|
||||||
format string
|
|
||||||
pretty bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts inspectOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "inspect [OPTIONS] self|NODE [NODE...]",
|
|
||||||
Short: "Display detailed information on one or more nodes",
|
|
||||||
Args: cli.RequiresMinArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.nodeIds = args
|
|
||||||
return runInspect(dockerCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given go template")
|
|
||||||
flags.BoolVar(&opts.pretty, "pretty", false, "Print the information in a human friendly format.")
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runInspect(dockerCli *client.DockerCli, opts inspectOptions) error {
|
|
||||||
client := dockerCli.Client()
|
|
||||||
ctx := context.Background()
|
|
||||||
getRef := func(ref string) (interface{}, []byte, error) {
|
|
||||||
nodeRef, err := Reference(client, ctx, ref)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
node, _, err := client.NodeInspectWithRaw(ctx, nodeRef)
|
|
||||||
return node, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !opts.pretty {
|
|
||||||
return inspect.Inspect(dockerCli.Out(), opts.nodeIds, opts.format, getRef)
|
|
||||||
}
|
|
||||||
return printHumanFriendly(dockerCli.Out(), opts.nodeIds, getRef)
|
|
||||||
}
|
|
||||||
|
|
||||||
func printHumanFriendly(out io.Writer, refs []string, getRef inspect.GetRefFunc) error {
|
|
||||||
for idx, ref := range refs {
|
|
||||||
obj, _, err := getRef(ref)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
printNode(out, obj.(swarm.Node))
|
|
||||||
|
|
||||||
// TODO: better way to do this?
|
|
||||||
// print extra space between objects, but not after the last one
|
|
||||||
if idx+1 != len(refs) {
|
|
||||||
fmt.Fprintf(out, "\n\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: use a template
|
|
||||||
func printNode(out io.Writer, node swarm.Node) {
|
|
||||||
fmt.Fprintf(out, "ID:\t\t\t%s\n", node.ID)
|
|
||||||
ioutils.FprintfIfNotEmpty(out, "Name:\t\t\t%s\n", node.Spec.Name)
|
|
||||||
if node.Spec.Labels != nil {
|
|
||||||
fmt.Fprintln(out, "Labels:")
|
|
||||||
for k, v := range node.Spec.Labels {
|
|
||||||
fmt.Fprintf(out, " - %s = %s\n", k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ioutils.FprintfIfNotEmpty(out, "Hostname:\t\t%s\n", node.Description.Hostname)
|
|
||||||
fmt.Fprintln(out, "Status:")
|
|
||||||
fmt.Fprintf(out, " State:\t\t\t%s\n", client.PrettyPrint(node.Status.State))
|
|
||||||
ioutils.FprintfIfNotEmpty(out, " Message:\t\t%s\n", client.PrettyPrint(node.Status.Message))
|
|
||||||
fmt.Fprintf(out, " Availability:\t\t%s\n", client.PrettyPrint(node.Spec.Availability))
|
|
||||||
|
|
||||||
if node.ManagerStatus != nil {
|
|
||||||
fmt.Fprintln(out, "Manager Status:")
|
|
||||||
fmt.Fprintf(out, " Address:\t\t%s\n", node.ManagerStatus.Addr)
|
|
||||||
fmt.Fprintf(out, " Raft Status:\t\t%s\n", client.PrettyPrint(node.ManagerStatus.Reachability))
|
|
||||||
leader := "No"
|
|
||||||
if node.ManagerStatus.Leader {
|
|
||||||
leader = "Yes"
|
|
||||||
}
|
|
||||||
fmt.Fprintf(out, " Leader:\t\t%s\n", leader)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintln(out, "Platform:")
|
|
||||||
fmt.Fprintf(out, " Operating System:\t%s\n", node.Description.Platform.OS)
|
|
||||||
fmt.Fprintf(out, " Architecture:\t\t%s\n", node.Description.Platform.Architecture)
|
|
||||||
|
|
||||||
fmt.Fprintln(out, "Resources:")
|
|
||||||
fmt.Fprintf(out, " CPUs:\t\t\t%d\n", node.Description.Resources.NanoCPUs/1e9)
|
|
||||||
fmt.Fprintf(out, " Memory:\t\t%s\n", units.BytesSize(float64(node.Description.Resources.MemoryBytes)))
|
|
||||||
|
|
||||||
var pluginTypes []string
|
|
||||||
pluginNamesByType := map[string][]string{}
|
|
||||||
for _, p := range node.Description.Engine.Plugins {
|
|
||||||
// append to pluginTypes only if not done previously
|
|
||||||
if _, ok := pluginNamesByType[p.Type]; !ok {
|
|
||||||
pluginTypes = append(pluginTypes, p.Type)
|
|
||||||
}
|
|
||||||
pluginNamesByType[p.Type] = append(pluginNamesByType[p.Type], p.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(pluginTypes) > 0 {
|
|
||||||
fmt.Fprintln(out, "Plugins:")
|
|
||||||
sort.Strings(pluginTypes) // ensure stable output
|
|
||||||
for _, pluginType := range pluginTypes {
|
|
||||||
fmt.Fprintf(out, " %s:\t\t%s\n", pluginType, strings.Join(pluginNamesByType[pluginType], ", "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Fprintf(out, "Engine Version:\t\t%s\n", node.Description.Engine.EngineVersion)
|
|
||||||
|
|
||||||
if len(node.Description.Engine.Labels) != 0 {
|
|
||||||
fmt.Fprintln(out, "Engine Labels:")
|
|
||||||
for k, v := range node.Description.Engine.Labels {
|
|
||||||
fmt.Fprintf(out, " - %s = %s", k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,113 +0,0 @@
|
||||||
package node
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
"github.com/docker/engine-api/types/swarm"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
listItemFmt = "%s\t%s\t%s\t%s\t%s\t%s\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
type listOptions struct {
|
|
||||||
quiet bool
|
|
||||||
filter opts.FilterOpt
|
|
||||||
}
|
|
||||||
|
|
||||||
func newListCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
opts := listOptions{filter: opts.NewFilterOpt()}
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "ls",
|
|
||||||
Aliases: []string{"list"},
|
|
||||||
Short: "List nodes in the swarm",
|
|
||||||
Args: cli.NoArgs,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return runList(dockerCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs")
|
|
||||||
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runList(dockerCli *client.DockerCli, opts listOptions) error {
|
|
||||||
client := dockerCli.Client()
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
nodes, err := client.NodeList(
|
|
||||||
ctx,
|
|
||||||
types.NodeListOptions{Filter: opts.filter.Value()})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
info, err := client.Info(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
out := dockerCli.Out()
|
|
||||||
if opts.quiet {
|
|
||||||
printQuiet(out, nodes)
|
|
||||||
} else {
|
|
||||||
printTable(out, nodes, info)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func printTable(out io.Writer, nodes []swarm.Node, info types.Info) {
|
|
||||||
writer := tabwriter.NewWriter(out, 0, 4, 2, ' ', 0)
|
|
||||||
|
|
||||||
// Ignore flushing errors
|
|
||||||
defer writer.Flush()
|
|
||||||
|
|
||||||
fmt.Fprintf(writer, listItemFmt, "ID", "HOSTNAME", "MEMBERSHIP", "STATUS", "AVAILABILITY", "MANAGER STATUS")
|
|
||||||
for _, node := range nodes {
|
|
||||||
name := node.Description.Hostname
|
|
||||||
availability := string(node.Spec.Availability)
|
|
||||||
membership := string(node.Spec.Membership)
|
|
||||||
|
|
||||||
reachability := ""
|
|
||||||
if node.ManagerStatus != nil {
|
|
||||||
if node.ManagerStatus.Leader {
|
|
||||||
reachability = "Leader"
|
|
||||||
} else {
|
|
||||||
reachability = string(node.ManagerStatus.Reachability)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ID := node.ID
|
|
||||||
if node.ID == info.Swarm.NodeID {
|
|
||||||
ID = ID + " *"
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(
|
|
||||||
writer,
|
|
||||||
listItemFmt,
|
|
||||||
ID,
|
|
||||||
name,
|
|
||||||
client.PrettyPrint(membership),
|
|
||||||
client.PrettyPrint(string(node.Status.State)),
|
|
||||||
client.PrettyPrint(availability),
|
|
||||||
client.PrettyPrint(reachability))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func printQuiet(out io.Writer, nodes []swarm.Node) {
|
|
||||||
for _, node := range nodes {
|
|
||||||
fmt.Fprintln(out, node.ID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
package node
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/docker/engine-api/types/swarm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type nodeOptions struct {
|
|
||||||
role string
|
|
||||||
membership string
|
|
||||||
availability string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (opts *nodeOptions) ToNodeSpec() (swarm.NodeSpec, error) {
|
|
||||||
var spec swarm.NodeSpec
|
|
||||||
|
|
||||||
switch swarm.NodeRole(strings.ToLower(opts.role)) {
|
|
||||||
case swarm.NodeRoleWorker:
|
|
||||||
spec.Role = swarm.NodeRoleWorker
|
|
||||||
case swarm.NodeRoleManager:
|
|
||||||
spec.Role = swarm.NodeRoleManager
|
|
||||||
case "":
|
|
||||||
default:
|
|
||||||
return swarm.NodeSpec{}, fmt.Errorf("invalid role %q, only worker and manager are supported", opts.role)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch swarm.NodeMembership(strings.ToLower(opts.membership)) {
|
|
||||||
case swarm.NodeMembershipAccepted:
|
|
||||||
spec.Membership = swarm.NodeMembershipAccepted
|
|
||||||
case "":
|
|
||||||
default:
|
|
||||||
return swarm.NodeSpec{}, fmt.Errorf("invalid membership %q, only accepted is supported", opts.membership)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch swarm.NodeAvailability(strings.ToLower(opts.availability)) {
|
|
||||||
case swarm.NodeAvailabilityActive:
|
|
||||||
spec.Availability = swarm.NodeAvailabilityActive
|
|
||||||
case swarm.NodeAvailabilityPause:
|
|
||||||
spec.Availability = swarm.NodeAvailabilityPause
|
|
||||||
case swarm.NodeAvailabilityDrain:
|
|
||||||
spec.Availability = swarm.NodeAvailabilityDrain
|
|
||||||
case "":
|
|
||||||
default:
|
|
||||||
return swarm.NodeSpec{}, fmt.Errorf("invalid availability %q, only active, pause and drain are supported", opts.availability)
|
|
||||||
}
|
|
||||||
|
|
||||||
return spec, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
package node
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/engine-api/types/swarm"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newPromoteCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
return &cobra.Command{
|
|
||||||
Use: "promote NODE [NODE...]",
|
|
||||||
Short: "Promote a node to a manager in the swarm",
|
|
||||||
Args: cli.RequiresMinArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return runPromote(dockerCli, args)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runPromote(dockerCli *client.DockerCli, nodes []string) error {
|
|
||||||
promote := func(node *swarm.Node) {
|
|
||||||
node.Spec.Role = swarm.NodeRoleManager
|
|
||||||
}
|
|
||||||
success := func(nodeID string) {
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "Node %s promoted to a manager in the swarm.\n", nodeID)
|
|
||||||
}
|
|
||||||
return updateNodes(dockerCli, nodes, promote, success)
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
package node
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newRemoveCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
return &cobra.Command{
|
|
||||||
Use: "rm NODE [NODE...]",
|
|
||||||
Aliases: []string{"remove"},
|
|
||||||
Short: "Remove a node from the swarm",
|
|
||||||
Args: cli.RequiresMinArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return runRemove(dockerCli, args)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runRemove(dockerCli *client.DockerCli, args []string) error {
|
|
||||||
client := dockerCli.Client()
|
|
||||||
ctx := context.Background()
|
|
||||||
for _, nodeID := range args {
|
|
||||||
err := client.NodeRemove(ctx, nodeID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "%s\n", nodeID)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
package node
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/api/client/idresolver"
|
|
||||||
"github.com/docker/docker/api/client/task"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
"github.com/docker/engine-api/types/swarm"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type tasksOptions struct {
|
|
||||||
nodeID string
|
|
||||||
all bool
|
|
||||||
noResolve bool
|
|
||||||
filter opts.FilterOpt
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTasksCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
opts := tasksOptions{filter: opts.NewFilterOpt()}
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "tasks [OPTIONS] self|NODE",
|
|
||||||
Short: "List tasks running on a node",
|
|
||||||
Args: cli.ExactArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.nodeID = args[0]
|
|
||||||
return runTasks(dockerCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.BoolVarP(&opts.all, "all", "a", false, "Display all instances")
|
|
||||||
flags.BoolVar(&opts.noResolve, "no-resolve", false, "Do not map IDs to Names")
|
|
||||||
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runTasks(dockerCli *client.DockerCli, opts tasksOptions) error {
|
|
||||||
client := dockerCli.Client()
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
nodeRef, err := Reference(client, ctx, opts.nodeID)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
node, _, err := client.NodeInspectWithRaw(ctx, nodeRef)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
filter := opts.filter.Value()
|
|
||||||
filter.Add("node", node.ID)
|
|
||||||
if !opts.all && !filter.Include("desired-state") {
|
|
||||||
filter.Add("desired-state", string(swarm.TaskStateRunning))
|
|
||||||
filter.Add("desired-state", string(swarm.TaskStateAccepted))
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks, err := client.TaskList(
|
|
||||||
ctx,
|
|
||||||
types.TaskListOptions{Filter: filter})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return task.Print(dockerCli, ctx, tasks, idresolver.New(client, opts.noResolve))
|
|
||||||
}
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
package node
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/engine-api/types/swarm"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/pflag"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts nodeOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "update [OPTIONS] NODE",
|
|
||||||
Short: "Update a node",
|
|
||||||
Args: cli.ExactArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return runUpdate(dockerCli, cmd.Flags(), args[0])
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.StringVar(&opts.role, flagRole, "", "Role of the node (worker/manager)")
|
|
||||||
flags.StringVar(&opts.membership, flagMembership, "", "Membership of the node (accepted/rejected)")
|
|
||||||
flags.StringVar(&opts.availability, flagAvailability, "", "Availability of the node (active/pause/drain)")
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, nodeID string) error {
|
|
||||||
success := func(_ string) {
|
|
||||||
fmt.Fprintln(dockerCli.Out(), nodeID)
|
|
||||||
}
|
|
||||||
return updateNodes(dockerCli, []string{nodeID}, mergeNodeUpdate(flags), success)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateNodes(dockerCli *client.DockerCli, nodes []string, mergeNode func(node *swarm.Node), success func(nodeID string)) error {
|
|
||||||
client := dockerCli.Client()
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
for _, nodeID := range nodes {
|
|
||||||
node, _, err := client.NodeInspectWithRaw(ctx, nodeID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
mergeNode(&node)
|
|
||||||
err = client.NodeUpdate(ctx, node.ID, node.Version, node.Spec)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
success(nodeID)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeNodeUpdate(flags *pflag.FlagSet) func(*swarm.Node) {
|
|
||||||
return func(node *swarm.Node) {
|
|
||||||
spec := &node.Spec
|
|
||||||
|
|
||||||
if flags.Changed(flagRole) {
|
|
||||||
str, _ := flags.GetString(flagRole)
|
|
||||||
spec.Role = swarm.NodeRole(str)
|
|
||||||
}
|
|
||||||
if flags.Changed(flagMembership) {
|
|
||||||
str, _ := flags.GetString(flagMembership)
|
|
||||||
spec.Membership = swarm.NodeMembership(str)
|
|
||||||
}
|
|
||||||
if flags.Changed(flagAvailability) {
|
|
||||||
str, _ := flags.GetString(flagAvailability)
|
|
||||||
spec.Availability = swarm.NodeAvailability(str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
flagRole = "role"
|
|
||||||
flagMembership = "membership"
|
|
||||||
flagAvailability = "availability"
|
|
||||||
)
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
// +build !experimental
|
|
||||||
|
|
||||||
package plugin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewPluginCommand returns a cobra command for `plugin` subcommands
|
|
||||||
func NewPluginCommand(cmd *cobra.Command, dockerCli *client.DockerCli) {
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
// +build experimental
|
|
||||||
|
|
||||||
package plugin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewPluginCommand returns a cobra command for `plugin` subcommands
|
|
||||||
func NewPluginCommand(rootCmd *cobra.Command, dockerCli *client.DockerCli) {
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "plugin",
|
|
||||||
Short: "Manage Docker plugins",
|
|
||||||
Args: cli.NoArgs,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.AddCommand(
|
|
||||||
newDisableCommand(dockerCli),
|
|
||||||
newEnableCommand(dockerCli),
|
|
||||||
newInspectCommand(dockerCli),
|
|
||||||
newInstallCommand(dockerCli),
|
|
||||||
newListCommand(dockerCli),
|
|
||||||
newRemoveCommand(dockerCli),
|
|
||||||
newSetCommand(dockerCli),
|
|
||||||
newPushCommand(dockerCli),
|
|
||||||
)
|
|
||||||
|
|
||||||
rootCmd.AddCommand(cmd)
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
// +build experimental
|
|
||||||
|
|
||||||
package plugin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/reference"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newDisableCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "disable PLUGIN",
|
|
||||||
Short: "Disable a plugin",
|
|
||||||
Args: cli.ExactArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return runDisable(dockerCli, args[0])
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runDisable(dockerCli *client.DockerCli, name string) error {
|
|
||||||
named, err := reference.ParseNamed(name) // FIXME: validate
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if reference.IsNameOnly(named) {
|
|
||||||
named = reference.WithDefaultTag(named)
|
|
||||||
}
|
|
||||||
ref, ok := named.(reference.NamedTagged)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("invalid name: %s", named.String())
|
|
||||||
}
|
|
||||||
return dockerCli.Client().PluginDisable(context.Background(), ref.String())
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
// +build experimental
|
|
||||||
|
|
||||||
package plugin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/reference"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newEnableCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "enable PLUGIN",
|
|
||||||
Short: "Enable a plugin",
|
|
||||||
Args: cli.ExactArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return runEnable(dockerCli, args[0])
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runEnable(dockerCli *client.DockerCli, name string) error {
|
|
||||||
named, err := reference.ParseNamed(name) // FIXME: validate
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if reference.IsNameOnly(named) {
|
|
||||||
named = reference.WithDefaultTag(named)
|
|
||||||
}
|
|
||||||
ref, ok := named.(reference.NamedTagged)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("invalid name: %s", named.String())
|
|
||||||
}
|
|
||||||
return dockerCli.Client().PluginEnable(context.Background(), ref.String())
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
// +build experimental
|
|
||||||
|
|
||||||
package plugin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/reference"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "inspect PLUGIN",
|
|
||||||
Short: "Inspect a plugin",
|
|
||||||
Args: cli.ExactArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return runInspect(dockerCli, args[0])
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runInspect(dockerCli *client.DockerCli, name string) error {
|
|
||||||
named, err := reference.ParseNamed(name) // FIXME: validate
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if reference.IsNameOnly(named) {
|
|
||||||
named = reference.WithDefaultTag(named)
|
|
||||||
}
|
|
||||||
ref, ok := named.(reference.NamedTagged)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("invalid name: %s", named.String())
|
|
||||||
}
|
|
||||||
p, err := dockerCli.Client().PluginInspect(context.Background(), ref.String())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := json.MarshalIndent(p, "", "\t")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = dockerCli.Out().Write(b)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
// +build experimental
|
|
||||||
|
|
||||||
package plugin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/reference"
|
|
||||||
"github.com/docker/docker/registry"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
type pluginOptions struct {
|
|
||||||
name string
|
|
||||||
grantPerms bool
|
|
||||||
disable bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newInstallCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var options pluginOptions
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "install PLUGIN",
|
|
||||||
Short: "Install a plugin",
|
|
||||||
Args: cli.RequiresMinArgs(1), // TODO: allow for set args
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
options.name = args[0]
|
|
||||||
return runInstall(dockerCli, options)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.BoolVar(&options.grantPerms, "grant-all-permissions", false, "grant all permissions necessary to run the plugin")
|
|
||||||
flags.BoolVar(&options.disable, "disable", false, "do not enable the plugin on install")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runInstall(dockerCli *client.DockerCli, opts pluginOptions) error {
|
|
||||||
named, err := reference.ParseNamed(opts.name) // FIXME: validate
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if reference.IsNameOnly(named) {
|
|
||||||
named = reference.WithDefaultTag(named)
|
|
||||||
}
|
|
||||||
ref, ok := named.(reference.NamedTagged)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("invalid name: %s", named.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
repoInfo, err := registry.ParseRepositoryInfo(named)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
authConfig := dockerCli.ResolveAuthConfig(ctx, repoInfo.Index)
|
|
||||||
|
|
||||||
encodedAuth, err := client.EncodeAuthToBase64(authConfig)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
registryAuthFunc := dockerCli.RegistryAuthenticationPrivilegedFunc(repoInfo.Index, "plugin install")
|
|
||||||
|
|
||||||
options := types.PluginInstallOptions{
|
|
||||||
RegistryAuth: encodedAuth,
|
|
||||||
Disabled: opts.disable,
|
|
||||||
AcceptAllPermissions: opts.grantPerms,
|
|
||||||
AcceptPermissionsFunc: acceptPrivileges(dockerCli, opts.name),
|
|
||||||
// TODO: Rename PrivilegeFunc, it has nothing to do with privileges
|
|
||||||
PrivilegeFunc: registryAuthFunc,
|
|
||||||
}
|
|
||||||
|
|
||||||
return dockerCli.Client().PluginInstall(ctx, ref.String(), options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func acceptPrivileges(dockerCli *client.DockerCli, name string) func(privileges types.PluginPrivileges) (bool, error) {
|
|
||||||
return func(privileges types.PluginPrivileges) (bool, error) {
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "Plugin %q is requesting the following privileges:\n", name)
|
|
||||||
for _, privilege := range privileges {
|
|
||||||
fmt.Fprintf(dockerCli.Out(), " - %s: %v\n", privilege.Name, privilege.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprint(dockerCli.Out(), "Do you grant the above permissions? [y/N] ")
|
|
||||||
reader := bufio.NewReader(dockerCli.In())
|
|
||||||
line, _, err := reader.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return strings.ToLower(string(line)) == "y", nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
// +build experimental
|
|
||||||
|
|
||||||
package plugin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newListCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "ls",
|
|
||||||
Short: "List plugins",
|
|
||||||
Aliases: []string{"list"},
|
|
||||||
Args: cli.ExactArgs(0),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return runList(dockerCli)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runList(dockerCli *client.DockerCli) error {
|
|
||||||
plugins, err := dockerCli.Client().PluginList(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w := tabwriter.NewWriter(dockerCli.Out(), 20, 1, 3, ' ', 0)
|
|
||||||
fmt.Fprintf(w, "NAME \tTAG \tACTIVE")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
for _, p := range plugins {
|
|
||||||
fmt.Fprintf(w, "%s\t%s\t%v\n", p.Name, p.Tag, p.Active)
|
|
||||||
}
|
|
||||||
w.Flush()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
// +build experimental
|
|
||||||
|
|
||||||
package plugin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/reference"
|
|
||||||
"github.com/docker/docker/registry"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newPushCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "push PLUGIN",
|
|
||||||
Short: "Push a plugin",
|
|
||||||
Args: cli.ExactArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return runPush(dockerCli, args[0])
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runPush(dockerCli *client.DockerCli, name string) error {
|
|
||||||
named, err := reference.ParseNamed(name) // FIXME: validate
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if reference.IsNameOnly(named) {
|
|
||||||
named = reference.WithDefaultTag(named)
|
|
||||||
}
|
|
||||||
ref, ok := named.(reference.NamedTagged)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("invalid name: %s", named.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
repoInfo, err := registry.ParseRepositoryInfo(named)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
authConfig := dockerCli.ResolveAuthConfig(ctx, repoInfo.Index)
|
|
||||||
|
|
||||||
encodedAuth, err := client.EncodeAuthToBase64(authConfig)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return dockerCli.Client().PluginPush(ctx, ref.String(), encodedAuth)
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
// +build experimental
|
|
||||||
|
|
||||||
package plugin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/reference"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newRemoveCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "rm PLUGIN",
|
|
||||||
Short: "Remove a plugin",
|
|
||||||
Aliases: []string{"remove"},
|
|
||||||
Args: cli.RequiresMinArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return runRemove(dockerCli, args)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runRemove(dockerCli *client.DockerCli, names []string) error {
|
|
||||||
var errs cli.Errors
|
|
||||||
for _, name := range names {
|
|
||||||
named, err := reference.ParseNamed(name) // FIXME: validate
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if reference.IsNameOnly(named) {
|
|
||||||
named = reference.WithDefaultTag(named)
|
|
||||||
}
|
|
||||||
ref, ok := named.(reference.NamedTagged)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("invalid name: %s", named.String())
|
|
||||||
}
|
|
||||||
// TODO: pass names to api instead of making multiple api calls
|
|
||||||
if err := dockerCli.Client().PluginRemove(context.Background(), ref.String()); err != nil {
|
|
||||||
errs = append(errs, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fmt.Fprintln(dockerCli.Out(), name)
|
|
||||||
}
|
|
||||||
// Do not simplify to `return errs` because even if errs == nil, it is not a nil-error interface value.
|
|
||||||
if errs != nil {
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
// +build experimental
|
|
||||||
|
|
||||||
package plugin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/reference"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newSetCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "set PLUGIN key1=value1 [key2=value2...]",
|
|
||||||
Short: "Change settings for a plugin",
|
|
||||||
Args: cli.RequiresMinArgs(2),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return runSet(dockerCli, args[0], args[1:])
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runSet(dockerCli *client.DockerCli, name string, args []string) error {
|
|
||||||
named, err := reference.ParseNamed(name) // FIXME: validate
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if reference.IsNameOnly(named) {
|
|
||||||
named = reference.WithDefaultTag(named)
|
|
||||||
}
|
|
||||||
ref, ok := named.(reference.NamedTagged)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("invalid name: %s", named.String())
|
|
||||||
}
|
|
||||||
return dockerCli.Client().PluginSet(context.Background(), ref.String(), args)
|
|
||||||
}
|
|
||||||
|
|
@ -1,188 +0,0 @@
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/term"
|
|
||||||
"github.com/docker/docker/reference"
|
|
||||||
"github.com/docker/docker/registry"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
registrytypes "github.com/docker/engine-api/types/registry"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ElectAuthServer returns the default registry to use (by asking the daemon)
|
|
||||||
func (cli *DockerCli) ElectAuthServer(ctx context.Context) string {
|
|
||||||
// The daemon `/info` endpoint informs us of the default registry being
|
|
||||||
// used. This is essential in cross-platforms environment, where for
|
|
||||||
// example a Linux client might be interacting with a Windows daemon, hence
|
|
||||||
// the default registry URL might be Windows specific.
|
|
||||||
serverAddress := registry.IndexServer
|
|
||||||
if info, err := cli.client.Info(ctx); err != nil {
|
|
||||||
fmt.Fprintf(cli.out, "Warning: failed to get default registry endpoint from daemon (%v). Using system default: %s\n", err, serverAddress)
|
|
||||||
} else {
|
|
||||||
serverAddress = info.IndexServerAddress
|
|
||||||
}
|
|
||||||
return serverAddress
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeAuthToBase64 serializes the auth configuration as JSON base64 payload
|
|
||||||
func EncodeAuthToBase64(authConfig types.AuthConfig) (string, error) {
|
|
||||||
buf, err := json.Marshal(authConfig)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return base64.URLEncoding.EncodeToString(buf), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
|
|
||||||
// for the given command.
|
|
||||||
func (cli *DockerCli) RegistryAuthenticationPrivilegedFunc(index *registrytypes.IndexInfo, cmdName string) types.RequestPrivilegeFunc {
|
|
||||||
return func() (string, error) {
|
|
||||||
fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName)
|
|
||||||
indexServer := registry.GetAuthConfigKey(index)
|
|
||||||
authConfig, err := cli.ConfigureAuth("", "", indexServer, false)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return EncodeAuthToBase64(authConfig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) promptWithDefault(prompt string, configDefault string) {
|
|
||||||
if configDefault == "" {
|
|
||||||
fmt.Fprintf(cli.out, "%s: ", prompt)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(cli.out, "%s (%s): ", prompt, configDefault)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResolveAuthConfig is like registry.ResolveAuthConfig, but if using the
|
|
||||||
// default index, it uses the default index name for the daemon's platform,
|
|
||||||
// not the client's platform.
|
|
||||||
func (cli *DockerCli) ResolveAuthConfig(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig {
|
|
||||||
configKey := index.Name
|
|
||||||
if index.Official {
|
|
||||||
configKey = cli.ElectAuthServer(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
a, _ := GetCredentials(cli.configFile, configKey)
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
// RetrieveAuthConfigs return all credentials.
|
|
||||||
func (cli *DockerCli) RetrieveAuthConfigs() map[string]types.AuthConfig {
|
|
||||||
acs, _ := GetAllCredentials(cli.configFile)
|
|
||||||
return acs
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConfigureAuth returns an AuthConfig from the specified user, password and server.
|
|
||||||
func (cli *DockerCli) ConfigureAuth(flUser, flPassword, serverAddress string, isDefaultRegistry bool) (types.AuthConfig, error) {
|
|
||||||
// On Windows, force the use of the regular OS stdin stream. Fixes #14336/#14210
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
cli.in = os.Stdin
|
|
||||||
}
|
|
||||||
|
|
||||||
authconfig, err := GetCredentials(cli.configFile, serverAddress)
|
|
||||||
if err != nil {
|
|
||||||
return authconfig, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some links documenting this:
|
|
||||||
// - https://code.google.com/archive/p/mintty/issues/56
|
|
||||||
// - https://github.com/docker/docker/issues/15272
|
|
||||||
// - https://mintty.github.io/ (compatibility)
|
|
||||||
// Linux will hit this if you attempt `cat | docker login`, and Windows
|
|
||||||
// will hit this if you attempt docker login from mintty where stdin
|
|
||||||
// is a pipe, not a character based console.
|
|
||||||
if flPassword == "" && !cli.isTerminalIn {
|
|
||||||
return authconfig, fmt.Errorf("Error: Cannot perform an interactive login from a non TTY device")
|
|
||||||
}
|
|
||||||
|
|
||||||
authconfig.Username = strings.TrimSpace(authconfig.Username)
|
|
||||||
|
|
||||||
if flUser = strings.TrimSpace(flUser); flUser == "" {
|
|
||||||
if isDefaultRegistry {
|
|
||||||
// if this is a default registry (docker hub), then display the following message.
|
|
||||||
fmt.Fprintln(cli.out, "Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.")
|
|
||||||
}
|
|
||||||
cli.promptWithDefault("Username", authconfig.Username)
|
|
||||||
flUser = readInput(cli.in, cli.out)
|
|
||||||
flUser = strings.TrimSpace(flUser)
|
|
||||||
if flUser == "" {
|
|
||||||
flUser = authconfig.Username
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if flUser == "" {
|
|
||||||
return authconfig, fmt.Errorf("Error: Non-null Username Required")
|
|
||||||
}
|
|
||||||
if flPassword == "" {
|
|
||||||
oldState, err := term.SaveState(cli.inFd)
|
|
||||||
if err != nil {
|
|
||||||
return authconfig, err
|
|
||||||
}
|
|
||||||
fmt.Fprintf(cli.out, "Password: ")
|
|
||||||
term.DisableEcho(cli.inFd, oldState)
|
|
||||||
|
|
||||||
flPassword = readInput(cli.in, cli.out)
|
|
||||||
fmt.Fprint(cli.out, "\n")
|
|
||||||
|
|
||||||
term.RestoreTerminal(cli.inFd, oldState)
|
|
||||||
if flPassword == "" {
|
|
||||||
return authconfig, fmt.Errorf("Error: Password Required")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
authconfig.Username = flUser
|
|
||||||
authconfig.Password = flPassword
|
|
||||||
authconfig.ServerAddress = serverAddress
|
|
||||||
authconfig.IdentityToken = ""
|
|
||||||
|
|
||||||
return authconfig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolveAuthConfigFromImage retrieves that AuthConfig using the image string
|
|
||||||
func (cli *DockerCli) resolveAuthConfigFromImage(ctx context.Context, image string) (types.AuthConfig, error) {
|
|
||||||
registryRef, err := reference.ParseNamed(image)
|
|
||||||
if err != nil {
|
|
||||||
return types.AuthConfig{}, err
|
|
||||||
}
|
|
||||||
repoInfo, err := registry.ParseRepositoryInfo(registryRef)
|
|
||||||
if err != nil {
|
|
||||||
return types.AuthConfig{}, err
|
|
||||||
}
|
|
||||||
authConfig := cli.ResolveAuthConfig(ctx, repoInfo.Index)
|
|
||||||
return authConfig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RetrieveAuthTokenFromImage retrieves an encoded auth token given a complete image
|
|
||||||
func (cli *DockerCli) RetrieveAuthTokenFromImage(ctx context.Context, image string) (string, error) {
|
|
||||||
// Retrieve encoded auth token from the image reference
|
|
||||||
authConfig, err := cli.resolveAuthConfigFromImage(ctx, image)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
encodedAuth, err := EncodeAuthToBase64(authConfig)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return encodedAuth, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readInput(in io.Reader, out io.Writer) string {
|
|
||||||
reader := bufio.NewReader(in)
|
|
||||||
line, _, err := reader.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(out, err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return string(line)
|
|
||||||
}
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
package registry
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type loginOptions struct {
|
|
||||||
serverAddress string
|
|
||||||
user string
|
|
||||||
password string
|
|
||||||
email string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewLoginCommand creates a new `docker login` command
|
|
||||||
func NewLoginCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts loginOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "login [OPTIONS] [SERVER]",
|
|
||||||
Short: "Log in to a Docker registry.",
|
|
||||||
Long: "Log in to a Docker registry.\nIf no server is specified, the default is defined by the daemon.",
|
|
||||||
Args: cli.RequiresMaxArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
if len(args) > 0 {
|
|
||||||
opts.serverAddress = args[0]
|
|
||||||
}
|
|
||||||
return runLogin(dockerCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
|
|
||||||
flags.StringVarP(&opts.user, "username", "u", "", "Username")
|
|
||||||
flags.StringVarP(&opts.password, "password", "p", "", "Password")
|
|
||||||
|
|
||||||
// Deprecated in 1.11: Should be removed in docker 1.13
|
|
||||||
flags.StringVarP(&opts.email, "email", "e", "", "Email")
|
|
||||||
flags.MarkDeprecated("email", "will be removed in 1.13.")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runLogin(dockerCli *client.DockerCli, opts loginOptions) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
clnt := dockerCli.Client()
|
|
||||||
|
|
||||||
var serverAddress string
|
|
||||||
var isDefaultRegistry bool
|
|
||||||
if opts.serverAddress != "" {
|
|
||||||
serverAddress = opts.serverAddress
|
|
||||||
} else {
|
|
||||||
serverAddress = dockerCli.ElectAuthServer(ctx)
|
|
||||||
isDefaultRegistry = true
|
|
||||||
}
|
|
||||||
authConfig, err := dockerCli.ConfigureAuth(opts.user, opts.password, serverAddress, isDefaultRegistry)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
response, err := clnt.RegistryLogin(ctx, authConfig)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if response.IdentityToken != "" {
|
|
||||||
authConfig.Password = ""
|
|
||||||
authConfig.IdentityToken = response.IdentityToken
|
|
||||||
}
|
|
||||||
if err := client.StoreCredentials(dockerCli.ConfigFile(), authConfig); err != nil {
|
|
||||||
return fmt.Errorf("Error saving credentials: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if response.Status != "" {
|
|
||||||
fmt.Fprintln(dockerCli.Out(), response.Status)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
package registry
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewLogoutCommand creates a new `docker login` command
|
|
||||||
func NewLogoutCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "logout [SERVER]",
|
|
||||||
Short: "Log out from a Docker registry.",
|
|
||||||
Long: "Log out from a Docker registry.\nIf no server is specified, the default is defined by the daemon.",
|
|
||||||
Args: cli.RequiresMaxArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
var serverAddress string
|
|
||||||
if len(args) > 0 {
|
|
||||||
serverAddress = args[0]
|
|
||||||
}
|
|
||||||
return runLogout(dockerCli, serverAddress)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runLogout(dockerCli *client.DockerCli, serverAddress string) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
if serverAddress == "" {
|
|
||||||
serverAddress = dockerCli.ElectAuthServer(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if we're logged in based on the records in the config file
|
|
||||||
// which means it couldn't have user/pass cause they may be in the creds store
|
|
||||||
if _, ok := dockerCli.ConfigFile().AuthConfigs[serverAddress]; !ok {
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "Not logged in to %s\n", serverAddress)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "Remove login credentials for %s\n", serverAddress)
|
|
||||||
if err := client.EraseCredentials(dockerCli.ConfigFile(), serverAddress); err != nil {
|
|
||||||
fmt.Fprintf(dockerCli.Err(), "WARNING: could not erase credentials: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewServiceCommand returns a cobra command for `service` subcommands
|
|
||||||
func NewServiceCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "service",
|
|
||||||
Short: "Manage Docker services",
|
|
||||||
Args: cli.NoArgs,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cmd.AddCommand(
|
|
||||||
newCreateCommand(dockerCli),
|
|
||||||
newInspectCommand(dockerCli),
|
|
||||||
newTasksCommand(dockerCli),
|
|
||||||
newListCommand(dockerCli),
|
|
||||||
newRemoveCommand(dockerCli),
|
|
||||||
newScaleCommand(dockerCli),
|
|
||||||
newUpdateCommand(dockerCli),
|
|
||||||
)
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newCreateCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
opts := newServiceOptions()
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "create [OPTIONS] IMAGE [COMMAND] [ARG...]",
|
|
||||||
Short: "Create a new service",
|
|
||||||
Args: cli.RequiresMinArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.image = args[0]
|
|
||||||
if len(args) > 1 {
|
|
||||||
opts.args = args[1:]
|
|
||||||
}
|
|
||||||
return runCreate(dockerCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.StringVar(&opts.mode, flagMode, "replicated", "Service mode (replicated or global)")
|
|
||||||
addServiceFlags(cmd, opts)
|
|
||||||
|
|
||||||
flags.VarP(&opts.labels, flagLabel, "l", "Service labels")
|
|
||||||
flags.VarP(&opts.env, flagEnv, "e", "Set environment variables")
|
|
||||||
flags.Var(&opts.mounts, flagMount, "Attach a mount to the service")
|
|
||||||
flags.StringSliceVar(&opts.constraints, flagConstraint, []string{}, "Placement constraints")
|
|
||||||
flags.StringSliceVar(&opts.networks, flagNetwork, []string{}, "Network attachments")
|
|
||||||
flags.VarP(&opts.endpoint.ports, flagPublish, "p", "Publish a port as a node port")
|
|
||||||
|
|
||||||
flags.SetInterspersed(false)
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runCreate(dockerCli *client.DockerCli, opts *serviceOptions) error {
|
|
||||||
apiClient := dockerCli.Client()
|
|
||||||
createOpts := types.ServiceCreateOptions{}
|
|
||||||
|
|
||||||
service, err := opts.ToService()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
// only send auth if flag was set
|
|
||||||
if opts.registryAuth {
|
|
||||||
// Retrieve encoded auth token from the image reference
|
|
||||||
encodedAuth, err := dockerCli.RetrieveAuthTokenFromImage(ctx, opts.image)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
createOpts.EncodedRegistryAuth = encodedAuth
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := apiClient.ServiceCreate(ctx, service, createOpts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "%s\n", response.ID)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,171 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/api/client/inspect"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
|
||||||
apiclient "github.com/docker/engine-api/client"
|
|
||||||
"github.com/docker/engine-api/types/swarm"
|
|
||||||
"github.com/docker/go-units"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type inspectOptions struct {
|
|
||||||
refs []string
|
|
||||||
format string
|
|
||||||
pretty bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts inspectOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "inspect [OPTIONS] SERVICE [SERVICE...]",
|
|
||||||
Short: "Display detailed information on one or more services",
|
|
||||||
Args: cli.RequiresMinArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.refs = args
|
|
||||||
|
|
||||||
if opts.pretty && len(opts.format) > 0 {
|
|
||||||
return fmt.Errorf("--format is incompatible with human friendly format")
|
|
||||||
}
|
|
||||||
return runInspect(dockerCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given go template")
|
|
||||||
flags.BoolVar(&opts.pretty, "pretty", false, "Print the information in a human friendly format.")
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runInspect(dockerCli *client.DockerCli, opts inspectOptions) error {
|
|
||||||
client := dockerCli.Client()
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
getRef := func(ref string) (interface{}, []byte, error) {
|
|
||||||
service, _, err := client.ServiceInspectWithRaw(ctx, ref)
|
|
||||||
if err == nil || !apiclient.IsErrServiceNotFound(err) {
|
|
||||||
return service, nil, err
|
|
||||||
}
|
|
||||||
return nil, nil, fmt.Errorf("Error: no such service: %s", ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !opts.pretty {
|
|
||||||
return inspect.Inspect(dockerCli.Out(), opts.refs, opts.format, getRef)
|
|
||||||
}
|
|
||||||
|
|
||||||
return printHumanFriendly(dockerCli.Out(), opts.refs, getRef)
|
|
||||||
}
|
|
||||||
|
|
||||||
func printHumanFriendly(out io.Writer, refs []string, getRef inspect.GetRefFunc) error {
|
|
||||||
for idx, ref := range refs {
|
|
||||||
obj, _, err := getRef(ref)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
printService(out, obj.(swarm.Service))
|
|
||||||
|
|
||||||
// TODO: better way to do this?
|
|
||||||
// print extra space between objects, but not after the last one
|
|
||||||
if idx+1 != len(refs) {
|
|
||||||
fmt.Fprintf(out, "\n\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: use a template
|
|
||||||
func printService(out io.Writer, service swarm.Service) {
|
|
||||||
fmt.Fprintf(out, "ID:\t\t%s\n", service.ID)
|
|
||||||
fmt.Fprintf(out, "Name:\t\t%s\n", service.Spec.Name)
|
|
||||||
if service.Spec.Labels != nil {
|
|
||||||
fmt.Fprintln(out, "Labels:")
|
|
||||||
for k, v := range service.Spec.Labels {
|
|
||||||
fmt.Fprintf(out, " - %s=%s\n", k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if service.Spec.Mode.Global != nil {
|
|
||||||
fmt.Fprintln(out, "Mode:\t\tGlobal")
|
|
||||||
} else {
|
|
||||||
fmt.Fprintln(out, "Mode:\t\tReplicated")
|
|
||||||
if service.Spec.Mode.Replicated.Replicas != nil {
|
|
||||||
fmt.Fprintf(out, " Replicas:\t%d\n", *service.Spec.Mode.Replicated.Replicas)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Fprintln(out, "Placement:")
|
|
||||||
if service.Spec.TaskTemplate.Placement != nil && len(service.Spec.TaskTemplate.Placement.Constraints) > 0 {
|
|
||||||
ioutils.FprintfIfNotEmpty(out, " Constraints\t: %s\n", strings.Join(service.Spec.TaskTemplate.Placement.Constraints, ", "))
|
|
||||||
}
|
|
||||||
fmt.Fprintf(out, "UpdateConfig:\n")
|
|
||||||
fmt.Fprintf(out, " Parallelism:\t%d\n", service.Spec.UpdateConfig.Parallelism)
|
|
||||||
if service.Spec.UpdateConfig.Delay.Nanoseconds() > 0 {
|
|
||||||
fmt.Fprintf(out, " Delay:\t\t%s\n", service.Spec.UpdateConfig.Delay)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(out, "ContainerSpec:\n")
|
|
||||||
printContainerSpec(out, service.Spec.TaskTemplate.ContainerSpec)
|
|
||||||
|
|
||||||
resources := service.Spec.TaskTemplate.Resources
|
|
||||||
if resources != nil {
|
|
||||||
fmt.Fprintln(out, "Resources:")
|
|
||||||
printResources := func(out io.Writer, requirement string, r *swarm.Resources) {
|
|
||||||
if r == nil || (r.MemoryBytes == 0 && r.NanoCPUs == 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Fprintf(out, " %s:\n", requirement)
|
|
||||||
if r.NanoCPUs != 0 {
|
|
||||||
fmt.Fprintf(out, " CPU:\t\t%g\n", float64(r.NanoCPUs)/1e9)
|
|
||||||
}
|
|
||||||
if r.MemoryBytes != 0 {
|
|
||||||
fmt.Fprintf(out, " Memory:\t%s\n", units.BytesSize(float64(r.MemoryBytes)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
printResources(out, "Reservations", resources.Reservations)
|
|
||||||
printResources(out, "Limits", resources.Limits)
|
|
||||||
}
|
|
||||||
if len(service.Spec.Networks) > 0 {
|
|
||||||
fmt.Fprintf(out, "Networks:")
|
|
||||||
for _, n := range service.Spec.Networks {
|
|
||||||
fmt.Fprintf(out, " %s", n.Target)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(service.Endpoint.Ports) > 0 {
|
|
||||||
fmt.Fprintln(out, "Ports:")
|
|
||||||
for _, port := range service.Endpoint.Ports {
|
|
||||||
fmt.Fprintf(out, " Name = %s\n", port.Name)
|
|
||||||
fmt.Fprintf(out, " Protocol = %s\n", port.Protocol)
|
|
||||||
fmt.Fprintf(out, " TargetPort = %d\n", port.TargetPort)
|
|
||||||
fmt.Fprintf(out, " PublishedPort = %d\n", port.PublishedPort)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func printContainerSpec(out io.Writer, containerSpec swarm.ContainerSpec) {
|
|
||||||
fmt.Fprintf(out, " Image:\t\t%s\n", containerSpec.Image)
|
|
||||||
if len(containerSpec.Args) > 0 {
|
|
||||||
fmt.Fprintf(out, " Args:\t\t%s\n", strings.Join(containerSpec.Args, " "))
|
|
||||||
}
|
|
||||||
if len(containerSpec.Env) > 0 {
|
|
||||||
fmt.Fprintf(out, " Env:\t\t%s\n", strings.Join(containerSpec.Env, " "))
|
|
||||||
}
|
|
||||||
ioutils.FprintfIfNotEmpty(out, " Dir\t\t%s\n", containerSpec.Dir)
|
|
||||||
ioutils.FprintfIfNotEmpty(out, " User\t\t%s\n", containerSpec.User)
|
|
||||||
if len(containerSpec.Mounts) > 0 {
|
|
||||||
fmt.Fprintln(out, " Mounts:")
|
|
||||||
for _, v := range containerSpec.Mounts {
|
|
||||||
fmt.Fprintf(out, " Target = %s\n", v.Target)
|
|
||||||
fmt.Fprintf(out, " Source = %s\n", v.Source)
|
|
||||||
fmt.Fprintf(out, " ReadOnly = %v\n", v.ReadOnly)
|
|
||||||
fmt.Fprintf(out, " Type = %v\n", v.Type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,124 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/docker/docker/pkg/stringid"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
"github.com/docker/engine-api/types/filters"
|
|
||||||
"github.com/docker/engine-api/types/swarm"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
listItemFmt = "%s\t%s\t%s\t%s\t%s\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
type listOptions struct {
|
|
||||||
quiet bool
|
|
||||||
filter opts.FilterOpt
|
|
||||||
}
|
|
||||||
|
|
||||||
func newListCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
opts := listOptions{filter: opts.NewFilterOpt()}
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "ls",
|
|
||||||
Aliases: []string{"list"},
|
|
||||||
Short: "List services",
|
|
||||||
Args: cli.NoArgs,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return runList(dockerCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs")
|
|
||||||
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runList(dockerCli *client.DockerCli, opts listOptions) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
client := dockerCli.Client()
|
|
||||||
|
|
||||||
services, err := client.ServiceList(ctx, types.ServiceListOptions{Filter: opts.filter.Value()})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
out := dockerCli.Out()
|
|
||||||
if opts.quiet {
|
|
||||||
printQuiet(out, services)
|
|
||||||
} else {
|
|
||||||
taskFilter := filters.NewArgs()
|
|
||||||
for _, service := range services {
|
|
||||||
taskFilter.Add("service", service.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks, err := client.TaskList(ctx, types.TaskListOptions{Filter: taskFilter})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
nodes, err := client.NodeList(ctx, types.NodeListOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
activeNodes := make(map[string]struct{})
|
|
||||||
for _, n := range nodes {
|
|
||||||
if n.Status.State == swarm.NodeStateReady {
|
|
||||||
activeNodes[n.ID] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
running := map[string]int{}
|
|
||||||
for _, task := range tasks {
|
|
||||||
if _, nodeActive := activeNodes[task.NodeID]; nodeActive && task.Status.State == "running" {
|
|
||||||
running[task.ServiceID]++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
printTable(out, services, running)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func printTable(out io.Writer, services []swarm.Service, running map[string]int) {
|
|
||||||
writer := tabwriter.NewWriter(out, 0, 4, 2, ' ', 0)
|
|
||||||
|
|
||||||
// Ignore flushing errors
|
|
||||||
defer writer.Flush()
|
|
||||||
|
|
||||||
fmt.Fprintf(writer, listItemFmt, "ID", "NAME", "REPLICAS", "IMAGE", "COMMAND")
|
|
||||||
for _, service := range services {
|
|
||||||
replicas := ""
|
|
||||||
if service.Spec.Mode.Replicated != nil && service.Spec.Mode.Replicated.Replicas != nil {
|
|
||||||
replicas = fmt.Sprintf("%d/%d", running[service.ID], *service.Spec.Mode.Replicated.Replicas)
|
|
||||||
} else if service.Spec.Mode.Global != nil {
|
|
||||||
replicas = "global"
|
|
||||||
}
|
|
||||||
fmt.Fprintf(
|
|
||||||
writer,
|
|
||||||
listItemFmt,
|
|
||||||
stringid.TruncateID(service.ID),
|
|
||||||
service.Spec.Name,
|
|
||||||
replicas,
|
|
||||||
service.Spec.TaskTemplate.ContainerSpec.Image,
|
|
||||||
strings.Join(service.Spec.TaskTemplate.ContainerSpec.Args, " "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func printQuiet(out io.Writer, services []swarm.Service) {
|
|
||||||
for _, service := range services {
|
|
||||||
fmt.Fprintln(out, service.ID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,523 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/csv"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
runconfigopts "github.com/docker/docker/runconfig/opts"
|
|
||||||
"github.com/docker/engine-api/types/swarm"
|
|
||||||
"github.com/docker/go-connections/nat"
|
|
||||||
units "github.com/docker/go-units"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// DefaultReplicas is the default replicas to use for a replicated service
|
|
||||||
DefaultReplicas uint64 = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
type int64Value interface {
|
|
||||||
Value() int64
|
|
||||||
}
|
|
||||||
|
|
||||||
type memBytes int64
|
|
||||||
|
|
||||||
func (m *memBytes) String() string {
|
|
||||||
return units.BytesSize(float64(m.Value()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *memBytes) Set(value string) error {
|
|
||||||
val, err := units.RAMInBytes(value)
|
|
||||||
*m = memBytes(val)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *memBytes) Type() string {
|
|
||||||
return "MemoryBytes"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *memBytes) Value() int64 {
|
|
||||||
return int64(*m)
|
|
||||||
}
|
|
||||||
|
|
||||||
type nanoCPUs int64
|
|
||||||
|
|
||||||
func (c *nanoCPUs) String() string {
|
|
||||||
return big.NewRat(c.Value(), 1e9).FloatString(3)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *nanoCPUs) Set(value string) error {
|
|
||||||
cpu, ok := new(big.Rat).SetString(value)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("Failed to parse %v as a rational number", value)
|
|
||||||
}
|
|
||||||
nano := cpu.Mul(cpu, big.NewRat(1e9, 1))
|
|
||||||
if !nano.IsInt() {
|
|
||||||
return fmt.Errorf("value is too precise")
|
|
||||||
}
|
|
||||||
*c = nanoCPUs(nano.Num().Int64())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *nanoCPUs) Type() string {
|
|
||||||
return "NanoCPUs"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *nanoCPUs) Value() int64 {
|
|
||||||
return int64(*c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DurationOpt is an option type for time.Duration that uses a pointer. This
|
|
||||||
// allows us to get nil values outside, instead of defaulting to 0
|
|
||||||
type DurationOpt struct {
|
|
||||||
value *time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set a new value on the option
|
|
||||||
func (d *DurationOpt) Set(s string) error {
|
|
||||||
v, err := time.ParseDuration(s)
|
|
||||||
d.value = &v
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type returns the type of this option
|
|
||||||
func (d *DurationOpt) Type() string {
|
|
||||||
return "duration-ptr"
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a string repr of this option
|
|
||||||
func (d *DurationOpt) String() string {
|
|
||||||
if d.value != nil {
|
|
||||||
return d.value.String()
|
|
||||||
}
|
|
||||||
return "none"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value returns the time.Duration
|
|
||||||
func (d *DurationOpt) Value() *time.Duration {
|
|
||||||
return d.value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint64Opt represents a uint64.
|
|
||||||
type Uint64Opt struct {
|
|
||||||
value *uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set a new value on the option
|
|
||||||
func (i *Uint64Opt) Set(s string) error {
|
|
||||||
v, err := strconv.ParseUint(s, 0, 64)
|
|
||||||
i.value = &v
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type returns the type of this option
|
|
||||||
func (i *Uint64Opt) Type() string {
|
|
||||||
return "uint64-ptr"
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a string repr of this option
|
|
||||||
func (i *Uint64Opt) String() string {
|
|
||||||
if i.value != nil {
|
|
||||||
return fmt.Sprintf("%v", *i.value)
|
|
||||||
}
|
|
||||||
return "none"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value returns the uint64
|
|
||||||
func (i *Uint64Opt) Value() *uint64 {
|
|
||||||
return i.value
|
|
||||||
}
|
|
||||||
|
|
||||||
// MountOpt is a Value type for parsing mounts
|
|
||||||
type MountOpt struct {
|
|
||||||
values []swarm.Mount
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set a new mount value
|
|
||||||
func (m *MountOpt) Set(value string) error {
|
|
||||||
csvReader := csv.NewReader(strings.NewReader(value))
|
|
||||||
fields, err := csvReader.Read()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
mount := swarm.Mount{}
|
|
||||||
|
|
||||||
volumeOptions := func() *swarm.VolumeOptions {
|
|
||||||
if mount.VolumeOptions == nil {
|
|
||||||
mount.VolumeOptions = &swarm.VolumeOptions{
|
|
||||||
Labels: make(map[string]string),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if mount.VolumeOptions.DriverConfig == nil {
|
|
||||||
mount.VolumeOptions.DriverConfig = &swarm.Driver{}
|
|
||||||
}
|
|
||||||
return mount.VolumeOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
bindOptions := func() *swarm.BindOptions {
|
|
||||||
if mount.BindOptions == nil {
|
|
||||||
mount.BindOptions = new(swarm.BindOptions)
|
|
||||||
}
|
|
||||||
return mount.BindOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
setValueOnMap := func(target map[string]string, value string) {
|
|
||||||
parts := strings.SplitN(value, "=", 2)
|
|
||||||
if len(parts) == 1 {
|
|
||||||
target[value] = ""
|
|
||||||
} else {
|
|
||||||
target[parts[0]] = parts[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set writable as the default
|
|
||||||
for _, field := range fields {
|
|
||||||
parts := strings.SplitN(field, "=", 2)
|
|
||||||
if len(parts) == 1 && strings.ToLower(parts[0]) == "readonly" {
|
|
||||||
mount.ReadOnly = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(parts) == 1 && strings.ToLower(parts[0]) == "volume-nocopy" {
|
|
||||||
volumeOptions().NoCopy = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(parts) != 2 {
|
|
||||||
return fmt.Errorf("invalid field '%s' must be a key=value pair", field)
|
|
||||||
}
|
|
||||||
|
|
||||||
key, value := parts[0], parts[1]
|
|
||||||
switch strings.ToLower(key) {
|
|
||||||
case "type":
|
|
||||||
mount.Type = swarm.MountType(strings.ToUpper(value))
|
|
||||||
case "source":
|
|
||||||
mount.Source = value
|
|
||||||
case "target":
|
|
||||||
mount.Target = value
|
|
||||||
case "readonly":
|
|
||||||
ro, err := strconv.ParseBool(value)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid value for readonly: %s", value)
|
|
||||||
}
|
|
||||||
mount.ReadOnly = ro
|
|
||||||
case "bind-propagation":
|
|
||||||
bindOptions().Propagation = swarm.MountPropagation(strings.ToUpper(value))
|
|
||||||
case "volume-nocopy":
|
|
||||||
volumeOptions().NoCopy, err = strconv.ParseBool(value)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid value for populate: %s", value)
|
|
||||||
}
|
|
||||||
case "volume-label":
|
|
||||||
setValueOnMap(volumeOptions().Labels, value)
|
|
||||||
case "volume-driver":
|
|
||||||
volumeOptions().DriverConfig.Name = value
|
|
||||||
case "volume-driver-opt":
|
|
||||||
if volumeOptions().DriverConfig.Options == nil {
|
|
||||||
volumeOptions().DriverConfig.Options = make(map[string]string)
|
|
||||||
}
|
|
||||||
setValueOnMap(volumeOptions().DriverConfig.Options, value)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unexpected key '%s' in '%s'", key, field)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if mount.Type == "" {
|
|
||||||
return fmt.Errorf("type is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
if mount.Target == "" {
|
|
||||||
return fmt.Errorf("target is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
if mount.VolumeOptions != nil && mount.Source == "" {
|
|
||||||
return fmt.Errorf("source is required when specifying volume-* options")
|
|
||||||
}
|
|
||||||
|
|
||||||
if mount.Type == swarm.MountType("BIND") && mount.VolumeOptions != nil {
|
|
||||||
return fmt.Errorf("cannot mix 'volume-*' options with mount type '%s'", swarm.MountTypeBind)
|
|
||||||
}
|
|
||||||
if mount.Type == swarm.MountType("VOLUME") && mount.BindOptions != nil {
|
|
||||||
return fmt.Errorf("cannot mix 'bind-*' options with mount type '%s'", swarm.MountTypeVolume)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.values = append(m.values, mount)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type returns the type of this option
|
|
||||||
func (m *MountOpt) Type() string {
|
|
||||||
return "mount"
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a string repr of this option
|
|
||||||
func (m *MountOpt) String() string {
|
|
||||||
mounts := []string{}
|
|
||||||
for _, mount := range m.values {
|
|
||||||
repr := fmt.Sprintf("%s %s %s", mount.Type, mount.Source, mount.Target)
|
|
||||||
mounts = append(mounts, repr)
|
|
||||||
}
|
|
||||||
return strings.Join(mounts, ", ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value returns the mounts
|
|
||||||
func (m *MountOpt) Value() []swarm.Mount {
|
|
||||||
return m.values
|
|
||||||
}
|
|
||||||
|
|
||||||
type updateOptions struct {
|
|
||||||
parallelism uint64
|
|
||||||
delay time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
type resourceOptions struct {
|
|
||||||
limitCPU nanoCPUs
|
|
||||||
limitMemBytes memBytes
|
|
||||||
resCPU nanoCPUs
|
|
||||||
resMemBytes memBytes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *resourceOptions) ToResourceRequirements() *swarm.ResourceRequirements {
|
|
||||||
return &swarm.ResourceRequirements{
|
|
||||||
Limits: &swarm.Resources{
|
|
||||||
NanoCPUs: r.limitCPU.Value(),
|
|
||||||
MemoryBytes: r.limitMemBytes.Value(),
|
|
||||||
},
|
|
||||||
Reservations: &swarm.Resources{
|
|
||||||
NanoCPUs: r.resCPU.Value(),
|
|
||||||
MemoryBytes: r.resMemBytes.Value(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type restartPolicyOptions struct {
|
|
||||||
condition string
|
|
||||||
delay DurationOpt
|
|
||||||
maxAttempts Uint64Opt
|
|
||||||
window DurationOpt
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *restartPolicyOptions) ToRestartPolicy() *swarm.RestartPolicy {
|
|
||||||
return &swarm.RestartPolicy{
|
|
||||||
Condition: swarm.RestartPolicyCondition(r.condition),
|
|
||||||
Delay: r.delay.Value(),
|
|
||||||
MaxAttempts: r.maxAttempts.Value(),
|
|
||||||
Window: r.window.Value(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertNetworks(networks []string) []swarm.NetworkAttachmentConfig {
|
|
||||||
nets := []swarm.NetworkAttachmentConfig{}
|
|
||||||
for _, network := range networks {
|
|
||||||
nets = append(nets, swarm.NetworkAttachmentConfig{Target: network})
|
|
||||||
}
|
|
||||||
return nets
|
|
||||||
}
|
|
||||||
|
|
||||||
type endpointOptions struct {
|
|
||||||
mode string
|
|
||||||
ports opts.ListOpts
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *endpointOptions) ToEndpointSpec() *swarm.EndpointSpec {
|
|
||||||
portConfigs := []swarm.PortConfig{}
|
|
||||||
// We can ignore errors because the format was already validated by ValidatePort
|
|
||||||
ports, portBindings, _ := nat.ParsePortSpecs(e.ports.GetAll())
|
|
||||||
|
|
||||||
for port := range ports {
|
|
||||||
portConfigs = append(portConfigs, convertPortToPortConfig(port, portBindings)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &swarm.EndpointSpec{
|
|
||||||
Mode: swarm.ResolutionMode(strings.ToLower(e.mode)),
|
|
||||||
Ports: portConfigs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertPortToPortConfig(
|
|
||||||
port nat.Port,
|
|
||||||
portBindings map[nat.Port][]nat.PortBinding,
|
|
||||||
) []swarm.PortConfig {
|
|
||||||
ports := []swarm.PortConfig{}
|
|
||||||
|
|
||||||
for _, binding := range portBindings[port] {
|
|
||||||
hostPort, _ := strconv.ParseUint(binding.HostPort, 10, 16)
|
|
||||||
ports = append(ports, swarm.PortConfig{
|
|
||||||
//TODO Name: ?
|
|
||||||
Protocol: swarm.PortConfigProtocol(strings.ToLower(port.Proto())),
|
|
||||||
TargetPort: uint32(port.Int()),
|
|
||||||
PublishedPort: uint32(hostPort),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return ports
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidatePort validates a string is in the expected format for a port definition
|
|
||||||
func ValidatePort(value string) (string, error) {
|
|
||||||
portMappings, err := nat.ParsePortSpec(value)
|
|
||||||
for _, portMapping := range portMappings {
|
|
||||||
if portMapping.Binding.HostIP != "" {
|
|
||||||
return "", fmt.Errorf("HostIP is not supported by a service.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value, err
|
|
||||||
}
|
|
||||||
|
|
||||||
type serviceOptions struct {
|
|
||||||
name string
|
|
||||||
labels opts.ListOpts
|
|
||||||
image string
|
|
||||||
args []string
|
|
||||||
env opts.ListOpts
|
|
||||||
workdir string
|
|
||||||
user string
|
|
||||||
mounts MountOpt
|
|
||||||
|
|
||||||
resources resourceOptions
|
|
||||||
stopGrace DurationOpt
|
|
||||||
|
|
||||||
replicas Uint64Opt
|
|
||||||
mode string
|
|
||||||
|
|
||||||
restartPolicy restartPolicyOptions
|
|
||||||
constraints []string
|
|
||||||
update updateOptions
|
|
||||||
networks []string
|
|
||||||
endpoint endpointOptions
|
|
||||||
|
|
||||||
registryAuth bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newServiceOptions() *serviceOptions {
|
|
||||||
return &serviceOptions{
|
|
||||||
labels: opts.NewListOpts(runconfigopts.ValidateEnv),
|
|
||||||
env: opts.NewListOpts(runconfigopts.ValidateEnv),
|
|
||||||
endpoint: endpointOptions{
|
|
||||||
ports: opts.NewListOpts(ValidatePort),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
|
|
||||||
var service swarm.ServiceSpec
|
|
||||||
|
|
||||||
service = swarm.ServiceSpec{
|
|
||||||
Annotations: swarm.Annotations{
|
|
||||||
Name: opts.name,
|
|
||||||
Labels: runconfigopts.ConvertKVStringsToMap(opts.labels.GetAll()),
|
|
||||||
},
|
|
||||||
TaskTemplate: swarm.TaskSpec{
|
|
||||||
ContainerSpec: swarm.ContainerSpec{
|
|
||||||
Image: opts.image,
|
|
||||||
Args: opts.args,
|
|
||||||
Env: opts.env.GetAll(),
|
|
||||||
Dir: opts.workdir,
|
|
||||||
User: opts.user,
|
|
||||||
Mounts: opts.mounts.Value(),
|
|
||||||
StopGracePeriod: opts.stopGrace.Value(),
|
|
||||||
},
|
|
||||||
Resources: opts.resources.ToResourceRequirements(),
|
|
||||||
RestartPolicy: opts.restartPolicy.ToRestartPolicy(),
|
|
||||||
Placement: &swarm.Placement{
|
|
||||||
Constraints: opts.constraints,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Mode: swarm.ServiceMode{},
|
|
||||||
UpdateConfig: &swarm.UpdateConfig{
|
|
||||||
Parallelism: opts.update.parallelism,
|
|
||||||
Delay: opts.update.delay,
|
|
||||||
},
|
|
||||||
Networks: convertNetworks(opts.networks),
|
|
||||||
EndpointSpec: opts.endpoint.ToEndpointSpec(),
|
|
||||||
}
|
|
||||||
|
|
||||||
switch opts.mode {
|
|
||||||
case "global":
|
|
||||||
if opts.replicas.Value() != nil {
|
|
||||||
return service, fmt.Errorf("replicas can only be used with replicated mode")
|
|
||||||
}
|
|
||||||
|
|
||||||
service.Mode.Global = &swarm.GlobalService{}
|
|
||||||
case "replicated":
|
|
||||||
service.Mode.Replicated = &swarm.ReplicatedService{
|
|
||||||
Replicas: opts.replicas.Value(),
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return service, fmt.Errorf("Unknown mode: %s", opts.mode)
|
|
||||||
}
|
|
||||||
return service, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// addServiceFlags adds all flags that are common to both `create` and `update`.
|
|
||||||
// Any flags that are not common are added separately in the individual command
|
|
||||||
func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) {
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.StringVar(&opts.name, flagName, "", "Service name")
|
|
||||||
|
|
||||||
flags.StringVarP(&opts.workdir, "workdir", "w", "", "Working directory inside the container")
|
|
||||||
flags.StringVarP(&opts.user, flagUser, "u", "", "Username or UID")
|
|
||||||
|
|
||||||
flags.Var(&opts.resources.limitCPU, flagLimitCPU, "Limit CPUs")
|
|
||||||
flags.Var(&opts.resources.limitMemBytes, flagLimitMemory, "Limit Memory")
|
|
||||||
flags.Var(&opts.resources.resCPU, flagReserveCPU, "Reserve CPUs")
|
|
||||||
flags.Var(&opts.resources.resMemBytes, flagReserveMemory, "Reserve Memory")
|
|
||||||
flags.Var(&opts.stopGrace, flagStopGracePeriod, "Time to wait before force killing a container")
|
|
||||||
|
|
||||||
flags.Var(&opts.replicas, flagReplicas, "Number of tasks")
|
|
||||||
|
|
||||||
flags.StringVar(&opts.restartPolicy.condition, flagRestartCondition, "", "Restart when condition is met (none, on-failure, or any)")
|
|
||||||
flags.Var(&opts.restartPolicy.delay, flagRestartDelay, "Delay between restart attempts")
|
|
||||||
flags.Var(&opts.restartPolicy.maxAttempts, flagRestartMaxAttempts, "Maximum number of restarts before giving up")
|
|
||||||
flags.Var(&opts.restartPolicy.window, flagRestartWindow, "Window used to evaluate the restart policy")
|
|
||||||
|
|
||||||
flags.Uint64Var(&opts.update.parallelism, flagUpdateParallelism, 0, "Maximum number of tasks updated simultaneously")
|
|
||||||
flags.DurationVar(&opts.update.delay, flagUpdateDelay, time.Duration(0), "Delay between updates")
|
|
||||||
|
|
||||||
flags.StringVar(&opts.endpoint.mode, flagEndpointMode, "", "Endpoint mode (vip or dnsrr)")
|
|
||||||
|
|
||||||
flags.BoolVar(&opts.registryAuth, flagRegistryAuth, false, "Send registry authentication details to Swarm agents")
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
flagConstraint = "constraint"
|
|
||||||
flagConstraintRemove = "constraint-rm"
|
|
||||||
flagConstraintAdd = "constraint-add"
|
|
||||||
flagEndpointMode = "endpoint-mode"
|
|
||||||
flagEnv = "env"
|
|
||||||
flagEnvRemove = "env-rm"
|
|
||||||
flagEnvAdd = "env-add"
|
|
||||||
flagLabel = "label"
|
|
||||||
flagLabelRemove = "label-rm"
|
|
||||||
flagLabelAdd = "label-add"
|
|
||||||
flagLimitCPU = "limit-cpu"
|
|
||||||
flagLimitMemory = "limit-memory"
|
|
||||||
flagMode = "mode"
|
|
||||||
flagMount = "mount"
|
|
||||||
flagMountRemove = "mount-rm"
|
|
||||||
flagMountAdd = "mount-add"
|
|
||||||
flagName = "name"
|
|
||||||
flagNetwork = "network"
|
|
||||||
flagNetworkRemove = "network-rm"
|
|
||||||
flagNetworkAdd = "network-add"
|
|
||||||
flagPublish = "publish"
|
|
||||||
flagPublishRemove = "publish-rm"
|
|
||||||
flagPublishAdd = "publish-add"
|
|
||||||
flagReplicas = "replicas"
|
|
||||||
flagReserveCPU = "reserve-cpu"
|
|
||||||
flagReserveMemory = "reserve-memory"
|
|
||||||
flagRestartCondition = "restart-condition"
|
|
||||||
flagRestartDelay = "restart-delay"
|
|
||||||
flagRestartMaxAttempts = "restart-max-attempts"
|
|
||||||
flagRestartWindow = "restart-window"
|
|
||||||
flagStopGracePeriod = "stop-grace-period"
|
|
||||||
flagUpdateDelay = "update-delay"
|
|
||||||
flagUpdateParallelism = "update-parallelism"
|
|
||||||
flagUser = "user"
|
|
||||||
flagRegistryAuth = "registry-auth"
|
|
||||||
)
|
|
||||||
|
|
@ -1,163 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/testutil/assert"
|
|
||||||
"github.com/docker/engine-api/types/swarm"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMemBytesString(t *testing.T) {
|
|
||||||
var mem memBytes = 1048576
|
|
||||||
assert.Equal(t, mem.String(), "1 MiB")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMemBytesSetAndValue(t *testing.T) {
|
|
||||||
var mem memBytes
|
|
||||||
assert.NilError(t, mem.Set("5kb"))
|
|
||||||
assert.Equal(t, mem.Value(), int64(5120))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNanoCPUsString(t *testing.T) {
|
|
||||||
var cpus nanoCPUs = 6100000000
|
|
||||||
assert.Equal(t, cpus.String(), "6.100")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNanoCPUsSetAndValue(t *testing.T) {
|
|
||||||
var cpus nanoCPUs
|
|
||||||
assert.NilError(t, cpus.Set("0.35"))
|
|
||||||
assert.Equal(t, cpus.Value(), int64(350000000))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDurationOptString(t *testing.T) {
|
|
||||||
dur := time.Duration(300 * 10e8)
|
|
||||||
duration := DurationOpt{value: &dur}
|
|
||||||
assert.Equal(t, duration.String(), "5m0s")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDurationOptSetAndValue(t *testing.T) {
|
|
||||||
var duration DurationOpt
|
|
||||||
assert.NilError(t, duration.Set("300s"))
|
|
||||||
assert.Equal(t, *duration.Value(), time.Duration(300*10e8))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUint64OptString(t *testing.T) {
|
|
||||||
value := uint64(2345678)
|
|
||||||
opt := Uint64Opt{value: &value}
|
|
||||||
assert.Equal(t, opt.String(), "2345678")
|
|
||||||
|
|
||||||
opt = Uint64Opt{}
|
|
||||||
assert.Equal(t, opt.String(), "none")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUint64OptSetAndValue(t *testing.T) {
|
|
||||||
var opt Uint64Opt
|
|
||||||
assert.NilError(t, opt.Set("14445"))
|
|
||||||
assert.Equal(t, *opt.Value(), uint64(14445))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMountOptString(t *testing.T) {
|
|
||||||
mount := MountOpt{
|
|
||||||
values: []swarm.Mount{
|
|
||||||
{
|
|
||||||
Type: swarm.MountType("BIND"),
|
|
||||||
Source: "/home/path",
|
|
||||||
Target: "/target",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: swarm.MountType("VOLUME"),
|
|
||||||
Source: "foo",
|
|
||||||
Target: "/target/foo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
expected := "BIND /home/path /target, VOLUME foo /target/foo"
|
|
||||||
assert.Equal(t, mount.String(), expected)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMountOptSetNoError(t *testing.T) {
|
|
||||||
var mount MountOpt
|
|
||||||
assert.NilError(t, mount.Set("type=bind,target=/target,source=/foo"))
|
|
||||||
|
|
||||||
mounts := mount.Value()
|
|
||||||
assert.Equal(t, len(mounts), 1)
|
|
||||||
assert.Equal(t, mounts[0], swarm.Mount{
|
|
||||||
Type: swarm.MountType("BIND"),
|
|
||||||
Source: "/foo",
|
|
||||||
Target: "/target",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMountOptSetErrorNoType(t *testing.T) {
|
|
||||||
var mount MountOpt
|
|
||||||
assert.Error(t, mount.Set("target=/target,source=/foo"), "type is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMountOptSetErrorNoTarget(t *testing.T) {
|
|
||||||
var mount MountOpt
|
|
||||||
assert.Error(t, mount.Set("type=VOLUME,source=/foo"), "target is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMountOptSetErrorInvalidKey(t *testing.T) {
|
|
||||||
var mount MountOpt
|
|
||||||
assert.Error(t, mount.Set("type=VOLUME,bogus=foo"), "unexpected key 'bogus'")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMountOptSetErrorInvalidField(t *testing.T) {
|
|
||||||
var mount MountOpt
|
|
||||||
assert.Error(t, mount.Set("type=VOLUME,bogus"), "invalid field 'bogus'")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMountOptSetErrorInvalidWritable(t *testing.T) {
|
|
||||||
var mount MountOpt
|
|
||||||
assert.Error(t, mount.Set("type=VOLUME,readonly=no"), "invalid value for readonly: no")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMountOptDefaultEnableWritable(t *testing.T) {
|
|
||||||
var m MountOpt
|
|
||||||
assert.NilError(t, m.Set("type=bind,target=/foo,source=/foo"))
|
|
||||||
assert.Equal(t, m.values[0].ReadOnly, false)
|
|
||||||
|
|
||||||
m = MountOpt{}
|
|
||||||
assert.NilError(t, m.Set("type=bind,target=/foo,source=/foo,readonly"))
|
|
||||||
assert.Equal(t, m.values[0].ReadOnly, true)
|
|
||||||
|
|
||||||
m = MountOpt{}
|
|
||||||
assert.NilError(t, m.Set("type=bind,target=/foo,source=/foo,readonly=1"))
|
|
||||||
assert.Equal(t, m.values[0].ReadOnly, true)
|
|
||||||
|
|
||||||
m = MountOpt{}
|
|
||||||
assert.NilError(t, m.Set("type=bind,target=/foo,source=/foo,readonly=0"))
|
|
||||||
assert.Equal(t, m.values[0].ReadOnly, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMountOptVolumeNoCopy(t *testing.T) {
|
|
||||||
var m MountOpt
|
|
||||||
assert.Error(t, m.Set("type=volume,target=/foo,volume-nocopy"), "source is required")
|
|
||||||
|
|
||||||
m = MountOpt{}
|
|
||||||
assert.NilError(t, m.Set("type=volume,target=/foo,source=foo"))
|
|
||||||
assert.Equal(t, m.values[0].VolumeOptions == nil, true)
|
|
||||||
|
|
||||||
m = MountOpt{}
|
|
||||||
assert.NilError(t, m.Set("type=volume,target=/foo,source=foo,volume-nocopy=true"))
|
|
||||||
assert.Equal(t, m.values[0].VolumeOptions != nil, true)
|
|
||||||
assert.Equal(t, m.values[0].VolumeOptions.NoCopy, true)
|
|
||||||
|
|
||||||
m = MountOpt{}
|
|
||||||
assert.NilError(t, m.Set("type=volume,target=/foo,source=foo,volume-nocopy"))
|
|
||||||
assert.Equal(t, m.values[0].VolumeOptions != nil, true)
|
|
||||||
assert.Equal(t, m.values[0].VolumeOptions.NoCopy, true)
|
|
||||||
|
|
||||||
m = MountOpt{}
|
|
||||||
assert.NilError(t, m.Set("type=volume,target=/foo,source=foo,volume-nocopy=1"))
|
|
||||||
assert.Equal(t, m.values[0].VolumeOptions != nil, true)
|
|
||||||
assert.Equal(t, m.values[0].VolumeOptions.NoCopy, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMountOptTypeConflict(t *testing.T) {
|
|
||||||
var m MountOpt
|
|
||||||
assert.Error(t, m.Set("type=bind,target=/foo,source=/foo,volume-nocopy=true"), "cannot mix")
|
|
||||||
assert.Error(t, m.Set("type=volume,target=/foo,source=/foo,bind-propagation=rprivate"), "cannot mix")
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newRemoveCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "rm [OPTIONS] SERVICE",
|
|
||||||
Aliases: []string{"remove"},
|
|
||||||
Short: "Remove a service",
|
|
||||||
Args: cli.RequiresMinArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return runRemove(dockerCli, args)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cmd.Flags()
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runRemove(dockerCli *client.DockerCli, sids []string) error {
|
|
||||||
client := dockerCli.Client()
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
var errs []string
|
|
||||||
for _, sid := range sids {
|
|
||||||
err := client.ServiceRemove(ctx, sid)
|
|
||||||
if err != nil {
|
|
||||||
errs = append(errs, err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "%s\n", sid)
|
|
||||||
}
|
|
||||||
if len(errs) > 0 {
|
|
||||||
return fmt.Errorf(strings.Join(errs, "\n"))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newScaleCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
return &cobra.Command{
|
|
||||||
Use: "scale SERVICE=REPLICAS [SERVICE=REPLICAS...]",
|
|
||||||
Short: "Scale one or multiple services",
|
|
||||||
Args: scaleArgs,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return runScale(dockerCli, args)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func scaleArgs(cmd *cobra.Command, args []string) error {
|
|
||||||
if err := cli.RequiresMinArgs(1)(cmd, args); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, arg := range args {
|
|
||||||
if parts := strings.SplitN(arg, "=", 2); len(parts) != 2 {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"Invalid scale specifier '%s'.\nSee '%s --help'.\n\nUsage: %s\n\n%s",
|
|
||||||
arg,
|
|
||||||
cmd.CommandPath(),
|
|
||||||
cmd.UseLine(),
|
|
||||||
cmd.Short,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func runScale(dockerCli *client.DockerCli, args []string) error {
|
|
||||||
var errors []string
|
|
||||||
for _, arg := range args {
|
|
||||||
parts := strings.SplitN(arg, "=", 2)
|
|
||||||
serviceID, scale := parts[0], parts[1]
|
|
||||||
if err := runServiceScale(dockerCli, serviceID, scale); err != nil {
|
|
||||||
errors = append(errors, fmt.Sprintf("%s: %s", serviceID, err.Error()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(errors) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf(strings.Join(errors, "\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func runServiceScale(dockerCli *client.DockerCli, serviceID string, scale string) error {
|
|
||||||
client := dockerCli.Client()
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
service, _, err := client.ServiceInspectWithRaw(ctx, serviceID)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
serviceMode := &service.Spec.Mode
|
|
||||||
if serviceMode.Replicated == nil {
|
|
||||||
return fmt.Errorf("scale can only be used with replicated mode")
|
|
||||||
}
|
|
||||||
uintScale, err := strconv.ParseUint(scale, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid replicas value %s: %s", scale, err.Error())
|
|
||||||
}
|
|
||||||
serviceMode.Replicated.Replicas = &uintScale
|
|
||||||
|
|
||||||
err = client.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, types.ServiceUpdateOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "%s scaled to %s\n", serviceID, scale)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/api/client/idresolver"
|
|
||||||
"github.com/docker/docker/api/client/node"
|
|
||||||
"github.com/docker/docker/api/client/task"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
"github.com/docker/engine-api/types/swarm"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type tasksOptions struct {
|
|
||||||
serviceID string
|
|
||||||
all bool
|
|
||||||
noResolve bool
|
|
||||||
filter opts.FilterOpt
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTasksCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
opts := tasksOptions{filter: opts.NewFilterOpt()}
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "tasks [OPTIONS] SERVICE",
|
|
||||||
Short: "List the tasks of a service",
|
|
||||||
Args: cli.ExactArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.serviceID = args[0]
|
|
||||||
return runTasks(dockerCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.BoolVarP(&opts.all, "all", "a", false, "Display all tasks")
|
|
||||||
flags.BoolVar(&opts.noResolve, "no-resolve", false, "Do not map IDs to Names")
|
|
||||||
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runTasks(dockerCli *client.DockerCli, opts tasksOptions) error {
|
|
||||||
client := dockerCli.Client()
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
service, _, err := client.ServiceInspectWithRaw(ctx, opts.serviceID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
filter := opts.filter.Value()
|
|
||||||
filter.Add("service", service.ID)
|
|
||||||
if !opts.all && !filter.Include("desired-state") {
|
|
||||||
filter.Add("desired-state", string(swarm.TaskStateRunning))
|
|
||||||
filter.Add("desired-state", string(swarm.TaskStateAccepted))
|
|
||||||
}
|
|
||||||
|
|
||||||
if filter.Include("node") {
|
|
||||||
nodeFilters := filter.Get("node")
|
|
||||||
for _, nodeFilter := range nodeFilters {
|
|
||||||
nodeReference, err := node.Reference(client, ctx, nodeFilter)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
filter.Del("node", nodeFilter)
|
|
||||||
filter.Add("node", nodeReference)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks, err := client.TaskList(ctx, types.TaskListOptions{Filter: filter})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return task.Print(dockerCli, ctx, tasks, idresolver.New(client, opts.noResolve))
|
|
||||||
}
|
|
||||||
|
|
@ -1,388 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
runconfigopts "github.com/docker/docker/runconfig/opts"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
"github.com/docker/engine-api/types/swarm"
|
|
||||||
"github.com/docker/go-connections/nat"
|
|
||||||
shlex "github.com/flynn-archive/go-shlex"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/pflag"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
opts := newServiceOptions()
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "update [OPTIONS] SERVICE",
|
|
||||||
Short: "Update a service",
|
|
||||||
Args: cli.ExactArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return runUpdate(dockerCli, cmd.Flags(), args[0])
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.String("image", "", "Service image tag")
|
|
||||||
flags.String("args", "", "Service command args")
|
|
||||||
addServiceFlags(cmd, opts)
|
|
||||||
|
|
||||||
flags.Var(newListOptsVar(), flagEnvRemove, "Remove an environment variable")
|
|
||||||
flags.Var(newListOptsVar(), flagLabelRemove, "Remove a label by its key")
|
|
||||||
flags.Var(newListOptsVar(), flagMountRemove, "Remove a mount by its target path")
|
|
||||||
flags.Var(newListOptsVar(), flagPublishRemove, "Remove a published port by its target port")
|
|
||||||
flags.Var(newListOptsVar(), flagNetworkRemove, "Remove a network by name")
|
|
||||||
flags.Var(newListOptsVar(), flagConstraintRemove, "Remove a constraint")
|
|
||||||
flags.Var(&opts.labels, flagLabelAdd, "Add or update service labels")
|
|
||||||
flags.Var(&opts.env, flagEnvAdd, "Add or update environment variables")
|
|
||||||
flags.Var(&opts.mounts, flagMountAdd, "Add or update a mount on a service")
|
|
||||||
flags.StringSliceVar(&opts.constraints, flagConstraintAdd, []string{}, "Add or update placement constraints")
|
|
||||||
flags.StringSliceVar(&opts.networks, flagNetworkAdd, []string{}, "Add or update network attachments")
|
|
||||||
flags.Var(&opts.endpoint.ports, flagPublishAdd, "Add or update a published port")
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func newListOptsVar() *opts.ListOpts {
|
|
||||||
return opts.NewListOptsRef(&[]string{}, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, serviceID string) error {
|
|
||||||
apiClient := dockerCli.Client()
|
|
||||||
ctx := context.Background()
|
|
||||||
updateOpts := types.ServiceUpdateOptions{}
|
|
||||||
|
|
||||||
service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = updateService(flags, &service.Spec)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// only send auth if flag was set
|
|
||||||
sendAuth, err := flags.GetBool(flagRegistryAuth)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if sendAuth {
|
|
||||||
// Retrieve encoded auth token from the image reference
|
|
||||||
// This would be the old image if it didn't change in this update
|
|
||||||
image := service.Spec.TaskTemplate.ContainerSpec.Image
|
|
||||||
encodedAuth, err := dockerCli.RetrieveAuthTokenFromImage(ctx, image)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
updateOpts.EncodedRegistryAuth = encodedAuth
|
|
||||||
}
|
|
||||||
|
|
||||||
err = apiClient.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, updateOpts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "%s\n", serviceID)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateService(flags *pflag.FlagSet, spec *swarm.ServiceSpec) error {
|
|
||||||
|
|
||||||
updateString := func(flag string, field *string) {
|
|
||||||
if flags.Changed(flag) {
|
|
||||||
*field, _ = flags.GetString(flag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateInt64Value := func(flag string, field *int64) {
|
|
||||||
if flags.Changed(flag) {
|
|
||||||
*field = flags.Lookup(flag).Value.(int64Value).Value()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDuration := func(flag string, field *time.Duration) {
|
|
||||||
if flags.Changed(flag) {
|
|
||||||
*field, _ = flags.GetDuration(flag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDurationOpt := func(flag string, field *time.Duration) {
|
|
||||||
if flags.Changed(flag) {
|
|
||||||
*field = *flags.Lookup(flag).Value.(*DurationOpt).Value()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateUint64 := func(flag string, field *uint64) {
|
|
||||||
if flags.Changed(flag) {
|
|
||||||
*field, _ = flags.GetUint64(flag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateUint64Opt := func(flag string, field *uint64) {
|
|
||||||
if flags.Changed(flag) {
|
|
||||||
*field = *flags.Lookup(flag).Value.(*Uint64Opt).Value()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cspec := &spec.TaskTemplate.ContainerSpec
|
|
||||||
task := &spec.TaskTemplate
|
|
||||||
|
|
||||||
taskResources := func() *swarm.ResourceRequirements {
|
|
||||||
if task.Resources == nil {
|
|
||||||
task.Resources = &swarm.ResourceRequirements{}
|
|
||||||
}
|
|
||||||
return task.Resources
|
|
||||||
}
|
|
||||||
|
|
||||||
updateString(flagName, &spec.Name)
|
|
||||||
updateLabels(flags, &spec.Labels)
|
|
||||||
updateString("image", &cspec.Image)
|
|
||||||
updateStringToSlice(flags, "args", &cspec.Args)
|
|
||||||
updateEnvironment(flags, &cspec.Env)
|
|
||||||
updateString("workdir", &cspec.Dir)
|
|
||||||
updateString(flagUser, &cspec.User)
|
|
||||||
updateMounts(flags, &cspec.Mounts)
|
|
||||||
|
|
||||||
if flags.Changed(flagLimitCPU) || flags.Changed(flagLimitMemory) {
|
|
||||||
taskResources().Limits = &swarm.Resources{}
|
|
||||||
updateInt64Value(flagLimitCPU, &task.Resources.Limits.NanoCPUs)
|
|
||||||
updateInt64Value(flagLimitMemory, &task.Resources.Limits.MemoryBytes)
|
|
||||||
|
|
||||||
}
|
|
||||||
if flags.Changed(flagReserveCPU) || flags.Changed(flagReserveMemory) {
|
|
||||||
taskResources().Reservations = &swarm.Resources{}
|
|
||||||
updateInt64Value(flagReserveCPU, &task.Resources.Reservations.NanoCPUs)
|
|
||||||
updateInt64Value(flagReserveMemory, &task.Resources.Reservations.MemoryBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDurationOpt(flagStopGracePeriod, cspec.StopGracePeriod)
|
|
||||||
|
|
||||||
if anyChanged(flags, flagRestartCondition, flagRestartDelay, flagRestartMaxAttempts, flagRestartWindow) {
|
|
||||||
if task.RestartPolicy == nil {
|
|
||||||
task.RestartPolicy = &swarm.RestartPolicy{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if flags.Changed(flagRestartCondition) {
|
|
||||||
value, _ := flags.GetString(flagRestartCondition)
|
|
||||||
task.RestartPolicy.Condition = swarm.RestartPolicyCondition(value)
|
|
||||||
}
|
|
||||||
updateDurationOpt(flagRestartDelay, task.RestartPolicy.Delay)
|
|
||||||
updateUint64Opt(flagRestartMaxAttempts, task.RestartPolicy.MaxAttempts)
|
|
||||||
updateDurationOpt((flagRestartWindow), task.RestartPolicy.Window)
|
|
||||||
}
|
|
||||||
|
|
||||||
if anyChanged(flags, flagConstraintAdd, flagConstraintRemove) {
|
|
||||||
if task.Placement == nil {
|
|
||||||
task.Placement = &swarm.Placement{}
|
|
||||||
}
|
|
||||||
updatePlacement(flags, task.Placement)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := updateReplicas(flags, &spec.Mode); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if anyChanged(flags, flagUpdateParallelism, flagUpdateDelay) {
|
|
||||||
if spec.UpdateConfig == nil {
|
|
||||||
spec.UpdateConfig = &swarm.UpdateConfig{}
|
|
||||||
}
|
|
||||||
updateUint64(flagUpdateParallelism, &spec.UpdateConfig.Parallelism)
|
|
||||||
updateDuration(flagUpdateDelay, &spec.UpdateConfig.Delay)
|
|
||||||
}
|
|
||||||
|
|
||||||
updateNetworks(flags, &spec.Networks)
|
|
||||||
if flags.Changed(flagEndpointMode) {
|
|
||||||
value, _ := flags.GetString(flagEndpointMode)
|
|
||||||
spec.EndpointSpec.Mode = swarm.ResolutionMode(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
if anyChanged(flags, flagPublishAdd, flagPublishRemove) {
|
|
||||||
if spec.EndpointSpec == nil {
|
|
||||||
spec.EndpointSpec = &swarm.EndpointSpec{}
|
|
||||||
}
|
|
||||||
updatePorts(flags, &spec.EndpointSpec.Ports)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateStringToSlice(flags *pflag.FlagSet, flag string, field *[]string) error {
|
|
||||||
if !flags.Changed(flag) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
value, _ := flags.GetString(flag)
|
|
||||||
valueSlice, err := shlex.Split(value)
|
|
||||||
*field = valueSlice
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func anyChanged(flags *pflag.FlagSet, fields ...string) bool {
|
|
||||||
for _, flag := range fields {
|
|
||||||
if flags.Changed(flag) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func updatePlacement(flags *pflag.FlagSet, placement *swarm.Placement) {
|
|
||||||
field, _ := flags.GetStringSlice(flagConstraintAdd)
|
|
||||||
placement.Constraints = append(placement.Constraints, field...)
|
|
||||||
|
|
||||||
toRemove := buildToRemoveSet(flags, flagConstraintRemove)
|
|
||||||
placement.Constraints = removeItems(placement.Constraints, toRemove, itemKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateLabels(flags *pflag.FlagSet, field *map[string]string) {
|
|
||||||
if flags.Changed(flagLabelAdd) {
|
|
||||||
if field == nil {
|
|
||||||
*field = map[string]string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
values := flags.Lookup(flagLabelAdd).Value.(*opts.ListOpts).GetAll()
|
|
||||||
for key, value := range runconfigopts.ConvertKVStringsToMap(values) {
|
|
||||||
(*field)[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if field != nil && flags.Changed(flagLabelRemove) {
|
|
||||||
toRemove := flags.Lookup(flagLabelRemove).Value.(*opts.ListOpts).GetAll()
|
|
||||||
for _, label := range toRemove {
|
|
||||||
delete(*field, label)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateEnvironment(flags *pflag.FlagSet, field *[]string) {
|
|
||||||
if flags.Changed(flagEnvAdd) {
|
|
||||||
value := flags.Lookup(flagEnvAdd).Value.(*opts.ListOpts)
|
|
||||||
*field = append(*field, value.GetAll()...)
|
|
||||||
}
|
|
||||||
toRemove := buildToRemoveSet(flags, flagEnvRemove)
|
|
||||||
*field = removeItems(*field, toRemove, envKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func envKey(value string) string {
|
|
||||||
kv := strings.SplitN(value, "=", 2)
|
|
||||||
return kv[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
func itemKey(value string) string {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildToRemoveSet(flags *pflag.FlagSet, flag string) map[string]struct{} {
|
|
||||||
var empty struct{}
|
|
||||||
toRemove := make(map[string]struct{})
|
|
||||||
|
|
||||||
if !flags.Changed(flag) {
|
|
||||||
return toRemove
|
|
||||||
}
|
|
||||||
|
|
||||||
toRemoveSlice := flags.Lookup(flag).Value.(*opts.ListOpts).GetAll()
|
|
||||||
for _, key := range toRemoveSlice {
|
|
||||||
toRemove[key] = empty
|
|
||||||
}
|
|
||||||
return toRemove
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeItems(
|
|
||||||
seq []string,
|
|
||||||
toRemove map[string]struct{},
|
|
||||||
keyFunc func(string) string,
|
|
||||||
) []string {
|
|
||||||
newSeq := []string{}
|
|
||||||
for _, item := range seq {
|
|
||||||
if _, exists := toRemove[keyFunc(item)]; !exists {
|
|
||||||
newSeq = append(newSeq, item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newSeq
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateMounts(flags *pflag.FlagSet, mounts *[]swarm.Mount) {
|
|
||||||
if flags.Changed(flagMountAdd) {
|
|
||||||
values := flags.Lookup(flagMountAdd).Value.(*MountOpt).Value()
|
|
||||||
*mounts = append(*mounts, values...)
|
|
||||||
}
|
|
||||||
toRemove := buildToRemoveSet(flags, flagMountRemove)
|
|
||||||
|
|
||||||
newMounts := []swarm.Mount{}
|
|
||||||
for _, mount := range *mounts {
|
|
||||||
if _, exists := toRemove[mount.Target]; !exists {
|
|
||||||
newMounts = append(newMounts, mount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*mounts = newMounts
|
|
||||||
}
|
|
||||||
|
|
||||||
func updatePorts(flags *pflag.FlagSet, portConfig *[]swarm.PortConfig) {
|
|
||||||
if flags.Changed(flagPublishAdd) {
|
|
||||||
values := flags.Lookup(flagPublishAdd).Value.(*opts.ListOpts).GetAll()
|
|
||||||
ports, portBindings, _ := nat.ParsePortSpecs(values)
|
|
||||||
|
|
||||||
for port := range ports {
|
|
||||||
*portConfig = append(*portConfig, convertPortToPortConfig(port, portBindings)...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !flags.Changed(flagPublishRemove) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
toRemove := flags.Lookup(flagPublishRemove).Value.(*opts.ListOpts).GetAll()
|
|
||||||
newPorts := []swarm.PortConfig{}
|
|
||||||
portLoop:
|
|
||||||
for _, port := range *portConfig {
|
|
||||||
for _, rawTargetPort := range toRemove {
|
|
||||||
targetPort := nat.Port(rawTargetPort)
|
|
||||||
if equalPort(targetPort, port) {
|
|
||||||
continue portLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newPorts = append(newPorts, port)
|
|
||||||
}
|
|
||||||
*portConfig = newPorts
|
|
||||||
}
|
|
||||||
|
|
||||||
func equalPort(targetPort nat.Port, port swarm.PortConfig) bool {
|
|
||||||
return (string(port.Protocol) == targetPort.Proto() &&
|
|
||||||
port.TargetPort == uint32(targetPort.Int()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateNetworks(flags *pflag.FlagSet, attachments *[]swarm.NetworkAttachmentConfig) {
|
|
||||||
if flags.Changed(flagNetworkAdd) {
|
|
||||||
networks, _ := flags.GetStringSlice(flagNetworkAdd)
|
|
||||||
for _, network := range networks {
|
|
||||||
*attachments = append(*attachments, swarm.NetworkAttachmentConfig{Target: network})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
toRemove := buildToRemoveSet(flags, flagNetworkRemove)
|
|
||||||
newNetworks := []swarm.NetworkAttachmentConfig{}
|
|
||||||
for _, network := range *attachments {
|
|
||||||
if _, exists := toRemove[network.Target]; !exists {
|
|
||||||
newNetworks = append(newNetworks, network)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*attachments = newNetworks
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateReplicas(flags *pflag.FlagSet, serviceMode *swarm.ServiceMode) error {
|
|
||||||
if !flags.Changed(flagReplicas) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if serviceMode.Replicated == nil {
|
|
||||||
return fmt.Errorf("replicas can only be used with replicated mode")
|
|
||||||
}
|
|
||||||
serviceMode.Replicated.Replicas = flags.Lookup(flagReplicas).Value.(*Uint64Opt).Value()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,133 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/testutil/assert"
|
|
||||||
"github.com/docker/engine-api/types/swarm"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUpdateServiceArgs(t *testing.T) {
|
|
||||||
flags := newUpdateCommand(nil).Flags()
|
|
||||||
flags.Set("args", "the \"new args\"")
|
|
||||||
|
|
||||||
spec := &swarm.ServiceSpec{}
|
|
||||||
cspec := &spec.TaskTemplate.ContainerSpec
|
|
||||||
cspec.Args = []string{"old", "args"}
|
|
||||||
|
|
||||||
updateService(flags, spec)
|
|
||||||
assert.EqualStringSlice(t, cspec.Args, []string{"the", "new args"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdateLabels(t *testing.T) {
|
|
||||||
flags := newUpdateCommand(nil).Flags()
|
|
||||||
flags.Set("label-add", "toadd=newlabel")
|
|
||||||
flags.Set("label-rm", "toremove")
|
|
||||||
|
|
||||||
labels := map[string]string{
|
|
||||||
"toremove": "thelabeltoremove",
|
|
||||||
"tokeep": "value",
|
|
||||||
}
|
|
||||||
|
|
||||||
updateLabels(flags, &labels)
|
|
||||||
assert.Equal(t, len(labels), 2)
|
|
||||||
assert.Equal(t, labels["tokeep"], "value")
|
|
||||||
assert.Equal(t, labels["toadd"], "newlabel")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdateLabelsRemoveALabelThatDoesNotExist(t *testing.T) {
|
|
||||||
flags := newUpdateCommand(nil).Flags()
|
|
||||||
flags.Set("label-rm", "dne")
|
|
||||||
|
|
||||||
labels := map[string]string{"foo": "theoldlabel"}
|
|
||||||
updateLabels(flags, &labels)
|
|
||||||
assert.Equal(t, len(labels), 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdatePlacement(t *testing.T) {
|
|
||||||
flags := newUpdateCommand(nil).Flags()
|
|
||||||
flags.Set("constraint-add", "node=toadd")
|
|
||||||
flags.Set("constraint-rm", "node!=toremove")
|
|
||||||
|
|
||||||
placement := &swarm.Placement{
|
|
||||||
Constraints: []string{"node!=toremove", "container=tokeep"},
|
|
||||||
}
|
|
||||||
|
|
||||||
updatePlacement(flags, placement)
|
|
||||||
assert.Equal(t, len(placement.Constraints), 2)
|
|
||||||
assert.Equal(t, placement.Constraints[0], "container=tokeep")
|
|
||||||
assert.Equal(t, placement.Constraints[1], "node=toadd")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdateEnvironment(t *testing.T) {
|
|
||||||
flags := newUpdateCommand(nil).Flags()
|
|
||||||
flags.Set("env-add", "toadd=newenv")
|
|
||||||
flags.Set("env-rm", "toremove")
|
|
||||||
|
|
||||||
envs := []string{"toremove=theenvtoremove", "tokeep=value"}
|
|
||||||
|
|
||||||
updateEnvironment(flags, &envs)
|
|
||||||
assert.Equal(t, len(envs), 2)
|
|
||||||
assert.Equal(t, envs[0], "tokeep=value")
|
|
||||||
assert.Equal(t, envs[1], "toadd=newenv")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdateEnvironmentWithDuplicateValues(t *testing.T) {
|
|
||||||
flags := newUpdateCommand(nil).Flags()
|
|
||||||
flags.Set("env-add", "foo=newenv")
|
|
||||||
flags.Set("env-add", "foo=dupe")
|
|
||||||
flags.Set("env-rm", "foo")
|
|
||||||
|
|
||||||
envs := []string{"foo=value"}
|
|
||||||
|
|
||||||
updateEnvironment(flags, &envs)
|
|
||||||
assert.Equal(t, len(envs), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdateMounts(t *testing.T) {
|
|
||||||
flags := newUpdateCommand(nil).Flags()
|
|
||||||
flags.Set("mount-add", "type=volume,target=/toadd")
|
|
||||||
flags.Set("mount-rm", "/toremove")
|
|
||||||
|
|
||||||
mounts := []swarm.Mount{
|
|
||||||
{Target: "/toremove", Type: swarm.MountType("BIND")},
|
|
||||||
{Target: "/tokeep", Type: swarm.MountType("BIND")},
|
|
||||||
}
|
|
||||||
|
|
||||||
updateMounts(flags, &mounts)
|
|
||||||
assert.Equal(t, len(mounts), 2)
|
|
||||||
assert.Equal(t, mounts[0].Target, "/tokeep")
|
|
||||||
assert.Equal(t, mounts[1].Target, "/toadd")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdateNetworks(t *testing.T) {
|
|
||||||
flags := newUpdateCommand(nil).Flags()
|
|
||||||
flags.Set("network-add", "toadd")
|
|
||||||
flags.Set("network-rm", "toremove")
|
|
||||||
|
|
||||||
attachments := []swarm.NetworkAttachmentConfig{
|
|
||||||
{Target: "toremove", Aliases: []string{"foo"}},
|
|
||||||
{Target: "tokeep"},
|
|
||||||
}
|
|
||||||
|
|
||||||
updateNetworks(flags, &attachments)
|
|
||||||
assert.Equal(t, len(attachments), 2)
|
|
||||||
assert.Equal(t, attachments[0].Target, "tokeep")
|
|
||||||
assert.Equal(t, attachments[1].Target, "toadd")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdatePorts(t *testing.T) {
|
|
||||||
flags := newUpdateCommand(nil).Flags()
|
|
||||||
flags.Set("publish-add", "1000:1000")
|
|
||||||
flags.Set("publish-rm", "333/udp")
|
|
||||||
|
|
||||||
portConfigs := []swarm.PortConfig{
|
|
||||||
{TargetPort: 333, Protocol: swarm.PortConfigProtocol("udp")},
|
|
||||||
{TargetPort: 555},
|
|
||||||
}
|
|
||||||
|
|
||||||
updatePorts(flags, &portConfigs)
|
|
||||||
assert.Equal(t, len(portConfigs), 2)
|
|
||||||
assert.Equal(t, portConfigs[0].TargetPort, uint32(555))
|
|
||||||
assert.Equal(t, portConfigs[1].TargetPort, uint32(1000))
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
// +build experimental
|
|
||||||
|
|
||||||
package stack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewStackCommand returns a cobra command for `stack` subcommands
|
|
||||||
func NewStackCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "stack",
|
|
||||||
Short: "Manage Docker stacks",
|
|
||||||
Args: cli.NoArgs,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cmd.AddCommand(
|
|
||||||
newConfigCommand(dockerCli),
|
|
||||||
newDeployCommand(dockerCli),
|
|
||||||
newRemoveCommand(dockerCli),
|
|
||||||
newTasksCommand(dockerCli),
|
|
||||||
)
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTopLevelDeployCommand returns a command for `docker deploy`
|
|
||||||
func NewTopLevelDeployCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
cmd := newDeployCommand(dockerCli)
|
|
||||||
// Remove the aliases at the top level
|
|
||||||
cmd.Aliases = []string{}
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
// +build !experimental
|
|
||||||
|
|
||||||
package stack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewStackCommand returns no command
|
|
||||||
func NewStackCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
return &cobra.Command{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTopLevelDeployCommand returns no command
|
|
||||||
func NewTopLevelDeployCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
return &cobra.Command{}
|
|
||||||
}
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
// +build experimental
|
|
||||||
|
|
||||||
package stack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/engine-api/client"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
"github.com/docker/engine-api/types/filters"
|
|
||||||
"github.com/docker/engine-api/types/swarm"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
labelNamespace = "com.docker.stack.namespace"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getStackLabels(namespace string, labels map[string]string) map[string]string {
|
|
||||||
if labels == nil {
|
|
||||||
labels = make(map[string]string)
|
|
||||||
}
|
|
||||||
labels[labelNamespace] = namespace
|
|
||||||
return labels
|
|
||||||
}
|
|
||||||
|
|
||||||
func getStackFilter(namespace string) filters.Args {
|
|
||||||
filter := filters.NewArgs()
|
|
||||||
filter.Add("label", labelNamespace+"="+namespace)
|
|
||||||
return filter
|
|
||||||
}
|
|
||||||
|
|
||||||
func getServices(
|
|
||||||
ctx context.Context,
|
|
||||||
apiclient client.APIClient,
|
|
||||||
namespace string,
|
|
||||||
) ([]swarm.Service, error) {
|
|
||||||
return apiclient.ServiceList(
|
|
||||||
ctx,
|
|
||||||
types.ServiceListOptions{Filter: getStackFilter(namespace)})
|
|
||||||
}
|
|
||||||
|
|
||||||
func getNetworks(
|
|
||||||
ctx context.Context,
|
|
||||||
apiclient client.APIClient,
|
|
||||||
namespace string,
|
|
||||||
) ([]types.NetworkResource, error) {
|
|
||||||
return apiclient.NetworkList(
|
|
||||||
ctx,
|
|
||||||
types.NetworkListOptions{Filters: getStackFilter(namespace)})
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
// +build experimental
|
|
||||||
|
|
||||||
package stack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/api/client/bundlefile"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type configOptions struct {
|
|
||||||
bundlefile string
|
|
||||||
namespace string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newConfigCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts configOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "config [OPTIONS] STACK",
|
|
||||||
Short: "Print the stack configuration",
|
|
||||||
Args: cli.ExactArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.namespace = args[0]
|
|
||||||
return runConfig(dockerCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
addBundlefileFlag(&opts.bundlefile, flags)
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runConfig(dockerCli *client.DockerCli, opts configOptions) error {
|
|
||||||
bundle, err := loadBundlefile(dockerCli.Err(), opts.namespace, opts.bundlefile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return bundlefile.Print(dockerCli.Out(), bundle)
|
|
||||||
}
|
|
||||||
|
|
@ -1,208 +0,0 @@
|
||||||
// +build experimental
|
|
||||||
|
|
||||||
package stack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
|
||||||
"github.com/docker/docker/api/client/bundlefile"
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
"github.com/docker/engine-api/types/network"
|
|
||||||
"github.com/docker/engine-api/types/swarm"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultNetworkDriver = "overlay"
|
|
||||||
)
|
|
||||||
|
|
||||||
type deployOptions struct {
|
|
||||||
bundlefile string
|
|
||||||
namespace string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newDeployCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
||||||
var opts deployOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "deploy [OPTIONS] STACK",
|
|
||||||
Aliases: []string{"up"},
|
|
||||||
Short: "Create and update a stack from a Distributed Application Bundle (DAB)",
|
|
||||||
Args: cli.ExactArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.namespace = args[0]
|
|
||||||
return runDeploy(dockerCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
addBundlefileFlag(&opts.bundlefile, flags)
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runDeploy(dockerCli *client.DockerCli, opts deployOptions) error {
|
|
||||||
bundle, err := loadBundlefile(dockerCli.Err(), opts.namespace, opts.bundlefile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
networks := getUniqueNetworkNames(bundle.Services)
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
if err := updateNetworks(ctx, dockerCli, networks, opts.namespace); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return deployServices(ctx, dockerCli, bundle.Services, opts.namespace)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getUniqueNetworkNames(services map[string]bundlefile.Service) []string {
|
|
||||||
networkSet := make(map[string]bool)
|
|
||||||
for _, service := range services {
|
|
||||||
for _, network := range service.Networks {
|
|
||||||
networkSet[network] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
networks := []string{}
|
|
||||||
for network := range networkSet {
|
|
||||||
networks = append(networks, network)
|
|
||||||
}
|
|
||||||
return networks
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateNetworks(
|
|
||||||
ctx context.Context,
|
|
||||||
dockerCli *client.DockerCli,
|
|
||||||
networks []string,
|
|
||||||
namespace string,
|
|
||||||
) error {
|
|
||||||
client := dockerCli.Client()
|
|
||||||
|
|
||||||
existingNetworks, err := getNetworks(ctx, client, namespace)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
existingNetworkMap := make(map[string]types.NetworkResource)
|
|
||||||
for _, network := range existingNetworks {
|
|
||||||
existingNetworkMap[network.Name] = network
|
|
||||||
}
|
|
||||||
|
|
||||||
createOpts := types.NetworkCreate{
|
|
||||||
Labels: getStackLabels(namespace, nil),
|
|
||||||
Driver: defaultNetworkDriver,
|
|
||||||
// TODO: remove when engine-api uses omitempty for IPAM
|
|
||||||
IPAM: network.IPAM{Driver: "default"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, internalName := range networks {
|
|
||||||
name := fmt.Sprintf("%s_%s", namespace, internalName)
|
|
||||||
|
|
||||||
if _, exists := existingNetworkMap[name]; exists {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "Creating network %s\n", name)
|
|
||||||
if _, err := client.NetworkCreate(ctx, name, createOpts); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertNetworks(networks []string, namespace string, name string) []swarm.NetworkAttachmentConfig {
|
|
||||||
nets := []swarm.NetworkAttachmentConfig{}
|
|
||||||
for _, network := range networks {
|
|
||||||
nets = append(nets, swarm.NetworkAttachmentConfig{
|
|
||||||
Target: namespace + "_" + network,
|
|
||||||
Aliases: []string{name},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return nets
|
|
||||||
}
|
|
||||||
|
|
||||||
func deployServices(
|
|
||||||
ctx context.Context,
|
|
||||||
dockerCli *client.DockerCli,
|
|
||||||
services map[string]bundlefile.Service,
|
|
||||||
namespace string,
|
|
||||||
) error {
|
|
||||||
apiClient := dockerCli.Client()
|
|
||||||
out := dockerCli.Out()
|
|
||||||
|
|
||||||
existingServices, err := getServices(ctx, apiClient, namespace)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
existingServiceMap := make(map[string]swarm.Service)
|
|
||||||
for _, service := range existingServices {
|
|
||||||
existingServiceMap[service.Spec.Name] = service
|
|
||||||
}
|
|
||||||
|
|
||||||
for internalName, service := range services {
|
|
||||||
name := fmt.Sprintf("%s_%s", namespace, internalName)
|
|
||||||
|
|
||||||
var ports []swarm.PortConfig
|
|
||||||
for _, portSpec := range service.Ports {
|
|
||||||
ports = append(ports, swarm.PortConfig{
|
|
||||||
Protocol: swarm.PortConfigProtocol(portSpec.Protocol),
|
|
||||||
TargetPort: portSpec.Port,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
serviceSpec := swarm.ServiceSpec{
|
|
||||||
Annotations: swarm.Annotations{
|
|
||||||
Name: name,
|
|
||||||
Labels: getStackLabels(namespace, service.Labels),
|
|
||||||
},
|
|
||||||
TaskTemplate: swarm.TaskSpec{
|
|
||||||
ContainerSpec: swarm.ContainerSpec{
|
|
||||||
Image: service.Image,
|
|
||||||
Command: service.Command,
|
|
||||||
Args: service.Args,
|
|
||||||
Env: service.Env,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
EndpointSpec: &swarm.EndpointSpec{
|
|
||||||
Ports: ports,
|
|
||||||
},
|
|
||||||
Networks: convertNetworks(service.Networks, namespace, internalName),
|
|
||||||
}
|
|
||||||
|
|
||||||
cspec := &serviceSpec.TaskTemplate.ContainerSpec
|
|
||||||
if service.WorkingDir != nil {
|
|
||||||
cspec.Dir = *service.WorkingDir
|
|
||||||
}
|
|
||||||
if service.User != nil {
|
|
||||||
cspec.User = *service.User
|
|
||||||
}
|
|
||||||
|
|
||||||
if service, exists := existingServiceMap[name]; exists {
|
|
||||||
fmt.Fprintf(out, "Updating service %s (id: %s)\n", name, service.ID)
|
|
||||||
|
|
||||||
// TODO(nishanttotla): Pass auth token
|
|
||||||
if err := apiClient.ServiceUpdate(
|
|
||||||
ctx,
|
|
||||||
service.ID,
|
|
||||||
service.Version,
|
|
||||||
serviceSpec,
|
|
||||||
types.ServiceUpdateOptions{},
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(out, "Creating service %s\n", name)
|
|
||||||
|
|
||||||
// TODO(nishanttotla): Pass headers with X-Registry-Auth
|
|
||||||
if _, err := apiClient.ServiceCreate(ctx, serviceSpec, types.ServiceCreateOptions{}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue