github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/cli/command/service/opts.go (about) 1 package service 2 3 import ( 4 "fmt" 5 "strconv" 6 "strings" 7 "time" 8 9 "github.com/docker/docker/api/types/container" 10 "github.com/docker/docker/api/types/swarm" 11 "github.com/docker/docker/opts" 12 runconfigopts "github.com/docker/docker/runconfig/opts" 13 "github.com/spf13/cobra" 14 ) 15 16 type int64Value interface { 17 Value() int64 18 } 19 20 // PositiveDurationOpt is an option type for time.Duration that uses a pointer. 21 // It bahave similarly to DurationOpt but only allows positive duration values. 22 type PositiveDurationOpt struct { 23 DurationOpt 24 } 25 26 // Set a new value on the option. Setting a negative duration value will cause 27 // an error to be returned. 28 func (d *PositiveDurationOpt) Set(s string) error { 29 err := d.DurationOpt.Set(s) 30 if err != nil { 31 return err 32 } 33 if *d.DurationOpt.value < 0 { 34 return fmt.Errorf("duration cannot be negative") 35 } 36 return nil 37 } 38 39 // DurationOpt is an option type for time.Duration that uses a pointer. This 40 // allows us to get nil values outside, instead of defaulting to 0 41 type DurationOpt struct { 42 value *time.Duration 43 } 44 45 // Set a new value on the option 46 func (d *DurationOpt) Set(s string) error { 47 v, err := time.ParseDuration(s) 48 d.value = &v 49 return err 50 } 51 52 // Type returns the type of this option, which will be displayed in `--help` output 53 func (d *DurationOpt) Type() string { 54 return "duration" 55 } 56 57 // String returns a string repr of this option 58 func (d *DurationOpt) String() string { 59 if d.value != nil { 60 return d.value.String() 61 } 62 return "" 63 } 64 65 // Value returns the time.Duration 66 func (d *DurationOpt) Value() *time.Duration { 67 return d.value 68 } 69 70 // Uint64Opt represents a uint64. 71 type Uint64Opt struct { 72 value *uint64 73 } 74 75 // Set a new value on the option 76 func (i *Uint64Opt) Set(s string) error { 77 v, err := strconv.ParseUint(s, 0, 64) 78 i.value = &v 79 return err 80 } 81 82 // Type returns the type of this option, which will be displayed in `--help` output 83 func (i *Uint64Opt) Type() string { 84 return "uint" 85 } 86 87 // String returns a string repr of this option 88 func (i *Uint64Opt) String() string { 89 if i.value != nil { 90 return fmt.Sprintf("%v", *i.value) 91 } 92 return "" 93 } 94 95 // Value returns the uint64 96 func (i *Uint64Opt) Value() *uint64 { 97 return i.value 98 } 99 100 type floatValue float32 101 102 func (f *floatValue) Set(s string) error { 103 v, err := strconv.ParseFloat(s, 32) 104 *f = floatValue(v) 105 return err 106 } 107 108 func (f *floatValue) Type() string { 109 return "float" 110 } 111 112 func (f *floatValue) String() string { 113 return strconv.FormatFloat(float64(*f), 'g', -1, 32) 114 } 115 116 func (f *floatValue) Value() float32 { 117 return float32(*f) 118 } 119 120 type updateOptions struct { 121 parallelism uint64 122 delay time.Duration 123 monitor time.Duration 124 onFailure string 125 maxFailureRatio floatValue 126 } 127 128 type resourceOptions struct { 129 limitCPU opts.NanoCPUs 130 limitMemBytes opts.MemBytes 131 resCPU opts.NanoCPUs 132 resMemBytes opts.MemBytes 133 } 134 135 func (r *resourceOptions) ToResourceRequirements() *swarm.ResourceRequirements { 136 return &swarm.ResourceRequirements{ 137 Limits: &swarm.Resources{ 138 NanoCPUs: r.limitCPU.Value(), 139 MemoryBytes: r.limitMemBytes.Value(), 140 }, 141 Reservations: &swarm.Resources{ 142 NanoCPUs: r.resCPU.Value(), 143 MemoryBytes: r.resMemBytes.Value(), 144 }, 145 } 146 } 147 148 type restartPolicyOptions struct { 149 condition string 150 delay DurationOpt 151 maxAttempts Uint64Opt 152 window DurationOpt 153 } 154 155 func (r *restartPolicyOptions) ToRestartPolicy() *swarm.RestartPolicy { 156 return &swarm.RestartPolicy{ 157 Condition: swarm.RestartPolicyCondition(r.condition), 158 Delay: r.delay.Value(), 159 MaxAttempts: r.maxAttempts.Value(), 160 Window: r.window.Value(), 161 } 162 } 163 164 func convertNetworks(networks []string) []swarm.NetworkAttachmentConfig { 165 nets := []swarm.NetworkAttachmentConfig{} 166 for _, network := range networks { 167 nets = append(nets, swarm.NetworkAttachmentConfig{Target: network}) 168 } 169 return nets 170 } 171 172 type endpointOptions struct { 173 mode string 174 publishPorts opts.PortOpt 175 } 176 177 func (e *endpointOptions) ToEndpointSpec() *swarm.EndpointSpec { 178 return &swarm.EndpointSpec{ 179 Mode: swarm.ResolutionMode(strings.ToLower(e.mode)), 180 Ports: e.publishPorts.Value(), 181 } 182 } 183 184 type logDriverOptions struct { 185 name string 186 opts opts.ListOpts 187 } 188 189 func newLogDriverOptions() logDriverOptions { 190 return logDriverOptions{opts: opts.NewListOpts(opts.ValidateEnv)} 191 } 192 193 func (ldo *logDriverOptions) toLogDriver() *swarm.Driver { 194 if ldo.name == "" { 195 return nil 196 } 197 198 // set the log driver only if specified. 199 return &swarm.Driver{ 200 Name: ldo.name, 201 Options: runconfigopts.ConvertKVStringsToMap(ldo.opts.GetAll()), 202 } 203 } 204 205 type healthCheckOptions struct { 206 cmd string 207 interval PositiveDurationOpt 208 timeout PositiveDurationOpt 209 retries int 210 noHealthcheck bool 211 } 212 213 func (opts *healthCheckOptions) toHealthConfig() (*container.HealthConfig, error) { 214 var healthConfig *container.HealthConfig 215 haveHealthSettings := opts.cmd != "" || 216 opts.interval.Value() != nil || 217 opts.timeout.Value() != nil || 218 opts.retries != 0 219 if opts.noHealthcheck { 220 if haveHealthSettings { 221 return nil, fmt.Errorf("--%s conflicts with --health-* options", flagNoHealthcheck) 222 } 223 healthConfig = &container.HealthConfig{Test: []string{"NONE"}} 224 } else if haveHealthSettings { 225 var test []string 226 if opts.cmd != "" { 227 test = []string{"CMD-SHELL", opts.cmd} 228 } 229 var interval, timeout time.Duration 230 if ptr := opts.interval.Value(); ptr != nil { 231 interval = *ptr 232 } 233 if ptr := opts.timeout.Value(); ptr != nil { 234 timeout = *ptr 235 } 236 healthConfig = &container.HealthConfig{ 237 Test: test, 238 Interval: interval, 239 Timeout: timeout, 240 Retries: opts.retries, 241 } 242 } 243 return healthConfig, nil 244 } 245 246 // convertExtraHostsToSwarmHosts converts an array of extra hosts in cli 247 // <host>:<ip> 248 // into a swarmkit host format: 249 // IP_address canonical_hostname [aliases...] 250 // This assumes input value (<host>:<ip>) has already been validated 251 func convertExtraHostsToSwarmHosts(extraHosts []string) []string { 252 hosts := []string{} 253 for _, extraHost := range extraHosts { 254 parts := strings.SplitN(extraHost, ":", 2) 255 hosts = append(hosts, fmt.Sprintf("%s %s", parts[1], parts[0])) 256 } 257 return hosts 258 } 259 260 type serviceOptions struct { 261 name string 262 labels opts.ListOpts 263 containerLabels opts.ListOpts 264 image string 265 args []string 266 hostname string 267 env opts.ListOpts 268 envFile opts.ListOpts 269 workdir string 270 user string 271 groups opts.ListOpts 272 tty bool 273 readOnly bool 274 mounts opts.MountOpt 275 dns opts.ListOpts 276 dnsSearch opts.ListOpts 277 dnsOption opts.ListOpts 278 hosts opts.ListOpts 279 280 resources resourceOptions 281 stopGrace DurationOpt 282 283 replicas Uint64Opt 284 mode string 285 286 restartPolicy restartPolicyOptions 287 constraints opts.ListOpts 288 update updateOptions 289 networks opts.ListOpts 290 endpoint endpointOptions 291 292 registryAuth bool 293 294 logDriver logDriverOptions 295 296 healthcheck healthCheckOptions 297 secrets opts.SecretOpt 298 } 299 300 func newServiceOptions() *serviceOptions { 301 return &serviceOptions{ 302 labels: opts.NewListOpts(opts.ValidateEnv), 303 constraints: opts.NewListOpts(nil), 304 containerLabels: opts.NewListOpts(opts.ValidateEnv), 305 env: opts.NewListOpts(opts.ValidateEnv), 306 envFile: opts.NewListOpts(nil), 307 groups: opts.NewListOpts(nil), 308 logDriver: newLogDriverOptions(), 309 dns: opts.NewListOpts(opts.ValidateIPAddress), 310 dnsOption: opts.NewListOpts(nil), 311 dnsSearch: opts.NewListOpts(opts.ValidateDNSSearch), 312 hosts: opts.NewListOpts(opts.ValidateExtraHost), 313 networks: opts.NewListOpts(nil), 314 } 315 } 316 317 func (opts *serviceOptions) ToServiceMode() (swarm.ServiceMode, error) { 318 serviceMode := swarm.ServiceMode{} 319 switch opts.mode { 320 case "global": 321 if opts.replicas.Value() != nil { 322 return serviceMode, fmt.Errorf("replicas can only be used with replicated mode") 323 } 324 325 serviceMode.Global = &swarm.GlobalService{} 326 case "replicated": 327 serviceMode.Replicated = &swarm.ReplicatedService{ 328 Replicas: opts.replicas.Value(), 329 } 330 default: 331 return serviceMode, fmt.Errorf("Unknown mode: %s, only replicated and global supported", opts.mode) 332 } 333 return serviceMode, nil 334 } 335 336 func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) { 337 var service swarm.ServiceSpec 338 339 envVariables, err := runconfigopts.ReadKVStrings(opts.envFile.GetAll(), opts.env.GetAll()) 340 if err != nil { 341 return service, err 342 } 343 344 currentEnv := make([]string, 0, len(envVariables)) 345 for _, env := range envVariables { // need to process each var, in order 346 k := strings.SplitN(env, "=", 2)[0] 347 for i, current := range currentEnv { // remove duplicates 348 if current == env { 349 continue // no update required, may hide this behind flag to preserve order of envVariables 350 } 351 if strings.HasPrefix(current, k+"=") { 352 currentEnv = append(currentEnv[:i], currentEnv[i+1:]...) 353 } 354 } 355 currentEnv = append(currentEnv, env) 356 } 357 358 healthConfig, err := opts.healthcheck.toHealthConfig() 359 if err != nil { 360 return service, err 361 } 362 363 serviceMode, err := opts.ToServiceMode() 364 if err != nil { 365 return service, err 366 } 367 368 service = swarm.ServiceSpec{ 369 Annotations: swarm.Annotations{ 370 Name: opts.name, 371 Labels: runconfigopts.ConvertKVStringsToMap(opts.labels.GetAll()), 372 }, 373 TaskTemplate: swarm.TaskSpec{ 374 ContainerSpec: swarm.ContainerSpec{ 375 Image: opts.image, 376 Args: opts.args, 377 Env: currentEnv, 378 Hostname: opts.hostname, 379 Labels: runconfigopts.ConvertKVStringsToMap(opts.containerLabels.GetAll()), 380 Dir: opts.workdir, 381 User: opts.user, 382 Groups: opts.groups.GetAll(), 383 TTY: opts.tty, 384 ReadOnly: opts.readOnly, 385 Mounts: opts.mounts.Value(), 386 DNSConfig: &swarm.DNSConfig{ 387 Nameservers: opts.dns.GetAll(), 388 Search: opts.dnsSearch.GetAll(), 389 Options: opts.dnsOption.GetAll(), 390 }, 391 Hosts: convertExtraHostsToSwarmHosts(opts.hosts.GetAll()), 392 StopGracePeriod: opts.stopGrace.Value(), 393 Secrets: nil, 394 Healthcheck: healthConfig, 395 }, 396 Networks: convertNetworks(opts.networks.GetAll()), 397 Resources: opts.resources.ToResourceRequirements(), 398 RestartPolicy: opts.restartPolicy.ToRestartPolicy(), 399 Placement: &swarm.Placement{ 400 Constraints: opts.constraints.GetAll(), 401 }, 402 LogDriver: opts.logDriver.toLogDriver(), 403 }, 404 Networks: convertNetworks(opts.networks.GetAll()), 405 Mode: serviceMode, 406 UpdateConfig: &swarm.UpdateConfig{ 407 Parallelism: opts.update.parallelism, 408 Delay: opts.update.delay, 409 Monitor: opts.update.monitor, 410 FailureAction: opts.update.onFailure, 411 MaxFailureRatio: opts.update.maxFailureRatio.Value(), 412 }, 413 EndpointSpec: opts.endpoint.ToEndpointSpec(), 414 } 415 416 return service, nil 417 } 418 419 // addServiceFlags adds all flags that are common to both `create` and `update`. 420 // Any flags that are not common are added separately in the individual command 421 func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) { 422 flags := cmd.Flags() 423 424 flags.StringVarP(&opts.workdir, flagWorkdir, "w", "", "Working directory inside the container") 425 flags.StringVarP(&opts.user, flagUser, "u", "", "Username or UID (format: <name|uid>[:<group|gid>])") 426 flags.StringVar(&opts.hostname, flagHostname, "", "Container hostname") 427 flags.SetAnnotation(flagHostname, "version", []string{"1.25"}) 428 429 flags.Var(&opts.resources.limitCPU, flagLimitCPU, "Limit CPUs") 430 flags.Var(&opts.resources.limitMemBytes, flagLimitMemory, "Limit Memory") 431 flags.Var(&opts.resources.resCPU, flagReserveCPU, "Reserve CPUs") 432 flags.Var(&opts.resources.resMemBytes, flagReserveMemory, "Reserve Memory") 433 flags.Var(&opts.stopGrace, flagStopGracePeriod, "Time to wait before force killing a container (ns|us|ms|s|m|h)") 434 435 flags.Var(&opts.replicas, flagReplicas, "Number of tasks") 436 437 flags.StringVar(&opts.restartPolicy.condition, flagRestartCondition, "", `Restart when condition is met ("none"|"on-failure"|"any")`) 438 flags.Var(&opts.restartPolicy.delay, flagRestartDelay, "Delay between restart attempts (ns|us|ms|s|m|h)") 439 flags.Var(&opts.restartPolicy.maxAttempts, flagRestartMaxAttempts, "Maximum number of restarts before giving up") 440 flags.Var(&opts.restartPolicy.window, flagRestartWindow, "Window used to evaluate the restart policy (ns|us|ms|s|m|h)") 441 442 flags.Uint64Var(&opts.update.parallelism, flagUpdateParallelism, 1, "Maximum number of tasks updated simultaneously (0 to update all at once)") 443 flags.DurationVar(&opts.update.delay, flagUpdateDelay, time.Duration(0), "Delay between updates (ns|us|ms|s|m|h) (default 0s)") 444 flags.DurationVar(&opts.update.monitor, flagUpdateMonitor, time.Duration(0), "Duration after each task update to monitor for failure (ns|us|ms|s|m|h) (default 0s)") 445 flags.SetAnnotation(flagUpdateMonitor, "version", []string{"1.25"}) 446 flags.StringVar(&opts.update.onFailure, flagUpdateFailureAction, "pause", `Action on update failure ("pause"|"continue")`) 447 flags.Var(&opts.update.maxFailureRatio, flagUpdateMaxFailureRatio, "Failure rate to tolerate during an update") 448 flags.SetAnnotation(flagUpdateMaxFailureRatio, "version", []string{"1.25"}) 449 450 flags.StringVar(&opts.endpoint.mode, flagEndpointMode, "vip", "Endpoint mode (vip or dnsrr)") 451 452 flags.BoolVar(&opts.registryAuth, flagRegistryAuth, false, "Send registry authentication details to swarm agents") 453 454 flags.StringVar(&opts.logDriver.name, flagLogDriver, "", "Logging driver for service") 455 flags.Var(&opts.logDriver.opts, flagLogOpt, "Logging driver options") 456 457 flags.StringVar(&opts.healthcheck.cmd, flagHealthCmd, "", "Command to run to check health") 458 flags.SetAnnotation(flagHealthCmd, "version", []string{"1.25"}) 459 flags.Var(&opts.healthcheck.interval, flagHealthInterval, "Time between running the check (ns|us|ms|s|m|h)") 460 flags.SetAnnotation(flagHealthInterval, "version", []string{"1.25"}) 461 flags.Var(&opts.healthcheck.timeout, flagHealthTimeout, "Maximum time to allow one check to run (ns|us|ms|s|m|h)") 462 flags.SetAnnotation(flagHealthTimeout, "version", []string{"1.25"}) 463 flags.IntVar(&opts.healthcheck.retries, flagHealthRetries, 0, "Consecutive failures needed to report unhealthy") 464 flags.SetAnnotation(flagHealthRetries, "version", []string{"1.25"}) 465 flags.BoolVar(&opts.healthcheck.noHealthcheck, flagNoHealthcheck, false, "Disable any container-specified HEALTHCHECK") 466 flags.SetAnnotation(flagNoHealthcheck, "version", []string{"1.25"}) 467 468 flags.BoolVarP(&opts.tty, flagTTY, "t", false, "Allocate a pseudo-TTY") 469 flags.SetAnnotation(flagTTY, "version", []string{"1.25"}) 470 471 flags.BoolVar(&opts.readOnly, flagReadOnly, false, "Mount the container's root filesystem as read only") 472 flags.SetAnnotation(flagReadOnly, "version", []string{"1.27"}) 473 } 474 475 const ( 476 flagConstraint = "constraint" 477 flagConstraintRemove = "constraint-rm" 478 flagConstraintAdd = "constraint-add" 479 flagContainerLabel = "container-label" 480 flagContainerLabelRemove = "container-label-rm" 481 flagContainerLabelAdd = "container-label-add" 482 flagDNS = "dns" 483 flagDNSRemove = "dns-rm" 484 flagDNSAdd = "dns-add" 485 flagDNSOption = "dns-option" 486 flagDNSOptionRemove = "dns-option-rm" 487 flagDNSOptionAdd = "dns-option-add" 488 flagDNSSearch = "dns-search" 489 flagDNSSearchRemove = "dns-search-rm" 490 flagDNSSearchAdd = "dns-search-add" 491 flagEndpointMode = "endpoint-mode" 492 flagHost = "host" 493 flagHostAdd = "host-add" 494 flagHostRemove = "host-rm" 495 flagHostname = "hostname" 496 flagEnv = "env" 497 flagEnvFile = "env-file" 498 flagEnvRemove = "env-rm" 499 flagEnvAdd = "env-add" 500 flagGroup = "group" 501 flagGroupAdd = "group-add" 502 flagGroupRemove = "group-rm" 503 flagLabel = "label" 504 flagLabelRemove = "label-rm" 505 flagLabelAdd = "label-add" 506 flagLimitCPU = "limit-cpu" 507 flagLimitMemory = "limit-memory" 508 flagMode = "mode" 509 flagMount = "mount" 510 flagMountRemove = "mount-rm" 511 flagMountAdd = "mount-add" 512 flagName = "name" 513 flagNetwork = "network" 514 flagPublish = "publish" 515 flagPublishRemove = "publish-rm" 516 flagPublishAdd = "publish-add" 517 flagReadOnly = "read-only" 518 flagReplicas = "replicas" 519 flagReserveCPU = "reserve-cpu" 520 flagReserveMemory = "reserve-memory" 521 flagRestartCondition = "restart-condition" 522 flagRestartDelay = "restart-delay" 523 flagRestartMaxAttempts = "restart-max-attempts" 524 flagRestartWindow = "restart-window" 525 flagStopGracePeriod = "stop-grace-period" 526 flagTTY = "tty" 527 flagUpdateDelay = "update-delay" 528 flagUpdateFailureAction = "update-failure-action" 529 flagUpdateMaxFailureRatio = "update-max-failure-ratio" 530 flagUpdateMonitor = "update-monitor" 531 flagUpdateParallelism = "update-parallelism" 532 flagUser = "user" 533 flagWorkdir = "workdir" 534 flagRegistryAuth = "with-registry-auth" 535 flagLogDriver = "log-driver" 536 flagLogOpt = "log-opt" 537 flagHealthCmd = "health-cmd" 538 flagHealthInterval = "health-interval" 539 flagHealthRetries = "health-retries" 540 flagHealthTimeout = "health-timeout" 541 flagNoHealthcheck = "no-healthcheck" 542 flagSecret = "secret" 543 flagSecretAdd = "secret-add" 544 flagSecretRemove = "secret-rm" 545 )