Merge pull request #10287 from hashicorp/implement_9990

Add configuration options to add additional storage to a cloned vm
This commit is contained in:
Megan Marsh 2020-11-20 13:34:56 -08:00 committed by GitHub
commit 60e62bbb51
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 495 additions and 299 deletions

View File

@ -37,6 +37,8 @@ type FlatConfig struct {
MacAddress *string `mapstructure:"mac_address" cty:"mac_address" hcl:"mac_address"`
Notes *string `mapstructure:"notes" cty:"notes" hcl:"notes"`
VAppConfig *FlatvAppConfig `mapstructure:"vapp" cty:"vapp" hcl:"vapp"`
DiskControllerType []string `mapstructure:"disk_controller_type" cty:"disk_controller_type" hcl:"disk_controller_type"`
Storage []common.FlatDiskConfig `mapstructure:"storage" cty:"storage" hcl:"storage"`
VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"`
Folder *string `mapstructure:"folder" cty:"folder" hcl:"folder"`
Cluster *string `mapstructure:"cluster" cty:"cluster" hcl:"cluster"`
@ -174,6 +176,8 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"mac_address": &hcldec.AttrSpec{Name: "mac_address", Type: cty.String, Required: false},
"notes": &hcldec.AttrSpec{Name: "notes", Type: cty.String, Required: false},
"vapp": &hcldec.BlockSpec{TypeName: "vapp", Nested: hcldec.ObjectSpec((*FlatvAppConfig)(nil).HCL2Spec())},
"disk_controller_type": &hcldec.AttrSpec{Name: "disk_controller_type", Type: cty.List(cty.String), Required: false},
"storage": &hcldec.BlockListSpec{TypeName: "storage", Nested: hcldec.ObjectSpec((*common.FlatDiskConfig)(nil).HCL2Spec())},
"vm_name": &hcldec.AttrSpec{Name: "vm_name", Type: cty.String, Required: false},
"folder": &hcldec.AttrSpec{Name: "folder", Type: cty.String, Required: false},
"cluster": &hcldec.AttrSpec{Name: "cluster", Type: cty.String, Required: false},

View File

@ -44,11 +44,13 @@ type CloneConfig struct {
// Set the vApp Options to a virtual machine.
// See the [vApp Options Configuration](/docs/builders/vmware/vsphere-clone#vapp-options-configuration)
// to know the available options and how to use it.
VAppConfig vAppConfig `mapstructure:"vapp"`
VAppConfig vAppConfig `mapstructure:"vapp"`
StorageConfig common.StorageConfig `mapstructure:",squash"`
}
func (c *CloneConfig) Prepare() []error {
var errs []error
errs = append(errs, c.StorageConfig.Prepare()...)
if c.Template == "" {
errs = append(errs, fmt.Errorf("'template' is required"))
@ -89,6 +91,16 @@ func (s *StepCloneVM) Run(ctx context.Context, state multistep.StateBag) multist
return multistep.ActionHalt
}
var disks []driver.Disk
for _, disk := range s.Config.StorageConfig.Storage {
disks = append(disks, driver.Disk{
DiskSize: disk.DiskSize,
DiskEagerlyScrub: disk.DiskEagerlyScrub,
DiskThinProvisioned: disk.DiskThinProvisioned,
ControllerIndex: disk.DiskControllerIndex,
})
}
vm, err := template.Clone(ctx, &driver.CloneConfig{
Name: s.Location.VMName,
Folder: s.Location.Folder,
@ -101,6 +113,10 @@ func (s *StepCloneVM) Run(ctx context.Context, state multistep.StateBag) multist
MacAddress: s.Config.MacAddress,
Annotation: s.Config.Notes,
VAppProperties: s.Config.VAppConfig.Properties,
StorageConfig: driver.StorageConfig{
DiskControllerType: s.Config.StorageConfig.DiskControllerType,
Storage: disks,
},
})
if err != nil {
state.Put("error", err)

View File

@ -3,19 +3,22 @@ package clone
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer/builder/vsphere/common"
"github.com/zclconf/go-cty/cty"
)
// FlatCloneConfig is an auto-generated flat version of CloneConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatCloneConfig struct {
Template *string `mapstructure:"template" cty:"template" hcl:"template"`
DiskSize *int64 `mapstructure:"disk_size" cty:"disk_size" hcl:"disk_size"`
LinkedClone *bool `mapstructure:"linked_clone" cty:"linked_clone" hcl:"linked_clone"`
Network *string `mapstructure:"network" cty:"network" hcl:"network"`
MacAddress *string `mapstructure:"mac_address" cty:"mac_address" hcl:"mac_address"`
Notes *string `mapstructure:"notes" cty:"notes" hcl:"notes"`
VAppConfig *FlatvAppConfig `mapstructure:"vapp" cty:"vapp" hcl:"vapp"`
Template *string `mapstructure:"template" cty:"template" hcl:"template"`
DiskSize *int64 `mapstructure:"disk_size" cty:"disk_size" hcl:"disk_size"`
LinkedClone *bool `mapstructure:"linked_clone" cty:"linked_clone" hcl:"linked_clone"`
Network *string `mapstructure:"network" cty:"network" hcl:"network"`
MacAddress *string `mapstructure:"mac_address" cty:"mac_address" hcl:"mac_address"`
Notes *string `mapstructure:"notes" cty:"notes" hcl:"notes"`
VAppConfig *FlatvAppConfig `mapstructure:"vapp" cty:"vapp" hcl:"vapp"`
DiskControllerType []string `mapstructure:"disk_controller_type" cty:"disk_controller_type" hcl:"disk_controller_type"`
Storage []common.FlatDiskConfig `mapstructure:"storage" cty:"storage" hcl:"storage"`
}
// FlatMapstructure returns a new FlatCloneConfig.
@ -30,13 +33,15 @@ func (*CloneConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.S
// The decoded values from this spec will then be applied to a FlatCloneConfig.
func (*FlatCloneConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"template": &hcldec.AttrSpec{Name: "template", Type: cty.String, Required: false},
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.Number, Required: false},
"linked_clone": &hcldec.AttrSpec{Name: "linked_clone", Type: cty.Bool, Required: false},
"network": &hcldec.AttrSpec{Name: "network", Type: cty.String, Required: false},
"mac_address": &hcldec.AttrSpec{Name: "mac_address", Type: cty.String, Required: false},
"notes": &hcldec.AttrSpec{Name: "notes", Type: cty.String, Required: false},
"vapp": &hcldec.BlockSpec{TypeName: "vapp", Nested: hcldec.ObjectSpec((*FlatvAppConfig)(nil).HCL2Spec())},
"template": &hcldec.AttrSpec{Name: "template", Type: cty.String, Required: false},
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.Number, Required: false},
"linked_clone": &hcldec.AttrSpec{Name: "linked_clone", Type: cty.Bool, Required: false},
"network": &hcldec.AttrSpec{Name: "network", Type: cty.String, Required: false},
"mac_address": &hcldec.AttrSpec{Name: "mac_address", Type: cty.String, Required: false},
"notes": &hcldec.AttrSpec{Name: "notes", Type: cty.String, Required: false},
"vapp": &hcldec.BlockSpec{TypeName: "vapp", Nested: hcldec.ObjectSpec((*FlatvAppConfig)(nil).HCL2Spec())},
"disk_controller_type": &hcldec.AttrSpec{Name: "disk_controller_type", Type: cty.List(cty.String), Required: false},
"storage": &hcldec.BlockListSpec{TypeName: "storage", Nested: hcldec.ObjectSpec((*common.FlatDiskConfig)(nil).HCL2Spec())},
}
return s
}

