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