diff --git a/components/engine/api/swagger.yaml b/components/engine/api/swagger.yaml index 568379f29d..b59888c17b 100644 --- a/components/engine/api/swagger.yaml +++ b/components/engine/api/swagger.yaml @@ -8069,7 +8069,7 @@ paths: parameters: - name: "id" in: "path" - description: "The ID of the secret" + description: "The ID or name of the secret" type: "string" required: true - name: "body" diff --git a/components/engine/daemon/cluster/secrets.go b/components/engine/daemon/cluster/secrets.go index fb76e66e31..af034a6e8c 100644 --- a/components/engine/daemon/cluster/secrets.go +++ b/components/engine/daemon/cluster/secrets.go @@ -95,13 +95,18 @@ func (c *Cluster) RemoveSecret(input string) error { // UpdateSecret updates a secret in a managed swarm cluster. // Note: this is not exposed to the CLI but is available from the API only -func (c *Cluster) UpdateSecret(id string, version uint64, spec types.SecretSpec) error { +func (c *Cluster) UpdateSecret(input string, version uint64, spec types.SecretSpec) error { return c.lockedManagerAction(func(ctx context.Context, state nodeState) error { + secret, err := getSecret(ctx, state.controlClient, input) + if err != nil { + return err + } + secretSpec := convert.SecretSpecToGRPC(spec) - _, err := state.controlClient.UpdateSecret(ctx, + _, err = state.controlClient.UpdateSecret(ctx, &swarmapi.UpdateSecretRequest{ - SecretID: id, + SecretID: secret.ID, SecretVersion: &swarmapi.Version{ Index: version, }, diff --git a/components/engine/integration-cli/daemon/daemon_swarm.go b/components/engine/integration-cli/daemon/daemon_swarm.go index bc9fd41ace..5fb68abb9b 100644 --- a/components/engine/integration-cli/daemon/daemon_swarm.go +++ b/components/engine/integration-cli/daemon/daemon_swarm.go @@ -115,6 +115,9 @@ type ServiceConstructor func(*swarm.Service) // NodeConstructor defines a swarm node constructor type NodeConstructor func(*swarm.Node) +// SecretConstructor defines a swarm secret constructor +type SecretConstructor func(*swarm.Secret) + // SpecConstructor defines a swarm spec constructor type SpecConstructor func(*swarm.Spec) @@ -319,7 +322,7 @@ func (d *Swarm) ListNodes(c *check.C) []swarm.Node { return nodes } -// ListServices return the list of the current swarm services +// ListServices returns the list of the current swarm services func (d *Swarm) ListServices(c *check.C) []swarm.Service { status, out, err := d.SockRequest("GET", "/services", nil) c.Assert(err, checker.IsNil, check.Commentf(string(out))) @@ -370,7 +373,20 @@ func (d *Swarm) DeleteSecret(c *check.C, id string) { c.Assert(status, checker.Equals, http.StatusNoContent, check.Commentf("output: %q", string(out))) } -// GetSwarm return the current swarm object +// UpdateSecret updates the swarm secret identified by the specified id +// Currently, only label update is supported. +func (d *Swarm) UpdateSecret(c *check.C, id string, f ...SecretConstructor) { + secret := d.GetSecret(c, id) + for _, fn := range f { + fn(secret) + } + url := fmt.Sprintf("/secrets/%s/update?version=%d", secret.ID, secret.Version.Index) + status, out, err := d.SockRequest("POST", url, secret.Spec) + c.Assert(err, checker.IsNil, check.Commentf(string(out))) + c.Assert(status, checker.Equals, http.StatusOK, check.Commentf("output: %q", string(out))) +} + +// GetSwarm returns the current swarm object func (d *Swarm) GetSwarm(c *check.C) swarm.Swarm { var sw swarm.Swarm status, out, err := d.SockRequest("GET", "/swarm", nil) diff --git a/components/engine/integration-cli/docker_api_swarm_secret_test.go b/components/engine/integration-cli/docker_api_swarm_secret_test.go index 4d9b50244d..70fc70c436 100644 --- a/components/engine/integration-cli/docker_api_swarm_secret_test.go +++ b/components/engine/integration-cli/docker_api_swarm_secret_test.go @@ -3,6 +3,7 @@ package main import ( + "fmt" "net/http" "github.com/docker/docker/api/types/swarm" @@ -56,3 +57,63 @@ func (s *DockerSwarmSuite) TestAPISwarmSecretsDelete(c *check.C) { c.Assert(err, checker.IsNil) c.Assert(status, checker.Equals, http.StatusNotFound, check.Commentf("secret delete: %s", string(out))) } + +func (s *DockerSwarmSuite) TestAPISwarmSecretsUpdate(c *check.C) { + d := s.AddDaemon(c, true, true) + + testName := "test_secret" + id := d.CreateSecret(c, swarm.SecretSpec{ + swarm.Annotations{ + Name: testName, + Labels: map[string]string{ + "test": "test1", + }, + }, + []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") + + url := fmt.Sprintf("/secrets/%s/update?version=%d", secret.ID, secret.Version.Index) + status, out, err := d.SockRequest("POST", url, secret.Spec) + + c.Assert(err, checker.IsNil, check.Commentf(string(out))) + c.Assert(status, checker.Equals, http.StatusInternalServerError, check.Commentf("output: %q", string(out))) +}