Skip to content

Commit 12a00e6

Browse files
dnephintonistiigi
authored andcommitted
Add Swarm management CLI commands
As described in our ROADMAP.md, introduce new Swarm management commands to call to the corresponding API endpoints. This PR is fully backward compatible (joining a Swarm is an optional feature of the Engine, and existing commands are not impacted). Signed-off-by: Daniel Nephin <dnephin@docker.com> Signed-off-by: Victor Vieux <vieux@docker.com> Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
1 parent d4abe1d commit 12a00e6

36 files changed

Lines changed: 2612 additions & 12 deletions
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package idresolver
2+
3+
import (
4+
"fmt"
5+
6+
"golang.org/x/net/context"
7+
8+
"github.com/docker/engine-api/client"
9+
"github.com/docker/engine-api/types/swarm"
10+
)
11+
12+
// IDResolver provides ID to Name resolution.
13+
type IDResolver struct {
14+
client client.APIClient
15+
noResolve bool
16+
cache map[string]string
17+
}
18+
19+
// New creates a new IDResolver.
20+
func New(client client.APIClient, noResolve bool) *IDResolver {
21+
return &IDResolver{
22+
client: client,
23+
noResolve: noResolve,
24+
cache: make(map[string]string),
25+
}
26+
}
27+
28+
func (r *IDResolver) get(ctx context.Context, t interface{}, id string) (string, error) {
29+
switch t.(type) {
30+
case swarm.Node:
31+
node, err := r.client.NodeInspect(ctx, id)
32+
if err != nil {
33+
return id, nil
34+
}
35+
if node.Spec.Annotations.Name != "" {
36+
return node.Spec.Annotations.Name, nil
37+
}
38+
if node.Description.Hostname != "" {
39+
return node.Description.Hostname, nil
40+
}
41+
return id, nil
42+
case swarm.Service:
43+
service, err := r.client.ServiceInspect(ctx, id)
44+
if err != nil {
45+
return id, nil
46+
}
47+
return service.Spec.Annotations.Name, nil
48+
default:
49+
return "", fmt.Errorf("unsupported type")
50+
}
51+
52+
}
53+
54+
// Resolve will attempt to resolve an ID to a Name by querying the manager.
55+
// Results are stored into a cache.
56+
// If the `-n` flag is used in the command-line, resolution is disabled.
57+
func (r *IDResolver) Resolve(ctx context.Context, t interface{}, id string) (string, error) {
58+
if r.noResolve {
59+
return id, nil
60+
}
61+
if name, ok := r.cache[id]; ok {
62+
return name, nil
63+
}
64+
name, err := r.get(ctx, t, id)
65+
if err != nil {
66+
return "", err
67+
}
68+
r.cache[id] = name
69+
return name, nil
70+
}

api/client/info.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/docker/docker/pkg/ioutils"
1111
flag "github.com/docker/docker/pkg/mflag"
1212
"github.com/docker/docker/utils"
13+
"github.com/docker/engine-api/types/swarm"
1314
"github.com/docker/go-units"
1415
)
1516

@@ -68,6 +69,21 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
6869
fmt.Fprintf(cli.out, "\n")
6970
}
7071

72+
fmt.Fprintf(cli.out, "Swarm: %v\n", info.Swarm.LocalNodeState)
73+
if info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive {
74+
fmt.Fprintf(cli.out, " NodeID: %s\n", info.Swarm.NodeID)
75+
if info.Swarm.Error != "" {
76+
fmt.Fprintf(cli.out, " Error: %v\n", info.Swarm.Error)
77+
}
78+
if info.Swarm.ControlAvailable {
79+
fmt.Fprintf(cli.out, " IsManager: Yes\n")
80+
fmt.Fprintf(cli.out, " Managers: %d\n", info.Swarm.Managers)
81+
fmt.Fprintf(cli.out, " Nodes: %d\n", info.Swarm.Nodes)
82+
ioutils.FprintfIfNotEmpty(cli.out, " CACertHash: %s\n", info.Swarm.CACertHash)
83+
} else {
84+
fmt.Fprintf(cli.out, " IsManager: No\n")
85+
}
86+
}
7187
ioutils.FprintfIfNotEmpty(cli.out, "Kernel Version: %s\n", info.KernelVersion)
7288
ioutils.FprintfIfNotEmpty(cli.out, "Operating System: %s\n", info.OperatingSystem)
7389
ioutils.FprintfIfNotEmpty(cli.out, "OSType: %s\n", info.OSType)

api/client/inspect.go

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,19 @@ import (
1111
"github.com/docker/engine-api/client"
1212
)
1313

