diff --git a/README.md b/README.md
index 23c8539..d0e301e 100644
--- a/README.md
+++ b/README.md
@@ -289,6 +289,45 @@ dns_record:
Use `--apply-exemptions` to ignore these known drifts during testing.
+**Import Annotations (Import-Only Resources):**
+
+Some Cloudflare resources cannot be created via Terraform and must be imported from existing infrastructure (e.g., `zero_trust_organization`). The E2E runner supports automatic imports via annotations in `_e2e.tf` files:
+
+```hcl
+# tf-migrate:import-address=${var.cloudflare_account_id}
+resource "cloudflare_access_organization" "test" {
+ account_id = var.cloudflare_account_id
+ name = "Test Organization"
+ auth_domain = "test.cloudflareaccess.com"
+}
+```
+
+**How It Works:**
+1. E2E runner scans for `# tf-migrate:import-address=
` annotations
+2. Substitutes variables: `${var.cloudflare_account_id}` → actual account ID (e.g., `abc123`)
+3. Executes: `terraform import module.. abc123`
+4. Continues with normal E2E workflow (apply, migrate, verify)
+
+**Supported Variables:**
+- `${var.cloudflare_account_id}` - Account ID from environment
+- `${var.cloudflare_zone_id}` - Zone ID from environment
+- `${var.cloudflare_domain}` - Domain from environment
+
+**Multiple Imports:**
+Multiple resources can be annotated in a single file - all will be imported automatically before v4 apply.
+
+**Example Import Addresses:**
+```hcl
+# Account-scoped resource (just the account ID)
+# tf-migrate:import-address=${var.cloudflare_account_id}
+
+# Zone-scoped resource with path
+# tf-migrate:import-address=zones/${var.cloudflare_zone_id}/settings/waf
+
+# Complex path (for resources that need multiple identifiers)
+# tf-migrate:import-address=${var.cloudflare_account_id}/item/${var.item_id}
+```
+
**Project Structure:**
```
diff --git a/integration/v4_to_v5/testdata/zero_trust_organization/expected/terraform.tfstate b/integration/v4_to_v5/testdata/zero_trust_organization/expected/terraform.tfstate
new file mode 100644
index 0000000..ac5255e
--- /dev/null
+++ b/integration/v4_to_v5/testdata/zero_trust_organization/expected/terraform.tfstate
@@ -0,0 +1,792 @@
+{
+ "lineage": "test-zero-trust-organization-lineage",
+ "outputs": {},
+ "resources": [
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "auth_domain": "cftftest-minimal-account.cloudflareaccess.com",
+ "id": "test-account-123",
+ "name": "Minimal Account Organization",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "minimal_account",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "auth_domain": "cftftest-minimal-zone.cloudflareaccess.com",
+ "id": "test-zone-456",
+ "name": "Minimal Zone Organization",
+ "zone_id": "test-zone-456",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "minimal_zone",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "allow_authenticate_via_warp": true,
+ "auth_domain": "cftftest-complete.cloudflareaccess.com",
+ "auto_redirect_to_identity": true,
+ "custom_pages": {
+ "forbidden": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
+ "identity_denied": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
+ },
+ "id": "test-account-123",
+ "is_ui_read_only": true,
+ "login_design": {
+ "background_color": "#000000",
+ "footer_text": "Powered by Cloudflare Access",
+ "header_text": "Welcome to Our Platform",
+ "logo_path": "https://assets.cf-tf-test.com/logo.png",
+ "text_color": "#FFFFFF"
+ },
+ "name": "Complete Organization",
+ "session_duration": "24h",
+ "ui_read_only_toggle_reason": "Managed via Terraform",
+ "user_seat_expiration_inactive_time": "730h",
+ "warp_auth_session_duration": "12h"
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "complete",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "auth_domain": "cftftest-login-design.cloudflareaccess.com",
+ "id": "test-account-123",
+ "login_design": {
+ "background_color": "#1a1a2e",
+ "footer_text": "© 2026 Enterprise Inc.",
+ "header_text": "Enterprise Portal",
+ "logo_path": "https://assets.cf-tf-test.com/custom-logo.png",
+ "text_color": "#eaeaea"
+ },
+ "name": "Organization with Custom Login Design",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "with_login_design",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "auth_domain": "cftftest-custom-pages.cloudflareaccess.com",
+ "custom_pages": {
+ "forbidden": "cccccccc-cccc-cccc-cccc-cccccccccccc",
+ "identity_denied": "dddddddd-dddd-dddd-dddd-dddddddddddd"
+ },
+ "id": "test-account-123",
+ "name": "Organization with Custom Pages",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "with_custom_pages",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "auth_domain": "cftftest-both-nested.cloudflareaccess.com",
+ "custom_pages": {
+ "forbidden": "eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee"
+ },
+ "id": "test-account-123",
+ "login_design": {
+ "background_color": "#2c3e50",
+ "text_color": "#ecf0f1"
+ },
+ "name": "Organization with Both Nested Blocks",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "with_both_nested",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "allow_authenticate_via_warp": true,
+ "auth_domain": "cftftest-booleans-true.cloudflareaccess.com",
+ "auto_redirect_to_identity": true,
+ "id": "test-account-123",
+ "is_ui_read_only": true,
+ "name": "Organization with Booleans True"
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "with_booleans_true",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "auth_domain": "cftftest-durations.cloudflareaccess.com",
+ "id": "test-account-123",
+ "name": "Organization with All Duration Fields",
+ "session_duration": "48h",
+ "user_seat_expiration_inactive_time": "1460h",
+ "warp_auth_session_duration": "2h30m",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "with_durations",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "auth_domain": "cftftest-deprecated.cloudflareaccess.com",
+ "id": "test-account-123",
+ "name": "Using Deprecated Resource Name",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "deprecated_name",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "auth_domain": "cftftest-current.cloudflareaccess.com",
+ "id": "test-account-123",
+ "name": "Using Current Resource Name",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "current_name",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "auth_domain": "cftftest-partial-login-1.cloudflareaccess.com",
+ "id": "test-account-123",
+ "login_design": {
+ "background_color": "#16213e",
+ "text_color": "#f1f1f1"
+ },
+ "name": "Partial Login Design - Colors Only",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "partial_login_1",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "auth_domain": "cftftest-partial-login-2.cloudflareaccess.com",
+ "id": "test-account-123",
+ "login_design": {
+ "logo_path": "https://assets.cf-tf-test.com/simple-logo.png"
+ },
+ "name": "Partial Login Design - Logo Only",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "partial_login_2",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "auth_domain": "cftftest-partial-custom-1.cloudflareaccess.com",
+ "custom_pages": {
+ "identity_denied": "11111111-1111-1111-1111-111111111111"
+ },
+ "id": "test-account-123",
+ "name": "Partial Custom Pages - Identity Denied Only",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "partial_custom_1",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "auth_domain": "cftftest-partial-custom-2.cloudflareaccess.com",
+ "custom_pages": {
+ "forbidden": "22222222-2222-2222-2222-222222222222"
+ },
+ "id": "test-account-123",
+ "name": "Partial Custom Pages - Forbidden Only",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "partial_custom_2",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "auth_domain": "cftftest-only-session.cloudflareaccess.com",
+ "id": "test-account-123",
+ "name": "Organization with Only Session Duration",
+ "session_duration": "6h",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "only_session",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "auth_domain": "cftftest-only-seat.cloudflareaccess.com",
+ "id": "test-account-123",
+ "name": "Organization with Only Seat Expiration",
+ "user_seat_expiration_inactive_time": "730h",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "only_seat_expiration",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "auth_domain": "cftftest-only-warp.cloudflareaccess.com",
+ "id": "test-account-123",
+ "name": "Organization with Only WARP Duration",
+ "warp_auth_session_duration": "1h30m",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "only_warp_duration",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "auth_domain": "cftftest-only-readonly.cloudflareaccess.com",
+ "id": "test-account-123",
+ "is_ui_read_only": true,
+ "name": "Organization with Only Read-Only Flag",
+ "ui_read_only_toggle_reason": "Locked down for compliance",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "only_readonly",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "auth_domain": "cftftest-special.cloudflareaccess.com",
+ "id": "test-account-123",
+ "login_design": {
+ "footer_text": "Questions? Email: support@cf-tf-test.com",
+ "header_text": "Welcome! Let's get started..."
+ },
+ "name": "Org with Special: @#$% & Chars!",
+ "ui_read_only_toggle_reason": "Locked: Deployment (2026-01-20 @ 14:00 UTC)",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "special_chars",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "auth_domain": "cftftest-long.cloudflareaccess.com",
+ "id": "test-account-123",
+ "login_design": {
+ "footer_text": "This footer contains legal disclaimers and copyright notices",
+ "header_text": "This is a very long header text about the organization"
+ },
+ "name": "Organization with Very Long Name That Contains Many Characters",
+ "ui_read_only_toggle_reason": "This organization is locked because of maintenance",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "long_strings",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "auth_domain": "cftftest-duration-formats.cloudflareaccess.com",
+ "id": "test-account-123",
+ "name": "Organization with Various Duration Formats",
+ "session_duration": "2h45m",
+ "user_seat_expiration_inactive_time": "730h",
+ "warp_auth_session_duration": "90m",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "duration_formats",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "auth_domain": "cftftest-colors.cloudflareaccess.com",
+ "id": "test-account-123",
+ "login_design": {
+ "background_color": "#abc",
+ "text_color": "#FFFFFF"
+ },
+ "name": "Organization with Various Color Formats",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "color_formats",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "allow_authenticate_via_warp": false,
+ "auth_domain": "cftftest-booleans-false.cloudflareaccess.com",
+ "auto_redirect_to_identity": false,
+ "id": "test-account-123",
+ "is_ui_read_only": false,
+ "name": "Organization with Booleans False"
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "booleans_false",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "auth_domain": "cftftest-org-alpha.cloudflareaccess.com",
+ "id": "test-account-123",
+ "name": "Organization org-alpha",
+ "session_duration": "24h",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ },
+ "index_key": "org-alpha",
+ "schema_version": 0
+ },
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "auth_domain": "cftftest-org-beta.cloudflareaccess.com",
+ "id": "test-account-123",
+ "name": "Organization org-beta",
+ "session_duration": "24h",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ },
+ "index_key": "org-beta",
+ "schema_version": 0
+ },
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "auth_domain": "cftftest-org-gamma.cloudflareaccess.com",
+ "id": "test-account-123",
+ "name": "Organization org-gamma",
+ "session_duration": "24h",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ },
+ "index_key": "org-gamma",
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "foreach_set",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "auth_domain": "cftftest-dev.cloudflareaccess.com",
+ "id": "test-account-123",
+ "name": "Environment: dev",
+ "session_duration": "12h",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ },
+ "index_key": "dev",
+ "schema_version": 0
+ },
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "auth_domain": "cftftest-staging.cloudflareaccess.com",
+ "id": "test-account-123",
+ "name": "Environment: staging",
+ "session_duration": "24h",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ },
+ "index_key": "staging",
+ "schema_version": 0
+ },
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "auth_domain": "cftftest-prod.cloudflareaccess.com",
+ "id": "test-account-123",
+ "name": "Environment: prod",
+ "session_duration": "8h",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ },
+ "index_key": "prod",
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "foreach_map",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "allow_authenticate_via_warp": true,
+ "auth_domain": "cftftest-count-0.cloudflareaccess.com",
+ "id": "test-account-123",
+ "name": "Count-based Organization 0",
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ },
+ "index_key": 0,
+ "schema_version": 0
+ },
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "allow_authenticate_via_warp": false,
+ "auth_domain": "cftftest-count-1.cloudflareaccess.com",
+ "id": "test-account-123",
+ "name": "Count-based Organization 1",
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ },
+ "index_key": 1,
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "with_count",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "auth_domain": "cftftest-locals.cloudflareaccess.com",
+ "id": "test-account-123",
+ "login_design": {
+ "header_text": "Welcome to cftftest"
+ },
+ "name": "cftftest Organization with Locals",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "with_locals",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "auth_domain": "cftftest-conditional.cloudflareaccess.com",
+ "auto_redirect_to_identity": true,
+ "id": "test-account-123",
+ "is_ui_read_only": true,
+ "name": "Organization with Conditional",
+ "session_duration": "12h",
+ "allow_authenticate_via_warp": false
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "conditional",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "allow_authenticate_via_warp": false,
+ "auth_domain": "cftftest-lifecycle.cloudflareaccess.com",
+ "auto_redirect_to_identity": false,
+ "id": "test-account-123",
+ "is_ui_read_only": false,
+ "name": "Organization with Lifecycle",
+ "session_duration": "24h"
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "with_lifecycle",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "allow_authenticate_via_warp": false,
+ "auth_domain": "cftftest-depends.cloudflareaccess.com",
+ "auto_redirect_to_identity": false,
+ "id": "test-account-123",
+ "is_ui_read_only": false,
+ "name": "Organization with depends_on"
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "with_depends",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "allow_authenticate_via_warp": true,
+ "auth_domain": "cftftest-zone-comprehensive.cloudflareaccess.com",
+ "auto_redirect_to_identity": true,
+ "custom_pages": {
+ "forbidden": "33333333-3333-3333-3333-333333333333",
+ "identity_denied": "44444444-4444-4444-4444-444444444444"
+ },
+ "id": "test-zone-456",
+ "is_ui_read_only": true,
+ "login_design": {
+ "background_color": "#1e1e1e",
+ "footer_text": "Zone-level Access",
+ "header_text": "Zone Portal",
+ "logo_path": "https://zone.cf-tf-test.com/logo.png",
+ "text_color": "#ffffff"
+ },
+ "name": "Comprehensive Zone-Scoped Organization",
+ "session_duration": "12h",
+ "ui_read_only_toggle_reason": "Zone-level configuration",
+ "warp_auth_session_duration": "6h",
+ "zone_id": "test-zone-456"
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "zone_comprehensive",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_organization"
+ }
+ ],
+ "serial": 1,
+ "terraform_version": "1.0.0",
+ "version": 4
+}
diff --git a/integration/v4_to_v5/testdata/zero_trust_organization/expected/zero_trust_organization.tf b/integration/v4_to_v5/testdata/zero_trust_organization/expected/zero_trust_organization.tf
new file mode 100644
index 0000000..b9645af
--- /dev/null
+++ b/integration/v4_to_v5/testdata/zero_trust_organization/expected/zero_trust_organization.tf
@@ -0,0 +1,347 @@
+variable "cloudflare_account_id" {
+ type = string
+}
+
+variable "cloudflare_zone_id" {
+ description = "Cloudflare zone ID"
+ type = string
+}
+
+variable "cloudflare_domain" {
+ description = "Cloudflare domain for testing"
+ type = string
+}
+
+locals {
+ name_prefix = "cftftest"
+ org_names = toset(["org-alpha", "org-beta", "org-gamma"])
+ org_configs = {
+ dev = { auth_domain = "${local.name_prefix}-dev.cloudflareaccess.com", session = "12h" }
+ staging = { auth_domain = "${local.name_prefix}-staging.cloudflareaccess.com", session = "24h" }
+ prod = { auth_domain = "${local.name_prefix}-prod.cloudflareaccess.com", session = "8h" }
+ }
+}
+
+# ===== Basic Scenarios (8) =====
+resource "cloudflare_zero_trust_organization" "minimal_account" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-minimal-account.cloudflareaccess.com"
+ name = "Minimal Account Organization"
+}
+
+resource "cloudflare_zero_trust_organization" "minimal_zone" {
+ zone_id = var.cloudflare_zone_id
+ auth_domain = "${local.name_prefix}-minimal-zone.cloudflareaccess.com"
+ name = "Minimal Zone Organization"
+}
+
+resource "cloudflare_zero_trust_organization" "complete" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-complete.cloudflareaccess.com"
+ name = "Complete Organization"
+ is_ui_read_only = true
+ ui_read_only_toggle_reason = "Managed via Terraform"
+ user_seat_expiration_inactive_time = "730h"
+ auto_redirect_to_identity = true
+ session_duration = "24h"
+ allow_authenticate_via_warp = true
+ warp_auth_session_duration = "12h"
+
+
+ login_design = {
+ background_color = "#000000"
+ text_color = "#FFFFFF"
+ logo_path = "https://assets.cf-tf-test.com/logo.png"
+ header_text = "Welcome to Our Platform"
+ footer_text = "Powered by Cloudflare Access"
+ }
+ custom_pages = {
+ forbidden = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
+ identity_denied = "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
+ }
+}
+
+resource "cloudflare_zero_trust_organization" "with_login_design" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-login-design.cloudflareaccess.com"
+ name = "Organization with Custom Login Design"
+
+ login_design = {
+ background_color = "#1a1a2e"
+ text_color = "#eaeaea"
+ logo_path = "https://assets.cf-tf-test.com/custom-logo.png"
+ header_text = "Enterprise Portal"
+ footer_text = "© 2026 Enterprise Inc."
+ }
+}
+
+resource "cloudflare_zero_trust_organization" "with_custom_pages" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-custom-pages.cloudflareaccess.com"
+ name = "Organization with Custom Pages"
+
+ custom_pages = {
+ forbidden = "cccccccc-cccc-cccc-cccc-cccccccccccc"
+ identity_denied = "dddddddd-dddd-dddd-dddd-dddddddddddd"
+ }
+}
+
+resource "cloudflare_zero_trust_organization" "with_both_nested" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-both-nested.cloudflareaccess.com"
+ name = "Organization with Both Nested Blocks"
+
+
+ login_design = {
+ background_color = "#2c3e50"
+ text_color = "#ecf0f1"
+ }
+ custom_pages = {
+ forbidden = "eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee"
+ }
+}
+
+resource "cloudflare_zero_trust_organization" "with_booleans_true" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-booleans-true.cloudflareaccess.com"
+ name = "Organization with Booleans True"
+ is_ui_read_only = true
+ auto_redirect_to_identity = true
+ allow_authenticate_via_warp = true
+}
+
+resource "cloudflare_zero_trust_organization" "with_durations" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-durations.cloudflareaccess.com"
+ name = "Organization with All Duration Fields"
+ session_duration = "48h"
+ user_seat_expiration_inactive_time = "1460h"
+ warp_auth_session_duration = "2h30m"
+}
+
+# ===== v4 Resource Name Variations (2) =====
+resource "cloudflare_zero_trust_organization" "deprecated_name" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-deprecated.cloudflareaccess.com"
+ name = "Using Deprecated Resource Name"
+}
+
+resource "cloudflare_zero_trust_organization" "current_name" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-current.cloudflareaccess.com"
+ name = "Using Current Resource Name"
+}
+
+# ===== Partial Field Combinations (8) =====
+resource "cloudflare_zero_trust_organization" "partial_login_1" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-partial-login-1.cloudflareaccess.com"
+ name = "Partial Login Design - Colors Only"
+
+ login_design = {
+ background_color = "#16213e"
+ text_color = "#f1f1f1"
+ }
+}
+
+resource "cloudflare_zero_trust_organization" "partial_login_2" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-partial-login-2.cloudflareaccess.com"
+ name = "Partial Login Design - Logo Only"
+
+ login_design = {
+ logo_path = "https://assets.cf-tf-test.com/simple-logo.png"
+ }
+}
+
+resource "cloudflare_zero_trust_organization" "partial_custom_1" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-partial-custom-1.cloudflareaccess.com"
+ name = "Partial Custom Pages - Identity Denied Only"
+
+ custom_pages = {
+ identity_denied = "11111111-1111-1111-1111-111111111111"
+ }
+}
+
+resource "cloudflare_zero_trust_organization" "partial_custom_2" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-partial-custom-2.cloudflareaccess.com"
+ name = "Partial Custom Pages - Forbidden Only"
+
+ custom_pages = {
+ forbidden = "22222222-2222-2222-2222-222222222222"
+ }
+}
+
+resource "cloudflare_zero_trust_organization" "only_session" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-only-session.cloudflareaccess.com"
+ name = "Organization with Only Session Duration"
+ session_duration = "6h"
+}
+
+resource "cloudflare_zero_trust_organization" "only_seat_expiration" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-only-seat.cloudflareaccess.com"
+ name = "Organization with Only Seat Expiration"
+ user_seat_expiration_inactive_time = "730h"
+}
+
+resource "cloudflare_zero_trust_organization" "only_warp_duration" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-only-warp.cloudflareaccess.com"
+ name = "Organization with Only WARP Duration"
+ warp_auth_session_duration = "1h30m"
+}
+
+resource "cloudflare_zero_trust_organization" "only_readonly" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-only-readonly.cloudflareaccess.com"
+ name = "Organization with Only Read-Only Flag"
+ is_ui_read_only = true
+ ui_read_only_toggle_reason = "Locked down for compliance"
+}
+
+# ===== Edge Cases (5) =====
+resource "cloudflare_zero_trust_organization" "special_chars" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-special.cloudflareaccess.com"
+ name = "Org with Special: @#$% & Chars!"
+ ui_read_only_toggle_reason = "Locked: Deployment (2026-01-20 @ 14:00 UTC)"
+
+ login_design = {
+ header_text = "Welcome! Let's get started..."
+ footer_text = "Questions? Email: support@cf-tf-test.com"
+ }
+}
+
+resource "cloudflare_zero_trust_organization" "long_strings" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-long.cloudflareaccess.com"
+ name = "Organization with Very Long Name That Contains Many Characters"
+ ui_read_only_toggle_reason = "This organization is locked because of maintenance"
+
+ login_design = {
+ header_text = "This is a very long header text about the organization"
+ footer_text = "This footer contains legal disclaimers and copyright notices"
+ }
+}
+
+resource "cloudflare_zero_trust_organization" "duration_formats" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-duration-formats.cloudflareaccess.com"
+ name = "Organization with Various Duration Formats"
+ session_duration = "2h45m"
+ user_seat_expiration_inactive_time = "730h"
+ warp_auth_session_duration = "90m"
+}
+
+resource "cloudflare_zero_trust_organization" "color_formats" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-colors.cloudflareaccess.com"
+ name = "Organization with Various Color Formats"
+
+ login_design = {
+ background_color = "#abc"
+ text_color = "#FFFFFF"
+ }
+}
+
+resource "cloudflare_zero_trust_organization" "booleans_false" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-booleans-false.cloudflareaccess.com"
+ name = "Organization with Booleans False"
+ is_ui_read_only = false
+ auto_redirect_to_identity = false
+ allow_authenticate_via_warp = false
+}
+
+# ===== Terraform Patterns (8) =====
+resource "cloudflare_zero_trust_organization" "foreach_set" {
+ for_each = local.org_names
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-${each.value}.cloudflareaccess.com"
+ name = "Organization ${each.value}"
+ session_duration = "24h"
+}
+
+resource "cloudflare_zero_trust_organization" "foreach_map" {
+ for_each = local.org_configs
+ account_id = var.cloudflare_account_id
+ auth_domain = each.value.auth_domain
+ name = "Environment: ${each.key}"
+ session_duration = each.value.session
+}
+
+resource "cloudflare_zero_trust_organization" "with_count" {
+ count = 2
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-count-${count.index}.cloudflareaccess.com"
+ name = "Count-based Organization ${count.index}"
+ allow_authenticate_via_warp = count.index == 0
+}
+
+resource "cloudflare_zero_trust_organization" "with_locals" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-locals.cloudflareaccess.com"
+ name = "${local.name_prefix} Organization with Locals"
+
+ login_design = {
+ header_text = "Welcome to ${local.name_prefix}"
+ }
+}
+
+resource "cloudflare_zero_trust_organization" "conditional" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-conditional.cloudflareaccess.com"
+ name = "Organization with Conditional"
+ is_ui_read_only = true
+ auto_redirect_to_identity = true
+ session_duration = true ? "12h" : "24h"
+}
+
+# ===== Meta-Arguments (3) =====
+resource "cloudflare_zero_trust_organization" "with_lifecycle" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-lifecycle.cloudflareaccess.com"
+ name = "Organization with Lifecycle"
+ session_duration = "24h"
+
+ lifecycle {
+ create_before_destroy = true
+ }
+}
+
+resource "cloudflare_zero_trust_organization" "with_depends" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-depends.cloudflareaccess.com"
+ name = "Organization with depends_on"
+
+ depends_on = [cloudflare_zero_trust_organization.minimal_account]
+}
+
+resource "cloudflare_zero_trust_organization" "zone_comprehensive" {
+ zone_id = var.cloudflare_zone_id
+ auth_domain = "${local.name_prefix}-zone-comprehensive.cloudflareaccess.com"
+ name = "Comprehensive Zone-Scoped Organization"
+ is_ui_read_only = true
+ ui_read_only_toggle_reason = "Zone-level configuration"
+ auto_redirect_to_identity = true
+ session_duration = "12h"
+ allow_authenticate_via_warp = true
+ warp_auth_session_duration = "6h"
+
+
+ login_design = {
+ background_color = "#1e1e1e"
+ text_color = "#ffffff"
+ logo_path = "https://zone.cf-tf-test.com/logo.png"
+ header_text = "Zone Portal"
+ footer_text = "Zone-level Access"
+ }
+ custom_pages = {
+ forbidden = "33333333-3333-3333-3333-333333333333"
+ identity_denied = "44444444-4444-4444-4444-444444444444"
+ }
+}
diff --git a/integration/v4_to_v5/testdata/zero_trust_organization/input/terraform.tfstate b/integration/v4_to_v5/testdata/zero_trust_organization/input/terraform.tfstate
new file mode 100644
index 0000000..5a9c4fc
--- /dev/null
+++ b/integration/v4_to_v5/testdata/zero_trust_organization/input/terraform.tfstate
@@ -0,0 +1,701 @@
+{
+ "lineage": "test-zero-trust-organization-lineage",
+ "outputs": {},
+ "resources": [
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-minimal-account.cloudflareaccess.com",
+ "name": "Minimal Account Organization"
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "minimal_account",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "zone_id": "test-zone-456",
+ "id": "test-zone-456",
+ "auth_domain": "cftftest-minimal-zone.cloudflareaccess.com",
+ "name": "Minimal Zone Organization"
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "minimal_zone",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-complete.cloudflareaccess.com",
+ "name": "Complete Organization",
+ "is_ui_read_only": true,
+ "ui_read_only_toggle_reason": "Managed via Terraform",
+ "user_seat_expiration_inactive_time": "730h",
+ "auto_redirect_to_identity": true,
+ "session_duration": "24h",
+ "allow_authenticate_via_warp": true,
+ "warp_auth_session_duration": "12h",
+ "login_design": {
+ "background_color": "#000000",
+ "text_color": "#FFFFFF",
+ "logo_path": "https://assets.cf-tf-test.com/logo.png",
+ "header_text": "Welcome to Our Platform",
+ "footer_text": "Powered by Cloudflare Access"
+ },
+ "custom_pages": {
+ "forbidden": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
+ "identity_denied": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
+ }
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "complete",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-login-design.cloudflareaccess.com",
+ "name": "Organization with Custom Login Design",
+ "login_design": {
+ "background_color": "#1a1a2e",
+ "text_color": "#eaeaea",
+ "logo_path": "https://assets.cf-tf-test.com/custom-logo.png",
+ "header_text": "Enterprise Portal",
+ "footer_text": "© 2026 Enterprise Inc."
+ }
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "with_login_design",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-custom-pages.cloudflareaccess.com",
+ "name": "Organization with Custom Pages",
+ "custom_pages": {
+ "forbidden": "cccccccc-cccc-cccc-cccc-cccccccccccc",
+ "identity_denied": "dddddddd-dddd-dddd-dddd-dddddddddddd"
+ }
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "with_custom_pages",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-both-nested.cloudflareaccess.com",
+ "name": "Organization with Both Nested Blocks",
+ "login_design": {
+ "background_color": "#2c3e50",
+ "text_color": "#ecf0f1"
+ },
+ "custom_pages": {
+ "forbidden": "eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee"
+ }
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "with_both_nested",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-booleans-true.cloudflareaccess.com",
+ "name": "Organization with Booleans True",
+ "is_ui_read_only": true,
+ "auto_redirect_to_identity": true,
+ "allow_authenticate_via_warp": true
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "with_booleans_true",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-durations.cloudflareaccess.com",
+ "name": "Organization with All Duration Fields",
+ "session_duration": "48h",
+ "user_seat_expiration_inactive_time": "1460h",
+ "warp_auth_session_duration": "2h30m"
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "with_durations",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-deprecated.cloudflareaccess.com",
+ "name": "Using Deprecated Resource Name"
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "deprecated_name",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-current.cloudflareaccess.com",
+ "name": "Using Current Resource Name"
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "current_name",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-partial-login-1.cloudflareaccess.com",
+ "name": "Partial Login Design - Colors Only",
+ "login_design": {
+ "background_color": "#16213e",
+ "text_color": "#f1f1f1"
+ }
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "partial_login_1",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-partial-login-2.cloudflareaccess.com",
+ "name": "Partial Login Design - Logo Only",
+ "login_design": {
+ "logo_path": "https://assets.cf-tf-test.com/simple-logo.png"
+ }
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "partial_login_2",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-partial-custom-1.cloudflareaccess.com",
+ "name": "Partial Custom Pages - Identity Denied Only",
+ "custom_pages": {
+ "identity_denied": "11111111-1111-1111-1111-111111111111"
+ }
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "partial_custom_1",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-partial-custom-2.cloudflareaccess.com",
+ "name": "Partial Custom Pages - Forbidden Only",
+ "custom_pages": {
+ "forbidden": "22222222-2222-2222-2222-222222222222"
+ }
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "partial_custom_2",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-only-session.cloudflareaccess.com",
+ "name": "Organization with Only Session Duration",
+ "session_duration": "6h"
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "only_session",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-only-seat.cloudflareaccess.com",
+ "name": "Organization with Only Seat Expiration",
+ "user_seat_expiration_inactive_time": "730h"
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "only_seat_expiration",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-only-warp.cloudflareaccess.com",
+ "name": "Organization with Only WARP Duration",
+ "warp_auth_session_duration": "1h30m"
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "only_warp_duration",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-only-readonly.cloudflareaccess.com",
+ "name": "Organization with Only Read-Only Flag",
+ "is_ui_read_only": true,
+ "ui_read_only_toggle_reason": "Locked down for compliance"
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "only_readonly",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-special.cloudflareaccess.com",
+ "name": "Org with Special: @#$% & Chars!",
+ "ui_read_only_toggle_reason": "Locked: Deployment (2026-01-20 @ 14:00 UTC)",
+ "login_design": {
+ "header_text": "Welcome! Let's get started...",
+ "footer_text": "Questions? Email: support@cf-tf-test.com"
+ }
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "special_chars",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-long.cloudflareaccess.com",
+ "name": "Organization with Very Long Name That Contains Many Characters",
+ "ui_read_only_toggle_reason": "This organization is locked because of maintenance",
+ "login_design": {
+ "header_text": "This is a very long header text about the organization",
+ "footer_text": "This footer contains legal disclaimers and copyright notices"
+ }
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "long_strings",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-duration-formats.cloudflareaccess.com",
+ "name": "Organization with Various Duration Formats",
+ "session_duration": "2h45m",
+ "user_seat_expiration_inactive_time": "730h",
+ "warp_auth_session_duration": "90m"
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "duration_formats",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-colors.cloudflareaccess.com",
+ "name": "Organization with Various Color Formats",
+ "login_design": {
+ "background_color": "#abc",
+ "text_color": "#FFFFFF"
+ }
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "color_formats",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-booleans-false.cloudflareaccess.com",
+ "name": "Organization with Booleans False",
+ "is_ui_read_only": false,
+ "auto_redirect_to_identity": false,
+ "allow_authenticate_via_warp": false
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "booleans_false",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-org-alpha.cloudflareaccess.com",
+ "name": "Organization org-alpha",
+ "session_duration": "24h"
+ },
+ "index_key": "org-alpha",
+ "schema_version": 0
+ },
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-org-beta.cloudflareaccess.com",
+ "name": "Organization org-beta",
+ "session_duration": "24h"
+ },
+ "index_key": "org-beta",
+ "schema_version": 0
+ },
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-org-gamma.cloudflareaccess.com",
+ "name": "Organization org-gamma",
+ "session_duration": "24h"
+ },
+ "index_key": "org-gamma",
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "foreach_set",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-dev.cloudflareaccess.com",
+ "name": "Environment: dev",
+ "session_duration": "12h"
+ },
+ "index_key": "dev",
+ "schema_version": 0
+ },
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-staging.cloudflareaccess.com",
+ "name": "Environment: staging",
+ "session_duration": "24h"
+ },
+ "index_key": "staging",
+ "schema_version": 0
+ },
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-prod.cloudflareaccess.com",
+ "name": "Environment: prod",
+ "session_duration": "8h"
+ },
+ "index_key": "prod",
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "foreach_map",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-count-0.cloudflareaccess.com",
+ "name": "Count-based Organization 0",
+ "allow_authenticate_via_warp": true
+ },
+ "index_key": 0,
+ "schema_version": 0
+ },
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-count-1.cloudflareaccess.com",
+ "name": "Count-based Organization 1",
+ "allow_authenticate_via_warp": false
+ },
+ "index_key": 1,
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "with_count",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-locals.cloudflareaccess.com",
+ "name": "cftftest Organization with Locals",
+ "login_design": {
+ "header_text": "Welcome to cftftest"
+ }
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "with_locals",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-conditional.cloudflareaccess.com",
+ "name": "Organization with Conditional",
+ "is_ui_read_only": true,
+ "auto_redirect_to_identity": true,
+ "session_duration": "12h"
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "conditional",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-lifecycle.cloudflareaccess.com",
+ "name": "Organization with Lifecycle",
+ "session_duration": "24h"
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "with_lifecycle",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "account_id": "test-account-123",
+ "id": "test-account-123",
+ "auth_domain": "cftftest-depends.cloudflareaccess.com",
+ "name": "Organization with depends_on"
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "with_depends",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_access_organization"
+ },
+ {
+ "instances": [
+ {
+ "attributes": {
+ "zone_id": "test-zone-456",
+ "id": "test-zone-456",
+ "auth_domain": "cftftest-zone-comprehensive.cloudflareaccess.com",
+ "name": "Comprehensive Zone-Scoped Organization",
+ "is_ui_read_only": true,
+ "ui_read_only_toggle_reason": "Zone-level configuration",
+ "auto_redirect_to_identity": true,
+ "session_duration": "12h",
+ "allow_authenticate_via_warp": true,
+ "warp_auth_session_duration": "6h",
+ "login_design": {
+ "background_color": "#1e1e1e",
+ "text_color": "#ffffff",
+ "logo_path": "https://zone.cf-tf-test.com/logo.png",
+ "header_text": "Zone Portal",
+ "footer_text": "Zone-level Access"
+ },
+ "custom_pages": {
+ "forbidden": "33333333-3333-3333-3333-333333333333",
+ "identity_denied": "44444444-4444-4444-4444-444444444444"
+ }
+ },
+ "schema_version": 0
+ }
+ ],
+ "mode": "managed",
+ "name": "zone_comprehensive",
+ "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
+ "type": "cloudflare_zero_trust_access_organization"
+ }
+ ],
+ "serial": 1,
+ "terraform_version": "1.0.0",
+ "version": 4
+}
diff --git a/integration/v4_to_v5/testdata/zero_trust_organization/input/zero_trust_organization.tf b/integration/v4_to_v5/testdata/zero_trust_organization/input/zero_trust_organization.tf
new file mode 100644
index 0000000..10c09ed
--- /dev/null
+++ b/integration/v4_to_v5/testdata/zero_trust_organization/input/zero_trust_organization.tf
@@ -0,0 +1,347 @@
+variable "cloudflare_account_id" {
+ type = string
+}
+
+variable "cloudflare_zone_id" {
+ description = "Cloudflare zone ID"
+ type = string
+}
+
+variable "cloudflare_domain" {
+ description = "Cloudflare domain for testing"
+ type = string
+}
+
+locals {
+ name_prefix = "cftftest"
+ org_names = toset(["org-alpha", "org-beta", "org-gamma"])
+ org_configs = {
+ dev = { auth_domain = "${local.name_prefix}-dev.cloudflareaccess.com", session = "12h" }
+ staging = { auth_domain = "${local.name_prefix}-staging.cloudflareaccess.com", session = "24h" }
+ prod = { auth_domain = "${local.name_prefix}-prod.cloudflareaccess.com", session = "8h" }
+ }
+}
+
+# ===== Basic Scenarios (8) =====
+resource "cloudflare_access_organization" "minimal_account" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-minimal-account.cloudflareaccess.com"
+ name = "Minimal Account Organization"
+}
+
+resource "cloudflare_access_organization" "minimal_zone" {
+ zone_id = var.cloudflare_zone_id
+ auth_domain = "${local.name_prefix}-minimal-zone.cloudflareaccess.com"
+ name = "Minimal Zone Organization"
+}
+
+resource "cloudflare_access_organization" "complete" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-complete.cloudflareaccess.com"
+ name = "Complete Organization"
+ is_ui_read_only = true
+ ui_read_only_toggle_reason = "Managed via Terraform"
+ user_seat_expiration_inactive_time = "730h"
+ auto_redirect_to_identity = true
+ session_duration = "24h"
+ allow_authenticate_via_warp = true
+ warp_auth_session_duration = "12h"
+
+ login_design {
+ background_color = "#000000"
+ text_color = "#FFFFFF"
+ logo_path = "https://assets.cf-tf-test.com/logo.png"
+ header_text = "Welcome to Our Platform"
+ footer_text = "Powered by Cloudflare Access"
+ }
+
+ custom_pages {
+ forbidden = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
+ identity_denied = "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
+ }
+}
+
+resource "cloudflare_access_organization" "with_login_design" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-login-design.cloudflareaccess.com"
+ name = "Organization with Custom Login Design"
+
+ login_design {
+ background_color = "#1a1a2e"
+ text_color = "#eaeaea"
+ logo_path = "https://assets.cf-tf-test.com/custom-logo.png"
+ header_text = "Enterprise Portal"
+ footer_text = "© 2026 Enterprise Inc."
+ }
+}
+
+resource "cloudflare_access_organization" "with_custom_pages" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-custom-pages.cloudflareaccess.com"
+ name = "Organization with Custom Pages"
+
+ custom_pages {
+ forbidden = "cccccccc-cccc-cccc-cccc-cccccccccccc"
+ identity_denied = "dddddddd-dddd-dddd-dddd-dddddddddddd"
+ }
+}
+
+resource "cloudflare_access_organization" "with_both_nested" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-both-nested.cloudflareaccess.com"
+ name = "Organization with Both Nested Blocks"
+
+ login_design {
+ background_color = "#2c3e50"
+ text_color = "#ecf0f1"
+ }
+
+ custom_pages {
+ forbidden = "eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee"
+ }
+}
+
+resource "cloudflare_access_organization" "with_booleans_true" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-booleans-true.cloudflareaccess.com"
+ name = "Organization with Booleans True"
+ is_ui_read_only = true
+ auto_redirect_to_identity = true
+ allow_authenticate_via_warp = true
+}
+
+resource "cloudflare_access_organization" "with_durations" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-durations.cloudflareaccess.com"
+ name = "Organization with All Duration Fields"
+ session_duration = "48h"
+ user_seat_expiration_inactive_time = "1460h"
+ warp_auth_session_duration = "2h30m"
+}
+
+# ===== v4 Resource Name Variations (2) =====
+resource "cloudflare_access_organization" "deprecated_name" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-deprecated.cloudflareaccess.com"
+ name = "Using Deprecated Resource Name"
+}
+
+resource "cloudflare_zero_trust_access_organization" "current_name" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-current.cloudflareaccess.com"
+ name = "Using Current Resource Name"
+}
+
+# ===== Partial Field Combinations (8) =====
+resource "cloudflare_access_organization" "partial_login_1" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-partial-login-1.cloudflareaccess.com"
+ name = "Partial Login Design - Colors Only"
+
+ login_design {
+ background_color = "#16213e"
+ text_color = "#f1f1f1"
+ }
+}
+
+resource "cloudflare_access_organization" "partial_login_2" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-partial-login-2.cloudflareaccess.com"
+ name = "Partial Login Design - Logo Only"
+
+ login_design {
+ logo_path = "https://assets.cf-tf-test.com/simple-logo.png"
+ }
+}
+
+resource "cloudflare_access_organization" "partial_custom_1" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-partial-custom-1.cloudflareaccess.com"
+ name = "Partial Custom Pages - Identity Denied Only"
+
+ custom_pages {
+ identity_denied = "11111111-1111-1111-1111-111111111111"
+ }
+}
+
+resource "cloudflare_access_organization" "partial_custom_2" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-partial-custom-2.cloudflareaccess.com"
+ name = "Partial Custom Pages - Forbidden Only"
+
+ custom_pages {
+ forbidden = "22222222-2222-2222-2222-222222222222"
+ }
+}
+
+resource "cloudflare_access_organization" "only_session" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-only-session.cloudflareaccess.com"
+ name = "Organization with Only Session Duration"
+ session_duration = "6h"
+}
+
+resource "cloudflare_access_organization" "only_seat_expiration" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-only-seat.cloudflareaccess.com"
+ name = "Organization with Only Seat Expiration"
+ user_seat_expiration_inactive_time = "730h"
+}
+
+resource "cloudflare_access_organization" "only_warp_duration" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-only-warp.cloudflareaccess.com"
+ name = "Organization with Only WARP Duration"
+ warp_auth_session_duration = "1h30m"
+}
+
+resource "cloudflare_access_organization" "only_readonly" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-only-readonly.cloudflareaccess.com"
+ name = "Organization with Only Read-Only Flag"
+ is_ui_read_only = true
+ ui_read_only_toggle_reason = "Locked down for compliance"
+}
+
+# ===== Edge Cases (5) =====
+resource "cloudflare_access_organization" "special_chars" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-special.cloudflareaccess.com"
+ name = "Org with Special: @#$% & Chars!"
+ ui_read_only_toggle_reason = "Locked: Deployment (2026-01-20 @ 14:00 UTC)"
+
+ login_design {
+ header_text = "Welcome! Let's get started..."
+ footer_text = "Questions? Email: support@cf-tf-test.com"
+ }
+}
+
+resource "cloudflare_access_organization" "long_strings" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-long.cloudflareaccess.com"
+ name = "Organization with Very Long Name That Contains Many Characters"
+ ui_read_only_toggle_reason = "This organization is locked because of maintenance"
+
+ login_design {
+ header_text = "This is a very long header text about the organization"
+ footer_text = "This footer contains legal disclaimers and copyright notices"
+ }
+}
+
+resource "cloudflare_access_organization" "duration_formats" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-duration-formats.cloudflareaccess.com"
+ name = "Organization with Various Duration Formats"
+ session_duration = "2h45m"
+ user_seat_expiration_inactive_time = "730h"
+ warp_auth_session_duration = "90m"
+}
+
+resource "cloudflare_access_organization" "color_formats" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-colors.cloudflareaccess.com"
+ name = "Organization with Various Color Formats"
+
+ login_design {
+ background_color = "#abc"
+ text_color = "#FFFFFF"
+ }
+}
+
+resource "cloudflare_access_organization" "booleans_false" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-booleans-false.cloudflareaccess.com"
+ name = "Organization with Booleans False"
+ is_ui_read_only = false
+ auto_redirect_to_identity = false
+ allow_authenticate_via_warp = false
+}
+
+# ===== Terraform Patterns (8) =====
+resource "cloudflare_access_organization" "foreach_set" {
+ for_each = local.org_names
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-${each.value}.cloudflareaccess.com"
+ name = "Organization ${each.value}"
+ session_duration = "24h"
+}
+
+resource "cloudflare_zero_trust_access_organization" "foreach_map" {
+ for_each = local.org_configs
+ account_id = var.cloudflare_account_id
+ auth_domain = each.value.auth_domain
+ name = "Environment: ${each.key}"
+ session_duration = each.value.session
+}
+
+resource "cloudflare_access_organization" "with_count" {
+ count = 2
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-count-${count.index}.cloudflareaccess.com"
+ name = "Count-based Organization ${count.index}"
+ allow_authenticate_via_warp = count.index == 0
+}
+
+resource "cloudflare_access_organization" "with_locals" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-locals.cloudflareaccess.com"
+ name = "${local.name_prefix} Organization with Locals"
+
+ login_design {
+ header_text = "Welcome to ${local.name_prefix}"
+ }
+}
+
+resource "cloudflare_access_organization" "conditional" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-conditional.cloudflareaccess.com"
+ name = "Organization with Conditional"
+ is_ui_read_only = true
+ auto_redirect_to_identity = true
+ session_duration = true ? "12h" : "24h"
+}
+
+# ===== Meta-Arguments (3) =====
+resource "cloudflare_access_organization" "with_lifecycle" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-lifecycle.cloudflareaccess.com"
+ name = "Organization with Lifecycle"
+ session_duration = "24h"
+
+ lifecycle {
+ create_before_destroy = true
+ }
+}
+
+resource "cloudflare_access_organization" "with_depends" {
+ account_id = var.cloudflare_account_id
+ auth_domain = "${local.name_prefix}-depends.cloudflareaccess.com"
+ name = "Organization with depends_on"
+
+ depends_on = [cloudflare_access_organization.minimal_account]
+}
+
+resource "cloudflare_zero_trust_access_organization" "zone_comprehensive" {
+ zone_id = var.cloudflare_zone_id
+ auth_domain = "${local.name_prefix}-zone-comprehensive.cloudflareaccess.com"
+ name = "Comprehensive Zone-Scoped Organization"
+ is_ui_read_only = true
+ ui_read_only_toggle_reason = "Zone-level configuration"
+ auto_redirect_to_identity = true
+ session_duration = "12h"
+ allow_authenticate_via_warp = true
+ warp_auth_session_duration = "6h"
+
+ login_design {
+ background_color = "#1e1e1e"
+ text_color = "#ffffff"
+ logo_path = "https://zone.cf-tf-test.com/logo.png"
+ header_text = "Zone Portal"
+ footer_text = "Zone-level Access"
+ }
+
+ custom_pages {
+ forbidden = "33333333-3333-3333-3333-333333333333"
+ identity_denied = "44444444-4444-4444-4444-444444444444"
+ }
+}
diff --git a/integration/v4_to_v5/testdata/zero_trust_organization/input/zero_trust_organization_e2e.tf b/integration/v4_to_v5/testdata/zero_trust_organization/input/zero_trust_organization_e2e.tf
new file mode 100644
index 0000000..632e65d
--- /dev/null
+++ b/integration/v4_to_v5/testdata/zero_trust_organization/input/zero_trust_organization_e2e.tf
@@ -0,0 +1,90 @@
+# E2E Test Configuration for zero_trust_organization
+#
+# IMPORTANT: This resource is IMPORT-ONLY in v4 and is a SINGLETON.
+# - Organizations cannot be created via Terraform
+# - They are created when you enable Zero Trust in the Cloudflare dashboard
+# - Each account has exactly ONE organization
+# - Therefore, this config has only ONE resource
+#
+# E2E TEST WORKFLOW (AUTOMATED):
+# ================================
+#
+# The E2E runner handles imports automatically via the tf-migrate:import-address annotation!
+#
+# Automated workflow (run via `e2e run`):
+# 1. PREREQUISITE: Enable Zero Trust in your Cloudflare account via dashboard
+# 2. Runner automatically imports the organization (detects annotation below)
+# 3. V4 apply configures the imported organization
+# 4. Migration transforms v4 config to v5
+# 5. V5 plan verifies no drift
+# 6. V5 apply succeeds
+#
+# Manual workflow (for testing without E2E runner):
+# 1. PREREQUISITE: Enable Zero Trust via dashboard
+# 2. IMPORT: terraform import cloudflare_access_organization.test YOUR_ACCOUNT_ID
+# 3. APPLY: terraform apply
+# 4. MIGRATE: tf-migrate migrate --config-dir .
+# 5. UPGRADE: terraform init -upgrade
+# 6. VERIFY: terraform plan # Should show "No changes"
+# 7. APPLY: terraform apply
+#
+# SUCCESS CRITERIA:
+# - Import succeeds (automatic in E2E runner)
+# - V4 apply succeeds
+# - Migration transforms config correctly
+# - V5 plan shows no changes
+# - V5 apply succeeds
+#
+# NOTE: We only have ONE resource because organizations are singletons.
+# We cannot test both v4 resource names (cloudflare_access_organization and
+# cloudflare_zero_trust_access_organization) simultaneously because they would
+# both try to manage the same underlying organization.
+
+locals {
+ name_prefix = "cftftest"
+}
+
+variable "cloudflare_account_id" {
+ description = "Cloudflare account ID"
+ type = string
+}
+
+variable "cloudflare_zone_id" {
+ description = "Cloudflare zone ID"
+ type = string
+}
+
+variable "cloudflare_domain" {
+ description = "Cloudflare domain for testing"
+ type = string
+}
+
+# Basic organization configuration for E2E testing
+# NOTE: This is a SINGLETON resource - only one organization per account.
+#
+# IMPORT ANNOTATION: The line below tells the E2E runner to automatically import this resource.
+# The runner will execute: terraform import module.zero_trust_organization.cloudflare_access_organization.test
+# tf-migrate:import-address=${var.cloudflare_account_id}
+resource "cloudflare_access_organization" "test" {
+ account_id = var.cloudflare_account_id
+ name = "${local.name_prefix} E2E Test Organization"
+ auth_domain = "${local.name_prefix}-e2e.cloudflareaccess.com"
+
+ # Test MaxItems:1 block transformation
+ login_design {
+ background_color = "#000000"
+ text_color = "#FFFFFF"
+ logo_path = "https://e2e-test.cf-tf-test.com/logo.png"
+ header_text = "E2E Test Portal"
+ footer_text = "E2E Testing"
+ }
+
+ # Test all optional fields
+ session_duration = "24h"
+ user_seat_expiration_inactive_time = "730h"
+ warp_auth_session_duration = "12h"
+ is_ui_read_only = true
+ # ui_read_only_toggle_reason = "E2E Testing"
+ auto_redirect_to_identity = true
+ allow_authenticate_via_warp = true
+}
diff --git a/internal/e2e-runner/import.go b/internal/e2e-runner/import.go
new file mode 100644
index 0000000..4770701
--- /dev/null
+++ b/internal/e2e-runner/import.go
@@ -0,0 +1,190 @@
+// import.go handles Terraform import operations for import-only resources.
+//
+// This file provides functionality to:
+// - Parse import annotations from Terraform configuration files
+// - Execute terraform import commands for resources that cannot be created
+// - Support variable interpolation in import addresses
+//
+// Import annotations use the format:
+// # tf-migrate:import-address=
+// resource "type" "name" { ... }
+//
+// Where can include variable references like ${var.cloudflare_account_id}
+package e2e
+
+import (
+ "bufio"
+ "fmt"
+ "os"
+ "path/filepath"
+ "regexp"
+ "strings"
+)
+
+// ImportSpec represents a resource that needs to be imported
+type ImportSpec struct {
+ ResourceType string // e.g., "cloudflare_access_organization"
+ ResourceName string // e.g., "test"
+ ResourceAddress string // e.g., "cloudflare_access_organization.test"
+ ImportAddress string // e.g., "account/${var.cloudflare_account_id}"
+ ModuleName string // e.g., "zero_trust_organization" (extracted from file path)
+}
+
+// findImportSpecs scans a directory for import annotations and returns import specifications
+func findImportSpecs(dir string) ([]ImportSpec, error) {
+ var specs []ImportSpec
+
+ // Walk through all .tf files in subdirectories (modules)
+ err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ // Skip if not a .tf file
+ if info.IsDir() || !strings.HasSuffix(info.Name(), ".tf") {
+ return nil
+ }
+
+ // Skip root directory files (only look in modules)
+ relPath, err := filepath.Rel(dir, path)
+ if err != nil {
+ return err
+ }
+ if !strings.Contains(relPath, string(filepath.Separator)) {
+ return nil // Skip files in root directory
+ }
+
+ // Extract module name from path (first directory component)
+ moduleName := strings.Split(relPath, string(filepath.Separator))[0]
+
+ // Parse file for import annotations
+ fileSpecs, err := parseImportAnnotations(path, moduleName)
+ if err != nil {
+ return fmt.Errorf("failed to parse %s: %w", path, err)
+ }
+
+ specs = append(specs, fileSpecs...)
+ return nil
+ })
+
+ return specs, err
+}
+
+// parseImportAnnotations parses a single .tf file for import annotations
+func parseImportAnnotations(filePath, moduleName string) ([]ImportSpec, error) {
+ file, err := os.Open(filePath)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+
+ var specs []ImportSpec
+ scanner := bufio.NewScanner(file)
+
+ // Regex patterns
+ importAnnotation := regexp.MustCompile(`^\s*#\s*tf-migrate:import-address=(.+)$`)
+ resourceDeclaration := regexp.MustCompile(`^\s*resource\s+"([^"]+)"\s+"([^"]+)"\s*{`)
+
+ var pendingImportAddress string
+
+ for scanner.Scan() {
+ line := scanner.Text()
+
+ // Check for import annotation
+ if matches := importAnnotation.FindStringSubmatch(line); matches != nil {
+ pendingImportAddress = strings.TrimSpace(matches[1])
+ continue
+ }
+
+ // Check for resource declaration following an import annotation
+ if pendingImportAddress != "" {
+ if matches := resourceDeclaration.FindStringSubmatch(line); matches != nil {
+ resourceType := matches[1]
+ resourceName := matches[2]
+
+ specs = append(specs, ImportSpec{
+ ResourceType: resourceType,
+ ResourceName: resourceName,
+ ResourceAddress: fmt.Sprintf("%s.%s", resourceType, resourceName),
+ ImportAddress: pendingImportAddress,
+ ModuleName: moduleName,
+ })
+
+ pendingImportAddress = "" // Reset
+ }
+ }
+ }
+
+ if err := scanner.Err(); err != nil {
+ return nil, err
+ }
+
+ return specs, nil
+}
+
+// resolveImportAddress replaces variable references in import address with actual values
+func resolveImportAddress(address string, env *E2EEnv) string {
+ // Replace common variable references
+ address = strings.ReplaceAll(address, "${var.cloudflare_account_id}", env.AccountID)
+ address = strings.ReplaceAll(address, "${var.cloudflare_zone_id}", env.ZoneID)
+ address = strings.ReplaceAll(address, "${var.cloudflare_domain}", env.Domain)
+
+ return address
+}
+
+// executeImports runs terraform import commands for all import specs
+func executeImports(ctx *testContext, specs []ImportSpec) error {
+ if len(specs) == 0 {
+ return nil // No imports needed
+ }
+
+ printHeader("Importing Resources")
+ printYellow("Found %d resource(s) marked for import", len(specs))
+ fmt.Println()
+
+ tf := NewTerraformRunner(ctx.v4Dir)
+
+ // Set R2 credentials for terraform commands
+ r2AccessKey := os.Getenv("CLOUDFLARE_R2_ACCESS_KEY_ID")
+ r2SecretKey := os.Getenv("CLOUDFLARE_R2_SECRET_ACCESS_KEY")
+ if r2AccessKey != "" && r2SecretKey != "" {
+ tf.EnvVars["AWS_ACCESS_KEY_ID"] = r2AccessKey
+ tf.EnvVars["AWS_SECRET_ACCESS_KEY"] = r2SecretKey
+ }
+
+ for _, spec := range specs {
+ // Resolve variables in import address
+ importAddress := resolveImportAddress(spec.ImportAddress, ctx.env)
+
+ // Build full resource address including module prefix
+ fullResourceAddress := fmt.Sprintf("module.%s.%s", spec.ModuleName, spec.ResourceAddress)
+
+ printYellow("Importing %s...", fullResourceAddress)
+ printBlue(" Import address: %s", importAddress)
+
+ // Run terraform import
+ output, err := tf.Run("import", "-no-color", "-input=false", fullResourceAddress, importAddress)
+ if err != nil {
+ // Check if resource already exists in state
+ if strings.Contains(output, "Resource already managed by Terraform") ||
+ strings.Contains(output, "already exists in state") {
+ printGreen(" ✓ Resource already imported")
+ continue
+ }
+
+ printError("Failed to import %s", fullResourceAddress)
+ fmt.Println()
+ printRed("Error output:")
+ fmt.Println(output)
+ return fmt.Errorf("import failed for %s: %w", fullResourceAddress, err)
+ }
+
+ printSuccess("Successfully imported %s", fullResourceAddress)
+ fmt.Println()
+ }
+
+ printSuccess("All imports completed")
+ fmt.Println()
+
+ return nil
+}
diff --git a/internal/e2e-runner/import_test.go b/internal/e2e-runner/import_test.go
new file mode 100644
index 0000000..1a74809
--- /dev/null
+++ b/internal/e2e-runner/import_test.go
@@ -0,0 +1,256 @@
+package e2e
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+)
+
+func TestParseImportAnnotations(t *testing.T) {
+ tests := []struct {
+ name string
+ fileContent string
+ moduleName string
+ expectedSpecs int
+ expectedFirst *ImportSpec
+ }{
+ {
+ name: "single import annotation",
+ fileContent: `# Some comment
+# tf-migrate:import-address=account/${var.cloudflare_account_id}
+resource "cloudflare_access_organization" "test" {
+ account_id = var.cloudflare_account_id
+}
+`,
+ moduleName: "zero_trust_organization",
+ expectedSpecs: 1,
+ expectedFirst: &ImportSpec{
+ ResourceType: "cloudflare_access_organization",
+ ResourceName: "test",
+ ResourceAddress: "cloudflare_access_organization.test",
+ ImportAddress: "account/${var.cloudflare_account_id}",
+ ModuleName: "zero_trust_organization",
+ },
+ },
+ {
+ name: "multiple import annotations",
+ fileContent: `# tf-migrate:import-address=zones/${var.cloudflare_zone_id}/settings/waf
+resource "cloudflare_waf_package" "test" {
+ zone_id = var.cloudflare_zone_id
+}
+
+# tf-migrate:import-address=account/${var.cloudflare_account_id}
+resource "cloudflare_access_organization" "test" {
+ account_id = var.cloudflare_account_id
+}
+`,
+ moduleName: "test_module",
+ expectedSpecs: 2,
+ expectedFirst: &ImportSpec{
+ ResourceType: "cloudflare_waf_package",
+ ResourceName: "test",
+ ResourceAddress: "cloudflare_waf_package.test",
+ ImportAddress: "zones/${var.cloudflare_zone_id}/settings/waf",
+ ModuleName: "test_module",
+ },
+ },
+ {
+ name: "no import annotations",
+ fileContent: `# Regular comment
+resource "cloudflare_record" "test" {
+ zone_id = var.cloudflare_zone_id
+ name = "test"
+}
+`,
+ moduleName: "dns_record",
+ expectedSpecs: 0,
+ },
+ {
+ name: "annotation with spaces",
+ fileContent: ` # tf-migrate:import-address=account/${var.cloudflare_account_id}
+resource "cloudflare_access_organization" "test" {
+ account_id = var.cloudflare_account_id
+}
+`,
+ moduleName: "zero_trust_organization",
+ expectedSpecs: 1,
+ expectedFirst: &ImportSpec{
+ ResourceType: "cloudflare_access_organization",
+ ResourceName: "test",
+ ResourceAddress: "cloudflare_access_organization.test",
+ ImportAddress: "account/${var.cloudflare_account_id}",
+ ModuleName: "zero_trust_organization",
+ },
+ },
+ {
+ name: "annotation not followed by resource",
+ fileContent: `# tf-migrate:import-address=account/${var.cloudflare_account_id}
+# Some other comment
+variable "test" {
+ type = string
+}
+`,
+ moduleName: "test_module",
+ expectedSpecs: 0,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ // Create temporary file
+ tmpDir := t.TempDir()
+ tmpFile := filepath.Join(tmpDir, "test.tf")
+ if err := os.WriteFile(tmpFile, []byte(tt.fileContent), 0644); err != nil {
+ t.Fatalf("failed to write test file: %v", err)
+ }
+
+ // Parse annotations
+ specs, err := parseImportAnnotations(tmpFile, tt.moduleName)
+ if err != nil {
+ t.Fatalf("parseImportAnnotations() error = %v", err)
+ }
+
+ // Check number of specs
+ if len(specs) != tt.expectedSpecs {
+ t.Errorf("parseImportAnnotations() got %d specs, want %d", len(specs), tt.expectedSpecs)
+ }
+
+ // Check first spec if expected
+ if tt.expectedFirst != nil && len(specs) > 0 {
+ got := specs[0]
+ want := tt.expectedFirst
+
+ if got.ResourceType != want.ResourceType {
+ t.Errorf("ResourceType = %q, want %q", got.ResourceType, want.ResourceType)
+ }
+ if got.ResourceName != want.ResourceName {
+ t.Errorf("ResourceName = %q, want %q", got.ResourceName, want.ResourceName)
+ }
+ if got.ResourceAddress != want.ResourceAddress {
+ t.Errorf("ResourceAddress = %q, want %q", got.ResourceAddress, want.ResourceAddress)
+ }
+ if got.ImportAddress != want.ImportAddress {
+ t.Errorf("ImportAddress = %q, want %q", got.ImportAddress, want.ImportAddress)
+ }
+ if got.ModuleName != want.ModuleName {
+ t.Errorf("ModuleName = %q, want %q", got.ModuleName, want.ModuleName)
+ }
+ }
+ })
+ }
+}
+
+func TestFindImportSpecs(t *testing.T) {
+ // Create temporary directory structure
+ tmpDir := t.TempDir()
+
+ // Create module directories
+ module1Dir := filepath.Join(tmpDir, "module1")
+ module2Dir := filepath.Join(tmpDir, "module2")
+ if err := os.MkdirAll(module1Dir, 0755); err != nil {
+ t.Fatalf("failed to create module1 dir: %v", err)
+ }
+ if err := os.MkdirAll(module2Dir, 0755); err != nil {
+ t.Fatalf("failed to create module2 dir: %v", err)
+ }
+
+ // Create test files
+ module1Content := `# tf-migrate:import-address=account/${var.cloudflare_account_id}
+resource "cloudflare_access_organization" "test" {
+ account_id = var.cloudflare_account_id
+}
+`
+ if err := os.WriteFile(filepath.Join(module1Dir, "main.tf"), []byte(module1Content), 0644); err != nil {
+ t.Fatalf("failed to write module1 file: %v", err)
+ }
+
+ module2Content := `# Regular resource, no import needed
+resource "cloudflare_record" "test" {
+ zone_id = var.cloudflare_zone_id
+ name = "test"
+}
+`
+ if err := os.WriteFile(filepath.Join(module2Dir, "main.tf"), []byte(module2Content), 0644); err != nil {
+ t.Fatalf("failed to write module2 file: %v", err)
+ }
+
+ // Create a root-level file (should be ignored)
+ rootContent := `# tf-migrate:import-address=should_be_ignored
+resource "cloudflare_something" "root" {
+ id = "test"
+}
+`
+ if err := os.WriteFile(filepath.Join(tmpDir, "provider.tf"), []byte(rootContent), 0644); err != nil {
+ t.Fatalf("failed to write root file: %v", err)
+ }
+
+ // Find import specs
+ specs, err := findImportSpecs(tmpDir)
+ if err != nil {
+ t.Fatalf("findImportSpecs() error = %v", err)
+ }
+
+ // Should find only the one from module1
+ if len(specs) != 1 {
+ t.Errorf("findImportSpecs() got %d specs, want 1", len(specs))
+ }
+
+ if len(specs) > 0 {
+ got := specs[0]
+ if got.ModuleName != "module1" {
+ t.Errorf("ModuleName = %q, want %q", got.ModuleName, "module1")
+ }
+ if got.ResourceType != "cloudflare_access_organization" {
+ t.Errorf("ResourceType = %q, want %q", got.ResourceType, "cloudflare_access_organization")
+ }
+ }
+}
+
+func TestResolveImportAddress(t *testing.T) {
+ env := &E2EEnv{
+ AccountID: "test-account-123",
+ ZoneID: "test-zone-456",
+ Domain: "example.com",
+ }
+
+ tests := []struct {
+ name string
+ address string
+ expected string
+ }{
+ {
+ name: "account ID substitution",
+ address: "account/${var.cloudflare_account_id}",
+ expected: "account/test-account-123",
+ },
+ {
+ name: "zone ID substitution",
+ address: "zones/${var.cloudflare_zone_id}/settings/waf",
+ expected: "zones/test-zone-456/settings/waf",
+ },
+ {
+ name: "domain substitution",
+ address: "${var.cloudflare_domain}/path",
+ expected: "example.com/path",
+ },
+ {
+ name: "multiple substitutions",
+ address: "account/${var.cloudflare_account_id}/zone/${var.cloudflare_zone_id}",
+ expected: "account/test-account-123/zone/test-zone-456",
+ },
+ {
+ name: "no substitutions",
+ address: "static/path/123",
+ expected: "static/path/123",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := resolveImportAddress(tt.address, env)
+ if got != tt.expected {
+ t.Errorf("resolveImportAddress() = %q, want %q", got, tt.expected)
+ }
+ })
+ }
+}
diff --git a/internal/e2e-runner/runner.go b/internal/e2e-runner/runner.go
index 63ede80..76e1773 100644
--- a/internal/e2e-runner/runner.go
+++ b/internal/e2e-runner/runner.go
@@ -739,6 +739,21 @@ func runV4Tests(ctx *testContext) error {
}
printSuccess("Terraform init successful (remote state loaded from R2)")
+ // Check for resources that need to be imported
+ fmt.Println()
+ importSpecs, err := findImportSpecs(ctx.v4Dir)
+ if err != nil {
+ printError("Failed to scan for import annotations")
+ return fmt.Errorf("failed to find import specs: %w", err)
+ }
+
+ // Execute imports if needed
+ if len(importSpecs) > 0 {
+ if err := executeImports(ctx, importSpecs); err != nil {
+ return err
+ }
+ }
+
// Run terraform plan
printYellow("Running terraform plan in v4/...")
planArgs := append([]string{"plan", "-no-color", "-out=" + filepath.Join(ctx.tmpDir, "v4.tfplan"), "-input=false"}, ctx.targetArgs...)
diff --git a/internal/registry/registry.go b/internal/registry/registry.go
index 68c5eec..58d634f 100644
--- a/internal/registry/registry.go
+++ b/internal/registry/registry.go
@@ -53,6 +53,7 @@ import (
"github.com/cloudflare/tf-migrate/internal/resources/zero_trust_gateway_policy"
"github.com/cloudflare/tf-migrate/internal/resources/zero_trust_list"
"github.com/cloudflare/tf-migrate/internal/resources/zero_trust_local_fallback_domain"
+ "github.com/cloudflare/tf-migrate/internal/resources/zero_trust_organization"
"github.com/cloudflare/tf-migrate/internal/resources/zero_trust_tunnel_cloudflared"
"github.com/cloudflare/tf-migrate/internal/resources/zero_trust_tunnel_cloudflared_config"
"github.com/cloudflare/tf-migrate/internal/resources/zero_trust_tunnel_cloudflared_route"
@@ -124,6 +125,7 @@ func RegisterAllMigrations() {
zero_trust_gateway_policy.NewV4ToV5Migrator()
zero_trust_list.NewV4ToV5Migrator()
zero_trust_local_fallback_domain.NewV4ToV5Migrator()
+ zero_trust_organization.NewV4ToV5Migrator()
zero_trust_tunnel_cloudflared.NewV4ToV5Migrator()
zero_trust_tunnel_cloudflared_config.NewV4ToV5Migrator()
zero_trust_tunnel_cloudflared_route.NewV4ToV5Migrator()
diff --git a/internal/resources/zero_trust_organization/v4_to_v5.go b/internal/resources/zero_trust_organization/v4_to_v5.go
new file mode 100644
index 0000000..fe4677e
--- /dev/null
+++ b/internal/resources/zero_trust_organization/v4_to_v5.go
@@ -0,0 +1,120 @@
+package zero_trust_organization
+
+import (
+ "github.com/hashicorp/hcl/v2/hclwrite"
+ "github.com/tidwall/gjson"
+ "github.com/tidwall/sjson"
+
+ "github.com/cloudflare/tf-migrate/internal"
+ "github.com/cloudflare/tf-migrate/internal/transform"
+ "github.com/cloudflare/tf-migrate/internal/transform/state"
+ tfhcl "github.com/cloudflare/tf-migrate/internal/transform/hcl"
+)
+
+type V4ToV5Migrator struct {
+}
+
+// NewV4ToV5Migrator creates a new migrator instance and registers BOTH v4 resource names.
+// v4 has two aliases: cloudflare_access_organization (deprecated) and
+// cloudflare_zero_trust_access_organization (current). Both use the same schema
+// and migrate to cloudflare_zero_trust_organization in v5.
+func NewV4ToV5Migrator() transform.ResourceTransformer {
+ migrator := &V4ToV5Migrator{}
+ // Register BOTH v4 resource names (they're aliases with identical schemas)
+ internal.RegisterMigrator("cloudflare_access_organization", "v4", "v5", migrator)
+ internal.RegisterMigrator("cloudflare_zero_trust_access_organization", "v4", "v5", migrator)
+ return migrator
+}
+
+func (m *V4ToV5Migrator) GetResourceType() string {
+ // Return the v5 resource name
+ return "cloudflare_zero_trust_organization"
+}
+
+func (m *V4ToV5Migrator) CanHandle(resourceType string) bool {
+ // Handle BOTH v4 resource names
+ return resourceType == "cloudflare_access_organization" ||
+ resourceType == "cloudflare_zero_trust_access_organization"
+}
+
+// GetResourceRename implements the ResourceRenamer interface.
+// Both v4 names rename to the same v5 name.
+func (m *V4ToV5Migrator) GetResourceRename() (string, string) {
+ // This is called for the registered name, but both v4 names go to the same v5 name
+ return "cloudflare_access_organization", "cloudflare_zero_trust_organization"
+}
+
+// Preprocess performs any string-level transformations before HCL parsing.
+// For zero_trust_organization, no preprocessing is needed.
+func (m *V4ToV5Migrator) Preprocess(content string) string {
+ return content
+}
+
+// TransformConfig transforms the HCL configuration from v4 to v5.
+func (m *V4ToV5Migrator) TransformConfig(ctx *transform.Context, block *hclwrite.Block) (*transform.TransformResult, error) {
+ // Rename resource type from EITHER v4 name to v5 name
+ currentType := tfhcl.GetResourceType(block)
+ if currentType == "cloudflare_access_organization" || currentType == "cloudflare_zero_trust_access_organization" {
+ tfhcl.RenameResourceType(block, currentType, "cloudflare_zero_trust_organization")
+ }
+
+ body := block.Body()
+
+ // Convert login_design block to attribute (MaxItems:1 → SingleNestedAttribute)
+ // v4: login_design { background_color = "#000" ... }
+ // v5: login_design = { background_color = "#000" ... }
+ tfhcl.ConvertBlocksToAttribute(body, "login_design", "login_design", func(block *hclwrite.Block) {})
+
+ // Convert custom_pages block to attribute (MaxItems:1 → SingleNestedAttribute)
+ // v4: custom_pages { forbidden = "id" ... }
+ // v5: custom_pages = { forbidden = "id" ... }
+ tfhcl.ConvertBlocksToAttribute(body, "custom_pages", "custom_pages", func(block *hclwrite.Block) {})
+
+ return &transform.TransformResult{
+ Blocks: []*hclwrite.Block{block},
+ RemoveOriginal: false,
+ }, nil
+}
+
+// TransformState transforms the JSON state from v4 to v5.
+func (m *V4ToV5Migrator) TransformState(ctx *transform.Context, stateJSON gjson.Result, resourcePath, resourceName string) (string, error) {
+ result := stateJSON.String()
+
+ // Get attributes
+ attrs := stateJSON.Get("attributes")
+ if !attrs.Exists() {
+ // Even for invalid instances, set schema_version
+ result, _ = sjson.Set(result, "schema_version", 0)
+ return result, nil
+ }
+
+ // Handle account_id / zone_id mutual exclusivity
+ // v5 requires that only one is set, the other must be null
+ accountID := attrs.Get("account_id")
+ zoneID := attrs.Get("zone_id")
+
+ if accountID.Exists() && accountID.String() != "" {
+ // This is an account-level organization, ensure zone_id is null
+ result, _ = sjson.Delete(result, "attributes.zone_id")
+ } else if zoneID.Exists() && zoneID.String() != "" {
+ // This is a zone-level organization, ensure account_id is null
+ result, _ = sjson.Delete(result, "attributes.account_id")
+ }
+
+ // Convert MaxItems:1 arrays to objects
+ // login_design: [{"background_color": "#000", ...}] → {"background_color": "#000", ...}
+ // Handles empty arrays by deleting them
+ result = state.ConvertMaxItemsOneArrayToObject(result, "attributes", attrs, "login_design")
+ result = state.ConvertMaxItemsOneArrayToObject(result, "attributes", attrs, "custom_pages")
+
+ // Add default boolean values if missing (v5 has defaults, v4 didn't)
+ // This prevents PATCH operations when migrating resources that didn't set these
+ result = state.EnsureField(result, "attributes", attrs, "allow_authenticate_via_warp", false)
+ result = state.EnsureField(result, "attributes", attrs, "auto_redirect_to_identity", false)
+ result = state.EnsureField(result, "attributes", attrs, "is_ui_read_only", false)
+
+ // Set schema_version to 0 for v5 (ALWAYS required!)
+ result, _ = sjson.Set(result, "schema_version", 0)
+
+ return result, nil
+}
diff --git a/internal/resources/zero_trust_organization/v4_to_v5_test.go b/internal/resources/zero_trust_organization/v4_to_v5_test.go
new file mode 100644
index 0000000..253f801
--- /dev/null
+++ b/internal/resources/zero_trust_organization/v4_to_v5_test.go
@@ -0,0 +1,485 @@
+package zero_trust_organization
+
+import (
+ "testing"
+
+ "github.com/cloudflare/tf-migrate/internal/testhelpers"
+)
+
+func TestV4ToV5Transformation(t *testing.T) {
+ migrator := NewV4ToV5Migrator()
+
+ t.Run("ConfigTransformation", func(t *testing.T) {
+ tests := []testhelpers.ConfigTestCase{
+ {
+ Name: "Minimal resource - cloudflare_access_organization",
+ Input: `resource "cloudflare_access_organization" "example" {
+ auth_domain = "example.cloudflareaccess.com"
+ name = "My Organization"
+}`,
+ Expected: `resource "cloudflare_zero_trust_organization" "example" {
+ auth_domain = "example.cloudflareaccess.com"
+ name = "My Organization"
+}`,
+ },
+ {
+ Name: "Minimal resource - cloudflare_zero_trust_access_organization",
+ Input: `resource "cloudflare_zero_trust_access_organization" "example" {
+ auth_domain = "example.cloudflareaccess.com"
+ name = "My Organization"
+}`,
+ Expected: `resource "cloudflare_zero_trust_organization" "example" {
+ auth_domain = "example.cloudflareaccess.com"
+ name = "My Organization"
+}`,
+ },
+ {
+ Name: "With login_design block",
+ Input: `resource "cloudflare_access_organization" "example" {
+ auth_domain = "example.cloudflareaccess.com"
+ name = "My Organization"
+
+ login_design {
+ background_color = "#000000"
+ text_color = "#FFFFFF"
+ logo_path = "https://example.com/logo.png"
+ header_text = "Welcome"
+ footer_text = "Powered by Cloudflare"
+ }
+}`,
+ Expected: `resource "cloudflare_zero_trust_organization" "example" {
+ auth_domain = "example.cloudflareaccess.com"
+ name = "My Organization"
+
+ login_design = {
+ background_color = "#000000"
+ text_color = "#FFFFFF"
+ logo_path = "https://example.com/logo.png"
+ header_text = "Welcome"
+ footer_text = "Powered by Cloudflare"
+ }
+}`,
+ },
+ {
+ Name: "With custom_pages block",
+ Input: `resource "cloudflare_access_organization" "example" {
+ auth_domain = "example.cloudflareaccess.com"
+ name = "My Organization"
+
+ custom_pages {
+ forbidden = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ identity_denied = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
+ }
+}`,
+ Expected: `resource "cloudflare_zero_trust_organization" "example" {
+ auth_domain = "example.cloudflareaccess.com"
+ name = "My Organization"
+
+ custom_pages = {
+ forbidden = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ identity_denied = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
+ }
+}`,
+ },
+ {
+ Name: "Complete organization with all fields",
+ Input: `resource "cloudflare_access_organization" "example" {
+ account_id = "f037e56e89293a057740de681ac9abbe"
+ auth_domain = "example.cloudflareaccess.com"
+ name = "Complete Organization"
+
+ is_ui_read_only = true
+ ui_read_only_toggle_reason = "Managed by Terraform"
+
+ user_seat_expiration_inactive_time = "730h"
+ auto_redirect_to_identity = true
+ session_duration = "24h"
+
+ login_design {
+ background_color = "#000000"
+ text_color = "#FFFFFF"
+ logo_path = "https://example.com/logo.png"
+ header_text = "Welcome"
+ footer_text = "Powered by Cloudflare"
+ }
+
+ custom_pages {
+ forbidden = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ identity_denied = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
+ }
+
+ allow_authenticate_via_warp = true
+ warp_auth_session_duration = "12h"
+}`,
+ Expected: `resource "cloudflare_zero_trust_organization" "example" {
+ account_id = "f037e56e89293a057740de681ac9abbe"
+ auth_domain = "example.cloudflareaccess.com"
+ name = "Complete Organization"
+
+ is_ui_read_only = true
+ ui_read_only_toggle_reason = "Managed by Terraform"
+
+ user_seat_expiration_inactive_time = "730h"
+ auto_redirect_to_identity = true
+ session_duration = "24h"
+
+ login_design = {
+ background_color = "#000000"
+ text_color = "#FFFFFF"
+ logo_path = "https://example.com/logo.png"
+ header_text = "Welcome"
+ footer_text = "Powered by Cloudflare"
+ }
+
+ custom_pages = {
+ forbidden = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ identity_denied = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
+ }
+
+ allow_authenticate_via_warp = true
+ warp_auth_session_duration = "12h"
+}`,
+ },
+ {
+ Name: "Partial login_design block",
+ Input: `resource "cloudflare_access_organization" "example" {
+ auth_domain = "example.cloudflareaccess.com"
+ name = "My Organization"
+
+ login_design {
+ background_color = "#000000"
+ text_color = "#FFFFFF"
+ }
+}`,
+ Expected: `resource "cloudflare_zero_trust_organization" "example" {
+ auth_domain = "example.cloudflareaccess.com"
+ name = "My Organization"
+
+ login_design = {
+ background_color = "#000000"
+ text_color = "#FFFFFF"
+ }
+}`,
+ },
+ {
+ Name: "Multiple resources in one file",
+ Input: `resource "cloudflare_access_organization" "account_org" {
+ account_id = "f037e56e89293a057740de681ac9abbe"
+ auth_domain = "account.cloudflareaccess.com"
+ name = "Account Organization"
+}
+
+resource "cloudflare_zero_trust_access_organization" "zone_org" {
+ zone_id = "023e105f4ecef8ad9ca31a8372d0c353"
+ auth_domain = "zone.cloudflareaccess.com"
+ name = "Zone Organization"
+}`,
+ Expected: `resource "cloudflare_zero_trust_organization" "account_org" {
+ account_id = "f037e56e89293a057740de681ac9abbe"
+ auth_domain = "account.cloudflareaccess.com"
+ name = "Account Organization"
+}
+
+resource "cloudflare_zero_trust_organization" "zone_org" {
+ zone_id = "023e105f4ecef8ad9ca31a8372d0c353"
+ auth_domain = "zone.cloudflareaccess.com"
+ name = "Zone Organization"
+}`,
+ },
+ }
+
+ testhelpers.RunConfigTransformTests(t, tests, migrator)
+ })
+
+ t.Run("StateTransformation", func(t *testing.T) {
+ tests := []testhelpers.StateTestCase{
+ {
+ Name: "Minimal state",
+ Input: `{
+ "type": "cloudflare_access_organization",
+ "name": "example",
+ "attributes": {
+ "account_id": "f037e56e89293a057740de681ac9abbe",
+ "auth_domain": "example.cloudflareaccess.com",
+ "name": "My Organization"
+ }
+}`,
+ Expected: `{
+ "type": "cloudflare_access_organization",
+ "name": "example",
+ "schema_version": 0,
+ "attributes": {
+ "account_id": "f037e56e89293a057740de681ac9abbe",
+ "auth_domain": "example.cloudflareaccess.com",
+ "name": "My Organization",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ }
+}`,
+ },
+ {
+ Name: "With login_design array (MaxItems:1)",
+ Input: `{
+ "type": "cloudflare_access_organization",
+ "name": "example",
+ "attributes": {
+ "account_id": "f037e56e89293a057740de681ac9abbe",
+ "auth_domain": "example.cloudflareaccess.com",
+ "name": "My Organization",
+ "login_design": [{
+ "background_color": "#000000",
+ "text_color": "#FFFFFF",
+ "logo_path": "https://example.com/logo.png",
+ "header_text": "Welcome",
+ "footer_text": "Powered by Cloudflare"
+ }]
+ }
+}`,
+ Expected: `{
+ "type": "cloudflare_access_organization",
+ "name": "example",
+ "schema_version": 0,
+ "attributes": {
+ "account_id": "f037e56e89293a057740de681ac9abbe",
+ "auth_domain": "example.cloudflareaccess.com",
+ "name": "My Organization",
+ "login_design": {
+ "background_color": "#000000",
+ "text_color": "#FFFFFF",
+ "logo_path": "https://example.com/logo.png",
+ "header_text": "Welcome",
+ "footer_text": "Powered by Cloudflare"
+ },
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ }
+}`,
+ },
+ {
+ Name: "Empty login_design array",
+ Input: `{
+ "type": "cloudflare_access_organization",
+ "name": "example",
+ "attributes": {
+ "account_id": "f037e56e89293a057740de681ac9abbe",
+ "auth_domain": "example.cloudflareaccess.com",
+ "name": "My Organization",
+ "login_design": []
+ }
+}`,
+ Expected: `{
+ "type": "cloudflare_access_organization",
+ "name": "example",
+ "schema_version": 0,
+ "attributes": {
+ "account_id": "f037e56e89293a057740de681ac9abbe",
+ "auth_domain": "example.cloudflareaccess.com",
+ "name": "My Organization",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ }
+}`,
+ },
+ {
+ Name: "Zone-scoped organization (zone_id instead of account_id)",
+ Input: `{
+ "type": "cloudflare_access_organization",
+ "name": "example",
+ "attributes": {
+ "zone_id": "023e105f4ecef8ad9ca31a8372d0c353",
+ "auth_domain": "example.cloudflareaccess.com",
+ "name": "My Organization"
+ }
+}`,
+ Expected: `{
+ "type": "cloudflare_access_organization",
+ "name": "example",
+ "schema_version": 0,
+ "attributes": {
+ "zone_id": "023e105f4ecef8ad9ca31a8372d0c353",
+ "auth_domain": "example.cloudflareaccess.com",
+ "name": "My Organization",
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ }
+}`,
+ },
+ {
+ Name: "With custom_pages array (MaxItems:1)",
+ Input: `{
+ "type": "cloudflare_access_organization",
+ "name": "example",
+ "attributes": {
+ "account_id": "f037e56e89293a057740de681ac9abbe",
+ "auth_domain": "example.cloudflareaccess.com",
+ "name": "My Organization",
+ "custom_pages": [{
+ "forbidden": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
+ "identity_denied": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
+ }]
+ }
+}`,
+ Expected: `{
+ "type": "cloudflare_access_organization",
+ "name": "example",
+ "schema_version": 0,
+ "attributes": {
+ "account_id": "f037e56e89293a057740de681ac9abbe",
+ "auth_domain": "example.cloudflareaccess.com",
+ "name": "My Organization",
+ "custom_pages": {
+ "forbidden": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
+ "identity_denied": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
+ },
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ }
+}`,
+ },
+ {
+ Name: "With both login_design and custom_pages arrays",
+ Input: `{
+ "type": "cloudflare_access_organization",
+ "name": "example",
+ "attributes": {
+ "account_id": "f037e56e89293a057740de681ac9abbe",
+ "auth_domain": "example.cloudflareaccess.com",
+ "name": "My Organization",
+ "login_design": [{
+ "background_color": "#000000",
+ "text_color": "#FFFFFF"
+ }],
+ "custom_pages": [{
+ "forbidden": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ }]
+ }
+}`,
+ Expected: `{
+ "type": "cloudflare_access_organization",
+ "name": "example",
+ "schema_version": 0,
+ "attributes": {
+ "account_id": "f037e56e89293a057740de681ac9abbe",
+ "auth_domain": "example.cloudflareaccess.com",
+ "name": "My Organization",
+ "login_design": {
+ "background_color": "#000000",
+ "text_color": "#FFFFFF"
+ },
+ "custom_pages": {
+ "forbidden": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "allow_authenticate_via_warp": false,
+ "auto_redirect_to_identity": false,
+ "is_ui_read_only": false
+ }
+}`,
+ },
+ {
+ Name: "With existing boolean values (should be preserved)",
+ Input: `{
+ "type": "cloudflare_access_organization",
+ "name": "example",
+ "attributes": {
+ "account_id": "f037e56e89293a057740de681ac9abbe",
+ "auth_domain": "example.cloudflareaccess.com",
+ "name": "My Organization",
+ "allow_authenticate_via_warp": true,
+ "auto_redirect_to_identity": true,
+ "is_ui_read_only": true
+ }
+}`,
+ Expected: `{
+ "type": "cloudflare_access_organization",
+ "name": "example",
+ "schema_version": 0,
+ "attributes": {
+ "account_id": "f037e56e89293a057740de681ac9abbe",
+ "auth_domain": "example.cloudflareaccess.com",
+ "name": "My Organization",
+ "allow_authenticate_via_warp": true,
+ "auto_redirect_to_identity": true,
+ "is_ui_read_only": true
+ }
+}`,
+ },
+ {
+ Name: "Complete state with all fields",
+ Input: `{
+ "type": "cloudflare_zero_trust_access_organization",
+ "name": "example",
+ "attributes": {
+ "account_id": "f037e56e89293a057740de681ac9abbe",
+ "auth_domain": "example.cloudflareaccess.com",
+ "name": "Complete Organization",
+ "session_duration": "24h",
+ "user_seat_expiration_inactive_time": "730h",
+ "warp_auth_session_duration": "12h",
+ "ui_read_only_toggle_reason": "Managed by Terraform",
+ "is_ui_read_only": true,
+ "auto_redirect_to_identity": true,
+ "allow_authenticate_via_warp": true,
+ "login_design": [{
+ "background_color": "#000000",
+ "text_color": "#FFFFFF",
+ "logo_path": "https://example.com/logo.png",
+ "header_text": "Welcome",
+ "footer_text": "Powered by Cloudflare"
+ }],
+ "custom_pages": [{
+ "forbidden": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
+ "identity_denied": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
+ }]
+ }
+}`,
+ Expected: `{
+ "type": "cloudflare_zero_trust_access_organization",
+ "name": "example",
+ "schema_version": 0,
+ "attributes": {
+ "account_id": "f037e56e89293a057740de681ac9abbe",
+ "auth_domain": "example.cloudflareaccess.com",
+ "name": "Complete Organization",
+ "session_duration": "24h",
+ "user_seat_expiration_inactive_time": "730h",
+ "warp_auth_session_duration": "12h",
+ "ui_read_only_toggle_reason": "Managed by Terraform",
+ "is_ui_read_only": true,
+ "auto_redirect_to_identity": true,
+ "allow_authenticate_via_warp": true,
+ "login_design": {
+ "background_color": "#000000",
+ "text_color": "#FFFFFF",
+ "logo_path": "https://example.com/logo.png",
+ "header_text": "Welcome",
+ "footer_text": "Powered by Cloudflare"
+ },
+ "custom_pages": {
+ "forbidden": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
+ "identity_denied": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
+ }
+ }
+}`,
+ },
+ {
+ Name: "Invalid instance (no attributes) - should still set schema_version",
+ Input: `{
+ "type": "cloudflare_access_organization",
+ "name": "example"
+}`,
+ Expected: `{
+ "type": "cloudflare_access_organization",
+ "name": "example",
+ "schema_version": 0
+}`,
+ },
+ }
+
+ testhelpers.RunStateTransformTests(t, tests, migrator)
+ })
+}