Skip to content

Commit f4eefe0

Browse files
Merge pull request #6713 from cloudflare/vaishak/pages-projects
feat(pages_project): State upgrade
2 parents e6a03b6 + f439772 commit f4eefe0

19 files changed

+1712
-254
lines changed

internal/acctest/acctest.go

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -926,35 +926,6 @@ func RunMigrationV2Command(t *testing.T, v4Config string, tmpDir string, sourceV
926926
t.Fatalf("tf-migrate binary not found at %s. Please set TF_MIGRATE_BINARY_PATH or ensure the binary is built.", migratorPath)
927927
}
928928

929-
// Find state file in tmpDir
930-
// First check if state file exists directly in tmpDir (from v4 import)
931-
var stateFilePath string
932-
directStateFile := filepath.Join(tmpDir, "terraform.tfstate")
933-
if _, err := os.Stat(directStateFile); err == nil {
934-
stateFilePath = directStateFile
935-
} else {
936-
// Look for state file in subdirectories (from test framework)
937-
entries, err := os.ReadDir(tmpDir)
938-
if err != nil {
939-
t.Logf("Failed to read test directory: %v", err)
940-
} else {
941-
for _, entry := range entries {
942-
if entry.IsDir() {
943-
inner_entries, _ := os.ReadDir(filepath.Join(tmpDir, entry.Name()))
944-
for _, inner_entry := range inner_entries {
945-
if inner_entry.Name() == "terraform.tfstate" {
946-
stateFilePath = filepath.Join(tmpDir, entry.Name(), "terraform.tfstate")
947-
break
948-
}
949-
}
950-
}
951-
if stateFilePath != "" {
952-
break
953-
}
954-
}
955-
}
956-
}
957-
958929
// Build the command
959930
args := []string{
960931
"migrate",
@@ -963,11 +934,6 @@ func RunMigrationV2Command(t *testing.T, v4Config string, tmpDir string, sourceV
963934
"--target-version", targetVersion,
964935
}
965936

966-
// Add state file argument if found
967-
if stateFilePath != "" {
968-
args = append(args, "--state-file", stateFilePath)
969-
}
970-
971937
// Add debug logging if TF_LOG is set
972938
if strings.ToLower(os.Getenv("TF_LOG")) == "debug" {
973939
args = append(args, "--log-level", "debug")
@@ -1325,6 +1291,3 @@ func MigrationV2TestStepAllowCreate(t *testing.T, v4Config string, tmpDir string
13251291
},
13261292
}
13271293
}
1328-
1329-
// ImportResourceWithV4Provider imports a resource using the v4 provider before migration testing.
1330-
// This is used for import-only resources that cannot be created via Terraform.

