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