github.com/kunnos/engine@v1.13.1/cli/command/container/exec.go (about) 1 package container 2 3 import ( 4 "fmt" 5 "io" 6 7 "golang.org/x/net/context" 8 9 "github.com/sirupsen/logrus" 10 "github.com/docker/docker/api/types" 11 "github.com/docker/docker/cli" 12 "github.com/docker/docker/cli/command" 13 apiclient "github.com/docker/docker/client" 14 options "github.com/docker/docker/opts" 15 "github.com/docker/docker/pkg/promise" 16 runconfigopts "github.com/docker/docker/runconfig/opts" 17 "github.com/spf13/cobra" 18 ) 19 20 type execOptions struct { 21 detachKeys string 22 interactive bool 23 tty bool 24 detach bool 25 user string 26 privileged bool 27 env *options.ListOpts 28 } 29 30 func newExecOptions() *execOptions { 31 var values []string 32 return &execOptions{ 33 env: options.NewListOptsRef(&values, runconfigopts.ValidateEnv), 34 } 35 } 36 37 // NewExecCommand creats a new cobra.Command for `docker exec` 38 func NewExecCommand(dockerCli *command.DockerCli) *cobra.Command { 39 opts := newExecOptions() 40 41 cmd := &cobra.Command{ 42 Use: "exec [OPTIONS] CONTAINER COMMAND [ARG...]", 43 Short: "Run a command in a running container", 44 Args: cli.RequiresMinArgs(2), 45 RunE: func(cmd *cobra.Command, args []string) error { 46 container := args[0] 47 execCmd := args[1:] 48 return runExec(dockerCli, opts, container, execCmd) 49 }, 50 } 51 52 flags := cmd.Flags() 53 flags.SetInterspersed(false) 54 55 flags.StringVarP(&opts.detachKeys, "detach-keys", "", "", "Override the key sequence for detaching a container") 56 flags.BoolVarP(&opts.interactive, "interactive", "i", false, "Keep STDIN open even if not attached") 57 flags.BoolVarP(&opts.tty, "tty", "t", false, "Allocate a pseudo-TTY") 58 flags.BoolVarP(&opts.detach, "detach", "d", false, "Detached mode: run command in the background") 59 flags.StringVarP(&opts.user, "user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])") 60 flags.BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the command") 61 flags.VarP(opts.env, "env", "e", "Set environment variables") 62 flags.SetAnnotation("env", "version", []string{"1.25"}) 63 64 return cmd 65 } 66 67 func runExec(dockerCli *command.DockerCli, opts *execOptions, container string, execCmd []string) error { 68 execConfig, err := parseExec(opts, execCmd) 69 // just in case the ParseExec does not exit 70 if container == "" || err != nil { 71 return cli.StatusError{StatusCode: 1} 72 } 73 74 if opts.detachKeys != "" { 75 dockerCli.ConfigFile().DetachKeys = opts.detachKeys 76 } 77 78 // Send client escape keys 79 execConfig.DetachKeys = dockerCli.ConfigFile().DetachKeys 80 81 ctx := context.Background() 82 client := dockerCli.Client() 83 84 response, err := client.ContainerExecCreate(ctx, container, *execConfig) 85 if err != nil { 86 return err 87 } 88 89 execID := response.ID 90 if execID == "" { 91 fmt.Fprintf(dockerCli.Out(), "exec ID empty") 92 return nil 93 } 94 95 //Temp struct for execStart so that we don't need to transfer all the execConfig 96 if !execConfig.Detach { 97 if err := dockerCli.In().CheckTty(execConfig.AttachStdin, execConfig.Tty); err != nil { 98 return err 99 } 100 } else { 101 execStartCheck := types.ExecStartCheck{ 102 Detach: execConfig.Detach, 103 Tty: execConfig.Tty, 104 } 105 106 if err := client.ContainerExecStart(ctx, execID, execStartCheck); err != nil { 107 return err 108 } 109 // For now don't print this - wait for when we support exec wait() 110 // fmt.Fprintf(dockerCli.Out(), "%s\n", execID) 111 return nil 112 } 113 114 // Interactive exec requested. 115 var ( 116 out, stderr io.Writer 117 in io.ReadCloser 118 errCh chan error 119 ) 120 121 if execConfig.AttachStdin { 122 in = dockerCli.In() 123 } 124 if execConfig.AttachStdout { 125 out = dockerCli.Out() 126 } 127 if execConfig.AttachStderr { 128 if execConfig.Tty { 129 stderr = dockerCli.Out() 130 } else { 131 stderr = dockerCli.Err() 132 } 133 } 134 135 resp, err := client.ContainerExecAttach(ctx, execID, *execConfig) 136 if err != nil { 137 return err 138 } 139 defer resp.Close() 140 errCh = promise.Go(func() error { 141 return holdHijackedConnection(ctx, dockerCli, execConfig.Tty, in, out, stderr, resp) 142 }) 143 144 if execConfig.Tty && dockerCli.In().IsTerminal() { 145 if err := MonitorTtySize(ctx, dockerCli, execID, true); err != nil { 146 fmt.Fprintf(dockerCli.Err(), "Error monitoring TTY size: %s\n", err) 147 } 148 } 149 150 if err := <-errCh; err != nil { 151 logrus.Debugf("Error hijack: %s", err) 152 return err 153 } 154 155 var status int 156 if _, status, err = getExecExitCode(ctx, client, execID); err != nil { 157 return err 158 } 159 160 if status != 0 { 161 return cli.StatusError{StatusCode: status} 162 } 163 164 return nil 165 } 166 167 // getExecExitCode perform an inspect on the exec command. It returns 168 // the running state and the exit code. 169 func getExecExitCode(ctx context.Context, client apiclient.ContainerAPIClient, execID string) (bool, int, error) { 170 resp, err := client.ContainerExecInspect(ctx, execID) 171 if err != nil { 172 // If we can't connect, then the daemon probably died. 173 if !apiclient.IsErrConnectionFailed(err) { 174 return false, -1, err 175 } 176 return false, -1, nil 177 } 178 179 return resp.Running, resp.ExitCode, nil 180 } 181 182 // parseExec parses the specified args for the specified command and generates 183 // an ExecConfig from it. 184 func parseExec(opts *execOptions, execCmd []string) (*types.ExecConfig, error) { 185 execConfig := &types.ExecConfig{ 186 User: opts.user, 187 Privileged: opts.privileged, 188 Tty: opts.tty, 189 Cmd: execCmd, 190 Detach: opts.detach, 191 } 192 193 // If -d is not set, attach to everything by default 194 if !opts.detach { 195 execConfig.AttachStdout = true 196 execConfig.AttachStderr = true 197 if opts.interactive { 198 execConfig.AttachStdin = true 199 } 200 } 201 202 if opts.env != nil { 203 execConfig.Env = opts.env.GetAll() 204 } 205 206 return execConfig, nil 207 }