github.com/endophage/docker@v1.4.2-0.20161027011718-242853499895/cli/command/service/update.go (about)

     1  package service
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strings"
     7  	"time"
     8  
     9  	"golang.org/x/net/context"
    10  
    11  	"github.com/docker/docker/api/types"
    12  	mounttypes "github.com/docker/docker/api/types/mount"
    13  	"github.com/docker/docker/api/types/swarm"
    14  	"github.com/docker/docker/cli"
    15  	"github.com/docker/docker/cli/command"
    16  	"github.com/docker/docker/opts"
    17  	runconfigopts "github.com/docker/docker/runconfig/opts"
    18  	"github.com/docker/go-connections/nat"
    19  	shlex "github.com/flynn-archive/go-shlex"
    20  	"github.com/spf13/cobra"
    21  	"github.com/spf13/pflag"
    22  )
    23  
    24  func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command {
    25  	opts := newServiceOptions()
    26  
    27  	cmd := &cobra.Command{
    28  		Use:   "update [OPTIONS] SERVICE",
    29  		Short: "Update a service",
    30  		Args:  cli.ExactArgs(1),
    31  		RunE: func(cmd *cobra.Command, args []string) error {
    32  			return runUpdate(dockerCli, cmd.Flags(), args[0])
    33  		},
    34  	}
    35  
    36  	flags := cmd.Flags()
    37  	flags.String("image", "", "Service image tag")
    38  	flags.String("args", "", "Service command args")
    39  	flags.Bool("rollback", false, "Rollback to previous specification")
    40  	flags.Bool("force", false, "Force update even if no changes require it")
    41  	addServiceFlags(cmd, opts)
    42  
    43  	flags.Var(newListOptsVar(), flagEnvRemove, "Remove an environment variable")
    44  	flags.Var(newListOptsVar(), flagGroupRemove, "Remove previously added user groups from the container")
    45  	flags.Var(newListOptsVar(), flagLabelRemove, "Remove a label by its key")
    46  	flags.Var(newListOptsVar(), flagContainerLabelRemove, "Remove a container label by its key")
    47  	flags.Var(newListOptsVar(), flagMountRemove, "Remove a mount by its target path")
    48  	flags.Var(newListOptsVar(), flagPublishRemove, "Remove a published port by its target port")
    49  	flags.Var(newListOptsVar(), flagConstraintRemove, "Remove a constraint")
    50  	flags.Var(&opts.labels, flagLabelAdd, "Add or update service labels")
    51  	flags.Var(&opts.containerLabels, flagContainerLabelAdd, "Add or update container labels")
    52  	flags.Var(&opts.env, flagEnvAdd, "Add or update environment variables")
    53  	flags.Var(&opts.mounts, flagMountAdd, "Add or update a mount on a service")
    54  	flags.StringSliceVar(&opts.constraints, flagConstraintAdd, []string{}, "Add or update placement constraints")
    55  	flags.Var(&opts.endpoint.ports, flagPublishAdd, "Add or update a published port")
    56  	return cmd
    57  }
    58  
    59  func newListOptsVar() *opts.ListOpts {
    60  	return opts.NewListOptsRef(&[]string{}, nil)
    61  }
    62  
    63  func runUpdate(dockerCli *command.DockerCli, flags *pflag.FlagSet, serviceID string) error {
    64  	apiClient := dockerCli.Client()
    65  	ctx := context.Background()
    66  	updateOpts := types.ServiceUpdateOptions{}
    67  
    68  	service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID)
    69  	if err != nil {
    70  		return err
    71  	}
    72  
    73  	rollback, err := flags.GetBool("rollback")
    74  	if err != nil {
    75  		return err
    76  	}
    77  
    78  	spec := &service.Spec
    79  	if rollback {
    80  		spec = service.PreviousSpec
    81  		if spec == nil {
    82  			return fmt.Errorf("service does not have a previous specification to roll back to")
    83  		}
    84  	}
    85  
    86  	err = updateService(flags, spec)
    87  	if err != nil {
    88  		return err
    89  	}
    90  
    91  	// only send auth if flag was set
    92  	sendAuth, err := flags.GetBool(flagRegistryAuth)
    93  	if err != nil {
    94  		return err
    95  	}
    96  	if sendAuth {
    97  		// Retrieve encoded auth token from the image reference
    98  		// This would be the old image if it didn't change in this update
    99  		image := spec.TaskTemplate.ContainerSpec.Image
   100  		encodedAuth, err := command.RetrieveAuthTokenFromImage(ctx, dockerCli, image)
   101  		if err != nil {
   102  			return err
   103  		}
   104  		updateOpts.EncodedRegistryAuth = encodedAuth
   105  	} else if rollback {
   106  		updateOpts.RegistryAuthFrom = types.RegistryAuthFromPreviousSpec
   107  	} else {
   108  		updateOpts.RegistryAuthFrom = types.RegistryAuthFromSpec
   109  	}
   110  
   111  	err = apiClient.ServiceUpdate(ctx, service.ID, service.Version, *spec, updateOpts)
   112  	if err != nil {
   113  		return err
   114  	}
   115  
   116  	fmt.Fprintf(dockerCli.Out(), "%s\n", serviceID)
   117  	return nil
   118  }
   119  
   120  func updateService(flags *pflag.FlagSet, spec *swarm.ServiceSpec) error {
   121  	updateString := func(flag string, field *string) {
   122  		if flags.Changed(flag) {
   123  			*field, _ = flags.GetString(flag)
   124  		}
   125  	}
   126  
   127  	updateInt64Value := func(flag string, field *int64) {
   128  		if flags.Changed(flag) {
   129  			*field = flags.Lookup(flag).Value.(int64Value).Value()
   130  		}
   131  	}
   132  
   133  	updateFloat32 := func(flag string, field *float32) {
   134  		if flags.Changed(flag) {
   135  			*field, _ = flags.GetFloat32(flag)
   136  		}
   137  	}
   138  
   139  	updateDuration := func(flag string, field *time.Duration) {
   140  		if flags.Changed(flag) {
   141  			*field, _ = flags.GetDuration(flag)
   142  		}
   143  	}
   144  
   145  	updateDurationOpt := func(flag string, field **time.Duration) {
   146  		if flags.Changed(flag) {
   147  			val := *flags.Lookup(flag).Value.(*DurationOpt).Value()
   148  			*field = &val
   149  		}
   150  	}
   151  
   152  	updateUint64 := func(flag string, field *uint64) {
   153  		if flags.Changed(flag) {
   154  			*field, _ = flags.GetUint64(flag)
   155  		}
   156  	}
   157  
   158  	updateUint64Opt := func(flag string, field **uint64) {
   159  		if flags.Changed(flag) {
   160  			val := *flags.Lookup(flag).Value.(*Uint64Opt).Value()
   161  			*field = &val
   162  		}
   163  	}
   164  
   165  	cspec := &spec.TaskTemplate.ContainerSpec
   166  	task := &spec.TaskTemplate
   167  
   168  	taskResources := func() *swarm.ResourceRequirements {
   169  		if task.Resources == nil {
   170  			task.Resources = &swarm.ResourceRequirements{}
   171  		}
   172  		return task.Resources
   173  	}
   174  
   175  	updateString(flagName, &spec.Name)
   176  	updateLabels(flags, &spec.Labels)
   177  	updateContainerLabels(flags, &cspec.Labels)
   178  	updateString("image", &cspec.Image)
   179  	updateStringToSlice(flags, "args", &cspec.Args)
   180  	updateEnvironment(flags, &cspec.Env)
   181  	updateString(flagWorkdir, &cspec.Dir)
   182  	updateString(flagUser, &cspec.User)
   183  	updateMounts(flags, &cspec.Mounts)
   184  
   185  	if flags.Changed(flagLimitCPU) || flags.Changed(flagLimitMemory) {
   186  		taskResources().Limits = &swarm.Resources{}
   187  		updateInt64Value(flagLimitCPU, &task.Resources.Limits.NanoCPUs)
   188  		updateInt64Value(flagLimitMemory, &task.Resources.Limits.MemoryBytes)
   189  	}
   190  	if flags.Changed(flagReserveCPU) || flags.Changed(flagReserveMemory) {
   191  		taskResources().Reservations = &swarm.Resources{}
   192  		updateInt64Value(flagReserveCPU, &task.Resources.Reservations.NanoCPUs)
   193  		updateInt64Value(flagReserveMemory, &task.Resources.Reservations.MemoryBytes)
   194  	}
   195  
   196  	updateDurationOpt(flagStopGracePeriod, &cspec.StopGracePeriod)
   197  
   198  	if anyChanged(flags, flagRestartCondition, flagRestartDelay, flagRestartMaxAttempts, flagRestartWindow) {
   199  		if task.RestartPolicy == nil {
   200  			task.RestartPolicy = &swarm.RestartPolicy{}
   201  		}
   202  
   203  		if flags.Changed(flagRestartCondition) {
   204  			value, _ := flags.GetString(flagRestartCondition)
   205  			task.RestartPolicy.Condition = swarm.RestartPolicyCondition(value)
   206  		}
   207  		updateDurationOpt(flagRestartDelay, &task.RestartPolicy.Delay)
   208  		updateUint64Opt(flagRestartMaxAttempts, &task.RestartPolicy.MaxAttempts)
   209  		updateDurationOpt(flagRestartWindow, &task.RestartPolicy.Window)
   210  	}
   211  
   212  	if anyChanged(flags, flagConstraintAdd, flagConstraintRemove) {
   213  		if task.Placement == nil {
   214  			task.Placement = &swarm.Placement{}
   215  		}
   216  		updatePlacement(flags, task.Placement)
   217  	}
   218  
   219  	if err := updateReplicas(flags, &spec.Mode); err != nil {
   220  		return err
   221  	}
   222  
   223  	if anyChanged(flags, flagUpdateParallelism, flagUpdateDelay, flagUpdateMonitor, flagUpdateFailureAction, flagUpdateMaxFailureRatio) {
   224  		if spec.UpdateConfig == nil {
   225  			spec.UpdateConfig = &swarm.UpdateConfig{}
   226  		}
   227  		updateUint64(flagUpdateParallelism, &spec.UpdateConfig.Parallelism)
   228  		updateDuration(flagUpdateDelay, &spec.UpdateConfig.Delay)
   229  		updateDuration(flagUpdateMonitor, &spec.UpdateConfig.Monitor)
   230  		updateString(flagUpdateFailureAction, &spec.UpdateConfig.FailureAction)
   231  		updateFloat32(flagUpdateMaxFailureRatio, &spec.UpdateConfig.MaxFailureRatio)
   232  	}
   233  
   234  	if flags.Changed(flagEndpointMode) {
   235  		value, _ := flags.GetString(flagEndpointMode)
   236  		if spec.EndpointSpec == nil {
   237  			spec.EndpointSpec = &swarm.EndpointSpec{}
   238  		}
   239  		spec.EndpointSpec.Mode = swarm.ResolutionMode(value)
   240  	}
   241  
   242  	if anyChanged(flags, flagGroupAdd, flagGroupRemove) {
   243  		if err := updateGroups(flags, &cspec.Groups); err != nil {
   244  			return err
   245  		}
   246  	}
   247  
   248  	if anyChanged(flags, flagPublishAdd, flagPublishRemove) {
   249  		if spec.EndpointSpec == nil {
   250  			spec.EndpointSpec = &swarm.EndpointSpec{}
   251  		}
   252  		if err := updatePorts(flags, &spec.EndpointSpec.Ports); err != nil {
   253  			return err
   254  		}
   255  	}
   256  
   257  	if err := updateLogDriver(flags, &spec.TaskTemplate); err != nil {
   258  		return err
   259  	}
   260  
   261  	force, err := flags.GetBool("force")
   262  	if err != nil {
   263  		return err
   264  	}
   265  
   266  	if force {
   267  		spec.TaskTemplate.ForceUpdate++
   268  	}
   269  
   270  	return nil
   271  }
   272  
   273  func updateStringToSlice(flags *pflag.FlagSet, flag string, field *[]string) error {
   274  	if !flags.Changed(flag) {
   275  		return nil
   276  	}
   277  
   278  	value, _ := flags.GetString(flag)
   279  	valueSlice, err := shlex.Split(value)
   280  	*field = valueSlice
   281  	return err
   282  }
   283  
   284  func anyChanged(flags *pflag.FlagSet, fields ...string) bool {
   285  	for _, flag := range fields {
   286  		if flags.Changed(flag) {
   287  			return true
   288  		}
   289  	}
   290  	return false
   291  }
   292  
   293  func updatePlacement(flags *pflag.FlagSet, placement *swarm.Placement) {
   294  	field, _ := flags.GetStringSlice(flagConstraintAdd)
   295  	placement.Constraints = append(placement.Constraints, field...)
   296  
   297  	toRemove := buildToRemoveSet(flags, flagConstraintRemove)
   298  	placement.Constraints = removeItems(placement.Constraints, toRemove, itemKey)
   299  }
   300  
   301  func updateContainerLabels(flags *pflag.FlagSet, field *map[string]string) {
   302  	if flags.Changed(flagContainerLabelAdd) {
   303  		if *field == nil {
   304  			*field = map[string]string{}
   305  		}
   306  
   307  		values := flags.Lookup(flagContainerLabelAdd).Value.(*opts.ListOpts).GetAll()
   308  		for key, value := range runconfigopts.ConvertKVStringsToMap(values) {
   309  			(*field)[key] = value
   310  		}
   311  	}
   312  
   313  	if *field != nil && flags.Changed(flagContainerLabelRemove) {
   314  		toRemove := flags.Lookup(flagContainerLabelRemove).Value.(*opts.ListOpts).GetAll()
   315  		for _, label := range toRemove {
   316  			delete(*field, label)
   317  		}
   318  	}
   319  }
   320  
   321  func updateLabels(flags *pflag.FlagSet, field *map[string]string) {
   322  	if flags.Changed(flagLabelAdd) {
   323  		if *field == nil {
   324  			*field = map[string]string{}
   325  		}
   326  
   327  		values := flags.Lookup(flagLabelAdd).Value.(*opts.ListOpts).GetAll()
   328  		for key, value := range runconfigopts.ConvertKVStringsToMap(values) {
   329  			(*field)[key] = value
   330  		}
   331  	}
   332  
   333  	if *field != nil && flags.Changed(flagLabelRemove) {
   334  		toRemove := flags.Lookup(flagLabelRemove).Value.(*opts.ListOpts).GetAll()
   335  		for _, label := range toRemove {
   336  			delete(*field, label)
   337  		}
   338  	}
   339  }
   340  
   341  func updateEnvironment(flags *pflag.FlagSet, field *[]string) {
   342  	envSet := map[string]string{}
   343  	for _, v := range *field {
   344  		envSet[envKey(v)] = v
   345  	}
   346  	if flags.Changed(flagEnvAdd) {
   347  		value := flags.Lookup(flagEnvAdd).Value.(*opts.ListOpts)
   348  		for _, v := range value.GetAll() {
   349  			envSet[envKey(v)] = v
   350  		}
   351  	}
   352  
   353  	*field = []string{}
   354  	for _, v := range envSet {
   355  		*field = append(*field, v)
   356  	}
   357  
   358  	toRemove := buildToRemoveSet(flags, flagEnvRemove)
   359  	*field = removeItems(*field, toRemove, envKey)
   360  }
   361  
   362  func envKey(value string) string {
   363  	kv := strings.SplitN(value, "=", 2)
   364  	return kv[0]
   365  }
   366  
   367  func itemKey(value string) string {
   368  	return value
   369  }
   370  
   371  func buildToRemoveSet(flags *pflag.FlagSet, flag string) map[string]struct{} {
   372  	var empty struct{}
   373  	toRemove := make(map[string]struct{})
   374  
   375  	if !flags.Changed(flag) {
   376  		return toRemove
   377  	}
   378  
   379  	toRemoveSlice := flags.Lookup(flag).Value.(*opts.ListOpts).GetAll()
   380  	for _, key := range toRemoveSlice {
   381  		toRemove[key] = empty
   382  	}
   383  	return toRemove
   384  }
   385  
   386  func removeItems(
   387  	seq []string,
   388  	toRemove map[string]struct{},
   389  	keyFunc func(string) string,
   390  ) []string {
   391  	newSeq := []string{}
   392  	for _, item := range seq {
   393  		if _, exists := toRemove[keyFunc(item)]; !exists {
   394  			newSeq = append(newSeq, item)
   395  		}
   396  	}
   397  	return newSeq
   398  }
   399  
   400  func updateMounts(flags *pflag.FlagSet, mounts *[]mounttypes.Mount) {
   401  	if flags.Changed(flagMountAdd) {
   402  		values := flags.Lookup(flagMountAdd).Value.(*MountOpt).Value()
   403  		*mounts = append(*mounts, values...)
   404  	}
   405  	toRemove := buildToRemoveSet(flags, flagMountRemove)
   406  
   407  	newMounts := []mounttypes.Mount{}
   408  	for _, mount := range *mounts {
   409  		if _, exists := toRemove[mount.Target]; !exists {
   410  			newMounts = append(newMounts, mount)
   411  		}
   412  	}
   413  	*mounts = newMounts
   414  }
   415  
   416  func updateGroups(flags *pflag.FlagSet, groups *[]string) error {
   417  	if flags.Changed(flagGroupAdd) {
   418  		values, err := flags.GetStringSlice(flagGroupAdd)
   419  		if err != nil {
   420  			return err
   421  		}
   422  		*groups = append(*groups, values...)
   423  	}
   424  	toRemove := buildToRemoveSet(flags, flagGroupRemove)
   425  
   426  	newGroups := []string{}
   427  	for _, group := range *groups {
   428  		if _, exists := toRemove[group]; !exists {
   429  			newGroups = append(newGroups, group)
   430  		}
   431  	}
   432  	// Sort so that result is predictable.
   433  	sort.Strings(newGroups)
   434  
   435  	*groups = newGroups
   436  	return nil
   437  }
   438  
   439  type byPortConfig []swarm.PortConfig
   440  
   441  func (r byPortConfig) Len() int      { return len(r) }
   442  func (r byPortConfig) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
   443  func (r byPortConfig) Less(i, j int) bool {
   444  	// We convert PortConfig into `port/protocol`, e.g., `80/tcp`
   445  	// In updatePorts we already filter out with map so there is duplicate entries
   446  	return portConfigToString(&r[i]) < portConfigToString(&r[j])
   447  }
   448  
   449  func portConfigToString(portConfig *swarm.PortConfig) string {
   450  	protocol := portConfig.Protocol
   451  	if protocol == "" {
   452  		protocol = "tcp"
   453  	}
   454  	return fmt.Sprintf("%v/%s", portConfig.PublishedPort, protocol)
   455  }
   456  
   457  func updatePorts(flags *pflag.FlagSet, portConfig *[]swarm.PortConfig) error {
   458  	// The key of the map is `port/protocol`, e.g., `80/tcp`
   459  	portSet := map[string]swarm.PortConfig{}
   460  	// Check to see if there are any conflict in flags.
   461  	if flags.Changed(flagPublishAdd) {
   462  		values := flags.Lookup(flagPublishAdd).Value.(*opts.ListOpts).GetAll()
   463  		ports, portBindings, _ := nat.ParsePortSpecs(values)
   464  
   465  		for port := range ports {
   466  			newConfigs := convertPortToPortConfig(port, portBindings)
   467  			for _, entry := range newConfigs {
   468  				if v, ok := portSet[portConfigToString(&entry)]; ok && v != entry {
   469  					return fmt.Errorf("conflicting port mapping between %v:%v/%s and %v:%v/%s", entry.PublishedPort, entry.TargetPort, entry.Protocol, v.PublishedPort, v.TargetPort, v.Protocol)
   470  				}
   471  				portSet[portConfigToString(&entry)] = entry
   472  			}
   473  		}
   474  	}
   475  
   476  	// Override previous PortConfig in service if there is any duplicate
   477  	for _, entry := range *portConfig {
   478  		if _, ok := portSet[portConfigToString(&entry)]; !ok {
   479  			portSet[portConfigToString(&entry)] = entry
   480  		}
   481  	}
   482  
   483  	toRemove := flags.Lookup(flagPublishRemove).Value.(*opts.ListOpts).GetAll()
   484  	newPorts := []swarm.PortConfig{}
   485  portLoop:
   486  	for _, port := range portSet {
   487  		for _, rawTargetPort := range toRemove {
   488  			targetPort := nat.Port(rawTargetPort)
   489  			if equalPort(targetPort, port) {
   490  				continue portLoop
   491  			}
   492  		}
   493  		newPorts = append(newPorts, port)
   494  	}
   495  	// Sort the PortConfig to avoid unnecessary updates
   496  	sort.Sort(byPortConfig(newPorts))
   497  	*portConfig = newPorts
   498  	return nil
   499  }
   500  
   501  func equalPort(targetPort nat.Port, port swarm.PortConfig) bool {
   502  	return (string(port.Protocol) == targetPort.Proto() &&
   503  		port.TargetPort == uint32(targetPort.Int()))
   504  }
   505  
   506  func updateReplicas(flags *pflag.FlagSet, serviceMode *swarm.ServiceMode) error {
   507  	if !flags.Changed(flagReplicas) {
   508  		return nil
   509  	}
   510  
   511  	if serviceMode == nil || serviceMode.Replicated == nil {
   512  		return fmt.Errorf("replicas can only be used with replicated mode")
   513  	}
   514  	serviceMode.Replicated.Replicas = flags.Lookup(flagReplicas).Value.(*Uint64Opt).Value()
   515  	return nil
   516  }
   517  
   518  // updateLogDriver updates the log driver only if the log driver flag is set.
   519  // All options will be replaced with those provided on the command line.
   520  func updateLogDriver(flags *pflag.FlagSet, taskTemplate *swarm.TaskSpec) error {
   521  	if !flags.Changed(flagLogDriver) {
   522  		return nil
   523  	}
   524  
   525  	name, err := flags.GetString(flagLogDriver)
   526  	if err != nil {
   527  		return err
   528  	}
   529  
   530  	if name == "" {
   531  		return nil
   532  	}
   533  
   534  	taskTemplate.LogDriver = &swarm.Driver{
   535  		Name:    name,
   536  		Options: runconfigopts.ConvertKVStringsToMap(flags.Lookup(flagLogOpt).Value.(*opts.ListOpts).GetAll()),
   537  	}
   538  
   539  	return nil
   540  }