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