pipeline: add testing improvements for branch-based workflows
Some checks failed
Run nix flake check / flake-check (push) Has been cancelled

Implement dual improvements to enable efficient testing of pipeline changes
without polluting master branch:

1. Add --force flag to create-host script
   - Skip hostname/IP uniqueness validation
   - Overwrite existing host configurations
   - Update entries in flake.nix and terraform/vms.tf (no duplicates)
   - Useful for iterating on configurations during testing

2. Add branch support to bootstrap mechanism
   - Bootstrap service reads NIXOS_FLAKE_BRANCH environment variable
   - Defaults to master if not set
   - Uses branch in git URL via ?ref= parameter
   - Service loads environment from /etc/environment

3. Add cloud-init disk support for branch configuration
   - VMs can specify flake_branch field in terraform/vms.tf
   - Automatically generates cloud-init snippet setting NIXOS_FLAKE_BRANCH
   - Uploads snippet to Proxmox via SSH
   - Production VMs omit flake_branch and use master

4. Update documentation
   - Document --force flag usage in create-host README
   - Add branch testing examples in terraform README
   - Update TODO.md with testing workflow
   - Add .generated/ to gitignore

Testing workflow: Create feature branch, set flake_branch in VM definition,
deploy with terraform, iterate with --force flag, clean up before merging.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-01 16:34:28 +01:00
parent 30addc5116
commit 83de9a3ffb
10 changed files with 268 additions and 40 deletions

View File

@@ -87,6 +87,21 @@ vms = {
}
```
### Example: Test VM with Custom Git Branch
For testing pipeline changes without polluting master:
```hcl
vms = {
"test-vm" = {
ip = "10.69.13.100/24"
flake_branch = "test-pipeline" # Bootstrap from this branch
}
}
```
This VM will bootstrap from the `test-pipeline` branch instead of `master`. Production VMs should omit the `flake_branch` field.
## Configuration Options
Each VM in the `vms` map supports the following fields (all optional):
@@ -98,6 +113,7 @@ Each VM in the `vms` map supports the following fields (all optional):
| `cpu_cores` | Number of CPU cores | `2` |
| `memory` | Memory in MB | `2048` |
| `disk_size` | Disk size (e.g., "20G", "100G") | `"20G"` |
| `flake_branch` | Git branch for bootstrap (for testing, omit for production) | `master` |
| `target_node` | Proxmox node to deploy to | `"pve1"` |
| `template_name` | Template VM to clone from | `"nixos-25.11.20260128.fa83fd8"` |
| `storage` | Storage backend | `"local-zfs"` |
@@ -182,9 +198,11 @@ deployment_summary = {
- `main.tf` - Provider configuration
- `variables.tf` - Variable definitions and defaults
- `vms.tf` - VM definitions and deployment logic
- `cloud-init.tf` - Custom cloud-init configuration for branch-specific bootstrap
- `outputs.tf` - Output definitions for deployed VMs
- `terraform.tfvars.example` - Example credentials file
- `terraform.tfvars` - Your actual credentials (gitignored)
- `.generated/` - Auto-generated cloud-init files (gitignored)
- `vm.tf.old` - Archived single-VM configuration (reference)
## Notes

55
terraform/cloud-init.tf Normal file
View File

@@ -0,0 +1,55 @@
# Cloud-init configuration for branch-specific bootstrap
#
# This file manages custom cloud-init snippets for VMs that need to bootstrap
# from a specific git branch (non-master). Production VMs omit flake_branch
# and use the default master branch.
# Generate cloud-init snippets for VMs with custom branch configuration
resource "local_file" "cloud_init_branch" {
for_each = {
for name, vm in local.vm_configs : name => vm
if vm.flake_branch != null
}
filename = "${path.module}/.generated/cloud-init-${each.key}.yml"
content = yamlencode({
# Write NIXOS_FLAKE_BRANCH to /etc/environment
# This will be read by bootstrap.nix service via EnvironmentFile
write_files = [{
path = "/etc/environment"
content = "NIXOS_FLAKE_BRANCH=${each.value.flake_branch}\n"
append = true
}]
})
file_permission = "0644"
}
# Upload cloud-init snippets to Proxmox
# Note: This requires SSH access to the Proxmox host
# Alternative: Manually copy files or use Proxmox API if available
resource "null_resource" "upload_cloud_init" {
for_each = {
for name, vm in local.vm_configs : name => vm
if vm.flake_branch != null
}
# Trigger re-upload when content changes
triggers = {
content_hash = local_file.cloud_init_branch[each.key].content
}
# Upload the cloud-init file to Proxmox snippets directory
provisioner "local-exec" {
command = <<-EOT
scp -o StrictHostKeyChecking=no \
${local_file.cloud_init_branch[each.key].filename} \
${var.proxmox_host}:/var/lib/vz/snippets/cloud-init-${each.key}.yml
EOT
}
depends_on = [local_file.cloud_init_branch]
}
# Ensure VMs depend on cloud-init being uploaded
# This is handled implicitly by the cicustom reference in vms.tf

View File

@@ -21,6 +21,12 @@ variable "proxmox_tls_insecure" {
default = true
}
variable "proxmox_host" {
description = "Proxmox host for SSH access (used to upload cloud-init snippets)"
type = string
default = "pve1.home.2rjus.net"
}
# Default values for VM configurations
# These can be overridden per-VM in vms.tf

View File

@@ -22,6 +22,12 @@ locals {
# disk_size = "50G"
# }
# Example Test VM with custom git branch (for testing pipeline changes):
# "test-vm" = {
# ip = "10.69.13.100/24"
# flake_branch = "test-pipeline" # Bootstrap from this branch instead of master
# }
# Example Minimal VM using all defaults (uncomment to deploy):
# "minimal-vm" = {}
# "bootstrap-verify-test" = {}
@@ -44,6 +50,8 @@ locals {
# Network configuration - detect DHCP vs static
ip = lookup(vm, "ip", null)
gateway = lookup(vm, "gateway", var.default_gateway)
# Branch configuration for bootstrap (optional, uses master if not set)
flake_branch = lookup(vm, "flake_branch", null)
}
}
}
@@ -111,6 +119,9 @@ resource "proxmox_vm_qemu" "vm" {
# Network configuration - DHCP or static IP
ipconfig0 = each.value.ip != null ? "ip=${each.value.ip},gw=${each.value.gateway}" : "ip=dhcp"
# Custom cloud-init disk for branch configuration (if flake_branch is set)
cicustom = each.value.flake_branch != null ? "user=${each.value.storage}:snippets/cloud-init-${each.key}.yml" : null
# Skip IPv6 since we don't use it
skip_ipv6 = true