integration-cli/docker_api_exec_test.go
c8a3d313
 package main
 
 import (
 	"bytes"
7d62e40f
 	"context"
801a7fed
 	"encoding/json"
c8a3d313
 	"fmt"
f85ee178
 	"io/ioutil"
c7845e27
 	"net/http"
6c4ce7cb
 	"os"
 	"strings"
59e55dcd
 	"testing"
1a60a805
 	"time"
dc944ea7
 
0fd5a654
 	"github.com/docker/docker/api/types"
e4408318
 	"github.com/docker/docker/api/types/versions"
0fd5a654
 	"github.com/docker/docker/client"
33968e6c
 	"github.com/docker/docker/integration-cli/checker"
b37c214e
 	"github.com/docker/docker/testutil/request"
9f0b3f56
 	"gotest.tools/v3/assert"
 	"gotest.tools/v3/poll"
c8a3d313
 )
 
 // Regression test for #9414
1d92789b
 func (s *DockerSuite) TestExecAPICreateNoCmd(c *testing.T) {
c8a3d313
 	name := "exec_test"
5c295460
 	dockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
c8a3d313
 
0fd5a654
 	res, body, err := request.Post(fmt.Sprintf("/containers/%s/exec", name), request.JSONBody(map[string]interface{}{"Cmd": nil}))
6345208b
 	assert.NilError(c, err)
e4408318
 	if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") {
6345208b
 		assert.Equal(c, res.StatusCode, http.StatusInternalServerError)
e4408318
 	} else {
6345208b
 		assert.Equal(c, res.StatusCode, http.StatusBadRequest)
e4408318
 	}
0fd5a654
 	b, err := request.ReadBody(body)
6345208b
 	assert.NilError(c, err)
 	assert.Assert(c, strings.Contains(getErrorMessage(c, b), "No exec command specified"), "Expected message when creating exec command with no Cmd specified")
c8a3d313
 }
801a7fed
 
1d92789b
 func (s *DockerSuite) TestExecAPICreateNoValidContentType(c *testing.T) {
801a7fed
 	name := "exec_test"
 	dockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
 
 	jsonData := bytes.NewBuffer(nil)
 	if err := json.NewEncoder(jsonData).Encode(map[string]interface{}{"Cmd": nil}); err != nil {
 		c.Fatalf("Can not encode data to json %s", err)
 	}
 
b11ba123
 	res, body, err := request.Post(fmt.Sprintf("/containers/%s/exec", name), request.RawContent(ioutil.NopCloser(jsonData)), request.ContentType("test/plain"))
6345208b
 	assert.NilError(c, err)
e4408318
 	if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") {
6345208b
 		assert.Equal(c, res.StatusCode, http.StatusInternalServerError)
e4408318
 	} else {
6345208b
 		assert.Equal(c, res.StatusCode, http.StatusBadRequest)
e4408318
 	}
4f304e72
 	b, err := request.ReadBody(body)
6345208b
 	assert.NilError(c, err)
 	assert.Assert(c, strings.Contains(getErrorMessage(c, b), "Content-Type specified"), "Expected message when creating exec command with invalid Content-Type specified")
801a7fed
 }
2d43d934
 
1d92789b
 func (s *DockerSuite) TestExecAPICreateContainerPaused(c *testing.T) {
9642c817
 	// Not relevant on Windows as Windows containers cannot be paused
01b86d61
 	testRequires(c, DaemonIsLinux)
 	name := "exec_create_test"
 	dockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
 
 	dockerCmd(c, "pause", name)
0fd5a654
 
c8ff5ecc
 	cli, err := client.NewClientWithOpts(client.FromEnv)
6345208b
 	assert.NilError(c, err)
0fd5a654
 	defer cli.Close()
 
 	config := types.ExecConfig{
 		Cmd: []string{"true"},
 	}
 	_, err = cli.ContainerExecCreate(context.Background(), name, config)
6345208b
 	assert.ErrorContains(c, err, "Container "+name+" is paused, unpause the container before exec", "Expected message when creating exec command with Container %s is paused", name)
01b86d61
 }
 
1d92789b
 func (s *DockerSuite) TestExecAPIStart(c *testing.T) {
40af5691
 	testRequires(c, DaemonIsLinux) // Uses pause/unpause but bits may be salvageable to Windows to Windows CI
2d43d934
 	dockerCmd(c, "run", "-d", "--name", "test", "busybox", "top")
 
0b5e628e
 	id := createExec(c, "test")
1a60a805
 	startExec(c, id, http.StatusOK)
2d43d934
 
18083481
 	var execJSON struct{ PID int }
 	inspectExec(c, id, &execJSON)
6345208b
 	assert.Assert(c, execJSON.PID > 1)
18083481
 
0b5e628e
 	id = createExec(c, "test")
2d43d934
 	dockerCmd(c, "stop", "test")
 
1a60a805
 	startExec(c, id, http.StatusNotFound)
2d43d934
 
 	dockerCmd(c, "start", "test")
1a60a805
 	startExec(c, id, http.StatusNotFound)
2d43d934
 
 	// make sure exec is created before pausing
0b5e628e
 	id = createExec(c, "test")
2d43d934
 	dockerCmd(c, "pause", "test")
1a60a805
 	startExec(c, id, http.StatusConflict)
2d43d934
 	dockerCmd(c, "unpause", "test")
1a60a805
 	startExec(c, id, http.StatusOK)
2d43d934
 }
