github.com/rish1988/moby@v25.0.2+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) TestContainerAPICopyPre124(c *testing.T) {
   962  	testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
   963  	const name = "test-container-api-copy"
   964  	cli.DockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt")
   965  
   966  	postData := types.CopyConfig{
   967  		Resource: "/test.txt",
   968  	}
   969  
   970  	res, body, err := request.Post(testutil.GetContext(c), "/v1.23/containers/"+name+"/copy", request.JSONBody(postData))
   971  	assert.NilError(c, err)
   972  	assert.Equal(c, res.StatusCode, http.StatusOK)
   973  
   974  	found := false
   975  	for tarReader := tar.NewReader(body); ; {
   976  		h, err := tarReader.Next()
   977  		if err != nil {
   978  			if err == io.EOF {
   979  				break
   980  			}
   981  			c.Fatal(err)
   982  		}
   983  		if h.Name == "test.txt" {
   984  			found = true
   985  			break
   986  		}
   987  	}
   988  	assert.Assert(c, found)
   989  }
   990  
   991  func (s *DockerAPISuite) TestContainerAPICopyResourcePathEmptyPre124(c *testing.T) {
   992  	testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
   993  	const name = "test-container-api-copy-resource-empty"
   994  	cli.DockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt")
   995  
   996  	postData := types.CopyConfig{
   997  		Resource: "",
   998  	}
   999  
  1000  	res, body, err := request.Post(testutil.GetContext(c), "/v1.23/containers/"+name+"/copy", request.JSONBody(postData))
  1001  	assert.NilError(c, err)
  1002  	assert.Equal(c, res.StatusCode, http.StatusBadRequest)
  1003  	b, err := request.ReadBody(body)
  1004  	assert.NilError(c, err)
  1005  	assert.Assert(c, is.Regexp("^Path cannot be empty\n$", string(b)))
  1006  }
  1007  
  1008  func (s *DockerAPISuite) TestContainerAPICopyResourcePathNotFoundPre124(c *testing.T) {
  1009  	testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
  1010  	const name = "test-container-api-copy-resource-not-found"
  1011  	cli.DockerCmd(c, "run", "--name", name, "busybox")
  1012  
  1013  	postData := types.CopyConfig{
  1014  		Resource: "/notexist",
  1015  	}
  1016  
  1017  	res, body, err := request.Post(testutil.GetContext(c), "/v1.23/containers/"+name+"/copy", request.JSONBody(postData))
  1018  	assert.NilError(c, err)
  1019  	assert.Equal(c, res.StatusCode, http.StatusNotFound)
  1020  	b, err := request.ReadBody(body)
  1021  	assert.NilError(c, err)
  1022  	assert.Assert(c, is.Regexp("^Could not find the file /notexist in container "+name+"\n$", string(b)))
  1023  }
  1024  
  1025  func (s *DockerAPISuite) TestContainerAPICopyContainerNotFoundPr124(c *testing.T) {
  1026  	testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
  1027  	postData := types.CopyConfig{
  1028  		Resource: "/something",
  1029  	}
  1030  
  1031  	res, _, err := request.Post(testutil.GetContext(c), "/v1.23/containers/notexists/copy", request.JSONBody(postData))
  1032  	assert.NilError(c, err)
  1033  	assert.Equal(c, res.StatusCode, http.StatusNotFound)
  1034  }
  1035  
  1036  func (s *DockerAPISuite) TestContainerAPIDelete(c *testing.T) {
  1037  	id := runSleepingContainer(c)
  1038  	cli.WaitRun(c, id)
  1039  	cli.DockerCmd(c, "stop", id)
  1040  
  1041  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1042  	assert.NilError(c, err)
  1043  	defer apiClient.Close()
  1044  
  1045  	err = apiClient.ContainerRemove(testutil.GetContext(c), id, container.RemoveOptions{})
  1046  	assert.NilError(c, err)
  1047  }
  1048  
  1049  func (s *DockerAPISuite) TestContainerAPIDeleteNotExist(c *testing.T) {
  1050  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1051  	assert.NilError(c, err)
  1052  	defer apiClient.Close()
  1053  
  1054  	err = apiClient.ContainerRemove(testutil.GetContext(c), "doesnotexist", container.RemoveOptions{})
  1055  	assert.ErrorContains(c, err, "No such container: doesnotexist")
  1056  }
  1057  
  1058  func (s *DockerAPISuite) TestContainerAPIDeleteForce(c *testing.T) {
  1059  	id := runSleepingContainer(c)
  1060  	cli.WaitRun(c, id)
  1061  
  1062  	removeOptions := container.RemoveOptions{
  1063  		Force: true,
  1064  	}
  1065  
  1066  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1067  	assert.NilError(c, err)
  1068  	defer apiClient.Close()
  1069  
  1070  	err = apiClient.ContainerRemove(testutil.GetContext(c), id, removeOptions)
  1071  	assert.NilError(c, err)
  1072  }
  1073  
  1074  func (s *DockerAPISuite) TestContainerAPIDeleteRemoveLinks(c *testing.T) {
  1075  	// Windows does not support links
  1076  	testRequires(c, DaemonIsLinux)
  1077  	out := cli.DockerCmd(c, "run", "-d", "--name", "tlink1", "busybox", "top").Stdout()
  1078  	id := strings.TrimSpace(out)
  1079  	cli.WaitRun(c, id)
  1080  
  1081  	out = cli.DockerCmd(c, "run", "--link", "tlink1:tlink1", "--name", "tlink2", "-d", "busybox", "top").Stdout()
  1082  	id2 := strings.TrimSpace(out)
  1083  	cli.WaitRun(c, id2)
  1084  
  1085  	links := inspectFieldJSON(c, id2, "HostConfig.Links")
  1086  	assert.Equal(c, links, `["/tlink1:/tlink2/tlink1"]`, "expected to have links between containers")
  1087  
  1088  	removeOptions := container.RemoveOptions{
  1089  		RemoveLinks: true,
  1090  	}
  1091  
  1092  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1093  	assert.NilError(c, err)
  1094  	defer apiClient.Close()
  1095  
  1096  	err = apiClient.ContainerRemove(testutil.GetContext(c), "tlink2/tlink1", removeOptions)
  1097  	assert.NilError(c, err)
  1098  
  1099  	linksPostRm := inspectFieldJSON(c, id2, "HostConfig.Links")
  1100  	assert.Equal(c, linksPostRm, "null", "call to api deleteContainer links should have removed the specified links")
  1101  }
  1102  
  1103  func (s *DockerAPISuite) TestContainerAPIDeleteRemoveVolume(c *testing.T) {
  1104  	testRequires(c, testEnv.IsLocalDaemon)
  1105  
  1106  	vol := "/testvolume"
  1107  	if testEnv.DaemonInfo.OSType == "windows" {
  1108  		vol = `c:\testvolume`
  1109  	}
  1110  
  1111  	id := runSleepingContainer(c, "-v", vol)
  1112  	cli.WaitRun(c, id)
  1113  
  1114  	source, err := inspectMountSourceField(id, vol)
  1115  	assert.NilError(c, err)
  1116  	_, err = os.Stat(source)
  1117  	assert.NilError(c, err)
  1118  
  1119  	removeOptions := container.RemoveOptions{
  1120  		Force:         true,
  1121  		RemoveVolumes: true,
  1122  	}
  1123  
  1124  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1125  	assert.NilError(c, err)
  1126  	defer apiClient.Close()
  1127  
  1128  	err = apiClient.ContainerRemove(testutil.GetContext(c), id, removeOptions)
  1129  	assert.NilError(c, err)
  1130  
  1131  	_, err = os.Stat(source)
  1132  	assert.Assert(c, os.IsNotExist(err), "expected to get ErrNotExist error, got %v", err)
  1133  }
  1134  
  1135  // Regression test for https://github.com/docker/docker/issues/6231
  1136  func (s *DockerAPISuite) TestContainerAPIChunkedEncoding(c *testing.T) {
  1137  	config := map[string]interface{}{
  1138  		"Image":     "busybox",
  1139  		"Cmd":       append([]string{"/bin/sh", "-c"}, sleepCommandForDaemonPlatform()...),
  1140  		"OpenStdin": true,
  1141  	}
  1142  
  1143  	resp, _, err := request.Post(testutil.GetContext(c), "/containers/create", request.JSONBody(config), request.With(func(req *http.Request) error {
  1144  		// This is a cheat to make the http request do chunked encoding
  1145  		// Otherwise (just setting the Content-Encoding to chunked) net/http will overwrite
  1146  		// https://golang.org/src/pkg/net/http/request.go?s=11980:12172
  1147  		req.ContentLength = -1
  1148  		return nil
  1149  	}))
  1150  	assert.Assert(c, err == nil, "error creating container with chunked encoding")
  1151  	defer resp.Body.Close()
  1152  	assert.Equal(c, resp.StatusCode, http.StatusCreated)
  1153  }
  1154  
  1155  func (s *DockerAPISuite) TestContainerAPIPostContainerStop(c *testing.T) {
  1156  	containerID := runSleepingContainer(c)
  1157  	cli.WaitRun(c, containerID)
  1158  
  1159  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1160  	assert.NilError(c, err)
  1161  	defer apiClient.Close()
  1162  
  1163  	err = apiClient.ContainerStop(testutil.GetContext(c), containerID, container.StopOptions{})
  1164  	assert.NilError(c, err)
  1165  	assert.Assert(c, waitInspect(containerID, "{{ .State.Running  }}", "false", 60*time.Second) == nil)
  1166  }
  1167  
  1168  // #14170
  1169  func (s *DockerAPISuite) TestPostContainerAPICreateWithStringOrSliceEntrypoint(c *testing.T) {
  1170  	config := container.Config{
  1171  		Image:      "busybox",
  1172  		Entrypoint: []string{"echo"},
  1173  		Cmd:        []string{"hello", "world"},
  1174  	}
  1175  
  1176  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1177  	assert.NilError(c, err)
  1178  	defer apiClient.Close()
  1179  
  1180  	_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "echotest")
  1181  	assert.NilError(c, err)
  1182  	out := cli.DockerCmd(c, "start", "-a", "echotest").Combined()
  1183  	assert.Equal(c, strings.TrimSpace(out), "hello world")
  1184  
  1185  	config2 := struct {
  1186  		Image      string
  1187  		Entrypoint string
  1188  		Cmd        []string
  1189  	}{"busybox", "echo", []string{"hello", "world"}}
  1190  	_, _, err = request.Post(testutil.GetContext(c), "/containers/create?name=echotest2", request.JSONBody(config2))
  1191  	assert.NilError(c, err)
  1192  	out = cli.DockerCmd(c, "start", "-a", "echotest2").Combined()
  1193  	assert.Equal(c, strings.TrimSpace(out), "hello world")
  1194  }
  1195  
  1196  // #14170
  1197  func (s *DockerAPISuite) TestPostContainersCreateWithStringOrSliceCmd(c *testing.T) {
  1198  	config := container.Config{
  1199  		Image: "busybox",
  1200  		Cmd:   []string{"echo", "hello", "world"},
  1201  	}
  1202  
  1203  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1204  	assert.NilError(c, err)
  1205  	defer apiClient.Close()
  1206  
  1207  	_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "echotest")
  1208  	assert.NilError(c, err)
  1209  	out := cli.DockerCmd(c, "start", "-a", "echotest").Combined()
  1210  	assert.Equal(c, strings.TrimSpace(out), "hello world")
  1211  
  1212  	config2 := struct {
  1213  		Image      string
  1214  		Entrypoint string
  1215  		Cmd        string
  1216  	}{"busybox", "echo", "hello world"}
  1217  	_, _, err = request.Post(testutil.GetContext(c), "/containers/create?name=echotest2", request.JSONBody(config2))
  1218  	assert.NilError(c, err)
  1219  	out = cli.DockerCmd(c, "start", "-a", "echotest2").Combined()
  1220  	assert.Equal(c, strings.TrimSpace(out), "hello world")
  1221  }
  1222  
  1223  // regression #14318
  1224  // for backward compatibility testing with and without CAP_ prefix
  1225  // and with upper and lowercase
  1226  func (s *DockerAPISuite) TestPostContainersCreateWithStringOrSliceCapAddDrop(c *testing.T) {
  1227  	// Windows doesn't support CapAdd/CapDrop
  1228  	testRequires(c, DaemonIsLinux)
  1229  	config := struct {
  1230  		Image   string
  1231  		CapAdd  string
  1232  		CapDrop string
  1233  	}{"busybox", "NET_ADMIN", "cap_sys_admin"}
  1234  	res, _, err := request.Post(testutil.GetContext(c), "/containers/create?name=capaddtest0", request.JSONBody(config))
  1235  	assert.NilError(c, err)
  1236  	assert.Equal(c, res.StatusCode, http.StatusCreated)
  1237  
  1238  	config2 := container.Config{
  1239  		Image: "busybox",
  1240  	}
  1241  	hostConfig := container.HostConfig{
  1242  		CapAdd:  []string{"net_admin", "SYS_ADMIN"},
  1243  		CapDrop: []string{"SETGID", "CAP_SETPCAP"},
  1244  	}
  1245  
  1246  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1247  	assert.NilError(c, err)
  1248  	defer apiClient.Close()
  1249  
  1250  	_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config2, &hostConfig, &network.NetworkingConfig{}, nil, "capaddtest1")
  1251  	assert.NilError(c, err)
  1252  }
  1253  
  1254  // #14915
  1255  func (s *DockerAPISuite) TestContainerAPICreateNoHostConfig118(c *testing.T) {
  1256  	testRequires(c, DaemonIsLinux) // Windows only support 1.25 or later
  1257  	config := container.Config{
  1258  		Image: "busybox",
  1259  	}
  1260  
  1261  	apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("v1.18"))
  1262  	assert.NilError(c, err)
  1263  
  1264  	_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "")
  1265  	assert.NilError(c, err)
  1266  }
  1267  
  1268  // Ensure an error occurs when you have a container read-only rootfs but you
  1269  // extract an archive to a symlink in a writable volume which points to a
  1270  // directory outside of the volume.
  1271  func (s *DockerAPISuite) TestPutContainerArchiveErrSymlinkInVolumeToReadOnlyRootfs(c *testing.T) {
  1272  	// Windows does not support read-only rootfs
  1273  	// Requires local volume mount bind.
  1274  	// --read-only + userns has remount issues
  1275  	testRequires(c, testEnv.IsLocalDaemon, NotUserNamespace, DaemonIsLinux)
  1276  
  1277  	testVol := getTestDir(c, "test-put-container-archive-err-symlink-in-volume-to-read-only-rootfs-")
  1278  	defer os.RemoveAll(testVol)
  1279  
  1280  	makeTestContentInDir(c, testVol)
  1281  
  1282  	cID := makeTestContainer(c, testContainerOptions{
  1283  		readOnly: true,
  1284  		volumes:  defaultVolumes(testVol), // Our bind mount is at /vol2
  1285  	})
  1286  
  1287  	// Attempt to extract to a symlink in the volume which points to a
  1288  	// directory outside the volume. This should cause an error because the
  1289  	// rootfs is read-only.
  1290  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1291  	assert.NilError(c, err)
  1292  
  1293  	err = apiClient.CopyToContainer(testutil.GetContext(c), cID, "/vol2/symlinkToAbsDir", nil, types.CopyToContainerOptions{})
  1294  	assert.ErrorContains(c, err, "container rootfs is marked read-only")
  1295  }
  1296  
  1297  func (s *DockerAPISuite) TestPostContainersCreateWithWrongCpusetValues(c *testing.T) {
  1298  	// Not supported on Windows
  1299  	testRequires(c, DaemonIsLinux)
  1300  
  1301  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1302  	assert.NilError(c, err)
  1303  	defer apiClient.Close()
  1304  
  1305  	config := container.Config{
  1306  		Image: "busybox",
  1307  	}
  1308  	hostConfig1 := container.HostConfig{
  1309  		Resources: container.Resources{
  1310  			CpusetCpus: "1-42,,",
  1311  		},
  1312  	}
  1313  	const name = "wrong-cpuset-cpus"
  1314  
  1315  	_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig1, &network.NetworkingConfig{}, nil, name)
  1316  	expected := "Invalid value 1-42,, for cpuset cpus"
  1317  	assert.ErrorContains(c, err, expected)
  1318  
  1319  	hostConfig2 := container.HostConfig{
  1320  		Resources: container.Resources{
  1321  			CpusetMems: "42-3,1--",
  1322  		},
  1323  	}
  1324  	const name2 = "wrong-cpuset-mems"
  1325  	_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig2, &network.NetworkingConfig{}, nil, name2)
  1326  	expected = "Invalid value 42-3,1-- for cpuset mems"
  1327  	assert.ErrorContains(c, err, expected)
  1328  }
  1329  
  1330  func (s *DockerAPISuite) TestPostContainersCreateShmSizeNegative(c *testing.T) {
  1331  	// ShmSize is not supported on Windows
  1332  	testRequires(c, DaemonIsLinux)
  1333  	config := container.Config{
  1334  		Image: "busybox",
  1335  	}
  1336  	hostConfig := container.HostConfig{
  1337  		ShmSize: -1,
  1338  	}
  1339  
  1340  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1341  	assert.NilError(c, err)
  1342  	defer apiClient.Close()
  1343  
  1344  	_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "")
  1345  	assert.ErrorContains(c, err, "SHM size can not be less than 0")
  1346  }
  1347  
  1348  func (s *DockerAPISuite) TestPostContainersCreateShmSizeHostConfigOmitted(c *testing.T) {
  1349  	// ShmSize is not supported on Windows
  1350  	testRequires(c, DaemonIsLinux)
  1351  
  1352  	config := container.Config{
  1353  		Image: "busybox",
  1354  		Cmd:   []string{"mount"},
  1355  	}
  1356  
  1357  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1358  	assert.NilError(c, err)
  1359  	defer apiClient.Close()
  1360  
  1361  	ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "")
  1362  	assert.NilError(c, err)
  1363  
  1364  	containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID)
  1365  	assert.NilError(c, err)
  1366  
  1367  	assert.Equal(c, containerJSON.HostConfig.ShmSize, dconfig.DefaultShmSize)
  1368  
  1369  	out := cli.DockerCmd(c, "start", "-i", containerJSON.ID).Combined()
  1370  	shmRegexp := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=65536k`)
  1371  	if !shmRegexp.MatchString(out) {
  1372  		c.Fatalf("Expected shm of 64MB in mount command, got %v", out)
  1373  	}
  1374  }
  1375  
  1376  func (s *DockerAPISuite) TestPostContainersCreateShmSizeOmitted(c *testing.T) {
  1377  	// ShmSize is not supported on Windows
  1378  	testRequires(c, DaemonIsLinux)
  1379  	config := container.Config{
  1380  		Image: "busybox",
  1381  		Cmd:   []string{"mount"},
  1382  	}
  1383  
  1384  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1385  	assert.NilError(c, err)
  1386  	defer apiClient.Close()
  1387  
  1388  	ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "")
  1389  	assert.NilError(c, err)
  1390  
  1391  	containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID)
  1392  	assert.NilError(c, err)
  1393  
  1394  	assert.Equal(c, containerJSON.HostConfig.ShmSize, int64(67108864))
  1395  
  1396  	out := cli.DockerCmd(c, "start", "-i", containerJSON.ID).Combined()
  1397  	shmRegexp := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=65536k`)
  1398  	if !shmRegexp.MatchString(out) {
  1399  		c.Fatalf("Expected shm of 64MB in mount command, got %v", out)
  1400  	}
  1401  }
  1402  
  1403  func (s *DockerAPISuite) TestPostContainersCreateWithShmSize(c *testing.T) {
  1404  	// ShmSize is not supported on Windows
  1405  	testRequires(c, DaemonIsLinux)
  1406  	config := container.Config{
  1407  		Image: "busybox",
  1408  		Cmd:   []string{"mount"},
  1409  	}
  1410  
  1411  	hostConfig := container.HostConfig{
  1412  		ShmSize: 1073741824,
  1413  	}
  1414  
  1415  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1416  	assert.NilError(c, err)
  1417  	defer apiClient.Close()
  1418  
  1419  	ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "")
  1420  	assert.NilError(c, err)
  1421  
  1422  	containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID)
  1423  	assert.NilError(c, err)
  1424  
  1425  	assert.Equal(c, containerJSON.HostConfig.ShmSize, int64(1073741824))
  1426  
  1427  	out := cli.DockerCmd(c, "start", "-i", containerJSON.ID).Combined()
  1428  	shmRegex := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=1048576k`)
  1429  	if !shmRegex.MatchString(out) {
  1430  		c.Fatalf("Expected shm of 1GB in mount command, got %v", out)
  1431  	}
  1432  }
  1433  
  1434  func (s *DockerAPISuite) TestPostContainersCreateMemorySwappinessHostConfigOmitted(c *testing.T) {
  1435  	// Swappiness is not supported on Windows
  1436  	testRequires(c, DaemonIsLinux)
  1437  	config := container.Config{
  1438  		Image: "busybox",
  1439  	}
  1440  
  1441  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1442  	assert.NilError(c, err)
  1443  	defer apiClient.Close()
  1444  
  1445  	ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "")
  1446  	assert.NilError(c, err)
  1447  
  1448  	containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID)
  1449  	assert.NilError(c, err)
  1450  
  1451  	assert.Assert(c, containerJSON.HostConfig.MemorySwappiness == nil)
  1452  }
  1453  
  1454  // check validation is done daemon side and not only in cli
  1455  func (s *DockerAPISuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *testing.T) {
  1456  	// OomScoreAdj is not supported on Windows
  1457  	testRequires(c, DaemonIsLinux)
  1458  
  1459  	config := container.Config{
  1460  		Image: "busybox",
  1461  	}
  1462  
  1463  	hostConfig := container.HostConfig{
  1464  		OomScoreAdj: 1001,
  1465  	}
  1466  
  1467  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1468  	assert.NilError(c, err)
  1469  	defer apiClient.Close()
  1470  
  1471  	const name = "oomscoreadj-over"
  1472  	_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, name)
  1473  
  1474  	expected := "Invalid value 1001, range for oom score adj is [-1000, 1000]"
  1475  	assert.ErrorContains(c, err, expected)
  1476  
  1477  	hostConfig = container.HostConfig{
  1478  		OomScoreAdj: -1001,
  1479  	}
  1480  
  1481  	const name2 = "oomscoreadj-low"
  1482  	_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, name2)
  1483  
  1484  	expected = "Invalid value -1001, range for oom score adj is [-1000, 1000]"
  1485  	assert.ErrorContains(c, err, expected)
  1486  }
  1487  
  1488  // test case for #22210 where an empty container name caused panic.
  1489  func (s *DockerAPISuite) TestContainerAPIDeleteWithEmptyName(c *testing.T) {
  1490  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1491  	assert.NilError(c, err)
  1492  	defer apiClient.Close()
  1493  
  1494  	err = apiClient.ContainerRemove(testutil.GetContext(c), "", container.RemoveOptions{})
  1495  	assert.Check(c, errdefs.IsNotFound(err))
  1496  }
  1497  
  1498  func (s *DockerAPISuite) TestContainerAPIStatsWithNetworkDisabled(c *testing.T) {
  1499  	// Problematic on Windows as Windows does not support stats
  1500  	testRequires(c, DaemonIsLinux)
  1501  
  1502  	const name = "testing-network-disabled"
  1503  
  1504  	config := container.Config{
  1505  		Image:           "busybox",
  1506  		Cmd:             []string{"top"},
  1507  		NetworkDisabled: true,
  1508  	}
  1509  
  1510  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1511  	assert.NilError(c, err)
  1512  	defer apiClient.Close()
  1513  
  1514  	_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, name)
  1515  	assert.NilError(c, err)
  1516  
  1517  	err = apiClient.ContainerStart(testutil.GetContext(c), name, container.StartOptions{})
  1518  	assert.NilError(c, err)
  1519  	cli.WaitRun(c, name)
  1520  
  1521  	type b struct {
  1522  		stats types.ContainerStats
  1523  		err   error
  1524  	}
  1525  	bc := make(chan b, 1)
  1526  	go func() {
  1527  		stats, err := apiClient.ContainerStats(testutil.GetContext(c), name, false)
  1528  		bc <- b{stats, err}
  1529  	}()
  1530  
  1531  	// allow some time to stream the stats from the container
  1532  	time.Sleep(4 * time.Second)
  1533  	cli.DockerCmd(c, "rm", "-f", name)
  1534  
  1535  	// collect the results from the stats stream or timeout and fail
  1536  	// if the stream was not disconnected.
  1537  	select {
  1538  	case <-time.After(2 * time.Second):
  1539  		c.Fatal("stream was not closed after container was removed")
  1540  	case sr := <-bc:
  1541  		assert.Assert(c, sr.err == nil)
  1542  		sr.stats.Body.Close()
  1543  	}
  1544  }
  1545  
  1546  func (s *DockerAPISuite) TestContainersAPICreateMountsValidation(c *testing.T) {
  1547  	type testCase struct {
  1548  		config     container.Config
  1549  		hostConfig container.HostConfig
  1550  		msg        string
  1551  	}
  1552  
  1553  	prefix, slash := getPrefixAndSlashFromDaemonPlatform()
  1554  	destPath := prefix + slash + "foo"
  1555  	notExistPath := prefix + slash + "notexist"
  1556  
  1557  	cases := []testCase{
  1558  		{
  1559  			config: container.Config{
  1560  				Image: "busybox",
  1561  			},
  1562  			hostConfig: container.HostConfig{
  1563  				Mounts: []mount.Mount{{
  1564  					Type:   "notreal",
  1565  					Target: destPath,
  1566  				}},
  1567  			},
  1568  
  1569  			msg: "mount type unknown",
  1570  		},
  1571  		{
  1572  			config: container.Config{
  1573  				Image: "busybox",
  1574  			},
  1575  			hostConfig: container.HostConfig{
  1576  				Mounts: []mount.Mount{{
  1577  					Type: "bind",
  1578  				}},
  1579  			},
  1580  			msg: "Target must not be empty",
  1581  		},
  1582  		{
  1583  			config: container.Config{
  1584  				Image: "busybox",
  1585  			},
  1586  			hostConfig: container.HostConfig{
  1587  				Mounts: []mount.Mount{{
  1588  					Type:   "bind",
  1589  					Target: destPath,
  1590  				}},
  1591  			},
  1592  			msg: "Source must not be empty",
  1593  		},
  1594  		{
  1595  			config: container.Config{
  1596  				Image: "busybox",
  1597  			},
  1598  			hostConfig: container.HostConfig{
  1599  				Mounts: []mount.Mount{{
  1600  					Type:   "bind",
  1601  					Source: notExistPath,
  1602  					Target: destPath,
  1603  				}},
  1604  			},
  1605  			msg: "source path does not exist",
  1606  			// FIXME(vdemeester) fails into e2e, migrate to integration/container anyway
  1607  			// msg: "source path does not exist: " + notExistPath,
  1608  		},
  1609  		{
  1610  			config: container.Config{
  1611  				Image: "busybox",
  1612  			},
  1613  			hostConfig: container.HostConfig{
  1614  				Mounts: []mount.Mount{{
  1615  					Type: "volume",
  1616  				}},
  1617  			},
  1618  			msg: "Target must not be empty",
  1619  		},
  1620  		{
  1621  			config: container.Config{
  1622  				Image: "busybox",
  1623  			},
  1624  			hostConfig: container.HostConfig{
  1625  				Mounts: []mount.Mount{{
  1626  					Type:   "volume",
  1627  					Source: "hello",
  1628  					Target: destPath,
  1629  				}},
  1630  			},
  1631  			msg: "",
  1632  		},
  1633  		{
  1634  			config: container.Config{
  1635  				Image: "busybox",
  1636  			},
  1637  			hostConfig: container.HostConfig{
  1638  				Mounts: []mount.Mount{{
  1639  					Type:   "volume",
  1640  					Source: "hello2",
  1641  					Target: destPath,
  1642  					VolumeOptions: &mount.VolumeOptions{
  1643  						DriverConfig: &mount.Driver{
  1644  							Name: "local",
  1645  						},
  1646  					},
  1647  				}},
  1648  			},
  1649  			msg: "",
  1650  		},
  1651  	}
  1652  
  1653  	if testEnv.IsLocalDaemon() {
  1654  		tmpDir, err := os.MkdirTemp("", "test-mounts-api")
  1655  		assert.NilError(c, err)
  1656  		defer os.RemoveAll(tmpDir)
  1657  		cases = append(cases, []testCase{
  1658  			{
  1659  				config: container.Config{
  1660  					Image: "busybox",
  1661  				},
  1662  				hostConfig: container.HostConfig{
  1663  					Mounts: []mount.Mount{{
  1664  						Type:   "bind",
  1665  						Source: tmpDir,
  1666  						Target: destPath,
  1667  					}},
  1668  				},
  1669  				msg: "",
  1670  			},
  1671  			{
  1672  				config: container.Config{
  1673  					Image: "busybox",
  1674  				},
  1675  				hostConfig: container.HostConfig{
  1676  					Mounts: []mount.Mount{{
  1677  						Type:          "bind",
  1678  						Source:        tmpDir,
  1679  						Target:        destPath,
  1680  						VolumeOptions: &mount.VolumeOptions{},
  1681  					}},
  1682  				},
  1683  				msg: "VolumeOptions must not be specified",
  1684  			},
  1685  		}...)
  1686  	}
  1687  
  1688  	if DaemonIsWindows() {
  1689  		cases = append(cases, []testCase{
  1690  			{
  1691  				config: container.Config{
  1692  					Image: "busybox",
  1693  				},
  1694  				hostConfig: container.HostConfig{
  1695  					Mounts: []mount.Mount{
  1696  						{
  1697  							Type:   "volume",
  1698  							Source: "not-supported-on-windows",
  1699  							Target: destPath,
  1700  							VolumeOptions: &mount.VolumeOptions{
  1701  								DriverConfig: &mount.Driver{
  1702  									Name:    "local",
  1703  									Options: map[string]string{"type": "tmpfs"},
  1704  								},
  1705  							},
  1706  						},
  1707  					},
  1708  				},
  1709  				msg: `options are not supported on this platform`,
  1710  			},
  1711  		}...)
  1712  	}
  1713  
  1714  	if DaemonIsLinux() {
  1715  		cases = append(cases, []testCase{
  1716  			{
  1717  				config: container.Config{
  1718  					Image: "busybox",
  1719  				},
  1720  				hostConfig: container.HostConfig{
  1721  					Mounts: []mount.Mount{
  1722  						{
  1723  							Type:   "volume",
  1724  							Source: "missing-device-opt",
  1725  							Target: destPath,
  1726  							VolumeOptions: &mount.VolumeOptions{
  1727  								DriverConfig: &mount.Driver{
  1728  									Name:    "local",
  1729  									Options: map[string]string{"foobar": "foobaz"},
  1730  								},
  1731  							},
  1732  						},
  1733  					},
  1734  				},
  1735  				msg: `invalid option: "foobar"`,
  1736  			},
  1737  			{
  1738  				config: container.Config{
  1739  					Image: "busybox",
  1740  				},
  1741  				hostConfig: container.HostConfig{
  1742  					Mounts: []mount.Mount{
  1743  						{
  1744  							Type:   "volume",
  1745  							Source: "missing-device-opt",
  1746  							Target: destPath,
  1747  							VolumeOptions: &mount.VolumeOptions{
  1748  								DriverConfig: &mount.Driver{
  1749  									Name:    "local",
  1750  									Options: map[string]string{"type": "tmpfs"},
  1751  								},
  1752  							},
  1753  						},
  1754  					},
  1755  				},
  1756  				msg: `missing required option: "device"`,
  1757  			},
  1758  			{
  1759  				config: container.Config{
  1760  					Image: "busybox",
  1761  				},
  1762  				hostConfig: container.HostConfig{
  1763  					Mounts: []mount.Mount{
  1764  						{
  1765  							Type:   "volume",
  1766  							Source: "missing-type-opt",
  1767  							Target: destPath,
  1768  							VolumeOptions: &mount.VolumeOptions{
  1769  								DriverConfig: &mount.Driver{
  1770  									Name:    "local",
  1771  									Options: map[string]string{"device": "tmpfs"},
  1772  								},
  1773  							},
  1774  						},
  1775  					},
  1776  				},
  1777  				msg: `missing required option: "type"`,
  1778  			},
  1779  			{
  1780  				config: container.Config{
  1781  					Image: "busybox",
  1782  				},
  1783  				hostConfig: container.HostConfig{
  1784  					Mounts: []mount.Mount{
  1785  						{
  1786  							Type:   "volume",
  1787  							Source: "hello4",
  1788  							Target: destPath,
  1789  							VolumeOptions: &mount.VolumeOptions{
  1790  								DriverConfig: &mount.Driver{
  1791  									Name:    "local",
  1792  									Options: map[string]string{"o": "size=1", "type": "tmpfs", "device": "tmpfs"},
  1793  								},
  1794  							},
  1795  						},
  1796  					},
  1797  				},
  1798  				msg: "",
  1799  			},
  1800  			{
  1801  				config: container.Config{
  1802  					Image: "busybox",
  1803  				},
  1804  				hostConfig: container.HostConfig{
  1805  					Mounts: []mount.Mount{{
  1806  						Type:   "tmpfs",
  1807  						Target: destPath,
  1808  					}},
  1809  				},
  1810  				msg: "",
  1811  			},
  1812  			{
  1813  				config: container.Config{
  1814  					Image: "busybox",
  1815  				},
  1816  				hostConfig: container.HostConfig{
  1817  					Mounts: []mount.Mount{{
  1818  						Type:   "tmpfs",
  1819  						Target: destPath,
  1820  						TmpfsOptions: &mount.TmpfsOptions{
  1821  							SizeBytes: 4096 * 1024,
  1822  							Mode:      0o700,
  1823  						},
  1824  					}},
  1825  				},
  1826  				msg: "",
  1827  			},
  1828  			{
  1829  				config: container.Config{
  1830  					Image: "busybox",
  1831  				},
  1832  				hostConfig: container.HostConfig{
  1833  					Mounts: []mount.Mount{{
  1834  						Type:   "tmpfs",
  1835  						Source: "/shouldnotbespecified",
  1836  						Target: destPath,
  1837  					}},
  1838  				},
  1839  				msg: "Source must not be specified",
  1840  			},
  1841  		}...)
  1842  	}
  1843  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1844  	assert.NilError(c, err)
  1845  	defer apiClient.Close()
  1846  
  1847  	// TODO add checks for statuscode returned by API
  1848  	for i, x := range cases {
  1849  		x := x
  1850  		c.Run(fmt.Sprintf("case %d", i), func(c *testing.T) {
  1851  			_, err = apiClient.ContainerCreate(testutil.GetContext(c), &x.config, &x.hostConfig, &network.NetworkingConfig{}, nil, "")
  1852  			if len(x.msg) > 0 {
  1853  				assert.ErrorContains(c, err, x.msg, "%v", cases[i].config)
  1854  			} else {
  1855  				assert.NilError(c, err)
  1856  			}
  1857  		})
  1858  	}
  1859  }
  1860  
  1861  func (s *DockerAPISuite) TestContainerAPICreateMountsBindRead(c *testing.T) {
  1862  	testRequires(c, NotUserNamespace, testEnv.IsLocalDaemon)
  1863  	// also with data in the host side
  1864  	prefix, slash := getPrefixAndSlashFromDaemonPlatform()
  1865  	destPath := prefix + slash + "foo"
  1866  	tmpDir, err := os.MkdirTemp("", "test-mounts-api-bind")
  1867  	assert.NilError(c, err)
  1868  	defer os.RemoveAll(tmpDir)
  1869  	err = os.WriteFile(filepath.Join(tmpDir, "bar"), []byte("hello"), 0o666)
  1870  	assert.NilError(c, err)
  1871  	config := container.Config{
  1872  		Image: "busybox",
  1873  		Cmd:   []string{"/bin/sh", "-c", "cat /foo/bar"},
  1874  	}
  1875  	hostConfig := container.HostConfig{
  1876  		Mounts: []mount.Mount{
  1877  			{Type: "bind", Source: tmpDir, Target: destPath},
  1878  		},
  1879  	}
  1880  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1881  	assert.NilError(c, err)
  1882  	defer apiClient.Close()
  1883  
  1884  	_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "test")
  1885  	assert.NilError(c, err)
  1886  
  1887  	out := cli.DockerCmd(c, "start", "-a", "test").Combined()
  1888  	assert.Equal(c, out, "hello")
  1889  }
  1890  
  1891  // Test Mounts comes out as expected for the MountPoint
  1892  func (s *DockerAPISuite) TestContainersAPICreateMountsCreate(c *testing.T) {
  1893  	prefix, slash := getPrefixAndSlashFromDaemonPlatform()
  1894  	destPath := prefix + slash + "foo"
  1895  
  1896  	var testImg string
  1897  	if testEnv.DaemonInfo.OSType != "windows" {
  1898  		testImg = "test-mount-config"
  1899  		buildImageSuccessfully(c, testImg, build.WithDockerfile(`
  1900  	FROM busybox
  1901  	RUN mkdir `+destPath+` && touch `+destPath+slash+`bar
  1902  	CMD cat `+destPath+slash+`bar
  1903  	`))
  1904  	} else {
  1905  		testImg = "busybox"
  1906  	}
  1907  
  1908  	type testCase struct {
  1909  		spec     mount.Mount
  1910  		expected types.MountPoint
  1911  	}
  1912  
  1913  	var selinuxSharedLabel string
  1914  	if runtime.GOOS == "linux" {
  1915  		selinuxSharedLabel = "z"
  1916  	}
  1917  
  1918  	cases := []testCase{
  1919  		// use literal strings here for `Type` instead of the defined constants in the volume package to keep this honest
  1920  		// Validation of the actual `Mount` struct is done in another test is not needed here
  1921  		{
  1922  			spec:     mount.Mount{Type: "volume", Target: destPath},
  1923  			expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  1924  		},
  1925  		{
  1926  			spec:     mount.Mount{Type: "volume", Target: destPath + slash},
  1927  			expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  1928  		},
  1929  		{
  1930  			spec:     mount.Mount{Type: "volume", Target: destPath, Source: "test1"},
  1931  			expected: types.MountPoint{Type: "volume", Name: "test1", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  1932  		},
  1933  		{
  1934  			spec:     mount.Mount{Type: "volume", Target: destPath, ReadOnly: true, Source: "test2"},
  1935  			expected: types.MountPoint{Type: "volume", Name: "test2", RW: false, Destination: destPath, Mode: selinuxSharedLabel},
  1936  		},
  1937  		{
  1938  			spec:     mount.Mount{Type: "volume", Target: destPath, Source: "test3", VolumeOptions: &mount.VolumeOptions{DriverConfig: &mount.Driver{Name: volume.DefaultDriverName}}},
  1939  			expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", Name: "test3", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  1940  		},
  1941  	}
  1942  
  1943  	if testEnv.IsLocalDaemon() {
  1944  		// setup temp dir for testing binds
  1945  		tmpDir1, err := os.MkdirTemp("", "test-mounts-api-1")
  1946  		assert.NilError(c, err)
  1947  		defer os.RemoveAll(tmpDir1)
  1948  		cases = append(cases, []testCase{
  1949  			{
  1950  				spec: mount.Mount{
  1951  					Type:   "bind",
  1952  					Source: tmpDir1,
  1953  					Target: destPath,
  1954  				},
  1955  				expected: types.MountPoint{
  1956  					Type:        "bind",
  1957  					RW:          true,
  1958  					Destination: destPath,
  1959  					Source:      tmpDir1,
  1960  				},
  1961  			},
  1962  			{
  1963  				spec:     mount.Mount{Type: "bind", Source: tmpDir1, Target: destPath, ReadOnly: true},
  1964  				expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir1},
  1965  			},
  1966  		}...)
  1967  
  1968  		// for modes only supported on Linux
  1969  		if DaemonIsLinux() {
  1970  			tmpDir3, err := os.MkdirTemp("", "test-mounts-api-3")
  1971  			assert.NilError(c, err)
  1972  			defer os.RemoveAll(tmpDir3)
  1973  
  1974  			if assert.Check(c, mountWrapper(c, tmpDir3, tmpDir3, "none", "bind,shared")) {
  1975  				cases = append(cases, []testCase{
  1976  					{
  1977  						spec:     mount.Mount{Type: "bind", Source: tmpDir3, Target: destPath},
  1978  						expected: types.MountPoint{Type: "bind", RW: true, Destination: destPath, Source: tmpDir3},
  1979  					},
  1980  					{
  1981  						spec:     mount.Mount{Type: "bind", Source: tmpDir3, Target: destPath, ReadOnly: true},
  1982  						expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir3},
  1983  					},
  1984  					{
  1985  						spec:     mount.Mount{Type: "bind", Source: tmpDir3, Target: destPath, ReadOnly: true, BindOptions: &mount.BindOptions{Propagation: "shared"}},
  1986  						expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir3, Propagation: "shared"},
  1987  					},
  1988  				}...)
  1989  			}
  1990  		}
  1991  	}
  1992  
  1993  	if testEnv.DaemonInfo.OSType != "windows" { // Windows does not support volume populate
  1994  		cases = append(cases, []testCase{
  1995  			{
  1996  				spec:     mount.Mount{Type: "volume", Target: destPath, VolumeOptions: &mount.VolumeOptions{NoCopy: true}},
  1997  				expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  1998  			},
  1999  			{
  2000  				spec:     mount.Mount{Type: "volume", Target: destPath + slash, VolumeOptions: &mount.VolumeOptions{NoCopy: true}},
  2001  				expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  2002  			},
  2003  			{
  2004  				spec:     mount.Mount{Type: "volume", Target: destPath, Source: "test4", VolumeOptions: &mount.VolumeOptions{NoCopy: true}},
  2005  				expected: types.MountPoint{Type: "volume", Name: "test4", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  2006  			},
  2007  			{
  2008  				spec:     mount.Mount{Type: "volume", Target: destPath, Source: "test5", ReadOnly: true, VolumeOptions: &mount.VolumeOptions{NoCopy: true}},
  2009  				expected: types.MountPoint{Type: "volume", Name: "test5", RW: false, Destination: destPath, Mode: selinuxSharedLabel},
  2010  			},
  2011  		}...)
  2012  	}
  2013  
  2014  	ctx := testutil.GetContext(c)
  2015  	apiclient := testEnv.APIClient()
  2016  	for i, x := range cases {
  2017  		x := x
  2018  		c.Run(fmt.Sprintf("%d config: %v", i, x.spec), func(c *testing.T) {
  2019  			ctr, err := apiclient.ContainerCreate(
  2020  				ctx,
  2021  				&container.Config{Image: testImg},
  2022  				&container.HostConfig{Mounts: []mount.Mount{x.spec}},
  2023  				&network.NetworkingConfig{},
  2024  				nil,
  2025  				"")
  2026  			assert.NilError(c, err)
  2027  
  2028  			containerInspect, err := apiclient.ContainerInspect(ctx, ctr.ID)
  2029  			assert.NilError(c, err)
  2030  			mps := containerInspect.Mounts
  2031  			assert.Assert(c, is.Len(mps, 1))
  2032  			mountPoint := mps[0]
  2033  
  2034  			if x.expected.Source != "" {
  2035  				assert.Check(c, is.Equal(x.expected.Source, mountPoint.Source))
  2036  			}
  2037  			if x.expected.Name != "" {
  2038  				assert.Check(c, is.Equal(x.expected.Name, mountPoint.Name))
  2039  			}
  2040  			if x.expected.Driver != "" {
  2041  				assert.Check(c, is.Equal(x.expected.Driver, mountPoint.Driver))
  2042  			}
  2043  			if x.expected.Propagation != "" {
  2044  				assert.Check(c, is.Equal(x.expected.Propagation, mountPoint.Propagation))
  2045  			}
  2046  			assert.Check(c, is.Equal(x.expected.RW, mountPoint.RW))
  2047  			assert.Check(c, is.Equal(x.expected.Type, mountPoint.Type))
  2048  			assert.Check(c, is.Equal(x.expected.Mode, mountPoint.Mode))
  2049  			assert.Check(c, is.Equal(x.expected.Destination, mountPoint.Destination))
  2050  
  2051  			err = apiclient.ContainerStart(ctx, ctr.ID, container.StartOptions{})
  2052  			assert.NilError(c, err)
  2053  			poll.WaitOn(c, containerExit(ctx, apiclient, ctr.ID), poll.WithDelay(time.Second))
  2054  
  2055  			err = apiclient.ContainerRemove(ctx, ctr.ID, container.RemoveOptions{
  2056  				RemoveVolumes: true,
  2057  				Force:         true,
  2058  			})
  2059  			assert.NilError(c, err)
  2060  
  2061  			switch {
  2062  			// Named volumes still exist after the container is removed
  2063  			case x.spec.Type == "volume" && len(x.spec.Source) > 0:
  2064  				_, err := apiclient.VolumeInspect(ctx, mountPoint.Name)
  2065  				assert.NilError(c, err)
  2066  
  2067  			// Bind mounts are never removed with the container
  2068  			case x.spec.Type == "bind":
  2069  
  2070  			// anonymous volumes are removed
  2071  			default:
  2072  				_, err := apiclient.VolumeInspect(ctx, mountPoint.Name)
  2073  				assert.Check(c, is.ErrorType(err, errdefs.IsNotFound))
  2074  			}
  2075  		})
  2076  	}
  2077  }
  2078  
  2079  func containerExit(ctx context.Context, apiclient client.APIClient, name string) func(poll.LogT) poll.Result {
  2080  	return func(logT poll.LogT) poll.Result {
  2081  		ctr, err := apiclient.ContainerInspect(ctx, name)
  2082  		if err != nil {
  2083  			return poll.Error(err)
  2084  		}
  2085  		switch ctr.State.Status {
  2086  		case "created", "running":
  2087  			return poll.Continue("container %s is %s, waiting for exit", name, ctr.State.Status)
  2088  		}
  2089  		return poll.Success()
  2090  	}
  2091  }
  2092  
  2093  func (s *DockerAPISuite) TestContainersAPICreateMountsTmpfs(c *testing.T) {
  2094  	testRequires(c, DaemonIsLinux)
  2095  	type testCase struct {
  2096  		cfg             mount.Mount
  2097  		expectedOptions []string
  2098  	}
  2099  	target := "/foo"
  2100  	cases := []testCase{
  2101  		{
  2102  			cfg: mount.Mount{
  2103  				Type:   "tmpfs",
  2104  				Target: target,
  2105  			},
  2106  			expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime"},
  2107  		},
  2108  		{
  2109  			cfg: mount.Mount{
  2110  				Type:   "tmpfs",
  2111  				Target: target,
  2112  				TmpfsOptions: &mount.TmpfsOptions{
  2113  					SizeBytes: 4096 * 1024, Mode: 0o700,
  2114  				},
  2115  			},
  2116  			expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime", "size=4096k", "mode=700"},
  2117  		},
  2118  	}
  2119  
  2120  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  2121  	assert.NilError(c, err)
  2122  	defer apiClient.Close()
  2123  
  2124  	config := container.Config{
  2125  		Image: "busybox",
  2126  		Cmd:   []string{"/bin/sh", "-c", fmt.Sprintf("mount | grep 'tmpfs on %s'", target)},
  2127  	}
  2128  	for i, x := range cases {
  2129  		cName := fmt.Sprintf("test-tmpfs-%d", i)
  2130  		hostConfig := container.HostConfig{
  2131  			Mounts: []mount.Mount{x.cfg},
  2132  		}
  2133  
  2134  		_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, cName)
  2135  		assert.NilError(c, err)
  2136  		out := cli.DockerCmd(c, "start", "-a", cName).Combined()
  2137  		for _, option := range x.expectedOptions {
  2138  			assert.Assert(c, strings.Contains(out, option))
  2139  		}
  2140  	}
  2141  }
  2142  
  2143  // Regression test for #33334
  2144  // Makes sure that when a container which has a custom stop signal + restart=always
  2145  // gets killed (with SIGKILL) by the kill API, that the restart policy is cancelled.
  2146  func (s *DockerAPISuite) TestContainerKillCustomStopSignal(c *testing.T) {
  2147  	id := strings.TrimSpace(runSleepingContainer(c, "--stop-signal=SIGTERM", "--restart=always"))
  2148  	res, _, err := request.Post(testutil.GetContext(c), "/containers/"+id+"/kill")
  2149  	assert.NilError(c, err)
  2150  	defer res.Body.Close()
  2151  
  2152  	b, err := io.ReadAll(res.Body)
  2153  	assert.NilError(c, err)
  2154  	assert.Equal(c, res.StatusCode, http.StatusNoContent, string(b))
  2155  	err = waitInspect(id, "{{.State.Running}} {{.State.Restarting}}", "false false", 30*time.Second)
  2156  	assert.NilError(c, err)
  2157  }