This guide will discuss how you can automate your oVirt / RHEV VM creation with Terraform. It is not just the creation of VMs that you can use terraform for, but provisioning of other resources such as Disk, Network, Snapshots e.t.c. In our previous article, we discussed how you can use Terraform to automate your KVM infrastructure.

For installation of terraform, refer to:

Install Terraform on Ubuntu / Debian / CentOS / Fedora / Arch Linux

Setup Pre-requisite

You should have installed Terraform provider for oVirt / RHEV using the guide below.

How To Install Terraform oVirt / RHEV Plugin

This guide will discuss how you can automate your oVirt / RHEV VM automation with Terraform. It is not just the creation of VMs that you can use terraform for, but provisioning of other resources such as Disk, Network, Snapshots e.t.c. In our previous article, we discussed how you can use Terraform to automate your KVM infrastructure.

VM Template preparation

Ensure your VM template have cloud-init installed.

----- CentOS / RHEL -----
$ sudo yum -y update
$ sudo yum -y install epel-release
$ sudo yum install cloud-init cloud-utils-growpart
$ sudo systemctl enable --now cloud-init


----- Debian / Ubuntu -----
$ sudo apt-get -y update
$ sudo apt-get -y install cloud-init

Setup Pre-requisite

You should have installed Terraform provider for oVirt / RHEV using the guide below.

How To Install Terraform oVirt / RHEV Plugin

Among the resources supported by terraform oVirt provider are:

  • ovirt_cluster
  • ovirt_datacenter
  • ovirt_disk
  • ovirt_disk_attachment
  • ovirt_host
  • ovirt_mac_pool
  • ovirt_network
  • ovirt_snapshot
  • ovirt_storage_domain
  • ovirt_tag
  • ovirt_user
  • ovirt_vm
  • ovirt_vnic
  • ovirt_vnic_profile

The data sources are:

  • ovirt_authzs
  • ovirt_clusters
  • ovirt_datacenters
  • ovirt_disks
  • ovirt_hosts
  • ovirt_mac_pools
  • ovirt_networks
  • ovirt_nics
  • ovirt_storagedomains
  • ovirt_users
  • ovirt_vms
  • ovirt_vnic_profiles

The official documentation for Terraform oVirt provider provides more details on the usage of these resources and data sources. We’ll only cover the most basic and common use cases.

Creating Terraform modules

A module is a container for multiple resources that are used together. Modules can be used to create lightweight abstractions, so that you can describe your infrastructure in terms of its architecture, rather than directly in terms of physical objects.

In terms of structure, most commonly, modules use:

  • Input variables to accept values from the calling module.
  • Output values to return results to the calling module, which it can then use to populate arguments elsewhere.
  • Resources to define one or more infrastructure objects that the module will manage.

We’ll create two modules for ovirt_vm and ovirt_network. Let’s start by creating project folder.

$ mkdir -p ~/terraform/modules/{vms,disk}

Create module for VM creation

Create module for ovirt_vm resource which is used to manage VM resource within oVirt.

Create resource:

$ vim ~/terraform/modules/vms/main.tf

resource "ovirt_vm" "vm" {
  name                 = "${var.vm_name}"
  clone                = "false"
  high_availability    = "true"
  cluster_id           = "${var.cluster_id}"
  memory               = "${var.vm_memory}"
  template_id          = "${var.vm_template_id}"
  cores                = "${var.vm_cpu_cores}"
  sockets              = "${var.vm_cpu_sockets}"
  threads              = "${var.vm_cpu_threads}"

  initialization {
    authorized_ssh_key = "${var.vm_authorized_ssh_key}"
    host_name          = "${var.vm_hostname}"
    timezone           = "${var.vm_timezone}"
    user_name          = "${var.vm_user_name}"
    custom_script      = "${var.vm_custom_script}"
    dns_search         = "${var.vm_dns_search}"
    dns_servers        = "${var.vm_dns_servers}"

    nic_configuration {
      label              = "${var.vm_nic_device}"
      boot_proto         = "${var.vm_nic_boot_proto}"
      address            = "${var.vm_nic_ip_address}"
      gateway            = "${var.vm_nic_gateway}"
      netmask            = "${var.vm_nic_netmask}"
      on_boot            = "${var.vm_nic_on_boot}"
    }
  }
}

Create variables file:

$ vim ~/terraform/modules/vms/vars.tf

