@celilo/terraform-fake
A fake Proxmox API you can point a real Terraform provider at.
Not a mock that returns 200 OK. It holds state, so a terraform apply creates
something, a second apply reports no changes, and a destroy removes it —
which is the part that actually catches bugs, because a fake that always says yes
lets a broken plan look like a working one.
bun add -d @celilo/terraform-fake # or npm / pnpm
Why this exists
Testing Terraform against a real hypervisor is slow, expensive, and hard to get into a known state. Testing it against nothing at all is the usual alternative, and it means the provider interaction — the part most likely to break — is the one part never exercised.
The endpoint list here is not a reading of the Proxmox API docs. It is what
telmate/proxmox and one production consumer were observed to call across a
full create → re-apply → destroy cycle, captured by pointing the real provider at
a logging server and implementing whatever it asked for next.
Usage
import { createProxmoxFake } from '@celilo/terraform-fake';
const fake = createProxmoxFake({
tls: { key: myKeyPem, cert: myCertPem }, // self-signed is fine
nodes: [{ name: 'pve1', cores: 8, memoryBytes: 16 * 1024 ** 3, diskBytes: 500 * 1024 ** 3 }],
});
const port = await fake.listen(8006);
// ... run terraform against https://127.0.0.1:8006/api2/json ...
await fake.close();
Point Terraform at it:
provider "proxmox" {
pm_api_url = "https://127.0.0.1:8006/api2/json"
pm_api_token_id = "you@pve!testing"
pm_api_token_secret = "any-value"
pm_tls_insecure = true # the cert is self-signed
}
The token's user does not need to exist in any config you write — the fake reports whoever your token says you are.
Making the guests real
By default nothing is provisioned: the fake is bookkeeping, which is all you need to exercise a provider. If you want a create to produce an actual machine — a container, a VM, a process — supply a provisioner:
const fake = createProxmoxFake({
tls,
provisioner: {
async createGuest(guest) {
// guest.vmid, guest.hostname, guest.config (the raw create parameters,
// including net0 with the address Terraform asked for)
await startSomethingReal(guest);
},
async destroyGuest(guest) {
await removeIt(guest.vmid);
},
},
});
This seam is deliberate. Everything environment-specific — Docker, networks, SSH keys, naming conventions — belongs to you, not to this package.
Asserting on what happened
fake.state is the state store, so a test can check what a plan actually did:
const guest = fake.state.findGuest(203);
expect(guest?.config.net0).toContain('ip=10.0.20.13/24');
Things that will bite you if you build this yourself
Each of these was found by a provider crash, and each is handled here.
The config read-back must be re-typed. A create arrives form-encoded, so every value is a string. Echo it back and the provider panics — it type-asserts without the comma-ok form, so a mismatch takes down the plugin rather than producing an error:
panic: interface conversion: interface {} is string, not float64
cpulimit is the trap. The obvious repair — coerce anything numeric-looking
— panics the other way, because cpulimit is semantically a number and read as
a string. The exact field list lives in config-types.ts.
/cluster/resources must honour ?type=. The provider fetches ?type=vm
and then reads vmid as a float on every row returned. A node row has no
vmid, so answering an unfiltered list crashes it.
State must outlive a single apply. Restart the fake between applies and its guests vanish, so the provider concludes the resource was deleted and recreates it. Correct provider behaviour; a badly misleading test.
The first call is /access/users. Not /version. A fake that omits it fails
before doing anything, with an error naming neither.
Scope
Supported: the proxmox_lxc lifecycle (create, read, update, delete, start,
stop), cluster resources, nodes, storage, tasks, access and pools.
Not yet: proxmox_vm_qemu. Its config parser has its own type map and, in all
likelihood, its own cpulimit-shaped surprise; it should be extracted from the
provider source the same way rather than guessed.
Verified against telmate/proxmox@3.0.2-rc07 and Terraform 1.7.1. Newer provider
versions may assert different types — re-extract rather than assume.
License
MIT