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
124 changes: 124 additions & 0 deletions provider/provider_program_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ package cloudflare
import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
"testing"

_ "embed"
Expand Down Expand Up @@ -88,6 +90,105 @@ func testProgram(t *testing.T, dir string, opts ...opttest.Option) *pulumitest.P
return pt
}

func testProgramNoCloudflareConfig(t *testing.T, dir string, opts ...opttest.Option) *pulumitest.PulumiTest {
rpFactory := providers.ResourceProviderFactory(providerFactory)
opts = append(opts, opttest.AttachProvider(providerName, rpFactory), opttest.SkipInstall())
return pulumitest.NewPulumiTest(t, dir, opts...)
}

func pulumiCommandEnv(pt *pulumitest.PulumiTest) []string {
workspace := pt.CurrentStack().Workspace()
env := []string{"PULUMI_DEBUG_COMMANDS=true"}
if pulumiHome := workspace.PulumiHome(); pulumiHome != "" {
env = append(env, "PULUMI_HOME="+pulumiHome)
}
for k, v := range workspace.GetEnvVars() {
env = append(env, strings.Join([]string{k, v}, "="))
}
return env
}

func importStackWithDisabledIntegrity(t *testing.T, pt *pulumitest.PulumiTest, source apitype.UntypedDeployment) {
t.Helper()
stack := pt.CurrentStack()
require.NotNil(t, stack)

f, err := os.CreateTemp(t.TempDir(), "stack-*.json")
require.NoError(t, err)
defer func() { require.NoError(t, f.Close()) }()

require.NoError(t, json.NewEncoder(f).Encode(source))

workspace := stack.Workspace()
stdout, stderr, _, err := workspace.PulumiCommand().Run(
pt.Context(),
workspace.WorkDir(),
nil,
nil,
nil,
pulumiCommandEnv(pt),
"--disable-integrity-checking",
"stack",
"import",
"--file",
f.Name(),
"--stack",
stack.Name(),
)
require.NoError(t, err, fmt.Sprintf("stdout:\n%s\nstderr:\n%s", stdout, stderr))
}

func previewWithDisabledIntegrity(t *testing.T, pt *pulumitest.PulumiTest) (string, string, error) {
t.Helper()
stack := pt.CurrentStack()
require.NotNil(t, stack)

workspace := stack.Workspace()
stdout, stderr, _, err := workspace.PulumiCommand().Run(
pt.Context(),
workspace.WorkDir(),
nil,
nil,
nil,
pulumiCommandEnv(pt),
"--disable-integrity-checking",
"preview",
"--non-interactive",
"--diff",
"--stack",
stack.Name(),
)
return stdout, stderr, err
}

func withArgoTieredCachingSchemaVersion(
t *testing.T, source apitype.UntypedDeployment, version string,
) apitype.UntypedDeployment {
t.Helper()
var deployment map[string]interface{}
require.NoError(t, json.Unmarshal(source.Deployment, &deployment))
resources, ok := deployment["resources"].([]interface{})
require.True(t, ok)
found := false
for _, rawResource := range resources {
res, ok := rawResource.(map[string]interface{})
require.True(t, ok)
if res["type"] != "cloudflare:index/argoTieredCaching:ArgoTieredCaching" {
continue
}
found = true
outputs, ok := res["outputs"].(map[string]interface{})
require.True(t, ok)
outputs["__meta"] = fmt.Sprintf(`{"schema_version":"%s"}`, version)
break
}
require.True(t, found, "did not find ArgoTieredCaching resource in test state")
updatedDeployment, err := json.Marshal(deployment)
require.NoError(t, err)
source.Deployment = updatedDeployment
return source
}

