Merge pull request #51244 from austinvazquez/refactor-client-swarm

client: refactor swarm api functions to wrap params/responses
This commit is contained in:
Austin Vazquez
2025-10-21 11:35:56 -05:00
committed by GitHub
28 changed files with 318 additions and 126 deletions

View File

@@ -174,13 +174,13 @@ type ServiceAPIClient interface {
// SwarmAPIClient defines API client methods for the swarm
type SwarmAPIClient interface {
SwarmInit(ctx context.Context, req swarm.InitRequest) (string, error)
SwarmJoin(ctx context.Context, req swarm.JoinRequest) error
SwarmGetUnlockKey(ctx context.Context) (swarm.UnlockKeyResponse, error)
SwarmUnlock(ctx context.Context, req swarm.UnlockRequest) error
SwarmLeave(ctx context.Context, force bool) error
SwarmInspect(ctx context.Context) (swarm.Swarm, error)
SwarmUpdate(ctx context.Context, version swarm.Version, swarm swarm.Spec, flags SwarmUpdateFlags) error
SwarmInit(ctx context.Context, options SwarmInitOptions) (SwarmInitResult, error)
SwarmJoin(ctx context.Context, options SwarmJoinOptions) (SwarmJoinResult, error)
SwarmGetUnlockKey(ctx context.Context) (SwarmGetUnlockKeyResult, error)
SwarmUnlock(ctx context.Context, options SwarmUnlockOptions) (SwarmUnlockResult, error)
SwarmLeave(ctx context.Context, options SwarmLeaveOptions) (SwarmLeaveResult, error)
SwarmInspect(ctx context.Context) (SwarmInspectResult, error)
SwarmUpdate(ctx context.Context, version swarm.Version, options SwarmUpdateOptions) (SwarmUpdateResult, error)
}
// SystemAPIClient defines API client methods for the system

View File

@@ -7,15 +7,20 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// SwarmGetUnlockKeyResult contains the swarm unlock key.
type SwarmGetUnlockKeyResult struct {
Key string
}
// SwarmGetUnlockKey retrieves the swarm's unlock key.
func (cli *Client) SwarmGetUnlockKey(ctx context.Context) (swarm.UnlockKeyResponse, error) {
func (cli *Client) SwarmGetUnlockKey(ctx context.Context) (SwarmGetUnlockKeyResult, error) {
resp, err := cli.get(ctx, "/swarm/unlockkey", nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return swarm.UnlockKeyResponse{}, err
return SwarmGetUnlockKeyResult{}, err
}
var response swarm.UnlockKeyResponse
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
return SwarmGetUnlockKeyResult{Key: response.UnlockKey}, err
}

View File

@@ -49,7 +49,7 @@ func TestSwarmGetUnlockKey(t *testing.T) {
}))
assert.NilError(t, err)
resp, err := client.SwarmGetUnlockKey(context.Background())
result, err := client.SwarmGetUnlockKey(context.Background())
assert.NilError(t, err)
assert.Check(t, is.Equal(unlockKey, resp.UnlockKey))
assert.Check(t, is.Equal(unlockKey, result.Key))
}

View File

