Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 3 additions & 17 deletions .github/workflows/migration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,10 @@ env:
CLOUDFLARE_ALT_ZONE_ID: ed9caae55809bfe3209699f602ce17fc
CLOUDFLARE_DOMAIN: terraform.cfapi.net
CLOUDFLARE_ZONE_ID: 0da42c8d2132a9ddaf714f9e7c920711
CLOUDFLARE_MUTUAL_TLS_CERTIFICATE: "-----BEGIN CERTIFICATE-----\nMIIF+DCCA+CgAwIBAgIUWc0b+WiKSZob8wl2g/ujewoKCvgwDQYJKoZIhvcNAQEN\nBQAwgZMxCzAJBgNVBAYTAlVTMQwwCgYDVQQIEwNOL0ExDDAKBgNVBAcTA04vQTEl\nMCMGA1UEChMcVGVycmFmb3JtIEFjY2VwdGFuY2UgVGVzdGluZzEMMAoGA1UECxMD\nTi9BMTMwMQYDVQQDEypUZXJyYWZvcm0gQWNjZXB0YW5jZSBUZXN0aW5nIENBIDE2\nMTgyODU5MjYwHhcNMjEwNDEzMDM0ODAwWhcNMjYwNDEyMDM0ODAwWjCBkzELMAkG\nA1UEBhMCVVMxDDAKBgNVBAgTA04vQTEMMAoGA1UEBxMDTi9BMSUwIwYDVQQKExxU\nZXJyYWZvcm0gQWNjZXB0YW5jZSBUZXN0aW5nMQwwCgYDVQQLEwNOL0ExMzAxBgNV\nBAMTKlRlcnJhZm0gQWNjZXB0YW5jZSBUZXN0aW5nIENBIDE2MTgyODU5MjZDQ\nAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANBzwmNB8g3eVp8Sn30z0U21\niEh/uwa+WLPEGj/F90mWg2EnW+yFvI9O8OETJAgmAQs39Z4ivt488uwLNVplshnW\nU5J7BqNk9MlBeUZwj6omuS1CZMST/YNSzmIHV5LtyJBcFaEZ2TAi4Ql9f+M9Y5HD\ncxofze5n5tfYzgB3/1lFLk7Vr5eVsqeH5QGOdKZAlsIHfTPS6TFDXP/zTInqCUz0\njfuNkRy9Mqg55JREHVGMufHcT7oTNZiLU+4B/2EfYXJ9YD6JwntKnwB2IC+iOfW7\nGc6QtAREPIlsH3yjmO0rPORrT/oAnnWZcAkkklR5XDnY7QwK5JQ3amN1aByXaPtS\nmbIJNMDxE84AeTREAqR8PmsPK5drRHr3qpWk9nUOVGUaeXwPV+M2t3Xe1WSAQwpv\nJup6PyE8O6KZGwbOiYme5KaKhxMB/ObzhajhTH9RQX7+RMwBzlL+/XTFDnd2B3Ep\nyndNFUHN7fAAapNGjPUXzez01G52N9asE8312JRmLaOqGQ2sWMzr8UgRPw7ZYL4v\nsdlqE2fxXddijGM3TEane6CiM3UdO1VcRAjvNFQjY5WQBUdAkj5+V790cxUQZiMR\nwfmh4hePo7bqXt9RjAS7OeFGBz//H5tQf9wFj3yJTsvKS5bIwP86quR969FFU8nW\na0zNkQLwWygqlhW/VlhxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB\nAf8EBTADAQH/MB0GA1UdDgQWBBT6PStM4ZTFmvpp6lASxuxOkNYZXzANBgkqhkiG\n9w0BAQ0FAAOCAgEACIs9YskrLq3huQXsPDQhHBu8/SLQTAtkj5vtYf1uSq6MXx1k\nj6nDzvixnLam/4HhrsJQyI3FjXnk5yNwaAVA1hQoVw0G2on4qk215fsIRJUKjlzK\npUfW49TFWZ+DPlhBJ/dmHSZsxG940p4xWmNjo2aJ2CraCgP2ns+FfPxXqtpthf1y\nVW5SxKhR9VYNLczXEz8fKvDTLictYYwQ/xFZjxPHpOdV8+DoL18brNKHN8Hs/Nk1\nkzhKrDk8fReEX+jmpG7n/q973nJ31KIBxk85owv/BFgnWpC7HPY+waIH0xNr2iZA\nOu1orlBiBYAqG8zDBq3AGVlxg8yUOc5bik9OhCIwYyT2RFmd6z4O36uIM3LEzJ64\nJj8TTjOP/ktqu+GZrUrnIjfu7mlGvc4u22P8ILJ2AZe5ITp/uhMRJbGbJGEMCCH3\nkAKIEDATrevGdmgWUpdj8RNBS7+BK98eN+vcDqtY4Sudri2TwTkMbAscraacqrSJ\n4rJfjSywVr4oWXyd2P83Hl398X3x04E0Rc15+wrGvaCSN5i1gzc30fTlz1X8dJQ3\nccaHajJlRVZfuCrFBk6m5YRL7AoG4iFfoOuDZZJpjr9nXEzEONhRR5QAG83yMedS\nd8//SuQhuJQTxJW7UzkWaao+32gW/RvuQun0XtCNoow/kMVMOeSjKL9xioM=\n-----END CERTIFICATE-----"
CLOUDFLARE_LOGPUSH_OWNERSHIP_TOKEN: ${{ secrets.CLOUDFLARE_LOGPUSH_OWNERSHIP_TOKEN }}
CLOUDFLARE_WORKSPACE_ONE_CLIENT_ID: d0ed71f01c884e8b94ec4e4d6639f609
CLOUDFLARE_WORKSPACE_ONE_CLIENT_SECRET: ${{ secrets.CLOUDFLARE_WORKSPACE_ONE_CLIENT_SECRET }}
CLOUDFLARE_WORKSPACE_ONE_API_URL: ${{ secrets.CLOUDFLARE_WORKSPACE_ONE_API_URL }}
CLOUDFLARE_WORKSPACE_ONE_AUTH_URL: ${{ secrets.CLOUDFLARE_WORKSPACE_ONE_AUTH_URL }}
CLOUDFLARE_PAGES_OWNER: cloudflare
CLOUDFLARE_PAGES_REPO: cf-pages-terraform-acceptance-testing
CLOUDFLARE_R2_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }}
CLOUDFLARE_R2_ACCESS_KEY_SECRET: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_SECRET }}
CLOUDFLARE_HYPERDRIVE_DATABASE_NAME: neondb
CLOUDFLARE_HYPERDRIVE_DATABASE_PORT: 5432
CLOUDFLARE_HYPERDRIVE_DATABASE_USER: neondb_owner
CLOUDFLARE_HYPERDRIVE_DATABASE_PASSWORD: ${{ secrets.CLOUDFLARE_HYPERDRIVE_DATABASE_PASSWORD }}
CLOUDFLARE_HYPERDRIVE_DATABASE_HOSTNAME: ${{ secrets.CLOUDFLARE_HYPERDRIVE_DATABASE_HOSTNAME }}
TF_ACC: 1
TF_MIGRATE_BINARY_PATH: ${{ github.workspace }}/tf-migrate

