Skip to content

Commit

Permalink
Add Swarm management CLI commands
Browse files Browse the repository at this point in the history
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 <[email protected]>
Signed-off-by: Victor Vieux <[email protected]>
Signed-off-by: Tonis Tiigi <[email protected]>
  • Loading branch information
dnephin authored and tonistiigi committed Jun 14, 2016
1 parent d4abe1d commit 12a00e6
Show file tree
Hide file tree
Showing 36 changed files with 2,612 additions and 12 deletions.
70 changes: 70 additions & 0 deletions api/client/idresolver/idresolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package idresolver

import (
"fmt"

"golang.org/x/net/context"

"github.com/docker/engine-api/client"
"github.com/docker/engine-api/types/swarm"
)

// IDResolver provides ID to Name resolution.
type IDResolver struct {
client client.APIClient
noResolve bool
cache map[string]string
}

// New creates a new IDResolver.
func New(client client.APIClient, noResolve bool) *IDResolver {
return &IDResolver{
client: client,
noResolve: noResolve,
cache: make(map[string]string),
}
}

func (r *IDResolver) get(ctx context.Context, t interface{}, id string) (string, error) {
switch t.(type) {
case swarm.Node:
node, err := r.client.NodeInspect(ctx, id)
if err != nil {
return id, nil
}
if node.Spec.Annotations.Name != "" {
return node.Spec.Annotations.Name, nil
}
if node.Description.Hostname != "" {
return node.Description.Hostname, nil
}
return id, nil
case swarm.Service:
service, err := r.client.ServiceInspect(ctx, id)
if err != nil {
return id, nil
}
return service.Spec.Annotations.Name, nil
default:
return "", fmt.Errorf("unsupported type")
}

}

// Resolve will attempt to resolve an ID to a Name by querying the manager.
// Results are stored into a cache.
// If the `-n` flag is used in the command-line, resolution is disabled.
func (r *IDResolver) Resolve(ctx context.Context, t interface{}, id string) (string, error) {
if r.noResolve {
return id, nil
}
if name, ok := r.cache[id]; ok {
return name, nil
}
name, err := r.get(ctx, t, id)
if err != nil {
return "", err
}
r.cache[id] = name
return name, nil
}
16 changes: 16 additions & 0 deletions api/client/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/docker/docker/pkg/ioutils"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/utils"
"github.com/docker/engine-api/types/swarm"
"github.com/docker/go-units"
)

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

fmt.Fprintf(cli.out, "Swarm: %v\n", info.Swarm.LocalNodeState)
if info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive {
fmt.Fprintf(cli.out, " NodeID: %s\n", info.Swarm.NodeID)
if info.Swarm.Error != "" {
fmt.Fprintf(cli.out, " Error: %v\n", info.Swarm.Error)
}
if info.Swarm.ControlAvailable {
fmt.Fprintf(cli.out, " IsManager: Yes\n")
fmt.Fprintf(cli.out, " Managers: %d\n", info.Swarm.Managers)
fmt.Fprintf(cli.out, " Nodes: %d\n", info.Swarm.Nodes)
ioutils.FprintfIfNotEmpty(cli.out, " CACertHash: %s\n", info.Swarm.CACertHash)
} else {
fmt.Fprintf(cli.out, " IsManager: No\n")
}
}
ioutils.FprintfIfNotEmpty(cli.out, "Kernel Version: %s\n", info.KernelVersion)
ioutils.FprintfIfNotEmpty(cli.out, "Operating System: %s\n", info.OperatingSystem)
ioutils.FprintfIfNotEmpty(cli.out, "OSType: %s\n", info.OSType)
Expand Down
31 changes: 25 additions & 6 deletions api/client/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,19 @@ import (
"github.com/docker/engine-api/client"
)

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

cmd.ParseFlags(args, true)

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

Expand All @@ -35,6 +35,11 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
elementSearcher = cli.inspectContainers(ctx, *size)
case "image":
elementSearcher = cli.inspectImages(ctx, *size)
case "task":
if *size {
fmt.Fprintln(cli.err, "WARNING: --size ignored for tasks")
}
elementSearcher = cli.inspectTasks(ctx)
default:
elementSearcher = cli.inspectAll(ctx, *size)
}
Expand All @@ -54,6 +59,12 @@ func (cli *DockerCli) inspectImages(ctx context.Context, getSize bool) inspect.G
}
}

func (cli *DockerCli) inspectTasks(ctx context.Context) inspect.GetRefFunc {
return func(ref string) (interface{}, []byte, error) {
return cli.client.TaskInspectWithRaw(ctx, ref)
}
}