# Basic
variable "vm_name" {
  description = " A unique name for the VM"
  default     = ""
}
variable "cluster_id" {
  description = "The ID of cluster the VM belongs to"
  default     = ""
}
variable "vm_template_id" {
  description = "The ID of template the VM based on"
  default     = ""
}
variable "vm_memory" {
  description = "The amount of memory of the VM (in metabytes)"
  default     = "4096"
}
variable "vm_cpu_cores" {
  description = "The amount of cores"
  default     = "2"
}
variable "vm_cpu_sockets" {
  description = "The amount of sockets"
  default     = "1"
}
variable "vm_cpu_threads" {
  description = " The amount of threads"
  default     = "1"
}

# VM initialization

variable "vm_authorized_ssh_key" {
  description = "The ssh key for the VM"
  default     = ""
}
variable "vm_hostname" {
  description = "The hostname for the VM"
  default     = ""
}
variable "vm_timezone" {
  description = "The timezone for the VM"
  default     = ""
}
variable "vm_user_name" {
  description = "The user name for the VM"
  default     = ""
}
variable "vm_custom_script" {
  description = "Set the custom script for the VM"
  default     = ""
}
variable "vm_dns_search" {
  description = "The dns server for the VM"
  default     = ""
}
variable "vm_dns_servers" {
  description = "The dns server for the VM"
  default     = ""
}

# Initialization - Nic Configurations
variable "vm_nic_device" {
  description = "The vNIC to apply this configuration."
  default     = ""
}
variable "vm_nic_boot_proto" {
  description = "The boot protocol for the vNIC configuration."
  default     = "static"
}
variable "vm_nic_ip_address" {
  description = "The IP address for the vNIC"
  default     = ""
}
variable "vm_nic_gateway" {
  description = "The gateway for the vNIC"
  default     = ""
}
variable "vm_nic_netmask" {
  description = "The netmask for the vNIC"
  default     = ""
}
variable "vm_nic_on_boot" {
  description = "The flag to indicate whether the vNIC will be activated at VM booting"
  default     = "true"
}

Outputs file:

$ vim ~/terraform/modules/vms/outputs.tf

output "id" {
  description = "VM ID"
  value       = "${ovirt_vm.vm.id}"
}

Create module for Disk management

Here we’ll put the following resources in one module:

  • ovirt_disk – Used to manage a Disk resource within oVirt.
  • ovirt_disk_attachment – For managing Disk attachment resource within oVirt.

Create resources file:

$ vim ~/terraform/modules/disk/main.tf

resource "ovirt_disk" "disk" {
  name              = "${var.name}"
  alias             = "${var.name}"
  size              = "${var.size}"
  format            = "${var.format}"
  storage_domain_id = "${var.storage_domain_id}"
  sparse            = "${var.sparse}"
  shareable         = "${var.shareable}"
}

resource "ovirt_disk_attachment" "diskattachment" {
  vm_id                = "${var.vm_id}"
  disk_id              = "${ovirt_disk.disk.id}"
  active               = "${var.active}"
  bootable             = "${var.bootable}"
  interface            = "${var.interface}"
  read_only            = "${var.read_only}"
  use_scsi_reservation = "${var.use_scsi_reservation}"
}

For variables:

$ vim ~/terraform/modules/disk/var.tf

# Disk resource variables
variable "name" {
  default = ""
}
variable "size" {
  default = ""
}
variable "format" {
 default = "cow"
}
variable "storage_domain_id" {
  default = ""
}
variable "sparse" {
 default = "true"
}
variable "shareable" {
  default = "false"
}

# Disk attachment
variable "vm_id" {
 default = ""
}
variable "disk_id" {
  default = ""
}
variable "active" {
  default = "true"
}
variable "bootable" {
  default = "false"
}
variable "interface" {
  default = "virtio_scsi"
}
variable "pass_discard" {
  default = ""
}
variable "read_only" {
  default =  "false"
}
variable "use_scsi_reservation" {
  default = "false"
}

Using Terraform modules

Create the main terraform configuration defining oVirt provider.

$ mkdir  ~/terraform/ovirt
$ cd ~/terraform/ovirt
$ vim main.tf

provider "ovirt" {
  url = "${var.ovirt_url}"
  username  = "${var.ovirt_username}"
  password  = "${var.ovirt_password}"
}

terraform {
  backend "local" {
    path = "ovirt_terraform.tfstate"
  }
}

Create variables file

$ cd ~/terraform/ovirt
$ vim vars.tf

variable "ovirt_url" {
    description = "oVirt API URL"
    default = "https://ovirthostname/ovirt-engine/api"
}
variable "ovirt_username" {
    description = "oVirt Admin user"
    default     = "[email protected]"
}
variable "ovirt_password" {
    description = "oVirt Admin password"
    default     = "ovirtuserpassword"
}

Set correct values for access credentials. Once done. you can now create terraform resources using the modules we created.

$ cd ~/terraform/ovirt
$ vim main.tf

# Create VM call temp01
module "temp01" {
  source            = "../modules/vms"
  cluster_id        = "c5529b5d-1b4e-46e6-9cb1-5d44c28cd65b"
  vm_name           = "temp01"
  vm_hostname       = "temp01.example.com"
  vm_dns_servers    = "8.8.8.8"
  vm_dns_search     = "example.com"
  vm_memory         = "2048"
  vm_cpu_cores      = "2"
  vm_timezone       = "Africa/Nairobi"
  vm_template_id    = "fd4b04a9-74b5-4287-adbc-3d5c6711d53f"
  vm_nic_device     = "eth0"
  vm_nic_ip_address = "192.168.10.11"
  vm_nic_gateway    = "192.168.10.254"
  vm_nic_netmask    = "255.255.255.0"
}

Other parameters used in vm module variables can be set. Ensure you set correct variables.

When done, initialize project

$ cd ~/terraform/ovirt
$ terraform init
Initializing modules...
- temp01 in ../modules/vms

Initializing the backend...

Successfully configured the backend "local"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...

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.

Validate terraform configurations.

$ terraform validate
Success! The configuration is valid.

Plan your deployment

$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.


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

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # module.temp01.ovirt_vm.vm will be created
  + resource "ovirt_vm" "vm" {
      + clone             = true
      + cluster_id        = "c5529b5d-1b4e-46e6-9cb1-5d44c28cd65b"
      + cores             = 2
      + high_availability = true
      + id                = (known after apply)
      + memory            = 2048
      + name              = "temp01"
      + sockets           = 1
      + status            = (known after apply)
      + template_id       = "fd4b04a9-74b5-4287-adbc-3d5c6711d53f"
      + threads           = 1

      + initialization {
          + dns_search  = "example.com"
          + dns_servers = "8.8.8.8"
          + host_name   = "temp01.example.com"
          + timezone    = "Africa/Nairobi"

          + nic_configuration {
              + address    = "192.168.10.11"
              + boot_proto = "static"
              + gateway    = "192.168.10.254"
              + label      = "eth0"
              + netmask    = "255.255.255.0"
              + on_boot    = true
            }
        }
    }

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

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

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

Execute creation

$ terraform apply
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # module.temp01.ovirt_vm.vm will be created
  + resource "ovirt_vm" "vm" {
      + clone             = true
      + cluster_id        = "c5529b5d-1b4e-46e6-9cb1-5d44c28cd65b"
      + cores             = 2
      + high_availability = true
      + id                = (known after apply)
      + memory            = 2048
      + name              = "temp01"
      + sockets           = 1
      + status            = (known after apply)
      + template_id       = "fd4b04a9-74b5-4287-adbc-3d5c6711d53f"
      + threads           = 1

      + initialization {
          + dns_search  = "example.com"
          + dns_servers = "8.8.8.8"
          + host_name   = "temp01.example.com"
          + timezone    = "Africa/Nairobi"

          + nic_configuration {
              + address    = "192.168.10.11"
              + boot_proto = "static"
              + gateway    = "192.168.10.254"
              + label      = "eth0"
              + netmask    = "255.255.255.0"
              + on_boot    = true
            }
        }
    }

Plan: 1 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

The apply command will have output similar to below.

module.temp01.ovirt_vm.vm: Creating...
module.temp01.ovirt_vm.vm: Still creating... [10s elapsed]
module.temp01.ovirt_vm.vm: Still creating... [20s elapsed]
module.temp01.ovirt_vm.vm: Still creating... [30s elapsed]
module.temp01.ovirt_vm.vm: Still creating... [40s elapsed]
module.temp01.ovirt_vm.vm: Still creating... [50s elapsed]
module.temp01.ovirt_vm.vm: Still creating... [1m0s elapsed]
module.temp01.ovirt_vm.vm: Still creating... [1m10s elapsed]
module.temp01.ovirt_vm.vm: Still creating... [1m20s elapsed]
module.temp01.ovirt_vm.vm: Still creating... [1m30s elapsed]
module.temp01.ovirt_vm.vm: Still creating... [1m40s elapsed]
module.temp01.ovirt_vm.vm: Still creating... [1m50s elapsed]
module.temp01.ovirt_vm.vm: Still creating... [2m0s elapsed]
module.temp01.ovirt_vm.vm: Still creating... [2m10s elapsed]
module.temp01.ovirt_vm.vm: Creation complete after 2m11s [id=2ad55c71-2e1a-4ce0-89a0-cda845f83fa2]

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

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path: ovirt_terraform.tfstate

