feat(terraform): add Proxmox VM and Unifi DHCP modules (Phase 2)
This commit is contained in:
88
terraform/proxmox/vm/main.tf
Normal file
88
terraform/proxmox/vm/main.tf
Normal file
@@ -0,0 +1,88 @@
|
||||
# ─── VM ID Derivation ────────────────────────────────────────────────────────
|
||||
# Convention carried from existing Ansible playbooks:
|
||||
# VM ID = {third_octet}{fourth_octet:03d}
|
||||
# Example: 10.1.71.35 → 71035
|
||||
|
||||
locals {
|
||||
ip_octets = split(".", var.ip_address)
|
||||
vm_id = tonumber("${local.ip_octets[2]}${format("%03d", tonumber(local.ip_octets[3]))}")
|
||||
}
|
||||
|
||||
# ─── Template Lookup ─────────────────────────────────────────────────────────
|
||||
# Find the template VM ID by name on the clone node
|
||||
|
||||
data "proxmox_virtual_environment_vms" "templates" {
|
||||
node_name = var.clone_node
|
||||
|
||||
filter {
|
||||
name = "template"
|
||||
values = ["true"]
|
||||
}
|
||||
|
||||
filter {
|
||||
name = "name"
|
||||
values = [var.template_name]
|
||||
}
|
||||
}
|
||||
|
||||
# ─── VM Resource ─────────────────────────────────────────────────────────────
|
||||
|
||||
resource "proxmox_virtual_environment_vm" "vm" {
|
||||
name = var.hostname
|
||||
vm_id = local.vm_id
|
||||
node_name = var.target_node
|
||||
|
||||
description = "Provisioned by mk-labs pipeline on ${timestamp()}"
|
||||
tags = ["managed", "pipeline"]
|
||||
|
||||
# Clone from template on fantasyland
|
||||
clone {
|
||||
vm_id = data.proxmox_virtual_environment_vms.templates.vms[0].vm_id
|
||||
node_name = var.clone_node
|
||||
full = true
|
||||
datastore_id = var.datastore
|
||||
}
|
||||
|
||||
# Cloud-init configuration
|
||||
# Uses DHCP — Unifi DHCP reservation is created before VM starts
|
||||
initialization {
|
||||
ip_config {
|
||||
ipv4 {
|
||||
address = "dhcp"
|
||||
}
|
||||
}
|
||||
|
||||
dns {
|
||||
servers = var.dns_servers
|
||||
domain = var.search_domain
|
||||
}
|
||||
|
||||
datastore_id = var.datastore
|
||||
}
|
||||
|
||||
# Network interface
|
||||
network_device {
|
||||
bridge = var.bridge
|
||||
model = "virtio"
|
||||
vlan_id = var.vlan_id
|
||||
}
|
||||
|
||||
# VM stays STOPPED after clone.
|
||||
# n8n handles: DHCP reservation → HA affinity rule → VM start
|
||||
# This ensures DHCP is in place before first boot.
|
||||
started = false
|
||||
|
||||
# QEMU guest agent is installed in template (via Packer).
|
||||
# Note: Proxmox 9 requires VM.GuestAgent.Audit privilege on the API token.
|
||||
agent {
|
||||
enabled = true
|
||||
}
|
||||
|
||||
# Prevent Terraform from fighting with manual changes
|
||||
lifecycle {
|
||||
ignore_changes = [
|
||||
description,
|
||||
tags,
|
||||
]
|
||||
}
|
||||
}
|
||||
24
terraform/proxmox/vm/outputs.tf
Normal file
24
terraform/proxmox/vm/outputs.tf
Normal file
@@ -0,0 +1,24 @@
|
||||
output "vm_id" {
|
||||
description = "Proxmox VM ID of the created VM."
|
||||
value = proxmox_virtual_environment_vm.vm.vm_id
|
||||
}
|
||||
|
||||
output "mac_address" {
|
||||
description = "MAC address of the VM's first network interface."
|
||||
value = proxmox_virtual_environment_vm.vm.network_device[0].mac_address
|
||||
}
|
||||
|
||||
output "hostname" {
|
||||
description = "Hostname of the created VM."
|
||||
value = var.hostname
|
||||
}
|
||||
|
||||
output "ip_address" {
|
||||
description = "IP address of the created VM."
|
||||
value = var.ip_address
|
||||
}
|
||||
|
||||
output "node_name" {
|
||||
description = "Proxmox node where the VM is running."
|
||||
value = proxmox_virtual_environment_vm.vm.node_name
|
||||
}
|
||||
20
terraform/proxmox/vm/providers.tf
Normal file
20
terraform/proxmox/vm/providers.tf
Normal file
@@ -0,0 +1,20 @@
|
||||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
|
||||
required_providers {
|
||||
proxmox = {
|
||||
source = "bpg/proxmox"
|
||||
version = ">= 0.74.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "proxmox" {
|
||||
endpoint = var.proxmox_api_url
|
||||
api_token = var.proxmox_api_token
|
||||
insecure = true
|
||||
|
||||
ssh {
|
||||
agent = true
|
||||
}
|
||||
}
|
||||
13
terraform/proxmox/vm/terraform.tfvars.example
Normal file
13
terraform/proxmox/vm/terraform.tfvars.example
Normal file
@@ -0,0 +1,13 @@
|
||||
# Copy this file to terraform.tfvars and fill in the values.
|
||||
# terraform.tfvars is in .gitignore — never commit secrets.
|
||||
|
||||
proxmox_api_url = "https://fantasyland.mk-labs.net:8006"
|
||||
proxmox_api_token = "terraform@pve!terraform-token=your-token-secret-here"
|
||||
|
||||
# These values come from n8n via -var flags at runtime:
|
||||
# hostname = "test-vm"
|
||||
# ip_address = "10.1.71.100"
|
||||
# target_node = "fantasyland"
|
||||
# template_name = "ubuntu-24.04-small"
|
||||
# datastore = "liberty-tree"
|
||||
# vlan_id = 71
|
||||
88
terraform/proxmox/vm/variables.tf
Normal file
88
terraform/proxmox/vm/variables.tf
Normal file
@@ -0,0 +1,88 @@
|
||||
# ─── Provider Authentication ─────────────────────────────────────────────────
|
||||
|
||||
variable "proxmox_api_url" {
|
||||
type = string
|
||||
description = "Proxmox API endpoint URL (e.g., https://fantasyland.mk-labs.net:8006)"
|
||||
}
|
||||
|
||||
variable "proxmox_api_token" {
|
||||
type = string
|
||||
description = "Proxmox API token in format: user@realm!token-name=token-secret"
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
# ─── VM Identity ─────────────────────────────────────────────────────────────
|
||||
|
||||
variable "hostname" {
|
||||
type = string
|
||||
description = "VM hostname. Used as the Proxmox VM name and cloud-init hostname."
|
||||
}
|
||||
|
||||
variable "ip_address" {
|
||||
type = string
|
||||
description = "Primary IP address for the VM (without CIDR, e.g., 10.1.71.100). Used for VM ID derivation and DHCP reservation."
|
||||
}
|
||||
|
||||
variable "dns_servers" {
|
||||
type = list(string)
|
||||
description = "DNS servers for the VM."
|
||||
default = ["10.1.71.102", "10.1.71.1"]
|
||||
}
|
||||
|
||||
variable "search_domain" {
|
||||
type = string
|
||||
description = "DNS search domain for the VM."
|
||||
default = "mk-labs.net"
|
||||
}
|
||||
|
||||
# ─── Proxmox Placement ──────────────────────────────────────────────────────
|
||||
|
||||
variable "clone_node" {
|
||||
type = string
|
||||
description = "Proxmox node where templates live. Cloning always happens here."
|
||||
default = "fantasyland"
|
||||
}
|
||||
|
||||
variable "target_node" {
|
||||
type = string
|
||||
description = "Proxmox node for final VM placement. VM is migrated here after clone."
|
||||
}
|
||||
|
||||
variable "datastore" {
|
||||
type = string
|
||||
description = "Proxmox storage pool for VM disks."
|
||||
default = "liberty-tree"
|
||||
}
|
||||
|
||||
# ─── Template Selection ─────────────────────────────────────────────────────
|
||||
|
||||
variable "template_name" {
|
||||
type = string
|
||||
description = "Name of the Proxmox VM template to clone."
|
||||
|
||||
validation {
|
||||
condition = contains([
|
||||
"ubuntu-24.04-small",
|
||||
"ubuntu-24.04-medium",
|
||||
"ubuntu-24.04-large",
|
||||
"ubuntu-24.04-large-plus",
|
||||
"ubuntu-24.04-xlarge",
|
||||
"ubuntu-24.04-xlarge-plus",
|
||||
], var.template_name)
|
||||
error_message = "template_name must be a valid Ubuntu 24.04 template."
|
||||
}
|
||||
}
|
||||
|
||||
# ─── Network ─────────────────────────────────────────────────────────────────
|
||||
|
||||
variable "vlan_id" {
|
||||
type = number
|
||||
description = "VLAN tag for the VM network interface."
|
||||
default = 71
|
||||
}
|
||||
|
||||
variable "bridge" {
|
||||
type = string
|
||||
description = "Proxmox network bridge for the VM."
|
||||
default = "vmbr0"
|
||||
}
|
||||
12
terraform/unifi/dhcp/main.tf
Normal file
12
terraform/unifi/dhcp/main.tf
Normal file
@@ -0,0 +1,12 @@
|
||||
# ─── DHCP Static Reservation ─────────────────────────────────────────────────
|
||||
# Creates a fixed IP assignment on the UDM Pro for the newly provisioned VM.
|
||||
# Triggered by n8n after Proxmox VM creation returns the MAC address.
|
||||
|
||||
resource "unifi_user" "vm" {
|
||||
name = var.hostname
|
||||
mac = lower(var.mac_address)
|
||||
fixed_ip = var.ip_address
|
||||
network_id = var.network_id
|
||||
|
||||
note = "Managed by mk-labs pipeline"
|
||||
}
|
||||
4
terraform/unifi/dhcp/outputs.tf
Normal file
4
terraform/unifi/dhcp/outputs.tf
Normal file
@@ -0,0 +1,4 @@
|
||||
output "reservation_id" {
|
||||
description = "Unifi user/client ID for the DHCP reservation."
|
||||
value = unifi_user.vm.id
|
||||
}
|
||||
19
terraform/unifi/dhcp/providers.tf
Normal file
19
terraform/unifi/dhcp/providers.tf
Normal file
@@ -0,0 +1,19 @@
|
||||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
|
||||
required_providers {
|
||||
unifi = {
|
||||
source = "paultyng/unifi"
|
||||
version = ">= 0.41.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "unifi" {
|
||||
username = var.unifi_username
|
||||
password = var.unifi_password
|
||||
api_url = var.unifi_api_url
|
||||
|
||||
# Set to true if using a self-signed certificate
|
||||
allow_insecure = true
|
||||
}
|
||||
12
terraform/unifi/dhcp/terraform.tfvars.example
Normal file
12
terraform/unifi/dhcp/terraform.tfvars.example
Normal file
@@ -0,0 +1,12 @@
|
||||
# Copy this file to terraform.tfvars and fill in the values.
|
||||
# terraform.tfvars is in .gitignore — never commit secrets.
|
||||
|
||||
unifi_username = "admin"
|
||||
unifi_password = "your-password-here"
|
||||
unifi_api_url = "https://10.1.0.1:443"
|
||||
|
||||
# These values come from n8n via -var flags at runtime:
|
||||
# hostname = "test-vm"
|
||||
# mac_address = "AA:BB:CC:DD:EE:FF"
|
||||
# ip_address = "10.1.71.100"
|
||||
# network_id = "your-server-trusted-network-id"
|
||||
41
terraform/unifi/dhcp/variables.tf
Normal file
41
terraform/unifi/dhcp/variables.tf
Normal file
@@ -0,0 +1,41 @@
|
||||
# ─── Provider Authentication ─────────────────────────────────────────────────
|
||||
|
||||
variable "unifi_username" {
|
||||
type = string
|
||||
description = "Unifi controller username."
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "unifi_password" {
|
||||
type = string
|
||||
description = "Unifi controller password."
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "unifi_api_url" {
|
||||
type = string
|
||||
description = "Unifi controller API URL (e.g., https://10.1.0.1:443)."
|
||||
}
|
||||
|
||||
# ─── DHCP Reservation ───────────────────────────────────────────────────────
|
||||
|
||||
variable "hostname" {
|
||||
type = string
|
||||
description = "Hostname for the DHCP reservation. Used as the client name."
|
||||
}
|
||||
|
||||
variable "mac_address" {
|
||||
type = string
|
||||
description = "MAC address of the VM network interface (from Terraform Proxmox output)."
|
||||
}
|
||||
|
||||
variable "ip_address" {
|
||||
type = string
|
||||
description = "Fixed IP address for the DHCP reservation (without CIDR, e.g., 10.1.71.100)."
|
||||
}
|
||||
|
||||
variable "network_id" {
|
||||
type = string
|
||||
description = "Unifi network ID for the DHCP reservation."
|
||||
default = ""
|
||||
}
|
||||
Reference in New Issue
Block a user