func testUpgrade(
t *testing.T, dir1 string, opts ...optproviderupgrade.PreviewProviderUpgradeOpt,
) auto.PreviewResult {
Expand Down Expand Up @@ -152,6 +253,29 @@ func TestZeroTrustAccessApplicationFromState(t *testing.T) {
pt.Preview(t)
}

func TestArgoTieredCachingFromV612State(t *testing.T) {
state, err := os.ReadFile("testdata/argo_tiered_caching_state_v6_12.json")
require.NoError(t, err)
depl := apitype.UntypedDeployment{}
require.NoError(t, json.Unmarshal(state, &depl))

t.Run("direct upgrade skips legacy argo migration for Pulumi state", func(t *testing.T) {
pt := testProgramNoCloudflareConfig(t, "test-programs/argo_tiered_caching_state",
opttest.NewStackOptions(optnewstack.DisableAutoDestroy()))
importStackWithDisabledIntegrity(t, pt, depl)
stdout, stderr, err := previewWithDisabledIntegrity(t, pt)
require.NoError(t, err, fmt.Sprintf("stdout:\n%s\nstderr:\n%s", stdout, stderr))
})

t.Run("schema version bump avoids legacy argo migration", func(t *testing.T) {
pt := testProgramNoCloudflareConfig(t, "test-programs/argo_tiered_caching_state",
opttest.NewStackOptions(optnewstack.DisableAutoDestroy()))
importStackWithDisabledIntegrity(t, pt, withArgoTieredCachingSchemaVersion(t, depl, "500"))
stdout, stderr, err := previewWithDisabledIntegrity(t, pt)
require.NoError(t, err, fmt.Sprintf("stdout:\n%s\nstderr:\n%s", stdout, stderr))
})
}

func TestRuleSetHeadersUpgrade(t *testing.T) {
testUpgrade(
t, "test-programs/ruleset_headers/ruleset_headers_v5",
Expand Down
35 changes: 35 additions & 0 deletions provider/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ func Provider() info.Provider {
ComputeID: delegateID("imageId"),
},

"cloudflare_argo_tiered_caching": {
PreStateUpgradeHook: argoTieredCachingPreStateUpgradeHook,
},

"cloudflare_ruleset": {
Tok: "cloudflare:index/ruleset:Ruleset",
PreStateUpgradeHook: func(
Expand Down Expand Up @@ -792,6 +796,37 @@ func resetMigratedResourcesSchemaVersion(prov *info.Provider) {
}
}

// argoTieredCachingPreStateUpgradeHook handles Pulumi state written before the
// upstream resource had a current schema marker. Upstream now treats schema
// version 0 as legacy Terraform cloudflare_argo state and expects
// tiered_caching, but old Pulumi ArgoTieredCaching state is already shaped like
// the current resource. Mark that Pulumi-shaped state as version 500 so the
// wrong legacy migration is skipped.
func argoTieredCachingPreStateUpgradeHook(
args info.PreStateUpgradeHookArgs,
) (int64, resource.PropertyMap, error) {
if args.PriorStateSchemaVersion == 0 && isPulumiArgoTieredCachingState(args.PriorState) {
return 500, args.PriorState, nil
}
return args.PriorStateSchemaVersion, args.PriorState, nil
}

// isPulumiArgoTieredCachingState narrowly identifies old Pulumi-created
// cloudflare_argo_tiered_caching state. The camelCase zoneId key distinguishes
// it from Terraform's legacy snake_case cloudflare_argo state, and the absence
// of tiered_caching keeps the real legacy migration path intact.
func isPulumiArgoTieredCachingState(state resource.PropertyMap) bool {
value, hasValue := state["value"]
zoneID, hasZoneID := state["zoneId"]
_, hasTerraformTieredCaching := state["tiered_caching"]
_, hasPulumiTieredCaching := state["tieredCaching"]

return hasValue && value.IsString() &&
hasZoneID && zoneID.IsString() &&
!hasTerraformTieredCaching &&
!hasPulumiTieredCaching
}

func delegateID(pulumiField resource.PropertyKey) tfbridge.ComputeID {
repoURL := "https://github.com/pulumi/pulumi-cloudflare"
d := tfbridge.DelegateIDField(pulumiField, "cloudflare", repoURL)
Expand Down
39 changes: 39 additions & 0 deletions provider/resources_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge"
shim "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfshim"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
Expand Down Expand Up @@ -80,6 +81,44 @@ func TestZeroTrustAccessApplicationVersionReminder(t *testing.T) {
"custom Pulumi PreStateUpgradeHook needs to be revisited or possibly dropped")
}

func TestArgoTieredCachingVersionReminder(t *testing.T) {
version.Version = "0.0.4"
p := Provider()
r := p.P.ResourcesMap().Get("cloudflare_argo_tiered_caching")
// See https://github.com/pulumi/pulumi-cloudflare/issues/1575
assert.Equalf(t, 500, r.SchemaVersion(),
"Reminder: cloudflare_argo_tiered_caching advanced schema version from 500 and "+
"custom Pulumi PreStateUpgradeHook needs to be revisited or possibly dropped")
}

func TestArgoTieredCachingPreStateUpgradeHook(t *testing.T) {
pulumiState := resource.PropertyMap{
"value": resource.NewStringProperty("on"),
"zoneId": resource.NewStringProperty("00000000000000000000000000000000"),
}
version, state, err := argoTieredCachingPreStateUpgradeHook(
tfbridge.PreStateUpgradeHookArgs{
PriorStateSchemaVersion: 0,
PriorState: pulumiState,
})
require.NoError(t, err)
assert.Equal(t, int64(500), version)
assert.Equal(t, pulumiState, state)

legacyArgoState := resource.PropertyMap{
"tiered_caching": resource.NewStringProperty("on"),
"zone_id": resource.NewStringProperty("00000000000000000000000000000000"),
}
version, state, err = argoTieredCachingPreStateUpgradeHook(
tfbridge.PreStateUpgradeHookArgs{
PriorStateSchemaVersion: 0,
PriorState: legacyArgoState,
})
require.NoError(t, err)
assert.Equal(t, int64(0), version)
assert.Equal(t, legacyArgoState, state)
}

func Test_delegateID(t *testing.T) {

type testCase struct {
Expand Down
9 changes: 9 additions & 0 deletions provider/test-programs/argo_tiered_caching_state/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: argo-tiered-caching-state
runtime: yaml

resources:
argo:
type: cloudflare:ArgoTieredCaching
properties:
zoneId: 00000000000000000000000000000000
value: "on"
67 changes: 67 additions & 0 deletions provider/testdata/argo_tiered_caching_state_v6_12.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{
"version": 3,
"deployment": {
"manifest": {
"time": "2026-05-18T00:00:00Z",
"magic": "0000000000000000000000000000000000000000000000000000000000000000",
"version": "v3.228.0"
},
"secrets_providers": {
"type": "passphrase",
"state": {
"salt": "v1:O1t5suqMRIo=:v1:7DqVMCoY+uij+EZC:Bc3SzKdajmbRZU5OXZWfP0oEcpUIJw=="
}
},
"resources": [
{
"urn": "urn:pulumi:test::argo-tiered-caching-state::pulumi:pulumi:Stack::argo-tiered-caching-state-test",
"custom": false,
"type": "pulumi:pulumi:Stack",
"outputs": {}
},
{
"urn": "urn:pulumi:test::argo-tiered-caching-state::pulumi:providers:cloudflare::default",
"custom": true,
"id": "00000000-0000-0000-0000-000000000001",
"type": "pulumi:providers:cloudflare",
"inputs": {
"apiClientLogging": "false",
"maxBackoff": "30",
"minBackoff": "1",
"retries": "3",
"rps": "4"
},
"outputs": {
"apiClientLogging": "false",
"maxBackoff": "30",
"minBackoff": "1",
"retries": "3",
"rps": "4"
}
},
{
"urn": "urn:pulumi:test::argo-tiered-caching-state::cloudflare:index/argoTieredCaching:ArgoTieredCaching::argo",
"custom": true,
"id": "00000000000000000000000000000000",
"type": "cloudflare:index/argoTieredCaching:ArgoTieredCaching",
"inputs": {
"value": "on",
"zoneId": "00000000000000000000000000000000"
},
"outputs": {
"editable": false,
"id": "00000000000000000000000000000000",
"modifiedOn": "2025-08-15T09:49:33Z",
"value": "on",
"zoneId": "00000000000000000000000000000000"
},
"parent": "urn:pulumi:test::argo-tiered-caching-state::pulumi:pulumi:Stack::argo-tiered-caching-state-test",
"provider": "urn:pulumi:test::argo-tiered-caching-state::pulumi:providers:cloudflare::default::00000000-0000-0000-0000-000000000001",
"propertyDependencies": {
"value": null,
"zoneId": null
}
}
]
}
}
Loading