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:
Vincent Demeester 2016-12-25 22:23:35 +01:00
parent d4791c7b64
commit f151c297eb
No known key found for this signature in database
GPG key ID: 083CC6FD6EB699A3
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
}