Skip to content

feat(docs): add support for terraform - #13

Open
ematipico wants to merge 2 commits into
mainfrom
feat/terraform
Open

ematipico wants to merge 2 commits into
mainfrom
feat/terraform

Conversation

@ematipico

@ematipico ematipico commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Adds Terraform documentation to API operation pages. Each operation shows the Terraform resources and data sources that call it, with their attributes, an HCL example and the import command.

The data comes from the released Cloudflare Terraform provider instead of Stainless' internal SDK JSON. Every input is public and pinned to a version. The generator is offline and read-only, and a scheduled workflow keeps the data in sync.

How it works

packages/docs-site/scripts/generate-terraform-docs.ts reads local checkouts of cloudflare/terraform-provider-cloudflare (at a release tag) and of the cloudflare-go modules its go.mod requires. From those checkouts it takes:

Data Source
Descriptions; Required / Optional / Read-only attributes; types; nested schemas; sensitive and deprecated markers docs/{resources,data-sources}/*.md: tfplugindocs output, the same content the Terraform Registry shows
HCL and import examples examples/
Declaration → API operations, with lifecycle role (create / read / update / delete / import) Provider internal/services/* client calls, resolved through cloudflare-go api.md
Versions and commits used internal/version.go and .git/HEAD in each checkout

The output, src/generated/terraform-docs.json (format 2), stores each declaration once and lists the linked declarations and roles for each SDK endpoint. It also records the provider and SDK versions and commit SHAs. At build time, src/terraform-extension.ts matches those endpoints to OpenAPI operations by method + path, with path parameter names ignored. Account, zone and combined account-or-zone paths match each other, so it doesn't depend on operationId. The build warns, instead of failing, about declarations that match no operation.

pnpm generate:terraform-docs --provider-dir <provider> \
  --sdk-dir github.com/cloudflare/cloudflare-go/v7=<sdk-v7> \
  --sdk-dir github.com/cloudflare/cloudflare-go/v6=<sdk-v6>   # add --check to verify the committed file

Security

The generator reads third-party repositories, so it treats their contents as untrusted data:

  • Nothing is executed. There's no terraform CLI and no provider binary. Attributes come from committed Markdown.
  • No network access or child processes. The script only reads local files and writes JSON. A test fails if any generator script imports child_process, http, fetch(, dynamic import(), etc.
  • Confined file reading (scripts/source-tree.ts): it refuses symbolic links, .. and absolute paths, paths that end up outside the checkout, and oversized files.
  • Strict parsers. Unexpected Markdown fails with file:line. An SDK call that can't be resolved also fails the run.
  • Input checks. Each SDK checkout must match the version required by the provider's go.mod. The output is validated against the same zod schema the site uses to read it.

Sync workflow

.github/workflows/sync-terraform-docs.yml runs daily at 06:17 UTC and can be started by hand:

  1. generate has read-only permissions, no secrets and no saved git credentials.
    • It finds the latest stable provider version on the Terraform registry.
    • It clones the provider and the required cloudflare-go modules at their tags as plain git data, with symlinks disabled.
    • It runs the generator and the docs-site tests, then uploads only the generated JSON as an artifact.
  2. pull-request never sees third-party sources.
    • It commits the generated file to bot/sync-terraform-docs and opens or updates a PR with gh.
    • It waits for every check on the PR's head commit, then merges only if all of them are green, using gh pr merge --match-head-commit.
    • If no checks appear, any check fails, or the branch moved, the PR stays open for manual review.

Third-party actions are pinned to commit SHAs, and workflow inputs reach shell scripts only through env and are validated. The job does nothing until a new provider version is published.

UI

  • Each Terraform declaration is shown next to its own sticky HCL sample card. Multiple declarations can attach to the same operation.
  • Declaration header: kind, name, the provider's description, and which operations it uses here, e.g. "Called by this resource to read and import."
  • Attribute groups are Required, Optional and Read-only, with nested attributes, type labels, and sensitive / deprecated markers.
  • The sample card has the HCL example and, for resources, the terraform import command.
  • Unrelated HTTP responses are hidden on the Terraform target. Operations without Terraform support show a fallback message.
  • Terraform examples are included in agent-facing Markdown.

Coverage (provider v5.27.0, cloudflare-go v6.10.0 / v7.12.0)

  • 741 declarations (resources, data sources and list data sources); 740 linked to API operations, and 0 unresolved SDK calls.
  • 1,114 OpenAPI operations show Terraform docs, 345 of them with more than one declaration. Every CRUD operation of a resource is linked, not only create.
  • Not linked: cloudflare_snippets (its resource makes no API calls). Not documented by the provider: cloudflare_rate_limits (list data source).

Validation

  • Generator tests cover api.md and service parsing, the tfplugindocs parser (including file:line errors), version checks, source-tree confinement (symlink and path-escape attempts), and the offline guard.
  • Runtime tests cover endpoint matching across account/zone path variants, role merging, the generated-file schema, and Markdown targets.
  • All 132 docs-site tests pass, as do pnpm --filter docs-site check (0 errors), oxlint and actionlint.
  • --check reproduces the committed file byte for byte from v5.27.0 sources.
  • The workflow hasn't run on GitHub yet. It needs a DOCS_SYNC_TOKEN secret (a GitHub App or fine-grained token): PRs opened with GITHUB_TOKEN don't trigger CI, so they would never be merged automatically.

Screenshots

From the first iteration. The layout is unchanged, but the labels were updated afterwards: "Computed" is now "Read-only", and the "forces replacement" badge is gone.

localhost_4321_api_zero-trust_dlp_entries_methods_list__lang=terraform (2) localhost_4321_api_zero-trust_dlp_sensitivity-levels_methods_create__lang=terraform

@musa-cf

musa-cf commented Oct 9, 2026

Copy link
Copy Markdown

Not feedback directly on your changes, but playing around with this rendered content makes it plainly obvious that we are severely lacking in attribute and top-level resource descriptions.

Looks good though.

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.

2 participants