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