Thank you for your interest in contributing. This document covers how to set up your development environment, add a new resource transformer, write tests, and submit changes.
- Ways to Contribute
- Development Setup
- Project Structure
- Adding a New Resource Transformer
- Testing
- Code Conventions
- Submitting a Pull Request
- Commit Message Style
- Bug reports — Open a bug report. Include the tf-migrate version, the input
.tfconfiguration, and the actual vs expected output. - Feature requests — Open a feature request.
- New resource transformers — The most common contribution. See Adding a New Resource Transformer below.
- Fixes to existing transformers — Find the relevant
internal/resources/<name>/v4_to_v5.go, add a regression test inv4_to_v5_test.go, and fix the logic. - Documentation — Each resource has a
README.mdatinternal/resources/<name>/README.md. User-facing messages are catalogued inDIAGNOSTICS.md.
Prerequisites: Go 1.25+, Make, Terraform CLI
git clone https://github.com/cloudflare/tf-migrate
cd tf-migrate
go mod download
make build-allThis produces two binaries in ./bin/:
tf-migrate— the migration CLIe2e— the E2E test runner
The codebase has three main areas:
internal/resources/ — one directory per Cloudflare resource, each containing v4_to_v5.go, v4_to_v5_test.go, and README.md. This is where the migration logic lives.
internal/transform/hcl/ — shared HCL manipulation helpers used by all transformers. If you need to rename an attribute, convert a block to an attribute, or generate a moved {} block, there is almost certainly a helper here for it.
integration/v4_to_v5/testdata/ — integration test fixtures, one directory per resource, each with input/ (v4 config) and expected/ (expected v5 output) subdirectories.
Create internal/resources/<name>/ using the v5 resource name without the cloudflare_ prefix — for example dns_record or zero_trust_access_application.
Create v4_to_v5.go in that directory. Look at an existing simple transformer (e.g. internal/resources/bot_management/v4_to_v5.go) to understand the structure. The key points:
- Export a
NewV4ToV5Migrator()function that creates the struct and callsinternal.RegisterMigrator(...)to register it. Do not useinit(). - Implement all four methods of the
ResourceTransformerinterface:CanHandle,TransformConfig,GetResourceType, andPreprocess. - If the resource is renamed in v5, implement
GetResourceRename()— this enables automatic cross-file reference rewriting. - If multiple v4 names map to the same v5 name, call
RegisterMigratormultiple times and return all v4 names fromGetResourceRename(). - Register the new migrator by adding a call to
NewV4ToV5Migrator()ininternal/registry/registry.go.
Use the helpers in internal/transform/hcl/ for all HCL manipulation — renaming attributes, converting blocks to attributes, generating moved {} and import {} blocks, and so on. Avoid manipulating raw HCL tokens directly.
Create v4_to_v5_test.go in the same directory. Follow the pattern in any existing test file. Cover the main transformation cases and any edge cases (dynamic blocks, expression attributes, missing optional fields).
Create integration/v4_to_v5/testdata/<name>/input/ with one or more .tf files representing realistic v4 configurations, and integration/v4_to_v5/testdata/<name>/expected/ with the exact v5 output tf-migrate should produce.
All resource names in fixtures must use the cftftest prefix — for example resource "cloudflare_dns_record" "cftftest_example". This is enforced by make lint-testdata.
Create integration/v4_to_v5/testdata/<name>/<name>_e2e.tf with the Terraform configuration the E2E runner uses to create real infrastructure. If the E2E config needs to differ from the integration test input (for example, to avoid patterns that are valid in v4 but fail to apply in v5), use this file to provide a simpler, applyable subset.
For resources that must be imported rather than created, add a # tf-migrate:import-address= annotation on the line before the resource block. See e2e/README.md for details.
Create internal/resources/<name>/README.md documenting what changed between v4 and v5, a before/after configuration example, and any manual steps required after migration.
make test
make lint-testdataThere are three test layers:
Unit tests — fast, no I/O, test individual transformer logic:
make test-unitIntegration tests — run the full migration pipeline against fixture files in integration/v4_to_v5/testdata/. No Cloudflare credentials required:
make test-integration
# Single resource
TEST_RESOURCE=dns_record go test -v -run TestSingleResource ./integration/...E2E tests — create and destroy real Cloudflare infrastructure. Use a dedicated test account; never run against production. Requires CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_ZONE_ID, CLOUDFLARE_DOMAIN, CLOUDFLARE_API_KEY, CLOUDFLARE_EMAIL, and R2 credentials for remote state. See e2e/README.md for the full setup and command reference.
- Registration — each resource registers itself via
NewV4ToV5Migrator()callinginternal.RegisterMigrator(...). Do not edit the registry map directly. - HCL manipulation — use helpers from
internal/transform/hcl/. Do not manipulate raw HCL tokens unless no helper covers the case. - Warning comments — use
tfhcl.AppendWarningComment(body, message)to write# MIGRATION WARNING: ...into output.tffiles. Document every such warning inDIAGNOSTICS.md. - Diagnostics — append to
ctx.Diagnosticsusinghcl.DiagWarningfor issues requiring user action andhcl.DiagErrorfor failures. Document them inDIAGNOSTICS.md. - Testdata naming — all resource names in
integration/testdata must use thecftftestprefix. Enforced bymake lint-testdata. - No real credentials in testdata — use placeholder account and zone IDs.
- Fork the repository and create a branch from
main. - Make your changes and ensure
make testandmake lint-testdatapass. - Open a pull request against
mainand fill in the template — describe which resource(s) are affected and which test layers you ran. - Link any related issues.
All pull requests require at least one approving review before merge.
Use Conventional Commits:
| Prefix | When to use |
|---|---|
feat: |
New resource transformer or new CLI feature |
fix: |
Bug fix in an existing transformer or handler |
docs: |
Documentation changes only |
test: |
Adding or updating tests with no production code change |
refactor: |
Internal refactoring with no behaviour change |
ci: |
Changes to GitHub Actions workflows or scripts |
chore: |
Dependency updates, build system changes |
GoReleaser uses these prefixes to group entries in the GitHub release changelog. Commits prefixed with docs:, test:, or ci: are excluded from release notes.