github.com/khulnasoft-lab/khulnasoft@v26.0.1-0.20240328202558-330a6f959fe0+incompatible/integration-cli/docker_api_containers_test.go (about)

     1  package main
     2  
     3  import (
     4  	"archive/tar"
     5  	"bytes"
     6  	"context"
     7  	"encoding/json"
     8  	"fmt"
     9  	"io"
    10  	"net/http"
    11  	"os"
    12  	"path/filepath"
    13  	"regexp"
    14  	"runtime"
    15  	"strings"
    16  	"testing"
    17  	"time"
    18  
    19  	"github.com/docker/docker/api/types"
    20  	"github.com/docker/docker/api/types/container"
    21  	"github.com/docker/docker/api/types/mount"
    22  	"github.com/docker/docker/api/types/network"
    23  	"github.com/docker/docker/client"
    24  	dconfig "github.com/docker/docker/daemon/config"
    25  	"github.com/docker/docker/errdefs"
    26  	"github.com/docker/docker/integration-cli/cli"
    27  	"github.com/docker/docker/integration-cli/cli/build"
    28  	"github.com/docker/docker/pkg/stringid"
    29  	"github.com/docker/docker/testutil"
    30  	"github.com/docker/docker/testutil/request"
    31  	"github.com/docker/docker/volume"
    32  	"github.com/docker/go-connections/nat"
    33  	"gotest.tools/v3/assert"
    34  	is "gotest.tools/v3/assert/cmp"
    35  	"gotest.tools/v3/poll"
    36  )
    37  
    38  func (s *DockerAPISuite) TestContainerAPIGetAll(c *testing.T) {
    39  	startCount := getContainerCount(c)
    40  	const name = "getall"
    41  	cli.DockerCmd(c, "run", "--name", name, "busybox", "true")
    42  
    43  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
    44  	assert.NilError(c, err)
    45  	defer apiClient.Close()
    46  
    47  	ctx := testutil.GetContext(c)
    48  	containers, err := apiClient.ContainerList(ctx, container.ListOptions{
    49  		All: true,
    50  	})
    51  	assert.NilError(c, err)
    52  	assert.Equal(c, len(containers), startCount+1)
    53  	actual := containers[0].Names[0]
    54  	assert.Equal(c, actual, "/"+name)
    55  }
    56  
    57  // regression test for empty json field being omitted #13691
    58  func (s *DockerAPISuite) TestContainerAPIGetJSONNoFieldsOmitted(c *testing.T) {
    59  	startCount := getContainerCount(c)
    60  	cli.DockerCmd(c, "run", "busybox", "true")
    61  
    62  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
    63  	assert.NilError(c, err)
    64  	defer apiClient.Close()
    65  
    66  	options := container.ListOptions{
    67  		All: true,
    68  	}
    69  	ctx := testutil.GetContext(c)
    70  	containers, err := apiClient.ContainerList(ctx, options)
    71  	assert.NilError(c, err)
    72  	assert.Equal(c, len(containers), startCount+1)
    73  	actual := fmt.Sprintf("%+v", containers[0])
    74  
    75  	// empty Labels field triggered this bug, make sense to check for everything
    76  	// cause even Ports for instance can trigger this bug
    77  	// better safe than sorry..
    78  	fields := []string{
    79  		"ID",
    80  		"Names",
    81  		"Image",
    82  		"Command",
    83  		"Created",
    84  		"Ports",
    85  		"Labels",
    86  		"Status",
    87  		"NetworkSettings",
    88  	}
    89  
    90  	// decoding into types.Container do not work since it eventually unmarshal
    91  	// and empty field to an empty go map, so we just check for a string
    92  	for _, f := range fields {
    93  		if !strings.Contains(actual, f) {
    94  			c.Fatalf("Field %s is missing and it shouldn't", f)
    95  		}
    96  	}
    97  }
    98  
    99  func (s *DockerAPISuite) TestContainerAPIGetExport(c *testing.T) {
   100  	// Not supported on Windows as Windows does not support docker export
   101  	testRequires(c, DaemonIsLinux)
   102  	const name = "exportcontainer"
   103  	cli.DockerCmd(c, "run", "--name", name, "busybox", "touch", "/test")
   104  
   105  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   106  	assert.NilError(c, err)
   107  	defer apiClient.Close()
   108  
   109  	body, err := apiClient.ContainerExport(testutil.GetContext(c), name)
   110  	assert.NilError(c, err)
   111  	defer body.Close()
   112  	found := false
   113  	for tarReader := tar.NewReader(body); ; {
   114  		h, err := tarReader.Next()
   115  		if err != nil && err == io.EOF {
   116  			break
   117  		}
   118  		if h.Name == "test" {
   119  			found = true
   120  			break
   121  		}
   122  	}
   123  	assert.Assert(c, found, "The created test file has not been found in the exported image")
   124  }
   125  
   126  func (s *DockerAPISuite) TestContainerAPIGetChanges(c *testing.T) {
   127  	// Not supported on Windows as Windows does not support docker diff (/containers/name/changes)
   128  	testRequires(c, DaemonIsLinux)
   129  	const name = "changescontainer"
   130  	cli.DockerCmd(c, "run", "--name", name, "busybox", "rm", "/etc/passwd")
   131  
   132  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   133  	assert.NilError(c, err)
   134  	defer apiClient.Close()
   135  
   136  	changes, err := apiClient.ContainerDiff(testutil.GetContext(c), name)
   137  	assert.NilError(c, err)
   138  
   139  	// Check the changelog for removal of /etc/passwd
   140  	success := false
   141  	for _, elem := range changes {
   142  		if elem.Path == "/etc/passwd" && elem.Kind == 2 {
   143  			success = true
   144  		}
   145  	}
   146  	assert.Assert(c, success, "/etc/passwd has been removed but is not present in the diff")
   147  }
   148  
   149  func (s *DockerAPISuite) TestGetContainerStats(c *testing.T) {
   150  	const name = "statscontainer"
   151  	runSleepingContainer(c, "--name", name)
   152  
   153  	type b struct {
   154  		stats types.ContainerStats
   155  		err   error
   156  	}
   157  
   158  	bc := make(chan b, 1)
   159  	go func() {
   160  		apiClient, err := client.NewClientWithOpts(client.FromEnv)
   161  		assert.NilError(c, err)
   162  		defer apiClient.Close()
   163  
   164  		stats, err := apiClient.ContainerStats(testutil.GetContext(c), name, true)
   165  		assert.NilError(c, err)
   166  		bc <- b{stats, err}
   167  	}()
   168  
   169  	// allow some time to stream the stats from the container
   170  	time.Sleep(4 * time.Second)
   171  	cli.DockerCmd(c, "rm", "-f", name)
   172  
   173  	// collect the results from the stats stream or timeout and fail
   174  	// if the stream was not disconnected.
   175  	select {
   176  	case <-time.After(2 * time.Second):
   177  		c.Fatal("stream was not closed after container was removed")
   178  	case sr := <-bc:
   179  		dec := json.NewDecoder(sr.stats.Body)
   180  		defer sr.stats.Body.Close()
   181  		var s *types.Stats
   182  		// decode only one object from the stream
   183  		assert.NilError(c, dec.Decode(&s))
   184  	}
   185  }
   186  
   187  func (s *DockerAPISuite) TestGetContainerStatsRmRunning(c *testing.T) {
   188  	id := runSleepingContainer(c)
   189  
   190  	buf := &ChannelBuffer{C: make(chan []byte, 1)}
   191  	defer buf.Close()
   192  
   193  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   194  	assert.NilError(c, err)
   195  	defer apiClient.Close()
   196  
   197  	stats, err := apiClient.ContainerStats(testutil.GetContext(c), id, true)
   198  	assert.NilError(c, err)
   199  	defer stats.Body.Close()
   200  
   201  	chErr := make(chan error, 1)
   202  	go func() {
   203  		_, err = io.Copy(buf, stats.Body)
   204  		chErr <- err
   205  	}()
   206  
   207  	b := make([]byte, 32)
   208  	// make sure we've got some stats
   209  	_, err = buf.ReadTimeout(b, 2*time.Second)
   210  	assert.NilError(c, err)
   211  
   212  	// Now remove without `-f` and make sure we are still pulling stats
   213  	_, _, err = dockerCmdWithError("rm", id)
   214  	assert.Assert(c, err != nil, "rm should have failed but didn't")
   215  	_, err = buf.ReadTimeout(b, 2*time.Second)
   216  	assert.NilError(c, err)
   217  
   218  	cli.DockerCmd(c, "rm", "-f", id)
   219  	assert.Assert(c, <-chErr == nil)
   220  }
   221  
   222  // ChannelBuffer holds a chan of byte array that can be populate in a goroutine.
   223  type ChannelBuffer struct {
   224  	C chan []byte
   225  }
   226  
   227  // Write implements Writer.
   228  func (c *ChannelBuffer) Write(b []byte) (int, error) {
   229  	c.C <- b
   230  	return len(b), nil
   231  }
   232  
   233  // Close closes the go channel.
   234  func (c *ChannelBuffer) Close() error {
   235  	close(c.C)
   236  	return nil
   237  }
   238  
   239  // ReadTimeout reads the content of the channel in the specified byte array with
   240  // the specified duration as timeout.
   241  func (c *ChannelBuffer) ReadTimeout(p []byte, n time.Duration) (int, error) {
   242  	select {
   243  	case b := <-c.C:
   244  		return copy(p[0:], b), nil
   245  	case <-time.After(n):
   246  		return -1, fmt.Errorf("timeout reading from channel")
   247  	}
   248  }
   249  
   250  // regression test for gh13421
   251  // previous test was just checking one stat entry so it didn't fail (stats with
   252  // stream false always return one stat)
   253  func (s *DockerAPISuite) TestGetContainerStatsStream(c *testing.T) {
   254  	const name = "statscontainer"
   255  	runSleepingContainer(c, "--name", name)
   256  
   257  	type b struct {
   258  		stats types.ContainerStats
   259  		err   error
   260  	}
   261  
   262  	bc := make(chan b, 1)
   263  	go func() {
   264  		apiClient, err := client.NewClientWithOpts(client.FromEnv)
   265  		assert.NilError(c, err)
   266  		defer apiClient.Close()
   267  
   268  		stats, err := apiClient.ContainerStats(testutil.GetContext(c), name, true)
   269  		assert.NilError(c, err)
   270  		bc <- b{stats, err}
   271  	}()
   272  
   273  	// allow some time to stream the stats from the container
   274  	time.Sleep(4 * time.Second)
   275  	cli.DockerCmd(c, "rm", "-f", name)
   276  
   277  	// collect the results from the stats stream or timeout and fail
   278  	// if the stream was not disconnected.
   279  	select {
   280  	case <-time.After(2 * time.Second):
   281  		c.Fatal("stream was not closed after container was removed")
   282  	case sr := <-bc:
   283  		b, err := io.ReadAll(sr.stats.Body)
   284  		defer sr.stats.Body.Close()
   285  		assert.NilError(c, err)
   286  		s := string(b)
   287  		// count occurrences of "read" of types.Stats
   288  		if l := strings.Count(s, "read"); l < 2 {
   289  			c.Fatalf("Expected more than one stat streamed, got %d", l)
   290  		}
   291  	}
   292  }
   293  
   294  func (s *DockerAPISuite) TestGetContainerStatsNoStream(c *testing.T) {
   295  	const name = "statscontainer2"
   296  	runSleepingContainer(c, "--name", name)
   297  
   298  	type b struct {
   299  		stats types.ContainerStats
   300  		err   error
   301  	}
   302  
   303  	bc := make(chan b, 1)
   304  
   305  	go func() {
   306  		apiClient, err := client.NewClientWithOpts(client.FromEnv)
   307  		assert.NilError(c, err)
   308  		defer apiClient.Close()
   309  
   310  		stats, err := apiClient.ContainerStats(testutil.GetContext(c), name, false)
   311  		assert.NilError(c, err)
   312  		bc <- b{stats, err}
   313  	}()
   314  
   315  	// allow some time to stream the stats from the container
   316  	time.Sleep(4 * time.Second)
   317  	cli.DockerCmd(c, "rm", "-f", name)
   318  
   319  	// collect the results from the stats stream or timeout and fail
   320  	// if the stream was not disconnected.
   321  	select {
   322  	case <-time.After(2 * time.Second):
   323  		c.Fatal("stream was not closed after container was removed")
   324  	case sr := <-bc:
   325  		b, err := io.ReadAll(sr.stats.Body)
   326  		defer sr.stats.Body.Close()
   327  		assert.NilError(c, err)
   328  		s := string(b)
   329  		// count occurrences of `"read"` of types.Stats
   330  		assert.Assert(c, strings.Count(s, `"read"`) == 1, "Expected only one stat streamed, got %d", strings.Count(s, `"read"`))
   331  	}
   332  }
   333  
   334  func (s *DockerAPISuite) TestGetStoppedContainerStats(c *testing.T) {
   335  	const name = "statscontainer3"
   336  	cli.DockerCmd(c, "create", "--name", name, "busybox", "ps")
   337  
   338  	chResp := make(chan error, 1)
   339  
   340  	// We expect an immediate response, but if it's not immediate, the test would hang, so put it in a goroutine
   341  	// below we'll check this on a timeout.
   342  	go func() {
   343  		apiClient, err := client.NewClientWithOpts(client.FromEnv)
   344  		assert.NilError(c, err)
   345  		defer apiClient.Close()
   346  
   347  		resp, err := apiClient.ContainerStats(testutil.GetContext(c), name, false)
   348  		assert.NilError(c, err)
   349  		defer resp.Body.Close()
   350  		chResp <- err
   351  	}()
   352  
   353  	select {
   354  	case err := <-chResp:
   355  		assert.NilError(c, err)
   356  	case <-time.After(10 * time.Second):
   357  		c.Fatal("timeout waiting for stats response for stopped container")
   358  	}
   359  }
   360  
   361  func (s *DockerAPISuite) TestContainerAPIPause(c *testing.T) {
   362  	// Problematic on Windows as Windows does not support pause
   363  	testRequires(c, DaemonIsLinux)
   364  
   365  	getPaused := func(c *testing.T) []string {
   366  		return strings.Fields(cli.DockerCmd(c, "ps", "-f", "status=paused", "-q", "-a").Combined())
   367  	}
   368  
   369  	out := cli.DockerCmd(c, "run", "-d", "busybox", "sleep", "30").Combined()
   370  	ContainerID := strings.TrimSpace(out)
   371  
   372  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   373  	assert.NilError(c, err)
   374  	defer apiClient.Close()
   375  
   376  	err = apiClient.ContainerPause(testutil.GetContext(c), ContainerID)
   377  	assert.NilError(c, err)
   378  
   379  	pausedContainers := getPaused(c)
   380  
   381  	if len(pausedContainers) != 1 || stringid.TruncateID(ContainerID) != pausedContainers[0] {
   382  		c.Fatalf("there should be one paused container and not %d", len(pausedContainers))
   383  	}
   384  
   385  	err = apiClient.ContainerUnpause(testutil.GetContext(c), ContainerID)
   386  	assert.NilError(c, err)
   387  
   388  	pausedContainers = getPaused(c)
   389  	assert.Equal(c, len(pausedContainers), 0, "There should be no paused container.")
   390  }
   391  
   392  func (s *DockerAPISuite) TestContainerAPITop(c *testing.T) {
   393  	testRequires(c, DaemonIsLinux)
   394  	out := cli.DockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "top && true").Stdout()
   395  	id := strings.TrimSpace(out)
   396  	cli.WaitRun(c, id)
   397  
   398  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   399  	assert.NilError(c, err)
   400  	defer apiClient.Close()
   401  
   402  	// sort by comm[andline] to make sure order stays the same in case of PID rollover
   403  	top, err := apiClient.ContainerTop(testutil.GetContext(c), id, []string{"aux", "--sort=comm"})
   404  	assert.NilError(c, err)
   405  	assert.Equal(c, len(top.Titles), 11, fmt.Sprintf("expected 11 titles, found %d: %v", len(top.Titles), top.Titles))
   406  
   407  	if top.Titles[0] != "USER" || top.Titles[10] != "COMMAND" {
   408  		c.Fatalf("expected `USER` at `Titles[0]` and `COMMAND` at Titles[10]: %v", top.Titles)
   409  	}
   410  	assert.Equal(c, len(top.Processes), 2, fmt.Sprintf("expected 2 processes, found %d: %v", len(top.Processes), top.Processes))
   411  	assert.Equal(c, top.Processes[0][10], "/bin/sh -c top && true")
   412  	assert.Equal(c, top.Processes[1][10], "top")
   413  }
   414  
   415  func (s *DockerAPISuite) TestContainerAPITopWindows(c *testing.T) {
   416  	testRequires(c, DaemonIsWindows)
   417  	id := runSleepingContainer(c, "-d")
   418  	cli.WaitRun(c, id)
   419  
   420  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   421  	assert.NilError(c, err)
   422  	defer apiClient.Close()
   423  
   424  	top, err := apiClient.ContainerTop(testutil.GetContext(c), id, nil)
   425  	assert.NilError(c, err)
   426  	assert.Equal(c, len(top.Titles), 4, "expected 4 titles, found %d: %v", len(top.Titles), top.Titles)
   427  
   428  	if top.Titles[0] != "Name" || top.Titles[3] != "Private Working Set" {
   429  		c.Fatalf("expected `Name` at `Titles[0]` and `Private Working Set` at Titles[3]: %v", top.Titles)
   430  	}
   431  	assert.Assert(c, len(top.Processes) >= 2, "expected at least 2 processes, found %d: %v", len(top.Processes), top.Processes)
   432  
   433  	foundProcess := false
   434  	expectedProcess := "busybox.exe"
   435  	for _, process := range top.Processes {
   436  		if process[0] == expectedProcess {
   437  			foundProcess = true
   438  			break
   439  		}
   440  	}
   441  
   442  	assert.Assert(c, foundProcess, "expected to find %s: %v", expectedProcess, top.Processes)
   443  }
   444  
   445  func (s *DockerAPISuite) TestContainerAPICommit(c *testing.T) {
   446  	const cName = "testapicommit"
   447  	cli.DockerCmd(c, "run", "--name="+cName, "busybox", "/bin/sh", "-c", "touch /test")
   448  
   449  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   450  	assert.NilError(c, err)
   451  	defer apiClient.Close()
   452  
   453  	options := container.CommitOptions{
   454  		Reference: "testcontainerapicommit:testtag",
   455  	}
   456  
   457  	img, err := apiClient.ContainerCommit(testutil.GetContext(c), cName, options)
   458  	assert.NilError(c, err)
   459  
   460  	cmd := inspectField(c, img.ID, "Config.Cmd")
   461  	assert.Equal(c, cmd, "[/bin/sh -c touch /test]", fmt.Sprintf("got wrong Cmd from commit: %q", cmd))
   462  
   463  	// sanity check, make sure the image is what we think it is
   464  	cli.DockerCmd(c, "run", img.ID, "ls", "/test")
   465  }
   466  
   467  func (s *DockerAPISuite) TestContainerAPICommitWithLabelInConfig(c *testing.T) {
   468  	const cName = "testapicommitwithconfig"
   469  	cli.DockerCmd(c, "run", "--name="+cName, "busybox", "/bin/sh", "-c", "touch /test")
   470  
   471  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   472  	assert.NilError(c, err)
   473  	defer apiClient.Close()
   474  
   475  	config := container.Config{
   476  		Labels: map[string]string{"key1": "value1", "key2": "value2"},
   477  	}
   478  
   479  	options := container.CommitOptions{
   480  		Reference: "testcontainerapicommitwithconfig",
   481  		Config:    &config,
   482  	}
   483  
   484  	img, err := apiClient.ContainerCommit(testutil.GetContext(c), cName, options)
   485  	assert.NilError(c, err)
   486  
   487  	label1 := inspectFieldMap(c, img.ID, "Config.Labels", "key1")
   488  	assert.Equal(c, label1, "value1")
   489  
   490  	label2 := inspectFieldMap(c, img.ID, "Config.Labels", "key2")
   491  	assert.Equal(c, label2, "value2")
   492  
   493  	cmd := inspectField(c, img.ID, "Config.Cmd")
   494  	assert.Equal(c, cmd, "[/bin/sh -c touch /test]", fmt.Sprintf("got wrong Cmd from commit: %q", cmd))
   495  
   496  	// sanity check, make sure the image is what we think it is
   497  	cli.DockerCmd(c, "run", img.ID, "ls", "/test")
   498  }
   499  
   500  func (s *DockerAPISuite) TestContainerAPIBadPort(c *testing.T) {
   501  	// TODO Windows to Windows CI - Port this test
   502  	testRequires(c, DaemonIsLinux)
   503  
   504  	config := container.Config{
   505  		Image: "busybox",
   506  		Cmd:   []string{"/bin/sh", "-c", "echo test"},
   507  	}
   508  
   509  	hostConfig := container.HostConfig{
   510  		PortBindings: nat.PortMap{
   511  			"8080/tcp": []nat.PortBinding{
   512  				{
   513  					HostIP:   "",
   514  					HostPort: "aa80",
   515  				},
   516  			},
   517  		},
   518  	}
   519  
   520  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   521  	assert.NilError(c, err)
   522  	defer apiClient.Close()
   523  
   524  	_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "")
   525  	assert.ErrorContains(c, err, `invalid port specification: "aa80"`)
   526  }
   527  
   528  func (s *DockerAPISuite) TestContainerAPICreate(c *testing.T) {
   529  	config := container.Config{
   530  		Image: "busybox",
   531  		Cmd:   []string{"/bin/sh", "-c", "touch /test && ls /test"},
   532  	}
   533  
   534  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   535  	assert.NilError(c, err)
   536  	defer apiClient.Close()
   537  
   538  	ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "")
   539  	assert.NilError(c, err)
   540  
   541  	out := cli.DockerCmd(c, "start", "-a", ctr.ID).Stdout()
   542  	assert.Equal(c, strings.TrimSpace(out), "/test")
   543  }
   544  
   545  func (s *DockerAPISuite) TestContainerAPICreateEmptyConfig(c *testing.T) {
   546  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   547  	assert.NilError(c, err)
   548  	defer apiClient.Close()
   549  
   550  	_, err = apiClient.ContainerCreate(testutil.GetContext(c), &container.Config{}, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "")
   551  
   552  	assert.ErrorContains(c, err, "no command specified")
   553  }
   554  
   555  func (s *DockerAPISuite) TestContainerAPICreateBridgeNetworkMode(c *testing.T) {
   556  	// Windows does not support bridge
   557  	testRequires(c, DaemonIsLinux)
   558  	UtilCreateNetworkMode(c, "bridge")
   559  }
   560  
   561  func (s *DockerAPISuite) TestContainerAPICreateOtherNetworkModes(c *testing.T) {
   562  	// Windows does not support these network modes
   563  	testRequires(c, DaemonIsLinux, NotUserNamespace)
   564  	UtilCreateNetworkMode(c, "host")
   565  	UtilCreateNetworkMode(c, "container:web1")
   566  }
   567  
   568  func UtilCreateNetworkMode(c *testing.T, networkMode container.NetworkMode) {
   569  	config := container.Config{
   570  		Image: "busybox",
   571  	}
   572  
   573  	hostConfig := container.HostConfig{
   574  		NetworkMode: networkMode,
   575  	}
   576  
   577  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   578  	assert.NilError(c, err)
   579  	defer apiClient.Close()
   580  
   581  	ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "")
   582  	assert.NilError(c, err)
   583  
   584  	containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID)
   585  	assert.NilError(c, err)
   586  
   587  	assert.Equal(c, containerJSON.HostConfig.NetworkMode, networkMode, "Mismatched NetworkMode")
   588  }
   589  
   590  func (s *DockerAPISuite) TestContainerAPICreateWithCpuSharesCpuset(c *testing.T) {
   591  	// TODO Windows to Windows CI. The CpuShares part could be ported.
   592  	testRequires(c, DaemonIsLinux)
   593  	config := container.Config{
   594  		Image: "busybox",
   595  	}
   596  
   597  	hostConfig := container.HostConfig{
   598  		Resources: container.Resources{
   599  			CPUShares:  512,
   600  			CpusetCpus: "0",
   601  		},
   602  	}
   603  
   604  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   605  	assert.NilError(c, err)
   606  	defer apiClient.Close()
   607  
   608  	ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "")
   609  	assert.NilError(c, err)
   610  
   611  	containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID)
   612  	assert.NilError(c, err)
   613  
   614  	out := inspectField(c, containerJSON.ID, "HostConfig.CpuShares")
   615  	assert.Equal(c, out, "512")
   616  
   617  	outCpuset := inspectField(c, containerJSON.ID, "HostConfig.CpusetCpus")
   618  	assert.Equal(c, outCpuset, "0")
   619  }
   620  
   621  func (s *DockerAPISuite) TestContainerAPIVerifyHeader(c *testing.T) {
   622  	config := map[string]interface{}{
   623  		"Image": "busybox",
   624  	}
   625  
   626  	create := func(ct string) (*http.Response, io.ReadCloser, error) {
   627  		jsonData := bytes.NewBuffer(nil)
   628  		assert.Assert(c, json.NewEncoder(jsonData).Encode(config) == nil)
   629  		return request.Post(testutil.GetContext(c), "/containers/create", request.RawContent(io.NopCloser(jsonData)), request.ContentType(ct))
   630  	}
   631  
   632  	// Try with no content-type
   633  	res, body, err := create("")
   634  	assert.NilError(c, err)
   635  	assert.Equal(c, res.StatusCode, http.StatusBadRequest)
   636  	_ = body.Close()
   637  
   638  	// Try with wrong content-type
   639  	res, body, err = create("application/xml")
   640  	assert.NilError(c, err)
   641  	assert.Equal(c, res.StatusCode, http.StatusBadRequest)
   642  	_ = body.Close()
   643  
   644  	// now application/json
   645  	res, body, err = create("application/json")
   646  	assert.NilError(c, err)
   647  	assert.Equal(c, res.StatusCode, http.StatusCreated)
   648  	_ = body.Close()
   649  }
   650  
   651  // Issue 14230. daemon should return 500 for invalid port syntax
   652  func (s *DockerAPISuite) TestContainerAPIInvalidPortSyntax(c *testing.T) {
   653  	config := `{
   654  				  "Image": "busybox",
   655  				  "HostConfig": {
   656  					"NetworkMode": "default",
   657  					"PortBindings": {
   658  					  "19039;1230": [
   659  						{}
   660  					  ]
   661  					}
   662  				  }
   663  				}`
   664  
   665  	res, body, err := request.Post(testutil.GetContext(c), "/containers/create", request.RawString(config), request.JSON)
   666  	assert.NilError(c, err)
   667  	assert.Equal(c, res.StatusCode, http.StatusBadRequest)
   668  
   669  	b, err := request.ReadBody(body)
   670  	assert.NilError(c, err)
   671  	assert.Assert(c, strings.Contains(string(b[:]), "invalid port"))
   672  }
   673  
   674  func (s *DockerAPISuite) TestContainerAPIRestartPolicyInvalidPolicyName(c *testing.T) {
   675  	config := `{
   676  		"Image": "busybox",
   677  		"HostConfig": {
   678  			"RestartPolicy": {
   679  				"Name": "something",
   680  				"MaximumRetryCount": 0
   681  			}
   682  		}
   683  	}`
   684  
   685  	res, body, err := request.Post(testutil.GetContext(c), "/containers/create", request.RawString(config), request.JSON)
   686  	assert.NilError(c, err)
   687  	assert.Equal(c, res.StatusCode, http.StatusBadRequest)
   688  
   689  	b, err := request.ReadBody(body)
   690  	assert.NilError(c, err)
   691  	assert.Assert(c, strings.Contains(string(b[:]), "invalid restart policy"))
   692  }
   693  
   694  func (s *DockerAPISuite) TestContainerAPIRestartPolicyRetryMismatch(c *testing.T) {
   695  	config := `{
   696  		"Image": "busybox",
   697  		"HostConfig": {
   698  			"RestartPolicy": {
   699  				"Name": "always",
   700  				"MaximumRetryCount": 2
   701  			}
   702  		}
   703  	}`
   704  
   705  	res, body, err := request.Post(testutil.GetContext(c), "/containers/create", request.RawString(config), request.JSON)
   706  	assert.NilError(c, err)
   707  	assert.Equal(c, res.StatusCode, http.StatusBadRequest)
   708  
   709  	b, err := request.ReadBody(body)
   710  	assert.NilError(c, err)
   711  	assert.Assert(c, strings.Contains(string(b[:]), "invalid restart policy: maximum retry count can only be used with 'on-failure'"))
   712  }
   713  
   714  func (s *DockerAPISuite) TestContainerAPIRestartPolicyNegativeRetryCount(c *testing.T) {
   715  	config := `{
   716  		"Image": "busybox",
   717  		"HostConfig": {
   718  			"RestartPolicy": {
   719  				"Name": "on-failure",
   720  				"MaximumRetryCount": -2
   721  			}
   722  		}
   723  	}`
   724  
   725  	res, body, err := request.Post(testutil.GetContext(c), "/containers/create", request.RawString(config), request.JSON)
   726  	assert.NilError(c, err)
   727  	assert.Equal(c, res.StatusCode, http.StatusBadRequest)
   728  
   729  	b, err := request.ReadBody(body)
   730  	assert.NilError(c, err)
   731  	assert.Assert(c, strings.Contains(string(b[:]), "maximum retry count cannot be negative"))
   732  }
   733  
   734  func (s *DockerAPISuite) TestContainerAPIRestartPolicyDefaultRetryCount(c *testing.T) {
   735  	config := `{
   736  		"Image": "busybox",
   737  		"HostConfig": {
   738  			"RestartPolicy": {
   739  				"Name": "on-failure",
   740  				"MaximumRetryCount": 0
   741  			}
   742  		}
   743  	}`
   744  
   745  	res, _, err := request.Post(testutil.GetContext(c), "/containers/create", request.RawString(config), request.JSON)
   746  	assert.NilError(c, err)
   747  	assert.Equal(c, res.StatusCode, http.StatusCreated)
   748  }
   749  
   750  // Issue 7941 - test to make sure a "null" in JSON is just ignored.
   751  // W/o this fix a null in JSON would be parsed into a string var as "null"
   752  func (s *DockerAPISuite) TestContainerAPIPostCreateNull(c *testing.T) {
   753  	config := `{
   754  		"Hostname":"",
   755  		"Domainname":"",
   756  		"Memory":0,
   757  		"MemorySwap":0,
   758  		"CpuShares":0,
   759  		"Cpuset":null,
   760  		"AttachStdin":true,
   761  		"AttachStdout":true,
   762  		"AttachStderr":true,
   763  		"ExposedPorts":{},
   764  		"Tty":true,
   765  		"OpenStdin":true,
   766  		"StdinOnce":true,
   767  		"Env":[],
   768  		"Cmd":"ls",
   769  		"Image":"busybox",
   770  		"Volumes":{},
   771  		"WorkingDir":"",
   772  		"Entrypoint":null,
   773  		"NetworkDisabled":false,
   774  		"OnBuild":null}`
   775  
   776  	res, body, err := request.Post(testutil.GetContext(c), "/containers/create", request.RawString(config), request.JSON)
   777  	assert.NilError(c, err)
   778  	assert.Equal(c, res.StatusCode, http.StatusCreated)
   779  
   780  	b, err := request.ReadBody(body)
   781  	assert.NilError(c, err)
   782  	type createResp struct {
   783  		ID string
   784  	}
   785  	var ctr createResp
   786  	assert.Assert(c, json.Unmarshal(b, &ctr) == nil)
   787  	out := inspectField(c, ctr.ID, "HostConfig.CpusetCpus")
   788  	assert.Equal(c, out, "")
   789  
   790  	outMemory := inspectField(c, ctr.ID, "HostConfig.Memory")
   791  	assert.Equal(c, outMemory, "0")
   792  	outMemorySwap := inspectField(c, ctr.ID, "HostConfig.MemorySwap")
   793  	assert.Equal(c, outMemorySwap, "0")
   794  }
   795  
   796  func (s *DockerAPISuite) TestCreateWithTooLowMemoryLimit(c *testing.T) {
   797  	// TODO Windows: Port once memory is supported
   798  	testRequires(c, DaemonIsLinux)
   799  	config := `{
   800  		"Image":     "busybox",
   801  		"Cmd":       "ls",
   802  		"OpenStdin": true,
   803  		"CpuShares": 100,
   804  		"Memory":    524287
   805  	}`
   806  
   807  	res, body, err := request.Post(testutil.GetContext(c), "/containers/create", request.RawString(config), request.JSON)
   808  	assert.NilError(c, err)
   809  	b, err2 := request.ReadBody(body)
   810  	assert.Assert(c, err2 == nil)
   811  
   812  	assert.Equal(c, res.StatusCode, http.StatusBadRequest)
   813  	assert.Assert(c, strings.Contains(string(b), "Minimum memory limit allowed is 6MB"))
   814  }
   815  
   816  func (s *DockerAPISuite) TestContainerAPIRename(c *testing.T) {
   817  	out := cli.DockerCmd(c, "run", "--name", "TestContainerAPIRename", "-d", "busybox", "sh").Stdout()
   818  	containerID := strings.TrimSpace(out)
   819  	const newName = "TestContainerAPIRenameNew"
   820  
   821  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   822  	assert.NilError(c, err)
   823  	defer apiClient.Close()
   824  
   825  	err = apiClient.ContainerRename(testutil.GetContext(c), containerID, newName)
   826  	assert.NilError(c, err)
   827  
   828  	name := inspectField(c, containerID, "Name")
   829  	assert.Equal(c, name, "/"+newName, "Failed to rename container")
   830  }
   831  
   832  func (s *DockerAPISuite) TestContainerAPIKill(c *testing.T) {
   833  	const name = "test-api-kill"
   834  	runSleepingContainer(c, "-i", "--name", name)
   835  
   836  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   837  	assert.NilError(c, err)
   838  	defer apiClient.Close()
   839  
   840  	err = apiClient.ContainerKill(testutil.GetContext(c), name, "SIGKILL")
   841  	assert.NilError(c, err)
   842  
   843  	state := inspectField(c, name, "State.Running")
   844  	assert.Equal(c, state, "false", fmt.Sprintf("got wrong State from container %s: %q", name, state))
   845  }
   846  
   847  func (s *DockerAPISuite) TestContainerAPIRestart(c *testing.T) {
   848  	const name = "test-api-restart"
   849  	runSleepingContainer(c, "-di", "--name", name)
   850  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   851  	assert.NilError(c, err)
   852  	defer apiClient.Close()
   853  
   854  	timeout := 1
   855  	err = apiClient.ContainerRestart(testutil.GetContext(c), name, container.StopOptions{Timeout: &timeout})
   856  	assert.NilError(c, err)
   857  
   858  	assert.Assert(c, waitInspect(name, "{{ .State.Restarting  }} {{ .State.Running  }}", "false true", 15*time.Second) == nil)
   859  }
   860  
   861  func (s *DockerAPISuite) TestContainerAPIRestartNotimeoutParam(c *testing.T) {
   862  	const name = "test-api-restart-no-timeout-param"
   863  	id := runSleepingContainer(c, "-di", "--name", name)
   864  	cli.WaitRun(c, id)
   865  
   866  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   867  	assert.NilError(c, err)
   868  	defer apiClient.Close()
   869  
   870  	err = apiClient.ContainerRestart(testutil.GetContext(c), name, container.StopOptions{})
   871  	assert.NilError(c, err)
   872  
   873  	assert.Assert(c, waitInspect(name, "{{ .State.Restarting  }} {{ .State.Running  }}", "false true", 15*time.Second) == nil)
   874  }
   875  
   876  func (s *DockerAPISuite) TestContainerAPIStart(c *testing.T) {
   877  	const name = "testing-start"
   878  	config := container.Config{
   879  		Image:     "busybox",
   880  		Cmd:       append([]string{"/bin/sh", "-c"}, sleepCommandForDaemonPlatform()...),
   881  		OpenStdin: true,
   882  	}
   883  
   884  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   885  	assert.NilError(c, err)
   886  	defer apiClient.Close()
   887  
   888  	_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, name)
   889  	assert.NilError(c, err)
   890  
   891  	err = apiClient.ContainerStart(testutil.GetContext(c), name, container.StartOptions{})
   892  	assert.NilError(c, err)
   893  
   894  	// second call to start should give 304
   895  	// maybe add ContainerStartWithRaw to test it
   896  	err = apiClient.ContainerStart(testutil.GetContext(c), name, container.StartOptions{})
   897  	assert.NilError(c, err)
   898  
   899  	// TODO(tibor): figure out why this doesn't work on windows
   900  }
   901  
   902  func (s *DockerAPISuite) TestContainerAPIStop(c *testing.T) {
   903  	const name = "test-api-stop"
   904  	runSleepingContainer(c, "-i", "--name", name)
   905  	timeout := 30
   906  
   907  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   908  	assert.NilError(c, err)
   909  	defer apiClient.Close()
   910  
   911  	err = apiClient.ContainerStop(testutil.GetContext(c), name, container.StopOptions{
   912  		Timeout: &timeout,
   913  	})
   914  	assert.NilError(c, err)
   915  	assert.Assert(c, waitInspect(name, "{{ .State.Running  }}", "false", 60*time.Second) == nil)
   916  
   917  	// second call to start should give 304
   918  	// maybe add ContainerStartWithRaw to test it
   919  	err = apiClient.ContainerStop(testutil.GetContext(c), name, container.StopOptions{
   920  		Timeout: &timeout,
   921  	})
   922  	assert.NilError(c, err)
   923  }
   924  
   925  func (s *DockerAPISuite) TestContainerAPIWait(c *testing.T) {
   926  	const name = "test-api-wait"
   927  
   928  	sleepCmd := "/bin/sleep"
   929  	if testEnv.DaemonInfo.OSType == "windows" {
   930  		sleepCmd = "sleep"
   931  	}
   932  	cli.DockerCmd(c, "run", "--name", name, "busybox", sleepCmd, "2")
   933  
   934  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   935  	assert.NilError(c, err)
   936  	defer apiClient.Close()
   937  
   938  	waitResC, errC := apiClient.ContainerWait(testutil.GetContext(c), name, "")
   939  
   940  	select {
   941  	case err = <-errC:
   942  		assert.NilError(c, err)
   943  	case waitRes := <-waitResC:
   944  		assert.Equal(c, waitRes.StatusCode, int64(0))
   945  	}
   946  }
   947  
   948  func (s *DockerAPISuite) TestContainerAPICopyNotExistsAnyMore(c *testing.T) {
   949  	const name = "test-container-api-copy"
   950  	cli.DockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt")
   951  
   952  	postData := types.CopyConfig{
   953  		Resource: "/test.txt",
   954  	}
   955  	// no copy in client/
   956  	res, _, err := request.Post(testutil.GetContext(c), "/containers/"+name+"/copy", request.JSONBody(postData))
   957  	assert.NilError(c, err)
   958  	assert.Equal(c, res.StatusCode, http.StatusNotFound)
   959  }
   960  
   961  func (s *DockerAPISuite) TestContainerAPIDelete(c *testing.T) {
   962  	id := runSleepingContainer(c)
   963  	cli.WaitRun(c, id)
   964  	cli.DockerCmd(c, "stop", id)
   965  
   966  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   967  	assert.NilError(c, err)
   968  	defer apiClient.Close()
   969  
   970  	err = apiClient.ContainerRemove(testutil.GetContext(c), id, container.RemoveOptions{})
   971  	assert.NilError(c, err)
   972  }
   973  
   974  func (s *DockerAPISuite) TestContainerAPIDeleteNotExist(c *testing.T) {
   975  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   976  	assert.NilError(c, err)
   977  	defer apiClient.Close()
   978  
   979  	err = apiClient.ContainerRemove(testutil.GetContext(c), "doesnotexist", container.RemoveOptions{})
   980  	assert.ErrorContains(c, err, "No such container: doesnotexist")
   981  }
   982  
   983  func (s *DockerAPISuite) TestContainerAPIDeleteForce(c *testing.T) {
   984  	id := runSleepingContainer(c)
   985  	cli.WaitRun(c, id)
   986  
   987  	removeOptions := container.RemoveOptions{
   988  		Force: true,
   989  	}
   990  
   991  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   992  	assert.NilError(c, err)
   993  	defer apiClient.Close()
   994  
   995  	err = apiClient.ContainerRemove(testutil.GetContext(c), id, removeOptions)
   996  	assert.NilError(c, err)
   997  }
   998  
   999  func (s *DockerAPISuite) TestContainerAPIDeleteRemoveLinks(c *testing.T) {
  1000  	// Windows does not support links
  1001  	testRequires(c, DaemonIsLinux)
  1002  	out := cli.DockerCmd(c, "run", "-d", "--name", "tlink1", "busybox", "top").Stdout()
  1003  	id := strings.TrimSpace(out)
  1004  	cli.WaitRun(c, id)
  1005  
  1006  	out = cli.DockerCmd(c, "run", "--link", "tlink1:tlink1", "--name", "tlink2", "-d", "busybox", "top").Stdout()
  1007  	id2 := strings.TrimSpace(out)
  1008  	cli.WaitRun(c, id2)
  1009  
  1010  	links := inspectFieldJSON(c, id2, "HostConfig.Links")
  1011  	assert.Equal(c, links, `["/tlink1:/tlink2/tlink1"]`, "expected to have links between containers")
  1012  
  1013  	removeOptions := container.RemoveOptions{
  1014  		RemoveLinks: true,
  1015  	}
  1016  
  1017  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1018  	assert.NilError(c, err)
  1019  	defer apiClient.Close()
  1020  
  1021  	err = apiClient.ContainerRemove(testutil.GetContext(c), "tlink2/tlink1", removeOptions)
  1022  	assert.NilError(c, err)
  1023  
  1024  	linksPostRm := inspectFieldJSON(c, id2, "HostConfig.Links")
  1025  	assert.Equal(c, linksPostRm, "null", "call to api deleteContainer links should have removed the specified links")
  1026  }
  1027  
  1028  func (s *DockerAPISuite) TestContainerAPIDeleteRemoveVolume(c *testing.T) {
  1029  	testRequires(c, testEnv.IsLocalDaemon)
  1030  
  1031  	vol := "/testvolume"
  1032  	if testEnv.DaemonInfo.OSType == "windows" {
  1033  		vol = `c:\testvolume`
  1034  	}
  1035  
  1036  	id := runSleepingContainer(c, "-v", vol)
  1037  	cli.WaitRun(c, id)
  1038  
  1039  	source, err := inspectMountSourceField(id, vol)
  1040  	assert.NilError(c, err)
  1041  	_, err = os.Stat(source)
  1042  	assert.NilError(c, err)
  1043  
  1044  	removeOptions := container.RemoveOptions{
  1045  		Force:         true,
  1046  		RemoveVolumes: true,
  1047  	}
  1048  
  1049  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1050  	assert.NilError(c, err)
  1051  	defer apiClient.Close()
  1052  
  1053  	err = apiClient.ContainerRemove(testutil.GetContext(c), id, removeOptions)
  1054  	assert.NilError(c, err)
  1055  
  1056  	_, err = os.Stat(source)
  1057  	assert.Assert(c, os.IsNotExist(err), "expected to get ErrNotExist error, got %v", err)
  1058  }
  1059  
  1060  // Regression test for https://github.com/docker/docker/issues/6231
  1061  func (s *DockerAPISuite) TestContainerAPIChunkedEncoding(c *testing.T) {
  1062  	config := map[string]interface{}{
  1063  		"Image":     "busybox",
  1064  		"Cmd":       append([]string{"/bin/sh", "-c"}, sleepCommandForDaemonPlatform()...),
  1065  		"OpenStdin": true,
  1066  	}
  1067  
  1068  	resp, _, err := request.Post(testutil.GetContext(c), "/containers/create", request.JSONBody(config), request.With(func(req *http.Request) error {
  1069  		// This is a cheat to make the http request do chunked encoding
  1070  		// Otherwise (just setting the Content-Encoding to chunked) net/http will overwrite
  1071  		// https://golang.org/src/pkg/net/http/request.go?s=11980:12172
  1072  		req.ContentLength = -1
  1073  		return nil
  1074  	}))
  1075  	assert.Assert(c, err == nil, "error creating container with chunked encoding")
  1076  	defer resp.Body.Close()
  1077  	assert.Equal(c, resp.StatusCode, http.StatusCreated)
  1078  }
  1079  
  1080  func (s *DockerAPISuite) TestContainerAPIPostContainerStop(c *testing.T) {
  1081  	containerID := runSleepingContainer(c)
  1082  	cli.WaitRun(c, containerID)
  1083  
  1084  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1085  	assert.NilError(c, err)
  1086  	defer apiClient.Close()
  1087  
  1088  	err = apiClient.ContainerStop(testutil.GetContext(c), containerID, container.StopOptions{})
  1089  	assert.NilError(c, err)
  1090  	assert.Assert(c, waitInspect(containerID, "{{ .State.Running  }}", "false", 60*time.Second) == nil)
  1091  }
  1092  
  1093  // #14170
  1094  func (s *DockerAPISuite) TestPostContainerAPICreateWithStringOrSliceEntrypoint(c *testing.T) {
  1095  	config := container.Config{
  1096  		Image:      "busybox",
  1097  		Entrypoint: []string{"echo"},
  1098  		Cmd:        []string{"hello", "world"},
  1099  	}
  1100  
  1101  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1102  	assert.NilError(c, err)
  1103  	defer apiClient.Close()
  1104  
  1105  	_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "echotest")
  1106  	assert.NilError(c, err)
  1107  	out := cli.DockerCmd(c, "start", "-a", "echotest").Combined()
  1108  	assert.Equal(c, strings.TrimSpace(out), "hello world")
  1109  
  1110  	config2 := struct {
  1111  		Image      string
  1112  		Entrypoint string
  1113  		Cmd        []string
  1114  	}{"busybox", "echo", []string{"hello", "world"}}
  1115  	_, _, err = request.Post(testutil.GetContext(c), "/containers/create?name=echotest2", request.JSONBody(config2))
  1116  	assert.NilError(c, err)
  1117  	out = cli.DockerCmd(c, "start", "-a", "echotest2").Combined()
  1118  	assert.Equal(c, strings.TrimSpace(out), "hello world")
  1119  }
  1120  
  1121  // #14170
  1122  func (s *DockerAPISuite) TestPostContainersCreateWithStringOrSliceCmd(c *testing.T) {
  1123  	config := container.Config{
  1124  		Image: "busybox",
  1125  		Cmd:   []string{"echo", "hello", "world"},
  1126  	}
  1127  
  1128  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1129  	assert.NilError(c, err)
  1130  	defer apiClient.Close()
  1131  
  1132  	_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "echotest")
  1133  	assert.NilError(c, err)
  1134  	out := cli.DockerCmd(c, "start", "-a", "echotest").Combined()
  1135  	assert.Equal(c, strings.TrimSpace(out), "hello world")
  1136  
  1137  	config2 := struct {
  1138  		Image      string
  1139  		Entrypoint string
  1140  		Cmd        string
  1141  	}{"busybox", "echo", "hello world"}
  1142  	_, _, err = request.Post(testutil.GetContext(c), "/containers/create?name=echotest2", request.JSONBody(config2))
  1143  	assert.NilError(c, err)
  1144  	out = cli.DockerCmd(c, "start", "-a", "echotest2").Combined()
  1145  	assert.Equal(c, strings.TrimSpace(out), "hello world")
  1146  }
  1147  
  1148  // regression #14318
  1149  // for backward compatibility testing with and without CAP_ prefix
  1150  // and with upper and lowercase
  1151  func (s *DockerAPISuite) TestPostContainersCreateWithStringOrSliceCapAddDrop(c *testing.T) {
  1152  	// Windows doesn't support CapAdd/CapDrop
  1153  	testRequires(c, DaemonIsLinux)
  1154  	config := struct {
  1155  		Image   string
  1156  		CapAdd  string
  1157  		CapDrop string
  1158  	}{"busybox", "NET_ADMIN", "cap_sys_admin"}
  1159  	res, _, err := request.Post(testutil.GetContext(c), "/containers/create?name=capaddtest0", request.JSONBody(config))
  1160  	assert.NilError(c, err)
  1161  	assert.Equal(c, res.StatusCode, http.StatusCreated)
  1162  
  1163  	config2 := container.Config{
  1164  		Image: "busybox",
  1165  	}
  1166  	hostConfig := container.HostConfig{
  1167  		CapAdd:  []string{"net_admin", "SYS_ADMIN"},
  1168  		CapDrop: []string{"SETGID", "CAP_SETPCAP"},
  1169  	}
  1170  
  1171  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1172  	assert.NilError(c, err)
  1173  	defer apiClient.Close()
  1174  
  1175  	_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config2, &hostConfig, &network.NetworkingConfig{}, nil, "capaddtest1")
  1176  	assert.NilError(c, err)
  1177  }
  1178  
  1179  // Ensure an error occurs when you have a container read-only rootfs but you
  1180  // extract an archive to a symlink in a writable volume which points to a
  1181  // directory outside of the volume.
  1182  func (s *DockerAPISuite) TestPutContainerArchiveErrSymlinkInVolumeToReadOnlyRootfs(c *testing.T) {
  1183  	// Windows does not support read-only rootfs
  1184  	// Requires local volume mount bind.
  1185  	// --read-only + userns has remount issues
  1186  	testRequires(c, testEnv.IsLocalDaemon, NotUserNamespace, DaemonIsLinux)
  1187  
  1188  	testVol := getTestDir(c, "test-put-container-archive-err-symlink-in-volume-to-read-only-rootfs-")
  1189  	defer os.RemoveAll(testVol)
  1190  
  1191  	makeTestContentInDir(c, testVol)
  1192  
  1193  	cID := makeTestContainer(c, testContainerOptions{
  1194  		readOnly: true,
  1195  		volumes:  defaultVolumes(testVol), // Our bind mount is at /vol2
  1196  	})
  1197  
  1198  	// Attempt to extract to a symlink in the volume which points to a
  1199  	// directory outside the volume. This should cause an error because the
  1200  	// rootfs is read-only.
  1201  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1202  	assert.NilError(c, err)
  1203  
  1204  	err = apiClient.CopyToContainer(testutil.GetContext(c), cID, "/vol2/symlinkToAbsDir", nil, types.CopyToContainerOptions{})
  1205  	assert.ErrorContains(c, err, "container rootfs is marked read-only")
  1206  }
  1207  
  1208  func (s *DockerAPISuite) TestPostContainersCreateWithWrongCpusetValues(c *testing.T) {
  1209  	// Not supported on Windows
  1210  	testRequires(c, DaemonIsLinux)
  1211  
  1212  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1213  	assert.NilError(c, err)
  1214  	defer apiClient.Close()
  1215  
  1216  	config := container.Config{
  1217  		Image: "busybox",
  1218  	}
  1219  	hostConfig1 := container.HostConfig{
  1220  		Resources: container.Resources{
  1221  			CpusetCpus: "1-42,,",
  1222  		},
  1223  	}
  1224  	const name = "wrong-cpuset-cpus"
  1225  
  1226  	_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig1, &network.NetworkingConfig{}, nil, name)
  1227  	expected := "Invalid value 1-42,, for cpuset cpus"
  1228  	assert.ErrorContains(c, err, expected)
  1229  
  1230  	hostConfig2 := container.HostConfig{
  1231  		Resources: container.Resources{
  1232  			CpusetMems: "42-3,1--",
  1233  		},
  1234  	}
  1235  	const name2 = "wrong-cpuset-mems"
  1236  	_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig2, &network.NetworkingConfig{}, nil, name2)
  1237  	expected = "Invalid value 42-3,1-- for cpuset mems"
  1238  	assert.ErrorContains(c, err, expected)
  1239  }
  1240  
  1241  func (s *DockerAPISuite) TestPostContainersCreateShmSizeNegative(c *testing.T) {
  1242  	// ShmSize is not supported on Windows
  1243  	testRequires(c, DaemonIsLinux)
  1244  	config := container.Config{
  1245  		Image: "busybox",
  1246  	}
  1247  	hostConfig := container.HostConfig{
  1248  		ShmSize: -1,
  1249  	}
  1250  
  1251  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1252  	assert.NilError(c, err)
  1253  	defer apiClient.Close()
  1254  
  1255  	_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "")
  1256  	assert.ErrorContains(c, err, "SHM size can not be less than 0")
  1257  }
  1258  
  1259  func (s *DockerAPISuite) TestPostContainersCreateShmSizeHostConfigOmitted(c *testing.T) {
  1260  	// ShmSize is not supported on Windows
  1261  	testRequires(c, DaemonIsLinux)
  1262  
  1263  	config := container.Config{
  1264  		Image: "busybox",
  1265  		Cmd:   []string{"mount"},
  1266  	}
  1267  
  1268  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1269  	assert.NilError(c, err)
  1270  	defer apiClient.Close()
  1271  
  1272  	ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "")
  1273  	assert.NilError(c, err)
  1274  
  1275  	containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID)
  1276  	assert.NilError(c, err)
  1277  
  1278  	assert.Equal(c, containerJSON.HostConfig.ShmSize, dconfig.DefaultShmSize)
  1279  
  1280  	out := cli.DockerCmd(c, "start", "-i", containerJSON.ID).Combined()
  1281  	shmRegexp := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=65536k`)
  1282  	if !shmRegexp.MatchString(out) {
  1283  		c.Fatalf("Expected shm of 64MB in mount command, got %v", out)
  1284  	}
  1285  }
  1286  
  1287  func (s *DockerAPISuite) TestPostContainersCreateShmSizeOmitted(c *testing.T) {
  1288  	// ShmSize is not supported on Windows
  1289  	testRequires(c, DaemonIsLinux)
  1290  	config := container.Config{
  1291  		Image: "busybox",
  1292  		Cmd:   []string{"mount"},
  1293  	}
  1294  
  1295  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1296  	assert.NilError(c, err)
  1297  	defer apiClient.Close()
  1298  
  1299  	ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "")
  1300  	assert.NilError(c, err)
  1301  
  1302  	containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID)
  1303  	assert.NilError(c, err)
  1304  
  1305  	assert.Equal(c, containerJSON.HostConfig.ShmSize, int64(67108864))
  1306  
  1307  	out := cli.DockerCmd(c, "start", "-i", containerJSON.ID).Combined()
  1308  	shmRegexp := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=65536k`)
  1309  	if !shmRegexp.MatchString(out) {
  1310  		c.Fatalf("Expected shm of 64MB in mount command, got %v", out)
  1311  	}
  1312  }
  1313  
  1314  func (s *DockerAPISuite) TestPostContainersCreateWithShmSize(c *testing.T) {
  1315  	// ShmSize is not supported on Windows
  1316  	testRequires(c, DaemonIsLinux)
  1317  	config := container.Config{
  1318  		Image: "busybox",
  1319  		Cmd:   []string{"mount"},
  1320  	}
  1321  
  1322  	hostConfig := container.HostConfig{
  1323  		ShmSize: 1073741824,
  1324  	}
  1325  
  1326  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1327  	assert.NilError(c, err)
  1328  	defer apiClient.Close()
  1329  
  1330  	ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "")
  1331  	assert.NilError(c, err)
  1332  
  1333  	containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID)
  1334  	assert.NilError(c, err)
  1335  
  1336  	assert.Equal(c, containerJSON.HostConfig.ShmSize, int64(1073741824))
  1337  
  1338  	out := cli.DockerCmd(c, "start", "-i", containerJSON.ID).Combined()
  1339  	shmRegex := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=1048576k`)
  1340  	if !shmRegex.MatchString(out) {
  1341  		c.Fatalf("Expected shm of 1GB in mount command, got %v", out)
  1342  	}
  1343  }
  1344  
  1345  func (s *DockerAPISuite) TestPostContainersCreateMemorySwappinessHostConfigOmitted(c *testing.T) {
  1346  	// Swappiness is not supported on Windows
  1347  	testRequires(c, DaemonIsLinux)
  1348  	config := container.Config{
  1349  		Image: "busybox",
  1350  	}
  1351  
  1352  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1353  	assert.NilError(c, err)
  1354  	defer apiClient.Close()
  1355  
  1356  	ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "")
  1357  	assert.NilError(c, err)
  1358  
  1359  	containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID)
  1360  	assert.NilError(c, err)
  1361  
  1362  	assert.Assert(c, containerJSON.HostConfig.MemorySwappiness == nil)
  1363  }
  1364  
  1365  // check validation is done daemon side and not only in cli
  1366  func (s *DockerAPISuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *testing.T) {
  1367  	// OomScoreAdj is not supported on Windows
  1368  	testRequires(c, DaemonIsLinux)
  1369  
  1370  	config := container.Config{
  1371  		Image: "busybox",
  1372  	}
  1373  
  1374  	hostConfig := container.HostConfig{
  1375  		OomScoreAdj: 1001,
  1376  	}
  1377  
  1378  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1379  	assert.NilError(c, err)
  1380  	defer apiClient.Close()
  1381  
  1382  	const name = "oomscoreadj-over"
  1383  	_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, name)
  1384  
  1385  	expected := "Invalid value 1001, range for oom score adj is [-1000, 1000]"
  1386  	assert.ErrorContains(c, err, expected)
  1387  
  1388  	hostConfig = container.HostConfig{
  1389  		OomScoreAdj: -1001,
  1390  	}
  1391  
  1392  	const name2 = "oomscoreadj-low"
  1393  	_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, name2)
  1394  
  1395  	expected = "Invalid value -1001, range for oom score adj is [-1000, 1000]"
  1396  	assert.ErrorContains(c, err, expected)
  1397  }
  1398  
  1399  // test case for #22210 where an empty container name caused panic.
  1400  func (s *DockerAPISuite) TestContainerAPIDeleteWithEmptyName(c *testing.T) {
  1401  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1402  	assert.NilError(c, err)
  1403  	defer apiClient.Close()
  1404  
  1405  	err = apiClient.ContainerRemove(testutil.GetContext(c), "", container.RemoveOptions{})
  1406  	assert.Check(c, errdefs.IsNotFound(err))
  1407  }
  1408  
  1409  func (s *DockerAPISuite) TestContainerAPIStatsWithNetworkDisabled(c *testing.T) {
  1410  	// Problematic on Windows as Windows does not support stats
  1411  	testRequires(c, DaemonIsLinux)
  1412  
  1413  	const name = "testing-network-disabled"
  1414  
  1415  	config := container.Config{
  1416  		Image:           "busybox",
  1417  		Cmd:             []string{"top"},
  1418  		NetworkDisabled: true,
  1419  	}
  1420  
  1421  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1422  	assert.NilError(c, err)
  1423  	defer apiClient.Close()
  1424  
  1425  	_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, name)
  1426  	assert.NilError(c, err)
  1427  
  1428  	err = apiClient.ContainerStart(testutil.GetContext(c), name, container.StartOptions{})
  1429  	assert.NilError(c, err)
  1430  	cli.WaitRun(c, name)
  1431  
  1432  	type b struct {
  1433  		stats types.ContainerStats
  1434  		err   error
  1435  	}
  1436  	bc := make(chan b, 1)
  1437  	go func() {
  1438  		stats, err := apiClient.ContainerStats(testutil.GetContext(c), name, false)
  1439  		bc <- b{stats, err}
  1440  	}()
  1441  
  1442  	// allow some time to stream the stats from the container
  1443  	time.Sleep(4 * time.Second)
  1444  	cli.DockerCmd(c, "rm", "-f", name)
  1445  
  1446  	// collect the results from the stats stream or timeout and fail
  1447  	// if the stream was not disconnected.
  1448  	select {
  1449  	case <-time.After(2 * time.Second):
  1450  		c.Fatal("stream was not closed after container was removed")
  1451  	case sr := <-bc:
  1452  		assert.Assert(c, sr.err == nil)
  1453  		sr.stats.Body.Close()
  1454  	}
  1455  }
  1456  
  1457  func (s *DockerAPISuite) TestContainersAPICreateMountsValidation(c *testing.T) {
  1458  	type testCase struct {
  1459  		config     container.Config
  1460  		hostConfig container.HostConfig
  1461  		msg        string
  1462  	}
  1463  
  1464  	prefix, slash := getPrefixAndSlashFromDaemonPlatform()
  1465  	destPath := prefix + slash + "foo"
  1466  	notExistPath := prefix + slash + "notexist"
  1467  
  1468  	cases := []testCase{
  1469  		{
  1470  			config: container.Config{
  1471  				Image: "busybox",
  1472  			},
  1473  			hostConfig: container.HostConfig{
  1474  				Mounts: []mount.Mount{{
  1475  					Type:   "notreal",
  1476  					Target: destPath,
  1477  				}},
  1478  			},
  1479  
  1480  			msg: "mount type unknown",
  1481  		},
  1482  		{
  1483  			config: container.Config{
  1484  				Image: "busybox",
  1485  			},
  1486  			hostConfig: container.HostConfig{
  1487  				Mounts: []mount.Mount{{
  1488  					Type: "bind",
  1489  				}},
  1490  			},
  1491  			msg: "Target must not be empty",
  1492  		},
  1493  		{
  1494  			config: container.Config{
  1495  				Image: "busybox",
  1496  			},
  1497  			hostConfig: container.HostConfig{
  1498  				Mounts: []mount.Mount{{
  1499  					Type:   "bind",
  1500  					Target: destPath,
  1501  				}},
  1502  			},
  1503  			msg: "Source must not be empty",
  1504  		},
  1505  		{
  1506  			config: container.Config{
  1507  				Image: "busybox",
  1508  			},
  1509  			hostConfig: container.HostConfig{
  1510  				Mounts: []mount.Mount{{
  1511  					Type:   "bind",
  1512  					Source: notExistPath,
  1513  					Target: destPath,
  1514  				}},
  1515  			},
  1516  			msg: "source path does not exist",
  1517  			// FIXME(vdemeester) fails into e2e, migrate to integration/container anyway
  1518  			// msg: "source path does not exist: " + notExistPath,
  1519  		},
  1520  		{
  1521  			config: container.Config{
  1522  				Image: "busybox",
  1523  			},
  1524  			hostConfig: container.HostConfig{
  1525  				Mounts: []mount.Mount{{
  1526  					Type: "volume",
  1527  				}},
  1528  			},
  1529  			msg: "Target must not be empty",
  1530  		},
  1531  		{
  1532  			config: container.Config{
  1533  				Image: "busybox",
  1534  			},
  1535  			hostConfig: container.HostConfig{
  1536  				Mounts: []mount.Mount{{
  1537  					Type:   "volume",
  1538  					Source: "hello",
  1539  					Target: destPath,
  1540  				}},
  1541  			},
  1542  			msg: "",
  1543  		},
  1544  		{
  1545  			config: container.Config{
  1546  				Image: "busybox",
  1547  			},
  1548  			hostConfig: container.HostConfig{
  1549  				Mounts: []mount.Mount{{
  1550  					Type:   "volume",
  1551  					Source: "hello2",
  1552  					Target: destPath,
  1553  					VolumeOptions: &mount.VolumeOptions{
  1554  						DriverConfig: &mount.Driver{
  1555  							Name: "local",
  1556  						},
  1557  					},
  1558  				}},
  1559  			},
  1560  			msg: "",
  1561  		},
  1562  	}
  1563  
  1564  	if testEnv.IsLocalDaemon() {
  1565  		tmpDir, err := os.MkdirTemp("", "test-mounts-api")
  1566  		assert.NilError(c, err)
  1567  		defer os.RemoveAll(tmpDir)
  1568  		cases = append(cases, []testCase{
  1569  			{
  1570  				config: container.Config{
  1571  					Image: "busybox",
  1572  				},
  1573  				hostConfig: container.HostConfig{
  1574  					Mounts: []mount.Mount{{
  1575  						Type:   "bind",
  1576  						Source: tmpDir,
  1577  						Target: destPath,
  1578  					}},
  1579  				},
  1580  				msg: "",
  1581  			},
  1582  			{
  1583  				config: container.Config{
  1584  					Image: "busybox",
  1585  				},
  1586  				hostConfig: container.HostConfig{
  1587  					Mounts: []mount.Mount{{
  1588  						Type:          "bind",
  1589  						Source:        tmpDir,
  1590  						Target:        destPath,
  1591  						VolumeOptions: &mount.VolumeOptions{},
  1592  					}},
  1593  				},
  1594  				msg: "VolumeOptions must not be specified",
  1595  			},
  1596  		}...)
  1597  	}
  1598  
  1599  	if DaemonIsWindows() {
  1600  		cases = append(cases, []testCase{
  1601  			{
  1602  				config: container.Config{
  1603  					Image: "busybox",
  1604  				},
  1605  				hostConfig: container.HostConfig{
  1606  					Mounts: []mount.Mount{
  1607  						{
  1608  							Type:   "volume",
  1609  							Source: "not-supported-on-windows",
  1610  							Target: destPath,
  1611  							VolumeOptions: &mount.VolumeOptions{
  1612  								DriverConfig: &mount.Driver{
  1613  									Name:    "local",
  1614  									Options: map[string]string{"type": "tmpfs"},
  1615  								},
  1616  							},
  1617  						},
  1618  					},
  1619  				},
  1620  				msg: `options are not supported on this platform`,
  1621  			},
  1622  		}...)
  1623  	}
  1624  
  1625  	if DaemonIsLinux() {
  1626  		cases = append(cases, []testCase{
  1627  			{
  1628  				config: container.Config{
  1629  					Image: "busybox",
  1630  				},
  1631  				hostConfig: container.HostConfig{
  1632  					Mounts: []mount.Mount{
  1633  						{
  1634  							Type:   "volume",
  1635  							Source: "missing-device-opt",
  1636  							Target: destPath,
  1637  							VolumeOptions: &mount.VolumeOptions{
  1638  								DriverConfig: &mount.Driver{
  1639  									Name:    "local",
  1640  									Options: map[string]string{"foobar": "foobaz"},
  1641  								},
  1642  							},
  1643  						},
  1644  					},
  1645  				},
  1646  				msg: `invalid option: "foobar"`,
  1647  			},
  1648  			{
  1649  				config: container.Config{
  1650  					Image: "busybox",
  1651  				},
  1652  				hostConfig: container.HostConfig{
  1653  					Mounts: []mount.Mount{
  1654  						{
  1655  							Type:   "volume",
  1656  							Source: "missing-device-opt",
  1657  							Target: destPath,
  1658  							VolumeOptions: &mount.VolumeOptions{
  1659  								DriverConfig: &mount.Driver{
  1660  									Name:    "local",
  1661  									Options: map[string]string{"type": "tmpfs"},
  1662  								},
  1663  							},
  1664  						},
  1665  					},
  1666  				},
  1667  				msg: `missing required option: "device"`,
  1668  			},
  1669  			{
  1670  				config: container.Config{
  1671  					Image: "busybox",
  1672  				},
  1673  				hostConfig: container.HostConfig{
  1674  					Mounts: []mount.Mount{
  1675  						{
  1676  							Type:   "volume",
  1677  							Source: "missing-type-opt",
  1678  							Target: destPath,
  1679  							VolumeOptions: &mount.VolumeOptions{
  1680  								DriverConfig: &mount.Driver{
  1681  									Name:    "local",
  1682  									Options: map[string]string{"device": "tmpfs"},
  1683  								},
  1684  							},
  1685  						},
  1686  					},
  1687  				},
  1688  				msg: `missing required option: "type"`,
  1689  			},
  1690  			{
  1691  				config: container.Config{
  1692  					Image: "busybox",
  1693  				},
  1694  				hostConfig: container.HostConfig{
  1695  					Mounts: []mount.Mount{
  1696  						{
  1697  							Type:   "volume",
  1698  							Source: "hello4",
  1699  							Target: destPath,
  1700  							VolumeOptions: &mount.VolumeOptions{
  1701  								DriverConfig: &mount.Driver{
  1702  									Name:    "local",
  1703  									Options: map[string]string{"o": "size=1", "type": "tmpfs", "device": "tmpfs"},
  1704  								},
  1705  							},
  1706  						},
  1707  					},
  1708  				},
  1709  				msg: "",
  1710  			},
  1711  			{
  1712  				config: container.Config{
  1713  					Image: "busybox",
  1714  				},
  1715  				hostConfig: container.HostConfig{
  1716  					Mounts: []mount.Mount{{
  1717  						Type:   "tmpfs",
  1718  						Target: destPath,
  1719  					}},
  1720  				},
  1721  				msg: "",
  1722  			},
  1723  			{
  1724  				config: container.Config{
  1725  					Image: "busybox",
  1726  				},
  1727  				hostConfig: container.HostConfig{
  1728  					Mounts: []mount.Mount{{
  1729  						Type:   "tmpfs",
  1730  						Target: destPath,
  1731  						TmpfsOptions: &mount.TmpfsOptions{
  1732  							SizeBytes: 4096 * 1024,
  1733  							Mode:      0o700,
  1734  						},
  1735  					}},
  1736  				},
  1737  				msg: "",
  1738  			},
  1739  			{
  1740  				config: container.Config{
  1741  					Image: "busybox",
  1742  				},
  1743  				hostConfig: container.HostConfig{
  1744  					Mounts: []mount.Mount{{
  1745  						Type:   "tmpfs",
  1746  						Source: "/shouldnotbespecified",
  1747  						Target: destPath,
  1748  					}},
  1749  				},
  1750  				msg: "Source must not be specified",
  1751  			},
  1752  		}...)
  1753  	}
  1754  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1755  	assert.NilError(c, err)
  1756  	defer apiClient.Close()
  1757  
  1758  	// TODO add checks for statuscode returned by API
  1759  	for i, x := range cases {
  1760  		x := x
  1761  		c.Run(fmt.Sprintf("case %d", i), func(c *testing.T) {
  1762  			_, err = apiClient.ContainerCreate(testutil.GetContext(c), &x.config, &x.hostConfig, &network.NetworkingConfig{}, nil, "")
  1763  			if len(x.msg) > 0 {
  1764  				assert.ErrorContains(c, err, x.msg, "%v", cases[i].config)
  1765  			} else {
  1766  				assert.NilError(c, err)
  1767  			}
  1768  		})
  1769  	}
  1770  }
  1771  
  1772  func (s *DockerAPISuite) TestContainerAPICreateMountsBindRead(c *testing.T) {
  1773  	testRequires(c, NotUserNamespace, testEnv.IsLocalDaemon)
  1774  	// also with data in the host side
  1775  	prefix, slash := getPrefixAndSlashFromDaemonPlatform()
  1776  	destPath := prefix + slash + "foo"
  1777  	tmpDir, err := os.MkdirTemp("", "test-mounts-api-bind")
  1778  	assert.NilError(c, err)
  1779  	defer os.RemoveAll(tmpDir)
  1780  	err = os.WriteFile(filepath.Join(tmpDir, "bar"), []byte("hello"), 0o666)
  1781  	assert.NilError(c, err)
  1782  	config := container.Config{
  1783  		Image: "busybox",
  1784  		Cmd:   []string{"/bin/sh", "-c", "cat /foo/bar"},
  1785  	}
  1786  	hostConfig := container.HostConfig{
  1787  		Mounts: []mount.Mount{
  1788  			{Type: "bind", Source: tmpDir, Target: destPath},
  1789  		},
  1790  	}
  1791  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1792  	assert.NilError(c, err)
  1793  	defer apiClient.Close()
  1794  
  1795  	_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "test")
  1796  	assert.NilError(c, err)
  1797  
  1798  	out := cli.DockerCmd(c, "start", "-a", "test").Combined()
  1799  	assert.Equal(c, out, "hello")
  1800  }
  1801  
  1802  // Test Mounts comes out as expected for the MountPoint
  1803  func (s *DockerAPISuite) TestContainersAPICreateMountsCreate(c *testing.T) {
  1804  	prefix, slash := getPrefixAndSlashFromDaemonPlatform()
  1805  	destPath := prefix + slash + "foo"
  1806  
  1807  	var testImg string
  1808  	if testEnv.DaemonInfo.OSType != "windows" {
  1809  		testImg = "test-mount-config"
  1810  		buildImageSuccessfully(c, testImg, build.WithDockerfile(`
  1811  	FROM busybox
  1812  	RUN mkdir `+destPath+` && touch `+destPath+slash+`bar
  1813  	CMD cat `+destPath+slash+`bar
  1814  	`))
  1815  	} else {
  1816  		testImg = "busybox"
  1817  	}
  1818  
  1819  	type testCase struct {
  1820  		spec     mount.Mount
  1821  		expected types.MountPoint
  1822  	}
  1823  
  1824  	var selinuxSharedLabel string
  1825  	if runtime.GOOS == "linux" {
  1826  		selinuxSharedLabel = "z"
  1827  	}
  1828  
  1829  	cases := []testCase{
  1830  		// use literal strings here for `Type` instead of the defined constants in the volume package to keep this honest
  1831  		// Validation of the actual `Mount` struct is done in another test is not needed here
  1832  		{
  1833  			spec:     mount.Mount{Type: "volume", Target: destPath},
  1834  			expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  1835  		},
  1836  		{
  1837  			spec:     mount.Mount{Type: "volume", Target: destPath + slash},
  1838  			expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  1839  		},
  1840  		{
  1841  			spec:     mount.Mount{Type: "volume", Target: destPath, Source: "test1"},
  1842  			expected: types.MountPoint{Type: "volume", Name: "test1", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  1843  		},
  1844  		{
  1845  			spec:     mount.Mount{Type: "volume", Target: destPath, ReadOnly: true, Source: "test2"},
  1846  			expected: types.MountPoint{Type: "volume", Name: "test2", RW: false, Destination: destPath, Mode: selinuxSharedLabel},
  1847  		},
  1848  		{
  1849  			spec:     mount.Mount{Type: "volume", Target: destPath, Source: "test3", VolumeOptions: &mount.VolumeOptions{DriverConfig: &mount.Driver{Name: volume.DefaultDriverName}}},
  1850  			expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", Name: "test3", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  1851  		},
  1852  	}
  1853  
  1854  	if testEnv.IsLocalDaemon() {
  1855  		// setup temp dir for testing binds
  1856  		tmpDir1, err := os.MkdirTemp("", "test-mounts-api-1")
  1857  		assert.NilError(c, err)
  1858  		defer os.RemoveAll(tmpDir1)
  1859  		cases = append(cases, []testCase{
  1860  			{
  1861  				spec: mount.Mount{
  1862  					Type:   "bind",
  1863  					Source: tmpDir1,
  1864  					Target: destPath,
  1865  				},
  1866  				expected: types.MountPoint{
  1867  					Type:        "bind",
  1868  					RW:          true,
  1869  					Destination: destPath,
  1870  					Source:      tmpDir1,
  1871  				},
  1872  			},
  1873  			{
  1874  				spec:     mount.Mount{Type: "bind", Source: tmpDir1, Target: destPath, ReadOnly: true},
  1875  				expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir1},
  1876  			},
  1877  		}...)
  1878  
  1879  		// for modes only supported on Linux
  1880  		if DaemonIsLinux() {
  1881  			tmpDir3, err := os.MkdirTemp("", "test-mounts-api-3")
  1882  			assert.NilError(c, err)
  1883  			defer os.RemoveAll(tmpDir3)
  1884  
  1885  			if assert.Check(c, mountWrapper(c, tmpDir3, tmpDir3, "none", "bind,shared")) {
  1886  				cases = append(cases, []testCase{
  1887  					{
  1888  						spec:     mount.Mount{Type: "bind", Source: tmpDir3, Target: destPath},
  1889  						expected: types.MountPoint{Type: "bind", RW: true, Destination: destPath, Source: tmpDir3},
  1890  					},
  1891  					{
  1892  						spec:     mount.Mount{Type: "bind", Source: tmpDir3, Target: destPath, ReadOnly: true},
  1893  						expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir3},
  1894  					},
  1895  					{
  1896  						spec:     mount.Mount{Type: "bind", Source: tmpDir3, Target: destPath, ReadOnly: true, BindOptions: &mount.BindOptions{Propagation: "shared"}},
  1897  						expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir3, Propagation: "shared"},
  1898  					},
  1899  				}...)
  1900  			}
  1901  		}
  1902  	}
  1903  
  1904  	if testEnv.DaemonInfo.OSType != "windows" { // Windows does not support volume populate
  1905  		cases = append(cases, []testCase{
  1906  			{
  1907  				spec:     mount.Mount{Type: "volume", Target: destPath, VolumeOptions: &mount.VolumeOptions{NoCopy: true}},
  1908  				expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  1909  			},
  1910  			{
  1911  				spec:     mount.Mount{Type: "volume", Target: destPath + slash, VolumeOptions: &mount.VolumeOptions{NoCopy: true}},
  1912  				expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  1913  			},
  1914  			{
  1915  				spec:     mount.Mount{Type: "volume", Target: destPath, Source: "test4", VolumeOptions: &mount.VolumeOptions{NoCopy: true}},
  1916  				expected: types.MountPoint{Type: "volume", Name: "test4", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  1917  			},
  1918  			{
  1919  				spec:     mount.Mount{Type: "volume", Target: destPath, Source: "test5", ReadOnly: true, VolumeOptions: &mount.VolumeOptions{NoCopy: true}},
  1920  				expected: types.MountPoint{Type: "volume", Name: "test5", RW: false, Destination: destPath, Mode: selinuxSharedLabel},
  1921  			},
  1922  		}...)
  1923  	}
  1924  
  1925  	ctx := testutil.GetContext(c)
  1926  	apiclient := testEnv.APIClient()
  1927  	for i, x := range cases {
  1928  		x := x
  1929  		c.Run(fmt.Sprintf("%d config: %v", i, x.spec), func(c *testing.T) {
  1930  			ctr, err := apiclient.ContainerCreate(
  1931  				ctx,
  1932  				&container.Config{Image: testImg},
  1933  				&container.HostConfig{Mounts: []mount.Mount{x.spec}},
  1934  				&network.NetworkingConfig{},
  1935  				nil,
  1936  				"")
  1937  			assert.NilError(c, err)
  1938  
  1939  			containerInspect, err := apiclient.ContainerInspect(ctx, ctr.ID)
  1940  			assert.NilError(c, err)
  1941  			mps := containerInspect.Mounts
  1942  			assert.Assert(c, is.Len(mps, 1))
  1943  			mountPoint := mps[0]
  1944  
  1945  			if x.expected.Source != "" {
  1946  				assert.Check(c, is.Equal(x.expected.Source, mountPoint.Source))
  1947  			}
  1948  			if x.expected.Name != "" {
  1949  				assert.Check(c, is.Equal(x.expected.Name, mountPoint.Name))
  1950  			}
  1951  			if x.expected.Driver != "" {
  1952  				assert.Check(c, is.Equal(x.expected.Driver, mountPoint.Driver))
  1953  			}
  1954  			if x.expected.Propagation != "" {
  1955  				assert.Check(c, is.Equal(x.expected.Propagation, mountPoint.Propagation))
  1956  			}
  1957  			assert.Check(c, is.Equal(x.expected.RW, mountPoint.RW))
  1958  			assert.Check(c, is.Equal(x.expected.Type, mountPoint.Type))
  1959  			assert.Check(c, is.Equal(x.expected.Mode, mountPoint.Mode))
  1960  			assert.Check(c, is.Equal(x.expected.Destination, mountPoint.Destination))
  1961  
  1962  			err = apiclient.ContainerStart(ctx, ctr.ID, container.StartOptions{})
  1963  			assert.NilError(c, err)
  1964  			poll.WaitOn(c, containerExit(ctx, apiclient, ctr.ID), poll.WithDelay(time.Second))
  1965  
  1966  			err = apiclient.ContainerRemove(ctx, ctr.ID, container.RemoveOptions{
  1967  				RemoveVolumes: true,
  1968  				Force:         true,
  1969  			})
  1970  			assert.NilError(c, err)
  1971  
  1972  			switch {
  1973  			// Named volumes still exist after the container is removed
  1974  			case x.spec.Type == "volume" && len(x.spec.Source) > 0:
  1975  				_, err := apiclient.VolumeInspect(ctx, mountPoint.Name)
  1976  				assert.NilError(c, err)
  1977  
  1978  			// Bind mounts are never removed with the container
  1979  			case x.spec.Type == "bind":
  1980  
  1981  			// anonymous volumes are removed
  1982  			default:
  1983  				_, err := apiclient.VolumeInspect(ctx, mountPoint.Name)
  1984  				assert.Check(c, is.ErrorType(err, errdefs.IsNotFound))
  1985  			}
  1986  		})
  1987  	}
  1988  }
  1989  
  1990  func containerExit(ctx context.Context, apiclient client.APIClient, name string) func(poll.LogT) poll.Result {
  1991  	return func(logT poll.LogT) poll.Result {
  1992  		ctr, err := apiclient.ContainerInspect(ctx, name)
  1993  		if err != nil {
  1994  			return poll.Error(err)
  1995  		}
  1996  		switch ctr.State.Status {
  1997  		case "created", "running":
  1998  			return poll.Continue("container %s is %s, waiting for exit", name, ctr.State.Status)
  1999  		}
  2000  		return poll.Success()
  2001  	}
  2002  }
  2003  
  2004  func (s *DockerAPISuite) TestContainersAPICreateMountsTmpfs(c *testing.T) {
  2005  	testRequires(c, DaemonIsLinux)
  2006  	type testCase struct {
  2007  		cfg             mount.Mount
  2008  		expectedOptions []string
  2009  	}
  2010  	target := "/foo"
  2011  	cases := []testCase{
  2012  		{
  2013  			cfg: mount.Mount{
  2014  				Type:   "tmpfs",
  2015  				Target: target,
  2016  			},
  2017  			expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime"},
  2018  		},
  2019  		{
  2020  			cfg: mount.Mount{
  2021  				Type:   "tmpfs",
  2022  				Target: target,
  2023  				TmpfsOptions: &mount.TmpfsOptions{
  2024  					SizeBytes: 4096 * 1024, Mode: 0o700,
  2025  				},
  2026  			},
  2027  			expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime", "size=4096k", "mode=700"},
  2028  		},
  2029  	}
  2030  
  2031  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  2032  	assert.NilError(c, err)
  2033  	defer apiClient.Close()
  2034  
  2035  	config := container.Config{
  2036  		Image: "busybox",
  2037  		Cmd:   []string{"/bin/sh", "-c", fmt.Sprintf("mount | grep 'tmpfs on %s'", target)},
  2038  	}
  2039  	for i, x := range cases {
  2040  		cName := fmt.Sprintf("test-tmpfs-%d", i)
  2041  		hostConfig := container.HostConfig{
  2042  			Mounts: []mount.Mount{x.cfg},
  2043  		}
  2044  
  2045  		_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, cName)
  2046  		assert.NilError(c, err)
  2047  		out := cli.DockerCmd(c, "start", "-a", cName).Combined()
  2048  		for _, option := range x.expectedOptions {
  2049  			assert.Assert(c, strings.Contains(out, option))
  2050  		}
  2051  	}
  2052  }
  2053  
  2054  // Regression test for #33334
  2055  // Makes sure that when a container which has a custom stop signal + restart=always
  2056  // gets killed (with SIGKILL) by the kill API, that the restart policy is cancelled.
  2057  func (s *DockerAPISuite) TestContainerKillCustomStopSignal(c *testing.T) {
  2058  	id := strings.TrimSpace(runSleepingContainer(c, "--stop-signal=SIGTERM", "--restart=always"))
  2059  	res, _, err := request.Post(testutil.GetContext(c), "/containers/"+id+"/kill")
  2060  	assert.NilError(c, err)
  2061  	defer res.Body.Close()
  2062  
  2063  	b, err := io.ReadAll(res.Body)
  2064  	assert.NilError(c, err)
  2065  	assert.Equal(c, res.StatusCode, http.StatusNoContent, string(b))
  2066  	err = waitInspect(id, "{{.State.Running}} {{.State.Restarting}}", "false false", 30*time.Second)
  2067  	assert.NilError(c, err)
  2068  }