View File

@ -0,0 +1,118 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type StorageConfig,DiskConfig
package common
import (
"fmt"
)
// Defines the disk storage for a VM.
//
// Example that will create a 15GB and a 20GB disk on the VM. The second disk will be thin provisioned:
//
// In JSON:
// ```json
// "storage": [
// {
// "disk_size": 15000
// },
// {
// "disk_size": 20000,
// "disk_thin_provisioned": true
// }
// ],
// ```
// In HCL2:
// ```hcl
// storage {
// disk_size = 15000
// }
// storage {
// disk_size = 20000
// disk_thin_provisioned = true
// }
// ```
//
// Example that creates 2 pvscsi controllers and adds 2 disks to each one:
//
// In JSON:
// ```json
// "disk_controller_type": ["pvscsi", "pvscsi"],
// "storage": [
// {
// "disk_size": 15000,
// "disk_controller_index": 0
// },
// {
// "disk_size": 15000,
// "disk_controller_index": 0
// },
// {
// "disk_size": 15000,
// "disk_controller_index": 1
// },
// {
// "disk_size": 15000,
// "disk_controller_index": 1
// }
// ],
// ```
//
// In HCL2:
// ```hcl
// disk_controller_type = ["pvscsi", "pvscsi"]
// storage {
// disk_size = 15000,
// disk_controller_index = 0
// }
// storage {
// disk_size = 15000
// disk_controller_index = 0
// }
// storage {
// disk_size = 15000
// disk_controller_index = 1
// }
// storage {
// disk_size = 15000
// disk_controller_index = 1
// }
// ```
type DiskConfig struct {
// The size of the disk in MB.
DiskSize int64 `mapstructure:"disk_size" required:"true"`
// Enable VMDK thin provisioning for VM. Defaults to `false`.
DiskThinProvisioned bool `mapstructure:"disk_thin_provisioned"`
// Enable VMDK eager scrubbing for VM. Defaults to `false`.
DiskEagerlyScrub bool `mapstructure:"disk_eagerly_scrub"`
// The assigned disk controller. Defaults to the first one (0)
DiskControllerIndex int `mapstructure:"disk_controller_index"`
}
type StorageConfig struct {
// Set VM disk controller type. Example `lsilogic`, `pvscsi`, `nvme`, or `scsi`. Use a list to define additional controllers.
// Defaults to `lsilogic`. See
// [SCSI, SATA, and NVMe Storage Controller Conditions, Limitations, and Compatibility](https://docs.vmware.com/en/VMware-vSphere/7.0/com.vmware.vsphere.vm_admin.doc/GUID-5872D173-A076-42FE-8D0B-9DB0EB0E7362.html#GUID-5872D173-A076-42FE-8D0B-9DB0EB0E7362)
// for additional details.
DiskControllerType []string `mapstructure:"disk_controller_type"`
// Configures a collection of one or more disks to be provisioned along with the VM. See the [Storage Configuration](#storage-configuration).
Storage []DiskConfig `mapstructure:"storage"`
}
func (c *StorageConfig) Prepare() []error {
var errs []error
if len(c.Storage) > 0 {
for i, storage := range c.Storage {
if storage.DiskSize == 0 {
errs = append(errs, fmt.Errorf("storage[%d].'disk_size' is required", i))
}
if storage.DiskControllerIndex >= len(c.DiskControllerType) {
errs = append(errs, fmt.Errorf("storage[%d].'disk_controller_index' references an unknown disk controller", i))
}
}
}
return errs
}

View File

