diff --git a/.envrc.template b/.envrc.template new file mode 100644 index 000000000..7b4941851 --- /dev/null +++ b/.envrc.template @@ -0,0 +1 @@ +eval $(esc env open pulumi/providers/cloudflare --format shell) diff --git a/.gitignore b/.gitignore index 0995047d7..acd06eccf 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,6 @@ go.work.sum # Ignore local build tracking directory .make + +# Developers define their own direnv config but the repo provides a template in `.envrc.template`. +.envrc diff --git a/provider/pkg/migrations/token/hook.go b/provider/pkg/migrations/token/hook.go new file mode 100644 index 000000000..eb285869d --- /dev/null +++ b/provider/pkg/migrations/token/hook.go @@ -0,0 +1,26 @@ +package migratetoken + +import ( + "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge/info" + "github.com/pulumi/pulumi-tool-generate-migration/pkg/bridge" + "github.com/pulumi/pulumi-tool-generate-migration/pkg/pvbind" + "github.com/pulumi/pulumi/sdk/v3/go/common/resource" +) + +var ( + unmarshalOpts = pvbind.UnmarshalOpts{ + StrictModeIgnoreField: func(pk resource.PropertyKey) bool { + return pk == "id" + }, + } + + hook = bridge.NewPreStateUpgradeHook(Migrate, unmarshalOpts) +) + +func PreStateUpgradeHook(args info.PreStateUpgradeHookArgs) (int64, resource.PropertyMap, error) { + // Only apply speculative schema correction when prior state is v=0. + if args.PriorStateSchemaVersion != 0 { + return args.ResourceSchemaVersion, args.PriorState, nil + } + return hook(args) +} diff --git a/provider/pkg/migrations/token/token.go b/provider/pkg/migrations/token/token.go new file mode 100644 index 000000000..e76991834 --- /dev/null +++ b/provider/pkg/migrations/token/token.go @@ -0,0 +1,116 @@ +package migratetoken + +import ( + "github.com/pulumi/pulumi/sdk/v3/go/common/resource" +) + +type ApiTokenConditionRequestIp struct { + Ins []resource.PropertyValue `pulumi:"ins"` + NotIns []resource.PropertyValue `pulumi:"notIns"` +} + +type ApiTokenCondition struct { + RequestIp ApiTokenConditionRequestIp `pulumi:"requestIp"` +} + +type ApiTokenPolicy struct { + Effect resource.PropertyValue `pulumi:"effect"` + PermissionGroups []resource.PropertyValue `pulumi:"permissionGroups"` + Resources map[string]resource.PropertyValue `pulumi:"resources"` +} + +type R1State struct { + Condition ApiTokenCondition `pulumi:"condition"` + IssuedOn resource.PropertyValue `pulumi:"issuedOn"` + Name resource.PropertyValue `pulumi:"name"` + NotBefore resource.PropertyValue `pulumi:"notBefore"` + Status resource.PropertyValue `pulumi:"status"` + Value resource.PropertyValue `pulumi:"value"` + ExpiresOn resource.PropertyValue `pulumi:"expiresOn"` + ModifiedOn resource.PropertyValue `pulumi:"modifiedOn"` + Policies []ApiTokenPolicy `pulumi:"policies"` +} + +type ApiTokenPolicyPermissionGroupMeta struct { + Key resource.PropertyValue `pulumi:"key"` + Value resource.PropertyValue `pulumi:"value"` +} + +type ApiTokenPolicyPermissionGroup struct { + Id resource.PropertyValue `pulumi:"id"` + Meta ApiTokenPolicyPermissionGroupMeta `pulumi:"meta"` + Name resource.PropertyValue `pulumi:"name"` +} + +type ApiTokenPolicy1 struct { + PermissionGroups []ApiTokenPolicyPermissionGroup `pulumi:"permissionGroups"` + Resources map[string]resource.PropertyValue `pulumi:"resources"` + Effect resource.PropertyValue `pulumi:"effect"` + Id resource.PropertyValue `pulumi:"id"` +} + +type ApiTokenConditionRequestIp1 struct { + NotIns []resource.PropertyValue `pulumi:"notIns"` + Ins []resource.PropertyValue `pulumi:"ins"` +} + +type ApiTokenCondition1 struct { + RequestIp ApiTokenConditionRequestIp1 `pulumi:"requestIp"` +} + +type R2State struct { + IssuedOn resource.PropertyValue `pulumi:"issuedOn"` + LastUsedOn resource.PropertyValue `pulumi:"lastUsedOn"` + Name resource.PropertyValue `pulumi:"name"` + Policies []ApiTokenPolicy1 `pulumi:"policies"` + Condition ApiTokenCondition1 `pulumi:"condition"` + ExpiresOn resource.PropertyValue `pulumi:"expiresOn"` + Status resource.PropertyValue `pulumi:"status"` + Value resource.PropertyValue `pulumi:"value"` + ModifiedOn resource.PropertyValue `pulumi:"modifiedOn"` + NotBefore resource.PropertyValue `pulumi:"notBefore"` +} + +func Migrate(r1 R1State) (R2State, error) { + // Migrate policies + var policies []ApiTokenPolicy1 + for _, p := range r1.Policies { + // Convert permissionGroups from []resource.PropertyValue to []ApiTokenPolicyPermissionGroup + var permissionGroups []ApiTokenPolicyPermissionGroup + for _, pg := range p.PermissionGroups { + permissionGroups = append(permissionGroups, ApiTokenPolicyPermissionGroup{ + Id: pg, + // Meta and Name are optional, so we leave them unset + }) + } + + policy1 := ApiTokenPolicy1{ + PermissionGroups: permissionGroups, + Resources: p.Resources, + Effect: p.Effect, + // Id is optional, so we leave it unset + } + policies = append(policies, policy1) + } + + // Migrate condition + condition1 := ApiTokenCondition1{ + RequestIp: ApiTokenConditionRequestIp1{ + NotIns: r1.Condition.RequestIp.NotIns, + Ins: r1.Condition.RequestIp.Ins, + }, + } + + return R2State{ + IssuedOn: r1.IssuedOn, + LastUsedOn: resource.PropertyValue{}, // New field, set to zero value + Name: r1.Name, + Policies: policies, + Condition: condition1, + ExpiresOn: r1.ExpiresOn, + Status: r1.Status, + Value: r1.Value, + ModifiedOn: r1.ModifiedOn, + NotBefore: r1.NotBefore, + }, nil +} diff --git a/provider/provider_program_test.go b/provider/provider_program_test.go index fe0facbfb..836a7f8e9 100644 --- a/provider/provider_program_test.go +++ b/provider/provider_program_test.go @@ -114,6 +114,11 @@ func TestAccessPolicyUpgrade(t *testing.T) { t, "test-programs/access_policy/access_policyv5", optproviderupgrade.NewSourcePath("test-programs/access_policy")) } +func TestApiTokenUpgrade(t *testing.T) { + testUpgrade(t, "test-programs/api_token/v5", + optproviderupgrade.NewSourcePath("test-programs/api_token")) +} + func TestRuleSetUpgrade(t *testing.T) { testUpgrade(t, "test-programs/ruleset/ruleset_v5") } diff --git a/provider/resources.go b/provider/resources.go index 19251f433..42a1ce896 100644 --- a/provider/resources.go +++ b/provider/resources.go @@ -32,6 +32,7 @@ import ( "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfgen" "github.com/pulumi/pulumi/sdk/v3/go/common/resource" + migratetoken "github.com/pulumi/pulumi-cloudflare/provider/v6/pkg/migrations/token" "github.com/pulumi/pulumi-cloudflare/provider/v6/pkg/version" ) @@ -94,6 +95,11 @@ func Provider() info.Provider { }, Resources: map[string]*info.Resource{ + "cloudflare_api_token": { + Tok: "cloudflare:index/apiToken:ApiToken", + PreStateUpgradeHook: migratetoken.PreStateUpgradeHook, + }, + // We cannot use TF's ID field as Pulumi's ID field automatically, // since it can (theoretically) be set by the user. // diff --git a/provider/test-programs/api_token/Pulumi.yaml b/provider/test-programs/api_token/Pulumi.yaml new file mode 100644 index 000000000..7d2462ecc --- /dev/null +++ b/provider/test-programs/api_token/Pulumi.yaml @@ -0,0 +1,21 @@ +name: example-ruleset +runtime: yaml + +config: + cloudflare-zone-id: + type: string + +resources: + exampleApiToken: + type: cloudflare:index/apiToken:ApiToken + properties: + name: "readonly token" + policies: + - effect: "allow" + permissionGroups: + - id: "c8fed203ed3043cba015a93ad1616f1f" + - id: "82e64a83756745bbbb1c9c2701bf816b" + resources: + "com.cloudflare.api.account.zone.${cloudflare-zone-id}": "*" + expiresOn: "2030-01-01T00:00:00Z" + notBefore: "2018-07-01T05:20:00Z" diff --git a/provider/test-programs/api_token/v5/Pulumi.yaml b/provider/test-programs/api_token/v5/Pulumi.yaml new file mode 100644 index 000000000..dba77ebb2 --- /dev/null +++ b/provider/test-programs/api_token/v5/Pulumi.yaml @@ -0,0 +1,23 @@ +name: example-ruleset +runtime: yaml + +config: + cloudflare-zone-id: + type: string + +resources: + exampleApiToken: + type: cloudflare:index/apiToken:ApiToken + options: + version: 5.49.1 + properties: + name: "readonly token" + policies: + - effect: "allow" + permissionGroups: + - "c8fed203ed3043cba015a93ad1616f1f" + - "82e64a83756745bbbb1c9c2701bf816b" + resources: + "com.cloudflare.api.account.zone.${cloudflare-zone-id}": "*" + expiresOn: "2030-01-01T00:00:00Z" + notBefore: "2018-07-01T05:20:00Z" diff --git a/provider/testdata/recorded/TestProviderUpgrade/v5/5.49.0/grpc.json b/provider/testdata/recorded/TestProviderUpgrade/v5/5.49.0/grpc.json new file mode 100644 index 000000000..312e34ae9 --- /dev/null +++ b/provider/testdata/recorded/TestProviderUpgrade/v5/5.49.0/grpc.json @@ -0,0 +1,22 @@ +{"method":"/pulumirpc.LanguageRuntime/GetPluginInfo","request":{},"response":{"version":"v1.21.1"}} +{"method":"/pulumirpc.LanguageRuntime/GetRequiredPackages","request":{"info":{"entryPoint":".","options":{},"programDirectory":"/private/var/folders/gd/3ncjb1lj5ljgk8xl5ssn_gvc0000gn/T/com.apple.shortcuts.mac-helper/TestApiTokenUpgrade3952610907/007/v5","rootDirectory":"/private/var/folders/gd/3ncjb1lj5ljgk8xl5ssn_gvc0000gn/T/com.apple.shortcuts.mac-helper/TestApiTokenUpgrade3952610907/007/v5"}},"response":{"packages":[{"kind":"resource","name":"cloudflare","version":"5.49.1"}]}} +{"method":"/pulumirpc.ResourceMonitor/SupportsFeature","request":{"id":"resourceReferences"},"response":{"hasSupport":true}} +{"method":"/pulumirpc.ResourceMonitor/SupportsFeature","request":{"id":"outputValues"},"response":{"hasSupport":true}} +{"method":"/pulumirpc.ResourceMonitor/SupportsFeature","request":{"id":"deletedWith"},"response":{"hasSupport":true}} +{"method":"/pulumirpc.ResourceMonitor/SupportsFeature","request":{"id":"aliasSpecs"},"response":{"hasSupport":true}} +{"method":"/pulumirpc.ResourceMonitor/SupportsFeature","request":{"id":"transforms"},"response":{"hasSupport":true}} +{"method":"/pulumirpc.ResourceMonitor/SupportsFeature","request":{"id":"invokeTransforms"},"response":{"hasSupport":true}} +{"method":"/pulumirpc.ResourceMonitor/SupportsFeature","request":{"id":"parameterization"},"response":{"hasSupport":true}} +{"method":"/pulumirpc.ResourceMonitor/RegisterResource","request":{"acceptResources":true,"acceptSecrets":true,"customTimeouts":{},"name":"example-ruleset-test","object":{},"sourcePosition":{"line":317,"uri":"project://%2Fhome%2Frunner%2Fwork%2Fpulumi-yaml%2Fpulumi-yaml%2Fpkg%2Fserver%2Fserver.go"},"supportsResultReporting":true,"type":"pulumi:pulumi:Stack"},"response":{"object":{},"urn":"urn:pulumi:test::example-ruleset::pulumi:pulumi:Stack::example-ruleset-test"}} +{"method":"/pulumirpc.ResourceMonitor/RegisterResourceOutputs","request":{"outputs":{},"urn":"urn:pulumi:test::example-ruleset::pulumi:pulumi:Stack::example-ruleset-test"},"response":{}} +{"method":"/pulumirpc.ResourceProvider/Handshake","request":{"configureWithUrn":true,"engineAddress":"127.0.0.1:60577","supportsRefreshBeforeUpdate":true,"supportsViews":true}} +{"method":"/pulumirpc.ResourceProvider/Attach","request":{"address":"127.0.0.1:60577"},"response":{}} +{"method":"/pulumirpc.ResourceProvider/GetPluginInfo","request":{},"response":{"version":"5.49.0"}} +{"method":"/pulumirpc.ResourceProvider/CheckConfig","request":{"name":"default_5_49_1","news":{"version":"5.49.1"},"olds":{},"type":"pulumi:providers:cloudflare","urn":"urn:pulumi:test::example-ruleset::pulumi:providers:cloudflare::default_5_49_1"},"response":{"inputs":{"apiClientLogging":"false","maxBackoff":"30","minBackoff":"1","retries":"3","rps":"4","version":"5.49.1"}}} +{"method":"/pulumirpc.ResourceProvider/Configure","request":{"acceptResources":true,"acceptSecrets":true,"args":{"apiClientLogging":"false","maxBackoff":"30","minBackoff":"1","retries":"3","rps":"4","version":"5.49.1"},"id":"e8048b51-bf6d-4fd8-bd29-bd4049eb5aea","name":"default_5_49_1","sendsOldInputs":true,"sendsOldInputsToDelete":true,"type":"pulumi:providers:cloudflare","urn":"urn:pulumi:test::example-ruleset::pulumi:providers:cloudflare::default_5_49_1","variables":{"cloudflare:config:apiClientLogging":"false","cloudflare:config:maxBackoff":"30","cloudflare:config:minBackoff":"1","cloudflare:config:retries":"3","cloudflare:config:rps":"4"}},"response":{"supportsAutonamingConfiguration":true,"supportsPreview":true}} +{"method":"/pulumirpc.ResourceProvider/Check","request":{"name":"exampleApiToken","news":{"expiresOn":"2030-01-01T00:00:00Z","name":"readonly token","notBefore":"2018-07-01T05:20:00Z","policies":[{"effect":"allow","permissionGroups":["c8fed203ed3043cba015a93ad1616f1f","82e64a83756745bbbb1c9c2701bf816b"],"resources":{"com.cloudflare.api.account.zone.cabcdb3c8548af2e275acc42ed1fea45":"*"}}]},"olds":{},"randomSeed":"9tE1mdYAiXR4mICtKrRcxU20R+0EL1hacIU99yMoLy8=","type":"cloudflare:index/apiToken:ApiToken","urn":"urn:pulumi:test::example-ruleset::cloudflare:index/apiToken:ApiToken::exampleApiToken"},"response":{"inputs":{"__defaults":[],"expiresOn":"2030-01-01T00:00:00Z","name":"readonly token","notBefore":"2018-07-01T05:20:00Z","policies":[{"__defaults":[],"effect":"allow","permissionGroups":["c8fed203ed3043cba015a93ad1616f1f","82e64a83756745bbbb1c9c2701bf816b"],"resources":{"com.cloudflare.api.account.zone.cabcdb3c8548af2e275acc42ed1fea45":"*"}}]}}} +{"method":"/pulumirpc.ResourceProvider/Create","request":{"name":"exampleApiToken","properties":{"__defaults":[],"expiresOn":"2030-01-01T00:00:00Z","name":"readonly token","notBefore":"2018-07-01T05:20:00Z","policies":[{"__defaults":[],"effect":"allow","permissionGroups":["c8fed203ed3043cba015a93ad1616f1f","82e64a83756745bbbb1c9c2701bf816b"],"resources":{"com.cloudflare.api.account.zone.cabcdb3c8548af2e275acc42ed1fea45":"*"}}]},"resourceStatusAddress":"127.0.0.1:60580","resourceStatusToken":"34007757-dabf-4778-b2b6-179eae598f78","type":"cloudflare:index/apiToken:ApiToken","urn":"urn:pulumi:test::example-ruleset::cloudflare:index/apiToken:ApiToken::exampleApiToken"},"response":{"id":"a87de25e1833e5e0b52e69547673b478","properties":{"condition":null,"expiresOn":"2030-01-01T00:00:00Z","id":"a87de25e1833e5e0b52e69547673b478","issuedOn":"2025-07-01T20:01:23Z","modifiedOn":"2025-07-01T20:01:24Z","name":"readonly token","notBefore":"2018-07-01T05:20:00Z","policies":[{"effect":"allow","permissionGroups":["82e64a83756745bbbb1c9c2701bf816b","c8fed203ed3043cba015a93ad1616f1f"],"resources":{"com.cloudflare.api.account.zone.cabcdb3c8548af2e275acc42ed1fea45":"*"}}],"status":"active","value":{"4dabf18193072939515e22adb298388d":"1b47061264138c4ac30d75fd1eb44270","value":"REDACTED BY PROVIDERTEST"}}}} +{"method":"/pulumirpc.ResourceMonitor/RegisterResource","request":{"acceptResources":true,"acceptSecrets":true,"additionalSecretOutputs":["value"],"custom":true,"customTimeouts":{},"name":"exampleApiToken","object":{"expiresOn":"2030-01-01T00:00:00Z","name":"readonly token","notBefore":"2018-07-01T05:20:00Z","policies":[{"effect":"allow","permissionGroups":["c8fed203ed3043cba015a93ad1616f1f","82e64a83756745bbbb1c9c2701bf816b"],"resources":{"com.cloudflare.api.account.zone.cabcdb3c8548af2e275acc42ed1fea45":"*"}}]},"parent":"urn:pulumi:test::example-ruleset::pulumi:pulumi:Stack::example-ruleset-test","propertyDependencies":{"expiresOn":{},"name":{},"notBefore":{},"policies":{}},"sourcePosition":{"line":1357,"uri":"project://%2Fhome%2Frunner%2Fwork%2Fpulumi-yaml%2Fpulumi-yaml%2Fpkg%2Fpulumiyaml%2Frun.go"},"supportsResultReporting":true,"type":"cloudflare:index/apiToken:ApiToken","version":"5.49.1"},"response":{"id":"a87de25e1833e5e0b52e69547673b478","object":{"condition":null,"expiresOn":"2030-01-01T00:00:00Z","id":"a87de25e1833e5e0b52e69547673b478","issuedOn":"2025-07-01T20:01:23Z","modifiedOn":"2025-07-01T20:01:24Z","name":"readonly token","notBefore":"2018-07-01T05:20:00Z","policies":[{"effect":"allow","permissionGroups":["82e64a83756745bbbb1c9c2701bf816b","c8fed203ed3043cba015a93ad1616f1f"],"resources":{"com.cloudflare.api.account.zone.cabcdb3c8548af2e275acc42ed1fea45":"*"}}],"status":"active","value":{"4dabf18193072939515e22adb298388d":"1b47061264138c4ac30d75fd1eb44270","value":"REDACTED BY PROVIDERTEST"}},"urn":"urn:pulumi:test::example-ruleset::cloudflare:index/apiToken:ApiToken::exampleApiToken"}} +{"method":"/pulumirpc.LanguageRuntime/Run","request":{"config":{"example-ruleset:cloudflare-account-id":"48364b099cc965f71761d67bc5314bd4","example-ruleset:cloudflare-domain":"pulumi-cloudflare-demo.com","example-ruleset:cloudflare-zone-id":"cabcdb3c8548af2e275acc42ed1fea45"},"configPropertyMap":{"example-ruleset:cloudflare-account-id":"48364b099cc965f71761d67bc5314bd4","example-ruleset:cloudflare-domain":"pulumi-cloudflare-demo.com","example-ruleset:cloudflare-zone-id":"cabcdb3c8548af2e275acc42ed1fea45"},"info":{"entryPoint":".","options":{},"programDirectory":"/private/var/folders/gd/3ncjb1lj5ljgk8xl5ssn_gvc0000gn/T/com.apple.shortcuts.mac-helper/TestApiTokenUpgrade3952610907/007/v5","rootDirectory":"/private/var/folders/gd/3ncjb1lj5ljgk8xl5ssn_gvc0000gn/T/com.apple.shortcuts.mac-helper/TestApiTokenUpgrade3952610907/007/v5"},"loaderTarget":"127.0.0.1:60582","monitorAddress":"127.0.0.1:60581","organization":"organization","parallel":48,"program":".","project":"example-ruleset","pwd":"/private/var/folders/gd/3ncjb1lj5ljgk8xl5ssn_gvc0000gn/T/com.apple.shortcuts.mac-helper/TestApiTokenUpgrade3952610907/007/v5","stack":"test"},"response":{}} +{"method":"/grpc.health.v1.Health/Check","request":{},"response":{"status":"SERVING"}} +{"method":"/grpc.health.v1.Health/Check","request":{},"response":{"status":"SERVING"}} \ No newline at end of file diff --git a/provider/testdata/recorded/TestProviderUpgrade/v5/5.49.0/stack.json b/provider/testdata/recorded/TestProviderUpgrade/v5/5.49.0/stack.json new file mode 100644 index 000000000..728b3bb02 --- /dev/null +++ b/provider/testdata/recorded/TestProviderUpgrade/v5/5.49.0/stack.json @@ -0,0 +1,115 @@ +{ + "version": 3, + "deployment": { + "manifest": { + "time": "2025-07-01T16:01:24.261247-04:00", + "magic": "6a5f1cff8ef65523800fc5c9de7d17699a6e5b813cf7d2dab78054c0abf00cfc", + "version": "v3.178.0" + }, + "secrets_providers": { + "type": "passphrase", + "state": { + "salt": "v1:9EFQYES7DEM=:v1:/yV4XJT+VnJdTPrr:YmC0elvpt+d94OiOUmUjZVa/2uig1Q==" + } + }, + "resources": [ + { + "urn": "urn:pulumi:test::example-ruleset::pulumi:pulumi:Stack::example-ruleset-test", + "custom": false, + "type": "pulumi:pulumi:Stack", + "created": "2025-07-01T20:01:23.607367Z", + "modified": "2025-07-01T20:01:23.607367Z" + }, + { + "urn": "urn:pulumi:test::example-ruleset::pulumi:providers:cloudflare::default_5_49_1", + "custom": true, + "id": "e8048b51-bf6d-4fd8-bd29-bd4049eb5aea", + "type": "pulumi:providers:cloudflare", + "inputs": { + "__internal": {}, + "apiClientLogging": "false", + "maxBackoff": "30", + "minBackoff": "1", + "retries": "3", + "rps": "4", + "version": "5.49.1" + }, + "outputs": { + "apiClientLogging": "false", + "maxBackoff": "30", + "minBackoff": "1", + "retries": "3", + "rps": "4", + "version": "5.49.1" + }, + "created": "2025-07-01T20:01:23.621232Z", + "modified": "2025-07-01T20:01:23.621232Z" + }, + { + "urn": "urn:pulumi:test::example-ruleset::cloudflare:index/apiToken:ApiToken::exampleApiToken", + "custom": true, + "id": "a87de25e1833e5e0b52e69547673b478", + "type": "cloudflare:index/apiToken:ApiToken", + "inputs": { + "__defaults": [], + "expiresOn": "2030-01-01T00:00:00Z", + "name": "readonly token", + "notBefore": "2018-07-01T05:20:00Z", + "policies": [ + { + "__defaults": [], + "effect": "allow", + "permissionGroups": [ + "c8fed203ed3043cba015a93ad1616f1f", + "82e64a83756745bbbb1c9c2701bf816b" + ], + "resources": { + "com.cloudflare.api.account.zone.cabcdb3c8548af2e275acc42ed1fea45": "*" + } + } + ] + }, + "outputs": { + "condition": null, + "expiresOn": "2030-01-01T00:00:00Z", + "id": "a87de25e1833e5e0b52e69547673b478", + "issuedOn": "2025-07-01T20:01:23Z", + "modifiedOn": "2025-07-01T20:01:24Z", + "name": "readonly token", + "notBefore": "2018-07-01T05:20:00Z", + "policies": [ + { + "effect": "allow", + "permissionGroups": [ + "82e64a83756745bbbb1c9c2701bf816b", + "c8fed203ed3043cba015a93ad1616f1f" + ], + "resources": { + "com.cloudflare.api.account.zone.cabcdb3c8548af2e275acc42ed1fea45": "*" + } + } + ], + "status": "active", + "value": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "plaintext": "\"REDACTED BY PROVIDERTEST\"" + } + }, + "parent": "urn:pulumi:test::example-ruleset::pulumi:pulumi:Stack::example-ruleset-test", + "provider": "urn:pulumi:test::example-ruleset::pulumi:providers:cloudflare::default_5_49_1::e8048b51-bf6d-4fd8-bd29-bd4049eb5aea", + "propertyDependencies": { + "expiresOn": [], + "name": [], + "notBefore": [], + "policies": [] + }, + "additionalSecretOutputs": [ + "value" + ], + "created": "2025-07-01T20:01:24.255885Z", + "modified": "2025-07-01T20:01:24.255885Z" + } + ], + "metadata": {} + } +} \ No newline at end of file