I’ve been working on a project over the last couple of weeks that has enabled me to sharpen my Terraform skills. There is nothing better than learning by doing and there is also nothing better than continuously improving code through more advanced constructs and methods. As this project evolved it became apparent that I would need to be able to optimize the Terraform/PowerShell to more easily deploy VMs based on specific vSphere templates.

Rather than have one set of Terraform declarations per template (resulting in a lot of code duplication), or having the declaration variables tied to specific operating systems changing (resulting in more manual change) depending on the what was being deployed, I looked for a way to make it even more “singularly declarative”.

Where this became very handy was when I was looking to deploy VMs based on Linux Distro. 98% of the Terraform code is the same no matter if Ubuntu, or CentOS was being used. The only difference was the vSphere Template being used to clone the new VM from, the Template Password and also, in this case a remote-exec call that needed to be made to open a Firewall port.

To get this working I used Terraform variable maps. As you can see below, the idea behind using maps is to allow groupings of like variables in one block declaration. These map values are then fed through to the rest of the Terraform code. Below is an example of a maps.tf file that I have seperate to the variables.tf file. This was an easier way to logically seperate what was being configured using maps.

variable "vpshere_linux_distro" { description = "Select the Linux Distro" } variable "remote_exec" { default = { ubuntu = "sudo ufw allow from any to any port 2500 proto tcp" centos = "firewall-cmd --zone=public --add-port=2500/tcp --permanent; firewall-cmd --reload" } } variable "linux_template" { default = { ubuntu = "TPM03-AS/TPM03-UBUNTU-ROOT" centos = "TPM03-AS/TPM03-CENTOS7-TEMPLATE" } } variable "linux_password" { default = { ubuntu = "password$12" centos = "Veeam1!" } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 variable "vpshere_linux_distro" { description = "Select the Linux Distro" } variable "remote_exec" { default = { ubuntu = "sudo ufw allow from any to any port 2500 proto tcp" centos = "firewall-cmd --zone=public --add-port=2500/tcp --permanent; firewall-cmd --reload" } } variable "linux_template" { default = { ubuntu = "TPM03-AS/TPM03-UBUNTU-ROOT" centos = "TPM03-AS/TPM03-CENTOS7-TEMPLATE" } } variable "linux_password" { default = { ubuntu = "password$12" centos = "Veeam1!" } }

At the top I have a standard variable that is the only variable that changes and needs setting. If ubuntu is set as the vsphere_linux_distro then all the map values that are ubuntu = will be used. The same if that variable is set to centos.

# Proxy Configuration vpshere_linux_distro ="centos" ... 1 2 3 # Proxy Configuration vpshere_linux_distro = "centos" . . .

This is set in the terraform.tfvars file, and links back to the mappings. From here Terraform will lookup the linux_template variable and map it with the various mapped values.

data "vsphere_virtual_machine" "template" { name = "${lookup(var.linux_template, var.vpshere_linux_distro)}" datacenter_id = "${data.vsphere_datacenter.dc.id}" } 1 2 3 4 data "vsphere_virtual_machine" "template" { name = "${lookup(var.linux_template, var.vpshere_linux_distro)}" datacenter_id = "${data.vsphere_datacenter.dc.id}" }

The above data source that dictates what template is used builds the name from the lookup function of the base variable and the map value.

# Create a vSphere VM in the folder # resource "vsphere_virtual_machine" "VBR-PROXY" { # VM placement # count = "${var.vsphere_proxy_number}" name = "${var.vsphere_vm_name}-L${random_integer.priority.result}-${count.index + 1}" resource_pool_id = "${data.vsphere_resource_pool.resource_pool.id}" datastore_id = "${data.vsphere_datastore.datastore.id}" folder = "${var.vsphere_vm_folder}" tags = ["${data.vsphere_tag.tag.id}"] ... ... provisioner "remote-exec" { inline = [ "${lookup(var.remote_exec, var.vpshere_linux_distro)}", ] connection { type = "ssh" user = "root" password = "${lookup(var.linux_password, var.vpshere_linux_distro)}" } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 # Create a vSphere VM in the folder # resource "vsphere_virtual_machine" "VBR-PROXY" { # VM placement # count = "${var.vsphere_proxy_number}" name = "${var.vsphere_vm_name}-L${random_integer.priority.result}-${count.index + 1}" resource_pool_id = "${data.vsphere_resource_pool.resource_pool.id}" datastore_id = "${data.vsphere_datastore.datastore.id}" folder = "${var.vsphere_vm_folder}" tags = [ "${data.vsphere_tag.tag.id}" ] . . . . . . provisioner "remote-exec" { inline = [ "${lookup(var.remote_exec, var.vpshere_linux_distro)}" , ] connection { type = "ssh" user = "root" password = "${lookup(var.linux_password, var.vpshere_linux_distro)}" } } }

Above, the values we set in the maps are being used to execute the right command depending if it is Ubuntu or Centos and also to use the correct password depending on the linux_distro set. As mentioned, the declared variable can either be set in the terraform.tfvars file, or passed through at the time the plan is executed.

The result is a more controlled and easily managed way to use Terraform to deploy VMs from different pre-existing VM templates. The variable mappings can be built up over time and used as a complete library or different operating systems with different options. An other awesome feature of Terraform!

References:

https://www.terraform.io/docs/configuration-0-11/variables.html#maps

Share this: Twitter

LinkedIn

Reddit

WhatsApp



Like this: Like Loading...