github.com/rumpl/bof@v23.0.0-rc.2+incompatible/integration/container/create_test.go (about)

     1  package container // import "github.com/docker/docker/integration/container"
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"strconv"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/docker/docker/api/types"
    12  	"github.com/docker/docker/api/types/container"
    13  	containertypes "github.com/docker/docker/api/types/container"
    14  	"github.com/docker/docker/api/types/network"
    15  	"github.com/docker/docker/api/types/versions"
    16  	"github.com/docker/docker/client"
    17  	"github.com/docker/docker/errdefs"
    18  	ctr "github.com/docker/docker/integration/internal/container"
    19  	"github.com/docker/docker/oci"
    20  	specs "github.com/opencontainers/image-spec/specs-go/v1"
    21  	"gotest.tools/v3/assert"
    22  	is "gotest.tools/v3/assert/cmp"
    23  	"gotest.tools/v3/poll"
    24  	"gotest.tools/v3/skip"
    25  )
    26  
    27  func TestCreateFailsWhenIdentifierDoesNotExist(t *testing.T) {
    28  	defer setupTest(t)()
    29  	client := testEnv.APIClient()
    30  
    31  	testCases := []struct {
    32  		doc           string
    33  		image         string
    34  		expectedError string
    35  	}{
    36  		{
    37  			doc:           "image and tag",
    38  			image:         "test456:v1",
    39  			expectedError: "No such image: test456:v1",
    40  		},
    41  		{
    42  			doc:           "image no tag",
    43  			image:         "test456",
    44  			expectedError: "No such image: test456",
    45  		},
    46  		{
    47  			doc:           "digest",
    48  			image:         "sha256:0cb40641836c461bc97c793971d84d758371ed682042457523e4ae701efeaaaa",
    49  			expectedError: "No such image: sha256:0cb40641836c461bc97c793971d84d758371ed682042457523e4ae701efeaaaa",
    50  		},
    51  	}
    52  
    53  	for _, tc := range testCases {
    54  		tc := tc
    55  		t.Run(tc.doc, func(t *testing.T) {
    56  			t.Parallel()
    57  			_, err := client.ContainerCreate(context.Background(),
    58  				&container.Config{Image: tc.image},
    59  				&container.HostConfig{},
    60  				&network.NetworkingConfig{},
    61  				nil,
    62  				"",
    63  			)
    64  			assert.Check(t, is.ErrorContains(err, tc.expectedError))
    65  			assert.Check(t, errdefs.IsNotFound(err))
    66  		})
    67  	}
    68  }
    69  
    70  // TestCreateLinkToNonExistingContainer verifies that linking to a non-existing
    71  // container returns an "invalid parameter" (400) status, and not the underlying
    72  // "non exists" (404).
    73  func TestCreateLinkToNonExistingContainer(t *testing.T) {
    74  	skip.If(t, testEnv.DaemonInfo.OSType == "windows", "legacy links are not supported on windows")
    75  	defer setupTest(t)()
    76  	c := testEnv.APIClient()
    77  
    78  	_, err := c.ContainerCreate(context.Background(),
    79  		&container.Config{
    80  			Image: "busybox",
    81  		},
    82  		&container.HostConfig{
    83  			Links: []string{"no-such-container"},
    84  		},
    85  		&network.NetworkingConfig{},
    86  		nil,
    87  		"",
    88  	)
    89  	assert.Check(t, is.ErrorContains(err, "could not get container for no-such-container"))
    90  	assert.Check(t, errdefs.IsInvalidParameter(err))
    91  }
    92  
    93  func TestCreateWithInvalidEnv(t *testing.T) {
    94  	defer setupTest(t)()
    95  	client := testEnv.APIClient()
    96  
    97  	testCases := []struct {
    98  		env           string
    99  		expectedError string
   100  	}{
   101  		{
   102  			env:           "",
   103  			expectedError: "invalid environment variable:",
   104  		},
   105  		{
   106  			env:           "=",
   107  			expectedError: "invalid environment variable: =",
   108  		},
   109  		{
   110  			env:           "=foo",
   111  			expectedError: "invalid environment variable: =foo",
   112  		},
   113  	}
   114  
   115  	for index, tc := range testCases {
   116  		tc := tc
   117  		t.Run(strconv.Itoa(index), func(t *testing.T) {
   118  			t.Parallel()
   119  			_, err := client.ContainerCreate(context.Background(),
   120  				&container.Config{
   121  					Image: "busybox",
   122  					Env:   []string{tc.env},
   123  				},
   124  				&container.HostConfig{},
   125  				&network.NetworkingConfig{},
   126  				nil,
   127  				"",
   128  			)
   129  			assert.Check(t, is.ErrorContains(err, tc.expectedError))
   130  			assert.Check(t, errdefs.IsInvalidParameter(err))
   131  		})
   132  	}
   133  }
   134  
   135  // Test case for #30166 (target was not validated)
   136  func TestCreateTmpfsMountsTarget(t *testing.T) {
   137  	skip.If(t, testEnv.DaemonInfo.OSType == "windows")
   138  
   139  	defer setupTest(t)()
   140  	client := testEnv.APIClient()
   141  
   142  	testCases := []struct {
   143  		target        string
   144  		expectedError string
   145  	}{
   146  		{
   147  			target:        ".",
   148  			expectedError: "mount path must be absolute",
   149  		},
   150  		{
   151  			target:        "foo",
   152  			expectedError: "mount path must be absolute",
   153  		},
   154  		{
   155  			target:        "/",
   156  			expectedError: "destination can't be '/'",
   157  		},
   158  		{
   159  			target:        "//",
   160  			expectedError: "destination can't be '/'",
   161  		},
   162  	}
   163  
   164  	for _, tc := range testCases {
   165  		_, err := client.ContainerCreate(context.Background(),
   166  			&container.Config{
   167  				Image: "busybox",
   168  			},
   169  			&container.HostConfig{
   170  				Tmpfs: map[string]string{tc.target: ""},
   171  			},
   172  			&network.NetworkingConfig{},
   173  			nil,
   174  			"",
   175  		)
   176  		assert.Check(t, is.ErrorContains(err, tc.expectedError))
   177  		assert.Check(t, errdefs.IsInvalidParameter(err))
   178  	}
   179  }
   180  func TestCreateWithCustomMaskedPaths(t *testing.T) {
   181  	skip.If(t, testEnv.DaemonInfo.OSType != "linux")
   182  
   183  	defer setupTest(t)()
   184  	client := testEnv.APIClient()
   185  	ctx := context.Background()
   186  
   187  	testCases := []struct {
   188  		maskedPaths []string
   189  		expected    []string
   190  	}{
   191  		{
   192  			maskedPaths: []string{},
   193  			expected:    []string{},
   194  		},
   195  		{
   196  			maskedPaths: nil,
   197  			expected:    oci.DefaultSpec().Linux.MaskedPaths,
   198  		},
   199  		{
   200  			maskedPaths: []string{"/proc/kcore", "/proc/keys"},
   201  			expected:    []string{"/proc/kcore", "/proc/keys"},
   202  		},
   203  	}
   204  
   205  	checkInspect := func(t *testing.T, ctx context.Context, name string, expected []string) {
   206  		_, b, err := client.ContainerInspectWithRaw(ctx, name, false)
   207  		assert.NilError(t, err)
   208  
   209  		var inspectJSON map[string]interface{}
   210  		err = json.Unmarshal(b, &inspectJSON)
   211  		assert.NilError(t, err)
   212  
   213  		cfg, ok := inspectJSON["HostConfig"].(map[string]interface{})
   214  		assert.Check(t, is.Equal(true, ok), name)
   215  
   216  		maskedPaths, ok := cfg["MaskedPaths"].([]interface{})
   217  		assert.Check(t, is.Equal(true, ok), name)
   218  
   219  		mps := []string{}
   220  		for _, mp := range maskedPaths {
   221  			mps = append(mps, mp.(string))
   222  		}
   223  
   224  		assert.DeepEqual(t, expected, mps)
   225  	}
   226  
   227  	for i, tc := range testCases {
   228  		name := fmt.Sprintf("create-masked-paths-%d", i)
   229  		config := container.Config{
   230  			Image: "busybox",
   231  			Cmd:   []string{"true"},
   232  		}
   233  		hc := container.HostConfig{}
   234  		if tc.maskedPaths != nil {
   235  			hc.MaskedPaths = tc.maskedPaths
   236  		}
   237  
   238  		// Create the container.
   239  		c, err := client.ContainerCreate(context.Background(),
   240  			&config,
   241  			&hc,
   242  			&network.NetworkingConfig{},
   243  			nil,
   244  			name,
   245  		)
   246  		assert.NilError(t, err)
   247  
   248  		checkInspect(t, ctx, name, tc.expected)
   249  
   250  		// Start the container.
   251  		err = client.ContainerStart(ctx, c.ID, types.ContainerStartOptions{})
   252  		assert.NilError(t, err)
   253  
   254  		poll.WaitOn(t, ctr.IsInState(ctx, client, c.ID, "exited"), poll.WithDelay(100*time.Millisecond))
   255  
   256  		checkInspect(t, ctx, name, tc.expected)
   257  	}
   258  }
   259  
   260  func TestCreateWithCustomReadonlyPaths(t *testing.T) {
   261  	skip.If(t, testEnv.DaemonInfo.OSType != "linux")
   262  
   263  	defer setupTest(t)()
   264  	client := testEnv.APIClient()
   265  	ctx := context.Background()
   266  
   267  	testCases := []struct {
   268  		readonlyPaths []string
   269  		expected      []string
   270  	}{
   271  		{
   272  			readonlyPaths: []string{},
   273  			expected:      []string{},
   274  		},
   275  		{
   276  			readonlyPaths: nil,
   277  			expected:      oci.DefaultSpec().Linux.ReadonlyPaths,
   278  		},
   279  		{
   280  			readonlyPaths: []string{"/proc/asound", "/proc/bus"},
   281  			expected:      []string{"/proc/asound", "/proc/bus"},
   282  		},
   283  	}
   284  
   285  	checkInspect := func(t *testing.T, ctx context.Context, name string, expected []string) {
   286  		_, b, err := client.ContainerInspectWithRaw(ctx, name, false)
   287  		assert.NilError(t, err)
   288  
   289  		var inspectJSON map[string]interface{}
   290  		err = json.Unmarshal(b, &inspectJSON)
   291  		assert.NilError(t, err)
   292  
   293  		cfg, ok := inspectJSON["HostConfig"].(map[string]interface{})
   294  		assert.Check(t, is.Equal(true, ok), name)
   295  
   296  		readonlyPaths, ok := cfg["ReadonlyPaths"].([]interface{})
   297  		assert.Check(t, is.Equal(true, ok), name)
   298  
   299  		rops := []string{}
   300  		for _, rop := range readonlyPaths {
   301  			rops = append(rops, rop.(string))
   302  		}
   303  		assert.DeepEqual(t, expected, rops)
   304  	}
   305  
   306  	for i, tc := range testCases {
   307  		name := fmt.Sprintf("create-readonly-paths-%d", i)
   308  		config := container.Config{
   309  			Image: "busybox",
   310  			Cmd:   []string{"true"},
   311  		}
   312  		hc := container.HostConfig{}
   313  		if tc.readonlyPaths != nil {
   314  			hc.ReadonlyPaths = tc.readonlyPaths
   315  		}
   316  
   317  		// Create the container.
   318  		c, err := client.ContainerCreate(context.Background(),
   319  			&config,
   320  			&hc,
   321  			&network.NetworkingConfig{},
   322  			nil,
   323  			name,
   324  		)
   325  		assert.NilError(t, err)
   326  
   327  		checkInspect(t, ctx, name, tc.expected)
   328  
   329  		// Start the container.
   330  		err = client.ContainerStart(ctx, c.ID, types.ContainerStartOptions{})
   331  		assert.NilError(t, err)
   332  
   333  		poll.WaitOn(t, ctr.IsInState(ctx, client, c.ID, "exited"), poll.WithDelay(100*time.Millisecond))
   334  
   335  		checkInspect(t, ctx, name, tc.expected)
   336  	}
   337  }
   338  
   339  func TestCreateWithInvalidHealthcheckParams(t *testing.T) {
   340  	defer setupTest(t)()
   341  	client := testEnv.APIClient()
   342  	ctx := context.Background()
   343  
   344  	testCases := []struct {
   345  		doc         string
   346  		interval    time.Duration
   347  		timeout     time.Duration
   348  		retries     int
   349  		startPeriod time.Duration
   350  		expectedErr string
   351  	}{
   352  		{
   353  			doc:         "test invalid Interval in Healthcheck: less than 0s",
   354  			interval:    -10 * time.Millisecond,
   355  			timeout:     time.Second,
   356  			retries:     1000,
   357  			expectedErr: fmt.Sprintf("Interval in Healthcheck cannot be less than %s", container.MinimumDuration),
   358  		},
   359  		{
   360  			doc:         "test invalid Interval in Healthcheck: larger than 0s but less than 1ms",
   361  			interval:    500 * time.Microsecond,
   362  			timeout:     time.Second,
   363  			retries:     1000,
   364  			expectedErr: fmt.Sprintf("Interval in Healthcheck cannot be less than %s", container.MinimumDuration),
   365  		},
   366  		{
   367  			doc:         "test invalid Timeout in Healthcheck: less than 1ms",
   368  			interval:    time.Second,
   369  			timeout:     -100 * time.Millisecond,
   370  			retries:     1000,
   371  			expectedErr: fmt.Sprintf("Timeout in Healthcheck cannot be less than %s", container.MinimumDuration),
   372  		},
   373  		{
   374  			doc:         "test invalid Retries in Healthcheck: less than 0",
   375  			interval:    time.Second,
   376  			timeout:     time.Second,
   377  			retries:     -10,
   378  			expectedErr: "Retries in Healthcheck cannot be negative",
   379  		},
   380  		{
   381  			doc:         "test invalid StartPeriod in Healthcheck: not 0 and less than 1ms",
   382  			interval:    time.Second,
   383  			timeout:     time.Second,
   384  			retries:     1000,
   385  			startPeriod: 100 * time.Microsecond,
   386  			expectedErr: fmt.Sprintf("StartPeriod in Healthcheck cannot be less than %s", container.MinimumDuration),
   387  		},
   388  	}
   389  
   390  	for _, tc := range testCases {
   391  		tc := tc
   392  		t.Run(tc.doc, func(t *testing.T) {
   393  			t.Parallel()
   394  			cfg := container.Config{
   395  				Image: "busybox",
   396  				Healthcheck: &container.HealthConfig{
   397  					Interval: tc.interval,
   398  					Timeout:  tc.timeout,
   399  					Retries:  tc.retries,
   400  				},
   401  			}
   402  			if tc.startPeriod != 0 {
   403  				cfg.Healthcheck.StartPeriod = tc.startPeriod
   404  			}
   405  
   406  			resp, err := client.ContainerCreate(ctx, &cfg, &container.HostConfig{}, nil, nil, "")
   407  			assert.Check(t, is.Equal(len(resp.Warnings), 0))
   408  
   409  			if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") {
   410  				assert.Check(t, errdefs.IsSystem(err))
   411  			} else {
   412  				assert.Check(t, errdefs.IsInvalidParameter(err))
   413  			}
   414  			assert.ErrorContains(t, err, tc.expectedErr)
   415  		})
   416  	}
   417  }
   418  
   419  // Make sure that anonymous volumes can be overritten by tmpfs
   420  // https://github.com/moby/moby/issues/40446
   421  func TestCreateTmpfsOverrideAnonymousVolume(t *testing.T) {
   422  	skip.If(t, testEnv.DaemonInfo.OSType == "windows", "windows does not support tmpfs")
   423  	defer setupTest(t)()
   424  	client := testEnv.APIClient()
   425  	ctx := context.Background()
   426  
   427  	id := ctr.Create(ctx, t, client,
   428  		ctr.WithVolume("/foo"),
   429  		ctr.WithTmpfs("/foo"),
   430  		ctr.WithVolume("/bar"),
   431  		ctr.WithTmpfs("/bar:size=999"),
   432  		ctr.WithCmd("/bin/sh", "-c", "mount | grep '/foo' | grep tmpfs && mount | grep '/bar' | grep tmpfs"),
   433  	)
   434  
   435  	defer func() {
   436  		err := client.ContainerRemove(ctx, id, types.ContainerRemoveOptions{Force: true})
   437  		assert.NilError(t, err)
   438  	}()
   439  
   440  	inspect, err := client.ContainerInspect(ctx, id)
   441  	assert.NilError(t, err)
   442  	// tmpfs do not currently get added to inspect.Mounts
   443  	// Normally an anonymous volume would, except now tmpfs should prevent that.
   444  	assert.Assert(t, is.Len(inspect.Mounts, 0))
   445  
   446  	chWait, chErr := client.ContainerWait(ctx, id, container.WaitConditionNextExit)
   447  	assert.NilError(t, client.ContainerStart(ctx, id, types.ContainerStartOptions{}))
   448  
   449  	timeout := time.NewTimer(30 * time.Second)
   450  	defer timeout.Stop()
   451  
   452  	select {
   453  	case <-timeout.C:
   454  		t.Fatal("timeout waiting for container to exit")
   455  	case status := <-chWait:
   456  		var errMsg string
   457  		if status.Error != nil {
   458  			errMsg = status.Error.Message
   459  		}
   460  		assert.Equal(t, int(status.StatusCode), 0, errMsg)
   461  	case err := <-chErr:
   462  		assert.NilError(t, err)
   463  	}
   464  }
   465  
   466  // Test that if the referenced image platform does not match the requested platform on container create that we get an
   467  // error.
   468  func TestCreateDifferentPlatform(t *testing.T) {
   469  	defer setupTest(t)()
   470  	c := testEnv.APIClient()
   471  	ctx := context.Background()
   472  
   473  	img, _, err := c.ImageInspectWithRaw(ctx, "busybox:latest")
   474  	assert.NilError(t, err)
   475  	assert.Assert(t, img.Architecture != "")
   476  
   477  	t.Run("different os", func(t *testing.T) {
   478  		p := specs.Platform{
   479  			OS:           img.Os + "DifferentOS",
   480  			Architecture: img.Architecture,
   481  			Variant:      img.Variant,
   482  		}
   483  		_, err := c.ContainerCreate(ctx, &containertypes.Config{Image: "busybox:latest"}, &containertypes.HostConfig{}, nil, &p, "")
   484  		assert.Assert(t, client.IsErrNotFound(err), err)
   485  	})
   486  	t.Run("different cpu arch", func(t *testing.T) {
   487  		p := specs.Platform{
   488  			OS:           img.Os,
   489  			Architecture: img.Architecture + "DifferentArch",
   490  			Variant:      img.Variant,
   491  		}
   492  		_, err := c.ContainerCreate(ctx, &containertypes.Config{Image: "busybox:latest"}, &containertypes.HostConfig{}, nil, &p, "")
   493  		assert.Assert(t, client.IsErrNotFound(err), err)
   494  	})
   495  }
   496  
   497  func TestCreateVolumesFromNonExistingContainer(t *testing.T) {
   498  	defer setupTest(t)()
   499  	cli := testEnv.APIClient()
   500  
   501  	_, err := cli.ContainerCreate(
   502  		context.Background(),
   503  		&container.Config{Image: "busybox"},
   504  		&container.HostConfig{VolumesFrom: []string{"nosuchcontainer"}},
   505  		nil,
   506  		nil,
   507  		"",
   508  	)
   509  	assert.Check(t, errdefs.IsInvalidParameter(err))
   510  }
   511  
   512  // Test that we can create a container from an image that is for a different platform even if a platform was not specified
   513  // This is for the regression detailed here: https://github.com/moby/moby/issues/41552
   514  func TestCreatePlatformSpecificImageNoPlatform(t *testing.T) {
   515  	defer setupTest(t)()
   516  
   517  	skip.If(t, testEnv.DaemonInfo.Architecture == "arm", "test only makes sense to run on non-arm systems")
   518  	skip.If(t, testEnv.OSType != "linux", "test image is only available on linux")
   519  	cli := testEnv.APIClient()
   520  
   521  	_, err := cli.ContainerCreate(
   522  		context.Background(),
   523  		&container.Config{Image: "arm32v7/hello-world"},
   524  		&container.HostConfig{},
   525  		nil,
   526  		nil,
   527  		"",
   528  	)
   529  	assert.NilError(t, err)
   530  }
   531  
   532  func TestCreateInvalidHostConfig(t *testing.T) {
   533  	skip.If(t, testEnv.DaemonInfo.OSType == "windows")
   534  
   535  	defer setupTest(t)()
   536  	apiClient := testEnv.APIClient()
   537  	ctx := context.Background()
   538  
   539  	testCases := []struct {
   540  		doc         string
   541  		hc          containertypes.HostConfig
   542  		expectedErr string
   543  	}{
   544  		{
   545  			doc:         "invalid IpcMode",
   546  			hc:          containertypes.HostConfig{IpcMode: "invalid"},
   547  			expectedErr: "Error response from daemon: invalid IPC mode: invalid",
   548  		},
   549  		{
   550  			doc:         "invalid PidMode",
   551  			hc:          containertypes.HostConfig{PidMode: "invalid"},
   552  			expectedErr: "Error response from daemon: invalid PID mode: invalid",
   553  		},
   554  		{
   555  			doc:         "invalid PidMode without container ID",
   556  			hc:          containertypes.HostConfig{PidMode: "container"},
   557  			expectedErr: "Error response from daemon: invalid PID mode: container",
   558  		},
   559  		{
   560  			doc:         "invalid UTSMode",
   561  			hc:          containertypes.HostConfig{UTSMode: "invalid"},
   562  			expectedErr: "Error response from daemon: invalid UTS mode: invalid",
   563  		},
   564  	}
   565  
   566  	for _, tc := range testCases {
   567  		tc := tc
   568  		t.Run(tc.doc, func(t *testing.T) {
   569  			t.Parallel()
   570  			cfg := container.Config{
   571  				Image: "busybox",
   572  			}
   573  			resp, err := apiClient.ContainerCreate(ctx, &cfg, &tc.hc, nil, nil, "")
   574  			assert.Check(t, is.Equal(len(resp.Warnings), 0))
   575  			assert.Check(t, errdefs.IsInvalidParameter(err), "got: %T", err)
   576  			assert.Error(t, err, tc.expectedErr)
   577  		})
   578  	}
   579  }