diff --git a/vsphere/internal/helper/ovfdeploy/ovf_helper.go b/vsphere/internal/helper/ovfdeploy/ovf_helper.go index 38628540e..304f3874e 100644 --- a/vsphere/internal/helper/ovfdeploy/ovf_helper.go +++ b/vsphere/internal/helper/ovfdeploy/ovf_helper.go @@ -1,7 +1,9 @@ package ovfdeploy import ( + "archive/tar" "context" + "crypto/tls" "errors" "fmt" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" @@ -11,6 +13,7 @@ import ( "github.com/vmware/govmomi/vim25/soap" "github.com/vmware/govmomi/vim25/types" "io" + "io/ioutil" "log" "net/http" "os" @@ -38,8 +41,8 @@ func (pr *ProgressReader) Read(p []byte) (n int, err error) { return } -func DeployOVFAndGetResult(ovfCreateImportSpecResult *types.OvfCreateImportSpecResult, resourcePoolObj *object.ResourcePool, - folder *object.Folder, host *object.HostSystem, OvfPath string, ovfFromLocal bool) error { +func DeployOvfAndGetResult(ovfCreateImportSpecResult *types.OvfCreateImportSpecResult, resourcePoolObj *object.ResourcePool, + folder *object.Folder, host *object.HostSystem, filePath string, deployOva bool, fromLocal bool, allowUnverifiedSSL bool) error { var currBytesRead int64 = 0 var totalBytes int64 = 0 @@ -85,46 +88,26 @@ func DeployOVFAndGetResult(ovfCreateImportSpecResult *types.OvfCreateImportSpecR for _, ovfFileItem := range ovfCreateImportSpecResult.FileItem { for _, deviceObj := range leaseInfo.DeviceUrl { - if ovfFileItem.DeviceId == deviceObj.ImportKey { - if ovfFromLocal { - absoluteFilePath := "" - if strings.Contains(OvfPath, string(os.PathSeparator)) { - absoluteFilePath = string(OvfPath[0 : strings.LastIndex(OvfPath, string(os.PathSeparator))+1]) - } - vmdkFilePath := absoluteFilePath + ovfFileItem.Path - log.Print(" [DEBUG] Absolute VMDK path: " + vmdkFilePath) - file, err := os.Open(vmdkFilePath) - if err != nil { - return err - } - err = Upload(context.Background(), ovfFileItem, file, deviceObj.Url, ovfFileItem.Size, &currBytesRead) - if err != nil { - return fmt.Errorf("error while uploading the file %s %s", vmdkFilePath, err) - } - err = file.Close() - if err != nil { - log.Printf("error while closing the file %s", vmdkFilePath) - } - + if ovfFileItem.DeviceId != deviceObj.ImportKey { + continue + } + if !deployOva { + if fromLocal { + err = uploadDisksFromLocal(filePath, ovfFileItem, deviceObj, &currBytesRead) + } else { + err = uploadDisksFromUrl(filePath, ovfFileItem, deviceObj, &currBytesRead, allowUnverifiedSSL) + } + } else { + if fromLocal { + err = uploadOvaDisksFromLocal(filePath, ovfFileItem, deviceObj, &currBytesRead) } else { - absoluteFilePath := "" - if strings.Contains(OvfPath, "/") { - absoluteFilePath = string(OvfPath[0 : strings.LastIndex(OvfPath, "/")+1]) - } - vmdkFilePath := absoluteFilePath + ovfFileItem.Path - resp, err := http.Get(vmdkFilePath) - log.Print("DEBUG Absolute VMDK path: " + vmdkFilePath) - if err != nil { - return err - } - defer resp.Body.Close() - err = Upload(context.Background(), ovfFileItem, resp.Body, deviceObj.Url, ovfFileItem.Size, &currBytesRead) - if err != nil { - return err - } + err = uploadOvaDisksFromUrl(filePath, ovfFileItem, deviceObj, &currBytesRead, allowUnverifiedSSL) } - log.Print("DEBUG : Completed uploading the VMDK file") } + if err != nil { + return fmt.Errorf("error while uploading the disk %s %s", ovfFileItem.Path, err) + } + log.Print(" DEBUG : Completed uploading the vmdk file", ovfFileItem.Path) } } statusChannel <- true @@ -138,7 +121,7 @@ func DeployOVFAndGetResult(ovfCreateImportSpecResult *types.OvfCreateImportSpecR return nil } -func Upload(ctx context.Context, item types.OvfFileItem, f io.Reader, url string, size int64, totalBytesRead *int64) error { +func upload(ctx context.Context, item types.OvfFileItem, f io.Reader, url string, size int64, totalBytesRead *int64) error { u, err := soap.ParseURL(url) if err != nil { @@ -197,6 +180,178 @@ func Upload(ctx context.Context, item types.OvfFileItem, f io.Reader, url string return err } +func uploadDisksFromLocal(filePath string, ovfFileItem types.OvfFileItem, deviceObj types.HttpNfcLeaseDeviceUrl, currBytesRead *int64) error { + absoluteFilePath := "" + if strings.Contains(filePath, string(os.PathSeparator)) { + absoluteFilePath = string(filePath[0 : strings.LastIndex(filePath, string(os.PathSeparator))+1]) + } + vmdkFilePath := absoluteFilePath + ovfFileItem.Path + log.Print(" [DEBUG] Absolute vmdk path: " + vmdkFilePath) + file, err := os.Open(vmdkFilePath) + if err != nil { + return err + } + err = upload(context.Background(), ovfFileItem, file, deviceObj.Url, ovfFileItem.Size, currBytesRead) + if err != nil { + return fmt.Errorf("error while uploading the file %s %s", vmdkFilePath, err) + } + err = file.Close() + if err != nil { + log.Printf("error while closing the file %s", vmdkFilePath) + } + return nil +} + +func uploadDisksFromUrl(filePath string, ovfFileItem types.OvfFileItem, deviceObj types.HttpNfcLeaseDeviceUrl, currBytesRead *int64, + allowUnverifiedSSL bool) error { + absoluteFilePath := "" + if strings.Contains(filePath, "/") { + absoluteFilePath = string(filePath[0 : strings.LastIndex(filePath, "/")+1]) + } + vmdkFilePath := absoluteFilePath + ovfFileItem.Path + client := getClient(allowUnverifiedSSL) + resp, err := client.Get(vmdkFilePath) + log.Print(" [DEBUG] Absolute vmdk path: " + vmdkFilePath) + if err != nil { + return err + } + defer resp.Body.Close() + err = upload(context.Background(), ovfFileItem, resp.Body, deviceObj.Url, ovfFileItem.Size, currBytesRead) + return err +} + +func uploadOvaDisksFromLocal(filePath string, ovfFileItem types.OvfFileItem, deviceObj types.HttpNfcLeaseDeviceUrl, currBytesRead *int64) error { + diskName := ovfFileItem.Path + ovaFile, err := os.Open(filePath) + if err != nil { + return err + } + defer ovaFile.Close() + + err = findAndUploadDiskFromOva(ovaFile, diskName, ovfFileItem, deviceObj, currBytesRead) + return err +} + +func uploadOvaDisksFromUrl(filePath string, ovfFileItem types.OvfFileItem, deviceObj types.HttpNfcLeaseDeviceUrl, currBytesRead *int64, + allowUnverifiedSSL bool) error { + diskName := ovfFileItem.Path + client := getClient(allowUnverifiedSSL) + resp, err := client.Get(filePath) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode == http.StatusOK { + err = findAndUploadDiskFromOva(resp.Body, diskName, ovfFileItem, deviceObj, currBytesRead) + if err != nil { + return err + } + } else { + return fmt.Errorf("got status %d while getting the file from remote url %s ", resp.StatusCode, filePath) + } + return nil +} + +func GetOvfDescriptor(filePath string, deployOva bool, fromLocal bool, allowUnverifiedSSL bool) (string, error) { + + ovfDescriptor := "" + if !deployOva { + if fromLocal { + fileBuffer, err := ioutil.ReadFile(filePath) + if err != nil { + return "", err + } + ovfDescriptor = string(fileBuffer) + } else { + client := getClient(allowUnverifiedSSL) + resp, err := client.Get(filePath) + if err != nil { + return "", err + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusOK { + bodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + ovfDescriptor = string(bodyBytes) + } + } + } else { + if fromLocal { + ovaFile, err := os.Open(filePath) + if err != nil { + return "", err + } + defer ovaFile.Close() + ovfDescriptor, err = getOvfDescriptorFromOva(ovaFile) + if err != nil { + return "", err + } + + } else { + client := getClient(allowUnverifiedSSL) + resp, err := client.Get(filePath) + if err != nil { + return "", err + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusOK { + + ovfDescriptor, err = getOvfDescriptorFromOva(resp.Body) + if err != nil { + return "", err + } + } else { + return "", fmt.Errorf("got status %d while getting the file from remote url %s ", resp.StatusCode, filePath) + } + } + } + return ovfDescriptor, nil +} + +func getOvfDescriptorFromOva(ovaFile io.Reader) (string, error) { + ovaReader := tar.NewReader(ovaFile) + for { + fileHdr, err := ovaReader.Next() + if err == io.EOF { + break + } + if err != nil { + return "", err + } + if strings.HasSuffix(fileHdr.Name, ".ovf") { + content, _ := ioutil.ReadAll(ovaReader) + ovfDescriptor := string(content) + return ovfDescriptor, nil + } + } + return "", fmt.Errorf("ovf file not found inside the ova") +} + +func findAndUploadDiskFromOva(ovaFile io.Reader, diskName string, ovfFileItem types.OvfFileItem, deviceObj types.HttpNfcLeaseDeviceUrl, currBytesRead *int64) error { + ovaReader := tar.NewReader(ovaFile) + for { + fileHdr, err := ovaReader.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + if fileHdr.Name == diskName { + err = upload(context.Background(), ovfFileItem, ovaReader, deviceObj.Url, ovfFileItem.Size, currBytesRead) + if err != nil { + return fmt.Errorf("error while uploading the file %s %s", diskName, err) + } + return nil + } + } + return fmt.Errorf("disk %s not found inside ova", diskName) +} + func GetNetworkMapping(client *govmomi.Client, d *schema.ResourceData) ([]types.OvfNetworkMapping, error) { var ovfNetworkMappings []types.OvfNetworkMapping m := d.Get("ovf_deploy.0.ovf_network_map").(map[string]interface{}) @@ -213,3 +368,14 @@ func GetNetworkMapping(client *govmomi.Client, d *schema.ResourceData) ([]types. } return ovfNetworkMappings, nil } + +func getClient(allowUnverifiedSSL bool) *http.Client { + if allowUnverifiedSSL { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + return &http.Client{Transport: tr} + } else { + return &http.Client{} + } +} diff --git a/vsphere/internal/vmworkflow/virtual_machine_ovfdeploy_subresource.go b/vsphere/internal/vmworkflow/virtual_machine_ovfdeploy_subresource.go index 300fe610d..0a219512e 100644 --- a/vsphere/internal/vmworkflow/virtual_machine_ovfdeploy_subresource.go +++ b/vsphere/internal/vmworkflow/virtual_machine_ovfdeploy_subresource.go @@ -4,19 +4,18 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/helper/schema" ) -func VirtualMachineOVFDeploySchema() map[string]*schema.Schema { +func VirtualMachineOvfDeploySchema() map[string]*schema.Schema { return map[string]*schema.Schema{ "local_ovf_path": { - Type: schema.TypeString, - Optional: true, - Description: "The absolute path to the ovf file in the local system. Make sure the other necessary files like" + - "the .vmdk files are also in the same directory as the given ovf file.", - ForceNew: true, + Type: schema.TypeString, + Optional: true, + Description: "The absolute path to the ovf/ova file in the local system.", + ForceNew: true, }, "remote_ovf_url": { Type: schema.TypeString, Optional: true, - Description: "URL to the remote ovf file to be deployed.", + Description: "URL to the remote ovf/ova file to be deployed.", ForceNew: true, }, "ip_allocation_policy": { @@ -34,7 +33,7 @@ func VirtualMachineOVFDeploySchema() map[string]*schema.Schema { "disk_provisioning": { Type: schema.TypeString, Optional: true, - Description: "An optional disk provisioning. If set, all the disks in the deployed OVF will have the same specified disk type (e.g., thin provisioned).", + Description: "An optional disk provisioning. If set, all the disks in the deployed ovf will have the same specified disk type (e.g., thin provisioned).", ForceNew: true, }, "ovf_network_map": { @@ -44,5 +43,11 @@ func VirtualMachineOVFDeploySchema() map[string]*schema.Schema { Elem: &schema.Schema{Type: schema.TypeString}, ForceNew: true, }, + "allow_unverified_ssl_cert": { + Type: schema.TypeBool, + Optional: true, + Default: true, + Description: "Allow unverified ssl certificates while deploying ovf/ova from url.", + }, } } diff --git a/vsphere/resource_vsphere_virtual_machine.go b/vsphere/resource_vsphere_virtual_machine.go index 9d4c546d9..7fd9d74ca 100644 --- a/vsphere/resource_vsphere_virtual_machine.go +++ b/vsphere/resource_vsphere_virtual_machine.go @@ -6,10 +6,8 @@ import ( "fmt" "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/contentlibrary" "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/ovfdeploy" - "io/ioutil" "log" "net" - "net/http" "os" "strings" "time" @@ -199,6 +197,12 @@ func resourceVSphereVirtualMachine() *schema.Resource { Default: string(types.VirtualSCSISharingNoSharing), Description: "Mode for sharing the SCSI bus. The modes are physicalSharing, virtualSharing, and noSharing.", ValidateFunc: validation.StringInSlice(virtualdevice.SCSIBusSharingAllowedValues, false), + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + if len(d.Get("ovf_deploy").([]interface{})) > 0 { + return true + } + return false + }, }, // NOTE: disk is only optional so that we can flag it as computed and use // it in ResourceDiff. We validate this field in ResourceDiff to enforce it @@ -261,9 +265,9 @@ func resourceVSphereVirtualMachine() *schema.Resource { "ovf_deploy": { Type: schema.TypeList, Optional: true, - Description: "A specification for deploying a virtual machine from OVF template.", + Description: "A specification for deploying a virtual machine from ovf/ova template.", MaxItems: 1, - Elem: &schema.Resource{Schema: vmworkflow.VirtualMachineOVFDeploySchema()}, + Elem: &schema.Resource{Schema: vmworkflow.VirtualMachineOvfDeploySchema()}, }, "reboot_required": { Type: schema.TypeBool, @@ -333,7 +337,7 @@ func resourceVSphereVirtualMachineCreate(d *schema.ResourceData, meta interface{ case len(d.Get("clone").([]interface{})) > 0: vm, err = resourceVSphereVirtualMachineCreateClone(d, meta) case len(d.Get("ovf_deploy").([]interface{})) > 0: - vm, err = resourceVsphereMachineDeployOVF(d, meta) + vm, err = resourceVsphereMachineDeployOvfAndOva(d, meta) default: vm, err = resourceVSphereVirtualMachineCreateBare(d, meta) } @@ -949,11 +953,14 @@ func resourceVSphereVirtualMachineCustomizeDiff(d *schema.ResourceDiff, meta int remoteOvfUrl := d.Get("ovf_deploy.0.remote_ovf_url").(string) if localOvfPath == "" && remoteOvfUrl == "" { - return fmt.Errorf("either local ovf path or remote ovf url is required, both can't be empty") + return fmt.Errorf("either local ovf/ova path or remote ovf/ova url is required, both can't be empty") + } + if localOvfPath != "" && remoteOvfUrl != "" { + return fmt.Errorf("both local ovf/ova path and remote ovf/ova url are provided, please specify only one source") } if localOvfPath != "" { if _, err := os.Stat(localOvfPath); os.IsNotExist(err) { - return fmt.Errorf("file doesn't exist %s", localOvfPath) + return fmt.Errorf("ovf/ova file doesn't exist %s", localOvfPath) } } } @@ -1310,20 +1317,29 @@ func resourceVSphereVirtualMachineCreateBareStandard( return vm, nil } -// Deploy vm from OVF template -func resourceVsphereMachineDeployOVF(d *schema.ResourceData, meta interface{}) (*object.VirtualMachine, error) { +// Deploy vm from ovf/ova template +func resourceVsphereMachineDeployOvfAndOva(d *schema.ResourceData, meta interface{}) (*object.VirtualMachine, error) { localOvfPath := d.Get("ovf_deploy.0.local_ovf_path").(string) remoteOvfUrl := d.Get("ovf_deploy.0.remote_ovf_url").(string) - ovFromLocal := true - if localOvfPath == "" { - ovFromLocal = false + + // check if Ovf or Ova is to be deployed from local/remote + deployOva := false + fromLocal := true + filePath := localOvfPath + + if remoteOvfUrl != "" { + fromLocal = false + filePath = remoteOvfUrl + } + if strings.HasSuffix(filePath, ".ova") { + deployOva = true } - log.Printf("[DEBUG] %s:%s VM is being deployed from OVF template ", localOvfPath, remoteOvfUrl) + log.Printf("[DEBUG] VM is being deployed from ovf/ova template %s", filePath) dataCenterId := d.Get("datacenter_id").(string) if dataCenterId == "" { - return nil, fmt.Errorf("data center ID is required for OVF deployment") + return nil, fmt.Errorf("data center ID is required for ovf deployment") } client := meta.(*VSphereClient).vimClient @@ -1344,7 +1360,7 @@ func resourceVsphereMachineDeployOVF(d *schema.ResourceData, meta interface{}) ( hostId := d.Get("host_system_id").(string) if hostId == "" { - return nil, fmt.Errorf("host system ID is required for OVF deployment") + return nil, fmt.Errorf("host system ID is required for ovf deployment") } hostObj, err := hostsystem.FromID(client, hostId) if err != nil { @@ -1354,7 +1370,7 @@ func resourceVsphereMachineDeployOVF(d *schema.ResourceData, meta interface{}) ( dsId := d.Get("datastore_id").(string) if dsId == "" { - return nil, fmt.Errorf("data store ID is required for OVF deployment") + return nil, fmt.Errorf("data store ID is required for ovf deployment") } dsObj, err := datastore.FromID(client, dsId) if err != nil { @@ -1362,6 +1378,7 @@ func resourceVsphereMachineDeployOVF(d *schema.ResourceData, meta interface{}) ( } dsMor := dsObj.Reference() + allowUnverifiedSSL := d.Get("ovf_deploy.0.allow_unverified_ssl_cert").(bool) networkMapping, err := ovfdeploy.GetNetworkMapping(client, d) if err != nil { return nil, err @@ -1375,30 +1392,13 @@ func resourceVsphereMachineDeployOVF(d *schema.ResourceData, meta interface{}) ( DiskProvisioning: d.Get("ovf_deploy.0.disk_provisioning").(string), } - ovfDescriptor := "" - if ovFromLocal { - fileBuffer, err := ioutil.ReadFile(localOvfPath) - if err != nil { - return nil, err - } - ovfDescriptor = string(fileBuffer) - } else { - resp, err := http.Get(remoteOvfUrl) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode == http.StatusOK { - bodyBytes, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - ovfDescriptor = string(bodyBytes) - } + ovfDescriptor, err := ovfdeploy.GetOvfDescriptor(filePath, deployOva, fromLocal, allowUnverifiedSSL) + if err != nil { + return nil, fmt.Errorf("error while reading the ovf file %s, %s ", filePath, err) } + if ovfDescriptor == "" { - return nil, fmt.Errorf("error while reading the OVF File %s %s", localOvfPath, remoteOvfUrl) + return nil, fmt.Errorf("the given ovf file %s is empty", filePath) } ovfManager := ovf.NewManager(client.Client) @@ -1408,15 +1408,10 @@ func resourceVsphereMachineDeployOVF(d *schema.ResourceData, meta interface{}) ( return nil, err } - ovfPath := localOvfPath - if !ovFromLocal { - ovfPath = remoteOvfUrl - } - - log.Print(" [DEBUG] start deploying from OVF Template") - err = ovfdeploy.DeployOVFAndGetResult(ovfCreateImportSpecResult, poolObj, folderObj, hostObj, ovfPath, ovFromLocal) + log.Print(" [DEBUG] start deploying from ovf/ova Template") + err = ovfdeploy.DeployOvfAndGetResult(ovfCreateImportSpecResult, poolObj, folderObj, hostObj, filePath, deployOva, fromLocal, allowUnverifiedSSL) if err != nil { - return nil, fmt.Errorf("error while importing OVF template %s", err) + return nil, fmt.Errorf("error while importing ovf/ova template, %s", err) } datacenterObj, err := datacenterFromID(client, dataCenterId) @@ -1425,7 +1420,7 @@ func resourceVsphereMachineDeployOVF(d *schema.ResourceData, meta interface{}) ( } vm, err = virtualmachine.FromPath(client, name, datacenterObj) if err != nil { - return nil, fmt.Errorf("error while fetching the created vm %s", err) + return nil, fmt.Errorf("error while fetching the created vm, %s", err) } // set ID for the vm diff --git a/vsphere/resource_vsphere_virtual_machine_test.go b/vsphere/resource_vsphere_virtual_machine_test.go index bee829d02..ffb3dcf75 100644 --- a/vsphere/resource_vsphere_virtual_machine_test.go +++ b/vsphere/resource_vsphere_virtual_machine_test.go @@ -2575,6 +2575,27 @@ func TestAccResourceVSphereVirtualMachine_deployOvfFromUrl(t *testing.T) { }) } +func TestAccResourceVSphereVirtualMachine_deployOvaFromUrl(t *testing.T) { + vmName := "terraform_test_vm_" + acctest.RandStringFromCharSet(4, acctest.CharSetAlphaNum) + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccResourceVSphereVirtualMachinePreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereVirtualMachineCheckExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereVirtualMachineDeployOvaFromUrl(vmName), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereVirtualMachineCheckExists(true), + resource.TestCheckResourceAttr("vsphere_virtual_machine.vm", "name", vmName), + ), + }, + }, + }) +} + func testAccResourceVSphereVirtualMachinePreCheck(t *testing.T) { // Note that TF_VAR_VSPHERE_USE_LINKED_CLONE is also a variable and its presence // speeds up tests greatly, but it's not a necessary variable, so we don't @@ -8897,7 +8918,7 @@ resource "vsphere_virtual_machine" "vm" { datacenter_id = data.vsphere_datacenter.dc.id host_system_id = data.vsphere_host.host.id wait_for_guest_net_timeout = 0 - wait_for_guest_ip_timeout = 1 + wait_for_guest_ip_timeout = 0 ovf_deploy { remote_ovf_url = "%s" } @@ -8913,6 +8934,69 @@ resource "vsphere_virtual_machine" "vm" { ) } +func testAccResourceVSphereVirtualMachineDeployOvaFromUrl(vmName string) string { + return fmt.Sprintf(` +variable "datacenter" { + type = "string" + default = "%s" +} + +variable "datastore" { + type = "string" + default = "%s" +} + +variable "resource_pool" { + type = "string" + default = "%s" +} + +variable "host" { + default = "%s" +} + +data "vsphere_datacenter" "dc" { + name = var.datacenter +} + +data "vsphere_datastore" "datastore" { + name = var.datastore + datacenter_id = data.vsphere_datacenter.dc.id +} + +data "vsphere_resource_pool" "pool" { + name = var.resource_pool + datacenter_id = data.vsphere_datacenter.dc.id +} + +data "vsphere_host" "host" { + name = var.host + datacenter_id = data.vsphere_datacenter.dc.id +} + +resource "vsphere_virtual_machine" "vm" { + name = "%s" + resource_pool_id = data.vsphere_resource_pool.pool.id + datastore_id = data.vsphere_datastore.datastore.id + datacenter_id = data.vsphere_datacenter.dc.id + host_system_id = data.vsphere_host.host.id + wait_for_guest_net_timeout = 0 + wait_for_guest_ip_timeout = 0 + ovf_deploy { + remote_ovf_url = "%s" + } +} + +`, + os.Getenv("TF_VAR_VSPHERE_DATACENTER"), + os.Getenv("TF_VAR_VSPHERE_DATASTORE"), + os.Getenv("TF_VAR_VSPHERE_RESOURCE_POOL"), + os.Getenv("TF_VAR_VSPHERE_ESXI_HOST"), + vmName, + os.Getenv("TF_VAR_REMOTE_OVA_URL"), + ) +} + // Tests to skip until new features are developed. // Needs storage policy resource diff --git a/website/docs/r/virtual_machine.html.markdown b/website/docs/r/virtual_machine.html.markdown index 0481fac49..afa50a173 100644 --- a/website/docs/r/virtual_machine.html.markdown +++ b/website/docs/r/virtual_machine.html.markdown @@ -245,18 +245,21 @@ resource "vsphere_virtual_machine" "vm" { } } ``` -### Deploying VM from an OVF template -OVF templates can be deployed both from local system and remote URL into the +### Deploying VM from an OVF/OVA template +Ovf and ova templates can be deployed both from local system and remote URL into the vcenter using the `ovf_deploy` property. When deploying from local system, the -path to the .ovf template needs to be given and all other necessary files like .vmdk -files also should be present in the same directory as the .ovf file. While deploying, -the VM properties like `name`, `datacenter_id`, `resource_pool_id`, `datastore_id`, -`host_system_id`, `folder`, `vapp` can only be set. All other VM properties are taken from the OVF +path to the ovf or ova template needs to be provided. While deploying ovf, all other +necessary files like vmdk files also should be present in the same directory as the ovf file. +While deploying, the VM properties like `name`, `datacenter_id`, `resource_pool_id`, `datastore_id`, +`host_system_id`, `folder`, `scsi_controller_count`, `vapp` can only be set. All other VM properties are taken from the ovf template and setting them in the configuration file is redundant. -~> **NOTE:** Only the vApp properties which are pre-defined in the OVF template can be overwritten. +~> **NOTE:** Only the vApp properties which are pre-defined in the ovf template can be overwritten. vApp properties from scratch cannot be created. +~> **NOTE:** ovf deployment requires vCenter and is not supported on direct ESXi +connections. + ```hcl data "vsphere_datacenter" "dc" { name = "DC" @@ -274,19 +277,31 @@ data "vsphere_resource_pool" "pool" { data "vsphere_host" "host" { name = "hostip" - datacenter_id = "${data.vsphere_datacenter.dc.id}" + datacenter_id = data.vsphere_datacenter.dc.id +} + +data "vsphere_network" "network" { + name = "VM Network" + datacenter_id = data.vsphere_datacenter.datacenter.id } resource "vsphere_virtual_machine" "vmFromLocalOvf" { name = "vm1" - resource_pool_id = data.vsphere_resource_pool.pool.id - datastore_id = data.vsphere_datastore.datastore.id - host_system_id = data.vsphere_host.host.id + resource_pool_id = data.vsphere_resource_pool.pool.id + datastore_id = data.vsphere_datastore.datastore.id + host_system_id = data.vsphere_host.host.id wait_for_guest_net_timeout = 0 - wait_for_guest_ip_timeout = 0 - datacenter_id = data.vsphere_datacenter.dc.id + wait_for_guest_ip_timeout = 0 + datacenter_id = data.vsphere_datacenter.dc.id ovf_deploy { - local_ovf_path = "Full Path to local OVF template file" + local_ovf_path = "Full Path to local ovf/ova file" + disk_provisioning = "thin" + ip_protocol = "IPV4" + ip_allocation_policy = "STATIC_MANUAL" + ovf_network_map = { + "ESX-port-1" = data.vsphere_network.network.id + "ESX-port-2" = data.vsphere_network.network.id + } } vapp { properties = { @@ -296,15 +311,16 @@ resource "vsphere_virtual_machine" "vmFromLocalOvf" { } resource "vsphere_virtual_machine" "vmFromRemoteOvf" { - name = "vm2" - resource_pool_id = data.vsphere_resource_pool.pool.id - datastore_id = data.vsphere_datastore.datastore.id - host_system_id = data.vsphere_host.host.id + name = "vm2" + resource_pool_id = data.vsphere_resource_pool.pool.id + datastore_id = data.vsphere_datastore.datastore.id + host_system_id = data.vsphere_host.host.id wait_for_guest_net_timeout = 0 - wait_for_guest_ip_timeout = 0 - datacenter_id = data.vsphere_datacenter.dc.id + wait_for_guest_ip_timeout = 0 + datacenter_id = data.vsphere_datacenter.dc.id ovf_deploy { - remote_ovf_url = "Url to remote ovf file" + // Url to remote ovf/ova file + remote_ovf_url = "https://download3.vmware.com/software/vmw-tools/nested-esxi/Nested_ESXi7.0_Appliance_Template_v1.ova" } } ``` @@ -530,8 +546,8 @@ external disks on virtual machines that are assigned to datastore clusters. connections. * `ovf_deploy` - (Optional) When specified, the VM will be deployed from the - provided ovf template. See [creating a virtual machine from a - ovf template](#creating-vm-from-deploying-a-ovf-template) for more details. + provided ovf/ova template. See [creating a virtual machine from a + ovf/ova template](#creating-vm-from-deploying-a-ovf-ova-template) for more details. * `vapp` - (Optional) Optional vApp configuration. The only sub-key available is `properties`, which is a key/value map of properties for virtual machines imported from OVF or OVA files. See [Using vApp properties to supply OVF/OVA @@ -1328,34 +1344,33 @@ resource "vsphere_virtual_machine" "vm" { Note this option is mutually exclusive to `windows_options` - one must not be included if the other is specified. -### Creating VM from deploying a OVF template +### Creating VM from deploying a OVF/OVA template -The `ovf_deploy` block can be used to create a new virtual machine from an OVF -template either from local system or remote URL. While deploying from OVF, the VM -properties are taken from OVF and setting them in configuration file is not necessary. +The `ovf_deploy` block can be used to create a new virtual machine from an ovf/ova +template either from local system or remote URL. While deploying, the VM +properties are taken from ovf and setting them in configuration file is not necessary. -See the [Deploying from OVF example](#deploying-vm-from-an-ovf-template) for a usage synopsis. +See the [Deploying from OVF example](#deploying-vm-from-an-ovf-ova-template) for a usage synopsis. ~> **NOTE:** Changing any option in `ovf_deploy` after creation forces a new resource. -~> **NOTE:** ovf deployment requires vCenter and is not supported on direct ESXi -connections. - The options available in the `ovf_deploy` block are: -* `local_ovf_path` - (Optional) The absolute path to the ovf file in the local system. Make sure the - other necessary files like the .vmdk files are also in the same directory as the given ovf file. -* `remote_ovf_url` - (Optional) URL to the remote ovf file to be deployed. +* `local_ovf_path` - (Optional) The absolute path to the ovf/ova file in the local system. While deploying from ovf, + make sure the other necessary files like the .vmdk files are also in the same directory as the given ovf file. +* `remote_ovf_url` - (Optional) URL to the remote ovf/ova file to be deployed. ~> **NOTE:** Either `local_ovf_path` or `remote_ovf_url` is required, both can't be empty. * `ip_allocation_policy` - (Optional) The IP allocation policy. * `ip_protocol` - (Optional) The IP protocol. * `disk_provisioning` - (Optional) The disk provisioning. If set, all the disks in the deployed OVF will have - the same specified disk type (e.g., thin provisioned). + the same specified disk type (accepted values {thin, flat, thick, sameAsSource}). * `ovf_network_map` - (Optional) The mapping of name of network identifiers from the ovf descriptor to network UUID in the VI infrastructure. +* `allow_unverified_ssl_cert` - (Optional) Allow unverified ssl certificates while deploying ovf/ova from url. + Defaults true. ### Using vApp properties to supply OVF/OVA configuration