0b5e628e
 
1d92789b
 func (s *DockerSuite) TestExecAPIStartEnsureHeaders(c *testing.T) {
f86db80b
 	testRequires(c, DaemonIsLinux)
 	dockerCmd(c, "run", "-d", "--name", "test", "busybox", "top")
 
 	id := createExec(c, "test")
b11ba123
 	resp, _, err := request.Post(fmt.Sprintf("/exec/%s/start", id), request.RawString(`{"Detach": true}`), request.JSON)
6345208b
 	assert.NilError(c, err)
 	assert.Assert(c, resp.Header.Get("Server") != "")
f86db80b
 }
 
1d92789b
 func (s *DockerSuite) TestExecAPIStartBackwardsCompatible(c *testing.T) {
f811d5b1
 	testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
9642c817
 	runSleepingContainer(c, "-d", "--name", "test")
0b5e628e
 	id := createExec(c, "test")
 
b11ba123
 	resp, body, err := request.Post(fmt.Sprintf("/v1.20/exec/%s/start", id), request.RawString(`{"Detach": true}`), request.ContentType("text/plain"))
6345208b
 	assert.NilError(c, err)
0b5e628e
 
4f304e72
 	b, err := request.ReadBody(body)
a2024a54
 	comment := fmt.Sprintf("response body: %s", b)
6345208b
 	assert.NilError(c, err, comment)
 	assert.Equal(c, resp.StatusCode, http.StatusOK, comment)
0b5e628e
 }
 
1a60a805
 // #19362
1d92789b
 func (s *DockerSuite) TestExecAPIStartMultipleTimesError(c *testing.T) {
9642c817
 	runSleepingContainer(c, "-d", "--name", "test")
1a60a805
 	execID := createExec(c, "test")
 	startExec(c, execID, http.StatusOK)
3cc0d6bb
 	waitForExec(c, execID)
1a60a805
 
 	startExec(c, execID, http.StatusConflict)
 }
 
fb0ac1af
 // #20638
1d92789b
 func (s *DockerSuite) TestExecAPIStartWithDetach(c *testing.T) {
fb0ac1af
 	name := "foo"
21c85111
 	runSleepingContainer(c, "-d", "-t", "--name", name)
0fd5a654
 
 	config := types.ExecConfig{
 		Cmd:          []string{"true"},
 		AttachStderr: true,
fb0ac1af
 	}
 
c8ff5ecc
 	cli, err := client.NewClientWithOpts(client.FromEnv)
6345208b
 	assert.NilError(c, err)
0fd5a654
 	defer cli.Close()
 
 	createResp, err := cli.ContainerExecCreate(context.Background(), name, config)
6345208b
 	assert.NilError(c, err)
fb0ac1af
 
b11ba123
 	_, body, err := request.Post(fmt.Sprintf("/exec/%s/start", createResp.ID), request.RawString(`{"Detach": true}`), request.JSON)
6345208b
 	assert.NilError(c, err)
fb0ac1af
 
0fd5a654
 	b, err := request.ReadBody(body)
a2024a54
 	comment := fmt.Sprintf("response body: %s", b)
6345208b
 	assert.NilError(c, err, comment)
fb0ac1af
 
b11ba123
 	resp, _, err := request.Get("/_ping")
6345208b
 	assert.NilError(c, err)
fb0ac1af
 	if resp.StatusCode != http.StatusOK {
 		c.Fatal("daemon is down, it should alive")
 	}
 }
 
3cc0d6bb
 // #30311
1d92789b
 func (s *DockerSuite) TestExecAPIStartValidCommand(c *testing.T) {
3cc0d6bb
 	name := "exec_test"
 	dockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
 
 	id := createExecCmd(c, name, "true")
 	startExec(c, id, http.StatusOK)
 
 	waitForExec(c, id)
 
 	var inspectJSON struct{ ExecIDs []string }
 	inspectContainer(c, name, &inspectJSON)
 
6345208b
 	assert.Assert(c, inspectJSON.ExecIDs == nil)
3cc0d6bb
 }
 
 // #30311
1d92789b
 func (s *DockerSuite) TestExecAPIStartInvalidCommand(c *testing.T) {
3cc0d6bb
 	name := "exec_test"
 	dockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
 
 	id := createExecCmd(c, name, "invalid")
e4408318
 	if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") {
 		startExec(c, id, http.StatusNotFound)
 	} else {
 		startExec(c, id, http.StatusBadRequest)
 	}
3cc0d6bb
 	waitForExec(c, id)
 
 	var inspectJSON struct{ ExecIDs []string }
 	inspectContainer(c, name, &inspectJSON)
 
