Skip to content
Open
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

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions internal/hcl/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,4 +257,18 @@ func TokensForSimpleValue(val interface{}) hclwrite.Tokens {
default:
return nil
}
}

// BuildArrayFromObjects creates array tokens from multiple object tokens
// Useful for converting multiple blocks to an array attribute
func BuildArrayFromObjects(objects []hclwrite.Tokens) hclwrite.Tokens {
if len(objects) == 0 {
return TokensForEmptyArray()
}
return hclwrite.TokensForTuple(objects)
}

// TokensForEmptyArray creates tokens for an empty array []
func TokensForEmptyArray() hclwrite.Tokens {
return hclwrite.TokensForTuple([]hclwrite.Tokens{})
}
4 changes: 3 additions & 1 deletion internal/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import (
"github.com/cloudflare/tf-migrate/internal/resources/api_token"
"github.com/cloudflare/tf-migrate/internal/resources/dns_record"
"github.com/cloudflare/tf-migrate/internal/resources/logpull_retention"
"github.com/cloudflare/tf-migrate/internal/resources/managed_transforms"
"github.com/cloudflare/tf-migrate/internal/resources/notification_policy_webhooks"
"github.com/cloudflare/tf-migrate/internal/resources/r2_bucket"
"github.com/cloudflare/tf-migrate/internal/resources/workers_kv"
"github.com/cloudflare/tf-migrate/internal/resources/workers_kv_namespace"
"github.com/cloudflare/tf-migrate/internal/resources/zero_trust_access_service_token"
"github.com/cloudflare/tf-migrate/internal/resources/zero_trust_device_posture_rule"
"github.com/cloudflare/tf-migrate/internal/resources/zero_trust_dlp_custom_profile"
"github.com/cloudflare/tf-migrate/internal/resources/zero_trust_gateway_policy"
"github.com/cloudflare/tf-migrate/internal/resources/zero_trust_device_posture_rule"
"github.com/cloudflare/tf-migrate/internal/resources/zero_trust_list"
"github.com/cloudflare/tf-migrate/internal/resources/zero_trust_tunnel_cloudflared"
"github.com/cloudflare/tf-migrate/internal/resources/zero_trust_tunnel_cloudflared_route"
Expand All @@ -28,6 +29,7 @@ func RegisterAllMigrations() {
dns_record.NewV4ToV5Migrator()
zone_dnssec.NewV4ToV5Migrator()
logpull_retention.NewV4ToV5Migrator()
managed_transforms.NewV4ToV5Migrator()
notification_policy_webhooks.NewV4ToV5Migrator()
r2_bucket.NewV4ToV5Migrator()
workers_kv.NewV4ToV5Migrator()
Expand Down
104 changes: 104 additions & 0 deletions internal/resources/managed_transforms/v4_to_v5.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package managed_transforms

import (
"github.com/hashicorp/hcl/v2/hclwrite"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"

"github.com/cloudflare/tf-migrate/internal"
"github.com/cloudflare/tf-migrate/internal/transform"
tfhcl "github.com/cloudflare/tf-migrate/internal/transform/hcl"
)

type V4ToV5Migrator struct {
}

func NewV4ToV5Migrator() transform.ResourceTransformer {
migrator := &V4ToV5Migrator{}
// Register the OLD (v4) resource name
internal.RegisterMigrator("cloudflare_managed_headers", "v4", "v5", migrator)
return migrator
}

func (m *V4ToV5Migrator) GetResourceType() string {
// Return the NEW (v5) resource name
return "cloudflare_managed_transforms"
}

func (m *V4ToV5Migrator) CanHandle(resourceType string) bool {
// Check for the OLD (v4) resource name
return resourceType == "cloudflare_managed_headers"
}

func (m *V4ToV5Migrator) Preprocess(content string) string {
// No preprocessing needed - block to attribute conversion handled in TransformConfig
return content
}

func (m *V4ToV5Migrator) TransformConfig(ctx *transform.Context, block *hclwrite.Block) (*transform.TransformResult, error) {
// Rename resource type
tfhcl.RenameResourceType(block, "cloudflare_managed_headers", "cloudflare_managed_transforms")

body := block.Body()

// Convert managed_request_headers blocks to array attribute
// Set empty array if no blocks found (since v5 requires this field)
tfhcl.ConvertBlocksToArrayAttribute(body, "managed_request_headers", true)

// Convert managed_response_headers blocks to array attribute
// Set empty array if no blocks found (since v5 requires this field)
tfhcl.ConvertBlocksToArrayAttribute(body, "managed_response_headers", true)

return &transform.TransformResult{
Blocks: []*hclwrite.Block{block},
RemoveOriginal: false,
}, nil
}

func (m *V4ToV5Migrator) TransformState(ctx *transform.Context, stateJSON gjson.Result, resourcePath string, resourceName string) (string, error) {
result := stateJSON.String()

// Get the attributes
attrs := stateJSON.Get("attributes")
if !attrs.Exists() {
return result, nil
}

// Handle managed_request_headers - ensure it's an array (even if empty)
// v5 requires this field, v4 allowed null
if !attrs.Get("managed_request_headers").Exists() || attrs.Get("managed_request_headers").Raw == "null" {
result, _ = sjson.Set(result, "attributes.managed_request_headers", []interface{}{})
}

// Handle managed_response_headers - ensure it's an array (even if empty)
// v5 requires this field, v4 allowed null
if !attrs.Get("managed_response_headers").Exists() || attrs.Get("managed_response_headers").Raw == "null" {
result, _ = sjson.Set(result, "attributes.managed_response_headers", []interface{}{})
}

// Set schema_version to 0 for v5
result, _ = sjson.Set(result, "schema_version", 0)

return result, nil
}

// convertBlocksToArrayAttribute converts multiple blocks of a given type to an array attribute
// This handles the v4 block syntax -> v5 attribute syntax transformation
//
// v4 block syntax:
// managed_request_headers {
// id = "header_id"
// enabled = true
// }
//
// v5 attribute syntax:
// managed_request_headers = [
// {
// id = "header_id"
// enabled = true
// }
// ]
func convertBlocksToArrayAttribute(body *hclwrite.Body, blockType string) {
// TODO: Implement in Subtask 4
// This is a placeholder for now - will be implemented when we do config transformations
}
Loading
Loading