feat(opentofu): destroy the rustfs LXCs 800/801/802 #515

Merged
k.renner merged 1 commits from decommission-rustfs-lxc into master 2026-08-19 06:55:17 +00:00
-122
View File
@@ -1,122 +0,0 @@
# TEMPORARY shims v3 — rustfs LXCs 800/801/802, decommission in progress.
#
# Stage 2 (#270) failed: both destroy attempts died instantly with
# "context deadline exceeded" on the pre-destroy status GET, while the PVE
# API answers the same call in <1s. Cause: the bpg provider reads
# `timeout_delete` from STATE at destroy time, and importing a resource
# whose config declares no timeouts stored 0 — a zero-second context.
# (The import/plan read path uses its own timeout, which is why stage 1
# planned clean.)
#
# Fix: declare the timeouts explicitly, apply ONCE so they land in state
# (in-place no-API update), then delete this file again and the next apply
# destroys the containers.
#
# v3: v2 declared ONLY the ids, `started`, `unprivileged` and the timeouts,
# claiming "the other attributes mirror the real containers so the plan stays
# in-place". They did not. Everything absent from config planned as a removal,
# and two of those removals — `mount_point` and `operating_system` — force
# replacement, so the plan came back `must be replaced` for all three. Applying
# that would have destroyed and recreated containers tagged `critical`,
# `state-backend`, `terraform-backend` instead of nudging a timeout into state.
#
# So the blocks below are not decoration: each one exists to keep an attribute
# out of the diff. They mirror the live containers, read from the plan's own
# state values. `tags` is declared for the same reason — without it the apply
# strips the tags off the real containers.
#
# 1. merge; run PIPELINE=deploy-infrastructure (plan MUST show in-place
# update only, no replace), then TOFU_APPLY=true
# 2. follow-up PR deletes this file; plan shows 3 destroys; apply.
locals {
# Per-container values mirrored from live state. hostname and mac_address
# differ per container; declaring them wrong would itself force a diff.
rustfs_decommission = {
"800" = { node = "saturn", hostname = "rustfs-1.srv.renner.uno", mac = "BC:24:11:0B:E5:7B" }
"801" = { node = "jupiter", hostname = "rustfs-2.srv.renner.uno", mac = "BC:24:11:EA:1D:38" }
"802" = { node = "neptune", hostname = "rustfs-3.srv.renner.uno", mac = "BC:24:11:8F:F4:67" }
}
}
resource "proxmox_virtual_environment_container" "rustfs_decommission" {
for_each = local.rustfs_decommission
node_name = each.value.node
vm_id = tonumber(each.key)
started = false
unprivileged = true
start_on_boot = true
tags = [
"critical",
"rustfs",
"state-backend",
"terraform-backend",
]
timeout_create = 1800
timeout_clone = 1800
timeout_update = 1800
timeout_delete = 1800
timeout_start = 300
cpu {
architecture = "amd64"
cores = 1
units = 1024
}
memory {
dedicated = 1024
swap = 0
}
disk {
datastore_id = "local"
size = 10
}
# forces replacement if absent — this is the block that broke v2.
mount_point {
path = "/mnt/data"
size = "50G"
volume = "local:${each.key}/vm-${each.key}-disk-1.raw"
backup = true
replicate = true
}
network_interface {
name = "eth0"
bridge = "v40srv"
enabled = true
firewall = false
mac_address = each.value.mac
}
initialization {
hostname = each.value.hostname
ip_config {
ipv4 {
address = "dhcp"
}
}
}
# operating_system is NOT declared, and must not be. The provider requires
# template_file_id inside it, which is a create-time-only value: PVE does not
# report the original template for an existing container, so it is absent
# from state and cannot be mirrored. Declaring the block without it fails
# validation ("Missing required argument"); declaring it with a guessed
# template would be a lie that only surfaces if something ever recreates
# these.
#
# Left undeclared it would plan as a removal, and removing operating_system
# forces replacement — the exact failure this file exists to avoid. So it is
# ignored instead. The timeouts above are deliberately NOT ignored: they are
# the one thing this shim needs to actually apply.
lifecycle {
ignore_changes = [operating_system]
}
}