func (cli *DockerCli) inspectAll(ctx context.Context, getSize bool) inspect.GetRefFunc {
return func(ref string) (interface{}, []byte, error) {
c, rawContainer, err := cli.client.ContainerInspectWithRaw(ctx, ref, getSize)
Expand All @@ -63,7 +74,15 @@ func (cli *DockerCli) inspectAll(ctx context.Context, getSize bool) inspect.GetR
i, rawImage, err := cli.client.ImageInspectWithRaw(ctx, ref, getSize)
if err != nil {
if client.IsErrImageNotFound(err) {
return nil, nil, fmt.Errorf("Error: No such image or container: %s", ref)
// Search for task with that id if an image doesn't exists.
t, rawTask, err := cli.client.TaskInspectWithRaw(ctx, ref)
if err != nil {
return nil, nil, fmt.Errorf("Error: No such image, container or task: %s", ref)
}
if getSize {
fmt.Fprintln(cli.err, "WARNING: --size ignored for tasks")
}
return t, rawTask, nil
}
return nil, nil, err
}
Expand Down
10 changes: 6 additions & 4 deletions api/client/network/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,26 +71,28 @@ func runList(dockerCli *client.DockerCli, opts listOptions) error {

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

sort.Sort(byNetworkName(networkResources))
for _, networkResource := range networkResources {
ID := networkResource.ID
netName := networkResource.Name
driver := networkResource.Driver
scope := networkResource.Scope
if !opts.noTrunc {
ID = stringid.TruncateID(ID)
}
if opts.quiet {
fmt.Fprintln(w, ID)
continue
}
driver := networkResource.Driver
fmt.Fprintf(w, "%s\t%s\t%s\t",
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t",
ID,
netName,
driver)
driver,
scope)
fmt.Fprint(w, "\n")
}
w.Flush()
Expand Down
40 changes: 40 additions & 0 deletions api/client/node/accept.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package node

import (
"fmt"

"github.com/docker/docker/api/client"
"github.com/docker/docker/cli"
"github.com/docker/engine-api/types/swarm"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)

func newAcceptCommand(dockerCli *client.DockerCli) *cobra.Command {
var flags *pflag.FlagSet

cmd := &cobra.Command{
Use: "accept NODE [NODE...]",
Short: "Accept a node in the swarm",
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runAccept(dockerCli, flags, args)
},
}

flags = cmd.Flags()
return cmd
}

func runAccept(dockerCli *client.DockerCli, flags *pflag.FlagSet, args []string) error {
for _, id := range args {
if err := runUpdate(dockerCli, id, func(node *swarm.Node) {
node.Spec.Membership = swarm.NodeMembershipAccepted
}); err != nil {
return err
}
fmt.Println(id, "attempting to accept a node in the swarm.")
}

return nil
}
49 changes: 49 additions & 0 deletions api/client/node/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package node

import (
"fmt"

"golang.org/x/net/context"

"github.com/spf13/cobra"

"github.com/docker/docker/api/client"
"github.com/docker/docker/cli"
apiclient "github.com/docker/engine-api/client"
)

// NewNodeCommand returns a cobra command for `node` subcommands
func NewNodeCommand(dockerCli *client.DockerCli) *cobra.Command {
cmd := &cobra.Command{
Use: "node",
Short: "Manage docker swarm nodes",
Args: cli.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
},
}
cmd.AddCommand(
newAcceptCommand(dockerCli),
newDemoteCommand(dockerCli),
newInspectCommand(dockerCli),
newListCommand(dockerCli),
newPromoteCommand(dockerCli),
newRemoveCommand(dockerCli),
newTasksCommand(dockerCli),
newUpdateCommand(dockerCli),
)
return cmd
}

func nodeReference(client apiclient.APIClient, ctx context.Context, ref string) (string, error) {
// The special value "self" for a node reference is mapped to the current
// node, hence the node ID is retrieved using the `/info` endpoint.
if ref == "self" {
info, err := client.Info(ctx)
if err != nil {
return "", err
}
return info.Swarm.NodeID, nil
}
return ref, nil
}
40 changes: 40 additions & 0 deletions api/client/node/demote.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package node

import (
"fmt"

"github.com/docker/docker/api/client"
"github.com/docker/docker/cli"
"github.com/docker/engine-api/types/swarm"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)

func newDemoteCommand(dockerCli *client.DockerCli) *cobra.Command {
var flags *pflag.FlagSet

cmd := &cobra.Command{
Use: "demote NODE [NODE...]",
Short: "Demote a node from manager in the swarm",
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runDemote(dockerCli, flags, args)
},
}

flags = cmd.Flags()
return cmd
}

func runDemote(dockerCli *client.DockerCli, flags *pflag.FlagSet, args []string) error {
for _, id := range args {
if err := runUpdate(dockerCli, id, func(node *swarm.Node) {
node.Spec.Role = swarm.NodeRoleWorker
}); err != nil {
return err
}
fmt.Println(id, "attempting to demote a manager in the swarm.")
}

return nil
}
Loading

0 comments on commit 12a00e6

Please sign in to comment.