Add some unit tests to the node and swarm cli code
Start work on adding unit tests to our cli code in order to have to write less costly integration test. Signed-off-by: Vincent Demeester <vincent@sbr.pm>
This commit is contained in:
parent
d4791c7b64
commit
f151c297eb
60 changed files with 2512 additions and 147 deletions
|
@ -32,7 +32,15 @@ type Streams interface {
|
|||
Err() io.Writer
|
||||
}
|
||||
|
||||
// DockerCli represents the docker command line client.
|
||||
// Cli represents the docker command line client.
|
||||
type Cli interface {
|
||||
Client() client.APIClient
|
||||
Out() *OutStream
|
||||
Err() io.Writer
|
||||
In() *InStream
|
||||
}
|
||||
|
||||
// DockerCli is an instance the docker command line client.
|
||||
// Instances of the client can be returned from NewDockerCli.
|
||||
type DockerCli struct {
|
||||
configFile *configfile.ConfigFile
|
||||
|
|
68
cli/command/node/client_test.go
Normal file
68
cli/command/node/client_test.go
Normal file
|
@ -0,0 +1,68 @@
|
|||
package node
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/client"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type fakeClient struct {
|
||||
client.Client
|
||||
infoFunc func() (types.Info, error)
|
||||
nodeInspectFunc func() (swarm.Node, []byte, error)
|
||||
nodeListFunc func() ([]swarm.Node, error)
|
||||
nodeRemoveFunc func() error
|
||||
nodeUpdateFunc func(nodeID string, version swarm.Version, node swarm.NodeSpec) error
|
||||
taskInspectFunc func(taskID string) (swarm.Task, []byte, error)
|
||||
taskListFunc func(options types.TaskListOptions) ([]swarm.Task, error)
|
||||
}
|
||||
|
||||
func (cli *fakeClient) NodeInspectWithRaw(ctx context.Context, ref string) (swarm.Node, []byte, error) {
|
||||
if cli.nodeInspectFunc != nil {
|
||||
return cli.nodeInspectFunc()
|
||||
}
|
||||
return swarm.Node{}, []byte{}, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) {
|
||||
if cli.nodeListFunc != nil {
|
||||
return cli.nodeListFunc()
|
||||
}
|
||||
return []swarm.Node{}, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) NodeRemove(ctx context.Context, nodeID string, options types.NodeRemoveOptions) error {
|
||||
if cli.nodeRemoveFunc != nil {
|
||||
return cli.nodeRemoveFunc()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) NodeUpdate(ctx context.Context, nodeID string, version swarm.Version, node swarm.NodeSpec) error {
|
||||
if cli.nodeUpdateFunc != nil {
|
||||
return cli.nodeUpdateFunc(nodeID, version, node)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) Info(ctx context.Context) (types.Info, error) {
|
||||
if cli.infoFunc != nil {
|
||||
return cli.infoFunc()
|
||||
}
|
||||
return types.Info{}, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error) {
|
||||
if cli.taskInspectFunc != nil {
|
||||
return cli.taskInspectFunc(taskID)
|
||||
}
|
||||
return swarm.Task{}, []byte{}, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) {
|
||||
if cli.taskListFunc != nil {
|
||||
return cli.taskListFunc(options)
|
||||
}
|
||||
return []swarm.Task{}, nil
|
||||
}
|
|
@ -9,7 +9,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newDemoteCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func newDemoteCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "demote NODE [NODE...]",
|
||||
Short: "Demote one or more nodes from manager in the swarm",
|
||||
|
@ -20,7 +20,7 @@ func newDemoteCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
}
|
||||
}
|
||||
|
||||
func runDemote(dockerCli *command.DockerCli, nodes []string) error {
|
||||
func runDemote(dockerCli command.Cli, nodes []string) error {
|
||||
demote := func(node *swarm.Node) error {
|
||||
if node.Spec.Role == swarm.NodeRoleWorker {
|
||||
fmt.Fprintf(dockerCli.Out(), "Node %s is already a worker.\n", node.ID)
|
||||
|
|
88
cli/command/node/demote_test.go
Normal file
88
cli/command/node/demote_test.go
Normal file
|
@ -0,0 +1,88 @@
|
|||
package node
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/cli/internal/test"
|
||||
// Import builders to get the builder function as package function
|
||||
. "github.com/docker/docker/cli/internal/test/builders"
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
)
|
||||
|
||||
func TestNodeDemoteErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
args []string
|
||||
nodeInspectFunc func() (swarm.Node, []byte, error)
|
||||
nodeUpdateFunc func(nodeID string, version swarm.Version, node swarm.NodeSpec) error
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
expectedError: "requires at least 1 argument",
|
||||
},
|
||||
{
|
||||
args: []string{"nodeID"},
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return swarm.Node{}, []byte{}, fmt.Errorf("error inspecting the node")
|
||||
},
|
||||
expectedError: "error inspecting the node",
|
||||
},
|
||||
{
|
||||
args: []string{"nodeID"},
|
||||
nodeUpdateFunc: func(nodeID string, version swarm.Version, node swarm.NodeSpec) error {
|
||||
return fmt.Errorf("error updating the node")
|
||||
},
|
||||
expectedError: "error updating the node",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newDemoteCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
nodeInspectFunc: tc.nodeInspectFunc,
|
||||
nodeUpdateFunc: tc.nodeUpdateFunc,
|
||||
}, buf))
|
||||
cmd.SetArgs(tc.args)
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
assert.Error(t, cmd.Execute(), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeDemoteNoChange(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newDemoteCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return *Node(), []byte{}, nil
|
||||
},
|
||||
nodeUpdateFunc: func(nodeID string, version swarm.Version, node swarm.NodeSpec) error {
|
||||
if node.Role != swarm.NodeRoleWorker {
|
||||
return fmt.Errorf("expected role worker, got %s", node.Role)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}, buf))
|
||||
cmd.SetArgs([]string{"nodeID"})
|
||||
assert.NilError(t, cmd.Execute())
|
||||
}
|
||||
|
||||
func TestNodeDemoteMultipleNode(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newDemoteCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return *Node(Manager()), []byte{}, nil
|
||||
},
|
||||
nodeUpdateFunc: func(nodeID string, version swarm.Version, node swarm.NodeSpec) error {
|
||||
if node.Role != swarm.NodeRoleWorker {
|
||||
return fmt.Errorf("expected role worker, got %s", node.Role)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}, buf))
|
||||
cmd.SetArgs([]string{"nodeID1", "nodeID2"})
|
||||
assert.NilError(t, cmd.Execute())
|
||||
}
|
|
@ -22,7 +22,7 @@ type inspectOptions struct {
|
|||
pretty bool
|
||||
}
|
||||
|
||||
func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func newInspectCommand(dockerCli command.Cli) *cobra.Command {
|
||||
var opts inspectOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -41,7 +41,7 @@ func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error {
|
||||
func runInspect(dockerCli command.Cli, opts inspectOptions) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
getRef := func(ref string) (interface{}, []byte, error) {
|
||||
|
|
122
cli/command/node/inspect_test.go
Normal file
122
cli/command/node/inspect_test.go
Normal file
|
@ -0,0 +1,122 @@
|
|||
package node
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/cli/internal/test"
|
||||
// Import builders to get the builder function as package function
|
||||
. "github.com/docker/docker/cli/internal/test/builders"
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
"github.com/docker/docker/pkg/testutil/golden"
|
||||
)
|
||||
|
||||
func TestNodeInspectErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
args []string
|
||||
flags map[string]string
|
||||
nodeInspectFunc func() (swarm.Node, []byte, error)
|
||||
infoFunc func() (types.Info, error)
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
expectedError: "requires at least 1 argument",
|
||||
},
|
||||
{
|
||||
args: []string{"self"},
|
||||
infoFunc: func() (types.Info, error) {
|
||||
return types.Info{}, fmt.Errorf("error asking for node info")
|
||||
},
|
||||
expectedError: "error asking for node info",
|
||||
},
|
||||
{
|
||||
args: []string{"nodeID"},
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return swarm.Node{}, []byte{}, fmt.Errorf("error inspecting the node")
|
||||
},
|
||||
infoFunc: func() (types.Info, error) {
|
||||
return types.Info{}, fmt.Errorf("error asking for node info")
|
||||
},
|
||||
expectedError: "error inspecting the node",
|
||||
},
|
||||
{
|
||||
args: []string{"self"},
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return swarm.Node{}, []byte{}, fmt.Errorf("error inspecting the node")
|
||||
},
|
||||
infoFunc: func() (types.Info, error) {
|
||||
return types.Info{}, nil
|
||||
},
|
||||
expectedError: "error inspecting the node",
|
||||
},
|
||||
{
|
||||
args: []string{"self"},
|
||||
flags: map[string]string{
|
||||
"pretty": "true",
|
||||
},
|
||||
infoFunc: func() (types.Info, error) {
|
||||
return types.Info{}, fmt.Errorf("error asking for node info")
|
||||
},
|
||||
expectedError: "error asking for node info",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newInspectCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
nodeInspectFunc: tc.nodeInspectFunc,
|
||||
infoFunc: tc.infoFunc,
|
||||
}, buf))
|
||||
cmd.SetArgs(tc.args)
|
||||
for key, value := range tc.flags {
|
||||
cmd.Flags().Set(key, value)
|
||||
}
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
assert.Error(t, cmd.Execute(), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeInspectPretty(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
nodeInspectFunc func() (swarm.Node, []byte, error)
|
||||
}{
|
||||
{
|
||||
name: "simple",
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return *Node(NodeLabels(map[string]string{
|
||||
"lbl1": "value1",
|
||||
})), []byte{}, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "manager",
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return *Node(Manager()), []byte{}, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "manager-leader",
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return *Node(Manager(Leader())), []byte{}, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newInspectCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
nodeInspectFunc: tc.nodeInspectFunc,
|
||||
}, buf))
|
||||
cmd.SetArgs([]string{"nodeID"})
|
||||
cmd.Flags().Set("pretty", "true")
|
||||
assert.NilError(t, cmd.Execute())
|
||||
actual := buf.String()
|
||||
expected := golden.Get(t, []byte(actual), fmt.Sprintf("node-inspect-pretty.%s.golden", tc.name))
|
||||
assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
|
||||
}
|
||||
}
|
|
@ -24,7 +24,7 @@ type listOptions struct {
|
|||
filter opts.FilterOpt
|
||||
}
|
||||
|
||||
func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func newListCommand(dockerCli command.Cli) *cobra.Command {
|
||||
opts := listOptions{filter: opts.NewFilterOpt()}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -43,7 +43,7 @@ func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func runList(dockerCli *command.DockerCli, opts listOptions) error {
|
||||
func runList(dockerCli command.Cli, opts listOptions) error {
|
||||
client := dockerCli.Client()
|
||||
out := dockerCli.Out()
|
||||
ctx := context.Background()
|
||||
|
|
101
cli/command/node/list_test.go
Normal file
101
cli/command/node/list_test.go
Normal file
|
@ -0,0 +1,101 @@
|
|||
package node
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/cli/internal/test"
|
||||
// Import builders to get the builder function as package function
|
||||
. "github.com/docker/docker/cli/internal/test/builders"
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
)
|
||||
|
||||
func TestNodeListErrorOnAPIFailure(t *testing.T) {
|
||||
testCases := []struct {
|
||||
nodeListFunc func() ([]swarm.Node, error)
|
||||
infoFunc func() (types.Info, error)
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
nodeListFunc: func() ([]swarm.Node, error) {
|
||||
return []swarm.Node{}, fmt.Errorf("error listing nodes")
|
||||
},
|
||||
expectedError: "error listing nodes",
|
||||
},
|
||||
{
|
||||
nodeListFunc: func() ([]swarm.Node, error) {
|
||||
return []swarm.Node{
|
||||
{
|
||||
ID: "nodeID",
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
infoFunc: func() (types.Info, error) {
|
||||
return types.Info{}, fmt.Errorf("error asking for node info")
|
||||
},
|
||||
expectedError: "error asking for node info",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newListCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
nodeListFunc: tc.nodeListFunc,
|
||||
infoFunc: tc.infoFunc,
|
||||
}, buf))
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
assert.Error(t, cmd.Execute(), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeList(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newListCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
nodeListFunc: func() ([]swarm.Node, error) {
|
||||
return []swarm.Node{
|
||||
*Node(NodeID("nodeID1"), Hostname("nodeHostname1"), Manager(Leader())),
|
||||
*Node(NodeID("nodeID2"), Hostname("nodeHostname2"), Manager()),
|
||||
*Node(NodeID("nodeID3"), Hostname("nodeHostname3")),
|
||||
}, nil
|
||||
},
|
||||
infoFunc: func() (types.Info, error) {
|
||||
return types.Info{
|
||||
Swarm: swarm.Info{
|
||||
NodeID: "nodeID1",
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
}, buf))
|
||||
assert.NilError(t, cmd.Execute())
|
||||
assert.Contains(t, buf.String(), `nodeID1 * nodeHostname1 Ready Active Leader`)
|
||||
assert.Contains(t, buf.String(), `nodeID2 nodeHostname2 Ready Active Reachable`)
|
||||
assert.Contains(t, buf.String(), `nodeID3 nodeHostname3 Ready Active`)
|
||||
}
|
||||
|
||||
func TestNodeListQuietShouldOnlyPrintIDs(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newListCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
nodeListFunc: func() ([]swarm.Node, error) {
|
||||
return []swarm.Node{
|
||||
*Node(),
|
||||
}, nil
|
||||
},
|
||||
}, buf))
|
||||
cmd.Flags().Set("quiet", "true")
|
||||
assert.NilError(t, cmd.Execute())
|
||||
assert.Contains(t, buf.String(), "nodeID")
|
||||
}
|
||||
|
||||
// Test case for #24090
|
||||
func TestNodeListContainsHostname(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newListCommand(test.NewFakeCli(&fakeClient{}, buf))
|
||||
assert.NilError(t, cmd.Execute())
|
||||
assert.Contains(t, buf.String(), "HOSTNAME")
|
||||
}
|
|
@ -1,12 +1,7 @@
|
|||
package node
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/opts"
|
||||
runconfigopts "github.com/docker/docker/runconfig/opts"
|
||||
)
|
||||
|
||||
type nodeOptions struct {
|
||||
|
@ -27,34 +22,3 @@ func newNodeOptions() *nodeOptions {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (opts *nodeOptions) ToNodeSpec() (swarm.NodeSpec, error) {
|
||||
var spec swarm.NodeSpec
|
||||
|
||||
spec.Annotations.Name = opts.annotations.name
|
||||
spec.Annotations.Labels = runconfigopts.ConvertKVStringsToMap(opts.annotations.labels.GetAll())
|
||||
|
||||
switch swarm.NodeRole(strings.ToLower(opts.role)) {
|
||||
case swarm.NodeRoleWorker:
|
||||
spec.Role = swarm.NodeRoleWorker
|
||||
case swarm.NodeRoleManager:
|
||||
spec.Role = swarm.NodeRoleManager
|
||||
case "":
|
||||
default:
|
||||
return swarm.NodeSpec{}, fmt.Errorf("invalid role %q, only worker and manager are supported", opts.role)
|
||||
}
|
||||
|
||||
switch swarm.NodeAvailability(strings.ToLower(opts.availability)) {
|
||||
case swarm.NodeAvailabilityActive:
|
||||
spec.Availability = swarm.NodeAvailabilityActive
|
||||
case swarm.NodeAvailabilityPause:
|
||||
spec.Availability = swarm.NodeAvailabilityPause
|
||||
case swarm.NodeAvailabilityDrain:
|
||||
spec.Availability = swarm.NodeAvailabilityDrain
|
||||
case "":
|
||||
default:
|
||||
return swarm.NodeSpec{}, fmt.Errorf("invalid availability %q, only active, pause and drain are supported", opts.availability)
|
||||
}
|
||||
|
||||
return spec, nil
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newPromoteCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func newPromoteCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "promote NODE [NODE...]",
|
||||
Short: "Promote one or more nodes to manager in the swarm",
|
||||
|
@ -20,7 +20,7 @@ func newPromoteCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
}
|
||||
}
|
||||
|
||||
func runPromote(dockerCli *command.DockerCli, nodes []string) error {
|
||||
func runPromote(dockerCli command.Cli, nodes []string) error {
|
||||
promote := func(node *swarm.Node) error {
|
||||
if node.Spec.Role == swarm.NodeRoleManager {
|
||||
fmt.Fprintf(dockerCli.Out(), "Node %s is already a manager.\n", node.ID)
|
||||
|
|
88
cli/command/node/promote_test.go
Normal file
88
cli/command/node/promote_test.go
Normal file
|
@ -0,0 +1,88 @@
|
|||
package node
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/cli/internal/test"
|
||||
// Import builders to get the builder function as package function
|
||||
. "github.com/docker/docker/cli/internal/test/builders"
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
)
|
||||
|
||||
func TestNodePromoteErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
args []string
|
||||
nodeInspectFunc func() (swarm.Node, []byte, error)
|
||||
nodeUpdateFunc func(nodeID string, version swarm.Version, node swarm.NodeSpec) error
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
expectedError: "requires at least 1 argument",
|
||||
},
|
||||
{
|
||||
args: []string{"nodeID"},
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return swarm.Node{}, []byte{}, fmt.Errorf("error inspecting the node")
|
||||
},
|
||||
expectedError: "error inspecting the node",
|
||||
},
|
||||
{
|
||||
args: []string{"nodeID"},
|
||||
nodeUpdateFunc: func(nodeID string, version swarm.Version, node swarm.NodeSpec) error {
|
||||
return fmt.Errorf("error updating the node")
|
||||
},
|
||||
expectedError: "error updating the node",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newPromoteCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
nodeInspectFunc: tc.nodeInspectFunc,
|
||||
nodeUpdateFunc: tc.nodeUpdateFunc,
|
||||
}, buf))
|
||||
cmd.SetArgs(tc.args)
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
assert.Error(t, cmd.Execute(), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodePromoteNoChange(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newPromoteCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return *Node(Manager()), []byte{}, nil
|
||||
},
|
||||
nodeUpdateFunc: func(nodeID string, version swarm.Version, node swarm.NodeSpec) error {
|
||||
if node.Role != swarm.NodeRoleManager {
|
||||
return fmt.Errorf("expected role manager, got %s", node.Role)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}, buf))
|
||||
cmd.SetArgs([]string{"nodeID"})
|
||||
assert.NilError(t, cmd.Execute())
|
||||
}
|
||||
|
||||
func TestNodePromoteMultipleNode(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newPromoteCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return *Node(), []byte{}, nil
|
||||
},
|
||||
nodeUpdateFunc: func(nodeID string, version swarm.Version, node swarm.NodeSpec) error {
|
||||
if node.Role != swarm.NodeRoleManager {
|
||||
return fmt.Errorf("expected role manager, got %s", node.Role)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}, buf))
|
||||
cmd.SetArgs([]string{"nodeID1", "nodeID2"})
|
||||
assert.NilError(t, cmd.Execute())
|
||||
}
|
|
@ -22,7 +22,7 @@ type psOptions struct {
|
|||
filter opts.FilterOpt
|
||||
}
|
||||
|
||||
func newPsCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func newPsCommand(dockerCli command.Cli) *cobra.Command {
|
||||
opts := psOptions{filter: opts.NewFilterOpt()}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -47,7 +47,7 @@ func newPsCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func runPs(dockerCli *command.DockerCli, opts psOptions) error {
|
||||
func runPs(dockerCli command.Cli, opts psOptions) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
|
|
132
cli/command/node/ps_test.go
Normal file
132
cli/command/node/ps_test.go
Normal file
|
@ -0,0 +1,132 @@
|
|||
package node
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/cli/internal/test"
|
||||
// Import builders to get the builder function as package function
|
||||
. "github.com/docker/docker/cli/internal/test/builders"
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
"github.com/docker/docker/pkg/testutil/golden"
|
||||
)
|
||||
|
||||
func TestNodePsErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
args []string
|
||||
flags map[string]string
|
||||
infoFunc func() (types.Info, error)
|
||||
nodeInspectFunc func() (swarm.Node, []byte, error)
|
||||
taskListFunc func(options types.TaskListOptions) ([]swarm.Task, error)
|
||||
taskInspectFunc func(taskID string) (swarm.Task, []byte, error)
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
infoFunc: func() (types.Info, error) {
|
||||
return types.Info{}, fmt.Errorf("error asking for node info")
|
||||
},
|
||||
expectedError: "error asking for node info",
|
||||
},
|
||||
{
|
||||
args: []string{"nodeID"},
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return swarm.Node{}, []byte{}, fmt.Errorf("error inspecting the node")
|
||||
},
|
||||
expectedError: "error inspecting the node",
|
||||
},
|
||||
{
|
||||
args: []string{"nodeID"},
|
||||
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
|
||||
return []swarm.Task{}, fmt.Errorf("error returning the task list")
|
||||
},
|
||||
expectedError: "error returning the task list",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newPsCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
infoFunc: tc.infoFunc,
|
||||
nodeInspectFunc: tc.nodeInspectFunc,
|
||||
taskInspectFunc: tc.taskInspectFunc,
|
||||
taskListFunc: tc.taskListFunc,
|
||||
}, buf))
|
||||
cmd.SetArgs(tc.args)
|
||||
for key, value := range tc.flags {
|
||||
cmd.Flags().Set(key, value)
|
||||
}
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
assert.Error(t, cmd.Execute(), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodePs(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
flags map[string]string
|
||||
infoFunc func() (types.Info, error)
|
||||
nodeInspectFunc func() (swarm.Node, []byte, error)
|
||||
taskListFunc func(options types.TaskListOptions) ([]swarm.Task, error)
|
||||
taskInspectFunc func(taskID string) (swarm.Task, []byte, error)
|
||||
}{
|
||||
{
|
||||
name: "simple",
|
||||
args: []string{"nodeID"},
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return *Node(), []byte{}, nil
|
||||
},
|
||||
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
|
||||
return []swarm.Task{
|
||||
*Task(WithStatus(Timestamp(time.Now().Add(-2*time.Hour)), PortStatus([]swarm.PortConfig{
|
||||
{
|
||||
TargetPort: 80,
|
||||
PublishedPort: 80,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
}))),
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with-errors",
|
||||
args: []string{"nodeID"},
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return *Node(), []byte{}, nil
|
||||
},
|
||||
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
|
||||
return []swarm.Task{
|
||||
*Task(TaskID("taskID1"), ServiceID("failure"),
|
||||
WithStatus(Timestamp(time.Now().Add(-2*time.Hour)), StatusErr("a task error"))),
|
||||
*Task(TaskID("taskID2"), ServiceID("failure"),
|
||||
WithStatus(Timestamp(time.Now().Add(-3*time.Hour)), StatusErr("a task error"))),
|
||||
*Task(TaskID("taskID3"), ServiceID("failure"),
|
||||
WithStatus(Timestamp(time.Now().Add(-4*time.Hour)), StatusErr("a task error"))),
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newPsCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
infoFunc: tc.infoFunc,
|
||||
nodeInspectFunc: tc.nodeInspectFunc,
|
||||
taskInspectFunc: tc.taskInspectFunc,
|
||||
taskListFunc: tc.taskListFunc,
|
||||
}, buf))
|
||||
cmd.SetArgs(tc.args)
|
||||
for key, value := range tc.flags {
|
||||
cmd.Flags().Set(key, value)
|
||||
}
|
||||
assert.NilError(t, cmd.Execute())
|
||||
actual := buf.String()
|
||||
expected := golden.Get(t, []byte(actual), fmt.Sprintf("node-ps.%s.golden", tc.name))
|
||||
assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@ type removeOptions struct {
|
|||
force bool
|
||||
}
|
||||
|
||||
func newRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
||||
opts := removeOptions{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -33,7 +33,7 @@ func newRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func runRemove(dockerCli *command.DockerCli, args []string, opts removeOptions) error {
|
||||
func runRemove(dockerCli command.Cli, args []string, opts removeOptions) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
|
|
47
cli/command/node/remove_test.go
Normal file
47
cli/command/node/remove_test.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package node
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/cli/internal/test"
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
)
|
||||
|
||||
func TestNodeRemoveErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
args []string
|
||||
nodeRemoveFunc func() error
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
expectedError: "requires at least 1 argument",
|
||||
},
|
||||
{
|
||||
args: []string{"nodeID"},
|
||||
nodeRemoveFunc: func() error {
|
||||
return fmt.Errorf("error removing the node")
|
||||
},
|
||||
expectedError: "error removing the node",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newRemoveCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
nodeRemoveFunc: tc.nodeRemoveFunc,
|
||||
}, buf))
|
||||
cmd.SetArgs(tc.args)
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
assert.Error(t, cmd.Execute(), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeRemoveMultiple(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newRemoveCommand(test.NewFakeCli(&fakeClient{}, buf))
|
||||
cmd.SetArgs([]string{"nodeID1", "nodeID2"})
|
||||
assert.NilError(t, cmd.Execute())
|
||||
}
|
25
cli/command/node/testdata/node-inspect-pretty.manager-leader.golden
vendored
Normal file
25
cli/command/node/testdata/node-inspect-pretty.manager-leader.golden
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
ID: nodeID
|
||||
Name: defaultNodeName
|
||||
Hostname: defaultNodeHostname
|
||||
Joined at: 2009-11-10 23:00:00 +0000 utc
|
||||
Status:
|
||||
State: Ready
|
||||
Availability: Active
|
||||
Address: 127.0.0.1
|
||||
Manager Status:
|
||||
Address: 127.0.0.1
|
||||
Raft Status: Reachable
|
||||
Leader: Yes
|
||||
Platform:
|
||||
Operating System: linux
|
||||
Architecture: x86_64
|
||||
Resources:
|
||||
CPUs: 0
|
||||
Memory: 20 MiB
|
||||
Plugins:
|
||||
Network: bridge, overlay
|
||||
Volume: local
|
||||
Engine Version: 1.13.0
|
||||
Engine Labels:
|
||||
- engine = label
|
||||
|
25
cli/command/node/testdata/node-inspect-pretty.manager.golden
vendored
Normal file
25
cli/command/node/testdata/node-inspect-pretty.manager.golden
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
ID: nodeID
|
||||
Name: defaultNodeName
|
||||
Hostname: defaultNodeHostname
|
||||
Joined at: 2009-11-10 23:00:00 +0000 utc
|
||||
Status:
|
||||
State: Ready
|
||||
Availability: Active
|
||||
Address: 127.0.0.1
|
||||
Manager Status:
|
||||
Address: 127.0.0.1
|
||||
Raft Status: Reachable
|
||||
Leader: No
|
||||
Platform:
|
||||
Operating System: linux
|
||||
Architecture: x86_64
|
||||
Resources:
|
||||
CPUs: 0
|
||||
Memory: 20 MiB
|
||||
Plugins:
|
||||
Network: bridge, overlay
|
||||
Volume: local
|
||||
Engine Version: 1.13.0
|
||||
Engine Labels:
|
||||
- engine = label
|
||||
|
23
cli/command/node/testdata/node-inspect-pretty.simple.golden
vendored
Normal file
23
cli/command/node/testdata/node-inspect-pretty.simple.golden
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
ID: nodeID
|
||||
Name: defaultNodeName
|
||||
Labels:
|
||||
- lbl1 = value1
|
||||
Hostname: defaultNodeHostname
|
||||
Joined at: 2009-11-10 23:00:00 +0000 utc
|
||||
Status:
|
||||
State: Ready
|
||||
Availability: Active
|
||||
Address: 127.0.0.1
|
||||
Platform:
|
||||
Operating System: linux
|
||||
Architecture: x86_64
|
||||
Resources:
|
||||
CPUs: 0
|
||||
Memory: 20 MiB
|
||||
Plugins:
|
||||
Network: bridge, overlay
|
||||
Volume: local
|
||||
Engine Version: 1.13.0
|
||||
Engine Labels:
|
||||
- engine = label
|
||||
|
2
cli/command/node/testdata/node-ps.simple.golden
vendored
Normal file
2
cli/command/node/testdata/node-ps.simple.golden
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
|
||||
taskID rl02d5gwz6chzu7il5fhtb8be.1 myimage:mytag defaultNodeName Ready Ready 2 hours ago *:80->80/tcp
|
4
cli/command/node/testdata/node-ps.with-errors.golden
vendored
Normal file
4
cli/command/node/testdata/node-ps.with-errors.golden
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
|
||||
taskID1 failure.1 myimage:mytag defaultNodeName Ready Ready 2 hours ago "a task error"
|
||||
taskID2 \_ failure.1 myimage:mytag defaultNodeName Ready Ready 3 hours ago "a task error"
|
||||
taskID3 \_ failure.1 myimage:mytag defaultNodeName Ready Ready 4 hours ago "a task error"
|
|
@ -18,7 +18,7 @@ var (
|
|||
errNoRoleChange = errors.New("role was already set to the requested value")
|
||||
)
|
||||
|
||||
func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
nodeOpts := newNodeOptions()
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -39,14 +39,14 @@ func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func runUpdate(dockerCli *command.DockerCli, flags *pflag.FlagSet, nodeID string) error {
|
||||
func runUpdate(dockerCli command.Cli, flags *pflag.FlagSet, nodeID string) error {
|
||||
success := func(_ string) {
|
||||
fmt.Fprintln(dockerCli.Out(), nodeID)
|
||||
}
|
||||
return updateNodes(dockerCli, []string{nodeID}, mergeNodeUpdate(flags), success)
|
||||
}
|
||||
|
||||
func updateNodes(dockerCli *command.DockerCli, nodes []string, mergeNode func(node *swarm.Node) error, success func(nodeID string)) error {
|
||||
func updateNodes(dockerCli command.Cli, nodes []string, mergeNode func(node *swarm.Node) error, success func(nodeID string)) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
|
|
172
cli/command/node/update_test.go
Normal file
172
cli/command/node/update_test.go
Normal file
|
@ -0,0 +1,172 @@
|
|||
package node
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/cli/internal/test"
|
||||
// Import builders to get the builder function as package function
|
||||
. "github.com/docker/docker/cli/internal/test/builders"
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
)
|
||||
|
||||
func TestNodeUpdateErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
args []string
|
||||
flags map[string]string
|
||||
nodeInspectFunc func() (swarm.Node, []byte, error)
|
||||
nodeUpdateFunc func(nodeID string, version swarm.Version, node swarm.NodeSpec) error
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
expectedError: "requires exactly 1 argument",
|
||||
},
|
||||
{
|
||||
args: []string{"node1", "node2"},
|
||||
expectedError: "requires exactly 1 argument",
|
||||
},
|
||||
{
|
||||
args: []string{"nodeID"},
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return swarm.Node{}, []byte{}, fmt.Errorf("error inspecting the node")
|
||||
},
|
||||
expectedError: "error inspecting the node",
|
||||
},
|
||||
{
|
||||
args: []string{"nodeID"},
|
||||
nodeUpdateFunc: func(nodeID string, version swarm.Version, node swarm.NodeSpec) error {
|
||||
return fmt.Errorf("error updating the node")
|
||||
},
|
||||
expectedError: "error updating the node",
|
||||
},
|
||||
{
|
||||
args: []string{"nodeID"},
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return *Node(NodeLabels(map[string]string{
|
||||
"key": "value",
|
||||
})), []byte{}, nil
|
||||
},
|
||||
flags: map[string]string{
|
||||
"label-rm": "notpresent",
|
||||
},
|
||||
expectedError: "key notpresent doesn't exist in node's labels",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newUpdateCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
nodeInspectFunc: tc.nodeInspectFunc,
|
||||
nodeUpdateFunc: tc.nodeUpdateFunc,
|
||||
}, buf))
|
||||
cmd.SetArgs(tc.args)
|
||||
for key, value := range tc.flags {
|
||||
cmd.Flags().Set(key, value)
|
||||
}
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
assert.Error(t, cmd.Execute(), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeUpdate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
args []string
|
||||
flags map[string]string
|
||||
nodeInspectFunc func() (swarm.Node, []byte, error)
|
||||
nodeUpdateFunc func(nodeID string, version swarm.Version, node swarm.NodeSpec) error
|
||||
}{
|
||||
{
|
||||
args: []string{"nodeID"},
|
||||
flags: map[string]string{
|
||||
"role": "manager",
|
||||
},
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return *Node(), []byte{}, nil
|
||||
},
|
||||
nodeUpdateFunc: func(nodeID string, version swarm.Version, node swarm.NodeSpec) error {
|
||||
if node.Role != swarm.NodeRoleManager {
|
||||
return fmt.Errorf("expected role manager, got %s", node.Role)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
args: []string{"nodeID"},
|
||||
flags: map[string]string{
|
||||
"availability": "drain",
|
||||
},
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return *Node(), []byte{}, nil
|
||||
},
|
||||
nodeUpdateFunc: func(nodeID string, version swarm.Version, node swarm.NodeSpec) error {
|
||||
if node.Availability != swarm.NodeAvailabilityDrain {
|
||||
return fmt.Errorf("expected drain availability, got %s", node.Availability)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
args: []string{"nodeID"},
|
||||
flags: map[string]string{
|
||||
"label-add": "lbl",
|
||||
},
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return *Node(), []byte{}, nil
|
||||
},
|
||||
nodeUpdateFunc: func(nodeID string, version swarm.Version, node swarm.NodeSpec) error {
|
||||
if _, present := node.Annotations.Labels["lbl"]; !present {
|
||||
return fmt.Errorf("expected 'lbl' label, got %v", node.Annotations.Labels)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
args: []string{"nodeID"},
|
||||
flags: map[string]string{
|
||||
"label-add": "key=value",
|
||||
},
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return *Node(), []byte{}, nil
|
||||
},
|
||||
nodeUpdateFunc: func(nodeID string, version swarm.Version, node swarm.NodeSpec) error {
|
||||
if value, present := node.Annotations.Labels["key"]; !present || value != "value" {
|
||||
return fmt.Errorf("expected 'key' label to be 'value', got %v", node.Annotations.Labels)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
args: []string{"nodeID"},
|
||||
flags: map[string]string{
|
||||
"label-rm": "key",
|
||||
},
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return *Node(NodeLabels(map[string]string{
|
||||
"key": "value",
|
||||
})), []byte{}, nil
|
||||
},
|
||||
nodeUpdateFunc: func(nodeID string, version swarm.Version, node swarm.NodeSpec) error {
|
||||
if len(node.Annotations.Labels) > 0 {
|
||||
return fmt.Errorf("expected no labels, got %v", node.Annotations.Labels)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newUpdateCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
nodeInspectFunc: tc.nodeInspectFunc,
|
||||
nodeUpdateFunc: tc.nodeUpdateFunc,
|
||||
}, buf))
|
||||
cmd.SetArgs(tc.args)
|
||||
for key, value := range tc.flags {
|
||||
cmd.Flags().Set(key, value)
|
||||
}
|
||||
assert.NilError(t, cmd.Execute())
|
||||
}
|
||||
}
|
84
cli/command/swarm/client_test.go
Normal file
84
cli/command/swarm/client_test.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
package swarm
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/client"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type fakeClient struct {
|
||||
client.Client
|
||||
infoFunc func() (types.Info, error)
|
||||
swarmInitFunc func() (string, error)
|
||||
swarmInspectFunc func() (swarm.Swarm, error)
|
||||
nodeInspectFunc func() (swarm.Node, []byte, error)
|
||||
swarmGetUnlockKeyFunc func() (types.SwarmUnlockKeyResponse, error)
|
||||
swarmJoinFunc func() error
|
||||
swarmLeaveFunc func() error
|
||||
swarmUpdateFunc func(swarm swarm.Spec, flags swarm.UpdateFlags) error
|
||||
swarmUnlockFunc func(req swarm.UnlockRequest) error
|
||||
}
|
||||
|
||||
func (cli *fakeClient) Info(ctx context.Context) (types.Info, error) {
|
||||
if cli.infoFunc != nil {
|
||||
return cli.infoFunc()
|
||||
}
|
||||
return types.Info{}, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) NodeInspectWithRaw(ctx context.Context, ref string) (swarm.Node, []byte, error) {
|
||||
if cli.nodeInspectFunc != nil {
|
||||
return cli.nodeInspectFunc()
|
||||
}
|
||||
return swarm.Node{}, []byte{}, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) SwarmInit(ctx context.Context, req swarm.InitRequest) (string, error) {
|
||||
if cli.swarmInitFunc != nil {
|
||||
return cli.swarmInitFunc()
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) SwarmInspect(ctx context.Context) (swarm.Swarm, error) {
|
||||
if cli.swarmInspectFunc != nil {
|
||||
return cli.swarmInspectFunc()
|
||||
}
|
||||
return swarm.Swarm{}, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) SwarmGetUnlockKey(ctx context.Context) (types.SwarmUnlockKeyResponse, error) {
|
||||
if cli.swarmGetUnlockKeyFunc != nil {
|
||||
return cli.swarmGetUnlockKeyFunc()
|
||||
}
|
||||
return types.SwarmUnlockKeyResponse{}, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) SwarmJoin(ctx context.Context, req swarm.JoinRequest) error {
|
||||
if cli.swarmJoinFunc != nil {
|
||||
return cli.swarmJoinFunc()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) SwarmLeave(ctx context.Context, force bool) error {
|
||||
if cli.swarmLeaveFunc != nil {
|
||||
return cli.swarmLeaveFunc()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) SwarmUpdate(ctx context.Context, version swarm.Version, swarm swarm.Spec, flags swarm.UpdateFlags) error {
|
||||
if cli.swarmUpdateFunc != nil {
|
||||
return cli.swarmUpdateFunc(swarm, flags)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) SwarmUnlock(ctx context.Context, req swarm.UnlockRequest) error {
|
||||
if cli.swarmUnlockFunc != nil {
|
||||
return cli.swarmUnlockFunc(req)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -22,7 +22,7 @@ type initOptions struct {
|
|||
forceNewCluster bool
|
||||
}
|
||||
|
||||
func newInitCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func newInitCommand(dockerCli command.Cli) *cobra.Command {
|
||||
opts := initOptions{
|
||||
listenAddr: NewListenAddrOption(),
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ func newInitCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func runInit(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts initOptions) error {
|
||||
func runInit(dockerCli command.Cli, flags *pflag.FlagSet, opts initOptions) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
|
@ -67,7 +67,7 @@ func runInit(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts initOption
|
|||
|
||||
fmt.Fprintf(dockerCli.Out(), "Swarm initialized: current node (%s) is now a manager.\n\n", nodeID)
|
||||
|
||||
if err := printJoinCommand(ctx, dockerCli, nodeID, true, false); err != nil {
|
||||
if err := printJoinCommand(ctx, dockerCli, nodeID, false, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
129
cli/command/swarm/init_test.go
Normal file
129
cli/command/swarm/init_test.go
Normal file
|
@ -0,0 +1,129 @@
|
|||
package swarm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/cli/internal/test"
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
"github.com/docker/docker/pkg/testutil/golden"
|
||||
)
|
||||
|
||||
func TestSwarmInitErrorOnAPIFailure(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
flags map[string]string
|
||||
swarmInitFunc func() (string, error)
|
||||
swarmInspectFunc func() (swarm.Swarm, error)
|
||||
swarmGetUnlockKeyFunc func() (types.SwarmUnlockKeyResponse, error)
|
||||
nodeInspectFunc func() (swarm.Node, []byte, error)
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "init-failed",
|
||||
swarmInitFunc: func() (string, error) {
|
||||
return "", fmt.Errorf("error initializing the swarm")
|
||||
},
|
||||
expectedError: "error initializing the swarm",
|
||||
},
|
||||
{
|
||||
name: "init-faild-with-ip-choice",
|
||||
swarmInitFunc: func() (string, error) {
|
||||
return "", fmt.Errorf("could not choose an IP address to advertise")
|
||||
},
|
||||
expectedError: "could not choose an IP address to advertise - specify one with --advertise-addr",
|
||||
},
|
||||
{
|
||||
name: "swarm-inspect-after-init-failed",
|
||||
swarmInspectFunc: func() (swarm.Swarm, error) {
|
||||
return swarm.Swarm{}, fmt.Errorf("error inspecting the swarm")
|
||||
},
|
||||
expectedError: "error inspecting the swarm",
|
||||
},
|
||||
{
|
||||
name: "node-inspect-after-init-failed",
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return swarm.Node{}, []byte{}, fmt.Errorf("error inspecting the node")
|
||||
},
|
||||
expectedError: "error inspecting the node",
|
||||
},
|
||||
{
|
||||
name: "swarm-get-unlock-key-after-init-failed",
|
||||
flags: map[string]string{
|
||||
flagAutolock: "true",
|
||||
},
|
||||
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
|
||||
return types.SwarmUnlockKeyResponse{}, fmt.Errorf("error getting swarm unlock key")
|
||||
},
|
||||
expectedError: "could not fetch unlock key: error getting swarm unlock key",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newInitCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
swarmInitFunc: tc.swarmInitFunc,
|
||||
swarmInspectFunc: tc.swarmInspectFunc,
|
||||
swarmGetUnlockKeyFunc: tc.swarmGetUnlockKeyFunc,
|
||||
nodeInspectFunc: tc.nodeInspectFunc,
|
||||
}, buf))
|
||||
for key, value := range tc.flags {
|
||||
cmd.Flags().Set(key, value)
|
||||
}
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
assert.Error(t, cmd.Execute(), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwarmInit(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
flags map[string]string
|
||||
swarmInitFunc func() (string, error)
|
||||
swarmInspectFunc func() (swarm.Swarm, error)
|
||||
swarmGetUnlockKeyFunc func() (types.SwarmUnlockKeyResponse, error)
|
||||
nodeInspectFunc func() (swarm.Node, []byte, error)
|
||||
}{
|
||||
{
|
||||
name: "init",
|
||||
swarmInitFunc: func() (string, error) {
|
||||
return "nodeID", nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "init-autolock",
|
||||
flags: map[string]string{
|
||||
flagAutolock: "true",
|
||||
},
|
||||
swarmInitFunc: func() (string, error) {
|
||||
return "nodeID", nil
|
||||
},
|
||||
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
|
||||
return types.SwarmUnlockKeyResponse{
|
||||
UnlockKey: "unlock-key",
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newInitCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
swarmInitFunc: tc.swarmInitFunc,
|
||||
swarmInspectFunc: tc.swarmInspectFunc,
|
||||
swarmGetUnlockKeyFunc: tc.swarmGetUnlockKeyFunc,
|
||||
nodeInspectFunc: tc.nodeInspectFunc,
|
||||
}, buf))
|
||||
for key, value := range tc.flags {
|
||||
cmd.Flags().Set(key, value)
|
||||
}
|
||||
assert.NilError(t, cmd.Execute())
|
||||
actual := buf.String()
|
||||
expected := golden.Get(t, []byte(actual), fmt.Sprintf("init-%s.golden", tc.name))
|
||||
assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ type joinOptions struct {
|
|||
token string
|
||||
}
|
||||
|
||||
func newJoinCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func newJoinCommand(dockerCli command.Cli) *cobra.Command {
|
||||
opts := joinOptions{
|
||||
listenAddr: NewListenAddrOption(),
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ func newJoinCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func runJoin(dockerCli *command.DockerCli, opts joinOptions) error {
|
||||
func runJoin(dockerCli command.Cli, opts joinOptions) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
|
|
102
cli/command/swarm/join_test.go
Normal file
102
cli/command/swarm/join_test.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
package swarm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/cli/internal/test"
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
)
|
||||
|
||||
func TestSwarmJoinErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
swarmJoinFunc func() error
|
||||
infoFunc func() (types.Info, error)
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "not-enough-args",
|
||||
expectedError: "requires exactly 1 argument",
|
||||
},
|
||||
{
|
||||
name: "too-many-args",
|
||||
args: []string{"remote1", "remote2"},
|
||||
expectedError: "requires exactly 1 argument",
|
||||
},
|
||||
{
|
||||
name: "join-failed",
|
||||
args: []string{"remote"},
|
||||
swarmJoinFunc: func() error {
|
||||
return fmt.Errorf("error joining the swarm")
|
||||
},
|
||||
expectedError: "error joining the swarm",
|
||||
},
|
||||
{
|
||||
name: "join-failed-on-init",
|
||||
args: []string{"remote"},
|
||||
infoFunc: func() (types.Info, error) {
|
||||
return types.Info{}, fmt.Errorf("error asking for node info")
|
||||
},
|
||||
expectedError: "error asking for node info",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newJoinCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
swarmJoinFunc: tc.swarmJoinFunc,
|
||||
infoFunc: tc.infoFunc,
|
||||
}, buf))
|
||||
cmd.SetArgs(tc.args)
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
assert.Error(t, cmd.Execute(), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwarmJoin(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
infoFunc func() (types.Info, error)
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "join-as-manager",
|
||||
infoFunc: func() (types.Info, error) {
|
||||
return types.Info{
|
||||
Swarm: swarm.Info{
|
||||
ControlAvailable: true,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
expected: "This node joined a swarm as a manager.",
|
||||
},
|
||||
{
|
||||
name: "join-as-worker",
|
||||
infoFunc: func() (types.Info, error) {
|
||||
return types.Info{
|
||||
Swarm: swarm.Info{
|
||||
ControlAvailable: false,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
expected: "This node joined a swarm as a worker.",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newJoinCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
infoFunc: tc.infoFunc,
|
||||
}, buf))
|
||||
cmd.SetArgs([]string{"remote"})
|
||||
assert.NilError(t, cmd.Execute())
|
||||
assert.Equal(t, strings.TrimSpace(buf.String()), tc.expected)
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ type joinTokenOptions struct {
|
|||
quiet bool
|
||||
}
|
||||
|
||||
func newJoinTokenCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func newJoinTokenCommand(dockerCli command.Cli) *cobra.Command {
|
||||
opts := joinTokenOptions{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -38,7 +38,7 @@ func newJoinTokenCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func runJoinToken(dockerCli *command.DockerCli, opts joinTokenOptions) error {
|
||||
func runJoinToken(dockerCli command.Cli, opts joinTokenOptions) error {
|
||||
worker := opts.role == "worker"
|
||||
manager := opts.role == "manager"
|
||||
|
||||
|
@ -94,7 +94,7 @@ func runJoinToken(dockerCli *command.DockerCli, opts joinTokenOptions) error {
|
|||
return printJoinCommand(ctx, dockerCli, info.Swarm.NodeID, worker, manager)
|
||||
}
|
||||
|
||||
func printJoinCommand(ctx context.Context, dockerCli *command.DockerCli, nodeID string, worker bool, manager bool) error {
|
||||
func printJoinCommand(ctx context.Context, dockerCli command.Cli, nodeID string, worker bool, manager bool) error {
|
||||
client := dockerCli.Client()
|
||||
|
||||
node, _, err := client.NodeInspectWithRaw(ctx, nodeID)
|
||||
|
|
215
cli/command/swarm/join_token_test.go
Normal file
215
cli/command/swarm/join_token_test.go
Normal file
|
@ -0,0 +1,215 @@
|
|||
package swarm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/cli/internal/test"
|
||||
// Import builders to get the builder function as package function
|
||||
. "github.com/docker/docker/cli/internal/test/builders"
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
"github.com/docker/docker/pkg/testutil/golden"
|
||||
)
|
||||
|
||||
func TestSwarmJoinTokenErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
flags map[string]string
|
||||
infoFunc func() (types.Info, error)
|
||||
swarmInspectFunc func() (swarm.Swarm, error)
|
||||
swarmUpdateFunc func(swarm swarm.Spec, flags swarm.UpdateFlags) error
|
||||
nodeInspectFunc func() (swarm.Node, []byte, error)
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "not-enough-args",
|
||||
expectedError: "requires exactly 1 argument",
|
||||
},
|
||||
{
|
||||
name: "too-many-args",
|
||||
args: []string{"worker", "manager"},
|
||||
expectedError: "requires exactly 1 argument",
|
||||
},
|
||||
{
|
||||
name: "invalid-args",
|
||||
args: []string{"foo"},
|
||||
expectedError: "unknown role foo",
|
||||
},
|
||||
{
|
||||
name: "swarm-inspect-failed",
|
||||
args: []string{"worker"},
|
||||
swarmInspectFunc: func() (swarm.Swarm, error) {
|
||||
return swarm.Swarm{}, fmt.Errorf("error inspecting the swarm")
|
||||
},
|
||||
expectedError: "error inspecting the swarm",
|
||||
},
|
||||
{
|
||||
name: "swarm-inspect-rotate-failed",
|
||||
args: []string{"worker"},
|
||||
flags: map[string]string{
|
||||
flagRotate: "true",
|
||||
},
|
||||
swarmInspectFunc: func() (swarm.Swarm, error) {
|
||||
return swarm.Swarm{}, fmt.Errorf("error inspecting the swarm")
|
||||
},
|
||||
expectedError: "error inspecting the swarm",
|
||||
},
|
||||
{
|
||||
name: "swarm-update-failed",
|
||||
args: []string{"worker"},
|
||||
flags: map[string]string{
|
||||
flagRotate: "true",
|
||||
},
|
||||
swarmUpdateFunc: func(swarm swarm.Spec, flags swarm.UpdateFlags) error {
|
||||
return fmt.Errorf("error updating the swarm")
|
||||
},
|
||||
expectedError: "error updating the swarm",
|
||||
},
|
||||
{
|
||||
name: "node-inspect-failed",
|
||||
args: []string{"worker"},
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return swarm.Node{}, []byte{}, fmt.Errorf("error inspecting node")
|
||||
},
|
||||
expectedError: "error inspecting node",
|
||||
},
|
||||
{
|
||||
name: "info-failed",
|
||||
args: []string{"worker"},
|
||||
infoFunc: func() (types.Info, error) {
|
||||
return types.Info{}, fmt.Errorf("error asking for node info")
|
||||
},
|
||||
expectedError: "error asking for node info",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newJoinTokenCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
swarmInspectFunc: tc.swarmInspectFunc,
|
||||
swarmUpdateFunc: tc.swarmUpdateFunc,
|
||||
infoFunc: tc.infoFunc,
|
||||
nodeInspectFunc: tc.nodeInspectFunc,
|
||||
}, buf))
|
||||
cmd.SetArgs(tc.args)
|
||||
for key, value := range tc.flags {
|
||||
cmd.Flags().Set(key, value)
|
||||
}
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
assert.Error(t, cmd.Execute(), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwarmJoinToken(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
flags map[string]string
|
||||
infoFunc func() (types.Info, error)
|
||||
swarmInspectFunc func() (swarm.Swarm, error)
|
||||
nodeInspectFunc func() (swarm.Node, []byte, error)
|
||||
}{
|
||||
{
|
||||
name: "worker",
|
||||
args: []string{"worker"},
|
||||
infoFunc: func() (types.Info, error) {
|
||||
return types.Info{
|
||||
Swarm: swarm.Info{
|
||||
NodeID: "nodeID",
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return *Node(Manager()), []byte{}, nil
|
||||
},
|
||||
swarmInspectFunc: func() (swarm.Swarm, error) {
|
||||
return *Swarm(), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "manager",
|
||||
args: []string{"manager"},
|
||||
infoFunc: func() (types.Info, error) {
|
||||
return types.Info{
|
||||
Swarm: swarm.Info{
|
||||
NodeID: "nodeID",
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return *Node(Manager()), []byte{}, nil
|
||||
},
|
||||
swarmInspectFunc: func() (swarm.Swarm, error) {
|
||||
return *Swarm(), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "manager-rotate",
|
||||
args: []string{"manager"},
|
||||
flags: map[string]string{
|
||||
flagRotate: "true",
|
||||
},
|
||||
infoFunc: func() (types.Info, error) {
|
||||
return types.Info{
|
||||
Swarm: swarm.Info{
|
||||
NodeID: "nodeID",
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return *Node(Manager()), []byte{}, nil
|
||||
},
|
||||
swarmInspectFunc: func() (swarm.Swarm, error) {
|
||||
return *Swarm(), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "worker-quiet",
|
||||
args: []string{"worker"},
|
||||
flags: map[string]string{
|
||||
flagQuiet: "true",
|
||||
},
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return *Node(Manager()), []byte{}, nil
|
||||
},
|
||||
swarmInspectFunc: func() (swarm.Swarm, error) {
|
||||
return *Swarm(), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "manager-quiet",
|
||||
args: []string{"manager"},
|
||||
flags: map[string]string{
|
||||
flagQuiet: "true",
|
||||
},
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return *Node(Manager()), []byte{}, nil
|
||||
},
|
||||
swarmInspectFunc: func() (swarm.Swarm, error) {
|
||||
return *Swarm(), nil
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newJoinTokenCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
swarmInspectFunc: tc.swarmInspectFunc,
|
||||
infoFunc: tc.infoFunc,
|
||||
nodeInspectFunc: tc.nodeInspectFunc,
|
||||
}, buf))
|
||||
cmd.SetArgs(tc.args)
|
||||
for key, value := range tc.flags {
|
||||
cmd.Flags().Set(key, value)
|
||||
}
|
||||
assert.NilError(t, cmd.Execute())
|
||||
actual := buf.String()
|
||||
expected := golden.Get(t, []byte(actual), fmt.Sprintf("jointoken-%s.golden", tc.name))
|
||||
assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ type leaveOptions struct {
|
|||
force bool
|
||||
}
|
||||
|
||||
func newLeaveCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func newLeaveCommand(dockerCli command.Cli) *cobra.Command {
|
||||
opts := leaveOptions{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -31,7 +31,7 @@ func newLeaveCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func runLeave(dockerCli *command.DockerCli, opts leaveOptions) error {
|
||||
func runLeave(dockerCli command.Cli, opts leaveOptions) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
|
|
52
cli/command/swarm/leave_test.go
Normal file
52
cli/command/swarm/leave_test.go
Normal file
|
@ -0,0 +1,52 @@
|
|||
package swarm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/cli/internal/test"
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
)
|
||||
|
||||
func TestSwarmLeaveErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
swarmLeaveFunc func() error
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "too-many-args",
|
||||
args: []string{"foo"},
|
||||
expectedError: "accepts no argument(s)",
|
||||
},
|
||||
{
|
||||
name: "leave-failed",
|
||||
swarmLeaveFunc: func() error {
|
||||
return fmt.Errorf("error leaving the swarm")
|
||||
},
|
||||
expectedError: "error leaving the swarm",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newLeaveCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
swarmLeaveFunc: tc.swarmLeaveFunc,
|
||||
}, buf))
|
||||
cmd.SetArgs(tc.args)
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
assert.Error(t, cmd.Execute(), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwarmLeave(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newLeaveCommand(
|
||||
test.NewFakeCli(&fakeClient{}, buf))
|
||||
assert.NilError(t, cmd.Execute())
|
||||
assert.Equal(t, strings.TrimSpace(buf.String()), "Node left the swarm.")
|
||||
}
|
|
@ -35,3 +35,76 @@ func TestNodeAddrOptionSetInvalidFormat(t *testing.T) {
|
|||
opt := NewListenAddrOption()
|
||||
assert.Error(t, opt.Set("http://localhost:4545"), "Invalid")
|
||||
}
|
||||
|
||||
func TestExternalCAOptionErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
externalCA string
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
externalCA: "",
|
||||
expectedError: "EOF",
|
||||
},
|
||||
{
|
||||
externalCA: "anything",
|
||||
expectedError: "invalid field 'anything' must be a key=value pair",
|
||||
},
|
||||
{
|
||||
externalCA: "foo=bar",
|
||||
expectedError: "the external-ca option needs a protocol= parameter",
|
||||
},
|
||||
{
|
||||
externalCA: "protocol=baz",
|
||||
expectedError: "unrecognized external CA protocol baz",
|
||||
},
|
||||
{
|
||||
externalCA: "protocol=cfssl",
|
||||
expectedError: "the external-ca option needs a url= parameter",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
opt := &ExternalCAOption{}
|
||||
assert.Error(t, opt.Set(tc.externalCA), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExternalCAOption(t *testing.T) {
|
||||
testCases := []struct {
|
||||
externalCA string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
externalCA: "protocol=cfssl,url=anything",
|
||||
expected: "cfssl: anything",
|
||||
},
|
||||
{
|
||||
externalCA: "protocol=CFSSL,url=anything",
|
||||
expected: "cfssl: anything",
|
||||
},
|
||||
{
|
||||
externalCA: "protocol=Cfssl,url=https://example.com",
|
||||
expected: "cfssl: https://example.com",
|
||||
},
|
||||
{
|
||||
externalCA: "protocol=Cfssl,url=https://example.com,foo=bar",
|
||||
expected: "cfssl: https://example.com",
|
||||
},
|
||||
{
|
||||
externalCA: "protocol=Cfssl,url=https://example.com,foo=bar,foo=baz",
|
||||
expected: "cfssl: https://example.com",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
opt := &ExternalCAOption{}
|
||||
assert.NilError(t, opt.Set(tc.externalCA))
|
||||
assert.Equal(t, opt.String(), tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExternalCAOptionMultiple(t *testing.T) {
|
||||
opt := &ExternalCAOption{}
|
||||
assert.NilError(t, opt.Set("protocol=cfssl,url=https://example.com"))
|
||||
assert.NilError(t, opt.Set("protocol=CFSSL,url=anything"))
|
||||
assert.Equal(t, len(opt.Value()), 2)
|
||||
assert.Equal(t, opt.String(), "cfssl: https://example.com, cfssl: anything")
|
||||
}
|
||||
|
|
11
cli/command/swarm/testdata/init-init-autolock.golden
vendored
Normal file
11
cli/command/swarm/testdata/init-init-autolock.golden
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
Swarm initialized: current node (nodeID) is now a manager.
|
||||
|
||||
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
|
||||
|
||||
To unlock a swarm manager after it restarts, run the `docker swarm unlock`
|
||||
command and provide the following key:
|
||||
|
||||
unlock-key
|
||||
|
||||
Please remember to store this key in a password manager, since without it you
|
||||
will not be able to restart the manager.
|
4
cli/command/swarm/testdata/init-init.golden
vendored
Normal file
4
cli/command/swarm/testdata/init-init.golden
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
Swarm initialized: current node (nodeID) is now a manager.
|
||||
|
||||
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
|
||||
|
1
cli/command/swarm/testdata/jointoken-manager-quiet.golden
vendored
Normal file
1
cli/command/swarm/testdata/jointoken-manager-quiet.golden
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
manager-join-token
|
8
cli/command/swarm/testdata/jointoken-manager-rotate.golden
vendored
Normal file
8
cli/command/swarm/testdata/jointoken-manager-rotate.golden
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
Successfully rotated manager join token.
|
||||
|
||||
To add a manager to this swarm, run the following command:
|
||||
|
||||
docker swarm join \
|
||||
--token manager-join-token \
|
||||
127.0.0.1
|
||||
|
6
cli/command/swarm/testdata/jointoken-manager.golden
vendored
Normal file
6
cli/command/swarm/testdata/jointoken-manager.golden
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
To add a manager to this swarm, run the following command:
|
||||
|
||||
docker swarm join \
|
||||
--token manager-join-token \
|
||||
127.0.0.1
|
||||
|
1
cli/command/swarm/testdata/jointoken-worker-quiet.golden
vendored
Normal file
1
cli/command/swarm/testdata/jointoken-worker-quiet.golden
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
worker-join-token
|
6
cli/command/swarm/testdata/jointoken-worker.golden
vendored
Normal file
6
cli/command/swarm/testdata/jointoken-worker.golden
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
To add a worker to this swarm, run the following command:
|
||||
|
||||
docker swarm join \
|
||||
--token worker-join-token \
|
||||
127.0.0.1
|
||||
|
1
cli/command/swarm/testdata/unlockkeys-unlock-key-quiet.golden
vendored
Normal file
1
cli/command/swarm/testdata/unlockkeys-unlock-key-quiet.golden
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
unlock-key
|
1
cli/command/swarm/testdata/unlockkeys-unlock-key-rotate-quiet.golden
vendored
Normal file
1
cli/command/swarm/testdata/unlockkeys-unlock-key-rotate-quiet.golden
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
unlock-key
|
9
cli/command/swarm/testdata/unlockkeys-unlock-key-rotate.golden
vendored
Normal file
9
cli/command/swarm/testdata/unlockkeys-unlock-key-rotate.golden
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
Successfully rotated manager unlock key.
|
||||
|
||||
To unlock a swarm manager after it restarts, run the `docker swarm unlock`
|
||||
command and provide the following key:
|
||||
|
||||
unlock-key
|
||||
|
||||
Please remember to store this key in a password manager, since without it you
|
||||
will not be able to restart the manager.
|
7
cli/command/swarm/testdata/unlockkeys-unlock-key.golden
vendored
Normal file
7
cli/command/swarm/testdata/unlockkeys-unlock-key.golden
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
To unlock a swarm manager after it restarts, run the `docker swarm unlock`
|
||||
command and provide the following key:
|
||||
|
||||
unlock-key
|
||||
|
||||
Please remember to store this key in a password manager, since without it you
|
||||
will not be able to restart the manager.
|
1
cli/command/swarm/testdata/update-all-flags-quiet.golden
vendored
Normal file
1
cli/command/swarm/testdata/update-all-flags-quiet.golden
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Swarm updated.
|
8
cli/command/swarm/testdata/update-autolock-unlock-key.golden
vendored
Normal file
8
cli/command/swarm/testdata/update-autolock-unlock-key.golden
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
Swarm updated.
|
||||
To unlock a swarm manager after it restarts, run the `docker swarm unlock`
|
||||
command and provide the following key:
|
||||
|
||||
unlock-key
|
||||
|
||||
Please remember to store this key in a password manager, since without it you
|
||||
will not be able to restart the manager.
|
13
cli/command/swarm/testdata/update-noargs.golden
vendored
Normal file
13
cli/command/swarm/testdata/update-noargs.golden
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
Update the swarm
|
||||
|
||||
Usage:
|
||||
update [OPTIONS] [flags]
|
||||
|
||||
Flags:
|
||||
--autolock Change manager autolocking setting (true|false)
|
||||
--cert-expiry duration Validity period for node certificates (ns|us|ms|s|m|h) (default 2160h0m0s)
|
||||
--dispatcher-heartbeat duration Dispatcher heartbeat period (ns|us|ms|s|m|h) (default 5s)
|
||||
--external-ca external-ca Specifications of one or more certificate signing endpoints
|
||||
--max-snapshots uint Number of additional Raft snapshots to retain
|
||||
--snapshot-interval uint Number of log entries between Raft snapshots (default 10000)
|
||||
--task-history-limit int Task history retention limit (default 5)
|
|
@ -18,7 +18,7 @@ import (
|
|||
|
||||
type unlockOptions struct{}
|
||||
|
||||
func newUnlockCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func newUnlockCommand(dockerCli command.Cli) *cobra.Command {
|
||||
opts := unlockOptions{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -33,7 +33,7 @@ func newUnlockCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func runUnlock(dockerCli *command.DockerCli, opts unlockOptions) error {
|
||||
func runUnlock(dockerCli command.Cli, opts unlockOptions) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
|
|
|
@ -3,12 +3,11 @@ package swarm
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/cli/command"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
|
@ -17,7 +16,7 @@ type unlockKeyOptions struct {
|
|||
quiet bool
|
||||
}
|
||||
|
||||
func newUnlockKeyCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func newUnlockKeyCommand(dockerCli command.Cli) *cobra.Command {
|
||||
opts := unlockKeyOptions{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -36,7 +35,7 @@ func newUnlockKeyCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func runUnlockKey(dockerCli *command.DockerCli, opts unlockKeyOptions) error {
|
||||
func runUnlockKey(dockerCli command.Cli, opts unlockKeyOptions) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
|
@ -79,7 +78,7 @@ func runUnlockKey(dockerCli *command.DockerCli, opts unlockKeyOptions) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func printUnlockCommand(ctx context.Context, dockerCli *command.DockerCli, unlockKey string) {
|
||||
func printUnlockCommand(ctx context.Context, dockerCli command.Cli, unlockKey string) {
|
||||
if len(unlockKey) > 0 {
|
||||
fmt.Fprintf(dockerCli.Out(), "To unlock a swarm manager after it restarts, run the `docker swarm unlock`\ncommand and provide the following key:\n\n %s\n\nPlease remember to store this key in a password manager, since without it you\nwill not be able to restart the manager.\n", unlockKey)
|
||||
}
|
||||
|
|
175
cli/command/swarm/unlock_key_test.go
Normal file
175
cli/command/swarm/unlock_key_test.go
Normal file
|
@ -0,0 +1,175 @@
|
|||
package swarm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/cli/internal/test"
|
||||
// Import builders to get the builder function as package function
|
||||
. "github.com/docker/docker/cli/internal/test/builders"
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
"github.com/docker/docker/pkg/testutil/golden"
|
||||
)
|
||||
|
||||
func TestSwarmUnlockKeyErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
flags map[string]string
|
||||
swarmInspectFunc func() (swarm.Swarm, error)
|
||||
swarmUpdateFunc func(swarm swarm.Spec, flags swarm.UpdateFlags) error
|
||||
swarmGetUnlockKeyFunc func() (types.SwarmUnlockKeyResponse, error)
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "too-many-args",
|
||||
args: []string{"foo"},
|
||||
expectedError: "accepts no argument(s)",
|
||||
},
|
||||
{
|
||||
name: "swarm-inspect-rotate-failed",
|
||||
flags: map[string]string{
|
||||
flagRotate: "true",
|
||||
},
|
||||
swarmInspectFunc: func() (swarm.Swarm, error) {
|
||||
return swarm.Swarm{}, fmt.Errorf("error inspecting the swarm")
|
||||
},
|
||||
expectedError: "error inspecting the swarm",
|
||||
},
|
||||
{
|
||||
name: "swarm-rotate-no-autolock-failed",
|
||||
flags: map[string]string{
|
||||
flagRotate: "true",
|
||||
},
|
||||
swarmInspectFunc: func() (swarm.Swarm, error) {
|
||||
return *Swarm(), nil
|
||||
},
|
||||
expectedError: "cannot rotate because autolock is not turned on",
|
||||
},
|
||||
{
|
||||
name: "swarm-update-failed",
|
||||
flags: map[string]string{
|
||||
flagRotate: "true",
|
||||
},
|
||||
swarmInspectFunc: func() (swarm.Swarm, error) {
|
||||
return *Swarm(Autolock()), nil
|
||||
},
|
||||
swarmUpdateFunc: func(swarm swarm.Spec, flags swarm.UpdateFlags) error {
|
||||
return fmt.Errorf("error updating the swarm")
|
||||
},
|
||||
expectedError: "error updating the swarm",
|
||||
},
|
||||
{
|
||||
name: "swarm-get-unlock-key-failed",
|
||||
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
|
||||
return types.SwarmUnlockKeyResponse{}, fmt.Errorf("error getting unlock key")
|
||||
},
|
||||
expectedError: "error getting unlock key",
|
||||
},
|
||||
{
|
||||
name: "swarm-no-unlock-key-failed",
|
||||
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
|
||||
return types.SwarmUnlockKeyResponse{
|
||||
UnlockKey: "",
|
||||
}, nil
|
||||
},
|
||||
expectedError: "no unlock key is set",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newUnlockKeyCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
swarmInspectFunc: tc.swarmInspectFunc,
|
||||
swarmUpdateFunc: tc.swarmUpdateFunc,
|
||||
swarmGetUnlockKeyFunc: tc.swarmGetUnlockKeyFunc,
|
||||
}, buf))
|
||||
cmd.SetArgs(tc.args)
|
||||
for key, value := range tc.flags {
|
||||
cmd.Flags().Set(key, value)
|
||||
}
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
assert.Error(t, cmd.Execute(), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwarmUnlockKey(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
flags map[string]string
|
||||
swarmInspectFunc func() (swarm.Swarm, error)
|
||||
swarmUpdateFunc func(swarm swarm.Spec, flags swarm.UpdateFlags) error
|
||||
swarmGetUnlockKeyFunc func() (types.SwarmUnlockKeyResponse, error)
|
||||
}{
|
||||
{
|
||||
name: "unlock-key",
|
||||
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
|
||||
return types.SwarmUnlockKeyResponse{
|
||||
UnlockKey: "unlock-key",
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unlock-key-quiet",
|
||||
flags: map[string]string{
|
||||
flagQuiet: "true",
|
||||
},
|
||||
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
|
||||
return types.SwarmUnlockKeyResponse{
|
||||
UnlockKey: "unlock-key",
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unlock-key-rotate",
|
||||
flags: map[string]string{
|
||||
flagRotate: "true",
|
||||
},
|
||||
swarmInspectFunc: func() (swarm.Swarm, error) {
|
||||
return *Swarm(Autolock()), nil
|
||||
},
|
||||
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
|
||||
return types.SwarmUnlockKeyResponse{
|
||||
UnlockKey: "unlock-key",
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unlock-key-rotate-quiet",
|
||||
flags: map[string]string{
|
||||
flagQuiet: "true",
|
||||
flagRotate: "true",
|
||||
},
|
||||
swarmInspectFunc: func() (swarm.Swarm, error) {
|
||||
return *Swarm(Autolock()), nil
|
||||
},
|
||||
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
|
||||
return types.SwarmUnlockKeyResponse{
|
||||
UnlockKey: "unlock-key",
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newUnlockKeyCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
swarmInspectFunc: tc.swarmInspectFunc,
|
||||
swarmUpdateFunc: tc.swarmUpdateFunc,
|
||||
swarmGetUnlockKeyFunc: tc.swarmGetUnlockKeyFunc,
|
||||
}, buf))
|
||||
cmd.SetArgs(tc.args)
|
||||
for key, value := range tc.flags {
|
||||
cmd.Flags().Set(key, value)
|
||||
}
|
||||
assert.NilError(t, cmd.Execute())
|
||||
actual := buf.String()
|
||||
expected := golden.Get(t, []byte(actual), fmt.Sprintf("unlockkeys-%s.golden", tc.name))
|
||||
assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
|
||||
}
|
||||
}
|
101
cli/command/swarm/unlock_test.go
Normal file
101
cli/command/swarm/unlock_test.go
Normal file
|
@ -0,0 +1,101 @@
|
|||
package swarm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/cli/internal/test"
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
)
|
||||
|
||||
func TestSwarmUnlockErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
input string
|
||||
swarmUnlockFunc func(req swarm.UnlockRequest) error
|
||||
infoFunc func() (types.Info, error)
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "too-many-args",
|
||||
args: []string{"foo"},
|
||||
expectedError: "accepts no argument(s)",
|
||||
},
|
||||
{
|
||||
name: "is-not-part-of-a-swarm",
|
||||
infoFunc: func() (types.Info, error) {
|
||||
return types.Info{
|
||||
Swarm: swarm.Info{
|
||||
LocalNodeState: swarm.LocalNodeStateInactive,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
expectedError: "This node is not part of a swarm",
|
||||
},
|
||||
{
|
||||
name: "is-not-locked",
|
||||
infoFunc: func() (types.Info, error) {
|
||||
return types.Info{
|
||||
Swarm: swarm.Info{
|
||||
LocalNodeState: swarm.LocalNodeStateActive,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
expectedError: "Error: swarm is not locked",
|
||||
},
|
||||
{
|
||||
name: "unlockrequest-failed",
|
||||
infoFunc: func() (types.Info, error) {
|
||||
return types.Info{
|
||||
Swarm: swarm.Info{
|
||||
LocalNodeState: swarm.LocalNodeStateLocked,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
swarmUnlockFunc: func(req swarm.UnlockRequest) error {
|
||||
return fmt.Errorf("error unlocking the swarm")
|
||||
},
|
||||
expectedError: "error unlocking the swarm",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newUnlockCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
infoFunc: tc.infoFunc,
|
||||
swarmUnlockFunc: tc.swarmUnlockFunc,
|
||||
}, buf))
|
||||
cmd.SetArgs(tc.args)
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
assert.Error(t, cmd.Execute(), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwarmUnlock(t *testing.T) {
|
||||
input := "unlockKey"
|
||||
buf := new(bytes.Buffer)
|
||||
dockerCli := test.NewFakeCli(&fakeClient{
|
||||
infoFunc: func() (types.Info, error) {
|
||||
return types.Info{
|
||||
Swarm: swarm.Info{
|
||||
LocalNodeState: swarm.LocalNodeStateLocked,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
swarmUnlockFunc: func(req swarm.UnlockRequest) error {
|
||||
if req.UnlockKey != input {
|
||||
return fmt.Errorf("Invalid unlock key")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}, buf)
|
||||
dockerCli.SetIn(ioutil.NopCloser(strings.NewReader(input)))
|
||||
cmd := newUnlockCommand(dockerCli)
|
||||
assert.NilError(t, cmd.Execute())
|
||||
}
|
|
@ -13,7 +13,7 @@ import (
|
|||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
opts := swarmOptions{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -36,24 +36,24 @@ func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func runUpdate(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts swarmOptions) error {
|
||||
func runUpdate(dockerCli command.Cli, flags *pflag.FlagSet, opts swarmOptions) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
var updateFlags swarm.UpdateFlags
|
||||
|
||||
swarm, err := client.SwarmInspect(ctx)
|
||||
swarmInspect, err := client.SwarmInspect(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
prevAutoLock := swarm.Spec.EncryptionConfig.AutoLockManagers
|
||||
prevAutoLock := swarmInspect.Spec.EncryptionConfig.AutoLockManagers
|
||||
|
||||
opts.mergeSwarmSpec(&swarm.Spec, flags)
|
||||
opts.mergeSwarmSpec(&swarmInspect.Spec, flags)
|
||||
|
||||
curAutoLock := swarm.Spec.EncryptionConfig.AutoLockManagers
|
||||
curAutoLock := swarmInspect.Spec.EncryptionConfig.AutoLockManagers
|
||||
|
||||
err = client.SwarmUpdate(ctx, swarm.Version, swarm.Spec, updateFlags)
|
||||
err = client.SwarmUpdate(ctx, swarmInspect.Version, swarmInspect.Spec, updateFlags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
182
cli/command/swarm/update_test.go
Normal file
182
cli/command/swarm/update_test.go
Normal file
|
@ -0,0 +1,182 @@
|
|||
package swarm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/cli/internal/test"
|
||||
// Import builders to get the builder function as package function
|
||||
. "github.com/docker/docker/cli/internal/test/builders"
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
"github.com/docker/docker/pkg/testutil/golden"
|
||||
)
|
||||
|
||||
func TestSwarmUpdateErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
flags map[string]string
|
||||
swarmInspectFunc func() (swarm.Swarm, error)
|
||||
swarmUpdateFunc func(swarm swarm.Spec, flags swarm.UpdateFlags) error
|
||||
swarmGetUnlockKeyFunc func() (types.SwarmUnlockKeyResponse, error)
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "too-many-args",
|
||||
args: []string{"foo"},
|
||||
expectedError: "accepts no argument(s)",
|
||||
},
|
||||
{
|
||||
name: "swarm-inspect-error",
|
||||
flags: map[string]string{
|
||||
flagTaskHistoryLimit: "10",
|
||||
},
|
||||
swarmInspectFunc: func() (swarm.Swarm, error) {
|
||||
return swarm.Swarm{}, fmt.Errorf("error inspecting the swarm")
|
||||
},
|
||||
expectedError: "error inspecting the swarm",
|
||||
},
|
||||
{
|
||||
name: "swarm-update-error",
|
||||
flags: map[string]string{
|
||||
flagTaskHistoryLimit: "10",
|
||||
},
|
||||
swarmUpdateFunc: func(swarm swarm.Spec, flags swarm.UpdateFlags) error {
|
||||
return fmt.Errorf("error updating the swarm")
|
||||
},
|
||||
expectedError: "error updating the swarm",
|
||||
},
|
||||
{
|
||||
name: "swarm-unlockkey-error",
|
||||
flags: map[string]string{
|
||||
flagAutolock: "true",
|
||||
},
|
||||
swarmInspectFunc: func() (swarm.Swarm, error) {
|
||||
return *Swarm(), nil
|
||||
},
|
||||
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
|
||||
return types.SwarmUnlockKeyResponse{}, fmt.Errorf("error getting unlock key")
|
||||
},
|
||||
expectedError: "error getting unlock key",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newUpdateCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
swarmInspectFunc: tc.swarmInspectFunc,
|
||||
swarmUpdateFunc: tc.swarmUpdateFunc,
|
||||
swarmGetUnlockKeyFunc: tc.swarmGetUnlockKeyFunc,
|
||||
}, buf))
|
||||
cmd.SetArgs(tc.args)
|
||||
for key, value := range tc.flags {
|
||||
cmd.Flags().Set(key, value)
|
||||
}
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
assert.Error(t, cmd.Execute(), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwarmUpdate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
flags map[string]string
|
||||
swarmInspectFunc func() (swarm.Swarm, error)
|
||||
swarmUpdateFunc func(swarm swarm.Spec, flags swarm.UpdateFlags) error
|
||||
swarmGetUnlockKeyFunc func() (types.SwarmUnlockKeyResponse, error)
|
||||
}{
|
||||
{
|
||||
name: "noargs",
|
||||
},
|
||||
{
|
||||
name: "all-flags-quiet",
|
||||
flags: map[string]string{
|
||||
flagTaskHistoryLimit: "10",
|
||||
flagDispatcherHeartbeat: "10s",
|
||||
flagCertExpiry: "20s",
|
||||
flagExternalCA: "protocol=cfssl,url=https://example.com.",
|
||||
flagMaxSnapshots: "10",
|
||||
flagSnapshotInterval: "100",
|
||||
flagAutolock: "true",
|
||||
flagQuiet: "true",
|
||||
},
|
||||
swarmUpdateFunc: func(swarm swarm.Spec, flags swarm.UpdateFlags) error {
|
||||
if *swarm.Orchestration.TaskHistoryRetentionLimit != 10 {
|
||||
return fmt.Errorf("historyLimit not correctly set")
|
||||
}
|
||||
heartbeatDuration, err := time.ParseDuration("10s")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if swarm.Dispatcher.HeartbeatPeriod != heartbeatDuration {
|
||||
return fmt.Errorf("heartbeatPeriodLimit not correctly set")
|
||||
}
|
||||
certExpiryDuration, err := time.ParseDuration("20s")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if swarm.CAConfig.NodeCertExpiry != certExpiryDuration {
|
||||
return fmt.Errorf("certExpiry not correctly set")
|
||||
}
|
||||
if len(swarm.CAConfig.ExternalCAs) != 1 {
|
||||
return fmt.Errorf("externalCA not correctly set")
|
||||
}
|
||||
if *swarm.Raft.KeepOldSnapshots != 10 {
|
||||
return fmt.Errorf("keepOldSnapshots not correctly set")
|
||||
}
|
||||
if swarm.Raft.SnapshotInterval != 100 {
|
||||
return fmt.Errorf("snapshotInterval not correctly set")
|
||||
}
|
||||
if !swarm.EncryptionConfig.AutoLockManagers {
|
||||
return fmt.Errorf("autolock not correctly set")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "autolock-unlock-key",
|
||||
flags: map[string]string{
|
||||
flagTaskHistoryLimit: "10",
|
||||
flagAutolock: "true",
|
||||
},
|
||||
swarmUpdateFunc: func(swarm swarm.Spec, flags swarm.UpdateFlags) error {
|
||||
if *swarm.Orchestration.TaskHistoryRetentionLimit != 10 {
|
||||
return fmt.Errorf("historyLimit not correctly set")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
swarmInspectFunc: func() (swarm.Swarm, error) {
|
||||
return *Swarm(), nil
|
||||
},
|
||||
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
|
||||
return types.SwarmUnlockKeyResponse{
|
||||
UnlockKey: "unlock-key",
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newUpdateCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
swarmInspectFunc: tc.swarmInspectFunc,
|
||||
swarmUpdateFunc: tc.swarmUpdateFunc,
|
||||
swarmGetUnlockKeyFunc: tc.swarmGetUnlockKeyFunc,
|
||||
}, buf))
|
||||
cmd.SetArgs(tc.args)
|
||||
for key, value := range tc.flags {
|
||||
cmd.Flags().Set(key, value)
|
||||
}
|
||||
cmd.SetOutput(buf)
|
||||
assert.NilError(t, cmd.Execute())
|
||||
actual := buf.String()
|
||||
expected := golden.Get(t, []byte(actual), fmt.Sprintf("update-%s.golden", tc.name))
|
||||
assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
|
||||
}
|
||||
}
|
|
@ -61,7 +61,7 @@ func (t tasksBySlot) Less(i, j int) bool {
|
|||
// Print task information in a table format.
|
||||
// Besides this, command `docker node ps <node>`
|
||||
// and `docker stack ps` will call this, too.
|
||||
func Print(dockerCli *command.DockerCli, ctx context.Context, tasks []swarm.Task, resolver *idresolver.IDResolver, noTrunc bool) error {
|
||||
func Print(dockerCli command.Cli, ctx context.Context, tasks []swarm.Task, resolver *idresolver.IDResolver, noTrunc bool) error {
|
||||
sort.Stable(tasksBySlot(tasks))
|
||||
|
||||
writer := tabwriter.NewWriter(dockerCli.Out(), 0, 4, 2, ' ', 0)
|
||||
|
@ -74,7 +74,7 @@ func Print(dockerCli *command.DockerCli, ctx context.Context, tasks []swarm.Task
|
|||
}
|
||||
|
||||
// PrintQuiet shows task list in a quiet way.
|
||||
func PrintQuiet(dockerCli *command.DockerCli, tasks []swarm.Task) error {
|
||||
func PrintQuiet(dockerCli command.Cli, tasks []swarm.Task) error {
|
||||
sort.Stable(tasksBySlot(tasks))
|
||||
|
||||
out := dockerCli.Out()
|
||||
|
|
117
cli/internal/test/builders/node.go
Normal file
117
cli/internal/test/builders/node.go
Normal file
|
@ -0,0 +1,117 @@
|
|||
package builders
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
)
|
||||
|
||||
// Node creates a node with default values.
|
||||
// Any number of node function builder can be pass to augment it.
|
||||
func Node(builders ...func(*swarm.Node)) *swarm.Node {
|
||||
t1 := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
|
||||
node := &swarm.Node{
|
||||
ID: "nodeID",
|
||||
Meta: swarm.Meta{
|
||||
CreatedAt: t1,
|
||||
},
|
||||
Description: swarm.NodeDescription{
|
||||
Hostname: "defaultNodeHostname",
|
||||
Platform: swarm.Platform{
|
||||
Architecture: "x86_64",
|
||||
OS: "linux",
|
||||
},
|
||||
Resources: swarm.Resources{
|
||||
NanoCPUs: 4,
|
||||
MemoryBytes: 20 * 1024 * 1024,
|
||||
},
|
||||
Engine: swarm.EngineDescription{
|
||||
EngineVersion: "1.13.0",
|
||||
Labels: map[string]string{
|
||||
"engine": "label",
|
||||
},
|
||||
Plugins: []swarm.PluginDescription{
|
||||
{
|
||||
Type: "Volume",
|
||||
Name: "local",
|
||||
},
|
||||
{
|
||||
Type: "Network",
|
||||
Name: "bridge",
|
||||
},
|
||||
{
|
||||
Type: "Network",
|
||||
Name: "overlay",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: swarm.NodeStatus{
|
||||
State: swarm.NodeStateReady,
|
||||
Addr: "127.0.0.1",
|
||||
},
|
||||
Spec: swarm.NodeSpec{
|
||||
Annotations: swarm.Annotations{
|
||||
Name: "defaultNodeName",
|
||||
},
|
||||
Role: swarm.NodeRoleWorker,
|
||||
Availability: swarm.NodeAvailabilityActive,
|
||||
},
|
||||
}
|
||||
|
||||
for _, builder := range builders {
|
||||
builder(node)
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
// NodeID sets the node id
|
||||
func NodeID(id string) func(*swarm.Node) {
|
||||
return func(node *swarm.Node) {
|
||||
node.ID = id
|
||||
}
|
||||
}
|
||||
|
||||
// NodeLabels sets the node labels
|
||||
func NodeLabels(labels map[string]string) func(*swarm.Node) {
|
||||
return func(node *swarm.Node) {
|
||||
node.Spec.Labels = labels
|
||||
}
|
||||
}
|
||||
|
||||
// Hostname sets the node hostname
|
||||
func Hostname(hostname string) func(*swarm.Node) {
|
||||
return func(node *swarm.Node) {
|
||||
node.Description.Hostname = hostname
|
||||
}
|
||||
}
|
||||
|
||||
// Leader sets the current node as a leader
|
||||
func Leader() func(*swarm.ManagerStatus) {
|
||||
return func(managerStatus *swarm.ManagerStatus) {
|
||||
managerStatus.Leader = true
|
||||
}
|
||||
}
|
||||
|
||||
// Manager set the current node as a manager
|
||||
func Manager(managerStatusBuilders ...func(*swarm.ManagerStatus)) func(*swarm.Node) {
|
||||
return func(node *swarm.Node) {
|
||||
node.Spec.Role = swarm.NodeRoleManager
|
||||
node.ManagerStatus = ManagerStatus(managerStatusBuilders...)
|
||||
}
|
||||
}
|
||||
|
||||
// ManagerStatus create a ManageStatus with default values.
|
||||
func ManagerStatus(managerStatusBuilders ...func(*swarm.ManagerStatus)) *swarm.ManagerStatus {
|
||||
managerStatus := &swarm.ManagerStatus{
|
||||
Reachability: swarm.ReachabilityReachable,
|
||||
Addr: "127.0.0.1",
|
||||
}
|
||||
|
||||
for _, builder := range managerStatusBuilders {
|
||||
builder(managerStatus)
|
||||
}
|
||||
|
||||
return managerStatus
|
||||
}
|
39
cli/internal/test/builders/swarm.go
Normal file
39
cli/internal/test/builders/swarm.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package builders
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
)
|
||||
|
||||
// Swarm creates a swarm with default values.
|
||||
// Any number of swarm function builder can be pass to augment it.
|
||||
func Swarm(swarmBuilders ...func(*swarm.Swarm)) *swarm.Swarm {
|
||||
t1 := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
|
||||
swarm := &swarm.Swarm{
|
||||
ClusterInfo: swarm.ClusterInfo{
|
||||
ID: "swarm",
|
||||
Meta: swarm.Meta{
|
||||
CreatedAt: t1,
|
||||
},
|
||||
Spec: swarm.Spec{},
|
||||
},
|
||||
JoinTokens: swarm.JoinTokens{
|
||||
Worker: "worker-join-token",
|
||||
Manager: "manager-join-token",
|
||||
},
|
||||
}
|
||||
|
||||
for _, builder := range swarmBuilders {
|
||||
builder(swarm)
|
||||
}
|
||||
|
||||
return swarm
|
||||
}
|
||||
|
||||
// Autolock set the swarm into autolock mode
|
||||
func Autolock() func(*swarm.Swarm) {
|
||||
return func(swarm *swarm.Swarm) {
|
||||
swarm.Spec.EncryptionConfig.AutoLockManagers = true
|
||||
}
|
||||
}
|
111
cli/internal/test/builders/task.go
Normal file
111
cli/internal/test/builders/task.go
Normal file
|
@ -0,0 +1,111 @@
|
|||
package builders
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultTime = time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
|
||||
)
|
||||
|
||||
// Task creates a task with default values .
|
||||
// Any number of task function builder can be pass to augment it.
|
||||
func Task(taskBuilders ...func(*swarm.Task)) *swarm.Task {
|
||||
task := &swarm.Task{
|
||||
ID: "taskID",
|
||||
Meta: swarm.Meta{
|
||||
CreatedAt: defaultTime,
|
||||
},
|
||||
Annotations: swarm.Annotations{
|
||||
Name: "defaultTaskName",
|
||||
},
|
||||
Spec: *TaskSpec(),
|
||||
ServiceID: "rl02d5gwz6chzu7il5fhtb8be",
|
||||
Slot: 1,
|
||||
Status: *TaskStatus(),
|
||||
DesiredState: swarm.TaskStateReady,
|
||||
}
|
||||
|
||||
for _, builder := range taskBuilders {
|
||||
builder(task)
|
||||
}
|
||||
|
||||
return task
|
||||
}
|
||||
|
||||
// TaskID sets the task ID
|
||||
func TaskID(id string) func(*swarm.Task) {
|
||||
return func(task *swarm.Task) {
|
||||
task.ID = id
|
||||
}
|
||||
}
|
||||
|
||||
// ServiceID sets the task service's ID
|
||||
func ServiceID(id string) func(*swarm.Task) {
|
||||
return func(task *swarm.Task) {
|
||||
task.ServiceID = id
|
||||
}
|
||||
}
|
||||
|
||||
// WithStatus sets the task status
|
||||
func WithStatus(statusBuilders ...func(*swarm.TaskStatus)) func(*swarm.Task) {
|
||||
return func(task *swarm.Task) {
|
||||
task.Status = *TaskStatus(statusBuilders...)
|
||||
}
|
||||
}
|
||||
|
||||
// TaskStatus creates a task status with default values .
|
||||
// Any number of taskStatus function builder can be pass to augment it.
|
||||
func TaskStatus(statusBuilders ...func(*swarm.TaskStatus)) *swarm.TaskStatus {
|
||||
timestamp := defaultTime.Add(1 * time.Hour)
|
||||
taskStatus := &swarm.TaskStatus{
|
||||
State: swarm.TaskStateReady,
|
||||
Timestamp: timestamp,
|
||||
}
|
||||
|
||||
for _, builder := range statusBuilders {
|
||||
builder(taskStatus)
|
||||
}
|
||||
|
||||
return taskStatus
|
||||
}
|
||||
|
||||
// Timestamp sets the task status timestamp
|
||||
func Timestamp(t time.Time) func(*swarm.TaskStatus) {
|
||||
return func(taskStatus *swarm.TaskStatus) {
|
||||
taskStatus.Timestamp = t
|
||||
}
|
||||
}
|
||||
|
||||
// StatusErr sets the tasks status error
|
||||
func StatusErr(err string) func(*swarm.TaskStatus) {
|
||||
return func(taskStatus *swarm.TaskStatus) {
|
||||
taskStatus.Err = err
|
||||
}
|
||||
}
|
||||
|
||||
// PortStatus sets the tasks port config status
|
||||
// FIXME(vdemeester) should be a sub builder 👼
|
||||
func PortStatus(portConfigs []swarm.PortConfig) func(*swarm.TaskStatus) {
|
||||
return func(taskStatus *swarm.TaskStatus) {
|
||||
taskStatus.PortStatus.Ports = portConfigs
|
||||
}
|
||||
}
|
||||
|
||||
// TaskSpec creates a task spec with default values .
|
||||
// Any number of taskSpec function builder can be pass to augment it.
|
||||
func TaskSpec(specBuilders ...func(*swarm.TaskSpec)) *swarm.TaskSpec {
|
||||
taskSpec := &swarm.TaskSpec{
|
||||
ContainerSpec: swarm.ContainerSpec{
|
||||
Image: "myimage:mytag",
|
||||
},
|
||||
}
|
||||
|
||||
for _, builder := range specBuilders {
|
||||
builder(taskSpec)
|
||||
}
|
||||
|
||||
return taskSpec
|
||||
}
|
48
cli/internal/test/cli.go
Normal file
48
cli/internal/test/cli.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
// Package test is a test-only package that can be used by other cli package to write unit test
|
||||
package test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/docker/docker/cli/command"
|
||||
"github.com/docker/docker/client"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// FakeCli emulates the default DockerCli
|
||||
type FakeCli struct {
|
||||
command.DockerCli
|
||||
client client.APIClient
|
||||
out io.Writer
|
||||
in io.ReadCloser
|
||||
}
|
||||
|
||||
// NewFakeCli returns a Cli backed by the fakeCli
|
||||
func NewFakeCli(client client.APIClient, out io.Writer) *FakeCli {
|
||||
return &FakeCli{
|
||||
client: client,
|
||||
out: out,
|
||||
in: ioutil.NopCloser(strings.NewReader("")),
|
||||
}
|
||||
}
|
||||
|
||||
// SetIn sets the input of the cli to the specified ReadCloser
|
||||
func (c *FakeCli) SetIn(in io.ReadCloser) {
|
||||
c.in = in
|
||||
}
|
||||
|
||||
// Client returns a docker API client
|
||||
func (c *FakeCli) Client() client.APIClient {
|
||||
return c.client
|
||||
}
|
||||
|
||||
// Out returns the output stream the cli should write on
|
||||
func (c *FakeCli) Out() *command.OutStream {
|
||||
return command.NewOutStream(c.out)
|
||||
}
|
||||
|
||||
// In returns thi input stream the cli will use
|
||||
func (c *FakeCli) In() *command.InStream {
|
||||
return command.NewInStream(c.in)
|
||||
}
|
|
@ -119,16 +119,6 @@ func (s *DockerSwarmSuite) TestSwarmIncompatibleDaemon(c *check.C) {
|
|||
d.Start(c)
|
||||
}
|
||||
|
||||
// Test case for #24090
|
||||
func (s *DockerSwarmSuite) TestSwarmNodeListHostname(c *check.C) {
|
||||
d := s.AddDaemon(c, true, true)
|
||||
|
||||
// The first line should contain "HOSTNAME"
|
||||
out, err := d.Cmd("node", "ls")
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(strings.Split(out, "\n")[0], checker.Contains, "HOSTNAME")
|
||||
}
|
||||
|
||||
func (s *DockerSwarmSuite) TestSwarmServiceTemplatingHostname(c *check.C) {
|
||||
d := s.AddDaemon(c, true, true)
|
||||
|
||||
|
@ -235,51 +225,23 @@ func (s *DockerSwarmSuite) TestSwarmNodeTaskListFilter(c *check.C) {
|
|||
func (s *DockerSwarmSuite) TestSwarmPublishAdd(c *check.C) {
|
||||
d := s.AddDaemon(c, true, true)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
publishAdd []string
|
||||
ports string
|
||||
}{
|
||||
{
|
||||
name: "simple-syntax",
|
||||
publishAdd: []string{
|
||||
"80:80",
|
||||
"80:80",
|
||||
"80:80",
|
||||
"80:20",
|
||||
},
|
||||
ports: "[{ tcp 80 80 ingress}]",
|
||||
},
|
||||
{
|
||||
name: "complex-syntax",
|
||||
publishAdd: []string{
|
||||
"target=90,published=90,protocol=tcp,mode=ingress",
|
||||
"target=90,published=90,protocol=tcp,mode=ingress",
|
||||
"target=90,published=90,protocol=tcp,mode=ingress",
|
||||
"target=30,published=90,protocol=tcp,mode=ingress",
|
||||
},
|
||||
ports: "[{ tcp 90 90 ingress}]",
|
||||
},
|
||||
}
|
||||
name := "top"
|
||||
out, err := d.Cmd("service", "create", "--name", name, "--label", "x=y", "busybox", "top")
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
|
||||
|
||||
for _, tc := range testCases {
|
||||
out, err := d.Cmd("service", "create", "--name", tc.name, "--label", "x=y", "busybox", "top")
|
||||
c.Assert(err, checker.IsNil, check.Commentf(out))
|
||||
c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
|
||||
out, err = d.Cmd("service", "update", "--publish-add", "80:80", name)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
out, err = d.CmdRetryOutOfSequence("service", "update", "--publish-add", tc.publishAdd[0], tc.name)
|
||||
c.Assert(err, checker.IsNil, check.Commentf(out))
|
||||
out, err = d.CmdRetryOutOfSequence("service", "update", "--publish-add", "80:80", name)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
out, err = d.CmdRetryOutOfSequence("service", "update", "--publish-add", tc.publishAdd[1], tc.name)
|
||||
c.Assert(err, checker.IsNil, check.Commentf(out))
|
||||
out, err = d.CmdRetryOutOfSequence("service", "update", "--publish-add", "80:80", "--publish-add", "80:20", name)
|
||||
c.Assert(err, checker.NotNil)
|
||||
|
||||
out, err = d.CmdRetryOutOfSequence("service", "update", "--publish-add", tc.publishAdd[2], "--publish-add", tc.publishAdd[3], tc.name)
|
||||
c.Assert(err, checker.NotNil, check.Commentf(out))
|
||||
|
||||
out, err = d.Cmd("service", "inspect", "--format", "{{ .Spec.EndpointSpec.Ports }}", tc.name)
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(strings.TrimSpace(out), checker.Equals, tc.ports)
|
||||
}
|
||||
out, err = d.Cmd("service", "inspect", "--format", "{{ .Spec.EndpointSpec.Ports }}", name)
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(strings.TrimSpace(out), checker.Equals, "[{ tcp 80 80 ingress}]")
|
||||
}
|
||||
|
||||
func (s *DockerSwarmSuite) TestSwarmServiceWithGroup(c *check.C) {
|
||||
|
@ -1413,24 +1375,6 @@ func (s *DockerSwarmSuite) TestSwarmNetworkIPAMOptions(c *check.C) {
|
|||
c.Assert(strings.TrimSpace(out), checker.Equals, "map[foo:bar]")
|
||||
}
|
||||
|
||||
// TODO: migrate to a unit test
|
||||
// This test could be migrated to unit test and save costly integration test,
|
||||
// once PR #29143 is merged.
|
||||
func (s *DockerSwarmSuite) TestSwarmUpdateWithoutArgs(c *check.C) {
|
||||
d := s.AddDaemon(c, true, true)
|
||||
|
||||
expectedOutput := `
|
||||
Usage: docker swarm update [OPTIONS]
|
||||
|
||||
Update the swarm
|
||||
|
||||
Options:`
|
||||
|
||||
out, err := d.Cmd("swarm", "update")
|
||||
c.Assert(err, checker.IsNil, check.Commentf("out: %v", out))
|
||||
c.Assert(out, checker.Contains, expectedOutput, check.Commentf(out))
|
||||
}
|
||||
|
||||
func (s *DockerTrustedSwarmSuite) TestTrustedServiceCreate(c *check.C) {
|
||||
d := s.swarmSuite.AddDaemon(c, true, true)
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
@ -25,6 +26,25 @@ func Equal(t TestingT, actual, expected interface{}) {
|
|||
}
|
||||
}
|
||||
|
||||
// EqualNormalizedString compare the actual value to the expected value after applying the specified
|
||||
// transform function. It fails the test if these two transformed string are not equal.
|
||||
// For example `EqualNormalizedString(t, RemoveSpace, "foo\n", "foo")` wouldn't fail the test as
|
||||
// spaces (and thus '\n') are removed before comparing the string.
|
||||
func EqualNormalizedString(t TestingT, transformFun func(rune) rune, actual, expected string) {
|
||||
if strings.Map(transformFun, actual) != strings.Map(transformFun, expected) {
|
||||
fatal(t, "Expected '%v' got '%v'", expected, expected, actual, actual)
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveSpace returns -1 if the specified runes is considered as a space (unicode)
|
||||
// and the rune itself otherwise.
|
||||
func RemoveSpace(r rune) rune {
|
||||
if unicode.IsSpace(r) {
|
||||
return -1
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
//EqualStringSlice compares two slices and fails the test if they do not contain
|
||||
// the same items.
|
||||
func EqualStringSlice(t TestingT, actual, expected []string) {
|
||||
|
|
28
pkg/testutil/golden/golden.go
Normal file
28
pkg/testutil/golden/golden.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
// Package golden provides function and helpers to use golden file for
|
||||
// testing purpose.
|
||||
package golden
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var update = flag.Bool("test.update", false, "update golden file")
|
||||
|
||||
// Get returns the golden file content. If the `test.update` is specified, it updates the
|
||||
// file with the current output and returns it.
|
||||
func Get(t *testing.T, actual []byte, filename string) []byte {
|
||||
golden := filepath.Join("testdata", filename)
|
||||
if *update {
|
||||
if err := ioutil.WriteFile(golden, actual, 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
expected, err := ioutil.ReadFile(golden)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return expected
|
||||
}
|
Loading…
Reference in a new issue