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