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