@@ -3,19 +3,52 @@ package client
import (
"context"
"encoding/json"
"net/netip"
"github.com/moby/moby/api/types/swarm"
)
// SwarmInitOptions contains options for initializing a new swarm.
type SwarmInitOptions struct {
ListenAddr string
AdvertiseAddr string
DataPathAddr string
DataPathPort uint32
ForceNewCluster bool
Spec swarm.Spec
AutoLockManagers bool
Availability swarm.NodeAvailability
DefaultAddrPool []netip.Prefix
SubnetSize uint32
}
// SwarmInitResult contains the result of a SwarmInit operation.
type SwarmInitResult struct {
NodeID string
}
// SwarmInit initializes the swarm.
func (cli *Client) SwarmInit(ctx context.Context, req swarm.InitRequest) (string, error) {
func (cli *Client) SwarmInit(ctx context.Context, options SwarmInitOptions) (SwarmInitResult, error) {
req := swarm.InitRequest{
ListenAddr: options.ListenAddr,
AdvertiseAddr: options.AdvertiseAddr,
DataPathAddr: options.DataPathAddr,
DataPathPort: options.DataPathPort,
ForceNewCluster: options.ForceNewCluster,
Spec: options.Spec,
AutoLockManagers: options.AutoLockManagers,
Availability: options.Availability,
DefaultAddrPool: options.DefaultAddrPool,
SubnetSize: options.SubnetSize,
}
resp, err := cli.post(ctx, "/swarm/init", nil, req, nil)
defer ensureReaderClosed(resp)
if err != nil {
return "", err
return SwarmInitResult{}, err
}
var response string
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
var nodeID string
err = json.NewDecoder(resp.Body).Decode(&nodeID)
return SwarmInitResult{NodeID: nodeID}, err
}

View File

@@ -8,7 +8,6 @@ import (
"testing"
cerrdefs "github.com/containerd/errdefs"
"github.com/moby/moby/api/types/swarm"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
@@ -17,7 +16,7 @@ func TestSwarmInitError(t *testing.T) {
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.SwarmInit(context.Background(), swarm.InitRequest{})
_, err = client.SwarmInit(context.Background(), SwarmInitOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -35,9 +34,9 @@ func TestSwarmInit(t *testing.T) {
}))
assert.NilError(t, err)
resp, err := client.SwarmInit(context.Background(), swarm.InitRequest{
result, err := client.SwarmInit(context.Background(), SwarmInitOptions{
ListenAddr: "0.0.0.0:2377",
})
assert.NilError(t, err)
assert.Check(t, is.Equal(resp, "body"))
assert.Check(t, is.Equal(result.NodeID, "body"))
}

View File

@@ -7,15 +7,20 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// type SwarmInspectResult represents the result of a SwarmInspect operation.
type SwarmInspectResult struct {
Swarm swarm.Swarm
}
// SwarmInspect inspects the swarm.
func (cli *Client) SwarmInspect(ctx context.Context) (swarm.Swarm, error) {
func (cli *Client) SwarmInspect(ctx context.Context) (SwarmInspectResult, error) {
resp, err := cli.get(ctx, "/swarm", nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return swarm.Swarm{}, err
return SwarmInspectResult{}, err
}
var response swarm.Swarm
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
var s swarm.Swarm
err = json.NewDecoder(resp.Body).Decode(&s)
return SwarmInspectResult{Swarm: s}, err
}

View File

@@ -43,7 +43,7 @@ func TestSwarmInspect(t *testing.T) {
}))
assert.NilError(t, err)
swarmInspect, err := client.SwarmInspect(context.Background())
inspect, err := client.SwarmInspect(context.Background())
assert.NilError(t, err)
assert.Check(t, is.Equal(swarmInspect.ID, "swarm_id"))
assert.Check(t, is.Equal(inspect.Swarm.ID, "swarm_id"))
}

View File

@@ -6,9 +6,31 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// SwarmJoinOptions specifies options for joining a swarm.
type SwarmJoinOptions struct {
ListenAddr string
AdvertiseAddr string
DataPathAddr string
RemoteAddrs []string
JoinToken string // accept by secret
Availability swarm.NodeAvailability
}
// SwarmJoinResult contains the result of joining a swarm.
type SwarmJoinResult struct{}
// SwarmJoin joins the swarm.
func (cli *Client) SwarmJoin(ctx context.Context, req swarm.JoinRequest) error {
func (cli *Client) SwarmJoin(ctx context.Context, options SwarmJoinOptions) (SwarmJoinResult, error) {
req := swarm.JoinRequest{
ListenAddr: options.ListenAddr,
AdvertiseAddr: options.AdvertiseAddr,
DataPathAddr: options.DataPathAddr,
RemoteAddrs: options.RemoteAddrs,
JoinToken: options.JoinToken,
Availability: options.Availability,
}
resp, err := cli.post(ctx, "/swarm/join", nil, req, nil)
defer ensureReaderClosed(resp)
return err
return SwarmJoinResult{}, err
}

View File

@@ -8,7 +8,6 @@ import (
"testing"
cerrdefs "github.com/containerd/errdefs"
"github.com/moby/moby/api/types/swarm"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
@@ -17,7 +16,7 @@ func TestSwarmJoinError(t *testing.T) {
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
err = client.SwarmJoin(context.Background(), swarm.JoinRequest{})
_, err = client.SwarmJoin(context.Background(), SwarmJoinOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -35,7 +34,7 @@ func TestSwarmJoin(t *testing.T) {
}))
assert.NilError(t, err)
err = client.SwarmJoin(context.Background(), swarm.JoinRequest{
_, err = client.SwarmJoin(context.Background(), SwarmJoinOptions{
ListenAddr: "0.0.0.0:2377",
})
assert.NilError(t, err)

View File

@@ -5,13 +5,21 @@ import (
"net/url"
)
// SwarmLeaveOptions contains options for leaving a swarm.
type SwarmLeaveOptions struct {
Force bool
}
// SwarmLeaveResult represents the result of a SwarmLeave operation.
type SwarmLeaveResult struct{}
// SwarmLeave leaves the swarm.
func (cli *Client) SwarmLeave(ctx context.Context, force bool) error {
func (cli *Client) SwarmLeave(ctx context.Context, options SwarmLeaveOptions) (SwarmLeaveResult, error) {
query := url.Values{}
if force {
if options.Force {
query.Set("force", "1")
}
resp, err := cli.post(ctx, "/swarm/leave", query, nil, nil)
defer ensureReaderClosed(resp)
return err
return SwarmLeaveResult{}, err
}

View File

@@ -17,7 +17,7 @@ func TestSwarmLeaveError(t *testing.T) {
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
err = client.SwarmLeave(context.Background(), false)
_, err = client.SwarmLeave(context.Background(), SwarmLeaveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -53,7 +53,7 @@ func TestSwarmLeave(t *testing.T) {
}))
assert.NilError(t, err)
err = client.SwarmLeave(context.Background(), leaveCase.force)
_, err = client.SwarmLeave(context.Background(), SwarmLeaveOptions{Force: leaveCase.force})
assert.NilError(t, err)
}
}

View File

@@ -6,9 +6,20 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// SwarmUnlockOptions specifies options for unlocking a swarm.
type SwarmUnlockOptions struct {
Key string
}
// SwarmUnlockResult represents the result of unlocking a swarm.
type SwarmUnlockResult struct{}
// SwarmUnlock unlocks locked swarm.
func (cli *Client) SwarmUnlock(ctx context.Context, req swarm.UnlockRequest) error {
func (cli *Client) SwarmUnlock(ctx context.Context, options SwarmUnlockOptions) (SwarmUnlockResult, error) {
req := &swarm.UnlockRequest{
UnlockKey: options.Key,
}
resp, err := cli.post(ctx, "/swarm/unlock", nil, req, nil)
defer ensureReaderClosed(resp)
return err
return SwarmUnlockResult{}, err
}

View File

@@ -8,7 +8,6 @@ import (
"testing"
cerrdefs "github.com/containerd/errdefs"
"github.com/moby/moby/api/types/swarm"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
@@ -17,7 +16,7 @@ func TestSwarmUnlockError(t *testing.T) {
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
err = client.SwarmUnlock(context.Background(), swarm.UnlockRequest{UnlockKey: "SWMKEY-1-y6guTZNTwpQeTL5RhUfOsdBdXoQjiB2GADHSRJvbXeU"})
_, err = client.SwarmUnlock(context.Background(), SwarmUnlockOptions{Key: "SWMKEY-1-y6guTZNTwpQeTL5RhUfOsdBdXoQjiB2GADHSRJvbXeU"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -35,6 +34,6 @@ func TestSwarmUnlock(t *testing.T) {
}))
assert.NilError(t, err)
err = client.SwarmUnlock(context.Background(), swarm.UnlockRequest{UnlockKey: "SWMKEY-1-y6guTZNTwpQeTL5RhUfOsdBdXoQjiB2GADHSRJvbXeU"})
_, err = client.SwarmUnlock(context.Background(), SwarmUnlockOptions{Key: "SWMKEY-1-y6guTZNTwpQeTL5RhUfOsdBdXoQjiB2GADHSRJvbXeU"})
assert.NilError(t, err)
}

View File

@@ -8,14 +8,25 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// SwarmUpdateOptions contains options for updating a swarm.
type SwarmUpdateOptions struct {
Swarm swarm.Spec
RotateWorkerToken bool
RotateManagerToken bool
RotateManagerUnlockKey bool
}
// SwarmUpdateResult represents the result of a SwarmUpdate operation.
type SwarmUpdateResult struct{}
// SwarmUpdate updates the swarm.
func (cli *Client) SwarmUpdate(ctx context.Context, version swarm.Version, swarm swarm.Spec, flags SwarmUpdateFlags) error {
func (cli *Client) SwarmUpdate(ctx context.Context, version swarm.Version, options SwarmUpdateOptions) (SwarmUpdateResult, error) {
query := url.Values{}
query.Set("version", version.String())
query.Set("rotateWorkerToken", strconv.FormatBool(flags.RotateWorkerToken))
query.Set("rotateManagerToken", strconv.FormatBool(flags.RotateManagerToken))
query.Set("rotateManagerUnlockKey", strconv.FormatBool(flags.RotateManagerUnlockKey))
resp, err := cli.post(ctx, "/swarm/update", query, swarm, nil)
query.Set("rotateWorkerToken", strconv.FormatBool(options.RotateWorkerToken))
query.Set("rotateManagerToken", strconv.FormatBool(options.RotateManagerToken))
query.Set("rotateManagerUnlockKey", strconv.FormatBool(options.RotateManagerUnlockKey))
resp, err := cli.post(ctx, "/swarm/update", query, options.Swarm, nil)
defer ensureReaderClosed(resp)
return err
return SwarmUpdateResult{}, err
}

View File

@@ -1,8 +0,0 @@
package client
// SwarmUpdateFlags contains flags for SwarmUpdate.
type SwarmUpdateFlags struct {
RotateWorkerToken bool
RotateManagerToken bool
RotateManagerUnlockKey bool
}

View File

@@ -17,7 +17,7 @@ func TestSwarmUpdateError(t *testing.T) {
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
err = client.SwarmUpdate(context.Background(), swarm.Version{}, swarm.Spec{}, SwarmUpdateFlags{})
_, err = client.SwarmUpdate(context.Background(), swarm.Version{}, SwarmUpdateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -35,6 +35,6 @@ func TestSwarmUpdate(t *testing.T) {
}))
assert.NilError(t, err)
err = client.SwarmUpdate(context.Background(), swarm.Version{}, swarm.Spec{}, SwarmUpdateFlags{})
_, err = client.SwarmUpdate(context.Background(), swarm.Version{}, SwarmUpdateOptions{})
assert.NilError(t, err)
}

View File

@@ -91,7 +91,7 @@ func (s *DockerSwarmSuite) TestAPISwarmJoinToken(c *testing.T) {
d2 := s.AddDaemon(ctx, c, false, false)
c2 := d2.NewClientT(c)
err := c2.SwarmJoin(testutil.GetContext(c), swarm.JoinRequest{
_, err := c2.SwarmJoin(testutil.GetContext(c), client.SwarmJoinOptions{
ListenAddr: d2.SwarmListenAddr(),
RemoteAddrs: []string{d1.SwarmListenAddr()},
})
@@ -99,7 +99,7 @@ func (s *DockerSwarmSuite) TestAPISwarmJoinToken(c *testing.T) {
info := d2.SwarmInfo(ctx, c)
assert.Equal(c, info.LocalNodeState, swarm.LocalNodeStateInactive)
err = c2.SwarmJoin(testutil.GetContext(c), swarm.JoinRequest{
_, err = c2.SwarmJoin(testutil.GetContext(c), client.SwarmJoinOptions{
ListenAddr: d2.SwarmListenAddr(),
JoinToken: "foobaz",
RemoteAddrs: []string{d1.SwarmListenAddr()},
@@ -124,7 +124,7 @@ func (s *DockerSwarmSuite) TestAPISwarmJoinToken(c *testing.T) {
// change tokens
d1.RotateTokens(c)
err = c2.SwarmJoin(testutil.GetContext(c), swarm.JoinRequest{
_, err = c2.SwarmJoin(testutil.GetContext(c), client.SwarmJoinOptions{
ListenAddr: d2.SwarmListenAddr(),
JoinToken: workerToken,
RemoteAddrs: []string{d1.SwarmListenAddr()},
@@ -145,7 +145,7 @@ func (s *DockerSwarmSuite) TestAPISwarmJoinToken(c *testing.T) {
// change spec, don't change tokens
d1.UpdateSwarm(c, func(s *swarm.Spec) {})
err = c2.SwarmJoin(testutil.GetContext(c), swarm.JoinRequest{
_, err = c2.SwarmJoin(testutil.GetContext(c), client.SwarmJoinOptions{
ListenAddr: d2.SwarmListenAddr(),
RemoteAddrs: []string{d1.SwarmListenAddr()},
})
@@ -192,7 +192,7 @@ func (s *DockerSwarmSuite) TestAPISwarmCAHash(c *testing.T) {
splitToken[2] = "1kxftv4ofnc6mt30lmgipg6ngf9luhwqopfk1tz6bdmnkubg0e"
replacementToken := strings.Join(splitToken, "-")
c2 := d2.NewClientT(c)
err := c2.SwarmJoin(testutil.GetContext(c), swarm.JoinRequest{
_, err := c2.SwarmJoin(testutil.GetContext(c), client.SwarmJoinOptions{
ListenAddr: d2.SwarmListenAddr(),
JoinToken: replacementToken,
RemoteAddrs: []string{d1.SwarmListenAddr()},
@@ -463,7 +463,7 @@ func (s *DockerSwarmSuite) TestAPISwarmLeaveOnPendingJoin(c *testing.T) {
id = strings.TrimSpace(id)
c2 := d2.NewClientT(c)
err = c2.SwarmJoin(testutil.GetContext(c), swarm.JoinRequest{
_, err = c2.SwarmJoin(testutil.GetContext(c), client.SwarmJoinOptions{
ListenAddr: d2.SwarmListenAddr(),
RemoteAddrs: []string{"123.123.123.123:1234"},
})
@@ -487,8 +487,8 @@ func (s *DockerSwarmSuite) TestAPISwarmRestoreOnPendingJoin(c *testing.T) {
ctx := testutil.GetContext(c)
d := s.AddDaemon(ctx, c, false, false)
client := d.NewClientT(c)
err := client.SwarmJoin(testutil.GetContext(c), swarm.JoinRequest{
cli := d.NewClientT(c)
_, err := cli.SwarmJoin(testutil.GetContext(c), client.SwarmJoinOptions{
ListenAddr: d.SwarmListenAddr(),
RemoteAddrs: []string{"123.123.123.123:1234"},
})
@@ -909,8 +909,8 @@ func (s *DockerSwarmSuite) TestAPISwarmErrorHandling(c *testing.T) {
assert.NilError(c, err)
defer ln.Close()
d := s.AddDaemon(ctx, c, false, false)
client := d.NewClientT(c)
_, err = client.SwarmInit(testutil.GetContext(c), swarm.InitRequest{
cli := d.NewClientT(c)
_, err = cli.SwarmInit(testutil.GetContext(c), client.SwarmInitOptions{
ListenAddr: d.SwarmListenAddr(),
})
assert.ErrorContains(c, err, "address already in use")

View File

@@ -9,6 +9,7 @@ import (
"github.com/moby/moby/api/types/build"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/client"
"github.com/moby/moby/v2/internal/testutil"
"github.com/moby/moby/v2/internal/testutil/daemon"
"github.com/moby/moby/v2/internal/testutil/request"
@@ -73,7 +74,7 @@ func TestPingSwarmHeader(t *testing.T) {
assert.Equal(t, p.SwarmStatus.ControlAvailable, false)
})
_, err := apiClient.SwarmInit(ctx, swarm.InitRequest{ListenAddr: "127.0.0.1", AdvertiseAddr: "127.0.0.1:2377"})
_, err := apiClient.SwarmInit(ctx, client.SwarmInitOptions{ListenAddr: "127.0.0.1", AdvertiseAddr: "127.0.0.1:2377"})
assert.NilError(t, err)
t.Run("after swarm init", func(t *testing.T) {
@@ -84,7 +85,7 @@ func TestPingSwarmHeader(t *testing.T) {
assert.Equal(t, p.SwarmStatus.ControlAvailable, true)
})
err = apiClient.SwarmLeave(ctx, true)
_, err = apiClient.SwarmLeave(ctx, client.SwarmLeaveOptions{Force: true})
assert.NilError(t, err)
t.Run("after swarm leave", func(t *testing.T) {

View File

@@ -101,7 +101,18 @@ func (d *Daemon) SwarmInitWithError(ctx context.Context, t testing.TB, req swarm
}
cli := d.NewClientT(t)
defer cli.Close()
_, err := cli.SwarmInit(ctx, req)
_, err := cli.SwarmInit(ctx, client.SwarmInitOptions{
ListenAddr: req.ListenAddr,
AdvertiseAddr: req.AdvertiseAddr,
DataPathAddr: req.DataPathAddr,
DataPathPort: req.DataPathPort,
ForceNewCluster: req.ForceNewCluster,
Spec: req.Spec,
AutoLockManagers: req.AutoLockManagers,
Availability: req.Availability,
DefaultAddrPool: req.DefaultAddrPool,
SubnetSize: req.SubnetSize,
})
if err == nil {
d.CachedInfo = d.Info(t)
}
@@ -123,7 +134,14 @@ func (d *Daemon) SwarmJoin(ctx context.Context, t testing.TB, req swarm.JoinRequ
}
cli := d.NewClientT(t)
defer cli.Close()
err := cli.SwarmJoin(ctx, req)
_, err := cli.SwarmJoin(ctx, client.SwarmJoinOptions{
ListenAddr: req.ListenAddr,
AdvertiseAddr: req.AdvertiseAddr,
DataPathAddr: req.DataPathAddr,
RemoteAddrs: req.RemoteAddrs,
JoinToken: req.JoinToken,
Availability: req.Availability,
})
assert.NilError(t, err, "[%s] joining swarm", d.id)
d.CachedInfo = d.Info(t)
}
@@ -136,7 +154,8 @@ func (d *Daemon) SwarmJoin(ctx context.Context, t testing.TB, req swarm.JoinRequ
func (d *Daemon) SwarmLeave(ctx context.Context, t testing.TB, force bool) error {
cli := d.NewClientT(t)
defer cli.Close()
return cli.SwarmLeave(ctx, force)
_, err := cli.SwarmLeave(ctx, client.SwarmLeaveOptions{Force: force})
return err
}
// SwarmInfo returns the swarm information of the daemon
@@ -157,7 +176,7 @@ func (d *Daemon) SwarmUnlock(t testing.TB, req swarm.UnlockRequest) error {
cli := d.NewClientT(t)
defer cli.Close()
err := cli.SwarmUnlock(context.Background(), req)
_, err := cli.SwarmUnlock(context.Background(), client.SwarmUnlockOptions{Key: req.UnlockKey})
if err != nil {
err = errors.Wrap(err, "unlocking swarm")
}
@@ -170,9 +189,9 @@ func (d *Daemon) GetSwarm(t testing.TB) swarm.Swarm {
cli := d.NewClientT(t)
defer cli.Close()
sw, err := cli.SwarmInspect(context.Background())
result, err := cli.SwarmInspect(context.Background())
assert.NilError(t, err)
return sw
return result.Swarm
}
// UpdateSwarm updates the current swarm object with the specified spec constructors
@@ -186,7 +205,9 @@ func (d *Daemon) UpdateSwarm(t testing.TB, f ...SpecConstructor) {
fn(&sw.Spec)
}
err := cli.SwarmUpdate(context.Background(), sw.Version, sw.Spec, client.SwarmUpdateFlags{})
_, err := cli.SwarmUpdate(context.Background(), sw.Version, client.SwarmUpdateOptions{
Swarm: sw.Spec,
})
assert.NilError(t, err)
}
@@ -196,15 +217,14 @@ func (d *Daemon) RotateTokens(t testing.TB) {
cli := d.NewClientT(t)
defer cli.Close()
sw, err := cli.SwarmInspect(context.Background())
result, err := cli.SwarmInspect(context.Background())
assert.NilError(t, err)
flags := client.SwarmUpdateFlags{
_, err = cli.SwarmUpdate(context.Background(), result.Swarm.Version, client.SwarmUpdateOptions{
Swarm: result.Swarm.Spec,
RotateManagerToken: true,
RotateWorkerToken: true,
}
err = cli.SwarmUpdate(context.Background(), sw.Version, sw.Spec, flags)
})
assert.NilError(t, err)
}
@@ -214,9 +234,9 @@ func (d *Daemon) JoinTokens(t testing.TB) swarm.JoinTokens {
cli := d.NewClientT(t)
defer cli.Close()
sw, err := cli.SwarmInspect(context.Background())
result, err := cli.SwarmInspect(context.Background())
assert.NilError(t, err)
return sw.JoinTokens
return result.Swarm.JoinTokens
}
func (d *Daemon) startArgs() []string {

View File

@@ -174,13 +174,13 @@ type ServiceAPIClient interface {
// SwarmAPIClient defines API client methods for the swarm
type SwarmAPIClient interface {
SwarmInit(ctx context.Context, req swarm.InitRequest) (string, error)
SwarmJoin(ctx context.Context, req swarm.JoinRequest) error
SwarmGetUnlockKey(ctx context.Context) (swarm.UnlockKeyResponse, error)
SwarmUnlock(ctx context.Context, req swarm.UnlockRequest) error
SwarmLeave(ctx context.Context, force bool) error
SwarmInspect(ctx context.Context) (swarm.Swarm, error)
SwarmUpdate(ctx context.Context, version swarm.Version, swarm swarm.Spec, flags SwarmUpdateFlags) error
SwarmInit(ctx context.Context, options SwarmInitOptions) (SwarmInitResult, error)
SwarmJoin(ctx context.Context, options SwarmJoinOptions) (SwarmJoinResult, error)
SwarmGetUnlockKey(ctx context.Context) (SwarmGetUnlockKeyResult, error)
SwarmUnlock(ctx context.Context, options SwarmUnlockOptions) (SwarmUnlockResult, error)
SwarmLeave(ctx context.Context, options SwarmLeaveOptions) (SwarmLeaveResult, error)
SwarmInspect(ctx context.Context) (SwarmInspectResult, error)
SwarmUpdate(ctx context.Context, version swarm.Version, options SwarmUpdateOptions) (SwarmUpdateResult, error)
}
// SystemAPIClient defines API client methods for the system

View File

@@ -7,15 +7,20 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// SwarmGetUnlockKeyResult contains the swarm unlock key.
type SwarmGetUnlockKeyResult struct {
Key string
}
// SwarmGetUnlockKey retrieves the swarm's unlock key.
func (cli *Client) SwarmGetUnlockKey(ctx context.Context) (swarm.UnlockKeyResponse, error) {
func (cli *Client) SwarmGetUnlockKey(ctx context.Context) (SwarmGetUnlockKeyResult, error) {
resp, err := cli.get(ctx, "/swarm/unlockkey", nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return swarm.UnlockKeyResponse{}, err
return SwarmGetUnlockKeyResult{}, err
}
var response swarm.UnlockKeyResponse
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
return SwarmGetUnlockKeyResult{Key: response.UnlockKey}, err
}

View File

@@ -3,19 +3,52 @@ package client
import (
"context"
"encoding/json"
"net/netip"
"github.com/moby/moby/api/types/swarm"
)
// SwarmInitOptions contains options for initializing a new swarm.
type SwarmInitOptions struct {
ListenAddr string
AdvertiseAddr string
DataPathAddr string
DataPathPort uint32
ForceNewCluster bool
Spec swarm.Spec
AutoLockManagers bool
Availability swarm.NodeAvailability
DefaultAddrPool []netip.Prefix
SubnetSize uint32
}
// SwarmInitResult contains the result of a SwarmInit operation.
type SwarmInitResult struct {
NodeID string
}
// SwarmInit initializes the swarm.
func (cli *Client) SwarmInit(ctx context.Context, req swarm.InitRequest) (string, error) {
func (cli *Client) SwarmInit(ctx context.Context, options SwarmInitOptions) (SwarmInitResult, error) {
req := swarm.InitRequest{
ListenAddr: options.ListenAddr,
AdvertiseAddr: options.AdvertiseAddr,
DataPathAddr: options.DataPathAddr,
DataPathPort: options.DataPathPort,
ForceNewCluster: options.ForceNewCluster,
Spec: options.Spec,
AutoLockManagers: options.AutoLockManagers,
Availability: options.Availability,
DefaultAddrPool: options.DefaultAddrPool,
SubnetSize: options.SubnetSize,
}
resp, err := cli.post(ctx, "/swarm/init", nil, req, nil)
defer ensureReaderClosed(resp)
if err != nil {
return "", err
return SwarmInitResult{}, err
}
var response string
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
var nodeID string
err = json.NewDecoder(resp.Body).Decode(&nodeID)
return SwarmInitResult{NodeID: nodeID}, err
}

View File

@@ -7,15 +7,20 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// type SwarmInspectResult represents the result of a SwarmInspect operation.
type SwarmInspectResult struct {
Swarm swarm.Swarm
}
// SwarmInspect inspects the swarm.
func (cli *Client) SwarmInspect(ctx context.Context) (swarm.Swarm, error) {
func (cli *Client) SwarmInspect(ctx context.Context) (SwarmInspectResult, error) {
resp, err := cli.get(ctx, "/swarm", nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return swarm.Swarm{}, err
return SwarmInspectResult{}, err
}
var response swarm.Swarm
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
var s swarm.Swarm
err = json.NewDecoder(resp.Body).Decode(&s)
return SwarmInspectResult{Swarm: s}, err
}

View File

@@ -6,9 +6,31 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// SwarmJoinOptions specifies options for joining a swarm.
type SwarmJoinOptions struct {
ListenAddr string
AdvertiseAddr string
DataPathAddr string
RemoteAddrs []string
JoinToken string // accept by secret
Availability swarm.NodeAvailability
}
// SwarmJoinResult contains the result of joining a swarm.
type SwarmJoinResult struct{}
// SwarmJoin joins the swarm.
func (cli *Client) SwarmJoin(ctx context.Context, req swarm.JoinRequest) error {
func (cli *Client) SwarmJoin(ctx context.Context, options SwarmJoinOptions) (SwarmJoinResult, error) {
req := swarm.JoinRequest{
ListenAddr: options.ListenAddr,
AdvertiseAddr: options.AdvertiseAddr,
DataPathAddr: options.DataPathAddr,
RemoteAddrs: options.RemoteAddrs,
JoinToken: options.JoinToken,
Availability: options.Availability,
}
resp, err := cli.post(ctx, "/swarm/join", nil, req, nil)
defer ensureReaderClosed(resp)
return err
return SwarmJoinResult{}, err
}

View File

@@ -5,13 +5,21 @@ import (
"net/url"
)
// SwarmLeaveOptions contains options for leaving a swarm.
type SwarmLeaveOptions struct {
Force bool
}
// SwarmLeaveResult represents the result of a SwarmLeave operation.
type SwarmLeaveResult struct{}
// SwarmLeave leaves the swarm.
func (cli *Client) SwarmLeave(ctx context.Context, force bool) error {
func (cli *Client) SwarmLeave(ctx context.Context, options SwarmLeaveOptions) (SwarmLeaveResult, error) {
query := url.Values{}
if force {
if options.Force {
query.Set("force", "1")
}
resp, err := cli.post(ctx, "/swarm/leave", query, nil, nil)
defer ensureReaderClosed(resp)
return err
return SwarmLeaveResult{}, err
}

View File

@@ -6,9 +6,20 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// SwarmUnlockOptions specifies options for unlocking a swarm.
type SwarmUnlockOptions struct {
Key string
}
// SwarmUnlockResult represents the result of unlocking a swarm.
type SwarmUnlockResult struct{}
// SwarmUnlock unlocks locked swarm.
func (cli *Client) SwarmUnlock(ctx context.Context, req swarm.UnlockRequest) error {
func (cli *Client) SwarmUnlock(ctx context.Context, options SwarmUnlockOptions) (SwarmUnlockResult, error) {
req := &swarm.UnlockRequest{
UnlockKey: options.Key,
}
resp, err := cli.post(ctx, "/swarm/unlock", nil, req, nil)
defer ensureReaderClosed(resp)
return err
return SwarmUnlockResult{}, err
}

View File

@@ -8,14 +8,25 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// SwarmUpdateOptions contains options for updating a swarm.
type SwarmUpdateOptions struct {
Swarm swarm.Spec
RotateWorkerToken bool
RotateManagerToken bool
RotateManagerUnlockKey bool
}
// SwarmUpdateResult represents the result of a SwarmUpdate operation.
type SwarmUpdateResult struct{}
// SwarmUpdate updates the swarm.
func (cli *Client) SwarmUpdate(ctx context.Context, version swarm.Version, swarm swarm.Spec, flags SwarmUpdateFlags) error {
func (cli *Client) SwarmUpdate(ctx context.Context, version swarm.Version, options SwarmUpdateOptions) (SwarmUpdateResult, error) {
query := url.Values{}
query.Set("version", version.String())
query.Set("rotateWorkerToken", strconv.FormatBool(flags.RotateWorkerToken))
query.Set("rotateManagerToken", strconv.FormatBool(flags.RotateManagerToken))
query.Set("rotateManagerUnlockKey", strconv.FormatBool(flags.RotateManagerUnlockKey))
resp, err := cli.post(ctx, "/swarm/update", query, swarm, nil)
query.Set("rotateWorkerToken", strconv.FormatBool(options.RotateWorkerToken))
query.Set("rotateManagerToken", strconv.FormatBool(options.RotateManagerToken))
query.Set("rotateManagerUnlockKey", strconv.FormatBool(options.RotateManagerUnlockKey))
resp, err := cli.post(ctx, "/swarm/update", query, options.Swarm, nil)
defer ensureReaderClosed(resp)
return err
return SwarmUpdateResult{}, err
}

View File

@@ -1,8 +0,0 @@
package client
// SwarmUpdateFlags contains flags for SwarmUpdate.
type SwarmUpdateFlags struct {
RotateWorkerToken bool
RotateManagerToken bool
RotateManagerUnlockKey bool
}