github.com/rawahars/moby@v24.0.4+incompatible/integration-cli/docker_api_containers_test.go (about)

     1  package main
     2  
     3  import (
     4  	"archive/tar"
     5  	"bytes"
     6  	"context"
     7  	"encoding/json"
     8  	"fmt"
     9  	"io"
    10  	"net/http"
    11  	"os"
    12  	"path/filepath"
    13  	"regexp"
    14  	"runtime"
    15  	"strings"
    16  	"testing"
    17  	"time"
    18  
    19  	"github.com/docker/docker/api/types"
    20  	"github.com/docker/docker/api/types/container"
    21  	"github.com/docker/docker/api/types/mount"
    22  	"github.com/docker/docker/api/types/network"
    23  	"github.com/docker/docker/api/types/versions"
    24  	"github.com/docker/docker/client"
    25  	dconfig "github.com/docker/docker/daemon/config"
    26  	"github.com/docker/docker/errdefs"
    27  	"github.com/docker/docker/integration-cli/cli"
    28  	"github.com/docker/docker/integration-cli/cli/build"
    29  	"github.com/docker/docker/pkg/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 *DockerAPISuite) TestContainerAPIGetAll(c *testing.T) {
    39  	startCount := getContainerCount(c)
    40  	name := "getall"
    41  	dockerCmd(c, "run", "--name", name, "busybox", "true")
    42  
    43  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
    44  	assert.NilError(c, err)
    45  	defer apiClient.Close()
    46  
    47  	options := types.ContainerListOptions{
    48  		All: true,
    49  	}
    50  	containers, err := apiClient.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 *DockerAPISuite) TestContainerAPIGetJSONNoFieldsOmitted(c *testing.T) {
    59  	startCount := getContainerCount(c)
    60  	dockerCmd(c, "run", "busybox", "true")
    61  
    62  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
    63  	assert.NilError(c, err)
    64  	defer apiClient.Close()
    65  
    66  	options := types.ContainerListOptions{
    67  		All: true,
    68  	}
    69  	containers, err := apiClient.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 *DockerAPISuite) 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  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   105  	assert.NilError(c, err)
   106  	defer apiClient.Close()
   107  
   108  	body, err := apiClient.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 *DockerAPISuite) 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  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   132  	assert.NilError(c, err)
   133  	defer apiClient.Close()
   134  
   135  	changes, err := apiClient.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 *DockerAPISuite) 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  		apiClient, err := client.NewClientWithOpts(client.FromEnv)
   162  		assert.NilError(c, err)
   163  		defer apiClient.Close()
   164  
   165  		stats, err := apiClient.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 *DockerAPISuite) 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  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   196  	assert.NilError(c, err)
   197  	defer apiClient.Close()
   198  
   199  	stats, err := apiClient.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 *DockerAPISuite) 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  		apiClient, err := client.NewClientWithOpts(client.FromEnv)
   267  		assert.NilError(c, err)
   268  		defer apiClient.Close()
   269  
   270  		stats, err := apiClient.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 := io.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 *DockerAPISuite) 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  		apiClient, err := client.NewClientWithOpts(client.FromEnv)
   309  		assert.NilError(c, err)
   310  		defer apiClient.Close()
   311  
   312  		stats, err := apiClient.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 := io.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 *DockerAPISuite) 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  		apiClient, err := client.NewClientWithOpts(client.FromEnv)
   346  		assert.NilError(c, err)
   347  		defer apiClient.Close()
   348  
   349  		resp, err := apiClient.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 *DockerAPISuite) 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  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   375  	assert.NilError(c, err)
   376  	defer apiClient.Close()
   377  
   378  	err = apiClient.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 = apiClient.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 *DockerAPISuite) 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  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   401  	assert.NilError(c, err)
   402  	defer apiClient.Close()
   403  
   404  	// sort by comm[andline] to make sure order stays the same in case of PID rollover
   405  	top, err := apiClient.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 *DockerAPISuite) 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  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   424  	assert.NilError(c, err)
   425  	defer apiClient.Close()
   426  
   427  	top, err := apiClient.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 *DockerAPISuite) TestContainerAPICommit(c *testing.T) {
   449  	cName := "testapicommit"
   450  	dockerCmd(c, "run", "--name="+cName, "busybox", "/bin/sh", "-c", "touch /test")
   451  
   452  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   453  	assert.NilError(c, err)
   454  	defer apiClient.Close()
   455  
   456  	options := types.ContainerCommitOptions{
   457  		Reference: "testcontainerapicommit:testtag",
   458  	}
   459  
   460  	img, err := apiClient.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 *DockerAPISuite) TestContainerAPICommitWithLabelInConfig(c *testing.T) {
   471  	cName := "testapicommitwithconfig"
   472  	dockerCmd(c, "run", "--name="+cName, "busybox", "/bin/sh", "-c", "touch /test")
   473  
   474  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   475  	assert.NilError(c, err)
   476  	defer apiClient.Close()
   477  
   478  	config := container.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 := apiClient.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 *DockerAPISuite) TestContainerAPIBadPort(c *testing.T) {
   503  	// TODO Windows to Windows CI - Port this test
   504  	testRequires(c, DaemonIsLinux)
   505  
   506  	config := container.Config{
   507  		Image: "busybox",
   508  		Cmd:   []string{"/bin/sh", "-c", "echo test"},
   509  	}
   510  
   511  	hostConfig := container.HostConfig{
   512  		PortBindings: nat.PortMap{
   513  			"8080/tcp": []nat.PortBinding{
   514  				{
   515  					HostIP:   "",
   516  					HostPort: "aa80"},
   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(context.Background(), &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(context.Background(), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "")
   540  	assert.NilError(c, err)
   541  
   542  	out, _ := dockerCmd(c, "start", "-a", ctr.ID)
   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(context.Background(), &container.Config{}, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "")
   552  
   553  	expected := "No command specified"
   554  	assert.ErrorContains(c, err, expected)
   555  }
   556  
   557  func (s *DockerAPISuite) TestContainerAPICreateMultipleNetworksConfig(c *testing.T) {
   558  	// Container creation must fail if client specified configurations for more than one network
   559  	config := container.Config{
   560  		Image: "busybox",
   561  	}
   562  
   563  	networkingConfig := network.NetworkingConfig{
   564  		EndpointsConfig: map[string]*network.EndpointSettings{
   565  			"net1": {},
   566  			"net2": {},
   567  			"net3": {},
   568  		},
   569  	}
   570  
   571  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   572  	assert.NilError(c, err)
   573  	defer apiClient.Close()
   574  
   575  	_, err = apiClient.ContainerCreate(context.Background(), &config, &container.HostConfig{}, &networkingConfig, nil, "")
   576  	msg := err.Error()
   577  	// network name order in error message is not deterministic
   578  	assert.Assert(c, strings.Contains(msg, "Container cannot be connected to network endpoints"))
   579  	assert.Assert(c, strings.Contains(msg, "net1"))
   580  	assert.Assert(c, strings.Contains(msg, "net2"))
   581  	assert.Assert(c, strings.Contains(msg, "net3"))
   582  }
   583  
   584  func (s *DockerAPISuite) TestContainerAPICreateBridgeNetworkMode(c *testing.T) {
   585  	// Windows does not support bridge
   586  	testRequires(c, DaemonIsLinux)
   587  	UtilCreateNetworkMode(c, "bridge")
   588  }
   589  
   590  func (s *DockerAPISuite) TestContainerAPICreateOtherNetworkModes(c *testing.T) {
   591  	// Windows does not support these network modes
   592  	testRequires(c, DaemonIsLinux, NotUserNamespace)
   593  	UtilCreateNetworkMode(c, "host")
   594  	UtilCreateNetworkMode(c, "container:web1")
   595  }
   596  
   597  func UtilCreateNetworkMode(c *testing.T, networkMode container.NetworkMode) {
   598  	config := container.Config{
   599  		Image: "busybox",
   600  	}
   601  
   602  	hostConfig := container.HostConfig{
   603  		NetworkMode: networkMode,
   604  	}
   605  
   606  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   607  	assert.NilError(c, err)
   608  	defer apiClient.Close()
   609  
   610  	ctr, err := apiClient.ContainerCreate(context.Background(), &config, &hostConfig, &network.NetworkingConfig{}, nil, "")
   611  	assert.NilError(c, err)
   612  
   613  	containerJSON, err := apiClient.ContainerInspect(context.Background(), ctr.ID)
   614  	assert.NilError(c, err)
   615  
   616  	assert.Equal(c, containerJSON.HostConfig.NetworkMode, networkMode, "Mismatched NetworkMode")
   617  }
   618  
   619  func (s *DockerAPISuite) TestContainerAPICreateWithCpuSharesCpuset(c *testing.T) {
   620  	// TODO Windows to Windows CI. The CpuShares part could be ported.
   621  	testRequires(c, DaemonIsLinux)
   622  	config := container.Config{
   623  		Image: "busybox",
   624  	}
   625  
   626  	hostConfig := container.HostConfig{
   627  		Resources: container.Resources{
   628  			CPUShares:  512,
   629  			CpusetCpus: "0",
   630  		},
   631  	}
   632  
   633  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   634  	assert.NilError(c, err)
   635  	defer apiClient.Close()
   636  
   637  	ctr, err := apiClient.ContainerCreate(context.Background(), &config, &hostConfig, &network.NetworkingConfig{}, nil, "")
   638  	assert.NilError(c, err)
   639  
   640  	containerJSON, err := apiClient.ContainerInspect(context.Background(), ctr.ID)
   641  	assert.NilError(c, err)
   642  
   643  	out := inspectField(c, containerJSON.ID, "HostConfig.CpuShares")
   644  	assert.Equal(c, out, "512")
   645  
   646  	outCpuset := inspectField(c, containerJSON.ID, "HostConfig.CpusetCpus")
   647  	assert.Equal(c, outCpuset, "0")
   648  }
   649  
   650  func (s *DockerAPISuite) TestContainerAPIVerifyHeader(c *testing.T) {
   651  	config := map[string]interface{}{
   652  		"Image": "busybox",
   653  	}
   654  
   655  	create := func(ct string) (*http.Response, io.ReadCloser, error) {
   656  		jsonData := bytes.NewBuffer(nil)
   657  		assert.Assert(c, json.NewEncoder(jsonData).Encode(config) == nil)
   658  		return request.Post("/containers/create", request.RawContent(io.NopCloser(jsonData)), request.ContentType(ct))
   659  	}
   660  
   661  	// Try with no content-type
   662  	res, body, err := create("")
   663  	assert.NilError(c, err)
   664  	// todo: we need to figure out a better way to compare between dockerd versions
   665  	// comparing between daemon API version is not precise.
   666  	if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") {
   667  		assert.Equal(c, res.StatusCode, http.StatusBadRequest)
   668  	} else {
   669  		assert.Assert(c, res.StatusCode != http.StatusOK)
   670  	}
   671  	body.Close()
   672  
   673  	// Try with wrong content-type
   674  	res, body, err = create("application/xml")
   675  	assert.NilError(c, err)
   676  	if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") {
   677  		assert.Equal(c, res.StatusCode, http.StatusBadRequest)
   678  	} else {
   679  		assert.Assert(c, res.StatusCode != http.StatusOK)
   680  	}
   681  	body.Close()
   682  
   683  	// now application/json
   684  	res, body, err = create("application/json")
   685  	assert.NilError(c, err)
   686  	assert.Equal(c, res.StatusCode, http.StatusCreated)
   687  	body.Close()
   688  }
   689  
   690  // Issue 14230. daemon should return 500 for invalid port syntax
   691  func (s *DockerAPISuite) TestContainerAPIInvalidPortSyntax(c *testing.T) {
   692  	config := `{
   693  				  "Image": "busybox",
   694  				  "HostConfig": {
   695  					"NetworkMode": "default",
   696  					"PortBindings": {
   697  					  "19039;1230": [
   698  						{}
   699  					  ]
   700  					}
   701  				  }
   702  				}`
   703  
   704  	res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON)
   705  	assert.NilError(c, err)
   706  	if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") {
   707  		assert.Equal(c, res.StatusCode, http.StatusBadRequest)
   708  	} else {
   709  		assert.Assert(c, res.StatusCode != http.StatusOK)
   710  	}
   711  
   712  	b, err := request.ReadBody(body)
   713  	assert.NilError(c, err)
   714  	assert.Assert(c, strings.Contains(string(b[:]), "invalid port"))
   715  }
   716  
   717  func (s *DockerAPISuite) TestContainerAPIRestartPolicyInvalidPolicyName(c *testing.T) {
   718  	config := `{
   719  		"Image": "busybox",
   720  		"HostConfig": {
   721  			"RestartPolicy": {
   722  				"Name": "something",
   723  				"MaximumRetryCount": 0
   724  			}
   725  		}
   726  	}`
   727  
   728  	res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON)
   729  	assert.NilError(c, err)
   730  	if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") {
   731  		assert.Equal(c, res.StatusCode, http.StatusBadRequest)
   732  	} else {
   733  		assert.Assert(c, res.StatusCode != http.StatusOK)
   734  	}
   735  
   736  	b, err := request.ReadBody(body)
   737  	assert.NilError(c, err)
   738  	assert.Assert(c, strings.Contains(string(b[:]), "invalid restart policy"))
   739  }
   740  
   741  func (s *DockerAPISuite) TestContainerAPIRestartPolicyRetryMismatch(c *testing.T) {
   742  	config := `{
   743  		"Image": "busybox",
   744  		"HostConfig": {
   745  			"RestartPolicy": {
   746  				"Name": "always",
   747  				"MaximumRetryCount": 2
   748  			}
   749  		}
   750  	}`
   751  
   752  	res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON)
   753  	assert.NilError(c, err)
   754  	if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") {
   755  		assert.Equal(c, res.StatusCode, http.StatusBadRequest)
   756  	} else {
   757  		assert.Assert(c, res.StatusCode != http.StatusOK)
   758  	}
   759  
   760  	b, err := request.ReadBody(body)
   761  	assert.NilError(c, err)
   762  	assert.Assert(c, strings.Contains(string(b[:]), "maximum retry count cannot be used with restart policy"))
   763  }
   764  
   765  func (s *DockerAPISuite) TestContainerAPIRestartPolicyNegativeRetryCount(c *testing.T) {
   766  	config := `{
   767  		"Image": "busybox",
   768  		"HostConfig": {
   769  			"RestartPolicy": {
   770  				"Name": "on-failure",
   771  				"MaximumRetryCount": -2
   772  			}
   773  		}
   774  	}`
   775  
   776  	res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON)
   777  	assert.NilError(c, err)
   778  	if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") {
   779  		assert.Equal(c, res.StatusCode, http.StatusBadRequest)
   780  	} else {
   781  		assert.Assert(c, res.StatusCode != http.StatusOK)
   782  	}
   783  
   784  	b, err := request.ReadBody(body)
   785  	assert.NilError(c, err)
   786  	assert.Assert(c, strings.Contains(string(b[:]), "maximum retry count cannot be negative"))
   787  }
   788  
   789  func (s *DockerAPISuite) TestContainerAPIRestartPolicyDefaultRetryCount(c *testing.T) {
   790  	config := `{
   791  		"Image": "busybox",
   792  		"HostConfig": {
   793  			"RestartPolicy": {
   794  				"Name": "on-failure",
   795  				"MaximumRetryCount": 0
   796  			}
   797  		}
   798  	}`
   799  
   800  	res, _, err := request.Post("/containers/create", request.RawString(config), request.JSON)
   801  	assert.NilError(c, err)
   802  	assert.Equal(c, res.StatusCode, http.StatusCreated)
   803  }
   804  
   805  // Issue 7941 - test to make sure a "null" in JSON is just ignored.
   806  // W/o this fix a null in JSON would be parsed into a string var as "null"
   807  func (s *DockerAPISuite) TestContainerAPIPostCreateNull(c *testing.T) {
   808  	config := `{
   809  		"Hostname":"",
   810  		"Domainname":"",
   811  		"Memory":0,
   812  		"MemorySwap":0,
   813  		"CpuShares":0,
   814  		"Cpuset":null,
   815  		"AttachStdin":true,
   816  		"AttachStdout":true,
   817  		"AttachStderr":true,
   818  		"ExposedPorts":{},
   819  		"Tty":true,
   820  		"OpenStdin":true,
   821  		"StdinOnce":true,
   822  		"Env":[],
   823  		"Cmd":"ls",
   824  		"Image":"busybox",
   825  		"Volumes":{},
   826  		"WorkingDir":"",
   827  		"Entrypoint":null,
   828  		"NetworkDisabled":false,
   829  		"OnBuild":null}`
   830  
   831  	res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON)
   832  	assert.NilError(c, err)
   833  	assert.Equal(c, res.StatusCode, http.StatusCreated)
   834  
   835  	b, err := request.ReadBody(body)
   836  	assert.NilError(c, err)
   837  	type createResp struct {
   838  		ID string
   839  	}
   840  	var ctr createResp
   841  	assert.Assert(c, json.Unmarshal(b, &ctr) == nil)
   842  	out := inspectField(c, ctr.ID, "HostConfig.CpusetCpus")
   843  	assert.Equal(c, out, "")
   844  
   845  	outMemory := inspectField(c, ctr.ID, "HostConfig.Memory")
   846  	assert.Equal(c, outMemory, "0")
   847  	outMemorySwap := inspectField(c, ctr.ID, "HostConfig.MemorySwap")
   848  	assert.Equal(c, outMemorySwap, "0")
   849  }
   850  
   851  func (s *DockerAPISuite) TestCreateWithTooLowMemoryLimit(c *testing.T) {
   852  	// TODO Windows: Port once memory is supported
   853  	testRequires(c, DaemonIsLinux)
   854  	config := `{
   855  		"Image":     "busybox",
   856  		"Cmd":       "ls",
   857  		"OpenStdin": true,
   858  		"CpuShares": 100,
   859  		"Memory":    524287
   860  	}`
   861  
   862  	res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON)
   863  	assert.NilError(c, err)
   864  	b, err2 := request.ReadBody(body)
   865  	assert.Assert(c, err2 == nil)
   866  
   867  	if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") {
   868  		assert.Equal(c, res.StatusCode, http.StatusBadRequest)
   869  	} else {
   870  		assert.Assert(c, res.StatusCode != http.StatusOK)
   871  	}
   872  	assert.Assert(c, strings.Contains(string(b), "Minimum memory limit allowed is 6MB"))
   873  }
   874  
   875  func (s *DockerAPISuite) TestContainerAPIRename(c *testing.T) {
   876  	out, _ := dockerCmd(c, "run", "--name", "TestContainerAPIRename", "-d", "busybox", "sh")
   877  
   878  	containerID := strings.TrimSpace(out)
   879  	newName := "TestContainerAPIRenameNew"
   880  
   881  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   882  	assert.NilError(c, err)
   883  	defer apiClient.Close()
   884  
   885  	err = apiClient.ContainerRename(context.Background(), containerID, newName)
   886  	assert.NilError(c, err)
   887  
   888  	name := inspectField(c, containerID, "Name")
   889  	assert.Equal(c, name, "/"+newName, "Failed to rename container")
   890  }
   891  
   892  func (s *DockerAPISuite) TestContainerAPIKill(c *testing.T) {
   893  	name := "test-api-kill"
   894  	runSleepingContainer(c, "-i", "--name", name)
   895  
   896  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   897  	assert.NilError(c, err)
   898  	defer apiClient.Close()
   899  
   900  	err = apiClient.ContainerKill(context.Background(), name, "SIGKILL")
   901  	assert.NilError(c, err)
   902  
   903  	state := inspectField(c, name, "State.Running")
   904  	assert.Equal(c, state, "false", fmt.Sprintf("got wrong State from container %s: %q", name, state))
   905  }
   906  
   907  func (s *DockerAPISuite) TestContainerAPIRestart(c *testing.T) {
   908  	name := "test-api-restart"
   909  	runSleepingContainer(c, "-di", "--name", name)
   910  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   911  	assert.NilError(c, err)
   912  	defer apiClient.Close()
   913  
   914  	timeout := 1
   915  	err = apiClient.ContainerRestart(context.Background(), name, container.StopOptions{Timeout: &timeout})
   916  	assert.NilError(c, err)
   917  
   918  	assert.Assert(c, waitInspect(name, "{{ .State.Restarting  }} {{ .State.Running  }}", "false true", 15*time.Second) == nil)
   919  }
   920  
   921  func (s *DockerAPISuite) TestContainerAPIRestartNotimeoutParam(c *testing.T) {
   922  	name := "test-api-restart-no-timeout-param"
   923  	out := runSleepingContainer(c, "-di", "--name", name)
   924  	id := strings.TrimSpace(out)
   925  	assert.NilError(c, waitRun(id))
   926  
   927  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   928  	assert.NilError(c, err)
   929  	defer apiClient.Close()
   930  
   931  	err = apiClient.ContainerRestart(context.Background(), name, container.StopOptions{})
   932  	assert.NilError(c, err)
   933  
   934  	assert.Assert(c, waitInspect(name, "{{ .State.Restarting  }} {{ .State.Running  }}", "false true", 15*time.Second) == nil)
   935  }
   936  
   937  func (s *DockerAPISuite) TestContainerAPIStart(c *testing.T) {
   938  	name := "testing-start"
   939  	config := container.Config{
   940  		Image:     "busybox",
   941  		Cmd:       append([]string{"/bin/sh", "-c"}, sleepCommandForDaemonPlatform()...),
   942  		OpenStdin: true,
   943  	}
   944  
   945  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   946  	assert.NilError(c, err)
   947  	defer apiClient.Close()
   948  
   949  	_, err = apiClient.ContainerCreate(context.Background(), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, name)
   950  	assert.NilError(c, err)
   951  
   952  	err = apiClient.ContainerStart(context.Background(), name, types.ContainerStartOptions{})
   953  	assert.NilError(c, err)
   954  
   955  	// second call to start should give 304
   956  	// maybe add ContainerStartWithRaw to test it
   957  	err = apiClient.ContainerStart(context.Background(), name, types.ContainerStartOptions{})
   958  	assert.NilError(c, err)
   959  
   960  	// TODO(tibor): figure out why this doesn't work on windows
   961  }
   962  
   963  func (s *DockerAPISuite) TestContainerAPIStop(c *testing.T) {
   964  	name := "test-api-stop"
   965  	runSleepingContainer(c, "-i", "--name", name)
   966  	timeout := 30
   967  
   968  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   969  	assert.NilError(c, err)
   970  	defer apiClient.Close()
   971  
   972  	err = apiClient.ContainerStop(context.Background(), name, container.StopOptions{
   973  		Timeout: &timeout,
   974  	})
   975  	assert.NilError(c, err)
   976  	assert.Assert(c, waitInspect(name, "{{ .State.Running  }}", "false", 60*time.Second) == nil)
   977  
   978  	// second call to start should give 304
   979  	// maybe add ContainerStartWithRaw to test it
   980  	err = apiClient.ContainerStop(context.Background(), name, container.StopOptions{
   981  		Timeout: &timeout,
   982  	})
   983  	assert.NilError(c, err)
   984  }
   985  
   986  func (s *DockerAPISuite) TestContainerAPIWait(c *testing.T) {
   987  	name := "test-api-wait"
   988  
   989  	sleepCmd := "/bin/sleep"
   990  	if testEnv.OSType == "windows" {
   991  		sleepCmd = "sleep"
   992  	}
   993  	dockerCmd(c, "run", "--name", name, "busybox", sleepCmd, "2")
   994  
   995  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   996  	assert.NilError(c, err)
   997  	defer apiClient.Close()
   998  
   999  	waitResC, errC := apiClient.ContainerWait(context.Background(), name, "")
  1000  
  1001  	select {
  1002  	case err = <-errC:
  1003  		assert.NilError(c, err)
  1004  	case waitRes := <-waitResC:
  1005  		assert.Equal(c, waitRes.StatusCode, int64(0))
  1006  	}
  1007  }
  1008  
  1009  func (s *DockerAPISuite) TestContainerAPICopyNotExistsAnyMore(c *testing.T) {
  1010  	name := "test-container-api-copy"
  1011  	dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt")
  1012  
  1013  	postData := types.CopyConfig{
  1014  		Resource: "/test.txt",
  1015  	}
  1016  	// no copy in client/
  1017  	res, _, err := request.Post("/containers/"+name+"/copy", request.JSONBody(postData))
  1018  	assert.NilError(c, err)
  1019  	assert.Equal(c, res.StatusCode, http.StatusNotFound)
  1020  }
  1021  
  1022  func (s *DockerAPISuite) TestContainerAPICopyPre124(c *testing.T) {
  1023  	testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
  1024  	name := "test-container-api-copy"
  1025  	dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt")
  1026  
  1027  	postData := types.CopyConfig{
  1028  		Resource: "/test.txt",
  1029  	}
  1030  
  1031  	res, body, err := request.Post("/v1.23/containers/"+name+"/copy", request.JSONBody(postData))
  1032  	assert.NilError(c, err)
  1033  	assert.Equal(c, res.StatusCode, http.StatusOK)
  1034  
  1035  	found := false
  1036  	for tarReader := tar.NewReader(body); ; {
  1037  		h, err := tarReader.Next()
  1038  		if err != nil {
  1039  			if err == io.EOF {
  1040  				break
  1041  			}
  1042  			c.Fatal(err)
  1043  		}
  1044  		if h.Name == "test.txt" {
  1045  			found = true
  1046  			break
  1047  		}
  1048  	}
  1049  	assert.Assert(c, found)
  1050  }
  1051  
  1052  func (s *DockerAPISuite) TestContainerAPICopyResourcePathEmptyPre124(c *testing.T) {
  1053  	testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
  1054  	name := "test-container-api-copy-resource-empty"
  1055  	dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt")
  1056  
  1057  	postData := types.CopyConfig{
  1058  		Resource: "",
  1059  	}
  1060  
  1061  	res, body, err := request.Post("/v1.23/containers/"+name+"/copy", request.JSONBody(postData))
  1062  	assert.NilError(c, err)
  1063  	if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") {
  1064  		assert.Equal(c, res.StatusCode, http.StatusBadRequest)
  1065  	} else {
  1066  		assert.Assert(c, res.StatusCode != http.StatusOK)
  1067  	}
  1068  	b, err := request.ReadBody(body)
  1069  	assert.NilError(c, err)
  1070  	assert.Assert(c, is.Regexp("^Path cannot be empty\n$", string(b)))
  1071  }
  1072  
  1073  func (s *DockerAPISuite) TestContainerAPICopyResourcePathNotFoundPre124(c *testing.T) {
  1074  	testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
  1075  	name := "test-container-api-copy-resource-not-found"
  1076  	dockerCmd(c, "run", "--name", name, "busybox")
  1077  
  1078  	postData := types.CopyConfig{
  1079  		Resource: "/notexist",
  1080  	}
  1081  
  1082  	res, body, err := request.Post("/v1.23/containers/"+name+"/copy", request.JSONBody(postData))
  1083  	assert.NilError(c, err)
  1084  	if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") {
  1085  		assert.Equal(c, res.StatusCode, http.StatusInternalServerError)
  1086  	} else {
  1087  		assert.Equal(c, res.StatusCode, http.StatusNotFound)
  1088  	}
  1089  	b, err := request.ReadBody(body)
  1090  	assert.NilError(c, err)
  1091  	assert.Assert(c, is.Regexp("^Could not find the file /notexist in container "+name+"\n$", string(b)))
  1092  }
  1093  
  1094  func (s *DockerAPISuite) TestContainerAPICopyContainerNotFoundPr124(c *testing.T) {
  1095  	testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
  1096  	postData := types.CopyConfig{
  1097  		Resource: "/something",
  1098  	}
  1099  
  1100  	res, _, err := request.Post("/v1.23/containers/notexists/copy", request.JSONBody(postData))
  1101  	assert.NilError(c, err)
  1102  	assert.Equal(c, res.StatusCode, http.StatusNotFound)
  1103  }
  1104  
  1105  func (s *DockerAPISuite) TestContainerAPIDelete(c *testing.T) {
  1106  	out := runSleepingContainer(c)
  1107  
  1108  	id := strings.TrimSpace(out)
  1109  	assert.NilError(c, waitRun(id))
  1110  
  1111  	dockerCmd(c, "stop", id)
  1112  
  1113  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1114  	assert.NilError(c, err)
  1115  	defer apiClient.Close()
  1116  
  1117  	err = apiClient.ContainerRemove(context.Background(), id, types.ContainerRemoveOptions{})
  1118  	assert.NilError(c, err)
  1119  }
  1120  
  1121  func (s *DockerAPISuite) TestContainerAPIDeleteNotExist(c *testing.T) {
  1122  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1123  	assert.NilError(c, err)
  1124  	defer apiClient.Close()
  1125  
  1126  	err = apiClient.ContainerRemove(context.Background(), "doesnotexist", types.ContainerRemoveOptions{})
  1127  	assert.ErrorContains(c, err, "No such container: doesnotexist")
  1128  }
  1129  
  1130  func (s *DockerAPISuite) TestContainerAPIDeleteForce(c *testing.T) {
  1131  	out := runSleepingContainer(c)
  1132  	id := strings.TrimSpace(out)
  1133  	assert.NilError(c, waitRun(id))
  1134  
  1135  	removeOptions := types.ContainerRemoveOptions{
  1136  		Force: true,
  1137  	}
  1138  
  1139  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1140  	assert.NilError(c, err)
  1141  	defer apiClient.Close()
  1142  
  1143  	err = apiClient.ContainerRemove(context.Background(), id, removeOptions)
  1144  	assert.NilError(c, err)
  1145  }
  1146  
  1147  func (s *DockerAPISuite) TestContainerAPIDeleteRemoveLinks(c *testing.T) {
  1148  	// Windows does not support links
  1149  	testRequires(c, DaemonIsLinux)
  1150  	out, _ := dockerCmd(c, "run", "-d", "--name", "tlink1", "busybox", "top")
  1151  
  1152  	id := strings.TrimSpace(out)
  1153  	assert.NilError(c, waitRun(id))
  1154  
  1155  	out, _ = dockerCmd(c, "run", "--link", "tlink1:tlink1", "--name", "tlink2", "-d", "busybox", "top")
  1156  
  1157  	id2 := strings.TrimSpace(out)
  1158  	assert.Assert(c, waitRun(id2) == nil)
  1159  
  1160  	links := inspectFieldJSON(c, id2, "HostConfig.Links")
  1161  	assert.Equal(c, links, "[\"/tlink1:/tlink2/tlink1\"]", "expected to have links between containers")
  1162  
  1163  	removeOptions := types.ContainerRemoveOptions{
  1164  		RemoveLinks: true,
  1165  	}
  1166  
  1167  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1168  	assert.NilError(c, err)
  1169  	defer apiClient.Close()
  1170  
  1171  	err = apiClient.ContainerRemove(context.Background(), "tlink2/tlink1", removeOptions)
  1172  	assert.NilError(c, err)
  1173  
  1174  	linksPostRm := inspectFieldJSON(c, id2, "HostConfig.Links")
  1175  	assert.Equal(c, linksPostRm, "null", "call to api deleteContainer links should have removed the specified links")
  1176  }
  1177  
  1178  func (s *DockerAPISuite) TestContainerAPIDeleteConflict(c *testing.T) {
  1179  	out := runSleepingContainer(c)
  1180  
  1181  	id := strings.TrimSpace(out)
  1182  	assert.NilError(c, waitRun(id))
  1183  
  1184  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1185  	assert.NilError(c, err)
  1186  	defer apiClient.Close()
  1187  
  1188  	err = apiClient.ContainerRemove(context.Background(), id, types.ContainerRemoveOptions{})
  1189  	expected := "cannot remove a running container"
  1190  	assert.ErrorContains(c, err, expected)
  1191  }
  1192  
  1193  func (s *DockerAPISuite) TestContainerAPIDeleteRemoveVolume(c *testing.T) {
  1194  	testRequires(c, testEnv.IsLocalDaemon)
  1195  
  1196  	vol := "/testvolume"
  1197  	if testEnv.OSType == "windows" {
  1198  		vol = `c:\testvolume`
  1199  	}
  1200  
  1201  	out := runSleepingContainer(c, "-v", vol)
  1202  
  1203  	id := strings.TrimSpace(out)
  1204  	assert.NilError(c, waitRun(id))
  1205  
  1206  	source, err := inspectMountSourceField(id, vol)
  1207  	assert.NilError(c, err)
  1208  	_, err = os.Stat(source)
  1209  	assert.NilError(c, err)
  1210  
  1211  	removeOptions := types.ContainerRemoveOptions{
  1212  		Force:         true,
  1213  		RemoveVolumes: true,
  1214  	}
  1215  
  1216  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1217  	assert.NilError(c, err)
  1218  	defer apiClient.Close()
  1219  
  1220  	err = apiClient.ContainerRemove(context.Background(), id, removeOptions)
  1221  	assert.NilError(c, err)
  1222  
  1223  	_, err = os.Stat(source)
  1224  	assert.Assert(c, os.IsNotExist(err), "expected to get ErrNotExist error, got %v", err)
  1225  }
  1226  
  1227  // Regression test for https://github.com/docker/docker/issues/6231
  1228  func (s *DockerAPISuite) TestContainerAPIChunkedEncoding(c *testing.T) {
  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 *DockerAPISuite) TestContainerAPIPostContainerStop(c *testing.T) {
  1248  	out := runSleepingContainer(c)
  1249  
  1250  	containerID := strings.TrimSpace(out)
  1251  	assert.Assert(c, waitRun(containerID) == nil)
  1252  
  1253  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1254  	assert.NilError(c, err)
  1255  	defer apiClient.Close()
  1256  
  1257  	err = apiClient.ContainerStop(context.Background(), containerID, container.StopOptions{})
  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 *DockerAPISuite) TestPostContainerAPICreateWithStringOrSliceEntrypoint(c *testing.T) {
  1264  	config := container.Config{
  1265  		Image:      "busybox",
  1266  		Entrypoint: []string{"echo"},
  1267  		Cmd:        []string{"hello", "world"},
  1268  	}
  1269  
  1270  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1271  	assert.NilError(c, err)
  1272  	defer apiClient.Close()
  1273  
  1274  	_, err = apiClient.ContainerCreate(context.Background(), &config, &container.HostConfig{}, &network.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 *DockerAPISuite) TestPostContainersCreateWithStringOrSliceCmd(c *testing.T) {
  1292  	config := container.Config{
  1293  		Image: "busybox",
  1294  		Cmd:   []string{"echo", "hello", "world"},
  1295  	}
  1296  
  1297  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1298  	assert.NilError(c, err)
  1299  	defer apiClient.Close()
  1300  
  1301  	_, err = apiClient.ContainerCreate(context.Background(), &config, &container.HostConfig{}, &network.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 *DockerAPISuite) 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 := container.Config{
  1333  		Image: "busybox",
  1334  	}
  1335  	hostConfig := container.HostConfig{
  1336  		CapAdd:  []string{"net_admin", "SYS_ADMIN"},
  1337  		CapDrop: []string{"SETGID", "CAP_SETPCAP"},
  1338  	}
  1339  
  1340  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1341  	assert.NilError(c, err)
  1342  	defer apiClient.Close()
  1343  
  1344  	_, err = apiClient.ContainerCreate(context.Background(), &config2, &hostConfig, &network.NetworkingConfig{}, nil, "capaddtest1")
  1345  	assert.NilError(c, err)
  1346  }
  1347  
  1348  // #14915
  1349  func (s *DockerAPISuite) TestContainerAPICreateNoHostConfig118(c *testing.T) {
  1350  	testRequires(c, DaemonIsLinux) // Windows only support 1.25 or later
  1351  	config := container.Config{
  1352  		Image: "busybox",
  1353  	}
  1354  
  1355  	apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("v1.18"))
  1356  	assert.NilError(c, err)
  1357  
  1358  	_, err = apiClient.ContainerCreate(context.Background(), &config, &container.HostConfig{}, &network.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 *DockerAPISuite) 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  	apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("v1.20"))
  1385  	assert.NilError(c, err)
  1386  
  1387  	err = apiClient.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 *DockerAPISuite) TestPostContainersCreateWithWrongCpusetValues(c *testing.T) {
  1392  	// Not supported on Windows
  1393  	testRequires(c, DaemonIsLinux)
  1394  
  1395  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1396  	assert.NilError(c, err)
  1397  	defer apiClient.Close()
  1398  
  1399  	config := container.Config{
  1400  		Image: "busybox",
  1401  	}
  1402  	hostConfig1 := container.HostConfig{
  1403  		Resources: container.Resources{
  1404  			CpusetCpus: "1-42,,",
  1405  		},
  1406  	}
  1407  	name := "wrong-cpuset-cpus"
  1408  
  1409  	_, err = apiClient.ContainerCreate(context.Background(), &config, &hostConfig1, &network.NetworkingConfig{}, nil, name)
  1410  	expected := "Invalid value 1-42,, for cpuset cpus"
  1411  	assert.ErrorContains(c, err, expected)
  1412  
  1413  	hostConfig2 := container.HostConfig{
  1414  		Resources: container.Resources{
  1415  			CpusetMems: "42-3,1--",
  1416  		},
  1417  	}
  1418  	name = "wrong-cpuset-mems"
  1419  	_, err = apiClient.ContainerCreate(context.Background(), &config, &hostConfig2, &network.NetworkingConfig{}, nil, name)
  1420  	expected = "Invalid value 42-3,1-- for cpuset mems"
  1421  	assert.ErrorContains(c, err, expected)
  1422  }
  1423  
  1424  func (s *DockerAPISuite) TestPostContainersCreateShmSizeNegative(c *testing.T) {
  1425  	// ShmSize is not supported on Windows
  1426  	testRequires(c, DaemonIsLinux)
  1427  	config := container.Config{
  1428  		Image: "busybox",
  1429  	}
  1430  	hostConfig := container.HostConfig{
  1431  		ShmSize: -1,
  1432  	}
  1433  
  1434  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1435  	assert.NilError(c, err)
  1436  	defer apiClient.Close()
  1437  
  1438  	_, err = apiClient.ContainerCreate(context.Background(), &config, &hostConfig, &network.NetworkingConfig{}, nil, "")
  1439  	assert.ErrorContains(c, err, "SHM size can not be less than 0")
  1440  }
  1441  
  1442  func (s *DockerAPISuite) TestPostContainersCreateShmSizeHostConfigOmitted(c *testing.T) {
  1443  	// ShmSize is not supported on Windows
  1444  	testRequires(c, DaemonIsLinux)
  1445  
  1446  	config := container.Config{
  1447  		Image: "busybox",
  1448  		Cmd:   []string{"mount"},
  1449  	}
  1450  
  1451  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1452  	assert.NilError(c, err)
  1453  	defer apiClient.Close()
  1454  
  1455  	ctr, err := apiClient.ContainerCreate(context.Background(), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "")
  1456  	assert.NilError(c, err)
  1457  
  1458  	containerJSON, err := apiClient.ContainerInspect(context.Background(), ctr.ID)
  1459  	assert.NilError(c, err)
  1460  
  1461  	assert.Equal(c, containerJSON.HostConfig.ShmSize, dconfig.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 *DockerAPISuite) TestPostContainersCreateShmSizeOmitted(c *testing.T) {
  1471  	// ShmSize is not supported on Windows
  1472  	testRequires(c, DaemonIsLinux)
  1473  	config := container.Config{
  1474  		Image: "busybox",
  1475  		Cmd:   []string{"mount"},
  1476  	}
  1477  
  1478  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1479  	assert.NilError(c, err)
  1480  	defer apiClient.Close()
  1481  
  1482  	ctr, err := apiClient.ContainerCreate(context.Background(), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "")
  1483  	assert.NilError(c, err)
  1484  
  1485  	containerJSON, err := apiClient.ContainerInspect(context.Background(), ctr.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 *DockerAPISuite) TestPostContainersCreateWithShmSize(c *testing.T) {
  1498  	// ShmSize is not supported on Windows
  1499  	testRequires(c, DaemonIsLinux)
  1500  	config := container.Config{
  1501  		Image: "busybox",
  1502  		Cmd:   []string{"mount"},
  1503  	}
  1504  
  1505  	hostConfig := container.HostConfig{
  1506  		ShmSize: 1073741824,
  1507  	}
  1508  
  1509  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1510  	assert.NilError(c, err)
  1511  	defer apiClient.Close()
  1512  
  1513  	ctr, err := apiClient.ContainerCreate(context.Background(), &config, &hostConfig, &network.NetworkingConfig{}, nil, "")
  1514  	assert.NilError(c, err)
  1515  
  1516  	containerJSON, err := apiClient.ContainerInspect(context.Background(), ctr.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 *DockerAPISuite) TestPostContainersCreateMemorySwappinessHostConfigOmitted(c *testing.T) {
  1529  	// Swappiness is not supported on Windows
  1530  	testRequires(c, DaemonIsLinux)
  1531  	config := container.Config{
  1532  		Image: "busybox",
  1533  	}
  1534  
  1535  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1536  	assert.NilError(c, err)
  1537  	defer apiClient.Close()
  1538  
  1539  	ctr, err := apiClient.ContainerCreate(context.Background(), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "")
  1540  	assert.NilError(c, err)
  1541  
  1542  	containerJSON, err := apiClient.ContainerInspect(context.Background(), ctr.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 *DockerAPISuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *testing.T) {
  1554  	// OomScoreAdj is not supported on Windows
  1555  	testRequires(c, DaemonIsLinux)
  1556  
  1557  	config := container.Config{
  1558  		Image: "busybox",
  1559  	}
  1560  
  1561  	hostConfig := container.HostConfig{
  1562  		OomScoreAdj: 1001,
  1563  	}
  1564  
  1565  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1566  	assert.NilError(c, err)
  1567  	defer apiClient.Close()
  1568  
  1569  	name := "oomscoreadj-over"
  1570  	_, err = apiClient.ContainerCreate(context.Background(), &config, &hostConfig, &network.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 = container.HostConfig{
  1576  		OomScoreAdj: -1001,
  1577  	}
  1578  
  1579  	name = "oomscoreadj-low"
  1580  	_, err = apiClient.ContainerCreate(context.Background(), &config, &hostConfig, &network.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 *DockerAPISuite) TestContainerAPIDeleteWithEmptyName(c *testing.T) {
  1588  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1589  	assert.NilError(c, err)
  1590  	defer apiClient.Close()
  1591  
  1592  	err = apiClient.ContainerRemove(context.Background(), "", types.ContainerRemoveOptions{})
  1593  	assert.Check(c, errdefs.IsNotFound(err))
  1594  }
  1595  
  1596  func (s *DockerAPISuite) 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 := container.Config{
  1603  		Image:           "busybox",
  1604  		Cmd:             []string{"top"},
  1605  		NetworkDisabled: true,
  1606  	}
  1607  
  1608  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1609  	assert.NilError(c, err)
  1610  	defer apiClient.Close()
  1611  
  1612  	_, err = apiClient.ContainerCreate(context.Background(), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, name)
  1613  	assert.NilError(c, err)
  1614  
  1615  	err = apiClient.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 := apiClient.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 *DockerAPISuite) TestContainersAPICreateMountsValidation(c *testing.T) {
  1646  	type testCase struct {
  1647  		config     container.Config
  1648  		hostConfig container.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: container.Config{
  1659  				Image: "busybox",
  1660  			},
  1661  			hostConfig: container.HostConfig{
  1662  				Mounts: []mount.Mount{{
  1663  					Type:   "notreal",
  1664  					Target: destPath,
  1665  				},
  1666  				},
  1667  			},
  1668  
  1669  			msg: "mount type unknown",
  1670  		},
  1671  		{
  1672  			config: container.Config{
  1673  				Image: "busybox",
  1674  			},
  1675  			hostConfig: container.HostConfig{
  1676  				Mounts: []mount.Mount{{
  1677  					Type: "bind"}}},
  1678  			msg: "Target must not be empty",
  1679  		},
  1680  		{
  1681  			config: container.Config{
  1682  				Image: "busybox",
  1683  			},
  1684  			hostConfig: container.HostConfig{
  1685  				Mounts: []mount.Mount{{
  1686  					Type:   "bind",
  1687  					Target: destPath}}},
  1688  			msg: "Source must not be empty",
  1689  		},
  1690  		{
  1691  			config: container.Config{
  1692  				Image: "busybox",
  1693  			},
  1694  			hostConfig: container.HostConfig{
  1695  				Mounts: []mount.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: container.Config{
  1705  				Image: "busybox",
  1706  			},
  1707  			hostConfig: container.HostConfig{
  1708  				Mounts: []mount.Mount{{
  1709  					Type: "volume"}}},
  1710  			msg: "Target must not be empty",
  1711  		},
  1712  		{
  1713  			config: container.Config{
  1714  				Image: "busybox",
  1715  			},
  1716  			hostConfig: container.HostConfig{
  1717  				Mounts: []mount.Mount{{
  1718  					Type:   "volume",
  1719  					Source: "hello",
  1720  					Target: destPath}}},
  1721  			msg: "",
  1722  		},
  1723  		{
  1724  			config: container.Config{
  1725  				Image: "busybox",
  1726  			},
  1727  			hostConfig: container.HostConfig{
  1728  				Mounts: []mount.Mount{{
  1729  					Type:   "volume",
  1730  					Source: "hello2",
  1731  					Target: destPath,
  1732  					VolumeOptions: &mount.VolumeOptions{
  1733  						DriverConfig: &mount.Driver{
  1734  							Name: "local"}}}}},
  1735  			msg: "",
  1736  		},
  1737  	}
  1738  
  1739  	if testEnv.IsLocalDaemon() {
  1740  		tmpDir, err := os.MkdirTemp("", "test-mounts-api")
  1741  		assert.NilError(c, err)
  1742  		defer os.RemoveAll(tmpDir)
  1743  		cases = append(cases, []testCase{
  1744  			{
  1745  				config: container.Config{
  1746  					Image: "busybox",
  1747  				},
  1748  				hostConfig: container.HostConfig{
  1749  					Mounts: []mount.Mount{{
  1750  						Type:   "bind",
  1751  						Source: tmpDir,
  1752  						Target: destPath}}},
  1753  				msg: "",
  1754  			},
  1755  			{
  1756  				config: container.Config{
  1757  					Image: "busybox",
  1758  				},
  1759  				hostConfig: container.HostConfig{
  1760  					Mounts: []mount.Mount{{
  1761  						Type:          "bind",
  1762  						Source:        tmpDir,
  1763  						Target:        destPath,
  1764  						VolumeOptions: &mount.VolumeOptions{}}}},
  1765  				msg: "VolumeOptions must not be specified",
  1766  			},
  1767  		}...)
  1768  	}
  1769  
  1770  	if DaemonIsWindows() {
  1771  		cases = append(cases, []testCase{
  1772  			{
  1773  				config: container.Config{
  1774  					Image: "busybox",
  1775  				},
  1776  				hostConfig: container.HostConfig{
  1777  					Mounts: []mount.Mount{
  1778  						{
  1779  							Type:   "volume",
  1780  							Source: "not-supported-on-windows",
  1781  							Target: destPath,
  1782  							VolumeOptions: &mount.VolumeOptions{
  1783  								DriverConfig: &mount.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: container.Config{
  1800  					Image: "busybox",
  1801  				},
  1802  				hostConfig: container.HostConfig{
  1803  					Mounts: []mount.Mount{
  1804  						{
  1805  							Type:   "volume",
  1806  							Source: "missing-device-opt",
  1807  							Target: destPath,
  1808  							VolumeOptions: &mount.VolumeOptions{
  1809  								DriverConfig: &mount.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: container.Config{
  1821  					Image: "busybox",
  1822  				},
  1823  				hostConfig: container.HostConfig{
  1824  					Mounts: []mount.Mount{
  1825  						{
  1826  							Type:   "volume",
  1827  							Source: "missing-device-opt",
  1828  							Target: destPath,
  1829  							VolumeOptions: &mount.VolumeOptions{
  1830  								DriverConfig: &mount.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: container.Config{
  1842  					Image: "busybox",
  1843  				},
  1844  				hostConfig: container.HostConfig{
  1845  					Mounts: []mount.Mount{
  1846  						{
  1847  							Type:   "volume",
  1848  							Source: "missing-type-opt",
  1849  							Target: destPath,
  1850  							VolumeOptions: &mount.VolumeOptions{
  1851  								DriverConfig: &mount.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: container.Config{
  1863  					Image: "busybox",
  1864  				},
  1865  				hostConfig: container.HostConfig{
  1866  					Mounts: []mount.Mount{
  1867  						{
  1868  							Type:   "volume",
  1869  							Source: "hello4",
  1870  							Target: destPath,
  1871  							VolumeOptions: &mount.VolumeOptions{
  1872  								DriverConfig: &mount.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: container.Config{
  1884  					Image: "busybox",
  1885  				},
  1886  				hostConfig: container.HostConfig{
  1887  					Mounts: []mount.Mount{{
  1888  						Type:   "tmpfs",
  1889  						Target: destPath}}},
  1890  				msg: "",
  1891  			},
  1892  			{
  1893  				config: container.Config{
  1894  					Image: "busybox",
  1895  				},
  1896  				hostConfig: container.HostConfig{
  1897  					Mounts: []mount.Mount{{
  1898  						Type:   "tmpfs",
  1899  						Target: destPath,
  1900  						TmpfsOptions: &mount.TmpfsOptions{
  1901  							SizeBytes: 4096 * 1024,
  1902  							Mode:      0700,
  1903  						}}}},
  1904  				msg: "",
  1905  			},
  1906  			{
  1907  				config: container.Config{
  1908  					Image: "busybox",
  1909  				},
  1910  				hostConfig: container.HostConfig{
  1911  					Mounts: []mount.Mount{{
  1912  						Type:   "tmpfs",
  1913  						Source: "/shouldnotbespecified",
  1914  						Target: destPath}}},
  1915  				msg: "Source must not be specified",
  1916  			},
  1917  		}...)
  1918  	}
  1919  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1920  	assert.NilError(c, err)
  1921  	defer apiClient.Close()
  1922  
  1923  	// TODO add checks for statuscode returned by API
  1924  	for i, x := range cases {
  1925  		x := x
  1926  		c.Run(fmt.Sprintf("case %d", i), func(c *testing.T) {
  1927  			_, err = apiClient.ContainerCreate(context.Background(), &x.config, &x.hostConfig, &network.NetworkingConfig{}, nil, "")
  1928  			if len(x.msg) > 0 {
  1929  				assert.ErrorContains(c, err, x.msg, "%v", cases[i].config)
  1930  			} else {
  1931  				assert.NilError(c, err)
  1932  			}
  1933  		})
  1934  	}
  1935  }
  1936  
  1937  func (s *DockerAPISuite) TestContainerAPICreateMountsBindRead(c *testing.T) {
  1938  	testRequires(c, NotUserNamespace, testEnv.IsLocalDaemon)
  1939  	// also with data in the host side
  1940  	prefix, slash := getPrefixAndSlashFromDaemonPlatform()
  1941  	destPath := prefix + slash + "foo"
  1942  	tmpDir, err := os.MkdirTemp("", "test-mounts-api-bind")
  1943  	assert.NilError(c, err)
  1944  	defer os.RemoveAll(tmpDir)
  1945  	err = os.WriteFile(filepath.Join(tmpDir, "bar"), []byte("hello"), 0666)
  1946  	assert.NilError(c, err)
  1947  	config := container.Config{
  1948  		Image: "busybox",
  1949  		Cmd:   []string{"/bin/sh", "-c", "cat /foo/bar"},
  1950  	}
  1951  	hostConfig := container.HostConfig{
  1952  		Mounts: []mount.Mount{
  1953  			{Type: "bind", Source: tmpDir, Target: destPath},
  1954  		},
  1955  	}
  1956  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1957  	assert.NilError(c, err)
  1958  	defer apiClient.Close()
  1959  
  1960  	_, err = apiClient.ContainerCreate(context.Background(), &config, &hostConfig, &network.NetworkingConfig{}, nil, "test")
  1961  	assert.NilError(c, err)
  1962  
  1963  	out, _ := dockerCmd(c, "start", "-a", "test")
  1964  	assert.Equal(c, out, "hello")
  1965  }
  1966  
  1967  // Test Mounts comes out as expected for the MountPoint
  1968  func (s *DockerAPISuite) TestContainersAPICreateMountsCreate(c *testing.T) {
  1969  	prefix, slash := getPrefixAndSlashFromDaemonPlatform()
  1970  	destPath := prefix + slash + "foo"
  1971  
  1972  	var (
  1973  		testImg string
  1974  	)
  1975  	if testEnv.OSType != "windows" {
  1976  		testImg = "test-mount-config"
  1977  		buildImageSuccessfully(c, testImg, build.WithDockerfile(`
  1978  	FROM busybox
  1979  	RUN mkdir `+destPath+` && touch `+destPath+slash+`bar
  1980  	CMD cat `+destPath+slash+`bar
  1981  	`))
  1982  	} else {
  1983  		testImg = "busybox"
  1984  	}
  1985  
  1986  	type testCase struct {
  1987  		spec     mount.Mount
  1988  		expected types.MountPoint
  1989  	}
  1990  
  1991  	var selinuxSharedLabel string
  1992  	// this test label was added after a bug fix in 1.32, thus add requirements min API >= 1.32
  1993  	// for the sake of making test pass in earlier versions
  1994  	// bug fixed in https://github.com/moby/moby/pull/34684
  1995  	if !versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") {
  1996  		if runtime.GOOS == "linux" {
  1997  			selinuxSharedLabel = "z"
  1998  		}
  1999  	}
  2000  
  2001  	cases := []testCase{
  2002  		// use literal strings here for `Type` instead of the defined constants in the volume package to keep this honest
  2003  		// Validation of the actual `Mount` struct is done in another test is not needed here
  2004  		{
  2005  			spec:     mount.Mount{Type: "volume", Target: destPath},
  2006  			expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  2007  		},
  2008  		{
  2009  			spec:     mount.Mount{Type: "volume", Target: destPath + slash},
  2010  			expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  2011  		},
  2012  		{
  2013  			spec:     mount.Mount{Type: "volume", Target: destPath, Source: "test1"},
  2014  			expected: types.MountPoint{Type: "volume", Name: "test1", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  2015  		},
  2016  		{
  2017  			spec:     mount.Mount{Type: "volume", Target: destPath, ReadOnly: true, Source: "test2"},
  2018  			expected: types.MountPoint{Type: "volume", Name: "test2", RW: false, Destination: destPath, Mode: selinuxSharedLabel},
  2019  		},
  2020  		{
  2021  			spec:     mount.Mount{Type: "volume", Target: destPath, Source: "test3", VolumeOptions: &mount.VolumeOptions{DriverConfig: &mount.Driver{Name: volume.DefaultDriverName}}},
  2022  			expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", Name: "test3", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  2023  		},
  2024  	}
  2025  
  2026  	if testEnv.IsLocalDaemon() {
  2027  		// setup temp dir for testing binds
  2028  		tmpDir1, err := os.MkdirTemp("", "test-mounts-api-1")
  2029  		assert.NilError(c, err)
  2030  		defer os.RemoveAll(tmpDir1)
  2031  		cases = append(cases, []testCase{
  2032  			{
  2033  				spec: mount.Mount{
  2034  					Type:   "bind",
  2035  					Source: tmpDir1,
  2036  					Target: destPath,
  2037  				},
  2038  				expected: types.MountPoint{
  2039  					Type:        "bind",
  2040  					RW:          true,
  2041  					Destination: destPath,
  2042  					Source:      tmpDir1,
  2043  				},
  2044  			},
  2045  			{
  2046  				spec:     mount.Mount{Type: "bind", Source: tmpDir1, Target: destPath, ReadOnly: true},
  2047  				expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir1},
  2048  			},
  2049  		}...)
  2050  
  2051  		// for modes only supported on Linux
  2052  		if DaemonIsLinux() {
  2053  			tmpDir3, err := os.MkdirTemp("", "test-mounts-api-3")
  2054  			assert.NilError(c, err)
  2055  			defer os.RemoveAll(tmpDir3)
  2056  
  2057  			assert.Assert(c, mountWrapper(tmpDir3, tmpDir3, "none", "bind,shared") == nil)
  2058  
  2059  			cases = append(cases, []testCase{
  2060  				{
  2061  					spec:     mount.Mount{Type: "bind", Source: tmpDir3, Target: destPath},
  2062  					expected: types.MountPoint{Type: "bind", RW: true, Destination: destPath, Source: tmpDir3},
  2063  				},
  2064  				{
  2065  					spec:     mount.Mount{Type: "bind", Source: tmpDir3, Target: destPath, ReadOnly: true},
  2066  					expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir3},
  2067  				},
  2068  				{
  2069  					spec:     mount.Mount{Type: "bind", Source: tmpDir3, Target: destPath, ReadOnly: true, BindOptions: &mount.BindOptions{Propagation: "shared"}},
  2070  					expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir3, Propagation: "shared"},
  2071  				},
  2072  			}...)
  2073  		}
  2074  	}
  2075  
  2076  	if testEnv.OSType != "windows" { // Windows does not support volume populate
  2077  		cases = append(cases, []testCase{
  2078  			{
  2079  				spec:     mount.Mount{Type: "volume", Target: destPath, VolumeOptions: &mount.VolumeOptions{NoCopy: true}},
  2080  				expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  2081  			},
  2082  			{
  2083  				spec:     mount.Mount{Type: "volume", Target: destPath + slash, VolumeOptions: &mount.VolumeOptions{NoCopy: true}},
  2084  				expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  2085  			},
  2086  			{
  2087  				spec:     mount.Mount{Type: "volume", Target: destPath, Source: "test4", VolumeOptions: &mount.VolumeOptions{NoCopy: true}},
  2088  				expected: types.MountPoint{Type: "volume", Name: "test4", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  2089  			},
  2090  			{
  2091  				spec:     mount.Mount{Type: "volume", Target: destPath, Source: "test5", ReadOnly: true, VolumeOptions: &mount.VolumeOptions{NoCopy: true}},
  2092  				expected: types.MountPoint{Type: "volume", Name: "test5", RW: false, Destination: destPath, Mode: selinuxSharedLabel},
  2093  			},
  2094  		}...)
  2095  	}
  2096  
  2097  	ctx := context.Background()
  2098  	apiclient := testEnv.APIClient()
  2099  	for i, x := range cases {
  2100  		x := x
  2101  		c.Run(fmt.Sprintf("%d config: %v", i, x.spec), func(c *testing.T) {
  2102  			ctr, err := apiclient.ContainerCreate(
  2103  				ctx,
  2104  				&container.Config{Image: testImg},
  2105  				&container.HostConfig{Mounts: []mount.Mount{x.spec}},
  2106  				&network.NetworkingConfig{},
  2107  				nil,
  2108  				"")
  2109  			assert.NilError(c, err)
  2110  
  2111  			containerInspect, err := apiclient.ContainerInspect(ctx, ctr.ID)
  2112  			assert.NilError(c, err)
  2113  			mps := containerInspect.Mounts
  2114  			assert.Assert(c, is.Len(mps, 1))
  2115  			mountPoint := mps[0]
  2116  
  2117  			if x.expected.Source != "" {
  2118  				assert.Check(c, is.Equal(x.expected.Source, mountPoint.Source))
  2119  			}
  2120  			if x.expected.Name != "" {
  2121  				assert.Check(c, is.Equal(x.expected.Name, mountPoint.Name))
  2122  			}
  2123  			if x.expected.Driver != "" {
  2124  				assert.Check(c, is.Equal(x.expected.Driver, mountPoint.Driver))
  2125  			}
  2126  			if x.expected.Propagation != "" {
  2127  				assert.Check(c, is.Equal(x.expected.Propagation, mountPoint.Propagation))
  2128  			}
  2129  			assert.Check(c, is.Equal(x.expected.RW, mountPoint.RW))
  2130  			assert.Check(c, is.Equal(x.expected.Type, mountPoint.Type))
  2131  			assert.Check(c, is.Equal(x.expected.Mode, mountPoint.Mode))
  2132  			assert.Check(c, is.Equal(x.expected.Destination, mountPoint.Destination))
  2133  
  2134  			err = apiclient.ContainerStart(ctx, ctr.ID, types.ContainerStartOptions{})
  2135  			assert.NilError(c, err)
  2136  			poll.WaitOn(c, containerExit(apiclient, ctr.ID), poll.WithDelay(time.Second))
  2137  
  2138  			err = apiclient.ContainerRemove(ctx, ctr.ID, types.ContainerRemoveOptions{
  2139  				RemoveVolumes: true,
  2140  				Force:         true,
  2141  			})
  2142  			assert.NilError(c, err)
  2143  
  2144  			switch {
  2145  			// Named volumes still exist after the container is removed
  2146  			case x.spec.Type == "volume" && len(x.spec.Source) > 0:
  2147  				_, err := apiclient.VolumeInspect(ctx, mountPoint.Name)
  2148  				assert.NilError(c, err)
  2149  
  2150  			// Bind mounts are never removed with the container
  2151  			case x.spec.Type == "bind":
  2152  
  2153  			// anonymous volumes are removed
  2154  			default:
  2155  				_, err := apiclient.VolumeInspect(ctx, mountPoint.Name)
  2156  				assert.Check(c, client.IsErrNotFound(err))
  2157  			}
  2158  		})
  2159  	}
  2160  }
  2161  
  2162  func containerExit(apiclient client.APIClient, name string) func(poll.LogT) poll.Result {
  2163  	return func(logT poll.LogT) poll.Result {
  2164  		ctr, err := apiclient.ContainerInspect(context.Background(), name)
  2165  		if err != nil {
  2166  			return poll.Error(err)
  2167  		}
  2168  		switch ctr.State.Status {
  2169  		case "created", "running":
  2170  			return poll.Continue("container %s is %s, waiting for exit", name, ctr.State.Status)
  2171  		}
  2172  		return poll.Success()
  2173  	}
  2174  }
  2175  
  2176  func (s *DockerAPISuite) TestContainersAPICreateMountsTmpfs(c *testing.T) {
  2177  	testRequires(c, DaemonIsLinux)
  2178  	type testCase struct {
  2179  		cfg             mount.Mount
  2180  		expectedOptions []string
  2181  	}
  2182  	target := "/foo"
  2183  	cases := []testCase{
  2184  		{
  2185  			cfg: mount.Mount{
  2186  				Type:   "tmpfs",
  2187  				Target: target},
  2188  			expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime"},
  2189  		},
  2190  		{
  2191  			cfg: mount.Mount{
  2192  				Type:   "tmpfs",
  2193  				Target: target,
  2194  				TmpfsOptions: &mount.TmpfsOptions{
  2195  					SizeBytes: 4096 * 1024, Mode: 0700}},
  2196  			expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime", "size=4096k", "mode=700"},
  2197  		},
  2198  	}
  2199  
  2200  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
  2201  	assert.NilError(c, err)
  2202  	defer apiClient.Close()
  2203  
  2204  	config := container.Config{
  2205  		Image: "busybox",
  2206  		Cmd:   []string{"/bin/sh", "-c", fmt.Sprintf("mount | grep 'tmpfs on %s'", target)},
  2207  	}
  2208  	for i, x := range cases {
  2209  		cName := fmt.Sprintf("test-tmpfs-%d", i)
  2210  		hostConfig := container.HostConfig{
  2211  			Mounts: []mount.Mount{x.cfg},
  2212  		}
  2213  
  2214  		_, err = apiClient.ContainerCreate(context.Background(), &config, &hostConfig, &network.NetworkingConfig{}, nil, cName)
  2215  		assert.NilError(c, err)
  2216  		out, _ := dockerCmd(c, "start", "-a", cName)
  2217  		for _, option := range x.expectedOptions {
  2218  			assert.Assert(c, strings.Contains(out, option))
  2219  		}
  2220  	}
  2221  }
  2222  
  2223  // Regression test for #33334
  2224  // Makes sure that when a container which has a custom stop signal + restart=always
  2225  // gets killed (with SIGKILL) by the kill API, that the restart policy is cancelled.
  2226  func (s *DockerAPISuite) TestContainerKillCustomStopSignal(c *testing.T) {
  2227  	id := strings.TrimSpace(runSleepingContainer(c, "--stop-signal=SIGTERM", "--restart=always"))
  2228  	res, _, err := request.Post("/containers/" + id + "/kill")
  2229  	assert.NilError(c, err)
  2230  	defer res.Body.Close()
  2231  
  2232  	b, err := io.ReadAll(res.Body)
  2233  	assert.NilError(c, err)
  2234  	assert.Equal(c, res.StatusCode, http.StatusNoContent, string(b))
  2235  	err = waitInspect(id, "{{.State.Running}} {{.State.Restarting}}", "false false", 30*time.Second)
  2236  	assert.NilError(c, err)
  2237  }