diff --git a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go index 1c90d6176b..73712b345a 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 { @@ -749,6 +795,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) @@ -940,6 +1011,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) @@ -1077,6 +1163,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) @@ -1104,6 +1191,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 } @@ -1255,6 +1348,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) @@ -1407,6 +1501,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 @@ -1416,7 +1515,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) @@ -1424,6 +1523,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: