github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/core/constraints/constraints.go (about) 1 // Copyright 2013, 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package constraints 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "math" 10 "strconv" 11 "strings" 12 13 "github.com/juju/errors" 14 "github.com/juju/names/v5" 15 16 "github.com/juju/juju/core/arch" 17 "github.com/juju/juju/core/instance" 18 ) 19 20 // The following constants list the supported constraint attribute names, as defined 21 // by the fields in the Value struct. 22 const ( 23 Arch = "arch" 24 Container = "container" 25 // cpuCores is an alias for Cores. 26 cpuCores = "cpu-cores" 27 Cores = "cores" 28 CpuPower = "cpu-power" 29 Mem = "mem" 30 RootDisk = "root-disk" 31 RootDiskSource = "root-disk-source" 32 Tags = "tags" 33 InstanceRole = "instance-role" 34 InstanceType = "instance-type" 35 Spaces = "spaces" 36 VirtType = "virt-type" 37 Zones = "zones" 38 AllocatePublicIP = "allocate-public-ip" 39 ImageID = "image-id" 40 ) 41 42 // Value describes a user's requirements of the hardware on which units 43 // of an application will run. Constraints are used to choose an existing machine 44 // onto which a unit will be deployed, or to provision a new machine if no 45 // existing one satisfies the requirements. 46 type Value struct { 47 48 // Arch, if not nil or empty, indicates that a machine must run the named 49 // architecture. 50 Arch *string `json:"arch,omitempty" yaml:"arch,omitempty"` 51 52 // Container, if not nil, indicates that a machine must be the specified container type. 53 Container *instance.ContainerType `json:"container,omitempty" yaml:"container,omitempty"` 54 55 // CpuCores, if not nil, indicates that a machine must have at least that 56 // number of effective cores available. 57 CpuCores *uint64 `json:"cores,omitempty" yaml:"cores,omitempty"` 58 59 // CpuPower, if not nil, indicates that a machine must have at least that 60 // amount of CPU power available, where 100 CpuPower is considered to be 61 // equivalent to 1 Amazon ECU (or, roughly, a single 2007-era Xeon). 62 CpuPower *uint64 `json:"cpu-power,omitempty" yaml:"cpu-power,omitempty"` 63 64 // Mem, if not nil, indicates that a machine must have at least that many 65 // megabytes of RAM. 66 Mem *uint64 `json:"mem,omitempty" yaml:"mem,omitempty"` 67 68 // RootDisk, if not nil, indicates that a machine must have at least 69 // that many megabytes of disk space available in the root disk. In 70 // providers where the root disk is configurable at instance startup 71 // time, an instance with the specified amount of disk space in the OS 72 // disk might be requested. 73 RootDisk *uint64 `json:"root-disk,omitempty" yaml:"root-disk,omitempty"` 74 75 // RootDiskSource, if specified, determines what storage the root 76 // disk should be allocated from. This will be provider specific - 77 // in the case of vSphere it identifies the datastore the root 78 // disk file should be created in. 79 RootDiskSource *string `json:"root-disk-source,omitempty" yaml:"root-disk-source,omitempty"` 80 81 // Tags, if not nil, indicates tags that the machine must have applied to it. 82 // An empty list is treated the same as a nil (unspecified) list, except an 83 // empty list will override any default tags, where a nil list will not. 84 Tags *[]string `json:"tags,omitempty" yaml:"tags,omitempty"` 85 86 // InstanceRole, if not nil, indicates that the specified role/profile for 87 // the given cloud should be used. Only valid for clouds which support 88 // instance roles. Currently only for AWS with instance-profiles 89 InstanceRole *string `json:"instance-role,omitempty" yaml:"instance-role,omitempty"` 90 91 // InstanceType, if not nil, indicates that the specified cloud instance type 92 // be used. Only valid for clouds which support instance types. 93 InstanceType *string `json:"instance-type,omitempty" yaml:"instance-type,omitempty"` 94 95 // Spaces, if not nil, holds a list of juju network spaces that 96 // should be available (or not) on the machine. Positive and 97 // negative values are accepted, and the difference is the latter 98 // have a "^" prefix to the name. 99 Spaces *[]string `json:"spaces,omitempty" yaml:"spaces,omitempty"` 100 101 // VirtType, if not nil or empty, indicates that a machine must run the named 102 // virtual type. Only valid for clouds with multi-hypervisor support. 103 VirtType *string `json:"virt-type,omitempty" yaml:"virt-type,omitempty"` 104 105 // Zones, if not nil, holds a list of availability zones limiting where 106 // the machine can be located. 107 Zones *[]string `json:"zones,omitempty" yaml:"zones,omitempty"` 108 109 // AllocatePublicIP, if nil or true, signals that machines should be 110 // created with a public IP address instead of a cloud local one. 111 // The default behaviour if the value is not specified is to allocate 112 // a public IP so that public cloud behaviour works out of the box. 113 AllocatePublicIP *bool `json:"allocate-public-ip,omitempty" yaml:"allocate-public-ip,omitempty"` 114 115 // ImageID, if not nil, indicates that a machine must use the specified 116 // image. This is provider specific, and for the moment is only 117 // implemented on MAAS clouds. 118 ImageID *string `json:"image-id,omitempty" yaml:"image-id,omitempty"` 119 } 120 121 var rawAliases = map[string]string{ 122 cpuCores: Cores, 123 } 124 125 // resolveAlias returns the canonical representation of the given key, if it'a 126 // an alias listed in aliases, otherwise it returns the original key. 127 func resolveAlias(key string) string { 128 if canonical, ok := rawAliases[key]; ok { 129 return canonical 130 } 131 return key 132 } 133 134 // IsEmpty returns if the given constraints value has no constraints set 135 func IsEmpty(v *Value) bool { 136 return v.String() == "" 137 } 138 139 // HasArch returns true if the constraints.Value specifies an architecture. 140 func (v *Value) HasArch() bool { 141 return v.Arch != nil && *v.Arch != "" 142 } 143 144 // HasMem returns true if the constraints.Value specifies a minimum amount 145 // of memory. 146 func (v *Value) HasMem() bool { 147 return v.Mem != nil && *v.Mem > 0 148 } 149 150 // HasCpuPower returns true if the constraints.Value specifies a minimum amount 151 // of CPU power. 152 func (v *Value) HasCpuPower() bool { 153 return v.CpuPower != nil && *v.CpuPower > 0 154 } 155 156 // HasCpuCores returns true if the constraints.Value specifies a minimum number 157 // of CPU cores. 158 func (v *Value) HasCpuCores() bool { 159 return v.CpuCores != nil && *v.CpuCores > 0 160 } 161 162 // HasRootDisk returns true if the constraints.Value specifies a RootDisk size. 163 func (v *Value) HasRootDisk() bool { 164 return v.RootDisk != nil && *v.RootDisk > 0 165 } 166 167 // HasRootDiskSource returns true if the constraints.Value specifies a 168 // source for its root disk. 169 func (v *Value) HasRootDiskSource() bool { 170 return v.RootDiskSource != nil && *v.RootDiskSource != "" 171 } 172 173 // HasInstanceRole returns true if the constraints.Value specifies an instance 174 // role. 175 func (v *Value) HasInstanceRole() bool { 176 return v.InstanceRole != nil && *v.InstanceRole != "" 177 } 178 179 // HasInstanceType returns true if the constraints.Value specifies an instance type. 180 func (v *Value) HasInstanceType() bool { 181 return v.InstanceType != nil && *v.InstanceType != "" 182 } 183 184 // extractItems returns the list of entries in the given field which 185 // are either positive (included) or negative (!included; with prefix 186 // "^"). 187 func (v *Value) extractItems(field []string, included bool) []string { 188 var items []string 189 for _, name := range field { 190 prefixed := strings.HasPrefix(name, "^") 191 if prefixed && !included { 192 // has prefix and we want negatives. 193 items = append(items, strings.TrimPrefix(name, "^")) 194 } else if !prefixed && included { 195 // no prefix and we want positives. 196 items = append(items, name) 197 } 198 } 199 return items 200 } 201 202 // IncludeSpaces returns a list of space IDs to include when starting a 203 // machine, if specified. 204 func (v *Value) IncludeSpaces() []string { 205 if v.Spaces == nil { 206 return nil 207 } 208 return v.extractItems(*v.Spaces, true) 209 } 210 211 // ExcludeSpaces returns a list of space IDs to exclude when starting a 212 // machine, if specified. They are given in the spaces constraint with 213 // a "^" prefix to the id, which is stripped before returning. 214 func (v *Value) ExcludeSpaces() []string { 215 if v.Spaces == nil { 216 return nil 217 } 218 return v.extractItems(*v.Spaces, false) 219 } 220 221 // HasSpaces returns whether any spaces constraints were specified. 222 func (v *Value) HasSpaces() bool { 223 return v.Spaces != nil && len(*v.Spaces) > 0 224 } 225 226 // HasVirtType returns true if the constraints.Value specifies an virtual type. 227 func (v *Value) HasVirtType() bool { 228 return v.VirtType != nil && *v.VirtType != "" 229 } 230 231 // HasZones returns whether any zone constraints were specified. 232 func (v *Value) HasZones() bool { 233 return v.Zones != nil && len(*v.Zones) > 0 234 } 235 236 // HasAllocatePublicIP returns whether the allocate-public-ip constraint was specified. 237 func (v *Value) HasAllocatePublicIP() bool { 238 return v.AllocatePublicIP != nil 239 } 240 241 // HasImageID returns true if the constraints.Value specifies an image-id. 242 func (v *Value) HasImageID() bool { 243 return v.ImageID != nil && *v.ImageID != "" 244 } 245 246 // String expresses a constraints.Value in the language in which it was specified. 247 func (v Value) String() string { 248 var strs []string 249 if v.Arch != nil { 250 strs = append(strs, "arch="+*v.Arch) 251 } 252 if v.Container != nil { 253 strs = append(strs, "container="+string(*v.Container)) 254 } 255 if v.CpuCores != nil { 256 strs = append(strs, "cores="+uintStr(*v.CpuCores)) 257 } 258 if v.CpuPower != nil { 259 strs = append(strs, "cpu-power="+uintStr(*v.CpuPower)) 260 } 261 if v.InstanceRole != nil { 262 strs = append(strs, "instance-role="+(*v.InstanceRole)) 263 } 264 if v.InstanceType != nil { 265 strs = append(strs, "instance-type="+(*v.InstanceType)) 266 } 267 if v.Mem != nil { 268 s := uintStr(*v.Mem) 269 if s != "" { 270 s += "M" 271 } 272 strs = append(strs, "mem="+s) 273 } 274 if v.RootDisk != nil { 275 s := uintStr(*v.RootDisk) 276 if s != "" { 277 s += "M" 278 } 279 strs = append(strs, "root-disk="+s) 280 } 281 if v.RootDiskSource != nil { 282 s := *v.RootDiskSource 283 strs = append(strs, "root-disk-source="+s) 284 } 285 if v.Tags != nil { 286 s := strings.Join(*v.Tags, ",") 287 strs = append(strs, "tags="+s) 288 } 289 if v.Spaces != nil { 290 s := strings.Join(*v.Spaces, ",") 291 strs = append(strs, "spaces="+s) 292 } 293 if v.VirtType != nil { 294 strs = append(strs, "virt-type="+(*v.VirtType)) 295 } 296 if v.Zones != nil { 297 s := strings.Join(*v.Zones, ",") 298 strs = append(strs, "zones="+s) 299 } 300 if v.AllocatePublicIP != nil { 301 strs = append(strs, "allocate-public-ip="+boolStr(*v.AllocatePublicIP)) 302 } 303 if v.ImageID != nil { 304 strs = append(strs, "image-id="+(*v.ImageID)) 305 } 306 307 // Ensure constraint values with spaces are properly escaped 308 for i := 0; i < len(strs); i++ { 309 strs[i] = strings.Replace(strs[i], " ", `\ `, -1) 310 } 311 312 return strings.Join(strs, " ") 313 } 314 315 // GoString allows printing a constraints.Value nicely with the fmt 316 // package, especially when nested inside other types. 317 func (v Value) GoString() string { 318 var values []string 319 if v.Arch != nil { 320 values = append(values, fmt.Sprintf("Arch: %q", *v.Arch)) 321 } 322 if v.CpuCores != nil { 323 values = append(values, fmt.Sprintf("Cores: %v", *v.CpuCores)) 324 } 325 if v.CpuPower != nil { 326 values = append(values, fmt.Sprintf("CpuPower: %v", *v.CpuPower)) 327 } 328 if v.Mem != nil { 329 values = append(values, fmt.Sprintf("Mem: %v", *v.Mem)) 330 } 331 if v.RootDisk != nil { 332 values = append(values, fmt.Sprintf("RootDisk: %v", *v.RootDisk)) 333 } 334 if v.InstanceRole != nil { 335 values = append(values, fmt.Sprintf("InstanceRole: %q", *v.InstanceRole)) 336 } 337 if v.InstanceType != nil { 338 values = append(values, fmt.Sprintf("InstanceType: %q", *v.InstanceType)) 339 } 340 if v.Container != nil { 341 values = append(values, fmt.Sprintf("Container: %q", *v.Container)) 342 } 343 if v.Tags != nil && *v.Tags != nil { 344 values = append(values, fmt.Sprintf("Tags: %q", *v.Tags)) 345 } else if v.Tags != nil { 346 values = append(values, "Tags: (*[]string)(nil)") 347 } 348 if v.Spaces != nil && *v.Spaces != nil { 349 values = append(values, fmt.Sprintf("Spaces: %q", *v.Spaces)) 350 } else if v.Spaces != nil { 351 values = append(values, "Spaces: (*[]string)(nil)") 352 } 353 if v.VirtType != nil { 354 values = append(values, fmt.Sprintf("VirtType: %q", *v.VirtType)) 355 } 356 if v.Zones != nil && *v.Zones != nil { 357 values = append(values, fmt.Sprintf("Zones: %q", *v.Zones)) 358 } else if v.Zones != nil { 359 values = append(values, "Zones: (*[]string)(nil)") 360 } 361 if v.AllocatePublicIP != nil { 362 values = append(values, fmt.Sprintf("AllocatePublicIP: %v", *v.AllocatePublicIP)) 363 } 364 if v.ImageID != nil { 365 values = append(values, fmt.Sprintf("ImageID: %q", *v.ImageID)) 366 } 367 return fmt.Sprintf("{%s}", strings.Join(values, ", ")) 368 } 369 370 func uintStr(i uint64) string { 371 if i == 0 { 372 return "" 373 } 374 return fmt.Sprintf("%d", i) 375 } 376 377 func boolStr(b bool) string { 378 return fmt.Sprintf("%v", b) 379 } 380 381 // Parse constructs a constraints.Value from the supplied arguments, 382 // each of which must contain only spaces and name=value pairs. If any 383 // name is specified more than once, an error is returned. 384 func Parse(args ...string) (Value, error) { 385 v, _, err := ParseWithAliases(args...) 386 return v, err 387 } 388 389 // ParseWithAliases constructs a constraints.Value from the supplied arguments, each 390 // of which must contain only spaces and name=value pairs. If any name is 391 // specified more than once, an error is returned. The aliases map returned 392 // contains a map of aliases used, and their canonical values. 393 func ParseWithAliases(args ...string) (cons Value, aliases map[string]string, err error) { 394 aliases = make(map[string]string) 395 for _, arg := range args { 396 // Replace slash-escaped spaces with a null byte so we can 397 // safely split on remaining whitespace. 398 arg = strings.Replace(arg, `\ `, "\x00", -1) 399 raws := strings.Split(strings.TrimSpace(arg), " ") 400 for _, raw := range raws { 401 // Replace null bytes back to spaces 402 raw = strings.TrimSpace(strings.Replace(raw, "\x00", " ", -1)) 403 if raw == "" { 404 continue 405 } 406 name, val, err := splitRaw(raw) 407 if err != nil { 408 return Value{}, nil, errors.Trace(err) 409 } 410 if canonical, ok := rawAliases[name]; ok { 411 aliases[name] = canonical 412 name = canonical 413 } 414 if err := cons.setRaw(name, val); err != nil { 415 return Value{}, aliases, errors.Trace(err) 416 } 417 } 418 } 419 return cons, aliases, nil 420 } 421 422 // Merge returns the effective constraints after merging any given 423 // existing values. 424 func Merge(values ...Value) (Value, error) { 425 var args []string 426 for _, value := range values { 427 args = append(args, value.String()) 428 } 429 return Parse(args...) 430 } 431 432 // MustParse constructs a constraints.Value from the supplied arguments, 433 // as Parse, but panics on failure. 434 func MustParse(args ...string) Value { 435 v, err := Parse(args...) 436 if err != nil { 437 panic(err) 438 } 439 return v 440 } 441 442 // Constraints implements gnuflag.Value for a Constraints. 443 type ConstraintsValue struct { 444 Target *Value 445 } 446 447 func (v ConstraintsValue) Set(s string) error { 448 cons, err := Parse(s) 449 if err != nil { 450 return err 451 } 452 *v.Target = cons 453 return nil 454 } 455 456 func (v ConstraintsValue) String() string { 457 return v.Target.String() 458 } 459 460 // attributesWithValues returns the non-zero attribute tags and their values from the constraint. 461 func (v *Value) attributesWithValues() map[string]interface{} { 462 // These can never fail, so we ignore the error for the sake of keeping our 463 // API clean. I'm sorry (but not that sorry). 464 b, _ := json.Marshal(v) 465 result := map[string]interface{}{} 466 _ = json.Unmarshal(b, &result) 467 return result 468 } 469 470 func fromAttributes(attr map[string]interface{}) Value { 471 b, _ := json.Marshal(attr) 472 var result Value 473 _ = json.Unmarshal(b, &result) 474 return result 475 } 476 477 // hasAny returns any attrTags for which the constraint has a non-nil value. 478 func (v *Value) hasAny(attrTags ...string) []string { 479 attributes := v.attributesWithValues() 480 var result []string 481 for _, tag := range attrTags { 482 _, ok := attributes[resolveAlias(tag)] 483 if ok { 484 result = append(result, tag) 485 } 486 } 487 return result 488 } 489 490 // without returns a copy of the constraint without values for 491 // the specified attributes. 492 func (v *Value) without(attrTags ...string) Value { 493 attributes := v.attributesWithValues() 494 for _, tag := range attrTags { 495 delete(attributes, resolveAlias(tag)) 496 } 497 return fromAttributes(attributes) 498 } 499 500 func splitRaw(s string) (name, val string, err error) { 501 eq := strings.Index(s, "=") 502 if eq <= 0 { 503 return "", "", errors.Errorf("malformed constraint %q", s) 504 } 505 return s[:eq], s[eq+1:], nil 506 } 507 508 // setRaw interprets a name=value string and sets the supplied value. 509 func (v *Value) setRaw(name, str string) error { 510 var err error 511 switch resolveAlias(name) { 512 case Arch: 513 err = v.setArch(str) 514 case Container: 515 err = v.setContainer(str) 516 case Cores: 517 err = v.setCpuCores(str) 518 case CpuPower: 519 err = v.setCpuPower(str) 520 case Mem: 521 err = v.setMem(str) 522 case RootDisk: 523 err = v.setRootDisk(str) 524 case RootDiskSource: 525 err = v.setRootDiskSource(str) 526 case Tags: 527 err = v.setTags(str) 528 case InstanceRole: 529 err = v.setInstanceRole(str) 530 case InstanceType: 531 err = v.setInstanceType(str) 532 case Spaces: 533 err = v.setSpaces(str) 534 case VirtType: 535 err = v.setVirtType(str) 536 case Zones: 537 err = v.setZones(str) 538 case AllocatePublicIP: 539 err = v.setAllocatePublicIP(str) 540 case ImageID: 541 err = v.setImageID(str) 542 default: 543 return errors.Errorf("unknown constraint %q", name) 544 } 545 if err != nil { 546 return errors.Annotatef(err, "bad %q constraint", name) 547 } 548 return nil 549 } 550 551 // UnmarshalYAML is required to unmarshal a constraints.Value object 552 // to ensure the container attribute is correctly handled when it is empty. 553 // Because ContainerType is an alias for string, Go's reflect logic used in the 554 // YAML decode determines that *string and *ContainerType are not assignable so 555 // the container value of "" in the YAML is ignored. 556 func (v *Value) UnmarshalYAML(unmarshal func(interface{}) error) error { 557 values := map[interface{}]interface{}{} 558 err := unmarshal(&values) 559 if err != nil { 560 return errors.Trace(err) 561 } 562 canonicals := map[string]string{} 563 for k, val := range values { 564 vstr := fmt.Sprintf("%v", val) 565 key, ok := k.(string) 566 if !ok { 567 return errors.Errorf("unexpected non-string key: %#v", k) 568 } 569 canonical := resolveAlias(key) 570 if v, ok := canonicals[canonical]; ok { 571 // duplicate entry 572 return errors.Errorf("constraint %q duplicates constraint %q", key, v) 573 } 574 canonicals[canonical] = key 575 switch canonical { 576 case Arch: 577 v.Arch = &vstr 578 case Container: 579 ctype := instance.ContainerType(vstr) 580 v.Container = &ctype 581 case InstanceRole: 582 v.InstanceRole = &vstr 583 case InstanceType: 584 v.InstanceType = &vstr 585 case Cores: 586 v.CpuCores, err = parseUint64(vstr) 587 case CpuPower: 588 v.CpuPower, err = parseUint64(vstr) 589 case Mem: 590 v.Mem, err = parseUint64(vstr) 591 case RootDisk: 592 v.RootDisk, err = parseUint64(vstr) 593 case RootDiskSource: 594 v.RootDiskSource = &vstr 595 case Tags: 596 v.Tags, err = parseYamlStrings("tags", val) 597 case Spaces: 598 var spaces *[]string 599 spaces, err = parseYamlStrings("spaces", val) 600 if err != nil { 601 return errors.Trace(err) 602 } 603 err = v.validateSpaces(spaces) 604 if err == nil { 605 v.Spaces = spaces 606 } 607 case VirtType: 608 v.VirtType = &vstr 609 case Zones: 610 v.Zones, err = parseYamlStrings("zones", val) 611 case AllocatePublicIP: 612 v.AllocatePublicIP, err = parseBool(vstr) 613 case ImageID: 614 v.ImageID = &vstr 615 default: 616 return errors.Errorf("unknown constraint value: %v", k) 617 } 618 if err != nil { 619 return errors.Trace(err) 620 } 621 } 622 return nil 623 } 624 625 func (v *Value) setContainer(str string) error { 626 if v.Container != nil { 627 return errors.Errorf("already set") 628 } 629 if str == "" { 630 ctype := instance.ContainerType("") 631 v.Container = &ctype 632 } else { 633 ctype, err := instance.ParseContainerTypeOrNone(str) 634 if err != nil { 635 return err 636 } 637 v.Container = &ctype 638 } 639 return nil 640 } 641 642 // HasContainer returns true if the constraints.Value specifies a container. 643 func (v *Value) HasContainer() bool { 644 return v.Container != nil && *v.Container != "" && *v.Container != instance.NONE 645 } 646 647 func (v *Value) setArch(str string) error { 648 if v.Arch != nil { 649 return errors.Errorf("already set") 650 } 651 if str != "" && !arch.IsSupportedArch(str) { 652 return errors.Errorf("%q not recognized", str) 653 } 654 v.Arch = &str 655 return nil 656 } 657 658 func (v *Value) setCpuCores(str string) (err error) { 659 if v.CpuCores != nil { 660 return errors.Errorf("already set") 661 } 662 v.CpuCores, err = parseUint64(str) 663 return 664 } 665 666 func (v *Value) setCpuPower(str string) (err error) { 667 if v.CpuPower != nil { 668 return errors.Errorf("already set") 669 } 670 v.CpuPower, err = parseUint64(str) 671 return 672 } 673 674 func (v *Value) setInstanceRole(str string) error { 675 if v.InstanceRole != nil { 676 return errors.Errorf("already set") 677 } 678 v.InstanceRole = &str 679 return nil 680 } 681 682 func (v *Value) setInstanceType(str string) error { 683 if v.InstanceType != nil { 684 return errors.Errorf("already set") 685 } 686 v.InstanceType = &str 687 return nil 688 } 689 690 func (v *Value) setMem(str string) (err error) { 691 if v.Mem != nil { 692 return errors.Errorf("already set") 693 } 694 v.Mem, err = parseSize(str) 695 return 696 } 697 698 func (v *Value) setRootDisk(str string) (err error) { 699 if v.RootDisk != nil { 700 return errors.Errorf("already set") 701 } 702 v.RootDisk, err = parseSize(str) 703 return 704 } 705 706 func (v *Value) setRootDiskSource(str string) error { 707 if v.RootDiskSource != nil { 708 return errors.Errorf("already set") 709 } 710 v.RootDiskSource = &str 711 return nil 712 } 713 714 func (v *Value) setTags(str string) error { 715 if v.Tags != nil { 716 return errors.Errorf("already set") 717 } 718 v.Tags = parseCommaDelimited(str) 719 return nil 720 } 721 722 func (v *Value) setSpaces(str string) error { 723 if v.Spaces != nil { 724 return errors.Errorf("already set") 725 } 726 spaces := parseCommaDelimited(str) 727 if err := v.validateSpaces(spaces); err != nil { 728 return err 729 } 730 v.Spaces = spaces 731 return nil 732 } 733 734 func (v *Value) validateSpaces(spaces *[]string) error { 735 if spaces == nil { 736 return nil 737 } 738 for _, name := range *spaces { 739 space := strings.TrimPrefix(name, "^") 740 if !names.IsValidSpace(space) { 741 return errors.Errorf("%q is not a valid space name", space) 742 } 743 } 744 return nil 745 } 746 747 func (v *Value) setVirtType(str string) error { 748 if v.VirtType != nil { 749 return errors.Errorf("already set") 750 } 751 v.VirtType = &str 752 return nil 753 } 754 755 func (v *Value) setZones(str string) error { 756 if v.Zones != nil { 757 return errors.Errorf("already set") 758 } 759 v.Zones = parseCommaDelimited(str) 760 return nil 761 } 762 763 func (v *Value) setAllocatePublicIP(str string) (err error) { 764 if str == "" { 765 return nil 766 } 767 if v.AllocatePublicIP != nil { 768 return errors.Errorf("already set") 769 } 770 v.AllocatePublicIP, err = parseBool(str) 771 return 772 } 773 774 func (v *Value) setImageID(str string) (err error) { 775 if v.ImageID != nil { 776 return errors.Errorf("already set") 777 } 778 v.ImageID = &str 779 return 780 } 781 782 func parseBool(str string) (*bool, error) { 783 var value bool 784 if str != "" { 785 val, err := strconv.ParseBool(str) 786 if err != nil { 787 return nil, errors.Errorf("must be 'true' or 'false'") 788 } 789 value = val 790 } 791 return &value, nil 792 } 793 794 func parseUint64(str string) (*uint64, error) { 795 var value uint64 796 if str != "" { 797 val, err := strconv.ParseUint(str, 10, 64) 798 if err != nil { 799 return nil, errors.Errorf("must be a non-negative integer") 800 } 801 value = val 802 } 803 return &value, nil 804 } 805 806 func parseSize(str string) (*uint64, error) { 807 var value uint64 808 if str != "" { 809 mult := 1.0 810 if m, ok := mbSuffixes[str[len(str)-1:]]; ok { 811 str = str[:len(str)-1] 812 mult = m 813 } 814 val, err := strconv.ParseFloat(str, 64) 815 if err != nil || val < 0 { 816 return nil, errors.Errorf("must be a non-negative float with optional M/G/T/P suffix") 817 } 818 val *= mult 819 value = uint64(math.Ceil(val)) 820 } 821 return &value, nil 822 } 823 824 // parseCommaDelimited returns the items in the value s. We expect the 825 // items to be comma delimited strings. 826 func parseCommaDelimited(s string) *[]string { 827 if s == "" { 828 return &[]string{} 829 } 830 t := strings.Split(s, ",") 831 return &t 832 } 833 834 func parseYamlStrings(entityName string, val interface{}) (*[]string, error) { 835 ifcs, ok := val.([]interface{}) 836 if !ok { 837 return nil, errors.Errorf("unexpected type passed to %s: %T", entityName, val) 838 } 839 items := make([]string, len(ifcs)) 840 for n, ifc := range ifcs { 841 s, ok := ifc.(string) 842 if !ok { 843 return nil, errors.Errorf("unexpected type passed as in %s: %T", entityName, ifc) 844 } 845 items[n] = s 846 } 847 return &items, nil 848 } 849 850 var mbSuffixes = map[string]float64{ 851 "M": 1, 852 "G": 1024, 853 "T": 1024 * 1024, 854 "P": 1024 * 1024 * 1024, 855 }