Merge pull request #29143 from vdemeester/node-cli-unit-tests

Add some unit tests to the node and swarm cli code
This commit is contained in:
Sebastiaan van Stijn 2017-01-09 20:52:23 +01:00 committed by GitHub
commit 38f766ae0e
60 changed files with 2512 additions and 147 deletions

View File

@ -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

View 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
}

View File

@ -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)

View 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())
}

View File

@ -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) {

View 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))
}
}

View File

@ -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()

View 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")
}

View File

@ -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
}

View File

@ -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)

View 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())
}

View File

@ -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
View 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))
}
}

View File

@ -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()

View 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())
}

View 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

View 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

View 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

View 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

View 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"

View File

@ -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()

View 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())
}
}

View 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
}

View File

@ -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
}

View 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))
}
}

View File

@ -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()

View 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)
}
}

View File

@ -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)

View 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))
}
}

View File

@ -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()

View 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.")
}

View File

@ -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")
}

View 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.

View 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.

View File

@ -0,0 +1 @@
manager-join-token

View 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

View 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

View File

@ -0,0 +1 @@
worker-join-token

View 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

View File

@ -0,0 +1 @@
unlock-key

View File

@ -0,0 +1 @@
unlock-key

View 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.

View 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.

View File

@ -0,0 +1 @@
Swarm updated.

View 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.

View 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)

View File

@ -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()

View File

@ -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)
}

View 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))
}
}

View 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())
}

View File

@ -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
}

View 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))
}
}

View File

@ -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()

View 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
}

View 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
}
}

View 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
View 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)
}

View File

@ -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)

View File

@ -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) {

View 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
}