diff --git a/components/engine/api/swagger.yaml b/components/engine/api/swagger.yaml index cc3ee80cb2..8174ce387c 100644 --- a/components/engine/api/swagger.yaml +++ b/components/engine/api/swagger.yaml @@ -6173,8 +6173,19 @@ paths: type: "integer" - name: "buildargs" in: "query" - description: "JSON map of string pairs for build-time variables. Users pass these values at build-time. Docker uses the buildargs as the environment context for commands run via the `Dockerfile` RUN instruction, or for variable expansion in other `Dockerfile` instructions. This is not meant for passing secret values. [Read more about the buildargs instruction.](https://docs.docker.com/engine/reference/builder/#arg)" - type: "integer" + description: > + JSON map of string pairs for build-time variables. Users pass these values at build-time. Docker + uses the buildargs as the environment context for commands run via the `Dockerfile` RUN + instruction, or for variable expansion in other `Dockerfile` instructions. This is not meant for + passing secret values. + + + For example, the build arg `FOO=bar` would become `{"FOO":"bar"}` in JSON. This would result in the + the query parameter `buildargs={"FOO":"bar"}`. Note that `{"FOO":"bar"}` should be URI component encoded. + + + [Read more about the buildargs instruction.](https://docs.docker.com/engine/reference/builder/#arg) + type: "string" - name: "shmsize" in: "query" description: "Size of `/dev/shm` in bytes. The size must be greater than 0. If omitted the system uses 64MB." diff --git a/components/engine/client/client.go b/components/engine/client/client.go index 240e32ae05..6ce0cdba1f 100644 --- a/components/engine/client/client.go +++ b/components/engine/client/client.go @@ -133,23 +133,15 @@ func FromEnv(c *Client) error { }, CheckRedirect: CheckRedirect, } + WithHTTPClient(httpClient)(c) } host := os.Getenv("DOCKER_HOST") if host != "" { - var err error + // WithHost will create an API client if it doesn't exist if err := WithHost(host)(c); err != nil { return err } - httpClient, err = defaultHTTPClient(host) - if err != nil { - return err - } - } - if httpClient != nil { - if err := WithHTTPClient(httpClient)(c); err != nil { - return err - } } version := os.Getenv("DOCKER_API_VERSION") if version != "" { @@ -167,7 +159,8 @@ func WithVersion(version string) func(*Client) error { } } -// WithHost overrides the client host with the specified one +// WithHost overrides the client host with the specified one, creating a new +// http client if one doesn't exist func WithHost(host string) func(*Client) error { return func(c *Client) error { hostURL, err := ParseHostURL(host) @@ -178,11 +171,17 @@ func WithHost(host string) func(*Client) error { c.proto = hostURL.Scheme c.addr = hostURL.Host c.basePath = hostURL.Path - client, err := defaultHTTPClient(host) - if err != nil { - return err + if c.client == nil { + client, err := defaultHTTPClient(host) + if err != nil { + return err + } + return WithHTTPClient(client)(c) } - return WithHTTPClient(client)(c) + if transport, ok := c.client.Transport.(*http.Transport); ok { + return sockets.ConfigureTransport(transport, c.proto, c.addr) + } + return fmt.Errorf("cannot apply host to http transport") } } diff --git a/components/engine/integration-cli/docker_api_containers_windows_test.go b/components/engine/integration-cli/docker_api_containers_windows_test.go index ae60cfec00..25bc767e55 100644 --- a/components/engine/integration-cli/docker_api_containers_windows_test.go +++ b/components/engine/integration-cli/docker_api_containers_windows_test.go @@ -16,7 +16,7 @@ import ( ) func (s *DockerSuite) TestContainersAPICreateMountsBindNamedPipe(c *check.C) { - testRequires(c, SameHostDaemon, DaemonIsWindowsAtLeastBuild(16210)) // Named pipe support was added in RS3 + testRequires(c, SameHostDaemon, DaemonIsWindowsAtLeastBuild(16299)) // Named pipe support was added in RS3 // Create a host pipe to map into the container hostPipeName := fmt.Sprintf(`\\.\pipe\docker-cli-test-pipe-%x`, rand.Uint64()) diff --git a/components/engine/integration-cli/docker_api_swarm_config_test.go b/components/engine/integration-cli/docker_api_swarm_config_test.go deleted file mode 100644 index c01d80ff7f..0000000000 --- a/components/engine/integration-cli/docker_api_swarm_config_test.go +++ /dev/null @@ -1,123 +0,0 @@ -// +build !windows - -package main - -import ( - "github.com/docker/docker/api/types/swarm" - "github.com/docker/docker/integration-cli/checker" - "github.com/go-check/check" - "golang.org/x/net/context" -) - -func (s *DockerSwarmSuite) TestAPISwarmConfigsEmptyList(c *check.C) { - d := s.AddDaemon(c, true, true) - - configs := d.ListConfigs(c) - c.Assert(configs, checker.NotNil) - c.Assert(len(configs), checker.Equals, 0, check.Commentf("configs: %#v", configs)) -} - -func (s *DockerSwarmSuite) TestAPISwarmConfigsCreate(c *check.C) { - d := s.AddDaemon(c, true, true) - - testName := "test_config" - id := d.CreateConfig(c, swarm.ConfigSpec{ - Annotations: swarm.Annotations{ - Name: testName, - }, - Data: []byte("TESTINGDATA"), - }) - c.Assert(id, checker.Not(checker.Equals), "", check.Commentf("configs: %s", id)) - - configs := d.ListConfigs(c) - c.Assert(len(configs), checker.Equals, 1, check.Commentf("configs: %#v", configs)) - name := configs[0].Spec.Annotations.Name - c.Assert(name, checker.Equals, testName, check.Commentf("configs: %s", name)) -} - -func (s *DockerSwarmSuite) TestAPISwarmConfigsDelete(c *check.C) { - d := s.AddDaemon(c, true, true) - - testName := "test_config" - id := d.CreateConfig(c, swarm.ConfigSpec{Annotations: swarm.Annotations{ - Name: testName, - }, - Data: []byte("TESTINGDATA"), - }) - c.Assert(id, checker.Not(checker.Equals), "", check.Commentf("configs: %s", id)) - - config := d.GetConfig(c, id) - c.Assert(config.ID, checker.Equals, id, check.Commentf("config: %v", config)) - - d.DeleteConfig(c, config.ID) - - cli, err := d.NewClient() - c.Assert(err, checker.IsNil) - defer cli.Close() - - _, _, err = cli.ConfigInspectWithRaw(context.Background(), id) - c.Assert(err.Error(), checker.Contains, "No such config") -} - -func (s *DockerSwarmSuite) TestAPISwarmConfigsUpdate(c *check.C) { - d := s.AddDaemon(c, true, true) - - testName := "test_config" - id := d.CreateConfig(c, swarm.ConfigSpec{ - Annotations: swarm.Annotations{ - Name: testName, - Labels: map[string]string{ - "test": "test1", - }, - }, - Data: []byte("TESTINGDATA"), - }) - c.Assert(id, checker.Not(checker.Equals), "", check.Commentf("configs: %s", id)) - - config := d.GetConfig(c, id) - c.Assert(config.ID, checker.Equals, id, check.Commentf("config: %v", config)) - - // test UpdateConfig with full ID - d.UpdateConfig(c, id, func(s *swarm.Config) { - s.Spec.Labels = map[string]string{ - "test": "test1", - } - }) - - config = d.GetConfig(c, id) - c.Assert(config.Spec.Labels["test"], checker.Equals, "test1", check.Commentf("config: %v", config)) - - // test UpdateConfig with full name - d.UpdateConfig(c, config.Spec.Name, func(s *swarm.Config) { - s.Spec.Labels = map[string]string{ - "test": "test2", - } - }) - - config = d.GetConfig(c, id) - c.Assert(config.Spec.Labels["test"], checker.Equals, "test2", check.Commentf("config: %v", config)) - - // test UpdateConfig with prefix ID - d.UpdateConfig(c, id[:1], func(s *swarm.Config) { - s.Spec.Labels = map[string]string{ - "test": "test3", - } - }) - - config = d.GetConfig(c, id) - c.Assert(config.Spec.Labels["test"], checker.Equals, "test3", check.Commentf("config: %v", config)) - - // test UpdateConfig in updating Data which is not supported in daemon - // this test will produce an error in func UpdateConfig - config = d.GetConfig(c, id) - config.Spec.Data = []byte("TESTINGDATA2") - - cli, err := d.NewClient() - c.Assert(err, checker.IsNil) - defer cli.Close() - - expected := "only updates to Labels are allowed" - - err = cli.ConfigUpdate(context.Background(), config.ID, config.Version, config.Spec) - c.Assert(err.Error(), checker.Contains, expected) -} diff --git a/components/engine/integration-cli/docker_api_swarm_secret_test.go b/components/engine/integration-cli/docker_api_swarm_secret_test.go deleted file mode 100644 index c30e2237d8..0000000000 --- a/components/engine/integration-cli/docker_api_swarm_secret_test.go +++ /dev/null @@ -1,136 +0,0 @@ -// +build !windows - -package main - -import ( - "net/http" - - "github.com/docker/docker/api/types/swarm" - "github.com/docker/docker/integration-cli/checker" - "github.com/go-check/check" - "golang.org/x/net/context" -) - -func (s *DockerSwarmSuite) TestAPISwarmSecretsEmptyList(c *check.C) { - d := s.AddDaemon(c, true, true) - - secrets := d.ListSecrets(c) - c.Assert(secrets, checker.NotNil) - c.Assert(len(secrets), checker.Equals, 0, check.Commentf("secrets: %#v", secrets)) -} - -func (s *DockerSwarmSuite) TestAPISwarmSecretsCreate(c *check.C) { - d := s.AddDaemon(c, true, true) - - testName := "test_secret" - secretSpec := swarm.SecretSpec{ - Annotations: swarm.Annotations{ - Name: testName, - }, - Data: []byte("TESTINGDATA"), - } - - id := d.CreateSecret(c, secretSpec) - c.Assert(id, checker.Not(checker.Equals), "", check.Commentf("secrets: %s", id)) - - secrets := d.ListSecrets(c) - c.Assert(len(secrets), checker.Equals, 1, check.Commentf("secrets: %#v", secrets)) - name := secrets[0].Spec.Annotations.Name - c.Assert(name, checker.Equals, testName, check.Commentf("secret: %s", name)) - - // create an already existing secret, daemon should return a status code of 409 - status, out, err := d.SockRequest("POST", "/secrets/create", secretSpec) - c.Assert(err, checker.IsNil) - c.Assert(status, checker.Equals, http.StatusConflict, check.Commentf("secret create: %s", string(out))) -} - -func (s *DockerSwarmSuite) TestAPISwarmSecretsDelete(c *check.C) { - d := s.AddDaemon(c, true, true) - - testName := "test_secret" - id := d.CreateSecret(c, swarm.SecretSpec{Annotations: swarm.Annotations{ - Name: testName, - }, - Data: []byte("TESTINGDATA"), - }) - c.Assert(id, checker.Not(checker.Equals), "", check.Commentf("secrets: %s", id)) - - secret := d.GetSecret(c, id) - c.Assert(secret.ID, checker.Equals, id, check.Commentf("secret: %v", secret)) - - d.DeleteSecret(c, secret.ID) - - cli, err := d.NewClient() - c.Assert(err, checker.IsNil) - defer cli.Close() - - _, _, err = cli.SecretInspectWithRaw(context.Background(), id) - c.Assert(err.Error(), checker.Contains, "No such secret") - - id = "non-existing" - err = cli.SecretRemove(context.Background(), id) - c.Assert(err.Error(), checker.Contains, "No such secret: non-existing") -} - -func (s *DockerSwarmSuite) TestAPISwarmSecretsUpdate(c *check.C) { - d := s.AddDaemon(c, true, true) - - testName := "test_secret" - id := d.CreateSecret(c, swarm.SecretSpec{ - Annotations: swarm.Annotations{ - Name: testName, - Labels: map[string]string{ - "test": "test1", - }, - }, - Data: []byte("TESTINGDATA"), - }) - c.Assert(id, checker.Not(checker.Equals), "", check.Commentf("secrets: %s", id)) - - secret := d.GetSecret(c, id) - c.Assert(secret.ID, checker.Equals, id, check.Commentf("secret: %v", secret)) - - // test UpdateSecret with full ID - d.UpdateSecret(c, id, func(s *swarm.Secret) { - s.Spec.Labels = map[string]string{ - "test": "test1", - } - }) - - secret = d.GetSecret(c, id) - c.Assert(secret.Spec.Labels["test"], checker.Equals, "test1", check.Commentf("secret: %v", secret)) - - // test UpdateSecret with full name - d.UpdateSecret(c, secret.Spec.Name, func(s *swarm.Secret) { - s.Spec.Labels = map[string]string{ - "test": "test2", - } - }) - - secret = d.GetSecret(c, id) - c.Assert(secret.Spec.Labels["test"], checker.Equals, "test2", check.Commentf("secret: %v", secret)) - - // test UpdateSecret with prefix ID - d.UpdateSecret(c, id[:1], func(s *swarm.Secret) { - s.Spec.Labels = map[string]string{ - "test": "test3", - } - }) - - secret = d.GetSecret(c, id) - c.Assert(secret.Spec.Labels["test"], checker.Equals, "test3", check.Commentf("secret: %v", secret)) - - // test UpdateSecret in updating Data which is not supported in daemon - // this test will produce an error in func UpdateSecret - secret = d.GetSecret(c, id) - secret.Spec.Data = []byte("TESTINGDATA2") - - cli, err := d.NewClient() - c.Assert(err, checker.IsNil) - defer cli.Close() - - expected := "only updates to Labels are allowed" - - err = cli.SecretUpdate(context.Background(), secret.ID, secret.Version, secret.Spec) - c.Assert(err.Error(), checker.Contains, expected) -} diff --git a/components/engine/integration-cli/docker_cli_config_ls_test.go b/components/engine/integration-cli/docker_cli_config_ls_test.go deleted file mode 100644 index 5f002bcab6..0000000000 --- a/components/engine/integration-cli/docker_cli_config_ls_test.go +++ /dev/null @@ -1,126 +0,0 @@ -// +build !windows - -package main - -import ( - "strings" - - "github.com/docker/docker/api/types/swarm" - "github.com/docker/docker/integration-cli/checker" - "github.com/go-check/check" -) - -func (s *DockerSwarmSuite) TestConfigList(c *check.C) { - testRequires(c, SameHostDaemon) - d := s.AddDaemon(c, true, true) - - testName0 := "test0" - testName1 := "test1" - - // create config test0 - id0 := d.CreateConfig(c, swarm.ConfigSpec{ - Annotations: swarm.Annotations{ - Name: testName0, - Labels: map[string]string{"type": "test"}, - }, - Data: []byte("TESTINGDATA0"), - }) - c.Assert(id0, checker.Not(checker.Equals), "", check.Commentf("configs: %s", id0)) - - config := d.GetConfig(c, id0) - c.Assert(config.Spec.Name, checker.Equals, testName0) - - // create config test1 - id1 := d.CreateConfig(c, swarm.ConfigSpec{ - Annotations: swarm.Annotations{ - Name: testName1, - Labels: map[string]string{"type": "production"}, - }, - Data: []byte("TESTINGDATA1"), - }) - c.Assert(id1, checker.Not(checker.Equals), "", check.Commentf("configs: %s", id1)) - - config = d.GetConfig(c, id1) - c.Assert(config.Spec.Name, checker.Equals, testName1) - - // test by command `docker config ls` - out, err := d.Cmd("config", "ls") - c.Assert(err, checker.IsNil, check.Commentf(out)) - c.Assert(strings.TrimSpace(out), checker.Contains, testName0) - c.Assert(strings.TrimSpace(out), checker.Contains, testName1) - - // test filter by name `docker config ls --filter name=xxx` - args := []string{ - "config", - "ls", - "--filter", - "name=test0", - } - out, err = d.Cmd(args...) - c.Assert(err, checker.IsNil, check.Commentf(out)) - - c.Assert(strings.TrimSpace(out), checker.Contains, testName0) - c.Assert(strings.TrimSpace(out), checker.Not(checker.Contains), testName1) - - // test filter by id `docker config ls --filter id=xxx` - args = []string{ - "config", - "ls", - "--filter", - "id=" + id1, - } - out, err = d.Cmd(args...) - c.Assert(err, checker.IsNil, check.Commentf(out)) - - c.Assert(strings.TrimSpace(out), checker.Not(checker.Contains), testName0) - c.Assert(strings.TrimSpace(out), checker.Contains, testName1) - - // test filter by label `docker config ls --filter label=xxx` - args = []string{ - "config", - "ls", - "--filter", - "label=type", - } - out, err = d.Cmd(args...) - c.Assert(err, checker.IsNil, check.Commentf(out)) - - c.Assert(strings.TrimSpace(out), checker.Contains, testName0) - c.Assert(strings.TrimSpace(out), checker.Contains, testName1) - - args = []string{ - "config", - "ls", - "--filter", - "label=type=test", - } - out, err = d.Cmd(args...) - c.Assert(err, checker.IsNil, check.Commentf(out)) - - c.Assert(strings.TrimSpace(out), checker.Contains, testName0) - c.Assert(strings.TrimSpace(out), checker.Not(checker.Contains), testName1) - - args = []string{ - "config", - "ls", - "--filter", - "label=type=production", - } - out, err = d.Cmd(args...) - c.Assert(err, checker.IsNil, check.Commentf(out)) - - c.Assert(strings.TrimSpace(out), checker.Not(checker.Contains), testName0) - c.Assert(strings.TrimSpace(out), checker.Contains, testName1) - - // test invalid filter `docker config ls --filter noexisttype=xxx` - args = []string{ - "config", - "ls", - "--filter", - "noexisttype=test0", - } - out, err = d.Cmd(args...) - c.Assert(err, checker.NotNil, check.Commentf(out)) - - c.Assert(strings.TrimSpace(out), checker.Contains, "Error response from daemon: Invalid filter 'noexisttype'") -} diff --git a/components/engine/integration-cli/docker_cli_oom_killed_test.go b/components/engine/integration-cli/docker_cli_oom_killed_test.go deleted file mode 100644 index 54c34d206a..0000000000 --- a/components/engine/integration-cli/docker_cli_oom_killed_test.go +++ /dev/null @@ -1,30 +0,0 @@ -// +build !windows - -package main - -import ( - "github.com/docker/docker/integration-cli/checker" - "github.com/go-check/check" -) - -func (s *DockerSuite) TestInspectOomKilledTrue(c *check.C) { - testRequires(c, DaemonIsLinux, memoryLimitSupport, swapMemorySupport) - - name := "testoomkilled" - _, exitCode, _ := dockerCmdWithError("run", "--name", name, "--memory", "32MB", "busybox", "sh", "-c", "x=a; while true; do x=$x$x$x$x; done") - - c.Assert(exitCode, checker.Equals, 137, check.Commentf("OOM exit should be 137")) - - oomKilled := inspectField(c, name, "State.OOMKilled") - c.Assert(oomKilled, checker.Equals, "true") -} - -func (s *DockerSuite) TestInspectOomKilledFalse(c *check.C) { - testRequires(c, DaemonIsLinux, memoryLimitSupport, swapMemorySupport) - - name := "testoomkilled" - dockerCmd(c, "run", "--name", name, "--memory", "32MB", "busybox", "sh", "-c", "echo hello world") - - oomKilled := inspectField(c, name, "State.OOMKilled") - c.Assert(oomKilled, checker.Equals, "false") -} diff --git a/components/engine/integration-cli/docker_cli_run_test.go b/components/engine/integration-cli/docker_cli_run_test.go index bf0cb0ddf8..2a652e35e4 100644 --- a/components/engine/integration-cli/docker_cli_run_test.go +++ b/components/engine/integration-cli/docker_cli_run_test.go @@ -4002,7 +4002,7 @@ func (s *DockerSuite) TestRunAttachFailedNoLeak(c *check.C) { v, err := kernel.GetKernelVersion() c.Assert(err, checker.IsNil) build, _ := strconv.Atoi(strings.Split(strings.SplitN(v.String(), " ", 3)[2][1:], ".")[0]) - if build >= 16292 { // @jhowardmsft TODO - replace with final RS3 build and == + if build == 16299 { c.Skip("Temporarily disabled on RS3 builds") } } diff --git a/components/engine/integration-cli/docker_cli_update_test.go b/components/engine/integration-cli/docker_cli_update_test.go deleted file mode 100644 index dce137ebf8..0000000000 --- a/components/engine/integration-cli/docker_cli_update_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package main - -import ( - "strings" - "time" - - "github.com/docker/docker/integration-cli/checker" - "github.com/docker/docker/integration-cli/cli" - "github.com/go-check/check" - "github.com/gotestyourself/gotestyourself/icmd" -) - -func (s *DockerSuite) TestUpdateRestartPolicy(c *check.C) { - out := cli.DockerCmd(c, "run", "-d", "--restart=on-failure:3", "busybox", "sh", "-c", "sleep 1 && false").Combined() - timeout := 60 * time.Second - if testEnv.OSType == "windows" { - timeout = 180 * time.Second - } - - id := strings.TrimSpace(string(out)) - - // update restart policy to on-failure:5 - cli.DockerCmd(c, "update", "--restart=on-failure:5", id) - - cli.WaitExited(c, id, timeout) - - count := inspectField(c, id, "RestartCount") - c.Assert(count, checker.Equals, "5") - - maximumRetryCount := inspectField(c, id, "HostConfig.RestartPolicy.MaximumRetryCount") - c.Assert(maximumRetryCount, checker.Equals, "5") -} - -func (s *DockerSuite) TestUpdateRestartWithAutoRemoveFlag(c *check.C) { - out := runSleepingContainer(c, "--rm") - id := strings.TrimSpace(out) - - // update restart policy for an AutoRemove container - cli.Docker(cli.Args("update", "--restart=always", id)).Assert(c, icmd.Expected{ - ExitCode: 1, - Err: "Restart policy cannot be updated because AutoRemove is enabled for the container", - }) -} diff --git a/components/engine/integration/config/config_test.go b/components/engine/integration/config/config_test.go new file mode 100644 index 0000000000..fa2a205953 --- /dev/null +++ b/components/engine/integration/config/config_test.go @@ -0,0 +1,190 @@ +package config + +import ( + "sort" + "testing" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + swarmtypes "github.com/docker/docker/api/types/swarm" + "github.com/docker/docker/client" + "github.com/docker/docker/integration/internal/swarm" + "github.com/docker/docker/internal/testutil" + "github.com/gotestyourself/gotestyourself/skip" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/net/context" +) + +func TestConfigList(t *testing.T) { + skip.If(t, testEnv.DaemonInfo.OSType != "linux") + + defer setupTest(t)() + d := swarm.NewSwarm(t, testEnv) + defer d.Stop(t) + client, err := client.NewClientWithOpts(client.WithHost((d.Sock()))) + require.NoError(t, err) + + ctx := context.Background() + + // This test case is ported from the original TestConfigsEmptyList + configs, err := client.ConfigList(ctx, types.ConfigListOptions{}) + require.NoError(t, err) + assert.Equal(t, len(configs), 0) + + testName0 := "test0" + testName1 := "test1" + testNames := []string{testName0, testName1} + sort.Strings(testNames) + + // create config test0 + createConfig(ctx, t, client, testName0, []byte("TESTINGDATA0"), map[string]string{"type": "test"}) + + config1ID := createConfig(ctx, t, client, testName1, []byte("TESTINGDATA1"), map[string]string{"type": "production"}) + + names := func(entries []swarmtypes.Config) []string { + values := []string{} + for _, entry := range entries { + values = append(values, entry.Spec.Name) + } + sort.Strings(values) + return values + } + + // test by `config ls` + entries, err := client.ConfigList(ctx, types.ConfigListOptions{}) + require.NoError(t, err) + assert.Equal(t, names(entries), testNames) + + testCases := []struct { + filters filters.Args + expected []string + }{ + // test filter by name `config ls --filter name=xxx` + { + filters: filters.NewArgs(filters.Arg("name", testName0)), + expected: []string{testName0}, + }, + // test filter by id `config ls --filter id=xxx` + { + filters: filters.NewArgs(filters.Arg("id", config1ID)), + expected: []string{testName1}, + }, + // test filter by label `config ls --filter label=xxx` + { + filters: filters.NewArgs(filters.Arg("label", "type")), + expected: testNames, + }, + { + filters: filters.NewArgs(filters.Arg("label", "type=test")), + expected: []string{testName0}, + }, + { + filters: filters.NewArgs(filters.Arg("label", "type=production")), + expected: []string{testName1}, + }, + } + for _, tc := range testCases { + entries, err = client.ConfigList(ctx, types.ConfigListOptions{ + Filters: tc.filters, + }) + require.NoError(t, err) + assert.Equal(t, names(entries), tc.expected) + + } +} + +func createConfig(ctx context.Context, t *testing.T, client client.APIClient, name string, data []byte, labels map[string]string) string { + config, err := client.ConfigCreate(ctx, swarmtypes.ConfigSpec{ + Annotations: swarmtypes.Annotations{ + Name: name, + Labels: labels, + }, + Data: data, + }) + require.NoError(t, err) + assert.NotEqual(t, config.ID, "") + return config.ID +} + +func TestConfigsCreateAndDelete(t *testing.T) { + skip.If(t, testEnv.DaemonInfo.OSType != "linux") + + defer setupTest(t)() + d := swarm.NewSwarm(t, testEnv) + defer d.Stop(t) + client, err := client.NewClientWithOpts(client.WithHost((d.Sock()))) + require.NoError(t, err) + + ctx := context.Background() + + testName := "test_config" + + // This test case is ported from the original TestConfigsCreate + configID := createConfig(ctx, t, client, testName, []byte("TESTINGDATA"), nil) + + insp, _, err := client.ConfigInspectWithRaw(ctx, configID) + require.NoError(t, err) + assert.Equal(t, insp.Spec.Name, testName) + + // This test case is ported from the original TestConfigsDelete + err = client.ConfigRemove(ctx, configID) + require.NoError(t, err) + + insp, _, err = client.ConfigInspectWithRaw(ctx, configID) + testutil.ErrorContains(t, err, "No such config") +} + +func TestConfigsUpdate(t *testing.T) { + skip.If(t, testEnv.DaemonInfo.OSType != "linux") + + defer setupTest(t)() + d := swarm.NewSwarm(t, testEnv) + defer d.Stop(t) + client, err := client.NewClientWithOpts(client.WithHost((d.Sock()))) + require.NoError(t, err) + + ctx := context.Background() + + testName := "test_config" + + // This test case is ported from the original TestConfigsCreate + configID := createConfig(ctx, t, client, testName, []byte("TESTINGDATA"), nil) + + insp, _, err := client.ConfigInspectWithRaw(ctx, configID) + require.NoError(t, err) + assert.Equal(t, insp.ID, configID) + + // test UpdateConfig with full ID + insp.Spec.Labels = map[string]string{"test": "test1"} + err = client.ConfigUpdate(ctx, configID, insp.Version, insp.Spec) + require.NoError(t, err) + + insp, _, err = client.ConfigInspectWithRaw(ctx, configID) + require.NoError(t, err) + assert.Equal(t, insp.Spec.Labels["test"], "test1") + + // test UpdateConfig with full name + insp.Spec.Labels = map[string]string{"test": "test2"} + err = client.ConfigUpdate(ctx, testName, insp.Version, insp.Spec) + require.NoError(t, err) + + insp, _, err = client.ConfigInspectWithRaw(ctx, configID) + require.NoError(t, err) + assert.Equal(t, insp.Spec.Labels["test"], "test2") + + // test UpdateConfig with prefix ID + insp.Spec.Labels = map[string]string{"test": "test3"} + err = client.ConfigUpdate(ctx, configID[:1], insp.Version, insp.Spec) + require.NoError(t, err) + + insp, _, err = client.ConfigInspectWithRaw(ctx, configID) + require.NoError(t, err) + assert.Equal(t, insp.Spec.Labels["test"], "test3") + + // test UpdateConfig in updating Data which is not supported in daemon + // this test will produce an error in func UpdateConfig + insp.Spec.Data = []byte("TESTINGDATA2") + err = client.ConfigUpdate(ctx, configID, insp.Version, insp.Spec) + testutil.ErrorContains(t, err, "only updates to Labels are allowed") +} diff --git a/components/engine/integration/config/main_test.go b/components/engine/integration/config/main_test.go new file mode 100644 index 0000000000..32df97d2c6 --- /dev/null +++ b/components/engine/integration/config/main_test.go @@ -0,0 +1,33 @@ +package config + +import ( + "fmt" + "os" + "testing" + + "github.com/docker/docker/internal/test/environment" +) + +var testEnv *environment.Execution + +func TestMain(m *testing.M) { + var err error + testEnv, err = environment.New() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + err = environment.EnsureFrozenImagesLinux(testEnv) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + testEnv.Print() + os.Exit(m.Run()) +} + +func setupTest(t *testing.T) func() { + environment.ProtectAll(t, testEnv) + return func() { testEnv.Clean(t) } +} diff --git a/components/engine/integration/container/kill_test.go b/components/engine/integration/container/kill_test.go index 6efc5fc61d..d399213ac6 100644 --- a/components/engine/integration/container/kill_test.go +++ b/components/engine/integration/container/kill_test.go @@ -11,6 +11,7 @@ import ( "github.com/docker/docker/integration/internal/request" "github.com/gotestyourself/gotestyourself/poll" "github.com/gotestyourself/gotestyourself/skip" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -146,3 +147,39 @@ func TestKillDifferentUserContainer(t *testing.T) { require.NoError(t, err) poll.WaitOn(t, containerIsInState(ctx, client, id, "exited"), poll.WithDelay(100*time.Millisecond)) } + +func TestInspectOomKilledTrue(t *testing.T) { + skip.If(t, testEnv.DaemonInfo.OSType != "linux" || !testEnv.DaemonInfo.MemoryLimit || !testEnv.DaemonInfo.SwapLimit) + + defer setupTest(t)() + ctx := context.Background() + client := request.NewAPIClient(t) + + name := "testoomkilled" + cID := container.Run(t, ctx, client, container.WithName(name), container.WithCmd("sh", "-c", "x=a; while true; do x=$x$x$x$x; done"), func(c *container.TestContainerConfig) { + c.HostConfig.Resources.Memory = 32 * 1024 * 1024 + }) + + poll.WaitOn(t, containerIsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond)) + + inspect, err := client.ContainerInspect(ctx, cID) + require.NoError(t, err) + assert.Equal(t, inspect.State.OOMKilled, true) +} + +func TestInspectOomKilledFalse(t *testing.T) { + skip.If(t, testEnv.DaemonInfo.OSType != "linux" || !testEnv.DaemonInfo.MemoryLimit || !testEnv.DaemonInfo.SwapLimit) + + defer setupTest(t)() + ctx := context.Background() + client := request.NewAPIClient(t) + + name := "testoomkilled" + cID := container.Run(t, ctx, client, container.WithName(name), container.WithCmd("sh", "-c", "echo hello world")) + + poll.WaitOn(t, containerIsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond)) + + inspect, err := client.ContainerInspect(ctx, cID) + require.NoError(t, err) + assert.Equal(t, inspect.State.OOMKilled, false) +} diff --git a/components/engine/integration/container/update_test.go b/components/engine/integration/container/update_test.go new file mode 100644 index 0000000000..aee95804a1 --- /dev/null +++ b/components/engine/integration/container/update_test.go @@ -0,0 +1,65 @@ +package container // import "github.com/docker/docker/integration/container" + +import ( + "context" + "testing" + "time" + + containertypes "github.com/docker/docker/api/types/container" + "github.com/docker/docker/integration/internal/container" + "github.com/docker/docker/integration/internal/request" + "github.com/docker/docker/internal/testutil" + "github.com/gotestyourself/gotestyourself/poll" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestUpdateRestartPolicy(t *testing.T) { + defer setupTest(t)() + client := request.NewAPIClient(t) + ctx := context.Background() + + cID := container.Run(t, ctx, client, container.WithCmd("sh", "-c", "sleep 1 && false"), func(c *container.TestContainerConfig) { + c.HostConfig.RestartPolicy = containertypes.RestartPolicy{ + Name: "on-failure", + MaximumRetryCount: 3, + } + }) + + _, err := client.ContainerUpdate(ctx, cID, containertypes.UpdateConfig{ + RestartPolicy: containertypes.RestartPolicy{ + Name: "on-failure", + MaximumRetryCount: 5, + }, + }) + require.NoError(t, err) + + timeout := 60 * time.Second + if testEnv.OSType == "windows" { + timeout = 180 * time.Second + } + + poll.WaitOn(t, containerIsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond), poll.WithTimeout(timeout)) + + inspect, err := client.ContainerInspect(ctx, cID) + require.NoError(t, err) + assert.Equal(t, inspect.RestartCount, 5) + assert.Equal(t, inspect.HostConfig.RestartPolicy.MaximumRetryCount, 5) +} + +func TestUpdateRestartWithAutoRemove(t *testing.T) { + defer setupTest(t)() + client := request.NewAPIClient(t) + ctx := context.Background() + + cID := container.Run(t, ctx, client, func(c *container.TestContainerConfig) { + c.HostConfig.AutoRemove = true + }) + + _, err := client.ContainerUpdate(ctx, cID, containertypes.UpdateConfig{ + RestartPolicy: containertypes.RestartPolicy{ + Name: "always", + }, + }) + testutil.ErrorContains(t, err, "Restart policy cannot be updated because AutoRemove is enabled for the container") +} diff --git a/components/engine/integration/secret/secret_test.go b/components/engine/integration/secret/secret_test.go index 3915bf5e82..a6e9983e23 100644 --- a/components/engine/integration/secret/secret_test.go +++ b/components/engine/integration/secret/secret_test.go @@ -9,6 +9,7 @@ import ( swarmtypes "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/client" "github.com/docker/docker/integration/internal/swarm" + "github.com/docker/docker/internal/testutil" "github.com/gotestyourself/gotestyourself/skip" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -35,7 +36,7 @@ func TestSecretInspect(t *testing.T) { secret, _, err = client.SecretInspectWithRaw(context.Background(), testName) require.NoError(t, err) - assert.Equal(t, secret.ID, secretID) + assert.Equal(t, secretID, secretID) } func TestSecretList(t *testing.T) { @@ -124,3 +125,110 @@ func createSecret(ctx context.Context, t *testing.T, client client.APIClient, na assert.NotEqual(t, secret.ID, "") return secret.ID } + +func TestSecretsCreate(t *testing.T) { + skip.If(t, testEnv.DaemonInfo.OSType != "linux") + + defer setupTest(t)() + d := swarm.NewSwarm(t, testEnv) + defer d.Stop(t) + client, err := client.NewClientWithOpts(client.WithHost((d.Sock()))) + require.NoError(t, err) + + ctx := context.Background() + + testName := "test_secret" + createSecret(ctx, t, client, testName, []byte("TESTINGDATA"), nil) + require.NoError(t, err) + + // create an already existin secret, daemon should return a status code of 409 + _, err = client.SecretCreate(ctx, swarmtypes.SecretSpec{ + Annotations: swarmtypes.Annotations{ + Name: testName, + }, + Data: []byte("TESTINGDATA"), + }) + testutil.ErrorContains(t, err, "already exists") +} + +func TestSecretsDelete(t *testing.T) { + skip.If(t, testEnv.DaemonInfo.OSType != "linux") + + defer setupTest(t)() + d := swarm.NewSwarm(t, testEnv) + defer d.Stop(t) + client, err := client.NewClientWithOpts(client.WithHost((d.Sock()))) + require.NoError(t, err) + + ctx := context.Background() + + testName := "test_secret" + secretID := createSecret(ctx, t, client, testName, []byte("TESTINGDATA"), nil) + require.NoError(t, err) + + insp, _, err := client.SecretInspectWithRaw(ctx, secretID) + require.NoError(t, err) + assert.Equal(t, insp.ID, secretID) + + err = client.SecretRemove(ctx, secretID) + require.NoError(t, err) + + _, _, err = client.SecretInspectWithRaw(ctx, secretID) + testutil.ErrorContains(t, err, "No such secret") + + err = client.SecretRemove(ctx, "non-existin") + testutil.ErrorContains(t, err, "No such secret: non-existin") +} + +func TestSecretsUpdate(t *testing.T) { + skip.If(t, testEnv.DaemonInfo.OSType != "linux") + + defer setupTest(t)() + d := swarm.NewSwarm(t, testEnv) + defer d.Stop(t) + client, err := client.NewClientWithOpts(client.WithHost((d.Sock()))) + require.NoError(t, err) + + ctx := context.Background() + + testName := "test_secret" + secretID := createSecret(ctx, t, client, testName, []byte("TESTINGDATA"), nil) + require.NoError(t, err) + + insp, _, err := client.SecretInspectWithRaw(ctx, secretID) + require.NoError(t, err) + assert.Equal(t, insp.ID, secretID) + + // test UpdateSecret with full ID + insp.Spec.Labels = map[string]string{"test": "test1"} + err = client.SecretUpdate(ctx, secretID, insp.Version, insp.Spec) + require.NoError(t, err) + + insp, _, err = client.SecretInspectWithRaw(ctx, secretID) + require.NoError(t, err) + assert.Equal(t, insp.Spec.Labels["test"], "test1") + + // test UpdateSecret with full name + insp.Spec.Labels = map[string]string{"test": "test2"} + err = client.SecretUpdate(ctx, testName, insp.Version, insp.Spec) + require.NoError(t, err) + + insp, _, err = client.SecretInspectWithRaw(ctx, secretID) + require.NoError(t, err) + assert.Equal(t, insp.Spec.Labels["test"], "test2") + + // test UpdateSecret with prefix ID + insp.Spec.Labels = map[string]string{"test": "test3"} + err = client.SecretUpdate(ctx, secretID[:1], insp.Version, insp.Spec) + require.NoError(t, err) + + insp, _, err = client.SecretInspectWithRaw(ctx, secretID) + require.NoError(t, err) + assert.Equal(t, insp.Spec.Labels["test"], "test3") + + // test UpdateSecret in updating Data which is not supported in daemon + // this test will produce an error in func UpdateSecret + insp.Spec.Data = []byte("TESTINGDATA2") + err = client.SecretUpdate(ctx, secretID, insp.Version, insp.Spec) + testutil.ErrorContains(t, err, "only updates to Labels are allowed") +} diff --git a/components/engine/libcontainerd/client_local_windows.go b/components/engine/libcontainerd/client_local_windows.go index 51475b42d0..34ad0d771e 100644 --- a/components/engine/libcontainerd/client_local_windows.go +++ b/components/engine/libcontainerd/client_local_windows.go @@ -223,7 +223,7 @@ func (c *client) createWindows(id string, spec *specs.Spec, runtimeOptions inter if configuration.HvPartition { // We don't currently support setting the utility VM image explicitly. - // TODO @swernli/jhowardmsft circa RS3/4, this may be re-locatable. + // TODO @swernli/jhowardmsft circa RS5, this may be re-locatable. if spec.Windows.HyperV.UtilityVMPath != "" { return errors.New("runtime does not support an explicit utility VM path for Hyper-V containers") } @@ -303,7 +303,7 @@ func (c *client) createWindows(id string, spec *specs.Spec, runtimeOptions inter } } configuration.MappedDirectories = mds - if len(mps) > 0 && system.GetOSVersion().Build < 16210 { // replace with Win10 RS3 build number at RTM + if len(mps) > 0 && system.GetOSVersion().Build < 16299 { // RS3 return errors.New("named pipe mounts are not supported on this version of Windows") } configuration.MappedPipes = mps diff --git a/components/engine/pkg/system/path.go b/components/engine/pkg/system/path.go index 3d209b1bdf..a3d957afab 100644 --- a/components/engine/pkg/system/path.go +++ b/components/engine/pkg/system/path.go @@ -35,7 +35,7 @@ func DefaultPathEnv(os string) string { // This is used, for example, when validating a user provided path in docker cp. // If a drive letter is supplied, it must be the system drive. The drive letter // is always removed. Also, it translates it to OS semantics (IOW / to \). We -// need the path in this syntax so that it can ultimately be contatenated with +// need the path in this syntax so that it can ultimately be concatenated with // a Windows long-path which doesn't support drive-letters. Examples: // C: --> Fail // C:\ --> \ diff --git a/components/engine/pkg/tailfile/tailfile.go b/components/engine/pkg/tailfile/tailfile.go index a223f66b75..e835893746 100644 --- a/components/engine/pkg/tailfile/tailfile.go +++ b/components/engine/pkg/tailfile/tailfile.go @@ -16,7 +16,7 @@ var eol = []byte("\n") // ErrNonPositiveLinesNumber is an error returned if the lines number was negative. var ErrNonPositiveLinesNumber = errors.New("The number of lines to extract from the file must be positive") -//TailFile returns last n lines of reader f (could be a fil). +//TailFile returns last n lines of reader f (could be a nil). func TailFile(f io.ReadSeeker, n int) ([][]byte, error) { if n <= 0 { return nil, ErrNonPositiveLinesNumber