github.com/afbjorklund/moby@v20.10.5+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  	"io/ioutil"
    11  	"net/http"
    12  	"os"
    13  	"path/filepath"
    14  	"regexp"
    15  	"runtime"
    16  	"strings"
    17  	"testing"
    18  	"time"
    19  
    20  	"github.com/docker/docker/api/types"
    21  	containertypes "github.com/docker/docker/api/types/container"
    22  	mounttypes "github.com/docker/docker/api/types/mount"
    23  	networktypes "github.com/docker/docker/api/types/network"
    24  	"github.com/docker/docker/api/types/versions"
    25  	"github.com/docker/docker/client"
    26  	"github.com/docker/docker/integration-cli/cli"
    27  	"github.com/docker/docker/integration-cli/cli/build"
    28  	"github.com/docker/docker/pkg/ioutils"
    29  	"github.com/docker/docker/pkg/stringid"
    30  	"github.com/docker/docker/testutil/request"
    31  	"github.com/docker/docker/volume"
    32  	"github.com/docker/go-connections/nat"
    33  	"gotest.tools/v3/assert"
    34  	is "gotest.tools/v3/assert/cmp"
    35  	"gotest.tools/v3/poll"
    36  )
    37  
    38  func (s *DockerSuite) TestContainerAPIGetAll(c *testing.T) {
    39  	startCount := getContainerCount(c)
    40  	name := "getall"
    41  	dockerCmd(c, "run", "--name", name, "busybox", "true")
    42  
    43  	cli, err := client.NewClientWithOpts(client.FromEnv)
    44  	assert.NilError(c, err)
    45  	defer cli.Close()
    46  
    47  	options := types.ContainerListOptions{
    48  		All: true,
    49  	}
    50  	containers, err := cli.ContainerList(context.Background(), options)
    51  	assert.NilError(c, err)
    52  	assert.Equal(c, len(containers), startCount+1)
    53  	actual := containers[0].Names[0]
    54  	assert.Equal(c, actual, "/"+name)
    55  }
    56  
    57  // regression test for empty json field being omitted #13691
    58  func (s *DockerSuite) TestContainerAPIGetJSONNoFieldsOmitted(c *testing.T) {
    59  	startCount := getContainerCount(c)
    60  	dockerCmd(c, "run", "busybox", "true")
    61  
    62  	cli, err := client.NewClientWithOpts(client.FromEnv)
    63  	assert.NilError(c, err)
    64  	defer cli.Close()
    65  
    66  	options := types.ContainerListOptions{
    67  		All: true,
    68  	}
    69  	containers, err := cli.ContainerList(context.Background(), options)
    70  	assert.NilError(c, err)
    71  	assert.Equal(c, len(containers), startCount+1)
    72  	actual := fmt.Sprintf("%+v", containers[0])
    73  
    74  	// empty Labels field triggered this bug, make sense to check for everything
    75  	// cause even Ports for instance can trigger this bug
    76  	// better safe than sorry..
    77  	fields := []string{
    78  		"ID",
    79  		"Names",
    80  		"Image",
    81  		"Command",
    82  		"Created",
    83  		"Ports",
    84  		"Labels",
    85  		"Status",
    86  		"NetworkSettings",
    87  	}
    88  
    89  	// decoding into types.Container do not work since it eventually unmarshal
    90  	// and empty field to an empty go map, so we just check for a string
    91  	for _, f := range fields {
    92  		if !strings.Contains(actual, f) {
    93  			c.Fatalf("Field %s is missing and it shouldn't", f)
    94  		}
    95  	}
    96  }
    97  
    98  func (s *DockerSuite) TestContainerAPIGetExport(c *testing.T) {
    99  	// Not supported on Windows as Windows does not support docker export
   100  	testRequires(c, DaemonIsLinux)
   101  	name := "exportcontainer"
   102  	dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test")
   103  
   104  	cli, err := client.NewClientWithOpts(client.FromEnv)
   105  	assert.NilError(c, err)
   106  	defer cli.Close()
   107  
   108  	body, err := cli.ContainerExport(context.Background(), name)
   109  	assert.NilError(c, err)
   110  	defer body.Close()
   111  	found := false
   112  	for tarReader := tar.NewReader(body); ; {
   113  		h, err := tarReader.Next()
   114  		if err != nil && err == io.EOF {
   115  			break
   116  		}
   117  		if h.Name == "test" {
   118  			found = true
   119  			break
   120  		}
   121  	}
   122  	assert.Assert(c, found, "The created test file has not been found in the exported image")
   123  }
   124  
   125  func (s *DockerSuite) TestContainerAPIGetChanges(c *testing.T) {
   126  	// Not supported on Windows as Windows does not support docker diff (/containers/name/changes)
   127  	testRequires(c, DaemonIsLinux)
   128  	name := "changescontainer"
   129  	dockerCmd(c, "run", "--name", name, "busybox", "rm", "/etc/passwd")
   130  
   131  	cli, err := client.NewClientWithOpts(client.FromEnv)
   132  	assert.NilError(c, err)
   133  	defer cli.Close()
   134  
   135  	changes, err := cli.ContainerDiff(context.Background(), name)
   136  	assert.NilError(c, err)
   137  
   138  	// Check the changelog for removal of /etc/passwd
   139  	success := false
   140  	for _, elem := range changes {
   141  		if elem.Path == "/etc/passwd" && elem.Kind == 2 {
   142  			success = true
   143  		}
   144  	}
   145  	assert.Assert(c, success, "/etc/passwd has been removed but is not present in the diff")
   146  }
   147  
   148  func (s *DockerSuite) TestGetContainerStats(c *testing.T) {
   149  	var (
   150  		name = "statscontainer"
   151  	)
   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  		cli, err := client.NewClientWithOpts(client.FromEnv)
   162  		assert.NilError(c, err)
   163  		defer cli.Close()
   164  
   165  		stats, err := cli.ContainerStats(context.Background(), 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  	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 *DockerSuite) TestGetContainerStatsRmRunning(c *testing.T) {
   189  	out := runSleepingContainer(c)
   190  	id := strings.TrimSpace(out)
   191  
   192  	buf := &ChannelBuffer{C: make(chan []byte, 1)}
   193  	defer buf.Close()
   194  
   195  	cli, err := client.NewClientWithOpts(client.FromEnv)
   196  	assert.NilError(c, err)
   197  	defer cli.Close()
   198  
   199  	stats, err := cli.ContainerStats(context.Background(), id, true)
   200  	assert.NilError(c, err)
   201  	defer stats.Body.Close()
   202  
   203  	chErr := make(chan error, 1)
   204  	go func() {
   205  		_, err = io.Copy(buf, stats.Body)
   206  		chErr <- err
   207  	}()
   208  
   209  	b := make([]byte, 32)
   210  	// make sure we've got some stats
   211  	_, err = buf.ReadTimeout(b, 2*time.Second)
   212  	assert.NilError(c, err)
   213  
   214  	// Now remove without `-f` and make sure we are still pulling stats
   215  	_, _, err = dockerCmdWithError("rm", id)
   216  	assert.Assert(c, err != nil, "rm should have failed but didn't")
   217  	_, err = buf.ReadTimeout(b, 2*time.Second)
   218  	assert.NilError(c, err)
   219  
   220  	dockerCmd(c, "rm", "-f", id)
   221  	assert.Assert(c, <-chErr == nil)
   222  }
   223  
   224  // ChannelBuffer holds a chan of byte array that can be populate in a goroutine.
   225  type ChannelBuffer struct {
   226  	C chan []byte
   227  }
   228  
   229  // Write implements Writer.
   230  func (c *ChannelBuffer) Write(b []byte) (int, error) {
   231  	c.C <- b
   232  	return len(b), nil
   233  }
   234  
   235  // Close closes the go channel.
   236  func (c *ChannelBuffer) Close() error {
   237  	close(c.C)
   238  	return nil
   239  }
   240  
   241  // ReadTimeout reads the content of the channel in the specified byte array with
   242  // the specified duration as timeout.
   243  func (c *ChannelBuffer) ReadTimeout(p []byte, n time.Duration) (int, error) {
   244  	select {
   245  	case b := <-c.C:
   246  		return copy(p[0:], b), nil
   247  	case <-time.After(n):
   248  		return -1, fmt.Errorf("timeout reading from channel")
   249  	}
   250  }
   251  
   252  // regression test for gh13421
   253  // previous test was just checking one stat entry so it didn't fail (stats with
   254  // stream false always return one stat)
   255  func (s *DockerSuite) TestGetContainerStatsStream(c *testing.T) {
   256  	name := "statscontainer"
   257  	runSleepingContainer(c, "--name", name)
   258  
   259  	type b struct {
   260  		stats types.ContainerStats
   261  		err   error
   262  	}
   263  
   264  	bc := make(chan b, 1)
   265  	go func() {
   266  		cli, err := client.NewClientWithOpts(client.FromEnv)
   267  		assert.NilError(c, err)
   268  		defer cli.Close()
   269  
   270  		stats, err := cli.ContainerStats(context.Background(), name, true)
   271  		assert.NilError(c, err)
   272  		bc <- b{stats, err}
   273  	}()
   274  
   275  	// allow some time to stream the stats from the container
   276  	time.Sleep(4 * time.Second)
   277  	dockerCmd(c, "rm", "-f", name)
   278  
   279  	// collect the results from the stats stream or timeout and fail
   280  	// if the stream was not disconnected.
   281  	select {
   282  	case <-time.After(2 * time.Second):
   283  		c.Fatal("stream was not closed after container was removed")
   284  	case sr := <-bc:
   285  		b, err := ioutil.ReadAll(sr.stats.Body)
   286  		defer sr.stats.Body.Close()
   287  		assert.NilError(c, err)
   288  		s := string(b)
   289  		// count occurrences of "read" of types.Stats
   290  		if l := strings.Count(s, "read"); l < 2 {
   291  			c.Fatalf("Expected more than one stat streamed, got %d", l)
   292  		}
   293  	}
   294  }
   295  
   296  func (s *DockerSuite) TestGetContainerStatsNoStream(c *testing.T) {
   297  	name := "statscontainer"
   298  	runSleepingContainer(c, "--name", name)
   299  
   300  	type b struct {
   301  		stats types.ContainerStats
   302  		err   error
   303  	}
   304  
   305  	bc := make(chan b, 1)
   306  
   307  	go func() {
   308  		cli, err := client.NewClientWithOpts(client.FromEnv)
   309  		assert.NilError(c, err)
   310  		defer cli.Close()
   311  
   312  		stats, err := cli.ContainerStats(context.Background(), name, false)
   313  		assert.NilError(c, err)
   314  		bc <- b{stats, err}
   315  	}()
   316  
   317  	// allow some time to stream the stats from the container
   318  	time.Sleep(4 * time.Second)
   319  	dockerCmd(c, "rm", "-f", name)
   320  
   321  	// collect the results from the stats stream or timeout and fail
   322  	// if the stream was not disconnected.
   323  	select {
   324  	case <-time.After(2 * time.Second):
   325  		c.Fatal("stream was not closed after container was removed")
   326  	case sr := <-bc:
   327  		b, err := ioutil.ReadAll(sr.stats.Body)
   328  		defer sr.stats.Body.Close()
   329  		assert.NilError(c, err)
   330  		s := string(b)
   331  		// count occurrences of `"read"` of types.Stats
   332  		assert.Assert(c, strings.Count(s, `"read"`) == 1, "Expected only one stat streamed, got %d", strings.Count(s, `"read"`))
   333  	}
   334  }
   335  
   336  func (s *DockerSuite) TestGetStoppedContainerStats(c *testing.T) {
   337  	name := "statscontainer"
   338  	dockerCmd(c, "create", "--name", name, "busybox", "ps")
   339  
   340  	chResp := make(chan error, 1)
   341  
   342  	// We expect an immediate response, but if it's not immediate, the test would hang, so put it in a goroutine
   343  	// below we'll check this on a timeout.
   344  	go func() {
   345  		cli, err := client.NewClientWithOpts(client.FromEnv)
   346  		assert.NilError(c, err)
   347  		defer cli.Close()
   348  
   349  		resp, err := cli.ContainerStats(context.Background(), name, false)
   350  		assert.NilError(c, err)
   351  		defer resp.Body.Close()
   352  		chResp <- err
   353  	}()
   354  
   355  	select {
   356  	case err := <-chResp:
   357  		assert.NilError(c, err)
   358  	case <-time.After(10 * time.Second):
   359  		c.Fatal("timeout waiting for stats response for stopped container")
   360  	}
   361  }
   362  
   363  func (s *DockerSuite) TestContainerAPIPause(c *testing.T) {
   364  	// Problematic on Windows as Windows does not support pause
   365  	testRequires(c, DaemonIsLinux)
   366  
   367  	getPaused := func(c *testing.T) []string {
   368  		return strings.Fields(cli.DockerCmd(c, "ps", "-f", "status=paused", "-q", "-a").Combined())
   369  	}
   370  
   371  	out := cli.DockerCmd(c, "run", "-d", "busybox", "sleep", "30").Combined()
   372  	ContainerID := strings.TrimSpace(out)
   373  
   374  	cli, err := client.NewClientWithOpts(client.FromEnv)
   375  	assert.NilError(c, err)
   376  	defer cli.Close()
   377  
   378  	err = cli.ContainerPause(context.Background(), ContainerID)
   379  	assert.NilError(c, err)
   380  
   381  	pausedContainers := getPaused(c)
   382  
   383  	if len(pausedContainers) != 1 || stringid.TruncateID(ContainerID) != pausedContainers[0] {
   384  		c.Fatalf("there should be one paused container and not %d", len(pausedContainers))
   385  	}
   386  
   387  	err = cli.ContainerUnpause(context.Background(), ContainerID)
   388  	assert.NilError(c, err)
   389  
   390  	pausedContainers = getPaused(c)
   391  	assert.Equal(c, len(pausedContainers), 0, "There should be no paused container.")
   392  }
   393  
   394  func (s *DockerSuite) TestContainerAPITop(c *testing.T) {
   395  	testRequires(c, DaemonIsLinux)
   396  	out, _ := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "top && true")
   397  	id := strings.TrimSpace(out)
   398  	assert.NilError(c, waitRun(id))
   399  
   400  	cli, err := client.NewClientWithOpts(client.FromEnv)
   401  	assert.NilError(c, err)
   402  	defer cli.Close()
   403  
   404  	// sort by comm[andline] to make sure order stays the same in case of PID rollover
   405  	top, err := cli.ContainerTop(context.Background(), id, []string{"aux", "--sort=comm"})
   406  	assert.NilError(c, err)
   407  	assert.Equal(c, len(top.Titles), 11, fmt.Sprintf("expected 11 titles, found %d: %v", len(top.Titles), top.Titles))
   408  
   409  	if top.Titles[0] != "USER" || top.Titles[10] != "COMMAND" {
   410  		c.Fatalf("expected `USER` at `Titles[0]` and `COMMAND` at Titles[10]: %v", top.Titles)
   411  	}
   412  	assert.Equal(c, len(top.Processes), 2, fmt.Sprintf("expected 2 processes, found %d: %v", len(top.Processes), top.Processes))
   413  	assert.Equal(c, top.Processes[0][10], "/bin/sh -c top && true")
   414  	assert.Equal(c, top.Processes[1][10], "top")
   415  }
   416  
   417  func (s *DockerSuite) TestContainerAPITopWindows(c *testing.T) {
   418  	testRequires(c, DaemonIsWindows)
   419  	out := runSleepingContainer(c, "-d")
   420  	id := strings.TrimSpace(out)
   421  	assert.NilError(c, waitRun(id))
   422  
   423  	cli, err := client.NewClientWithOpts(client.FromEnv)
   424  	assert.NilError(c, err)
   425  	defer cli.Close()
   426  
   427  	top, err := cli.ContainerTop(context.Background(), id, nil)
   428  	assert.NilError(c, err)
   429  	assert.Equal(c, len(top.Titles), 4, "expected 4 titles, found %d: %v", len(top.Titles), top.Titles)
   430  
   431  	if top.Titles[0] != "Name" || top.Titles[3] != "Private Working Set" {
   432  		c.Fatalf("expected `Name` at `Titles[0]` and `Private Working Set` at Titles[3]: %v", top.Titles)
   433  	}
   434  	assert.Assert(c, len(top.Processes) >= 2, "expected at least 2 processes, found %d: %v", len(top.Processes), top.Processes)
   435  
   436  	foundProcess := false
   437  	expectedProcess := "busybox.exe"
   438  	for _, process := range top.Processes {
   439  		if process[0] == expectedProcess {
   440  			foundProcess = true
   441  			break
   442  		}
   443  	}
   444  
   445  	assert.Assert(c, foundProcess, "expected to find %s: %v", expectedProcess, top.Processes)
   446  }
   447  
   448  func (s *DockerSuite) TestContainerAPICommit(c *testing.T) {
   449  	cName := "testapicommit"
   450  	dockerCmd(c, "run", "--name="+cName, "busybox", "/bin/sh", "-c", "touch /test")
   451  
   452  	cli, err := client.NewClientWithOpts(client.FromEnv)
   453  	assert.NilError(c, err)
   454  	defer cli.Close()
   455  
   456  	options := types.ContainerCommitOptions{
   457  		Reference: "testcontainerapicommit:testtag",
   458  	}
   459  
   460  	img, err := cli.ContainerCommit(context.Background(), cName, options)
   461  	assert.NilError(c, err)
   462  
   463  	cmd := inspectField(c, img.ID, "Config.Cmd")
   464  	assert.Equal(c, cmd, "[/bin/sh -c touch /test]", fmt.Sprintf("got wrong Cmd from commit: %q", cmd))
   465  
   466  	// sanity check, make sure the image is what we think it is
   467  	dockerCmd(c, "run", img.ID, "ls", "/test")
   468  }
   469  
   470  func (s *DockerSuite) TestContainerAPICommitWithLabelInConfig(c *testing.T) {
   471  	cName := "testapicommitwithconfig"
   472  	dockerCmd(c, "run", "--name="+cName, "busybox", "/bin/sh", "-c", "touch /test")
   473  
   474  	cli, err := client.NewClientWithOpts(client.FromEnv)
   475  	assert.NilError(c, err)
   476  	defer cli.Close()
   477  
   478  	config := containertypes.Config{
   479  		Labels: map[string]string{"key1": "value1", "key2": "value2"}}
   480  
   481  	options := types.ContainerCommitOptions{
   482  		Reference: "testcontainerapicommitwithconfig",
   483  		Config:    &config,
   484  	}
   485  
   486  	img, err := cli.ContainerCommit(context.Background(), cName, options)
   487  	assert.NilError(c, err)
   488  
   489  	label1 := inspectFieldMap(c, img.ID, "Config.Labels", "key1")
   490  	assert.Equal(c, label1, "value1")
   491  
   492  	label2 := inspectFieldMap(c, img.ID, "Config.Labels", "key2")
   493  	assert.Equal(c, label2, "value2")
   494  
   495  	cmd := inspectField(c, img.ID, "Config.Cmd")
   496  	assert.Equal(c, cmd, "[/bin/sh -c touch /test]", fmt.Sprintf("got wrong Cmd from commit: %q", cmd))
   497  
   498  	// sanity check, make sure the image is what we think it is
   499  	dockerCmd(c, "run", img.ID, "ls", "/test")
   500  }
   501  
   502  func (s *DockerSuite) TestContainerAPIBadPort(c *testing.T) {
   503  	// TODO Windows to Windows CI - Port this test
   504  	testRequires(c, DaemonIsLinux)
   505  
   506  	config := containertypes.Config{
   507  		Image: "busybox",
   508  		Cmd:   []string{"/bin/sh", "-c", "echo test"},
   509  	}
   510  
   511  	hostConfig := containertypes.HostConfig{
   512  		PortBindings: nat.PortMap{
   513  			"8080/tcp": []nat.PortBinding{
   514  				{
   515  					HostIP:   "",
   516  					HostPort: "aa80"},
   517  			},
   518  		},
   519  	}
   520  
   521  	cli, err := client.NewClientWithOpts(client.FromEnv)
   522  	assert.NilError(c, err)
   523  	defer cli.Close()
   524  
   525  	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, nil, "")
   526  	assert.ErrorContains(c, err, `invalid port specification: "aa80"`)
   527  }
   528  
   529  func (s *DockerSuite) TestContainerAPICreate(c *testing.T) {
   530  	config := containertypes.Config{
   531  		Image: "busybox",
   532  		Cmd:   []string{"/bin/sh", "-c", "touch /test && ls /test"},
   533  	}
   534  
   535  	cli, err := client.NewClientWithOpts(client.FromEnv)
   536  	assert.NilError(c, err)
   537  	defer cli.Close()
   538  
   539  	container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, "")
   540  	assert.NilError(c, err)
   541  
   542  	out, _ := dockerCmd(c, "start", "-a", container.ID)
   543  	assert.Equal(c, strings.TrimSpace(out), "/test")
   544  }
   545  
   546  func (s *DockerSuite) TestContainerAPICreateEmptyConfig(c *testing.T) {
   547  
   548  	cli, err := client.NewClientWithOpts(client.FromEnv)
   549  	assert.NilError(c, err)
   550  	defer cli.Close()
   551  
   552  	_, err = cli.ContainerCreate(context.Background(), &containertypes.Config{}, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, "")
   553  
   554  	expected := "No command specified"
   555  	assert.ErrorContains(c, err, expected)
   556  }
   557  
   558  func (s *DockerSuite) TestContainerAPICreateMultipleNetworksConfig(c *testing.T) {
   559  	// Container creation must fail if client specified configurations for more than one network
   560  	config := containertypes.Config{
   561  		Image: "busybox",
   562  	}
   563  
   564  	networkingConfig := networktypes.NetworkingConfig{
   565  		EndpointsConfig: map[string]*networktypes.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, &containertypes.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 *DockerSuite) TestContainerAPICreateBridgeNetworkMode(c *testing.T) {
   586  	// Windows does not support bridge
   587  	testRequires(c, DaemonIsLinux)
   588  	UtilCreateNetworkMode(c, "bridge")
   589  }
   590  
   591  func (s *DockerSuite) 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 containertypes.NetworkMode) {
   599  	config := containertypes.Config{
   600  		Image: "busybox",
   601  	}
   602  
   603  	hostConfig := containertypes.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, &networktypes.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 *DockerSuite) TestContainerAPICreateWithCpuSharesCpuset(c *testing.T) {
   621  	// TODO Windows to Windows CI. The CpuShares part could be ported.
   622  	testRequires(c, DaemonIsLinux)
   623  	config := containertypes.Config{
   624  		Image: "busybox",
   625  	}
   626  
   627  	hostConfig := containertypes.HostConfig{
   628  		Resources: containertypes.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, &networktypes.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 *DockerSuite) 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(ioutil.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 *DockerSuite) 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 *DockerSuite) 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 *DockerSuite) 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 *DockerSuite) 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 *DockerSuite) 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 *DockerSuite) 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 *DockerSuite) 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 *DockerSuite) 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 *DockerSuite) 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 *DockerSuite) 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 * time.Second
   916  	err = cli.ContainerRestart(context.Background(), name, &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 *DockerSuite) 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, nil)
   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 *DockerSuite) TestContainerAPIStart(c *testing.T) {
   939  	name := "testing-start"
   940  	config := containertypes.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, &containertypes.HostConfig{}, &networktypes.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 *DockerSuite) TestContainerAPIStop(c *testing.T) {
   965  	name := "test-api-stop"
   966  	runSleepingContainer(c, "-i", "--name", name)
   967  	timeout := 30 * time.Second
   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, &timeout)
   974  	assert.NilError(c, err)
   975  	assert.Assert(c, waitInspect(name, "{{ .State.Running  }}", "false", 60*time.Second) == nil)
   976  
   977  	// second call to start should give 304
   978  	// maybe add ContainerStartWithRaw to test it
   979  	err = cli.ContainerStop(context.Background(), name, &timeout)
   980  	assert.NilError(c, err)
   981  }
   982  
   983  func (s *DockerSuite) TestContainerAPIWait(c *testing.T) {
   984  	name := "test-api-wait"
   985  
   986  	sleepCmd := "/bin/sleep"
   987  	if testEnv.OSType == "windows" {
   988  		sleepCmd = "sleep"
   989  	}
   990  	dockerCmd(c, "run", "--name", name, "busybox", sleepCmd, "2")
   991  
   992  	cli, err := client.NewClientWithOpts(client.FromEnv)
   993  	assert.NilError(c, err)
   994  	defer cli.Close()
   995  
   996  	waitResC, errC := cli.ContainerWait(context.Background(), name, "")
   997  
   998  	select {
   999  	case err = <-errC:
  1000  		assert.NilError(c, err)
  1001  	case waitRes := <-waitResC:
  1002  		assert.Equal(c, waitRes.StatusCode, int64(0))
  1003  	}
  1004  }
  1005  
  1006  func (s *DockerSuite) TestContainerAPICopyNotExistsAnyMore(c *testing.T) {
  1007  	name := "test-container-api-copy"
  1008  	dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt")
  1009  
  1010  	postData := types.CopyConfig{
  1011  		Resource: "/test.txt",
  1012  	}
  1013  	// no copy in client/
  1014  	res, _, err := request.Post("/containers/"+name+"/copy", request.JSONBody(postData))
  1015  	assert.NilError(c, err)
  1016  	assert.Equal(c, res.StatusCode, http.StatusNotFound)
  1017  }
  1018  
  1019  func (s *DockerSuite) TestContainerAPICopyPre124(c *testing.T) {
  1020  	testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
  1021  	name := "test-container-api-copy"
  1022  	dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt")
  1023  
  1024  	postData := types.CopyConfig{
  1025  		Resource: "/test.txt",
  1026  	}
  1027  
  1028  	res, body, err := request.Post("/v1.23/containers/"+name+"/copy", request.JSONBody(postData))
  1029  	assert.NilError(c, err)
  1030  	assert.Equal(c, res.StatusCode, http.StatusOK)
  1031  
  1032  	found := false
  1033  	for tarReader := tar.NewReader(body); ; {
  1034  		h, err := tarReader.Next()
  1035  		if err != nil {
  1036  			if err == io.EOF {
  1037  				break
  1038  			}
  1039  			c.Fatal(err)
  1040  		}
  1041  		if h.Name == "test.txt" {
  1042  			found = true
  1043  			break
  1044  		}
  1045  	}
  1046  	assert.Assert(c, found)
  1047  }
  1048  
  1049  func (s *DockerSuite) TestContainerAPICopyResourcePathEmptyPre124(c *testing.T) {
  1050  	testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
  1051  	name := "test-container-api-copy-resource-empty"
  1052  	dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt")
  1053  
  1054  	postData := types.CopyConfig{
  1055  		Resource: "",
  1056  	}
  1057  
  1058  	res, body, err := request.Post("/v1.23/containers/"+name+"/copy", request.JSONBody(postData))
  1059  	assert.NilError(c, err)
  1060  	if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") {
  1061  		assert.Equal(c, res.StatusCode, http.StatusBadRequest)
  1062  	} else {
  1063  		assert.Assert(c, res.StatusCode != http.StatusOK)
  1064  	}
  1065  	b, err := request.ReadBody(body)
  1066  	assert.NilError(c, err)
  1067  	assert.Assert(c, is.Regexp("^Path cannot be empty\n$", string(b)))
  1068  
  1069  }
  1070  
  1071  func (s *DockerSuite) TestContainerAPICopyResourcePathNotFoundPre124(c *testing.T) {
  1072  	testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
  1073  	name := "test-container-api-copy-resource-not-found"
  1074  	dockerCmd(c, "run", "--name", name, "busybox")
  1075  
  1076  	postData := types.CopyConfig{
  1077  		Resource: "/notexist",
  1078  	}
  1079  
  1080  	res, body, err := request.Post("/v1.23/containers/"+name+"/copy", request.JSONBody(postData))
  1081  	assert.NilError(c, err)
  1082  	if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") {
  1083  		assert.Equal(c, res.StatusCode, http.StatusInternalServerError)
  1084  	} else {
  1085  		assert.Equal(c, res.StatusCode, http.StatusNotFound)
  1086  	}
  1087  	b, err := request.ReadBody(body)
  1088  	assert.NilError(c, err)
  1089  	assert.Assert(c, is.Regexp("^Could not find the file /notexist in container "+name+"\n$", string(b)))
  1090  
  1091  }
  1092  
  1093  func (s *DockerSuite) TestContainerAPICopyContainerNotFoundPr124(c *testing.T) {
  1094  	testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
  1095  	postData := types.CopyConfig{
  1096  		Resource: "/something",
  1097  	}
  1098  
  1099  	res, _, err := request.Post("/v1.23/containers/notexists/copy", request.JSONBody(postData))
  1100  	assert.NilError(c, err)
  1101  	assert.Equal(c, res.StatusCode, http.StatusNotFound)
  1102  }
  1103  
  1104  func (s *DockerSuite) TestContainerAPIDelete(c *testing.T) {
  1105  	out := runSleepingContainer(c)
  1106  
  1107  	id := strings.TrimSpace(out)
  1108  	assert.NilError(c, waitRun(id))
  1109  
  1110  	dockerCmd(c, "stop", id)
  1111  
  1112  	cli, err := client.NewClientWithOpts(client.FromEnv)
  1113  	assert.NilError(c, err)
  1114  	defer cli.Close()
  1115  
  1116  	err = cli.ContainerRemove(context.Background(), id, types.ContainerRemoveOptions{})
  1117  	assert.NilError(c, err)
  1118  }
  1119  
  1120  func (s *DockerSuite) TestContainerAPIDeleteNotExist(c *testing.T) {
  1121  	cli, err := client.NewClientWithOpts(client.FromEnv)
  1122  	assert.NilError(c, err)
  1123  	defer cli.Close()
  1124  
  1125  	err = cli.ContainerRemove(context.Background(), "doesnotexist", types.ContainerRemoveOptions{})
  1126  	assert.ErrorContains(c, err, "No such container: doesnotexist")
  1127  }
  1128  
  1129  func (s *DockerSuite) TestContainerAPIDeleteForce(c *testing.T) {
  1130  	out := runSleepingContainer(c)
  1131  	id := strings.TrimSpace(out)
  1132  	assert.NilError(c, waitRun(id))
  1133  
  1134  	removeOptions := types.ContainerRemoveOptions{
  1135  		Force: true,
  1136  	}
  1137  
  1138  	cli, err := client.NewClientWithOpts(client.FromEnv)
  1139  	assert.NilError(c, err)
  1140  	defer cli.Close()
  1141  
  1142  	err = cli.ContainerRemove(context.Background(), id, removeOptions)
  1143  	assert.NilError(c, err)
  1144  }
  1145  
  1146  func (s *DockerSuite) TestContainerAPIDeleteRemoveLinks(c *testing.T) {
  1147  	// Windows does not support links
  1148  	testRequires(c, DaemonIsLinux)
  1149  	out, _ := dockerCmd(c, "run", "-d", "--name", "tlink1", "busybox", "top")
  1150  
  1151  	id := strings.TrimSpace(out)
  1152  	assert.NilError(c, waitRun(id))
  1153  
  1154  	out, _ = dockerCmd(c, "run", "--link", "tlink1:tlink1", "--name", "tlink2", "-d", "busybox", "top")
  1155  
  1156  	id2 := strings.TrimSpace(out)
  1157  	assert.Assert(c, waitRun(id2) == nil)
  1158  
  1159  	links := inspectFieldJSON(c, id2, "HostConfig.Links")
  1160  	assert.Equal(c, links, "[\"/tlink1:/tlink2/tlink1\"]", "expected to have links between containers")
  1161  
  1162  	removeOptions := types.ContainerRemoveOptions{
  1163  		RemoveLinks: true,
  1164  	}
  1165  
  1166  	cli, err := client.NewClientWithOpts(client.FromEnv)
  1167  	assert.NilError(c, err)
  1168  	defer cli.Close()
  1169  
  1170  	err = cli.ContainerRemove(context.Background(), "tlink2/tlink1", removeOptions)
  1171  	assert.NilError(c, err)
  1172  
  1173  	linksPostRm := inspectFieldJSON(c, id2, "HostConfig.Links")
  1174  	assert.Equal(c, linksPostRm, "null", "call to api deleteContainer links should have removed the specified links")
  1175  }
  1176  
  1177  func (s *DockerSuite) TestContainerAPIDeleteConflict(c *testing.T) {
  1178  	out := runSleepingContainer(c)
  1179  
  1180  	id := strings.TrimSpace(out)
  1181  	assert.NilError(c, waitRun(id))
  1182  
  1183  	cli, err := client.NewClientWithOpts(client.FromEnv)
  1184  	assert.NilError(c, err)
  1185  	defer cli.Close()
  1186  
  1187  	err = cli.ContainerRemove(context.Background(), id, types.ContainerRemoveOptions{})
  1188  	expected := "cannot remove a running container"
  1189  	assert.ErrorContains(c, err, expected)
  1190  }
  1191  
  1192  func (s *DockerSuite) TestContainerAPIDeleteRemoveVolume(c *testing.T) {
  1193  	testRequires(c, testEnv.IsLocalDaemon)
  1194  
  1195  	vol := "/testvolume"
  1196  	if testEnv.OSType == "windows" {
  1197  		vol = `c:\testvolume`
  1198  	}
  1199  
  1200  	out := runSleepingContainer(c, "-v", vol)
  1201  
  1202  	id := strings.TrimSpace(out)
  1203  	assert.NilError(c, waitRun(id))
  1204  
  1205  	source, err := inspectMountSourceField(id, vol)
  1206  	assert.NilError(c, err)
  1207  	_, err = os.Stat(source)
  1208  	assert.NilError(c, err)
  1209  
  1210  	removeOptions := types.ContainerRemoveOptions{
  1211  		Force:         true,
  1212  		RemoveVolumes: true,
  1213  	}
  1214  
  1215  	cli, err := client.NewClientWithOpts(client.FromEnv)
  1216  	assert.NilError(c, err)
  1217  	defer cli.Close()
  1218  
  1219  	err = cli.ContainerRemove(context.Background(), id, removeOptions)
  1220  	assert.NilError(c, err)
  1221  
  1222  	_, err = os.Stat(source)
  1223  	assert.Assert(c, os.IsNotExist(err), "expected to get ErrNotExist error, got %v", err)
  1224  }
  1225  
  1226  // Regression test for https://github.com/docker/docker/issues/6231
  1227  func (s *DockerSuite) TestContainerAPIChunkedEncoding(c *testing.T) {
  1228  
  1229  	config := map[string]interface{}{
  1230  		"Image":     "busybox",
  1231  		"Cmd":       append([]string{"/bin/sh", "-c"}, sleepCommandForDaemonPlatform()...),
  1232  		"OpenStdin": true,
  1233  	}
  1234  
  1235  	resp, _, err := request.Post("/containers/create", request.JSONBody(config), request.With(func(req *http.Request) error {
  1236  		// This is a cheat to make the http request do chunked encoding
  1237  		// Otherwise (just setting the Content-Encoding to chunked) net/http will overwrite
  1238  		// https://golang.org/src/pkg/net/http/request.go?s=11980:12172
  1239  		req.ContentLength = -1
  1240  		return nil
  1241  	}))
  1242  	assert.Assert(c, err == nil, "error creating container with chunked encoding")
  1243  	defer resp.Body.Close()
  1244  	assert.Equal(c, resp.StatusCode, http.StatusCreated)
  1245  }
  1246  
  1247  func (s *DockerSuite) TestContainerAPIPostContainerStop(c *testing.T) {
  1248  	out := runSleepingContainer(c)
  1249  
  1250  	containerID := strings.TrimSpace(out)
  1251  	assert.Assert(c, waitRun(containerID) == nil)
  1252  
  1253  	cli, err := client.NewClientWithOpts(client.FromEnv)
  1254  	assert.NilError(c, err)
  1255  	defer cli.Close()
  1256  
  1257  	err = cli.ContainerStop(context.Background(), containerID, nil)
  1258  	assert.NilError(c, err)
  1259  	assert.Assert(c, waitInspect(containerID, "{{ .State.Running  }}", "false", 60*time.Second) == nil)
  1260  }
  1261  
  1262  // #14170
  1263  func (s *DockerSuite) TestPostContainerAPICreateWithStringOrSliceEntrypoint(c *testing.T) {
  1264  	config := containertypes.Config{
  1265  		Image:      "busybox",
  1266  		Entrypoint: []string{"echo"},
  1267  		Cmd:        []string{"hello", "world"},
  1268  	}
  1269  
  1270  	cli, err := client.NewClientWithOpts(client.FromEnv)
  1271  	assert.NilError(c, err)
  1272  	defer cli.Close()
  1273  
  1274  	_, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, "echotest")
  1275  	assert.NilError(c, err)
  1276  	out, _ := dockerCmd(c, "start", "-a", "echotest")
  1277  	assert.Equal(c, strings.TrimSpace(out), "hello world")
  1278  
  1279  	config2 := struct {
  1280  		Image      string
  1281  		Entrypoint string
  1282  		Cmd        []string
  1283  	}{"busybox", "echo", []string{"hello", "world"}}
  1284  	_, _, err = request.Post("/containers/create?name=echotest2", request.JSONBody(config2))
  1285  	assert.NilError(c, err)
  1286  	out, _ = dockerCmd(c, "start", "-a", "echotest2")
  1287  	assert.Equal(c, strings.TrimSpace(out), "hello world")
  1288  }
  1289  
  1290  // #14170
  1291  func (s *DockerSuite) TestPostContainersCreateWithStringOrSliceCmd(c *testing.T) {
  1292  	config := containertypes.Config{
  1293  		Image: "busybox",
  1294  		Cmd:   []string{"echo", "hello", "world"},
  1295  	}
  1296  
  1297  	cli, err := client.NewClientWithOpts(client.FromEnv)
  1298  	assert.NilError(c, err)
  1299  	defer cli.Close()
  1300  
  1301  	_, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, "echotest")
  1302  	assert.NilError(c, err)
  1303  	out, _ := dockerCmd(c, "start", "-a", "echotest")
  1304  	assert.Equal(c, strings.TrimSpace(out), "hello world")
  1305  
  1306  	config2 := struct {
  1307  		Image      string
  1308  		Entrypoint string
  1309  		Cmd        string
  1310  	}{"busybox", "echo", "hello world"}
  1311  	_, _, err = request.Post("/containers/create?name=echotest2", request.JSONBody(config2))
  1312  	assert.NilError(c, err)
  1313  	out, _ = dockerCmd(c, "start", "-a", "echotest2")
  1314  	assert.Equal(c, strings.TrimSpace(out), "hello world")
  1315  }
  1316  
  1317  // regression #14318
  1318  // for backward compatibility testing with and without CAP_ prefix
  1319  // and with upper and lowercase
  1320  func (s *DockerSuite) TestPostContainersCreateWithStringOrSliceCapAddDrop(c *testing.T) {
  1321  	// Windows doesn't support CapAdd/CapDrop
  1322  	testRequires(c, DaemonIsLinux)
  1323  	config := struct {
  1324  		Image   string
  1325  		CapAdd  string
  1326  		CapDrop string
  1327  	}{"busybox", "NET_ADMIN", "cap_sys_admin"}
  1328  	res, _, err := request.Post("/containers/create?name=capaddtest0", request.JSONBody(config))
  1329  	assert.NilError(c, err)
  1330  	assert.Equal(c, res.StatusCode, http.StatusCreated)
  1331  
  1332  	config2 := containertypes.Config{
  1333  		Image: "busybox",
  1334  	}
  1335  	hostConfig := containertypes.HostConfig{
  1336  		CapAdd:  []string{"net_admin", "SYS_ADMIN"},
  1337  		CapDrop: []string{"SETGID", "CAP_SETPCAP"},
  1338  	}
  1339  
  1340  	cli, err := client.NewClientWithOpts(client.FromEnv)
  1341  	assert.NilError(c, err)
  1342  	defer cli.Close()
  1343  
  1344  	_, err = cli.ContainerCreate(context.Background(), &config2, &hostConfig, &networktypes.NetworkingConfig{}, nil, "capaddtest1")
  1345  	assert.NilError(c, err)
  1346  }
  1347  
  1348  // #14915
  1349  func (s *DockerSuite) TestContainerAPICreateNoHostConfig118(c *testing.T) {
  1350  	testRequires(c, DaemonIsLinux) // Windows only support 1.25 or later
  1351  	config := containertypes.Config{
  1352  		Image: "busybox",
  1353  	}
  1354  
  1355  	cli, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("v1.18"))
  1356  	assert.NilError(c, err)
  1357  
  1358  	_, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, "")
  1359  	assert.NilError(c, err)
  1360  }
  1361  
  1362  // Ensure an error occurs when you have a container read-only rootfs but you
  1363  // extract an archive to a symlink in a writable volume which points to a
  1364  // directory outside of the volume.
  1365  func (s *DockerSuite) TestPutContainerArchiveErrSymlinkInVolumeToReadOnlyRootfs(c *testing.T) {
  1366  	// Windows does not support read-only rootfs
  1367  	// Requires local volume mount bind.
  1368  	// --read-only + userns has remount issues
  1369  	testRequires(c, testEnv.IsLocalDaemon, NotUserNamespace, DaemonIsLinux)
  1370  
  1371  	testVol := getTestDir(c, "test-put-container-archive-err-symlink-in-volume-to-read-only-rootfs-")
  1372  	defer os.RemoveAll(testVol)
  1373  
  1374  	makeTestContentInDir(c, testVol)
  1375  
  1376  	cID := makeTestContainer(c, testContainerOptions{
  1377  		readOnly: true,
  1378  		volumes:  defaultVolumes(testVol), // Our bind mount is at /vol2
  1379  	})
  1380  
  1381  	// Attempt to extract to a symlink in the volume which points to a
  1382  	// directory outside the volume. This should cause an error because the
  1383  	// rootfs is read-only.
  1384  	cli, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("v1.20"))
  1385  	assert.NilError(c, err)
  1386  
  1387  	err = cli.CopyToContainer(context.Background(), cID, "/vol2/symlinkToAbsDir", nil, types.CopyToContainerOptions{})
  1388  	assert.ErrorContains(c, err, "container rootfs is marked read-only")
  1389  }
  1390  
  1391  func (s *DockerSuite) TestPostContainersCreateWithWrongCpusetValues(c *testing.T) {
  1392  	// Not supported on Windows
  1393  	testRequires(c, DaemonIsLinux)
  1394  
  1395  	cli, err := client.NewClientWithOpts(client.FromEnv)
  1396  	assert.NilError(c, err)
  1397  	defer cli.Close()
  1398  
  1399  	config := containertypes.Config{
  1400  		Image: "busybox",
  1401  	}
  1402  	hostConfig1 := containertypes.HostConfig{
  1403  		Resources: containertypes.Resources{
  1404  			CpusetCpus: "1-42,,",
  1405  		},
  1406  	}
  1407  	name := "wrong-cpuset-cpus"
  1408  
  1409  	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig1, &networktypes.NetworkingConfig{}, nil, name)
  1410  	expected := "Invalid value 1-42,, for cpuset cpus"
  1411  	assert.ErrorContains(c, err, expected)
  1412  
  1413  	hostConfig2 := containertypes.HostConfig{
  1414  		Resources: containertypes.Resources{
  1415  			CpusetMems: "42-3,1--",
  1416  		},
  1417  	}
  1418  	name = "wrong-cpuset-mems"
  1419  	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig2, &networktypes.NetworkingConfig{}, nil, name)
  1420  	expected = "Invalid value 42-3,1-- for cpuset mems"
  1421  	assert.ErrorContains(c, err, expected)
  1422  }
  1423  
  1424  func (s *DockerSuite) TestPostContainersCreateShmSizeNegative(c *testing.T) {
  1425  	// ShmSize is not supported on Windows
  1426  	testRequires(c, DaemonIsLinux)
  1427  	config := containertypes.Config{
  1428  		Image: "busybox",
  1429  	}
  1430  	hostConfig := containertypes.HostConfig{
  1431  		ShmSize: -1,
  1432  	}
  1433  
  1434  	cli, err := client.NewClientWithOpts(client.FromEnv)
  1435  	assert.NilError(c, err)
  1436  	defer cli.Close()
  1437  
  1438  	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, nil, "")
  1439  	assert.ErrorContains(c, err, "SHM size can not be less than 0")
  1440  }
  1441  
  1442  func (s *DockerSuite) TestPostContainersCreateShmSizeHostConfigOmitted(c *testing.T) {
  1443  	// ShmSize is not supported on Windows
  1444  	testRequires(c, DaemonIsLinux)
  1445  	var defaultSHMSize int64 = 67108864
  1446  	config := containertypes.Config{
  1447  		Image: "busybox",
  1448  		Cmd:   []string{"mount"},
  1449  	}
  1450  
  1451  	cli, err := client.NewClientWithOpts(client.FromEnv)
  1452  	assert.NilError(c, err)
  1453  	defer cli.Close()
  1454  
  1455  	container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, "")
  1456  	assert.NilError(c, err)
  1457  
  1458  	containerJSON, err := cli.ContainerInspect(context.Background(), container.ID)
  1459  	assert.NilError(c, err)
  1460  
  1461  	assert.Equal(c, containerJSON.HostConfig.ShmSize, defaultSHMSize)
  1462  
  1463  	out, _ := dockerCmd(c, "start", "-i", containerJSON.ID)
  1464  	shmRegexp := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=65536k`)
  1465  	if !shmRegexp.MatchString(out) {
  1466  		c.Fatalf("Expected shm of 64MB in mount command, got %v", out)
  1467  	}
  1468  }
  1469  
  1470  func (s *DockerSuite) TestPostContainersCreateShmSizeOmitted(c *testing.T) {
  1471  	// ShmSize is not supported on Windows
  1472  	testRequires(c, DaemonIsLinux)
  1473  	config := containertypes.Config{
  1474  		Image: "busybox",
  1475  		Cmd:   []string{"mount"},
  1476  	}
  1477  
  1478  	cli, err := client.NewClientWithOpts(client.FromEnv)
  1479  	assert.NilError(c, err)
  1480  	defer cli.Close()
  1481  
  1482  	container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, "")
  1483  	assert.NilError(c, err)
  1484  
  1485  	containerJSON, err := cli.ContainerInspect(context.Background(), container.ID)
  1486  	assert.NilError(c, err)
  1487  
  1488  	assert.Equal(c, containerJSON.HostConfig.ShmSize, int64(67108864))
  1489  
  1490  	out, _ := dockerCmd(c, "start", "-i", containerJSON.ID)
  1491  	shmRegexp := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=65536k`)
  1492  	if !shmRegexp.MatchString(out) {
  1493  		c.Fatalf("Expected shm of 64MB in mount command, got %v", out)
  1494  	}
  1495  }
  1496  
  1497  func (s *DockerSuite) TestPostContainersCreateWithShmSize(c *testing.T) {
  1498  	// ShmSize is not supported on Windows
  1499  	testRequires(c, DaemonIsLinux)
  1500  	config := containertypes.Config{
  1501  		Image: "busybox",
  1502  		Cmd:   []string{"mount"},
  1503  	}
  1504  
  1505  	hostConfig := containertypes.HostConfig{
  1506  		ShmSize: 1073741824,
  1507  	}
  1508  
  1509  	cli, err := client.NewClientWithOpts(client.FromEnv)
  1510  	assert.NilError(c, err)
  1511  	defer cli.Close()
  1512  
  1513  	container, err := cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, nil, "")
  1514  	assert.NilError(c, err)
  1515  
  1516  	containerJSON, err := cli.ContainerInspect(context.Background(), container.ID)
  1517  	assert.NilError(c, err)
  1518  
  1519  	assert.Equal(c, containerJSON.HostConfig.ShmSize, int64(1073741824))
  1520  
  1521  	out, _ := dockerCmd(c, "start", "-i", containerJSON.ID)
  1522  	shmRegex := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=1048576k`)
  1523  	if !shmRegex.MatchString(out) {
  1524  		c.Fatalf("Expected shm of 1GB in mount command, got %v", out)
  1525  	}
  1526  }
  1527  
  1528  func (s *DockerSuite) TestPostContainersCreateMemorySwappinessHostConfigOmitted(c *testing.T) {
  1529  	// Swappiness is not supported on Windows
  1530  	testRequires(c, DaemonIsLinux)
  1531  	config := containertypes.Config{
  1532  		Image: "busybox",
  1533  	}
  1534  
  1535  	cli, err := client.NewClientWithOpts(client.FromEnv)
  1536  	assert.NilError(c, err)
  1537  	defer cli.Close()
  1538  
  1539  	container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, "")
  1540  	assert.NilError(c, err)
  1541  
  1542  	containerJSON, err := cli.ContainerInspect(context.Background(), container.ID)
  1543  	assert.NilError(c, err)
  1544  
  1545  	if versions.LessThan(testEnv.DaemonAPIVersion(), "1.31") {
  1546  		assert.Equal(c, *containerJSON.HostConfig.MemorySwappiness, int64(-1))
  1547  	} else {
  1548  		assert.Assert(c, containerJSON.HostConfig.MemorySwappiness == nil)
  1549  	}
  1550  }
  1551  
  1552  // check validation is done daemon side and not only in cli
  1553  func (s *DockerSuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *testing.T) {
  1554  	// OomScoreAdj is not supported on Windows
  1555  	testRequires(c, DaemonIsLinux)
  1556  
  1557  	config := containertypes.Config{
  1558  		Image: "busybox",
  1559  	}
  1560  
  1561  	hostConfig := containertypes.HostConfig{
  1562  		OomScoreAdj: 1001,
  1563  	}
  1564  
  1565  	cli, err := client.NewClientWithOpts(client.FromEnv)
  1566  	assert.NilError(c, err)
  1567  	defer cli.Close()
  1568  
  1569  	name := "oomscoreadj-over"
  1570  	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, nil, name)
  1571  
  1572  	expected := "Invalid value 1001, range for oom score adj is [-1000, 1000]"
  1573  	assert.ErrorContains(c, err, expected)
  1574  
  1575  	hostConfig = containertypes.HostConfig{
  1576  		OomScoreAdj: -1001,
  1577  	}
  1578  
  1579  	name = "oomscoreadj-low"
  1580  	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, nil, name)
  1581  
  1582  	expected = "Invalid value -1001, range for oom score adj is [-1000, 1000]"
  1583  	assert.ErrorContains(c, err, expected)
  1584  }
  1585  
  1586  // test case for #22210 where an empty container name caused panic.
  1587  func (s *DockerSuite) TestContainerAPIDeleteWithEmptyName(c *testing.T) {
  1588  	cli, err := client.NewClientWithOpts(client.FromEnv)
  1589  	assert.NilError(c, err)
  1590  	defer cli.Close()
  1591  
  1592  	err = cli.ContainerRemove(context.Background(), "", types.ContainerRemoveOptions{})
  1593  	assert.ErrorContains(c, err, "No such container")
  1594  }
  1595  
  1596  func (s *DockerSuite) TestContainerAPIStatsWithNetworkDisabled(c *testing.T) {
  1597  	// Problematic on Windows as Windows does not support stats
  1598  	testRequires(c, DaemonIsLinux)
  1599  
  1600  	name := "testing-network-disabled"
  1601  
  1602  	config := containertypes.Config{
  1603  		Image:           "busybox",
  1604  		Cmd:             []string{"top"},
  1605  		NetworkDisabled: true,
  1606  	}
  1607  
  1608  	cli, err := client.NewClientWithOpts(client.FromEnv)
  1609  	assert.NilError(c, err)
  1610  	defer cli.Close()
  1611  
  1612  	_, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, name)
  1613  	assert.NilError(c, err)
  1614  
  1615  	err = cli.ContainerStart(context.Background(), name, types.ContainerStartOptions{})
  1616  	assert.NilError(c, err)
  1617  
  1618  	assert.Assert(c, waitRun(name) == nil)
  1619  
  1620  	type b struct {
  1621  		stats types.ContainerStats
  1622  		err   error
  1623  	}
  1624  	bc := make(chan b, 1)
  1625  	go func() {
  1626  		stats, err := cli.ContainerStats(context.Background(), name, false)
  1627  		bc <- b{stats, err}
  1628  	}()
  1629  
  1630  	// allow some time to stream the stats from the container
  1631  	time.Sleep(4 * time.Second)
  1632  	dockerCmd(c, "rm", "-f", name)
  1633  
  1634  	// collect the results from the stats stream or timeout and fail
  1635  	// if the stream was not disconnected.
  1636  	select {
  1637  	case <-time.After(2 * time.Second):
  1638  		c.Fatal("stream was not closed after container was removed")
  1639  	case sr := <-bc:
  1640  		assert.Assert(c, sr.err == nil)
  1641  		sr.stats.Body.Close()
  1642  	}
  1643  }
  1644  
  1645  func (s *DockerSuite) TestContainersAPICreateMountsValidation(c *testing.T) {
  1646  	type testCase struct {
  1647  		config     containertypes.Config
  1648  		hostConfig containertypes.HostConfig
  1649  		msg        string
  1650  	}
  1651  
  1652  	prefix, slash := getPrefixAndSlashFromDaemonPlatform()
  1653  	destPath := prefix + slash + "foo"
  1654  	notExistPath := prefix + slash + "notexist"
  1655  
  1656  	cases := []testCase{
  1657  		{
  1658  			config: containertypes.Config{
  1659  				Image: "busybox",
  1660  			},
  1661  			hostConfig: containertypes.HostConfig{
  1662  				Mounts: []mounttypes.Mount{{
  1663  					Type:   "notreal",
  1664  					Target: destPath,
  1665  				},
  1666  				},
  1667  			},
  1668  
  1669  			msg: "mount type unknown",
  1670  		},
  1671  		{
  1672  			config: containertypes.Config{
  1673  				Image: "busybox",
  1674  			},
  1675  			hostConfig: containertypes.HostConfig{
  1676  				Mounts: []mounttypes.Mount{{
  1677  					Type: "bind"}}},
  1678  			msg: "Target must not be empty",
  1679  		},
  1680  		{
  1681  			config: containertypes.Config{
  1682  				Image: "busybox",
  1683  			},
  1684  			hostConfig: containertypes.HostConfig{
  1685  				Mounts: []mounttypes.Mount{{
  1686  					Type:   "bind",
  1687  					Target: destPath}}},
  1688  			msg: "Source must not be empty",
  1689  		},
  1690  		{
  1691  			config: containertypes.Config{
  1692  				Image: "busybox",
  1693  			},
  1694  			hostConfig: containertypes.HostConfig{
  1695  				Mounts: []mounttypes.Mount{{
  1696  					Type:   "bind",
  1697  					Source: notExistPath,
  1698  					Target: destPath}}},
  1699  			msg: "source path does not exist",
  1700  			// FIXME(vdemeester) fails into e2e, migrate to integration/container anyway
  1701  			// msg: "source path does not exist: " + notExistPath,
  1702  		},
  1703  		{
  1704  			config: containertypes.Config{
  1705  				Image: "busybox",
  1706  			},
  1707  			hostConfig: containertypes.HostConfig{
  1708  				Mounts: []mounttypes.Mount{{
  1709  					Type: "volume"}}},
  1710  			msg: "Target must not be empty",
  1711  		},
  1712  		{
  1713  			config: containertypes.Config{
  1714  				Image: "busybox",
  1715  			},
  1716  			hostConfig: containertypes.HostConfig{
  1717  				Mounts: []mounttypes.Mount{{
  1718  					Type:   "volume",
  1719  					Source: "hello",
  1720  					Target: destPath}}},
  1721  			msg: "",
  1722  		},
  1723  		{
  1724  			config: containertypes.Config{
  1725  				Image: "busybox",
  1726  			},
  1727  			hostConfig: containertypes.HostConfig{
  1728  				Mounts: []mounttypes.Mount{{
  1729  					Type:   "volume",
  1730  					Source: "hello2",
  1731  					Target: destPath,
  1732  					VolumeOptions: &mounttypes.VolumeOptions{
  1733  						DriverConfig: &mounttypes.Driver{
  1734  							Name: "local"}}}}},
  1735  			msg: "",
  1736  		},
  1737  	}
  1738  
  1739  	if testEnv.IsLocalDaemon() {
  1740  		tmpDir, err := ioutils.TempDir("", "test-mounts-api")
  1741  		assert.NilError(c, err)
  1742  		defer os.RemoveAll(tmpDir)
  1743  		cases = append(cases, []testCase{
  1744  			{
  1745  				config: containertypes.Config{
  1746  					Image: "busybox",
  1747  				},
  1748  				hostConfig: containertypes.HostConfig{
  1749  					Mounts: []mounttypes.Mount{{
  1750  						Type:   "bind",
  1751  						Source: tmpDir,
  1752  						Target: destPath}}},
  1753  				msg: "",
  1754  			},
  1755  			{
  1756  				config: containertypes.Config{
  1757  					Image: "busybox",
  1758  				},
  1759  				hostConfig: containertypes.HostConfig{
  1760  					Mounts: []mounttypes.Mount{{
  1761  						Type:          "bind",
  1762  						Source:        tmpDir,
  1763  						Target:        destPath,
  1764  						VolumeOptions: &mounttypes.VolumeOptions{}}}},
  1765  				msg: "VolumeOptions must not be specified",
  1766  			},
  1767  		}...)
  1768  	}
  1769  
  1770  	if DaemonIsWindows() {
  1771  		cases = append(cases, []testCase{
  1772  			{
  1773  				config: containertypes.Config{
  1774  					Image: "busybox",
  1775  				},
  1776  				hostConfig: containertypes.HostConfig{
  1777  					Mounts: []mounttypes.Mount{
  1778  						{
  1779  							Type:   "volume",
  1780  							Source: "not-supported-on-windows",
  1781  							Target: destPath,
  1782  							VolumeOptions: &mounttypes.VolumeOptions{
  1783  								DriverConfig: &mounttypes.Driver{
  1784  									Name:    "local",
  1785  									Options: map[string]string{"type": "tmpfs"},
  1786  								},
  1787  							},
  1788  						},
  1789  					},
  1790  				},
  1791  				msg: `options are not supported on this platform`,
  1792  			},
  1793  		}...)
  1794  	}
  1795  
  1796  	if DaemonIsLinux() {
  1797  		cases = append(cases, []testCase{
  1798  			{
  1799  				config: containertypes.Config{
  1800  					Image: "busybox",
  1801  				},
  1802  				hostConfig: containertypes.HostConfig{
  1803  					Mounts: []mounttypes.Mount{
  1804  						{
  1805  							Type:   "volume",
  1806  							Source: "missing-device-opt",
  1807  							Target: destPath,
  1808  							VolumeOptions: &mounttypes.VolumeOptions{
  1809  								DriverConfig: &mounttypes.Driver{
  1810  									Name:    "local",
  1811  									Options: map[string]string{"foobar": "foobaz"},
  1812  								},
  1813  							},
  1814  						},
  1815  					},
  1816  				},
  1817  				msg: `invalid option: "foobar"`,
  1818  			},
  1819  			{
  1820  				config: containertypes.Config{
  1821  					Image: "busybox",
  1822  				},
  1823  				hostConfig: containertypes.HostConfig{
  1824  					Mounts: []mounttypes.Mount{
  1825  						{
  1826  							Type:   "volume",
  1827  							Source: "missing-device-opt",
  1828  							Target: destPath,
  1829  							VolumeOptions: &mounttypes.VolumeOptions{
  1830  								DriverConfig: &mounttypes.Driver{
  1831  									Name:    "local",
  1832  									Options: map[string]string{"type": "tmpfs"},
  1833  								},
  1834  							},
  1835  						},
  1836  					},
  1837  				},
  1838  				msg: `missing required option: "device"`,
  1839  			},
  1840  			{
  1841  				config: containertypes.Config{
  1842  					Image: "busybox",
  1843  				},
  1844  				hostConfig: containertypes.HostConfig{
  1845  					Mounts: []mounttypes.Mount{
  1846  						{
  1847  							Type:   "volume",
  1848  							Source: "missing-type-opt",
  1849  							Target: destPath,
  1850  							VolumeOptions: &mounttypes.VolumeOptions{
  1851  								DriverConfig: &mounttypes.Driver{
  1852  									Name:    "local",
  1853  									Options: map[string]string{"device": "tmpfs"},
  1854  								},
  1855  							},
  1856  						},
  1857  					},
  1858  				},
  1859  				msg: `missing required option: "type"`,
  1860  			},
  1861  			{
  1862  				config: containertypes.Config{
  1863  					Image: "busybox",
  1864  				},
  1865  				hostConfig: containertypes.HostConfig{
  1866  					Mounts: []mounttypes.Mount{
  1867  						{
  1868  							Type:   "volume",
  1869  							Source: "hello4",
  1870  							Target: destPath,
  1871  							VolumeOptions: &mounttypes.VolumeOptions{
  1872  								DriverConfig: &mounttypes.Driver{
  1873  									Name:    "local",
  1874  									Options: map[string]string{"o": "size=1", "type": "tmpfs", "device": "tmpfs"},
  1875  								},
  1876  							},
  1877  						},
  1878  					},
  1879  				},
  1880  				msg: "",
  1881  			},
  1882  			{
  1883  				config: containertypes.Config{
  1884  					Image: "busybox",
  1885  				},
  1886  				hostConfig: containertypes.HostConfig{
  1887  					Mounts: []mounttypes.Mount{{
  1888  						Type:   "tmpfs",
  1889  						Target: destPath}}},
  1890  				msg: "",
  1891  			},
  1892  			{
  1893  				config: containertypes.Config{
  1894  					Image: "busybox",
  1895  				},
  1896  				hostConfig: containertypes.HostConfig{
  1897  					Mounts: []mounttypes.Mount{{
  1898  						Type:   "tmpfs",
  1899  						Target: destPath,
  1900  						TmpfsOptions: &mounttypes.TmpfsOptions{
  1901  							SizeBytes: 4096 * 1024,
  1902  							Mode:      0700,
  1903  						}}}},
  1904  				msg: "",
  1905  			},
  1906  			{
  1907  				config: containertypes.Config{
  1908  					Image: "busybox",
  1909  				},
  1910  				hostConfig: containertypes.HostConfig{
  1911  					Mounts: []mounttypes.Mount{{
  1912  						Type:   "tmpfs",
  1913  						Source: "/shouldnotbespecified",
  1914  						Target: destPath}}},
  1915  				msg: "Source must not be specified",
  1916  			},
  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, &networktypes.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 *DockerSuite) 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 := ioutil.TempDir("", "test-mounts-api-bind")
  1944  	assert.NilError(c, err)
  1945  	defer os.RemoveAll(tmpDir)
  1946  	err = ioutil.WriteFile(filepath.Join(tmpDir, "bar"), []byte("hello"), 0666)
  1947  	assert.NilError(c, err)
  1948  	config := containertypes.Config{
  1949  		Image: "busybox",
  1950  		Cmd:   []string{"/bin/sh", "-c", "cat /foo/bar"},
  1951  	}
  1952  	hostConfig := containertypes.HostConfig{
  1953  		Mounts: []mounttypes.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, &networktypes.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 *DockerSuite) 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     mounttypes.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:     mounttypes.Mount{Type: "volume", Target: destPath},
  2007  			expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  2008  		},
  2009  		{
  2010  			spec:     mounttypes.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:     mounttypes.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:     mounttypes.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:     mounttypes.Mount{Type: "volume", Target: destPath, Source: "test3", VolumeOptions: &mounttypes.VolumeOptions{DriverConfig: &mounttypes.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 := ioutil.TempDir("", "test-mounts-api-1")
  2030  		assert.NilError(c, err)
  2031  		defer os.RemoveAll(tmpDir1)
  2032  		cases = append(cases, []testCase{
  2033  			{
  2034  				spec: mounttypes.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:     mounttypes.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:     mounttypes.Mount{Type: "bind", Source: tmpDir3, Target: destPath},
  2063  					expected: types.MountPoint{Type: "bind", RW: true, Destination: destPath, Source: tmpDir3},
  2064  				},
  2065  				{
  2066  					spec:     mounttypes.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:     mounttypes.Mount{Type: "bind", Source: tmpDir3, Target: destPath, ReadOnly: true, BindOptions: &mounttypes.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:     mounttypes.Mount{Type: "volume", Target: destPath, VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true}},
  2081  				expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  2082  			},
  2083  			{
  2084  				spec:     mounttypes.Mount{Type: "volume", Target: destPath + slash, VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true}},
  2085  				expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  2086  			},
  2087  			{
  2088  				spec:     mounttypes.Mount{Type: "volume", Target: destPath, Source: "test4", VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true}},
  2089  				expected: types.MountPoint{Type: "volume", Name: "test4", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  2090  			},
  2091  			{
  2092  				spec:     mounttypes.Mount{Type: "volume", Target: destPath, Source: "test5", ReadOnly: true, VolumeOptions: &mounttypes.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  				&containertypes.Config{Image: testImg},
  2106  				&containertypes.HostConfig{Mounts: []mounttypes.Mount{x.spec}},
  2107  				&networktypes.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  
  2147  			// Named volumes still exist after the container is removed
  2148  			case x.spec.Type == "volume" && len(x.spec.Source) > 0:
  2149  				_, err := apiclient.VolumeInspect(ctx, mountPoint.Name)
  2150  				assert.NilError(c, err)
  2151  
  2152  			// Bind mounts are never removed with the container
  2153  			case x.spec.Type == "bind":
  2154  
  2155  			// anonymous volumes are removed
  2156  			default:
  2157  				_, err := apiclient.VolumeInspect(ctx, mountPoint.Name)
  2158  				assert.Check(c, client.IsErrNotFound(err))
  2159  			}
  2160  		})
  2161  	}
  2162  }
  2163  
  2164  func containerExit(apiclient client.APIClient, name string) func(poll.LogT) poll.Result {
  2165  	return func(logT poll.LogT) poll.Result {
  2166  		container, err := apiclient.ContainerInspect(context.Background(), name)
  2167  		if err != nil {
  2168  			return poll.Error(err)
  2169  		}
  2170  		switch container.State.Status {
  2171  		case "created", "running":
  2172  			return poll.Continue("container %s is %s, waiting for exit", name, container.State.Status)
  2173  		}
  2174  		return poll.Success()
  2175  	}
  2176  }
  2177  
  2178  func (s *DockerSuite) TestContainersAPICreateMountsTmpfs(c *testing.T) {
  2179  	testRequires(c, DaemonIsLinux)
  2180  	type testCase struct {
  2181  		cfg             mounttypes.Mount
  2182  		expectedOptions []string
  2183  	}
  2184  	target := "/foo"
  2185  	cases := []testCase{
  2186  		{
  2187  			cfg: mounttypes.Mount{
  2188  				Type:   "tmpfs",
  2189  				Target: target},
  2190  			expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime"},
  2191  		},
  2192  		{
  2193  			cfg: mounttypes.Mount{
  2194  				Type:   "tmpfs",
  2195  				Target: target,
  2196  				TmpfsOptions: &mounttypes.TmpfsOptions{
  2197  					SizeBytes: 4096 * 1024, Mode: 0700}},
  2198  			expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime", "size=4096k", "mode=700"},
  2199  		},
  2200  	}
  2201  
  2202  	cli, err := client.NewClientWithOpts(client.FromEnv)
  2203  	assert.NilError(c, err)
  2204  	defer cli.Close()
  2205  
  2206  	config := containertypes.Config{
  2207  		Image: "busybox",
  2208  		Cmd:   []string{"/bin/sh", "-c", fmt.Sprintf("mount | grep 'tmpfs on %s'", target)},
  2209  	}
  2210  	for i, x := range cases {
  2211  		cName := fmt.Sprintf("test-tmpfs-%d", i)
  2212  		hostConfig := containertypes.HostConfig{
  2213  			Mounts: []mounttypes.Mount{x.cfg},
  2214  		}
  2215  
  2216  		_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, nil, cName)
  2217  		assert.NilError(c, err)
  2218  		out, _ := dockerCmd(c, "start", "-a", cName)
  2219  		for _, option := range x.expectedOptions {
  2220  			assert.Assert(c, strings.Contains(out, option))
  2221  		}
  2222  	}
  2223  }
  2224  
  2225  // Regression test for #33334
  2226  // Makes sure that when a container which has a custom stop signal + restart=always
  2227  // gets killed (with SIGKILL) by the kill API, that the restart policy is cancelled.
  2228  func (s *DockerSuite) TestContainerKillCustomStopSignal(c *testing.T) {
  2229  	id := strings.TrimSpace(runSleepingContainer(c, "--stop-signal=SIGTERM", "--restart=always"))
  2230  	res, _, err := request.Post("/containers/" + id + "/kill")
  2231  	assert.NilError(c, err)
  2232  	defer res.Body.Close()
  2233  
  2234  	b, err := ioutil.ReadAll(res.Body)
  2235  	assert.NilError(c, err)
  2236  	assert.Equal(c, res.StatusCode, http.StatusNoContent, string(b))
  2237  	err = waitInspect(id, "{{.State.Running}} {{.State.Restarting}}", "false false", 30*time.Second)
  2238  	assert.NilError(c, err)
  2239  }