github.com/endophage/docker@v1.4.2-0.20161027011718-242853499895/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 63 return cmd 64 } 65 66 func runExec(dockerCli *command.DockerCli, opts *execOptions, container string, execCmd []string) error { 67 execConfig, err := parseExec(opts, container, execCmd) 68 // just in case the ParseExec does not exit 69 if container == "" || err != nil { 70 return cli.StatusError{StatusCode: 1} 71 } 72 73 if opts.detachKeys != "" { 74 dockerCli.ConfigFile().DetachKeys = opts.detachKeys 75 } 76 77 // Send client escape keys 78 execConfig.DetachKeys = dockerCli.ConfigFile().DetachKeys 79 80 ctx := context.Background() 81 client := dockerCli.Client() 82 83 response, err := client.ContainerExecCreate(ctx, container, *execConfig) 84 if err != nil { 85 return err 86 } 87 88 execID := response.ID 89 if execID == "" { 90 fmt.Fprintf(dockerCli.Out(), "exec ID empty") 91 return nil 92 } 93 94 //Temp struct for execStart so that we don't need to transfer all the execConfig 95 if !execConfig.Detach { 96 if err := dockerCli.In().CheckTty(execConfig.AttachStdin, execConfig.Tty); err != nil { 97 return err 98 } 99 } else { 100 execStartCheck := types.ExecStartCheck{ 101 Detach: execConfig.Detach, 102 Tty: execConfig.Tty, 103 } 104 105 if err := client.ContainerExecStart(ctx, execID, execStartCheck); err != nil { 106 return err 107 } 108 // For now don't print this - wait for when we support exec wait() 109 // fmt.Fprintf(dockerCli.Out(), "%s\n", execID) 110 return nil 111 } 112 113 // Interactive exec requested. 114 var ( 115 out, stderr io.Writer 116 in io.ReadCloser 117 errCh chan error 118 ) 119 120 if execConfig.AttachStdin { 121 in = dockerCli.In() 122 } 123 if execConfig.AttachStdout { 124 out = dockerCli.Out() 125 } 126 if execConfig.AttachStderr { 127 if execConfig.Tty { 128 stderr = dockerCli.Out() 129 } else { 130 stderr = dockerCli.Err() 131 } 132 } 133 134 resp, err := client.ContainerExecAttach(ctx, execID, *execConfig) 135 if err != nil { 136 return err 137 } 138 defer resp.Close() 139 errCh = promise.Go(func() error { 140 return holdHijackedConnection(ctx, dockerCli, execConfig.Tty, in, out, stderr, resp) 141 }) 142 143 if execConfig.Tty && dockerCli.In().IsTerminal() { 144 if err := MonitorTtySize(ctx, dockerCli, execID, true); err != nil { 145 fmt.Fprintf(dockerCli.Err(), "Error monitoring TTY size: %s\n", err) 146 } 147 } 148 149 if err := <-errCh; err != nil { 150 logrus.Debugf("Error hijack: %s", err) 151 return err 152 } 153 154 var status int 155 if _, status, err = getExecExitCode(ctx, client, execID); err != nil { 156 return err 157 } 158 159 if status != 0 { 160 return cli.StatusError{StatusCode: status} 161 } 162 163 return nil 164 } 165 166 // getExecExitCode perform an inspect on the exec command. It returns 167 // the running state and the exit code. 168 func getExecExitCode(ctx context.Context, client apiclient.ContainerAPIClient, execID string) (bool, int, error) { 169 resp, err := client.ContainerExecInspect(ctx, execID) 170 if err != nil { 171 // If we can't connect, then the daemon probably died. 172 if err != apiclient.ErrConnectionFailed { 173 return false, -1, err 174 } 175 return false, -1, nil 176 } 177 178 return resp.Running, resp.ExitCode, nil 179 } 180 181 // parseExec parses the specified args for the specified command and generates 182 // an ExecConfig from it. 183 func parseExec(opts *execOptions, container string, execCmd []string) (*types.ExecConfig, error) { 184 execConfig := &types.ExecConfig{ 185 User: opts.user, 186 Privileged: opts.privileged, 187 Tty: opts.tty, 188 Cmd: execCmd, 189 Detach: opts.detach, 190 // container is not used here 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 }