github.com/ali-iotechsys/cli@v20.10.0+incompatible/cli/command/service/opts.go (about) 1 package service 2 3 import ( 4 "context" 5 "fmt" 6 "sort" 7 "strconv" 8 "strings" 9 "time" 10 11 "github.com/docker/cli/opts" 12 "github.com/docker/docker/api/types" 13 "github.com/docker/docker/api/types/container" 14 "github.com/docker/docker/api/types/swarm" 15 "github.com/docker/docker/api/types/versions" 16 "github.com/docker/docker/client" 17 "github.com/docker/swarmkit/api" 18 "github.com/docker/swarmkit/api/defaults" 19 gogotypes "github.com/gogo/protobuf/types" 20 "github.com/google/shlex" 21 "github.com/pkg/errors" 22 "github.com/spf13/pflag" 23 ) 24 25 type int64Value interface { 26 Value() int64 27 } 28 29 // Uint64Opt represents a uint64. 30 type Uint64Opt struct { 31 value *uint64 32 } 33 34 // Set a new value on the option 35 func (i *Uint64Opt) Set(s string) error { 36 v, err := strconv.ParseUint(s, 0, 64) 37 i.value = &v 38 return err 39 } 40 41 // Type returns the type of this option, which will be displayed in `--help` output 42 func (i *Uint64Opt) Type() string { 43 return "uint" 44 } 45 46 // String returns a string repr of this option 47 func (i *Uint64Opt) String() string { 48 if i.value != nil { 49 return fmt.Sprintf("%v", *i.value) 50 } 51 return "" 52 } 53 54 // Value returns the uint64 55 func (i *Uint64Opt) Value() *uint64 { 56 return i.value 57 } 58 59 type floatValue float32 60 61 func (f *floatValue) Set(s string) error { 62 v, err := strconv.ParseFloat(s, 32) 63 *f = floatValue(v) 64 return err 65 } 66 67 func (f *floatValue) Type() string { 68 return "float" 69 } 70 71 func (f *floatValue) String() string { 72 return strconv.FormatFloat(float64(*f), 'g', -1, 32) 73 } 74 75 func (f *floatValue) Value() float32 { 76 return float32(*f) 77 } 78 79 // placementPrefOpts holds a list of placement preferences. 80 type placementPrefOpts struct { 81 prefs []swarm.PlacementPreference 82 strings []string 83 } 84 85 func (opts *placementPrefOpts) String() string { 86 if len(opts.strings) == 0 { 87 return "" 88 } 89 return fmt.Sprintf("%v", opts.strings) 90 } 91 92 // Set validates the input value and adds it to the internal slices. 93 // Note: in the future strategies other than "spread", may be supported, 94 // as well as additional comma-separated options. 95 func (opts *placementPrefOpts) Set(value string) error { 96 fields := strings.Split(value, "=") 97 if len(fields) != 2 { 98 return errors.New(`placement preference must be of the format "<strategy>=<arg>"`) 99 } 100 if fields[0] != "spread" { 101 return errors.Errorf("unsupported placement preference %s (only spread is supported)", fields[0]) 102 } 103 104 opts.prefs = append(opts.prefs, swarm.PlacementPreference{ 105 Spread: &swarm.SpreadOver{ 106 SpreadDescriptor: fields[1], 107 }, 108 }) 109 opts.strings = append(opts.strings, value) 110 return nil 111 } 112 113 // Type returns a string name for this Option type 114 func (opts *placementPrefOpts) Type() string { 115 return "pref" 116 } 117 118 // ShlexOpt is a flag Value which parses a string as a list of shell words 119 type ShlexOpt []string 120 121 // Set the value 122 func (s *ShlexOpt) Set(value string) error { 123 valueSlice, err := shlex.Split(value) 124 *s = ShlexOpt(valueSlice) 125 return err 126 } 127 128 // Type returns the tyep of the value 129 func (s *ShlexOpt) Type() string { 130 return "command" 131 } 132 133 func (s *ShlexOpt) String() string { 134 if len(*s) == 0 { 135 return "" 136 } 137 return fmt.Sprint(*s) 138 } 139 140 // Value returns the value as a string slice 141 func (s *ShlexOpt) Value() []string { 142 return []string(*s) 143 } 144 145 type updateOptions struct { 146 parallelism uint64 147 delay time.Duration 148 monitor time.Duration 149 onFailure string 150 maxFailureRatio floatValue 151 order string 152 } 153 154 func updateConfigFromDefaults(defaultUpdateConfig *api.UpdateConfig) *swarm.UpdateConfig { 155 defaultFailureAction := strings.ToLower(api.UpdateConfig_FailureAction_name[int32(defaultUpdateConfig.FailureAction)]) 156 defaultMonitor, _ := gogotypes.DurationFromProto(defaultUpdateConfig.Monitor) 157 return &swarm.UpdateConfig{ 158 Parallelism: defaultUpdateConfig.Parallelism, 159 Delay: defaultUpdateConfig.Delay, 160 Monitor: defaultMonitor, 161 FailureAction: defaultFailureAction, 162 MaxFailureRatio: defaultUpdateConfig.MaxFailureRatio, 163 Order: defaultOrder(defaultUpdateConfig.Order), 164 } 165 } 166 167 func (opts updateOptions) updateConfig(flags *pflag.FlagSet) *swarm.UpdateConfig { 168 if !anyChanged(flags, flagUpdateParallelism, flagUpdateDelay, flagUpdateMonitor, flagUpdateFailureAction, flagUpdateMaxFailureRatio) { 169 return nil 170 } 171 172 updateConfig := updateConfigFromDefaults(defaults.Service.Update) 173 174 if flags.Changed(flagUpdateParallelism) { 175 updateConfig.Parallelism = opts.parallelism 176 } 177 if flags.Changed(flagUpdateDelay) { 178 updateConfig.Delay = opts.delay 179 } 180 if flags.Changed(flagUpdateMonitor) { 181 updateConfig.Monitor = opts.monitor 182 } 183 if flags.Changed(flagUpdateFailureAction) { 184 updateConfig.FailureAction = opts.onFailure 185 } 186 if flags.Changed(flagUpdateMaxFailureRatio) { 187 updateConfig.MaxFailureRatio = opts.maxFailureRatio.Value() 188 } 189 if flags.Changed(flagUpdateOrder) { 190 updateConfig.Order = opts.order 191 } 192 193 return updateConfig 194 } 195 196 func (opts updateOptions) rollbackConfig(flags *pflag.FlagSet) *swarm.UpdateConfig { 197 if !anyChanged(flags, flagRollbackParallelism, flagRollbackDelay, flagRollbackMonitor, flagRollbackFailureAction, flagRollbackMaxFailureRatio) { 198 return nil 199 } 200 201 updateConfig := updateConfigFromDefaults(defaults.Service.Rollback) 202 203 if flags.Changed(flagRollbackParallelism) { 204 updateConfig.Parallelism = opts.parallelism 205 } 206 if flags.Changed(flagRollbackDelay) { 207 updateConfig.Delay = opts.delay 208 } 209 if flags.Changed(flagRollbackMonitor) { 210 updateConfig.Monitor = opts.monitor 211 } 212 if flags.Changed(flagRollbackFailureAction) { 213 updateConfig.FailureAction = opts.onFailure 214 } 215 if flags.Changed(flagRollbackMaxFailureRatio) { 216 updateConfig.MaxFailureRatio = opts.maxFailureRatio.Value() 217 } 218 if flags.Changed(flagRollbackOrder) { 219 updateConfig.Order = opts.order 220 } 221 222 return updateConfig 223 } 224 225 type resourceOptions struct { 226 limitCPU opts.NanoCPUs 227 limitMemBytes opts.MemBytes 228 limitPids int64 229 resCPU opts.NanoCPUs 230 resMemBytes opts.MemBytes 231 resGenericResources []string 232 } 233 234 func (r *resourceOptions) ToResourceRequirements() (*swarm.ResourceRequirements, error) { 235 generic, err := ParseGenericResources(r.resGenericResources) 236 if err != nil { 237 return nil, err 238 } 239 240 return &swarm.ResourceRequirements{ 241 Limits: &swarm.Limit{ 242 NanoCPUs: r.limitCPU.Value(), 243 MemoryBytes: r.limitMemBytes.Value(), 244 Pids: r.limitPids, 245 }, 246 Reservations: &swarm.Resources{ 247 NanoCPUs: r.resCPU.Value(), 248 MemoryBytes: r.resMemBytes.Value(), 249 GenericResources: generic, 250 }, 251 }, nil 252 } 253 254 type restartPolicyOptions struct { 255 condition string 256 delay opts.DurationOpt 257 maxAttempts Uint64Opt 258 window opts.DurationOpt 259 } 260 261 func defaultRestartPolicy() *swarm.RestartPolicy { 262 defaultMaxAttempts := defaults.Service.Task.Restart.MaxAttempts 263 rp := &swarm.RestartPolicy{ 264 MaxAttempts: &defaultMaxAttempts, 265 } 266 267 if defaults.Service.Task.Restart.Delay != nil { 268 defaultRestartDelay, _ := gogotypes.DurationFromProto(defaults.Service.Task.Restart.Delay) 269 rp.Delay = &defaultRestartDelay 270 } 271 if defaults.Service.Task.Restart.Window != nil { 272 defaultRestartWindow, _ := gogotypes.DurationFromProto(defaults.Service.Task.Restart.Window) 273 rp.Window = &defaultRestartWindow 274 } 275 rp.Condition = defaultRestartCondition() 276 277 return rp 278 } 279 280 func defaultRestartCondition() swarm.RestartPolicyCondition { 281 switch defaults.Service.Task.Restart.Condition { 282 case api.RestartOnNone: 283 return "none" 284 case api.RestartOnFailure: 285 return "on-failure" 286 case api.RestartOnAny: 287 return "any" 288 default: 289 return "" 290 } 291 } 292 293 func defaultOrder(order api.UpdateConfig_UpdateOrder) string { 294 switch order { 295 case api.UpdateConfig_STOP_FIRST: 296 return "stop-first" 297 case api.UpdateConfig_START_FIRST: 298 return "start-first" 299 default: 300 return "" 301 } 302 } 303 304 func (r *restartPolicyOptions) ToRestartPolicy(flags *pflag.FlagSet) *swarm.RestartPolicy { 305 if !anyChanged(flags, flagRestartDelay, flagRestartMaxAttempts, flagRestartWindow, flagRestartCondition) { 306 return nil 307 } 308 309 restartPolicy := defaultRestartPolicy() 310 311 if flags.Changed(flagRestartDelay) { 312 restartPolicy.Delay = r.delay.Value() 313 } 314 if flags.Changed(flagRestartCondition) { 315 restartPolicy.Condition = swarm.RestartPolicyCondition(r.condition) 316 } 317 if flags.Changed(flagRestartMaxAttempts) { 318 restartPolicy.MaxAttempts = r.maxAttempts.Value() 319 } 320 if flags.Changed(flagRestartWindow) { 321 restartPolicy.Window = r.window.Value() 322 } 323 324 return restartPolicy 325 } 326 327 type credentialSpecOpt struct { 328 value *swarm.CredentialSpec 329 source string 330 } 331 332 func (c *credentialSpecOpt) Set(value string) error { 333 c.source = value 334 c.value = &swarm.CredentialSpec{} 335 switch { 336 case strings.HasPrefix(value, "config://"): 337 // NOTE(dperny): we allow the user to specify the value of 338 // CredentialSpec Config using the Name of the config, but the API 339 // requires the ID of the config. For simplicity, we will parse 340 // whatever value is provided into the "Config" field, but before 341 // making API calls, we may need to swap the Config Name for the ID. 342 // Therefore, this isn't the definitive location for the value of 343 // Config that is passed to the API. 344 c.value.Config = strings.TrimPrefix(value, "config://") 345 case strings.HasPrefix(value, "file://"): 346 c.value.File = strings.TrimPrefix(value, "file://") 347 case strings.HasPrefix(value, "registry://"): 348 c.value.Registry = strings.TrimPrefix(value, "registry://") 349 case value == "": 350 // if the value of the flag is an empty string, that means there is no 351 // CredentialSpec needed. This is useful for removing a CredentialSpec 352 // during a service update. 353 default: 354 return errors.New(`invalid credential spec: value must be prefixed with "config://", "file://", or "registry://"`) 355 } 356 357 return nil 358 } 359 360 func (c *credentialSpecOpt) Type() string { 361 return "credential-spec" 362 } 363 364 func (c *credentialSpecOpt) String() string { 365 return c.source 366 } 367 368 func (c *credentialSpecOpt) Value() *swarm.CredentialSpec { 369 return c.value 370 } 371 372 func resolveNetworkID(ctx context.Context, apiClient client.NetworkAPIClient, networkIDOrName string) (string, error) { 373 nw, err := apiClient.NetworkInspect(ctx, networkIDOrName, types.NetworkInspectOptions{Scope: "swarm"}) 374 return nw.ID, err 375 } 376 377 func convertNetworks(networks opts.NetworkOpt) []swarm.NetworkAttachmentConfig { 378 var netAttach []swarm.NetworkAttachmentConfig 379 for _, net := range networks.Value() { 380 netAttach = append(netAttach, swarm.NetworkAttachmentConfig{ 381 Target: net.Target, 382 Aliases: net.Aliases, 383 DriverOpts: net.DriverOpts, 384 }) 385 } 386 return netAttach 387 } 388 389 type endpointOptions struct { 390 mode string 391 publishPorts opts.PortOpt 392 } 393 394 func (e *endpointOptions) ToEndpointSpec() *swarm.EndpointSpec { 395 return &swarm.EndpointSpec{ 396 Mode: swarm.ResolutionMode(strings.ToLower(e.mode)), 397 Ports: e.publishPorts.Value(), 398 } 399 } 400 401 type logDriverOptions struct { 402 name string 403 opts opts.ListOpts 404 } 405 406 func newLogDriverOptions() logDriverOptions { 407 return logDriverOptions{opts: opts.NewListOpts(opts.ValidateEnv)} 408 } 409 410 func (ldo *logDriverOptions) toLogDriver() *swarm.Driver { 411 if ldo.name == "" { 412 return nil 413 } 414 415 // set the log driver only if specified. 416 return &swarm.Driver{ 417 Name: ldo.name, 418 Options: opts.ConvertKVStringsToMap(ldo.opts.GetAll()), 419 } 420 } 421 422 type healthCheckOptions struct { 423 cmd string 424 interval opts.PositiveDurationOpt 425 timeout opts.PositiveDurationOpt 426 retries int 427 startPeriod opts.PositiveDurationOpt 428 noHealthcheck bool 429 } 430 431 func (opts *healthCheckOptions) toHealthConfig() (*container.HealthConfig, error) { 432 var healthConfig *container.HealthConfig 433 haveHealthSettings := opts.cmd != "" || 434 opts.interval.Value() != nil || 435 opts.timeout.Value() != nil || 436 opts.retries != 0 437 if opts.noHealthcheck { 438 if haveHealthSettings { 439 return nil, errors.Errorf("--%s conflicts with --health-* options", flagNoHealthcheck) 440 } 441 healthConfig = &container.HealthConfig{Test: []string{"NONE"}} 442 } else if haveHealthSettings { 443 var test []string 444 if opts.cmd != "" { 445 test = []string{"CMD-SHELL", opts.cmd} 446 } 447 var interval, timeout, startPeriod time.Duration 448 if ptr := opts.interval.Value(); ptr != nil { 449 interval = *ptr 450 } 451 if ptr := opts.timeout.Value(); ptr != nil { 452 timeout = *ptr 453 } 454 if ptr := opts.startPeriod.Value(); ptr != nil { 455 startPeriod = *ptr 456 } 457 healthConfig = &container.HealthConfig{ 458 Test: test, 459 Interval: interval, 460 Timeout: timeout, 461 Retries: opts.retries, 462 StartPeriod: startPeriod, 463 } 464 } 465 return healthConfig, nil 466 } 467 468 // convertExtraHostsToSwarmHosts converts an array of extra hosts in cli 469 // <host>:<ip> 470 // into a swarmkit host format: 471 // IP_address canonical_hostname [aliases...] 472 // This assumes input value (<host>:<ip>) has already been validated 473 func convertExtraHostsToSwarmHosts(extraHosts []string) []string { 474 hosts := []string{} 475 for _, extraHost := range extraHosts { 476 parts := strings.SplitN(extraHost, ":", 2) 477 hosts = append(hosts, fmt.Sprintf("%s %s", parts[1], parts[0])) 478 } 479 return hosts 480 } 481 482 type serviceOptions struct { 483 detach bool 484 quiet bool 485 486 name string 487 labels opts.ListOpts 488 containerLabels opts.ListOpts 489 image string 490 entrypoint ShlexOpt 491 args []string 492 hostname string 493 env opts.ListOpts 494 envFile opts.ListOpts 495 workdir string 496 user string 497 groups opts.ListOpts 498 credentialSpec credentialSpecOpt 499 init bool 500 stopSignal string 501 tty bool 502 readOnly bool 503 mounts opts.MountOpt 504 dns opts.ListOpts 505 dnsSearch opts.ListOpts 506 dnsOption opts.ListOpts 507 hosts opts.ListOpts 508 sysctls opts.ListOpts 509 capAdd opts.ListOpts 510 capDrop opts.ListOpts 511 ulimits opts.UlimitOpt 512 513 resources resourceOptions 514 stopGrace opts.DurationOpt 515 516 replicas Uint64Opt 517 mode string 518 maxConcurrent Uint64Opt 519 520 restartPolicy restartPolicyOptions 521 constraints opts.ListOpts 522 placementPrefs placementPrefOpts 523 maxReplicas uint64 524 update updateOptions 525 rollback updateOptions 526 networks opts.NetworkOpt 527 endpoint endpointOptions 528 529 registryAuth bool 530 noResolveImage bool 531 532 logDriver logDriverOptions 533 534 healthcheck healthCheckOptions 535 secrets opts.SecretOpt 536 configs opts.ConfigOpt 537 538 isolation string 539 } 540 541 func newServiceOptions() *serviceOptions { 542 return &serviceOptions{ 543 labels: opts.NewListOpts(opts.ValidateLabel), 544 constraints: opts.NewListOpts(nil), 545 containerLabels: opts.NewListOpts(opts.ValidateLabel), 546 env: opts.NewListOpts(opts.ValidateEnv), 547 envFile: opts.NewListOpts(nil), 548 groups: opts.NewListOpts(nil), 549 logDriver: newLogDriverOptions(), 550 dns: opts.NewListOpts(opts.ValidateIPAddress), 551 dnsOption: opts.NewListOpts(nil), 552 dnsSearch: opts.NewListOpts(opts.ValidateDNSSearch), 553 hosts: opts.NewListOpts(opts.ValidateExtraHost), 554 sysctls: opts.NewListOpts(nil), 555 capAdd: opts.NewListOpts(nil), 556 capDrop: opts.NewListOpts(nil), 557 ulimits: *opts.NewUlimitOpt(nil), 558 } 559 } 560 561 func (options *serviceOptions) ToServiceMode() (swarm.ServiceMode, error) { 562 serviceMode := swarm.ServiceMode{} 563 switch options.mode { 564 case "global": 565 if options.replicas.Value() != nil { 566 return serviceMode, errors.Errorf("replicas can only be used with replicated or replicated-job mode") 567 } 568 569 if options.maxReplicas > 0 { 570 return serviceMode, errors.New("replicas-max-per-node can only be used with replicated or replicated-job mode") 571 } 572 if options.maxConcurrent.Value() != nil { 573 return serviceMode, errors.New("max-concurrent can only be used with replicated-job mode") 574 } 575 576 serviceMode.Global = &swarm.GlobalService{} 577 case "replicated": 578 if options.maxConcurrent.Value() != nil { 579 return serviceMode, errors.New("max-concurrent can only be used with replicated-job mode") 580 } 581 582 serviceMode.Replicated = &swarm.ReplicatedService{ 583 Replicas: options.replicas.Value(), 584 } 585 case "replicated-job": 586 concurrent := options.maxConcurrent.Value() 587 if concurrent == nil { 588 concurrent = options.replicas.Value() 589 } 590 serviceMode.ReplicatedJob = &swarm.ReplicatedJob{ 591 MaxConcurrent: concurrent, 592 TotalCompletions: options.replicas.Value(), 593 } 594 case "global-job": 595 if options.maxReplicas > 0 { 596 return serviceMode, errors.New("replicas-max-per-node can only be used with replicated or replicated-job mode") 597 } 598 if options.maxConcurrent.Value() != nil { 599 return serviceMode, errors.New("max-concurrent can only be used with replicated-job mode") 600 } 601 if options.replicas.Value() != nil { 602 return serviceMode, errors.Errorf("replicas can only be used with replicated or replicated-job mode") 603 } 604 serviceMode.GlobalJob = &swarm.GlobalJob{} 605 default: 606 return serviceMode, errors.Errorf("Unknown mode: %s, only replicated and global supported", options.mode) 607 } 608 return serviceMode, nil 609 } 610 611 func (options *serviceOptions) ToStopGracePeriod(flags *pflag.FlagSet) *time.Duration { 612 if flags.Changed(flagStopGracePeriod) { 613 return options.stopGrace.Value() 614 } 615 return nil 616 } 617 618 // makeEnv gets the environment variables from the command line options and 619 // returns a slice of strings to use in the service spec when doing ToService 620 func (options *serviceOptions) makeEnv() ([]string, error) { 621 envVariables, err := opts.ReadKVEnvStrings(options.envFile.GetAll(), options.env.GetAll()) 622 if err != nil { 623 return nil, err 624 } 625 currentEnv := make([]string, 0, len(envVariables)) 626 for _, env := range envVariables { // need to process each var, in order 627 k := strings.SplitN(env, "=", 2)[0] 628 for i, current := range currentEnv { // remove duplicates 629 if current == env { 630 continue // no update required, may hide this behind flag to preserve order of envVariables 631 } 632 if strings.HasPrefix(current, k+"=") { 633 currentEnv = append(currentEnv[:i], currentEnv[i+1:]...) 634 } 635 } 636 currentEnv = append(currentEnv, env) 637 } 638 639 return currentEnv, nil 640 } 641 642 // ToService takes the set of flags passed to the command and converts them 643 // into a service spec. 644 // 645 // Takes an API client as the second argument in order to resolve network names 646 // from the flags into network IDs. 647 // 648 // Returns an error if any flags are invalid or contradictory. 649 func (options *serviceOptions) ToService(ctx context.Context, apiClient client.NetworkAPIClient, flags *pflag.FlagSet) (swarm.ServiceSpec, error) { 650 var service swarm.ServiceSpec 651 652 currentEnv, err := options.makeEnv() 653 if err != nil { 654 return service, err 655 } 656 657 healthConfig, err := options.healthcheck.toHealthConfig() 658 if err != nil { 659 return service, err 660 } 661 662 serviceMode, err := options.ToServiceMode() 663 if err != nil { 664 return service, err 665 } 666 667 updateConfig := options.update.updateConfig(flags) 668 rollbackConfig := options.rollback.rollbackConfig(flags) 669 670 // update and rollback configuration is not supported for jobs. If these 671 // flags are not set, then the values will be nil. If they are non-nil, 672 // then return an error. 673 if (serviceMode.ReplicatedJob != nil || serviceMode.GlobalJob != nil) && (updateConfig != nil || rollbackConfig != nil) { 674 return service, errors.Errorf("update and rollback configuration is not supported for jobs") 675 } 676 677 networks := convertNetworks(options.networks) 678 for i, net := range networks { 679 nwID, err := resolveNetworkID(ctx, apiClient, net.Target) 680 if err != nil { 681 return service, err 682 } 683 networks[i].Target = nwID 684 } 685 sort.Slice(networks, func(i, j int) bool { 686 return networks[i].Target < networks[j].Target 687 }) 688 689 resources, err := options.resources.ToResourceRequirements() 690 if err != nil { 691 return service, err 692 } 693 694 capAdd, capDrop := opts.EffectiveCapAddCapDrop(options.capAdd.GetAll(), options.capDrop.GetAll()) 695 696 service = swarm.ServiceSpec{ 697 Annotations: swarm.Annotations{ 698 Name: options.name, 699 Labels: opts.ConvertKVStringsToMap(options.labels.GetAll()), 700 }, 701 TaskTemplate: swarm.TaskSpec{ 702 ContainerSpec: &swarm.ContainerSpec{ 703 Image: options.image, 704 Args: options.args, 705 Command: options.entrypoint.Value(), 706 Env: currentEnv, 707 Hostname: options.hostname, 708 Labels: opts.ConvertKVStringsToMap(options.containerLabels.GetAll()), 709 Dir: options.workdir, 710 User: options.user, 711 Groups: options.groups.GetAll(), 712 StopSignal: options.stopSignal, 713 TTY: options.tty, 714 ReadOnly: options.readOnly, 715 Mounts: options.mounts.Value(), 716 Init: &options.init, 717 DNSConfig: &swarm.DNSConfig{ 718 Nameservers: options.dns.GetAll(), 719 Search: options.dnsSearch.GetAll(), 720 Options: options.dnsOption.GetAll(), 721 }, 722 Hosts: convertExtraHostsToSwarmHosts(options.hosts.GetAll()), 723 StopGracePeriod: options.ToStopGracePeriod(flags), 724 Healthcheck: healthConfig, 725 Isolation: container.Isolation(options.isolation), 726 Sysctls: opts.ConvertKVStringsToMap(options.sysctls.GetAll()), 727 CapabilityAdd: capAdd, 728 CapabilityDrop: capDrop, 729 Ulimits: options.ulimits.GetList(), 730 }, 731 Networks: networks, 732 Resources: resources, 733 RestartPolicy: options.restartPolicy.ToRestartPolicy(flags), 734 Placement: &swarm.Placement{ 735 Constraints: options.constraints.GetAll(), 736 Preferences: options.placementPrefs.prefs, 737 MaxReplicas: options.maxReplicas, 738 }, 739 LogDriver: options.logDriver.toLogDriver(), 740 }, 741 Mode: serviceMode, 742 UpdateConfig: updateConfig, 743 RollbackConfig: rollbackConfig, 744 EndpointSpec: options.endpoint.ToEndpointSpec(), 745 } 746 747 if options.credentialSpec.String() != "" && options.credentialSpec.Value() != nil { 748 service.TaskTemplate.ContainerSpec.Privileges = &swarm.Privileges{ 749 CredentialSpec: options.credentialSpec.Value(), 750 } 751 } 752 753 return service, nil 754 } 755 756 type flagDefaults map[string]interface{} 757 758 func (fd flagDefaults) getUint64(flagName string) uint64 { 759 if val, ok := fd[flagName].(uint64); ok { 760 return val 761 } 762 return 0 763 } 764 765 func (fd flagDefaults) getString(flagName string) string { 766 if val, ok := fd[flagName].(string); ok { 767 return val 768 } 769 return "" 770 } 771 772 func buildServiceDefaultFlagMapping() flagDefaults { 773 defaultFlagValues := make(map[string]interface{}) 774 775 defaultFlagValues[flagStopGracePeriod], _ = gogotypes.DurationFromProto(defaults.Service.Task.GetContainer().StopGracePeriod) 776 defaultFlagValues[flagRestartCondition] = `"` + defaultRestartCondition() + `"` 777 defaultFlagValues[flagRestartDelay], _ = gogotypes.DurationFromProto(defaults.Service.Task.Restart.Delay) 778 779 if defaults.Service.Task.Restart.MaxAttempts != 0 { 780 defaultFlagValues[flagRestartMaxAttempts] = defaults.Service.Task.Restart.MaxAttempts 781 } 782 783 defaultRestartWindow, _ := gogotypes.DurationFromProto(defaults.Service.Task.Restart.Window) 784 if defaultRestartWindow != 0 { 785 defaultFlagValues[flagRestartWindow] = defaultRestartWindow 786 } 787 788 defaultFlagValues[flagUpdateParallelism] = defaults.Service.Update.Parallelism 789 defaultFlagValues[flagUpdateDelay] = defaults.Service.Update.Delay 790 defaultFlagValues[flagUpdateMonitor], _ = gogotypes.DurationFromProto(defaults.Service.Update.Monitor) 791 defaultFlagValues[flagUpdateFailureAction] = `"` + strings.ToLower(api.UpdateConfig_FailureAction_name[int32(defaults.Service.Update.FailureAction)]) + `"` 792 defaultFlagValues[flagUpdateMaxFailureRatio] = defaults.Service.Update.MaxFailureRatio 793 defaultFlagValues[flagUpdateOrder] = `"` + defaultOrder(defaults.Service.Update.Order) + `"` 794 795 defaultFlagValues[flagRollbackParallelism] = defaults.Service.Rollback.Parallelism 796 defaultFlagValues[flagRollbackDelay] = defaults.Service.Rollback.Delay 797 defaultFlagValues[flagRollbackMonitor], _ = gogotypes.DurationFromProto(defaults.Service.Rollback.Monitor) 798 defaultFlagValues[flagRollbackFailureAction] = `"` + strings.ToLower(api.UpdateConfig_FailureAction_name[int32(defaults.Service.Rollback.FailureAction)]) + `"` 799 defaultFlagValues[flagRollbackMaxFailureRatio] = defaults.Service.Rollback.MaxFailureRatio 800 defaultFlagValues[flagRollbackOrder] = `"` + defaultOrder(defaults.Service.Rollback.Order) + `"` 801 802 defaultFlagValues[flagEndpointMode] = "vip" 803 804 return defaultFlagValues 805 } 806 807 func addDetachFlag(flags *pflag.FlagSet, detach *bool) { 808 flags.BoolVarP(detach, flagDetach, "d", false, "Exit immediately instead of waiting for the service to converge") 809 flags.SetAnnotation(flagDetach, "version", []string{"1.29"}) 810 } 811 812 // addServiceFlags adds all flags that are common to both `create` and `update`. 813 // Any flags that are not common are added separately in the individual command 814 func addServiceFlags(flags *pflag.FlagSet, opts *serviceOptions, defaultFlagValues flagDefaults) { 815 flagDesc := func(flagName string, desc string) string { 816 if defaultValue, ok := defaultFlagValues[flagName]; ok { 817 return fmt.Sprintf("%s (default %v)", desc, defaultValue) 818 } 819 return desc 820 } 821 822 addDetachFlag(flags, &opts.detach) 823 flags.BoolVarP(&opts.quiet, flagQuiet, "q", false, "Suppress progress output") 824 825 flags.StringVarP(&opts.workdir, flagWorkdir, "w", "", "Working directory inside the container") 826 flags.StringVarP(&opts.user, flagUser, "u", "", "Username or UID (format: <name|uid>[:<group|gid>])") 827 flags.Var(&opts.credentialSpec, flagCredentialSpec, "Credential spec for managed service account (Windows only)") 828 flags.SetAnnotation(flagCredentialSpec, "version", []string{"1.29"}) 829 flags.StringVar(&opts.hostname, flagHostname, "", "Container hostname") 830 flags.SetAnnotation(flagHostname, "version", []string{"1.25"}) 831 flags.Var(&opts.entrypoint, flagEntrypoint, "Overwrite the default ENTRYPOINT of the image") 832 flags.Var(&opts.capAdd, flagCapAdd, "Add Linux capabilities") 833 flags.SetAnnotation(flagCapAdd, "version", []string{"1.41"}) 834 flags.Var(&opts.capDrop, flagCapDrop, "Drop Linux capabilities") 835 flags.SetAnnotation(flagCapDrop, "version", []string{"1.41"}) 836 837 flags.Var(&opts.resources.limitCPU, flagLimitCPU, "Limit CPUs") 838 flags.Var(&opts.resources.limitMemBytes, flagLimitMemory, "Limit Memory") 839 flags.Var(&opts.resources.resCPU, flagReserveCPU, "Reserve CPUs") 840 flags.Var(&opts.resources.resMemBytes, flagReserveMemory, "Reserve Memory") 841 flags.Int64Var(&opts.resources.limitPids, flagLimitPids, 0, "Limit maximum number of processes (default 0 = unlimited)") 842 flags.SetAnnotation(flagLimitPids, "version", []string{"1.41"}) 843 flags.SetAnnotation(flagLimitPids, "swarm", nil) 844 845 flags.Var(&opts.stopGrace, flagStopGracePeriod, flagDesc(flagStopGracePeriod, "Time to wait before force killing a container (ns|us|ms|s|m|h)")) 846 flags.Var(&opts.replicas, flagReplicas, "Number of tasks") 847 flags.Var(&opts.maxConcurrent, flagConcurrent, "Number of job tasks to run concurrently (default equal to --replicas)") 848 flags.SetAnnotation(flagConcurrent, "version", []string{"1.41"}) 849 flags.Uint64Var(&opts.maxReplicas, flagMaxReplicas, defaultFlagValues.getUint64(flagMaxReplicas), "Maximum number of tasks per node (default 0 = unlimited)") 850 flags.SetAnnotation(flagMaxReplicas, "version", []string{"1.40"}) 851 852 flags.StringVar(&opts.restartPolicy.condition, flagRestartCondition, "", flagDesc(flagRestartCondition, `Restart when condition is met ("none"|"on-failure"|"any")`)) 853 flags.Var(&opts.restartPolicy.delay, flagRestartDelay, flagDesc(flagRestartDelay, "Delay between restart attempts (ns|us|ms|s|m|h)")) 854 flags.Var(&opts.restartPolicy.maxAttempts, flagRestartMaxAttempts, flagDesc(flagRestartMaxAttempts, "Maximum number of restarts before giving up")) 855 856 flags.Var(&opts.restartPolicy.window, flagRestartWindow, flagDesc(flagRestartWindow, "Window used to evaluate the restart policy (ns|us|ms|s|m|h)")) 857 858 flags.Uint64Var(&opts.update.parallelism, flagUpdateParallelism, defaultFlagValues.getUint64(flagUpdateParallelism), "Maximum number of tasks updated simultaneously (0 to update all at once)") 859 flags.DurationVar(&opts.update.delay, flagUpdateDelay, 0, flagDesc(flagUpdateDelay, "Delay between updates (ns|us|ms|s|m|h)")) 860 flags.DurationVar(&opts.update.monitor, flagUpdateMonitor, 0, flagDesc(flagUpdateMonitor, "Duration after each task update to monitor for failure (ns|us|ms|s|m|h)")) 861 flags.SetAnnotation(flagUpdateMonitor, "version", []string{"1.25"}) 862 flags.StringVar(&opts.update.onFailure, flagUpdateFailureAction, "", flagDesc(flagUpdateFailureAction, `Action on update failure ("pause"|"continue"|"rollback")`)) 863 flags.Var(&opts.update.maxFailureRatio, flagUpdateMaxFailureRatio, flagDesc(flagUpdateMaxFailureRatio, "Failure rate to tolerate during an update")) 864 flags.SetAnnotation(flagUpdateMaxFailureRatio, "version", []string{"1.25"}) 865 flags.StringVar(&opts.update.order, flagUpdateOrder, "", flagDesc(flagUpdateOrder, `Update order ("start-first"|"stop-first")`)) 866 flags.SetAnnotation(flagUpdateOrder, "version", []string{"1.29"}) 867 868 flags.Uint64Var(&opts.rollback.parallelism, flagRollbackParallelism, defaultFlagValues.getUint64(flagRollbackParallelism), 869 "Maximum number of tasks rolled back simultaneously (0 to roll back all at once)") 870 flags.SetAnnotation(flagRollbackParallelism, "version", []string{"1.28"}) 871 flags.DurationVar(&opts.rollback.delay, flagRollbackDelay, 0, flagDesc(flagRollbackDelay, "Delay between task rollbacks (ns|us|ms|s|m|h)")) 872 flags.SetAnnotation(flagRollbackDelay, "version", []string{"1.28"}) 873 flags.DurationVar(&opts.rollback.monitor, flagRollbackMonitor, 0, flagDesc(flagRollbackMonitor, "Duration after each task rollback to monitor for failure (ns|us|ms|s|m|h)")) 874 flags.SetAnnotation(flagRollbackMonitor, "version", []string{"1.28"}) 875 flags.StringVar(&opts.rollback.onFailure, flagRollbackFailureAction, "", flagDesc(flagRollbackFailureAction, `Action on rollback failure ("pause"|"continue")`)) 876 flags.SetAnnotation(flagRollbackFailureAction, "version", []string{"1.28"}) 877 flags.Var(&opts.rollback.maxFailureRatio, flagRollbackMaxFailureRatio, flagDesc(flagRollbackMaxFailureRatio, "Failure rate to tolerate during a rollback")) 878 flags.SetAnnotation(flagRollbackMaxFailureRatio, "version", []string{"1.28"}) 879 flags.StringVar(&opts.rollback.order, flagRollbackOrder, "", flagDesc(flagRollbackOrder, `Rollback order ("start-first"|"stop-first")`)) 880 flags.SetAnnotation(flagRollbackOrder, "version", []string{"1.29"}) 881 882 flags.StringVar(&opts.endpoint.mode, flagEndpointMode, defaultFlagValues.getString(flagEndpointMode), "Endpoint mode (vip or dnsrr)") 883 884 flags.BoolVar(&opts.registryAuth, flagRegistryAuth, false, "Send registry authentication details to swarm agents") 885 flags.BoolVar(&opts.noResolveImage, flagNoResolveImage, false, "Do not query the registry to resolve image digest and supported platforms") 886 flags.SetAnnotation(flagNoResolveImage, "version", []string{"1.30"}) 887 888 flags.StringVar(&opts.logDriver.name, flagLogDriver, "", "Logging driver for service") 889 flags.Var(&opts.logDriver.opts, flagLogOpt, "Logging driver options") 890 891 flags.StringVar(&opts.healthcheck.cmd, flagHealthCmd, "", "Command to run to check health") 892 flags.SetAnnotation(flagHealthCmd, "version", []string{"1.25"}) 893 flags.Var(&opts.healthcheck.interval, flagHealthInterval, "Time between running the check (ms|s|m|h)") 894 flags.SetAnnotation(flagHealthInterval, "version", []string{"1.25"}) 895 flags.Var(&opts.healthcheck.timeout, flagHealthTimeout, "Maximum time to allow one check to run (ms|s|m|h)") 896 flags.SetAnnotation(flagHealthTimeout, "version", []string{"1.25"}) 897 flags.IntVar(&opts.healthcheck.retries, flagHealthRetries, 0, "Consecutive failures needed to report unhealthy") 898 flags.SetAnnotation(flagHealthRetries, "version", []string{"1.25"}) 899 flags.Var(&opts.healthcheck.startPeriod, flagHealthStartPeriod, "Start period for the container to initialize before counting retries towards unstable (ms|s|m|h)") 900 flags.SetAnnotation(flagHealthStartPeriod, "version", []string{"1.29"}) 901 flags.BoolVar(&opts.healthcheck.noHealthcheck, flagNoHealthcheck, false, "Disable any container-specified HEALTHCHECK") 902 flags.SetAnnotation(flagNoHealthcheck, "version", []string{"1.25"}) 903 904 flags.BoolVarP(&opts.tty, flagTTY, "t", false, "Allocate a pseudo-TTY") 905 flags.SetAnnotation(flagTTY, "version", []string{"1.25"}) 906 907 flags.BoolVar(&opts.readOnly, flagReadOnly, false, "Mount the container's root filesystem as read only") 908 flags.SetAnnotation(flagReadOnly, "version", []string{"1.28"}) 909 910 flags.StringVar(&opts.stopSignal, flagStopSignal, "", "Signal to stop the container") 911 flags.SetAnnotation(flagStopSignal, "version", []string{"1.28"}) 912 flags.StringVar(&opts.isolation, flagIsolation, "", "Service container isolation mode") 913 flags.SetAnnotation(flagIsolation, "version", []string{"1.35"}) 914 } 915 916 const ( 917 flagCredentialSpec = "credential-spec" 918 flagPlacementPref = "placement-pref" 919 flagPlacementPrefAdd = "placement-pref-add" 920 flagPlacementPrefRemove = "placement-pref-rm" 921 flagConstraint = "constraint" 922 flagConstraintRemove = "constraint-rm" 923 flagConstraintAdd = "constraint-add" 924 flagContainerLabel = "container-label" 925 flagContainerLabelRemove = "container-label-rm" 926 flagContainerLabelAdd = "container-label-add" 927 flagDetach = "detach" 928 flagDNS = "dns" 929 flagDNSRemove = "dns-rm" 930 flagDNSAdd = "dns-add" 931 flagDNSOption = "dns-option" 932 flagDNSOptionRemove = "dns-option-rm" 933 flagDNSOptionAdd = "dns-option-add" 934 flagDNSSearch = "dns-search" 935 flagDNSSearchRemove = "dns-search-rm" 936 flagDNSSearchAdd = "dns-search-add" 937 flagEndpointMode = "endpoint-mode" 938 flagEntrypoint = "entrypoint" 939 flagEnv = "env" 940 flagEnvFile = "env-file" 941 flagEnvRemove = "env-rm" 942 flagEnvAdd = "env-add" 943 flagGenericResourcesRemove = "generic-resource-rm" 944 flagGenericResourcesAdd = "generic-resource-add" 945 flagGroup = "group" 946 flagGroupAdd = "group-add" 947 flagGroupRemove = "group-rm" 948 flagHost = "host" 949 flagHostAdd = "host-add" 950 flagHostRemove = "host-rm" 951 flagHostname = "hostname" 952 flagLabel = "label" 953 flagLabelRemove = "label-rm" 954 flagLabelAdd = "label-add" 955 flagLimitCPU = "limit-cpu" 956 flagLimitMemory = "limit-memory" 957 flagLimitPids = "limit-pids" 958 flagMaxReplicas = "replicas-max-per-node" 959 flagConcurrent = "max-concurrent" 960 flagMode = "mode" 961 flagMount = "mount" 962 flagMountRemove = "mount-rm" 963 flagMountAdd = "mount-add" 964 flagName = "name" 965 flagNetwork = "network" 966 flagNetworkAdd = "network-add" 967 flagNetworkRemove = "network-rm" 968 flagPublish = "publish" 969 flagPublishRemove = "publish-rm" 970 flagPublishAdd = "publish-add" 971 flagQuiet = "quiet" 972 flagReadOnly = "read-only" 973 flagReplicas = "replicas" 974 flagReserveCPU = "reserve-cpu" 975 flagReserveMemory = "reserve-memory" 976 flagRestartCondition = "restart-condition" 977 flagRestartDelay = "restart-delay" 978 flagRestartMaxAttempts = "restart-max-attempts" 979 flagRestartWindow = "restart-window" 980 flagRollback = "rollback" 981 flagRollbackDelay = "rollback-delay" 982 flagRollbackFailureAction = "rollback-failure-action" 983 flagRollbackMaxFailureRatio = "rollback-max-failure-ratio" 984 flagRollbackMonitor = "rollback-monitor" 985 flagRollbackOrder = "rollback-order" 986 flagRollbackParallelism = "rollback-parallelism" 987 flagInit = "init" 988 flagSysCtl = "sysctl" 989 flagSysCtlAdd = "sysctl-add" 990 flagSysCtlRemove = "sysctl-rm" 991 flagStopGracePeriod = "stop-grace-period" 992 flagStopSignal = "stop-signal" 993 flagTTY = "tty" 994 flagUpdateDelay = "update-delay" 995 flagUpdateFailureAction = "update-failure-action" 996 flagUpdateMaxFailureRatio = "update-max-failure-ratio" 997 flagUpdateMonitor = "update-monitor" 998 flagUpdateOrder = "update-order" 999 flagUpdateParallelism = "update-parallelism" 1000 flagUser = "user" 1001 flagWorkdir = "workdir" 1002 flagRegistryAuth = "with-registry-auth" 1003 flagNoResolveImage = "no-resolve-image" 1004 flagLogDriver = "log-driver" 1005 flagLogOpt = "log-opt" 1006 flagHealthCmd = "health-cmd" 1007 flagHealthInterval = "health-interval" 1008 flagHealthRetries = "health-retries" 1009 flagHealthTimeout = "health-timeout" 1010 flagHealthStartPeriod = "health-start-period" 1011 flagNoHealthcheck = "no-healthcheck" 1012 flagSecret = "secret" 1013 flagSecretAdd = "secret-add" 1014 flagSecretRemove = "secret-rm" 1015 flagConfig = "config" 1016 flagConfigAdd = "config-add" 1017 flagConfigRemove = "config-rm" 1018 flagIsolation = "isolation" 1019 flagCapAdd = "cap-add" 1020 flagCapDrop = "cap-drop" 1021 flagUlimit = "ulimit" 1022 flagUlimitAdd = "ulimit-add" 1023 flagUlimitRemove = "ulimit-rm" 1024 ) 1025 1026 func validateAPIVersion(c swarm.ServiceSpec, serverAPIVersion string) error { 1027 for _, m := range c.TaskTemplate.ContainerSpec.Mounts { 1028 if m.BindOptions != nil && m.BindOptions.NonRecursive && versions.LessThan(serverAPIVersion, "1.40") { 1029 return errors.Errorf("bind-nonrecursive requires API v1.40 or later") 1030 } 1031 } 1032 return nil 1033 }