From 48f37d1a69389e32ef103a5690f3602fbb227103 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Tue, 26 Aug 2025 12:53:45 +0200 Subject: [PATCH 1/6] track unused errors --- internal/convert/convert_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/internal/convert/convert_test.go b/internal/convert/convert_test.go index da2b1cc..63c47a7 100644 --- a/internal/convert/convert_test.go +++ b/internal/convert/convert_test.go @@ -27,6 +27,11 @@ func runConvertTests(t *testing.T, cmdName string, convert func(testName string, require.NoError(t, err) err = json.Unmarshal(errContent, &errMap) require.NoError(t, err) + unusedErrors := make(map[string]struct{}) + for name := range errMap { + unusedErrors[name] = struct{}{} + } + g := goldie.New(t, goldie.WithFixtureDir(root), goldie.WithNameSuffix(outSuffix)) @@ -46,7 +51,9 @@ func runConvertTests(t *testing.T, cmdName string, convert func(testName string, errMsg, found := errMap[testName] assert.True(t, found, "error not found in file %s for test %s, errMsg: %v", errFilename, testName, err) assert.Contains(t, err.Error(), errMsg) + delete(unusedErrors, testName) } }) } + assert.Empty(t, unusedErrors, "some errors are not being used") } From db91f274e07141d546d73d5a9978ca022f6db62a Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Tue, 26 Aug 2025 15:15:18 +0200 Subject: [PATCH 2/6] implement error handling --- internal/convert/adv2v2.go | 9 +++++ internal/convert/clu2adv.go | 6 ++++ internal/convert/shared.go | 5 +++ ...gions_config_invalid_multiple_blocks.in.tf | 29 +++++++++++++++ ...cation_specs_invalid_multiple_blocks.in.tf | 34 ++++++++++++++++++ ...specs_invalid_multiple_config_blocks.in.tf | 32 +++++++++++++++++ internal/convert/testdata/adv2v2/errors.json | 5 ++- ...gions_config_invalid_multiple_blocks.in.tf | 23 ++++++++++++ ...cation_specs_invalid_multiple_blocks.in.tf | 35 +++++++++++++++++++ ...specs_invalid_multiple_config_blocks.in.tf | 31 ++++++++++++++++ internal/convert/testdata/clu2adv/errors.json | 5 ++- 11 files changed, 212 insertions(+), 2 deletions(-) create mode 100644 internal/convert/testdata/adv2v2/dynamic_regions_config_invalid_multiple_blocks.in.tf create mode 100644 internal/convert/testdata/adv2v2/dynamic_replication_specs_invalid_multiple_blocks.in.tf create mode 100644 internal/convert/testdata/adv2v2/dynamic_replication_specs_invalid_multiple_config_blocks.in.tf create mode 100644 internal/convert/testdata/clu2adv/dynamic_regions_config_invalid_multiple_blocks.in.tf create mode 100644 internal/convert/testdata/clu2adv/dynamic_replication_specs_invalid_multiple_blocks.in.tf create mode 100644 internal/convert/testdata/clu2adv/dynamic_replication_specs_invalid_multiple_config_blocks.in.tf diff --git a/internal/convert/adv2v2.go b/internal/convert/adv2v2.go index 2e3e3c3..24e0836 100644 --- a/internal/convert/adv2v2.go +++ b/internal/convert/adv2v2.go @@ -85,6 +85,9 @@ func convertRepSpecs(resourceb *hclwrite.Body, diskSizeGB hclwrite.Tokens) error return err } if dConfig.IsPresent() { + if len(collectBlocks(blockb, nConfig)) > 0 { + return errDynamicBockAlone + } transformReferences(dConfig.content.Body(), getResourceName(dConfig.block), nRegion) copyAttributesSorted(dConfig.content.Body(), dConfig.content.Body().Attributes()) processAllSpecs(dConfig.content.Body(), diskSizeGB) @@ -129,6 +132,9 @@ func convertRepSpecsWithDynamicBlock(resourceb *hclwrite.Body, diskSizeGB hclwri if err != nil || !dSpec.IsPresent() { return dynamicBlock{}, err } + if len(collectBlocks(resourceb, nRepSpecs)) > 0 { + return dynamicBlock{}, errDynamicBockAlone + } transformReferences(dSpec.content.Body(), nRepSpecs, nSpec) dConfig, err := convertConfigsWithDynamicBlock(dSpec.content.Body(), diskSizeGB) if err != nil { @@ -144,6 +150,9 @@ func convertConfigsWithDynamicBlock(specbSrc *hclwrite.Body, diskSizeGB hclwrite if err != nil { return dynamicBlock{}, err } + if len(collectBlocks(specbSrc, nConfig)) > 0 { + return dynamicBlock{}, errDynamicBockAlone + } configBody := d.content.Body() transformReferences(configBody, getResourceName(d.block), nRegion) regionConfigBody := hclwrite.NewEmptyFile().Body() diff --git a/internal/convert/clu2adv.go b/internal/convert/clu2adv.go index 7fae2a3..b3954d3 100644 --- a/internal/convert/clu2adv.go +++ b/internal/convert/clu2adv.go @@ -225,6 +225,9 @@ func fillRepSpecsWithDynamicBlock(resourceb *hclwrite.Body, root attrVals) (dyna if err != nil || !dSpec.IsPresent() { return dynamicBlock{}, err } + if len(collectBlocks(resourceb, nRepSpecs)) > 0 { + return dynamicBlock{}, errDynamicBockAlone + } transformReferences(dSpec.content.Body(), nRepSpecs, nSpec) dConfig, err := fillConfigsWithDynamicRegion(dSpec.content.Body(), root, true) if err != nil { @@ -243,6 +246,9 @@ func fillConfigsWithDynamicRegion(specbSrc *hclwrite.Body, root attrVals, change if err != nil || !d.IsPresent() { return dynamicBlock{}, err } + if len(collectBlocks(specbSrc, nConfigSrc)) > 0 { + return dynamicBlock{}, errDynamicBockAlone + } repSpecb := hclwrite.NewEmptyFile().Body() if zoneName := hcl.GetAttrExpr(specbSrc.GetAttribute(nZoneName)); zoneName != "" { repSpecb.SetAttributeRaw(nZoneName, hcl.TokensFromExpr(zoneName)) diff --git a/internal/convert/shared.go b/internal/convert/shared.go index 9d11e77..d296d86 100644 --- a/internal/convert/shared.go +++ b/internal/convert/shared.go @@ -1,6 +1,7 @@ package convert import ( + "errors" "fmt" "slices" "strings" @@ -9,6 +10,10 @@ import ( "github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/hcl" ) +var ( + errDynamicBockAlone = errors.New("dynamic block must be the only block, see docs for more information") +) + // hasVariableNumShards checks if any block has a variable (non-literal) num_shards attribute func hasVariableNumShards(blocks []*hclwrite.Block) bool { for _, block := range blocks { diff --git a/internal/convert/testdata/adv2v2/dynamic_regions_config_invalid_multiple_blocks.in.tf b/internal/convert/testdata/adv2v2/dynamic_regions_config_invalid_multiple_blocks.in.tf new file mode 100644 index 0000000..dfc77d4 --- /dev/null +++ b/internal/convert/testdata/adv2v2/dynamic_regions_config_invalid_multiple_blocks.in.tf @@ -0,0 +1,29 @@ +resource "mongodbatlas_advanced_cluster" "multiple_blocks" { + project_id = var.project_id + name = var.cluster_name + cluster_type = var.cluster_type + replication_specs { + num_shards = var.replication_specs.num_shards + dynamic "region_configs" { + for_each = var.replication_specs.region_configs + content { + priority = region_configs.value.priority + provider_name = region_configs.value.provider_name + region_name = region_configs.value.region_name + electable_specs { + instance_size = region_configs.value.instance_size + node_count = region_configs.value.electable_node_count + } + } + } + region_configs { # inline block is not allowed with dynamic blocks + priority = 0 + provider_name = "AWS" + region_name = "US_EAST_1" + read_only_specs { + instance_size = var.instance_size + node_count = 1 + } + } + } +} diff --git a/internal/convert/testdata/adv2v2/dynamic_replication_specs_invalid_multiple_blocks.in.tf b/internal/convert/testdata/adv2v2/dynamic_replication_specs_invalid_multiple_blocks.in.tf new file mode 100644 index 0000000..04f23fd --- /dev/null +++ b/internal/convert/testdata/adv2v2/dynamic_replication_specs_invalid_multiple_blocks.in.tf @@ -0,0 +1,34 @@ +resource "mongodbatlas_advanced_cluster" "multiple_blocks" { + project_id = var.project_id + name = var.cluster_name + cluster_type = var.cluster_type + dynamic "replication_specs" { + for_each = var.replication_specs + content { + num_shards = replication_specs.value.num_shards + dynamic "region_configs" { + for_each = replication_specs.value.region_configs + content { + priority = region_configs.value.priority + provider_name = region_configs.value.provider_name + region_name = region_configs.value.region_name + electable_specs { + instance_size = region_configs.value.instance_size + node_count = region_configs.value.electable_node_count + } + } + } + } + } + replication_specs { # inline block is not allowed with dynamic blocks + region_configs { + priority = 7 + provider_name = "AWS" + region_name = "EU_WEST_1" + electable_specs { + instance_size = "M10" + node_count = 3 + } + } + } +} diff --git a/internal/convert/testdata/adv2v2/dynamic_replication_specs_invalid_multiple_config_blocks.in.tf b/internal/convert/testdata/adv2v2/dynamic_replication_specs_invalid_multiple_config_blocks.in.tf new file mode 100644 index 0000000..3641ffe --- /dev/null +++ b/internal/convert/testdata/adv2v2/dynamic_replication_specs_invalid_multiple_config_blocks.in.tf @@ -0,0 +1,32 @@ +resource "mongodbatlas_advanced_cluster" "multiple_blocks" { + project_id = var.project_id + name = var.cluster_name + cluster_type = var.cluster_type + dynamic "replication_specs" { + for_each = var.replication_specs + content { + num_shards = replication_specs.value.num_shards + dynamic "region_configs" { + for_each = replication_specs.value.region_configs + content { + priority = region_configs.value.priority + provider_name = region_configs.value.provider_name + region_name = region_configs.value.region_name + electable_specs { + instance_size = region_configs.value.instance_size + node_count = region_configs.value.electable_node_count + } + } + } + region_configs { # inline block is not allowed with dynamic blocks + priority = 0 + provider_name = "AWS" + region_name = "US_EAST_1" + read_only_specs { + instance_size = var.instance_size + node_count = 1 + } + } + } + } +} diff --git a/internal/convert/testdata/adv2v2/errors.json b/internal/convert/testdata/adv2v2/errors.json index dae5a07..a744913 100644 --- a/internal/convert/testdata/adv2v2/errors.json +++ b/internal/convert/testdata/adv2v2/errors.json @@ -2,5 +2,8 @@ "configuration_file_error": "failed to parse Terraform config file", "replication_specs_missing_region_configs": "replication_specs must have at least one region_configs", "missing_replication_specs": "must have at least one replication_specs", - "dynamic_unsupported_tag": "dynamic blocks are not supported for advanced_configuration" + "dynamic_unsupported_tag": "dynamic blocks are not supported for advanced_configuration", + "dynamic_regions_config_invalid_multiple_blocks": "dynamic block must be the only block", + "dynamic_replication_specs_invalid_multiple_blocks": "dynamic block must be the only block", + "dynamic_replication_specs_invalid_multiple_config_blocks": "dynamic block must be the only block" } diff --git a/internal/convert/testdata/clu2adv/dynamic_regions_config_invalid_multiple_blocks.in.tf b/internal/convert/testdata/clu2adv/dynamic_regions_config_invalid_multiple_blocks.in.tf new file mode 100644 index 0000000..6e246c6 --- /dev/null +++ b/internal/convert/testdata/clu2adv/dynamic_regions_config_invalid_multiple_blocks.in.tf @@ -0,0 +1,23 @@ +resource "mongodbatlas_cluster" "multiple_blocks" { + project_id = var.project_id + name = "cluster" + cluster_type = "SHARDED" + provider_name = "AWS" + provider_instance_size_name = "M10" + replication_specs { + num_shards = var.replication_specs.num_shards + dynamic "regions_config" { + for_each = var.replication_specs.regions_config + content { + region_name = regions_config.value.region_name + electable_nodes = regions_config.value.electable_nodes + priority = regions_config.value.prio + read_only_nodes = regions_config.value.read_only_nodes + } + } + regions_config { # inline block is not allowed with dynamic blocks + region_name = "US_EAST_1" + read_only_nodes = 1 + } + } +} diff --git a/internal/convert/testdata/clu2adv/dynamic_replication_specs_invalid_multiple_blocks.in.tf b/internal/convert/testdata/clu2adv/dynamic_replication_specs_invalid_multiple_blocks.in.tf new file mode 100644 index 0000000..d01ab87 --- /dev/null +++ b/internal/convert/testdata/clu2adv/dynamic_replication_specs_invalid_multiple_blocks.in.tf @@ -0,0 +1,35 @@ +# Based on https://github.com/mongodb/terraform-provider-mongodbatlas/blob/master/examples/migrate_cluster_to_advanced_cluster/module_maintainer/v1/main.tf +resource "mongodbatlas_cluster" "this" { + project_id = var.project_id + name = var.cluster_name + auto_scaling_disk_gb_enabled = var.auto_scaling_disk_gb_enabled + cluster_type = var.cluster_type + disk_size_gb = var.disk_size + mongo_db_major_version = var.mongo_db_major_version + provider_instance_size_name = var.instance_size + provider_name = var.provider_name + dynamic "replication_specs" { + for_each = var.replication_specs + content { + num_shards = replication_specs.value.num_shards + zone_name = replication_specs.value.zone_name + dynamic "regions_config" { + for_each = replication_specs.value.regions_config + content { + electable_nodes = regions_config.value.electable_nodes + priority = regions_config.value.priority + read_only_nodes = regions_config.value.read_only_nodes + region_name = regions_config.value.region_name + } + } + } + } + replication_specs { # inline block is not allowed with dynamic blocks + num_shards = 1 + regions_config { + region_name = "US_EAST_1" + electable_nodes = 3 + priority = 7 + } + } +} diff --git a/internal/convert/testdata/clu2adv/dynamic_replication_specs_invalid_multiple_config_blocks.in.tf b/internal/convert/testdata/clu2adv/dynamic_replication_specs_invalid_multiple_config_blocks.in.tf new file mode 100644 index 0000000..6516d28 --- /dev/null +++ b/internal/convert/testdata/clu2adv/dynamic_replication_specs_invalid_multiple_config_blocks.in.tf @@ -0,0 +1,31 @@ +# Based on https://github.com/mongodb/terraform-provider-mongodbatlas/blob/master/examples/migrate_cluster_to_advanced_cluster/module_maintainer/v1/main.tf +resource "mongodbatlas_cluster" "this" { + project_id = var.project_id + name = var.cluster_name + auto_scaling_disk_gb_enabled = var.auto_scaling_disk_gb_enabled + cluster_type = var.cluster_type + disk_size_gb = var.disk_size + mongo_db_major_version = var.mongo_db_major_version + provider_instance_size_name = var.instance_size + provider_name = var.provider_name + dynamic "replication_specs" { + for_each = var.replication_specs + content { + num_shards = replication_specs.value.num_shards + zone_name = replication_specs.value.zone_name + dynamic "regions_config" { + for_each = replication_specs.value.regions_config + content { + electable_nodes = regions_config.value.electable_nodes + priority = regions_config.value.priority + read_only_nodes = regions_config.value.read_only_nodes + region_name = regions_config.value.region_name + } + } + regions_config { # inline block is not allowed with dynamic blocks + region_name = "US_EAST_1" + read_only_nodes = 1 + } + } + } +} diff --git a/internal/convert/testdata/clu2adv/errors.json b/internal/convert/testdata/clu2adv/errors.json index 266aa5e..fcee178 100644 --- a/internal/convert/testdata/clu2adv/errors.json +++ b/internal/convert/testdata/clu2adv/errors.json @@ -5,5 +5,8 @@ "regions_config_missing_priority": "setting replication_specs: attribute priority not found", "replication_specs_missing_num_shards": "num_shards not found", "replication_specs_missing_regions_config": "setting replication_specs: regions_config not found", - "dynamic_unsupported_tag": "dynamic blocks are not supported for advanced_configuration" + "dynamic_unsupported_tag": "dynamic blocks are not supported for advanced_configuration", + "dynamic_regions_config_invalid_multiple_blocks": "dynamic block must be the only block", + "dynamic_replication_specs_invalid_multiple_blocks": "dynamic block must be the only block", + "dynamic_replication_specs_invalid_multiple_config_blocks": "dynamic block must be the only block" } From bc83bc66cfaacc96d102b6c8105d451115b98d1c Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Tue, 26 Aug 2025 15:26:47 +0200 Subject: [PATCH 3/6] fix typo --- internal/convert/adv2v2.go | 6 +++--- internal/convert/clu2adv.go | 4 ++-- internal/convert/shared.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/convert/adv2v2.go b/internal/convert/adv2v2.go index 24e0836..9951c74 100644 --- a/internal/convert/adv2v2.go +++ b/internal/convert/adv2v2.go @@ -86,7 +86,7 @@ func convertRepSpecs(resourceb *hclwrite.Body, diskSizeGB hclwrite.Tokens) error } if dConfig.IsPresent() { if len(collectBlocks(blockb, nConfig)) > 0 { - return errDynamicBockAlone + return errDynamicBlockAlone } transformReferences(dConfig.content.Body(), getResourceName(dConfig.block), nRegion) copyAttributesSorted(dConfig.content.Body(), dConfig.content.Body().Attributes()) @@ -133,7 +133,7 @@ func convertRepSpecsWithDynamicBlock(resourceb *hclwrite.Body, diskSizeGB hclwri return dynamicBlock{}, err } if len(collectBlocks(resourceb, nRepSpecs)) > 0 { - return dynamicBlock{}, errDynamicBockAlone + return dynamicBlock{}, errDynamicBlockAlone } transformReferences(dSpec.content.Body(), nRepSpecs, nSpec) dConfig, err := convertConfigsWithDynamicBlock(dSpec.content.Body(), diskSizeGB) @@ -151,7 +151,7 @@ func convertConfigsWithDynamicBlock(specbSrc *hclwrite.Body, diskSizeGB hclwrite return dynamicBlock{}, err } if len(collectBlocks(specbSrc, nConfig)) > 0 { - return dynamicBlock{}, errDynamicBockAlone + return dynamicBlock{}, errDynamicBlockAlone } configBody := d.content.Body() transformReferences(configBody, getResourceName(d.block), nRegion) diff --git a/internal/convert/clu2adv.go b/internal/convert/clu2adv.go index b3954d3..31910f9 100644 --- a/internal/convert/clu2adv.go +++ b/internal/convert/clu2adv.go @@ -226,7 +226,7 @@ func fillRepSpecsWithDynamicBlock(resourceb *hclwrite.Body, root attrVals) (dyna return dynamicBlock{}, err } if len(collectBlocks(resourceb, nRepSpecs)) > 0 { - return dynamicBlock{}, errDynamicBockAlone + return dynamicBlock{}, errDynamicBlockAlone } transformReferences(dSpec.content.Body(), nRepSpecs, nSpec) dConfig, err := fillConfigsWithDynamicRegion(dSpec.content.Body(), root, true) @@ -247,7 +247,7 @@ func fillConfigsWithDynamicRegion(specbSrc *hclwrite.Body, root attrVals, change return dynamicBlock{}, err } if len(collectBlocks(specbSrc, nConfigSrc)) > 0 { - return dynamicBlock{}, errDynamicBockAlone + return dynamicBlock{}, errDynamicBlockAlone } repSpecb := hclwrite.NewEmptyFile().Body() if zoneName := hcl.GetAttrExpr(specbSrc.GetAttribute(nZoneName)); zoneName != "" { diff --git a/internal/convert/shared.go b/internal/convert/shared.go index d296d86..7d4dc16 100644 --- a/internal/convert/shared.go +++ b/internal/convert/shared.go @@ -11,7 +11,7 @@ import ( ) var ( - errDynamicBockAlone = errors.New("dynamic block must be the only block, see docs for more information") + errDynamicBlockAlone = errors.New("dynamic block must be the only block, see docs for more information") ) // hasVariableNumShards checks if any block has a variable (non-literal) num_shards attribute From 0b6603385093eb012a4aae922aed1d8c1fb386df Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Wed, 27 Aug 2025 09:01:02 +0200 Subject: [PATCH 4/6] refactor convertConfigsWithDynamicBlock --- internal/convert/adv2v2.go | 41 ++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/internal/convert/adv2v2.go b/internal/convert/adv2v2.go index 9951c74..0c34aeb 100644 --- a/internal/convert/adv2v2.go +++ b/internal/convert/adv2v2.go @@ -80,21 +80,14 @@ func convertRepSpecs(resourceb *hclwrite.Body, diskSizeGB hclwrite.Tokens) error blockb := block.Body() shardsAttr := blockb.GetAttribute(nNumShards) blockb.RemoveAttribute(nNumShards) - dConfig, err := getDynamicBlock(blockb, nConfig) + dConfig, err := convertConfigsWithDynamicBlock(blockb, diskSizeGB, false) if err != nil { return err } if dConfig.IsPresent() { - if len(collectBlocks(blockb, nConfig)) > 0 { - return errDynamicBlockAlone - } - transformReferences(dConfig.content.Body(), getResourceName(dConfig.block), nRegion) - copyAttributesSorted(dConfig.content.Body(), dConfig.content.Body().Attributes()) - processAllSpecs(dConfig.content.Body(), diskSizeGB) - tokens := hcl.TokensFromExpr(buildForExpr(nRegion, hcl.GetAttrExpr(dConfig.forEach), false)) - tokens = append(tokens, hcl.TokensObject(dConfig.content.Body())...) - blockb.SetAttributeRaw(nConfig, hcl.EncloseBracketsNewLines(tokens)) + // Process the dynamic config and set it on this block blockb.RemoveBlock(dConfig.block) + blockb.SetAttributeRaw(nConfig, dConfig.tokens) } else { var configs []*hclwrite.Body for _, configBlock := range collectBlocks(blockb, nConfig) { @@ -136,7 +129,7 @@ func convertRepSpecsWithDynamicBlock(resourceb *hclwrite.Body, diskSizeGB hclwri return dynamicBlock{}, errDynamicBlockAlone } transformReferences(dSpec.content.Body(), nRepSpecs, nSpec) - dConfig, err := convertConfigsWithDynamicBlock(dSpec.content.Body(), diskSizeGB) + dConfig, err := convertConfigsWithDynamicBlock(dSpec.content.Body(), diskSizeGB, true) if err != nil { return dynamicBlock{}, err } @@ -145,9 +138,11 @@ func convertRepSpecsWithDynamicBlock(resourceb *hclwrite.Body, diskSizeGB hclwri return dSpec, nil } -func convertConfigsWithDynamicBlock(specbSrc *hclwrite.Body, diskSizeGB hclwrite.Tokens) (dynamicBlock, error) { +// convertConfigsWithDynamicBlock is used for processing dynamic blocks in region_configs +func convertConfigsWithDynamicBlock(specbSrc *hclwrite.Body, diskSizeGB hclwrite.Tokens, + insideDynamicRepSpec bool) (dynamicBlock, error) { d, err := getDynamicBlock(specbSrc, nConfig) - if err != nil { + if err != nil || !d.IsPresent() { return dynamicBlock{}, err } if len(collectBlocks(specbSrc, nConfig)) > 0 { @@ -167,17 +162,25 @@ func convertConfigsWithDynamicBlock(specbSrc *hclwrite.Body, diskSizeGB hclwrite } regionConfigBody.SetAttributeRaw(blockType, hcl.TokensObject(blockBody)) } + forEach := hcl.GetAttrExpr(d.forEach) + if insideDynamicRepSpec { + forEach = fmt.Sprintf("%s.%s", nSpec, nConfig) + } + regionTokens := hcl.TokensFromExpr(buildForExpr(nRegion, forEach, false)) + regionTokens = append(regionTokens, hcl.TokensObject(regionConfigBody)...) + if !insideDynamicRepSpec { + d.tokens = hcl.EncloseBracketsNewLines(regionTokens) + return d, nil + } repSpecb := hclwrite.NewEmptyFile().Body() if zoneNameAttr := specbSrc.GetAttribute(nZoneName); zoneNameAttr != nil { - repSpecb.SetAttributeRaw(nZoneName, hcl.TokensFromExpr( - transformReference(hcl.GetAttrExpr(zoneNameAttr), nRepSpecs, nSpec))) + zoneNameExpr := transformReference(hcl.GetAttrExpr(zoneNameAttr), nRepSpecs, nSpec) + repSpecb.SetAttributeRaw(nZoneName, hcl.TokensFromExpr(zoneNameExpr)) } - regionTokens := hcl.TokensFromExpr(buildForExpr(nRegion, fmt.Sprintf("%s.%s", nSpec, nConfig), false)) - regionTokens = append(regionTokens, hcl.TokensObject(regionConfigBody)...) repSpecb.SetAttributeRaw(nConfig, hcl.EncloseBracketsNewLines(regionTokens)) if numShardsAttr := specbSrc.GetAttribute(nNumShards); numShardsAttr != nil { - tokens := hcl.TokensFromExpr(buildForExpr("i", - fmt.Sprintf("range(%s)", transformReference(hcl.GetAttrExpr(numShardsAttr), nRepSpecs, nSpec)), false)) + numShardsExpr := transformReference(hcl.GetAttrExpr(numShardsAttr), nRepSpecs, nSpec) + tokens := hcl.TokensFromExpr(buildForExpr("i", fmt.Sprintf("range(%s)", numShardsExpr), false)) tokens = append(tokens, hcl.TokensObject(repSpecb)...) return dynamicBlock{tokens: hcl.EncloseBracketsNewLines(tokens)}, nil } From c4d2c7d59edfbd09d2759803a58dc07add7ee320 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Wed, 27 Aug 2025 09:20:06 +0200 Subject: [PATCH 5/6] check only one block in getDynamicBlock --- internal/convert/adv2v2.go | 10 ++-------- internal/convert/clu2adv.go | 12 +++--------- internal/convert/shared.go | 18 ++++++++++++++---- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/internal/convert/adv2v2.go b/internal/convert/adv2v2.go index 0c34aeb..27e6698 100644 --- a/internal/convert/adv2v2.go +++ b/internal/convert/adv2v2.go @@ -121,13 +121,10 @@ func convertRepSpecs(resourceb *hclwrite.Body, diskSizeGB hclwrite.Tokens) error } func convertRepSpecsWithDynamicBlock(resourceb *hclwrite.Body, diskSizeGB hclwrite.Tokens) (dynamicBlock, error) { - dSpec, err := getDynamicBlock(resourceb, nRepSpecs) + dSpec, err := getDynamicBlock(resourceb, nRepSpecs, true) if err != nil || !dSpec.IsPresent() { return dynamicBlock{}, err } - if len(collectBlocks(resourceb, nRepSpecs)) > 0 { - return dynamicBlock{}, errDynamicBlockAlone - } transformReferences(dSpec.content.Body(), nRepSpecs, nSpec) dConfig, err := convertConfigsWithDynamicBlock(dSpec.content.Body(), diskSizeGB, true) if err != nil { @@ -141,13 +138,10 @@ func convertRepSpecsWithDynamicBlock(resourceb *hclwrite.Body, diskSizeGB hclwri // convertConfigsWithDynamicBlock is used for processing dynamic blocks in region_configs func convertConfigsWithDynamicBlock(specbSrc *hclwrite.Body, diskSizeGB hclwrite.Tokens, insideDynamicRepSpec bool) (dynamicBlock, error) { - d, err := getDynamicBlock(specbSrc, nConfig) + d, err := getDynamicBlock(specbSrc, nConfig, true) if err != nil || !d.IsPresent() { return dynamicBlock{}, err } - if len(collectBlocks(specbSrc, nConfig)) > 0 { - return dynamicBlock{}, errDynamicBlockAlone - } configBody := d.content.Body() transformReferences(configBody, getResourceName(d.block), nRegion) regionConfigBody := hclwrite.NewEmptyFile().Body() diff --git a/internal/convert/clu2adv.go b/internal/convert/clu2adv.go index 31910f9..620c990 100644 --- a/internal/convert/clu2adv.go +++ b/internal/convert/clu2adv.go @@ -75,7 +75,7 @@ func convertResource(block *hclwrite.Block) (bool, error) { } func isFreeTierCluster(resourceb *hclwrite.Body) bool { - d, _ := getDynamicBlock(resourceb, nRepSpecs) + d, _ := getDynamicBlock(resourceb, nRepSpecs, true) return resourceb.FirstMatchingBlock(nRepSpecs, nil) == nil && !d.IsPresent() } @@ -221,13 +221,10 @@ func fillRepSpecs(resourceb *hclwrite.Body, root attrVals) error { // fillRepSpecsWithDynamicBlock used for dynamic blocks in replication_specs func fillRepSpecsWithDynamicBlock(resourceb *hclwrite.Body, root attrVals) (dynamicBlock, error) { - dSpec, err := getDynamicBlock(resourceb, nRepSpecs) + dSpec, err := getDynamicBlock(resourceb, nRepSpecs, true) if err != nil || !dSpec.IsPresent() { return dynamicBlock{}, err } - if len(collectBlocks(resourceb, nRepSpecs)) > 0 { - return dynamicBlock{}, errDynamicBlockAlone - } transformReferences(dSpec.content.Body(), nRepSpecs, nSpec) dConfig, err := fillConfigsWithDynamicRegion(dSpec.content.Body(), root, true) if err != nil { @@ -242,13 +239,10 @@ func fillRepSpecsWithDynamicBlock(resourceb *hclwrite.Body, root attrVals) (dyna // fillConfigsWithDynamicRegion is used for dynamic blocks in region_configs func fillConfigsWithDynamicRegion(specbSrc *hclwrite.Body, root attrVals, changeReferences bool) (dynamicBlock, error) { - d, err := getDynamicBlock(specbSrc, nConfigSrc) + d, err := getDynamicBlock(specbSrc, nConfigSrc, true) if err != nil || !d.IsPresent() { return dynamicBlock{}, err } - if len(collectBlocks(specbSrc, nConfigSrc)) > 0 { - return dynamicBlock{}, errDynamicBlockAlone - } repSpecb := hclwrite.NewEmptyFile().Body() if zoneName := hcl.GetAttrExpr(specbSrc.GetAttribute(nZoneName)); zoneName != "" { repSpecb.SetAttributeRaw(nZoneName, hcl.TokensFromExpr(zoneName)) diff --git a/internal/convert/shared.go b/internal/convert/shared.go index 7d4dc16..ffdff9f 100644 --- a/internal/convert/shared.go +++ b/internal/convert/shared.go @@ -56,8 +56,13 @@ func (d dynamicBlock) IsPresent() bool { } // getDynamicBlock finds and returns a dynamic block with the given name from the body -func getDynamicBlock(body *hclwrite.Body, name string) (dynamicBlock, error) { +func getDynamicBlock(body *hclwrite.Body, name string, checkAlone bool) (dynamicBlock, error) { + var db dynamicBlock + staticBlockCount := 0 for _, block := range body.Blocks() { + if block.Type() == name { + staticBlockCount++ + } if block.Type() != nDynamic || name != getResourceName(block) { continue } @@ -70,9 +75,14 @@ func getDynamicBlock(body *hclwrite.Body, name string) (dynamicBlock, error) { if content == nil { return dynamicBlock{}, fmt.Errorf("dynamic block %s: block %s not found", name, nContent) } - return dynamicBlock{forEach: forEach, block: block, content: content}, nil + if !db.IsPresent() { + db = dynamicBlock{forEach: forEach, block: block, content: content} + } + } + if checkAlone && db.IsPresent() && staticBlockCount > 0 { + return dynamicBlock{}, errDynamicBlockAlone } - return dynamicBlock{}, nil + return db, nil } func checkDynamicBlock(body *hclwrite.Body) error { @@ -185,7 +195,7 @@ func fillTagsLabelsOpt(resourceb *hclwrite.Body, name string) error { } func extractTagsLabelsDynamicBlock(resourceb *hclwrite.Body, name string) (hclwrite.Tokens, error) { - d, err := getDynamicBlock(resourceb, name) + d, err := getDynamicBlock(resourceb, name, false) if err != nil || !d.IsPresent() { return nil, err } From 5d504eb274b4c00977ceaaacc3016766fafbe70a Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Wed, 27 Aug 2025 09:22:36 +0200 Subject: [PATCH 6/6] remove unneeded comments --- internal/convert/adv2v2.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/convert/adv2v2.go b/internal/convert/adv2v2.go index 27e6698..734da45 100644 --- a/internal/convert/adv2v2.go +++ b/internal/convert/adv2v2.go @@ -85,7 +85,6 @@ func convertRepSpecs(resourceb *hclwrite.Body, diskSizeGB hclwrite.Tokens) error return err } if dConfig.IsPresent() { - // Process the dynamic config and set it on this block blockb.RemoveBlock(dConfig.block) blockb.SetAttributeRaw(nConfig, dConfig.tokens) } else { @@ -135,7 +134,6 @@ func convertRepSpecsWithDynamicBlock(resourceb *hclwrite.Body, diskSizeGB hclwri return dSpec, nil } -// convertConfigsWithDynamicBlock is used for processing dynamic blocks in region_configs func convertConfigsWithDynamicBlock(specbSrc *hclwrite.Body, diskSizeGB hclwrite.Tokens, insideDynamicRepSpec bool) (dynamicBlock, error) { d, err := getDynamicBlock(specbSrc, nConfig, true)