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