Compare commits

...

34 Commits

Author SHA1 Message Date
Lukas Greve
41302119a7 increase vCPU to 2 and make variable ssh_key optional 2025-10-06 12:08:45 +02:00
Lukas Greve
d0dc1f02f6 phyllome os support 2025-10-06 12:07:48 +02:00
Lukas Greve
1f1f2ce12f q35 chipset by default 2025-10-06 12:07:19 +02:00
Lukas Greve
17a810d9c0 make domain name definition a function of the vm name 2025-09-18 20:35:33 +02:00
Lukas Greve
902420a3ea Make enabling cloud_init optionnal
For cloud images that needs it, cloud_init can be enabled in the main.tf file. This change will allow deployments that does not require cloud_init to be supported
2025-09-18 20:24:38 +02:00
Lukas Greve
75b2ae6b40 add support for ubuntu uefi vm 2025-09-18 16:36:48 +02:00
Lukas Greve
3ae8b81859 simplify name of vm 2025-09-18 16:36:33 +02:00
Lukas Greve
3da3aa5cc4 first implementation of UEFI support 2025-09-18 16:36:09 +02:00
Lukas Greve
bfb5f780c8 make the pool name a function of the vm name 2025-09-14 12:15:02 +02:00
Lukas Greve
79be0a2461 network names as a function of vm the name 2025-09-14 12:07:07 +02:00
Lukas Greve
2e6d1c1b8a remove output file as it is not working now 2025-09-14 11:33:13 +02:00
Lukas Greve
5f2e5dc16b add support for fedora 42 2025-09-14 11:33:00 +02:00
Lukas Greve
64f4701507 rename file 2025-09-14 11:32:48 +02:00
Lukas Greve
28b6775c90 add provider file in shared module folder, otherwise deployment fails to identify correct provider to fetch 2025-09-13 16:20:56 +02:00
Lukas Greve
8d451e12d2 remove unnecessary file 2025-09-10 20:54:28 +02:00
Lukas Greve
c68ca02018 add another output, but does not seem to return anything for the moment 2025-09-10 20:54:10 +02:00
Lukas Greve
727903412f ssh public key now defined as variable. User can define it under the main deployment file 2025-09-10 20:53:54 +02:00
Lukas Greve
af31f8c4b2 ignore aider-related files 2025-09-10 20:52:24 +02:00
Lukas Greve
3498b877c8 feat: add ssh_key variable support to cloud-init module 2025-09-10 20:40:25 +02:00
Lukas Greve
798cc871c8 remove some unused variables 2025-09-07 20:10:23 +02:00
Lukas Greve
86be10c776 add some outputs, but not avails 2025-09-07 20:10:09 +02:00
Lukas Greve
b1b7a6e311 host-passthrough cpu mode and scsi for the storage 2025-09-07 20:10:00 +02:00
Lukas Greve
778711fdfb just allow ssh-key authentication 2025-09-07 20:09:26 +02:00
Lukas Greve
8078d9c041 cloud_init.yaml insecure example 2025-09-04 11:34:09 +02:00
Lukas Greve
4409d31ae9 unnecessary files 2025-09-04 11:33:55 +02:00
Lukas Greve
2433e08f90 added some explanations 2025-09-04 11:32:42 +02:00
Lukas Greve
07ed85724b first example of a deployment, a simple ubuntu-based BIOS virtual machine with the i440fx virtual chipset 2025-09-04 11:32:20 +02:00
Lukas Greve
7e470c0bc4 cloud_init.yaml, intented to be shared 2025-09-04 11:31:28 +02:00
Lukas Greve
ac81cb65ce shared modules to be used across deployments 2025-09-04 11:30:46 +02:00
Lukas Greve
96e8bd7588 first simple example 2025-09-04 11:30:25 +02:00
Lukas Greve
286594939b allow for the deployment of multiple domains 2025-08-30 11:19:29 +02:00
Lukas Greve
e0518f90b6 first (insecure) cloud-init configuration 2025-08-26 20:17:27 +02:00
Lukas Greve
7cc9094dff add first example, ubuntu 2404 with BIOS firmware and i440fx chipset 2025-08-26 20:16:33 +02:00
Lukas Greve
470abd06f8 add README explaining how to use examples 2025-08-26 20:14:16 +02:00
17 changed files with 834 additions and 2 deletions

16
.gitignore vendored Normal file
View File

@@ -0,0 +1,16 @@
# Terraform
.terraform/
.terraform.lock.hcl
*.tfstate
*.tfstate.backup
*.tfstate.lock.info
terraform.tfvars
terraform.tfvars.example
# Terraform plan and output files
*.tfplan
*.tfout
# Aider files
*.aider*

351
README.md
View File

@@ -1,3 +1,350 @@
# advanced-libvirt-terraform-examples
# Modern libvirt-driven Terraform examples
This repository contains terraform examples to deploy various modern virtual machines using QEMU and libvirt
This repository contains Terraform recipes to deploy various modern virtual machines using QEMU and libvirt.
By modern, it is meant virtual machines that leverage the use of modern desktop-oriented technologies, like UEFI firmware and recent virtual motherboard chipset (i.e. Phyllome OS itself), by staying as close as possible as domain definitions maintained [here](https://git.phyllo.me/roots/xml-definition-for-domains).
## Organization
The folder *multiple* contains two subfolders, one with shared modules and the other with the various target deployment environments.
The idea is to reuse modules across multiple virtual machines and operating systems.
```
./multiple:
environments shared_modules
./multiple/environments:
cloud_init.yaml ubuntu-cloud-server-2404-bios
./multiple/environments/ubuntu-cloud-server-2404-bios:
ubuntu-cloud-server-2404-bios.tf
./multiple/shared_modules:
cloud-init.tf domain.tf network.tf outputs.tf pool.tf provider.tf variables.tf volume.tf
```
## Requirements
- [QEMU](https://www.qemu.org/)
- [libvirt](https://libvirt.org/)
- [Terraform provider for Libvirt](https://github.com/dmacvicar/terraform-provider-libvirt)
## Assumptions
Your Linux x86_64-based machine has at least 4 GB of available memory and 2 CPUs.
## How to use it
- Clone this repository
- Go to folder *example*
- Execute the following commands, which will download and install the required Terraform provider if not already present
```
$ terraform init
Initializing the backend...
Initializing provider plugins...
- Reusing previous version of dmacvicar/libvirt from the dependency lock file
- Using previously-installed dmacvicar/libvirt v0.8.3
Terraform has been successfully initialized!
[...]
```
- The following command will plan the deployment, describing actions that will be taken when applied
```
$ terraform plan
[...]
Terraform will perform the following actions
# A cloud-init ISO disk is created, which provides pre-configured settings and scripts that are applied to a cloud-native disk image during its initial boot. Without it, no user would be created and it would not be possible to log into the virtual machine
+ resource "libvirt_cloudinit_disk" "commoninit" {
+ name = "commoninit.iso"
+ pool = "ubuntu-bios"
[...]
}
# The libvirt domain or virtual machine will be created
+ resource "libvirt_domain" "domain" {
+ cloudinit = (known after apply)
[...]
}
# Here, a libvirt pool to store the virtual machine disk image will be created. It should be possible to use the default one
+ resource "libvirt_pool" "ubuntu-bios" {
[...]
+ name = "ubuntu-bios"
+ type = "dir"
+ target {
+ path = "/tmp/ubuntu-bios"
}
}
# A qcow2 disk volume will be created and stored in the previously created pool, based on a Ubuntu noble (24.04) hosted cloud image
+ resource "libvirt_volume" "ubuntu-qcow2" {
+ format = "qcow2"
[...]
# The plan summaries the action to be taken, which in this case is about creating resources
Plan: 4 to add, 0 to change, 0 to destroy.
```
- The last command will carry out the plan
```
$ terraform apply
[...]
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 actions are carried out
libvirt_pool.ubuntu-bios: Creating...
libvirt_pool.ubuntu-bios: Creation complete after 0s
[...]
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
```
- Identify the created machine
```
$ sudo virsh list --all
Id Name State
---------------------------------------------
10 ubuntu-cloud-server-2404-0 running
```
- Determine its IP address
```
$ sudo virsh domifaddr ubuntu-cloud-server-2404-0
```
```
Name MAC address Protocol Address
----------------------------------------------------------------
vnet3 52:54:00:e2:51:c0 ipv4 192.168.122.24/24
```
- Connect to the machine
```
$ ssh root@192.168.122.24
[...]
# Use the password defined in the cloud-init.cfg file
root@192.168.122.24's password:
Welcome to Ubuntu 24.04.3 LTS (GNU/Linux 6.8.0-71-generic x86_64)
[...]
System information as of Tue Aug 26 10:40:49 UTC 2025
System load: 0.0 Processes: 113
Usage of /: 67.4% of 2.35GB Users logged in: 0
Memory usage: 5% IPv4 address for ens3: 192.168.122.24
Swap usage: 0%
```
- Exit the virtual machine
```
root@ubuntu$ exit
```
- To destroy the virtual machine, execute the following command
```
$ terraform destroy
[...]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
# libvirt_cloudinit_disk.commoninit will be destroyed
- resource "libvirt_cloudinit_disk" "commoninit" {
[...]
# libvirt_domain.domain[0] will be destroyed
- resource "libvirt_domain" "domain" {
- arch = "x86_64" -> null
[...]
}
}
# libvirt_pool.ubuntu2 will be destroyed
- resource "libvirt_pool" "ubuntu2" {
- allocation = 798310400 -> null
- available = 16019255296 -> null
[...]
}
}
# libvirt_volume.ubuntu-qcow2 will be destroyed
- resource "libvirt_volume" "ubuntu-qcow2" {
[...]
}
Plan: 0 to add, 0 to change, 4 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
libvirt_domain.domain[0]: Destroying... [id=611d5ede-e4b4-4ca5-ad83-83030942a6b5]
libvirt_domain.domain[0]: Destruction complete after 0s
libvirt_cloudinit_disk.commoninit: Destroying... [id=/tmp/cluster_storage2/commoninit.iso;5f4e08ef-ad51-484f-a9f2-c926f582974a]
libvirt_volume.ubuntu-qcow2: Destroying... [id=/tmp/cluster_storage2/ubuntu-qcow2]
libvirt_cloudinit_disk.commoninit: Destruction complete after 0s
libvirt_volume.ubuntu-qcow2: Destruction complete after 0s
libvirt_pool.ubuntu2: Destroying... [id=dbd62f8b-5d09-4e96-87e2-88e95c582896]
libvirt_pool.ubuntu2: Destruction complete after 0s
Destroy complete! Resources: 4 destroyed.
```
## Explanations
Let's take a look inside the *ubuntu-cloud-server-2404-bios* folder, which contains two files, *ubuntu-cloud-server-2404-bios.tf* and *cloud_init.cfg*
The first file *ubuntu-cloud-server-2404-bios.tf* contains the main configuration for the Terraform deployment.
- It starts by defining the required Terraform version and provider
```
terraform {
required_version = ">= 0.13"
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
version = "0.8.3"
}
}
}
```
- The specific provider is defined here
```
provider "libvirt" {
uri = "qemu:///system"
}
```
> The [connection URI](https://libvirt.org/uri.html#qemu-qemu-and-kvm-uris) of the libvirt instance can be defined. One could for instance specific a libvirt instance that is hosted remotely
- A libvirt pool, to store the virtual machine image, is created:
```
resource "libvirt_pool" "ubuntu-bios" {
name = "ubuntu-bios"
type = "dir"
target {
path = "/tmp/ubuntu-bios"
}
}
```
- The cloud-init user data will be fetched from a specific file whose path has to be declared:
```
data "template_file" "user_data" {
template = file("${path.module}/cloud_init.cfg")
}
```
- The ISO cloud-init disk will be created:
```
resource "libvirt_cloudinit_disk" "commoninit" {
name = "commoninit.iso"
user_data = data.template_file.user_data.rendered
pool = libvirt_pool.ubuntu-bios.name
}
```
- Perhaps the most important, the domain will be created:
> Values can be adjusted, such as memory or vCPU counts. In the examples, multiple virtio-based device hardware are created, such as a virtio-gpu.
```
resource "libvirt_domain" "domain" {
count = 1
name = "ubuntu-cloud-server-2404-${count.index}"
memory = "4092"
vcpu = 2
cloudinit = libvirt_cloudinit_disk.commoninit.id
cpu {
mode = "host-model"
}
disk {
volume_id = libvirt_volume.ubuntu-qcow2.id
}
console {
type = "pty"
target_port = "0"
target_type = "virtio"
}
video {
type = "virtio"
}
tpm {
backend_type = "emulator"
backend_version = "2.0"
}
network_interface {
network_name = "default"
}
}
```
## Resources
- [Terraform provider domain documentation](https://github.com/dmacvicar/terraform-provider-libvirt/blob/master/website/docs/r/domain.html.markdown)

18
example/cloud_init.yaml Normal file
View File

@@ -0,0 +1,18 @@
#cloud-config
# vim: syntax=yaml
# examples:
# https://cloudinit.readthedocs.io/en/latest/topics/examples.html
---
ssh_pwauth: true
disable_root: false
chpasswd:
list: |
root:password
expire: false
users:
- name: ubuntu
sudo: ALL=(ALL) NOPASSWD:ALL
groups: users, admin
home: /home/ubuntu
shell: /bin/bash
lock_passwd: false

View File

@@ -0,0 +1,71 @@
terraform {
required_version = ">= 0.13"
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
version = "0.8.3"
}
}
}
provider "libvirt" {
uri = "qemu:///system"
}
resource "libvirt_pool" "ubuntu-bios" {
name = "ubuntu-bios"
type = "dir"
target {
path = "/tmp/ubuntu-bios"
}
}
resource "libvirt_volume" "ubuntu-bios" {
name = "ubuntu-bios-${count.index}"
pool = libvirt_pool.ubuntu-bios.name
source = "/var/lib/libvirt/images/noble-server-cloudimg-amd64.img"
format = "qcow2"
count = 1
}
resource "libvirt_cloudinit_disk" "commoninit" {
name = "commoninit.iso"
user_data = templatefile("${path.module}/cloud_init.yaml", {})
}
resource "libvirt_domain" "domain" {
count = 1
name = "ubuntu-cloud-server-2404-${count.index}"
memory = "4092"
vcpu = 2
cloudinit = libvirt_cloudinit_disk.commoninit.id
cpu {
mode = "host-model"
}
disk {
volume_id = element(libvirt_volume.ubuntu-bios.*.id, count.index)
}
console {
type = "pty"
target_port = "0"
target_type = "virtio"
}
video {
type = "virtio"
}
tpm {
backend_type = "emulator"
backend_version = "2.0"
}
network_interface {
network_name = "default"
}
}

View File

@@ -0,0 +1,22 @@
terraform {
required_version = ">= 0.13"
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
version = "0.8.3"
}
}
}
provider "libvirt" {
uri = "qemu:///system"
}
module "shared_modules" {
source = "../../shared_modules"
vm_name = "f42-bios"
image_location = "https://download.fedoraproject.org/pub/fedora/linux/releases/42/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-42-1.1.x86_64.qcow2"
ssh_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDMSuVlvOsMqx9qOrKKB1295FjCf2QhHfR1qola9brGkUcFL9dAztG2qdQnpiuPQ4OJpkedrO3C/ixEw1MLTL8l12SvYy/Q9QFguwylp35Nbw1p8h7jrX1FcNLRYltxkMgVhCs1InT5m0lf56bu1h7JfsMs7Ovsy3lU5OdK4h2MysTSKOLctsE4jDJ+XbJYQzj4rbfB/U7/9ple366cGl6xlaHxVfI4BUFWUOiVU4HWvZjrOM5fqPt+AUFRx1l2D7hLUZgOdVQwgO8GFn0sCyCIw0NCXbDn/H05pvWtTUPnyhj5TiseF8qW1byrrT5G8saxwvx8nbIK2tpPfKFdIiL7aj9bYQdltn1knJtvk3hpTPy4QvAbaoGfnfrPAsyU1A/CTw9SD/idvDT2wt1hVsm8EsnpovF7WT5z22fcgoFLDo+QCQrp7t1Wx0/Djay2nThi3FO3N051y5fQWoKOvTsm+rRhrzpDoc+Wtrtss3ua54qnQxHRx3YC0M5Xl9DINkwrcunbZBhozsDG2DzX9qcyzJsSfm9Zt5yM2lpcq+dGPRO1wedw4ogoOpobRr9Cja9W/lJvxmjgIiHz2HbSFPtk/VGjL6M7aQor/GDNN3ugSsfUoTTmNaS9+lWeg+tQWcFUPhYQtQB4/gHQ2u7+mQ0H3hVybsIKIh5XBpAdHQ7pww=="
enable_cloudinit = true
}

View File

@@ -0,0 +1,27 @@
terraform {
required_version = ">= 0.13"
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
version = "0.8.3"
}
}
}
provider "libvirt" {
uri = "qemu:///system"
}
module "shared_modules" {
source = "../../shared_modules"
vm_name = "phyllome-42-uefi"
image_location = "/var/lib/libvirt/images/virtual-desktop-hypervisor.img"
enable_cloudinit = false
# ---- OPTIONAL UEFI SETTINGS ----------------------------------------------
uefi_firmware = "/usr/share/edk2/x64/OVMF_CODE.4m.fd"
uefi_nvram_template = "/usr/share/edk2/x64/OVMF_VARS.4m.fd"
uefi_nvram_file_suffix = "-uefi"
# ----------------------------------------------------------------
}

