github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/integration-cli/docker_api_exec_test.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"os"
    11  	"strings"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/Prakhar-Agarwal-byte/moby/api/types"
    16  	"github.com/Prakhar-Agarwal-byte/moby/api/types/versions"
    17  	"github.com/Prakhar-Agarwal-byte/moby/client"
    18  	"github.com/Prakhar-Agarwal-byte/moby/integration-cli/checker"
    19  	"github.com/Prakhar-Agarwal-byte/moby/integration-cli/cli"
    20  	"github.com/Prakhar-Agarwal-byte/moby/testutil"
    21  	"github.com/Prakhar-Agarwal-byte/moby/testutil/request"
    22  	"gotest.tools/v3/assert"
    23  	is "gotest.tools/v3/assert/cmp"
    24  	"gotest.tools/v3/poll"
    25  )
    26  
    27  // Regression test for #9414
    28  func (s *DockerAPISuite) TestExecAPICreateNoCmd(c *testing.T) {
    29  	name := "exec_test"
    30  	cli.DockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
    31  
    32  	res, body, err := request.Post(testutil.GetContext(c), fmt.Sprintf("/containers/%s/exec", name), request.JSONBody(map[string]interface{}{"Cmd": nil}))
    33  	assert.NilError(c, err)
    34  	if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") {
    35  		assert.Equal(c, res.StatusCode, http.StatusInternalServerError)
    36  	} else {
    37  		assert.Equal(c, res.StatusCode, http.StatusBadRequest)
    38  	}
    39  	b, err := request.ReadBody(body)
    40  	assert.NilError(c, err)
    41  	assert.Assert(c, strings.Contains(getErrorMessage(c, b), "No exec command specified"), "Expected message when creating exec command with no Cmd specified")
    42  }
    43  
    44  func (s *DockerAPISuite) TestExecAPICreateNoValidContentType(c *testing.T) {
    45  	name := "exec_test"
    46  	cli.DockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
    47  
    48  	jsonData := bytes.NewBuffer(nil)
    49  	if err := json.NewEncoder(jsonData).Encode(map[string]interface{}{"Cmd": nil}); err != nil {
    50  		c.Fatalf("Can not encode data to json %s", err)
    51  	}
    52  
    53  	res, body, err := request.Post(testutil.GetContext(c), fmt.Sprintf("/containers/%s/exec", name), request.RawContent(io.NopCloser(jsonData)), request.ContentType("test/plain"))
    54  	assert.NilError(c, err)
    55  	if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") {
    56  		assert.Equal(c, res.StatusCode, http.StatusInternalServerError)
    57  	} else {
    58  		assert.Equal(c, res.StatusCode, http.StatusBadRequest)
    59  	}
    60  	b, err := request.ReadBody(body)
    61  	assert.NilError(c, err)
    62  	assert.Assert(c, is.Contains(getErrorMessage(c, b), "unsupported Content-Type header (test/plain): must be 'application/json'"))
    63  }
    64  
    65  func (s *DockerAPISuite) TestExecAPICreateContainerPaused(c *testing.T) {
    66  	// Not relevant on Windows as Windows containers cannot be paused
    67  	testRequires(c, DaemonIsLinux)
    68  	name := "exec_create_test"
    69  	cli.DockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
    70  
    71  	cli.DockerCmd(c, "pause", name)
    72  
    73  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
    74  	assert.NilError(c, err)
    75  	defer apiClient.Close()
    76  
    77  	config := types.ExecConfig{
    78  		Cmd: []string{"true"},
    79  	}
    80  	_, err = apiClient.ContainerExecCreate(testutil.GetContext(c), name, config)
    81  	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)
    82  }
    83  
    84  func (s *DockerAPISuite) TestExecAPIStart(c *testing.T) {
    85  	testRequires(c, DaemonIsLinux) // Uses pause/unpause but bits may be salvageable to Windows to Windows CI
    86  	cli.DockerCmd(c, "run", "-d", "--name", "test", "busybox", "top")
    87  
    88  	id := createExec(c, "test")
    89  	startExec(c, id, http.StatusOK)
    90  
    91  	var execJSON struct{ PID int }
    92  	inspectExec(testutil.GetContext(c), c, id, &execJSON)
    93  	assert.Assert(c, execJSON.PID > 1)
    94  
    95  	id = createExec(c, "test")
    96  	cli.DockerCmd(c, "stop", "test")
    97  
    98  	startExec(c, id, http.StatusNotFound)
    99  
   100  	cli.DockerCmd(c, "start", "test")
   101  	startExec(c, id, http.StatusNotFound)
   102  
   103  	// make sure exec is created before pausing
   104  	id = createExec(c, "test")
   105  	cli.DockerCmd(c, "pause", "test")
   106  	startExec(c, id, http.StatusConflict)
   107  	cli.DockerCmd(c, "unpause", "test")
   108  	startExec(c, id, http.StatusOK)
   109  }
   110  
   111  func (s *DockerAPISuite) TestExecAPIStartEnsureHeaders(c *testing.T) {
   112  	testRequires(c, DaemonIsLinux)
   113  	cli.DockerCmd(c, "run", "-d", "--name", "test", "busybox", "top")
   114  
   115  	id := createExec(c, "test")
   116  	resp, _, err := request.Post(testutil.GetContext(c), fmt.Sprintf("/exec/%s/start", id), request.RawString(`{"Detach": true}`), request.JSON)
   117  	assert.NilError(c, err)
   118  	assert.Assert(c, resp.Header.Get("Server") != "")
   119  }
   120  
   121  func (s *DockerAPISuite) TestExecAPIStartBackwardsCompatible(c *testing.T) {
   122  	testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
   123  	runSleepingContainer(c, "-d", "--name", "test")
   124  	id := createExec(c, "test")
   125  
   126  	resp, body, err := request.Post(testutil.GetContext(c), fmt.Sprintf("/v1.20/exec/%s/start", id), request.RawString(`{"Detach": true}`), request.ContentType("text/plain"))
   127  	assert.NilError(c, err)
   128  
   129  	b, err := request.ReadBody(body)
   130  	comment := fmt.Sprintf("response body: %s", b)
   131  	assert.NilError(c, err, comment)
   132  	assert.Equal(c, resp.StatusCode, http.StatusOK, comment)
   133  }
   134  
   135  // #19362
   136  func (s *DockerAPISuite) TestExecAPIStartMultipleTimesError(c *testing.T) {
   137  	runSleepingContainer(c, "-d", "--name", "test")
   138  	execID := createExec(c, "test")
   139  	startExec(c, execID, http.StatusOK)
   140  	waitForExec(testutil.GetContext(c), c, execID)
   141  
   142  	startExec(c, execID, http.StatusConflict)
   143  }
   144  
   145  // #20638
   146  func (s *DockerAPISuite) TestExecAPIStartWithDetach(c *testing.T) {
   147  	name := "foo"
   148  	runSleepingContainer(c, "-d", "-t", "--name", name)
   149  
   150  	ctx := testutil.GetContext(c)
   151  
   152  	config := types.ExecConfig{
   153  		Cmd:          []string{"true"},
   154  		AttachStderr: true,
   155  	}
   156  
   157  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   158  	assert.NilError(c, err)
   159  	defer apiClient.Close()
   160  
   161  	createResp, err := apiClient.ContainerExecCreate(ctx, name, config)
   162  	assert.NilError(c, err)
   163  
   164  	_, body, err := request.Post(ctx, fmt.Sprintf("/exec/%s/start", createResp.ID), request.RawString(`{"Detach": true}`), request.JSON)
   165  	assert.NilError(c, err)
   166  
   167  	b, err := request.ReadBody(body)
   168  	comment := fmt.Sprintf("response body: %s", b)
   169  	assert.NilError(c, err, comment)
   170  
   171  	resp, _, err := request.Get(ctx, "/_ping")
   172  	assert.NilError(c, err)
   173  	if resp.StatusCode != http.StatusOK {
   174  		c.Fatal("daemon is down, it should alive")
   175  	}
   176  }
   177  
   178  // #30311
   179  func (s *DockerAPISuite) TestExecAPIStartValidCommand(c *testing.T) {
   180  	name := "exec_test"
   181  	cli.DockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
   182  
   183  	id := createExecCmd(c, name, "true")
   184  	startExec(c, id, http.StatusOK)
   185  
   186  	ctx := testutil.GetContext(c)
   187  	waitForExec(ctx, c, id)
   188  
   189  	var inspectJSON struct{ ExecIDs []string }
   190  	inspectContainer(ctx, c, name, &inspectJSON)
   191  
   192  	assert.Assert(c, inspectJSON.ExecIDs == nil)
   193  }
   194  
   195  // #30311
   196  func (s *DockerAPISuite) TestExecAPIStartInvalidCommand(c *testing.T) {
   197  	name := "exec_test"
   198  	cli.DockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
   199  
   200  	id := createExecCmd(c, name, "invalid")
   201  	if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") {
   202  		startExec(c, id, http.StatusNotFound)
   203  	} else {
   204  		startExec(c, id, http.StatusBadRequest)
   205  	}
   206  	ctx := testutil.GetContext(c)
   207  	waitForExec(ctx, c, id)
   208  
   209  	var inspectJSON struct{ ExecIDs []string }
   210  	inspectContainer(ctx, c, name, &inspectJSON)
   211  
   212  	assert.Assert(c, inspectJSON.ExecIDs == nil)
   213  }
   214  
   215  func (s *DockerAPISuite) TestExecStateCleanup(c *testing.T) {
   216  	testRequires(c, DaemonIsLinux, testEnv.IsLocalDaemon)
   217  
   218  	// This test checks accidental regressions. Not part of stable API.
   219  
   220  	name := "exec_cleanup"
   221  	cid := cli.DockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh").Stdout()
   222  	cid = strings.TrimSpace(cid)
   223  
   224  	stateDir := "/var/run/docker/containerd/" + cid
   225  
   226  	checkReadDir := func(c *testing.T) (interface{}, string) {
   227  		fi, err := os.ReadDir(stateDir)
   228  		assert.NilError(c, err)
   229  		return len(fi), ""
   230  	}
   231  
   232  	fi, err := os.ReadDir(stateDir)
   233  	assert.NilError(c, err)
   234  	assert.Assert(c, len(fi) > 1)
   235  
   236  	id := createExecCmd(c, name, "ls")
   237  	startExec(c, id, http.StatusOK)
   238  
   239  	ctx := testutil.GetContext(c)
   240  	waitForExec(ctx, c, id)
   241  
   242  	poll.WaitOn(c, pollCheck(c, checkReadDir, checker.Equals(len(fi))), poll.WithTimeout(5*time.Second))
   243  
   244  	id = createExecCmd(c, name, "invalid")
   245  	startExec(c, id, http.StatusBadRequest)
   246  	waitForExec(ctx, c, id)
   247  
   248  	poll.WaitOn(c, pollCheck(c, checkReadDir, checker.Equals(len(fi))), poll.WithTimeout(5*time.Second))
   249  
   250  	cli.DockerCmd(c, "stop", name)
   251  	_, err = os.Stat(stateDir)
   252  	assert.ErrorContains(c, err, "")
   253  	assert.Assert(c, os.IsNotExist(err))
   254  }
   255  
   256  func createExec(c *testing.T, name string) string {
   257  	return createExecCmd(c, name, "true")
   258  }
   259  
   260  func createExecCmd(c *testing.T, name string, cmd string) string {
   261  	_, reader, err := request.Post(testutil.GetContext(c), fmt.Sprintf("/containers/%s/exec", name), request.JSONBody(map[string]interface{}{"Cmd": []string{cmd}}))
   262  	assert.NilError(c, err)
   263  	b, err := io.ReadAll(reader)
   264  	assert.NilError(c, err)
   265  	defer reader.Close()
   266  	createResp := struct {
   267  		ID string `json:"Id"`
   268  	}{}
   269  	assert.NilError(c, json.Unmarshal(b, &createResp), string(b))
   270  	return createResp.ID
   271  }
   272  
   273  func startExec(c *testing.T, id string, code int) {
   274  	resp, body, err := request.Post(testutil.GetContext(c), fmt.Sprintf("/exec/%s/start", id), request.RawString(`{"Detach": true}`), request.JSON)
   275  	assert.NilError(c, err)
   276  
   277  	b, err := request.ReadBody(body)
   278  	assert.NilError(c, err, "response body: %s", b)
   279  	assert.Equal(c, resp.StatusCode, code, "response body: %s", b)
   280  }
   281  
   282  func inspectExec(ctx context.Context, c *testing.T, id string, out interface{}) {
   283  	resp, body, err := request.Get(ctx, fmt.Sprintf("/exec/%s/json", id))
   284  	assert.NilError(c, err)
   285  	defer body.Close()
   286  	assert.Equal(c, resp.StatusCode, http.StatusOK)
   287  	err = json.NewDecoder(body).Decode(out)
   288  	assert.NilError(c, err)
   289  }
   290  
   291  func waitForExec(ctx context.Context, c *testing.T, id string) {
   292  	timeout := time.After(60 * time.Second)
   293  	var execJSON struct{ Running bool }
   294  	for {
   295  		select {
   296  		case <-timeout:
   297  			c.Fatal("timeout waiting for exec to start")
   298  		default:
   299  		}
   300  
   301  		inspectExec(ctx, c, id, &execJSON)
   302  		if !execJSON.Running {
   303  			break
   304  		}
   305  	}
   306  }
   307  
   308  func inspectContainer(ctx context.Context, c *testing.T, id string, out interface{}) {
   309  	resp, body, err := request.Get(ctx, "/containers/"+id+"/json")
   310  	assert.NilError(c, err)
   311  	defer body.Close()
   312  	assert.Equal(c, resp.StatusCode, http.StatusOK)
   313  	err = json.NewDecoder(body).Decode(out)
   314  	assert.NilError(c, err)
   315  }