github.com/portworx/docker@v1.12.1/api/client/service/opts.go (about) 1 package service 2 3 import ( 4 "encoding/csv" 5 "fmt" 6 "math/big" 7 "strconv" 8 "strings" 9 "time" 10 11 "github.com/docker/docker/opts" 12 runconfigopts "github.com/docker/docker/runconfig/opts" 13 "github.com/docker/engine-api/types/swarm" 14 "github.com/docker/go-connections/nat" 15 units "github.com/docker/go-units" 16 "github.com/spf13/cobra" 17 ) 18 19 type int64Value interface { 20 Value() int64 21 } 22 23 type memBytes int64 24 25 func (m *memBytes) String() string { 26 return units.BytesSize(float64(m.Value())) 27 } 28 29 func (m *memBytes) Set(value string) error { 30 val, err := units.RAMInBytes(value) 31 *m = memBytes(val) 32 return err 33 } 34 35 func (m *memBytes) Type() string { 36 return "MemoryBytes" 37 } 38 39 func (m *memBytes) Value() int64 { 40 return int64(*m) 41 } 42 43 type nanoCPUs int64 44 45 func (c *nanoCPUs) String() string { 46 return big.NewRat(c.Value(), 1e9).FloatString(3) 47 } 48 49 func (c *nanoCPUs) Set(value string) error { 50 cpu, ok := new(big.Rat).SetString(value) 51 if !ok { 52 return fmt.Errorf("Failed to parse %v as a rational number", value) 53 } 54 nano := cpu.Mul(cpu, big.NewRat(1e9, 1)) 55 if !nano.IsInt() { 56 return fmt.Errorf("value is too precise") 57 } 58 *c = nanoCPUs(nano.Num().Int64()) 59 return nil 60 } 61 62 func (c *nanoCPUs) Type() string { 63 return "NanoCPUs" 64 } 65 66 func (c *nanoCPUs) Value() int64 { 67 return int64(*c) 68 } 69 70 // DurationOpt is an option type for time.Duration that uses a pointer. This 71 // allows us to get nil values outside, instead of defaulting to 0 72 type DurationOpt struct { 73 value *time.Duration 74 } 75 76 // Set a new value on the option 77 func (d *DurationOpt) Set(s string) error { 78 v, err := time.ParseDuration(s) 79 d.value = &v 80 return err 81 } 82 83 // Type returns the type of this option 84 func (d *DurationOpt) Type() string { 85 return "duration-ptr" 86 } 87 88 // String returns a string repr of this option 89 func (d *DurationOpt) String() string { 90 if d.value != nil { 91 return d.value.String() 92 } 93 return "none" 94 } 95 96 // Value returns the time.Duration 97 func (d *DurationOpt) Value() *time.Duration { 98 return d.value 99 } 100 101 // Uint64Opt represents a uint64. 102 type Uint64Opt struct { 103 value *uint64 104 } 105 106 // Set a new value on the option 107 func (i *Uint64Opt) Set(s string) error { 108 v, err := strconv.ParseUint(s, 0, 64) 109 i.value = &v 110 return err 111 } 112 113 // Type returns the type of this option 114 func (i *Uint64Opt) Type() string { 115 return "uint64-ptr" 116 } 117 118 // String returns a string repr of this option 119 func (i *Uint64Opt) String() string { 120 if i.value != nil { 121 return fmt.Sprintf("%v", *i.value) 122 } 123 return "none" 124 } 125 126 // Value returns the uint64 127 func (i *Uint64Opt) Value() *uint64 { 128 return i.value 129 } 130 131 // MountOpt is a Value type for parsing mounts 132 type MountOpt struct { 133 values []swarm.Mount 134 } 135 136 // Set a new mount value 137 func (m *MountOpt) Set(value string) error { 138 csvReader := csv.NewReader(strings.NewReader(value)) 139 fields, err := csvReader.Read() 140 if err != nil { 141 return err 142 } 143 144 mount := swarm.Mount{} 145 146 volumeOptions := func() *swarm.VolumeOptions { 147 if mount.VolumeOptions == nil { 148 mount.VolumeOptions = &swarm.VolumeOptions{ 149 Labels: make(map[string]string), 150 } 151 } 152 if mount.VolumeOptions.DriverConfig == nil { 153 mount.VolumeOptions.DriverConfig = &swarm.Driver{} 154 } 155 return mount.VolumeOptions 156 } 157 158 bindOptions := func() *swarm.BindOptions { 159 if mount.BindOptions == nil { 160 mount.BindOptions = new(swarm.BindOptions) 161 } 162 return mount.BindOptions 163 } 164 165 setValueOnMap := func(target map[string]string, value string) { 166 parts := strings.SplitN(value, "=", 2) 167 if len(parts) == 1 { 168 target[value] = "" 169 } else { 170 target[parts[0]] = parts[1] 171 } 172 } 173 174 mount.Type = swarm.MountTypeVolume // default to volume mounts 175 // Set writable as the default 176 for _, field := range fields { 177 parts := strings.SplitN(field, "=", 2) 178 key := strings.ToLower(parts[0]) 179 180 if len(parts) == 1 { 181 switch key { 182 case "readonly", "ro": 183 mount.ReadOnly = true 184 continue 185 case "volume-nocopy": 186 volumeOptions().NoCopy = true 187 continue 188 } 189 } 190 191 if len(parts) != 2 { 192 return fmt.Errorf("invalid field '%s' must be a key=value pair", field) 193 } 194 195 value := parts[1] 196 switch key { 197 case "type": 198 mount.Type = swarm.MountType(strings.ToLower(value)) 199 case "source", "src": 200 mount.Source = value 201 case "target", "dst", "destination": 202 mount.Target = value 203 case "readonly", "ro": 204 mount.ReadOnly, err = strconv.ParseBool(value) 205 if err != nil { 206 return fmt.Errorf("invalid value for %s: %s", key, value) 207 } 208 case "bind-propagation": 209 bindOptions().Propagation = swarm.MountPropagation(strings.ToLower(value)) 210 case "volume-nocopy": 211 volumeOptions().NoCopy, err = strconv.ParseBool(value) 212 if err != nil { 213 return fmt.Errorf("invalid value for populate: %s", value) 214 } 215 case "volume-label": 216 setValueOnMap(volumeOptions().Labels, value) 217 case "volume-driver": 218 volumeOptions().DriverConfig.Name = value 219 case "volume-opt": 220 if volumeOptions().DriverConfig.Options == nil { 221 volumeOptions().DriverConfig.Options = make(map[string]string) 222 } 223 setValueOnMap(volumeOptions().DriverConfig.Options, value) 224 default: 225 return fmt.Errorf("unexpected key '%s' in '%s'", key, field) 226 } 227 } 228 229 if mount.Type == "" { 230 return fmt.Errorf("type is required") 231 } 232 233 if mount.Target == "" { 234 return fmt.Errorf("target is required") 235 } 236 237 if mount.VolumeOptions != nil && mount.Source == "" { 238 return fmt.Errorf("source is required when specifying volume-* options") 239 } 240 241 if mount.Type == swarm.MountTypeBind && mount.VolumeOptions != nil { 242 return fmt.Errorf("cannot mix 'volume-*' options with mount type '%s'", swarm.MountTypeBind) 243 } 244 if mount.Type == swarm.MountTypeVolume && mount.BindOptions != nil { 245 return fmt.Errorf("cannot mix 'bind-*' options with mount type '%s'", swarm.MountTypeVolume) 246 } 247 248 m.values = append(m.values, mount) 249 return nil 250 } 251 252 // Type returns the type of this option 253 func (m *MountOpt) Type() string { 254 return "mount" 255 } 256 257 // String returns a string repr of this option 258 func (m *MountOpt) String() string { 259 mounts := []string{} 260 for _, mount := range m.values { 261 repr := fmt.Sprintf("%s %s %s", mount.Type, mount.Source, mount.Target) 262 mounts = append(mounts, repr) 263 } 264 return strings.Join(mounts, ", ") 265 } 266 267 // Value returns the mounts 268 func (m *MountOpt) Value() []swarm.Mount { 269 return m.values 270 } 271 272 type updateOptions struct { 273 parallelism uint64 274 delay time.Duration 275 onFailure string 276 } 277 278 type resourceOptions struct { 279 limitCPU nanoCPUs 280 limitMemBytes memBytes 281 resCPU nanoCPUs 282 resMemBytes memBytes 283 } 284 285 func (r *resourceOptions) ToResourceRequirements() *swarm.ResourceRequirements { 286 return &swarm.ResourceRequirements{ 287 Limits: &swarm.Resources{ 288 NanoCPUs: r.limitCPU.Value(), 289 MemoryBytes: r.limitMemBytes.Value(), 290 }, 291 Reservations: &swarm.Resources{ 292 NanoCPUs: r.resCPU.Value(), 293 MemoryBytes: r.resMemBytes.Value(), 294 }, 295 } 296 } 297 298 type restartPolicyOptions struct { 299 condition string 300 delay DurationOpt 301 maxAttempts Uint64Opt 302 window DurationOpt 303 } 304 305 func (r *restartPolicyOptions) ToRestartPolicy() *swarm.RestartPolicy { 306 return &swarm.RestartPolicy{ 307 Condition: swarm.RestartPolicyCondition(r.condition), 308 Delay: r.delay.Value(), 309 MaxAttempts: r.maxAttempts.Value(), 310 Window: r.window.Value(), 311 } 312 } 313 314 func convertNetworks(networks []string) []swarm.NetworkAttachmentConfig { 315 nets := []swarm.NetworkAttachmentConfig{} 316 for _, network := range networks { 317 nets = append(nets, swarm.NetworkAttachmentConfig{Target: network}) 318 } 319 return nets 320 } 321 322 type endpointOptions struct { 323 mode string 324 ports opts.ListOpts 325 } 326 327 func (e *endpointOptions) ToEndpointSpec() *swarm.EndpointSpec { 328 portConfigs := []swarm.PortConfig{} 329 // We can ignore errors because the format was already validated by ValidatePort 330 ports, portBindings, _ := nat.ParsePortSpecs(e.ports.GetAll()) 331 332 for port := range ports { 333 portConfigs = append(portConfigs, convertPortToPortConfig(port, portBindings)...) 334 } 335 336 return &swarm.EndpointSpec{ 337 Mode: swarm.ResolutionMode(strings.ToLower(e.mode)), 338 Ports: portConfigs, 339 } 340 } 341 342 func convertPortToPortConfig( 343 port nat.Port, 344 portBindings map[nat.Port][]nat.PortBinding, 345 ) []swarm.PortConfig { 346 ports := []swarm.PortConfig{} 347 348 for _, binding := range portBindings[port] { 349 hostPort, _ := strconv.ParseUint(binding.HostPort, 10, 16) 350 ports = append(ports, swarm.PortConfig{ 351 //TODO Name: ? 352 Protocol: swarm.PortConfigProtocol(strings.ToLower(port.Proto())), 353 TargetPort: uint32(port.Int()), 354 PublishedPort: uint32(hostPort), 355 }) 356 } 357 return ports 358 } 359 360 type logDriverOptions struct { 361 name string 362 opts opts.ListOpts 363 } 364 365 func newLogDriverOptions() logDriverOptions { 366 return logDriverOptions{opts: opts.NewListOpts(runconfigopts.ValidateEnv)} 367 } 368 369 func (ldo *logDriverOptions) toLogDriver() *swarm.Driver { 370 if ldo.name == "" { 371 return nil 372 } 373 374 // set the log driver only if specified. 375 return &swarm.Driver{ 376 Name: ldo.name, 377 Options: runconfigopts.ConvertKVStringsToMap(ldo.opts.GetAll()), 378 } 379 } 380 381 // ValidatePort validates a string is in the expected format for a port definition 382 func ValidatePort(value string) (string, error) { 383 portMappings, err := nat.ParsePortSpec(value) 384 for _, portMapping := range portMappings { 385 if portMapping.Binding.HostIP != "" { 386 return "", fmt.Errorf("HostIP is not supported by a service.") 387 } 388 } 389 return value, err 390 } 391 392 type serviceOptions struct { 393 name string 394 labels opts.ListOpts 395 containerLabels opts.ListOpts 396 image string 397 args []string 398 env opts.ListOpts 399 workdir string 400 user string 401 mounts MountOpt 402 403 resources resourceOptions 404 stopGrace DurationOpt 405 406 replicas Uint64Opt 407 mode string 408 409 restartPolicy restartPolicyOptions 410 constraints []string 411 update updateOptions 412 networks []string 413 endpoint endpointOptions 414 415 registryAuth bool 416 417 logDriver logDriverOptions 418 } 419 420 func newServiceOptions() *serviceOptions { 421 return &serviceOptions{ 422 labels: opts.NewListOpts(runconfigopts.ValidateEnv), 423 containerLabels: opts.NewListOpts(runconfigopts.ValidateEnv), 424 env: opts.NewListOpts(runconfigopts.ValidateEnv), 425 endpoint: endpointOptions{ 426 ports: opts.NewListOpts(ValidatePort), 427 }, 428 logDriver: newLogDriverOptions(), 429 } 430 } 431 432 func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) { 433 var service swarm.ServiceSpec 434 435 service = swarm.ServiceSpec{ 436 Annotations: swarm.Annotations{ 437 Name: opts.name, 438 Labels: runconfigopts.ConvertKVStringsToMap(opts.labels.GetAll()), 439 }, 440 TaskTemplate: swarm.TaskSpec{ 441 ContainerSpec: swarm.ContainerSpec{ 442 Image: opts.image, 443 Args: opts.args, 444 Env: opts.env.GetAll(), 445 Labels: runconfigopts.ConvertKVStringsToMap(opts.containerLabels.GetAll()), 446 Dir: opts.workdir, 447 User: opts.user, 448 Mounts: opts.mounts.Value(), 449 StopGracePeriod: opts.stopGrace.Value(), 450 }, 451 Resources: opts.resources.ToResourceRequirements(), 452 RestartPolicy: opts.restartPolicy.ToRestartPolicy(), 453 Placement: &swarm.Placement{ 454 Constraints: opts.constraints, 455 }, 456 LogDriver: opts.logDriver.toLogDriver(), 457 }, 458 Mode: swarm.ServiceMode{}, 459 UpdateConfig: &swarm.UpdateConfig{ 460 Parallelism: opts.update.parallelism, 461 Delay: opts.update.delay, 462 FailureAction: opts.update.onFailure, 463 }, 464 Networks: convertNetworks(opts.networks), 465 EndpointSpec: opts.endpoint.ToEndpointSpec(), 466 } 467 468 switch opts.mode { 469 case "global": 470 if opts.replicas.Value() != nil { 471 return service, fmt.Errorf("replicas can only be used with replicated mode") 472 } 473 474 service.Mode.Global = &swarm.GlobalService{} 475 case "replicated": 476 service.Mode.Replicated = &swarm.ReplicatedService{ 477 Replicas: opts.replicas.Value(), 478 } 479 default: 480 return service, fmt.Errorf("Unknown mode: %s", opts.mode) 481 } 482 return service, nil 483 } 484 485 // addServiceFlags adds all flags that are common to both `create` and `update`. 486 // Any flags that are not common are added separately in the individual command 487 func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) { 488 flags := cmd.Flags() 489 flags.StringVar(&opts.name, flagName, "", "Service name") 490 491 flags.StringVarP(&opts.workdir, "workdir", "w", "", "Working directory inside the container") 492 flags.StringVarP(&opts.user, flagUser, "u", "", "Username or UID") 493 494 flags.Var(&opts.resources.limitCPU, flagLimitCPU, "Limit CPUs") 495 flags.Var(&opts.resources.limitMemBytes, flagLimitMemory, "Limit Memory") 496 flags.Var(&opts.resources.resCPU, flagReserveCPU, "Reserve CPUs") 497 flags.Var(&opts.resources.resMemBytes, flagReserveMemory, "Reserve Memory") 498 flags.Var(&opts.stopGrace, flagStopGracePeriod, "Time to wait before force killing a container") 499 500 flags.Var(&opts.replicas, flagReplicas, "Number of tasks") 501 502 flags.StringVar(&opts.restartPolicy.condition, flagRestartCondition, "", "Restart when condition is met (none, on-failure, or any)") 503 flags.Var(&opts.restartPolicy.delay, flagRestartDelay, "Delay between restart attempts") 504 flags.Var(&opts.restartPolicy.maxAttempts, flagRestartMaxAttempts, "Maximum number of restarts before giving up") 505 flags.Var(&opts.restartPolicy.window, flagRestartWindow, "Window used to evaluate the restart policy") 506 507 flags.Uint64Var(&opts.update.parallelism, flagUpdateParallelism, 1, "Maximum number of tasks updated simultaneously (0 to update all at once)") 508 flags.DurationVar(&opts.update.delay, flagUpdateDelay, time.Duration(0), "Delay between updates") 509 flags.StringVar(&opts.update.onFailure, flagUpdateFailureAction, "pause", "Action on update failure (pause|continue)") 510 511 flags.StringVar(&opts.endpoint.mode, flagEndpointMode, "", "Endpoint mode (vip or dnsrr)") 512 513 flags.BoolVar(&opts.registryAuth, flagRegistryAuth, false, "Send registry authentication details to swarm agents") 514 515 flags.StringVar(&opts.logDriver.name, flagLogDriver, "", "Logging driver for service") 516 flags.Var(&opts.logDriver.opts, flagLogOpt, "Logging driver options") 517 } 518 519 const ( 520 flagConstraint = "constraint" 521 flagConstraintRemove = "constraint-rm" 522 flagConstraintAdd = "constraint-add" 523 flagContainerLabel = "container-label" 524 flagContainerLabelRemove = "container-label-rm" 525 flagContainerLabelAdd = "container-label-add" 526 flagEndpointMode = "endpoint-mode" 527 flagEnv = "env" 528 flagEnvRemove = "env-rm" 529 flagEnvAdd = "env-add" 530 flagLabel = "label" 531 flagLabelRemove = "label-rm" 532 flagLabelAdd = "label-add" 533 flagLimitCPU = "limit-cpu" 534 flagLimitMemory = "limit-memory" 535 flagMode = "mode" 536 flagMount = "mount" 537 flagMountRemove = "mount-rm" 538 flagMountAdd = "mount-add" 539 flagName = "name" 540 flagNetwork = "network" 541 flagPublish = "publish" 542 flagPublishRemove = "publish-rm" 543 flagPublishAdd = "publish-add" 544 flagReplicas = "replicas" 545 flagReserveCPU = "reserve-cpu" 546 flagReserveMemory = "reserve-memory" 547 flagRestartCondition = "restart-condition" 548 flagRestartDelay = "restart-delay" 549 flagRestartMaxAttempts = "restart-max-attempts" 550 flagRestartWindow = "restart-window" 551 flagStopGracePeriod = "stop-grace-period" 552 flagUpdateDelay = "update-delay" 553 flagUpdateFailureAction = "update-failure-action" 554 flagUpdateParallelism = "update-parallelism" 555 flagUser = "user" 556 flagRegistryAuth = "with-registry-auth" 557 flagLogDriver = "log-driver" 558 flagLogOpt = "log-opt" 559 )