From a67fa662bf85439ea3ae2327ad666f62ef86cdf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristinn=20=C3=96rn=20Sigur=C3=B0sson?= Date: Wed, 25 Nov 2015 15:35:37 +0100 Subject: [PATCH] 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. --- .../resource_vsphere_virtual_machine.go | 102 +++++++++++++++++- .../resource_vsphere_virtual_machine_test.go | 88 ++++++++++++++- .../providers/vsphere/index.html.markdown | 5 + .../vsphere/r/virtual_machine.html.markdown | 12 +++ 4 files changed, 205 insertions(+), 2 deletions(-) diff --git a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go index cfab8a016f..4e0c5f1336 100644 --- a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go +++ b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go @@ -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()) diff --git a/builtin/providers/vsphere/resource_vsphere_virtual_machine_test.go b/builtin/providers/vsphere/resource_vsphere_virtual_machine_test.go index 17197f63d5..264884b391 100644 --- a/builtin/providers/vsphere/resource_vsphere_virtual_machine_test.go +++ b/builtin/providers/vsphere/resource_vsphere_virtual_machine_test.go @@ -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" + } +} +` diff --git a/website/source/docs/providers/vsphere/index.html.markdown b/website/source/docs/providers/vsphere/index.html.markdown index 2138b5084f..ef6ff62952 100644 --- a/website/source/docs/providers/vsphere/index.html.markdown +++ b/website/source/docs/providers/vsphere/index.html.markdown @@ -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. diff --git a/website/source/docs/providers/vsphere/r/virtual_machine.html.markdown b/website/source/docs/providers/vsphere/r/virtual_machine.html.markdown index 47791ba381..0593e3c9b4 100644 --- a/website/source/docs/providers/vsphere/r/virtual_machine.html.markdown +++ b/website/source/docs/providers/vsphere/r/virtual_machine.html.markdown @@ -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. + +## 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. + +## 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: