github.com/khulnasoft-lab/khulnasoft@v26.0.1-0.20240328202558-330a6f959fe0+incompatible/integration-cli/docker_cli_exec_test.go (about) 1 package main 2 3 import ( 4 "bufio" 5 "context" 6 "fmt" 7 "os" 8 "os/exec" 9 "reflect" 10 "runtime" 11 "sort" 12 "strings" 13 "sync" 14 "testing" 15 "time" 16 17 "github.com/docker/docker/client" 18 "github.com/docker/docker/integration-cli/cli" 19 "github.com/docker/docker/integration-cli/cli/build" 20 "github.com/docker/docker/testutil" 21 "gotest.tools/v3/assert" 22 is "gotest.tools/v3/assert/cmp" 23 "gotest.tools/v3/icmd" 24 ) 25 26 type DockerCLIExecSuite struct { 27 ds *DockerSuite 28 } 29 30 func (s *DockerCLIExecSuite) TearDownTest(ctx context.Context, c *testing.T) { 31 s.ds.TearDownTest(ctx, c) 32 } 33 34 func (s *DockerCLIExecSuite) OnTimeout(c *testing.T) { 35 s.ds.OnTimeout(c) 36 } 37 38 func (s *DockerCLIExecSuite) TestExec(c *testing.T) { 39 testRequires(c, DaemonIsLinux) 40 out := cli.DockerCmd(c, "run", "-d", "--name", "testing", "busybox", "sh", "-c", "echo test > /tmp/file && top").Stdout() 41 cli.WaitRun(c, strings.TrimSpace(out)) 42 43 out = cli.DockerCmd(c, "exec", "testing", "cat", "/tmp/file").Stdout() 44 assert.Equal(c, strings.Trim(out, "\r\n"), "test") 45 } 46 47 func (s *DockerCLIExecSuite) TestExecInteractive(c *testing.T) { 48 testRequires(c, DaemonIsLinux) 49 cli.DockerCmd(c, "run", "-d", "--name", "testing", "busybox", "sh", "-c", "echo test > /tmp/file && top") 50 51 execCmd := exec.Command(dockerBinary, "exec", "-i", "testing", "sh") 52 stdin, err := execCmd.StdinPipe() 53 assert.NilError(c, err) 54 stdout, err := execCmd.StdoutPipe() 55 assert.NilError(c, err) 56 57 err = execCmd.Start() 58 assert.NilError(c, err) 59 _, err = stdin.Write([]byte("cat /tmp/file\n")) 60 assert.NilError(c, err) 61 62 r := bufio.NewReader(stdout) 63 line, err := r.ReadString('\n') 64 assert.NilError(c, err) 65 line = strings.TrimSpace(line) 66 assert.Equal(c, line, "test") 67 err = stdin.Close() 68 assert.NilError(c, err) 69 errChan := make(chan error, 1) 70 go func() { 71 errChan <- execCmd.Wait() 72 close(errChan) 73 }() 74 select { 75 case err := <-errChan: 76 assert.NilError(c, err) 77 case <-time.After(1 * time.Second): 78 c.Fatal("docker exec failed to exit on stdin close") 79 } 80 } 81 82 func (s *DockerCLIExecSuite) TestExecAfterContainerRestart(c *testing.T) { 83 cID := runSleepingContainer(c) 84 cli.WaitRun(c, cID) 85 cli.DockerCmd(c, "restart", cID) 86 cli.WaitRun(c, cID) 87 88 out := cli.DockerCmd(c, "exec", cID, "echo", "hello").Combined() 89 assert.Equal(c, strings.TrimSpace(out), "hello") 90 } 91 92 func (s *DockerDaemonSuite) TestExecAfterDaemonRestart(c *testing.T) { 93 ctx := testutil.GetContext(c) 94 // TODO Windows CI: DockerDaemonSuite doesn't run on Windows, and requires a little work to get this ported. 95 s.d.StartWithBusybox(ctx, c) 96 97 out, err := s.d.Cmd("run", "-d", "--name", "top", "-p", "80", "busybox:latest", "top") 98 assert.NilError(c, err, "Could not run top: %s", out) 99 100 s.d.Restart(c) 101 102 out, err = s.d.Cmd("start", "top") 103 assert.NilError(c, err, "Could not start top after daemon restart: %s", out) 104 105 out, err = s.d.Cmd("exec", "top", "echo", "hello") 106 assert.NilError(c, err, "Could not exec on container top: %s", out) 107 assert.Equal(c, strings.TrimSpace(out), "hello") 108 } 109 110 // Regression test for #9155, #9044 111 func (s *DockerCLIExecSuite) TestExecEnv(c *testing.T) { 112 // TODO Windows CI: This one is interesting and may just end up being a feature 113 // difference between Windows and Linux. On Windows, the environment is passed 114 // into the process that is launched, not into the machine environment. Hence 115 // a subsequent exec will not have LALA set/ 116 testRequires(c, DaemonIsLinux) 117 runSleepingContainer(c, "-e", "LALA=value1", "-e", "LALA=value2", "-d", "--name", "testing") 118 cli.WaitRun(c, "testing") 119 120 out := cli.DockerCmd(c, "exec", "testing", "env").Stdout() 121 assert.Check(c, !strings.Contains(out, "LALA=value1")) 122 assert.Check(c, strings.Contains(out, "LALA=value2")) 123 assert.Check(c, strings.Contains(out, "HOME=/root")) 124 } 125 126 func (s *DockerCLIExecSuite) TestExecSetEnv(c *testing.T) { 127 testRequires(c, DaemonIsLinux) 128 runSleepingContainer(c, "-e", "HOME=/root", "-d", "--name", "testing") 129 cli.WaitRun(c, "testing") 130 131 out := cli.DockerCmd(c, "exec", "-e", "HOME=/another", "-e", "ABC=xyz", "testing", "env").Stdout() 132 assert.Check(c, !strings.Contains(out, "HOME=/root")) 133 assert.Check(c, strings.Contains(out, "HOME=/another")) 134 assert.Check(c, strings.Contains(out, "ABC=xyz")) 135 } 136 137 func (s *DockerCLIExecSuite) TestExecExitStatus(c *testing.T) { 138 runSleepingContainer(c, "-d", "--name", "top") 139 140 result := icmd.RunCommand(dockerBinary, "exec", "top", "sh", "-c", "exit 23") 141 result.Assert(c, icmd.Expected{ExitCode: 23, Error: "exit status 23"}) 142 } 143 144 func (s *DockerCLIExecSuite) TestExecPausedContainer(c *testing.T) { 145 testRequires(c, IsPausable) 146 147 ContainerID := runSleepingContainer(c, "-d", "--name", "testing") 148 149 cli.DockerCmd(c, "pause", "testing") 150 out, _, err := dockerCmdWithError("exec", ContainerID, "echo", "hello") 151 assert.ErrorContains(c, err, "", "container should fail to exec new command if it is paused") 152 153 expected := ContainerID + " is paused, unpause the container before exec" 154 assert.Assert(c, is.Contains(out, expected), "container should not exec new command if it is paused") 155 } 156 157 // regression test for #9476 158 func (s *DockerCLIExecSuite) TestExecTTYCloseStdin(c *testing.T) { 159 // TODO Windows CI: This requires some work to port to Windows. 160 testRequires(c, DaemonIsLinux) 161 cli.DockerCmd(c, "run", "-d", "-it", "--name", "exec_tty_stdin", "busybox") 162 163 cmd := exec.Command(dockerBinary, "exec", "-i", "exec_tty_stdin", "cat") 164 stdinRw, err := cmd.StdinPipe() 165 assert.NilError(c, err) 166 167 stdinRw.Write([]byte("test")) 168 stdinRw.Close() 169 170 out, _, err := runCommandWithOutput(cmd) 171 assert.NilError(c, err, out) 172 173 out = cli.DockerCmd(c, "top", "exec_tty_stdin").Combined() 174 outArr := strings.Split(out, "\n") 175 assert.Assert(c, len(outArr) <= 3, "exec process left running") 176 assert.Assert(c, !strings.Contains(out, "nsenter-exec")) 177 } 178 179 func (s *DockerCLIExecSuite) TestExecTTYWithoutStdin(c *testing.T) { 180 out := cli.DockerCmd(c, "run", "-d", "-ti", "busybox").Stdout() 181 id := strings.TrimSpace(out) 182 cli.WaitRun(c, id) 183 184 errChan := make(chan error, 1) 185 go func() { 186 defer close(errChan) 187 188 cmd := exec.Command(dockerBinary, "exec", "-ti", id, "true") 189 if _, err := cmd.StdinPipe(); err != nil { 190 errChan <- err 191 return 192 } 193 194 expected := "the input device is not a TTY" 195 if runtime.GOOS == "windows" { 196 expected += ". If you are using mintty, try prefixing the command with 'winpty'" 197 } 198 if out, _, err := runCommandWithOutput(cmd); err == nil { 199 errChan <- fmt.Errorf("exec should have failed") 200 return 201 } else if !strings.Contains(out, expected) { 202 errChan <- fmt.Errorf("exec failed with error %q: expected %q", out, expected) 203 return 204 } 205 }() 206 207 select { 208 case err := <-errChan: 209 assert.NilError(c, err) 210 case <-time.After(3 * time.Second): 211 c.Fatal("exec is running but should have failed") 212 } 213 } 214 215 // FIXME(vdemeester) this should be a unit tests on cli/command/container package 216 func (s *DockerCLIExecSuite) TestExecParseError(c *testing.T) { 217 // TODO Windows CI: Requires some extra work. Consider copying the 218 // runSleepingContainer helper to have an exec version. 219 testRequires(c, DaemonIsLinux) 220 cli.DockerCmd(c, "run", "-d", "--name", "top", "busybox", "top") 221 222 // Test normal (non-detached) case first 223 icmd.RunCommand(dockerBinary, "exec", "top").Assert(c, icmd.Expected{ 224 ExitCode: 1, 225 Error: "exit status 1", 226 Err: "See 'docker exec --help'", 227 }) 228 } 229 230 func (s *DockerCLIExecSuite) TestExecStopNotHanging(c *testing.T) { 231 // TODO Windows CI: Requires some extra work. Consider copying the 232 // runSleepingContainer helper to have an exec version. 233 testRequires(c, DaemonIsLinux) 234 cli.DockerCmd(c, "run", "-d", "--name", "testing", "busybox", "top") 235 236 result := icmd.StartCmd(icmd.Command(dockerBinary, "exec", "testing", "top")) 237 result.Assert(c, icmd.Success) 238 go icmd.WaitOnCmd(0, result) 239 240 type dstop struct { 241 out string 242 err error 243 } 244 ch := make(chan dstop, 1) 245 go func() { 246 result := icmd.RunCommand(dockerBinary, "stop", "testing") 247 ch <- dstop{result.Combined(), result.Error} 248 close(ch) 249 }() 250 select { 251 case <-time.After(3 * time.Second): 252 c.Fatal("Container stop timed out") 253 case s := <-ch: 254 assert.NilError(c, s.err) 255 } 256 } 257 258 func (s *DockerCLIExecSuite) TestExecCgroup(c *testing.T) { 259 // Not applicable on Windows - using Linux specific functionality 260 testRequires(c, NotUserNamespace) 261 testRequires(c, DaemonIsLinux) 262 cli.DockerCmd(c, "run", "-d", "--name", "testing", "busybox", "top") 263 264 out := cli.DockerCmd(c, "exec", "testing", "cat", "/proc/1/cgroup").Stdout() 265 containerCgroups := sort.StringSlice(strings.Split(out, "\n")) 266 267 var wg sync.WaitGroup 268 var mu sync.Mutex 269 var execCgroups []sort.StringSlice 270 errChan := make(chan error, 5) 271 // exec a few times concurrently to get consistent failure 272 for i := 0; i < 5; i++ { 273 wg.Add(1) 274 go func() { 275 defer wg.Done() 276 out, _, err := dockerCmdWithError("exec", "testing", "cat", "/proc/self/cgroup") 277 if err != nil { 278 errChan <- err 279 return 280 } 281 cg := sort.StringSlice(strings.Split(out, "\n")) 282 283 mu.Lock() 284 execCgroups = append(execCgroups, cg) 285 mu.Unlock() 286 }() 287 } 288 wg.Wait() 289 close(errChan) 290 291 for err := range errChan { 292 assert.NilError(c, err) 293 } 294 295 for _, cg := range execCgroups { 296 if !reflect.DeepEqual(cg, containerCgroups) { 297 fmt.Println("exec cgroups:") 298 for _, name := range cg { 299 fmt.Printf(" %s\n", name) 300 } 301 302 fmt.Println("container cgroups:") 303 for _, name := range containerCgroups { 304 fmt.Printf(" %s\n", name) 305 } 306 c.Fatal("cgroups mismatched") 307 } 308 } 309 } 310 311 func (s *DockerCLIExecSuite) TestExecInspectID(c *testing.T) { 312 id := runSleepingContainer(c, "-d") 313 314 out := inspectField(c, id, "ExecIDs") 315 assert.Equal(c, out, "[]", "ExecIDs should be empty, got: %s", out) 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 /execid1; do sleep 1; done") 320 321 err := cmd.Start() 322 assert.NilError(c, err, "failed to start the exec cmd") 323 324 // Give the exec 10 chances/seconds to start then give up and stop the test 325 tries := 10 326 for i := 0; i < tries; i++ { 327 // Since its still running we should see exec as part of the container 328 out = strings.TrimSpace(inspectField(c, id, "ExecIDs")) 329 330 if out != "[]" && out != "<no value>" { 331 break 332 } 333 assert.Check(c, i+1 != tries, "ExecIDs still empty after 10 second") 334 time.Sleep(1 * time.Second) 335 } 336 337 // Save execID for later 338 execID, err := inspectFilter(id, "index .ExecIDs 0") 339 assert.NilError(c, err, "failed to get the exec id") 340 341 // End the exec by creating the missing file 342 err = exec.Command(dockerBinary, "exec", id, "sh", "-c", "touch /execid1").Run() 343 assert.NilError(c, err, "failed to run the 2nd exec cmd") 344 345 // Wait for 1st exec to complete 346 cmd.Wait() 347 348 // Give the exec 10 chances/seconds to stop then give up and stop the test 349 for i := 0; i < tries; i++ { 350 // Since its still running we should see exec as part of the container 351 out = strings.TrimSpace(inspectField(c, id, "ExecIDs")) 352 353 if out == "[]" { 354 break 355 } 356 assert.Check(c, i+1 != tries, "ExecIDs still empty after 10 second") 357 time.Sleep(1 * time.Second) 358 } 359 360 // But we should still be able to query the execID 361 apiClient, err := client.NewClientWithOpts(client.FromEnv) 362 assert.NilError(c, err) 363 defer apiClient.Close() 364 365 _, err = apiClient.ContainerExecInspect(testutil.GetContext(c), execID) 366 assert.NilError(c, err) 367 368 // Now delete the container and then an 'inspect' on the exec should 369 // result in a 404 (not 'container not running') 370 res := cli.DockerCmd(c, "rm", "-f", id) 371 assert.Equal(c, res.ExitCode, 0, "error removing container: %s", res.Combined()) 372 373 _, err = apiClient.ContainerExecInspect(testutil.GetContext(c), execID) 374 assert.ErrorContains(c, err, "No such exec instance") 375 } 376 377 func (s *DockerCLIExecSuite) TestLinksPingLinkedContainersOnRename(c *testing.T) { 378 // Problematic on Windows as Windows does not support links 379 testRequires(c, DaemonIsLinux) 380 out := cli.DockerCmd(c, "run", "-d", "--name", "container1", "busybox", "top").Stdout() 381 idA := strings.TrimSpace(out) 382 assert.Assert(c, idA != "", "%s, id should not be nil", out) 383 out = cli.DockerCmd(c, "run", "-d", "--link", "container1:alias1", "--name", "container2", "busybox", "top").Stdout() 384 idB := strings.TrimSpace(out) 385 assert.Assert(c, idB != "", "%s, id should not be nil", out) 386 387 cli.DockerCmd(c, "exec", "container2", "ping", "-c", "1", "alias1", "-W", "1") 388 cli.DockerCmd(c, "rename", "container1", "container_new") 389 cli.DockerCmd(c, "exec", "container2", "ping", "-c", "1", "alias1", "-W", "1") 390 } 391 392 func (s *DockerCLIExecSuite) TestRunMutableNetworkFiles(c *testing.T) { 393 // Not applicable on Windows to Windows CI. 394 testRequires(c, testEnv.IsLocalDaemon, DaemonIsLinux) 395 for _, fn := range []string{"resolv.conf", "hosts"} { 396 containers := cli.DockerCmd(c, "ps", "-q", "-a").Combined() 397 if containers != "" { 398 cli.DockerCmd(c, append([]string{"rm", "-fv"}, strings.Split(strings.TrimSpace(containers), "\n")...)...) 399 } 400 401 content := runCommandAndReadContainerFile(c, fn, dockerBinary, "run", "-d", "--name", "c1", "busybox", "sh", "-c", fmt.Sprintf("echo success >/etc/%s && top", fn)) 402 403 assert.Equal(c, strings.TrimSpace(string(content)), "success", "Content was not what was modified in the container", string(content)) 404 405 out := cli.DockerCmd(c, "run", "-d", "--name", "c2", "busybox", "top").Stdout() 406 contID := strings.TrimSpace(out) 407 netFilePath := containerStorageFile(contID, fn) 408 409 f, err := os.OpenFile(netFilePath, os.O_WRONLY|os.O_SYNC|os.O_APPEND, 0o644) 410 assert.NilError(c, err) 411 412 if _, err := f.Seek(0, 0); err != nil { 413 f.Close() 414 c.Fatal(err) 415 } 416 417 if err := f.Truncate(0); err != nil { 418 f.Close() 419 c.Fatal(err) 420 } 421 422 if _, err := f.Write([]byte("success2\n")); err != nil { 423 f.Close() 424 c.Fatal(err) 425 } 426 f.Close() 427 428 res := cli.DockerCmd(c, "exec", contID, "cat", "/etc/"+fn).Stdout() 429 assert.Equal(c, res, "success2\n") 430 } 431 } 432 433 func (s *DockerCLIExecSuite) TestExecWithUser(c *testing.T) { 434 // TODO Windows CI: This may be fixable in the future once Windows 435 // supports users 436 testRequires(c, DaemonIsLinux) 437 cli.DockerCmd(c, "run", "-d", "--name", "parent", "busybox", "top") 438 439 out := cli.DockerCmd(c, "exec", "-u", "1", "parent", "id").Stdout() 440 assert.Assert(c, strings.Contains(out, "uid=1(daemon) gid=1(daemon)")) 441 442 out = cli.DockerCmd(c, "exec", "-u", "root", "parent", "id").Stdout() 443 assert.Assert(c, strings.Contains(out, "uid=0(root) gid=0(root)"), "exec with user by id expected daemon user got %s", out) 444 } 445 446 func (s *DockerCLIExecSuite) TestExecWithPrivileged(c *testing.T) { 447 // Not applicable on Windows 448 testRequires(c, DaemonIsLinux, NotUserNamespace) 449 // Start main loop which attempts mknod repeatedly 450 cli.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`) 451 452 // Check exec mknod doesn't work 453 icmd.RunCommand(dockerBinary, "exec", "parent", "sh", "-c", "mknod /tmp/sdb b 8 16").Assert(c, icmd.Expected{ 454 ExitCode: 1, 455 Err: "Operation not permitted", 456 }) 457 458 // Check exec mknod does work with --privileged 459 result := icmd.RunCommand(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`) 460 result.Assert(c, icmd.Success) 461 462 actual := strings.TrimSpace(result.Combined()) 463 assert.Equal(c, actual, "ok", "exec mknod in --cap-drop=ALL container with --privileged failed, output: %q", result.Combined()) 464 465 // Check subsequent unprivileged exec cannot mknod 466 icmd.RunCommand(dockerBinary, "exec", "parent", "sh", "-c", "mknod /tmp/sdc b 8 32").Assert(c, icmd.Expected{ 467 ExitCode: 1, 468 Err: "Operation not permitted", 469 }) 470 // Confirm at no point was mknod allowed 471 result = icmd.RunCommand(dockerBinary, "logs", "parent") 472 result.Assert(c, icmd.Success) 473 assert.Assert(c, !strings.Contains(result.Combined(), "Success")) 474 } 475 476 func (s *DockerCLIExecSuite) TestExecWithImageUser(c *testing.T) { 477 // Not applicable on Windows 478 testRequires(c, DaemonIsLinux) 479 const name = "testbuilduser" 480 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox 481 RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd 482 USER dockerio`)) 483 cli.DockerCmd(c, "run", "-d", "--name", "dockerioexec", name, "top") 484 485 out := cli.DockerCmd(c, "exec", "dockerioexec", "whoami").Stdout() 486 assert.Assert(c, strings.Contains(out, "dockerio"), "exec with user by id expected dockerio user got %s", out) 487 } 488 489 func (s *DockerCLIExecSuite) TestExecOnReadonlyContainer(c *testing.T) { 490 // Windows does not support read-only 491 // --read-only + userns has remount issues 492 testRequires(c, DaemonIsLinux, NotUserNamespace) 493 cli.DockerCmd(c, "run", "-d", "--read-only", "--name", "parent", "busybox", "top") 494 cli.DockerCmd(c, "exec", "parent", "true") 495 } 496 497 func (s *DockerCLIExecSuite) TestExecUlimits(c *testing.T) { 498 testRequires(c, DaemonIsLinux) 499 const name = "testexeculimits" 500 runSleepingContainer(c, "-d", "--ulimit", "nofile=511:511", "--name", name) 501 cli.WaitRun(c, name) 502 503 out, _, err := dockerCmdWithError("exec", name, "sh", "-c", "ulimit -n") 504 assert.NilError(c, err) 505 assert.Equal(c, strings.TrimSpace(out), "511") 506 } 507 508 // #15750 509 func (s *DockerCLIExecSuite) TestExecStartFails(c *testing.T) { 510 const name = "exec-15750" 511 runSleepingContainer(c, "-d", "--name", name) 512 cli.WaitRun(c, name) 513 514 out, _, err := dockerCmdWithError("exec", name, "no-such-cmd") 515 assert.ErrorContains(c, err, "", out) 516 517 expectedMsg := "executable file not found" 518 if DaemonIsWindows() { 519 expectedMsg = "The system cannot find the file specified" 520 } 521 assert.Assert(c, is.Contains(out, expectedMsg)) 522 } 523 524 // Fix regression in https://github.com/docker/docker/pull/26461#issuecomment-250287297 525 func (s *DockerCLIExecSuite) TestExecWindowsPathNotWiped(c *testing.T) { 526 testRequires(c, DaemonIsWindows) 527 out := cli.DockerCmd(c, "run", "-d", "--name", "testing", minimalBaseImage(), "powershell", "start-sleep", "60").Stdout() 528 cli.WaitRun(c, strings.TrimSpace(out)) 529 530 out = cli.DockerCmd(c, "exec", "testing", "powershell", "write-host", "$env:PATH").Stdout() 531 out = strings.ToLower(strings.Trim(out, "\r\n")) 532 assert.Assert(c, strings.Contains(out, `windowspowershell\v1.0`)) 533 } 534 535 func (s *DockerCLIExecSuite) TestExecEnvLinksHost(c *testing.T) { 536 testRequires(c, DaemonIsLinux) 537 runSleepingContainer(c, "-d", "--name", "foo") 538 runSleepingContainer(c, "-d", "--link", "foo:db", "--hostname", "myhost", "--name", "bar") 539 out := cli.DockerCmd(c, "exec", "bar", "env").Stdout() 540 assert.Check(c, is.Contains(out, "HOSTNAME=myhost")) 541 assert.Check(c, is.Contains(out, "DB_NAME=/bar/db")) 542 }