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