EN 
09.03.2026 Františka WELCOME IN MY WORLD

This website is originally written in the Czech language. Most content is machine (AI) translated into English. The translation may not be exact and may contain errors.

Tento článek si můžete zobrazit v originální české verzi. You can view this article in the original Czech version.
Terraform a VMware Cloud Director Provider pro začátečníky

Terraform and VMware Cloud Director Provider for Beginners

| Petr Bouška - Samuraj |
Terraform is an Infrastructure as Code (IaC) tool that allows you to define, deploy, and manage infrastructure in a declarative manner using configuration files. We can use it to create, for example, vApps, VMs, and networks. We will describe the basics of using Terraform, specifically focusing on working with VMware Cloud Director (VCD) cloud infrastructure. We will use the official Terraform Provider for VMware Cloud Director.
displayed: 1 138x (691 CZ, 447 EN) | Comments [0]

Note: I have only studied this area for a few days from official documentation and other materials on the internet. The description contains basic information. There are a number of general questions that are important for production use. Such as optimal division of configuration files, use of variables, naming conventions, etc. Some answers are provided in the official documentation, but it's not entirely consistent and doesn't align with various examples. This means further study and testing is needed.

HashiCorp Terraform

Terraform is a software tool created by HashiCorp. It allows you to use infrastructure as code (Infrastructure as Code) to provision and manage an organization's infrastructure. We can automate the lifecycle of cloud, private datacenter, and SaaS infrastructure.

Terraform enables you to safely and efficiently build, change, and version infrastructure. For declarative description, it uses the HashiCorp Configuration Language (HCL), alternatively also JSON-compatible syntax. We can create resources (such as vApps, networks, VMs, FW) and repeat deployment in different environments with consistent configuration. Terraform compares the description with the current state and makes only necessary changes. We can integrate into CI/CD pipeline (DevOps processes).

Terraform State

Using Terraform, we can define new resources (objects) that will be created in the infrastructure after application. Terraform stores the state State of managed infrastructure and configuration in the terraform.tfstate file. It is used for mapping resources declared in configuration to actual objects in the target infrastructure. It contains information about identities and attributes of created resources. Using the state, Terraform determines the differences between the current and desired state that need to be applied.

Terraform State file terraform.tfstate

Terraform expects that it is the only one creating (or destroying) objects and stores their identity in the state. So there is a one-to-one mapping between configured resource instances and remote objects. If this is not the case, we have commands available for state recovery, import of externally created objects, forgetting objects, or basic state editing. These are more advanced operations that this article does not cover.

The state file is therefore important and we should not lose it (if multiple people are working with Terraform, we should share it). When an object exists in the state file but is not found in the configuration, it will be removed during application. If we create an object configuration (resource block) that already exists in the infrastructure but we don't have information about it in the state, an error will occur during application.

Error: error creating vApp Demo-vApp: error executing task request: error instantiating a new vApp:: API Error: 400: [ ]
 The VMware Cloud Director entity Demo-vApp already exists.

Note: If we define only a new object in the configuration that we want to create in the infrastructure, Terraform works only with it and ignores existing infrastructure objects. In other words, it works only with objects described in configuration files.

Official Documentation

Installation (on Windows)

Terraform CLI

Terraform can run on various operating systems such as Windows, macOS, or Linux (or using Windows Subsystem for Linux). From the official page Install Terraform we download the latest version (at the time of writing 1.14.4). On Windows, it's a single EXE file that we save somewhere on the disk and ideally add its path to the PATH system environment variable.

We can verify the version on the command line:

C:\>terraform -version
Terraform v1.14.4
on windows_amd64

Microsoft Visual Studio Code (VS Code)

As an editor for Terraform (HCL) files, Visual Studio Code is very commonly used. There is an official HashiCorp Terraform extension for it. It highlights syntax, has auto-completion, validation, etc. We download the installation from the official site Visual Studio Code.

After installation and launch, we install extensions (in the left panel, click on the Extensions icon (Ctrl+Shift+X)):

  • HashiCorp Terraform
  • HashiCorp HCL

VMware Cloud Director Provider

Terraform uses binary plugins called providers to interact with cloud services, SaaS, and other APIs. Terraform configuration must declare which providers it requires so that Terraform can install and use them. HashiCorp maintains the Terraform Registry, from which we can obtain public providers and modules.

There is an official VMware Cloud Director Provider vmware/vcd. The current version 3.14 supports Cloud Director (VCD) versions 10.4, 10.5, and 10.6. Documentation VMware Cloud Director Provider 3.14. It allows you to define Cloud Director infrastructure as code in Terraform configuration files. We can define resources that are automatically provisioned or deprovisioned.

Our Terraform Project for VMware Cloud Director (VCD)

Terraform uses configuration files in HCL language, which are text files with the .tf extension. When we perform operations using Terraform CLI, all configuration files from the current directory are loaded and dependencies in our configuration are resolved. This allows organizing configuration into multiple files, which is recommended. File naming and content division is up to us. Certain examples are in the Terraform documentation and tutorials.

On disk, we create a folder for our VCD project (in the example, it's d:\VCD-terraform). Here we will create individual configuration files. In VS Code, we open the created folder using File - Open Folder.

In our example, we will create files

  • terraform.tf - Terraform configuration and VMware Cloud Director Provider
  • variables.tf - variable name definitions
  • terraform.tfvars - variable value assignments
  • output.tf - output of information about existing infrastructure objects
  • main.tf - resource configuration, creating infrastructure objects in VCD
Visual Studio Code - Terraform project - terraform.tf file

Terraform and VCD Provider Configuration

We create a terraform.tf file, into which we insert the configuration of Terraform itself (terraform block), including specifying which providers to install. We add the configuration of the VMware Cloud Director provider (provider block), which we could also place in another file.

# Version and providers requirement
terraform {
  required_version = ">= 1.5.0"

  required_providers {
    vcd = {
      source  = "vmware/vcd"
      version = "~> 3.14"
    }
  }
}

# Configure the VMware Cloud Director Provider
provider "vcd" {
  user                 = var.vcd_user
  password             = var.vcd_pass
  auth_type            = "integrated"
  org                  = var.vcd_org
  vdc                  = var.vcd_vdc
  url                  = var.vcd_url
  max_retry_timeout    = var.vcd_max_retry_timeout
  allow_unverified_ssl = var.vcd_allow_unverified_ssl
  logging              = var.vcd_log
  logging_file         = var.vcd_log_file
}

Variable Definitions

In many cases (repeated data, reusability), it is recommended not to insert values directly into configuration files, but to use variables. We must define them first and then assign their value. We have already used variables in the VCD provider configuration. For definition, we create a variables.tf file. In a variable definition, we can specify a number of parameters, such as type, validation, description, but everything is optional.

We can use only an empty definition variable <LABEL> { }. We reference variables in the configuration using var.<NAME>.

# Logging
variable "vcd_log" {
  description = "Enable Logging"
  type = bool
}
variable "vcd_log_file" {
  description = "Logging File"
  type = string
}

# VMware Cloud Director variables
variable "vcd_user" {
  description = "VCD username"
  type = string
}
variable "vcd_pass" {
  description = "VCD username password"
  type = string
  sensitive = true
}
variable "vcd_org" {
  description = "VMware Cloud Director Organization"
  type = string
}
variable "vcd_vdc" {
  description = "Virtual Data Center"
  type = string
}
variable "vcd_url" {
  description = "VCD API URL"
  type = string
}
variable "vcd_max_retry_timeout" {
  type = number
}
variable "vcd_allow_unverified_ssl" {
  description = "Allow unverified SSL"
  type = bool
}
variable "vcd_catalog_name" { 
  description = "VCD Catalog"
  type = string
}
variable "vcd_template_name" {
  description = "Catalog Template"
  type = string
}

Assigning Values to Variables

Assigning values to variables can be done in several ways. For example, using a parameter when calling Terraform CLI, using environment variables, or a file with variable definitions. Here we will use the terraform.tfvars file.

# Logging
vcd_log = "false"
vcd_log_file = "vcd-debug.log"

# VMware Cloud Director Connection Variables
vcd_org = "Firma"
vcd_vdc = "Firma vDC"
vcd_url = "https://server.domain.com/api"
vcd_user = "test2"
vcd_pass = "XXX"
vcd_max_retry_timeout = "240"
vcd_allow_unverified_ssl = false

# Catalog
vcd_catalog_name = "Firma_catalog"
vcd_template_name = "template-Alma"

Connecting to VMware Cloud Director

Login Credentials

To connect to VMware Cloud Director, credentials with sufficient permission (role) are required. This is of course a very sensitive matter. Here we show the simplest method - a specially created user and entering the username and password. We can also use Bearer or API token or SAML. If we enter the credentials in a file, we must protect this file and not store it in Git.

Alternatively, we can enter variables (such as password) at runtime:

terraform apply -var vcd_pass = "XXX"

Connection Parameters

In this article, we are connecting to a specific organization (tenant) and a specific VDC. We are not a Sys Admin managing multiple organizations. When configuring resources, we can specify Org and VDC. We will not do that, so these values set in the provider will be used.

In the connection parameters to VMware Cloud Director, we must enter the correct details of our environment. At what address (URL) the Cloud Director API runs for Terraform connection. The name of our organization and Virtual Data Center in which the API operations will be performed. The organization name can be found under Administration - Settings - General - Organization name (not Organization full name). The vDC name can be found under Data Centers - our vDC - Settings - General - Name.

Organization name is case sensitive. If we don't enter the name correctly, we will get an error that the organization was not found.

Error: org FIrma not found: [ENF] entity not found

If we encounter problems, we can enable logging of API calls (requests and responses, plus debug) to a file in the provider.

Terraform Workspace Initialization

When we have the basic Terraform configuration ready, we must perform working directory initialization for the first time. The specified provider will be downloaded. We perform initialization with the CLI command terraform init, which must be run in the project folder. In VS Code, we can use the terminal (display View - Terminal).

PS D:\VCD-terraform> terraform init
Initializing the backend...
Initializing provider plugins...
- Finding vmware/vcd versions matching "~> 3.14"...
- Installing vmware/vcd v3.14.1...
- Installed vmware/vcd v3.14.1 (signed by a HashiCorp partner, key ID 8BF53DB49CDB70B0)
Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://developer.hashicorp.com/terraform/cli/plugins/signing
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

Configuration Validation

We can perform syntax validation and internal consistency of our configuration. Execution does not occur, only code checking.

PS D:\VCD-terraform> terraform validate
Success! The configuration is valid.

VMware Cloud Director Resources

A Resource is any infrastructure object that we want to create or manage. Resource types depend on installed providers. In each resource, we can use various arguments that define its properties.

The VMware Cloud Director Provider offers a number of resources (detailed description can be found in the documentation), for example:

Querying Information About Existing Objects (Resources)

Data Sources fetches data from the provider but do not create or modify resources. The data <TYPE> <LABEL> block is used. Type specifies the data source (resource) type. Label specifies the object name (label). We can then reference the fetched data using data.<type>.<label>.<attribute>. Inside the block, we must use (in our examples) at least the name argument, where we specify the resource name of the given type.

Fetched (or configured) information can be displayed on the command line using the output <LABEL> block. Using the required value argument, we specify the returned value (it can be computed). Here we reference the loaded object and can display it entirely or some of its attributes.

As an example, we create an output.tf file (commonly used as output for modules), where we load information about the vApp Demo-vApp and display the list of VM names found in it.

# read vApp details
data "vcd_vapp" "my-vapp" {
  name = "Demo-vApp"
}

# display vApp details
output "vapp-details" {
  value = data.vcd_vapp.my-vapp.vm_names
}

Configuration Evaluation (terraform plan) - Loading and Displaying Data

When we have the configuration ready (whether fetches data sources or creating resources), the next step is to use the Terraform CLI command terraform plan. It evaluates the configuration and determines the desired state of all declared resources. Then it compares this state with the actual infrastructure objects. And displays a description of the changes needed to achieve the desired state. It does not make any infrastructure changes, only presents a change plan.

If there are output values (output block), they will be reported thanks to terraform plan.

PS D:\VCD-terraform> terraform plan  
data.vcd_vapp.my-vapp: Reading...
data.vcd_vapp.my-vapp: Read complete after 3s [id=urn:vcloud:vapp:8855943a-e16a-416e-bae5-0fd1dfc251e3]

Changes to Outputs:
  + vApp-details = [
      + "Demo-VM",
    ]

You can apply this plan to save these new output values to the Terraform state, without changing any real infrastructure.

-------------------------------------------------------------------------------------------------------------------------

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run
 "terraform apply" now.

We can also use the terraform apply command. If we don't have new resources in the configuration (resource block), only these values will be saved (fetched and outputs) to the State. No infrastructure changes occur.

PS D:\VCD-terraform> terraform apply
data.vcd_vapp.my-vapp: Reading...
data.vcd_vapp.my-vapp: Read complete after 2s [id=urn:vcloud:vapp:8855943a-e16a-416e-bae5-0fd1dfc251e3]

Changes to Outputs:
  + vApp-details = [
      + "Demo-VM",
    ]

You can apply this plan to save these new output values to the Terraform state, without changing any real infrastructure.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

vApp-details = tolist([
  "Demo-VM",
])

Saved outputs (to State) can be displayed using terraform output.

PS D:\VCD-terraform> terraform output
vApp-details = tolist([
  "Demo-VM",
])

Creating New Objects (provisioning infrastructure)

For resource configuration, the resource <TYPE> <LABEL> block is used. We must specify the resource type (Type) from our provider. And a unique resource name - object label (Label), which is used to track the resource in the state file. In the configuration, we can reference the resource using <type>.<label>.<attribute>. Inside the resource block, we configure the arguments of the given resource.

In our project, we create a main.tf file, into which we insert various configuration blocks for different objects.

Creating a New vApp

We configure a new vApp Demo-vApp.

# create vApp
resource "vcd_vapp" "Demo" {
  name = "Demo-vApp"
}

Attaching Network

We attach an existing organization network Demo_net to the vApp. We set a dependency that the vApp must be completed first before Terraform starts working on the network.

# add vApp network attached to existing Org network
resource "vcd_vapp_org_network" "vappOrgNet" {
  vapp_name        = vcd_vapp.Demo.name
  org_network_name = "Demo_net"
  depends_on       = [ vcd_vapp.Demo ]
}

Loading Template

We retrieve information about our catalog and find a specific template in it, from which we will create a VM.

# read Catalog
data "vcd_catalog" "my-catalog" {
  name = var.vcd_catalog_name
}

# read Catalog Template
data "vcd_catalog_vapp_template" "my-vapp-template" {
  catalog_id = data.vcd_catalog.my-catalog.id
  name       = var.vcd_template_name
}

Creating VM from Template

We create a VM from a predefined template. We define its parameters (update will occur after creation). We assign a network with a manually entered IP address (must fall within the given range). After VM creation, it automatically starts (we can change this with the power_on argument).

# create VM from template wtih manual IP assignment
resource "vcd_vapp_vm" "DemoVM" {
  vapp_name        = vcd_vapp.Demo.name
  name             = "Demo-VM"
  computer_name    = "Demo-VM"
  vapp_template_id = data.vcd_catalog_vapp_template.my-vapp-template.id
  memory           = 2048 
  cpus             = 2
  cpu_cores        = 1
  depends_on       = [ vcd_vapp_org_network.vappOrgNet ]

  network {
    type               = "org"
    name               = vcd_vapp_org_network.vappOrgNet.org_network_name
    ip_allocation_mode = "MANUAL"
    ip                 = "172.30.21.100"
    is_primary         = true
  }
}

Applying Configuration Changes (terraform apply)

The last step is (execution) applying changes according to the Terraform plan, i.e., actually creating or modifying objects (or destroying them) according to our configuration.

User permissions are important when connecting to Cloud Director (VCD). When we don't want to use the Organization Administrator role, for example. I tried vApp Author, who could create a vApp but couldn't load the catalog (even though they should have those permissions). An error was displayed

Error: [catalog read DS] error retrieving catalog Firma_catalog: [ENF] entity not found - no records found for catalog
 'Firma_catalog' in org 'Firma'

Note: Before applying, we can first use the Terraform CLI command terraform plan and check the output.

To execute operations proposed by the Terraform plan, the Terraform CLI command terraform apply is used. The command creates an execution plan, prompts us to approve this plan (we enter yes), and performs the specified operations.

Visual Studio Code - Terraform project - terminal - terraform apply
PS D:\VCD-terraform> terraform apply
data.vcd_catalog.my-catalog: Reading...
data.vcd_catalog.my-catalog: Read complete after 3s [id=urn:vcloud:catalog:e9e5c417-cfbc-4d4d-8136-6623b8615d6d]
data.vcd_catalog_vapp_template.my-vapp-template: Reading...
data.vcd_catalog_vapp_template.my-vapp-template: Read complete after 3s [id=urn:vcloud:vapptemplate:22ad75a3-bb77-4c36-8cee-8faf7
8b60a03]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following
 symbols:
  + create

Terraform will perform the following actions:

  # vcd_vapp.Demo will be created
  + resource "vcd_vapp" "Demo" {
      + href                   = (known after apply)
      + id                     = (known after apply)
      + inherited_metadata     = (known after apply)
      + metadata               = (known after apply)
      + name                   = "Demo-vApp"
      + power_on               = false
      + status                 = (known after apply)
      + status_text            = (known after apply)
      + vapp_network_names     = (known after apply)
      + vapp_org_network_names = (known after apply)
      + vm_names               = (known after apply)

      + lease (known after apply)

      + metadata_entry (known after apply)
  }

# vcd_vapp_org_network.vappOrgNet will be created
+ resource "vcd_vapp_org_network" "vappOrgNet" {
    + id                     = (known after apply)
    + is_fenced              = false
    + org_network_name       = "Demo_net"
    + reboot_vapp_on_removal = false
    + retain_ip_mac_enabled  = false
    + vapp_name              = "Demo-vApp"
  }

# vcd_vapp_vm.DemoAS will be created
+ resource "vcd_vapp_vm" "DemoVM" {
    + accept_all_eulas               = true
    + computer_name                  = "DemoVM"
    + consolidate_disks_on_create    = false
    + cpu_cores                      = 1
    + cpu_hot_add_enabled            = false
    + cpu_limit                      = (known after apply)
    + cpu_priority                   = (known after apply)
    + cpu_reservation                = (known after apply)
    + cpu_shares                     = (known after apply)
    + cpus                           = 2
    + description                    = (known after apply)
    + expose_hardware_virtualization = false
    + extra_config                   = (known after apply)
    + firmware                       = (known after apply)
    + hardware_version               = (known after apply)
    + href                           = (known after apply)
    + id                             = (known after apply)
    + imported                       = (known after apply)
    + inherited_metadata             = (known after apply)
    + internal_disk                  = (known after apply)
    + memory                         = 2048
    + memory_hot_add_enabled         = false
    + memory_limit                   = (known after apply)
    + memory_priority                = (known after apply)
    + memory_reservation             = (known after apply)
    + memory_shares                  = (known after apply)
    + metadata                       = (known after apply)
    + name                           = "DemoVM"
    + os_type                        = (known after apply)
    + placement_policy_id            = (known after apply)
    + power_on                       = true
    + prevent_update_power_off       = false
    + security_tags                  = (known after apply)
    + sizing_policy_id               = (known after apply)
    + status                         = (known after apply)
    + status_text                    = (known after apply)
    + storage_profile                = (known after apply)
    + vapp_id                        = (known after apply)
    + vapp_name                      = "Demo-vApp"
    + vapp_template_id               = "urn:vcloud:vapptemplate:22ad75a3-bb77-4c36-8cee-8faf78b60a03"
    + vm_type                        = (known after apply)

    + boot_options (known after apply)

    + customization (known after apply)

    + metadata_entry (known after apply)

    + network {
        + adapter_type                 = (known after apply)
        + connected                    = true
        + ip                           = "172.30.21.100"
        + ip_allocation_mode           = "MANUAL"
        + is_primary                   = true
        + mac                          = (known after apply)
        + name                         = "Demo_net"
        + secondary_ip                 = (known after apply)
        + secondary_ip_allocation_mode = (known after apply)
        + type                         = "org"
      }
  }

Plan: 3 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

vcd_vapp.Demo: Creating...
vcd_vapp.Demo: Creation complete after 6s [id=urn:vcloud:vapp:8855943a-e16a-416e-bae5-0fd1dfc251e3]
vcd_vapp_org_network.vappOrgNet: Creating...
vcd_vapp_org_network.vappOrgNet: Creation complete after 5s [id=urn:vcloud:network:3be9573d-b435-4f73-ad36-734aa010e910]
vcd_vapp_vm.DemoAS: Creating...
vcd_vapp_vm.DemoAS: Still creating... [00m10s elapsed]
vcd_vapp_vm.DemoAS: Still creating... [00m20s elapsed]
vcd_vapp_vm.DemoAS: Still creating... [00m30s elapsed]
vcd_vapp_vm.DemoAS: Still creating... [00m40s elapsed]
vcd_vapp_vm.DemoAS: Still creating... [00m50s elapsed]
vcd_vapp_vm.DemoAS: Still creating... [01m00s elapsed]
vcd_vapp_vm.DemoAS: Still creating... [01m10s elapsed]
vcd_vapp_vm.DemoAS: Still creating... [01m20s elapsed]
vcd_vapp_vm.DemoAS: Still creating... [01m30s elapsed]
vcd_vapp_vm.DemoAS: Still creating... [01m40s elapsed]
vcd_vapp_vm.DemoAS: Creation complete after 1m46s [id=urn:vcloud:vm:c33102c0-77d0-4455-a220-df52dcf19658]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
VMware Cloud Director - new vApp and VM created using Terraform

For terraform apply and terraform plan, we can use a number of parameters that affect its behavior. For example, skipping automatic state refresh.

terraform apply -refresh=false

Or limit the number of concurrent operations (default is 10).

terraform apply -parallelism=1

Destroying Objects (terraform destroy)

If we are testing or developing, we may want to deprovision the created objects. In Terraform CLI, we have the terraform destroy command available (actually terraform apply -destroy is used), which deletes all objects managed by the Terraform configuration.

Note: We can also use terraform plan -destroy.

By default, removing a vApp network from a vApp will not succeed if the vApp is running, and then the vApp won't be deleted either. In the network configuration (vcd_vapp_org_network), we can allow shutting down the parent vApp for network removal using:

reboot_vapp_on_removal = true
Author:

Related articles:

Infrastructure as Code - Terraform

Infrastructure as Code (IaC) tools allow you to define, deploy, and manage infrastructure in a declarative (or imperative) way using configuration files. We describe the resources (servers, networks, storage, etc.) in a text file that defines the desired state. The tool ensures that the real environment matches the definition. For now, we will focus on the Terraform tool.

Virtualization

Articles from popular topics about virtualization of servers and workstations.

If you want write something about this article use comments.

Comments

There are no comments yet.

Add comment

Insert tag: strong em link

Help:
  • maximum length of comment is 2000 characters
  • HTML tags are not allowed (they will be removed), you can use only the special tags listed above the input field
  • new line (ENTER) ends paragraph and start new one
  • when you respond to a comment, put the original comment number in squar brackets at the beginning of the paragraph (line)