mirror of
				https://github.com/moby/moby.git
				synced 2022-11-09 12:21:53 -05:00 
			
		
		
		
	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>
This commit is contained in:
		
							parent
							
								
									d4abe1d84a
								
							
						
					
					
						commit
						12a00e6017
					
				
					 36 changed files with 2612 additions and 12 deletions
				
			
		
							
								
								
									
										70
									
								
								api/client/idresolver/idresolver.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								api/client/idresolver/idresolver.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
				}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -71,7 +71,7 @@ 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")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -79,6 +79,8 @@ func runList(dockerCli *client.DockerCli, opts listOptions) error {
 | 
			
		|||
	for _, networkResource := range networkResources {
 | 
			
		||||
		ID := networkResource.ID
 | 
			
		||||
		netName := networkResource.Name
 | 
			
		||||
		driver := networkResource.Driver
 | 
			
		||||
		scope := networkResource.Scope
 | 
			
		||||
		if !opts.noTrunc {
 | 
			
		||||
			ID = stringid.TruncateID(ID)
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -86,11 +88,11 @@ func runList(dockerCli *client.DockerCli, opts listOptions) error {
 | 
			
		|||
			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()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										40
									
								
								api/client/node/accept.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								api/client/node/accept.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
									
								
								api/client/node/cmd.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								api/client/node/cmd.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
									
								
								api/client/node/demote.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								api/client/node/demote.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										141
									
								
								api/client/node/inspect.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								api/client/node/inspect.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,141 @@
 | 
			
		|||
package node
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/api/client"
 | 
			
		||||
	"github.com/docker/docker/api/client/inspect"
 | 
			
		||||
	"github.com/docker/docker/cli"
 | 
			
		||||
	"github.com/docker/docker/pkg/ioutils"
 | 
			
		||||
	"github.com/docker/engine-api/types/swarm"
 | 
			
		||||
	"github.com/docker/go-units"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type inspectOptions struct {
 | 
			
		||||
	nodeIds []string
 | 
			
		||||
	format  string
 | 
			
		||||
	pretty  bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
 | 
			
		||||
	var opts inspectOptions
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "inspect [OPTIONS] self|NODE [NODE...]",
 | 
			
		||||
		Short: "Inspect a node in the swarm",
 | 
			
		||||
		Args:  cli.RequiresMinArgs(1),
 | 
			
		||||
		RunE: func(cmd *cobra.Command, args []string) error {
 | 
			
		||||
			opts.nodeIds = args
 | 
			
		||||
			return runInspect(dockerCli, opts)
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	flags := cmd.Flags()
 | 
			
		||||
	flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given go template")
 | 
			
		||||
	flags.BoolVarP(&opts.pretty, "pretty", "p", false, "Print the information in a human friendly format.")
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runInspect(dockerCli *client.DockerCli, opts inspectOptions) error {
 | 
			
		||||
	client := dockerCli.Client()
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	getRef := func(ref string) (interface{}, []byte, error) {
 | 
			
		||||
		nodeRef, err := nodeReference(client, ctx, ref)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, nil, err
 | 
			
		||||
		}
 | 
			
		||||
		node, err := client.NodeInspect(ctx, nodeRef)
 | 
			
		||||
		return node, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !opts.pretty {
 | 
			
		||||
		return inspect.Inspect(dockerCli.Out(), opts.nodeIds, opts.format, getRef)
 | 
			
		||||
	}
 | 
			
		||||
	return printHumanFriendly(dockerCli.Out(), opts.nodeIds, getRef)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func printHumanFriendly(out io.Writer, refs []string, getRef inspect.GetRefFunc) error {
 | 
			
		||||
	for idx, ref := range refs {
 | 
			
		||||
		obj, _, err := getRef(ref)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		printNode(out, obj.(swarm.Node))
 | 
			
		||||
 | 
			
		||||
		// TODO: better way to do this?
 | 
			
		||||
		// print extra space between objects, but not after the last one
 | 
			
		||||
		if idx+1 != len(refs) {
 | 
			
		||||
			fmt.Fprintf(out, "\n\n")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: use a template
 | 
			
		||||
func printNode(out io.Writer, node swarm.Node) {
 | 
			
		||||
	fmt.Fprintf(out, "ID:\t\t\t%s\n", node.ID)
 | 
			
		||||
	ioutils.FprintfIfNotEmpty(out, "Name:\t\t\t%s\n", node.Spec.Name)
 | 
			
		||||
	if node.Spec.Labels != nil {
 | 
			
		||||
		fmt.Fprintln(out, "Labels:")
 | 
			
		||||
		for k, v := range node.Spec.Labels {
 | 
			
		||||
			fmt.Fprintf(out, " - %s = %s\n", k, v)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ioutils.FprintfIfNotEmpty(out, "Hostname:\t\t%s\n", node.Description.Hostname)
 | 
			
		||||
	fmt.Fprintln(out, "Status:")
 | 
			
		||||
	fmt.Fprintf(out, " State:\t\t\t%s\n", client.PrettyPrint(node.Status.State))
 | 
			
		||||
	ioutils.FprintfIfNotEmpty(out, " Message:\t\t%s\n", client.PrettyPrint(node.Status.Message))
 | 
			
		||||
	fmt.Fprintf(out, " Availability:\t\t%s\n", client.PrettyPrint(node.Spec.Availability))
 | 
			
		||||
 | 
			
		||||
	if node.ManagerStatus != nil {
 | 
			
		||||
		fmt.Fprintln(out, "Manager Status:")
 | 
			
		||||
		fmt.Fprintf(out, " Address:\t\t%s\n", node.ManagerStatus.Addr)
 | 
			
		||||
		fmt.Fprintf(out, " Raft status:\t\t%s\n", client.PrettyPrint(node.ManagerStatus.Reachability))
 | 
			
		||||
		leader := "No"
 | 
			
		||||
		if node.ManagerStatus.Leader {
 | 
			
		||||
			leader = "Yes"
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Fprintf(out, " Leader:\t\t%s\n", leader)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Fprintln(out, "Platform:")
 | 
			
		||||
	fmt.Fprintf(out, " Operating System:\t%s\n", node.Description.Platform.OS)
 | 
			
		||||
	fmt.Fprintf(out, " Architecture:\t\t%s\n", node.Description.Platform.Architecture)
 | 
			
		||||
 | 
			
		||||
	fmt.Fprintln(out, "Resources:")
 | 
			
		||||
	fmt.Fprintf(out, " CPUs:\t\t\t%d\n", node.Description.Resources.NanoCPUs/1e9)
 | 
			
		||||
	fmt.Fprintf(out, " Memory:\t\t%s\n", units.BytesSize(float64(node.Description.Resources.MemoryBytes)))
 | 
			
		||||
 | 
			
		||||
	var pluginTypes []string
 | 
			
		||||
	pluginNamesByType := map[string][]string{}
 | 
			
		||||
	for _, p := range node.Description.Engine.Plugins {
 | 
			
		||||
		// append to pluginTypes only if not done previously
 | 
			
		||||
		if _, ok := pluginNamesByType[p.Type]; !ok {
 | 
			
		||||
			pluginTypes = append(pluginTypes, p.Type)
 | 
			
		||||
		}
 | 
			
		||||
		pluginNamesByType[p.Type] = append(pluginNamesByType[p.Type], p.Name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(pluginTypes) > 0 {
 | 
			
		||||
		fmt.Fprintln(out, "Plugins:")
 | 
			
		||||
		sort.Strings(pluginTypes) // ensure stable output
 | 
			
		||||
		for _, pluginType := range pluginTypes {
 | 
			
		||||
			fmt.Fprintf(out, "  %s:\t\t%s\n", pluginType, strings.Join(pluginNamesByType[pluginType], ", "))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Fprintf(out, "Engine Version:\t\t%s\n", node.Description.Engine.EngineVersion)
 | 
			
		||||
 | 
			
		||||
	if len(node.Description.Engine.Labels) != 0 {
 | 
			
		||||
		fmt.Fprintln(out, "Engine Labels:")
 | 
			
		||||
		for k, v := range node.Description.Engine.Labels {
 | 
			
		||||
			fmt.Fprintf(out, " - %s = %s", k, v)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										119
									
								
								api/client/node/list.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								api/client/node/list.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,119 @@
 | 
			
		|||
package node
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"text/tabwriter"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/api/client"
 | 
			
		||||
	"github.com/docker/docker/cli"
 | 
			
		||||
	"github.com/docker/docker/opts"
 | 
			
		||||
	"github.com/docker/engine-api/types"
 | 
			
		||||
	"github.com/docker/engine-api/types/swarm"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	listItemFmt = "%s\t%s\t%s\t%s\t%s\t%s\t%s\n"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type listOptions struct {
 | 
			
		||||
	quiet  bool
 | 
			
		||||
	filter opts.FilterOpt
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newListCommand(dockerCli *client.DockerCli) *cobra.Command {
 | 
			
		||||
	opts := listOptions{filter: opts.NewFilterOpt()}
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:     "ls",
 | 
			
		||||
		Aliases: []string{"list"},
 | 
			
		||||
		Short:   "List nodes in the swarm",
 | 
			
		||||
		Args:    cli.NoArgs,
 | 
			
		||||
		RunE: func(cmd *cobra.Command, args []string) error {
 | 
			
		||||
			return runList(dockerCli, opts)
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	flags := cmd.Flags()
 | 
			
		||||
	flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs")
 | 
			
		||||
	flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runList(dockerCli *client.DockerCli, opts listOptions) error {
 | 
			
		||||
	client := dockerCli.Client()
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
 | 
			
		||||
	nodes, err := client.NodeList(
 | 
			
		||||
		ctx,
 | 
			
		||||
		types.NodeListOptions{Filter: opts.filter.Value()})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	info, err := client.Info(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out := dockerCli.Out()
 | 
			
		||||
	if opts.quiet {
 | 
			
		||||
		printQuiet(out, nodes)
 | 
			
		||||
	} else {
 | 
			
		||||
		printTable(out, nodes, info)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func printTable(out io.Writer, nodes []swarm.Node, info types.Info) {
 | 
			
		||||
	writer := tabwriter.NewWriter(out, 0, 4, 2, ' ', 0)
 | 
			
		||||
 | 
			
		||||
	// Ignore flushing errors
 | 
			
		||||
	defer writer.Flush()
 | 
			
		||||
 | 
			
		||||
	fmt.Fprintf(writer, listItemFmt, "ID", "NAME", "MEMBERSHIP", "STATUS", "AVAILABILITY", "MANAGER STATUS", "LEADER")
 | 
			
		||||
	for _, node := range nodes {
 | 
			
		||||
		name := node.Spec.Name
 | 
			
		||||
		availability := string(node.Spec.Availability)
 | 
			
		||||
		membership := string(node.Spec.Membership)
 | 
			
		||||
 | 
			
		||||
		if name == "" {
 | 
			
		||||
			name = node.Description.Hostname
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		leader := ""
 | 
			
		||||
		if node.ManagerStatus != nil && node.ManagerStatus.Leader {
 | 
			
		||||
			leader = "Yes"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		reachability := ""
 | 
			
		||||
		if node.ManagerStatus != nil {
 | 
			
		||||
			reachability = string(node.ManagerStatus.Reachability)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ID := node.ID
 | 
			
		||||
		if node.ID == info.Swarm.NodeID {
 | 
			
		||||
			ID = ID + " *"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fmt.Fprintf(
 | 
			
		||||
			writer,
 | 
			
		||||
			listItemFmt,
 | 
			
		||||
			ID,
 | 
			
		||||
			name,
 | 
			
		||||
			client.PrettyPrint(membership),
 | 
			
		||||
			client.PrettyPrint(string(node.Status.State)),
 | 
			
		||||
			client.PrettyPrint(availability),
 | 
			
		||||
			client.PrettyPrint(reachability),
 | 
			
		||||
			leader)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func printQuiet(out io.Writer, nodes []swarm.Node) {
 | 
			
		||||
	for _, node := range nodes {
 | 
			
		||||
		fmt.Fprintln(out, node.ID)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										50
									
								
								api/client/node/opts.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								api/client/node/opts.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,50 @@
 | 
			
		|||
package node
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/engine-api/types/swarm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type nodeOptions struct {
 | 
			
		||||
	role         string
 | 
			
		||||
	membership   string
 | 
			
		||||
	availability string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (opts *nodeOptions) ToNodeSpec() (swarm.NodeSpec, error) {
 | 
			
		||||
	var spec swarm.NodeSpec
 | 
			
		||||
 | 
			
		||||
	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.NodeMembership(strings.ToLower(opts.membership)) {
 | 
			
		||||
	case swarm.NodeMembershipAccepted:
 | 
			
		||||
		spec.Membership = swarm.NodeMembershipAccepted
 | 
			
		||||
	case "":
 | 
			
		||||
	default:
 | 
			
		||||
		return swarm.NodeSpec{}, fmt.Errorf("invalid membership %q, only accepted is supported", opts.membership)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										40
									
								
								api/client/node/promote.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								api/client/node/promote.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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 newPromoteCommand(dockerCli *client.DockerCli) *cobra.Command {
 | 
			
		||||
	var flags *pflag.FlagSet
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "promote NODE [NODE...]",
 | 
			
		||||
		Short: "Promote a node to a manager in the swarm",
 | 
			
		||||
		Args:  cli.RequiresMinArgs(1),
 | 
			
		||||
		RunE: func(cmd *cobra.Command, args []string) error {
 | 
			
		||||
			return runPromote(dockerCli, flags, args)
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	flags = cmd.Flags()
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runPromote(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.NodeRoleManager
 | 
			
		||||
		}); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Println(id, "attempting to promote a node to a manager in the swarm.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										36
									
								
								api/client/node/remove.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								api/client/node/remove.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,36 @@
 | 
			
		|||
package node
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/api/client"
 | 
			
		||||
	"github.com/docker/docker/cli"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func newRemoveCommand(dockerCli *client.DockerCli) *cobra.Command {
 | 
			
		||||
	return &cobra.Command{
 | 
			
		||||
		Use:     "rm NODE [NODE...]",
 | 
			
		||||
		Aliases: []string{"remove"},
 | 
			
		||||
		Short:   "Remove a node from the swarm",
 | 
			
		||||
		Args:    cli.RequiresMinArgs(1),
 | 
			
		||||
		RunE: func(cmd *cobra.Command, args []string) error {
 | 
			
		||||
			return runRemove(dockerCli, args)
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runRemove(dockerCli *client.DockerCli, args []string) error {
 | 
			
		||||
	client := dockerCli.Client()
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	for _, nodeID := range args {
 | 
			
		||||
		err := client.NodeRemove(ctx, nodeID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Fprintf(dockerCli.Out(), "%s\n", nodeID)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										72
									
								
								api/client/node/tasks.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								api/client/node/tasks.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,72 @@
 | 
			
		|||
package node
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/api/client"
 | 
			
		||||
	"github.com/docker/docker/api/client/idresolver"
 | 
			
		||||
	"github.com/docker/docker/api/client/task"
 | 
			
		||||
	"github.com/docker/docker/cli"
 | 
			
		||||
	"github.com/docker/docker/opts"
 | 
			
		||||
	"github.com/docker/engine-api/types"
 | 
			
		||||
	"github.com/docker/engine-api/types/swarm"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type tasksOptions struct {
 | 
			
		||||
	nodeID    string
 | 
			
		||||
	all       bool
 | 
			
		||||
	noResolve bool
 | 
			
		||||
	filter    opts.FilterOpt
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newTasksCommand(dockerCli *client.DockerCli) *cobra.Command {
 | 
			
		||||
	opts := tasksOptions{filter: opts.NewFilterOpt()}
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "tasks [OPTIONS] self|NODE",
 | 
			
		||||
		Short: "List tasks running on a node",
 | 
			
		||||
		Args:  cli.ExactArgs(1),
 | 
			
		||||
		RunE: func(cmd *cobra.Command, args []string) error {
 | 
			
		||||
			opts.nodeID = args[0]
 | 
			
		||||
			return runTasks(dockerCli, opts)
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	flags := cmd.Flags()
 | 
			
		||||
	flags.BoolVarP(&opts.all, "all", "a", false, "Display all instances")
 | 
			
		||||
	flags.BoolVarP(&opts.noResolve, "no-resolve", "n", false, "Do not map IDs to Names")
 | 
			
		||||
	flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runTasks(dockerCli *client.DockerCli, opts tasksOptions) error {
 | 
			
		||||
	client := dockerCli.Client()
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
 | 
			
		||||
	nodeRef, err := nodeReference(client, ctx, opts.nodeID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	node, err := client.NodeInspect(ctx, nodeRef)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	filter := opts.filter.Value()
 | 
			
		||||
	filter.Add("node", node.ID)
 | 
			
		||||
	if !opts.all {
 | 
			
		||||
		filter.Add("desired_state", string(swarm.TaskStateRunning))
 | 
			
		||||
		filter.Add("desired_state", string(swarm.TaskStateAccepted))
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tasks, err := client.TaskList(
 | 
			
		||||
		ctx,
 | 
			
		||||
		types.TaskListOptions{Filter: filter})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return task.Print(dockerCli, ctx, tasks, idresolver.New(client, opts.noResolve))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										100
									
								
								api/client/node/update.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								api/client/node/update.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,100 @@
 | 
			
		|||
package node
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/api/client"
 | 
			
		||||
	"github.com/docker/docker/cli"
 | 
			
		||||
	runconfigopts "github.com/docker/docker/runconfig/opts"
 | 
			
		||||
	"github.com/docker/engine-api/types/swarm"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	"github.com/spf13/pflag"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
 | 
			
		||||
	var opts nodeOptions
 | 
			
		||||
	var flags *pflag.FlagSet
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "update [OPTIONS] NODE",
 | 
			
		||||
		Short: "Update a node",
 | 
			
		||||
		Args:  cli.ExactArgs(1),
 | 
			
		||||
		RunE: func(cmd *cobra.Command, args []string) error {
 | 
			
		||||
			return runUpdate(dockerCli, args[0], mergeNodeUpdate(flags))
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	flags = cmd.Flags()
 | 
			
		||||
	flags.StringVar(&opts.role, "role", "", "Role of the node (worker/manager)")
 | 
			
		||||
	flags.StringVar(&opts.membership, "membership", "", "Membership of the node (accepted/rejected)")
 | 
			
		||||
	flags.StringVar(&opts.availability, "availability", "", "Availability of the node (active/pause/drain)")
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runUpdate(dockerCli *client.DockerCli, nodeID string, mergeNode func(node *swarm.Node)) error {
 | 
			
		||||
	client := dockerCli.Client()
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
 | 
			
		||||
	node, err := client.NodeInspect(ctx, nodeID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mergeNode(&node)
 | 
			
		||||
	err = client.NodeUpdate(ctx, nodeID, node.Version, node.Spec)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Fprintf(dockerCli.Out(), "%s\n", nodeID)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func mergeNodeUpdate(flags *pflag.FlagSet) func(*swarm.Node) {
 | 
			
		||||
	return func(node *swarm.Node) {
 | 
			
		||||
		mergeString := func(flag string, field *string) {
 | 
			
		||||
			if flags.Changed(flag) {
 | 
			
		||||
				*field, _ = flags.GetString(flag)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		mergeRole := func(flag string, field *swarm.NodeRole) {
 | 
			
		||||
			if flags.Changed(flag) {
 | 
			
		||||
				str, _ := flags.GetString(flag)
 | 
			
		||||
				*field = swarm.NodeRole(str)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		mergeMembership := func(flag string, field *swarm.NodeMembership) {
 | 
			
		||||
			if flags.Changed(flag) {
 | 
			
		||||
				str, _ := flags.GetString(flag)
 | 
			
		||||
				*field = swarm.NodeMembership(str)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		mergeAvailability := func(flag string, field *swarm.NodeAvailability) {
 | 
			
		||||
			if flags.Changed(flag) {
 | 
			
		||||
				str, _ := flags.GetString(flag)
 | 
			
		||||
				*field = swarm.NodeAvailability(str)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		mergeLabels := func(flag string, field *map[string]string) {
 | 
			
		||||
			if flags.Changed(flag) {
 | 
			
		||||
				values, _ := flags.GetStringSlice(flag)
 | 
			
		||||
				for key, value := range runconfigopts.ConvertKVStringsToMap(values) {
 | 
			
		||||
					(*field)[key] = value
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		spec := &node.Spec
 | 
			
		||||
		mergeString("name", &spec.Name)
 | 
			
		||||
		// TODO: setting labels is not working
 | 
			
		||||
		mergeLabels("label", &spec.Labels)
 | 
			
		||||
		mergeRole("role", &spec.Role)
 | 
			
		||||
		mergeMembership("membership", &spec.Membership)
 | 
			
		||||
		mergeAvailability("availability", &spec.Availability)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										32
									
								
								api/client/service/cmd.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								api/client/service/cmd.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,32 @@
 | 
			
		|||
package service
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/api/client"
 | 
			
		||||
	"github.com/docker/docker/cli"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// NewServiceCommand returns a cobra command for `service` subcommands
 | 
			
		||||
func NewServiceCommand(dockerCli *client.DockerCli) *cobra.Command {
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "service",
 | 
			
		||||
		Short: "Manage docker services",
 | 
			
		||||
		Args:  cli.NoArgs,
 | 
			
		||||
		Run: func(cmd *cobra.Command, args []string) {
 | 
			
		||||
			fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	cmd.AddCommand(
 | 
			
		||||
		newCreateCommand(dockerCli),
 | 
			
		||||
		newInspectCommand(dockerCli),
 | 
			
		||||
		newTasksCommand(dockerCli),
 | 
			
		||||
		newListCommand(dockerCli),
 | 
			
		||||
		newRemoveCommand(dockerCli),
 | 
			
		||||
		newScaleCommand(dockerCli),
 | 
			
		||||
		newUpdateCommand(dockerCli),
 | 
			
		||||
	)
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										47
									
								
								api/client/service/create.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								api/client/service/create.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,47 @@
 | 
			
		|||
package service
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/api/client"
 | 
			
		||||
	"github.com/docker/docker/cli"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func newCreateCommand(dockerCli *client.DockerCli) *cobra.Command {
 | 
			
		||||
	opts := newServiceOptions()
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "create [OPTIONS] IMAGE [COMMAND] [ARG...]",
 | 
			
		||||
		Short: "Create a new service",
 | 
			
		||||
		Args:  cli.RequiresMinArgs(1),
 | 
			
		||||
		RunE: func(cmd *cobra.Command, args []string) error {
 | 
			
		||||
			opts.image = args[0]
 | 
			
		||||
			if len(args) > 1 {
 | 
			
		||||
				opts.args = args[1:]
 | 
			
		||||
			}
 | 
			
		||||
			return runCreate(dockerCli, opts)
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	addServiceFlags(cmd, opts)
 | 
			
		||||
	cmd.Flags().SetInterspersed(false)
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runCreate(dockerCli *client.DockerCli, opts *serviceOptions) error {
 | 
			
		||||
	client := dockerCli.Client()
 | 
			
		||||
 | 
			
		||||
	service, err := opts.ToService()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	response, err := client.ServiceCreate(context.Background(), service)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Fprintf(dockerCli.Out(), "%s\n", response.ID)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										127
									
								
								api/client/service/inspect.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								api/client/service/inspect.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,127 @@
 | 
			
		|||
package service
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/api/client"
 | 
			
		||||
	"github.com/docker/docker/api/client/inspect"
 | 
			
		||||
	"github.com/docker/docker/cli"
 | 
			
		||||
	"github.com/docker/docker/pkg/ioutils"
 | 
			
		||||
	apiclient "github.com/docker/engine-api/client"
 | 
			
		||||
	"github.com/docker/engine-api/types/swarm"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type inspectOptions struct {
 | 
			
		||||
	refs   []string
 | 
			
		||||
	format string
 | 
			
		||||
	pretty bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
 | 
			
		||||
	var opts inspectOptions
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "inspect [OPTIONS] SERVICE [SERVICE...]",
 | 
			
		||||
		Short: "Inspect a service",
 | 
			
		||||
		Args:  cli.RequiresMinArgs(1),
 | 
			
		||||
		RunE: func(cmd *cobra.Command, args []string) error {
 | 
			
		||||
			opts.refs = args
 | 
			
		||||
 | 
			
		||||
			if opts.pretty && len(opts.format) > 0 {
 | 
			
		||||
				return fmt.Errorf("--format is incompatible with human friendly format")
 | 
			
		||||
			}
 | 
			
		||||
			return runInspect(dockerCli, opts)
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	flags := cmd.Flags()
 | 
			
		||||
	flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given go template")
 | 
			
		||||
	flags.BoolVarP(&opts.pretty, "pretty", "p", false, "Print the information in a human friendly format.")
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runInspect(dockerCli *client.DockerCli, opts inspectOptions) error {
 | 
			
		||||
	client := dockerCli.Client()
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
 | 
			
		||||
	getRef := func(ref string) (interface{}, []byte, error) {
 | 
			
		||||
		service, err := client.ServiceInspect(ctx, ref)
 | 
			
		||||
		if err == nil || !apiclient.IsErrServiceNotFound(err) {
 | 
			
		||||
			return service, nil, err
 | 
			
		||||
		}
 | 
			
		||||
		return nil, nil, fmt.Errorf("Error: no such service: %s", ref)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !opts.pretty {
 | 
			
		||||
		return inspect.Inspect(dockerCli.Out(), opts.refs, opts.format, getRef)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return printHumanFriendly(dockerCli.Out(), opts.refs, getRef)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func printHumanFriendly(out io.Writer, refs []string, getRef inspect.GetRefFunc) error {
 | 
			
		||||
	for idx, ref := range refs {
 | 
			
		||||
		obj, _, err := getRef(ref)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		printService(out, obj.(swarm.Service))
 | 
			
		||||
 | 
			
		||||
		// TODO: better way to do this?
 | 
			
		||||
		// print extra space between objects, but not after the last one
 | 
			
		||||
		if idx+1 != len(refs) {
 | 
			
		||||
			fmt.Fprintf(out, "\n\n")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: use a template
 | 
			
		||||
func printService(out io.Writer, service swarm.Service) {
 | 
			
		||||
	fmt.Fprintf(out, "ID:\t\t%s\n", service.ID)
 | 
			
		||||
	fmt.Fprintf(out, "Name:\t\t%s\n", service.Spec.Name)
 | 
			
		||||
	if service.Spec.Labels != nil {
 | 
			
		||||
		fmt.Fprintln(out, "Labels:")
 | 
			
		||||
		for k, v := range service.Spec.Labels {
 | 
			
		||||
			fmt.Fprintf(out, " - %s=%s\n", k, v)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if service.Spec.Mode.Global != nil {
 | 
			
		||||
		fmt.Fprintln(out, "Mode:\t\tGLOBAL")
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Fprintln(out, "Mode:\t\tREPLICATED")
 | 
			
		||||
		if service.Spec.Mode.Replicated.Replicas != nil {
 | 
			
		||||
			fmt.Fprintf(out, " Replicas:\t\t%d\n", *service.Spec.Mode.Replicated.Replicas)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Fprintln(out, "Placement:")
 | 
			
		||||
	fmt.Fprintln(out, " Strategy:\tSPREAD")
 | 
			
		||||
	fmt.Fprintf(out, "UpateConfig:\n")
 | 
			
		||||
	fmt.Fprintf(out, " Parallelism:\t%d\n", service.Spec.UpdateConfig.Parallelism)
 | 
			
		||||
	if service.Spec.UpdateConfig.Delay.Nanoseconds() > 0 {
 | 
			
		||||
		fmt.Fprintf(out, " Delay:\t\t%s\n", service.Spec.UpdateConfig.Delay)
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Fprintf(out, "ContainerSpec:\n")
 | 
			
		||||
	printContainerSpec(out, service.Spec.TaskTemplate.ContainerSpec)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func printContainerSpec(out io.Writer, containerSpec swarm.ContainerSpec) {
 | 
			
		||||
	fmt.Fprintf(out, " Image:\t\t%s\n", containerSpec.Image)
 | 
			
		||||
	if len(containerSpec.Command) > 0 {
 | 
			
		||||
		fmt.Fprintf(out, " Command:\t%s\n", strings.Join(containerSpec.Command, " "))
 | 
			
		||||
	}
 | 
			
		||||
	if len(containerSpec.Args) > 0 {
 | 
			
		||||
		fmt.Fprintf(out, " Args:\t%s\n", strings.Join(containerSpec.Args, " "))
 | 
			
		||||
	}
 | 
			
		||||
	if len(containerSpec.Env) > 0 {
 | 
			
		||||
		fmt.Fprintf(out, " Env:\t\t%s\n", strings.Join(containerSpec.Env, " "))
 | 
			
		||||
	}
 | 
			
		||||
	ioutils.FprintfIfNotEmpty(out, " Dir\t\t%s\n", containerSpec.Dir)
 | 
			
		||||
	ioutils.FprintfIfNotEmpty(out, " User\t\t%s\n", containerSpec.User)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										97
									
								
								api/client/service/list.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								api/client/service/list.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,97 @@
 | 
			
		|||
package service
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"text/tabwriter"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/api/client"
 | 
			
		||||
	"github.com/docker/docker/cli"
 | 
			
		||||
	"github.com/docker/docker/opts"
 | 
			
		||||
	"github.com/docker/docker/pkg/stringid"
 | 
			
		||||
	"github.com/docker/engine-api/types"
 | 
			
		||||
	"github.com/docker/engine-api/types/swarm"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	listItemFmt = "%s\t%s\t%s\t%s\t%s\n"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type listOptions struct {
 | 
			
		||||
	quiet  bool
 | 
			
		||||
	filter opts.FilterOpt
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newListCommand(dockerCli *client.DockerCli) *cobra.Command {
 | 
			
		||||
	opts := listOptions{filter: opts.NewFilterOpt()}
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:     "ls",
 | 
			
		||||
		Aliases: []string{"list"},
 | 
			
		||||
		Short:   "List services",
 | 
			
		||||
		Args:    cli.NoArgs,
 | 
			
		||||
		RunE: func(cmd *cobra.Command, args []string) error {
 | 
			
		||||
			return runList(dockerCli, opts)
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	flags := cmd.Flags()
 | 
			
		||||
	flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs")
 | 
			
		||||
	flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runList(dockerCli *client.DockerCli, opts listOptions) error {
 | 
			
		||||
	client := dockerCli.Client()
 | 
			
		||||
 | 
			
		||||
	services, err := client.ServiceList(
 | 
			
		||||
		context.Background(),
 | 
			
		||||
		types.ServiceListOptions{Filter: opts.filter.Value()})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out := dockerCli.Out()
 | 
			
		||||
	if opts.quiet {
 | 
			
		||||
		printQuiet(out, services)
 | 
			
		||||
	} else {
 | 
			
		||||
		printTable(out, services)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func printTable(out io.Writer, services []swarm.Service) {
 | 
			
		||||
	writer := tabwriter.NewWriter(out, 0, 4, 2, ' ', 0)
 | 
			
		||||
 | 
			
		||||
	// Ignore flushing errors
 | 
			
		||||
	defer writer.Flush()
 | 
			
		||||
 | 
			
		||||
	fmt.Fprintf(writer, listItemFmt, "ID", "NAME", "SCALE", "IMAGE", "COMMAND")
 | 
			
		||||
	for _, service := range services {
 | 
			
		||||
		scale := ""
 | 
			
		||||
		if service.Spec.Mode.Replicated != nil && service.Spec.Mode.Replicated.Replicas != nil {
 | 
			
		||||
			scale = fmt.Sprintf("%d", *service.Spec.Mode.Replicated.Replicas)
 | 
			
		||||
		} else if service.Spec.Mode.Global != nil {
 | 
			
		||||
			scale = "global"
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Fprintf(
 | 
			
		||||
			writer,
 | 
			
		||||
			listItemFmt,
 | 
			
		||||
			stringid.TruncateID(service.ID),
 | 
			
		||||
			service.Spec.Name,
 | 
			
		||||
			scale,
 | 
			
		||||
			service.Spec.TaskTemplate.ContainerSpec.Image,
 | 
			
		||||
			strings.Join(service.Spec.TaskTemplate.ContainerSpec.Args, " "))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func printQuiet(out io.Writer, services []swarm.Service) {
 | 
			
		||||
	for _, service := range services {
 | 
			
		||||
		fmt.Fprintln(out, service.ID)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										462
									
								
								api/client/service/opts.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										462
									
								
								api/client/service/opts.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,462 @@
 | 
			
		|||
package service
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/csv"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"math/big"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/opts"
 | 
			
		||||
	runconfigopts "github.com/docker/docker/runconfig/opts"
 | 
			
		||||
	"github.com/docker/engine-api/types/swarm"
 | 
			
		||||
	"github.com/docker/go-connections/nat"
 | 
			
		||||
	units "github.com/docker/go-units"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	// DefaultReplicas is the default replicas to use for a replicated service
 | 
			
		||||
	DefaultReplicas uint64 = 1
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type int64Value interface {
 | 
			
		||||
	Value() int64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type memBytes int64
 | 
			
		||||
 | 
			
		||||
func (m *memBytes) String() string {
 | 
			
		||||
	return strconv.FormatInt(m.Value(), 10)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *memBytes) Set(value string) error {
 | 
			
		||||
	val, err := units.RAMInBytes(value)
 | 
			
		||||
	*m = memBytes(val)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *memBytes) Type() string {
 | 
			
		||||
	return "MemoryBytes"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *memBytes) Value() int64 {
 | 
			
		||||
	return int64(*m)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type nanoCPUs int64
 | 
			
		||||
 | 
			
		||||
func (c *nanoCPUs) String() string {
 | 
			
		||||
	return strconv.FormatInt(c.Value(), 10)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *nanoCPUs) Set(value string) error {
 | 
			
		||||
	cpu, ok := new(big.Rat).SetString(value)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return fmt.Errorf("Failed to parse %v as a rational number", value)
 | 
			
		||||
	}
 | 
			
		||||
	nano := cpu.Mul(cpu, big.NewRat(1e9, 1))
 | 
			
		||||
	if !nano.IsInt() {
 | 
			
		||||
		return fmt.Errorf("value is too precise")
 | 
			
		||||
	}
 | 
			
		||||
	*c = nanoCPUs(nano.Num().Int64())
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *nanoCPUs) Type() string {
 | 
			
		||||
	return "NanoCPUs"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *nanoCPUs) Value() int64 {
 | 
			
		||||
	return int64(*c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DurationOpt is an option type for time.Duration that uses a pointer. This
 | 
			
		||||
// allows us to get nil values outside, instead of defaulting to 0
 | 
			
		||||
type DurationOpt struct {
 | 
			
		||||
	value *time.Duration
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Set a new value on the option
 | 
			
		||||
func (d *DurationOpt) Set(s string) error {
 | 
			
		||||
	v, err := time.ParseDuration(s)
 | 
			
		||||
	d.value = &v
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Type returns the type of this option
 | 
			
		||||
func (d *DurationOpt) Type() string {
 | 
			
		||||
	return "duration-ptr"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// String returns a string repr of this option
 | 
			
		||||
func (d *DurationOpt) String() string {
 | 
			
		||||
	if d.value != nil {
 | 
			
		||||
		return d.value.String()
 | 
			
		||||
	}
 | 
			
		||||
	return "none"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Value returns the time.Duration
 | 
			
		||||
func (d *DurationOpt) Value() *time.Duration {
 | 
			
		||||
	return d.value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Uint64Opt represents a uint64.
 | 
			
		||||
type Uint64Opt struct {
 | 
			
		||||
	value *uint64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Set a new value on the option
 | 
			
		||||
func (i *Uint64Opt) Set(s string) error {
 | 
			
		||||
	v, err := strconv.ParseUint(s, 0, 64)
 | 
			
		||||
	i.value = &v
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Type returns the type of this option
 | 
			
		||||
func (i *Uint64Opt) Type() string {
 | 
			
		||||
	return "uint64-ptr"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// String returns a string repr of this option
 | 
			
		||||
func (i *Uint64Opt) String() string {
 | 
			
		||||
	if i.value != nil {
 | 
			
		||||
		return fmt.Sprintf("%v", *i.value)
 | 
			
		||||
	}
 | 
			
		||||
	return "none"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Value returns the uint64
 | 
			
		||||
func (i *Uint64Opt) Value() *uint64 {
 | 
			
		||||
	return i.value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MountOpt is a Value type for parsing mounts
 | 
			
		||||
type MountOpt struct {
 | 
			
		||||
	values []swarm.Mount
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Set a new mount value
 | 
			
		||||
func (m *MountOpt) Set(value string) error {
 | 
			
		||||
	csvReader := csv.NewReader(strings.NewReader(value))
 | 
			
		||||
	fields, err := csvReader.Read()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mount := swarm.Mount{}
 | 
			
		||||
 | 
			
		||||
	volumeOptions := func() *swarm.VolumeOptions {
 | 
			
		||||
		if mount.VolumeOptions == nil {
 | 
			
		||||
			mount.VolumeOptions = &swarm.VolumeOptions{
 | 
			
		||||
				Labels: make(map[string]string),
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return mount.VolumeOptions
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	setValueOnMap := func(target map[string]string, value string) {
 | 
			
		||||
		parts := strings.SplitN(value, "=", 2)
 | 
			
		||||
		if len(parts) == 1 {
 | 
			
		||||
			target[value] = ""
 | 
			
		||||
		} else {
 | 
			
		||||
			target[parts[0]] = parts[1]
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, field := range fields {
 | 
			
		||||
		parts := strings.SplitN(field, "=", 2)
 | 
			
		||||
		if len(parts) == 1 && strings.ToLower(parts[0]) == "writable" {
 | 
			
		||||
			mount.Writable = true
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(parts) != 2 {
 | 
			
		||||
			return fmt.Errorf("invald field '%s' must be a key=value pair", field)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		key, value := parts[0], parts[1]
 | 
			
		||||
		switch strings.ToLower(key) {
 | 
			
		||||
		case "type":
 | 
			
		||||
			mount.Type = swarm.MountType(strings.ToUpper(value))
 | 
			
		||||
		case "source":
 | 
			
		||||
			mount.Source = value
 | 
			
		||||
		case "target":
 | 
			
		||||
			mount.Target = value
 | 
			
		||||
		case "writable":
 | 
			
		||||
			mount.Writable, err = strconv.ParseBool(value)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return fmt.Errorf("invald value for writable: %s", err.Error())
 | 
			
		||||
			}
 | 
			
		||||
		case "bind-propagation":
 | 
			
		||||
			mount.BindOptions.Propagation = swarm.MountPropagation(strings.ToUpper(value))
 | 
			
		||||
		case "volume-populate":
 | 
			
		||||
			volumeOptions().Populate, err = strconv.ParseBool(value)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return fmt.Errorf("invald value for populate: %s", err.Error())
 | 
			
		||||
			}
 | 
			
		||||
		case "volume-label":
 | 
			
		||||
			setValueOnMap(volumeOptions().Labels, value)
 | 
			
		||||
		case "volume-driver":
 | 
			
		||||
			volumeOptions().DriverConfig.Name = value
 | 
			
		||||
		case "volume-driver-opt":
 | 
			
		||||
			if volumeOptions().DriverConfig.Options == nil {
 | 
			
		||||
				volumeOptions().DriverConfig.Options = make(map[string]string)
 | 
			
		||||
			}
 | 
			
		||||
			setValueOnMap(volumeOptions().DriverConfig.Options, value)
 | 
			
		||||
		default:
 | 
			
		||||
			return fmt.Errorf("unexpected key '%s' in '%s'", key, value)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if mount.Type == "" {
 | 
			
		||||
		return fmt.Errorf("type is required")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if mount.Target == "" {
 | 
			
		||||
		return fmt.Errorf("target is required")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m.values = append(m.values, mount)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Type returns the type of this option
 | 
			
		||||
func (m *MountOpt) Type() string {
 | 
			
		||||
	return "mount"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// String returns a string repr of this option
 | 
			
		||||
func (m *MountOpt) String() string {
 | 
			
		||||
	mounts := []string{}
 | 
			
		||||
	for _, mount := range m.values {
 | 
			
		||||
		mounts = append(mounts, fmt.Sprintf("%v", mount))
 | 
			
		||||
	}
 | 
			
		||||
	return strings.Join(mounts, ", ")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Value returns the mounts
 | 
			
		||||
func (m *MountOpt) Value() []swarm.Mount {
 | 
			
		||||
	return m.values
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type updateOptions struct {
 | 
			
		||||
	parallelism uint64
 | 
			
		||||
	delay       time.Duration
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type resourceOptions struct {
 | 
			
		||||
	limitCPU      nanoCPUs
 | 
			
		||||
	limitMemBytes memBytes
 | 
			
		||||
	resCPU        nanoCPUs
 | 
			
		||||
	resMemBytes   memBytes
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *resourceOptions) ToResourceRequirements() *swarm.ResourceRequirements {
 | 
			
		||||
	return &swarm.ResourceRequirements{
 | 
			
		||||
		Limits: &swarm.Resources{
 | 
			
		||||
			NanoCPUs:    r.limitCPU.Value(),
 | 
			
		||||
			MemoryBytes: r.limitMemBytes.Value(),
 | 
			
		||||
		},
 | 
			
		||||
		Reservations: &swarm.Resources{
 | 
			
		||||
			NanoCPUs:    r.resCPU.Value(),
 | 
			
		||||
			MemoryBytes: r.resMemBytes.Value(),
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type restartPolicyOptions struct {
 | 
			
		||||
	condition   string
 | 
			
		||||
	delay       DurationOpt
 | 
			
		||||
	maxAttempts Uint64Opt
 | 
			
		||||
	window      DurationOpt
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *restartPolicyOptions) ToRestartPolicy() *swarm.RestartPolicy {
 | 
			
		||||
	return &swarm.RestartPolicy{
 | 
			
		||||
		Condition:   swarm.RestartPolicyCondition(r.condition),
 | 
			
		||||
		Delay:       r.delay.Value(),
 | 
			
		||||
		MaxAttempts: r.maxAttempts.Value(),
 | 
			
		||||
		Window:      r.window.Value(),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func convertNetworks(networks []string) []swarm.NetworkAttachmentConfig {
 | 
			
		||||
	nets := []swarm.NetworkAttachmentConfig{}
 | 
			
		||||
	for _, network := range networks {
 | 
			
		||||
		nets = append(nets, swarm.NetworkAttachmentConfig{Target: network})
 | 
			
		||||
	}
 | 
			
		||||
	return nets
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type endpointOptions struct {
 | 
			
		||||
	mode  string
 | 
			
		||||
	ports opts.ListOpts
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *endpointOptions) ToEndpointSpec() *swarm.EndpointSpec {
 | 
			
		||||
	portConfigs := []swarm.PortConfig{}
 | 
			
		||||
	// We can ignore errors because the format was already validated by ValidatePort
 | 
			
		||||
	ports, portBindings, _ := nat.ParsePortSpecs(e.ports.GetAll())
 | 
			
		||||
 | 
			
		||||
	for port := range ports {
 | 
			
		||||
		portConfigs = append(portConfigs, convertPortToPortConfig(port, portBindings)...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &swarm.EndpointSpec{
 | 
			
		||||
		Mode:  swarm.ResolutionMode(e.mode),
 | 
			
		||||
		Ports: portConfigs,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func convertPortToPortConfig(
 | 
			
		||||
	port nat.Port,
 | 
			
		||||
	portBindings map[nat.Port][]nat.PortBinding,
 | 
			
		||||
) []swarm.PortConfig {
 | 
			
		||||
	ports := []swarm.PortConfig{}
 | 
			
		||||
 | 
			
		||||
	for _, binding := range portBindings[port] {
 | 
			
		||||
		hostPort, _ := strconv.ParseUint(binding.HostPort, 10, 16)
 | 
			
		||||
		ports = append(ports, swarm.PortConfig{
 | 
			
		||||
			//TODO Name: ?
 | 
			
		||||
			Protocol:      swarm.PortConfigProtocol(strings.ToLower(port.Proto())),
 | 
			
		||||
			TargetPort:    uint32(port.Int()),
 | 
			
		||||
			PublishedPort: uint32(hostPort),
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return ports
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidatePort validates a string is in the expected format for a port definition
 | 
			
		||||
func ValidatePort(value string) (string, error) {
 | 
			
		||||
	portMappings, err := nat.ParsePortSpec(value)
 | 
			
		||||
	for _, portMapping := range portMappings {
 | 
			
		||||
		if portMapping.Binding.HostIP != "" {
 | 
			
		||||
			return "", fmt.Errorf("HostIP is not supported by a service.")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return value, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type serviceOptions struct {
 | 
			
		||||
	name    string
 | 
			
		||||
	labels  opts.ListOpts
 | 
			
		||||
	image   string
 | 
			
		||||
	command []string
 | 
			
		||||
	args    []string
 | 
			
		||||
	env     opts.ListOpts
 | 
			
		||||
	workdir string
 | 
			
		||||
	user    string
 | 
			
		||||
	mounts  MountOpt
 | 
			
		||||
 | 
			
		||||
	resources resourceOptions
 | 
			
		||||
	stopGrace DurationOpt
 | 
			
		||||
 | 
			
		||||
	replicas Uint64Opt
 | 
			
		||||
	mode     string
 | 
			
		||||
 | 
			
		||||
	restartPolicy restartPolicyOptions
 | 
			
		||||
	constraints   []string
 | 
			
		||||
	update        updateOptions
 | 
			
		||||
	networks      []string
 | 
			
		||||
	endpoint      endpointOptions
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newServiceOptions() *serviceOptions {
 | 
			
		||||
	return &serviceOptions{
 | 
			
		||||
		labels: opts.NewListOpts(runconfigopts.ValidateEnv),
 | 
			
		||||
		env:    opts.NewListOpts(runconfigopts.ValidateEnv),
 | 
			
		||||
		endpoint: endpointOptions{
 | 
			
		||||
			ports: opts.NewListOpts(ValidatePort),
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
 | 
			
		||||
	var service swarm.ServiceSpec
 | 
			
		||||
 | 
			
		||||
	service = swarm.ServiceSpec{
 | 
			
		||||
		Annotations: swarm.Annotations{
 | 
			
		||||
			Name:   opts.name,
 | 
			
		||||
			Labels: runconfigopts.ConvertKVStringsToMap(opts.labels.GetAll()),
 | 
			
		||||
		},
 | 
			
		||||
		TaskTemplate: swarm.TaskSpec{
 | 
			
		||||
			ContainerSpec: swarm.ContainerSpec{
 | 
			
		||||
				Image:           opts.image,
 | 
			
		||||
				Command:         opts.command,
 | 
			
		||||
				Args:            opts.args,
 | 
			
		||||
				Env:             opts.env.GetAll(),
 | 
			
		||||
				Dir:             opts.workdir,
 | 
			
		||||
				User:            opts.user,
 | 
			
		||||
				Mounts:          opts.mounts.Value(),
 | 
			
		||||
				StopGracePeriod: opts.stopGrace.Value(),
 | 
			
		||||
			},
 | 
			
		||||
			Resources:     opts.resources.ToResourceRequirements(),
 | 
			
		||||
			RestartPolicy: opts.restartPolicy.ToRestartPolicy(),
 | 
			
		||||
			Placement: &swarm.Placement{
 | 
			
		||||
				Constraints: opts.constraints,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		Mode: swarm.ServiceMode{},
 | 
			
		||||
		UpdateConfig: &swarm.UpdateConfig{
 | 
			
		||||
			Parallelism: opts.update.parallelism,
 | 
			
		||||
			Delay:       opts.update.delay,
 | 
			
		||||
		},
 | 
			
		||||
		Networks:     convertNetworks(opts.networks),
 | 
			
		||||
		EndpointSpec: opts.endpoint.ToEndpointSpec(),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch opts.mode {
 | 
			
		||||
	case "global":
 | 
			
		||||
		if opts.replicas.Value() != nil {
 | 
			
		||||
			return service, fmt.Errorf("replicas can only be used with replicated mode")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		service.Mode.Global = &swarm.GlobalService{}
 | 
			
		||||
	case "replicated":
 | 
			
		||||
		service.Mode.Replicated = &swarm.ReplicatedService{
 | 
			
		||||
			Replicas: opts.replicas.Value(),
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		return service, fmt.Errorf("Unknown mode: %s", opts.mode)
 | 
			
		||||
	}
 | 
			
		||||
	return service, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// addServiceFlags adds all flags that are common to both `create` and `update.
 | 
			
		||||
// Any flags that are not common are added separately in the individual command
 | 
			
		||||
func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) {
 | 
			
		||||
	flags := cmd.Flags()
 | 
			
		||||
	flags.StringVar(&opts.name, "name", "", "Service name")
 | 
			
		||||
	flags.VarP(&opts.labels, "label", "l", "Service labels")
 | 
			
		||||
 | 
			
		||||
	flags.VarP(&opts.env, "env", "e", "Set environment variables")
 | 
			
		||||
	flags.StringVarP(&opts.workdir, "workdir", "w", "", "Working directory inside the container")
 | 
			
		||||
	flags.StringVarP(&opts.user, "user", "u", "", "Username or UID")
 | 
			
		||||
	flags.VarP(&opts.mounts, "mount", "m", "Attach a mount to the service")
 | 
			
		||||
 | 
			
		||||
	flags.Var(&opts.resources.limitCPU, "limit-cpu", "Limit CPUs")
 | 
			
		||||
	flags.Var(&opts.resources.limitMemBytes, "limit-memory", "Limit Memory")
 | 
			
		||||
	flags.Var(&opts.resources.resCPU, "reserve-cpu", "Reserve CPUs")
 | 
			
		||||
	flags.Var(&opts.resources.resMemBytes, "reserve-memory", "Reserve Memory")
 | 
			
		||||
	flags.Var(&opts.stopGrace, "stop-grace-period", "Time to wait before force killing a container")
 | 
			
		||||
 | 
			
		||||
	flags.StringVar(&opts.mode, "mode", "replicated", "Service mode (replicated or global)")
 | 
			
		||||
	flags.Var(&opts.replicas, "replicas", "Number of tasks")
 | 
			
		||||
 | 
			
		||||
	flags.StringVar(&opts.restartPolicy.condition, "restart-condition", "", "Restart when condition is met (none, on_failure, or any)")
 | 
			
		||||
	flags.Var(&opts.restartPolicy.delay, "restart-delay", "Delay between restart attempts")
 | 
			
		||||
	flags.Var(&opts.restartPolicy.maxAttempts, "restart-max-attempts", "Maximum number of restarts before giving up")
 | 
			
		||||
	flags.Var(&opts.restartPolicy.window, "restart-window", "Window used to evalulate the restart policy")
 | 
			
		||||
 | 
			
		||||
	flags.StringSliceVar(&opts.constraints, "constraint", []string{}, "Placement constraints")
 | 
			
		||||
 | 
			
		||||
	flags.Uint64Var(&opts.update.parallelism, "update-parallelism", 1, "Maximum number of tasks updated simultaneously")
 | 
			
		||||
	flags.DurationVar(&opts.update.delay, "update-delay", time.Duration(0), "Delay between updates")
 | 
			
		||||
 | 
			
		||||
	flags.StringSliceVar(&opts.networks, "network", []string{}, "Network attachments")
 | 
			
		||||
	flags.StringVar(&opts.endpoint.mode, "endpoint-mode", "", "Endpoint mode(Valid values: VIP, DNSRR)")
 | 
			
		||||
	flags.VarP(&opts.endpoint.ports, "publish", "p", "Publish a port as a node port")
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										47
									
								
								api/client/service/remove.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								api/client/service/remove.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,47 @@
 | 
			
		|||
package service
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/api/client"
 | 
			
		||||
	"github.com/docker/docker/cli"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func newRemoveCommand(dockerCli *client.DockerCli) *cobra.Command {
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:     "rm [OPTIONS] SERVICE",
 | 
			
		||||
		Aliases: []string{"remove"},
 | 
			
		||||
		Short:   "Remove a service",
 | 
			
		||||
		Args:    cli.RequiresMinArgs(1),
 | 
			
		||||
		RunE: func(cmd *cobra.Command, args []string) error {
 | 
			
		||||
			return runRemove(dockerCli, args)
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	cmd.Flags()
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runRemove(dockerCli *client.DockerCli, sids []string) error {
 | 
			
		||||
	client := dockerCli.Client()
 | 
			
		||||
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
 | 
			
		||||
	var errs []string
 | 
			
		||||
	for _, sid := range sids {
 | 
			
		||||
		err := client.ServiceRemove(ctx, sid)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errs = append(errs, err.Error())
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Fprintf(dockerCli.Out(), "%s\n", sid)
 | 
			
		||||
	}
 | 
			
		||||
	if len(errs) > 0 {
 | 
			
		||||
		return fmt.Errorf(strings.Join(errs, "\n"))
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										86
									
								
								api/client/service/scale.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								api/client/service/scale.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,86 @@
 | 
			
		|||
package service
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/api/client"
 | 
			
		||||
	"github.com/docker/docker/cli"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func newScaleCommand(dockerCli *client.DockerCli) *cobra.Command {
 | 
			
		||||
	return &cobra.Command{
 | 
			
		||||
		Use:   "scale SERVICE=SCALE [SERVICE=SCALE...]",
 | 
			
		||||
		Short: "Scale one or multiple services",
 | 
			
		||||
		Args:  scaleArgs,
 | 
			
		||||
		RunE: func(cmd *cobra.Command, args []string) error {
 | 
			
		||||
			return runScale(dockerCli, args)
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func scaleArgs(cmd *cobra.Command, args []string) error {
 | 
			
		||||
	if err := cli.RequiresMinArgs(1)(cmd, args); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	for _, arg := range args {
 | 
			
		||||
		if parts := strings.SplitN(arg, "=", 2); len(parts) != 2 {
 | 
			
		||||
			return fmt.Errorf(
 | 
			
		||||
				"Invalid scale specifier '%s'.\nSee '%s --help'.\n\nUsage:  %s\n\n%s",
 | 
			
		||||
				arg,
 | 
			
		||||
				cmd.CommandPath(),
 | 
			
		||||
				cmd.UseLine(),
 | 
			
		||||
				cmd.Short,
 | 
			
		||||
			)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runScale(dockerCli *client.DockerCli, args []string) error {
 | 
			
		||||
	var errors []string
 | 
			
		||||
	for _, arg := range args {
 | 
			
		||||
		parts := strings.SplitN(arg, "=", 2)
 | 
			
		||||
		serviceID, scale := parts[0], parts[1]
 | 
			
		||||
		if err := runServiceScale(dockerCli, serviceID, scale); err != nil {
 | 
			
		||||
			errors = append(errors, fmt.Sprintf("%s: %s", serviceID, err.Error()))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(errors) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Errorf(strings.Join(errors, "\n"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runServiceScale(dockerCli *client.DockerCli, serviceID string, scale string) error {
 | 
			
		||||
	client := dockerCli.Client()
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
 | 
			
		||||
	service, err := client.ServiceInspect(ctx, serviceID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serviceMode := &service.Spec.Mode
 | 
			
		||||
	if serviceMode.Replicated == nil {
 | 
			
		||||
		return fmt.Errorf("scale can only be used with replicated mode")
 | 
			
		||||
	}
 | 
			
		||||
	uintScale, err := strconv.ParseUint(scale, 10, 64)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("invalid replicas value %s: %s", scale, err.Error())
 | 
			
		||||
	}
 | 
			
		||||
	serviceMode.Replicated.Replicas = &uintScale
 | 
			
		||||
 | 
			
		||||
	err = client.ServiceUpdate(ctx, service.ID, service.Version, service.Spec)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Fprintf(dockerCli.Out(), "%s scaled to %s\n", serviceID, scale)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										65
									
								
								api/client/service/tasks.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								api/client/service/tasks.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,65 @@
 | 
			
		|||
package service
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/api/client"
 | 
			
		||||
	"github.com/docker/docker/api/client/idresolver"
 | 
			
		||||
	"github.com/docker/docker/api/client/task"
 | 
			
		||||
	"github.com/docker/docker/cli"
 | 
			
		||||
	"github.com/docker/docker/opts"
 | 
			
		||||
	"github.com/docker/engine-api/types"
 | 
			
		||||
	"github.com/docker/engine-api/types/swarm"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type tasksOptions struct {
 | 
			
		||||
	serviceID string
 | 
			
		||||
	all       bool
 | 
			
		||||
	noResolve bool
 | 
			
		||||
	filter    opts.FilterOpt
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newTasksCommand(dockerCli *client.DockerCli) *cobra.Command {
 | 
			
		||||
	opts := tasksOptions{filter: opts.NewFilterOpt()}
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "tasks [OPTIONS] SERVICE",
 | 
			
		||||
		Short: "List the tasks of a service",
 | 
			
		||||
		Args:  cli.ExactArgs(1),
 | 
			
		||||
		RunE: func(cmd *cobra.Command, args []string) error {
 | 
			
		||||
			opts.serviceID = args[0]
 | 
			
		||||
			return runTasks(dockerCli, opts)
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	flags := cmd.Flags()
 | 
			
		||||
	flags.BoolVarP(&opts.all, "all", "a", false, "Display all tasks")
 | 
			
		||||
	flags.BoolVarP(&opts.noResolve, "no-resolve", "n", false, "Do not map IDs to Names")
 | 
			
		||||
	flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runTasks(dockerCli *client.DockerCli, opts tasksOptions) error {
 | 
			
		||||
	client := dockerCli.Client()
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
 | 
			
		||||
	service, err := client.ServiceInspect(ctx, opts.serviceID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	filter := opts.filter.Value()
 | 
			
		||||
	filter.Add("service", service.ID)
 | 
			
		||||
	if !opts.all && !filter.Include("desired_state") {
 | 
			
		||||
		filter.Add("desired_state", string(swarm.TaskStateRunning))
 | 
			
		||||
		filter.Add("desired_state", string(swarm.TaskStateAccepted))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tasks, err := client.TaskList(ctx, types.TaskListOptions{Filter: filter})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return task.Print(dockerCli, ctx, tasks, idresolver.New(client, opts.noResolve))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										244
									
								
								api/client/service/update.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										244
									
								
								api/client/service/update.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,244 @@
 | 
			
		|||
package service
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/api/client"
 | 
			
		||||
	"github.com/docker/docker/cli"
 | 
			
		||||
	"github.com/docker/docker/opts"
 | 
			
		||||
	runconfigopts "github.com/docker/docker/runconfig/opts"
 | 
			
		||||
	"github.com/docker/engine-api/types/swarm"
 | 
			
		||||
	"github.com/docker/go-connections/nat"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	"github.com/spf13/pflag"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
 | 
			
		||||
	opts := newServiceOptions()
 | 
			
		||||
	var flags *pflag.FlagSet
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "update [OPTIONS] SERVICE",
 | 
			
		||||
		Short: "Update a service",
 | 
			
		||||
		Args:  cli.ExactArgs(1),
 | 
			
		||||
		RunE: func(cmd *cobra.Command, args []string) error {
 | 
			
		||||
			return runUpdate(dockerCli, flags, args[0])
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	flags = cmd.Flags()
 | 
			
		||||
	flags.String("image", "", "Service image tag")
 | 
			
		||||
	flags.StringSlice("command", []string{}, "Service command")
 | 
			
		||||
	flags.StringSlice("arg", []string{}, "Service command args")
 | 
			
		||||
	addServiceFlags(cmd, opts)
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, serviceID string) error {
 | 
			
		||||
	client := dockerCli.Client()
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
 | 
			
		||||
	service, err := client.ServiceInspect(ctx, serviceID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = mergeService(&service.Spec, flags)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	err = client.ServiceUpdate(ctx, service.ID, service.Version, service.Spec)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Fprintf(dockerCli.Out(), "%s\n", serviceID)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func mergeService(spec *swarm.ServiceSpec, flags *pflag.FlagSet) error {
 | 
			
		||||
 | 
			
		||||
	mergeString := func(flag string, field *string) {
 | 
			
		||||
		if flags.Changed(flag) {
 | 
			
		||||
			*field, _ = flags.GetString(flag)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mergeListOpts := func(flag string, field *[]string) {
 | 
			
		||||
		if flags.Changed(flag) {
 | 
			
		||||
			value := flags.Lookup(flag).Value.(*opts.ListOpts)
 | 
			
		||||
			*field = value.GetAll()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mergeSlice := func(flag string, field *[]string) {
 | 
			
		||||
		if flags.Changed(flag) {
 | 
			
		||||
			*field, _ = flags.GetStringSlice(flag)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mergeInt64Value := func(flag string, field *int64) {
 | 
			
		||||
		if flags.Changed(flag) {
 | 
			
		||||
			*field = flags.Lookup(flag).Value.(int64Value).Value()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mergeDuration := func(flag string, field *time.Duration) {
 | 
			
		||||
		if flags.Changed(flag) {
 | 
			
		||||
			*field, _ = flags.GetDuration(flag)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mergeDurationOpt := func(flag string, field *time.Duration) {
 | 
			
		||||
		if flags.Changed(flag) {
 | 
			
		||||
			*field = *flags.Lookup(flag).Value.(*DurationOpt).Value()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mergeUint64 := func(flag string, field *uint64) {
 | 
			
		||||
		if flags.Changed(flag) {
 | 
			
		||||
			*field, _ = flags.GetUint64(flag)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mergeUint64Opt := func(flag string, field *uint64) {
 | 
			
		||||
		if flags.Changed(flag) {
 | 
			
		||||
			*field = *flags.Lookup(flag).Value.(*Uint64Opt).Value()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cspec := &spec.TaskTemplate.ContainerSpec
 | 
			
		||||
	task := &spec.TaskTemplate
 | 
			
		||||
	mergeString("name", &spec.Name)
 | 
			
		||||
	mergeLabels(flags, &spec.Labels)
 | 
			
		||||
	mergeString("image", &cspec.Image)
 | 
			
		||||
	mergeSlice("command", &cspec.Command)
 | 
			
		||||
	mergeSlice("arg", &cspec.Command)
 | 
			
		||||
	mergeListOpts("env", &cspec.Env)
 | 
			
		||||
	mergeString("workdir", &cspec.Dir)
 | 
			
		||||
	mergeString("user", &cspec.User)
 | 
			
		||||
	mergeMounts(flags, &cspec.Mounts)
 | 
			
		||||
 | 
			
		||||
	mergeInt64Value("limit-cpu", &task.Resources.Limits.NanoCPUs)
 | 
			
		||||
	mergeInt64Value("limit-memory", &task.Resources.Limits.MemoryBytes)
 | 
			
		||||
	mergeInt64Value("reserve-cpu", &task.Resources.Reservations.NanoCPUs)
 | 
			
		||||
	mergeInt64Value("reserve-memory", &task.Resources.Reservations.MemoryBytes)
 | 
			
		||||
 | 
			
		||||
	mergeDurationOpt("stop-grace-period", cspec.StopGracePeriod)
 | 
			
		||||
 | 
			
		||||
	if flags.Changed("restart-policy-condition") {
 | 
			
		||||
		value, _ := flags.GetString("restart-policy-condition")
 | 
			
		||||
		task.RestartPolicy.Condition = swarm.RestartPolicyCondition(value)
 | 
			
		||||
	}
 | 
			
		||||
	mergeDurationOpt("restart-policy-delay", task.RestartPolicy.Delay)
 | 
			
		||||
	mergeUint64Opt("restart-policy-max-attempts", task.RestartPolicy.MaxAttempts)
 | 
			
		||||
	mergeDurationOpt("restart-policy-window", task.RestartPolicy.Window)
 | 
			
		||||
	mergeSlice("constraint", &task.Placement.Constraints)
 | 
			
		||||
 | 
			
		||||
	if err := mergeMode(flags, &spec.Mode); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mergeUint64("updateconfig-parallelism", &spec.UpdateConfig.Parallelism)
 | 
			
		||||
	mergeDuration("updateconfig-delay", &spec.UpdateConfig.Delay)
 | 
			
		||||
 | 
			
		||||
	mergeNetworks(flags, &spec.Networks)
 | 
			
		||||
	if flags.Changed("endpoint-mode") {
 | 
			
		||||
		value, _ := flags.GetString("endpoint-mode")
 | 
			
		||||
		spec.EndpointSpec.Mode = swarm.ResolutionMode(value)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mergePorts(flags, &spec.EndpointSpec.Ports)
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func mergeLabels(flags *pflag.FlagSet, field *map[string]string) {
 | 
			
		||||
	if !flags.Changed("label") {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if *field == nil {
 | 
			
		||||
		*field = make(map[string]string)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	values := flags.Lookup("label").Value.(*opts.ListOpts).GetAll()
 | 
			
		||||
	for key, value := range runconfigopts.ConvertKVStringsToMap(values) {
 | 
			
		||||
		(*field)[key] = value
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: should this override by destination path, or does swarm handle that?
 | 
			
		||||
func mergeMounts(flags *pflag.FlagSet, mounts *[]swarm.Mount) {
 | 
			
		||||
	if !flags.Changed("mount") {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	values := flags.Lookup("mount").Value.(*MountOpt).Value()
 | 
			
		||||
	*mounts = append(*mounts, values...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: should this override by name, or does swarm handle that?
 | 
			
		||||
func mergePorts(flags *pflag.FlagSet, portConfig *[]swarm.PortConfig) {
 | 
			
		||||
	if !flags.Changed("ports") {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	values := flags.Lookup("ports").Value.(*opts.ListOpts).GetAll()
 | 
			
		||||
	ports, portBindings, _ := nat.ParsePortSpecs(values)
 | 
			
		||||
 | 
			
		||||
	for port := range ports {
 | 
			
		||||
		*portConfig = append(*portConfig, convertPortToPortConfig(port, portBindings)...)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func mergeNetworks(flags *pflag.FlagSet, attachments *[]swarm.NetworkAttachmentConfig) {
 | 
			
		||||
	if !flags.Changed("network") {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	networks, _ := flags.GetStringSlice("network")
 | 
			
		||||
	for _, network := range networks {
 | 
			
		||||
		*attachments = append(*attachments, swarm.NetworkAttachmentConfig{Target: network})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func mergeMode(flags *pflag.FlagSet, serviceMode *swarm.ServiceMode) error {
 | 
			
		||||
	if !flags.Changed("mode") && !flags.Changed("scale") {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var mode string
 | 
			
		||||
	if flags.Changed("mode") {
 | 
			
		||||
		mode, _ = flags.GetString("mode")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !(mode == "replicated" || serviceMode.Replicated != nil) && flags.Changed("replicas") {
 | 
			
		||||
		return fmt.Errorf("replicas can only be used with replicated mode")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if mode == "global" {
 | 
			
		||||
		serviceMode.Replicated = nil
 | 
			
		||||
		serviceMode.Global = &swarm.GlobalService{}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if flags.Changed("replicas") {
 | 
			
		||||
		replicas := flags.Lookup("replicas").Value.(*Uint64Opt).Value()
 | 
			
		||||
		serviceMode.Replicated = &swarm.ReplicatedService{Replicas: replicas}
 | 
			
		||||
		serviceMode.Global = nil
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if mode == "replicated" {
 | 
			
		||||
		if serviceMode.Replicated != nil {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		serviceMode.Replicated = &swarm.ReplicatedService{Replicas: &DefaultReplicas}
 | 
			
		||||
		serviceMode.Global = nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								api/client/swarm/cmd.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								api/client/swarm/cmd.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,30 @@
 | 
			
		|||
package swarm
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/api/client"
 | 
			
		||||
	"github.com/docker/docker/cli"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// NewSwarmCommand returns a cobra command for `swarm` subcommands
 | 
			
		||||
func NewSwarmCommand(dockerCli *client.DockerCli) *cobra.Command {
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "swarm",
 | 
			
		||||
		Short: "Manage docker swarm",
 | 
			
		||||
		Args:  cli.NoArgs,
 | 
			
		||||
		Run: func(cmd *cobra.Command, args []string) {
 | 
			
		||||
			fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	cmd.AddCommand(
 | 
			
		||||
		newInitCommand(dockerCli),
 | 
			
		||||
		newJoinCommand(dockerCli),
 | 
			
		||||
		newUpdateCommand(dockerCli),
 | 
			
		||||
		newLeaveCommand(dockerCli),
 | 
			
		||||
		newInspectCommand(dockerCli),
 | 
			
		||||
	)
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										61
									
								
								api/client/swarm/init.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								api/client/swarm/init.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,61 @@
 | 
			
		|||
package swarm
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/api/client"
 | 
			
		||||
	"github.com/docker/docker/cli"
 | 
			
		||||
	"github.com/docker/engine-api/types/swarm"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type initOptions struct {
 | 
			
		||||
	listenAddr      NodeAddrOption
 | 
			
		||||
	autoAccept      AutoAcceptOption
 | 
			
		||||
	forceNewCluster bool
 | 
			
		||||
	secret          string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newInitCommand(dockerCli *client.DockerCli) *cobra.Command {
 | 
			
		||||
	opts := initOptions{
 | 
			
		||||
		listenAddr: NewNodeAddrOption(),
 | 
			
		||||
		autoAccept: NewAutoAcceptOption(),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "init",
 | 
			
		||||
		Short: "Initialize a Swarm.",
 | 
			
		||||
		Args:  cli.NoArgs,
 | 
			
		||||
		RunE: func(cmd *cobra.Command, args []string) error {
 | 
			
		||||
			return runInit(dockerCli, opts)
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	flags := cmd.Flags()
 | 
			
		||||
	flags.Var(&opts.listenAddr, "listen-addr", "Listen address")
 | 
			
		||||
	flags.Var(&opts.autoAccept, "auto-accept", "Auto acceptance policy (worker, manager, or none)")
 | 
			
		||||
	flags.StringVar(&opts.secret, "secret", "", "Set secret value needed to accept nodes into cluster")
 | 
			
		||||
	flags.BoolVar(&opts.forceNewCluster, "force-new-cluster", false, "Force create a new cluster from current state.")
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runInit(dockerCli *client.DockerCli, opts initOptions) error {
 | 
			
		||||
	client := dockerCli.Client()
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
 | 
			
		||||
	req := swarm.InitRequest{
 | 
			
		||||
		ListenAddr:      opts.listenAddr.String(),
 | 
			
		||||
		ForceNewCluster: opts.forceNewCluster,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req.Spec.AcceptancePolicy.Policies = opts.autoAccept.Policies(opts.secret)
 | 
			
		||||
 | 
			
		||||
	nodeID, err := client.SwarmInit(ctx, req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Printf("Swarm initialized: current node (%s) is now a manager.\n", nodeID)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										56
									
								
								api/client/swarm/inspect.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								api/client/swarm/inspect.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,56 @@
 | 
			
		|||
package swarm
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/api/client"
 | 
			
		||||
	"github.com/docker/docker/api/client/inspect"
 | 
			
		||||
	"github.com/docker/docker/cli"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type inspectOptions struct {
 | 
			
		||||
	format string
 | 
			
		||||
	//	pretty  bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
 | 
			
		||||
	var opts inspectOptions
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "inspect [OPTIONS]",
 | 
			
		||||
		Short: "Inspect the Swarm",
 | 
			
		||||
		Args:  cli.NoArgs,
 | 
			
		||||
		RunE: func(cmd *cobra.Command, args []string) error {
 | 
			
		||||
			// if opts.pretty && len(opts.format) > 0 {
 | 
			
		||||
			//	return fmt.Errorf("--format is incompatible with human friendly format")
 | 
			
		||||
			// }
 | 
			
		||||
			return runInspect(dockerCli, opts)
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	flags := cmd.Flags()
 | 
			
		||||
	flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given go template")
 | 
			
		||||
	//flags.BoolVarP(&opts.pretty, "pretty", "h", false, "Print the information in a human friendly format.")
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runInspect(dockerCli *client.DockerCli, opts inspectOptions) error {
 | 
			
		||||
	client := dockerCli.Client()
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
 | 
			
		||||
	swarm, err := client.SwarmInspect(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	getRef := func(_ string) (interface{}, []byte, error) {
 | 
			
		||||
		return swarm, nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//	if !opts.pretty {
 | 
			
		||||
	return inspect.Inspect(dockerCli.Out(), []string{""}, opts.format, getRef)
 | 
			
		||||
	//	}
 | 
			
		||||
 | 
			
		||||
	//return printHumanFriendly(dockerCli.Out(), opts.refs, getRef)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										65
									
								
								api/client/swarm/join.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								api/client/swarm/join.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,65 @@
 | 
			
		|||
package swarm
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/api/client"
 | 
			
		||||
	"github.com/docker/docker/cli"
 | 
			
		||||
	"github.com/docker/engine-api/types/swarm"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type joinOptions struct {
 | 
			
		||||
	remote     string
 | 
			
		||||
	listenAddr NodeAddrOption
 | 
			
		||||
	manager    bool
 | 
			
		||||
	secret     string
 | 
			
		||||
	CACertHash string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newJoinCommand(dockerCli *client.DockerCli) *cobra.Command {
 | 
			
		||||
	opts := joinOptions{
 | 
			
		||||
		listenAddr: NodeAddrOption{addr: defaultListenAddr},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "join [OPTIONS] HOST:PORT",
 | 
			
		||||
		Short: "Join a Swarm as a node and/or manager.",
 | 
			
		||||
		Args:  cli.ExactArgs(1),
 | 
			
		||||
		RunE: func(cmd *cobra.Command, args []string) error {
 | 
			
		||||
			opts.remote = args[0]
 | 
			
		||||
			return runJoin(dockerCli, opts)
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	flags := cmd.Flags()
 | 
			
		||||
	flags.Var(&opts.listenAddr, "listen-addr", "Listen address")
 | 
			
		||||
	flags.BoolVar(&opts.manager, "manager", false, "Try joining as a manager.")
 | 
			
		||||
	flags.StringVar(&opts.secret, "secret", "", "Secret for node acceptance")
 | 
			
		||||
	flags.StringVar(&opts.CACertHash, "ca-hash", "", "Hash of the Root Certificate Authority certificate used for trusted join")
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runJoin(dockerCli *client.DockerCli, opts joinOptions) error {
 | 
			
		||||
	client := dockerCli.Client()
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
 | 
			
		||||
	req := swarm.JoinRequest{
 | 
			
		||||
		Manager:     opts.manager,
 | 
			
		||||
		Secret:      opts.secret,
 | 
			
		||||
		ListenAddr:  opts.listenAddr.String(),
 | 
			
		||||
		RemoteAddrs: []string{opts.remote},
 | 
			
		||||
		CACertHash:  opts.CACertHash,
 | 
			
		||||
	}
 | 
			
		||||
	err := client.SwarmJoin(ctx, req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if opts.manager {
 | 
			
		||||
		fmt.Fprintln(dockerCli.Out(), "This node joined a Swarm as a manager.")
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Fprintln(dockerCli.Out(), "This node joined a Swarm as a worker.")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								api/client/swarm/leave.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								api/client/swarm/leave.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,44 @@
 | 
			
		|||
package swarm
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/api/client"
 | 
			
		||||
	"github.com/docker/docker/cli"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type leaveOptions struct {
 | 
			
		||||
	force bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newLeaveCommand(dockerCli *client.DockerCli) *cobra.Command {
 | 
			
		||||
	opts := leaveOptions{}
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "leave",
 | 
			
		||||
		Short: "Leave a Swarm.",
 | 
			
		||||
		Args:  cli.NoArgs,
 | 
			
		||||
		RunE: func(cmd *cobra.Command, args []string) error {
 | 
			
		||||
			return runLeave(dockerCli, opts)
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	flags := cmd.Flags()
 | 
			
		||||
	flags.BoolVar(&opts.force, "force", false, "Force leave ignoring warnings.")
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runLeave(dockerCli *client.DockerCli, opts leaveOptions) error {
 | 
			
		||||
	client := dockerCli.Client()
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
 | 
			
		||||
	if err := client.SwarmLeave(ctx, opts.force); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Fprintln(dockerCli.Out(), "Node left the default swarm.")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										120
									
								
								api/client/swarm/opts.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								api/client/swarm/opts.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,120 @@
 | 
			
		|||
package swarm
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/engine-api/types/swarm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	defaultListenAddr = "0.0.0.0:2377"
 | 
			
		||||
	// WORKER constant for worker name
 | 
			
		||||
	WORKER = "WORKER"
 | 
			
		||||
	// MANAGER constant for manager name
 | 
			
		||||
	MANAGER = "MANAGER"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	defaultPolicies = []swarm.Policy{
 | 
			
		||||
		{Role: WORKER, Autoaccept: true},
 | 
			
		||||
		{Role: MANAGER, Autoaccept: false},
 | 
			
		||||
	}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// NodeAddrOption is a pflag.Value for listen and remote addresses
 | 
			
		||||
type NodeAddrOption struct {
 | 
			
		||||
	addr string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// String prints the representation of this flag
 | 
			
		||||
func (a *NodeAddrOption) String() string {
 | 
			
		||||
	return a.addr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Set the value for this flag
 | 
			
		||||
func (a *NodeAddrOption) Set(value string) error {
 | 
			
		||||
	if !strings.Contains(value, ":") {
 | 
			
		||||
		return fmt.Errorf("Invalud url, a host and port are required")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	parts := strings.Split(value, ":")
 | 
			
		||||
	if len(parts) != 2 {
 | 
			
		||||
		return fmt.Errorf("Invalud url, too many colons")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	a.addr = value
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Type returns the type of this flag
 | 
			
		||||
func (a *NodeAddrOption) Type() string {
 | 
			
		||||
	return "node-addr"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewNodeAddrOption returns a new node address option
 | 
			
		||||
func NewNodeAddrOption() NodeAddrOption {
 | 
			
		||||
	return NodeAddrOption{addr: defaultListenAddr}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AutoAcceptOption is a value type for auto-accept policy
 | 
			
		||||
type AutoAcceptOption struct {
 | 
			
		||||
	values map[string]bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// String prints a string representation of this option
 | 
			
		||||
func (o *AutoAcceptOption) String() string {
 | 
			
		||||
	keys := []string{}
 | 
			
		||||
	for key := range o.values {
 | 
			
		||||
		keys = append(keys, key)
 | 
			
		||||
	}
 | 
			
		||||
	return strings.Join(keys, " ")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Set sets a new value on this option
 | 
			
		||||
func (o *AutoAcceptOption) Set(value string) error {
 | 
			
		||||
	value = strings.ToUpper(value)
 | 
			
		||||
	switch value {
 | 
			
		||||
	case "", "NONE":
 | 
			
		||||
		if accept, ok := o.values[WORKER]; ok && accept {
 | 
			
		||||
			return fmt.Errorf("value NONE is incompatible with %s", WORKER)
 | 
			
		||||
		}
 | 
			
		||||
		if accept, ok := o.values[MANAGER]; ok && accept {
 | 
			
		||||
			return fmt.Errorf("value NONE is incompatible with %s", MANAGER)
 | 
			
		||||
		}
 | 
			
		||||
		o.values[WORKER] = false
 | 
			
		||||
		o.values[MANAGER] = false
 | 
			
		||||
	case WORKER, MANAGER:
 | 
			
		||||
		if accept, ok := o.values[value]; ok && !accept {
 | 
			
		||||
			return fmt.Errorf("value NONE is incompatible with %s", value)
 | 
			
		||||
		}
 | 
			
		||||
		o.values[value] = true
 | 
			
		||||
	default:
 | 
			
		||||
		return fmt.Errorf("must be one of %s, %s, NONE", WORKER, MANAGER)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Type returns the type of this option
 | 
			
		||||
func (o *AutoAcceptOption) Type() string {
 | 
			
		||||
	return "auto-accept"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Policies returns a representation of this option for the api
 | 
			
		||||
func (o *AutoAcceptOption) Policies(secret string) []swarm.Policy {
 | 
			
		||||
	policies := []swarm.Policy{}
 | 
			
		||||
	for _, p := range defaultPolicies {
 | 
			
		||||
		if len(o.values) != 0 {
 | 
			
		||||
			p.Autoaccept = o.values[string(p.Role)]
 | 
			
		||||
		}
 | 
			
		||||
		p.Secret = secret
 | 
			
		||||
		policies = append(policies, p)
 | 
			
		||||
	}
 | 
			
		||||
	return policies
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewAutoAcceptOption returns a new auto-accept option
 | 
			
		||||
func NewAutoAcceptOption() AutoAcceptOption {
 | 
			
		||||
	return AutoAcceptOption{values: make(map[string]bool)}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										93
									
								
								api/client/swarm/update.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								api/client/swarm/update.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,93 @@
 | 
			
		|||
package swarm
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	"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"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type updateOptions struct {
 | 
			
		||||
	autoAccept       AutoAcceptOption
 | 
			
		||||
	secret           string
 | 
			
		||||
	taskHistoryLimit int64
 | 
			
		||||
	heartbeatPeriod  uint64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
 | 
			
		||||
	opts := updateOptions{autoAccept: NewAutoAcceptOption()}
 | 
			
		||||
	var flags *pflag.FlagSet
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "update",
 | 
			
		||||
		Short: "update the Swarm.",
 | 
			
		||||
		Args:  cli.NoArgs,
 | 
			
		||||
		RunE: func(cmd *cobra.Command, args []string) error {
 | 
			
		||||
			return runUpdate(dockerCli, flags, opts)
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	flags = cmd.Flags()
 | 
			
		||||
	flags.Var(&opts.autoAccept, "auto-accept", "Auto acceptance policy (worker, manager or none)")
 | 
			
		||||
	flags.StringVar(&opts.secret, "secret", "", "Set secret value needed to accept nodes into cluster")
 | 
			
		||||
	flags.Int64Var(&opts.taskHistoryLimit, "task-history-limit", 10, "Task history retention limit")
 | 
			
		||||
	flags.Uint64Var(&opts.heartbeatPeriod, "dispatcher-heartbeat-period", 5000000000, "Dispatcher heartbeat period")
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts updateOptions) error {
 | 
			
		||||
	client := dockerCli.Client()
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
 | 
			
		||||
	swarm, err := client.SwarmInspect(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = mergeSwarm(&swarm, flags)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	err = client.SwarmUpdate(ctx, swarm.Version, swarm.Spec)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Swarm updated.")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func mergeSwarm(swarm *swarm.Swarm, flags *pflag.FlagSet) error {
 | 
			
		||||
	spec := &swarm.Spec
 | 
			
		||||
 | 
			
		||||
	if flags.Changed("auto-accept") {
 | 
			
		||||
		value := flags.Lookup("auto-accept").Value.(*AutoAcceptOption)
 | 
			
		||||
		if len(spec.AcceptancePolicy.Policies) > 0 {
 | 
			
		||||
			spec.AcceptancePolicy.Policies = value.Policies(spec.AcceptancePolicy.Policies[0].Secret)
 | 
			
		||||
		} else {
 | 
			
		||||
			spec.AcceptancePolicy.Policies = value.Policies("")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if flags.Changed("secret") {
 | 
			
		||||
		secret, _ := flags.GetString("secret")
 | 
			
		||||
		for _, policy := range spec.AcceptancePolicy.Policies {
 | 
			
		||||
			policy.Secret = secret
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if flags.Changed("task-history-limit") {
 | 
			
		||||
		spec.Orchestration.TaskHistoryRetentionLimit, _ = flags.GetInt64("task-history-limit")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if flags.Changed("dispatcher-heartbeat-period") {
 | 
			
		||||
		spec.Dispatcher.HeartbeatPeriod, _ = flags.GetUint64("dispatcher-heartbeat-period")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								api/client/tag.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								api/client/tag.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,20 @@
 | 
			
		|||
package client
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	Cli "github.com/docker/docker/cli"
 | 
			
		||||
	flag "github.com/docker/docker/pkg/mflag"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// CmdTag tags an image into a repository.
 | 
			
		||||
//
 | 
			
		||||
// Usage: docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]
 | 
			
		||||
func (cli *DockerCli) CmdTag(args ...string) error {
 | 
			
		||||
	cmd := Cli.Subcmd("tag", []string{"IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]"}, Cli.DockerCommands["tag"].Description, true)
 | 
			
		||||
	cmd.Require(flag.Exact, 2)
 | 
			
		||||
 | 
			
		||||
	cmd.ParseFlags(args, true)
 | 
			
		||||
 | 
			
		||||
	return cli.client.ImageTag(context.Background(), cmd.Arg(0), cmd.Arg(1))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										79
									
								
								api/client/task/print.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								api/client/task/print.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,79 @@
 | 
			
		|||
package task
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"text/tabwriter"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/api/client"
 | 
			
		||||
	"github.com/docker/docker/api/client/idresolver"
 | 
			
		||||
	"github.com/docker/engine-api/types/swarm"
 | 
			
		||||
	"github.com/docker/go-units"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	psTaskItemFmt = "%s\t%s\t%s\t%s\t%s %s\t%s\t%s\n"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type tasksBySlot []swarm.Task
 | 
			
		||||
 | 
			
		||||
func (t tasksBySlot) Len() int {
 | 
			
		||||
	return len(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t tasksBySlot) Swap(i, j int) {
 | 
			
		||||
	t[i], t[j] = t[j], t[i]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t tasksBySlot) Less(i, j int) bool {
 | 
			
		||||
	// Sort by slot.
 | 
			
		||||
	if t[i].Slot != t[j].Slot {
 | 
			
		||||
		return t[i].Slot < t[j].Slot
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If same slot, sort by most recent.
 | 
			
		||||
	return t[j].Meta.CreatedAt.Before(t[i].CreatedAt)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Print task information in a table format
 | 
			
		||||
func Print(dockerCli *client.DockerCli, ctx context.Context, tasks []swarm.Task, resolver *idresolver.IDResolver) error {
 | 
			
		||||
	sort.Stable(tasksBySlot(tasks))
 | 
			
		||||
 | 
			
		||||
	writer := tabwriter.NewWriter(dockerCli.Out(), 0, 4, 2, ' ', 0)
 | 
			
		||||
 | 
			
		||||
	// Ignore flushing errors
 | 
			
		||||
	defer writer.Flush()
 | 
			
		||||
	fmt.Fprintln(writer, strings.Join([]string{"ID", "NAME", "SERVICE", "IMAGE", "LAST STATE", "DESIRED STATE", "NODE"}, "\t"))
 | 
			
		||||
	for _, task := range tasks {
 | 
			
		||||
		serviceValue, err := resolver.Resolve(ctx, swarm.Service{}, task.ServiceID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		nodeValue, err := resolver.Resolve(ctx, swarm.Node{}, task.NodeID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		name := serviceValue
 | 
			
		||||
		if task.Slot > 0 {
 | 
			
		||||
			name = fmt.Sprintf("%s.%d", name, task.Slot)
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Fprintf(
 | 
			
		||||
			writer,
 | 
			
		||||
			psTaskItemFmt,
 | 
			
		||||
			task.ID,
 | 
			
		||||
			name,
 | 
			
		||||
			serviceValue,
 | 
			
		||||
			task.Spec.ContainerSpec.Image,
 | 
			
		||||
			client.PrettyPrint(task.Status.State),
 | 
			
		||||
			units.HumanDuration(time.Since(task.Status.Timestamp)),
 | 
			
		||||
			client.PrettyPrint(task.DesiredState),
 | 
			
		||||
			nodeValue,
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -8,6 +8,7 @@ import (
 | 
			
		|||
	gosignal "os/signal"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
| 
						 | 
				
			
			@ -163,3 +164,27 @@ func (cli *DockerCli) ForwardAllSignals(ctx context.Context, cid string) chan os
 | 
			
		|||
	}()
 | 
			
		||||
	return sigc
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// capitalizeFirst capitalizes the first character of string
 | 
			
		||||
func capitalizeFirst(s string) string {
 | 
			
		||||
	switch l := len(s); l {
 | 
			
		||||
	case 0:
 | 
			
		||||
		return s
 | 
			
		||||
	case 1:
 | 
			
		||||
		return strings.ToLower(s)
 | 
			
		||||
	default:
 | 
			
		||||
		return strings.ToUpper(string(s[0])) + strings.ToLower(s[1:])
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PrettyPrint outputs arbitrary data for human formatted output by uppercasing the first letter.
 | 
			
		||||
func PrettyPrint(i interface{}) string {
 | 
			
		||||
	switch t := i.(type) {
 | 
			
		||||
	case nil:
 | 
			
		||||
		return "None"
 | 
			
		||||
	case string:
 | 
			
		||||
		return capitalizeFirst(t)
 | 
			
		||||
	default:
 | 
			
		||||
		return capitalizeFirst(fmt.Sprintf("%s", t))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,10 @@ import (
 | 
			
		|||
	"github.com/docker/docker/api/client/container"
 | 
			
		||||
	"github.com/docker/docker/api/client/image"
 | 
			
		||||
	"github.com/docker/docker/api/client/network"
 | 
			
		||||
	"github.com/docker/docker/api/client/node"
 | 
			
		||||
	"github.com/docker/docker/api/client/registry"
 | 
			
		||||
	"github.com/docker/docker/api/client/service"
 | 
			
		||||
	"github.com/docker/docker/api/client/swarm"
 | 
			
		||||
	"github.com/docker/docker/api/client/system"
 | 
			
		||||
	"github.com/docker/docker/api/client/volume"
 | 
			
		||||
	"github.com/docker/docker/cli"
 | 
			
		||||
| 
						 | 
				
			
			@ -36,6 +39,9 @@ func NewCobraAdaptor(clientFlags *cliflags.ClientFlags) CobraAdaptor {
 | 
			
		|||
	rootCmd.SetFlagErrorFunc(cli.FlagErrorFunc)
 | 
			
		||||
	rootCmd.SetOutput(stdout)
 | 
			
		||||
	rootCmd.AddCommand(
 | 
			
		||||
		node.NewNodeCommand(dockerCli),
 | 
			
		||||
		service.NewServiceCommand(dockerCli),
 | 
			
		||||
		swarm.NewSwarmCommand(dockerCli),
 | 
			
		||||
		container.NewAttachCommand(dockerCli),
 | 
			
		||||
		container.NewCommitCommand(dockerCli),
 | 
			
		||||
		container.NewCreateCommand(dockerCli),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,7 +11,7 @@ var DockerCommandUsage = []Command{
 | 
			
		|||
	{"cp", "Copy files/folders between a container and the local filesystem"},
 | 
			
		||||
	{"exec", "Run a command in a running container"},
 | 
			
		||||
	{"info", "Display system-wide information"},
 | 
			
		||||
	{"inspect", "Return low-level information on a container or image"},
 | 
			
		||||
	{"inspect", "Return low-level information on a container, image or task"},
 | 
			
		||||
	{"update", "Update configuration of one or more containers"},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -63,7 +63,7 @@ func (s *DockerSuite) TestRenameCheckNames(c *check.C) {
 | 
			
		|||
 | 
			
		||||
	name, err := inspectFieldWithError("first_name", "Name")
 | 
			
		||||
	c.Assert(err, checker.NotNil, check.Commentf(name))
 | 
			
		||||
	c.Assert(err.Error(), checker.Contains, "No such image or container: first_name")
 | 
			
		||||
	c.Assert(err.Error(), checker.Contains, "No such image, container or task: first_name")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *DockerSuite) TestRenameInvalidName(c *check.C) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue