github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/cli/command/service/update.go (about)

     1  package service
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/docker/docker/api/types"
    10  	"github.com/docker/docker/api/types/container"
    11  	mounttypes "github.com/docker/docker/api/types/mount"
    12  	"github.com/docker/docker/api/types/swarm"
    13  	"github.com/docker/docker/cli"
    14  	"github.com/docker/docker/cli/command"
    15  	"github.com/docker/docker/client"
    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  	"golang.org/x/net/context"
    23  )
    24  
    25  func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command {
    26  	serviceOpts := newServiceOptions()
    27  
    28  	cmd := &cobra.Command{
    29  		Use:   "update [OPTIONS] SERVICE",
    30  		Short: "Update a service",
    31  		Args:  cli.ExactArgs(1),
    32  		RunE: func(cmd *cobra.Command, args []string) error {
    33  			return runUpdate(dockerCli, cmd.Flags(), args[0])
    34  		},
    35  	}
    36  
    37  	flags := cmd.Flags()
    38  	flags.String("image", "", "Service image tag")
    39  	flags.String("args", "", "Service command args")
    40  	flags.Bool("rollback", false, "Rollback to previous specification")
    41  	flags.SetAnnotation("rollback", "version", []string{"1.25"})
    42  	flags.Bool("force", false, "Force update even if no changes require it")
    43  	flags.SetAnnotation("force", "version", []string{"1.25"})
    44  	addServiceFlags(cmd, serviceOpts)
    45  
    46  	flags.Var(newListOptsVar(), flagEnvRemove, "Remove an environment variable")
    47  	flags.Var(newListOptsVar(), flagGroupRemove, "Remove a previously added supplementary user group from the container")
    48  	flags.SetAnnotation(flagGroupRemove, "version", []string{"1.25"})
    49  	flags.Var(newListOptsVar(), flagLabelRemove, "Remove a label by its key")
    50  	flags.Var(newListOptsVar(), flagContainerLabelRemove, "Remove a container label by its key")
    51  	flags.Var(newListOptsVar(), flagMountRemove, "Remove a mount by its target path")
    52  	// flags.Var(newListOptsVar().WithValidator(validatePublishRemove), flagPublishRemove, "Remove a published port by its target port")
    53  	flags.Var(&opts.PortOpt{}, flagPublishRemove, "Remove a published port by its target port")
    54  	flags.Var(newListOptsVar(), flagConstraintRemove, "Remove a constraint")
    55  	flags.Var(newListOptsVar(), flagDNSRemove, "Remove a custom DNS server")
    56  	flags.SetAnnotation(flagDNSRemove, "version", []string{"1.25"})
    57  	flags.Var(newListOptsVar(), flagDNSOptionRemove, "Remove a DNS option")
    58  	flags.SetAnnotation(flagDNSOptionRemove, "version", []string{"1.25"})
    59  	flags.Var(newListOptsVar(), flagDNSSearchRemove, "Remove a DNS search domain")
    60  	flags.SetAnnotation(flagDNSSearchRemove, "version", []string{"1.25"})
    61  	flags.Var(newListOptsVar(), flagHostRemove, "Remove a custom host-to-IP mapping (host:ip)")
    62  	flags.SetAnnotation(flagHostRemove, "version", []string{"1.25"})
    63  	flags.Var(&serviceOpts.labels, flagLabelAdd, "Add or update a service label")
    64  	flags.Var(&serviceOpts.containerLabels, flagContainerLabelAdd, "Add or update a container label")
    65  	flags.Var(&serviceOpts.env, flagEnvAdd, "Add or update an environment variable")
    66  	flags.Var(newListOptsVar(), flagSecretRemove, "Remove a secret")
    67  	flags.SetAnnotation(flagSecretRemove, "version", []string{"1.25"})
    68  	flags.Var(&serviceOpts.secrets, flagSecretAdd, "Add or update a secret on a service")
    69  	flags.SetAnnotation(flagSecretAdd, "version", []string{"1.25"})
    70  	flags.Var(&serviceOpts.mounts, flagMountAdd, "Add or update a mount on a service")
    71  	flags.Var(&serviceOpts.constraints, flagConstraintAdd, "Add or update a placement constraint")
    72  	flags.Var(&serviceOpts.endpoint.publishPorts, flagPublishAdd, "Add or update a published port")
    73  	flags.Var(&serviceOpts.groups, flagGroupAdd, "Add an additional supplementary user group to the container")
    74  	flags.SetAnnotation(flagGroupAdd, "version", []string{"1.25"})
    75  	flags.Var(&serviceOpts.dns, flagDNSAdd, "Add or update a custom DNS server")
    76  	flags.SetAnnotation(flagDNSAdd, "version", []string{"1.25"})
    77  	flags.Var(&serviceOpts.dnsOption, flagDNSOptionAdd, "Add or update a DNS option")
    78  	flags.SetAnnotation(flagDNSOptionAdd, "version", []string{"1.25"})
    79  	flags.Var(&serviceOpts.dnsSearch, flagDNSSearchAdd, "Add or update a custom DNS search domain")
    80  	flags.SetAnnotation(flagDNSSearchAdd, "version", []string{"1.25"})
    81  	flags.Var(&serviceOpts.hosts, flagHostAdd, "Add or update a custom host-to-IP mapping (host:ip)")
    82  	flags.SetAnnotation(flagHostAdd, "version", []string{"1.25"})
    83  
    84  	return cmd
    85  }
    86  
    87  func newListOptsVar() *opts.ListOpts {
    88  	return opts.NewListOptsRef(&[]string{}, nil)
    89  }
    90  
    91  func runUpdate(dockerCli *command.DockerCli, flags *pflag.FlagSet, serviceID string) error {
    92  	apiClient := dockerCli.Client()
    93  	ctx := context.Background()
    94  	updateOpts := types.ServiceUpdateOptions{}
    95  
    96  	service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID)
    97  	if err != nil {
    98  		return err
    99  	}
   100  
   101  	rollback, err := flags.GetBool("rollback")
   102  	if err != nil {
   103  		return err
   104  	}
   105  
   106  	spec := &service.Spec
   107  	if rollback {
   108  		spec = service.PreviousSpec
   109  		if spec == nil {
   110  			return fmt.Errorf("service does not have a previous specification to roll back to")
   111  		}
   112  	}
   113  
   114  	err = updateService(flags, spec)
   115  	if err != nil {
   116  		return err
   117  	}
   118  
   119  	if flags.Changed("image") {
   120  		if err := resolveServiceImageDigest(dockerCli, spec); err != nil {
   121  			return err
   122  		}
   123  	}
   124  
   125  	updatedSecrets, err := getUpdatedSecrets(apiClient, flags, spec.TaskTemplate.ContainerSpec.Secrets)
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	spec.TaskTemplate.ContainerSpec.Secrets = updatedSecrets
   131  
   132  	// only send auth if flag was set
   133  	sendAuth, err := flags.GetBool(flagRegistryAuth)
   134  	if err != nil {
   135  		return err
   136  	}
   137  	if sendAuth {
   138  		// Retrieve encoded auth token from the image reference
   139  		// This would be the old image if it didn't change in this update
   140  		image := spec.TaskTemplate.ContainerSpec.Image
   141  		encodedAuth, err := command.RetrieveAuthTokenFromImage(ctx, dockerCli, image)
   142  		if err != nil {
   143  			return err
   144  		}
   145  		updateOpts.EncodedRegistryAuth = encodedAuth
   146  	} else if rollback {
   147  		updateOpts.RegistryAuthFrom = types.RegistryAuthFromPreviousSpec
   148  	} else {
   149  		updateOpts.RegistryAuthFrom = types.RegistryAuthFromSpec
   150  	}
   151  
   152  	response, err := apiClient.ServiceUpdate(ctx, service.ID, service.Version, *spec, updateOpts)
   153  	if err != nil {
   154  		return err
   155  	}
   156  
   157  	for _, warning := range response.Warnings {
   158  		fmt.Fprintln(dockerCli.Err(), warning)
   159  	}
   160  
   161  	fmt.Fprintf(dockerCli.Out(), "%s\n", serviceID)
   162  	return nil
   163  }
   164  
   165  func updateService(flags *pflag.FlagSet, spec *swarm.ServiceSpec) error {
   166  	updateString := func(flag string, field *string) {
   167  		if flags.Changed(flag) {
   168  			*field, _ = flags.GetString(flag)
   169  		}
   170  	}
   171  
   172  	updateInt64Value := func(flag string, field *int64) {
   173  		if flags.Changed(flag) {
   174  			*field = flags.Lookup(flag).Value.(int64Value).Value()
   175  		}
   176  	}
   177  
   178  	updateFloatValue := func(flag string, field *float32) {
   179  		if flags.Changed(flag) {
   180  			*field = flags.Lookup(flag).Value.(*floatValue).Value()
   181  		}
   182  	}
   183  
   184  	updateDuration := func(flag string, field *time.Duration) {
   185  		if flags.Changed(flag) {
   186  			*field, _ = flags.GetDuration(flag)
   187  		}
   188  	}
   189  
   190  	updateDurationOpt := func(flag string, field **time.Duration) {
   191  		if flags.Changed(flag) {
   192  			val := *flags.Lookup(flag).Value.(*DurationOpt).Value()
   193  			*field = &val
   194  		}
   195  	}
   196  
   197  	updateUint64 := func(flag string, field *uint64) {
   198  		if flags.Changed(flag) {
   199  			*field, _ = flags.GetUint64(flag)
   200  		}
   201  	}
   202  
   203  	updateUint64Opt := func(flag string, field **uint64) {
   204  		if flags.Changed(flag) {
   205  			val := *flags.Lookup(flag).Value.(*Uint64Opt).Value()
   206  			*field = &val
   207  		}
   208  	}
   209  
   210  	cspec := &spec.TaskTemplate.ContainerSpec
   211  	task := &spec.TaskTemplate
   212  
   213  	taskResources := func() *swarm.ResourceRequirements {
   214  		if task.Resources == nil {
   215  			task.Resources = &swarm.ResourceRequirements{}
   216  		}
   217  		return task.Resources
   218  	}
   219  
   220  	updateLabels(flags, &spec.Labels)
   221  	updateContainerLabels(flags, &cspec.Labels)
   222  	updateString("image", &cspec.Image)
   223  	updateStringToSlice(flags, "args", &cspec.Args)
   224  	updateEnvironment(flags, &cspec.Env)
   225  	updateString(flagWorkdir, &cspec.Dir)
   226  	updateString(flagUser, &cspec.User)
   227  	updateString(flagHostname, &cspec.Hostname)
   228  	if err := updateMounts(flags, &cspec.Mounts); err != nil {
   229  		return err
   230  	}
   231  
   232  	if flags.Changed(flagLimitCPU) || flags.Changed(flagLimitMemory) {
   233  		taskResources().Limits = &swarm.Resources{}
   234  		updateInt64Value(flagLimitCPU, &task.Resources.Limits.NanoCPUs)
   235  		updateInt64Value(flagLimitMemory, &task.Resources.Limits.MemoryBytes)
   236  	}
   237  	if flags.Changed(flagReserveCPU) || flags.Changed(flagReserveMemory) {
   238  		taskResources().Reservations = &swarm.Resources{}
   239  		updateInt64Value(flagReserveCPU, &task.Resources.Reservations.NanoCPUs)
   240  		updateInt64Value(flagReserveMemory, &task.Resources.Reservations.MemoryBytes)
   241  	}
   242  
   243  	updateDurationOpt(flagStopGracePeriod, &cspec.StopGracePeriod)
   244  
   245  	if anyChanged(flags, flagRestartCondition, flagRestartDelay, flagRestartMaxAttempts, flagRestartWindow) {
   246  		if task.RestartPolicy == nil {
   247  			task.RestartPolicy = &swarm.RestartPolicy{}
   248  		}
   249  
   250  		if flags.Changed(flagRestartCondition) {
   251  			value, _ := flags.GetString(flagRestartCondition)
   252  			task.RestartPolicy.Condition = swarm.RestartPolicyCondition(value)
   253  		}
   254  		updateDurationOpt(flagRestartDelay, &task.RestartPolicy.Delay)
   255  		updateUint64Opt(flagRestartMaxAttempts, &task.RestartPolicy.MaxAttempts)
   256  		updateDurationOpt(flagRestartWindow, &task.RestartPolicy.Window)
   257  	}
   258  
   259  	if anyChanged(flags, flagConstraintAdd, flagConstraintRemove) {
   260  		if task.Placement == nil {
   261  			task.Placement = &swarm.Placement{}
   262  		}
   263  		updatePlacement(flags, task.Placement)
   264  	}
   265  
   266  	if err := updateReplicas(flags, &spec.Mode); err != nil {
   267  		return err
   268  	}
   269  
   270  	if anyChanged(flags, flagUpdateParallelism, flagUpdateDelay, flagUpdateMonitor, flagUpdateFailureAction, flagUpdateMaxFailureRatio) {
   271  		if spec.UpdateConfig == nil {
   272  			spec.UpdateConfig = &swarm.UpdateConfig{}
   273  		}
   274  		updateUint64(flagUpdateParallelism, &spec.UpdateConfig.Parallelism)
   275  		updateDuration(flagUpdateDelay, &spec.UpdateConfig.Delay)
   276  		updateDuration(flagUpdateMonitor, &spec.UpdateConfig.Monitor)
   277  		updateString(flagUpdateFailureAction, &spec.UpdateConfig.FailureAction)
   278  		updateFloatValue(flagUpdateMaxFailureRatio, &spec.UpdateConfig.MaxFailureRatio)
   279  	}
   280  
   281  	if flags.Changed(flagEndpointMode) {
   282  		value, _ := flags.GetString(flagEndpointMode)
   283  		if spec.EndpointSpec == nil {
   284  			spec.EndpointSpec = &swarm.EndpointSpec{}
   285  		}
   286  		spec.EndpointSpec.Mode = swarm.ResolutionMode(value)
   287  	}
   288  
   289  	if anyChanged(flags, flagGroupAdd, flagGroupRemove) {
   290  		if err := updateGroups(flags, &cspec.Groups); err != nil {
   291  			return err
   292  		}
   293  	}
   294  
   295  	if anyChanged(flags, flagPublishAdd, flagPublishRemove) {
   296  		if spec.EndpointSpec == nil {
   297  			spec.EndpointSpec = &swarm.EndpointSpec{}
   298  		}
   299  		if err := updatePorts(flags, &spec.EndpointSpec.Ports); err != nil {
   300  			return err
   301  		}
   302  	}
   303  
   304  	if anyChanged(flags, flagDNSAdd, flagDNSRemove, flagDNSOptionAdd, flagDNSOptionRemove, flagDNSSearchAdd, flagDNSSearchRemove) {
   305  		if cspec.DNSConfig == nil {
   306  			cspec.DNSConfig = &swarm.DNSConfig{}
   307  		}
   308  		if err := updateDNSConfig(flags, &cspec.DNSConfig); err != nil {
   309  			return err
   310  		}
   311  	}
   312  
   313  	if anyChanged(flags, flagHostAdd, flagHostRemove) {
   314  		if err := updateHosts(flags, &cspec.Hosts); err != nil {
   315  			return err
   316  		}
   317  	}
   318  
   319  	if err := updateLogDriver(flags, &spec.TaskTemplate); err != nil {
   320  		return err
   321  	}
   322  
   323  	force, err := flags.GetBool("force")
   324  	if err != nil {
   325  		return err
   326  	}
   327  
   328  	if force {
   329  		spec.TaskTemplate.ForceUpdate++
   330  	}
   331  
   332  	if err := updateHealthcheck(flags, cspec); err != nil {
   333  		return err
   334  	}
   335  
   336  	if flags.Changed(flagTTY) {
   337  		tty, err := flags.GetBool(flagTTY)
   338  		if err != nil {
   339  			return err
   340  		}
   341  		cspec.TTY = tty
   342  	}
   343  
   344  	if flags.Changed(flagReadOnly) {
   345  		readOnly, err := flags.GetBool(flagReadOnly)
   346  		if err != nil {
   347  			return err
   348  		}
   349  		cspec.ReadOnly = readOnly
   350  	}
   351  
   352  	return nil
   353  }
   354  
   355  func updateStringToSlice(flags *pflag.FlagSet, flag string, field *[]string) error {
   356  	if !flags.Changed(flag) {
   357  		return nil
   358  	}
   359  
   360  	value, _ := flags.GetString(flag)
   361  	valueSlice, err := shlex.Split(value)
   362  	*field = valueSlice
   363  	return err
   364  }
   365  
   366  func anyChanged(flags *pflag.FlagSet, fields ...string) bool {
   367  	for _, flag := range fields {
   368  		if flags.Changed(flag) {
   369  			return true
   370  		}
   371  	}
   372  	return false
   373  }
   374  
   375  func updatePlacement(flags *pflag.FlagSet, placement *swarm.Placement) {
   376  	if flags.Changed(flagConstraintAdd) {
   377  		values := flags.Lookup(flagConstraintAdd).Value.(*opts.ListOpts).GetAll()
   378  		placement.Constraints = append(placement.Constraints, values...)
   379  	}
   380  	toRemove := buildToRemoveSet(flags, flagConstraintRemove)
   381  
   382  	newConstraints := []string{}
   383  	for _, constraint := range placement.Constraints {
   384  		if _, exists := toRemove[constraint]; !exists {
   385  			newConstraints = append(newConstraints, constraint)
   386  		}
   387  	}
   388  	// Sort so that result is predictable.
   389  	sort.Strings(newConstraints)
   390  
   391  	placement.Constraints = newConstraints
   392  }
   393  
   394  func updateContainerLabels(flags *pflag.FlagSet, field *map[string]string) {
   395  	if flags.Changed(flagContainerLabelAdd) {
   396  		if *field == nil {
   397  			*field = map[string]string{}
   398  		}
   399  
   400  		values := flags.Lookup(flagContainerLabelAdd).Value.(*opts.ListOpts).GetAll()
   401  		for key, value := range runconfigopts.ConvertKVStringsToMap(values) {
   402  			(*field)[key] = value
   403  		}
   404  	}
   405  
   406  	if *field != nil && flags.Changed(flagContainerLabelRemove) {
   407  		toRemove := flags.Lookup(flagContainerLabelRemove).Value.(*opts.ListOpts).GetAll()
   408  		for _, label := range toRemove {
   409  			delete(*field, label)
   410  		}
   411  	}
   412  }
   413  
   414  func updateLabels(flags *pflag.FlagSet, field *map[string]string) {
   415  	if flags.Changed(flagLabelAdd) {
   416  		if *field == nil {
   417  			*field = map[string]string{}
   418  		}
   419  
   420  		values := flags.Lookup(flagLabelAdd).Value.(*opts.ListOpts).GetAll()
   421  		for key, value := range runconfigopts.ConvertKVStringsToMap(values) {
   422  			(*field)[key] = value
   423  		}
   424  	}
   425  
   426  	if *field != nil && flags.Changed(flagLabelRemove) {
   427  		toRemove := flags.Lookup(flagLabelRemove).Value.(*opts.ListOpts).GetAll()
   428  		for _, label := range toRemove {
   429  			delete(*field, label)
   430  		}
   431  	}
   432  }
   433  
   434  func updateEnvironment(flags *pflag.FlagSet, field *[]string) {
   435  	envSet := map[string]string{}
   436  	for _, v := range *field {
   437  		envSet[envKey(v)] = v
   438  	}
   439  	if flags.Changed(flagEnvAdd) {
   440  		value := flags.Lookup(flagEnvAdd).Value.(*opts.ListOpts)
   441  		for _, v := range value.GetAll() {
   442  			envSet[envKey(v)] = v
   443  		}
   444  	}
   445  
   446  	*field = []string{}
   447  	for _, v := range envSet {
   448  		*field = append(*field, v)
   449  	}
   450  
   451  	toRemove := buildToRemoveSet(flags, flagEnvRemove)
   452  	*field = removeItems(*field, toRemove, envKey)
   453  }
   454  
   455  func getUpdatedSecrets(apiClient client.SecretAPIClient, flags *pflag.FlagSet, secrets []*swarm.SecretReference) ([]*swarm.SecretReference, error) {
   456  	newSecrets := []*swarm.SecretReference{}
   457  
   458  	toRemove := buildToRemoveSet(flags, flagSecretRemove)
   459  	for _, secret := range secrets {
   460  		if _, exists := toRemove[secret.SecretName]; !exists {
   461  			newSecrets = append(newSecrets, secret)
   462  		}
   463  	}
   464  
   465  	if flags.Changed(flagSecretAdd) {
   466  		values := flags.Lookup(flagSecretAdd).Value.(*opts.SecretOpt).Value()
   467  
   468  		addSecrets, err := ParseSecrets(apiClient, values)
   469  		if err != nil {
   470  			return nil, err
   471  		}
   472  		newSecrets = append(newSecrets, addSecrets...)
   473  	}
   474  
   475  	return newSecrets, nil
   476  }
   477  
   478  func envKey(value string) string {
   479  	kv := strings.SplitN(value, "=", 2)
   480  	return kv[0]
   481  }
   482  
   483  func itemKey(value string) string {
   484  	return value
   485  }
   486  
   487  func buildToRemoveSet(flags *pflag.FlagSet, flag string) map[string]struct{} {
   488  	var empty struct{}
   489  	toRemove := make(map[string]struct{})
   490  
   491  	if !flags.Changed(flag) {
   492  		return toRemove
   493  	}
   494  
   495  	toRemoveSlice := flags.Lookup(flag).Value.(*opts.ListOpts).GetAll()
   496  	for _, key := range toRemoveSlice {
   497  		toRemove[key] = empty
   498  	}
   499  	return toRemove
   500  }
   501  
   502  func removeItems(
   503  	seq []string,
   504  	toRemove map[string]struct{},
   505  	keyFunc func(string) string,
   506  ) []string {
   507  	newSeq := []string{}
   508  	for _, item := range seq {
   509  		if _, exists := toRemove[keyFunc(item)]; !exists {
   510  			newSeq = append(newSeq, item)
   511  		}
   512  	}
   513  	return newSeq
   514  }
   515  
   516  type byMountSource []mounttypes.Mount
   517  
   518  func (m byMountSource) Len() int      { return len(m) }
   519  func (m byMountSource) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
   520  func (m byMountSource) Less(i, j int) bool {
   521  	a, b := m[i], m[j]
   522  
   523  	if a.Source == b.Source {
   524  		return a.Target < b.Target
   525  	}
   526  
   527  	return a.Source < b.Source
   528  }
   529  
   530  func updateMounts(flags *pflag.FlagSet, mounts *[]mounttypes.Mount) error {
   531  
   532  	mountsByTarget := map[string]mounttypes.Mount{}
   533  
   534  	if flags.Changed(flagMountAdd) {
   535  		values := flags.Lookup(flagMountAdd).Value.(*opts.MountOpt).Value()
   536  		for _, mount := range values {
   537  			if _, ok := mountsByTarget[mount.Target]; ok {
   538  				return fmt.Errorf("duplicate mount target")
   539  			}
   540  			mountsByTarget[mount.Target] = mount
   541  		}
   542  	}
   543  
   544  	// Add old list of mount points minus updated one.
   545  	for _, mount := range *mounts {
   546  		if _, ok := mountsByTarget[mount.Target]; !ok {
   547  			mountsByTarget[mount.Target] = mount
   548  		}
   549  	}
   550  
   551  	newMounts := []mounttypes.Mount{}
   552  
   553  	toRemove := buildToRemoveSet(flags, flagMountRemove)
   554  
   555  	for _, mount := range mountsByTarget {
   556  		if _, exists := toRemove[mount.Target]; !exists {
   557  			newMounts = append(newMounts, mount)
   558  		}
   559  	}
   560  	sort.Sort(byMountSource(newMounts))
   561  	*mounts = newMounts
   562  	return nil
   563  }
   564  
   565  func updateGroups(flags *pflag.FlagSet, groups *[]string) error {
   566  	if flags.Changed(flagGroupAdd) {
   567  		values := flags.Lookup(flagGroupAdd).Value.(*opts.ListOpts).GetAll()
   568  		*groups = append(*groups, values...)
   569  	}
   570  	toRemove := buildToRemoveSet(flags, flagGroupRemove)
   571  
   572  	newGroups := []string{}
   573  	for _, group := range *groups {
   574  		if _, exists := toRemove[group]; !exists {
   575  			newGroups = append(newGroups, group)
   576  		}
   577  	}
   578  	// Sort so that result is predictable.
   579  	sort.Strings(newGroups)
   580  
   581  	*groups = newGroups
   582  	return nil
   583  }
   584  
   585  func removeDuplicates(entries []string) []string {
   586  	hit := map[string]bool{}
   587  	newEntries := []string{}
   588  	for _, v := range entries {
   589  		if !hit[v] {
   590  			newEntries = append(newEntries, v)
   591  			hit[v] = true
   592  		}
   593  	}
   594  	return newEntries
   595  }
   596  
   597  func updateDNSConfig(flags *pflag.FlagSet, config **swarm.DNSConfig) error {
   598  	newConfig := &swarm.DNSConfig{}
   599  
   600  	nameservers := (*config).Nameservers
   601  	if flags.Changed(flagDNSAdd) {
   602  		values := flags.Lookup(flagDNSAdd).Value.(*opts.ListOpts).GetAll()
   603  		nameservers = append(nameservers, values...)
   604  	}
   605  	nameservers = removeDuplicates(nameservers)
   606  	toRemove := buildToRemoveSet(flags, flagDNSRemove)
   607  	for _, nameserver := range nameservers {
   608  		if _, exists := toRemove[nameserver]; !exists {
   609  			newConfig.Nameservers = append(newConfig.Nameservers, nameserver)
   610  
   611  		}
   612  	}
   613  	// Sort so that result is predictable.
   614  	sort.Strings(newConfig.Nameservers)
   615  
   616  	search := (*config).Search
   617  	if flags.Changed(flagDNSSearchAdd) {
   618  		values := flags.Lookup(flagDNSSearchAdd).Value.(*opts.ListOpts).GetAll()
   619  		search = append(search, values...)
   620  	}
   621  	search = removeDuplicates(search)
   622  	toRemove = buildToRemoveSet(flags, flagDNSSearchRemove)
   623  	for _, entry := range search {
   624  		if _, exists := toRemove[entry]; !exists {
   625  			newConfig.Search = append(newConfig.Search, entry)
   626  		}
   627  	}
   628  	// Sort so that result is predictable.
   629  	sort.Strings(newConfig.Search)
   630  
   631  	options := (*config).Options
   632  	if flags.Changed(flagDNSOptionAdd) {
   633  		values := flags.Lookup(flagDNSOptionAdd).Value.(*opts.ListOpts).GetAll()
   634  		options = append(options, values...)
   635  	}
   636  	options = removeDuplicates(options)
   637  	toRemove = buildToRemoveSet(flags, flagDNSOptionRemove)
   638  	for _, option := range options {
   639  		if _, exists := toRemove[option]; !exists {
   640  			newConfig.Options = append(newConfig.Options, option)
   641  		}
   642  	}
   643  	// Sort so that result is predictable.
   644  	sort.Strings(newConfig.Options)
   645  
   646  	*config = newConfig
   647  	return nil
   648  }
   649  
   650  type byPortConfig []swarm.PortConfig
   651  
   652  func (r byPortConfig) Len() int      { return len(r) }
   653  func (r byPortConfig) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
   654  func (r byPortConfig) Less(i, j int) bool {
   655  	// We convert PortConfig into `port/protocol`, e.g., `80/tcp`
   656  	// In updatePorts we already filter out with map so there is duplicate entries
   657  	return portConfigToString(&r[i]) < portConfigToString(&r[j])
   658  }
   659  
   660  func portConfigToString(portConfig *swarm.PortConfig) string {
   661  	protocol := portConfig.Protocol
   662  	mode := portConfig.PublishMode
   663  	return fmt.Sprintf("%v:%v/%s/%s", portConfig.PublishedPort, portConfig.TargetPort, protocol, mode)
   664  }
   665  
   666  func updatePorts(flags *pflag.FlagSet, portConfig *[]swarm.PortConfig) error {
   667  	// The key of the map is `port/protocol`, e.g., `80/tcp`
   668  	portSet := map[string]swarm.PortConfig{}
   669  
   670  	// Build the current list of portConfig
   671  	for _, entry := range *portConfig {
   672  		if _, ok := portSet[portConfigToString(&entry)]; !ok {
   673  			portSet[portConfigToString(&entry)] = entry
   674  		}
   675  	}
   676  
   677  	newPorts := []swarm.PortConfig{}
   678  
   679  	// Clean current ports
   680  	toRemove := flags.Lookup(flagPublishRemove).Value.(*opts.PortOpt).Value()
   681  portLoop:
   682  	for _, port := range portSet {
   683  		for _, pConfig := range toRemove {
   684  			if equalProtocol(port.Protocol, pConfig.Protocol) &&
   685  				port.TargetPort == pConfig.TargetPort &&
   686  				equalPublishMode(port.PublishMode, pConfig.PublishMode) {
   687  				continue portLoop
   688  			}
   689  		}
   690  
   691  		newPorts = append(newPorts, port)
   692  	}
   693  
   694  	// Check to see if there are any conflict in flags.
   695  	if flags.Changed(flagPublishAdd) {
   696  		ports := flags.Lookup(flagPublishAdd).Value.(*opts.PortOpt).Value()
   697  
   698  		for _, port := range ports {
   699  			if _, ok := portSet[portConfigToString(&port)]; ok {
   700  				continue
   701  			}
   702  			//portSet[portConfigToString(&port)] = port
   703  			newPorts = append(newPorts, port)
   704  		}
   705  	}
   706  
   707  	// Sort the PortConfig to avoid unnecessary updates
   708  	sort.Sort(byPortConfig(newPorts))
   709  	*portConfig = newPorts
   710  	return nil
   711  }
   712  
   713  func equalProtocol(prot1, prot2 swarm.PortConfigProtocol) bool {
   714  	return prot1 == prot2 ||
   715  		(prot1 == swarm.PortConfigProtocol("") && prot2 == swarm.PortConfigProtocolTCP) ||
   716  		(prot2 == swarm.PortConfigProtocol("") && prot1 == swarm.PortConfigProtocolTCP)
   717  }
   718  
   719  func equalPublishMode(mode1, mode2 swarm.PortConfigPublishMode) bool {
   720  	return mode1 == mode2 ||
   721  		(mode1 == swarm.PortConfigPublishMode("") && mode2 == swarm.PortConfigPublishModeIngress) ||
   722  		(mode2 == swarm.PortConfigPublishMode("") && mode1 == swarm.PortConfigPublishModeIngress)
   723  }
   724  
   725  func equalPort(targetPort nat.Port, port swarm.PortConfig) bool {
   726  	return (string(port.Protocol) == targetPort.Proto() &&
   727  		port.TargetPort == uint32(targetPort.Int()))
   728  }
   729  
   730  func updateReplicas(flags *pflag.FlagSet, serviceMode *swarm.ServiceMode) error {
   731  	if !flags.Changed(flagReplicas) {
   732  		return nil
   733  	}
   734  
   735  	if serviceMode == nil || serviceMode.Replicated == nil {
   736  		return fmt.Errorf("replicas can only be used with replicated mode")
   737  	}
   738  	serviceMode.Replicated.Replicas = flags.Lookup(flagReplicas).Value.(*Uint64Opt).Value()
   739  	return nil
   740  }
   741  
   742  func updateHosts(flags *pflag.FlagSet, hosts *[]string) error {
   743  	// Combine existing Hosts (in swarmkit format) with the host to add (convert to swarmkit format)
   744  	if flags.Changed(flagHostAdd) {
   745  		values := convertExtraHostsToSwarmHosts(flags.Lookup(flagHostAdd).Value.(*opts.ListOpts).GetAll())
   746  		*hosts = append(*hosts, values...)
   747  	}
   748  	// Remove duplicate
   749  	*hosts = removeDuplicates(*hosts)
   750  
   751  	keysToRemove := make(map[string]struct{})
   752  	if flags.Changed(flagHostRemove) {
   753  		var empty struct{}
   754  		extraHostsToRemove := flags.Lookup(flagHostRemove).Value.(*opts.ListOpts).GetAll()
   755  		for _, entry := range extraHostsToRemove {
   756  			key := strings.SplitN(entry, ":", 2)[0]
   757  			keysToRemove[key] = empty
   758  		}
   759  	}
   760  
   761  	newHosts := []string{}
   762  	for _, entry := range *hosts {
   763  		// Since this is in swarmkit format, we need to find the key, which is canonical_hostname of:
   764  		// IP_address canonical_hostname [aliases...]
   765  		parts := strings.Fields(entry)
   766  		if len(parts) > 1 {
   767  			key := parts[1]
   768  			if _, exists := keysToRemove[key]; !exists {
   769  				newHosts = append(newHosts, entry)
   770  			}
   771  		} else {
   772  			newHosts = append(newHosts, entry)
   773  		}
   774  	}
   775  
   776  	// Sort so that result is predictable.
   777  	sort.Strings(newHosts)
   778  
   779  	*hosts = newHosts
   780  	return nil
   781  }
   782  
   783  // updateLogDriver updates the log driver only if the log driver flag is set.
   784  // All options will be replaced with those provided on the command line.
   785  func updateLogDriver(flags *pflag.FlagSet, taskTemplate *swarm.TaskSpec) error {
   786  	if !flags.Changed(flagLogDriver) {
   787  		return nil
   788  	}
   789  
   790  	name, err := flags.GetString(flagLogDriver)
   791  	if err != nil {
   792  		return err
   793  	}
   794  
   795  	if name == "" {
   796  		return nil
   797  	}
   798  
   799  	taskTemplate.LogDriver = &swarm.Driver{
   800  		Name:    name,
   801  		Options: runconfigopts.ConvertKVStringsToMap(flags.Lookup(flagLogOpt).Value.(*opts.ListOpts).GetAll()),
   802  	}
   803  
   804  	return nil
   805  }
   806  
   807  func updateHealthcheck(flags *pflag.FlagSet, containerSpec *swarm.ContainerSpec) error {
   808  	if !anyChanged(flags, flagNoHealthcheck, flagHealthCmd, flagHealthInterval, flagHealthRetries, flagHealthTimeout) {
   809  		return nil
   810  	}
   811  	if containerSpec.Healthcheck == nil {
   812  		containerSpec.Healthcheck = &container.HealthConfig{}
   813  	}
   814  	noHealthcheck, err := flags.GetBool(flagNoHealthcheck)
   815  	if err != nil {
   816  		return err
   817  	}
   818  	if noHealthcheck {
   819  		if !anyChanged(flags, flagHealthCmd, flagHealthInterval, flagHealthRetries, flagHealthTimeout) {
   820  			containerSpec.Healthcheck = &container.HealthConfig{
   821  				Test: []string{"NONE"},
   822  			}
   823  			return nil
   824  		}
   825  		return fmt.Errorf("--%s conflicts with --health-* options", flagNoHealthcheck)
   826  	}
   827  	if len(containerSpec.Healthcheck.Test) > 0 && containerSpec.Healthcheck.Test[0] == "NONE" {
   828  		containerSpec.Healthcheck.Test = nil
   829  	}
   830  	if flags.Changed(flagHealthInterval) {
   831  		val := *flags.Lookup(flagHealthInterval).Value.(*PositiveDurationOpt).Value()
   832  		containerSpec.Healthcheck.Interval = val
   833  	}
   834  	if flags.Changed(flagHealthTimeout) {
   835  		val := *flags.Lookup(flagHealthTimeout).Value.(*PositiveDurationOpt).Value()
   836  		containerSpec.Healthcheck.Timeout = val
   837  	}
   838  	if flags.Changed(flagHealthRetries) {
   839  		containerSpec.Healthcheck.Retries, _ = flags.GetInt(flagHealthRetries)
   840  	}
   841  	if flags.Changed(flagHealthCmd) {
   842  		cmd, _ := flags.GetString(flagHealthCmd)
   843  		if cmd != "" {
   844  			containerSpec.Healthcheck.Test = []string{"CMD-SHELL", cmd}
   845  		} else {
   846  			containerSpec.Healthcheck.Test = nil
   847  		}
   848  	}
   849  	return nil
   850  }