@ -0,0 +1,61 @@
// Code generated by "mapstructure-to-hcl2 -type StorageConfig,DiskConfig"; DO NOT EDIT.
package common
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatDiskConfig is an auto-generated flat version of DiskConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatDiskConfig struct {
DiskSize *int64 `mapstructure:"disk_size" required:"true" cty:"disk_size" hcl:"disk_size"`
DiskThinProvisioned *bool `mapstructure:"disk_thin_provisioned" cty:"disk_thin_provisioned" hcl:"disk_thin_provisioned"`
DiskEagerlyScrub *bool `mapstructure:"disk_eagerly_scrub" cty:"disk_eagerly_scrub" hcl:"disk_eagerly_scrub"`
DiskControllerIndex *int `mapstructure:"disk_controller_index" cty:"disk_controller_index" hcl:"disk_controller_index"`
}
// FlatMapstructure returns a new FlatDiskConfig.
// FlatDiskConfig is an auto-generated flat version of DiskConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*DiskConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatDiskConfig)
}
// HCL2Spec returns the hcl spec of a DiskConfig.
// This spec is used by HCL to read the fields of DiskConfig.
// The decoded values from this spec will then be applied to a FlatDiskConfig.
func (*FlatDiskConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.Number, Required: false},
"disk_thin_provisioned": &hcldec.AttrSpec{Name: "disk_thin_provisioned", Type: cty.Bool, Required: false},
"disk_eagerly_scrub": &hcldec.AttrSpec{Name: "disk_eagerly_scrub", Type: cty.Bool, Required: false},
"disk_controller_index": &hcldec.AttrSpec{Name: "disk_controller_index", Type: cty.Number, Required: false},
}
return s
}
// FlatStorageConfig is an auto-generated flat version of StorageConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatStorageConfig struct {
DiskControllerType []string `mapstructure:"disk_controller_type" cty:"disk_controller_type" hcl:"disk_controller_type"`
Storage []FlatDiskConfig `mapstructure:"storage" cty:"storage" hcl:"storage"`
}
// FlatMapstructure returns a new FlatStorageConfig.
// FlatStorageConfig is an auto-generated flat version of StorageConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*StorageConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatStorageConfig)
}
// HCL2Spec returns the hcl spec of a StorageConfig.
// This spec is used by HCL to read the fields of StorageConfig.
// The decoded values from this spec will then be applied to a FlatStorageConfig.
func (*FlatStorageConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"disk_controller_type": &hcldec.AttrSpec{Name: "disk_controller_type", Type: cty.List(cty.String), Required: false},
"storage": &hcldec.BlockListSpec{TypeName: "storage", Nested: hcldec.ObjectSpec((*FlatDiskConfig)(nil).HCL2Spec())},
}
return s
}

View File

@ -0,0 +1,84 @@
package driver
import (
"errors"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/types"
)
type Disk struct {
DiskSize int64
DiskEagerlyScrub bool
DiskThinProvisioned bool
ControllerIndex int
}
type StorageConfig struct {
DiskControllerType []string // example: "scsi", "pvscsi", "nvme", "lsilogic"
Storage []Disk
}
func (c *StorageConfig) AddStorageDevices(existingDevices object.VirtualDeviceList) ([]types.BaseVirtualDeviceConfigSpec, error) {
newDevices := object.VirtualDeviceList{}
// Create new controller based on existing devices list and add it to the new devices list
// to confirm creation
var controllers []types.BaseVirtualController
for _, controllerType := range c.DiskControllerType {
var device types.BaseVirtualDevice
var err error
if controllerType == "nvme" {
device, err = existingDevices.CreateNVMEController()
} else {
device, err = existingDevices.CreateSCSIController(controllerType)
}
if err != nil {
return nil, err
}
existingDevices = append(existingDevices, device)
newDevices = append(newDevices, device)
controller, err := existingDevices.FindDiskController(existingDevices.Name(device))
if err != nil {
return nil, err
}
controllers = append(controllers, controller)
}
for _, dc := range c.Storage {
disk := &types.VirtualDisk{
VirtualDevice: types.VirtualDevice{
Key: existingDevices.NewKey(),
Backing: &types.VirtualDiskFlatVer2BackingInfo{
DiskMode: string(types.VirtualDiskModePersistent),
ThinProvisioned: types.NewBool(dc.DiskThinProvisioned),
EagerlyScrub: types.NewBool(dc.DiskEagerlyScrub),
},
},
CapacityInKB: dc.DiskSize * 1024,
}
existingDevices.AssignController(disk, controllers[dc.ControllerIndex])
newDevices = append(newDevices, disk)
}
return newDevices.ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd)
}
func findDisk(devices object.VirtualDeviceList) (*types.VirtualDisk, error) {
var disks []*types.VirtualDisk
for _, device := range devices {
switch d := device.(type) {
case *types.VirtualDisk:
disks = append(disks, d)
}
}
switch len(disks) {
case 0:
return nil, errors.New("VM has no disks")
case 1:
return disks[0], nil
}
return nil, errors.New("VM has multiple disks")
}

View File

