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