Skip to content

Config-as-code with Terraform

Agent templates — the catalog that says what kinds of agent exist, their image, authority, and delegation policy — don’t have to be seeded by a script. They can be managed declaratively with the terraform-provider-spawnly provider, which talks directly to the registry’s control-plane API.

This page covers the control plane that gates that API, then how to drive it with Terraform. For the field-by-field template schema itself, see Defining a Template.

The control plane is a separate, secret-gated surface

The registry serves two very different kinds of caller, and they authenticate differently:

  • Agents self-register and emit events using their SPIFFE JWT-SVID as a bearer. This is the workload-identity path the rest of the platform is built on — it is never behind a shared secret.
  • Operators and tooling manage the catalog and the consent broker — creating, updating, disabling, and deleting templates. These control-plane endpoints sit behind a shared-secret bearer.

The mode is set by CONTROL_PLANE_AUTH:

ValueBehaviour
shared-secret (default)Control-plane endpoints require the bearer token from the control-plane-auth Secret.
noneControl plane is open — no token required. Convenient for a throwaway local registry.

make bootstrap generates the token once into a Kubernetes Secret named control-plane-auth and wires the same value into the registry, the orchestrator, IdentityServer, and seed.sh — so every in-cluster caller’s token matches by construction. Re-bootstrapping reuses the existing token, so clients seeded mid-session (including a running Terraform provider) keep working.

To drive the control plane from outside the cluster, pull the token out of the Secret:

Terminal window
export SPAWNLY_TOKEN=$(kubectl get secret control-plane-auth \
-o jsonpath='{.data.token}' | base64 -d)

Installing the provider

The provider module lives outside the repo’s go.work workspace and builds standalone:

Terminal window
cd terraform-provider-spawnly
make install # builds ./bin and writes dev.tfrc
export TF_CLI_CONFIG_FILE=$(pwd)/dev.tfrc

make install writes dev.tfrc, a CLI config with a dev_overrides block. Under dev overrides you skip terraform init — just run plan / apply.

Pointing it at a registry

Port-forward the in-cluster registry and point the provider at it. The provider reads its endpoint and bearer from the environment (or from endpoint / token in HCL):

Terminal window
kubectl port-forward svc/registry 18080:8080 &
export SPAWNLY_ENDPOINT=http://localhost:18080
export SPAWNLY_TOKEN=$(kubectl get secret control-plane-auth \
-o jsonpath='{.data.token}' | base64 -d)

Against a registry running open (CONTROL_PLANE_AUTH=none) leave SPAWNLY_TOKEN unset.

What the provider exposes

KindNamePurpose
Resourcespawnly_agent_templateA managed agent template — image, authority (authz_template, oauth_scopes), and a delegation block (allowed child types, grantable scopes, max_depth, per-child consent gating).
Data sourcespawnly_agent_templateRead one existing template by name.
Data sourcespawnly_agent_templatesList every template in the registry.
Data sourcespawnly_schemaRead the active SpiceDB schema the registry is enforcing.
Terminal window
cd examples/agent-template
terraform plan && terraform apply
terraform destroy

The destroy caveat

The registry refuses to delete an active template — that would strand any agent type mid-flight. terraform destroy handles this for you: it disables the template first, then deletes it. The same two-step (disable → delete) is what the control plane requires of any caller, Terraform or not.

Reference and tests

The provider’s own README has the full provider configuration table, the richer delegation example, and the generated per-resource docs. From the repo root, make test-provider runs the whole gate — fmt/vet, unit + acceptance tests, and a parity check that the provider can reproduce every seeded template — against an ephemeral registry it stands up and tears down. No Kind cluster required; just docker and the terraform CLI.