Login to your RHEV / oVirt environment and confirm the VM creation.

Modify main.tf to add more resources

Let’s update the file to create another VM and disk.

# Create VM call temp01
module "temp01" {
  source            = "../modules/vms"
  cluster_id        = "c5529b5d-1b4e-46e6-9cb1-5d44c28cd65b"
  vm_name           = "temp01"
  vm_hostname       = "temp01.example.com"
  vm_dns_servers    = "8.8.8.8"
  vm_dns_search     = "example.com"
  vm_memory         = "2048"
  vm_cpu_cores      = "2"
  vm_timezone       = "Africa/Nairobi"
  vm_template_id    = "fd4b04a9-74b5-4287-adbc-3d5c6711d53f"
  vm_nic_device     = "eth0"
  vm_nic_ip_address = "192.168.10.11"
  vm_nic_gateway    = "192.168.10.254"
  vm_nic_netmask    = "255.255.255.0"
}

module "temp02" {
  source            = "../modules/vms"
  cluster_id        = "c5529b5d-1b4e-46e6-9cb1-5d44c28cd65b"
  vm_name           = "temp02"
  vm_hostname       = "temp02.example.com"
  vm_dns_servers    = "8.8.8.8"
  vm_dns_search     = "example.com"
  vm_memory         = "2048"
  vm_cpu_cores      = "2"
  vm_timezone       = "Africa/Nairobi"
  vm_template_id    = "fd4b04a9-74b5-4287-adbc-3d5c6711d53f"
  vm_nic_device     = "eth0"
  vm_nic_ip_address = "192.168.10.12"
  vm_nic_gateway    = "192.168.10.254"
  vm_nic_netmask    = "255.255.255.0"
}

## Create and attach Disk to VM temp02
module "temp02_disk02" {
  source            = "../modules/vms"
  name              = "temp02_disk02"
  size              = "50"
  storage_domain_id = "a5542689-bd73-4f16-846e-697822e4ad2c"
  vm_id             = "${module.temp02.id}"
}

Run initialization.

$ terraform init

Apply configurations:

$ terraform plan
$ terraform apply

Destroying your infrastructure only requires one command

$ terraform destroy
module.temp01.ovirt_vm.vm: Refreshing state... [id=2ad55c71-2e1a-4ce0-89a0-cda845f83fa2]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # module.temp01.ovirt_vm.vm will be destroyed
  - resource "ovirt_vm" "vm" {
      - clone             = true -> null
      - cluster_id        = "c5529b5d-1b4e-46e6-9cb1-5d44c28cd65b" -> null
      - cores             = 2 -> null
      - high_availability = true -> null
      - id                = "2ad55c71-2e1a-4ce0-89a0-cda845f83fa2" -> null
      - memory            = 2048 -> null
      - name              = "temp01" -> null
      - sockets           = 1 -> null
      - status            = "up" -> null
      - template_id       = "fd4b04a9-74b5-4287-adbc-3d5c6711d53f" -> null
      - threads           = 1 -> null

      - initialization {
          - dns_search  = "example.com" -> null
          - dns_servers = "8.8.8.8" -> null
          - host_name   = "temp01.example.com" -> null
          - timezone    = "Africa/Nairobi" -> null

          - nic_configuration {
              - address    = "192.168.10.11" -> null
              - boot_proto = "static" -> null
              - gateway    = "192.168.10.254" -> null
              - label      = "eth0" -> null
              - netmask    = "255.255.255.0" -> null
              - on_boot    = true -> null
            }
        }
    }

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

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes
module.temp01.ovirt_vm.vm: Destroying... [id=2ad55c71-2e1a-4ce0-89a0-cda845f83fa2]
module.temp01.ovirt_vm.vm: Still destroying... [id=2ad55c71-2e1a-4ce0-89a0-cda845f83fa2, 10s elapsed]
module.temp01.ovirt_vm.vm: Destruction complete after 20s

Destroy complete! Resources: 1 destroyed.

Refer to documentation pages for more examples on how to use oVirt provider resources and data sources.

More on terraform:

Deploy VM Instances on Hetzner Cloud with Terraform

How To Provision VMs on KVM with Terraform

Install Terraform on Windows 10 / Windows Server 2019