14-
// CmdInspect displays low-level information on one or more containers or images.
14+
// CmdInspect displays low-level information on one or more containers, images or tasks.
1515
//
16-
// Usage: docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...]
16+
// Usage: docker inspect [OPTIONS] CONTAINER|IMAGE|TASK [CONTAINER|IMAGE|TASK...]
1717
func (cli *DockerCli) CmdInspect(args ...string) error {
18-
cmd := Cli.Subcmd("inspect", []string{"CONTAINER|IMAGE [CONTAINER|IMAGE...]"}, Cli.DockerCommands["inspect"].Description, true)
18+
cmd := Cli.Subcmd("inspect", []string{"CONTAINER|IMAGE|TASK [CONTAINER|IMAGE|TASK...]"}, Cli.DockerCommands["inspect"].Description, true)
1919
tmplStr := cmd.String([]string{"f", "-format"}, "", "Format the output using the given go template")
20-
inspectType := cmd.String([]string{"-type"}, "", "Return JSON for specified type, (e.g image or container)")
20+
inspectType := cmd.String([]string{"-type"}, "", "Return JSON for specified type, (e.g image, container or task)")
2121
size := cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes if the type is container")
2222
cmd.Require(flag.Min, 1)
2323

2424
cmd.ParseFlags(args, true)
2525

26-
if *inspectType != "" && *inspectType != "container" && *inspectType != "image" {
26+
if *inspectType != "" && *inspectType != "container" && *inspectType != "image" && *inspectType != "task" {
2727
return fmt.Errorf("%q is not a valid value for --type", *inspectType)
2828
}
2929

@@ -35,6 +35,11 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
3535
elementSearcher = cli.inspectContainers(ctx, *size)
3636
case "image":
3737
elementSearcher = cli.inspectImages(ctx, *size)
38+
case "task":
39+
if *size {
40+
fmt.Fprintln(cli.err, "WARNING: --size ignored for tasks")
41+
}
42+
elementSearcher = cli.inspectTasks(ctx)
3843
default:
3944
elementSearcher = cli.inspectAll(ctx, *size)
4045
}
@@ -54,6 +59,12 @@ func (cli *DockerCli) inspectImages(ctx context.Context, getSize bool) inspect.G
5459
}
5560
}
5661