TF_MIG_TEST: true
TF_ACC: 1
LAST_V4_VERSION: "4.52.5"
jobs:
migration-tests:
name: ${{ matrix.test.display_name }} Tests
Expand Down
9 changes: 9 additions & 0 deletions internal/acctest/acctest.go
Original file line number Diff line number Diff line change
Expand Up @@ -1129,6 +1129,15 @@ func MigrationV2TestStepWithPlan(t *testing.T, v4Config string, tmpDir string, e
return []resource.TestStep{migrationStep, planStep, validationStep}
}

// InferMigrationVersions determines source and target versions from test provider version.
// Returns ("v4", "v5") for v4.x versions, ("v5", "v5") for v5.x versions.
func InferMigrationVersions(testVersion string) (source, target string) {
if strings.HasPrefix(testVersion, "5.") {
return "v5", "v5"
}
return "v4", "v5"
}

// MigrationTestStep creates a test step that runs the migration command and validates with v5 provider
func MigrationTestStep(t *testing.T, v4Config string, tmpDir string, exactVersion string, stateChecks []statecheck.StateCheck) resource.TestStep {
// Choose the appropriate plan check based on the version
Expand Down
24 changes: 24 additions & 0 deletions internal/migrations/schema_version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package migrations

import "os"

// GetSchemaVersion returns the appropriate schema version based on TF_MIG_TEST environment variable.
//
// This function allows controlled rollout of StateUpgrader migrations:
// - During development/testing: Set TF_MIG_TEST=1 to enable migrations (returns postMigration version)
// - In production: StateUpgraders remain dormant (returns preMigration version)
// - For coordinated release: Remove this wrapper and set Version directly to enable all migrations at once
//
// Parameters:
// - preMigration: The version to use when migrations are disabled (typically 0)
// - postMigration: The version to use when migrations are enabled (typically 500)
//
// Example usage:
//
// Version: GetSchemaVersion(0, 500) // Returns 0 normally, 500 when TF_MIG_TEST=1
func GetSchemaVersion(preMigration, postMigration int64) int64 {
if os.Getenv("TF_MIG_TEST") == "" {
return preMigration
}
return postMigration
}
51 changes: 51 additions & 0 deletions internal/services/dns_record/migration/v500/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package v500

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-log/tflog"
)

// UpgradeFromV0 handles state upgrades from earlier v500 versions (schema_version=0) to current v500.
// This is a no-op upgrade since the schema is compatible - just copy state through.
//
func UpgradeFromV0(
ctx context.Context,
req resource.UpgradeStateRequest,
resp *resource.UpgradeStateResponse,
) {
tflog.Info(ctx, "Upgrading DNS record state from schema_version=0")
// No-op upgrade: schema is compatible, just copy raw state through
// We use the raw state value directly to avoid issues with custom field type serialization
resp.State.Raw = req.State.Raw
}

// UpgradeFromLegacyV3 handles state upgrades from the legacy cloudflare_record resource to cloudflare_dns_record.
// This is triggered when users manually run `terraform state mv cloudflare_record.x cloudflare_dns_record.x`
// (Terraform < 1.8), which preserves the source schema_version=3 from the legacy provider.
//
// Note: schema_version=3 was the final schema version of cloudflare_record in the legacy (SDKv2) provider
// before it was deprecated. The state structure matches SourceCloudflareRecordModel.
func UpgradeFromLegacyV3(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) {
tflog.Info(ctx, "Upgrading DNS record state from legacy cloudflare_record (schema_version=3)")

// Parse the state (schema_version=3, source resource type)
var sourceState SourceCloudflareRecordModel
resp.Diagnostics.Append(req.State.Get(ctx, &sourceState)...)
if resp.Diagnostics.HasError() {
return
}

// Transform to target
targetState, diags := Transform(ctx, sourceState)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

// Set the upgraded state
resp.Diagnostics.Append(resp.State.Set(ctx, targetState)...)

tflog.Info(ctx, "State upgrade from legacy cloudflare_record completed successfully")
}
Loading