2016-09-08 13:11:39 -04:00
|
|
|
package container
|
|
|
|
|
|
|
|
import (
|
2018-05-03 21:02:44 -04:00
|
|
|
"context"
|
2016-09-08 13:11:39 -04:00
|
|
|
"fmt"
|
|
|
|
"io"
|
2022-03-30 09:27:25 -04:00
|
|
|
"os"
|
2016-09-08 13:11:39 -04:00
|
|
|
"strings"
|
|
|
|
"syscall"
|
|
|
|
|
2017-04-17 18:07:56 -04:00
|
|
|
"github.com/docker/cli/cli"
|
|
|
|
"github.com/docker/cli/cli/command"
|
2022-03-30 09:27:25 -04:00
|
|
|
"github.com/docker/cli/cli/command/completion"
|
2017-01-31 00:15:51 -05:00
|
|
|
"github.com/docker/cli/opts"
|
2017-03-07 17:19:54 -05:00
|
|
|
"github.com/docker/docker/api/types/container"
|
2021-08-09 13:15:46 -04:00
|
|
|
"github.com/moby/sys/signal"
|
2020-04-16 05:23:37 -04:00
|
|
|
"github.com/moby/term"
|
2017-03-27 21:21:59 -04:00
|
|
|
"github.com/pkg/errors"
|
2017-08-07 05:52:40 -04:00
|
|
|
"github.com/sirupsen/logrus"
|
2016-09-08 13:11:39 -04:00
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/spf13/pflag"
|
|
|
|
)
|
|
|
|
|
|
|
|
type runOptions struct {
|
2018-03-08 08:35:17 -05:00
|
|
|
createOptions
|
2016-09-08 13:11:39 -04:00
|
|
|
detach bool
|
|
|
|
sigProxy bool
|
|
|
|
detachKeys string
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewRunCommand create a new `docker run` command
|
2017-10-11 12:18:27 -04:00
|
|
|
func NewRunCommand(dockerCli command.Cli) *cobra.Command {
|
2022-06-23 13:35:40 -04:00
|
|
|
var options runOptions
|
2016-12-23 14:09:12 -05:00
|
|
|
var copts *containerOptions
|
2016-09-08 13:11:39 -04:00
|
|
|
|
|
|
|
cmd := &cobra.Command{
|
|
|
|
Use: "run [OPTIONS] IMAGE [COMMAND] [ARG...]",
|
2022-03-29 17:58:36 -04:00
|
|
|
Short: "Create and run a new container from an image",
|
2016-09-08 13:11:39 -04:00
|
|
|
Args: cli.RequiresMinArgs(1),
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
|
|
copts.Image = args[0]
|
|
|
|
if len(args) > 1 {
|
|
|
|
copts.Args = args[1:]
|
|
|
|
}
|
2023-09-09 18:27:44 -04:00
|
|
|
return runRun(cmd.Context(), dockerCli, cmd.Flags(), &options, copts)
|
2016-09-08 13:11:39 -04:00
|
|
|
},
|
2022-03-30 09:27:25 -04:00
|
|
|
ValidArgsFunction: completion.ImageNames(dockerCli),
|
2022-03-30 03:37:08 -04:00
|
|
|
Annotations: map[string]string{
|
|
|
|
"category-top": "1",
|
cli: use custom annotation for aliases
Cobra allows for aliases to be defined for a command, but only allows these
to be defined at the same level (for example, `docker image ls` as alias for
`docker image list`). Our CLI has some commands that are available both as a
top-level shorthand as well as `docker <object> <verb>` subcommands. For example,
`docker ps` is a shorthand for `docker container ps` / `docker container ls`.
This patch introduces a custom "aliases" annotation that can be used to print
all available aliases for a command. While this requires these aliases to be
defined manually, in practice the list of aliases rarely changes, so maintenance
should be minimal.
As a convention, we could consider the first command in this list to be the
canonical command, so that we can use this information to add redirects in
our documentation in future.
Before this patch:
docker images --help
Usage: docker images [OPTIONS] [REPOSITORY[:TAG]]
List images
Options:
-a, --all Show all images (default hides intermediate images)
...
With this patch:
docker images --help
Usage: docker images [OPTIONS] [REPOSITORY[:TAG]]
List images
Aliases:
docker image ls, docker image list, docker images
Options:
-a, --all Show all images (default hides intermediate images)
...
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-06-28 04:52:25 -04:00
|
|
|
"aliases": "docker container run, docker run",
|
2022-03-30 03:37:08 -04:00
|
|
|
},
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
flags := cmd.Flags()
|
|
|
|
flags.SetInterspersed(false)
|
|
|
|
|
|
|
|
// These are flags not stored in Config/HostConfig
|
2022-06-23 13:35:40 -04:00
|
|
|
flags.BoolVarP(&options.detach, "detach", "d", false, "Run container in background and print container ID")
|
|
|
|
flags.BoolVar(&options.sigProxy, "sig-proxy", true, "Proxy received signals to the process")
|
|
|
|
flags.StringVar(&options.name, "name", "", "Assign a name to the container")
|
|
|
|
flags.StringVar(&options.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container")
|
2023-01-03 05:12:24 -05:00
|
|
|
flags.StringVar(&options.pull, "pull", PullImageMissing, `Pull image before running ("`+PullImageAlways+`", "`+PullImageMissing+`", "`+PullImageNever+`")`)
|
2022-06-23 13:35:40 -04:00
|
|
|
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Suppress the pull output")
|
2016-09-08 13:11:39 -04:00
|
|
|
|
|
|
|
// Add an explicit help that doesn't have a `-h` to prevent the conflict
|
|
|
|
// with hostname
|
|
|
|
flags.Bool("help", false, "Print usage")
|
|
|
|
|
2022-06-23 13:35:40 -04:00
|
|
|
command.AddPlatformFlag(flags, &options.platform)
|
|
|
|
command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled())
|
2016-12-23 14:09:12 -05:00
|
|
|
copts = addFlags(flags)
|
2022-03-30 09:27:25 -04:00
|
|
|
|
2024-07-04 15:05:07 -04:00
|
|
|
_ = cmd.RegisterFlagCompletionFunc("env", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
|
|
|
|
return os.Environ(), cobra.ShellCompDirectiveNoFileComp
|
|
|
|
})
|
|
|
|
_ = cmd.RegisterFlagCompletionFunc("env-file", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
|
|
|
|
return nil, cobra.ShellCompDirectiveDefault
|
|
|
|
})
|
|
|
|
_ = cmd.RegisterFlagCompletionFunc("network", completion.NetworkNames(dockerCli))
|
2016-09-08 13:11:39 -04:00
|
|
|
return cmd
|
|
|
|
}
|
|
|
|
|
2023-09-09 18:27:44 -04:00
|
|
|
func runRun(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, ropts *runOptions, copts *containerOptions) error {
|
2022-06-27 11:16:44 -04:00
|
|
|
if err := validatePullOpt(ropts.pull); err != nil {
|
|
|
|
reportError(dockerCli.Err(), "run", err.Error(), true)
|
|
|
|
return cli.StatusError{StatusCode: 125}
|
|
|
|
}
|
2017-10-15 15:39:56 -04:00
|
|
|
proxyConfig := dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(copts.env.GetAll()))
|
2017-01-31 00:15:51 -05:00
|
|
|
newEnv := []string{}
|
|
|
|
for k, v := range proxyConfig {
|
|
|
|
if v == nil {
|
|
|
|
newEnv = append(newEnv, k)
|
|
|
|
} else {
|
2023-06-12 12:19:50 -04:00
|
|
|
newEnv = append(newEnv, k+"="+*v)
|
2017-01-31 00:15:51 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
copts.env = *opts.NewListOptsRef(&newEnv, nil)
|
2023-01-18 11:39:35 -05:00
|
|
|
containerCfg, err := parse(flags, copts, dockerCli.ServerInfo().OSType)
|
2016-12-23 14:09:12 -05:00
|
|
|
// just in case the parse does not exit
|
2016-09-08 13:11:39 -04:00
|
|
|
if err != nil {
|
2017-03-07 17:19:54 -05:00
|
|
|
reportError(dockerCli.Err(), "run", err.Error(), true)
|
2016-09-08 13:11:39 -04:00
|
|
|
return cli.StatusError{StatusCode: 125}
|
|
|
|
}
|
2023-01-18 11:39:35 -05:00
|
|
|
if err = validateAPIVersion(containerCfg, dockerCli.CurrentVersion()); err != nil {
|
2018-10-10 06:18:29 -04:00
|
|
|
reportError(dockerCli.Err(), "run", err.Error(), true)
|
|
|
|
return cli.StatusError{StatusCode: 125}
|
|
|
|
}
|
2023-09-09 18:27:44 -04:00
|
|
|
return runContainer(ctx, dockerCli, ropts, copts, containerCfg)
|
2017-03-07 17:19:54 -05:00
|
|
|
}
|
2016-09-08 13:11:39 -04:00
|
|
|
|
2022-07-13 06:29:49 -04:00
|
|
|
//nolint:gocyclo
|
2023-09-09 18:27:44 -04:00
|
|
|
func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOptions, copts *containerOptions, containerCfg *containerConfig) error {
|
2024-07-11 03:49:14 -04:00
|
|
|
ctx = context.WithoutCancel(ctx)
|
|
|
|
|
2023-01-18 11:39:35 -05:00
|
|
|
config := containerCfg.Config
|
2017-03-07 17:19:54 -05:00
|
|
|
stdout, stderr := dockerCli.Out(), dockerCli.Err()
|
2023-11-06 04:34:37 -05:00
|
|
|
apiClient := dockerCli.Client()
|
2016-09-08 13:11:39 -04:00
|
|
|
|
|
|
|
config.ArgsEscaped = false
|
|
|
|
|
2023-11-20 11:38:50 -05:00
|
|
|
if !runOpts.detach {
|
2016-09-08 13:11:39 -04:00
|
|
|
if err := dockerCli.In().CheckTty(config.AttachStdin, config.Tty); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
2017-03-07 17:19:54 -05:00
|
|
|
if copts.attach.Len() != 0 {
|
|
|
|
return errors.New("Conflicting options: -a and -d")
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
config.AttachStdin = false
|
|
|
|
config.AttachStdout = false
|
|
|
|
config.AttachStderr = false
|
|
|
|
config.StdinOnce = false
|
|
|
|
}
|
|
|
|
|
2023-09-09 18:27:44 -04:00
|
|
|
ctx, cancelFun := context.WithCancel(ctx)
|
2018-05-07 17:31:30 -04:00
|
|
|
defer cancelFun()
|
2016-09-08 13:11:39 -04:00
|
|
|
|
2023-11-20 11:38:50 -05:00
|
|
|
containerID, err := createContainer(ctx, dockerCli, containerCfg, &runOpts.createOptions)
|
2016-09-08 13:11:39 -04:00
|
|
|
if err != nil {
|
2018-04-23 08:57:29 -04:00
|
|
|
reportError(stderr, "run", err.Error(), true)
|
2016-09-08 13:11:39 -04:00
|
|
|
return runStartContainerErr(err)
|
|
|
|
}
|
2023-11-20 11:38:50 -05:00
|
|
|
if runOpts.sigProxy {
|
2021-09-16 09:46:27 -04:00
|
|
|
sigc := notifyAllSignals()
|
2024-04-08 04:11:09 -04:00
|
|
|
// since we're explicitly setting up signal handling here, and the daemon will
|
|
|
|
// get notified independently of the clients ctx cancellation, we use this context
|
|
|
|
// but without cancellation to avoid ForwardAllSignals from returning
|
|
|
|
// before all signals are forwarded.
|
|
|
|
bgCtx := context.WithoutCancel(ctx)
|
|
|
|
go ForwardAllSignals(bgCtx, apiClient, containerID, sigc)
|
2016-09-08 13:11:39 -04:00
|
|
|
defer signal.StopCatch(sigc)
|
|
|
|
}
|
2017-01-31 00:15:51 -05:00
|
|
|
|
2016-09-08 13:11:39 -04:00
|
|
|
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)
|
2023-06-08 10:46:15 -04:00
|
|
|
_, _ = fmt.Fprintln(stdout, containerID)
|
2016-09-08 13:11:39 -04:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
attach := config.AttachStdin || config.AttachStdout || config.AttachStderr
|
|
|
|
if attach {
|
2023-06-08 10:53:30 -04:00
|
|
|
detachKeys := dockerCli.ConfigFile().DetachKeys
|
2023-11-20 11:38:50 -05:00
|
|
|
if runOpts.detachKeys != "" {
|
|
|
|
detachKeys = runOpts.detachKeys
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
2024-07-08 11:44:33 -04:00
|
|
|
// ctx should not be cancellable here, as this would kill the stream to the container
|
|
|
|
// and we want to keep the stream open until the process in the container exits or until
|
|
|
|
// the user forcefully terminates the CLI.
|
2024-07-11 03:49:14 -04:00
|
|
|
closeFn, err := attachContainer(ctx, dockerCli, containerID, &errCh, config, container.AttachOptions{
|
2023-06-08 10:53:30 -04:00
|
|
|
Stream: true,
|
|
|
|
Stdin: config.AttachStdin,
|
|
|
|
Stdout: config.AttachStdout,
|
|
|
|
Stderr: config.AttachStderr,
|
|
|
|
DetachKeys: detachKeys,
|
|
|
|
})
|
2017-03-07 17:19:54 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
2023-03-29 09:32:00 -04:00
|
|
|
defer closeFn()
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
2024-05-05 17:45:37 -04:00
|
|
|
// New context here because we don't to cancel waiting on container exit/remove
|
|
|
|
// when we cancel attach, etc.
|
|
|
|
statusCtx, cancelStatusCtx := context.WithCancel(context.WithoutCancel(ctx))
|
|
|
|
defer cancelStatusCtx()
|
|
|
|
statusChan := waitExitOrRemoved(statusCtx, apiClient, containerID, copts.autoRemove)
|
2016-09-08 13:11:39 -04:00
|
|
|
|
2020-01-16 06:47:12 -05:00
|
|
|
// start the container
|
2023-11-06 04:34:37 -05:00
|
|
|
if err := apiClient.ContainerStart(ctx, containerID, container.StartOptions{}); err != nil {
|
2017-05-17 14:40:56 -04:00
|
|
|
// If we have hijackedIOStreamer, we should notify
|
|
|
|
// hijackedIOStreamer we are going to exit and wait
|
2016-09-08 13:11:39 -04:00
|
|
|
// to avoid the terminal are not restored.
|
|
|
|
if attach {
|
|
|
|
cancelFun()
|
|
|
|
<-errCh
|
|
|
|
}
|
|
|
|
|
2018-04-23 08:57:29 -04:00
|
|
|
reportError(stderr, "run", err.Error(), false)
|
Don't use AutoRemove on older daemons
Docker 1.13 moves the `--rm` flag to the daemon,
through an AutoRemove option in HostConfig.
When using API 1.24 and under, AutoRemove should not be
used, even if the daemon is version 1.13 or above and
"supports" this feature.
This patch fixes a situation where an 1.13 client,
talking to an 1.13 daemon, but using the 1.24 API
version, still set the AutoRemove property.
As a result, both the client _and_ the daemon
were attempting to remove the container, resulting
in an error:
ERRO[0000] error removing container: Error response from daemon:
removal of container ce0976ad22495c7cbe9487752ea32721a282164862db036b2f3377bd07461c3a
is already in progress
In addition, the validation of conflicting options
is moved from `docker run` to `opts.parse()`, so
that conflicting options are also detected when
running `docker create` and `docker start` separately.
To resolve the issue, the `AutoRemove` option is now
always set to `false` both by the client and the
daemon, if API version 1.24 or under is used.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-01-12 19:05:39 -05:00
|
|
|
if copts.autoRemove {
|
2016-09-08 13:11:39 -04:00
|
|
|
// wait container to be removed
|
|
|
|
<-statusChan
|
|
|
|
}
|
|
|
|
return runStartContainerErr(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && dockerCli.Out().IsTerminal() {
|
2023-06-08 10:46:15 -04:00
|
|
|
if err := MonitorTtySize(ctx, dockerCli, containerID, false); err != nil {
|
|
|
|
_, _ = fmt.Fprintln(stderr, "Error monitoring TTY size:", err)
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if errCh != nil {
|
|
|
|
if err := <-errCh; err != nil {
|
2017-05-16 21:31:04 -04:00
|
|
|
if _, ok := err.(term.EscapeError); ok {
|
|
|
|
// The user entered the detach escape sequence.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-09-08 13:11:39 -04:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
status := <-statusChan
|
|
|
|
if status != 0 {
|
|
|
|
return cli.StatusError{StatusCode: status}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-10-13 14:34:32 -04:00
|
|
|
func attachContainer(ctx context.Context, dockerCli command.Cli, containerID string, errCh *chan error, config *container.Config, options container.AttachOptions) (func(), error) {
|
2021-02-22 13:08:43 -05:00
|
|
|
resp, errAttach := dockerCli.Client().ContainerAttach(ctx, containerID, options)
|
|
|
|
if errAttach != nil {
|
|
|
|
return nil, errAttach
|
|
|
|
}
|
|
|
|
|
2017-03-07 17:19:54 -05:00
|
|
|
var (
|
|
|
|
out, cerr io.Writer
|
|
|
|
in io.ReadCloser
|
|
|
|
)
|
2023-06-08 10:53:30 -04:00
|
|
|
if options.Stdin {
|
2017-03-07 17:19:54 -05:00
|
|
|
in = dockerCli.In()
|
|
|
|
}
|
2023-06-08 10:53:30 -04:00
|
|
|
if options.Stdout {
|
2021-02-22 13:08:43 -05:00
|
|
|
out = dockerCli.Out()
|
2017-03-07 17:19:54 -05:00
|
|
|
}
|
2023-06-08 10:53:30 -04:00
|
|
|
if options.Stderr {
|
2017-03-07 17:19:54 -05:00
|
|
|
if config.Tty {
|
2021-02-22 13:08:43 -05:00
|
|
|
cerr = dockerCli.Out()
|
2017-03-07 17:19:54 -05:00
|
|
|
} else {
|
2021-02-22 13:08:43 -05:00
|
|
|
cerr = dockerCli.Err()
|
2017-03-07 17:19:54 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-29 21:12:03 -04:00
|
|
|
ch := make(chan error, 1)
|
|
|
|
*errCh = ch
|
|
|
|
|
2017-09-29 08:18:19 -04:00
|
|
|
go func() {
|
2017-09-29 21:12:03 -04:00
|
|
|
ch <- func() error {
|
2017-09-29 08:18:19 -04:00
|
|
|
streamer := hijackedIOStreamer{
|
|
|
|
streams: dockerCli,
|
|
|
|
inputStream: in,
|
|
|
|
outputStream: out,
|
|
|
|
errorStream: cerr,
|
|
|
|
resp: resp,
|
|
|
|
tty: config.Tty,
|
|
|
|
detachKeys: options.DetachKeys,
|
|
|
|
}
|
2017-05-17 14:40:56 -04:00
|
|
|
|
2017-09-29 08:18:19 -04:00
|
|
|
if errHijack := streamer.stream(ctx); errHijack != nil {
|
|
|
|
return errHijack
|
|
|
|
}
|
|
|
|
return errAttach
|
|
|
|
}()
|
|
|
|
}()
|
2017-03-07 17:19:54 -05:00
|
|
|
return resp.Close, nil
|
|
|
|
}
|
|
|
|
|
2016-09-08 13:11:39 -04:00
|
|
|
// 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) {
|
2016-12-28 12:09:25 -05:00
|
|
|
str = strings.TrimSuffix(str, ".") + "."
|
2016-09-08 13:11:39 -04:00
|
|
|
if withHelp {
|
2020-06-09 10:19:45 -04:00
|
|
|
str += "\nSee 'docker " + name + " --help'."
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
2021-02-22 13:08:43 -05:00
|
|
|
_, _ = fmt.Fprintln(stderr, "docker:", str)
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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}
|
2023-02-03 17:49:12 -05:00
|
|
|
} else if strings.Contains(trimmedErr, syscall.EACCES.Error()) ||
|
|
|
|
strings.Contains(trimmedErr, syscall.EISDIR.Error()) {
|
2016-09-08 13:11:39 -04:00
|
|
|
statusError = cli.StatusError{StatusCode: 126}
|
|
|
|
}
|
|
|
|
|
|
|
|
return statusError
|
|
|
|
}
|