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