Monday, March 1, 2021

Deploy VCSA Appliance with Terraform

I am back to an older project involving VMware products and Terraform. For those of you new to the subject, Terraform is an open source infrastructure as code tool developed by HashiCorp. It allows to define the entire infrastructure in a language called HashiCorp Configuration Language (HCL) and JSON files (where HCL is not enough). 

The interest for Terraform is its ability to easily deliver infrastructure across different infrastructures: public cloud, private cloud, Kubernetes. You write your configuration files, test it (with plan) and then you apply it to the infrastructure to get your resources deployed. There are other software tools that can be used such as HashiCorp Vault which is a secret management solution that can be consumed programmatically. In my example I will be using Vault to store the passwords required for setting up VCSA. 

In this example we will use Terraform to update the VCSA JSON template with values provided in a variable file and then run the VCSA cli installer. So we are not using the vSphere provider, rather local provider for modifying the template file and null provider to run a local command. I chose this example though because it is something I struggled to get it working. 

I've used the following simple project structure:

Templates folder contains VCSA modified template. Although all .tf files could be made into one (, I prefer this way of making the code more readable (and yes, has variables and has the Vault provider definition and the keys to secrets) defines 2 resources: update a template file and a command to execute 

resource "local_file" "vcsa_json" {
    content = templatefile (
              vc_fqdn = var.vcenterserver,
              vc_user = var.vcenterserver_user
              vc_user_pass =["value"],
              vm_network = var.pg_mgmt,
              vdc = var.vdc,
              datastore = var.datastore,
              host =,
              cluster = var.cluster,
              vcsa_name = element(split(".", var.vcsa_fqdn),0),
              vcsa_fqdn = var.vcsa_fqdn,
              vcsa_ip = var.vcsa_ip,
              prefix = var.prefix,
              gateway = var.gateway,
              dns = var.dns,
              vcsa_root_pass =["value"],
              ntp_servers = var.ntp,
              sso_password =["value"]
    filename = var.config_file_path

resource "null_resource" "vcsa_install" {
  provisioner "local-exec" {
    command = "${var.installcmd_file_path}/vcsa-deploy install --accept-eula 
            --acknowledge-ceip --no-esx-ssl-verify ${var.config_file_path}"

Local_file resource takes the template given by template_file_path variable and creates a configuration file at the path given in config_file_path variable. Null_resource executes a local command, in this case vcsa-deploy command to which we input updated configuration file. 

Within the template file you can see references to variables from (var.something) and also to data from (data.vault_generic_secret.some_path). Let's look at the the two files. 

variable "template_file_path" {
  description = "JSON template file path"
  type = string
  default = "templates/vcsa70_embedded_vCSA_on_VC.json"

variable "config_file_path" {
  description = "vcsa configuration JSON file path"
  type = string
  default = "/data/build/vcsa01_embedded_vCSA_on_VC.json"

variable "installcmd_file_path" {
  description = "command line file path"
  type = string
  default = "/data/VMware-VCSA-all-7.0.1-17491101/vcsa-cli-installer/lin64"

variable "vcsa_fqdn" {
  description = "vcsa hostname"
  default = "vcsa01.mylab.local"

variable "vcsa_ip" {
  description = "vcsa ip address"
  default = ""

variable "prefix" {
  description = "IP prefix"
  default = "24"

Each variable is defined by a name and a value. It can also have a description and a type (Please note that not all variables have been posted in this listing)

provider "vault" {
    address = ""
    token = "ABCD"
    skip_tls_verify = true

# vcsa deploy
data "vault_generic_secret" "vcsa_admin" {
    path = "kv-vmware-stgdev/administrator@vsphere.local"

data "vault_generic_secret" "vcsa_root" {
    path = "kv-vmware-stgdev/root"

The file contains the Vault provider definition and two keys for the VCSA admin and root passwords. 

template file (vcsa70_embedded_vCSA_on_VC.json) 

The values from and are updated in the template. To be able to update the default  template, you need to modify it first by adding keys that can be interpreted by Terraform provider. In my case I took the VCSA 7.0 embedded template and changed it as following:

    "__version": "2.13.0",
    "__comments": "Sample template to deploy a vCenter Server Appliance with an embedded Platform Services Controller on a vCenter Server instance.",
    "new_vcsa": {
        "vc": {
            "__comments": [
                "'datacenter' must end with a datacenter name, and only with a datacenter name. ",
                "'target' must end with an ESXi hostname, a cluster name, or a resource pool name. ",
                "The item 'Resources' must precede the resource pool name. ",
                "All names are case-sensitive. ",
                "For details and examples, refer to template help, i.e. vcsa-deploy {install|upgrade|migrate} --template-help"
            "hostname": "${vc_fqdn}",
            "username": "${vc_user}",
            "password": "${vc_user_pass}",
            "deployment_network": "${vm_network}",
            "datacenter": [
            "datastore": "${datastore}",
            "target": [
        "appliance": {
            "__comments": [
                "You must provide the 'deployment_option' key with a value, which will affect the vCenter Server Appliance's configuration parameters, such as the vCenter Server Appliance's number of vCPUs, the memory size, the storage size, and the maximum numbers of ESXi hosts and VMs which can be managed. For a list of acceptable values, run the supported deployment sizes help, i.e. vcsa-deploy --supported-deployment-sizes"
            "thin_disk_mode": true,
            "deployment_option": "small",
            "name": "${vcsa_name}"
        "network": {
            "ip_family": "ipv4",
            "mode": "static",
            "system_name": "${vcsa_fqdn}",
            "ip": "${vcsa_ip}",
            "prefix": "${prefix}",
            "gateway": "${gateway}",
            "dns_servers": [
        "os": {
            "password": "${vcsa_root_pass}",
            "ntp_servers": "${ntp_servers}",
            "ssh_enable": false
        "sso": {
            "password": "${sso_password}",
            "domain_name": "vsphere.local"
    "ceip": {
        "description": {
            "__comments": [
                "++++VMware Customer Experience Improvement Program (CEIP)++++",
                "VMware's Customer Experience Improvement Program (CEIP) ",
                "provides VMware with information that enables VMware to ",
                "improve its products and services, to fix problems, ",
                "and to advise you on how best to deploy and use our ",
                "products. As part of CEIP, VMware collects technical ",
                "information about your organization's use of VMware ",
                "products and services on a regular basis in association ",
                "with your organization's VMware license key(s). This ",
                "information does not personally identify any individual. ",
                "Additional information regarding the data collected ",
                "through CEIP and the purposes for which it is used by ",
                "VMware is set forth in the Trust & Assurance Center at ",
                " . If you ",
                "prefer not to participate in VMware's CEIP for this ",
                "product, you should disable CEIP by setting ",
                "'ceip_enabled': false. You may join or leave VMware's ",
                "CEIP for this product at any time. Please confirm your ",
                "acknowledgement by passing in the parameter ",
                "--acknowledge-ceip in the command line.",
        "settings": {
            "ceip_enabled": false

If you look at resource definition you will see the same keys from JSON file between {}.

Now all the code is written down and it's a simple matter of running terraform plan and terraform apply.