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