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	"bytes"
9	"encoding/json"
10	"net"
11	"net/http"
12	"net/http/httptest"
13	"net/url"
14	"reflect"
15	"strings"
16	"testing"
17)
18
19func TestExecCreate(t *testing.T) {
20	t.Parallel()
21	jsonContainer := `{"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"}`
22	var expected struct{ ID string }
23	err := json.Unmarshal([]byte(jsonContainer), &expected)
24	if err != nil {
25		t.Fatal(err)
26	}
27	fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK}
28	client := newTestClient(fakeRT)
29	config := CreateExecOptions{
30		Container:    "test",
31		AttachStdin:  true,
32		AttachStdout: true,
33		AttachStderr: false,
34		Tty:          false,
35		Cmd:          []string{"touch", "/tmp/file"},
36		User:         "a-user",
37	}
38	execObj, err := client.CreateExec(config)
39	if err != nil {
40		t.Fatal(err)
41	}
42	expectedID := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
43	if execObj.ID != expectedID {
44		t.Errorf("ExecCreate: wrong ID. Want %q. Got %q.", expectedID, execObj.ID)
45	}
46	req := fakeRT.requests[0]
47	if req.Method != "POST" {
48		t.Errorf("ExecCreate: wrong HTTP method. Want %q. Got %q.", "POST", req.Method)
49	}
50	expectedURL, _ := url.Parse(client.getURL("/containers/test/exec"))
51	if gotPath := req.URL.Path; gotPath != expectedURL.Path {
52		t.Errorf("ExecCreate: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath)
53	}
54	var gotBody struct{ ID string }
55	err = json.NewDecoder(req.Body).Decode(&gotBody)
56	if err != nil {
57		t.Fatal(err)
58	}
59}
60
61func TestExecCreateWithEnvErr(t *testing.T) {
62	t.Parallel()
63	jsonContainer := `{"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"}`
64	var expected struct{ ID string }
65	err := json.Unmarshal([]byte(jsonContainer), &expected)
66	if err != nil {
67		t.Fatal(err)
68	}
69	fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK}
70	client := newTestClient(fakeRT)
71	config := CreateExecOptions{
72		Container:    "test",
73		AttachStdin:  true,
74		AttachStdout: true,
75		AttachStderr: false,
76		Tty:          false,
77		Env:          []string{"foo=bar"},
78		Cmd:          []string{"touch", "/tmp/file"},
79		User:         "a-user",
80	}
81	_, err = client.CreateExec(config)
82	if err == nil || err.Error() != "exec configuration Env is only supported in API#1.25 and above" {
83		t.Error("CreateExec: options contain Env for unsupported api version")
84	}
85}
86
87func TestExecCreateWithEnv(t *testing.T) {
88	t.Parallel()
89	jsonContainer := `{"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"}`
90	var expected struct{ ID string }
91	err := json.Unmarshal([]byte(jsonContainer), &expected)
92	if err != nil {
93		t.Fatal(err)
94	}
95	fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK}
96	endpoint := "http://localhost:4243"
97	u, _ := parseEndpoint("http://localhost:4243", false)
98	testAPIVersion, _ := NewAPIVersion("1.25")
99	client := Client{
100		HTTPClient:             &http.Client{Transport: fakeRT},
101		Dialer:                 &net.Dialer{},
102		endpoint:               endpoint,
103		endpointURL:            u,
104		SkipServerVersionCheck: true,
105		serverAPIVersion:       testAPIVersion,
106	}
107	config := CreateExecOptions{
108		Container:    "test",
109		AttachStdin:  true,
110		AttachStdout: true,
111		AttachStderr: false,
112		Tty:          false,
113		Env:          []string{"foo=bar"},
114		Cmd:          []string{"touch", "/tmp/file"},
115		User:         "a-user",
116	}
117	_, err = client.CreateExec(config)
118	if err != nil {
119		t.Error(err)
120	}
121}
122
123func TestExecCreateWithWorkingDirErr(t *testing.T) {
124	t.Parallel()
125	jsonContainer := `{"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"}`
126	var expected struct{ ID string }
127	err := json.Unmarshal([]byte(jsonContainer), &expected)
128	if err != nil {
129		t.Fatal(err)
130	}
131	fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK}
132	client := newTestClient(fakeRT)
133	config := CreateExecOptions{
134		Container:    "test",
135		AttachStdin:  true,
136		AttachStdout: true,
137		AttachStderr: false,
138		Tty:          false,
139		WorkingDir:   "/tmp",
140		Cmd:          []string{"touch", "file"},
141		User:         "a-user",
142	}
143	_, err = client.CreateExec(config)
144	if err == nil || err.Error() != "exec configuration WorkingDir is only supported in API#1.35 and above" {
145		t.Error("CreateExec: options contain WorkingDir for unsupported api version")
146	}
147}
148
149func TestExecCreateWithWorkingDir(t *testing.T) {
150	t.Parallel()
151	jsonContainer := `{"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"}`
152	var expected struct{ ID string }
153	err := json.Unmarshal([]byte(jsonContainer), &expected)
154	if err != nil {
155		t.Fatal(err)
156	}
157	fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK}
158	endpoint := "http://localhost:4243"
159	u, _ := parseEndpoint("http://localhost:4243", false)
160	testAPIVersion, _ := NewAPIVersion("1.35")
161	client := Client{
162		HTTPClient:             &http.Client{Transport: fakeRT},
163		Dialer:                 &net.Dialer{},
164		endpoint:               endpoint,
165		endpointURL:            u,
166		SkipServerVersionCheck: true,
167		serverAPIVersion:       testAPIVersion,
168	}
169	config := CreateExecOptions{
170		Container:    "test",
171		AttachStdin:  true,
172		AttachStdout: true,
173		AttachStderr: false,
174		Tty:          false,
175		WorkingDir:   "/tmp",
176		Cmd:          []string{"touch", "file"},
177		User:         "a-user",
178	}
179	_, err = client.CreateExec(config)
180	if err != nil {
181		t.Error(err)
182	}
183}
184
185func TestExecStartDetached(t *testing.T) {
186	t.Parallel()
187	execID := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
188	fakeRT := &FakeRoundTripper{status: http.StatusOK}
189	client := newTestClient(fakeRT)
190	config := StartExecOptions{
191		Detach: true,
192	}
193	err := client.StartExec(execID, config)
194	if err != nil {
195		t.Fatal(err)
196	}
197	req := fakeRT.requests[0]
198	if req.Method != "POST" {
199		t.Errorf("ExecStart: wrong HTTP method. Want %q. Got %q.", "POST", req.Method)
200	}
201	expectedURL, _ := url.Parse(client.getURL("/exec/" + execID + "/start"))
202	if gotPath := req.URL.Path; gotPath != expectedURL.Path {
203		t.Errorf("ExecCreate: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath)
204	}
205	t.Log(req.Body)
206	var gotBody struct{ Detach bool }
207	err = json.NewDecoder(req.Body).Decode(&gotBody)
208	if err != nil {
209		t.Fatal(err)
210	}
211	if !gotBody.Detach {
212		t.Fatal("Expected Detach in StartExecOptions to be true")
213	}
214}
215
216func TestExecStartAndAttach(t *testing.T) {
217	var reader = strings.NewReader("send value")
218	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
219		w.Write([]byte{1, 0, 0, 0, 0, 0, 0, 5})
220		w.Write([]byte("hello"))
221	}))
222	defer server.Close()
223	client, _ := NewClient(server.URL)
224	client.SkipServerVersionCheck = true
225	var stdout, stderr bytes.Buffer
226	success := make(chan struct{})
227	execID := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
228	opts := StartExecOptions{
229		OutputStream: &stdout,
230		ErrorStream:  &stderr,
231		InputStream:  reader,
232		RawTerminal:  true,
233		Success:      success,
234	}
235	go func() {
236		if err := client.StartExec(execID, opts); err != nil {
237			t.Error(err)
238		}
239	}()
240	<-success
241}
242
243func TestExecResize(t *testing.T) {
244	t.Parallel()
245	execID := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
246	fakeRT := &FakeRoundTripper{status: http.StatusOK}
247	client := newTestClient(fakeRT)
248	err := client.ResizeExecTTY(execID, 10, 20)
249	if err != nil {
250		t.Fatal(err)
251	}
252	req := fakeRT.requests[0]
253	if req.Method != "POST" {
254		t.Errorf("ExecStart: wrong HTTP method. Want %q. Got %q.", "POST", req.Method)
255	}
256	expectedURL, _ := url.Parse(client.getURL("/exec/" + execID + "/resize?h=10&w=20"))
257	if gotPath := req.URL.RequestURI(); gotPath != expectedURL.RequestURI() {
258		t.Errorf("ExecCreate: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath)
259	}
260}
261
262func TestExecInspect(t *testing.T) {
263	t.Parallel()
264	jsonExec := `{
265	  "CanRemove": false,
266	  "ContainerID": "b53ee82b53a40c7dca428523e34f741f3abc51d9f297a14ff874bf761b995126",
267	  "DetachKeys": "",
268	  "ExitCode": 2,
269	  "ID": "f33bbfb39f5b142420f4759b2348913bd4a8d1a6d7fd56499cb41a1bb91d7b3b",
270	  "OpenStderr": true,
271	  "OpenStdin": true,
272	  "OpenStdout": true,
273	  "ProcessConfig": {
274	    "arguments": [
275	      "-c",
276	      "exit 2"
277	    ],
278	    "entrypoint": "sh",
279	    "privileged": false,
280	    "tty": true,
281	    "user": "1000"
282	  },
283	  "Running": false
284	}`
285	var expected ExecInspect
286	err := json.Unmarshal([]byte(jsonExec), &expected)
287	if err != nil {
288		t.Fatal(err)
289	}
290	fakeRT := &FakeRoundTripper{message: jsonExec, status: http.StatusOK}
291	client := newTestClient(fakeRT)
292	expectedID := "b53ee82b53a40c7dca428523e34f741f3abc51d9f297a14ff874bf761b995126"
293	execObj, err := client.InspectExec(expectedID)
294	if err != nil {
295		t.Fatal(err)
296	}
297	if !reflect.DeepEqual(*execObj, expected) {
298		t.Errorf("ExecInspect: Expected %#v. Got %#v.", expected, *execObj)
299	}
300	req := fakeRT.requests[0]
301	if req.Method != "GET" {
302		t.Errorf("ExecInspect: wrong HTTP method. Want %q. Got %q.", "GET", req.Method)
303	}
304	expectedURL, _ := url.Parse(client.getURL("/exec/" + expectedID + "/json"))
305	if gotPath := fakeRT.requests[0].URL.Path; gotPath != expectedURL.Path {
306		t.Errorf("ExecInspect: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath)
307	}
308}
309