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