diff --git a/api/client/node/accept.go b/api/client/node/accept.go index e0b8c86a84..da9e8f14d5 100644 --- a/api/client/node/accept.go +++ b/api/client/node/accept.go @@ -21,8 +21,9 @@ func newAcceptCommand(dockerCli *client.DockerCli) *cobra.Command { } func runAccept(dockerCli *client.DockerCli, nodes []string) error { - accept := func(node *swarm.Node) { + accept := func(node *swarm.Node) error { node.Spec.Membership = swarm.NodeMembershipAccepted + return nil } success := func(nodeID string) { fmt.Fprintf(dockerCli.Out(), "Node %s accepted in the swarm.\n", nodeID) diff --git a/api/client/node/demote.go b/api/client/node/demote.go index af6c4c30eb..058471daaf 100644 --- a/api/client/node/demote.go +++ b/api/client/node/demote.go @@ -21,8 +21,9 @@ func newDemoteCommand(dockerCli *client.DockerCli) *cobra.Command { } func runDemote(dockerCli *client.DockerCli, nodes []string) error { - demote := func(node *swarm.Node) { + demote := func(node *swarm.Node) error { node.Spec.Role = swarm.NodeRoleWorker + return nil } success := func(nodeID string) { fmt.Fprintf(dockerCli.Out(), "Manager %s demoted in the swarm.\n", nodeID) diff --git a/api/client/node/opts.go b/api/client/node/opts.go index cd160252d9..381e1dfcea 100644 --- a/api/client/node/opts.go +++ b/api/client/node/opts.go @@ -4,18 +4,37 @@ import ( "fmt" "strings" + "github.com/docker/docker/opts" + runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/docker/engine-api/types/swarm" ) type nodeOptions struct { + annotations role string membership string availability string } +type annotations struct { + name string + labels opts.ListOpts +} + +func newNodeOptions() *nodeOptions { + return &nodeOptions{ + annotations: annotations{ + labels: opts.NewListOpts(nil), + }, + } +} + func (opts *nodeOptions) ToNodeSpec() (swarm.NodeSpec, error) { var spec swarm.NodeSpec + spec.Annotations.Name = opts.annotations.name + spec.Annotations.Labels = runconfigopts.ConvertKVStringsToMap(opts.annotations.labels.GetAll()) + switch swarm.NodeRole(strings.ToLower(opts.role)) { case swarm.NodeRoleWorker: spec.Role = swarm.NodeRoleWorker diff --git a/api/client/node/promote.go b/api/client/node/promote.go index b3e0f4d7e5..760ec6f62b 100644 --- a/api/client/node/promote.go +++ b/api/client/node/promote.go @@ -21,8 +21,9 @@ func newPromoteCommand(dockerCli *client.DockerCli) *cobra.Command { } func runPromote(dockerCli *client.DockerCli, nodes []string) error { - promote := func(node *swarm.Node) { + promote := func(node *swarm.Node) error { node.Spec.Role = swarm.NodeRoleManager + return nil } success := func(nodeID string) { fmt.Fprintf(dockerCli.Out(), "Node %s promoted to a manager in the swarm.\n", nodeID) diff --git a/api/client/node/update.go b/api/client/node/update.go index 840182de0c..5cbdd908d8 100644 --- a/api/client/node/update.go +++ b/api/client/node/update.go @@ -5,6 +5,8 @@ import ( "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/spf13/cobra" "github.com/spf13/pflag" @@ -12,7 +14,7 @@ import ( ) func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command { - var opts nodeOptions + nodeOpts := newNodeOptions() cmd := &cobra.Command{ Use: "update [OPTIONS] NODE", @@ -24,9 +26,12 @@ func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command { } flags := cmd.Flags() - flags.StringVar(&opts.role, flagRole, "", "Role of the node (worker/manager)") - flags.StringVar(&opts.membership, flagMembership, "", "Membership of the node (accepted/rejected)") - flags.StringVar(&opts.availability, flagAvailability, "", "Availability of the node (active/pause/drain)") + flags.StringVar(&nodeOpts.role, flagRole, "", "Role of the node (worker/manager)") + flags.StringVar(&nodeOpts.membership, flagMembership, "", "Membership of the node (accepted/rejected)") + flags.StringVar(&nodeOpts.availability, flagAvailability, "", "Availability of the node (active/pause/drain)") + flags.Var(&nodeOpts.annotations.labels, flagLabelAdd, "Add or update a node label (key=value)") + labelKeys := opts.NewListOpts(nil) + flags.Var(&labelKeys, flagLabelRemove, "Remove a node label if exists") return cmd } @@ -37,7 +42,7 @@ func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, nodeID string) return updateNodes(dockerCli, []string{nodeID}, mergeNodeUpdate(flags), success) } -func updateNodes(dockerCli *client.DockerCli, nodes []string, mergeNode func(node *swarm.Node), success func(nodeID string)) error { +func updateNodes(dockerCli *client.DockerCli, nodes []string, mergeNode func(node *swarm.Node) error, success func(nodeID string)) error { client := dockerCli.Client() ctx := context.Background() @@ -47,7 +52,10 @@ func updateNodes(dockerCli *client.DockerCli, nodes []string, mergeNode func(nod return err } - mergeNode(&node) + err = mergeNode(&node) + if err != nil { + return err + } err = client.NodeUpdate(ctx, node.ID, node.Version, node.Spec) if err != nil { return err @@ -57,22 +65,51 @@ func updateNodes(dockerCli *client.DockerCli, nodes []string, mergeNode func(nod return nil } -func mergeNodeUpdate(flags *pflag.FlagSet) func(*swarm.Node) { - return func(node *swarm.Node) { +func mergeNodeUpdate(flags *pflag.FlagSet) func(*swarm.Node) error { + return func(node *swarm.Node) error { spec := &node.Spec if flags.Changed(flagRole) { - str, _ := flags.GetString(flagRole) + str, err := flags.GetString(flagRole) + if err != nil { + return err + } spec.Role = swarm.NodeRole(str) } if flags.Changed(flagMembership) { - str, _ := flags.GetString(flagMembership) + str, err := flags.GetString(flagMembership) + if err != nil { + return err + } spec.Membership = swarm.NodeMembership(str) } if flags.Changed(flagAvailability) { - str, _ := flags.GetString(flagAvailability) + str, err := flags.GetString(flagAvailability) + if err != nil { + return err + } spec.Availability = swarm.NodeAvailability(str) } + if spec.Annotations.Labels == nil { + spec.Annotations.Labels = make(map[string]string) + } + if flags.Changed(flagLabelAdd) { + labels := flags.Lookup(flagLabelAdd).Value.(*opts.ListOpts).GetAll() + for k, v := range runconfigopts.ConvertKVStringsToMap(labels) { + spec.Annotations.Labels[k] = v + } + } + if flags.Changed(flagLabelRemove) { + keys := flags.Lookup(flagLabelRemove).Value.(*opts.ListOpts).GetAll() + for _, k := range keys { + // if a key doesn't exist, fail the command explicitly + if _, exists := spec.Annotations.Labels[k]; !exists { + return fmt.Errorf("key %s doesn't exist in node's labels", k) + } + delete(spec.Annotations.Labels, k) + } + } + return nil } } @@ -80,4 +117,6 @@ const ( flagRole = "role" flagMembership = "membership" flagAvailability = "availability" + flagLabelAdd = "label-add" + flagLabelRemove = "label-rm" ) diff --git a/docs/reference/commandline/node_update.md b/docs/reference/commandline/node_update.md index 32e8582d2c..ed3dc60d02 100644 --- a/docs/reference/commandline/node_update.md +++ b/docs/reference/commandline/node_update.md @@ -19,6 +19,8 @@ Update a node Options: --availability string Availability of the node (active/pause/drain) --help Print usage + --label-add value Add or update a node label (key=value) (default []) + --label-rm value Remove a node label if exists (default []) --membership string Membership of the node (accepted/rejected) --role string Role of the node (worker/manager) ```