github.com/kobeld/docker@v1.12.0-rc1/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 strconv.FormatInt(m.Value(), 10) 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 strconv.FormatInt(c.Value(), 10) 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 setValueOnMap := func(target map[string]string, value string) { 164 parts := strings.SplitN(value, "=", 2) 165 if len(parts) == 1 { 166 target[value] = "" 167 } else { 168 target[parts[0]] = parts[1] 169 } 170 } 171 172 for _, field := range fields { 173 parts := strings.SplitN(field, "=", 2) 174 if len(parts) == 1 && strings.ToLower(parts[0]) == "writable" { 175 mount.Writable = true 176 continue 177 } 178 179 if len(parts) != 2 { 180 return fmt.Errorf("invald field '%s' must be a key=value pair", field) 181 } 182 183 key, value := parts[0], parts[1] 184 switch strings.ToLower(key) { 185 case "type": 186 mount.Type = swarm.MountType(strings.ToUpper(value)) 187 case "source": 188 mount.Source = value 189 case "target": 190 mount.Target = value 191 case "writable": 192 mount.Writable, err = strconv.ParseBool(value) 193 if err != nil { 194 return fmt.Errorf("invald value for writable: %s", err.Error()) 195 } 196 case "bind-propagation": 197 mount.BindOptions.Propagation = swarm.MountPropagation(strings.ToUpper(value)) 198 case "volume-populate": 199 volumeOptions().Populate, err = strconv.ParseBool(value) 200 if err != nil { 201 return fmt.Errorf("invald value for populate: %s", err.Error()) 202 } 203 case "volume-label": 204 setValueOnMap(volumeOptions().Labels, value) 205 case "volume-driver": 206 volumeOptions().DriverConfig.Name = value 207 case "volume-driver-opt": 208 if volumeOptions().DriverConfig.Options == nil { 209 volumeOptions().DriverConfig.Options = make(map[string]string) 210 } 211 setValueOnMap(volumeOptions().DriverConfig.Options, value) 212 default: 213 return fmt.Errorf("unexpected key '%s' in '%s'", key, value) 214 } 215 } 216 217 if mount.Type == "" { 218 return fmt.Errorf("type is required") 219 } 220 221 if mount.Target == "" { 222 return fmt.Errorf("target is required") 223 } 224 225 m.values = append(m.values, mount) 226 return nil 227 } 228 229 // Type returns the type of this option 230 func (m *MountOpt) Type() string { 231 return "mount" 232 } 233 234 // String returns a string repr of this option 235 func (m *MountOpt) String() string { 236 mounts := []string{} 237 for _, mount := range m.values { 238 mounts = append(mounts, fmt.Sprintf("%v", mount)) 239 } 240 return strings.Join(mounts, ", ") 241 } 242 243 // Value returns the mounts 244 func (m *MountOpt) Value() []swarm.Mount { 245 return m.values 246 } 247 248 type updateOptions struct { 249 parallelism uint64 250 delay time.Duration 251 } 252 253 type resourceOptions struct { 254 limitCPU nanoCPUs 255 limitMemBytes memBytes 256 resCPU nanoCPUs 257 resMemBytes memBytes 258 } 259 260 func (r *resourceOptions) ToResourceRequirements() *swarm.ResourceRequirements { 261 return &swarm.ResourceRequirements{ 262 Limits: &swarm.Resources{ 263 NanoCPUs: r.limitCPU.Value(), 264 MemoryBytes: r.limitMemBytes.Value(), 265 }, 266 Reservations: &swarm.Resources{ 267 NanoCPUs: r.resCPU.Value(), 268 MemoryBytes: r.resMemBytes.Value(), 269 }, 270 } 271 } 272 273 type restartPolicyOptions struct { 274 condition string 275 delay DurationOpt 276 maxAttempts Uint64Opt 277 window DurationOpt 278 } 279 280 func (r *restartPolicyOptions) ToRestartPolicy() *swarm.RestartPolicy { 281 return &swarm.RestartPolicy{ 282 Condition: swarm.RestartPolicyCondition(r.condition), 283 Delay: r.delay.Value(), 284 MaxAttempts: r.maxAttempts.Value(), 285 Window: r.window.Value(), 286 } 287 } 288 289 func convertNetworks(networks []string) []swarm.NetworkAttachmentConfig { 290 nets := []swarm.NetworkAttachmentConfig{} 291 for _, network := range networks { 292 nets = append(nets, swarm.NetworkAttachmentConfig{Target: network}) 293 } 294 return nets 295 } 296 297 type endpointOptions struct { 298 mode string 299 ports opts.ListOpts 300 } 301 302 func (e *endpointOptions) ToEndpointSpec() *swarm.EndpointSpec { 303 portConfigs := []swarm.PortConfig{} 304 // We can ignore errors because the format was already validated by ValidatePort 305 ports, portBindings, _ := nat.ParsePortSpecs(e.ports.GetAll()) 306 307 for port := range ports { 308 portConfigs = append(portConfigs, convertPortToPortConfig(port, portBindings)...) 309 } 310 311 return &swarm.EndpointSpec{ 312 Mode: swarm.ResolutionMode(e.mode), 313 Ports: portConfigs, 314 } 315 } 316 317 func convertPortToPortConfig( 318 port nat.Port, 319 portBindings map[nat.Port][]nat.PortBinding, 320 ) []swarm.PortConfig { 321 ports := []swarm.PortConfig{} 322 323 for _, binding := range portBindings[port] { 324 hostPort, _ := strconv.ParseUint(binding.HostPort, 10, 16) 325 ports = append(ports, swarm.PortConfig{ 326 //TODO Name: ? 327 Protocol: swarm.PortConfigProtocol(strings.ToLower(port.Proto())), 328 TargetPort: uint32(port.Int()), 329 PublishedPort: uint32(hostPort), 330 }) 331 } 332 return ports 333 } 334 335 // ValidatePort validates a string is in the expected format for a port definition 336 func ValidatePort(value string) (string, error) { 337 portMappings, err := nat.ParsePortSpec(value) 338 for _, portMapping := range portMappings { 339 if portMapping.Binding.HostIP != "" { 340 return "", fmt.Errorf("HostIP is not supported by a service.") 341 } 342 } 343 return value, err 344 } 345 346 type serviceOptions struct { 347 name string 348 labels opts.ListOpts 349 image string 350 command []string 351 args []string 352 env opts.ListOpts 353 workdir string 354 user string 355 mounts MountOpt 356 357 resources resourceOptions 358 stopGrace DurationOpt 359 360 replicas Uint64Opt 361 mode string 362 363 restartPolicy restartPolicyOptions 364 constraints []string 365 update updateOptions 366 networks []string 367 endpoint endpointOptions 368 } 369 370 func newServiceOptions() *serviceOptions { 371 return &serviceOptions{ 372 labels: opts.NewListOpts(runconfigopts.ValidateEnv), 373 env: opts.NewListOpts(runconfigopts.ValidateEnv), 374 endpoint: endpointOptions{ 375 ports: opts.NewListOpts(ValidatePort), 376 }, 377 } 378 } 379 380 func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) { 381 var service swarm.ServiceSpec 382 383 service = swarm.ServiceSpec{ 384 Annotations: swarm.Annotations{ 385 Name: opts.name, 386 Labels: runconfigopts.ConvertKVStringsToMap(opts.labels.GetAll()), 387 }, 388 TaskTemplate: swarm.TaskSpec{ 389 ContainerSpec: swarm.ContainerSpec{ 390 Image: opts.image, 391 Command: opts.command, 392 Args: opts.args, 393 Env: opts.env.GetAll(), 394 Dir: opts.workdir, 395 User: opts.user, 396 Mounts: opts.mounts.Value(), 397 StopGracePeriod: opts.stopGrace.Value(), 398 }, 399 Resources: opts.resources.ToResourceRequirements(), 400 RestartPolicy: opts.restartPolicy.ToRestartPolicy(), 401 Placement: &swarm.Placement{ 402 Constraints: opts.constraints, 403 }, 404 }, 405 Mode: swarm.ServiceMode{}, 406 UpdateConfig: &swarm.UpdateConfig{ 407 Parallelism: opts.update.parallelism, 408 Delay: opts.update.delay, 409 }, 410 Networks: convertNetworks(opts.networks), 411 EndpointSpec: opts.endpoint.ToEndpointSpec(), 412 } 413 414 switch opts.mode { 415 case "global": 416 if opts.replicas.Value() != nil { 417 return service, fmt.Errorf("replicas can only be used with replicated mode") 418 } 419 420 service.Mode.Global = &swarm.GlobalService{} 421 case "replicated": 422 service.Mode.Replicated = &swarm.ReplicatedService{ 423 Replicas: opts.replicas.Value(), 424 } 425 default: 426 return service, fmt.Errorf("Unknown mode: %s", opts.mode) 427 } 428 return service, nil 429 } 430 431 // addServiceFlags adds all flags that are common to both `create` and `update. 432 // Any flags that are not common are added separately in the individual command 433 func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) { 434 flags := cmd.Flags() 435 flags.StringVar(&opts.name, flagName, "", "Service name") 436 flags.VarP(&opts.labels, flagLabel, "l", "Service labels") 437 438 flags.VarP(&opts.env, "env", "e", "Set environment variables") 439 flags.StringVarP(&opts.workdir, "workdir", "w", "", "Working directory inside the container") 440 flags.StringVarP(&opts.user, "user", "u", "", "Username or UID") 441 flags.VarP(&opts.mounts, flagMount, "m", "Attach a mount to the service") 442 443 flags.Var(&opts.resources.limitCPU, flagLimitCPU, "Limit CPUs") 444 flags.Var(&opts.resources.limitMemBytes, flagLimitMemory, "Limit Memory") 445 flags.Var(&opts.resources.resCPU, flagReserveCPU, "Reserve CPUs") 446 flags.Var(&opts.resources.resMemBytes, flagReserveMemory, "Reserve Memory") 447 flags.Var(&opts.stopGrace, "stop-grace-period", "Time to wait before force killing a container") 448 449 flags.StringVar(&opts.mode, flagMode, "replicated", "Service mode (replicated or global)") 450 flags.Var(&opts.replicas, flagReplicas, "Number of tasks") 451 452 flags.StringVar(&opts.restartPolicy.condition, flagRestartCondition, "", "Restart when condition is met (none, on_failure, or any)") 453 flags.Var(&opts.restartPolicy.delay, flagRestartDelay, "Delay between restart attempts") 454 flags.Var(&opts.restartPolicy.maxAttempts, flagRestartMaxAttempts, "Maximum number of restarts before giving up") 455 flags.Var(&opts.restartPolicy.window, flagRestartWindow, "Window used to evalulate the restart policy") 456 457 flags.StringSliceVar(&opts.constraints, flagConstraint, []string{}, "Placement constraints") 458 459 flags.Uint64Var(&opts.update.parallelism, flagUpdateParallelism, 1, "Maximum number of tasks updated simultaneously") 460 flags.DurationVar(&opts.update.delay, flagUpdateDelay, time.Duration(0), "Delay between updates") 461 462 flags.StringSliceVar(&opts.networks, flagNetwork, []string{}, "Network attachments") 463 flags.StringVar(&opts.endpoint.mode, flagEndpointMode, "", "Endpoint mode(Valid values: VIP, DNSRR)") 464 flags.VarP(&opts.endpoint.ports, flagPublish, "p", "Publish a port as a node port") 465 } 466 467 const ( 468 flagConstraint = "constraint" 469 flagName = "name" 470 flagLabel = "label" 471 flagLimitCPU = "limit-cpu" 472 flagLimitMemory = "limit-memory" 473 flagReserveCPU = "reserve-cpu" 474 flagReserveMemory = "reserve-memory" 475 flagMount = "mount" 476 flagMode = "mode" 477 flagReplicas = "replicas" 478 flagPublish = "publish" 479 flagNetwork = "network" 480 flagRestartCondition = "restart-condition" 481 flagRestartDelay = "restart-delay" 482 flagRestartMaxAttempts = "restart-max-attempts" 483 flagRestartWindow = "restart-window" 484 flagEndpointMode = "endpoint-mode" 485 flagUpdateParallelism = "update-parallelism" 486 flagUpdateDelay = "update-delay" 487 )