1package service
2
3import (
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	"github.com/docker/swarmkit/api/defaults"
20	"github.com/pkg/errors"
21	"github.com/spf13/cobra"
22	"github.com/spf13/pflag"
23)
24
25func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
26	options := newServiceOptions()
27
28	cmd := &cobra.Command{
29		Use:   "update [OPTIONS] SERVICE",
30		Short: "Update a service",
31		Args:  cli.ExactArgs(1),
32		RunE: func(cmd *cobra.Command, args []string) error {
33			return runUpdate(dockerCli, cmd.Flags(), options, args[0])
34		},
35	}
36
37	flags := cmd.Flags()
38	flags.String("image", "", "Service image tag")
39	flags.Var(&ShlexOpt{}, "args", "Service command args")
40	flags.Bool(flagRollback, false, "Rollback to previous specification")
41	flags.SetAnnotation(flagRollback, "version", []string{"1.25"})
42	flags.Bool("force", false, "Force update even if no changes require it")
43	flags.SetAnnotation("force", "version", []string{"1.25"})
44	addServiceFlags(flags, options, nil)
45
46	flags.Var(newListOptsVar(), flagEnvRemove, "Remove an environment variable")
47	flags.Var(newListOptsVar(), flagGroupRemove, "Remove a previously added supplementary user group from the container")
48	flags.SetAnnotation(flagGroupRemove, "version", []string{"1.25"})
49	flags.Var(newListOptsVar(), flagLabelRemove, "Remove a label by its key")
50	flags.Var(newListOptsVar(), flagContainerLabelRemove, "Remove a container label by its key")
51	flags.Var(newListOptsVar(), flagMountRemove, "Remove a mount by its target path")
52	// flags.Var(newListOptsVar().WithValidator(validatePublishRemove), flagPublishRemove, "Remove a published port by its target port")
53	flags.Var(&opts.PortOpt{}, flagPublishRemove, "Remove a published port by its target port")
54	flags.Var(newListOptsVar(), flagConstraintRemove, "Remove a constraint")
55	flags.Var(newListOptsVar(), flagDNSRemove, "Remove a custom DNS server")
56	flags.SetAnnotation(flagDNSRemove, "version", []string{"1.25"})
57	flags.Var(newListOptsVar(), flagDNSOptionRemove, "Remove a DNS option")
58	flags.SetAnnotation(flagDNSOptionRemove, "version", []string{"1.25"})
59	flags.Var(newListOptsVar(), flagDNSSearchRemove, "Remove a DNS search domain")
60	flags.SetAnnotation(flagDNSSearchRemove, "version", []string{"1.25"})
61	flags.Var(newListOptsVar(), flagHostRemove, "Remove a custom host-to-IP mapping (host:ip)")
62	flags.SetAnnotation(flagHostRemove, "version", []string{"1.25"})
63	flags.Var(&options.labels, flagLabelAdd, "Add or update a service label")
64	flags.Var(&options.containerLabels, flagContainerLabelAdd, "Add or update a container label")
65	flags.Var(&options.env, flagEnvAdd, "Add or update an environment variable")
66	flags.Var(newListOptsVar(), flagSecretRemove, "Remove a secret")
67	flags.SetAnnotation(flagSecretRemove, "version", []string{"1.25"})
68	flags.Var(&options.secrets, flagSecretAdd, "Add or update a secret on a service")
69	flags.SetAnnotation(flagSecretAdd, "version", []string{"1.25"})
70
71	flags.Var(newListOptsVar(), flagConfigRemove, "Remove a configuration file")
72	flags.SetAnnotation(flagConfigRemove, "version", []string{"1.30"})
73	flags.Var(&options.configs, flagConfigAdd, "Add or update a config file on a service")
74	flags.SetAnnotation(flagConfigAdd, "version", []string{"1.30"})
75
76	flags.Var(&options.mounts, flagMountAdd, "Add or update a mount on a service")
77	flags.Var(&options.constraints, flagConstraintAdd, "Add or update a placement constraint")
78	flags.Var(&options.placementPrefs, flagPlacementPrefAdd, "Add a placement preference")
79	flags.SetAnnotation(flagPlacementPrefAdd, "version", []string{"1.28"})
80	flags.Var(&placementPrefOpts{}, flagPlacementPrefRemove, "Remove a placement preference")
81	flags.SetAnnotation(flagPlacementPrefRemove, "version", []string{"1.28"})
82	flags.Var(&options.networks, flagNetworkAdd, "Add a network")
83	flags.SetAnnotation(flagNetworkAdd, "version", []string{"1.29"})
84	flags.Var(newListOptsVar(), flagNetworkRemove, "Remove a network")
85	flags.SetAnnotation(flagNetworkRemove, "version", []string{"1.29"})
86	flags.Var(&options.endpoint.publishPorts, flagPublishAdd, "Add or update a published port")
87	flags.Var(&options.groups, flagGroupAdd, "Add an additional supplementary user group to the container")
88	flags.SetAnnotation(flagGroupAdd, "version", []string{"1.25"})
89	flags.Var(&options.dns, flagDNSAdd, "Add or update a custom DNS server")
90	flags.SetAnnotation(flagDNSAdd, "version", []string{"1.25"})
91	flags.Var(&options.dnsOption, flagDNSOptionAdd, "Add or update a DNS option")
92	flags.SetAnnotation(flagDNSOptionAdd, "version", []string{"1.25"})
93	flags.Var(&options.dnsSearch, flagDNSSearchAdd, "Add or update a custom DNS search domain")
94	flags.SetAnnotation(flagDNSSearchAdd, "version", []string{"1.25"})
95	flags.Var(&options.hosts, flagHostAdd, "Add a custom host-to-IP mapping (host:ip)")
96	flags.SetAnnotation(flagHostAdd, "version", []string{"1.25"})
97	flags.BoolVar(&options.init, flagInit, false, "Use an init inside each service container to forward signals and reap processes")
98	flags.SetAnnotation(flagInit, "version", []string{"1.37"})
99
100	// Add needs parsing, Remove only needs the key
101	flags.Var(newListOptsVar(), flagGenericResourcesRemove, "Remove a Generic resource")
102	flags.SetAnnotation(flagHostAdd, "version", []string{"1.32"})
103	flags.Var(newListOptsVarWithValidator(ValidateSingleGenericResource), flagGenericResourcesAdd, "Add a Generic resource")
104	flags.SetAnnotation(flagHostAdd, "version", []string{"1.32"})
105
106	return cmd
107}
108
109func newListOptsVar() *opts.ListOpts {
110	return opts.NewListOptsRef(&[]string{}, nil)
111}
112
113func newListOptsVarWithValidator(validator opts.ValidatorFctType) *opts.ListOpts {
114	return opts.NewListOptsRef(&[]string{}, validator)
115}
116
117// nolint: gocyclo
118func runUpdate(dockerCli command.Cli, flags *pflag.FlagSet, options *serviceOptions, serviceID string) error {
119	apiClient := dockerCli.Client()
120	ctx := context.Background()
121
122	service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
123	if err != nil {
124		return err
125	}
126
127	rollback, err := flags.GetBool(flagRollback)
128	if err != nil {
129		return err
130	}
131
132	// There are two ways to do user-requested rollback. The old way is
133	// client-side, but with a sufficiently recent daemon we prefer
134	// server-side, because it will honor the rollback parameters.
135	var (
136		clientSideRollback bool
137		serverSideRollback bool
138	)
139
140	spec := &service.Spec
141	if rollback {
142		// Rollback can't be combined with other flags.
143		otherFlagsPassed := false
144		flags.VisitAll(func(f *pflag.Flag) {
145			if f.Name == flagRollback || f.Name == flagDetach || f.Name == flagQuiet {
146				return
147			}
148			if flags.Changed(f.Name) {
149				otherFlagsPassed = true
150			}
151		})
152		if otherFlagsPassed {
153			return errors.New("other flags may not be combined with --rollback")
154		}
155
156		if versions.LessThan(apiClient.ClientVersion(), "1.28") {
157			clientSideRollback = true
158			spec = service.PreviousSpec
159			if spec == nil {
160				return errors.Errorf("service does not have a previous specification to roll back to")
161			}
162		} else {
163			serverSideRollback = true
164		}
165	}
166
167	updateOpts := types.ServiceUpdateOptions{}
168	if serverSideRollback {
169		updateOpts.Rollback = "previous"
170	}
171
172	err = updateService(ctx, apiClient, flags, spec)
173	if err != nil {
174		return err
175	}
176
177	if flags.Changed("image") {
178		if err := resolveServiceImageDigestContentTrust(dockerCli, spec); err != nil {
179			return err
180		}
181		if !options.noResolveImage && versions.GreaterThanOrEqualTo(apiClient.ClientVersion(), "1.30") {
182			updateOpts.QueryRegistry = true
183		}
184	}
185
186	updatedSecrets, err := getUpdatedSecrets(apiClient, flags, spec.TaskTemplate.ContainerSpec.Secrets)
187	if err != nil {
188		return err
189	}
190
191	spec.TaskTemplate.ContainerSpec.Secrets = updatedSecrets
192
193	updatedConfigs, err := getUpdatedConfigs(apiClient, flags, spec.TaskTemplate.ContainerSpec.Configs)
194	if err != nil {
195		return err
196	}
197
198	spec.TaskTemplate.ContainerSpec.Configs = updatedConfigs
199
200	// only send auth if flag was set
201	sendAuth, err := flags.GetBool(flagRegistryAuth)
202	if err != nil {
203		return err
204	}
205	if sendAuth {
206		// Retrieve encoded auth token from the image reference
207		// This would be the old image if it didn't change in this update
208		image := spec.TaskTemplate.ContainerSpec.Image
209		encodedAuth, err := command.RetrieveAuthTokenFromImage(ctx, dockerCli, image)
210		if err != nil {
211			return err
212		}
213		updateOpts.EncodedRegistryAuth = encodedAuth
214	} else if clientSideRollback {
215		updateOpts.RegistryAuthFrom = types.RegistryAuthFromPreviousSpec
216	} else {
217		updateOpts.RegistryAuthFrom = types.RegistryAuthFromSpec
218	}
219
220	response, err := apiClient.ServiceUpdate(ctx, service.ID, service.Version, *spec, updateOpts)
221	if err != nil {
222		return err
223	}
224
225	for _, warning := range response.Warnings {
226		fmt.Fprintln(dockerCli.Err(), warning)
227	}
228
229	fmt.Fprintf(dockerCli.Out(), "%s\n", serviceID)
230
231	if options.detach || versions.LessThan(apiClient.ClientVersion(), "1.29") {
232		return nil
233	}
234
235	return waitOnService(ctx, dockerCli, serviceID, options.quiet)
236}
237
238// nolint: gocyclo
239func updateService(ctx context.Context, apiClient client.NetworkAPIClient, flags *pflag.FlagSet, spec *swarm.ServiceSpec) error {
240	updateBoolPtr := func(flag string, field **bool) {
241		if flags.Changed(flag) {
242			b, _ := flags.GetBool(flag)
243			*field = &b
244		}
245	}
246	updateString := func(flag string, field *string) {
247		if flags.Changed(flag) {
248			*field, _ = flags.GetString(flag)
249		}
250	}
251
252	updateInt64Value := func(flag string, field *int64) {
253		if flags.Changed(flag) {
254			*field = flags.Lookup(flag).Value.(int64Value).Value()
255		}
256	}
257
258	updateFloatValue := func(flag string, field *float32) {
259		if flags.Changed(flag) {
260			*field = flags.Lookup(flag).Value.(*floatValue).Value()
261		}
262	}
263
264	updateDuration := func(flag string, field *time.Duration) {
265		if flags.Changed(flag) {
266			*field, _ = flags.GetDuration(flag)
267		}
268	}
269
270	updateDurationOpt := func(flag string, field **time.Duration) {
271		if flags.Changed(flag) {
272			val := *flags.Lookup(flag).Value.(*opts.DurationOpt).Value()
273			*field = &val
274		}
275	}
276
277	updateUint64 := func(flag string, field *uint64) {
278		if flags.Changed(flag) {
279			*field, _ = flags.GetUint64(flag)
280		}
281	}
282
283	updateUint64Opt := func(flag string, field **uint64) {
284		if flags.Changed(flag) {
285			val := *flags.Lookup(flag).Value.(*Uint64Opt).Value()
286			*field = &val
287		}
288	}
289
290	updateIsolation := func(flag string, field *container.Isolation) error {
291		if flags.Changed(flag) {
292			val, _ := flags.GetString(flag)
293			*field = container.Isolation(val)
294		}
295		return nil
296	}
297
298	cspec := spec.TaskTemplate.ContainerSpec
299	task := &spec.TaskTemplate
300
301	taskResources := func() *swarm.ResourceRequirements {
302		if task.Resources == nil {
303			task.Resources = &swarm.ResourceRequirements{}
304		}
305		if task.Resources.Limits == nil {
306			task.Resources.Limits = &swarm.Resources{}
307		}
308		if task.Resources.Reservations == nil {
309			task.Resources.Reservations = &swarm.Resources{}
310		}
311		return task.Resources
312	}
313
314	updateLabels(flags, &spec.Labels)
315	updateContainerLabels(flags, &cspec.Labels)
316	updateString("image", &cspec.Image)
317	updateStringToSlice(flags, "args", &cspec.Args)
318	updateStringToSlice(flags, flagEntrypoint, &cspec.Command)
319	updateEnvironment(flags, &cspec.Env)
320	updateString(flagWorkdir, &cspec.Dir)
321	updateString(flagUser, &cspec.User)
322	updateString(flagHostname, &cspec.Hostname)
323	updateBoolPtr(flagInit, &cspec.Init)
324	if err := updateIsolation(flagIsolation, &cspec.Isolation); err != nil {
325		return err
326	}
327	if err := updateMounts(flags, &cspec.Mounts); err != nil {
328		return err
329	}
330
331	if anyChanged(flags, flagLimitCPU, flagLimitMemory) {
332		taskResources().Limits = spec.TaskTemplate.Resources.Limits
333		updateInt64Value(flagLimitCPU, &task.Resources.Limits.NanoCPUs)
334		updateInt64Value(flagLimitMemory, &task.Resources.Limits.MemoryBytes)
335	}
336
337	if anyChanged(flags, flagReserveCPU, flagReserveMemory) {
338		taskResources().Reservations = spec.TaskTemplate.Resources.Reservations
339		updateInt64Value(flagReserveCPU, &task.Resources.Reservations.NanoCPUs)
340		updateInt64Value(flagReserveMemory, &task.Resources.Reservations.MemoryBytes)
341	}
342
343	if err := addGenericResources(flags, task); err != nil {
344		return err
345	}
346
347	if err := removeGenericResources(flags, task); err != nil {
348		return err
349	}
350
351	updateDurationOpt(flagStopGracePeriod, &cspec.StopGracePeriod)
352
353	if anyChanged(flags, flagRestartCondition, flagRestartDelay, flagRestartMaxAttempts, flagRestartWindow) {
354		if task.RestartPolicy == nil {
355			task.RestartPolicy = defaultRestartPolicy()
356		}
357		if flags.Changed(flagRestartCondition) {
358			value, _ := flags.GetString(flagRestartCondition)
359			task.RestartPolicy.Condition = swarm.RestartPolicyCondition(value)
360		}
361		updateDurationOpt(flagRestartDelay, &task.RestartPolicy.Delay)
362		updateUint64Opt(flagRestartMaxAttempts, &task.RestartPolicy.MaxAttempts)
363		updateDurationOpt(flagRestartWindow, &task.RestartPolicy.Window)
364	}
365
366	if anyChanged(flags, flagConstraintAdd, flagConstraintRemove) {
367		if task.Placement == nil {
368			task.Placement = &swarm.Placement{}
369		}
370		updatePlacementConstraints(flags, task.Placement)
371	}
372
373	if anyChanged(flags, flagPlacementPrefAdd, flagPlacementPrefRemove) {
374		if task.Placement == nil {
375			task.Placement = &swarm.Placement{}
376		}
377		updatePlacementPreferences(flags, task.Placement)
378	}
379
380	if anyChanged(flags, flagNetworkAdd, flagNetworkRemove) {
381		if err := updateNetworks(ctx, apiClient, flags, spec); err != nil {
382			return err
383		}
384	}
385
386	if err := updateReplicas(flags, &spec.Mode); err != nil {
387		return err
388	}
389
390	if anyChanged(flags, flagUpdateParallelism, flagUpdateDelay, flagUpdateMonitor, flagUpdateFailureAction, flagUpdateMaxFailureRatio, flagUpdateOrder) {
391		if spec.UpdateConfig == nil {
392			spec.UpdateConfig = updateConfigFromDefaults(defaults.Service.Update)
393		}
394		updateUint64(flagUpdateParallelism, &spec.UpdateConfig.Parallelism)
395		updateDuration(flagUpdateDelay, &spec.UpdateConfig.Delay)
396		updateDuration(flagUpdateMonitor, &spec.UpdateConfig.Monitor)
397		updateString(flagUpdateFailureAction, &spec.UpdateConfig.FailureAction)
398		updateFloatValue(flagUpdateMaxFailureRatio, &spec.UpdateConfig.MaxFailureRatio)
399		updateString(flagUpdateOrder, &spec.UpdateConfig.Order)
400	}
401
402	if anyChanged(flags, flagRollbackParallelism, flagRollbackDelay, flagRollbackMonitor, flagRollbackFailureAction, flagRollbackMaxFailureRatio, flagRollbackOrder) {
403		if spec.RollbackConfig == nil {
404			spec.RollbackConfig = updateConfigFromDefaults(defaults.Service.Rollback)
405		}
406		updateUint64(flagRollbackParallelism, &spec.RollbackConfig.Parallelism)
407		updateDuration(flagRollbackDelay, &spec.RollbackConfig.Delay)
408		updateDuration(flagRollbackMonitor, &spec.RollbackConfig.Monitor)
409		updateString(flagRollbackFailureAction, &spec.RollbackConfig.FailureAction)
410		updateFloatValue(flagRollbackMaxFailureRatio, &spec.RollbackConfig.MaxFailureRatio)
411		updateString(flagRollbackOrder, &spec.RollbackConfig.Order)
412	}
413
414	if flags.Changed(flagEndpointMode) {
415		value, _ := flags.GetString(flagEndpointMode)
416		if spec.EndpointSpec == nil {
417			spec.EndpointSpec = &swarm.EndpointSpec{}
418		}
419		spec.EndpointSpec.Mode = swarm.ResolutionMode(value)
420	}
421
422	if anyChanged(flags, flagGroupAdd, flagGroupRemove) {
423		if err := updateGroups(flags, &cspec.Groups); err != nil {
424			return err
425		}
426	}
427
428	if anyChanged(flags, flagPublishAdd, flagPublishRemove) {
429		if spec.EndpointSpec == nil {
430			spec.EndpointSpec = &swarm.EndpointSpec{}
431		}
432		if err := updatePorts(flags, &spec.EndpointSpec.Ports); err != nil {
433			return err
434		}
435	}
436
437	if anyChanged(flags, flagDNSAdd, flagDNSRemove, flagDNSOptionAdd, flagDNSOptionRemove, flagDNSSearchAdd, flagDNSSearchRemove) {
438		if cspec.DNSConfig == nil {
439			cspec.DNSConfig = &swarm.DNSConfig{}
440		}
441		if err := updateDNSConfig(flags, &cspec.DNSConfig); err != nil {
442			return err
443		}
444	}
445
446	if anyChanged(flags, flagHostAdd, flagHostRemove) {
447		if err := updateHosts(flags, &cspec.Hosts); err != nil {
448			return err
449		}
450	}
451
452	if err := updateLogDriver(flags, &spec.TaskTemplate); err != nil {
453		return err
454	}
455
456	force, err := flags.GetBool("force")
457	if err != nil {
458		return err
459	}
460
461	if force {
462		spec.TaskTemplate.ForceUpdate++
463	}
464
465	if err := updateHealthcheck(flags, cspec); err != nil {
466		return err
467	}
468
469	if flags.Changed(flagTTY) {
470		tty, err := flags.GetBool(flagTTY)
471		if err != nil {
472			return err
473		}
474		cspec.TTY = tty
475	}
476
477	if flags.Changed(flagReadOnly) {
478		readOnly, err := flags.GetBool(flagReadOnly)
479		if err != nil {
480			return err
481		}
482		cspec.ReadOnly = readOnly
483	}
484
485	updateString(flagStopSignal, &cspec.StopSignal)
486
487	return nil
488}
489
490func updateStringToSlice(flags *pflag.FlagSet, flag string, field *[]string) {
491	if !flags.Changed(flag) {
492		return
493	}
494
495	*field = flags.Lookup(flag).Value.(*ShlexOpt).Value()
496}
497
498func anyChanged(flags *pflag.FlagSet, fields ...string) bool {
499	for _, flag := range fields {
500		if flags.Changed(flag) {
501			return true
502		}
503	}
504	return false
505}
506
507func addGenericResources(flags *pflag.FlagSet, spec *swarm.TaskSpec) error {
508	if !flags.Changed(flagGenericResourcesAdd) {
509		return nil
510	}
511
512	if spec.Resources == nil {
513		spec.Resources = &swarm.ResourceRequirements{}
514	}
515
516	if spec.Resources.Reservations == nil {
517		spec.Resources.Reservations = &swarm.Resources{}
518	}
519
520	values := flags.Lookup(flagGenericResourcesAdd).Value.(*opts.ListOpts).GetAll()
521	generic, err := ParseGenericResources(values)
522	if err != nil {
523		return err
524	}
525
526	m, err := buildGenericResourceMap(spec.Resources.Reservations.GenericResources)
527	if err != nil {
528		return err
529	}
530
531	for _, toAddRes := range generic {
532		m[toAddRes.DiscreteResourceSpec.Kind] = toAddRes
533	}
534
535	spec.Resources.Reservations.GenericResources = buildGenericResourceList(m)
536
537	return nil
538}
539
540func removeGenericResources(flags *pflag.FlagSet, spec *swarm.TaskSpec) error {
541	// Can only be Discrete Resources
542	if !flags.Changed(flagGenericResourcesRemove) {
543		return nil
544	}
545
546	if spec.Resources == nil {
547		spec.Resources = &swarm.ResourceRequirements{}
548	}
549
550	if spec.Resources.Reservations == nil {
551		spec.Resources.Reservations = &swarm.Resources{}
552	}
553
554	values := flags.Lookup(flagGenericResourcesRemove).Value.(*opts.ListOpts).GetAll()
555
556	m, err := buildGenericResourceMap(spec.Resources.Reservations.GenericResources)
557	if err != nil {
558		return err
559	}
560
561	for _, toRemoveRes := range values {
562		if _, ok := m[toRemoveRes]; !ok {
563			return fmt.Errorf("could not find generic-resource `%s` to remove it", toRemoveRes)
564		}
565
566		delete(m, toRemoveRes)
567	}
568
569	spec.Resources.Reservations.GenericResources = buildGenericResourceList(m)
570	return nil
571}
572
573func updatePlacementConstraints(flags *pflag.FlagSet, placement *swarm.Placement) {
574	if flags.Changed(flagConstraintAdd) {
575		values := flags.Lookup(flagConstraintAdd).Value.(*opts.ListOpts).GetAll()
576		placement.Constraints = append(placement.Constraints, values...)
577	}
578	toRemove := buildToRemoveSet(flags, flagConstraintRemove)
579
580	newConstraints := []string{}
581	for _, constraint := range placement.Constraints {
582		if _, exists := toRemove[constraint]; !exists {
583			newConstraints = append(newConstraints, constraint)
584		}
585	}
586	// Sort so that result is predictable.
587	sort.Strings(newConstraints)
588
589	placement.Constraints = newConstraints
590}
591
592func updatePlacementPreferences(flags *pflag.FlagSet, placement *swarm.Placement) {
593	var newPrefs []swarm.PlacementPreference
594
595	if flags.Changed(flagPlacementPrefRemove) {
596		for _, existing := range placement.Preferences {
597			removed := false
598			for _, removal := range flags.Lookup(flagPlacementPrefRemove).Value.(*placementPrefOpts).prefs {
599				if removal.Spread != nil && existing.Spread != nil && removal.Spread.SpreadDescriptor == existing.Spread.SpreadDescriptor {
600					removed = true
601					break
602				}
603			}
604			if !removed {
605				newPrefs = append(newPrefs, existing)
606			}
607		}
608	} else {
609		newPrefs = placement.Preferences
610	}
611
612	if flags.Changed(flagPlacementPrefAdd) {
613		newPrefs = append(newPrefs,
614			flags.Lookup(flagPlacementPrefAdd).Value.(*placementPrefOpts).prefs...)
615	}
616
617	placement.Preferences = newPrefs
618}
619
620func updateContainerLabels(flags *pflag.FlagSet, field *map[string]string) {
621	if flags.Changed(flagContainerLabelAdd) {
622		if *field == nil {
623			*field = map[string]string{}
624		}
625
626		values := flags.Lookup(flagContainerLabelAdd).Value.(*opts.ListOpts).GetAll()
627		for key, value := range opts.ConvertKVStringsToMap(values) {
628			(*field)[key] = value
629		}
630	}
631
632	if *field != nil && flags.Changed(flagContainerLabelRemove) {
633		toRemove := flags.Lookup(flagContainerLabelRemove).Value.(*opts.ListOpts).GetAll()
634		for _, label := range toRemove {
635			delete(*field, label)
636		}
637	}
638}
639
640func updateLabels(flags *pflag.FlagSet, field *map[string]string) {
641	if flags.Changed(flagLabelAdd) {
642		if *field == nil {
643			*field = map[string]string{}
644		}
645
646		values := flags.Lookup(flagLabelAdd).Value.(*opts.ListOpts).GetAll()
647		for key, value := range opts.ConvertKVStringsToMap(values) {
648			(*field)[key] = value
649		}
650	}
651
652	if *field != nil && flags.Changed(flagLabelRemove) {
653		toRemove := flags.Lookup(flagLabelRemove).Value.(*opts.ListOpts).GetAll()
654		for _, label := range toRemove {
655			delete(*field, label)
656		}
657	}
658}
659
660func updateEnvironment(flags *pflag.FlagSet, field *[]string) {
661	if flags.Changed(flagEnvAdd) {
662		envSet := map[string]string{}
663		for _, v := range *field {
664			envSet[envKey(v)] = v
665		}
666
667		value := flags.Lookup(flagEnvAdd).Value.(*opts.ListOpts)
668		for _, v := range value.GetAll() {
669			envSet[envKey(v)] = v
670		}
671
672		*field = []string{}
673		for _, v := range envSet {
674			*field = append(*field, v)
675		}
676	}
677
678	toRemove := buildToRemoveSet(flags, flagEnvRemove)
679	*field = removeItems(*field, toRemove, envKey)
680}
681
682func getUpdatedSecrets(apiClient client.SecretAPIClient, flags *pflag.FlagSet, secrets []*swarm.SecretReference) ([]*swarm.SecretReference, error) {
683	newSecrets := []*swarm.SecretReference{}
684
685	toRemove := buildToRemoveSet(flags, flagSecretRemove)
686	for _, secret := range secrets {
687		if _, exists := toRemove[secret.SecretName]; !exists {
688			newSecrets = append(newSecrets, secret)
689		}
690	}
691
692	if flags.Changed(flagSecretAdd) {
693		values := flags.Lookup(flagSecretAdd).Value.(*opts.SecretOpt).Value()
694
695		addSecrets, err := ParseSecrets(apiClient, values)
696		if err != nil {
697			return nil, err
698		}
699		newSecrets = append(newSecrets, addSecrets...)
700	}
701
702	return newSecrets, nil
703}
704
705func getUpdatedConfigs(apiClient client.ConfigAPIClient, flags *pflag.FlagSet, configs []*swarm.ConfigReference) ([]*swarm.ConfigReference, error) {
706	newConfigs := []*swarm.ConfigReference{}
707
708	toRemove := buildToRemoveSet(flags, flagConfigRemove)
709	for _, config := range configs {
710		if _, exists := toRemove[config.ConfigName]; !exists {
711			newConfigs = append(newConfigs, config)
712		}
713	}
714
715	if flags.Changed(flagConfigAdd) {
716		values := flags.Lookup(flagConfigAdd).Value.(*opts.ConfigOpt).Value()
717
718		addConfigs, err := ParseConfigs(apiClient, values)
719		if err != nil {
720			return nil, err
721		}
722		newConfigs = append(newConfigs, addConfigs...)
723	}
724
725	return newConfigs, nil
726}
727
728func envKey(value string) string {
729	kv := strings.SplitN(value, "=", 2)
730	return kv[0]
731}
732
733func buildToRemoveSet(flags *pflag.FlagSet, flag string) map[string]struct{} {
734	var empty struct{}
735	toRemove := make(map[string]struct{})
736
737	if !flags.Changed(flag) {
738		return toRemove
739	}
740
741	toRemoveSlice := flags.Lookup(flag).Value.(*opts.ListOpts).GetAll()
742	for _, key := range toRemoveSlice {
743		toRemove[key] = empty
744	}
745	return toRemove
746}
747
748func removeItems(
749	seq []string,
750	toRemove map[string]struct{},
751	keyFunc func(string) string,
752) []string {
753	newSeq := []string{}
754	for _, item := range seq {
755		if _, exists := toRemove[keyFunc(item)]; !exists {
756			newSeq = append(newSeq, item)
757		}
758	}
759	return newSeq
760}
761
762func updateMounts(flags *pflag.FlagSet, mounts *[]mounttypes.Mount) error {
763	mountsByTarget := map[string]mounttypes.Mount{}
764
765	if flags.Changed(flagMountAdd) {
766		values := flags.Lookup(flagMountAdd).Value.(*opts.MountOpt).Value()
767		for _, mount := range values {
768			if _, ok := mountsByTarget[mount.Target]; ok {
769				return errors.Errorf("duplicate mount target")
770			}
771			mountsByTarget[mount.Target] = mount
772		}
773	}
774
775	// Add old list of mount points minus updated one.
776	for _, mount := range *mounts {
777		if _, ok := mountsByTarget[mount.Target]; !ok {
778			mountsByTarget[mount.Target] = mount
779		}
780	}
781
782	newMounts := []mounttypes.Mount{}
783
784	toRemove := buildToRemoveSet(flags, flagMountRemove)
785
786	for _, mount := range mountsByTarget {
787		if _, exists := toRemove[mount.Target]; !exists {
788			newMounts = append(newMounts, mount)
789		}
790	}
791	sort.Slice(newMounts, func(i, j int) bool {
792		a, b := newMounts[i], newMounts[j]
793
794		if a.Source == b.Source {
795			return a.Target < b.Target
796		}
797
798		return a.Source < b.Source
799	})
800	*mounts = newMounts
801	return nil
802}
803
804func updateGroups(flags *pflag.FlagSet, groups *[]string) error {
805	if flags.Changed(flagGroupAdd) {
806		values := flags.Lookup(flagGroupAdd).Value.(*opts.ListOpts).GetAll()
807		*groups = append(*groups, values...)
808	}
809	toRemove := buildToRemoveSet(flags, flagGroupRemove)
810
811	newGroups := []string{}
812	for _, group := range *groups {
813		if _, exists := toRemove[group]; !exists {
814			newGroups = append(newGroups, group)
815		}
816	}
817	// Sort so that result is predictable.
818	sort.Strings(newGroups)
819
820	*groups = newGroups
821	return nil
822}
823
824func removeDuplicates(entries []string) []string {
825	hit := map[string]bool{}
826	newEntries := []string{}
827	for _, v := range entries {
828		if !hit[v] {
829			newEntries = append(newEntries, v)
830			hit[v] = true
831		}
832	}
833	return newEntries
834}
835
836func updateDNSConfig(flags *pflag.FlagSet, config **swarm.DNSConfig) error {
837	newConfig := &swarm.DNSConfig{}
838
839	nameservers := (*config).Nameservers
840	if flags.Changed(flagDNSAdd) {
841		values := flags.Lookup(flagDNSAdd).Value.(*opts.ListOpts).GetAll()
842		nameservers = append(nameservers, values...)
843	}
844	nameservers = removeDuplicates(nameservers)
845	toRemove := buildToRemoveSet(flags, flagDNSRemove)
846	for _, nameserver := range nameservers {
847		if _, exists := toRemove[nameserver]; !exists {
848			newConfig.Nameservers = append(newConfig.Nameservers, nameserver)
849
850		}
851	}
852	// Sort so that result is predictable.
853	sort.Strings(newConfig.Nameservers)
854
855	search := (*config).Search
856	if flags.Changed(flagDNSSearchAdd) {
857		values := flags.Lookup(flagDNSSearchAdd).Value.(*opts.ListOpts).GetAll()
858		search = append(search, values...)
859	}
860	search = removeDuplicates(search)
861	toRemove = buildToRemoveSet(flags, flagDNSSearchRemove)
862	for _, entry := range search {
863		if _, exists := toRemove[entry]; !exists {
864			newConfig.Search = append(newConfig.Search, entry)
865		}
866	}
867	// Sort so that result is predictable.
868	sort.Strings(newConfig.Search)
869
870	options := (*config).Options
871	if flags.Changed(flagDNSOptionAdd) {
872		values := flags.Lookup(flagDNSOptionAdd).Value.(*opts.ListOpts).GetAll()
873		options = append(options, values...)
874	}
875	options = removeDuplicates(options)
876	toRemove = buildToRemoveSet(flags, flagDNSOptionRemove)
877	for _, option := range options {
878		if _, exists := toRemove[option]; !exists {
879			newConfig.Options = append(newConfig.Options, option)
880		}
881	}
882	// Sort so that result is predictable.
883	sort.Strings(newConfig.Options)
884
885	*config = newConfig
886	return nil
887}
888
889func portConfigToString(portConfig *swarm.PortConfig) string {
890	protocol := portConfig.Protocol
891	mode := portConfig.PublishMode
892	return fmt.Sprintf("%v:%v/%s/%s", portConfig.PublishedPort, portConfig.TargetPort, protocol, mode)
893}
894
895func updatePorts(flags *pflag.FlagSet, portConfig *[]swarm.PortConfig) error {
896	// The key of the map is `port/protocol`, e.g., `80/tcp`
897	portSet := map[string]swarm.PortConfig{}
898
899	// Build the current list of portConfig
900	for _, entry := range *portConfig {
901		if _, ok := portSet[portConfigToString(&entry)]; !ok {
902			portSet[portConfigToString(&entry)] = entry
903		}
904	}
905
906	newPorts := []swarm.PortConfig{}
907
908	// Clean current ports
909	toRemove := flags.Lookup(flagPublishRemove).Value.(*opts.PortOpt).Value()
910portLoop:
911	for _, port := range portSet {
912		for _, pConfig := range toRemove {
913			if equalProtocol(port.Protocol, pConfig.Protocol) &&
914				port.TargetPort == pConfig.TargetPort &&
915				equalPublishMode(port.PublishMode, pConfig.PublishMode) {
916				continue portLoop
917			}
918		}
919
920		newPorts = append(newPorts, port)
921	}
922
923	// Check to see if there are any conflict in flags.
924	if flags.Changed(flagPublishAdd) {
925		ports := flags.Lookup(flagPublishAdd).Value.(*opts.PortOpt).Value()
926
927		for _, port := range ports {
928			if _, ok := portSet[portConfigToString(&port)]; ok {
929				continue
930			}
931			//portSet[portConfigToString(&port)] = port
932			newPorts = append(newPorts, port)
933		}
934	}
935
936	// Sort the PortConfig to avoid unnecessary updates
937	sort.Slice(newPorts, func(i, j int) bool {
938		// We convert PortConfig into `port/protocol`, e.g., `80/tcp`
939		// In updatePorts we already filter out with map so there is duplicate entries
940		return portConfigToString(&newPorts[i]) < portConfigToString(&newPorts[j])
941	})
942	*portConfig = newPorts
943	return nil
944}
945
946func equalProtocol(prot1, prot2 swarm.PortConfigProtocol) bool {
947	return prot1 == prot2 ||
948		(prot1 == swarm.PortConfigProtocol("") && prot2 == swarm.PortConfigProtocolTCP) ||
949		(prot2 == swarm.PortConfigProtocol("") && prot1 == swarm.PortConfigProtocolTCP)
950}
951
952func equalPublishMode(mode1, mode2 swarm.PortConfigPublishMode) bool {
953	return mode1 == mode2 ||
954		(mode1 == swarm.PortConfigPublishMode("") && mode2 == swarm.PortConfigPublishModeIngress) ||
955		(mode2 == swarm.PortConfigPublishMode("") && mode1 == swarm.PortConfigPublishModeIngress)
956}
957
958func updateReplicas(flags *pflag.FlagSet, serviceMode *swarm.ServiceMode) error {
959	if !flags.Changed(flagReplicas) {
960		return nil
961	}
962
963	if serviceMode == nil || serviceMode.Replicated == nil {
964		return errors.Errorf("replicas can only be used with replicated mode")
965	}
966	serviceMode.Replicated.Replicas = flags.Lookup(flagReplicas).Value.(*Uint64Opt).Value()
967	return nil
968}
969
970type hostMapping struct {
971	IPAddr string
972	Host   string
973}
974
975// updateHosts performs a diff between existing host entries, entries to be
976// removed, and entries to be added. Host entries preserve the order in which they
977// were added, as the specification mentions that in case multiple entries for a
978// host exist, the first entry should be used (by default).
979//
980// Note that, even though unsupported by the the CLI, the service specs format
981// allow entries with both a _canonical_ hostname, and one or more aliases
982// in an entry (IP-address canonical_hostname [alias ...])
983//
984// Entries can be removed by either a specific `<host-name>:<ip-address>` mapping,
985// or by `<host>` alone:
986//
987// - If both IP-address and host-name is provided, the hostname is removed only
988//   from entries that match the given IP-address.
989// - If only a host-name is provided, the hostname is removed from any entry it
990//   is part of (either as canonical host-name, or as alias).
991// - If, after removing the host-name from an entry, no host-names remain in
992//   the entry, the entry itself is removed.
993//
994// For example, the list of host-entries before processing could look like this:
995//
996//    hosts = &[]string{
997//        "127.0.0.2 host3 host1 host2 host4",
998//        "127.0.0.1 host1 host4",
999//        "127.0.0.3 host1",
1000//        "127.0.0.1 host1",
1001//    }
1002//
1003// Removing `host1` removes every occurrence:
1004//
1005//    hosts = &[]string{
1006//        "127.0.0.2 host3 host2 host4",
1007//        "127.0.0.1 host4",
1008//    }
1009//
1010// Removing `host1:127.0.0.1` on the other hand, only remove the host if the
1011// IP-address matches:
1012//
1013//    hosts = &[]string{
1014//        "127.0.0.2 host3 host1 host2 host4",
1015//        "127.0.0.1 host4",
1016//        "127.0.0.3 host1",
1017//    }
1018func updateHosts(flags *pflag.FlagSet, hosts *[]string) error {
1019	var toRemove []hostMapping
1020	if flags.Changed(flagHostRemove) {
1021		extraHostsToRemove := flags.Lookup(flagHostRemove).Value.(*opts.ListOpts).GetAll()
1022		for _, entry := range extraHostsToRemove {
1023			v := strings.SplitN(entry, ":", 2)
1024			if len(v) > 1 {
1025				toRemove = append(toRemove, hostMapping{IPAddr: v[1], Host: v[0]})
1026			} else {
1027				toRemove = append(toRemove, hostMapping{Host: v[0]})
1028			}
1029		}
1030	}
1031
1032	var newHosts []string
1033	for _, entry := range *hosts {
1034		// Since this is in SwarmKit format, we need to find the key, which is canonical_hostname of:
1035		// IP_address canonical_hostname [aliases...]
1036		parts := strings.Fields(entry)
1037		if len(parts) == 0 {
1038			continue
1039		}
1040		ip := parts[0]
1041		hostNames := parts[1:]
1042		for _, rm := range toRemove {
1043			if rm.IPAddr != "" && rm.IPAddr != ip {
1044				continue
1045			}
1046			for i, h := range hostNames {
1047				if h == rm.Host {
1048					hostNames = append(hostNames[:i], hostNames[i+1:]...)
1049				}
1050			}
1051		}
1052		if len(hostNames) > 0 {
1053			newHosts = append(newHosts, fmt.Sprintf("%s %s", ip, strings.Join(hostNames, " ")))
1054		}
1055	}
1056
1057	// Append new hosts (in SwarmKit format)
1058	if flags.Changed(flagHostAdd) {
1059		values := convertExtraHostsToSwarmHosts(flags.Lookup(flagHostAdd).Value.(*opts.ListOpts).GetAll())
1060		newHosts = append(newHosts, values...)
1061	}
1062	*hosts = removeDuplicates(newHosts)
1063	return nil
1064}
1065
1066// updateLogDriver updates the log driver only if the log driver flag is set.
1067// All options will be replaced with those provided on the command line.
1068func updateLogDriver(flags *pflag.FlagSet, taskTemplate *swarm.TaskSpec) error {
1069	if !flags.Changed(flagLogDriver) {
1070		return nil
1071	}
1072
1073	name, err := flags.GetString(flagLogDriver)
1074	if err != nil {
1075		return err
1076	}
1077
1078	if name == "" {
1079		return nil
1080	}
1081
1082	taskTemplate.LogDriver = &swarm.Driver{
1083		Name:    name,
1084		Options: opts.ConvertKVStringsToMap(flags.Lookup(flagLogOpt).Value.(*opts.ListOpts).GetAll()),
1085	}
1086
1087	return nil
1088}
1089
1090func updateHealthcheck(flags *pflag.FlagSet, containerSpec *swarm.ContainerSpec) error {
1091	if !anyChanged(flags, flagNoHealthcheck, flagHealthCmd, flagHealthInterval, flagHealthRetries, flagHealthTimeout, flagHealthStartPeriod) {
1092		return nil
1093	}
1094	if containerSpec.Healthcheck == nil {
1095		containerSpec.Healthcheck = &container.HealthConfig{}
1096	}
1097	noHealthcheck, err := flags.GetBool(flagNoHealthcheck)
1098	if err != nil {
1099		return err
1100	}
1101	if noHealthcheck {
1102		if !anyChanged(flags, flagHealthCmd, flagHealthInterval, flagHealthRetries, flagHealthTimeout, flagHealthStartPeriod) {
1103			containerSpec.Healthcheck = &container.HealthConfig{
1104				Test: []string{"NONE"},
1105			}
1106			return nil
1107		}
1108		return errors.Errorf("--%s conflicts with --health-* options", flagNoHealthcheck)
1109	}
1110	if len(containerSpec.Healthcheck.Test) > 0 && containerSpec.Healthcheck.Test[0] == "NONE" {
1111		containerSpec.Healthcheck.Test = nil
1112	}
1113	if flags.Changed(flagHealthInterval) {
1114		val := *flags.Lookup(flagHealthInterval).Value.(*opts.PositiveDurationOpt).Value()
1115		containerSpec.Healthcheck.Interval = val
1116	}
1117	if flags.Changed(flagHealthTimeout) {
1118		val := *flags.Lookup(flagHealthTimeout).Value.(*opts.PositiveDurationOpt).Value()
1119		containerSpec.Healthcheck.Timeout = val
1120	}
1121	if flags.Changed(flagHealthStartPeriod) {
1122		val := *flags.Lookup(flagHealthStartPeriod).Value.(*opts.PositiveDurationOpt).Value()
1123		containerSpec.Healthcheck.StartPeriod = val
1124	}
1125	if flags.Changed(flagHealthRetries) {
1126		containerSpec.Healthcheck.Retries, _ = flags.GetInt(flagHealthRetries)
1127	}
1128	if flags.Changed(flagHealthCmd) {
1129		cmd, _ := flags.GetString(flagHealthCmd)
1130		if cmd != "" {
1131			containerSpec.Healthcheck.Test = []string{"CMD-SHELL", cmd}
1132		} else {
1133			containerSpec.Healthcheck.Test = nil
1134		}
1135	}
1136	return nil
1137}
1138
1139func updateNetworks(ctx context.Context, apiClient client.NetworkAPIClient, flags *pflag.FlagSet, spec *swarm.ServiceSpec) error {
1140	// spec.TaskTemplate.Networks takes precedence over the deprecated
1141	// spec.Networks field. If spec.Network is in use, we'll migrate those
1142	// values to spec.TaskTemplate.Networks.
1143	specNetworks := spec.TaskTemplate.Networks
1144	if len(specNetworks) == 0 {
1145		specNetworks = spec.Networks
1146	}
1147	spec.Networks = nil
1148
1149	toRemove := buildToRemoveSet(flags, flagNetworkRemove)
1150	idsToRemove := make(map[string]struct{})
1151	for networkIDOrName := range toRemove {
1152		network, err := apiClient.NetworkInspect(ctx, networkIDOrName, types.NetworkInspectOptions{Scope: "swarm"})
1153		if err != nil {
1154			return err
1155		}
1156		idsToRemove[network.ID] = struct{}{}
1157	}
1158
1159	existingNetworks := make(map[string]struct{})
1160	var newNetworks []swarm.NetworkAttachmentConfig
1161	for _, network := range specNetworks {
1162		if _, exists := idsToRemove[network.Target]; exists {
1163			continue
1164		}
1165
1166		newNetworks = append(newNetworks, network)
1167		existingNetworks[network.Target] = struct{}{}
1168	}
1169
1170	if flags.Changed(flagNetworkAdd) {
1171		values := flags.Lookup(flagNetworkAdd).Value.(*opts.NetworkOpt)
1172		networks := convertNetworks(*values)
1173		for _, network := range networks {
1174			nwID, err := resolveNetworkID(ctx, apiClient, network.Target)
1175			if err != nil {
1176				return err
1177			}
1178			if _, exists := existingNetworks[nwID]; exists {
1179				return errors.Errorf("service is already attached to network %s", network.Target)
1180			}
1181			network.Target = nwID
1182			newNetworks = append(newNetworks, network)
1183			existingNetworks[network.Target] = struct{}{}
1184		}
1185	}
1186
1187	sort.Slice(newNetworks, func(i, j int) bool {
1188		return newNetworks[i].Target < newNetworks[j].Target
1189	})
1190
1191	spec.TaskTemplate.Networks = newNetworks
1192	return nil
1193}
1194