github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/provider/vsphere/config.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package vsphere
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/schema"
     9  	"gopkg.in/juju/environschema.v1"
    10  
    11  	"github.com/juju/juju/environs/config"
    12  	"github.com/juju/juju/provider/vsphere/internal/vsphereclient"
    13  )
    14  
    15  // The vmware-specific config keys.
    16  const (
    17  	cfgPrimaryNetwork         = "primary-network"
    18  	cfgExternalNetwork        = "external-network"
    19  	cfgDatastore              = "datastore"
    20  	cfgForceVMHardwareVersion = "force-vm-hardware-version"
    21  	cfgEnableDiskUUID         = "enable-disk-uuid"
    22  	cfgDiskProvisioningType   = "disk-provisioning-type"
    23  )
    24  
    25  // configFields is the spec for each vmware config value's type.
    26  var (
    27  	configSchema = environschema.Fields{
    28  		cfgExternalNetwork: {
    29  			Description: "An external network that VMs will be connected to. The resulting IP address for a VM will be used as its public address.",
    30  			Type:        environschema.Tstring,
    31  		},
    32  		cfgDatastore: {
    33  			Description: "The datastore in which to create VMs. If this is not specified, the process will abort unless there is only one datastore available.",
    34  			Type:        environschema.Tstring,
    35  		},
    36  		cfgPrimaryNetwork: {
    37  			Description: "The primary network that VMs will be connected to. If this is not specified, Juju will look for a network named \"VM Network\".",
    38  			Type:        environschema.Tstring,
    39  		},
    40  		cfgForceVMHardwareVersion: {
    41  			Description: "The HW compatibility version to use when cloning a VM template to create a VM. The version must be supported by the remote compute resource, and greater or equal to the template’s version.",
    42  			Type:        environschema.Tint,
    43  		},
    44  		cfgEnableDiskUUID: {
    45  			Description: "Expose consistent disk UUIDs to the VM, equivalent to disk.EnableUUID. The default is True.",
    46  			Type:        environschema.Tbool,
    47  		},
    48  		cfgDiskProvisioningType: {
    49  			Description: "Specify how the disk should be provisioned when cloning the VM template. Allowed values are: thickEagerZero (default), thick and thin.",
    50  			Type:        environschema.Tstring,
    51  		},
    52  	}
    53  
    54  	configDefaults = schema.Defaults{
    55  		cfgExternalNetwork:        "",
    56  		cfgDatastore:              schema.Omit,
    57  		cfgPrimaryNetwork:         schema.Omit,
    58  		cfgForceVMHardwareVersion: int(0),
    59  		cfgEnableDiskUUID:         true,
    60  		cfgDiskProvisioningType:   string(vsphereclient.DiskTypeThick),
    61  	}
    62  
    63  	configRequiredFields  = []string{}
    64  	configImmutableFields = []string{}
    65  )
    66  
    67  type environConfig struct {
    68  	*config.Config
    69  	attrs map[string]interface{}
    70  }
    71  
    72  // newConfig builds a new environConfig from the provided Config and
    73  // returns it.
    74  func newConfig(cfg *config.Config) *environConfig {
    75  	return &environConfig{
    76  		Config: cfg,
    77  		attrs:  cfg.UnknownAttrs(),
    78  	}
    79  }
    80  
    81  var configFields = func() schema.Fields {
    82  	fs, _, err := configSchema.ValidationSchema()
    83  	if err != nil {
    84  		panic(err)
    85  	}
    86  	return fs
    87  }()
    88  
    89  // newValidConfig builds a new environConfig from the provided Config
    90  // and returns it. The resulting config values are validated.
    91  func newValidConfig(cfg *config.Config) (*environConfig, error) {
    92  	// Ensure that the provided config is valid.
    93  	if err := config.Validate(cfg, nil); err != nil {
    94  		return nil, errors.Trace(err)
    95  	}
    96  
    97  	// Apply the defaults and coerce/validate the custom config attrs.
    98  	validated, err := cfg.ValidateUnknownAttrs(configFields, configDefaults)
    99  	if err != nil {
   100  		return nil, errors.Trace(err)
   101  	}
   102  	validCfg, err := cfg.Apply(validated)
   103  	if err != nil {
   104  		return nil, errors.Trace(err)
   105  	}
   106  
   107  	// Build the config.
   108  	ecfg := newConfig(validCfg)
   109  
   110  	// Do final validation.
   111  	if err := ecfg.validate(); err != nil {
   112  		return nil, errors.Trace(err)
   113  	}
   114  
   115  	return ecfg, nil
   116  }
   117  
   118  func (c *environConfig) externalNetwork() string {
   119  	return c.attrs[cfgExternalNetwork].(string)
   120  }
   121  
   122  func (c *environConfig) datastore() string {
   123  	ds, _ := c.attrs[cfgDatastore].(string)
   124  	return ds
   125  }
   126  
   127  func (c *environConfig) primaryNetwork() string {
   128  	network, _ := c.attrs[cfgPrimaryNetwork].(string)
   129  	return network
   130  }
   131  
   132  func (c *environConfig) enableDiskUUID() bool {
   133  	return c.attrs[cfgEnableDiskUUID].(bool)
   134  }
   135  
   136  func (c *environConfig) forceVMHardwareVersion() int64 {
   137  	versionVal := c.attrs[cfgForceVMHardwareVersion]
   138  	// It seems the value is properly cast to int when bootstrapping
   139  	// but it comes back as a float64 from the database, regardless of
   140  	// the checker used in configFields.
   141  	switch versionVal.(type) {
   142  	case float64:
   143  		v := c.attrs[cfgForceVMHardwareVersion].(float64)
   144  		return int64(v)
   145  	case int:
   146  		v := c.attrs[cfgForceVMHardwareVersion].(int)
   147  		return int64(v)
   148  	default:
   149  		return 0
   150  	}
   151  }
   152  
   153  func (c *environConfig) diskProvisioningType() vsphereclient.DiskProvisioningType {
   154  	provType, ok := c.attrs[cfgDiskProvisioningType]
   155  	if !ok {
   156  		// Return the default in case none is set.
   157  		return vsphereclient.DefaultDiskProvisioningType
   158  	}
   159  
   160  	provTypeStr, ok := provType.(string)
   161  	if !ok || provTypeStr == "" {
   162  		// We got an invalid value set, return default.
   163  		return vsphereclient.DefaultDiskProvisioningType
   164  	}
   165  
   166  	return vsphereclient.DiskProvisioningType(provTypeStr)
   167  }
   168  
   169  // Schema returns the configuration schema for an environment.
   170  func (environProvider) Schema() environschema.Fields {
   171  	fields, err := config.Schema(configSchema)
   172  	if err != nil {
   173  		panic(err)
   174  	}
   175  	return fields
   176  }
   177  
   178  // ConfigSchema returns extra config attributes specific
   179  // to this provider only.
   180  func (p environProvider) ConfigSchema() schema.Fields {
   181  	return configFields
   182  }
   183  
   184  // ConfigDefaults returns the default values for the
   185  // provider specific config attributes.
   186  func (p environProvider) ConfigDefaults() schema.Defaults {
   187  	return configDefaults
   188  }
   189  
   190  // validate checks vmware-specific config values.
   191  func (c environConfig) validate() error {
   192  	// All fields must be populated, even with just the default.
   193  	for _, field := range configRequiredFields {
   194  		if c.attrs[field].(string) == "" {
   195  			return errors.Errorf("%s: must not be empty", field)
   196  		}
   197  	}
   198  
   199  	if diskProvType, ok := c.attrs[cfgDiskProvisioningType]; ok {
   200  		diskProvTypeStr, ok := diskProvType.(string)
   201  		if !ok {
   202  			return errors.Errorf("%s must be a string", cfgDiskProvisioningType)
   203  		}
   204  
   205  		if diskProvTypeStr != "" {
   206  			found := false
   207  			for _, val := range vsphereclient.ValidDiskProvisioningTypes {
   208  				if vsphereclient.DiskProvisioningType(diskProvTypeStr) == val {
   209  					found = true
   210  					break
   211  				}
   212  			}
   213  			if !found {
   214  				return errors.Errorf(
   215  					"%q must be one of %q", cfgDiskProvisioningType, vsphereclient.ValidDiskProvisioningTypes)
   216  			}
   217  		}
   218  	}
   219  	return nil
   220  }
   221  
   222  // update applies changes from the provided config to the env config.
   223  // Changes to any immutable attributes result in an error.
   224  func (c *environConfig) update(cfg *config.Config) error {
   225  	// Validate the updates. newValidConfig does not modify the "known"
   226  	// config attributes so it is safe to call Validate here first.
   227  	if err := config.Validate(cfg, c.Config); err != nil {
   228  		return errors.Trace(err)
   229  	}
   230  
   231  	updates, err := newValidConfig(cfg)
   232  	if err != nil {
   233  		return errors.Trace(err)
   234  	}
   235  
   236  	// Check that no immutable fields have changed.
   237  	attrs := updates.UnknownAttrs()
   238  	for _, field := range configImmutableFields {
   239  		if attrs[field] != c.attrs[field] {
   240  			return errors.Errorf("%s: cannot change from %v to %v", field, c.attrs[field], attrs[field])
   241  		}
   242  	}
   243  
   244  	// Apply the updates.
   245  	c.Config = cfg
   246  	c.attrs = cfg.UnknownAttrs()
   247  	return nil
   248  }