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