github.com/rsampaio/docker@v0.7.2-0.20150827203920-fdc73cc3fc31/integration-cli/docker_cli_exec_test.go (about) 1 // +build !test_no_exec 2 3 package main 4 5 import ( 6 "bufio" 7 "fmt" 8 "net/http" 9 "os" 10 "os/exec" 11 "path/filepath" 12 "reflect" 13 "sort" 14 "strings" 15 "sync" 16 "time" 17 18 "github.com/go-check/check" 19 ) 20 21 func (s *DockerSuite) TestExec(c *check.C) { 22 dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "sh", "-c", "echo test > /tmp/file && top") 23 24 out, _ := dockerCmd(c, "exec", "testing", "cat", "/tmp/file") 25 out = strings.Trim(out, "\r\n") 26 if out != "test" { 27 c.Errorf("container exec should've printed test but printed %q", out) 28 } 29 30 } 31 32 func (s *DockerSuite) TestExecInteractive(c *check.C) { 33 dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "sh", "-c", "echo test > /tmp/file && top") 34 35 execCmd := exec.Command(dockerBinary, "exec", "-i", "testing", "sh") 36 stdin, err := execCmd.StdinPipe() 37 if err != nil { 38 c.Fatal(err) 39 } 40 stdout, err := execCmd.StdoutPipe() 41 if err != nil { 42 c.Fatal(err) 43 } 44 45 if err := execCmd.Start(); err != nil { 46 c.Fatal(err) 47 } 48 if _, err := stdin.Write([]byte("cat /tmp/file\n")); err != nil { 49 c.Fatal(err) 50 } 51 52 r := bufio.NewReader(stdout) 53 line, err := r.ReadString('\n') 54 if err != nil { 55 c.Fatal(err) 56 } 57 line = strings.TrimSpace(line) 58 if line != "test" { 59 c.Fatalf("Output should be 'test', got '%q'", line) 60 } 61 if err := stdin.Close(); err != nil { 62 c.Fatal(err) 63 } 64 errChan := make(chan error) 65 go func() { 66 errChan <- execCmd.Wait() 67 close(errChan) 68 }() 69 select { 70 case err := <-errChan: 71 c.Assert(err, check.IsNil) 72 case <-time.After(1 * time.Second): 73 c.Fatal("docker exec failed to exit on stdin close") 74 } 75 76 } 77 78 func (s *DockerSuite) TestExecAfterContainerRestart(c *check.C) { 79 out, _ := dockerCmd(c, "run", "-d", "busybox", "top") 80 cleanedContainerID := strings.TrimSpace(out) 81 dockerCmd(c, "restart", cleanedContainerID) 82 83 out, _ = dockerCmd(c, "exec", cleanedContainerID, "echo", "hello") 84 outStr := strings.TrimSpace(out) 85 if outStr != "hello" { 86 c.Errorf("container should've printed hello, instead printed %q", outStr) 87 } 88 } 89 90 func (s *DockerDaemonSuite) TestExecAfterDaemonRestart(c *check.C) { 91 testRequires(c, SameHostDaemon) 92 93 if err := s.d.StartWithBusybox(); err != nil { 94 c.Fatalf("Could not start daemon with busybox: %v", err) 95 } 96 97 if out, err := s.d.Cmd("run", "-d", "--name", "top", "-p", "80", "busybox:latest", "top"); err != nil { 98 c.Fatalf("Could not run top: err=%v\n%s", err, out) 99 } 100 101 if err := s.d.Restart(); err != nil { 102 c.Fatalf("Could not restart daemon: %v", err) 103 } 104 105 if out, err := s.d.Cmd("start", "top"); err != nil { 106 c.Fatalf("Could not start top after daemon restart: err=%v\n%s", err, out) 107 } 108 109 out, err := s.d.Cmd("exec", "top", "echo", "hello") 110 if err != nil { 111 c.Fatalf("Could not exec on container top: err=%v\n%s", err, out) 112 } 113 114 outStr := strings.TrimSpace(string(out)) 115 if outStr != "hello" { 116 c.Errorf("container should've printed hello, instead printed %q", outStr) 117 } 118 } 119 120 // Regression test for #9155, #9044 121 func (s *DockerSuite) TestExecEnv(c *check.C) { 122 dockerCmd(c, "run", "-e", "LALA=value1", "-e", "LALA=value2", 123 "-d", "--name", "testing", "busybox", "top") 124 125 out, _ := dockerCmd(c, "exec", "testing", "env") 126 if strings.Contains(out, "LALA=value1") || 127 !strings.Contains(out, "LALA=value2") || 128 !strings.Contains(out, "HOME=/root") { 129 c.Errorf("exec env(%q), expect %q, %q", out, "LALA=value2", "HOME=/root") 130 } 131 } 132 133 func (s *DockerSuite) TestExecExitStatus(c *check.C) { 134 dockerCmd(c, "run", "-d", "--name", "top", "busybox", "top") 135 136 // Test normal (non-detached) case first 137 cmd := exec.Command(dockerBinary, "exec", "top", "sh", "-c", "exit 23") 138 ec, _ := runCommand(cmd) 139 if ec != 23 { 140 c.Fatalf("Should have had an ExitCode of 23, not: %d", ec) 141 } 142 } 143 144 func (s *DockerSuite) TestExecPausedContainer(c *check.C) { 145 defer unpauseAllContainers() 146 147 out, _ := dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "top") 148 ContainerID := strings.TrimSpace(out) 149 150 dockerCmd(c, "pause", "testing") 151 out, _, err := dockerCmdWithError("exec", "-i", "-t", ContainerID, "echo", "hello") 152 if err == nil { 153 c.Fatal("container should fail to exec new command if it is paused") 154 } 155 156 expected := ContainerID + " is paused, unpause the container before exec" 157 if !strings.Contains(out, expected) { 158 c.Fatal("container should not exec new command if it is paused") 159 } 160 } 161 162 // regression test for #9476 163 func (s *DockerSuite) TestExecTtyCloseStdin(c *check.C) { 164 dockerCmd(c, "run", "-d", "-it", "--name", "exec_tty_stdin", "busybox") 165 166 cmd := exec.Command(dockerBinary, "exec", "-i", "exec_tty_stdin", "cat") 167 stdinRw, err := cmd.StdinPipe() 168 if err != nil { 169 c.Fatal(err) 170 } 171 172 stdinRw.Write([]byte("test")) 173 stdinRw.Close() 174 175 if out, _, err := runCommandWithOutput(cmd); err != nil { 176 c.Fatal(out, err) 177 } 178 179 out, _ := dockerCmd(c, "top", "exec_tty_stdin") 180 outArr := strings.Split(out, "\n") 181 if len(outArr) > 3 || strings.Contains(out, "nsenter-exec") { 182 c.Fatalf("exec process left running\n\t %s", out) 183 } 184 } 185 186 func (s *DockerSuite) TestExecTtyWithoutStdin(c *check.C) { 187 out, _ := dockerCmd(c, "run", "-d", "-ti", "busybox") 188 id := strings.TrimSpace(out) 189 c.Assert(waitRun(id), check.IsNil) 190 191 errChan := make(chan error) 192 go func() { 193 defer close(errChan) 194 195 cmd := exec.Command(dockerBinary, "exec", "-ti", id, "true") 196 if _, err := cmd.StdinPipe(); err != nil { 197 errChan <- err 198 return 199 } 200 201 expected := "cannot enable tty mode" 202 if out, _, err := runCommandWithOutput(cmd); err == nil { 203 errChan <- fmt.Errorf("exec should have failed") 204 return 205 } else if !strings.Contains(out, expected) { 206 errChan <- fmt.Errorf("exec failed with error %q: expected %q", out, expected) 207 return 208 } 209 }() 210 211 select { 212 case err := <-errChan: 213 c.Assert(err, check.IsNil) 214 case <-time.After(3 * time.Second): 215 c.Fatal("exec is running but should have failed") 216 } 217 } 218 219 func (s *DockerSuite) TestExecParseError(c *check.C) { 220 dockerCmd(c, "run", "-d", "--name", "top", "busybox", "top") 221 222 // Test normal (non-detached) case first 223 cmd := exec.Command(dockerBinary, "exec", "top") 224 if _, stderr, code, err := runCommandWithStdoutStderr(cmd); err == nil || !strings.Contains(stderr, "See '"+dockerBinary+" exec --help'") || code == 0 { 225 c.Fatalf("Should have thrown error & point to help: %s", stderr) 226 } 227 } 228 229 func (s *DockerSuite) TestExecStopNotHanging(c *check.C) { 230 dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "top") 231 232 if err := exec.Command(dockerBinary, "exec", "testing", "top").Start(); err != nil { 233 c.Fatal(err) 234 } 235 236 type dstop struct { 237 out []byte 238 err error 239 } 240 241 ch := make(chan dstop) 242 go func() { 243 out, err := exec.Command(dockerBinary, "stop", "testing").CombinedOutput() 244 ch <- dstop{out, err} 245 close(ch) 246 }() 247 select { 248 case <-time.After(3 * time.Second): 249 c.Fatal("Container stop timed out") 250 case s := <-ch: 251 c.Assert(s.err, check.IsNil) 252 } 253 } 254 255 func (s *DockerSuite) TestExecCgroup(c *check.C) { 256 dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "top") 257 258 out, _ := dockerCmd(c, "exec", "testing", "cat", "/proc/1/cgroup") 259 containerCgroups := sort.StringSlice(strings.Split(out, "\n")) 260 261 var wg sync.WaitGroup 262 var mu sync.Mutex 263 execCgroups := []sort.StringSlice{} 264 errChan := make(chan error) 265 // exec a few times concurrently to get consistent failure 266 for i := 0; i < 5; i++ { 267 wg.Add(1) 268 go func() { 269 out, _, err := dockerCmdWithError("exec", "testing", "cat", "/proc/self/cgroup") 270 if err != nil { 271 errChan <- err 272 return 273 } 274 cg := sort.StringSlice(strings.Split(out, "\n")) 275 276 mu.Lock() 277 execCgroups = append(execCgroups, cg) 278 mu.Unlock() 279 wg.Done() 280 }() 281 } 282 wg.Wait() 283 close(errChan) 284 285 for err := range errChan { 286 c.Assert(err, check.IsNil) 287 } 288 289 for _, cg := range execCgroups { 290 if !reflect.DeepEqual(cg, containerCgroups) { 291 fmt.Println("exec cgroups:") 292 for _, name := range cg { 293 fmt.Printf(" %s\n", name) 294 } 295 296 fmt.Println("container cgroups:") 297 for _, name := range containerCgroups { 298 fmt.Printf(" %s\n", name) 299 } 300 c.Fatal("cgroups mismatched") 301 } 302 } 303 } 304 305 func (s *DockerSuite) TestInspectExecID(c *check.C) { 306 out, _ := dockerCmd(c, "run", "-d", "busybox", "top") 307 id := strings.TrimSuffix(out, "\n") 308 309 out, err := inspectField(id, "ExecIDs") 310 if err != nil { 311 c.Fatalf("failed to inspect container: %s, %v", out, err) 312 } 313 if out != "[]" { 314 c.Fatalf("ExecIDs should be empty, got: %s", out) 315 } 316 317 // Start an exec, have it block waiting so we can do some checking 318 cmd := exec.Command(dockerBinary, "exec", id, "sh", "-c", 319 "while ! test -e /tmp/execid1; do sleep 1; done") 320 321 if err = cmd.Start(); err != nil { 322 c.Fatalf("failed to start the exec cmd: %q", err) 323 } 324 325 // Give the exec 10 chances/seconds to start then give up and stop the test 326 tries := 10 327 for i := 0; i < tries; i++ { 328 // Since its still running we should see exec as part of the container 329 out, err = inspectField(id, "ExecIDs") 330 if err != nil { 331 c.Fatalf("failed to inspect container: %s, %v", out, err) 332 } 333 334 out = strings.TrimSuffix(out, "\n") 335 if out != "[]" && out != "<no value>" { 336 break 337 } 338 if i+1 == tries { 339 c.Fatalf("ExecIDs should not be empty, got: %s", out) 340 } 341 time.Sleep(1 * time.Second) 342 } 343 344 // Save execID for later 345 execID, err := inspectFilter(id, "index .ExecIDs 0") 346 if err != nil { 347 c.Fatalf("failed to get the exec id: %v", err) 348 } 349 350 // End the exec by creating the missing file 351 err = exec.Command(dockerBinary, "exec", id, 352 "sh", "-c", "touch /tmp/execid1").Run() 353 354 if err != nil { 355 c.Fatalf("failed to run the 2nd exec cmd: %q", err) 356 } 357 358 // Wait for 1st exec to complete 359 cmd.Wait() 360 361 // All execs for the container should be gone now 362 out, err = inspectField(id, "ExecIDs") 363 if err != nil { 364 c.Fatalf("failed to inspect container: %s, %v", out, err) 365 } 366 367 out = strings.TrimSuffix(out, "\n") 368 if out != "[]" && out != "<no value>" { 369 c.Fatalf("ExecIDs should be empty, got: %s", out) 370 } 371 372 // But we should still be able to query the execID 373 sc, body, err := sockRequest("GET", "/exec/"+execID+"/json", nil) 374 if sc != http.StatusOK { 375 c.Fatalf("received status != 200 OK: %d\n%s", sc, body) 376 } 377 378 // Now delete the container and then an 'inspect' on the exec should 379 // result in a 404 (not 'container not running') 380 out, ec := dockerCmd(c, "rm", "-f", id) 381 if ec != 0 { 382 c.Fatalf("error removing container: %s", out) 383 } 384 sc, body, err = sockRequest("GET", "/exec/"+execID+"/json", nil) 385 if sc != http.StatusNotFound { 386 c.Fatalf("received status != 404: %d\n%s", sc, body) 387 } 388 } 389 390 func (s *DockerSuite) TestLinksPingLinkedContainersOnRename(c *check.C) { 391 var out string 392 out, _ = dockerCmd(c, "run", "-d", "--name", "container1", "busybox", "top") 393 idA := strings.TrimSpace(out) 394 if idA == "" { 395 c.Fatal(out, "id should not be nil") 396 } 397 out, _ = dockerCmd(c, "run", "-d", "--link", "container1:alias1", "--name", "container2", "busybox", "top") 398 idB := strings.TrimSpace(out) 399 if idB == "" { 400 c.Fatal(out, "id should not be nil") 401 } 402 403 dockerCmd(c, "exec", "container2", "ping", "-c", "1", "alias1", "-W", "1") 404 dockerCmd(c, "rename", "container1", "container_new") 405 dockerCmd(c, "exec", "container2", "ping", "-c", "1", "alias1", "-W", "1") 406 } 407 408 func (s *DockerSuite) TestRunExecDir(c *check.C) { 409 testRequires(c, SameHostDaemon) 410 411 out, _ := dockerCmd(c, "run", "-d", "busybox", "top") 412 id := strings.TrimSpace(out) 413 execDir := filepath.Join(execDriverPath, id) 414 stateFile := filepath.Join(execDir, "state.json") 415 416 { 417 fi, err := os.Stat(execDir) 418 if err != nil { 419 c.Fatal(err) 420 } 421 if !fi.IsDir() { 422 c.Fatalf("%q must be a directory", execDir) 423 } 424 fi, err = os.Stat(stateFile) 425 if err != nil { 426 c.Fatal(err) 427 } 428 } 429 430 dockerCmd(c, "stop", id) 431 { 432 _, err := os.Stat(execDir) 433 if err == nil { 434 c.Fatal(err) 435 } 436 if err == nil { 437 c.Fatalf("Exec directory %q exists for removed container!", execDir) 438 } 439 if !os.IsNotExist(err) { 440 c.Fatalf("Error should be about non-existing, got %s", err) 441 } 442 } 443 dockerCmd(c, "start", id) 444 { 445 fi, err := os.Stat(execDir) 446 if err != nil { 447 c.Fatal(err) 448 } 449 if !fi.IsDir() { 450 c.Fatalf("%q must be a directory", execDir) 451 } 452 fi, err = os.Stat(stateFile) 453 if err != nil { 454 c.Fatal(err) 455 } 456 } 457 dockerCmd(c, "rm", "-f", id) 458 { 459 _, err := os.Stat(execDir) 460 if err == nil { 461 c.Fatal(err) 462 } 463 if err == nil { 464 c.Fatalf("Exec directory %q is exists for removed container!", execDir) 465 } 466 if !os.IsNotExist(err) { 467 c.Fatalf("Error should be about non-existing, got %s", err) 468 } 469 } 470 } 471 472 func (s *DockerSuite) TestRunMutableNetworkFiles(c *check.C) { 473 testRequires(c, SameHostDaemon) 474 475 for _, fn := range []string{"resolv.conf", "hosts"} { 476 deleteAllContainers() 477 478 content, err := runCommandAndReadContainerFile(fn, exec.Command(dockerBinary, "run", "-d", "--name", "c1", "busybox", "sh", "-c", fmt.Sprintf("echo success >/etc/%s && top", fn))) 479 if err != nil { 480 c.Fatal(err) 481 } 482 483 if strings.TrimSpace(string(content)) != "success" { 484 c.Fatal("Content was not what was modified in the container", string(content)) 485 } 486 487 out, _ := dockerCmd(c, "run", "-d", "--name", "c2", "busybox", "top") 488 contID := strings.TrimSpace(out) 489 netFilePath := containerStorageFile(contID, fn) 490 491 f, err := os.OpenFile(netFilePath, os.O_WRONLY|os.O_SYNC|os.O_APPEND, 0644) 492 if err != nil { 493 c.Fatal(err) 494 } 495 496 if _, err := f.Seek(0, 0); err != nil { 497 f.Close() 498 c.Fatal(err) 499 } 500 501 if err := f.Truncate(0); err != nil { 502 f.Close() 503 c.Fatal(err) 504 } 505 506 if _, err := f.Write([]byte("success2\n")); err != nil { 507 f.Close() 508 c.Fatal(err) 509 } 510 f.Close() 511 512 res, _ := dockerCmd(c, "exec", contID, "cat", "/etc/"+fn) 513 if res != "success2\n" { 514 c.Fatalf("Expected content of %s: %q, got: %q", fn, "success2\n", res) 515 } 516 } 517 } 518 519 func (s *DockerSuite) TestExecWithUser(c *check.C) { 520 dockerCmd(c, "run", "-d", "--name", "parent", "busybox", "top") 521 522 out, _ := dockerCmd(c, "exec", "-u", "1", "parent", "id") 523 if !strings.Contains(out, "uid=1(daemon) gid=1(daemon)") { 524 c.Fatalf("exec with user by id expected daemon user got %s", out) 525 } 526 527 out, _ = dockerCmd(c, "exec", "-u", "root", "parent", "id") 528 if !strings.Contains(out, "uid=0(root) gid=0(root)") { 529 c.Fatalf("exec with user by root expected root user got %s", out) 530 } 531 } 532 533 func (s *DockerSuite) TestExecWithPrivileged(c *check.C) { 534 535 // Start main loop which attempts mknod repeatedly 536 dockerCmd(c, "run", "-d", "--name", "parent", "--cap-drop=ALL", "busybox", "sh", "-c", `while (true); do if [ -e /exec_priv ]; then cat /exec_priv && mknod /tmp/sda b 8 0 && echo "Success"; else echo "Privileged exec has not run yet"; fi; usleep 10000; done`) 537 538 // Check exec mknod doesn't work 539 cmd := exec.Command(dockerBinary, "exec", "parent", "sh", "-c", "mknod /tmp/sdb b 8 16") 540 out, _, err := runCommandWithOutput(cmd) 541 if err == nil || !strings.Contains(out, "Operation not permitted") { 542 c.Fatalf("exec mknod in --cap-drop=ALL container without --privileged should fail") 543 } 544 545 // Check exec mknod does work with --privileged 546 cmd = exec.Command(dockerBinary, "exec", "--privileged", "parent", "sh", "-c", `echo "Running exec --privileged" > /exec_priv && mknod /tmp/sdb b 8 16 && usleep 50000 && echo "Finished exec --privileged" > /exec_priv && echo ok`) 547 out, _, err = runCommandWithOutput(cmd) 548 if err != nil { 549 c.Fatal(err, out) 550 } 551 552 if actual := strings.TrimSpace(out); actual != "ok" { 553 c.Fatalf("exec mknod in --cap-drop=ALL container with --privileged failed: %v, output: %q", err, out) 554 } 555 556 // Check subsequent unprivileged exec cannot mknod 557 cmd = exec.Command(dockerBinary, "exec", "parent", "sh", "-c", "mknod /tmp/sdc b 8 32") 558 out, _, err = runCommandWithOutput(cmd) 559 if err == nil || !strings.Contains(out, "Operation not permitted") { 560 c.Fatalf("repeating exec mknod in --cap-drop=ALL container after --privileged without --privileged should fail") 561 } 562 563 // Confirm at no point was mknod allowed 564 logCmd := exec.Command(dockerBinary, "logs", "parent") 565 if out, _, err := runCommandWithOutput(logCmd); err != nil || strings.Contains(out, "Success") { 566 c.Fatal(out, err) 567 } 568 569 } 570 571 func (s *DockerSuite) TestExecWithImageUser(c *check.C) { 572 name := "testbuilduser" 573 _, err := buildImage(name, 574 `FROM busybox 575 RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd 576 USER dockerio`, 577 true) 578 if err != nil { 579 c.Fatalf("Could not build image %s: %v", name, err) 580 } 581 582 dockerCmd(c, "run", "-d", "--name", "dockerioexec", name, "top") 583 584 out, _ := dockerCmd(c, "exec", "dockerioexec", "whoami") 585 if !strings.Contains(out, "dockerio") { 586 c.Fatalf("exec with user by id expected dockerio user got %s", out) 587 } 588 } 589 590 func (s *DockerSuite) TestExecOnReadonlyContainer(c *check.C) { 591 dockerCmd(c, "run", "-d", "--read-only", "--name", "parent", "busybox", "top") 592 if _, status := dockerCmd(c, "exec", "parent", "true"); status != 0 { 593 c.Fatalf("exec into a read-only container failed with exit status %d", status) 594 } 595 } 596 597 // #15750 598 func (s *DockerSuite) TestExecStartFails(c *check.C) { 599 name := "exec-15750" 600 dockerCmd(c, "run", "-d", "--name", name, "busybox", "top") 601 602 _, errmsg, status := dockerCmdWithStdoutStderr(nil, "exec", name, "no-such-cmd") 603 if status == 255 && !strings.Contains(errmsg, "executable file not found") { 604 c.Fatal("exec error message not received. The daemon might had crashed") 605 } 606 }