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