@ -79,6 +79,7 @@ type CloneConfig struct {
MacAddress string
Annotation string
VAppProperties map[string]string
StorageConfig StorageConfig
}
type HardwareConfig struct {
@ -106,8 +107,6 @@ type NIC struct {
}
type CreateConfig struct {
DiskControllerType []string // example: "scsi", "pvscsi", "nvme", "lsilogic"
Annotation string
Name string
Folder string
@ -119,14 +118,7 @@ type CreateConfig struct {
NICs []NIC
USBController []string
Version uint // example: 10
Storage []Disk
}
type Disk struct {
DiskSize int64
DiskEagerlyScrub bool
DiskThinProvisioned bool
ControllerIndex int
StorageConfig StorageConfig
}
func (d *VCenterDriver) NewVM(ref *types.ManagedObjectReference) VirtualMachine {
@ -207,11 +199,12 @@ func (d *VCenterDriver) CreateVM(config *CreateConfig) (VirtualMachine, error) {
}
devices := object.VirtualDeviceList{}
devices, err = addDisk(d, devices, config)
storageConfigSpec, err := config.StorageConfig.AddStorageDevices(devices)
if err != nil {
return nil, err
}
createSpec.DeviceChange = append(createSpec.DeviceChange, storageConfigSpec...)
devices, err = addNetwork(d, devices, config)
if err != nil {
return nil, err
@ -235,10 +228,11 @@ func (d *VCenterDriver) CreateVM(config *CreateConfig) (VirtualMachine, error) {
devices = append(devices, usb)
}
createSpec.DeviceChange, err = devices.ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd)
devicesConfigSpec, err := devices.ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd)
if err != nil {
return nil, err
}
createSpec.DeviceChange = append(createSpec.DeviceChange, devicesConfigSpec...)
createSpec.Files = &types.VirtualMachineFileInfo{
VmPathName: fmt.Sprintf("[%s]", datastore.Name()),
@ -341,6 +335,24 @@ func (vm *VirtualMachineDriver) Clone(ctx context.Context, config *CloneConfig)
configSpec.Annotation = config.Annotation
}
devices, err := vm.vm.Device(vm.driver.ctx)
if err != nil {
return nil, err
}
virtualDisks := devices.SelectByType((*types.VirtualDisk)(nil))
virtualControllers := devices.SelectByType((*types.VirtualController)(nil))
// Use existing devices to avoid overlapping configuration
existingDevices := object.VirtualDeviceList{}
existingDevices = append(existingDevices, virtualDisks...)
existingDevices = append(existingDevices, virtualControllers...)
storageConfigSpec, err := config.StorageConfig.AddStorageDevices(existingDevices)
if err != nil {
return nil, err
}
configSpec.DeviceChange = append(configSpec.DeviceChange, storageConfigSpec...)
if config.Network != "" {
net, err := vm.driver.FindNetwork(config.Network)
if err != nil {
@ -616,24 +628,6 @@ func (vm *VirtualMachineDriver) ResizeDisk(diskSize int64) error {
return err
}
func findDisk(devices object.VirtualDeviceList) (*types.VirtualDisk, error) {
var disks []*types.VirtualDisk
for _, device := range devices {
switch d := device.(type) {
case *types.VirtualDisk:
disks = append(disks, d)
}
}
switch len(disks) {
case 0:
return nil, errors.New("VM has no disks")
case 1:
return disks[0], nil
}
return nil, errors.New("VM has multiple disks")
}
func (vm *VirtualMachineDriver) PowerOn() error {
task, err := vm.vm.PowerOn(vm.driver.ctx)
if err != nil {
@ -849,55 +843,6 @@ func (vm *VirtualMachineDriver) GetDir() (string, error) {
return "", fmt.Errorf("cannot find '%s'", vmxName)
}
func addDisk(_ *VCenterDriver, devices object.VirtualDeviceList, config *CreateConfig) (object.VirtualDeviceList, error) {
if len(config.Storage) == 0 {
return nil, errors.New("no storage devices have been defined")
}
if len(config.DiskControllerType) == 0 {
return nil, errors.New("no controllers have been defined")
}
var controllers []types.BaseVirtualController
for _, controllerType := range config.DiskControllerType {
var device types.BaseVirtualDevice
var err error
if controllerType == "nvme" {
device, err = devices.CreateNVMEController()
} else {
device, err = devices.CreateSCSIController(controllerType)
}
if err != nil {
return nil, err
}
devices = append(devices, device)
controller, err := devices.FindDiskController(devices.Name(device))
if err != nil {
return nil, err
}
controllers = append(controllers, controller)
}
for _, dc := range config.Storage {
disk := &types.VirtualDisk{
VirtualDevice: types.VirtualDevice{
Key: devices.NewKey(),
Backing: &types.VirtualDiskFlatVer2BackingInfo{
DiskMode: string(types.VirtualDiskModePersistent),
ThinProvisioned: types.NewBool(dc.DiskThinProvisioned),
EagerlyScrub: types.NewBool(dc.DiskEagerlyScrub),
},
},
CapacityInKB: dc.DiskSize * 1024,
}
devices.AssignController(disk, controllers[dc.ControllerIndex])
devices = append(devices, disk)
}
return devices, nil
}
func addNetwork(d *VCenterDriver, devices object.VirtualDeviceList, config *CreateConfig) (object.VirtualDeviceList, error) {
if len(config.NICs) == 0 {
return nil, errors.New("no network adapters have been defined")

View File

@ -33,7 +33,7 @@ type FlatConfig struct {
Version *uint `mapstructure:"vm_version" cty:"vm_version" hcl:"vm_version"`
GuestOSType *string `mapstructure:"guest_os_type" cty:"guest_os_type" hcl:"guest_os_type"`
DiskControllerType []string `mapstructure:"disk_controller_type" cty:"disk_controller_type" hcl:"disk_controller_type"`
Storage []FlatDiskConfig `mapstructure:"storage" cty:"storage" hcl:"storage"`
Storage []common.FlatDiskConfig `mapstructure:"storage" cty:"storage" hcl:"storage"`
NICs []FlatNIC `mapstructure:"network_adapters" cty:"network_adapters" hcl:"network_adapters"`
USBController []string `mapstructure:"usb_controller" cty:"usb_controller" hcl:"usb_controller"`
Notes *string `mapstructure:"notes" cty:"notes" hcl:"notes"`
@ -174,7 +174,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"vm_version": &hcldec.AttrSpec{Name: "vm_version", Type: cty.Number, Required: false},
"guest_os_type": &hcldec.AttrSpec{Name: "guest_os_type", Type: cty.String, Required: false},
"disk_controller_type": &hcldec.AttrSpec{Name: "disk_controller_type", Type: cty.List(cty.String), Required: false},
"storage": &hcldec.BlockListSpec{TypeName: "storage", Nested: hcldec.ObjectSpec((*FlatDiskConfig)(nil).HCL2Spec())},
"storage": &hcldec.BlockListSpec{TypeName: "storage", Nested: hcldec.ObjectSpec((*common.FlatDiskConfig)(nil).HCL2Spec())},
"network_adapters": &hcldec.BlockListSpec{TypeName: "network_adapters", Nested: hcldec.ObjectSpec((*FlatNIC)(nil).HCL2Spec())},
"usb_controller": &hcldec.AttrSpec{Name: "usb_controller", Type: cty.List(cty.String), Required: false},
"notes": &hcldec.AttrSpec{Name: "notes", Type: cty.String, Required: false},

View File

@ -1,5 +1,5 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type NIC,CreateConfig,DiskConfig
//go:generate mapstructure-to-hcl2 -type NIC,CreateConfig
package iso
@ -56,89 +56,6 @@ type NIC struct {
Passthrough *bool `mapstructure:"passthrough"`
}
// Defines the disk storage for a VM.
//
// Example that will create a 15GB and a 20GB disk on the VM. The second disk will be thin provisioned:
//
// In JSON:
// ```json
// "storage": [
// {
// "disk_size": 15000
// },
// {
// "disk_size": 20000,
// "disk_thin_provisioned": true
// }
// ],
// ```
// In HCL2:
// ```hcl
// storage {
// disk_size = 15000
// }
// storage {
// disk_size = 20000
// disk_thin_provisioned = true
// }
// ```
//
// Example that creates 2 pvscsi controllers and adds 2 disks to each one:
//
// In JSON:
// ```json
// "disk_controller_type": ["pvscsi", "pvscsi"],
// "storage": [
// {
// "disk_size": 15000,
// "disk_controller_index": 0
// },
// {
// "disk_size": 15000,
// "disk_controller_index": 0
// },
// {
// "disk_size": 15000,
// "disk_controller_index": 1
// },
// {
// "disk_size": 15000,
// "disk_controller_index": 1
// }
// ],
// ```
//
// In HCL2:
// ```hcl
// disk_controller_type = ["pvscsi", "pvscsi"]
// storage {
// disk_size = 15000,
// disk_controller_index = 0
// }
// storage {
// disk_size = 15000
// disk_controller_index = 0
// }
// storage {
// disk_size = 15000
// disk_controller_index = 1
// }
// storage {
// disk_size = 15000
// disk_controller_index = 1
// }
// ```
type DiskConfig struct {
// The size of the disk in MB.
DiskSize int64 `mapstructure:"disk_size" required:"true"`
// Enable VMDK thin provisioning for VM. Defaults to `false`.
DiskThinProvisioned bool `mapstructure:"disk_thin_provisioned"`
// Enable VMDK eager scrubbing for VM. Defaults to `false`.
DiskEagerlyScrub bool `mapstructure:"disk_eagerly_scrub"`
// The assigned disk controller. Defaults to the first one (0)
DiskControllerIndex int `mapstructure:"disk_controller_index"`
}
type CreateConfig struct {
// Set VM hardware version. Defaults to the most current VM hardware
// version supported by vCenter. See
@ -148,14 +65,8 @@ type CreateConfig struct {
// Set VM OS type. Defaults to `otherGuest`. See [
// here](https://code.vmware.com/apis/358/vsphere/doc/vim.vm.GuestOsDescriptor.GuestOsIdentifier.html)
// for a full list of possible values.
GuestOSType string `mapstructure:"guest_os_type"`
// Set VM disk controller type. Example `lsilogic`, `pvscsi`, `nvme`, or `scsi`. Use a list to define additional controllers.
// Defaults to `lsilogic`. See
// [SCSI, SATA, and NVMe Storage Controller Conditions, Limitations, and Compatibility](https://docs.vmware.com/en/VMware-vSphere/7.0/com.vmware.vsphere.vm_admin.doc/GUID-5872D173-A076-42FE-8D0B-9DB0EB0E7362.html#GUID-5872D173-A076-42FE-8D0B-9DB0EB0E7362)
// for additional details.
DiskControllerType []string `mapstructure:"disk_controller_type"`
// A collection of one or more disks to be provisioned along with the VM.
Storage []DiskConfig `mapstructure:"storage"`
GuestOSType string `mapstructure:"guest_os_type"`
StorageConfig common.StorageConfig `mapstructure:",squash"`
// Network adapters
NICs []NIC `mapstructure:"network_adapters"`
// Create USB controllers for the virtual machine. "usb" for a usb 2.0 controller. "xhci" for a usb 3.0 controller. There can only be at most one of each.
@ -167,21 +78,15 @@ type CreateConfig struct {
func (c *CreateConfig) Prepare() []error {
var errs []error
// there should be at least one
if len(c.DiskControllerType) == 0 {
c.DiskControllerType = append(c.DiskControllerType, "")
if len(c.StorageConfig.DiskControllerType) == 0 {
c.StorageConfig.DiskControllerType = append(c.StorageConfig.DiskControllerType, "")
}
if len(c.Storage) > 0 {
for i, storage := range c.Storage {
if storage.DiskSize == 0 {
errs = append(errs, fmt.Errorf("storage[%d].'disk_size' is required", i))
}
if storage.DiskControllerIndex >= len(c.DiskControllerType) {
errs = append(errs, fmt.Errorf("storage[%d].'disk_controller_index' references an unknown disk controller", i))
}
}
// there should be at least one
if len(c.StorageConfig.Storage) == 0 {
errs = append(errs, fmt.Errorf("no storage devices have been defined"))
}
errs = append(errs, c.StorageConfig.Prepare()...)
if c.GuestOSType == "" {
c.GuestOSType = "otherGuest"
@ -243,7 +148,7 @@ func (s *StepCreateVM) Run(_ context.Context, state multistep.StateBag) multiste
// add disk as the first drive for backwards compatibility if the type is defined
var disks []driver.Disk
for _, disk := range s.Config.Storage {
for _, disk := range s.Config.StorageConfig.Storage {
disks = append(disks, driver.Disk{
DiskSize: disk.DiskSize,
DiskEagerlyScrub: disk.DiskEagerlyScrub,
@ -253,19 +158,21 @@ func (s *StepCreateVM) Run(_ context.Context, state multistep.StateBag) multiste
}
vm, err := d.CreateVM(&driver.CreateConfig{
DiskControllerType: s.Config.DiskControllerType,
Storage: disks,
Annotation: s.Config.Notes,
Name: s.Location.VMName,
Folder: s.Location.Folder,
Cluster: s.Location.Cluster,
Host: s.Location.Host,
ResourcePool: s.Location.ResourcePool,
Datastore: s.Location.Datastore,
GuestOS: s.Config.GuestOSType,
NICs: networkCards,
USBController: s.Config.USBController,
Version: s.Config.Version,
StorageConfig: driver.StorageConfig{
DiskControllerType: s.Config.StorageConfig.DiskControllerType,
Storage: disks,
},
Annotation: s.Config.Notes,
Name: s.Location.VMName,
Folder: s.Location.Folder,
Cluster: s.Location.Cluster,
Host: s.Location.Host,
ResourcePool: s.Location.ResourcePool,
Datastore: s.Location.Datastore,
GuestOS: s.Config.GuestOSType,
NICs: networkCards,
USBController: s.Config.USBController,
Version: s.Config.Version,
})
if err != nil {
state.Put("error", fmt.Errorf("error creating vm: %v", err))

View File

@ -1,21 +1,22 @@
// Code generated by "mapstructure-to-hcl2 -type NIC,CreateConfig,DiskConfig"; DO NOT EDIT.
// Code generated by "mapstructure-to-hcl2 -type NIC,CreateConfig"; DO NOT EDIT.
package iso
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer/builder/vsphere/common"
"github.com/zclconf/go-cty/cty"
)
// FlatCreateConfig is an auto-generated flat version of CreateConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatCreateConfig struct {
Version *uint `mapstructure:"vm_version" cty:"vm_version" hcl:"vm_version"`
GuestOSType *string `mapstructure:"guest_os_type" cty:"guest_os_type" hcl:"guest_os_type"`
DiskControllerType []string `mapstructure:"disk_controller_type" cty:"disk_controller_type" hcl:"disk_controller_type"`
Storage []FlatDiskConfig `mapstructure:"storage" cty:"storage" hcl:"storage"`
NICs []FlatNIC `mapstructure:"network_adapters" cty:"network_adapters" hcl:"network_adapters"`
USBController []string `mapstructure:"usb_controller" cty:"usb_controller" hcl:"usb_controller"`
Notes *string `mapstructure:"notes" cty:"notes" hcl:"notes"`
Version *uint `mapstructure:"vm_version" cty:"vm_version" hcl:"vm_version"`
GuestOSType *string `mapstructure:"guest_os_type" cty:"guest_os_type" hcl:"guest_os_type"`
DiskControllerType []string `mapstructure:"disk_controller_type" cty:"disk_controller_type" hcl:"disk_controller_type"`
Storage []common.FlatDiskConfig `mapstructure:"storage" cty:"storage" hcl:"storage"`
NICs []FlatNIC `mapstructure:"network_adapters" cty:"network_adapters" hcl:"network_adapters"`
USBController []string `mapstructure:"usb_controller" cty:"usb_controller" hcl:"usb_controller"`
Notes *string `mapstructure:"notes" cty:"notes" hcl:"notes"`
}
// FlatMapstructure returns a new FlatCreateConfig.
@ -33,7 +34,7 @@ func (*FlatCreateConfig) HCL2Spec() map[string]hcldec.Spec {
"vm_version": &hcldec.AttrSpec{Name: "vm_version", Type: cty.Number, Required: false},
"guest_os_type": &hcldec.AttrSpec{Name: "guest_os_type", Type: cty.String, Required: false},
"disk_controller_type": &hcldec.AttrSpec{Name: "disk_controller_type", Type: cty.List(cty.String), Required: false},
"storage": &hcldec.BlockListSpec{TypeName: "storage", Nested: hcldec.ObjectSpec((*FlatDiskConfig)(nil).HCL2Spec())},
"storage": &hcldec.BlockListSpec{TypeName: "storage", Nested: hcldec.ObjectSpec((*common.FlatDiskConfig)(nil).HCL2Spec())},
"network_adapters": &hcldec.BlockListSpec{TypeName: "network_adapters", Nested: hcldec.ObjectSpec((*FlatNIC)(nil).HCL2Spec())},
"usb_controller": &hcldec.AttrSpec{Name: "usb_controller", Type: cty.List(cty.String), Required: false},
"notes": &hcldec.AttrSpec{Name: "notes", Type: cty.String, Required: false},
@ -41,35 +42,6 @@ func (*FlatCreateConfig) HCL2Spec() map[string]hcldec.Spec {
return s
}
// FlatDiskConfig is an auto-generated flat version of DiskConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatDiskConfig struct {
DiskSize *int64 `mapstructure:"disk_size" required:"true" cty:"disk_size" hcl:"disk_size"`
DiskThinProvisioned *bool `mapstructure:"disk_thin_provisioned" cty:"disk_thin_provisioned" hcl:"disk_thin_provisioned"`
DiskEagerlyScrub *bool `mapstructure:"disk_eagerly_scrub" cty:"disk_eagerly_scrub" hcl:"disk_eagerly_scrub"`
DiskControllerIndex *int `mapstructure:"disk_controller_index" cty:"disk_controller_index" hcl:"disk_controller_index"`
}
// FlatMapstructure returns a new FlatDiskConfig.
// FlatDiskConfig is an auto-generated flat version of DiskConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*DiskConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatDiskConfig)
}
// HCL2Spec returns the hcl spec of a DiskConfig.
// This spec is used by HCL to read the fields of DiskConfig.
// The decoded values from this spec will then be applied to a FlatDiskConfig.
func (*FlatDiskConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.Number, Required: false},
"disk_thin_provisioned": &hcldec.AttrSpec{Name: "disk_thin_provisioned", Type: cty.Bool, Required: false},
"disk_eagerly_scrub": &hcldec.AttrSpec{Name: "disk_eagerly_scrub", Type: cty.Bool, Required: false},
"disk_controller_index": &hcldec.AttrSpec{Name: "disk_controller_index", Type: cty.Number, Required: false},
}
return s
}
// FlatNIC is an auto-generated flat version of NIC.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatNIC struct {

View File

@ -17,14 +17,23 @@ import (
func TestCreateConfig_Prepare(t *testing.T) {
// Empty config - check defaults
config := &CreateConfig{}
config := &CreateConfig{
// Storage is required
StorageConfig: common.StorageConfig{
Storage: []common.DiskConfig{
{
DiskSize: 32768,
},
},
},
}
if errs := config.Prepare(); len(errs) != 0 {
t.Fatalf("Config preprare should not fail")
t.Fatalf("Config preprare should not fail: %s", errs[0])
}
if config.GuestOSType != "otherGuest" {
t.Fatalf("GuestOSType should default to 'otherGuest'")
}
if len(config.DiskControllerType) != 1 {
if len(config.StorageConfig.DiskControllerType) != 1 {
t.Fatalf("DiskControllerType should have at least one element as default")
}
@ -38,10 +47,12 @@ func TestCreateConfig_Prepare(t *testing.T) {
{
name: "Storage validate disk_size",
config: &CreateConfig{
Storage: []DiskConfig{
{
DiskSize: 0,
DiskThinProvisioned: true,
StorageConfig: common.StorageConfig{
Storage: []common.DiskConfig{
{
DiskSize: 0,
DiskThinProvisioned: true,
},
},
},
},
@ -51,10 +62,12 @@ func TestCreateConfig_Prepare(t *testing.T) {
{
name: "Storage validate disk_controller_index",
config: &CreateConfig{
Storage: []DiskConfig{
{
DiskSize: 32768,
DiskControllerIndex: 3,
StorageConfig: common.StorageConfig{
Storage: []common.DiskConfig{
{
DiskSize: 32768,
DiskControllerIndex: 3,
},
},
},
},
@ -65,6 +78,13 @@ func TestCreateConfig_Prepare(t *testing.T) {
name: "USBController validate 'usb' and 'xhci' can be set together",
config: &CreateConfig{
USBController: []string{"usb", "xhci"},
StorageConfig: common.StorageConfig{
Storage: []common.DiskConfig{
{
DiskSize: 32768,
},
},
},
},
fail: false,
},
@ -72,6 +92,13 @@ func TestCreateConfig_Prepare(t *testing.T) {
name: "USBController validate '1' and '0' can be set together",
config: &CreateConfig{
USBController: []string{"1", "0"},
StorageConfig: common.StorageConfig{
Storage: []common.DiskConfig{
{
DiskSize: 32768,
},
},
},
},
fail: false,
},
@ -79,6 +106,13 @@ func TestCreateConfig_Prepare(t *testing.T) {
name: "USBController validate 'true' and 'false' can be set together",
config: &CreateConfig{
USBController: []string{"true", "false"},
StorageConfig: common.StorageConfig{
Storage: []common.DiskConfig{
{
DiskSize: 32768,
},
},
},
},
fail: false,
},
@ -86,6 +120,13 @@ func TestCreateConfig_Prepare(t *testing.T) {
name: "USBController validate 'true' and 'usb' cannot be set together",
config: &CreateConfig{
USBController: []string{"true", "usb"},
StorageConfig: common.StorageConfig{
Storage: []common.DiskConfig{
{
DiskSize: 32768,
},
},
},
},
fail: true,
expectedErrMsg: "there can only be one usb controller and one xhci controller",
@ -94,6 +135,13 @@ func TestCreateConfig_Prepare(t *testing.T) {
name: "USBController validate '1' and 'usb' cannot be set together",
config: &CreateConfig{
USBController: []string{"1", "usb"},
StorageConfig: common.StorageConfig{
Storage: []common.DiskConfig{
{
DiskSize: 32768,
},
},
},
},
fail: true,
expectedErrMsg: "there can only be one usb controller and one xhci controller",
@ -102,6 +150,13 @@ func TestCreateConfig_Prepare(t *testing.T) {
name: "USBController validate 'xhci' cannot be set more that once",
config: &CreateConfig{
USBController: []string{"xhci", "xhci"},
StorageConfig: common.StorageConfig{
Storage: []common.DiskConfig{
{
DiskSize: 32768,
},
},
},
},
fail: true,
expectedErrMsg: "there can only be one usb controller and one xhci controller",
@ -110,6 +165,13 @@ func TestCreateConfig_Prepare(t *testing.T) {
name: "USBController validate unknown value cannot be set",
config: &CreateConfig{
USBController: []string{"unknown"},
StorageConfig: common.StorageConfig{
Storage: []common.DiskConfig{
{
DiskSize: 32768,
},
},
},
},
fail: true,
expectedErrMsg: "usb_controller[0] references an unknown usb controller",
@ -127,7 +189,7 @@ func TestCreateConfig_Prepare(t *testing.T) {
}
} else {
if len(errs) != 0 {
t.Fatalf("Config preprare should not fail")
t.Fatalf("Config preprare should not fail: %s", errs[0])
}
}
}
@ -294,13 +356,15 @@ func basicLocationConfig() *common.LocationConfig {
func createConfig() *CreateConfig {
return &CreateConfig{
Version: 1,
GuestOSType: "ubuntu64Guest",
DiskControllerType: []string{"pvscsi"},
Storage: []DiskConfig{
{
DiskSize: 32768,
DiskThinProvisioned: true,
Version: 1,
GuestOSType: "ubuntu64Guest",
StorageConfig: common.StorageConfig{
DiskControllerType: []string{"pvscsi"},
Storage: []common.DiskConfig{
{
DiskSize: 32768,
DiskThinProvisioned: true,
},
},
},
NICs: []NIC{
@ -324,7 +388,7 @@ func driverCreateConfig(config *CreateConfig, location *common.LocationConfig) *
}
var disks []driver.Disk
for _, disk := range config.Storage {
for _, disk := range config.StorageConfig.Storage {
disks = append(disks, driver.Disk{
DiskSize: disk.DiskSize,
DiskEagerlyScrub: disk.DiskEagerlyScrub,
@ -334,18 +398,20 @@ func driverCreateConfig(config *CreateConfig, location *common.LocationConfig) *
}
return &driver.CreateConfig{
DiskControllerType: config.DiskControllerType,
Storage: disks,
Annotation: config.Notes,
Name: location.VMName,
Folder: location.Folder,
Cluster: location.Cluster,
Host: location.Host,
ResourcePool: location.ResourcePool,
Datastore: location.Datastore,
GuestOS: config.GuestOSType,
NICs: networkCards,
USBController: config.USBController,
Version: config.Version,
StorageConfig: driver.StorageConfig{
DiskControllerType: config.StorageConfig.DiskControllerType,
Storage: disks,
},
Annotation: config.Notes,
Name: location.VMName,
Folder: location.Folder,
Cluster: location.Cluster,
Host: location.Host,
ResourcePool: location.ResourcePool,
Datastore: location.Datastore,
GuestOS: config.GuestOSType,
NICs: networkCards,
USBController: config.USBController,
Version: config.Version,
}
}

View File

@ -45,6 +45,21 @@ necessary for this build to succeed and can be found further down the page.
@include 'builder/vsphere/clone/CloneConfig-not-required.mdx'
@include 'builder/vsphere/common/StorageConfig-not-required.mdx'
### Storage Configuration
When cloning a VM, the storage configuration can be used to add additional storage and disk controllers. The resulting VM
will contain the origin VM storage and disk controller plus the new configured ones.
@include 'builder/vsphere/common/DiskConfig.mdx'
@include 'builder/vsphere/common/DiskConfig-required.mdx'
#### Optional
@include 'builder/vsphere/common/DiskConfig-not-required.mdx'
### vApp Options Configuration
@include 'builder/vsphere/clone/vAppConfig-not-required.mdx'

View File

@ -186,6 +186,8 @@ iso_paths = [
@include 'builder/vsphere/iso/CreateConfig-not-required.mdx'
@include 'builder/vsphere/common/StorageConfig-not-required.mdx'
### Network Adapter Configuration
@include 'builder/vsphere/iso/NIC.mdx'
@ -198,13 +200,13 @@ iso_paths = [
### Storage Configuration
@include 'builder/vsphere/iso/DiskConfig.mdx'
@include 'builder/vsphere/common/DiskConfig.mdx'
@include 'builder/vsphere/iso/DiskConfig-required.mdx'
@include 'builder/vsphere/common/DiskConfig-required.mdx'
#### Optional
@include 'builder/vsphere/iso/DiskConfig-not-required.mdx'
@include 'builder/vsphere/common/DiskConfig-not-required.mdx'
### Export Configuration

View File

@ -1,4 +1,4 @@
<!-- Code generated from the comments of the DiskConfig struct in builder/vsphere/iso/step_create.go; DO NOT EDIT MANUALLY -->
<!-- Code generated from the comments of the DiskConfig struct in builder/vsphere/common/storage_config.go; DO NOT EDIT MANUALLY -->
- `disk_thin_provisioned` (bool) - Enable VMDK thin provisioning for VM. Defaults to `false`.

View File

@ -1,3 +1,3 @@
<!-- Code generated from the comments of the DiskConfig struct in builder/vsphere/iso/step_create.go; DO NOT EDIT MANUALLY -->
<!-- Code generated from the comments of the DiskConfig struct in builder/vsphere/common/storage_config.go; DO NOT EDIT MANUALLY -->
- `disk_size` (int64) - The size of the disk in MB.

View File

@ -1,4 +1,4 @@
<!-- Code generated from the comments of the DiskConfig struct in builder/vsphere/iso/step_create.go; DO NOT EDIT MANUALLY -->
<!-- Code generated from the comments of the DiskConfig struct in builder/vsphere/common/storage_config.go; DO NOT EDIT MANUALLY -->
Defines the disk storage for a VM.

View File

@ -0,0 +1,8 @@
<!-- Code generated from the comments of the StorageConfig struct in builder/vsphere/common/storage_config.go; DO NOT EDIT MANUALLY -->
- `disk_controller_type` ([]string) - Set VM disk controller type. Example `lsilogic`, `pvscsi`, `nvme`, or `scsi`. Use a list to define additional controllers.
Defaults to `lsilogic`. See
[SCSI, SATA, and NVMe Storage Controller Conditions, Limitations, and Compatibility](https://docs.vmware.com/en/VMware-vSphere/7.0/com.vmware.vsphere.vm_admin.doc/GUID-5872D173-A076-42FE-8D0B-9DB0EB0E7362.html#GUID-5872D173-A076-42FE-8D0B-9DB0EB0E7362)
for additional details.
- `storage` ([]DiskConfig) - Configures a collection of one or more disks to be provisioned along with the VM. See the [Storage Configuration](#storage-configuration).

View File

@ -9,13 +9,6 @@
here](https://code.vmware.com/apis/358/vsphere/doc/vim.vm.GuestOsDescriptor.GuestOsIdentifier.html)
for a full list of possible values.
- `disk_controller_type` ([]string) - Set VM disk controller type. Example `lsilogic`, `pvscsi`, `nvme`, or `scsi`. Use a list to define additional controllers.
Defaults to `lsilogic`. See
[SCSI, SATA, and NVMe Storage Controller Conditions, Limitations, and Compatibility](https://docs.vmware.com/en/VMware-vSphere/7.0/com.vmware.vsphere.vm_admin.doc/GUID-5872D173-A076-42FE-8D0B-9DB0EB0E7362.html#GUID-5872D173-A076-42FE-8D0B-9DB0EB0E7362)
for additional details.
- `storage` ([]DiskConfig) - A collection of one or more disks to be provisioned along with the VM.
- `network_adapters` ([]NIC) - Network adapters
- `usb_controller` ([]string) - Create USB controllers for the virtual machine. "usb" for a usb 2.0 controller. "xhci" for a usb 3.0 controller. There can only be at most one of each.