vSphere: Support mounting ISO images to virtual cdrom drives.

It can come in handy to be able to mount ISOs programmatically.
For instance if you're developing a custom appliance (that automatically installs itself on the hard drive volume)
that you want to automatically test on every successful build (given the ISO is uploaded to the vmware datastore).

There are probably lots of other reasons for using this functionality.
This commit is contained in:
Kristinn Örn Sigurðsson 2015-11-25 15:35:37 +01:00
parent c682dece84
commit a67fa662bf
4 changed files with 205 additions and 2 deletions

View File

@ -53,6 +53,11 @@ type windowsOptConfig struct {
domainUserPassword string
}
type cdrom struct {
datastore string
path string
}
type virtualMachine struct {
name string
folder string
@ -65,6 +70,7 @@ type virtualMachine struct {
template string
networkInterfaces []networkInterface
hardDisks []hardDisk
cdroms []cdrom
gateway string
domain string
timeZone string
@ -328,6 +334,27 @@ func resourceVSphereVirtualMachine() *schema.Resource {
},
},
"cdrom": &schema.Schema{
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"datastore": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"path": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
},
},
"boot_delay": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
@ -492,6 +519,25 @@ func resourceVSphereVirtualMachineCreate(d *schema.ResourceData, meta interface{
log.Printf("[DEBUG] disk init: %v", disks)
}
if vL, ok := d.GetOk("cdrom"); ok {
cdroms := make([]cdrom, len(vL.([]interface{})))
for i, v := range vL.([]interface{}) {
c := v.(map[string]interface{})
if v, ok := c["datastore"].(string); ok && v != "" {
cdroms[i].datastore = v
} else {
return fmt.Errorf("Datastore argument must be specified when attaching a cdrom image.")
}
if v, ok := c["path"].(string); ok && v != "" {
cdroms[i].path = v
} else {
return fmt.Errorf("Path argument must be specified when attaching a cdrom image.")
}
}
vm.cdroms = cdroms
log.Printf("[DEBUG] cdrom init: %v", cdroms)
}
if vm.template != "" {
err := vm.deployVirtualMachine(client)
if err != nil {
@ -743,6 +789,31 @@ func addHardDisk(vm *object.VirtualMachine, size, iops int64, diskType string) e
}
}
// addCdrom adds a new virtual cdrom drive to the VirtualMachine and attaches an image (ISO) to it from a datastore path.
func addCdrom(vm *object.VirtualMachine, datastore, path string) error {
devices, err := vm.Device(context.TODO())
if err != nil {
return err
}
log.Printf("[DEBUG] vm devices: %#v", devices)
controller, err := devices.FindIDEController("")
if err != nil {
return err
}
log.Printf("[DEBUG] ide controller: %#v", controller)
c, err := devices.CreateCdrom(controller)
if err != nil {
return err
}
c = devices.InsertIso(c, fmt.Sprintf("[%s] %s", datastore, path))
log.Printf("[DEBUG] addCdrom: %#v", c)
return vm.AddDevice(context.TODO(), c)
}
// buildNetworkDevice builds VirtualDeviceConfigSpec for Network Device.
func buildNetworkDevice(f *find.Finder, label, adapterType string) (*types.VirtualDeviceConfigSpec, error) {
network, err := f.Network(context.TODO(), "*"+label)
@ -934,6 +1005,21 @@ func findDatastore(c *govmomi.Client, sps types.StoragePlacementSpec) (*object.D
return datastore, nil
}
// createCdroms is a helper function to attach virtual cdrom devices (and their attached disk images) to a virtual IDE controller.
func createCdroms(vm *object.VirtualMachine, cdroms []cdrom) error {
log.Printf("[DEBUG] add cdroms: %v", cdroms)
for _, cd := range cdroms {
log.Printf("[DEBUG] add cdrom (datastore): %v", cd.datastore)
log.Printf("[DEBUG] add cdrom (cd path): %v", cd.path)
err := addCdrom(vm, cd.datastore, cd.path)
if err != nil {
return err
}
}
return nil
}
// createVirtualMachine creates a new VirtualMachine.
func (vm *virtualMachine) createVirtualMachine(c *govmomi.Client) error {
dc, err := getDatacenter(c, vm.datacenter)
@ -1071,6 +1157,7 @@ func (vm *virtualMachine) createVirtualMachine(c *govmomi.Client) error {
Operation: types.VirtualDeviceConfigSpecOperationAdd,
Device: scsi,
})
configSpec.Files = &types.VirtualMachineFileInfo{VmPathName: fmt.Sprintf("[%s]", mds.Name)}
task, err := folder.CreateVM(context.TODO(), configSpec, resourcePool, nil)
@ -1098,6 +1185,12 @@ func (vm *virtualMachine) createVirtualMachine(c *govmomi.Client) error {
return err
}
}
// Create the cdroms if needed.
if err := createCdroms(newVM, vm.cdroms); err != nil {
return err
}
return nil
}
@ -1249,6 +1342,7 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error {
NumCoresPerSocket: 1,
MemoryMB: vm.memoryMb,
}
log.Printf("[DEBUG] virtual machine config spec: %v", configSpec)
log.Printf("[DEBUG] starting extra custom config spec: %v", vm.customConfigurations)
@ -1401,6 +1495,11 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error {
}
}
// Create the cdroms if needed.
if err := createCdroms(newVM, vm.cdroms); err != nil {
return err
}
taskb, err := newVM.Customize(context.TODO(), customSpec)
if err != nil {
return err
@ -1410,7 +1509,7 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error {
if err != nil {
return err
}
log.Printf("[DEBUG]VM customization finished")
log.Printf("[DEBUG] VM customization finished")
for i := 1; i < len(vm.hardDisks); i++ {
err = addHardDisk(newVM, vm.hardDisks[i].size, vm.hardDisks[i].iops, vm.hardDisks[i].initType)
@ -1418,6 +1517,7 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error {
return err
}
}
log.Printf("[DEBUG] virtual machine config spec: %v", configSpec)
newVM.PowerOn(context.TODO())

View File

@ -388,6 +388,71 @@ func TestAccVSphereVirtualMachine_createWithFolder(t *testing.T) {
})
}
func TestAccVSphereVirtualMachine_createWithCdrom(t *testing.T) {
var vm virtualMachine
var locationOpt string
var datastoreOpt string
if v := os.Getenv("VSPHERE_DATACENTER"); v != "" {
locationOpt += fmt.Sprintf(" datacenter = \"%s\"\n", v)
}
if v := os.Getenv("VSPHERE_CLUSTER"); v != "" {
locationOpt += fmt.Sprintf(" cluster = \"%s\"\n", v)
}
if v := os.Getenv("VSPHERE_RESOURCE_POOL"); v != "" {
locationOpt += fmt.Sprintf(" resource_pool = \"%s\"\n", v)
}
if v := os.Getenv("VSPHERE_DATASTORE"); v != "" {
datastoreOpt = fmt.Sprintf(" datastore = \"%s\"\n", v)
}
template := os.Getenv("VSPHERE_TEMPLATE")
label := os.Getenv("VSPHERE_NETWORK_LABEL_DHCP")
cdromDatastore := os.Getenv("VSPHERE_CDROM_DATASTORE")
cdromPath := os.Getenv("VSPHERE_CDROM_PATH")
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckVSphereVirtualMachineDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: fmt.Sprintf(
testAccCheckVsphereVirtualMachineConfig_cdrom,
locationOpt,
label,
datastoreOpt,
template,
cdromDatastore,
cdromPath,
),
Check: resource.ComposeTestCheckFunc(
testAccCheckVSphereVirtualMachineExists("vsphere_virtual_machine.with_cdrom", &vm),
resource.TestCheckResourceAttr(
"vsphere_virtual_machine.with_cdrom", "name", "terraform-test-with-cdrom"),
resource.TestCheckResourceAttr(
"vsphere_virtual_machine.with_cdrom", "vcpu", "2"),
resource.TestCheckResourceAttr(
"vsphere_virtual_machine.with_cdrom", "memory", "4096"),
resource.TestCheckResourceAttr(
"vsphere_virtual_machine.with_cdrom", "disk.#", "1"),
resource.TestCheckResourceAttr(
"vsphere_virtual_machine.with_cdrom", "disk.0.template", template),
resource.TestCheckResourceAttr(
"vsphere_virtual_machine.with_cdrom", "cdrom.#", "1"),
resource.TestCheckResourceAttr(
"vsphere_virtual_machine.with_cdrom", "cdrom.0.datastore", cdromDatastore),
resource.TestCheckResourceAttr(
"vsphere_virtual_machine.with_cdrom", "cdrom.0.path", cdromPath),
resource.TestCheckResourceAttr(
"vsphere_virtual_machine.with_cdrom", "network_interface.#", "1"),
resource.TestCheckResourceAttr(
"vsphere_virtual_machine.with_cdrom", "network_interface.0.label", label),
),
},
},
})
}
func testAccCheckVSphereVirtualMachineDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*govmomi.Client)
finder := find.NewFinder(client.Client, true)
@ -664,7 +729,7 @@ resource "vsphere_virtual_machine" "folder" {
const testAccCheckVSphereVirtualMachineConfig_createWithFolder = `
resource "vsphere_folder" "with_folder" {
path = "%s"
path = "%s"
%s
}
resource "vsphere_virtual_machine" "with_folder" {
@ -682,3 +747,24 @@ resource "vsphere_virtual_machine" "with_folder" {
}
}
`
const testAccCheckVsphereVirtualMachineConfig_cdrom = `
resource "vsphere_virtual_machine" "with_cdrom" {
name = "terraform-test-with-cdrom"
%s
vcpu = 2
memory = 4096
network_interface {
label = "%s"
}
disk {
%s
template = "%s"
}
cdrom {
datastore = "%s"
path = "%s"
}
}
`

View File

@ -89,6 +89,11 @@ The following environment variables depend on your vSphere environment:
* VSPHERE\_RESOURCE\_POOL
* VSPHERE\_DATASTORE
The following additional environment variables are needed for running the "Mount ISO as CDROM media" acceptance tests.
* VSPHERE\_CDROM\_DATASTORE
* VSPHERE\_CDROM\_PATH
These are used to set and verify attributes on the `vsphere_virtual_machine`
resource in tests.

View File

@ -46,6 +46,7 @@ The following arguments are supported:
* `dns_servers` - (Optional) List of DNS servers for the virtual network adapter; defaults to 8.8.8.8, 8.8.4.4
* `network_interface` - (Required) Configures virtual network interfaces; see [Network Interfaces](#network-interfaces) below for details.
* `disk` - (Required) Configures virtual disks; see [Disks](#disks) below for details
* `cdrom` - (Optional) Configures a CDROM device and mounts an image as its media; see [CDROM](#cdrom) below for more details.
* `boot_delay` - (Optional) Time in seconds to wait for machine network to be ready.
* `windows_opt_config` - (Optional) Extra options for clones of Windows machines.
* `linked_clone` - (Optional) Specifies if the new machine is a [linked clone](https://www.vmware.com/support/ws5/doc/ws_clone_overview.html#wp1036396) of another machine or not.
@ -71,6 +72,9 @@ The `windows_opt_config` block supports:
* `domain_user` - (Optional) User that is a member of the specified domain.
* `domain_user_password` - (Optional) Password for domain user, in plain text.
<a id="disks"></a>
## Disks
The `disk` block supports:
* `template` - (Required if size not provided) Template for this disk.
@ -79,6 +83,14 @@ The `disk` block supports:
* `iops` - (Optional) Number of virtual iops to allocate for this disk.
* `type` - (Optional) 'eager_zeroed' (the default), or 'thin' are supported options.
<a id="cdrom"></a>
## CDROM
The `cdrom` block supports:
* `datastore` - (Required) The name of the datastore where the disk image is stored.
* `path` - (Required) The absolute path to the image within the datastore.
## Attributes Reference
The following attributes are exported: