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