View File

@@ -0,0 +1,22 @@
terraform {
required_version = ">= 0.13"
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
version = "0.8.3"
}
}
}
provider "libvirt" {
uri = "qemu:///system"
}
module "shared_modules" {
source = "../../shared_modules"
vm_name = "u24-bios"
image_location = "/var/lib/libvirt/images/noble-server-cloudimg-amd64.img"
ssh_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDMSuVlvOsMqx9qOrKKB1295FjCf2QhHfR1qola9brGkUcFL9dAztG2qdQnpiuPQ4OJpkedrO3C/ixEw1MLTL8l12SvYy/Q9QFguwylp35Nbw1p8h7jrX1FcNLRYltxkMgVhCs1InT5m0lf56bu1h7JfsMs7Ovsy3lU5OdK4h2MysTSKOLctsE4jDJ+XbJYQzj4rbfB/U7/9ple366cGl6xlaHxVfI4BUFWUOiVU4HWvZjrOM5fqPt+AUFRx1l2D7hLUZgOdVQwgO8GFn0sCyCIw0NCXbDn/H05pvWtTUPnyhj5TiseF8qW1byrrT5G8saxwvx8nbIK2tpPfKFdIiL7aj9bYQdltn1knJtvk3hpTPy4QvAbaoGfnfrPAsyU1A/CTw9SD/idvDT2wt1hVsm8EsnpovF7WT5z22fcgoFLDo+QCQrp7t1Wx0/Djay2nThi3FO3N051y5fQWoKOvTsm+rRhrzpDoc+Wtrtss3ua54qnQxHRx3YC0M5Xl9DINkwrcunbZBhozsDG2DzX9qcyzJsSfm9Zt5yM2lpcq+dGPRO1wedw4ogoOpobRr9Cja9W/lJvxmjgIiHz2HbSFPtk/VGjL6M7aQor/GDNN3ugSsfUoTTmNaS9+lWeg+tQWcFUPhYQtQB4/gHQ2u7+mQ0H3hVybsIKIh5XBpAdHQ7pww=="
enable_cloudinit = true
}

