client/secrets: Wrap results and options

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2025-10-21 21:33:26 +02:00
parent 2401bd1e12
commit 95fac07ccc
26 changed files with 360 additions and 295 deletions

View File

@@ -203,11 +203,11 @@ type VolumeAPIClient interface {
// SecretAPIClient defines API client methods for secrets // SecretAPIClient defines API client methods for secrets
type SecretAPIClient interface { type SecretAPIClient interface {
SecretList(ctx context.Context, options SecretListOptions) ([]swarm.Secret, error) SecretList(ctx context.Context, options SecretListOptions) (SecretListResult, error)
SecretCreate(ctx context.Context, secret swarm.SecretSpec) (swarm.SecretCreateResponse, error) SecretCreate(ctx context.Context, options SecretCreateOptions) (SecretCreateResult, error)
SecretRemove(ctx context.Context, id string) error SecretRemove(ctx context.Context, id string, options SecretRemoveOptions) (SecretRemoveResult, error)
SecretInspectWithRaw(ctx context.Context, name string) (swarm.Secret, []byte, error) SecretInspect(ctx context.Context, id string, options SecretInspectOptions) (SecretInspectResult, error)
SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error SecretUpdate(ctx context.Context, id string, options SecretUpdateOptions) (SecretUpdateResult, error)
} }
// ConfigAPIClient defines API client methods for configs // ConfigAPIClient defines API client methods for configs

View File

@@ -7,15 +7,28 @@ import (
"github.com/moby/moby/api/types/swarm" "github.com/moby/moby/api/types/swarm"
) )
// SecretCreateOptions holds options for creating a secret.
type SecretCreateOptions struct {
Spec swarm.SecretSpec
}
// SecretCreateResult holds the result from the [Client.SecretCreate] method.
type SecretCreateResult struct {
ID string
}
// SecretCreate creates a new secret. // SecretCreate creates a new secret.
func (cli *Client) SecretCreate(ctx context.Context, secret swarm.SecretSpec) (swarm.SecretCreateResponse, error) { func (cli *Client) SecretCreate(ctx context.Context, options SecretCreateOptions) (SecretCreateResult, error) {
resp, err := cli.post(ctx, "/secrets/create", nil, secret, nil) resp, err := cli.post(ctx, "/secrets/create", nil, options.Spec, nil)
defer ensureReaderClosed(resp) defer ensureReaderClosed(resp)
if err != nil { if err != nil {
return swarm.SecretCreateResponse{}, err return SecretCreateResult{}, err
} }
var response swarm.SecretCreateResponse var out swarm.ConfigCreateResponse
err = json.NewDecoder(resp.Body).Decode(&response) err = json.NewDecoder(resp.Body).Decode(&out)
return response, err if err != nil {
return SecretCreateResult{}, err
}
return SecretCreateResult{ID: out.ID}, nil
} }

View File

@@ -17,7 +17,7 @@ import (
func TestSecretCreateError(t *testing.T) { func TestSecretCreateError(t *testing.T) {
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error"))) client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err) assert.NilError(t, err)
_, err = client.SecretCreate(context.Background(), swarm.SecretSpec{}) _, err = client.SecretCreate(context.Background(), SecretCreateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal)) assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
} }
@@ -40,7 +40,7 @@ func TestSecretCreate(t *testing.T) {
})) }))
assert.NilError(t, err) assert.NilError(t, err)
r, err := client.SecretCreate(context.Background(), swarm.SecretSpec{}) r, err := client.SecretCreate(context.Background(), SecretCreateOptions{})
assert.NilError(t, err) assert.NilError(t, err)
assert.Check(t, is.Equal(r.ID, "test_secret")) assert.Check(t, is.Equal(r.ID, "test_secret"))
} }

View File

@@ -1,34 +1,35 @@
package client package client
import ( import (
"bytes"
"context" "context"
"encoding/json"
"io"
"github.com/moby/moby/api/types/swarm" "github.com/moby/moby/api/types/swarm"
) )
// SecretInspectWithRaw returns the secret information with raw data // SecretInspectOptions holds options for inspecting a secret.
func (cli *Client) SecretInspectWithRaw(ctx context.Context, id string) (swarm.Secret, []byte, error) { type SecretInspectOptions struct {
// Add future optional parameters here
}
// SecretInspectResult holds the result from the [Client.SecretInspect]. method.
type SecretInspectResult struct {
Secret swarm.Secret
Raw []byte
}
// SecretInspect returns the secret information with raw data.
func (cli *Client) SecretInspect(ctx context.Context, id string, options SecretInspectOptions) (SecretInspectResult, error) {
id, err := trimID("secret", id) id, err := trimID("secret", id)
if err != nil { if err != nil {
return swarm.Secret{}, nil, err return SecretInspectResult{}, err
} }
resp, err := cli.get(ctx, "/secrets/"+id, nil, nil) resp, err := cli.get(ctx, "/secrets/"+id, nil, nil)
defer ensureReaderClosed(resp) defer ensureReaderClosed(resp)
if err != nil { if err != nil {
return swarm.Secret{}, nil, err return SecretInspectResult{}, err
} }
body, err := io.ReadAll(resp.Body) var out SecretInspectResult
if err != nil { out.Raw, err = decodeWithRaw(resp, &out.Secret)
return swarm.Secret{}, nil, err return out, err
}
var secret swarm.Secret
rdr := bytes.NewReader(body)
err = json.NewDecoder(rdr).Decode(&secret)
return secret, body, err
} }

View File

@@ -19,7 +19,7 @@ func TestSecretInspectError(t *testing.T) {
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error"))) client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err) assert.NilError(t, err)
_, _, err = client.SecretInspectWithRaw(context.Background(), "nothing") _, err = client.SecretInspect(context.Background(), "nothing", SecretInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal)) assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
} }
@@ -27,7 +27,7 @@ func TestSecretInspectSecretNotFound(t *testing.T) {
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusNotFound, "Server error"))) client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusNotFound, "Server error")))
assert.NilError(t, err) assert.NilError(t, err)
_, _, err = client.SecretInspectWithRaw(context.Background(), "unknown") _, err = client.SecretInspect(context.Background(), "unknown", SecretInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound)) assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
} }
@@ -36,11 +36,11 @@ func TestSecretInspectWithEmptyID(t *testing.T) {
return nil, errors.New("should not make request") return nil, errors.New("should not make request")
})) }))
assert.NilError(t, err) assert.NilError(t, err)
_, _, err = client.SecretInspectWithRaw(context.Background(), "") _, err = client.SecretInspect(context.Background(), "", SecretInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty")) assert.Check(t, is.ErrorContains(err, "value is empty"))
_, _, err = client.SecretInspectWithRaw(context.Background(), " ") _, err = client.SecretInspect(context.Background(), " ", SecretInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty")) assert.Check(t, is.ErrorContains(err, "value is empty"))
} }
@@ -64,7 +64,7 @@ func TestSecretInspect(t *testing.T) {
})) }))
assert.NilError(t, err) assert.NilError(t, err)
secretInspect, _, err := client.SecretInspectWithRaw(context.Background(), "secret_id") res, err := client.SecretInspect(context.Background(), "secret_id", SecretInspectOptions{})
assert.NilError(t, err) assert.NilError(t, err)
assert.Check(t, is.Equal(secretInspect.ID, "secret_id")) assert.Check(t, is.Equal(res.Secret.ID, "secret_id"))
} }

