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