@@ -71,6 +71,7 @@ This tool provides automated transformations for:
7171
7272var validVersionPath = map [string ]struct {}{
7373 "v4-v5" : {},
74+ "v5-v5" : {}, // Allow same-version "migrations" (bypass mode - generates moved blocks only)
7475}
7576
7677func main () {
@@ -198,6 +199,14 @@ func runMigration(log hclog.Logger, cfg config) error {
198199 return err
199200 }
200201
202+ // Check for same-version migration (bypass mode)
203+ if cfg .sourceVersion == cfg .targetVersion {
204+ fmt .Printf ("\n ⚠ Same-version migration detected (%s → %s)\n " , cfg .sourceVersion , cfg .targetVersion )
205+ fmt .Println ("Running in bypass mode: Config will be processed but transformations will be minimal" )
206+ fmt .Println ("This triggers provider StateUpgraders via moved blocks for testing purposes" )
207+ fmt .Println ()
208+ }
209+
201210 // Load state file first if present (needed for cross-referencing in config transformations)
202211 var stateJSON string
203212 if cfg .stateFile != "" {
@@ -337,7 +346,6 @@ func processConfigFiles(log hclog.Logger, p *pipeline.Pipeline, cfg config, stat
337346 return parsedConfigs , nil
338347}
339348
340- // applyGlobalPostprocessing applies cross-file reference updates for resource and attribute renames
341349func applyGlobalPostprocessing (log hclog.Logger , cfg config , outputPaths []string ) error {
342350 // Collect resource renames and attribute renames from all migrators
343351 providers := getProviders (cfg .resourcesToMigrate ... )
@@ -406,28 +414,25 @@ func applyGlobalPostprocessing(log hclog.Logger, cfg config, outputPaths []strin
406414 contentStr := string (content )
407415 modified := false
408416
409- // Apply all resource type renames
417+ // Apply all resource type renames, but skip content within moved blocks
410418 for oldType , newType := range renames {
411- newContent := strings . ReplaceAll (contentStr , oldType + "." , newType + "." )
419+ newContent := replaceSkippingMovedBlocks (contentStr , oldType + "." , newType + "." )
412420 if newContent != contentStr {
413421 modified = true
414422 contentStr = newContent
415423 log .Debug ("Updated references" , "file" , filepath .Base (outputPath ), "old" , oldType , "new" , newType )
416424 }
417425 }
418426
419- // Apply all attribute renames
427+ // Apply all attribute renames, but skip content within moved blocks
420428 // Pattern: data.cloudflare_zones.<instance_name>.zones → data.cloudflare_zones.<instance_name>.result
421429 // We need to match: <ResourceType>.<instance_name>.<OldAttribute>
422430 for _ , rename := range attributeRenames {
423431 // Build regex pattern: data\.cloudflare_zones\.([a-zA-Z0-9_-]+)\.zones
424432 // The instance name can contain letters, numbers, underscores, and hyphens
425- pattern := regexp .QuoteMeta (rename .ResourceType ) + `\.([a-zA-Z0-9_-]+)\.` + regexp .QuoteMeta (rename .OldAttribute )
426- re := regexp .MustCompile (pattern )
427-
428- // Replace with: data.cloudflare_zones.$1.result (preserving instance name)
433+ pattern := rename .ResourceType + `\.([a-zA-Z0-9_-]+)\.` + rename .OldAttribute
429434 replacement := rename .ResourceType + ".$1." + rename .NewAttribute
430- newContent := re . ReplaceAllString (contentStr , replacement )
435+ newContent := regexReplaceSkippingMovedBlocks (contentStr , pattern , replacement )
431436
432437 if newContent != contentStr {
433438 modified = true
@@ -452,6 +457,79 @@ func applyGlobalPostprocessing(log hclog.Logger, cfg config, outputPaths []strin
452457 return nil
453458}
454459
460+ // replaceSkippingMovedBlocks replaces old with new in content, but skips any content within moved blocks
461+ func replaceSkippingMovedBlocks (content , old , new string ) string {
462+ // Regex to match moved blocks: moved { ... }
463+ // This matches multiline moved blocks with any content inside
464+ movedBlockPattern := regexp .MustCompile (`(?s)moved\s*\{[^}]*\}` )
465+
466+ // Find all moved block positions
467+ matches := movedBlockPattern .FindAllStringIndex (content , - 1 )
468+ if len (matches ) == 0 {
469+ // No moved blocks, do simple replacement
470+ return strings .ReplaceAll (content , old , new )
471+ }
472+
473+ // Build result by processing content in segments
474+ var result strings.Builder
475+ lastEnd := 0
476+
477+ for _ , match := range matches {
478+ start , end := match [0 ], match [1 ]
479+
480+ // Process content before this moved block
481+ before := content [lastEnd :start ]
482+ result .WriteString (strings .ReplaceAll (before , old , new ))
483+
484+ // Copy the moved block as-is
485+ result .WriteString (content [start :end ])
486+
487+ lastEnd = end
488+ }
489+
490+ // Process remaining content after last moved block
491+ result .WriteString (strings .ReplaceAll (content [lastEnd :], old , new ))
492+
493+ return result .String ()
494+ }
495+
496+ // regexReplaceSkippingMovedBlocks replaces regex matches in content, but skips any content within moved blocks
497+ func regexReplaceSkippingMovedBlocks (content , pattern , replacement string ) string {
498+ // Regex to match moved blocks
499+ movedBlockPattern := regexp .MustCompile (`(?s)moved\s*\{[^}]*\}` )
500+
501+ // Find all moved block positions
502+ matches := movedBlockPattern .FindAllStringIndex (content , - 1 )
503+ if len (matches ) == 0 {
504+ // No moved blocks, do simple replacement
505+ re := regexp .MustCompile (pattern )
506+ return re .ReplaceAllString (content , replacement )
507+ }
508+
509+ // Build result by processing content in segments
510+ var result strings.Builder
511+ lastEnd := 0
512+ re := regexp .MustCompile (pattern )
513+
514+ for _ , match := range matches {
515+ start , end := match [0 ], match [1 ]
516+
517+ // Process content before this moved block
518+ before := content [lastEnd :start ]
519+ result .WriteString (re .ReplaceAllString (before , replacement ))
520+
521+ // Copy the moved block as-is
522+ result .WriteString (content [start :end ])
523+
524+ lastEnd = end
525+ }
526+
527+ // Process remaining content after last moved block
528+ result .WriteString (re .ReplaceAllString (content [lastEnd :], replacement ))
529+
530+ return result .String ()
531+ }
532+
455533func processStateFile (log hclog.Logger , p * pipeline.Pipeline , cfg config , apiClient * cloudflare.Client , parsedConfigs map [string ]* hclwrite.File ) error {
456534 if p == nil {
457535 return fmt .Errorf ("state pipeline is nil" )
0 commit comments