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