View File

@@ -8,18 +8,31 @@ import (
"github.com/moby/moby/api/types/swarm" "github.com/moby/moby/api/types/swarm"
) )
// SecretList returns the list of secrets. // SecretListOptions holds parameters to list secrets
func (cli *Client) SecretList(ctx context.Context, options SecretListOptions) ([]swarm.Secret, error) { type SecretListOptions struct {
query := url.Values{} Filters Filters
}
// SecretListResult holds the result from the [client.SecretList] method.
type SecretListResult struct {
Items []swarm.Secret
}
// SecretList returns the list of secrets.
func (cli *Client) SecretList(ctx context.Context, options SecretListOptions) (SecretListResult, error) {
query := url.Values{}
options.Filters.updateURLValues(query) options.Filters.updateURLValues(query)
resp, err := cli.get(ctx, "/secrets", query, nil) resp, err := cli.get(ctx, "/secrets", query, nil)
defer ensureReaderClosed(resp) defer ensureReaderClosed(resp)
if err != nil { if err != nil {
return nil, err return SecretListResult{}, err
} }
var secrets []swarm.Secret var out SecretListResult
err = json.NewDecoder(resp.Body).Decode(&secrets) err = json.NewDecoder(resp.Body).Decode(&out.Items)
return secrets, err if err != nil {
return SecretListResult{}, err
}
return out, nil
} }

View File

@@ -1,6 +0,0 @@
package client
// SecretListOptions holds parameters to list secrets
type SecretListOptions struct {
Filters Filters
}

View File

@@ -75,8 +75,8 @@ func TestSecretList(t *testing.T) {
})) }))
assert.NilError(t, err) assert.NilError(t, err)
secrets, err := client.SecretList(context.Background(), listCase.options) res, err := client.SecretList(context.Background(), listCase.options)
assert.NilError(t, err) assert.NilError(t, err)
assert.Check(t, is.Len(secrets, 2)) assert.Check(t, is.Len(res.Items, 2))
} }
} }

View File

@@ -2,13 +2,24 @@ package client
import "context" import "context"
type SecretRemoveOptions struct {
// Add future optional parameters here
}
type SecretRemoveResult struct {
// Add future fields here
}
// SecretRemove removes a secret. // SecretRemove removes a secret.
func (cli *Client) SecretRemove(ctx context.Context, id string) error { func (cli *Client) SecretRemove(ctx context.Context, id string, options SecretRemoveOptions) (SecretRemoveResult, error) {
id, err := trimID("secret", id) id, err := trimID("secret", id)
if err != nil { if err != nil {
return err return SecretRemoveResult{}, err
} }
resp, err := cli.delete(ctx, "/secrets/"+id, nil, nil) resp, err := cli.delete(ctx, "/secrets/"+id, nil, nil)
defer ensureReaderClosed(resp) defer ensureReaderClosed(resp)
return err if err != nil {
return SecretRemoveResult{}, err
}
return SecretRemoveResult{}, nil
} }

View File

@@ -16,14 +16,14 @@ func TestSecretRemoveError(t *testing.T) {
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error"))) client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err) assert.NilError(t, err)
err = client.SecretRemove(context.Background(), "secret_id") _, err = client.SecretRemove(context.Background(), "secret_id", SecretRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal)) assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
err = client.SecretRemove(context.Background(), "") _, err = client.SecretRemove(context.Background(), "", SecretRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty")) assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.SecretRemove(context.Background(), " ") _, err = client.SecretRemove(context.Background(), " ", SecretRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty")) assert.Check(t, is.ErrorContains(err, "value is empty"))
} }
@@ -42,6 +42,6 @@ func TestSecretRemove(t *testing.T) {
})) }))
assert.NilError(t, err) assert.NilError(t, err)
err = client.SecretRemove(context.Background(), "secret_id") _, err = client.SecretRemove(context.Background(), "secret_id", SecretRemoveOptions{})
assert.NilError(t, err) assert.NilError(t, err)
} }

View File

@@ -7,15 +7,26 @@ import (
"github.com/moby/moby/api/types/swarm" "github.com/moby/moby/api/types/swarm"
) )
// SecretUpdateOptions holds options for updating a secret.
type SecretUpdateOptions struct {
Version swarm.Version
Spec swarm.SecretSpec
}
type SecretUpdateResult struct{}
// SecretUpdate attempts to update a secret. // SecretUpdate attempts to update a secret.
func (cli *Client) SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error { func (cli *Client) SecretUpdate(ctx context.Context, id string, options SecretUpdateOptions) (SecretUpdateResult, error) {
id, err := trimID("secret", id) id, err := trimID("secret", id)
if err != nil { if err != nil {
return err return SecretUpdateResult{}, err
} }
query := url.Values{} query := url.Values{}
query.Set("version", version.String()) query.Set("version", options.Version.String())
resp, err := cli.post(ctx, "/secrets/"+id+"/update", query, secret, nil) resp, err := cli.post(ctx, "/secrets/"+id+"/update", query, options.Spec, nil)
defer ensureReaderClosed(resp) defer ensureReaderClosed(resp)
return err if err != nil {
return SecretUpdateResult{}, err
}
return SecretUpdateResult{}, nil
} }

View File

