Skip to content

Commit a67fa66

Browse files
committed
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.
1 parent c682dec commit a67fa66

4 files changed

Lines changed: 205 additions & 2 deletions

File tree

builtin/providers/vsphere/resource_vsphere_virtual_machine.go

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ type windowsOptConfig struct {
5353
domainUserPassword string
5454
}
5555

56+
type cdrom struct {
57+
datastore string
58+
path string
59+
}
60+
5661
type virtualMachine struct {
5762
name string
5863
folder string
@@ -65,6 +70,7 @@ type virtualMachine struct {
6570
template string
6671
networkInterfaces []networkInterface
6772
hardDisks []hardDisk
73+
cdroms []cdrom
6874
gateway string
6975
domain string
7076
timeZone string
@@ -328,6 +334,27 @@ func resourceVSphereVirtualMachine() *schema.Resource {
328334
},
329335
},
330336

337+
"cdrom": &schema.Schema{
338+
Type: schema.TypeList,
339+
Optional: true,
340+
ForceNew: true,
341+
Elem: &schema.Resource{
342+
Schema: map[string]*schema.Schema{
343+
"datastore": &schema.Schema{
344+
Type: schema.TypeString,
345+
Required: true,
346+
ForceNew: true,
347+
},
348+
349+
"path": &schema.Schema{
350+
Type: schema.TypeString,
351+
Required: true,
352+
ForceNew: true,
353+
},
354+
},
355+
},
356+
},
357+
331358
"boot_delay": &schema.Schema{
332359
Type: schema.TypeInt,
333360
Optional: true,
@@ -492,6 +519,25 @@ func resourceVSphereVirtualMachineCreate(d *schema.ResourceData, meta interface{
492519
log.Printf("[DEBUG] disk init: %v", disks)
493520
}
494521

522+
if vL, ok := d.GetOk("cdrom"); ok {
523+
cdroms := make([]cdrom, len(vL.([]interface{})))
524+
for i, v := range vL.([]interface{}) {
525+
c := v.(map[string]interface{})
526+
if v, ok := c["datastore"].(string); ok && v != "" {
527+
cdroms[i].datastore = v
528+
} else {
529+
return fmt.Errorf("Datastore argument must be specified when attaching a cdrom image.")
530+
}
531+
if v, ok := c["path"].(string); ok && v != "" {
532+
cdroms[i].path = v
533+
} else {
534+
return fmt.Errorf("Path argument must be specified when attaching a cdrom image.")
535+
}
536+
}
537+
vm.cdroms = cdroms
538+
log.Printf("[DEBUG] cdrom init: %v", cdroms)
539+
}
540+
495541
if vm.template != "" {
496542
err := vm.deployVirtualMachine(client)
497543
if err != nil {
@@ -743,6 +789,31 @@ func addHardDisk(vm *object.VirtualMachine, size, iops int64, diskType string) e
743789
}
744790
}
745791

792+
// addCdrom adds a new virtual cdrom drive to the VirtualMachine and attaches an image (ISO) to it from a datastore path.
793+
func addCdrom(vm *object.VirtualMachine, datastore, path string) error {
794+
devices, err := vm.Device(context.TODO())
795+
if err != nil {
796+
return err
797+
}
798+
log.Printf("[DEBUG] vm devices: %#v", devices)
799+
800+
controller, err := devices.FindIDEController("")
801+
if err != nil {
802+
return err
803+
}
804+
log.Printf("[DEBUG] ide controller: %#v", controller)
805+
806+
c, err := devices.CreateCdrom(controller)
807+
if err != nil {
808+
return err
809+
}
810+
811+
c = devices.InsertIso(c, fmt.Sprintf("[%s] %s", datastore, path))
812+
log.Printf("[DEBUG] addCdrom: %#v", c)
813+
814+
return vm.AddDevice(context.TODO(), c)
815+
}
816+
746817
// buildNetworkDevice builds VirtualDeviceConfigSpec for Network Device.
747818
func buildNetworkDevice(f *find.Finder, label, adapterType string) (*types.VirtualDeviceConfigSpec, error) {
748819
network, err := f.Network(context.TODO(), "*"+label)
@@ -934,6 +1005,21 @@ func findDatastore(c *govmomi.Client, sps types.StoragePlacementSpec) (*object.D
9341005
return datastore, nil
9351006
}
9361007

1008+
// createCdroms is a helper function to attach virtual cdrom devices (and their attached disk images) to a virtual IDE controller.
1009+
func createCdroms(vm *object.VirtualMachine, cdroms []cdrom) error {
1010+
log.Printf("[DEBUG] add cdroms: %v", cdroms)
1011+
for _, cd := range cdroms {
1012+
log.Printf("[DEBUG] add cdrom (datastore): %v", cd.datastore)
1013+
log.Printf("[DEBUG] add cdrom (cd path): %v", cd.path)
1014+
err := addCdrom(vm, cd.datastore, cd.path)
1015+
if err != nil {
1016+
return err
1017+
}
1018+
}
1019+
1020+
return nil
1021+
}
1022+
9371023
// createVirtualMachine creates a new VirtualMachine.
9381024
func (vm *virtualMachine) createVirtualMachine(c *govmomi.Client) error {
9391025
dc, err := getDatacenter(c, vm.datacenter)
@@ -1071,6 +1157,7 @@ func (vm *virtualMachine) createVirtualMachine(c *govmomi.Client) error {
10711157
Operation: types.VirtualDeviceConfigSpecOperationAdd,
10721158
Device: scsi,
10731159
})
1160+
10741161
configSpec.Files = &types.VirtualMachineFileInfo{VmPathName: fmt.Sprintf("[%s]", mds.Name)}
10751162

10761163
task, err := folder.CreateVM(context.TODO(), configSpec, resourcePool, nil)
@@ -1098,6 +1185,12 @@ func (vm *virtualMachine) createVirtualMachine(c *govmomi.Client) error {
10981185
return err
10991186
}
11001187
}
1188+
1189+
// Create the cdroms if needed.
1190+
if err := createCdroms(newVM, vm.cdroms); err != nil {
1191+
return err
1192+
}
1193+
11011194
return nil
11021195
}
11031196

@@ -1249,6 +1342,7 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error {
12491342
NumCoresPerSocket: 1,
12501343
MemoryMB: vm.memoryMb,
12511344
}
1345+
12521346
log.Printf("[DEBUG] virtual machine config spec: %v", configSpec)
12531347

12541348
log.Printf("[DEBUG] starting extra custom config spec: %v", vm.customConfigurations)
@@ -1401,6 +1495,11 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error {
14011495
}
14021496
}
14031497

1498+
// Create the cdroms if needed.
1499+
if err := createCdroms(newVM, vm.cdroms); err != nil {
1500+
return err
1501+
}
1502+
14041503
taskb, err := newVM.Customize(context.TODO(), customSpec)
14051504
if err != nil {
14061505
return err
@@ -1410,14 +1509,15 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error {
14101509
if err != nil {
14111510
return err
14121511
}
1413-
log.Printf("[DEBUG]VM customization finished")
1512+
log.Printf("[DEBUG] VM customization finished")
14141513

14151514
for i := 1; i < len(vm.hardDisks); i++ {
14161515
err = addHardDisk(newVM, vm.hardDisks[i].size, vm.hardDisks[i].iops, vm.hardDisks[i].initType)
14171516
if err != nil {
14181517
return err
14191518
}
14201519
}
1520+
14211521
log.Printf("[DEBUG] virtual machine config spec: %v", configSpec)
14221522

14231523
newVM.PowerOn(context.TODO())

builtin/providers/vsphere/resource_vsphere_virtual_machine_test.go

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,71 @@ func TestAccVSphereVirtualMachine_createWithFolder(t *testing.T) {
388388
})
389389
}
390390

391+
func TestAccVSphereVirtualMachine_createWithCdrom(t *testing.T) {
392+
var vm virtualMachine
393+
var locationOpt string
394+
var datastoreOpt string
395+
396+
if v := os.Getenv("VSPHERE_DATACENTER"); v != "" {
397+
locationOpt += fmt.Sprintf(" datacenter = \"%s\"\n", v)
398+
}
399+
if v := os.Getenv("VSPHERE_CLUSTER"); v != "" {
400+
locationOpt += fmt.Sprintf(" cluster = \"%s\"\n", v)
401+
}
402+
if v := os.Getenv("VSPHERE_RESOURCE_POOL"); v != "" {
403+
locationOpt += fmt.Sprintf(" resource_pool = \"%s\"\n", v)
404+
}
405+
if v := os.Getenv("VSPHERE_DATASTORE"); v != "" {
406+
datastoreOpt = fmt.Sprintf(" datastore = \"%s\"\n", v)
407+
}
408+
template := os.Getenv("VSPHERE_TEMPLATE")
409+
label := os.Getenv("VSPHERE_NETWORK_LABEL_DHCP")
410+
cdromDatastore := os.Getenv("VSPHERE_CDROM_DATASTORE")
411+
cdromPath := os.Getenv("VSPHERE_CDROM_PATH")
412+
413+
resource.Test(t, resource.TestCase{
414+
PreCheck: func() { testAccPreCheck(t) },
415+
Providers: testAccProviders,
416+
CheckDestroy: testAccCheckVSphereVirtualMachineDestroy,
417+
Steps: []resource.TestStep{
418+
resource.TestStep{
419+
Config: fmt.Sprintf(
420+
testAccCheckVsphereVirtualMachineConfig_cdrom,
421+
locationOpt,
422+
label,
423+
datastoreOpt,
424+
template,
425+
cdromDatastore,
426+
cdromPath,
427+
),
428+
Check: resource.ComposeTestCheckFunc(
429+
testAccCheckVSphereVirtualMachineExists("vsphere_virtual_machine.with_cdrom", &vm),
430+
resource.TestCheckResourceAttr(
431+
"vsphere_virtual_machine.with_cdrom", "name", "terraform-test-with-cdrom"),
432+
resource.TestCheckResourceAttr(
433+
"vsphere_virtual_machine.with_cdrom", "vcpu", "2"),
434+
resource.TestCheckResourceAttr(
435+
"vsphere_virtual_machine.with_cdrom", "memory", "4096"),
436+
resource.TestCheckResourceAttr(
437+
"vsphere_virtual_machine.with_cdrom", "disk.#", "1"),
438+
resource.TestCheckResourceAttr(
439+
"vsphere_virtual_machine.with_cdrom", "disk.0.template", template),
440+
resource.TestCheckResourceAttr(
441+
"vsphere_virtual_machine.with_cdrom", "cdrom.#", "1"),
442+
resource.TestCheckResourceAttr(
443+
"vsphere_virtual_machine.with_cdrom", "cdrom.0.datastore", cdromDatastore),
444+
resource.TestCheckResourceAttr(
445+
"vsphere_virtual_machine.with_cdrom", "cdrom.0.path", cdromPath),
446+
resource.TestCheckResourceAttr(
447+
"vsphere_virtual_machine.with_cdrom", "network_interface.#", "1"),
448+
resource.TestCheckResourceAttr(
449+
"vsphere_virtual_machine.with_cdrom", "network_interface.0.label", label),
450+
),
451+
},
452+
},
453+
})
454+
}
455+
391456
func testAccCheckVSphereVirtualMachineDestroy(s *terraform.State) error {
392457
client := testAccProvider.Meta().(*govmomi.Client)
393458
finder := find.NewFinder(client.Client, true)
@@ -664,7 +729,7 @@ resource "vsphere_virtual_machine" "folder" {
664729

665730
const testAccCheckVSphereVirtualMachineConfig_createWithFolder = `
666731
resource "vsphere_folder" "with_folder" {
667-
path = "%s"
732+
path = "%s"
668733
%s
669734
}
670735
resource "vsphere_virtual_machine" "with_folder" {
@@ -682,3 +747,24 @@ resource "vsphere_virtual_machine" "with_folder" {
682747
}
683748
}
684749
`
750+
751+
const testAccCheckVsphereVirtualMachineConfig_cdrom = `
752+
resource "vsphere_virtual_machine" "with_cdrom" {
753+
name = "terraform-test-with-cdrom"
754+
%s
755+
vcpu = 2
756+
memory = 4096
757+
network_interface {
758+
label = "%s"
759+
}
760+
disk {
761+
%s
762+
template = "%s"
763+
}
764+
765+
cdrom {
766+
datastore = "%s"
767+
path = "%s"
768+
}
769+
}
770+
`

website/source/docs/providers/vsphere/index.html.markdown

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ The following environment variables depend on your vSphere environment:
8989
* VSPHERE\_RESOURCE\_POOL
9090
* VSPHERE\_DATASTORE
9191

92+
The following additional environment variables are needed for running the "Mount ISO as CDROM media" acceptance tests.
93+
94+
* VSPHERE\_CDROM\_DATASTORE
95+
* VSPHERE\_CDROM\_PATH
96+
9297

9398
These are used to set and verify attributes on the `vsphere_virtual_machine`
9499
resource in tests.

website/source/docs/providers/vsphere/r/virtual_machine.html.markdown

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ The following arguments are supported:
4646
* `dns_servers` - (Optional) List of DNS servers for the virtual network adapter; defaults to 8.8.8.8, 8.8.4.4
4747
* `network_interface` - (Required) Configures virtual network interfaces; see [Network Interfaces](#network-interfaces) below for details.
4848
* `disk` - (Required) Configures virtual disks; see [Disks](#disks) below for details
49+
* `cdrom` - (Optional) Configures a CDROM device and mounts an image as its media; see [CDROM](#cdrom) below for more details.
4950
* `boot_delay` - (Optional) Time in seconds to wait for machine network to be ready.
5051
* `windows_opt_config` - (Optional) Extra options for clones of Windows machines.
5152
* `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:
7172
* `domain_user` - (Optional) User that is a member of the specified domain.
7273
* `domain_user_password` - (Optional) Password for domain user, in plain text.
7374

75+
<a id="disks"></a>
76+
## Disks
77+
7478
The `disk` block supports:
7579

7680
* `template` - (Required if size not provided) Template for this disk.
@@ -79,6 +83,14 @@ The `disk` block supports:
7983
* `iops` - (Optional) Number of virtual iops to allocate for this disk.
8084
* `type` - (Optional) 'eager_zeroed' (the default), or 'thin' are supported options.
8185

86+
<a id="cdrom"></a>
87+
## CDROM
88+
89+
The `cdrom` block supports:
90+
91+
* `datastore` - (Required) The name of the datastore where the disk image is stored.
92+
* `path` - (Required) The absolute path to the image within the datastore.
93+
8294
## Attributes Reference
8395

8496
The following attributes are exported:

0 commit comments

Comments
 (0)