mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
2b7ad47bd2
The parse.go file is used almost exclusively in the client. The few small functions that are used outside of the client could easily be copied out when the client is extracted, allowing this runconfig/opts package to move to the client. Signed-off-by: Daniel Nephin <dnephin@docker.com>
283 lines
8.1 KiB
Go
283 lines
8.1 KiB
Go
package client
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
"github.com/docker/docker/api/types"
|
|
Cli "github.com/docker/docker/cli"
|
|
derr "github.com/docker/docker/errors"
|
|
"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/libnetwork/resolvconf/dns"
|
|
)
|
|
|
|
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
|
|
}
|
|
|
|
// if container start fails with 'command not found' error, return 127
|
|
// if container start fails with 'command cannot be invoked' error, return 126
|
|
// return 125 for generic docker daemon failures
|
|
func runStartContainerErr(err error) error {
|
|
trimmedErr := strings.Trim(err.Error(), "Error response from daemon: ")
|
|
statusError := Cli.StatusError{}
|
|
derrCmdNotFound := derr.ErrorCodeCmdNotFound.Message()
|
|
derrCouldNotInvoke := derr.ErrorCodeCmdCouldNotBeInvoked.Message()
|
|
derrNoSuchImage := derr.ErrorCodeNoSuchImageHash.Message()
|
|
derrNoSuchImageTag := derr.ErrorCodeNoSuchImageTag.Message()
|
|
switch trimmedErr {
|
|
case derrCmdNotFound:
|
|
statusError = Cli.StatusError{StatusCode: 127}
|
|
case derrCouldNotInvoke:
|
|
statusError = Cli.StatusError{StatusCode: 126}
|
|
case derrNoSuchImage, derrNoSuchImageTag:
|
|
statusError = Cli.StatusError{StatusCode: 125}
|
|
default:
|
|
statusError = Cli.StatusError{StatusCode: 125}
|
|
}
|
|
return statusError
|
|
}
|
|
|
|
// CmdRun runs a command in a new container.
|
|
//
|
|
// Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
|
|
func (cli *DockerCli) CmdRun(args ...string) error {
|
|
cmd := Cli.Subcmd("run", []string{"IMAGE [COMMAND] [ARG...]"}, Cli.DockerCommands["run"].Description, true)
|
|
addTrustedFlags(cmd, true)
|
|
|
|
// These are flags not stored in Config/HostConfig
|
|
var (
|
|
flAutoRemove = cmd.Bool([]string{"-rm"}, false, "Automatically remove the container when it exits")
|
|
flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Run container in background and print container ID")
|
|
flSigProxy = cmd.Bool([]string{"-sig-proxy"}, true, "Proxy received signals to the process")
|
|
flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
|
|
flDetachKeys = cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container")
|
|
flAttach *opts.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, cmd, err := runconfigopts.Parse(cmd, args)
|
|
// just in case the Parse does not exit
|
|
if err != nil {
|
|
cmd.ReportError(err.Error(), true)
|
|
os.Exit(125)
|
|
}
|
|
|
|
if hostConfig.OomKillDisable && hostConfig.Memory == 0 {
|
|
fmt.Fprintf(cli.err, "WARNING: Dangerous only disable the OOM Killer on containers but not set the '-m/--memory' option\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(cli.err, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if config.Image == "" {
|
|
cmd.Usage()
|
|
return nil
|
|
}
|
|
|
|
config.ArgsEscaped = false
|
|
|
|
if !*flDetach {
|
|
if err := cli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if fl := cmd.Lookup("-attach"); fl != nil {
|
|
flAttach = fl.Value.(*opts.ListOpts)
|
|
if flAttach.Len() != 0 {
|
|
return ErrConflictAttachDetach
|
|
}
|
|
}
|
|
if *flAutoRemove {
|
|
return ErrConflictDetachAutoRemove
|
|
}
|
|
|
|
config.AttachStdin = false
|
|
config.AttachStdout = false
|
|
config.AttachStderr = false
|
|
config.StdinOnce = false
|
|
}
|
|
|
|
// Disable flSigProxy when in TTY mode
|
|
sigProxy := *flSigProxy
|
|
if config.Tty {
|
|
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] = cli.getTtySize()
|
|
}
|
|
|
|
createResponse, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
|
|
if err != nil {
|
|
cmd.ReportError(err.Error(), true)
|
|
return runStartContainerErr(err)
|
|
}
|
|
if sigProxy {
|
|
sigc := cli.forwardAllSignals(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(cli.out, "%s\n", createResponse.ID)
|
|
}()
|
|
}
|
|
if *flAutoRemove && (hostConfig.RestartPolicy.IsAlways() || hostConfig.RestartPolicy.IsOnFailure()) {
|
|
return ErrConflictRestartPolicyAndAutoRemove
|
|
}
|
|
|
|
if config.AttachStdin || config.AttachStdout || config.AttachStderr {
|
|
var (
|
|
out, stderr io.Writer
|
|
in io.ReadCloser
|
|
)
|
|
if config.AttachStdin {
|
|
in = cli.in
|
|
}
|
|
if config.AttachStdout {
|
|
out = cli.out
|
|
}
|
|
if config.AttachStderr {
|
|
if config.Tty {
|
|
stderr = cli.out
|
|
} else {
|
|
stderr = cli.err
|
|
}
|
|
}
|
|
|
|
if *flDetachKeys != "" {
|
|
cli.configFile.DetachKeys = *flDetachKeys
|
|
}
|
|
|
|
options := types.ContainerAttachOptions{
|
|
ContainerID: createResponse.ID,
|
|
Stream: true,
|
|
Stdin: config.AttachStdin,
|
|
Stdout: config.AttachStdout,
|
|
Stderr: config.AttachStderr,
|
|
DetachKeys: cli.configFile.DetachKeys,
|
|
}
|
|
|
|
resp, err := cli.client.ContainerAttach(options)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
errCh = promise.Go(func() error {
|
|
return cli.holdHijackedConnection(config.Tty, in, out, stderr, resp)
|
|
})
|
|
}
|
|
|
|
defer func() {
|
|
if *flAutoRemove {
|
|
options := types.ContainerRemoveOptions{
|
|
ContainerID: createResponse.ID,
|
|
RemoveVolumes: true,
|
|
}
|
|
if err := cli.client.ContainerRemove(options); err != nil {
|
|
fmt.Fprintf(cli.err, "Error deleting container: %s\n", err)
|
|
}
|
|
}
|
|
}()
|
|
|
|
//start the container
|
|
if err := cli.client.ContainerStart(createResponse.ID); err != nil {
|
|
cmd.ReportError(err.Error(), false)
|
|
return runStartContainerErr(err)
|
|
}
|
|
|
|
if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminalOut {
|
|
if err := cli.monitorTtySize(createResponse.ID, false); err != nil {
|
|
fmt.Fprintf(cli.err, "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 *flAutoRemove {
|
|
// Autoremove: wait for the container to finish, retrieve
|
|
// the exit code and remove the container
|
|
if status, err = cli.client.ContainerWait(createResponse.ID); err != nil {
|
|
return runStartContainerErr(err)
|
|
}
|
|
if _, status, err = getExitCode(cli, createResponse.ID); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// No Autoremove: Simply retrieve the exit code
|
|
if !config.Tty {
|
|
// In non-TTY mode, we can't detach, so we must wait for container exit
|
|
if status, err = cli.client.ContainerWait(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(cli, createResponse.ID); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
if status != 0 {
|
|
return Cli.StatusError{StatusCode: status}
|
|
}
|
|
return nil
|
|
}
|