A formae resource plugin for Hetzner Cloud (hcloud).
Status: v0.1.0. Targets formae >= 0.87.0 (the minFormaeVersion declared
in formae-plugin.pkl).
A single Go binary that implements the formae resource-plugin contract for
Hetzner Cloud under the HETZNER namespace. The formae agent discovers the
installed binary from ~/.pel/formae/plugins/ and dispatches CRUD, inventory,
sync, extract, and discovery operations to it; the plugin translates those
into hcloud API calls via
hcloud-go.
11 resource types across four categories:
- Compute —
HETZNER::Compute::Server,HETZNER::Compute::Image,HETZNER::Compute::PlacementGroup - Network —
HETZNER::Network::Network,HETZNER::Network::LoadBalancer,HETZNER::Network::FloatingIP,HETZNER::Network::PrimaryIP - Storage —
HETZNER::Storage::Volume - Security —
HETZNER::Security::SSHKey,HETZNER::Security::Firewall,HETZNER::Security::Certificate
HETZNER::Compute::Image— formae only owns snapshots: a snapshot is created from an existing server (Server.CreateImage) and is otherwise read/deleted. Image creation is therefore excluded from the conformance fixtures; the unit tests cover it against mocks. See docs/testing.md.
- Go
1.26.0or newer (seego.mod). - The
pklCLI,0.30.0or newer — used by the Makefile to readformae-plugin.pkland validate the schema. The Makefile'scheck-pkltarget (a prerequisite ofinstall,pkl-eval, andpkg-pkl) enforces this. - An hcloud API token (for anything that hits the real API).
cp .env-sample .env
# edit .env: HCLOUD_TOKEN="<your hcloud project token>"
.env is gitignored. The live/conformance Make targets source it
automatically. Never use a production project token.
The plugin resolves its hcloud API token via resolveToken in
pkg/plugin.go:
- A
tokenfield in the formae target config JSON ({"token":"..."}) wins. - Otherwise the
HCLOUD_TOKENenvironment variable is consulted.
Empty/null config ({"token":""}) falls back to the env var. A malformed
config (genuine JSON parse error) is surfaced as an error rather than
silently falling through to the env var — a typo'd token config should not
authenticate as a different credential drawn from the environment.
make build # compiles ./bin/formae-plugin-hcloud
make install # installs into ~/.pel/formae/plugins/<name>/v<version>/
make install honours the formae agent's versioned-layout discovery contract;
without it the agent never sees the plugin and apply/conformance fail with
timeout waiting for plugin <NS> to register. Override the base dir with
make install PLUGINS_DIR=/tmp/plugins.
Three tiers — full details in docs/testing.md:
make test # mock-only unit tests; never hits the API
make test-live-hcloud # direct live hcloud API smoke tests (opt-in, real API)
make conformance-test # full plugin contract via a real formae agent (real API)
make conformance-test-crud TEST=ssh-key # single-resource conformance filter
| Tier | Hits real API? | Build tag |
|---|---|---|
make test |
No | (none) |
make test-live-hcloud |
Yes | integration |
make conformance-test |
Yes | conformance |
The live and conformance suites are mutually exclusive by build tag, and each has its own safety gates (double opt-in, labelled resource sweeps). The credential check and labelled-resource sweeps are implemented inline in the Makefile — no shell scripts ship with this repo.
The Pkl schema lives under schema/pkl/ and defines the
desired-state surface, field hints (required, createOnly,
hasProviderDefault), and resource hints (identifier, portable,
extractable). Full layout and conventions in
docs/schema.md.
make pkl-eval # validate formae-plugin.pkl and schema/pkl/**/*.pkl
make pkg-pkl # pkl project package schema/pkl -> .out/
- Reserved label key
managed_by. The plugin injectsmanaged_by=formaeon every resource it manages and strips it from observed state.managed_byis reserved — do not declare it in desired state; a user-supplied value is silently overwritten withformae. - Optional string fields cannot be cleared to empty. On update,
empty/omitted string fields are treated as "no change" rather than "clear".
Omit the field to leave it untouched; clear it out-of-band if required.
createOnlyfields are not updatable at all. - Discovery surfaces errors, not empty lists.
Listreturns the underlying error on client-resolution failures (e.g. invalid token) and per-type API errors, so the drift workflow can distinguish "no resources exist" from "the plugin could not enumerate resources". The one exception is an unsupported resource type, which yields an empty list (the formae agent fansListout across every registered type and types this plugin does not handle legitimately enumerate as empty). extractable = falseby default. The schema package is not yet published to the registry, soformae extractis disabled until it is. See docs/schema.md.
Building an orbital .opkg and publishing it to the formae registry is
covered in docs/packaging.md.
make pkg # build bin/formae-plugin-hcloud-<version>.opkg (needs pel/orbital ops)
make publish # publish the .opkg to repo pel, channel $(CHANNEL)
- docs/development.md — local workflow, prerequisites, Make targets, env vars.
- docs/testing.md — unit vs live hcloud smoke vs conformance, safety gates, labels, filters, timeouts, cleanup.
- docs/schema.md — Pkl schema layout, resources, hints, fixtures, extractability.
- docs/schema-data-types.md — copy-pasteable Pkl examples for every schema data shape used by the plugin.
- docs/packaging.md —
.opkgbuild/publish flow.
Apache-2.0. See LICENSE.