internal/services/pages_project/custom_test.go

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -266,9 +266,9 @@ func TestEnvVarsMatchIgnoringSecrets(t *testing.T) {
266266
expected: true,
267267
},
268268
{
269-
name: "source nil, dest empty",
270-
source: nil,
271-
dest: &map[string]PagesProjectDeploymentConfigsPreviewEnvVarsModel{},
269+
name: "source nil, dest empty",
270+
source: nil,
271+
dest: &map[string]PagesProjectDeploymentConfigsPreviewEnvVarsModel{},
272272
expected: true,
273273
},
274274
{
@@ -402,7 +402,7 @@ func TestCompatibilityFlagsEqual(t *testing.T) {
402402
}
403403

404404
// TestNormalizeBuildConfig_EmptyStrings tests that build_config with empty strings
405-
// is normalized to nil. This is the fix for the issue where the API returns empty
405+
// is normalized to null. This is the fix for the issue where the API returns empty
406406
// strings for build_config fields (like build_command = ""), and the provider would
407407
// incorrectly preserve these in the plan, causing "planned value for a non-computed
408408
// attribute" errors.
@@ -414,14 +414,14 @@ func TestNormalizeBuildConfig_EmptyStrings(t *testing.T) {
414414
Name: types.StringValue("test-project"),
415415
AccountID: types.StringValue("abc123"),
416416
ProductionBranch: types.StringValue("main"),
417-
BuildConfig: &PagesProjectBuildConfigModel{
418-
BuildCaching: types.BoolNull(), // null bool
419-
BuildCommand: types.StringValue(""), // empty string
420-
DestinationDir: types.StringValue(""), // empty string
421-
RootDir: types.StringValue(""), // empty string
422-
WebAnalyticsTag: types.StringValue(""), // empty string
423-
WebAnalyticsToken: types.StringValue(""), // empty string
424-
},
417+
BuildConfig: customfield.NewObjectMust(ctx, &PagesProjectBuildConfigModel{
418+
BuildCaching: types.BoolNull(), // null bool
419+
BuildCommand: types.StringValue(""), // empty string
420+
DestinationDir: types.StringValue(""), // empty string
421+
RootDir: types.StringValue(""), // empty string
422+
WebAnalyticsTag: types.StringValue(""), // empty string
423+
WebAnalyticsToken: types.StringValue(""), // empty string
424+
}),
425425
}
426426

427427
// Call NormalizeDeploymentConfigs
@@ -431,14 +431,14 @@ func TestNormalizeBuildConfig_EmptyStrings(t *testing.T) {
431431
t.Fatalf("unexpected diagnostics: %v", diags)
432432
}
433433

434-
// build_config should be normalized to nil when all fields are empty/null
435-
if result.BuildConfig != nil {
436-
t.Errorf("expected build_config to be nil when all fields are empty/null, got %+v", result.BuildConfig)
434+
// build_config should be normalized to null when all fields are empty/null
435+
if !result.BuildConfig.IsNull() {
436+
t.Errorf("expected build_config to be null when all fields are empty/null, got %+v", result.BuildConfig)
437437
}
438438
}
439439

440440
// TestNormalizeBuildConfig_WithActualValues tests that build_config with actual
441-
// values is NOT normalized to nil.
441+
// values is NOT normalized to null.
442442
func TestNormalizeBuildConfig_WithActualValues(t *testing.T) {
443443
ctx := context.Background()
444444

@@ -447,14 +447,14 @@ func TestNormalizeBuildConfig_WithActualValues(t *testing.T) {
447447
Name: types.StringValue("test-project"),
448448
AccountID: types.StringValue("abc123"),
449449
ProductionBranch: types.StringValue("main"),
450-
BuildConfig: &PagesProjectBuildConfigModel{
450+
BuildConfig: customfield.NewObjectMust(ctx, &PagesProjectBuildConfigModel{
451451
BuildCaching: types.BoolValue(true),
452452
BuildCommand: types.StringValue("npm run build"),
453453
DestinationDir: types.StringValue("dist"),
454454
RootDir: types.StringValue("/"),
455-
WebAnalyticsTag: types.StringValue(""), // empty is ok if others have values
456-
WebAnalyticsToken: types.StringValue(""), // empty is ok if others have values
457-
},
455+
WebAnalyticsTag: types.StringValue(""), // empty is ok if others have values
456+
WebAnalyticsToken: types.StringValue(""), // empty is ok if others have values
457+
}),
458458
}
459459

460460
// Call NormalizeDeploymentConfigs
@@ -464,8 +464,8 @@ func TestNormalizeBuildConfig_WithActualValues(t *testing.T) {
464464
t.Fatalf("unexpected diagnostics: %v", diags)
465465
}
466466

467-
// build_config should NOT be nil when fields have actual values
468-
if result.BuildConfig == nil {
467+
// build_config should NOT be null when fields have actual values
468+
if result.BuildConfig.IsNull() {
469469
t.Error("expected build_config to be preserved when fields have actual values")
470470
}
471471
}
@@ -475,12 +475,12 @@ func TestNormalizeBuildConfig_WithActualValues(t *testing.T) {
475475
func TestMergeBuildConfigFromState(t *testing.T) {
476476
// Plan has some values but not all
477477
plan := &PagesProjectBuildConfigModel{
478-
BuildCaching: types.BoolNull(), // not specified by user
478+
BuildCaching: types.BoolNull(), // not specified by user
479479
BuildCommand: types.StringValue("yarn build"), // user specified
480-
DestinationDir: types.StringUnknown(), // unknown
481-
RootDir: types.StringValue("/app"), // user specified
482-
WebAnalyticsTag: types.StringNull(), // not specified
483-
WebAnalyticsToken: types.StringNull(), // not specified
480+
DestinationDir: types.StringUnknown(), // unknown
481+
RootDir: types.StringValue("/app"), // user specified
482+
WebAnalyticsTag: types.StringNull(), // not specified
483+
WebAnalyticsToken: types.StringNull(), // not specified
484484
}
485485

486486
// State has values from a previous apply/import
@@ -526,4 +526,3 @@ func TestMergeBuildConfigFromState_NilInputs(t *testing.T) {
526526
mergeBuildConfigFromState(nil, &PagesProjectBuildConfigModel{})
527527
mergeBuildConfigFromState(&PagesProjectBuildConfigModel{}, nil)
528528
}
529-
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package v500
2+
3+
import (
4+
"context"
5+
6+
"github.com/hashicorp/terraform-plugin-framework/resource"
7+
"github.com/hashicorp/terraform-plugin-log/tflog"
8+
)
9+
10+
// UpgradeNoOp handles state upgrades within the v5 series (schema_version=1+).
11+
// This is a no-op upgrade since the schema is compatible - just copy state through.
12+
func UpgradeNoOp(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) {
13+
tflog.Info(ctx, "Upgrading pages_project state (no-op)")
14+
// No-op upgrade: schema is compatible, just copy raw state through
15+
resp.State.Raw = req.State.Raw
16+
}
17+
18+
// UpgradeFromV0 upgrades the state from v4 (version 0) to v5.
19+
// v4 SDKv2 stores blocks as lists, so we access the first element of each slice.
20+
func UpgradeFromV0(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) {
21+
var priorState SourcePagesProjectModelV0
22+
resp.Diagnostics.Append(req.State.Get(ctx, &priorState)...)
23+
if resp.Diagnostics.HasError() {
24+
return
25+
}
26+
27+
// Transform the v0 (v4 provider) state to v5 state
28+
newState, diags := Transform(ctx, priorState)
29+
resp.Diagnostics.Append(diags...)
30+
if resp.Diagnostics.HasError() {
31+
return
32+
}
33+
34+
resp.Diagnostics.Append(resp.State.Set(ctx, newState)...)
35+
}

0 commit comments

Comments
 (0)