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