6345208b
 	assert.Assert(c, inspectJSON.ExecIDs == nil)
3cc0d6bb
 }
 
1d92789b
 func (s *DockerSuite) TestExecStateCleanup(c *testing.T) {
43b15e92
 	testRequires(c, DaemonIsLinux, testEnv.IsLocalDaemon)
6c4ce7cb
 
 	// This test checks accidental regressions. Not part of stable API.
 
 	name := "exec_cleanup"
 	cid, _ := dockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
 	cid = strings.TrimSpace(cid)
 
 	stateDir := "/var/run/docker/containerd/" + cid
 
3a24472c
 	checkReadDir := func(c *testing.T) (interface{}, string) {
6c4ce7cb
 		fi, err := ioutil.ReadDir(stateDir)
6345208b
 		assert.NilError(c, err)
64de5e82
 		return len(fi), ""
6c4ce7cb
 	}
 
 	fi, err := ioutil.ReadDir(stateDir)
6345208b
 	assert.NilError(c, err)
 	assert.Assert(c, len(fi) > 1)
6c4ce7cb
 
 	id := createExecCmd(c, name, "ls")
 	startExec(c, id, http.StatusOK)
 	waitForExec(c, id)
 
ac2f24e7
 	poll.WaitOn(c, pollCheck(c, checkReadDir, checker.Equals(len(fi))), poll.WithTimeout(5*time.Second))
6c4ce7cb
 
 	id = createExecCmd(c, name, "invalid")
 	startExec(c, id, http.StatusBadRequest)
 	waitForExec(c, id)
 
ac2f24e7
 	poll.WaitOn(c, pollCheck(c, checkReadDir, checker.Equals(len(fi))), poll.WithTimeout(5*time.Second))
6c4ce7cb
 
 	dockerCmd(c, "stop", name)
 	_, err = os.Stat(stateDir)
6345208b
 	assert.ErrorContains(c, err, "")
 	assert.Assert(c, os.IsNotExist(err))
6c4ce7cb
 }
 
1d92789b
 func createExec(c *testing.T, name string) string {
3cc0d6bb
 	return createExecCmd(c, name, "true")
 }
 
1d92789b
 func createExecCmd(c *testing.T, name string, cmd string) string {
b11ba123
 	_, reader, err := request.Post(fmt.Sprintf("/containers/%s/exec", name), request.JSONBody(map[string]interface{}{"Cmd": []string{cmd}}))
6345208b
 	assert.NilError(c, err)
f85ee178
 	b, err := ioutil.ReadAll(reader)
6345208b
 	assert.NilError(c, err)
f85ee178
 	defer reader.Close()
0b5e628e
 	createResp := struct {
 		ID string `json:"Id"`
 	}{}
6345208b
 	assert.NilError(c, json.Unmarshal(b, &createResp), string(b))
0b5e628e
 	return createResp.ID
 }
1a60a805
 
1d92789b
 func startExec(c *testing.T, id string, code int) {
b11ba123
 	resp, body, err := request.Post(fmt.Sprintf("/exec/%s/start", id), request.RawString(`{"Detach": true}`), request.JSON)
6345208b
 	assert.NilError(c, err)
1a60a805
 
4f304e72
 	b, err := request.ReadBody(body)
6345208b
 	assert.NilError(c, err, "response body: %s", b)
 	assert.Equal(c, resp.StatusCode, code, "response body: %s", b)
1a60a805
 }
 
1d92789b
 func inspectExec(c *testing.T, id string, out interface{}) {
b11ba123
 	resp, body, err := request.Get(fmt.Sprintf("/exec/%s/json", id))
6345208b
 	assert.NilError(c, err)
1a60a805
 	defer body.Close()
6345208b
 	assert.Equal(c, resp.StatusCode, http.StatusOK)
1a60a805
 	err = json.NewDecoder(body).Decode(out)
6345208b
 	assert.NilError(c, err)
1a60a805
 }
3cc0d6bb
 
1d92789b
 func waitForExec(c *testing.T, id string) {
3cc0d6bb
 	timeout := time.After(60 * time.Second)
 	var execJSON struct{ Running bool }
 	for {
 		select {
 		case <-timeout:
 			c.Fatal("timeout waiting for exec to start")
 		default:
 		}
 
 		inspectExec(c, id, &execJSON)
 		if !execJSON.Running {
 			break
 		}
 	}
 }
 
1d92789b
 func inspectContainer(c *testing.T, id string, out interface{}) {
b11ba123
 	resp, body, err := request.Get(fmt.Sprintf("/containers/%s/json", id))
6345208b
 	assert.NilError(c, err)
3cc0d6bb
 	defer body.Close()
6345208b
 	assert.Equal(c, resp.StatusCode, http.StatusOK)
3cc0d6bb
 	err = json.NewDecoder(body).Decode(out)
6345208b
 	assert.NilError(c, err)
3cc0d6bb
 }