diff --git a/cli/command/service/update_test.go b/cli/command/service/update_test.go index 08fe248769..3a112f2aff 100644 --- a/cli/command/service/update_test.go +++ b/cli/command/service/update_test.go @@ -1,11 +1,13 @@ package service import ( + "context" "reflect" "sort" "testing" "time" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" mounttypes "github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/swarm" @@ -382,3 +384,61 @@ func TestValidatePort(t *testing.T) { assert.Error(t, err, e) } } + +type secretAPIClientMock struct { + listResult []swarm.Secret +} + +func (s secretAPIClientMock) SecretList(ctx context.Context, options types.SecretListOptions) ([]swarm.Secret, error) { + return s.listResult, nil +} +func (s secretAPIClientMock) SecretCreate(ctx context.Context, secret swarm.SecretSpec) (types.SecretCreateResponse, error) { + return types.SecretCreateResponse{}, nil +} +func (s secretAPIClientMock) SecretRemove(ctx context.Context, id string) error { + return nil +} +func (s secretAPIClientMock) SecretInspectWithRaw(ctx context.Context, name string) (swarm.Secret, []byte, error) { + return swarm.Secret{}, []byte{}, nil +} +func (s secretAPIClientMock) SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error { + return nil +} + +// TestUpdateSecretUpdateInPlace tests the ability to update the "target" of an secret with "docker service update" +// by combining "--secret-rm" and "--secret-add" for the same secret. +func TestUpdateSecretUpdateInPlace(t *testing.T) { + apiClient := secretAPIClientMock{ + listResult: []swarm.Secret{ + { + ID: "tn9qiblgnuuut11eufquw5dev", + Spec: swarm.SecretSpec{Annotations: swarm.Annotations{Name: "foo"}}, + }, + }, + } + + flags := newUpdateCommand(nil).Flags() + flags.Set("secret-add", "source=foo,target=foo2") + flags.Set("secret-rm", "foo") + + secrets := []*swarm.SecretReference{ + { + File: &swarm.SecretReferenceFileTarget{ + Name: "foo", + UID: "0", + GID: "0", + Mode: 292, + }, + SecretID: "tn9qiblgnuuut11eufquw5dev", + SecretName: "foo", + }, + } + + updatedSecrets, err := getUpdatedSecrets(apiClient, flags, secrets) + + assert.Equal(t, err, nil) + assert.Equal(t, len(updatedSecrets), 1) + assert.Equal(t, updatedSecrets[0].SecretID, "tn9qiblgnuuut11eufquw5dev") + assert.Equal(t, updatedSecrets[0].SecretName, "foo") + assert.Equal(t, updatedSecrets[0].File.Name, "foo2") +} diff --git a/client/interface.go b/client/interface.go index 00b9adea32..924b22bc04 100644 --- a/client/interface.go +++ b/client/interface.go @@ -166,4 +166,5 @@ type SecretAPIClient interface { SecretCreate(ctx context.Context, secret swarm.SecretSpec) (types.SecretCreateResponse, error) SecretRemove(ctx context.Context, id string) error SecretInspectWithRaw(ctx context.Context, name string) (swarm.Secret, []byte, error) + SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error } diff --git a/client/secret_update.go b/client/secret_update.go new file mode 100644 index 0000000000..b94e24aab0 --- /dev/null +++ b/client/secret_update.go @@ -0,0 +1,19 @@ +package client + +import ( + "net/url" + "strconv" + + "github.com/docker/docker/api/types/swarm" + "golang.org/x/net/context" +) + +// SecretUpdate updates a Secret. Currently, the only part of a secret spec +// which can be updated is Labels. +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)) + resp, err := cli.post(ctx, "/secrets/"+id+"/update", query, secret, nil) + ensureReaderClosed(resp) + return err +} diff --git a/client/secret_update_test.go b/client/secret_update_test.go new file mode 100644 index 0000000000..c620985bd5 --- /dev/null +++ b/client/secret_update_test.go @@ -0,0 +1,49 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/types/swarm" +) + +func TestSecretUpdateError(t *testing.T) { + client := &Client{ + client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")), + } + + err := client.SecretUpdate(context.Background(), "secret_id", swarm.Version{}, swarm.SecretSpec{}) + if err == nil || err.Error() != "Error response from daemon: Server error" { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestSecretUpdate(t *testing.T) { + expectedURL := "/secrets/secret_id/update" + + client := &Client{ + client: newMockClient(func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) + } + if req.Method != "POST" { + return nil, fmt.Errorf("expected POST method, got %s", req.Method) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte("body"))), + }, nil + }), + } + + err := client.SecretUpdate(context.Background(), "secret_id", swarm.Version{}, swarm.SecretSpec{}) + if err != nil { + t.Fatal(err) + } +}