github.com/justincormack/cli@v0.0.0-20201215022714-831ebeae9675/cli/command/service/update.go (about)

     1  package service
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sort"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/docker/cli/cli"
    11  	"github.com/docker/cli/cli/command"
    12  	"github.com/docker/cli/opts"
    13  	"github.com/docker/docker/api/types"
    14  	"github.com/docker/docker/api/types/container"
    15  	mounttypes "github.com/docker/docker/api/types/mount"
    16  	"github.com/docker/docker/api/types/swarm"
    17  	"github.com/docker/docker/api/types/versions"
    18  	"github.com/docker/docker/client"
    19  	units "github.com/docker/go-units"
    20  	"github.com/docker/swarmkit/api/defaults"
    21  	"github.com/pkg/errors"
    22  	"github.com/spf13/cobra"
    23  	"github.com/spf13/pflag"
    24  )
    25  
    26  func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
    27  	options := 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(), options, args[0])
    35  		},
    36  	}
    37  
    38  	flags := cmd.Flags()
    39  	flags.String("image", "", "Service image tag")
    40  	flags.Var(&ShlexOpt{}, "args", "Service command args")
    41  	flags.Bool(flagRollback, false, "Rollback to previous specification")
    42  	flags.SetAnnotation(flagRollback, "version", []string{"1.25"})
    43  	flags.Bool("force", false, "Force update even if no changes require it")
    44  	flags.SetAnnotation("force", "version", []string{"1.25"})
    45  	addServiceFlags(flags, options, nil)
    46  
    47  	flags.Var(newListOptsVar(), flagEnvRemove, "Remove an environment variable")
    48  	flags.Var(newListOptsVar(), flagGroupRemove, "Remove a previously added supplementary user group from the container")
    49  	flags.SetAnnotation(flagGroupRemove, "version", []string{"1.25"})
    50  	flags.Var(newListOptsVar(), flagLabelRemove, "Remove a label by its key")
    51  	flags.Var(newListOptsVar(), flagContainerLabelRemove, "Remove a container label by its key")
    52  	flags.Var(newListOptsVar(), flagMountRemove, "Remove a mount by its target path")
    53  	// flags.Var(newListOptsVar().WithValidator(validatePublishRemove), flagPublishRemove, "Remove a published port by its target port")
    54  	flags.Var(&opts.PortOpt{}, flagPublishRemove, "Remove a published port by its target port")
    55  	flags.Var(newListOptsVar(), flagConstraintRemove, "Remove a constraint")
    56  	flags.Var(newListOptsVar(), flagDNSRemove, "Remove a custom DNS server")
    57  	flags.SetAnnotation(flagDNSRemove, "version", []string{"1.25"})
    58  	flags.Var(newListOptsVar(), flagDNSOptionRemove, "Remove a DNS option")
    59  	flags.SetAnnotation(flagDNSOptionRemove, "version", []string{"1.25"})
    60  	flags.Var(newListOptsVar(), flagDNSSearchRemove, "Remove a DNS search domain")
    61  	flags.SetAnnotation(flagDNSSearchRemove, "version", []string{"1.25"})
    62  	flags.Var(newListOptsVar(), flagHostRemove, "Remove a custom host-to-IP mapping (host:ip)")
    63  	flags.SetAnnotation(flagHostRemove, "version", []string{"1.25"})
    64  	flags.Var(&options.labels, flagLabelAdd, "Add or update a service label")
    65  	flags.Var(&options.containerLabels, flagContainerLabelAdd, "Add or update a container label")
    66  	flags.Var(&options.env, flagEnvAdd, "Add or update an environment variable")
    67  	flags.Var(newListOptsVar(), flagSecretRemove, "Remove a secret")
    68  	flags.SetAnnotation(flagSecretRemove, "version", []string{"1.25"})
    69  	flags.Var(&options.secrets, flagSecretAdd, "Add or update a secret on a service")
    70  	flags.SetAnnotation(flagSecretAdd, "version", []string{"1.25"})
    71  
    72  	flags.Var(newListOptsVar(), flagConfigRemove, "Remove a configuration file")
    73  	flags.SetAnnotation(flagConfigRemove, "version", []string{"1.30"})
    74  	flags.Var(&options.configs, flagConfigAdd, "Add or update a config file on a service")
    75  	flags.SetAnnotation(flagConfigAdd, "version", []string{"1.30"})
    76  
    77  	flags.Var(&options.mounts, flagMountAdd, "Add or update a mount on a service")
    78  	flags.Var(&options.constraints, flagConstraintAdd, "Add or update a placement constraint")
    79  	flags.Var(&options.placementPrefs, flagPlacementPrefAdd, "Add a placement preference")
    80  	flags.SetAnnotation(flagPlacementPrefAdd, "version", []string{"1.28"})
    81  	flags.Var(&placementPrefOpts{}, flagPlacementPrefRemove, "Remove a placement preference")
    82  	flags.SetAnnotation(flagPlacementPrefRemove, "version", []string{"1.28"})
    83  	flags.Var(&options.networks, flagNetworkAdd, "Add a network")
    84  	flags.SetAnnotation(flagNetworkAdd, "version", []string{"1.29"})
    85  	flags.Var(newListOptsVar(), flagNetworkRemove, "Remove a network")
    86  	flags.SetAnnotation(flagNetworkRemove, "version", []string{"1.29"})
    87  	flags.Var(&options.endpoint.publishPorts, flagPublishAdd, "Add or update a published port")
    88  	flags.Var(&options.groups, flagGroupAdd, "Add an additional supplementary user group to the container")
    89  	flags.SetAnnotation(flagGroupAdd, "version", []string{"1.25"})
    90  	flags.Var(&options.dns, flagDNSAdd, "Add or update a custom DNS server")
    91  	flags.SetAnnotation(flagDNSAdd, "version", []string{"1.25"})
    92  	flags.Var(&options.dnsOption, flagDNSOptionAdd, "Add or update a DNS option")
    93  	flags.SetAnnotation(flagDNSOptionAdd, "version", []string{"1.25"})
    94  	flags.Var(&options.dnsSearch, flagDNSSearchAdd, "Add or update a custom DNS search domain")
    95  	flags.SetAnnotation(flagDNSSearchAdd, "version", []string{"1.25"})
    96  	flags.Var(&options.hosts, flagHostAdd, "Add a custom host-to-IP mapping (host:ip)")
    97  	flags.SetAnnotation(flagHostAdd, "version", []string{"1.25"})
    98  	flags.BoolVar(&options.init, flagInit, false, "Use an init inside each service container to forward signals and reap processes")
    99  	flags.SetAnnotation(flagInit, "version", []string{"1.37"})
   100  	flags.Var(&options.sysctls, flagSysCtlAdd, "Add or update a Sysctl option")
   101  	flags.SetAnnotation(flagSysCtlAdd, "version", []string{"1.40"})
   102  	flags.Var(newListOptsVar(), flagSysCtlRemove, "Remove a Sysctl option")
   103  	flags.SetAnnotation(flagSysCtlRemove, "version", []string{"1.40"})
   104  	flags.Var(&options.ulimits, flagUlimitAdd, "Add or update a ulimit option")
   105  	flags.SetAnnotation(flagUlimitAdd, "version", []string{"1.41"})
   106  	flags.Var(newListOptsVar(), flagUlimitRemove, "Remove a ulimit option")
   107  	flags.SetAnnotation(flagUlimitRemove, "version", []string{"1.41"})
   108  
   109  	// Add needs parsing, Remove only needs the key
   110  	flags.Var(newListOptsVar(), flagGenericResourcesRemove, "Remove a Generic resource")
   111  	flags.SetAnnotation(flagHostAdd, "version", []string{"1.32"})
   112  	flags.Var(newListOptsVarWithValidator(ValidateSingleGenericResource), flagGenericResourcesAdd, "Add a Generic resource")
   113  	flags.SetAnnotation(flagHostAdd, "version", []string{"1.32"})
   114  
   115  	return cmd
   116  }
   117  
   118  func newListOptsVar() *opts.ListOpts {
   119  	return opts.NewListOptsRef(&[]string{}, nil)
   120  }
   121  
   122  func newListOptsVarWithValidator(validator opts.ValidatorFctType) *opts.ListOpts {
   123  	return opts.NewListOptsRef(&[]string{}, validator)
   124  }
   125  
   126  // nolint: gocyclo
   127  func runUpdate(dockerCli command.Cli, flags *pflag.FlagSet, options *serviceOptions, serviceID string) error {
   128  	apiClient := dockerCli.Client()
   129  	ctx := context.Background()
   130  
   131  	service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
   132  	if err != nil {
   133  		return err
   134  	}
   135  
   136  	rollback, err := flags.GetBool(flagRollback)
   137  	if err != nil {
   138  		return err
   139  	}
   140  
   141  	// There are two ways to do user-requested rollback. The old way is
   142  	// client-side, but with a sufficiently recent daemon we prefer
   143  	// server-side, because it will honor the rollback parameters.
   144  	var (
   145  		clientSideRollback bool
   146  		serverSideRollback bool
   147  	)
   148  
   149  	spec := &service.Spec
   150  	if rollback {
   151  		// Rollback can't be combined with other flags.
   152  		otherFlagsPassed := false
   153  		flags.VisitAll(func(f *pflag.Flag) {
   154  			if f.Name == flagRollback || f.Name == flagDetach || f.Name == flagQuiet {
   155  				return
   156  			}
   157  			if flags.Changed(f.Name) {
   158  				otherFlagsPassed = true
   159  			}
   160  		})
   161  		if otherFlagsPassed {
   162  			return errors.New("other flags may not be combined with --rollback")
   163  		}
   164  
   165  		if versions.LessThan(apiClient.ClientVersion(), "1.28") {
   166  			clientSideRollback = true
   167  			spec = service.PreviousSpec
   168  			if spec == nil {
   169  				return errors.Errorf("service does not have a previous specification to roll back to")
   170  			}
   171  		} else {
   172  			serverSideRollback = true
   173  		}
   174  	}
   175  
   176  	updateOpts := types.ServiceUpdateOptions{}
   177  	if serverSideRollback {
   178  		updateOpts.Rollback = "previous"
   179  	}
   180  
   181  	err = updateService(ctx, apiClient, flags, spec)
   182  	if err != nil {
   183  		return err
   184  	}
   185  
   186  	if flags.Changed("image") {
   187  		if err := resolveServiceImageDigestContentTrust(dockerCli, spec); err != nil {
   188  			return err
   189  		}
   190  		if !options.noResolveImage && versions.GreaterThanOrEqualTo(apiClient.ClientVersion(), "1.30") {
   191  			updateOpts.QueryRegistry = true
   192  		}
   193  	}
   194  
   195  	updatedSecrets, err := getUpdatedSecrets(apiClient, flags, spec.TaskTemplate.ContainerSpec.Secrets)
   196  	if err != nil {
   197  		return err
   198  	}
   199  
   200  	spec.TaskTemplate.ContainerSpec.Secrets = updatedSecrets
   201  
   202  	updatedConfigs, err := getUpdatedConfigs(apiClient, flags, spec.TaskTemplate.ContainerSpec)
   203  	if err != nil {
   204  		return err
   205  	}
   206  
   207  	spec.TaskTemplate.ContainerSpec.Configs = updatedConfigs
   208  
   209  	// set the credential spec value after get the updated configs, because we
   210  	// might need the updated configs to set the correct value of the
   211  	// CredentialSpec.
   212  	updateCredSpecConfig(flags, spec.TaskTemplate.ContainerSpec)
   213  
   214  	// only send auth if flag was set
   215  	sendAuth, err := flags.GetBool(flagRegistryAuth)
   216  	if err != nil {
   217  		return err
   218  	}
   219  	if sendAuth {
   220  		// Retrieve encoded auth token from the image reference
   221  		// This would be the old image if it didn't change in this update
   222  		image := spec.TaskTemplate.ContainerSpec.Image
   223  		encodedAuth, err := command.RetrieveAuthTokenFromImage(ctx, dockerCli, image)
   224  		if err != nil {
   225  			return err
   226  		}
   227  		updateOpts.EncodedRegistryAuth = encodedAuth
   228  	} else if clientSideRollback {
   229  		updateOpts.RegistryAuthFrom = types.RegistryAuthFromPreviousSpec
   230  	} else {
   231  		updateOpts.RegistryAuthFrom = types.RegistryAuthFromSpec
   232  	}
   233  
   234  	response, err := apiClient.ServiceUpdate(ctx, service.ID, service.Version, *spec, updateOpts)
   235  	if err != nil {
   236  		return err
   237  	}
   238  
   239  	for _, warning := range response.Warnings {
   240  		fmt.Fprintln(dockerCli.Err(), warning)
   241  	}
   242  
   243  	fmt.Fprintf(dockerCli.Out(), "%s\n", serviceID)
   244  
   245  	if options.detach || versions.LessThan(apiClient.ClientVersion(), "1.29") {
   246  		return nil
   247  	}
   248  
   249  	return waitOnService(ctx, dockerCli, serviceID, options.quiet)
   250  }
   251  
   252  // nolint: gocyclo
   253  func updateService(ctx context.Context, apiClient client.NetworkAPIClient, flags *pflag.FlagSet, spec *swarm.ServiceSpec) error {
   254  	updateBoolPtr := func(flag string, field **bool) {
   255  		if flags.Changed(flag) {
   256  			b, _ := flags.GetBool(flag)
   257  			*field = &b
   258  		}
   259  	}
   260  	updateString := func(flag string, field *string) {
   261  		if flags.Changed(flag) {
   262  			*field, _ = flags.GetString(flag)
   263  		}
   264  	}
   265  
   266  	updateInt64Value := func(flag string, field *int64) {
   267  		if flags.Changed(flag) {
   268  			*field = flags.Lookup(flag).Value.(int64Value).Value()
   269  		}
   270  	}
   271  
   272  	updateFloatValue := func(flag string, field *float32) {
   273  		if flags.Changed(flag) {
   274  			*field = flags.Lookup(flag).Value.(*floatValue).Value()
   275  		}
   276  	}
   277  
   278  	updateDuration := func(flag string, field *time.Duration) {
   279  		if flags.Changed(flag) {
   280  			*field, _ = flags.GetDuration(flag)
   281  		}
   282  	}
   283  
   284  	updateDurationOpt := func(flag string, field **time.Duration) {
   285  		if flags.Changed(flag) {
   286  			val := *flags.Lookup(flag).Value.(*opts.DurationOpt).Value()
   287  			*field = &val
   288  		}
   289  	}
   290  
   291  	updateInt64 := func(flag string, field *int64) {
   292  		if flags.Changed(flag) {
   293  			*field, _ = flags.GetInt64(flag)
   294  		}
   295  	}
   296  
   297  	updateUint64 := func(flag string, field *uint64) {
   298  		if flags.Changed(flag) {
   299  			*field, _ = flags.GetUint64(flag)
   300  		}
   301  	}
   302  
   303  	updateUint64Opt := func(flag string, field **uint64) {
   304  		if flags.Changed(flag) {
   305  			val := *flags.Lookup(flag).Value.(*Uint64Opt).Value()
   306  			*field = &val
   307  		}
   308  	}
   309  
   310  	updateIsolation := func(flag string, field *container.Isolation) error {
   311  		if flags.Changed(flag) {
   312  			val, _ := flags.GetString(flag)
   313  			*field = container.Isolation(val)
   314  		}
   315  		return nil
   316  	}
   317  
   318  	cspec := spec.TaskTemplate.ContainerSpec
   319  	task := &spec.TaskTemplate
   320  
   321  	taskResources := func() *swarm.ResourceRequirements {
   322  		if task.Resources == nil {
   323  			task.Resources = &swarm.ResourceRequirements{}
   324  		}
   325  		if task.Resources.Limits == nil {
   326  			task.Resources.Limits = &swarm.Limit{}
   327  		}
   328  		if task.Resources.Reservations == nil {
   329  			task.Resources.Reservations = &swarm.Resources{}
   330  		}
   331  		return task.Resources
   332  	}
   333  
   334  	updateLabels(flags, &spec.Labels)
   335  	updateContainerLabels(flags, &cspec.Labels)
   336  	updateString("image", &cspec.Image)
   337  	updateStringToSlice(flags, "args", &cspec.Args)
   338  	updateStringToSlice(flags, flagEntrypoint, &cspec.Command)
   339  	updateEnvironment(flags, &cspec.Env)
   340  	updateString(flagWorkdir, &cspec.Dir)
   341  	updateString(flagUser, &cspec.User)
   342  	updateString(flagHostname, &cspec.Hostname)
   343  	updateBoolPtr(flagInit, &cspec.Init)
   344  	if err := updateIsolation(flagIsolation, &cspec.Isolation); err != nil {
   345  		return err
   346  	}
   347  	if err := updateMounts(flags, &cspec.Mounts); err != nil {
   348  		return err
   349  	}
   350  
   351  	updateSysCtls(flags, &task.ContainerSpec.Sysctls)
   352  	task.ContainerSpec.Ulimits = updateUlimits(flags, task.ContainerSpec.Ulimits)
   353  
   354  	if anyChanged(flags, flagLimitCPU, flagLimitMemory, flagLimitPids) {
   355  		taskResources().Limits = spec.TaskTemplate.Resources.Limits
   356  		updateInt64Value(flagLimitCPU, &task.Resources.Limits.NanoCPUs)
   357  		updateInt64Value(flagLimitMemory, &task.Resources.Limits.MemoryBytes)
   358  		updateInt64(flagLimitPids, &task.Resources.Limits.Pids)
   359  	}
   360  
   361  	if anyChanged(flags, flagReserveCPU, flagReserveMemory) {
   362  		taskResources().Reservations = spec.TaskTemplate.Resources.Reservations
   363  		updateInt64Value(flagReserveCPU, &task.Resources.Reservations.NanoCPUs)
   364  		updateInt64Value(flagReserveMemory, &task.Resources.Reservations.MemoryBytes)
   365  	}
   366  
   367  	if err := addGenericResources(flags, task); err != nil {
   368  		return err
   369  	}
   370  
   371  	if err := removeGenericResources(flags, task); err != nil {
   372  		return err
   373  	}
   374  
   375  	updateDurationOpt(flagStopGracePeriod, &cspec.StopGracePeriod)
   376  
   377  	if anyChanged(flags, flagRestartCondition, flagRestartDelay, flagRestartMaxAttempts, flagRestartWindow) {
   378  		if task.RestartPolicy == nil {
   379  			task.RestartPolicy = defaultRestartPolicy()
   380  		}
   381  		if flags.Changed(flagRestartCondition) {
   382  			value, _ := flags.GetString(flagRestartCondition)
   383  			task.RestartPolicy.Condition = swarm.RestartPolicyCondition(value)
   384  		}
   385  		updateDurationOpt(flagRestartDelay, &task.RestartPolicy.Delay)
   386  		updateUint64Opt(flagRestartMaxAttempts, &task.RestartPolicy.MaxAttempts)
   387  		updateDurationOpt(flagRestartWindow, &task.RestartPolicy.Window)
   388  	}
   389  
   390  	if anyChanged(flags, flagConstraintAdd, flagConstraintRemove) {
   391  		if task.Placement == nil {
   392  			task.Placement = &swarm.Placement{}
   393  		}
   394  		updatePlacementConstraints(flags, task.Placement)
   395  	}
   396  
   397  	if anyChanged(flags, flagPlacementPrefAdd, flagPlacementPrefRemove) {
   398  		if task.Placement == nil {
   399  			task.Placement = &swarm.Placement{}
   400  		}
   401  		updatePlacementPreferences(flags, task.Placement)
   402  	}
   403  
   404  	if anyChanged(flags, flagNetworkAdd, flagNetworkRemove) {
   405  		if err := updateNetworks(ctx, apiClient, flags, spec); err != nil {
   406  			return err
   407  		}
   408  	}
   409  
   410  	if err := updateReplicas(flags, &spec.Mode); err != nil {
   411  		return err
   412  	}
   413  
   414  	if anyChanged(flags, flagMaxReplicas) {
   415  		updateUint64(flagMaxReplicas, &task.Placement.MaxReplicas)
   416  	}
   417  
   418  	if anyChanged(flags, flagUpdateParallelism, flagUpdateDelay, flagUpdateMonitor, flagUpdateFailureAction, flagUpdateMaxFailureRatio, flagUpdateOrder) {
   419  		if spec.UpdateConfig == nil {
   420  			spec.UpdateConfig = updateConfigFromDefaults(defaults.Service.Update)
   421  		}
   422  		updateUint64(flagUpdateParallelism, &spec.UpdateConfig.Parallelism)
   423  		updateDuration(flagUpdateDelay, &spec.UpdateConfig.Delay)
   424  		updateDuration(flagUpdateMonitor, &spec.UpdateConfig.Monitor)
   425  		updateString(flagUpdateFailureAction, &spec.UpdateConfig.FailureAction)
   426  		updateFloatValue(flagUpdateMaxFailureRatio, &spec.UpdateConfig.MaxFailureRatio)
   427  		updateString(flagUpdateOrder, &spec.UpdateConfig.Order)
   428  	}
   429  
   430  	if anyChanged(flags, flagRollbackParallelism, flagRollbackDelay, flagRollbackMonitor, flagRollbackFailureAction, flagRollbackMaxFailureRatio, flagRollbackOrder) {
   431  		if spec.RollbackConfig == nil {
   432  			spec.RollbackConfig = updateConfigFromDefaults(defaults.Service.Rollback)
   433  		}
   434  		updateUint64(flagRollbackParallelism, &spec.RollbackConfig.Parallelism)
   435  		updateDuration(flagRollbackDelay, &spec.RollbackConfig.Delay)
   436  		updateDuration(flagRollbackMonitor, &spec.RollbackConfig.Monitor)
   437  		updateString(flagRollbackFailureAction, &spec.RollbackConfig.FailureAction)
   438  		updateFloatValue(flagRollbackMaxFailureRatio, &spec.RollbackConfig.MaxFailureRatio)
   439  		updateString(flagRollbackOrder, &spec.RollbackConfig.Order)
   440  	}
   441  
   442  	if flags.Changed(flagEndpointMode) {
   443  		value, _ := flags.GetString(flagEndpointMode)
   444  		if spec.EndpointSpec == nil {
   445  			spec.EndpointSpec = &swarm.EndpointSpec{}
   446  		}
   447  		spec.EndpointSpec.Mode = swarm.ResolutionMode(value)
   448  	}
   449  
   450  	if anyChanged(flags, flagGroupAdd, flagGroupRemove) {
   451  		if err := updateGroups(flags, &cspec.Groups); err != nil {
   452  			return err
   453  		}
   454  	}
   455  
   456  	if anyChanged(flags, flagPublishAdd, flagPublishRemove) {
   457  		if spec.EndpointSpec == nil {
   458  			spec.EndpointSpec = &swarm.EndpointSpec{}
   459  		}
   460  		if err := updatePorts(flags, &spec.EndpointSpec.Ports); err != nil {
   461  			return err
   462  		}
   463  	}
   464  
   465  	if anyChanged(flags, flagDNSAdd, flagDNSRemove, flagDNSOptionAdd, flagDNSOptionRemove, flagDNSSearchAdd, flagDNSSearchRemove) {
   466  		if cspec.DNSConfig == nil {
   467  			cspec.DNSConfig = &swarm.DNSConfig{}
   468  		}
   469  		if err := updateDNSConfig(flags, &cspec.DNSConfig); err != nil {
   470  			return err
   471  		}
   472  	}
   473  
   474  	if anyChanged(flags, flagHostAdd, flagHostRemove) {
   475  		if err := updateHosts(flags, &cspec.Hosts); err != nil {
   476  			return err
   477  		}
   478  	}
   479  
   480  	if err := updateLogDriver(flags, &spec.TaskTemplate); err != nil {
   481  		return err
   482  	}
   483  
   484  	force, err := flags.GetBool("force")
   485  	if err != nil {
   486  		return err
   487  	}
   488  
   489  	if force {
   490  		spec.TaskTemplate.ForceUpdate++
   491  	}
   492  
   493  	if err := updateHealthcheck(flags, cspec); err != nil {
   494  		return err
   495  	}
   496  
   497  	if flags.Changed(flagTTY) {
   498  		tty, err := flags.GetBool(flagTTY)
   499  		if err != nil {
   500  			return err
   501  		}
   502  		cspec.TTY = tty
   503  	}
   504  
   505  	if flags.Changed(flagReadOnly) {
   506  		readOnly, err := flags.GetBool(flagReadOnly)
   507  		if err != nil {
   508  			return err
   509  		}
   510  		cspec.ReadOnly = readOnly
   511  	}
   512  
   513  	updateString(flagStopSignal, &cspec.StopSignal)
   514  
   515  	if anyChanged(flags, flagCapAdd, flagCapDrop) {
   516  		updateCapabilities(flags, cspec)
   517  	}
   518  
   519  	return nil
   520  }
   521  
   522  func updateStringToSlice(flags *pflag.FlagSet, flag string, field *[]string) {
   523  	if !flags.Changed(flag) {
   524  		return
   525  	}
   526  
   527  	*field = flags.Lookup(flag).Value.(*ShlexOpt).Value()
   528  }
   529  
   530  func anyChanged(flags *pflag.FlagSet, fields ...string) bool {
   531  	for _, flag := range fields {
   532  		if flags.Changed(flag) {
   533  			return true
   534  		}
   535  	}
   536  	return false
   537  }
   538  
   539  func addGenericResources(flags *pflag.FlagSet, spec *swarm.TaskSpec) error {
   540  	if !flags.Changed(flagGenericResourcesAdd) {
   541  		return nil
   542  	}
   543  
   544  	if spec.Resources == nil {
   545  		spec.Resources = &swarm.ResourceRequirements{}
   546  	}
   547  
   548  	if spec.Resources.Reservations == nil {
   549  		spec.Resources.Reservations = &swarm.Resources{}
   550  	}
   551  
   552  	values := flags.Lookup(flagGenericResourcesAdd).Value.(*opts.ListOpts).GetAll()
   553  	generic, err := ParseGenericResources(values)
   554  	if err != nil {
   555  		return err
   556  	}
   557  
   558  	m, err := buildGenericResourceMap(spec.Resources.Reservations.GenericResources)
   559  	if err != nil {
   560  		return err
   561  	}
   562  
   563  	for _, toAddRes := range generic {
   564  		m[toAddRes.DiscreteResourceSpec.Kind] = toAddRes
   565  	}
   566  
   567  	spec.Resources.Reservations.GenericResources = buildGenericResourceList(m)
   568  
   569  	return nil
   570  }
   571  
   572  func removeGenericResources(flags *pflag.FlagSet, spec *swarm.TaskSpec) error {
   573  	// Can only be Discrete Resources
   574  	if !flags.Changed(flagGenericResourcesRemove) {
   575  		return nil
   576  	}
   577  
   578  	if spec.Resources == nil {
   579  		spec.Resources = &swarm.ResourceRequirements{}
   580  	}
   581  
   582  	if spec.Resources.Reservations == nil {
   583  		spec.Resources.Reservations = &swarm.Resources{}
   584  	}
   585  
   586  	values := flags.Lookup(flagGenericResourcesRemove).Value.(*opts.ListOpts).GetAll()
   587  
   588  	m, err := buildGenericResourceMap(spec.Resources.Reservations.GenericResources)
   589  	if err != nil {
   590  		return err
   591  	}
   592  
   593  	for _, toRemoveRes := range values {
   594  		if _, ok := m[toRemoveRes]; !ok {
   595  			return fmt.Errorf("could not find generic-resource `%s` to remove it", toRemoveRes)
   596  		}
   597  
   598  		delete(m, toRemoveRes)
   599  	}
   600  
   601  	spec.Resources.Reservations.GenericResources = buildGenericResourceList(m)
   602  	return nil
   603  }
   604  
   605  func updatePlacementConstraints(flags *pflag.FlagSet, placement *swarm.Placement) {
   606  	if flags.Changed(flagConstraintAdd) {
   607  		values := flags.Lookup(flagConstraintAdd).Value.(*opts.ListOpts).GetAll()
   608  		placement.Constraints = append(placement.Constraints, values...)
   609  	}
   610  	toRemove := buildToRemoveSet(flags, flagConstraintRemove)
   611  
   612  	newConstraints := []string{}
   613  	for _, constraint := range placement.Constraints {
   614  		if _, exists := toRemove[constraint]; !exists {
   615  			newConstraints = append(newConstraints, constraint)
   616  		}
   617  	}
   618  	// Sort so that result is predictable.
   619  	sort.Strings(newConstraints)
   620  
   621  	placement.Constraints = newConstraints
   622  }
   623  
   624  func updatePlacementPreferences(flags *pflag.FlagSet, placement *swarm.Placement) {
   625  	var newPrefs []swarm.PlacementPreference
   626  
   627  	if flags.Changed(flagPlacementPrefRemove) {
   628  		for _, existing := range placement.Preferences {
   629  			removed := false
   630  			for _, removal := range flags.Lookup(flagPlacementPrefRemove).Value.(*placementPrefOpts).prefs {
   631  				if removal.Spread != nil && existing.Spread != nil && removal.Spread.SpreadDescriptor == existing.Spread.SpreadDescriptor {
   632  					removed = true
   633  					break
   634  				}
   635  			}
   636  			if !removed {
   637  				newPrefs = append(newPrefs, existing)
   638  			}
   639  		}
   640  	} else {
   641  		newPrefs = placement.Preferences
   642  	}
   643  
   644  	if flags.Changed(flagPlacementPrefAdd) {
   645  		newPrefs = append(newPrefs,
   646  			flags.Lookup(flagPlacementPrefAdd).Value.(*placementPrefOpts).prefs...)
   647  	}
   648  
   649  	placement.Preferences = newPrefs
   650  }
   651  
   652  func updateContainerLabels(flags *pflag.FlagSet, field *map[string]string) {
   653  	if *field != nil && flags.Changed(flagContainerLabelRemove) {
   654  		toRemove := flags.Lookup(flagContainerLabelRemove).Value.(*opts.ListOpts).GetAll()
   655  		for _, label := range toRemove {
   656  			delete(*field, label)
   657  		}
   658  	}
   659  	if flags.Changed(flagContainerLabelAdd) {
   660  		if *field == nil {
   661  			*field = map[string]string{}
   662  		}
   663  
   664  		values := flags.Lookup(flagContainerLabelAdd).Value.(*opts.ListOpts).GetAll()
   665  		for key, value := range opts.ConvertKVStringsToMap(values) {
   666  			(*field)[key] = value
   667  		}
   668  	}
   669  }
   670  
   671  func updateLabels(flags *pflag.FlagSet, field *map[string]string) {
   672  	if *field != nil && flags.Changed(flagLabelRemove) {
   673  		toRemove := flags.Lookup(flagLabelRemove).Value.(*opts.ListOpts).GetAll()
   674  		for _, label := range toRemove {
   675  			delete(*field, label)
   676  		}
   677  	}
   678  	if flags.Changed(flagLabelAdd) {
   679  		if *field == nil {
   680  			*field = map[string]string{}
   681  		}
   682  
   683  		values := flags.Lookup(flagLabelAdd).Value.(*opts.ListOpts).GetAll()
   684  		for key, value := range opts.ConvertKVStringsToMap(values) {
   685  			(*field)[key] = value
   686  		}
   687  	}
   688  }
   689  
   690  func updateSysCtls(flags *pflag.FlagSet, field *map[string]string) {
   691  	if *field != nil && flags.Changed(flagSysCtlRemove) {
   692  		values := flags.Lookup(flagSysCtlRemove).Value.(*opts.ListOpts).GetAll()
   693  		for key := range opts.ConvertKVStringsToMap(values) {
   694  			delete(*field, key)
   695  		}
   696  	}
   697  	if flags.Changed(flagSysCtlAdd) {
   698  		if *field == nil {
   699  			*field = map[string]string{}
   700  		}
   701  
   702  		values := flags.Lookup(flagSysCtlAdd).Value.(*opts.ListOpts).GetAll()
   703  		for key, value := range opts.ConvertKVStringsToMap(values) {
   704  			(*field)[key] = value
   705  		}
   706  	}
   707  }
   708  
   709  func updateUlimits(flags *pflag.FlagSet, ulimits []*units.Ulimit) []*units.Ulimit {
   710  	newUlimits := make(map[string]*units.Ulimit)
   711  
   712  	for _, ulimit := range ulimits {
   713  		newUlimits[ulimit.Name] = ulimit
   714  	}
   715  	if flags.Changed(flagUlimitRemove) {
   716  		values := flags.Lookup(flagUlimitRemove).Value.(*opts.ListOpts).GetAll()
   717  		for key := range opts.ConvertKVStringsToMap(values) {
   718  			delete(newUlimits, key)
   719  		}
   720  	}
   721  
   722  	if flags.Changed(flagUlimitAdd) {
   723  		for _, ulimit := range flags.Lookup(flagUlimitAdd).Value.(*opts.UlimitOpt).GetList() {
   724  			newUlimits[ulimit.Name] = ulimit
   725  		}
   726  	}
   727  
   728  	var limits []*units.Ulimit
   729  	for _, ulimit := range newUlimits {
   730  		limits = append(limits, ulimit)
   731  	}
   732  	sort.SliceStable(limits, func(i, j int) bool {
   733  		return limits[i].Name < limits[j].Name
   734  	})
   735  	return limits
   736  }
   737  
   738  func updateEnvironment(flags *pflag.FlagSet, field *[]string) {
   739  	toRemove := buildToRemoveSet(flags, flagEnvRemove)
   740  	*field = removeItems(*field, toRemove, envKey)
   741  
   742  	if flags.Changed(flagEnvAdd) {
   743  		envSet := map[string]string{}
   744  		for _, v := range *field {
   745  			envSet[envKey(v)] = v
   746  		}
   747  
   748  		value := flags.Lookup(flagEnvAdd).Value.(*opts.ListOpts)
   749  		for _, v := range value.GetAll() {
   750  			envSet[envKey(v)] = v
   751  		}
   752  
   753  		*field = []string{}
   754  		for _, v := range envSet {
   755  			*field = append(*field, v)
   756  		}
   757  	}
   758  }
   759  
   760  func getUpdatedSecrets(apiClient client.SecretAPIClient, flags *pflag.FlagSet, secrets []*swarm.SecretReference) ([]*swarm.SecretReference, error) {
   761  	newSecrets := []*swarm.SecretReference{}
   762  
   763  	toRemove := buildToRemoveSet(flags, flagSecretRemove)
   764  	for _, secret := range secrets {
   765  		if _, exists := toRemove[secret.SecretName]; !exists {
   766  			newSecrets = append(newSecrets, secret)
   767  		}
   768  	}
   769  
   770  	if flags.Changed(flagSecretAdd) {
   771  		values := flags.Lookup(flagSecretAdd).Value.(*opts.SecretOpt).Value()
   772  
   773  		addSecrets, err := ParseSecrets(apiClient, values)
   774  		if err != nil {
   775  			return nil, err
   776  		}
   777  		newSecrets = append(newSecrets, addSecrets...)
   778  	}
   779  
   780  	return newSecrets, nil
   781  }
   782  
   783  func getUpdatedConfigs(apiClient client.ConfigAPIClient, flags *pflag.FlagSet, spec *swarm.ContainerSpec) ([]*swarm.ConfigReference, error) {
   784  	var (
   785  		// credSpecConfigName stores the name of the config specified by the
   786  		// credential-spec flag. if a Runtime target Config with this name is
   787  		// already in the containerSpec, then this value will be set to
   788  		// emptystring in the removeConfigs stage. otherwise, a ConfigReference
   789  		// will be created to pass to ParseConfigs to get the ConfigID.
   790  		credSpecConfigName string
   791  		// credSpecConfigID stores the ID of the credential spec config if that
   792  		// config is being carried over from the old set of references
   793  		credSpecConfigID string
   794  	)
   795  
   796  	if flags.Changed(flagCredentialSpec) {
   797  		credSpec := flags.Lookup(flagCredentialSpec).Value.(*credentialSpecOpt).Value()
   798  		credSpecConfigName = credSpec.Config
   799  	} else {
   800  		// if the credential spec flag has not changed, then check if there
   801  		// already is a credentialSpec. if there is one, and it's for a Config,
   802  		// then it's from the old object, and its value is the config ID. we
   803  		// need this so we don't remove the config if the credential spec is
   804  		// not being updated.
   805  		if spec.Privileges != nil && spec.Privileges.CredentialSpec != nil {
   806  			if config := spec.Privileges.CredentialSpec.Config; config != "" {
   807  				credSpecConfigID = config
   808  			}
   809  		}
   810  	}
   811  
   812  	newConfigs := removeConfigs(flags, spec, credSpecConfigName, credSpecConfigID)
   813  
   814  	// resolveConfigs is a slice of any new configs that need to have the ID
   815  	// resolved
   816  	resolveConfigs := []*swarm.ConfigReference{}
   817  
   818  	if flags.Changed(flagConfigAdd) {
   819  		resolveConfigs = append(resolveConfigs, flags.Lookup(flagConfigAdd).Value.(*opts.ConfigOpt).Value()...)
   820  	}
   821  
   822  	// if credSpecConfigNameis non-empty at this point, it means its a new
   823  	// config, and we need to resolve its ID accordingly.
   824  	if credSpecConfigName != "" {
   825  		resolveConfigs = append(resolveConfigs, &swarm.ConfigReference{
   826  			ConfigName: credSpecConfigName,
   827  			Runtime:    &swarm.ConfigReferenceRuntimeTarget{},
   828  		})
   829  	}
   830  
   831  	if len(resolveConfigs) > 0 {
   832  		addConfigs, err := ParseConfigs(apiClient, resolveConfigs)
   833  		if err != nil {
   834  			return nil, err
   835  		}
   836  		newConfigs = append(newConfigs, addConfigs...)
   837  	}
   838  
   839  	return newConfigs, nil
   840  }
   841  
   842  // removeConfigs figures out which configs in the existing spec should be kept
   843  // after the update.
   844  func removeConfigs(flags *pflag.FlagSet, spec *swarm.ContainerSpec, credSpecName, credSpecID string) []*swarm.ConfigReference {
   845  	keepConfigs := []*swarm.ConfigReference{}
   846  
   847  	toRemove := buildToRemoveSet(flags, flagConfigRemove)
   848  	// all configs in spec.Configs should have both a Name and ID, because
   849  	// they come from an already-accepted spec.
   850  	for _, config := range spec.Configs {
   851  		// if the config is a Runtime target, make sure it's still in use right
   852  		// now, the only use for Runtime target is credential specs.  if, in
   853  		// the future, more uses are added, then this check will need to be
   854  		// made more intelligent.
   855  		if config.Runtime != nil {
   856  			// if we're carrying over a credential spec explicitly (because the
   857  			// user passed --credential-spec with the same config name) then we
   858  			// should match on credSpecName. if we're carrying over a
   859  			// credential spec implicitly (because the user did not pass any
   860  			// --credential-spec flag) then we should match on credSpecID. in
   861  			// either case, we're keeping the config that already exists.
   862  			if config.ConfigName == credSpecName || config.ConfigID == credSpecID {
   863  				keepConfigs = append(keepConfigs, config)
   864  			}
   865  			// continue the loop, to skip the part where we check if the config
   866  			// is in toRemove.
   867  			continue
   868  		}
   869  
   870  		if _, exists := toRemove[config.ConfigName]; !exists {
   871  			keepConfigs = append(keepConfigs, config)
   872  		}
   873  	}
   874  
   875  	return keepConfigs
   876  }
   877  
   878  func envKey(value string) string {
   879  	kv := strings.SplitN(value, "=", 2)
   880  	return kv[0]
   881  }
   882  
   883  func buildToRemoveSet(flags *pflag.FlagSet, flag string) map[string]struct{} {
   884  	var empty struct{}
   885  	toRemove := make(map[string]struct{})
   886  
   887  	if !flags.Changed(flag) {
   888  		return toRemove
   889  	}
   890  
   891  	toRemoveSlice := flags.Lookup(flag).Value.(*opts.ListOpts).GetAll()
   892  	for _, key := range toRemoveSlice {
   893  		toRemove[key] = empty
   894  	}
   895  	return toRemove
   896  }
   897  
   898  func removeItems(
   899  	seq []string,
   900  	toRemove map[string]struct{},
   901  	keyFunc func(string) string,
   902  ) []string {
   903  	newSeq := []string{}
   904  	for _, item := range seq {
   905  		if _, exists := toRemove[keyFunc(item)]; !exists {
   906  			newSeq = append(newSeq, item)
   907  		}
   908  	}
   909  	return newSeq
   910  }
   911  
   912  func updateMounts(flags *pflag.FlagSet, mounts *[]mounttypes.Mount) error {
   913  	mountsByTarget := map[string]mounttypes.Mount{}
   914  
   915  	if flags.Changed(flagMountAdd) {
   916  		values := flags.Lookup(flagMountAdd).Value.(*opts.MountOpt).Value()
   917  		for _, mount := range values {
   918  			if _, ok := mountsByTarget[mount.Target]; ok {
   919  				return errors.Errorf("duplicate mount target")
   920  			}
   921  			mountsByTarget[mount.Target] = mount
   922  		}
   923  	}
   924  
   925  	// Add old list of mount points minus updated one.
   926  	for _, mount := range *mounts {
   927  		if _, ok := mountsByTarget[mount.Target]; !ok {
   928  			mountsByTarget[mount.Target] = mount
   929  		}
   930  	}
   931  
   932  	newMounts := []mounttypes.Mount{}
   933  
   934  	toRemove := buildToRemoveSet(flags, flagMountRemove)
   935  
   936  	for _, mount := range mountsByTarget {
   937  		if _, exists := toRemove[mount.Target]; !exists {
   938  			newMounts = append(newMounts, mount)
   939  		}
   940  	}
   941  	sort.Slice(newMounts, func(i, j int) bool {
   942  		a, b := newMounts[i], newMounts[j]
   943  
   944  		if a.Source == b.Source {
   945  			return a.Target < b.Target
   946  		}
   947  
   948  		return a.Source < b.Source
   949  	})
   950  	*mounts = newMounts
   951  	return nil
   952  }
   953  
   954  func updateGroups(flags *pflag.FlagSet, groups *[]string) error {
   955  	if flags.Changed(flagGroupAdd) {
   956  		values := flags.Lookup(flagGroupAdd).Value.(*opts.ListOpts).GetAll()
   957  		*groups = append(*groups, values...)
   958  	}
   959  	toRemove := buildToRemoveSet(flags, flagGroupRemove)
   960  
   961  	newGroups := []string{}
   962  	for _, group := range *groups {
   963  		if _, exists := toRemove[group]; !exists {
   964  			newGroups = append(newGroups, group)
   965  		}
   966  	}
   967  	// Sort so that result is predictable.
   968  	sort.Strings(newGroups)
   969  
   970  	*groups = newGroups
   971  	return nil
   972  }
   973  
   974  func removeDuplicates(entries []string) []string {
   975  	hit := map[string]bool{}
   976  	newEntries := []string{}
   977  	for _, v := range entries {
   978  		if !hit[v] {
   979  			newEntries = append(newEntries, v)
   980  			hit[v] = true
   981  		}
   982  	}
   983  	return newEntries
   984  }
   985  
   986  func updateDNSConfig(flags *pflag.FlagSet, config **swarm.DNSConfig) error {
   987  	newConfig := &swarm.DNSConfig{}
   988  
   989  	nameservers := (*config).Nameservers
   990  	if flags.Changed(flagDNSAdd) {
   991  		values := flags.Lookup(flagDNSAdd).Value.(*opts.ListOpts).GetAll()
   992  		nameservers = append(nameservers, values...)
   993  	}
   994  	nameservers = removeDuplicates(nameservers)
   995  	toRemove := buildToRemoveSet(flags, flagDNSRemove)
   996  	for _, nameserver := range nameservers {
   997  		if _, exists := toRemove[nameserver]; !exists {
   998  			newConfig.Nameservers = append(newConfig.Nameservers, nameserver)
   999  
  1000  		}
  1001  	}
  1002  	// Sort so that result is predictable.
  1003  	sort.Strings(newConfig.Nameservers)
  1004  
  1005  	search := (*config).Search
  1006  	if flags.Changed(flagDNSSearchAdd) {
  1007  		values := flags.Lookup(flagDNSSearchAdd).Value.(*opts.ListOpts).GetAll()
  1008  		search = append(search, values...)
  1009  	}
  1010  	search = removeDuplicates(search)
  1011  	toRemove = buildToRemoveSet(flags, flagDNSSearchRemove)
  1012  	for _, entry := range search {
  1013  		if _, exists := toRemove[entry]; !exists {
  1014  			newConfig.Search = append(newConfig.Search, entry)
  1015  		}
  1016  	}
  1017  	// Sort so that result is predictable.
  1018  	sort.Strings(newConfig.Search)
  1019  
  1020  	options := (*config).Options
  1021  	if flags.Changed(flagDNSOptionAdd) {
  1022  		values := flags.Lookup(flagDNSOptionAdd).Value.(*opts.ListOpts).GetAll()
  1023  		options = append(options, values...)
  1024  	}
  1025  	options = removeDuplicates(options)
  1026  	toRemove = buildToRemoveSet(flags, flagDNSOptionRemove)
  1027  	for _, option := range options {
  1028  		if _, exists := toRemove[option]; !exists {
  1029  			newConfig.Options = append(newConfig.Options, option)
  1030  		}
  1031  	}
  1032  	// Sort so that result is predictable.
  1033  	sort.Strings(newConfig.Options)
  1034  
  1035  	*config = newConfig
  1036  	return nil
  1037  }
  1038  
  1039  func portConfigToString(portConfig *swarm.PortConfig) string {
  1040  	protocol := portConfig.Protocol
  1041  	mode := portConfig.PublishMode
  1042  	return fmt.Sprintf("%v:%v/%s/%s", portConfig.PublishedPort, portConfig.TargetPort, protocol, mode)
  1043  }
  1044  
  1045  func updatePorts(flags *pflag.FlagSet, portConfig *[]swarm.PortConfig) error {
  1046  	// The key of the map is `port/protocol`, e.g., `80/tcp`
  1047  	portSet := map[string]swarm.PortConfig{}
  1048  
  1049  	// Build the current list of portConfig
  1050  	for _, entry := range *portConfig {
  1051  		entry := entry
  1052  		if _, ok := portSet[portConfigToString(&entry)]; !ok {
  1053  			portSet[portConfigToString(&entry)] = entry
  1054  		}
  1055  	}
  1056  
  1057  	newPorts := []swarm.PortConfig{}
  1058  
  1059  	// Clean current ports
  1060  	toRemove := flags.Lookup(flagPublishRemove).Value.(*opts.PortOpt).Value()
  1061  portLoop:
  1062  	for _, port := range portSet {
  1063  		for _, pConfig := range toRemove {
  1064  			if equalProtocol(port.Protocol, pConfig.Protocol) &&
  1065  				port.TargetPort == pConfig.TargetPort &&
  1066  				equalPublishMode(port.PublishMode, pConfig.PublishMode) {
  1067  				continue portLoop
  1068  			}
  1069  		}
  1070  
  1071  		newPorts = append(newPorts, port)
  1072  	}
  1073  
  1074  	// Check to see if there are any conflict in flags.
  1075  	if flags.Changed(flagPublishAdd) {
  1076  		ports := flags.Lookup(flagPublishAdd).Value.(*opts.PortOpt).Value()
  1077  
  1078  		for _, port := range ports {
  1079  			port := port
  1080  			if _, ok := portSet[portConfigToString(&port)]; ok {
  1081  				continue
  1082  			}
  1083  			// portSet[portConfigToString(&port)] = port
  1084  			newPorts = append(newPorts, port)
  1085  		}
  1086  	}
  1087  
  1088  	// Sort the PortConfig to avoid unnecessary updates
  1089  	sort.Slice(newPorts, func(i, j int) bool {
  1090  		// We convert PortConfig into `port/protocol`, e.g., `80/tcp`
  1091  		// In updatePorts we already filter out with map so there is duplicate entries
  1092  		return portConfigToString(&newPorts[i]) < portConfigToString(&newPorts[j])
  1093  	})
  1094  	*portConfig = newPorts
  1095  	return nil
  1096  }
  1097  
  1098  func equalProtocol(prot1, prot2 swarm.PortConfigProtocol) bool {
  1099  	return prot1 == prot2 ||
  1100  		(prot1 == swarm.PortConfigProtocol("") && prot2 == swarm.PortConfigProtocolTCP) ||
  1101  		(prot2 == swarm.PortConfigProtocol("") && prot1 == swarm.PortConfigProtocolTCP)
  1102  }
  1103  
  1104  func equalPublishMode(mode1, mode2 swarm.PortConfigPublishMode) bool {
  1105  	return mode1 == mode2 ||
  1106  		(mode1 == swarm.PortConfigPublishMode("") && mode2 == swarm.PortConfigPublishModeIngress) ||
  1107  		(mode2 == swarm.PortConfigPublishMode("") && mode1 == swarm.PortConfigPublishModeIngress)
  1108  }
  1109  
  1110  func updateReplicas(flags *pflag.FlagSet, serviceMode *swarm.ServiceMode) error {
  1111  	if !flags.Changed(flagReplicas) {
  1112  		return nil
  1113  	}
  1114  
  1115  	if serviceMode == nil || serviceMode.Replicated == nil {
  1116  		return errors.Errorf("replicas can only be used with replicated mode")
  1117  	}
  1118  	serviceMode.Replicated.Replicas = flags.Lookup(flagReplicas).Value.(*Uint64Opt).Value()
  1119  	return nil
  1120  }
  1121  
  1122  type hostMapping struct {
  1123  	IPAddr string
  1124  	Host   string
  1125  }
  1126  
  1127  // updateHosts performs a diff between existing host entries, entries to be
  1128  // removed, and entries to be added. Host entries preserve the order in which they
  1129  // were added, as the specification mentions that in case multiple entries for a
  1130  // host exist, the first entry should be used (by default).
  1131  //
  1132  // Note that, even though unsupported by the CLI, the service specs format
  1133  // allow entries with both a _canonical_ hostname, and one or more aliases
  1134  // in an entry (IP-address canonical_hostname [alias ...])
  1135  //
  1136  // Entries can be removed by either a specific `<host-name>:<ip-address>` mapping,
  1137  // or by `<host>` alone:
  1138  //
  1139  // - If both IP-address and host-name is provided, the hostname is removed only
  1140  //   from entries that match the given IP-address.
  1141  // - If only a host-name is provided, the hostname is removed from any entry it
  1142  //   is part of (either as canonical host-name, or as alias).
  1143  // - If, after removing the host-name from an entry, no host-names remain in
  1144  //   the entry, the entry itself is removed.
  1145  //
  1146  // For example, the list of host-entries before processing could look like this:
  1147  //
  1148  //    hosts = &[]string{
  1149  //        "127.0.0.2 host3 host1 host2 host4",
  1150  //        "127.0.0.1 host1 host4",
  1151  //        "127.0.0.3 host1",
  1152  //        "127.0.0.1 host1",
  1153  //    }
  1154  //
  1155  // Removing `host1` removes every occurrence:
  1156  //
  1157  //    hosts = &[]string{
  1158  //        "127.0.0.2 host3 host2 host4",
  1159  //        "127.0.0.1 host4",
  1160  //    }
  1161  //
  1162  // Removing `host1:127.0.0.1` on the other hand, only remove the host if the
  1163  // IP-address matches:
  1164  //
  1165  //    hosts = &[]string{
  1166  //        "127.0.0.2 host3 host1 host2 host4",
  1167  //        "127.0.0.1 host4",
  1168  //        "127.0.0.3 host1",
  1169  //    }
  1170  func updateHosts(flags *pflag.FlagSet, hosts *[]string) error {
  1171  	var toRemove []hostMapping
  1172  	if flags.Changed(flagHostRemove) {
  1173  		extraHostsToRemove := flags.Lookup(flagHostRemove).Value.(*opts.ListOpts).GetAll()
  1174  		for _, entry := range extraHostsToRemove {
  1175  			v := strings.SplitN(entry, ":", 2)
  1176  			if len(v) > 1 {
  1177  				toRemove = append(toRemove, hostMapping{IPAddr: v[1], Host: v[0]})
  1178  			} else {
  1179  				toRemove = append(toRemove, hostMapping{Host: v[0]})
  1180  			}
  1181  		}
  1182  	}
  1183  
  1184  	var newHosts []string
  1185  	for _, entry := range *hosts {
  1186  		// Since this is in SwarmKit format, we need to find the key, which is canonical_hostname of:
  1187  		// IP_address canonical_hostname [aliases...]
  1188  		parts := strings.Fields(entry)
  1189  		if len(parts) == 0 {
  1190  			continue
  1191  		}
  1192  		ip := parts[0]
  1193  		hostNames := parts[1:]
  1194  		for _, rm := range toRemove {
  1195  			if rm.IPAddr != "" && rm.IPAddr != ip {
  1196  				continue
  1197  			}
  1198  			for i, h := range hostNames {
  1199  				if h == rm.Host {
  1200  					hostNames = append(hostNames[:i], hostNames[i+1:]...)
  1201  				}
  1202  			}
  1203  		}
  1204  		if len(hostNames) > 0 {
  1205  			newHosts = append(newHosts, fmt.Sprintf("%s %s", ip, strings.Join(hostNames, " ")))
  1206  		}
  1207  	}
  1208  
  1209  	// Append new hosts (in SwarmKit format)
  1210  	if flags.Changed(flagHostAdd) {
  1211  		values := convertExtraHostsToSwarmHosts(flags.Lookup(flagHostAdd).Value.(*opts.ListOpts).GetAll())
  1212  		newHosts = append(newHosts, values...)
  1213  	}
  1214  	*hosts = removeDuplicates(newHosts)
  1215  	return nil
  1216  }
  1217  
  1218  // updateLogDriver updates the log driver only if the log driver flag is set.
  1219  // All options will be replaced with those provided on the command line.
  1220  func updateLogDriver(flags *pflag.FlagSet, taskTemplate *swarm.TaskSpec) error {
  1221  	if !flags.Changed(flagLogDriver) {
  1222  		return nil
  1223  	}
  1224  
  1225  	name, err := flags.GetString(flagLogDriver)
  1226  	if err != nil {
  1227  		return err
  1228  	}
  1229  
  1230  	if name == "" {
  1231  		return nil
  1232  	}
  1233  
  1234  	taskTemplate.LogDriver = &swarm.Driver{
  1235  		Name:    name,
  1236  		Options: opts.ConvertKVStringsToMap(flags.Lookup(flagLogOpt).Value.(*opts.ListOpts).GetAll()),
  1237  	}
  1238  
  1239  	return nil
  1240  }
  1241  
  1242  func updateHealthcheck(flags *pflag.FlagSet, containerSpec *swarm.ContainerSpec) error {
  1243  	if !anyChanged(flags, flagNoHealthcheck, flagHealthCmd, flagHealthInterval, flagHealthRetries, flagHealthTimeout, flagHealthStartPeriod) {
  1244  		return nil
  1245  	}
  1246  	if containerSpec.Healthcheck == nil {
  1247  		containerSpec.Healthcheck = &container.HealthConfig{}
  1248  	}
  1249  	noHealthcheck, err := flags.GetBool(flagNoHealthcheck)
  1250  	if err != nil {
  1251  		return err
  1252  	}
  1253  	if noHealthcheck {
  1254  		if !anyChanged(flags, flagHealthCmd, flagHealthInterval, flagHealthRetries, flagHealthTimeout, flagHealthStartPeriod) {
  1255  			containerSpec.Healthcheck = &container.HealthConfig{
  1256  				Test: []string{"NONE"},
  1257  			}
  1258  			return nil
  1259  		}
  1260  		return errors.Errorf("--%s conflicts with --health-* options", flagNoHealthcheck)
  1261  	}
  1262  	if len(containerSpec.Healthcheck.Test) > 0 && containerSpec.Healthcheck.Test[0] == "NONE" {
  1263  		containerSpec.Healthcheck.Test = nil
  1264  	}
  1265  	if flags.Changed(flagHealthInterval) {
  1266  		val := *flags.Lookup(flagHealthInterval).Value.(*opts.PositiveDurationOpt).Value()
  1267  		containerSpec.Healthcheck.Interval = val
  1268  	}
  1269  	if flags.Changed(flagHealthTimeout) {
  1270  		val := *flags.Lookup(flagHealthTimeout).Value.(*opts.PositiveDurationOpt).Value()
  1271  		containerSpec.Healthcheck.Timeout = val
  1272  	}
  1273  	if flags.Changed(flagHealthStartPeriod) {
  1274  		val := *flags.Lookup(flagHealthStartPeriod).Value.(*opts.PositiveDurationOpt).Value()
  1275  		containerSpec.Healthcheck.StartPeriod = val
  1276  	}
  1277  	if flags.Changed(flagHealthRetries) {
  1278  		containerSpec.Healthcheck.Retries, _ = flags.GetInt(flagHealthRetries)
  1279  	}
  1280  	if flags.Changed(flagHealthCmd) {
  1281  		cmd, _ := flags.GetString(flagHealthCmd)
  1282  		if cmd != "" {
  1283  			containerSpec.Healthcheck.Test = []string{"CMD-SHELL", cmd}
  1284  		} else {
  1285  			containerSpec.Healthcheck.Test = nil
  1286  		}
  1287  	}
  1288  	return nil
  1289  }
  1290  
  1291  func updateNetworks(ctx context.Context, apiClient client.NetworkAPIClient, flags *pflag.FlagSet, spec *swarm.ServiceSpec) error {
  1292  	// spec.TaskTemplate.Networks takes precedence over the deprecated
  1293  	// spec.Networks field. If spec.Network is in use, we'll migrate those
  1294  	// values to spec.TaskTemplate.Networks.
  1295  	specNetworks := spec.TaskTemplate.Networks
  1296  	if len(specNetworks) == 0 {
  1297  		specNetworks = spec.Networks
  1298  	}
  1299  	spec.Networks = nil
  1300  
  1301  	toRemove := buildToRemoveSet(flags, flagNetworkRemove)
  1302  	idsToRemove := make(map[string]struct{})
  1303  	for networkIDOrName := range toRemove {
  1304  		network, err := apiClient.NetworkInspect(ctx, networkIDOrName, types.NetworkInspectOptions{Scope: "swarm"})
  1305  		if err != nil {
  1306  			return err
  1307  		}
  1308  		idsToRemove[network.ID] = struct{}{}
  1309  	}
  1310  
  1311  	existingNetworks := make(map[string]struct{})
  1312  	var newNetworks []swarm.NetworkAttachmentConfig
  1313  	for _, network := range specNetworks {
  1314  		if _, exists := idsToRemove[network.Target]; exists {
  1315  			continue
  1316  		}
  1317  
  1318  		newNetworks = append(newNetworks, network)
  1319  		existingNetworks[network.Target] = struct{}{}
  1320  	}
  1321  
  1322  	if flags.Changed(flagNetworkAdd) {
  1323  		values := flags.Lookup(flagNetworkAdd).Value.(*opts.NetworkOpt)
  1324  		networks := convertNetworks(*values)
  1325  		for _, network := range networks {
  1326  			nwID, err := resolveNetworkID(ctx, apiClient, network.Target)
  1327  			if err != nil {
  1328  				return err
  1329  			}
  1330  			if _, exists := existingNetworks[nwID]; exists {
  1331  				return errors.Errorf("service is already attached to network %s", network.Target)
  1332  			}
  1333  			network.Target = nwID
  1334  			newNetworks = append(newNetworks, network)
  1335  			existingNetworks[network.Target] = struct{}{}
  1336  		}
  1337  	}
  1338  
  1339  	sort.Slice(newNetworks, func(i, j int) bool {
  1340  		return newNetworks[i].Target < newNetworks[j].Target
  1341  	})
  1342  
  1343  	spec.TaskTemplate.Networks = newNetworks
  1344  	return nil
  1345  }
  1346  
  1347  // updateCredSpecConfig updates the value of the credential spec Config field
  1348  // to the config ID if the credential spec has changed. it mutates the passed
  1349  // spec. it does not handle the case where the credential spec specifies a
  1350  // config that does not exist -- that case is handled as part of
  1351  // getUpdatedConfigs
  1352  func updateCredSpecConfig(flags *pflag.FlagSet, containerSpec *swarm.ContainerSpec) {
  1353  	if flags.Changed(flagCredentialSpec) {
  1354  		credSpecOpt := flags.Lookup(flagCredentialSpec)
  1355  		// if the flag has changed, and the value is empty string, then we
  1356  		// should remove any credential spec that might be present
  1357  		if credSpecOpt.Value.String() == "" {
  1358  			if containerSpec.Privileges != nil {
  1359  				containerSpec.Privileges.CredentialSpec = nil
  1360  			}
  1361  			return
  1362  		}
  1363  
  1364  		// otherwise, set the credential spec to be the parsed value
  1365  		credSpec := credSpecOpt.Value.(*credentialSpecOpt).Value()
  1366  
  1367  		// if this is a Config credential spec, we we still need to replace the
  1368  		// value of credSpec.Config with the config ID instead of Name.
  1369  		if credSpec.Config != "" {
  1370  			for _, config := range containerSpec.Configs {
  1371  				// if the config name matches, then set the config ID. we do
  1372  				// not need to worry about if this is a Runtime target or not.
  1373  				// even if it is not a Runtime target, getUpdatedConfigs
  1374  				// ensures that a Runtime target for this config exists, and
  1375  				// the Name is unique so the ID is correct no matter the
  1376  				// target.
  1377  				if config.ConfigName == credSpec.Config {
  1378  					credSpec.Config = config.ConfigID
  1379  					break
  1380  				}
  1381  			}
  1382  		}
  1383  
  1384  		if containerSpec.Privileges == nil {
  1385  			containerSpec.Privileges = &swarm.Privileges{}
  1386  		}
  1387  
  1388  		containerSpec.Privileges.CredentialSpec = credSpec
  1389  	}
  1390  }
  1391  
  1392  // updateCapabilities calculates the list of capabilities to "drop" and to "add"
  1393  // after applying the capabilities passed through `--cap-add` and `--cap-drop`
  1394  // to the existing list of added/dropped capabilities in the service spec.
  1395  //
  1396  // Adding capabilities takes precedence over "dropping" the same capability, so
  1397  // if both `--cap-add` and `--cap-drop` are specifying the same capability, the
  1398  // `--cap-drop` is ignored.
  1399  //
  1400  // Capabilities to "drop" are removed from the existing list of "added"
  1401  // capabilities, and vice-versa (capabilities to "add" are removed from the existing
  1402  // list of capabilities to "drop").
  1403  //
  1404  // Capabilities are normalized, sorted, and duplicates are removed to prevent
  1405  // service tasks from being updated if no changes are made. If a list has the "ALL"
  1406  // capability set, then any other capability is removed from that list.
  1407  //
  1408  // Adding/removing capabilities when updating a service is handled as a tri-state;
  1409  //
  1410  // - if the capability was previously "dropped", then remove it from "CapabilityDrop",
  1411  //   but NOT added to "CapabilityAdd". However, if the capability was not yet in
  1412  //   the service's "CapabilityDrop", then it's simply added to the service's "CapabilityAdd"
  1413  // - likewise, if the capability was previously "added", then it's removed from
  1414  //   "CapabilityAdd", but NOT added to "CapabilityDrop". If the capability was
  1415  //   not yet in the service's "CapabilityAdd", then simply add it to the service's
  1416  //   "CapabilityDrop".
  1417  //
  1418  // In other words, given a service with the following:
  1419  //
  1420  // | CapDrop        | CapAdd        |
  1421  // | -------------- | ------------- |
  1422  // | CAP_SOME_CAP   |               |
  1423  //
  1424  // When updating the service, and applying `--cap-add CAP_SOME_CAP`, the previously
  1425  // dropped capability is removed:
  1426  //
  1427  // | CapDrop        | CapAdd        |
  1428  // | -------------- | ------------- |
  1429  // |                |               |
  1430  //
  1431  // After updating the service a second time, applying `--cap-add CAP_SOME_CAP`,
  1432  // capability is now added:
  1433  //
  1434  // | CapDrop        | CapAdd        |
  1435  // | -------------- | ------------- |
  1436  // |                | CAP_SOME_CAP  |
  1437  //
  1438  func updateCapabilities(flags *pflag.FlagSet, containerSpec *swarm.ContainerSpec) {
  1439  	var (
  1440  		toAdd, toDrop map[string]bool
  1441  
  1442  		capDrop = opts.CapabilitiesMap(containerSpec.CapabilityDrop)
  1443  		capAdd  = opts.CapabilitiesMap(containerSpec.CapabilityAdd)
  1444  	)
  1445  	if flags.Changed(flagCapAdd) {
  1446  		toAdd = opts.CapabilitiesMap(flags.Lookup(flagCapAdd).Value.(*opts.ListOpts).GetAll())
  1447  		if toAdd[opts.ResetCapabilities] {
  1448  			capAdd = make(map[string]bool)
  1449  			delete(toAdd, opts.ResetCapabilities)
  1450  		}
  1451  	}
  1452  	if flags.Changed(flagCapDrop) {
  1453  		toDrop = opts.CapabilitiesMap(flags.Lookup(flagCapDrop).Value.(*opts.ListOpts).GetAll())
  1454  		if toDrop[opts.ResetCapabilities] {
  1455  			capDrop = make(map[string]bool)
  1456  			delete(toDrop, opts.ResetCapabilities)
  1457  		}
  1458  	}
  1459  
  1460  	// First remove the capabilities to "drop" from the service's exiting
  1461  	// list of capabilities to "add". If a capability is both added and dropped
  1462  	// on update, then "adding" takes precedence.
  1463  	//
  1464  	// Dropping a capability when updating a service is considered a tri-state;
  1465  	//
  1466  	// - if the capability was previously "added", then remove it from
  1467  	//   "CapabilityAdd", and do NOT add it to "CapabilityDrop"
  1468  	// - if the capability was not yet in the service's "CapabilityAdd",
  1469  	//   then simply add it to the service's "CapabilityDrop"
  1470  	for c := range toDrop {
  1471  		if !toAdd[c] {
  1472  			if capAdd[c] {
  1473  				delete(capAdd, c)
  1474  			} else {
  1475  				capDrop[c] = true
  1476  			}
  1477  		}
  1478  	}
  1479  
  1480  	// And remove the capabilities we're "adding" from the service's existing
  1481  	// list of capabilities to "drop".
  1482  	//
  1483  	// "Adding" capabilities takes precedence over "dropping" them, so if a
  1484  	// capability is set both as "add" and "drop", remove the capability from
  1485  	// the service's list of dropped capabilities (if present).
  1486  	//
  1487  	// Adding a capability when updating a service is considered a tri-state;
  1488  	//
  1489  	// - if the capability was previously "dropped", then remove it from
  1490  	//   "CapabilityDrop", and do NOT add it to "CapabilityAdd"
  1491  	// - if the capability was not yet in the service's "CapabilityDrop",
  1492  	//   then simply add it to the service's "CapabilityAdd"
  1493  	for c := range toAdd {
  1494  		if capDrop[c] {
  1495  			delete(capDrop, c)
  1496  		} else {
  1497  			capAdd[c] = true
  1498  		}
  1499  	}
  1500  
  1501  	// Now that the service's existing lists are updated, apply the new
  1502  	// capabilities to add/drop to both lists. Sort the lists to prevent
  1503  	// unneeded updates to service-tasks.
  1504  	containerSpec.CapabilityDrop = capsList(capDrop)
  1505  	containerSpec.CapabilityAdd = capsList(capAdd)
  1506  }
  1507  
  1508  func capsList(caps map[string]bool) []string {
  1509  	if caps[opts.AllCapabilities] {
  1510  		return []string{opts.AllCapabilities}
  1511  	}
  1512  	var out []string
  1513  	for c := range caps {
  1514  		out = append(out, c)
  1515  	}
  1516  	sort.Strings(out)
  1517  	return out
  1518  }