@@ -8,7 +8,6 @@ import (
"testing" "testing"
cerrdefs "github.com/containerd/errdefs" cerrdefs "github.com/containerd/errdefs"
"github.com/moby/moby/api/types/swarm"
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp" is "gotest.tools/v3/assert/cmp"
) )
@@ -17,14 +16,14 @@ func TestSecretUpdateError(t *testing.T) {
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error"))) client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err) assert.NilError(t, err)
err = client.SecretUpdate(context.Background(), "secret_id", swarm.Version{}, swarm.SecretSpec{}) _, err = client.SecretUpdate(context.Background(), "secret_id", SecretUpdateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal)) assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
err = client.SecretUpdate(context.Background(), "", swarm.Version{}, swarm.SecretSpec{}) _, err = client.SecretUpdate(context.Background(), "", SecretUpdateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty")) assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.SecretUpdate(context.Background(), " ", swarm.Version{}, swarm.SecretSpec{}) _, err = client.SecretUpdate(context.Background(), " ", SecretUpdateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty")) assert.Check(t, is.ErrorContains(err, "value is empty"))
} }
@@ -43,6 +42,6 @@ func TestSecretUpdate(t *testing.T) {
})) }))
assert.NilError(t, err) assert.NilError(t, err)
err = client.SecretUpdate(context.Background(), "secret_id", swarm.Version{}, swarm.SecretSpec{}) _, err = client.SecretUpdate(context.Background(), "secret_id", SecretUpdateOptions{})
assert.NilError(t, err) assert.NilError(t, err)
} }

View File

