1// Copyright 2014 go-dockerclient authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package docker 6 7import ( 8 "context" 9 "encoding/json" 10 "errors" 11 "fmt" 12 "io" 13 "net/http" 14 "net/url" 15 "strconv" 16) 17 18// Exec is the type representing a `docker exec` instance and containing the 19// instance ID 20type Exec struct { 21 ID string `json:"Id,omitempty" yaml:"Id,omitempty"` 22} 23 24// CreateExecOptions specify parameters to the CreateExecContainer function. 25// 26// See https://goo.gl/60TeBP for more details 27type CreateExecOptions struct { 28 AttachStdin bool `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty" toml:"AttachStdin,omitempty"` 29 AttachStdout bool `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty" toml:"AttachStdout,omitempty"` 30 AttachStderr bool `json:"AttachStderr,omitempty" yaml:"AttachStderr,omitempty" toml:"AttachStderr,omitempty"` 31 Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty" toml:"Tty,omitempty"` 32 Env []string `json:"Env,omitempty" yaml:"Env,omitempty" toml:"Env,omitempty"` 33 Cmd []string `json:"Cmd,omitempty" yaml:"Cmd,omitempty" toml:"Cmd,omitempty"` 34 Container string `json:"Container,omitempty" yaml:"Container,omitempty" toml:"Container,omitempty"` 35 User string `json:"User,omitempty" yaml:"User,omitempty" toml:"User,omitempty"` 36 WorkingDir string `json:"WorkingDir,omitempty" yaml:"WorkingDir,omitempty" toml:"WorkingDir,omitempty"` 37 Context context.Context `json:"-"` 38 Privileged bool `json:"Privileged,omitempty" yaml:"Privileged,omitempty" toml:"Privileged,omitempty"` 39} 40 41// CreateExec sets up an exec instance in a running container `id`, returning the exec 42// instance, or an error in case of failure. 43// 44// See https://goo.gl/60TeBP for more details 45func (c *Client) CreateExec(opts CreateExecOptions) (*Exec, error) { 46 if len(opts.Env) > 0 && c.serverAPIVersion.LessThan(apiVersion125) { 47 return nil, errors.New("exec configuration Env is only supported in API#1.25 and above") 48 } 49 if len(opts.WorkingDir) > 0 && c.serverAPIVersion.LessThan(apiVersion135) { 50 return nil, errors.New("exec configuration WorkingDir is only supported in API#1.35 and above") 51 } 52 path := fmt.Sprintf("/containers/%s/exec", opts.Container) 53 resp, err := c.do("POST", path, doOptions{data: opts, context: opts.Context}) 54 if err != nil { 55 if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { 56 return nil, &NoSuchContainer{ID: opts.Container} 57 } 58 return nil, err 59 } 60 defer resp.Body.Close() 61 var exec Exec 62 if err := json.NewDecoder(resp.Body).Decode(&exec); err != nil { 63 return nil, err 64 } 65 66 return &exec, nil 67} 68 69// StartExecOptions specify parameters to the StartExecContainer function. 70// 71// See https://goo.gl/1EeDWi for more details 72type StartExecOptions struct { 73 InputStream io.Reader `qs:"-"` 74 OutputStream io.Writer `qs:"-"` 75 ErrorStream io.Writer `qs:"-"` 76 77 Detach bool `json:"Detach,omitempty" yaml:"Detach,omitempty" toml:"Detach,omitempty"` 78 Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty" toml:"Tty,omitempty"` 79 80 // Use raw terminal? Usually true when the container contains a TTY. 81 RawTerminal bool `qs:"-"` 82 83 // If set, after a successful connect, a sentinel will be sent and then the 84 // client will block on receive before continuing. 85 // 86 // It must be an unbuffered channel. Using a buffered channel can lead 87 // to unexpected behavior. 88 Success chan struct{} `json:"-"` 89 90 Context context.Context `json:"-"` 91} 92 93// StartExec starts a previously set up exec instance id. If opts.Detach is 94// true, it returns after starting the exec command. Otherwise, it sets up an 95// interactive session with the exec command. 96// 97// See https://goo.gl/1EeDWi for more details 98func (c *Client) StartExec(id string, opts StartExecOptions) error { 99 cw, err := c.StartExecNonBlocking(id, opts) 100 if err != nil { 101 return err 102 } 103 if cw != nil { 104 return cw.Wait() 105 } 106 return nil 107} 108 109// StartExecNonBlocking starts a previously set up exec instance id. If opts.Detach is 110// true, it returns after starting the exec command. Otherwise, it sets up an 111// interactive session with the exec command. 112// 113// See https://goo.gl/1EeDWi for more details 114func (c *Client) StartExecNonBlocking(id string, opts StartExecOptions) (CloseWaiter, error) { 115 if id == "" { 116 return nil, &NoSuchExec{ID: id} 117 } 118 119 path := fmt.Sprintf("/exec/%s/start", id) 120 121 if opts.Detach { 122 resp, err := c.do("POST", path, doOptions{data: opts, context: opts.Context}) 123 if err != nil { 124 if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { 125 return nil, &NoSuchExec{ID: id} 126 } 127 return nil, err 128 } 129 defer resp.Body.Close() 130 return nil, nil 131 } 132 133 return c.hijack("POST", path, hijackOptions{ 134 success: opts.Success, 135 setRawTerminal: opts.RawTerminal, 136 in: opts.InputStream, 137 stdout: opts.OutputStream, 138 stderr: opts.ErrorStream, 139 data: opts, 140 }) 141} 142 143// ResizeExecTTY resizes the tty session used by the exec command id. This API 144// is valid only if Tty was specified as part of creating and starting the exec 145// command. 146// 147// See https://goo.gl/Mo5bxx for more details 148func (c *Client) ResizeExecTTY(id string, height, width int) error { 149 params := make(url.Values) 150 params.Set("h", strconv.Itoa(height)) 151 params.Set("w", strconv.Itoa(width)) 152 153 path := fmt.Sprintf("/exec/%s/resize?%s", id, params.Encode()) 154 resp, err := c.do("POST", path, doOptions{}) 155 if err != nil { 156 return err 157 } 158 resp.Body.Close() 159 return nil 160} 161 162// ExecProcessConfig is a type describing the command associated to a Exec 163// instance. It's used in the ExecInspect type. 164type ExecProcessConfig struct { 165 User string `json:"user,omitempty" yaml:"user,omitempty" toml:"user,omitempty"` 166 Privileged bool `json:"privileged,omitempty" yaml:"privileged,omitempty" toml:"privileged,omitempty"` 167 Tty bool `json:"tty,omitempty" yaml:"tty,omitempty" toml:"tty,omitempty"` 168 EntryPoint string `json:"entrypoint,omitempty" yaml:"entrypoint,omitempty" toml:"entrypoint,omitempty"` 169 Arguments []string `json:"arguments,omitempty" yaml:"arguments,omitempty" toml:"arguments,omitempty"` 170} 171 172// ExecInspect is a type with details about a exec instance, including the 173// exit code if the command has finished running. It's returned by a api 174// call to /exec/(id)/json 175// 176// See https://goo.gl/ctMUiW for more details 177type ExecInspect struct { 178 ID string `json:"ID,omitempty" yaml:"ID,omitempty" toml:"ID,omitempty"` 179 ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty" toml:"ExitCode,omitempty"` 180 Running bool `json:"Running,omitempty" yaml:"Running,omitempty" toml:"Running,omitempty"` 181 OpenStdin bool `json:"OpenStdin,omitempty" yaml:"OpenStdin,omitempty" toml:"OpenStdin,omitempty"` 182 OpenStderr bool `json:"OpenStderr,omitempty" yaml:"OpenStderr,omitempty" toml:"OpenStderr,omitempty"` 183 OpenStdout bool `json:"OpenStdout,omitempty" yaml:"OpenStdout,omitempty" toml:"OpenStdout,omitempty"` 184 ProcessConfig ExecProcessConfig `json:"ProcessConfig,omitempty" yaml:"ProcessConfig,omitempty" toml:"ProcessConfig,omitempty"` 185 ContainerID string `json:"ContainerID,omitempty" yaml:"ContainerID,omitempty" toml:"ContainerID,omitempty"` 186 DetachKeys string `json:"DetachKeys,omitempty" yaml:"DetachKeys,omitempty" toml:"DetachKeys,omitempty"` 187 CanRemove bool `json:"CanRemove,omitempty" yaml:"CanRemove,omitempty" toml:"CanRemove,omitempty"` 188} 189 190// InspectExec returns low-level information about the exec command id. 191// 192// See https://goo.gl/ctMUiW for more details 193func (c *Client) InspectExec(id string) (*ExecInspect, error) { 194 path := fmt.Sprintf("/exec/%s/json", id) 195 resp, err := c.do("GET", path, doOptions{}) 196 if err != nil { 197 if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { 198 return nil, &NoSuchExec{ID: id} 199 } 200 return nil, err 201 } 202 defer resp.Body.Close() 203 var exec ExecInspect 204 if err := json.NewDecoder(resp.Body).Decode(&exec); err != nil { 205 return nil, err 206 } 207 return &exec, nil 208} 209 210// NoSuchExec is the error returned when a given exec instance does not exist. 211type NoSuchExec struct { 212 ID string 213} 214 215func (err *NoSuchExec) Error() string { 216 return "No such exec instance: " + err.ID 217} 218