Skip to content

Commit 615c969

Browse files
author
cortlyons
committed
feat: v4 to v5 migration for zero trust access service token
1 parent 0111d93 commit 615c969

File tree

8 files changed

+812
-0
lines changed

8 files changed

+812
-0
lines changed

integration/v4_to_v5/integration_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ func TestV4ToV5Migration(t *testing.T) {
3737
}
3838

3939
tests := []integration.TestCase{
40+
{
41+
Name: "DNSRecord",
42+
Description: "Migrate cloudflare_record to cloudflare_dns_record",
43+
Resource: "dns_record",
44+
},
4045
{
4146
Name: "AccountMember",
4247
Description: "Migrate cloudflare_account_member email_address to email and role_ids to roles",
@@ -47,6 +52,11 @@ func TestV4ToV5Migration(t *testing.T) {
4752
Description: "Migrate cloudflare_record to cloudflare_dns_record",
4853
Resource: "dns_record",
4954
},
55+
{
56+
Name: "ZeroTrustAccessServiceToken",
57+
Description: "Migrate zero_trust_access_service_token to zero_trust_access_service_token v5",
58+
Resource: "zero_trust_access_service_token",
59+
},
5060
{
5161
Name: "APIToken",
5262
Description: "Migrate cloudflare_api_token policy blocks to policies list",
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
{
2+
"version": 4,
3+
"terraform_version": "1.15.0",
4+
"serial": 7,
5+
"lineage": "711dca58-1045-8360-2b93-c9a8646b64cc",
6+
"outputs": {},
7+
"resources": [
8+
{
9+
"mode": "managed",
10+
"type": "cloudflare_zero_trust_access_service_token",
11+
"name": "basic_token_legacy",
12+
"provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
13+
"instances": [
14+
{
15+
"schema_version": 0,
16+
"attributes": {
17+
"account_id": "f037e56e89293a057740de681ac9abbe",
18+
"client_id": "123456.access",
19+
"client_secret": "123456",
20+
"client_secret_version": 2.0,
21+
"duration": "8760h",
22+
"expires_at": "2026-10-31T16:42:49Z",
23+
"id": "a3689b90-4fd8-4655-8444-faa0446287ed",
24+
"name": "basic_token_legacy",
25+
"previous_client_secret_expires_at": "2024-12-31T23:59:59Z",
26+
"zone_id": null
27+
},
28+
"sensitive_attributes": [
29+
[
30+
{
31+
"type": "get_attr",
32+
"value": "client_secret"
33+
}
34+
]
35+
],
36+
"identity_schema_version": 0,
37+
"private": "bnVsbA=="
38+
}
39+
]
40+
},
41+
{
42+
"mode": "managed",
43+
"type": "cloudflare_zero_trust_access_service_token",
44+
"name": "basic_token",
45+
"provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
46+
"instances": [
47+
{
48+
"schema_version": 0,
49+
"attributes": {
50+
"account_id": "f037e56e89293a057740de681ac9abbe",
51+
"client_id": "123456.access",
52+
"client_secret": "123456",
53+
"client_secret_version": 2.0,
54+
"duration": "8760h",
55+
"expires_at": "2026-10-31T16:42:49Z",
56+
"id": "89500dab-f7ac-4a1d-9157-b43a78429292",
57+
"name": "basic_token",
58+
"previous_client_secret_expires_at": "2024-12-31T23:59:59Z",
59+
"zone_id": null
60+
},
61+
"sensitive_attributes": [
62+
[
63+
{
64+
"type": "get_attr",
65+
"value": "client_secret"
66+
}
67+
]
68+
],
69+
"identity_schema_version": 0,
70+
"private": "bnVsbA=="
71+
}
72+
]
73+
}
74+
],
75+
"check_results": null
76+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Test Case 1: Basic access service token with all fields
2+
resource "cloudflare_zero_trust_access_service_token" "basic_token" {
3+
account_id = "f037e56e89293a057740de681ac9abbe"
4+
name = "basic_token"
5+
duration = "8760h"
6+
client_secret_version = 2
7+
previous_client_secret_expires_at = "2024-12-31T23:59:59Z"
8+
}
9+
10+
# Test Case 2: Legacy access service token name
11+
resource "cloudflare_zero_trust_access_service_token" "basic_token_legacy" {
12+
account_id = "f037e56e89293a057740de681ac9abbe"
13+
name = "basic_token_legacy"
14+
duration = "8760h"
15+
client_secret_version = 2
16+
previous_client_secret_expires_at = "2024-12-31T23:59:59Z"
17+
}
18+
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
{
2+
"version": 4,
3+
"terraform_version": "1.15.0",
4+
"serial": 7,
5+
"lineage": "711dca58-1045-8360-2b93-c9a8646b64cc",
6+
"outputs": {},
7+
"resources": [
8+
{
9+
"mode": "managed",
10+
"type": "cloudflare_access_service_token",
11+
"name": "basic_token_legacy",
12+
"provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
13+
"instances": [
14+
{
15+
"schema_version": 0,
16+
"attributes": {
17+
"account_id": "f037e56e89293a057740de681ac9abbe",
18+
"client_id": "123456.access",
19+
"client_secret": "123456",
20+
"client_secret_version": 2,
21+
"duration": "8760h",
22+
"expires_at": "2026-10-31T16:42:49Z",
23+
"id": "a3689b90-4fd8-4655-8444-faa0446287ed",
24+
"min_days_for_renewal": 30,
25+
"name": "basic_token_legacy",
26+
"previous_client_secret_expires_at": "2024-12-31T23:59:59Z",
27+
"zone_id": null
28+
},
29+
"sensitive_attributes": [
30+
[
31+
{
32+
"type": "get_attr",
33+
"value": "client_secret"
34+
}
35+
]
36+
],
37+
"identity_schema_version": 0,
38+
"private": "bnVsbA=="
39+
}
40+
]
41+
},
42+
{
43+
"mode": "managed",
44+
"type": "cloudflare_zero_trust_access_service_token",
45+
"name": "basic_token",
46+
"provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
47+
"instances": [
48+
{
49+
"schema_version": 0,
50+
"attributes": {
51+
"account_id": "f037e56e89293a057740de681ac9abbe",
52+
"client_id": "123456.access",
53+
"client_secret": "123456",
54+
"client_secret_version": 2,
55+
"duration": "8760h",
56+
"expires_at": "2026-10-31T16:42:49Z",
57+
"id": "89500dab-f7ac-4a1d-9157-b43a78429292",
58+
"min_days_for_renewal": 30,
59+
"name": "basic_token",
60+
"previous_client_secret_expires_at": "2024-12-31T23:59:59Z",
61+
"zone_id": null
62+
},
63+
"sensitive_attributes": [
64+
[
65+
{
66+
"type": "get_attr",
67+
"value": "client_secret"
68+
}
69+
]
70+
],
71+
"identity_schema_version": 0,
72+
"private": "bnVsbA=="
73+
}
74+
]
75+
}
76+
],
77+
"check_results": null
78+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Test Case 1: Basic access service token with all fields
2+
resource "cloudflare_zero_trust_access_service_token" "basic_token" {
3+
account_id = "f037e56e89293a057740de681ac9abbe"
4+
name = "basic_token"
5+
duration = "8760h"
6+
min_days_for_renewal = 30
7+
client_secret_version = 2
8+
previous_client_secret_expires_at = "2024-12-31T23:59:59Z"
9+
}
10+
11+
# Test Case 2: Legacy access service token name
12+
resource "cloudflare_access_service_token" "basic_token_legacy" {
13+
account_id = "f037e56e89293a057740de681ac9abbe"
14+
name = "basic_token_legacy"
15+
duration = "8760h"
16+
min_days_for_renewal = 30
17+
client_secret_version = 2
18+
previous_client_secret_expires_at = "2024-12-31T23:59:59Z"
19+
}

internal/registry/registry.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"github.com/cloudflare/tf-migrate/internal/resources/account_member"
55
"github.com/cloudflare/tf-migrate/internal/resources/api_token"
66
"github.com/cloudflare/tf-migrate/internal/resources/dns_record"
7+
"github.com/cloudflare/tf-migrate/internal/resources/zero_trust_access_service_token"
78
"github.com/cloudflare/tf-migrate/internal/resources/zero_trust_list"
89
)
910

@@ -16,4 +17,5 @@ func RegisterAllMigrations() {
1617
api_token.NewV4ToV5Migrator()
1718
dns_record.NewV4ToV5Migrator()
1819
zero_trust_list.NewV4ToV5Migrator()
20+
zero_trust_access_service_token.NewV4ToV5Migrator()
1921
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package zero_trust_access_service_token
2+
3+
import (
4+
"github.com/cloudflare/tf-migrate/internal"
5+
"github.com/hashicorp/hcl/v2/hclwrite"
6+
"github.com/tidwall/gjson"
7+
"github.com/tidwall/sjson"
8+
9+
"github.com/cloudflare/tf-migrate/internal/transform"
10+
tfhcl "github.com/cloudflare/tf-migrate/internal/transform/hcl"
11+
"github.com/cloudflare/tf-migrate/internal/transform/state"
12+
)
13+
14+
type V4ToV5Migrator struct {
15+
}
16+
17+
func NewV4ToV5Migrator() transform.ResourceTransformer {
18+
migrator := &V4ToV5Migrator{}
19+
20+
// Deprecated v4 Name
21+
internal.RegisterMigrator("cloudflare_access_service_token", "v4", "v5", migrator)
22+
internal.RegisterMigrator("cloudflare_zero_trust_access_service_token", "v4", "v5", migrator)
23+
24+
return migrator
25+
}
26+
27+
func (m *V4ToV5Migrator) GetResourceType() string {
28+
return "cloudflare_zero_trust_access_service_token"
29+
}
30+
31+
func (m *V4ToV5Migrator) CanHandle(resourceType string) bool {
32+
// Handle both the current name and the deprecated v4 name
33+
return resourceType == "cloudflare_access_service_token" || resourceType == "cloudflare_zero_trust_access_service_token"
34+
}
35+
36+
func (m *V4ToV5Migrator) Preprocess(content string) string {
37+
return content
38+
}
39+
40+
func (m *V4ToV5Migrator) TransformConfig(ctx *transform.Context, block *hclwrite.Block) (*transform.TransformResult, error) {
41+
resourceType := tfhcl.GetResourceType(block)
42+
if resourceType == "cloudflare_access_service_token" {
43+
tfhcl.RenameResourceType(block, "cloudflare_access_service_token", "cloudflare_zero_trust_access_service_token")
44+
}
45+
46+
body := block.Body()
47+
48+
// Remove deprecated field: min_days_for_renewal
49+
tfhcl.RemoveAttributes(body, "min_days_for_renewal")
50+
51+
return &transform.TransformResult{
52+
Blocks: []*hclwrite.Block{block},
53+
RemoveOriginal: false,
54+
}, nil
55+
}
56+
57+
func (m *V4ToV5Migrator) TransformState(ctx *transform.Context, stateJSON gjson.Result, resourcePath string) (string, error) {
58+
result := stateJSON.String()
59+
60+
if stateJSON.Get("resources").Exists() {
61+
return m.transformFullState(result, stateJSON)
62+
}
63+
64+
if !stateJSON.Exists() || !stateJSON.Get("attributes").Exists() {
65+
return result, nil
66+
}
67+
68+
result = m.transformSingleInstance(result, stateJSON)
69+
70+
return result, nil
71+
}
72+
73+
func (m *V4ToV5Migrator) transformFullState(result string, stateJSON gjson.Result) (string, error) {
74+
resources := stateJSON.Get("resources")
75+
if !resources.Exists() {
76+
return result, nil
77+
}
78+
79+
resources.ForEach(func(key, resource gjson.Result) bool {
80+
resourceType := resource.Get("type").String()
81+
82+
if !m.CanHandle(resourceType) {
83+
return true // continue
84+
}
85+
86+
// Rename cloudflare_access_service_token to cloudflare_zero_trust_access_service_token
87+
if resourceType == "cloudflare_access_service_token" {
88+
resourcePath := "resources." + key.String() + ".type"
89+
result, _ = sjson.Set(result, resourcePath, "cloudflare_zero_trust_access_service_token")
90+
}
91+
92+
instances := resource.Get("instances")
93+
instances.ForEach(func(instKey, instance gjson.Result) bool {
94+
instPath := "resources." + key.String() + ".instances." + instKey.String()
95+
96+
attrs := instance.Get("attributes")
97+
if attrs.Exists() {
98+
instJSON := instance.String()
99+
transformedInst := m.transformSingleInstance(instJSON, instance)
100+
transformedInstParsed := gjson.Parse(transformedInst)
101+
result, _ = sjson.SetRaw(result, instPath, transformedInstParsed.Raw)
102+
}
103+
return true
104+
})
105+
106+
return true
107+
})
108+
109+
return result, nil
110+
}
111+
112+
func (m *V4ToV5Migrator) transformSingleInstance(result string, instance gjson.Result) string {
113+
attrs := instance.Get("attributes")
114+
115+
// Remove deprecated field: min_days_for_renewal
116+
result = state.RemoveFields(result, "attributes", attrs, "min_days_for_renewal")
117+
118+
// Convert client_secret_version from int to float64
119+
clientSecretVersion := instance.Get("attributes.client_secret_version")
120+
if clientSecretVersion.Exists() && clientSecretVersion.Type == gjson.Number {
121+
result, _ = sjson.Set(result, "attributes.client_secret_version", clientSecretVersion.Float())
122+
}
123+
124+
return result
125+
}

0 commit comments

Comments
 (0)