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