View File

@@ -0,0 +1,28 @@
terraform {
required_version = ">= 0.13"
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
version = "0.8.3"
}
}
}
provider "libvirt" {
uri = "qemu:///system"
}
module "shared_modules" {
source = "../../shared_modules"
vm_name = "u24-uefi"
image_location = "/var/lib/libvirt/images/noble-server-cloudimg-amd64.img"
ssh_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDMSuVlvOsMqx9qOrKKB1295FjCf2QhHfR1qola9brGkUcFL9dAztG2qdQnpiuPQ4OJpkedrO3C/ixEw1MLTL8l12SvYy/Q9QFguwylp35Nbw1p8h7jrX1FcNLRYltxkMgVhCs1InT5m0lf56bu1h7JfsMs7Ovsy3lU5OdK4h2MysTSKOLctsE4jDJ+XbJYQzj4rbfB/U7/9ple366cGl6xlaHxVfI4BUFWUOiVU4HWvZjrOM5fqPt+AUFRx1l2D7hLUZgOdVQwgO8GFn0sCyCIw0NCXbDn/H05pvWtTUPnyhj5TiseF8qW1byrrT5G8saxwvx8nbIK2tpPfKFdIiL7aj9bYQdltn1knJtvk3hpTPy4QvAbaoGfnfrPAsyU1A/CTw9SD/idvDT2wt1hVsm8EsnpovF7WT5z22fcgoFLDo+QCQrp7t1Wx0/Djay2nThi3FO3N051y5fQWoKOvTsm+rRhrzpDoc+Wtrtss3ua54qnQxHRx3YC0M5Xl9DINkwrcunbZBhozsDG2DzX9qcyzJsSfm9Zt5yM2lpcq+dGPRO1wedw4ogoOpobRr9Cja9W/lJvxmjgIiHz2HbSFPtk/VGjL6M7aQor/GDNN3ugSsfUoTTmNaS9+lWeg+tQWcFUPhYQtQB4/gHQ2u7+mQ0H3hVybsIKIh5XBpAdHQ7pww=="
enable_cloudinit = true
# ---- OPTIONAL UEFI SETTINGS ----------------------------------------------
uefi_firmware = "/usr/share/edk2/x64/OVMF_CODE.4m.fd"
uefi_nvram_template = "/usr/share/edk2/x64/OVMF_VARS.4m.fd"
uefi_nvram_file_suffix = "-uefi"
# ----------------------------------------------------------------
}