@@ -12,6 +12,7 @@ import (
"github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/mount" "github.com/moby/moby/api/types/mount"
"github.com/moby/moby/api/types/swarm" "github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/client"
"github.com/moby/moby/v2/integration-cli/checker" "github.com/moby/moby/v2/integration-cli/checker"
"github.com/moby/moby/v2/internal/testutil" "github.com/moby/moby/v2/internal/testutil"
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
@@ -71,16 +72,20 @@ func (s *DockerSwarmSuite) TestServiceCreateMountVolume(c *testing.T) {
func (s *DockerSwarmSuite) TestServiceCreateWithSecretSimple(c *testing.T) { func (s *DockerSwarmSuite) TestServiceCreateWithSecretSimple(c *testing.T) {
ctx := testutil.GetContext(c) ctx := testutil.GetContext(c)
d := s.AddDaemon(ctx, c, true, true) d := s.AddDaemon(ctx, c, true, true)
apiClient := d.NewClientT(c)
serviceName := "test-service-secret" serviceName := "test-service-secret"
testName := "test_secret" testName := "test_secret"
id := d.CreateSecret(c, swarm.SecretSpec{ scr, err := apiClient.SecretCreate(ctx, client.SecretCreateOptions{
Spec: swarm.SecretSpec{
Annotations: swarm.Annotations{ Annotations: swarm.Annotations{
Name: testName, Name: testName,
}, },
Data: []byte("TESTINGDATA"), Data: []byte("TESTINGDATA"),
},
}) })
assert.Assert(c, id != "", "secrets: %s", id) assert.NilError(c, err)
assert.Assert(c, scr.ID != "", "secrets: %s", scr.ID)
out, err := d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", serviceName, "--secret", testName, "busybox", "top") out, err := d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", serviceName, "--secret", testName, "busybox", "top")
assert.NilError(c, err, out) assert.NilError(c, err, out)
@@ -100,7 +105,8 @@ func (s *DockerSwarmSuite) TestServiceCreateWithSecretSimple(c *testing.T) {
out, err = d.Cmd("service", "rm", serviceName) out, err = d.Cmd("service", "rm", serviceName)
assert.NilError(c, err, out) assert.NilError(c, err, out)
d.DeleteSecret(c, testName) _, err = apiClient.SecretRemove(c.Context(), testName, client.SecretRemoveOptions{})
assert.NilError(c, err)
} }
func (s *DockerSwarmSuite) TestServiceCreateWithSecretSourceTargetPaths(c *testing.T) { func (s *DockerSwarmSuite) TestServiceCreateWithSecretSourceTargetPaths(c *testing.T) {
@@ -116,15 +122,18 @@ func (s *DockerSwarmSuite) TestServiceCreateWithSecretSourceTargetPaths(c *testi
var secretFlags []string var secretFlags []string
apiClient := d.NewClientT(c)
for testName, testTarget := range testPaths { for testName, testTarget := range testPaths {
id := d.CreateSecret(c, swarm.SecretSpec{ scr, err := apiClient.SecretCreate(ctx, client.SecretCreateOptions{
Spec: swarm.SecretSpec{
Annotations: swarm.Annotations{ Annotations: swarm.Annotations{
Name: testName, Name: testName,
}, },
Data: []byte("TESTINGDATA " + testName + " " + testTarget), Data: []byte("TESTINGDATA " + testName + " " + testTarget),
},
}) })
assert.Assert(c, id != "", "secrets: %s", id) assert.NilError(c, err)
assert.Assert(c, scr.ID != "", "secrets: %s", scr.ID)
secretFlags = append(secretFlags, "--secret", fmt.Sprintf("source=%s,target=%s", testName, testTarget)) secretFlags = append(secretFlags, "--secret", fmt.Sprintf("source=%s,target=%s", testName, testTarget))
} }
@@ -174,13 +183,17 @@ func (s *DockerSwarmSuite) TestServiceCreateWithSecretReferencedTwice(c *testing
ctx := testutil.GetContext(c) ctx := testutil.GetContext(c)
d := s.AddDaemon(ctx, c, true, true) d := s.AddDaemon(ctx, c, true, true)
id := d.CreateSecret(c, swarm.SecretSpec{ apiClient := d.NewClientT(c)
scr, err := apiClient.SecretCreate(ctx, client.SecretCreateOptions{
Spec: swarm.SecretSpec{
Annotations: swarm.Annotations{ Annotations: swarm.Annotations{
Name: "mysecret", Name: "mysecret",
}, },
Data: []byte("TESTINGDATA"), Data: []byte("TESTINGDATA"),
},
}) })
assert.Assert(c, id != "", "secrets: %s", id) assert.NilError(c, err)
assert.Assert(c, scr.ID != "", "secrets: %s", scr.ID)
serviceName := "svc" serviceName := "svc"
out, err := d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", serviceName, "--secret", "source=mysecret,target=target1", "--secret", "source=mysecret,target=target2", "busybox", "top") out, err := d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", serviceName, "--secret", "source=mysecret,target=target1", "--secret", "source=mysecret,target=target2", "busybox", "top")

View File

@@ -21,6 +21,7 @@ import (
"github.com/cloudflare/cfssl/helpers" "github.com/cloudflare/cfssl/helpers"
"github.com/moby/moby/api/types/swarm" "github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/api/types/versions" "github.com/moby/moby/api/types/versions"
"github.com/moby/moby/client"
"github.com/moby/moby/v2/daemon/libnetwork/driverapi" "github.com/moby/moby/v2/daemon/libnetwork/driverapi"
"github.com/moby/moby/v2/daemon/libnetwork/ipamapi" "github.com/moby/moby/v2/daemon/libnetwork/ipamapi"
remoteipam "github.com/moby/moby/v2/daemon/libnetwork/ipams/remote/api" remoteipam "github.com/moby/moby/v2/daemon/libnetwork/ipams/remote/api"
@@ -1985,20 +1986,27 @@ func (s *DockerSwarmSuite) TestSwarmClusterEventsNetwork(c *testing.T) {
func (s *DockerSwarmSuite) TestSwarmClusterEventsSecret(c *testing.T) { func (s *DockerSwarmSuite) TestSwarmClusterEventsSecret(c *testing.T) {
ctx := testutil.GetContext(c) ctx := testutil.GetContext(c)
d := s.AddDaemon(ctx, c, true, true) d := s.AddDaemon(ctx, c, true, true)
apiClient := d.NewClientT(c)
testName := "test_secret" testName := "test_secret"
id := d.CreateSecret(c, swarm.SecretSpec{ scr, err := apiClient.SecretCreate(ctx, client.SecretCreateOptions{
Spec: swarm.SecretSpec{
Annotations: swarm.Annotations{ Annotations: swarm.Annotations{
Name: testName, Name: testName,
}, },
Data: []byte("TESTINGDATA"), Data: []byte("TESTINGDATA"),
},
}) })
assert.Assert(c, id != "", "secrets: %s", id) assert.NilError(c, err)
assert.Assert(c, scr.ID != "", "secrets: %s", scr.ID)
id := scr.ID
waitForEvent(c, d, "0", "-f scope=swarm", "secret create "+id, defaultRetryCount) waitForEvent(c, d, "0", "-f scope=swarm", "secret create "+id, defaultRetryCount)
t1 := daemonUnixTime(c) t1 := daemonUnixTime(c)
d.DeleteSecret(c, id) _, err = apiClient.SecretRemove(c.Context(), id, client.SecretRemoveOptions{})
assert.NilError(c, err)
// filtered by secret // filtered by secret
waitForEvent(c, d, t1, "-f type=secret", "secret remove "+id, defaultRetryCount) waitForEvent(c, d, t1, "-f type=secret", "secret remove "+id, defaultRetryCount)
} }

View File

@@ -236,7 +236,9 @@ func TestTemplatedConfig(t *testing.T) {
}, },
Data: []byte("this is a secret"), Data: []byte("this is a secret"),
} }
referencedSecret, err := c.SecretCreate(ctx, referencedSecretSpec) referencedSecret, err := c.SecretCreate(ctx, client.SecretCreateOptions{
Spec: referencedSecretSpec,
})
assert.Check(t, err) assert.Check(t, err)
referencedConfigName := "referencedconfig-" + t.Name() referencedConfigName := "referencedconfig-" + t.Name()

View File

@@ -32,14 +32,14 @@ func TestSecretInspect(t *testing.T) {
testName := t.Name() testName := t.Name()
secretID := createSecret(ctx, t, c, testName, []byte("TESTINGDATA"), nil) secretID := createSecret(ctx, t, c, testName, []byte("TESTINGDATA"), nil)
insp, body, err := c.SecretInspectWithRaw(ctx, secretID) result, err := c.SecretInspect(ctx, secretID, client.SecretInspectOptions{})
assert.NilError(t, err) assert.NilError(t, err)
assert.Check(t, is.Equal(insp.Spec.Name, testName)) assert.Check(t, is.Equal(result.Secret.Spec.Name, testName))
var secret swarmtypes.Secret var secret swarmtypes.Secret
err = json.Unmarshal(body, &secret) err = json.Unmarshal(result.Raw, &secret)
assert.NilError(t, err) assert.NilError(t, err)
assert.Check(t, is.DeepEqual(secret, insp)) assert.Check(t, is.DeepEqual(secret, result.Secret))
} }
func TestSecretList(t *testing.T) { func TestSecretList(t *testing.T) {
@@ -51,12 +51,12 @@ func TestSecretList(t *testing.T) {
c := d.NewClientT(t) c := d.NewClientT(t)
defer c.Close() defer c.Close()
configs, err := c.SecretList(ctx, client.SecretListOptions{}) result, err := c.SecretList(ctx, client.SecretListOptions{})
assert.NilError(t, err) assert.NilError(t, err)
assert.Check(t, is.Equal(len(configs), 0)) assert.Check(t, is.Equal(len(result.Items), 0))
testName0 := "test0_" + t.Name() testName0 := "test0-" + t.Name()
testName1 := "test1_" + t.Name() testName1 := "test1-" + t.Name()
testNames := []string{testName0, testName1} testNames := []string{testName0, testName1}
sort.Strings(testNames) sort.Strings(testNames)
@@ -67,58 +67,66 @@ func TestSecretList(t *testing.T) {
secret1ID := createSecret(ctx, t, c, testName1, []byte("TESTINGDATA1"), map[string]string{"type": "production"}) secret1ID := createSecret(ctx, t, c, testName1, []byte("TESTINGDATA1"), map[string]string{"type": "production"})
// test by `secret ls` // test by `secret ls`
entries, err := c.SecretList(ctx, client.SecretListOptions{}) res, err := c.SecretList(ctx, client.SecretListOptions{})
assert.NilError(t, err) assert.NilError(t, err)
assert.Check(t, is.DeepEqual(secretNamesFromList(entries), testNames)) assert.Check(t, is.DeepEqual(namesFromList(res.Items), testNames))
testCases := []struct { testCases := []struct {
desc string
filters client.Filters filters client.Filters
expected []string expected []string
}{ }{
// test filter by name `secret ls --filter name=xxx`
{ {
desc: "test filter by name",
filters: make(client.Filters).Add("name", testName0), filters: make(client.Filters).Add("name", testName0),
expected: []string{testName0}, expected: []string{testName0},
}, },
// test filter by id `secret ls --filter id=xxx`
{ {
desc: "test filter by id",
filters: make(client.Filters).Add("id", secret1ID), filters: make(client.Filters).Add("id", secret1ID),
expected: []string{testName1}, expected: []string{testName1},
}, },
// test filter by label `secret ls --filter label=xxx`
{ {
desc: "test filter by label key only",
filters: make(client.Filters).Add("label", "type"), filters: make(client.Filters).Add("label", "type"),
expected: testNames, expected: testNames,
}, },
{ {
desc: "test filter by label key=value " + testName0,
filters: make(client.Filters).Add("label", "type=test"), filters: make(client.Filters).Add("label", "type=test"),
expected: []string{testName0}, expected: []string{testName0},
}, },
{ {
desc: "test filter by label key=value " + testName1,
filters: make(client.Filters).Add("label", "type=production"), filters: make(client.Filters).Add("label", "type=production"),
expected: []string{testName1}, expected: []string{testName1},
}, },
} }
for _, tc := range testCases { for _, tc := range testCases {
entries, err = c.SecretList(ctx, client.SecretListOptions{ t.Run(tc.desc, func(t *testing.T) {
ctx := testutil.StartSpan(ctx, t)
res, err = c.SecretList(ctx, client.SecretListOptions{
Filters: tc.filters, Filters: tc.filters,
}) })
assert.NilError(t, err) assert.NilError(t, err)
assert.Check(t, is.DeepEqual(secretNamesFromList(entries), tc.expected)) assert.Check(t, is.DeepEqual(namesFromList(res.Items), tc.expected))
})
} }
} }
func createSecret(ctx context.Context, t *testing.T, client client.APIClient, name string, data []byte, labels map[string]string) string { func createSecret(ctx context.Context, t *testing.T, apiClient client.APIClient, name string, data []byte, labels map[string]string) string {
secret, err := client.SecretCreate(ctx, swarmtypes.SecretSpec{ result, err := apiClient.SecretCreate(ctx, client.SecretCreateOptions{
Spec: swarmtypes.SecretSpec{
Annotations: swarmtypes.Annotations{ Annotations: swarmtypes.Annotations{
Name: name, Name: name,
Labels: labels, Labels: labels,
}, },
Data: data, Data: data,
},
}) })
assert.NilError(t, err) assert.NilError(t, err)
assert.Check(t, secret.ID != "") assert.Check(t, result.ID != "")
return secret.ID return result.ID
} }
func TestSecretsCreateAndDelete(t *testing.T) { func TestSecretsCreateAndDelete(t *testing.T) {
@@ -134,23 +142,25 @@ func TestSecretsCreateAndDelete(t *testing.T) {
secretID := createSecret(ctx, t, c, testName, []byte("TESTINGDATA"), nil) secretID := createSecret(ctx, t, c, testName, []byte("TESTINGDATA"), nil)
// create an already existing secret, daemon should return a status code of 409 // create an already existing secret, daemon should return a status code of 409
_, err := c.SecretCreate(ctx, swarmtypes.SecretSpec{ _, err := c.SecretCreate(ctx, client.SecretCreateOptions{
Spec: swarmtypes.SecretSpec{
Annotations: swarmtypes.Annotations{ Annotations: swarmtypes.Annotations{
Name: testName, Name: testName,
}, },
Data: []byte("TESTINGDATA"), Data: []byte("TESTINGDATA"),
},
}) })
assert.Check(t, cerrdefs.IsConflict(err)) assert.Check(t, cerrdefs.IsConflict(err))
assert.Check(t, is.ErrorContains(err, testName)) assert.Check(t, is.ErrorContains(err, testName))
err = c.SecretRemove(ctx, secretID) _, err = c.SecretRemove(ctx, secretID, client.SecretRemoveOptions{})
assert.NilError(t, err) assert.NilError(t, err)
_, _, err = c.SecretInspectWithRaw(ctx, secretID) _, err = c.SecretInspect(ctx, secretID, client.SecretInspectOptions{})
assert.Check(t, cerrdefs.IsNotFound(err)) assert.Check(t, cerrdefs.IsNotFound(err))
assert.Check(t, is.ErrorContains(err, secretID)) assert.Check(t, is.ErrorContains(err, secretID))
err = c.SecretRemove(ctx, "non-existing") _, err = c.SecretRemove(ctx, "non-existing", client.SecretRemoveOptions{})
assert.Check(t, cerrdefs.IsNotFound(err)) assert.Check(t, cerrdefs.IsNotFound(err))
assert.Check(t, is.ErrorContains(err, "non-existing")) assert.Check(t, is.ErrorContains(err, "non-existing"))
@@ -160,12 +170,12 @@ func TestSecretsCreateAndDelete(t *testing.T) {
"key2": "value2", "key2": "value2",
}) })
insp, _, err := c.SecretInspectWithRaw(ctx, secretID) result, err := c.SecretInspect(ctx, secretID, client.SecretInspectOptions{})
assert.NilError(t, err) assert.NilError(t, err)
assert.Check(t, is.Equal(insp.Spec.Name, testName)) assert.Check(t, is.Equal(result.Secret.Spec.Name, testName))
assert.Check(t, is.Equal(len(insp.Spec.Labels), 2)) assert.Check(t, is.Equal(len(result.Secret.Spec.Labels), 2))
assert.Check(t, is.Equal(insp.Spec.Labels["key1"], "value1")) assert.Check(t, is.Equal(result.Secret.Spec.Labels["key1"], "value1"))
assert.Check(t, is.Equal(insp.Spec.Labels["key2"], "value2")) assert.Check(t, is.Equal(result.Secret.Spec.Labels["key2"], "value2"))
} }
func TestSecretsUpdate(t *testing.T) { func TestSecretsUpdate(t *testing.T) {
@@ -180,41 +190,53 @@ func TestSecretsUpdate(t *testing.T) {
testName := "test_secret_" + t.Name() testName := "test_secret_" + t.Name()
secretID := createSecret(ctx, t, c, testName, []byte("TESTINGDATA"), nil) secretID := createSecret(ctx, t, c, testName, []byte("TESTINGDATA"), nil)
insp, _, err := c.SecretInspectWithRaw(ctx, secretID) insp, err := c.SecretInspect(ctx, secretID, client.SecretInspectOptions{})
assert.NilError(t, err) assert.NilError(t, err)
assert.Check(t, is.Equal(insp.ID, secretID)) assert.Check(t, is.Equal(insp.Secret.ID, secretID))
// test UpdateSecret with full ID // test UpdateSecret with full ID
insp.Spec.Labels = map[string]string{"test": "test1"} insp.Secret.Spec.Labels = map[string]string{"test": "test1"}
err = c.SecretUpdate(ctx, secretID, insp.Version, insp.Spec) _, err = c.SecretUpdate(ctx, secretID, client.SecretUpdateOptions{
Version: insp.Secret.Version,
Spec: insp.Secret.Spec,
})
assert.NilError(t, err) assert.NilError(t, err)
insp, _, err = c.SecretInspectWithRaw(ctx, secretID) insp, err = c.SecretInspect(ctx, secretID, client.SecretInspectOptions{})
assert.NilError(t, err) assert.NilError(t, err)
assert.Check(t, is.Equal(insp.Spec.Labels["test"], "test1")) assert.Check(t, is.Equal(insp.Secret.Spec.Labels["test"], "test1"))
// test UpdateSecret with full name // test UpdateSecret with full name
insp.Spec.Labels = map[string]string{"test": "test2"} insp.Secret.Spec.Labels = map[string]string{"test": "test2"}
err = c.SecretUpdate(ctx, testName, insp.Version, insp.Spec) _, err = c.SecretUpdate(ctx, testName, client.SecretUpdateOptions{
Version: insp.Secret.Version,
Spec: insp.Secret.Spec,
})
assert.NilError(t, err) assert.NilError(t, err)
insp, _, err = c.SecretInspectWithRaw(ctx, secretID) insp, err = c.SecretInspect(ctx, secretID, client.SecretInspectOptions{})
assert.NilError(t, err) assert.NilError(t, err)
assert.Check(t, is.Equal(insp.Spec.Labels["test"], "test2")) assert.Check(t, is.Equal(insp.Secret.Spec.Labels["test"], "test2"))
// test UpdateSecret with prefix ID // test UpdateSecret with prefix ID
insp.Spec.Labels = map[string]string{"test": "test3"} insp.Secret.Spec.Labels = map[string]string{"test": "test3"}
err = c.SecretUpdate(ctx, secretID[:1], insp.Version, insp.Spec) _, err = c.SecretUpdate(ctx, secretID[:1], client.SecretUpdateOptions{
Version: insp.Secret.Version,
Spec: insp.Secret.Spec,
})
assert.NilError(t, err) assert.NilError(t, err)
insp, _, err = c.SecretInspectWithRaw(ctx, secretID) insp, err = c.SecretInspect(ctx, secretID, client.SecretInspectOptions{})
assert.NilError(t, err) assert.NilError(t, err)
assert.Check(t, is.Equal(insp.Spec.Labels["test"], "test3")) assert.Check(t, is.Equal(insp.Secret.Spec.Labels["test"], "test3"))
// test UpdateSecret in updating Data which is not supported in daemon // test UpdateSecret in updating Data which is not supported in daemon
// this test will produce an error in func UpdateSecret // this test will produce an error in func UpdateSecret
insp.Spec.Data = []byte("TESTINGDATA2") insp.Secret.Spec.Data = []byte("TESTINGDATA2")
err = c.SecretUpdate(ctx, secretID, insp.Version, insp.Spec) _, err = c.SecretUpdate(ctx, secretID, client.SecretUpdateOptions{
Version: insp.Secret.Version,
Spec: insp.Secret.Spec,
})
assert.Check(t, cerrdefs.IsInvalidArgument(err)) assert.Check(t, cerrdefs.IsInvalidArgument(err))
assert.Check(t, is.ErrorContains(err, "only updates to Labels are allowed")) assert.Check(t, is.ErrorContains(err, "only updates to Labels are allowed"))
} }
@@ -236,7 +258,9 @@ func TestTemplatedSecret(t *testing.T) {
}, },
Data: []byte("this is a secret"), Data: []byte("this is a secret"),
} }
referencedSecret, err := c.SecretCreate(ctx, referencedSecretSpec) referencedSecret, err := c.SecretCreate(ctx, client.SecretCreateOptions{
Spec: referencedSecretSpec,
})
assert.Check(t, err) assert.Check(t, err)
referencedConfigName := "referencedconfig_" + t.Name() referencedConfigName := "referencedconfig_" + t.Name()
@@ -259,12 +283,15 @@ func TestTemplatedSecret(t *testing.T) {
Templating: &swarmtypes.Driver{ Templating: &swarmtypes.Driver{
Name: "golang", Name: "golang",
}, },
Data: []byte("SERVICE_NAME={{.Service.Name}}\n" + Data: []byte(`SERVICE_NAME={{.Service.Name}}
"{{secret \"referencedsecrettarget\"}}\n" + {{secret "referencedsecrettarget"}}
"{{config \"referencedconfigtarget\"}}\n"), {{config "referencedconfigtarget"}}
`),
} }
templatedSecret, err := c.SecretCreate(ctx, secretSpec) templatedSecret, err := c.SecretCreate(ctx, client.SecretCreateOptions{
Spec: secretSpec,
})
assert.Check(t, err) assert.Check(t, err)
const serviceName = "svc_templated_secret" const serviceName = "svc_templated_secret"
@@ -356,39 +383,39 @@ func TestSecretCreateResolve(t *testing.T) {
fakeName := secretID fakeName := secretID
fakeID := createSecret(ctx, t, c, fakeName, []byte("fake foo"), nil) fakeID := createSecret(ctx, t, c, fakeName, []byte("fake foo"), nil)
entries, err := c.SecretList(ctx, client.SecretListOptions{}) res, err := c.SecretList(ctx, client.SecretListOptions{})
assert.NilError(t, err) assert.NilError(t, err)
assert.Check(t, is.Contains(secretNamesFromList(entries), testName)) assert.Check(t, is.Contains(namesFromList(res.Items), testName))
assert.Check(t, is.Contains(secretNamesFromList(entries), fakeName)) assert.Check(t, is.Contains(namesFromList(res.Items), fakeName))
err = c.SecretRemove(ctx, secretID) _, err = c.SecretRemove(ctx, secretID, client.SecretRemoveOptions{})
assert.NilError(t, err) assert.NilError(t, err)
// Fake one will remain // Fake one will remain
entries, err = c.SecretList(ctx, client.SecretListOptions{}) res, err = c.SecretList(ctx, client.SecretListOptions{})
assert.NilError(t, err) assert.NilError(t, err)
assert.Assert(t, is.DeepEqual(secretNamesFromList(entries), []string{fakeName})) assert.Assert(t, is.DeepEqual(namesFromList(res.Items), []string{fakeName}))
// Remove based on name prefix of the fake one should not work // Remove based on name prefix of the fake one should not work
// as search is only done based on: // as search is only done based on:
// - Full ID // - Full ID
// - Full Name // - Full Name
// - Partial ID (prefix) // - Partial ID (prefix)
err = c.SecretRemove(ctx, fakeName[:5]) _, err = c.SecretRemove(ctx, fakeName[:5], client.SecretRemoveOptions{})
assert.Assert(t, err != nil) assert.Assert(t, err != nil)
entries, err = c.SecretList(ctx, client.SecretListOptions{}) res, err = c.SecretList(ctx, client.SecretListOptions{})
assert.NilError(t, err) assert.NilError(t, err)
assert.Assert(t, is.DeepEqual(secretNamesFromList(entries), []string{fakeName})) assert.Assert(t, is.DeepEqual(namesFromList(res.Items), []string{fakeName}))
// Remove based on ID prefix of the fake one should succeed // Remove based on ID prefix of the fake one should succeed
err = c.SecretRemove(ctx, fakeID[:5]) _, err = c.SecretRemove(ctx, fakeID[:5], client.SecretRemoveOptions{})
assert.NilError(t, err) assert.NilError(t, err)
entries, err = c.SecretList(ctx, client.SecretListOptions{}) res, err = c.SecretList(ctx, client.SecretListOptions{})
assert.NilError(t, err) assert.NilError(t, err)
assert.Assert(t, is.Equal(0, len(entries))) assert.Assert(t, is.Equal(0, len(res.Items)))
} }
func secretNamesFromList(entries []swarmtypes.Secret) []string { func namesFromList(entries []swarmtypes.Secret) []string {
var values []string var values []string
for _, entry := range entries { for _, entry := range entries {
values = append(values, entry.Spec.Name) values = append(values, entry.Spec.Name)

View File

@@ -199,11 +199,13 @@ func TestCreateServiceSecretFileMode(t *testing.T) {
defer apiClient.Close() defer apiClient.Close()
secretName := "TestSecret_" + t.Name() secretName := "TestSecret_" + t.Name()
secretResp, err := apiClient.SecretCreate(ctx, swarmtypes.SecretSpec{ secretResp, err := apiClient.SecretCreate(ctx, client.SecretCreateOptions{
Spec: swarmtypes.SecretSpec{
Annotations: swarmtypes.Annotations{ Annotations: swarmtypes.Annotations{
Name: secretName, Name: secretName,
}, },
Data: []byte("TESTSECRET"), Data: []byte("TESTSECRET"),
},
}) })
assert.NilError(t, err) assert.NilError(t, err)
@@ -242,7 +244,7 @@ func TestCreateServiceSecretFileMode(t *testing.T) {
assert.NilError(t, err) assert.NilError(t, err)
poll.WaitOn(t, swarm.NoTasksForService(ctx, apiClient, serviceID), swarm.ServicePoll) poll.WaitOn(t, swarm.NoTasksForService(ctx, apiClient, serviceID), swarm.ServicePoll)
err = apiClient.SecretRemove(ctx, secretName) _, err = apiClient.SecretRemove(ctx, secretName, client.SecretRemoveOptions{})
assert.NilError(t, err) assert.NilError(t, err)
} }

View File

@@ -83,11 +83,13 @@ func TestServiceUpdateSecrets(t *testing.T) {
secretName := "TestSecret_" + t.Name() secretName := "TestSecret_" + t.Name()
secretTarget := "targetName" secretTarget := "targetName"
secretResp, err := apiClient.SecretCreate(ctx, swarmtypes.SecretSpec{ secretResp, err := apiClient.SecretCreate(ctx, client.SecretCreateOptions{
Spec: swarmtypes.SecretSpec{
Annotations: swarmtypes.Annotations{ Annotations: swarmtypes.Annotations{
Name: secretName, Name: secretName,
}, },
Data: []byte("TESTINGDATA"), Data: []byte("TESTINGDATA"),
},
}) })
assert.NilError(t, err) assert.NilError(t, err)
assert.Check(t, secretResp.ID != "") assert.Check(t, secretResp.ID != "")

View File

@@ -1,74 +0,0 @@
package daemon
import (
"context"
"testing"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
)
// SecretConstructor defines a swarm secret constructor
type SecretConstructor func(*swarm.Secret)
// CreateSecret creates a secret given the specified spec
func (d *Daemon) CreateSecret(t testing.TB, secretSpec swarm.SecretSpec) string {
t.Helper()
cli := d.NewClientT(t)
defer cli.Close()
scr, err := cli.SecretCreate(context.Background(), secretSpec)
assert.NilError(t, err)
return scr.ID
}
// ListSecrets returns the list of the current swarm secrets
func (d *Daemon) ListSecrets(t testing.TB) []swarm.Secret {
t.Helper()
cli := d.NewClientT(t)
defer cli.Close()
secrets, err := cli.SecretList(context.Background(), client.SecretListOptions{})
assert.NilError(t, err)
return secrets
}
// GetSecret returns a swarm secret identified by the specified id
func (d *Daemon) GetSecret(t testing.TB, id string) *swarm.Secret {
t.Helper()
cli := d.NewClientT(t)
defer cli.Close()
secret, _, err := cli.SecretInspectWithRaw(context.Background(), id)
assert.NilError(t, err)
return &secret
}
// DeleteSecret removes the swarm secret identified by the specified id
func (d *Daemon) DeleteSecret(t testing.TB, id string) {
t.Helper()
cli := d.NewClientT(t)
defer cli.Close()
err := cli.SecretRemove(context.Background(), id)
assert.NilError(t, err)
}
// UpdateSecret updates the swarm secret identified by the specified id
// Currently, only label update is supported.
func (d *Daemon) UpdateSecret(t testing.TB, id string, f ...SecretConstructor) {
t.Helper()
cli := d.NewClientT(t)
defer cli.Close()
secret := d.GetSecret(t, id)
for _, fn := range f {
fn(secret)
}
err := cli.SecretUpdate(context.Background(), secret.ID, secret.Version, secret.Spec)
assert.NilError(t, err)
}

View File

@@ -203,11 +203,11 @@ type VolumeAPIClient interface {
// SecretAPIClient defines API client methods for secrets // SecretAPIClient defines API client methods for secrets
type SecretAPIClient interface { type SecretAPIClient interface {
SecretList(ctx context.Context, options SecretListOptions) ([]swarm.Secret, error) SecretList(ctx context.Context, options SecretListOptions) (SecretListResult, error)
SecretCreate(ctx context.Context, secret swarm.SecretSpec) (swarm.SecretCreateResponse, error) SecretCreate(ctx context.Context, options SecretCreateOptions) (SecretCreateResult, error)
SecretRemove(ctx context.Context, id string) error SecretRemove(ctx context.Context, id string, options SecretRemoveOptions) (SecretRemoveResult, error)
SecretInspectWithRaw(ctx context.Context, name string) (swarm.Secret, []byte, error) SecretInspect(ctx context.Context, id string, options SecretInspectOptions) (SecretInspectResult, error)
SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error SecretUpdate(ctx context.Context, id string, options SecretUpdateOptions) (SecretUpdateResult, error)
} }
// ConfigAPIClient defines API client methods for configs // ConfigAPIClient defines API client methods for configs

View File

@@ -7,15 +7,28 @@ import (
"github.com/moby/moby/api/types/swarm" "github.com/moby/moby/api/types/swarm"
) )
// SecretCreateOptions holds options for creating a secret.
type SecretCreateOptions struct {
Spec swarm.SecretSpec
}
// SecretCreateResult holds the result from the [Client.SecretCreate] method.
type SecretCreateResult struct {
ID string
}
// SecretCreate creates a new secret. // SecretCreate creates a new secret.
func (cli *Client) SecretCreate(ctx context.Context, secret swarm.SecretSpec) (swarm.SecretCreateResponse, error) { func (cli *Client) SecretCreate(ctx context.Context, options SecretCreateOptions) (SecretCreateResult, error) {
resp, err := cli.post(ctx, "/secrets/create", nil, secret, nil) resp, err := cli.post(ctx, "/secrets/create", nil, options.Spec, nil)
defer ensureReaderClosed(resp) defer ensureReaderClosed(resp)
if err != nil { if err != nil {
return swarm.SecretCreateResponse{}, err return SecretCreateResult{}, err
} }
var response swarm.SecretCreateResponse var out swarm.ConfigCreateResponse
err = json.NewDecoder(resp.Body).Decode(&response) err = json.NewDecoder(resp.Body).Decode(&out)
return response, err if err != nil {
return SecretCreateResult{}, err
}
return SecretCreateResult{ID: out.ID}, nil
} }

View File

@@ -1,34 +1,35 @@
package client package client
import ( import (
"bytes"
"context" "context"
"encoding/json"
"io"
"github.com/moby/moby/api/types/swarm" "github.com/moby/moby/api/types/swarm"
) )
// SecretInspectWithRaw returns the secret information with raw data // SecretInspectOptions holds options for inspecting a secret.
func (cli *Client) SecretInspectWithRaw(ctx context.Context, id string) (swarm.Secret, []byte, error) { type SecretInspectOptions struct {
// Add future optional parameters here
}
// SecretInspectResult holds the result from the [Client.SecretInspect]. method.
type SecretInspectResult struct {
Secret swarm.Secret
Raw []byte
}
// SecretInspect returns the secret information with raw data.
func (cli *Client) SecretInspect(ctx context.Context, id string, options SecretInspectOptions) (SecretInspectResult, error) {
id, err := trimID("secret", id) id, err := trimID("secret", id)
if err != nil { if err != nil {
return swarm.Secret{}, nil, err return SecretInspectResult{}, err
} }
resp, err := cli.get(ctx, "/secrets/"+id, nil, nil) resp, err := cli.get(ctx, "/secrets/"+id, nil, nil)
defer ensureReaderClosed(resp) defer ensureReaderClosed(resp)
if err != nil { if err != nil {
return swarm.Secret{}, nil, err return SecretInspectResult{}, err
} }
body, err := io.ReadAll(resp.Body) var out SecretInspectResult
if err != nil { out.Raw, err = decodeWithRaw(resp, &out.Secret)
return swarm.Secret{}, nil, err return out, err
}
var secret swarm.Secret
rdr := bytes.NewReader(body)
err = json.NewDecoder(rdr).Decode(&secret)
return secret, body, err
} }

View File

@@ -8,18 +8,31 @@ import (
"github.com/moby/moby/api/types/swarm" "github.com/moby/moby/api/types/swarm"
) )
// SecretList returns the list of secrets. // SecretListOptions holds parameters to list secrets
func (cli *Client) SecretList(ctx context.Context, options SecretListOptions) ([]swarm.Secret, error) { type SecretListOptions struct {
query := url.Values{} Filters Filters
}
// SecretListResult holds the result from the [client.SecretList] method.
type SecretListResult struct {
Items []swarm.Secret
}
// SecretList returns the list of secrets.
func (cli *Client) SecretList(ctx context.Context, options SecretListOptions) (SecretListResult, error) {
query := url.Values{}
options.Filters.updateURLValues(query) options.Filters.updateURLValues(query)
resp, err := cli.get(ctx, "/secrets", query, nil) resp, err := cli.get(ctx, "/secrets", query, nil)
defer ensureReaderClosed(resp) defer ensureReaderClosed(resp)
if err != nil { if err != nil {
return nil, err return SecretListResult{}, err
} }
var secrets []swarm.Secret var out SecretListResult
err = json.NewDecoder(resp.Body).Decode(&secrets) err = json.NewDecoder(resp.Body).Decode(&out.Items)
return secrets, err if err != nil {
return SecretListResult{}, err
}
return out, nil
} }

View File

@@ -1,6 +0,0 @@
package client
// SecretListOptions holds parameters to list secrets
type SecretListOptions struct {
Filters Filters
}

View File

@@ -2,13 +2,24 @@ package client
import "context" import "context"
type SecretRemoveOptions struct {
// Add future optional parameters here
}
type SecretRemoveResult struct {
// Add future fields here
}
// SecretRemove removes a secret. // SecretRemove removes a secret.
func (cli *Client) SecretRemove(ctx context.Context, id string) error { func (cli *Client) SecretRemove(ctx context.Context, id string, options SecretRemoveOptions) (SecretRemoveResult, error) {
id, err := trimID("secret", id) id, err := trimID("secret", id)
if err != nil { if err != nil {
return err return SecretRemoveResult{}, err
} }
resp, err := cli.delete(ctx, "/secrets/"+id, nil, nil) resp, err := cli.delete(ctx, "/secrets/"+id, nil, nil)
defer ensureReaderClosed(resp) defer ensureReaderClosed(resp)
return err if err != nil {
return SecretRemoveResult{}, err
}
return SecretRemoveResult{}, nil
} }

View File

@@ -7,15 +7,26 @@ import (
"github.com/moby/moby/api/types/swarm" "github.com/moby/moby/api/types/swarm"
) )
// SecretUpdateOptions holds options for updating a secret.
type SecretUpdateOptions struct {
Version swarm.Version
Spec swarm.SecretSpec
}
type SecretUpdateResult struct{}
// SecretUpdate attempts to update a secret. // SecretUpdate attempts to update a secret.
func (cli *Client) SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error { func (cli *Client) SecretUpdate(ctx context.Context, id string, options SecretUpdateOptions) (SecretUpdateResult, error) {
id, err := trimID("secret", id) id, err := trimID("secret", id)
if err != nil { if err != nil {
return err return SecretUpdateResult{}, err
} }
query := url.Values{} query := url.Values{}
query.Set("version", version.String()) query.Set("version", options.Version.String())
resp, err := cli.post(ctx, "/secrets/"+id+"/update", query, secret, nil) resp, err := cli.post(ctx, "/secrets/"+id+"/update", query, options.Spec, nil)
defer ensureReaderClosed(resp) defer ensureReaderClosed(resp)
return err if err != nil {
return SecretUpdateResult{}, err
}
return SecretUpdateResult{}, nil
} }