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