62+
func (cli *DockerCli) inspectTasks(ctx context.Context) inspect.GetRefFunc {
63+
return func(ref string) (interface{}, []byte, error) {
64+
return cli.client.TaskInspectWithRaw(ctx, ref)
65+
}
66+
}
67+
5768
func (cli *DockerCli) inspectAll(ctx context.Context, getSize bool) inspect.GetRefFunc {
5869
return func(ref string) (interface{}, []byte, error) {
5970
c, rawContainer, err := cli.client.ContainerInspectWithRaw(ctx, ref, getSize)
@@ -63,7 +74,15 @@ func (cli *DockerCli) inspectAll(ctx context.Context, getSize bool) inspect.GetR
6374
i, rawImage, err := cli.client.ImageInspectWithRaw(ctx, ref, getSize)
6475
if err != nil {
6576
if client.IsErrImageNotFound(err) {
66-
return nil, nil, fmt.Errorf("Error: No such image or container: %s", ref)
77+
// Search for task with that id if an image doesn't exists.
78+
t, rawTask, err := cli.client.TaskInspectWithRaw(ctx, ref)
79+
if err != nil {
80+
return nil, nil, fmt.Errorf("Error: No such image, container or task: %s", ref)
81+
}
82+
if getSize {
83+
fmt.Fprintln(cli.err, "WARNING: --size ignored for tasks")
84+
}
85+
return t, rawTask, nil
6786
}
6887
return nil, nil, err
6988
}

api/client/network/list.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,26 +71,28 @@ func runList(dockerCli *client.DockerCli, opts listOptions) error {
7171

7272
w := tabwriter.NewWriter(dockerCli.Out(), 20, 1, 3, ' ', 0)
7373
if !opts.quiet {
74-
fmt.Fprintf(w, "NETWORK ID\tNAME\tDRIVER")
74+
fmt.Fprintf(w, "NETWORK ID\tNAME\tDRIVER\tSCOPE")
7575
fmt.Fprintf(w, "\n")
7676
}
7777

7878
sort.Sort(byNetworkName(networkResources))
7979
for _, networkResource := range networkResources {
8080
ID := networkResource.ID
8181
netName := networkResource.Name
82+
driver := networkResource.Driver
83+
scope := networkResource.Scope
8284
if !opts.noTrunc {
8385
ID = stringid.TruncateID(ID)
8486
}
8587
if opts.quiet {
8688
fmt.Fprintln(w, ID)
8789
continue
8890
}
89-
driver := networkResource.Driver
90-
fmt.Fprintf(w, "%s\t%s\t%s\t",
91+
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t",
9192
ID,
9293
netName,
93-
driver)
94+
driver,
95+
scope)
9496
fmt.Fprint(w, "\n")
9597
}
9698
w.Flush()

api/client/node/accept.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package node
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/docker/docker/api/client"
7+
"github.com/docker/docker/cli"
8+
"github.com/docker/engine-api/types/swarm"
9+
"github.com/spf13/cobra"
10+
"github.com/spf13/pflag"
11+
)
12+
13+
func newAcceptCommand(dockerCli *client.DockerCli) *cobra.Command {
14+
var flags *pflag.FlagSet
15+
16+
cmd := &cobra.Command{
17+
Use: "accept NODE [NODE...]",
18+
Short: "Accept a node in the swarm",
19+
Args: cli.RequiresMinArgs(1),
20+
RunE: func(cmd *cobra.Command, args []string) error {
21+
return runAccept(dockerCli, flags, args)
22+
},
23+
}
24+
25+
flags = cmd.Flags()
26+
return cmd
27+
}
28+
29+
func runAccept(dockerCli *client.DockerCli, flags *pflag.FlagSet, args []string) error {
30+
for _, id := range args {
31+
if err := runUpdate(dockerCli, id, func(node *swarm.Node) {
32+
node.Spec.Membership = swarm.NodeMembershipAccepted
33+
}); err != nil {
34+
return err
35+
}
36+
fmt.Println(id, "attempting to accept a node in the swarm.")
37+
}
38+
39+
return nil
40+
}

api/client/node/cmd.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package node
2+
3+
import (
4+
"fmt"
5+
6+
"golang.org/x/net/context"
7+
8+
"github.com/spf13/cobra"
9+
10+
"github.com/docker/docker/api/client"
11+
"github.com/docker/docker/cli"
12+
apiclient "github.com/docker/engine-api/client"
13+
)
14+
15+
// NewNodeCommand returns a cobra command for `node` subcommands
16+
func NewNodeCommand(dockerCli *client.DockerCli) *cobra.Command {
17+
cmd := &cobra.Command{
18+
Use: "node",
19+
Short: "Manage docker swarm nodes",
20+
Args: cli.NoArgs,
21+
Run: func(cmd *cobra.Command, args []string) {
22+
fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
23+
},
24+
}
25+
cmd.AddCommand(
26+
newAcceptCommand(dockerCli),
27+
newDemoteCommand(dockerCli),
28+
newInspectCommand(dockerCli),
29+
newListCommand(dockerCli),
30+
newPromoteCommand(dockerCli),
31+
newRemoveCommand(dockerCli),
32+
newTasksCommand(dockerCli),
33+
newUpdateCommand(dockerCli),
34+
)
35+
return cmd
36+
}
37+
38+
func nodeReference(client apiclient.APIClient, ctx context.Context, ref string) (string, error) {
39+
// The special value "self" for a node reference is mapped to the current
40+
// node, hence the node ID is retrieved using the `/info` endpoint.
41+
if ref == "self" {
42+
info, err := client.Info(ctx)
43+
if err != nil {
44+
return "", err
45+
}
46+
return info.Swarm.NodeID, nil
47+
}
48+
return ref, nil
49+
}

api/client/node/demote.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package node
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/docker/docker/api/client"
7+
"github.com/docker/docker/cli"
8+
"github.com/docker/engine-api/types/swarm"
9+
"github.com/spf13/cobra"
10+
"github.com/spf13/pflag"
11+
)
12+
13+
func newDemoteCommand(dockerCli *client.DockerCli) *cobra.Command {
14+
var flags *pflag.FlagSet
15+
16+
cmd := &cobra.Command{
17+
Use: "demote NODE [NODE...]",
18+
Short: "Demote a node from manager in the swarm",
19+
Args: cli.RequiresMinArgs(1),
20+
RunE: func(cmd *cobra.Command, args []string) error {
21+
return runDemote(dockerCli, flags, args)
22+
},
23+
}
24+
25+
flags = cmd.Flags()
26+
return cmd
27+
}
28+
29+
func runDemote(dockerCli *client.DockerCli, flags *pflag.FlagSet, args []string) error {
30+
for _, id := range args {
31+
if err := runUpdate(dockerCli, id, func(node *swarm.Node) {
32+
node.Spec.Role = swarm.NodeRoleWorker
33+
}); err != nil {
34+
return err
35+
}
36+
fmt.Println(id, "attempting to demote a manager in the swarm.")
37+
}
38+
39+
return nil
40+
}

0 commit comments

Comments
 (0)