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  }