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