diff --git a/cli/command/secret/utils.go b/cli/command/secret/utils.go index 42493896ca..11d31ffd16 100644 --- a/cli/command/secret/utils.go +++ b/cli/command/secret/utils.go @@ -11,7 +11,8 @@ import ( "golang.org/x/net/context" ) -func getSecretsByNameOrIDPrefixes(ctx context.Context, client client.APIClient, terms []string) ([]swarm.Secret, error) { +// GetSecretsByNameOrIDPrefixes returns secrets given a list of ids or names +func GetSecretsByNameOrIDPrefixes(ctx context.Context, client client.APIClient, terms []string) ([]swarm.Secret, error) { args := filters.NewArgs() for _, n := range terms { args.Add("names", n) @@ -24,7 +25,7 @@ func getSecretsByNameOrIDPrefixes(ctx context.Context, client client.APIClient, } func getCliRequestedSecretIDs(ctx context.Context, client client.APIClient, terms []string) ([]string, error) { - secrets, err := getSecretsByNameOrIDPrefixes(ctx, client, terms) + secrets, err := GetSecretsByNameOrIDPrefixes(ctx, client, terms) if err != nil { return nil, err } diff --git a/cli/command/stack/deploy.go b/cli/command/stack/deploy.go index 6856624128..203ae6d39c 100644 --- a/cli/command/stack/deploy.go +++ b/cli/command/stack/deploy.go @@ -1,24 +1,24 @@ package stack import ( - "errors" "fmt" "io/ioutil" "os" "sort" "strings" - "github.com/spf13/cobra" - "golang.org/x/net/context" - "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" + secretcli "github.com/docker/docker/cli/command/secret" "github.com/docker/docker/cli/compose/convert" "github.com/docker/docker/cli/compose/loader" composetypes "github.com/docker/docker/cli/compose/types" dockerclient "github.com/docker/docker/client" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "golang.org/x/net/context" ) const ( @@ -228,9 +228,22 @@ func createSecrets( ) error { client := dockerCli.Client() - for _, secret := range secrets { - fmt.Fprintf(dockerCli.Out(), "Creating secret %s\n", secret.Name) - _, err := client.SecretCreate(ctx, secret) + for _, secretSpec := range secrets { + // TODO: fix this after https://github.com/docker/docker/pull/29218 + secrets, err := secretcli.GetSecretsByNameOrIDPrefixes(ctx, client, []string{secretSpec.Name}) + switch { + case err != nil: + return err + case len(secrets) > 1: + return errors.Errorf("ambiguous secret name: %s", secretSpec.Name) + case len(secrets) == 0: + fmt.Fprintf(dockerCli.Out(), "Creating secret %s\n", secretSpec.Name) + _, err = client.SecretCreate(ctx, secretSpec) + default: + secret := secrets[0] + // Update secret to ensure that the local data hasn't changed + err = client.SecretUpdate(ctx, secret.ID, secret.Meta.Version, secretSpec) + } if err != nil { return err } diff --git a/cli/compose/convert/compose_test.go b/cli/compose/convert/compose_test.go index d88ac7f7c4..18c7aac938 100644 --- a/cli/compose/convert/compose_test.go +++ b/cli/compose/convert/compose_test.go @@ -7,6 +7,7 @@ import ( "github.com/docker/docker/api/types/network" composetypes "github.com/docker/docker/cli/compose/types" "github.com/docker/docker/pkg/testutil/assert" + "github.com/docker/docker/pkg/testutil/tempfile" ) func TestNamespaceScope(t *testing.T) { @@ -88,3 +89,34 @@ func TestNetworks(t *testing.T) { assert.DeepEqual(t, networks, expected) assert.DeepEqual(t, externals, []string{"special"}) } + +func TestSecrets(t *testing.T) { + namespace := Namespace{name: "foo"} + + secretText := "this is the first secret" + secretFile := tempfile.NewTempFile(t, "convert-secrets", secretText) + defer secretFile.Remove() + + source := map[string]composetypes.SecretConfig{ + "one": { + File: secretFile.Name(), + Labels: map[string]string{"monster": "mash"}, + }, + "ext": { + External: composetypes.External{ + External: true, + }, + }, + } + + specs, err := Secrets(namespace, source) + assert.NilError(t, err) + assert.Equal(t, len(specs), 1) + secret := specs[0] + assert.Equal(t, secret.Name, "foo_one") + assert.DeepEqual(t, secret.Labels, map[string]string{ + "monster": "mash", + LabelNamespace: "foo", + }) + assert.DeepEqual(t, secret.Data, []byte(secretText)) +} diff --git a/client/secret_update.go b/client/secret_update.go index b94e24aab0..42cdbbe176 100644 --- a/client/secret_update.go +++ b/client/secret_update.go @@ -8,8 +8,7 @@ import ( "golang.org/x/net/context" ) -// SecretUpdate updates a Secret. Currently, the only part of a secret spec -// which can be updated is Labels. +// SecretUpdate attempts to updates a Secret func (cli *Client) SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error { query := url.Values{} query.Set("version", strconv.FormatUint(version.Index, 10)) diff --git a/integration-cli/docker_cli_stack_test.go b/integration-cli/docker_cli_stack_test.go index 2354e1aafa..03e1e5f08c 100644 --- a/integration-cli/docker_cli_stack_test.go +++ b/integration-cli/docker_cli_stack_test.go @@ -1,15 +1,17 @@ package main import ( + "encoding/json" "io/ioutil" "os" + "sort" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/integration-cli/checker" "github.com/go-check/check" ) func (s *DockerSwarmSuite) TestStackRemove(c *check.C) { - testRequires(c, ExperimentalDaemon) d := s.AddDaemon(c, true, true) stackArgs := append([]string{"stack", "remove", "UNKNOWN_STACK"}) @@ -20,7 +22,6 @@ func (s *DockerSwarmSuite) TestStackRemove(c *check.C) { } func (s *DockerSwarmSuite) TestStackTasks(c *check.C) { - testRequires(c, ExperimentalDaemon) d := s.AddDaemon(c, true, true) stackArgs := append([]string{"stack", "ps", "UNKNOWN_STACK"}) @@ -31,7 +32,6 @@ func (s *DockerSwarmSuite) TestStackTasks(c *check.C) { } func (s *DockerSwarmSuite) TestStackServices(c *check.C) { - testRequires(c, ExperimentalDaemon) d := s.AddDaemon(c, true, true) stackArgs := append([]string{"stack", "services", "UNKNOWN_STACK"}) @@ -42,7 +42,6 @@ func (s *DockerSwarmSuite) TestStackServices(c *check.C) { } func (s *DockerSwarmSuite) TestStackDeployComposeFile(c *check.C) { - testRequires(c, ExperimentalDaemon) d := s.AddDaemon(c, true, true) testStackName := "testdeploy" @@ -65,6 +64,43 @@ func (s *DockerSwarmSuite) TestStackDeployComposeFile(c *check.C) { c.Assert(out, check.Equals, "NAME SERVICES\n") } +func (s *DockerSwarmSuite) TestStackDeployWithSecretsTwice(c *check.C) { + d := s.AddDaemon(c, true, true) + + testStackName := "testdeploy" + stackArgs := []string{ + "stack", "deploy", + "--compose-file", "fixtures/deploy/secrets.yaml", + testStackName, + } + out, err := d.Cmd(stackArgs...) + c.Assert(err, checker.IsNil, check.Commentf(out)) + + out, err = d.Cmd("service", "inspect", "--format", "{{ json .Spec.TaskTemplate.ContainerSpec.Secrets }}", "testdeploy_web") + c.Assert(err, checker.IsNil) + + var refs []swarm.SecretReference + c.Assert(json.Unmarshal([]byte(out), &refs), checker.IsNil) + c.Assert(refs, checker.HasLen, 2) + + sort.Sort(sortSecrets(refs)) + c.Assert(refs[0].SecretName, checker.Equals, "testdeploy_special") + c.Assert(refs[0].File.Name, checker.Equals, "special") + c.Assert(refs[1].SecretName, checker.Equals, "testdeploy_super") + c.Assert(refs[1].File.Name, checker.Equals, "foo.txt") + c.Assert(refs[1].File.Mode, checker.Equals, os.FileMode(0400)) + + // Deploy again to ensure there are no errors when secret hasn't changed + out, err = d.Cmd(stackArgs...) + c.Assert(err, checker.IsNil, check.Commentf(out)) +} + +type sortSecrets []swarm.SecretReference + +func (s sortSecrets) Len() int { return len(s) } +func (s sortSecrets) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s sortSecrets) Less(i, j int) bool { return s[i].SecretName < s[j].SecretName } + // testDAB is the DAB JSON used for testing. // TODO: Use template/text and substitute "Image" with the result of // `docker inspect --format '{{index .RepoDigests 0}}' busybox:latest` diff --git a/integration-cli/fixtures/deploy/secrets.yaml b/integration-cli/fixtures/deploy/secrets.yaml new file mode 100644 index 0000000000..965260f8cd --- /dev/null +++ b/integration-cli/fixtures/deploy/secrets.yaml @@ -0,0 +1,16 @@ + +version: "3.1" +services: + web: + image: busybox@sha256:e4f93f6ed15a0cdd342f5aae387886fba0ab98af0a102da6276eaf24d6e6ade0 + command: top + secrets: + - special + - source: super + target: foo.txt + mode: 0400 +secrets: + special: + file: fixtures/secrets/default + super: + file: fixtures/secrets/default diff --git a/integration-cli/fixtures/secrets/default b/integration-cli/fixtures/secrets/default new file mode 100644 index 0000000000..e8ca7d8874 --- /dev/null +++ b/integration-cli/fixtures/secrets/default @@ -0,0 +1 @@ +this is the secret