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
|
|
|
|
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-05-09 18:35:25 -04:00
|
|
|
"github.com/docker/cli/cli/config/configfile"
|
2017-05-15 08:45:19 -04:00
|
|
|
"github.com/docker/cli/opts"
|
2024-06-09 07:54:37 -04:00
|
|
|
"github.com/docker/docker/api/types/container"
|
2024-06-09 08:34:01 -04:00
|
|
|
"github.com/docker/docker/client"
|
2017-05-09 18:35:25 -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"
|
|
|
|
)
|
|
|
|
|
2022-02-22 04:36:43 -05:00
|
|
|
// ExecOptions group options for `exec` command
|
|
|
|
type ExecOptions struct {
|
|
|
|
DetachKeys string
|
|
|
|
Interactive bool
|
|
|
|
TTY bool
|
|
|
|
Detach bool
|
|
|
|
User string
|
|
|
|
Privileged bool
|
|
|
|
Env opts.ListOpts
|
|
|
|
Workdir string
|
|
|
|
Command []string
|
|
|
|
EnvFile opts.ListOpts
|
2016-07-13 13:24:41 -04:00
|
|
|
}
|
|
|
|
|
2022-02-22 04:36:43 -05:00
|
|
|
// NewExecOptions creates a new ExecOptions
|
|
|
|
func NewExecOptions() ExecOptions {
|
|
|
|
return ExecOptions{
|
|
|
|
Env: opts.NewListOpts(opts.ValidateEnv),
|
|
|
|
EnvFile: opts.NewListOpts(nil),
|
2020-06-29 18:32:44 -04:00
|
|
|
}
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
2017-02-16 10:56:53 -05:00
|
|
|
// NewExecCommand creates a new cobra.Command for `docker exec`
|
2017-06-07 19:03:52 -04:00
|
|
|
func NewExecCommand(dockerCli command.Cli) *cobra.Command {
|
2022-02-22 04:36:43 -05:00
|
|
|
options := NewExecOptions()
|
2016-09-08 13:11:39 -04:00
|
|
|
|
|
|
|
cmd := &cobra.Command{
|
|
|
|
Use: "exec [OPTIONS] CONTAINER COMMAND [ARG...]",
|
2022-03-29 17:54:35 -04:00
|
|
|
Short: "Execute a command in a running container",
|
2016-09-08 13:11:39 -04:00
|
|
|
Args: cli.RequiresMinArgs(2),
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
2024-06-09 08:34:01 -04:00
|
|
|
containerIDorName := args[0]
|
2022-02-22 04:36:43 -05:00
|
|
|
options.Command = args[1:]
|
2024-06-09 08:34:01 -04:00
|
|
|
return RunExec(cmd.Context(), dockerCli, containerIDorName, options)
|
2016-09-08 13:11:39 -04:00
|
|
|
},
|
2024-07-03 09:35:44 -04:00
|
|
|
ValidArgsFunction: completion.ContainerNames(dockerCli, false, func(ctr container.Summary) bool {
|
2024-06-09 08:34:01 -04:00
|
|
|
return ctr.State != "paused"
|
2022-05-12 07:18:48 -04:00
|
|
|
}),
|
2022-03-30 03:37:08 -04:00
|
|
|
Annotations: map[string]string{
|
|
|
|
"category-top": "2",
|
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 exec, docker exec",
|
2022-03-30 03:37:08 -04:00
|
|
|
},
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
flags := cmd.Flags()
|
|
|
|
flags.SetInterspersed(false)
|
|
|
|
|
2024-01-29 05:16:14 -05:00
|
|
|
flags.StringVar(&options.DetachKeys, "detach-keys", "", "Override the key sequence for detaching a container")
|
2022-02-22 04:36:43 -05:00
|
|
|
flags.BoolVarP(&options.Interactive, "interactive", "i", false, "Keep STDIN open even if not attached")
|
|
|
|
flags.BoolVarP(&options.TTY, "tty", "t", false, "Allocate a pseudo-TTY")
|
|
|
|
flags.BoolVarP(&options.Detach, "detach", "d", false, "Detached mode: run command in the background")
|
2023-01-03 05:12:24 -05:00
|
|
|
flags.StringVarP(&options.User, "user", "u", "", `Username or UID (format: "<name|uid>[:<group|gid>]")`)
|
2024-01-29 05:16:14 -05:00
|
|
|
flags.BoolVar(&options.Privileged, "privileged", false, "Give extended privileges to the command")
|
2022-02-22 04:36:43 -05:00
|
|
|
flags.VarP(&options.Env, "env", "e", "Set environment variables")
|
2016-11-02 20:43:32 -04:00
|
|
|
flags.SetAnnotation("env", "version", []string{"1.25"})
|
2022-02-22 04:36:43 -05:00
|
|
|
flags.Var(&options.EnvFile, "env-file", "Read in a file of environment variables")
|
2020-06-29 18:32:44 -04:00
|
|
|
flags.SetAnnotation("env-file", "version", []string{"1.25"})
|
2022-02-22 04:36:43 -05:00
|
|
|
flags.StringVarP(&options.Workdir, "workdir", "w", "", "Working directory inside the container")
|
2017-12-06 04:33:36 -05:00
|
|
|
flags.SetAnnotation("workdir", "version", []string{"1.35"})
|
2022-03-30 09:27:25 -04:00
|
|
|
|
2024-06-09 08:34:01 -04:00
|
|
|
_ = cmd.RegisterFlagCompletionFunc("env", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
|
|
|
|
return os.Environ(), cobra.ShellCompDirectiveNoFileComp
|
|
|
|
})
|
2024-07-05 07:42:46 -04:00
|
|
|
_ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames)
|
2016-09-08 13:11:39 -04:00
|
|
|
|
|
|
|
return cmd
|
|
|
|
}
|
|
|
|
|
2022-02-22 04:36:43 -05:00
|
|
|
// RunExec executes an `exec` command
|
2024-06-09 08:34:01 -04:00
|
|
|
func RunExec(ctx context.Context, dockerCli command.Cli, containerIDorName string, options ExecOptions) error {
|
|
|
|
execOptions, err := parseExec(options, dockerCli.ConfigFile())
|
2020-06-29 18:32:44 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-06-09 08:34:01 -04:00
|
|
|
apiClient := dockerCli.Client()
|
2016-09-08 13:11:39 -04:00
|
|
|
|
2017-05-09 07:07:40 -04:00
|
|
|
// We need to check the tty _before_ we do the ContainerExecCreate, because
|
|
|
|
// otherwise if we error out we will leak execIDs on the server (and
|
|
|
|
// there's no easy way to clean those up). But also in order to make "not
|
|
|
|
// exist" errors take precedence we do a dummy inspect first.
|
2024-06-09 08:34:01 -04:00
|
|
|
if _, err := apiClient.ContainerInspect(ctx, containerIDorName); err != nil {
|
2017-05-09 07:07:40 -04:00
|
|
|
return err
|
|
|
|
}
|
2024-06-09 08:34:01 -04:00
|
|
|
if !execOptions.Detach {
|
|
|
|
if err := dockerCli.In().CheckTty(execOptions.AttachStdin, execOptions.Tty); err != nil {
|
2017-05-09 07:07:40 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-09 08:34:01 -04:00
|
|
|
fillConsoleSize(execOptions, dockerCli)
|
2022-06-14 12:20:00 -04:00
|
|
|
|
2024-06-09 08:34:01 -04:00
|
|
|
response, err := apiClient.ContainerExecCreate(ctx, containerIDorName, *execOptions)
|
2016-09-08 13:11:39 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
execID := response.ID
|
|
|
|
if execID == "" {
|
2017-05-09 18:35:25 -04:00
|
|
|
return errors.New("exec ID empty")
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
2024-06-09 08:34:01 -04:00
|
|
|
if execOptions.Detach {
|
2024-06-09 07:54:37 -04:00
|
|
|
return apiClient.ContainerExecStart(ctx, execID, container.ExecStartOptions{
|
2024-06-09 08:34:01 -04:00
|
|
|
Detach: execOptions.Detach,
|
|
|
|
Tty: execOptions.Tty,
|
|
|
|
ConsoleSize: execOptions.ConsoleSize,
|
|
|
|
})
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
2024-06-09 08:34:01 -04:00
|
|
|
return interactiveExec(ctx, dockerCli, execOptions, execID)
|
2017-05-09 18:35:25 -04:00
|
|
|
}
|
2016-09-08 13:11:39 -04:00
|
|
|
|
2024-06-09 07:54:37 -04:00
|
|
|
func fillConsoleSize(execOptions *container.ExecOptions, dockerCli command.Cli) {
|
|
|
|
if execOptions.Tty {
|
2022-06-14 12:20:00 -04:00
|
|
|
height, width := dockerCli.Out().GetTtySize()
|
2024-06-09 07:54:37 -04:00
|
|
|
execOptions.ConsoleSize = &[2]uint{height, width}
|
2022-06-14 12:20:00 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-09 07:54:37 -04:00
|
|
|
func interactiveExec(ctx context.Context, dockerCli command.Cli, execOptions *container.ExecOptions, execID string) error {
|
2016-09-08 13:11:39 -04:00
|
|
|
// Interactive exec requested.
|
|
|
|
var (
|
|
|
|
out, stderr io.Writer
|
|
|
|
in io.ReadCloser
|
|
|
|
)
|
|
|
|
|
2024-06-09 08:34:01 -04:00
|
|
|
if execOptions.AttachStdin {
|
2016-09-08 13:11:39 -04:00
|
|
|
in = dockerCli.In()
|
|
|
|
}
|
2024-06-09 08:34:01 -04:00
|
|
|
if execOptions.AttachStdout {
|
2016-09-08 13:11:39 -04:00
|
|
|
out = dockerCli.Out()
|
|
|
|
}
|
2024-06-09 08:34:01 -04:00
|
|
|
if execOptions.AttachStderr {
|
|
|
|
if execOptions.Tty {
|
2016-09-08 13:11:39 -04:00
|
|
|
stderr = dockerCli.Out()
|
|
|
|
} else {
|
|
|
|
stderr = dockerCli.Err()
|
|
|
|
}
|
|
|
|
}
|
2024-06-09 08:34:01 -04:00
|
|
|
fillConsoleSize(execOptions, dockerCli)
|
2016-09-08 13:11:39 -04:00
|
|
|
|
2024-06-09 08:34:01 -04:00
|
|
|
apiClient := dockerCli.Client()
|
2024-06-09 07:54:37 -04:00
|
|
|
resp, err := apiClient.ContainerExecAttach(ctx, execID, container.ExecAttachOptions{
|
2024-06-09 08:34:01 -04:00
|
|
|
Tty: execOptions.Tty,
|
|
|
|
ConsoleSize: execOptions.ConsoleSize,
|
|
|
|
})
|
2016-09-08 13:11:39 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer resp.Close()
|
2017-05-17 14:40:56 -04:00
|
|
|
|
2017-09-29 08:18:19 -04:00
|
|
|
errCh := make(chan error, 1)
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
defer close(errCh)
|
|
|
|
errCh <- func() error {
|
|
|
|
streamer := hijackedIOStreamer{
|
|
|
|
streams: dockerCli,
|
|
|
|
inputStream: in,
|
|
|
|
outputStream: out,
|
|
|
|
errorStream: stderr,
|
|
|
|
resp: resp,
|
2024-06-09 08:34:01 -04:00
|
|
|
tty: execOptions.Tty,
|
|
|
|
detachKeys: execOptions.DetachKeys,
|
2017-09-29 08:18:19 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return streamer.stream(ctx)
|
|
|
|
}()
|
|
|
|
}()
|
2016-09-08 13:11:39 -04:00
|
|
|
|
2024-06-09 08:34:01 -04:00
|
|
|
if execOptions.Tty && dockerCli.In().IsTerminal() {
|
2016-09-08 13:11:39 -04:00
|
|
|
if err := MonitorTtySize(ctx, dockerCli, execID, true); err != nil {
|
2024-06-09 08:34:01 -04:00
|
|
|
_, _ = fmt.Fprintln(dockerCli.Err(), "Error monitoring TTY size:", err)
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := <-errCh; err != nil {
|
|
|
|
logrus.Debugf("Error hijack: %s", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-06-09 08:34:01 -04:00
|
|
|
return getExecExitStatus(ctx, apiClient, execID)
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
2024-06-09 08:34:01 -04:00
|
|
|
func getExecExitStatus(ctx context.Context, apiClient client.ContainerAPIClient, execID string) error {
|
|
|
|
resp, err := apiClient.ContainerExecInspect(ctx, execID)
|
2016-09-08 13:11:39 -04:00
|
|
|
if err != nil {
|
|
|
|
// If we can't connect, then the daemon probably died.
|
2024-06-09 08:34:01 -04:00
|
|
|
if !client.IsErrConnectionFailed(err) {
|
2017-05-09 18:35:25 -04:00
|
|
|
return err
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
2017-05-09 18:35:25 -04:00
|
|
|
return cli.StatusError{StatusCode: -1}
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
2017-05-09 18:35:25 -04:00
|
|
|
status := resp.ExitCode
|
|
|
|
if status != 0 {
|
|
|
|
return cli.StatusError{StatusCode: status}
|
|
|
|
}
|
|
|
|
return nil
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// parseExec parses the specified args for the specified command and generates
|
|
|
|
// an ExecConfig from it.
|
2024-06-09 07:54:37 -04:00
|
|
|
func parseExec(execOpts ExecOptions, configFile *configfile.ConfigFile) (*container.ExecOptions, error) {
|
|
|
|
execOptions := &container.ExecOptions{
|
2022-02-22 04:36:43 -05:00
|
|
|
User: execOpts.User,
|
|
|
|
Privileged: execOpts.Privileged,
|
|
|
|
Tty: execOpts.TTY,
|
|
|
|
Cmd: execOpts.Command,
|
|
|
|
Detach: execOpts.Detach,
|
|
|
|
WorkingDir: execOpts.Workdir,
|
2020-06-29 18:32:44 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// collect all the environment variables for the container
|
|
|
|
var err error
|
2024-06-09 08:34:01 -04:00
|
|
|
if execOptions.Env, err = opts.ReadKVEnvStrings(execOpts.EnvFile.GetAll(), execOpts.Env.GetAll()); err != nil {
|
2020-06-29 18:32:44 -04:00
|
|
|
return nil, err
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// If -d is not set, attach to everything by default
|
2022-02-22 04:36:43 -05:00
|
|
|
if !execOpts.Detach {
|
2024-06-09 08:34:01 -04:00
|
|
|
execOptions.AttachStdout = true
|
|
|
|
execOptions.AttachStderr = true
|
2022-02-22 04:36:43 -05:00
|
|
|
if execOpts.Interactive {
|
2024-06-09 08:34:01 -04:00
|
|
|
execOptions.AttachStdin = true
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-22 04:36:43 -05:00
|
|
|
if execOpts.DetachKeys != "" {
|
2024-06-09 08:34:01 -04:00
|
|
|
execOptions.DetachKeys = execOpts.DetachKeys
|
2017-05-09 18:35:25 -04:00
|
|
|
} else {
|
2024-06-09 08:34:01 -04:00
|
|
|
execOptions.DetachKeys = configFile.DetachKeys
|
2016-07-13 13:24:41 -04:00
|
|
|
}
|
2024-06-09 08:34:01 -04:00
|
|
|
return execOptions, nil
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|