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