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