Terraform is one of the most popular Infrastructure as Code (IaC) tool out there today and it should come as no surprise there is Terraform provider for vSphere which many of our customers have been using. In fact, VMware just recently released a couple more new providers (here and here) supporting VMware Cloud on AWS and NSX-T solutions respectively.
Although I have used Terraform and the vSphere provider in the past, it has not been my tool of choice for automation as it still lacks a number of basic vSphere capabilities which I require on a regular basis. The most common one being the ability to deploy a Virtual Appliance (OVA/OVF) which has been my biggest barrier and I know this has been a highly requested feature from the community as well.
In early May of this year, I noticed that v1.18 of the vSphere provider finally added support for OVA/OVF deployment and I was pretty excited to give this a try and may even have been the first to kick the tires on this feature? Although OVA/OVF support was added, it looks like support for customizing OVF properties which is commonly included as part of an OVA/OVF would only possible if you are cloning from an existing imported OVA/OVF image. One of the most common use case is to import an OVF/OVA from either your local computer or from a URL and it looks like this use case was not possible.
I filed two Github issues, one for supporting OVF properties for initial OVA/OVF deployment and another regarding a bug I ran into when importing OVA/OVF from a remote URL. Just yesterday, I got the good news that my feature request has been completed and I was given an early drop of the vSphere provider to try out this feature. I may have also hinted to the Engineering team to use my popular Nested ESXi Appliance OVA as a reference test implementation as I knew this was something many customers will want to deploy π
UPDATE (11/05/21) - Thanks to Ryan Johnson, it looks like there has been some changes to the Terraform Provider for vSphere in how to deploy OVF/OVA. I've gone ahead an updated the example below to reflect these changes, it certainly looks a bit more verbose than before, which is a bit unfortunate from readability standpoint.
UPDATE (06/23/20) - Support for OVA/OVF properties is now available as part of 1.20 of the Terraform Provider for vSphere
Here is a working example of deploying my Nested ESXi OVA both from my local computer as well as from a remote URL:
terraform { required_providers { vsphere = { source = "hashicorp/vsphere" version = ">= 2.0.2" } } required_version = ">= 1.0.10" } provider "vsphere" { user = "*protected email*" password = "VMware1!" vsphere_server = "192.168.30.3" allow_unverified_ssl = true } data "vsphere_datacenter" "datacenter" { name = "Primp-Datacenter" } data "vsphere_datastore" "datastore" { name = "sm-vsanDatastore" datacenter_id = data.vsphere_datacenter.datacenter.id } data "vsphere_compute_cluster" "cluster" { name = "Supermicro-Cluster" datacenter_id = data.vsphere_datacenter.datacenter.id } data "vsphere_resource_pool" "default" { name = format("%s%s", data.vsphere_compute_cluster.cluster.name, "/Resources/Workload") datacenter_id = data.vsphere_datacenter.datacenter.id } data "vsphere_host" "host" { name = "192.168.30.5" datacenter_id = data.vsphere_datacenter.datacenter.id } data "vsphere_network" "network" { name = "VM Network" datacenter_id = data.vsphere_datacenter.datacenter.id } data "vsphere_folder" "folder" { path = "/${data.vsphere_datacenter.datacenter.name}/vm/Workloads" } ## Remote OVF/OVA Source data "vsphere_ovf_vm_template" "ovfRemote" { name = "foo" disk_provisioning = "thin" resource_pool_id = data.vsphere_resource_pool.default.id datastore_id = data.vsphere_datastore.datastore.id host_system_id = data.vsphere_host.host.id remote_ovf_url = "https://download3.vmware.com/software/vmw-tools/nested-esxi/Nested_ESXi7.0u3_Appliance_Template_v1.ova" ovf_network_map = { "VM Network" : data.vsphere_network.network.id } } ## Local OVF/OVA Source data "vsphere_ovf_vm_template" "ovfLocal" { name = "foo" disk_provisioning = "thin" resource_pool_id = data.vsphere_resource_pool.default.id datastore_id = data.vsphere_datastore.datastore.id host_system_id = data.vsphere_host.host.id local_ovf_path = "/Volumes/Storage/Software/Nested_ESXi7.0u3c_Appliance_Template_v1.ova" ovf_network_map = { "VM Network" : data.vsphere_network.network.id } } ## Deployment of VM from Remote OVF resource "vsphere_virtual_machine" "vmFromRemoteOvf" { name = "Nested-ESXi-7.0-Terraform-Deploy-1" folder = trimprefix(data.vsphere_folder.folder.path, "/${data.vsphere_datacenter.datacenter.name}/vm") datacenter_id = data.vsphere_datacenter.datacenter.id datastore_id = data.vsphere_datastore.datastore.id host_system_id = data.vsphere_host.host.id resource_pool_id = data.vsphere_resource_pool.default.id num_cpus = data.vsphere_ovf_vm_template.ovfRemote.num_cpus num_cores_per_socket = data.vsphere_ovf_vm_template.ovfRemote.num_cores_per_socket memory = data.vsphere_ovf_vm_template.ovfRemote.memory guest_id = data.vsphere_ovf_vm_template.ovfRemote.guest_id scsi_type = data.vsphere_ovf_vm_template.ovfRemote.scsi_type nested_hv_enabled = data.vsphere_ovf_vm_template.ovfRemote.nested_hv_enabled dynamic "network_interface" { for_each = data.vsphere_ovf_vm_template.ovfRemote.ovf_network_map content { network_id = network_interface.value } } wait_for_guest_net_timeout = 0 wait_for_guest_ip_timeout = 0 ovf_deploy { allow_unverified_ssl_cert = false remote_ovf_url = data.vsphere_ovf_vm_template.ovfRemote.remote_ovf_url disk_provisioning = data.vsphere_ovf_vm_template.ovfRemote.disk_provisioning ovf_network_map = data.vsphere_ovf_vm_template.ovfRemote.ovf_network_map } vapp { properties = { "guestinfo.hostname" = "tf-nested-esxi-1.primp-industries.com", "guestinfo.ipaddress" = "192.168.30.180", "guestinfo.netmask" = "255.255.255.0", "guestinfo.gateway" = "192.168.30.1", "guestinfo.dns" = "192.168.30.1", "guestinfo.domain" = "primp-industries.com", "guestinfo.ntp" = "pool.ntp.org", "guestinfo.password" = "VMware1!23", "guestinfo.ssh" = "True" } } lifecycle { ignore_changes = [ annotation, disk[0].io_share_count, disk[1].io_share_count, disk[2].io_share_count, vapp[0].properties, ] } } ## Deployment of VM from Local OVF resource "vsphere_virtual_machine" "vmFromLocalOvf" { name = "Nested-ESXi-7.0-Terraform-Deploy-2" folder = trimprefix(data.vsphere_folder.folder.path, "/${data.vsphere_datacenter.datacenter.name}/vm") datacenter_id = data.vsphere_datacenter.datacenter.id datastore_id = data.vsphere_datastore.datastore.id host_system_id = data.vsphere_host.host.id resource_pool_id = data.vsphere_resource_pool.default.id num_cpus = data.vsphere_ovf_vm_template.ovfLocal.num_cpus num_cores_per_socket = data.vsphere_ovf_vm_template.ovfLocal.num_cores_per_socket memory = data.vsphere_ovf_vm_template.ovfLocal.memory guest_id = data.vsphere_ovf_vm_template.ovfLocal.guest_id scsi_type = data.vsphere_ovf_vm_template.ovfLocal.scsi_type nested_hv_enabled = data.vsphere_ovf_vm_template.ovfLocal.nested_hv_enabled dynamic "network_interface" { for_each = data.vsphere_ovf_vm_template.ovfLocal.ovf_network_map content { network_id = network_interface.value } } wait_for_guest_net_timeout = 0 wait_for_guest_ip_timeout = 0 ovf_deploy { allow_unverified_ssl_cert = false local_ovf_path = data.vsphere_ovf_vm_template.ovfLocal.local_ovf_path disk_provisioning = data.vsphere_ovf_vm_template.ovfLocal.disk_provisioning ovf_network_map = data.vsphere_ovf_vm_template.ovfLocal.ovf_network_map } vapp { properties = { "guestinfo.hostname" = "tf-nested-esxi-2.primp-industries.com", "guestinfo.ipaddress" = "192.168.30.181", "guestinfo.netmask" = "255.255.255.0", "guestinfo.gateway" = "192.168.30.1", "guestinfo.dns" = "192.168.30.1", "guestinfo.domain" = "primp-industries.com", "guestinfo.ntp" = "pool.ntp.org", "guestinfo.password" = "VMware1!23", "guestinfo.ssh" = "True" } } lifecycle { ignore_changes = [ annotation, disk[0].io_share_count, disk[1].io_share_count, disk[2].io_share_count, vapp[0].properties, ] } }
With this upcoming capability, I think this really opens up the door for more possibilities and may even convince me to start using Terraform on a more regular basis π
I am sure many of you are asking when will this be available and happy to say very soon! It looks like the vSphere provider is roughly on a bi-weekly release cadence, so folks should expect to see this available in the next couple of weeks and I will update this blog post once it is available. I will be curious to learn what folks will be deploying and as always, if you have any feedback feel free to leave it here or better yet, file an issue directly in the Github repo.
I played with this about a month ago. The biggest blocker I still have is it only deploys to vCenter. I wanted to use Terraform to deploy directly to ESXi and ended up using govc instead, which then I just went back to pcli π
Does this have everything needed to deploy a VCSA now?
Please help here:
I'm facing the error when using ovf deploy remote url, retired multiple times but same error
Im recieving the Error: error while importing ovf/ova template, error while uploading the disk ubuntu-bionic-18.04-cloudimg.vmdk error while uploading the file ubuntu-bionic-18.04-cloudimg.vmdk Post "https://esxihostname/nfc/52664264-2800-4db7-abc4-91b312f3efe9/disk-0.vmdk": dial tcp: lookup esxihostname on 127.0.0.11:53: no such host
Please file any issues you have on the Github Terraform Provider for vSphere https://github.com/hashicorp/terraform-provider-vsphere/issues to get further assistance
Its a simple issue.. sorted out... Thanks for the help
https://github.com/hashicorp/terraform-provider-vsphere/issues/1096#issuecomment-653136056
In your example:
properties = {
"guestinfo.hostname" = "tf-nested-esxi-2.primp-industries.com",
What is the syntax here . What is "guestinfo" and what is "hostname" ?
How do I extract the name of the properties for my ova ? I used ovatool to fetch details about it but couldn't make out ?
The syntax is based on what is defined in the OVF properties, which you need to inspect using ovftool or by simply looking at the XML. If its not clear, I suggest you take a look at my example and the Nested ESXi Virtual Appliance to get an idea. As a side note, the property key is the same ones you'd use to pass into ovftool if you've used that to deploy any OVF/OVAs that contain OVF properties, so there is nothing new here with respect to Terraform
Hi William Lam,
I know this is a different topic, but I would like to know if it is possible to access ovf properties from vRA 8 blueprints? I have been trying to inject userdata metadata through guestinfo properties without any luck. Any help will be greatly appreciated.
Regards
Sorry, I don't work with vRA so I can't say what it can or can't do.
doesn't terraform need a vmdk file to deploy from a local ovf?
Did you test this on vSphere 7?
On the GitHub vSphere Provider for Terraform repository it mention that one of the requirements is vSphere 6.5
"Currently, this provider is not tested for vSphere 7, but plans are underway to add support."
Cant get this to working, when using exactly your example, the Nested ESXi gets deployed, but with 1gb ram and doesnt boot, when I specify 4GB of ram, it boots without NIC, when i specify to add NIC, it boots ok but none of the vapp config is applied, any idea what could cause this? running it against vcenter 7.0d
so, it seems that the initial VM is OK with all config, but once terraform does reconfiguration in vCenter, it sets it to 1cpu, 1gb ram and removes networking.
Is it possible to pull ova file via smb or another protocol?
No, itβs HTTP(s) or local. If you want to see other option, you can file Feature Enhancement in GitHub for TF provider
I managed to get it from datastore and thats enough for me , thanks for quick reply π
When you use the URL written under dataStore, which starts like ds:///vmfs...., it locates the file
What I don't get is, if you want to create a normal vm you don't have to specify host_system_id, then why I need to specify it to create a vm from ovf? :thinking:
Hmmm, dataStore thing didn't work actually π It was okay when terraform planing, but said " unsupported protocol scheme "ds" " while applying.
As mentioned earlier, there's only two supported methods: remotely over HTTP(s) endpoint OR locally from your filesystem. If you want to have other methods, you need to file an Issue/FR
Hi...the deployment of the vm using ovf is successful but how do I assign ip for that vm
I get the following error:
Error: error while creating vapp properties config unsupported vApp properties in vapp.properties: [guestinfo.password guestinfo.hostname guestinfo.dns guestinfo.netmask guestinfo.ntp guestinfo.ipaddress guestinfo.gateway guestinfo.domain]
How can I solve it?
In your config, what is the purpose of the data "vsphere_host" "host" {
name = "192.168.30.5"
This is required as part of the underlying vSphere API where an ESXi host must be specified. I'm not sure if the vSphere TF provider allows you to do something like automatically select a random ESXi host within a vSphere Cluster, you can certainly do this using PowerCLI and other imperative tools but not sure if this is possible with vSphere TF provider
I haven't seen any methods to auto select a ESXi host either. I just spun up my config in a non-Nested config. The master and worker nodes got distributed among the three ESXi hosts in the cluster. I used "extra_config" instead of vapp to deploy.