Signed-off-by: Daniel Nephin <dnephin@docker.com>
(cherry picked from commit 6ec84ef76df30663d5728f903b314f4486587135)
Signed-off-by: Victor Vieux <vieux@docker.com>
| ... | ... |
@@ -11,7 +11,8 @@ import ( |
| 11 | 11 |
"golang.org/x/net/context" |
| 12 | 12 |
) |
| 13 | 13 |
|
| 14 |
-func getSecretsByNameOrIDPrefixes(ctx context.Context, client client.APIClient, terms []string) ([]swarm.Secret, error) {
|
|
| 14 |
+// GetSecretsByNameOrIDPrefixes returns secrets given a list of ids or names |
|
| 15 |
+func GetSecretsByNameOrIDPrefixes(ctx context.Context, client client.APIClient, terms []string) ([]swarm.Secret, error) {
|
|
| 15 | 16 |
args := filters.NewArgs() |
| 16 | 17 |
for _, n := range terms {
|
| 17 | 18 |
args.Add("names", n)
|
| ... | ... |
@@ -24,7 +25,7 @@ func getSecretsByNameOrIDPrefixes(ctx context.Context, client client.APIClient, |
| 24 | 24 |
} |
| 25 | 25 |
|
| 26 | 26 |
func getCliRequestedSecretIDs(ctx context.Context, client client.APIClient, terms []string) ([]string, error) {
|
| 27 |
- secrets, err := getSecretsByNameOrIDPrefixes(ctx, client, terms) |
|
| 27 |
+ secrets, err := GetSecretsByNameOrIDPrefixes(ctx, client, terms) |
|
| 28 | 28 |
if err != nil {
|
| 29 | 29 |
return nil, err |
| 30 | 30 |
} |
| ... | ... |
@@ -1,7 +1,6 @@ |
| 1 | 1 |
package stack |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "errors" |
|
| 5 | 4 |
"fmt" |
| 6 | 5 |
"io/ioutil" |
| 7 | 6 |
"os" |
| ... | ... |
@@ -12,10 +11,12 @@ import ( |
| 12 | 12 |
"github.com/docker/docker/api/types/swarm" |
| 13 | 13 |
"github.com/docker/docker/cli" |
| 14 | 14 |
"github.com/docker/docker/cli/command" |
| 15 |
+ secretcli "github.com/docker/docker/cli/command/secret" |
|
| 15 | 16 |
"github.com/docker/docker/cli/compose/convert" |
| 16 | 17 |
"github.com/docker/docker/cli/compose/loader" |
| 17 | 18 |
composetypes "github.com/docker/docker/cli/compose/types" |
| 18 | 19 |
dockerclient "github.com/docker/docker/client" |
| 20 |
+ "github.com/pkg/errors" |
|
| 19 | 21 |
"github.com/spf13/cobra" |
| 20 | 22 |
"golang.org/x/net/context" |
| 21 | 23 |
) |
| ... | ... |
@@ -225,9 +226,22 @@ func createSecrets( |
| 225 | 225 |
) error {
|
| 226 | 226 |
client := dockerCli.Client() |
| 227 | 227 |
|
| 228 |
- for _, secret := range secrets {
|
|
| 229 |
- fmt.Fprintf(dockerCli.Out(), "Creating secret %s\n", secret.Name) |
|
| 230 |
- _, err := client.SecretCreate(ctx, secret) |
|
| 228 |
+ for _, secretSpec := range secrets {
|
|
| 229 |
+ // TODO: fix this after https://github.com/docker/docker/pull/29218 |
|
| 230 |
+ secrets, err := secretcli.GetSecretsByNameOrIDPrefixes(ctx, client, []string{secretSpec.Name})
|
|
| 231 |
+ switch {
|
|
| 232 |
+ case err != nil: |
|
| 233 |
+ return err |
|
| 234 |
+ case len(secrets) > 1: |
|
| 235 |
+ return errors.Errorf("ambiguous secret name: %s", secretSpec.Name)
|
|
| 236 |
+ case len(secrets) == 0: |
|
| 237 |
+ fmt.Fprintf(dockerCli.Out(), "Creating secret %s\n", secretSpec.Name) |
|
| 238 |
+ _, err = client.SecretCreate(ctx, secretSpec) |
|
| 239 |
+ default: |
|
| 240 |
+ secret := secrets[0] |
|
| 241 |
+ // Update secret to ensure that the local data hasn't changed |
|
| 242 |
+ err = client.SecretUpdate(ctx, secret.ID, secret.Meta.Version, secretSpec) |
|
| 243 |
+ } |
|
| 231 | 244 |
if err != nil {
|
| 232 | 245 |
return err |
| 233 | 246 |
} |
| ... | ... |
@@ -7,6 +7,7 @@ import ( |
| 7 | 7 |
"github.com/docker/docker/api/types/network" |
| 8 | 8 |
composetypes "github.com/docker/docker/cli/compose/types" |
| 9 | 9 |
"github.com/docker/docker/pkg/testutil/assert" |
| 10 |
+ "github.com/docker/docker/pkg/testutil/tempfile" |
|
| 10 | 11 |
) |
| 11 | 12 |
|
| 12 | 13 |
func TestNamespaceScope(t *testing.T) {
|
| ... | ... |
@@ -88,3 +89,34 @@ func TestNetworks(t *testing.T) {
|
| 88 | 88 |
assert.DeepEqual(t, networks, expected) |
| 89 | 89 |
assert.DeepEqual(t, externals, []string{"special"})
|
| 90 | 90 |
} |
| 91 |
+ |
|
| 92 |
+func TestSecrets(t *testing.T) {
|
|
| 93 |
+ namespace := Namespace{name: "foo"}
|
|
| 94 |
+ |
|
| 95 |
+ secretText := "this is the first secret" |
|
| 96 |
+ secretFile := tempfile.NewTempFile(t, "convert-secrets", secretText) |
|
| 97 |
+ defer secretFile.Remove() |
|
| 98 |
+ |
|
| 99 |
+ source := map[string]composetypes.SecretConfig{
|
|
| 100 |
+ "one": {
|
|
| 101 |
+ File: secretFile.Name(), |
|
| 102 |
+ Labels: map[string]string{"monster": "mash"},
|
|
| 103 |
+ }, |
|
| 104 |
+ "ext": {
|
|
| 105 |
+ External: composetypes.External{
|
|
| 106 |
+ External: true, |
|
| 107 |
+ }, |
|
| 108 |
+ }, |
|
| 109 |
+ } |
|
| 110 |
+ |
|
| 111 |
+ specs, err := Secrets(namespace, source) |
|
| 112 |
+ assert.NilError(t, err) |
|
| 113 |
+ assert.Equal(t, len(specs), 1) |
|
| 114 |
+ secret := specs[0] |
|
| 115 |
+ assert.Equal(t, secret.Name, "foo_one") |
|
| 116 |
+ assert.DeepEqual(t, secret.Labels, map[string]string{
|
|
| 117 |
+ "monster": "mash", |
|
| 118 |
+ LabelNamespace: "foo", |
|
| 119 |
+ }) |
|
| 120 |
+ assert.DeepEqual(t, secret.Data, []byte(secretText)) |
|
| 121 |
+} |
| ... | ... |
@@ -1,15 +1,18 @@ |
| 1 | 1 |
package main |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "encoding/json" |
|
| 4 | 5 |
"io/ioutil" |
| 5 | 6 |
"os" |
| 7 |
+ "sort" |
|
| 6 | 8 |
|
| 9 |
+ "github.com/docker/docker/api/types/swarm" |
|
| 10 |
+ "github.com/docker/docker/integration-cli/checker" |
|
| 7 | 11 |
"github.com/docker/docker/pkg/integration/checker" |
| 8 | 12 |
"github.com/go-check/check" |
| 9 | 13 |
) |
| 10 | 14 |
|
| 11 | 15 |
func (s *DockerSwarmSuite) TestStackRemove(c *check.C) {
|
| 12 |
- testRequires(c, ExperimentalDaemon) |
|
| 13 | 16 |
d := s.AddDaemon(c, true, true) |
| 14 | 17 |
|
| 15 | 18 |
stackArgs := append([]string{"stack", "remove", "UNKNOWN_STACK"})
|
| ... | ... |
@@ -20,7 +23,6 @@ func (s *DockerSwarmSuite) TestStackRemove(c *check.C) {
|
| 20 | 20 |
} |
| 21 | 21 |
|
| 22 | 22 |
func (s *DockerSwarmSuite) TestStackTasks(c *check.C) {
|
| 23 |
- testRequires(c, ExperimentalDaemon) |
|
| 24 | 23 |
d := s.AddDaemon(c, true, true) |
| 25 | 24 |
|
| 26 | 25 |
stackArgs := append([]string{"stack", "ps", "UNKNOWN_STACK"})
|
| ... | ... |
@@ -31,7 +33,6 @@ func (s *DockerSwarmSuite) TestStackTasks(c *check.C) {
|
| 31 | 31 |
} |
| 32 | 32 |
|
| 33 | 33 |
func (s *DockerSwarmSuite) TestStackServices(c *check.C) {
|
| 34 |
- testRequires(c, ExperimentalDaemon) |
|
| 35 | 34 |
d := s.AddDaemon(c, true, true) |
| 36 | 35 |
|
| 37 | 36 |
stackArgs := append([]string{"stack", "services", "UNKNOWN_STACK"})
|
| ... | ... |
@@ -42,7 +43,6 @@ func (s *DockerSwarmSuite) TestStackServices(c *check.C) {
|
| 42 | 42 |
} |
| 43 | 43 |
|
| 44 | 44 |
func (s *DockerSwarmSuite) TestStackDeployComposeFile(c *check.C) {
|
| 45 |
- testRequires(c, ExperimentalDaemon) |
|
| 46 | 45 |
d := s.AddDaemon(c, true, true) |
| 47 | 46 |
|
| 48 | 47 |
testStackName := "testdeploy" |
| ... | ... |
@@ -65,6 +65,43 @@ func (s *DockerSwarmSuite) TestStackDeployComposeFile(c *check.C) {
|
| 65 | 65 |
c.Assert(out, check.Equals, "NAME SERVICES\n") |
| 66 | 66 |
} |
| 67 | 67 |
|
| 68 |
+func (s *DockerSwarmSuite) TestStackDeployWithSecretsTwice(c *check.C) {
|
|
| 69 |
+ d := s.AddDaemon(c, true, true) |
|
| 70 |
+ |
|
| 71 |
+ testStackName := "testdeploy" |
|
| 72 |
+ stackArgs := []string{
|
|
| 73 |
+ "stack", "deploy", |
|
| 74 |
+ "--compose-file", "fixtures/deploy/secrets.yaml", |
|
| 75 |
+ testStackName, |
|
| 76 |
+ } |
|
| 77 |
+ out, err := d.Cmd(stackArgs...) |
|
| 78 |
+ c.Assert(err, checker.IsNil, check.Commentf(out)) |
|
| 79 |
+ |
|
| 80 |
+ out, err = d.Cmd("service", "inspect", "--format", "{{ json .Spec.TaskTemplate.ContainerSpec.Secrets }}", "testdeploy_web")
|
|
| 81 |
+ c.Assert(err, checker.IsNil) |
|
| 82 |
+ |
|
| 83 |
+ var refs []swarm.SecretReference |
|
| 84 |
+ c.Assert(json.Unmarshal([]byte(out), &refs), checker.IsNil) |
|
| 85 |
+ c.Assert(refs, checker.HasLen, 2) |
|
| 86 |
+ |
|
| 87 |
+ sort.Sort(sortSecrets(refs)) |
|
| 88 |
+ c.Assert(refs[0].SecretName, checker.Equals, "testdeploy_special") |
|
| 89 |
+ c.Assert(refs[0].File.Name, checker.Equals, "special") |
|
| 90 |
+ c.Assert(refs[1].SecretName, checker.Equals, "testdeploy_super") |
|
| 91 |
+ c.Assert(refs[1].File.Name, checker.Equals, "foo.txt") |
|
| 92 |
+ c.Assert(refs[1].File.Mode, checker.Equals, os.FileMode(0400)) |
|
| 93 |
+ |
|
| 94 |
+ // Deploy again to ensure there are no errors when secret hasn't changed |
|
| 95 |
+ out, err = d.Cmd(stackArgs...) |
|
| 96 |
+ c.Assert(err, checker.IsNil, check.Commentf(out)) |
|
| 97 |
+} |
|
| 98 |
+ |
|
| 99 |
+type sortSecrets []swarm.SecretReference |
|
| 100 |
+ |
|
| 101 |
+func (s sortSecrets) Len() int { return len(s) }
|
|
| 102 |
+func (s sortSecrets) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
|
| 103 |
+func (s sortSecrets) Less(i, j int) bool { return s[i].SecretName < s[j].SecretName }
|
|
| 104 |
+ |
|
| 68 | 105 |
// testDAB is the DAB JSON used for testing. |
| 69 | 106 |
// TODO: Use template/text and substitute "Image" with the result of |
| 70 | 107 |
// `docker inspect --format '{{index .RepoDigests 0}}' busybox:latest`
|
| 71 | 108 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,16 @@ |
| 0 |
+ |
|
| 1 |
+version: "3.1" |
|
| 2 |
+services: |
|
| 3 |
+ web: |
|
| 4 |
+ image: busybox@sha256:e4f93f6ed15a0cdd342f5aae387886fba0ab98af0a102da6276eaf24d6e6ade0 |
|
| 5 |
+ command: top |
|
| 6 |
+ secrets: |
|
| 7 |
+ - special |
|
| 8 |
+ - source: super |
|
| 9 |
+ target: foo.txt |
|
| 10 |
+ mode: 0400 |
|
| 11 |
+secrets: |
|
| 12 |
+ special: |
|
| 13 |
+ file: fixtures/secrets/default |
|
| 14 |
+ super: |
|
| 15 |
+ file: fixtures/secrets/default |