View File

@@ -0,0 +1,11 @@
# Only create the cloudinit disk if enabled
resource "libvirt_cloudinit_disk" "commoninit" {
count = var.enable_cloudinit ? var.instance_count : 0
name = "${var.cloudinit_filename}-${count.index}"
user_data = templatefile("${path.module}/cloud_init.yaml", {
ssh_key = var.ssh_key
})
pool = "${var.vm_name}-pool"
depends_on = [libvirt_pool.tf_tmp_storage]
}

View File

@@ -0,0 +1,10 @@
#cloud-config
disable_root: true
users:
- name: groot
sudo: ALL=(ALL) NOPASSWD:ALL
groups: wheel,sudo,adm
shell: /bin/bash
ssh_authorized_keys:
- ${ssh_key}
ssh_pwauth: false

View File

@@ -0,0 +1,67 @@
resource "libvirt_domain" "domain" {
count = var.instance_count
name = "${var.vm_name}-${count.index}"
memory = var.memory
vcpu = var.vcpu
machine = "q35"
# The chipset q35, which does not support the IDE bus, does not work with the terraform-provider-libvirt cloud-init implementation,
# which creates an ISO attached to an IDE bus by default. Workaround is implemented
# https://github.com/dmacvicar/terraform-provider-libvirt/issues/1137#issuecomment-2592329846
# A cleaner solution might be the following :
# https://github.com/dmacvicar/terraform-provider-libvirt/pull/895#issuecomment-1911167872
xml {
xslt = file("${path.module}/q35-workaround.xslt")
}
# Only include cloudinit if enabled
cloudinit = var.enable_cloudinit ? libvirt_cloudinit_disk.commoninit[count.index].id : null
# ---- optional UEFI support ------------------------------------
# Firmware only add the string when a path is supplied
firmware = can(var.uefi_firmware) && length(var.uefi_firmware) > 0 ? var.uefi_firmware : null
# NVRAM block dynamic block that is evaluated once per VM
dynamic "nvram" {
# create the block once if a firmware path *and* a template were given
for_each = (can(var.uefi_firmware) && length(var.uefi_firmware) > 0
&& can(var.uefi_nvram_template) && length(var.uefi_nvram_template) > 0
) ? [1] : []
content {
# The NVRAM filename is perVM, but we can honour an optional suffix
file = "/var/lib/libvirt/qemu/nvram/${var.vm_name}-${count.index}${var.uefi_nvram_file_suffix}_VARS.fd"
template = var.uefi_nvram_template
}
}
# ----------------------------------------------------------------
cpu {
mode = "host-passthrough"
}
disk {
volume_id = element(libvirt_volume.vm_disk.*.id, count.index)
scsi = "true"
}
console {
type = "pty"
target_port = "0"
target_type = "virtio"
}
video {
type = "virtio"
}
tpm {
backend_type = "emulator"
backend_version = "2.0"
}
network_interface {
network_name = "${var.vm_name}-network"
}
}

