Skip to content

feat(cloudflare): native provider for DNS records and Tunnels#120

Merged
astrojerms merged 1 commit into
mainfrom
feat/cloudflare-provider
Jul 11, 2026
Merged

feat(cloudflare): native provider for DNS records and Tunnels#120
astrojerms merged 1 commit into
mainfrom
feat/cloudflare-provider

Conversation

@astrojerms

Copy link
Copy Markdown
Owner

What

Adds plugins/cloudflare — a native openctl provider (pluginproto v2) for deploying Cloudflare infrastructure declaratively:

Kind
cloudflare.openctl.io/v1 DNSRecord A/AAAA/CNAME/TXT/MX/… records, full CRUD
cloudflare.openctl.io/v1 Tunnel remotely-managed Cloudflare Tunnel + ingress rules

Design decisions

  • Native plugin over Terraform-wrapping. I scouted both paths. tfhost is architecturally proven but not ready for a real framework provider — it'd need msgpack state decoding, nested-block/typed config encoding, protocol-v5, and a real-binary e2e (it's only ever run against a JSON fake). The native plugin ships working Cloudflare today and validates the "wide, infra-only" thesis. tfhost remains the long-game breadth multiplier.
  • Root module, stdlib-only. Hand-rolled REST v4 client (no cloudflare-go SDK), so every existing CI job covers it with zero new module wiring.
  • Stateful via CapabilityState. Cloudflare-assigned record/tunnel IDs round-trip through the provider_state store; the controller owns create→update→delete by ID. Out-of-band deletes self-heal (apply recreates a resource whose stored ID is gone).
  • Tunnel run token is a get-token action, not status. It's a secret, so it's returned as a downloadable payload and never written to the git-synced state mirror — mirroring the k3s kubeconfig-as-action precedent.

Auth / config

API token via a credential's tokenSecretFile → the Configure bag; account/zone defaults via provider defaults. Full setup in plugins/cloudflare/README.md.

Tests

  • Unit lifecycle against a fake Cloudflare API for both kinds: create → state blob → update-in-place (same ID) → list → delete → NotFound, idempotent delete, get-token, and an assertion that the token never leaks into status.
  • Schema validation through openctl's real external-schema path (valid accepted, missing-required rejected).
  • Subprocess handshake e2e — builds the binary, spawns it through the external adapter, asserts identity/kinds/Actioner over the wire.

Local CI

gofmt / go vet / staticcheck / golangci-lint / modernize (generated-filtered) / go test ./... / go test -race (plugin + external) all green.

Follow-ups

Validate against a real Cloudflare account; round-trip tunnel ingress into observed state for precise drift; consider Zone (observed) and WAF/ruleset kinds.

Adds plugins/cloudflare, a native pluginproto (v2) external provider that
manages cloudflare.openctl.io/v1 DNSRecord and Tunnel via the Cloudflare
REST API v4. Stdlib-only (hand-rolled client, no cloudflare-go SDK) and in
the root module, so all existing CI jobs cover it with no new wiring.

Stateful via CapabilityState: Cloudflare-assigned record/tunnel IDs
round-trip through openctl's provider_state store, so the controller owns
create -> update -> delete by ID. Out-of-band deletes self-heal (apply
recreates a record/tunnel whose stored ID is gone).

- DNSRecord: full CRUD (A/AAAA/CNAME/TXT/MX/...), zone from spec or the
  provider's defaults.zoneId.
- Tunnel: create + ingress config (auto catch-all rule), named after
  metadata.name. The run token is a `get-token` ACTION returning a
  downloadable payload — never written to status/the git-synced mirror
  (mirrors the k3s kubeconfig-as-action precedent).
- CUE schemas shipped in the handshake and validated through openctl's
  real external-schema path in tests.
- Config: API token via a credential's tokenSecretFile -> Configure bag;
  account/zone defaults via provider defaults.

Chose a native plugin over wrapping the Terraform Cloudflare provider
through tfhost: tfhost is architecturally proven but still needs msgpack
state decode, nested-block/typed config encoding, protocol-v5, and a
real-binary e2e before it can host a real framework provider. The native
plugin ships working Cloudflare today.

Tests: fake-CF-API unit lifecycle for both kinds (create/state/update/
list/delete/NotFound + get-token + no-token-leak), schema validation, and
a subprocess handshake e2e through the external adapter. Makefile
build-plugin-cloudflare + install wiring; plugin README with setup.
@astrojerms astrojerms merged commit 45fae7c into main Jul 11, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant