From 788f42272c6c63044635bfe641815b7a29a408fe Mon Sep 17 00:00:00 2001 From: SUMIT AGRAWAL Date: Sun, 14 Jun 2020 16:31:17 +0530 Subject: [PATCH 1/3] Adding ova deployment feature --- .../internal/helper/ovfdeploy/ovf_helper.go | 246 +++++++++++++++--- .../virtual_machine_ovfdeploy_subresource.go | 21 +- vsphere/resource_vsphere_virtual_machine.go | 89 +++---- .../resource_vsphere_virtual_machine_test.go | 86 +++++- website/docs/r/virtual_machine.html.markdown | 85 +++--- 5 files changed, 396 insertions(+), 131 deletions(-) diff --git a/vsphere/internal/helper/ovfdeploy/ovf_helper.go b/vsphere/internal/helper/ovfdeploy/ovf_helper.go index 38628540e..c494e034d 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, isDeployOva 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 !isDeployOva { + 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..87b5efa0e 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: "If to 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..2ffd272d8 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 is given, 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 + isDeployOva := false + fromLocal := true + filePath := localOvfPath + + if remoteOvfUrl != "" { + fromLocal = false + filePath = remoteOvfUrl + } + if strings.HasSuffix(filePath, ".ova") { + isDeployOva = 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, isDeployOva, 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, isDeployOva, 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..7987e8577 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 given. 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) If to allow unverified ssl certificates while deploying ovf/ova from url. + Defaults true. ### Using vApp properties to supply OVF/OVA configuration From 59cc2df374db1483e91171dff3f2c0c85593a091 Mon Sep 17 00:00:00 2001 From: SUMIT AGRAWAL Date: Tue, 16 Jun 2020 11:37:47 +0530 Subject: [PATCH 2/3] fixing typos and minor changes in ova deployment. --- .../virtual_machine_ovfdeploy_subresource.go | 2 +- vsphere/resource_vsphere_virtual_machine.go | 12 ++++++------ website/docs/r/virtual_machine.html.markdown | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/vsphere/internal/vmworkflow/virtual_machine_ovfdeploy_subresource.go b/vsphere/internal/vmworkflow/virtual_machine_ovfdeploy_subresource.go index 87b5efa0e..0a219512e 100644 --- a/vsphere/internal/vmworkflow/virtual_machine_ovfdeploy_subresource.go +++ b/vsphere/internal/vmworkflow/virtual_machine_ovfdeploy_subresource.go @@ -47,7 +47,7 @@ func VirtualMachineOvfDeploySchema() map[string]*schema.Schema { Type: schema.TypeBool, Optional: true, Default: true, - Description: "If to allow unverified ssl certificates while deploying ovf/ova from url.", + 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 2ffd272d8..7fd9d74ca 100644 --- a/vsphere/resource_vsphere_virtual_machine.go +++ b/vsphere/resource_vsphere_virtual_machine.go @@ -956,7 +956,7 @@ func resourceVSphereVirtualMachineCustomizeDiff(d *schema.ResourceDiff, meta int 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 is given, please specify only one source") + 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) { @@ -1324,7 +1324,7 @@ func resourceVsphereMachineDeployOvfAndOva(d *schema.ResourceData, meta interfac remoteOvfUrl := d.Get("ovf_deploy.0.remote_ovf_url").(string) // check if Ovf or Ova is to be deployed from local/remote - isDeployOva := false + deployOva := false fromLocal := true filePath := localOvfPath @@ -1333,7 +1333,7 @@ func resourceVsphereMachineDeployOvfAndOva(d *schema.ResourceData, meta interfac filePath = remoteOvfUrl } if strings.HasSuffix(filePath, ".ova") { - isDeployOva = true + deployOva = true } log.Printf("[DEBUG] VM is being deployed from ovf/ova template %s", filePath) @@ -1392,9 +1392,9 @@ func resourceVsphereMachineDeployOvfAndOva(d *schema.ResourceData, meta interfac DiskProvisioning: d.Get("ovf_deploy.0.disk_provisioning").(string), } - ovfDescriptor, err := ovfdeploy.GetOvfDescriptor(filePath, isDeployOva, fromLocal, allowUnverifiedSSL) + 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) + return nil, fmt.Errorf("error while reading the ovf file %s, %s ", filePath, err) } if ovfDescriptor == "" { @@ -1409,7 +1409,7 @@ func resourceVsphereMachineDeployOvfAndOva(d *schema.ResourceData, meta interfac } log.Print(" [DEBUG] start deploying from ovf/ova Template") - err = ovfdeploy.DeployOvfAndGetResult(ovfCreateImportSpecResult, poolObj, folderObj, hostObj, filePath, isDeployOva, fromLocal, allowUnverifiedSSL) + err = ovfdeploy.DeployOvfAndGetResult(ovfCreateImportSpecResult, poolObj, folderObj, hostObj, filePath, deployOva, fromLocal, allowUnverifiedSSL) if err != nil { return nil, fmt.Errorf("error while importing ovf/ova template, %s", err) } diff --git a/website/docs/r/virtual_machine.html.markdown b/website/docs/r/virtual_machine.html.markdown index 7987e8577..afa50a173 100644 --- a/website/docs/r/virtual_machine.html.markdown +++ b/website/docs/r/virtual_machine.html.markdown @@ -248,7 +248,7 @@ resource "vsphere_virtual_machine" "vm" { ### 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 or ova template needs to be given. While deploying ovf, all other +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 @@ -1369,7 +1369,7 @@ The options available in the `ovf_deploy` block are: 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) If to allow unverified ssl certificates while deploying ovf/ova from url. +* `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 From eefc17dde5db16b801528c67c6a31f1f698ee821 Mon Sep 17 00:00:00 2001 From: SUMIT AGRAWAL Date: Tue, 16 Jun 2020 12:07:31 +0530 Subject: [PATCH 3/3] changing variable isDeployOva to deployOva --- vsphere/internal/helper/ovfdeploy/ovf_helper.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vsphere/internal/helper/ovfdeploy/ovf_helper.go b/vsphere/internal/helper/ovfdeploy/ovf_helper.go index c494e034d..304f3874e 100644 --- a/vsphere/internal/helper/ovfdeploy/ovf_helper.go +++ b/vsphere/internal/helper/ovfdeploy/ovf_helper.go @@ -42,7 +42,7 @@ func (pr *ProgressReader) Read(p []byte) (n int, err error) { } func DeployOvfAndGetResult(ovfCreateImportSpecResult *types.OvfCreateImportSpecResult, resourcePoolObj *object.ResourcePool, - folder *object.Folder, host *object.HostSystem, filePath string, isDeployOva bool, fromLocal bool, allowUnverifiedSSL bool) error { + folder *object.Folder, host *object.HostSystem, filePath string, deployOva bool, fromLocal bool, allowUnverifiedSSL bool) error { var currBytesRead int64 = 0 var totalBytes int64 = 0 @@ -91,7 +91,7 @@ func DeployOvfAndGetResult(ovfCreateImportSpecResult *types.OvfCreateImportSpecR if ovfFileItem.DeviceId != deviceObj.ImportKey { continue } - if !isDeployOva { + if !deployOva { if fromLocal { err = uploadDisksFromLocal(filePath, ovfFileItem, deviceObj, &currBytesRead) } else {