View File

@@ -0,0 +1,11 @@
resource "libvirt_network" "tf_libvirt_network" {
name = "${var.vm_name}-network"
mode = var.network_mode
domain = local.computed_network_domain
addresses = var.network_addresses
dns {
enabled = var.dns_enabled
local_only = var.dns_local_only
}
}

View File

@@ -0,0 +1,7 @@
resource "libvirt_pool" "tf_tmp_storage" {
name = "${var.vm_name}-pool"
type = "dir"
target {
path = var.pool_path
}
}

View File

@@ -0,0 +1,9 @@
terraform {
required_version = ">= 0.13"
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
version = "0.8.3"
}
}
}

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/domain/devices/disk[@device='cdrom']/target/@bus">
<xsl:attribute name="bus">
<xsl:value-of select="'sata'"/>
</xsl:attribute>
</xsl:template>
<xsl:template match="/domain/devices/disk[@device='cdrom']/alias" />
</xsl:stylesheet>

View File

@@ -0,0 +1,141 @@
variable "libvirt_uri" {
description = "URI for libvirt connection"
type = string
default = "qemu:///system"
}
variable "pool_name" {
description = "Name of the storage pool"
type = string
default = "tf_tmp_pool"
}
variable "pool_path" {
description = "Path for the storage pool"
type = string
default = "/tmp/tf_tmp_storage"
}
variable "instance_count" {
description = "Number of instances to create"
type = number
default = 1
}
variable "vm_name" {
description = "Name prefix for VMs"
type = string
}
variable "image_location" {
description = "Location of the OS image"
type = string
default = "https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img"
}
# To avoid refetching the cloud ISO each time, it could could be set to a local directory, like : "/var/lib/libvirt/images/noble-server-cloudimg-amd64.img"
variable "cloudinit_filename" {
description = "Name of the cloud-init ISO file"
type = string
default = "commoninit.iso"
}
variable "enable_cloudinit" {
description = "Enable cloud-init support"
type = bool
default = false
}
variable "ssh_key" {
description = "SSH authorized keys for cloud-init"
type = string
default = ""
}
variable "user_data" {
description = "User data for cloud-init"
type = string
default = ""
}
variable "memory" {
description = "Memory allocation in MB"
type = number
default = 2048
validation {
condition = var.memory >= 512
error_message = "Memory must be at least 512MB."
}
}
variable "vcpu" {
description = "Number of virtual CPUs"
type = number
default = 2
}
variable "network_mode" {
description = "Network mode (nat, none, route, open, bridge)"
type = string
default = "nat"
}
variable "network_domain" {
description = "Domain name for the network (derived from vm_name)"
type = string
# Default dynamically based on vm_name
default = ""
}
variable "network_addresses" {
description = "List of network addresses"
type = list(string)
default = ["10.17.3.0/24", "2001:db8:ca2:2::1/64"]
}
variable "dns_enabled" {
description = "Enable DNS for the network"
type = bool
default = true
}
variable "dns_local_only" {
description = "DNS requests only resolved by virtual network's DNS server"
type = bool
default = false
}
variable "uefi_firmware" {
description = <<EOT
Path to the UEFI firmware binary (OVMF_CODE.fd, QEMU_CODE.fd, …).
Leave empty (or omit on the module call) to create a plain BIOS VM.
EOT
type = string
default = "" # “BIOS only” when empty
}
variable "uefi_nvram_template" {
description = <<EOT
Path to an NVRAM template that backs the UEFI NVRAM.
If you specify a template, the VM will get a writable NVRAM block.
Leave empty for a plain BIOS VM or if you dont need UEFI NVRAM.
EOT
type = string
default = "" # no NVRAM when empty
}
variable "uefi_nvram_file_suffix" {
description = <<EOT
Optional filesuffix fragment that is appended to the
generated NVRAM file name. Useful when you want to put the
files under a dedicated directory (`/var/lib/libvirt/qemu/uefi/nvram/…`).
Empty string means “no suffix” (default behaviour).
EOT
type = string
default = ""
}
# Computed variable for network domain (derived from vm_name)
locals {
computed_network_domain = var.network_domain != "" ? var.network_domain : "${var.vm_name}.local"
}

View File

@@ -0,0 +1,9 @@
resource "libvirt_volume" "vm_disk" {
count = var.instance_count
name = "${var.vm_name}-${count.index}"
pool = "${var.vm_name}-pool"
source = var.image_location
format = "qcow2"
depends_on = [libvirt_pool.tf_tmp_storage]
}