Skip to content

Commit f23bb1d

Browse files
authored
feat: Support region_configs without dynamic block inside a replication_specs with dynamic blocks (#82)
1 parent 0d16e2b commit f23bb1d

10 files changed

+532
-118
lines changed

docs/command_adv2v2.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ atlas tf adv2v2 -f in.tf -o out.tf
2626
- `--replaceOutput` or `-r`: Overwrite the file at the output path if it already exists. You can also modify the input file in-place.
2727
- `--watch` or `-w`: Keep the plugin running and watching for changes in the input file
2828

29+
## Comments and formatting
30+
31+
During the conversion process, some formatting elements may not be preserved:
32+
- Some comments from the original resources may not be preserved in the output
33+
- Custom blank lines and spacing may be modified
34+
- The output file will have standardized formatting
35+
36+
We recommend reviewing the converted output and re-adding any important comments or documentation that you need to maintain.
37+
2938
## Examples
3039

3140
You can find [here](https://github.com/mongodb-labs/atlas-cli-plugin-terraform/tree/main/internal/convert/testdata/adv2v2) examples of input files (suffix .in.tf) and the corresponding output files (suffix .out.tf).
@@ -56,6 +65,7 @@ dynamic "tags" {
5665
### Dynamic blocks in region_configs
5766

5867
You can use `dynamic` blocks for `region_configs`. The plugin assumes that the value of `for_each` is an expression which evaluates to a `list` of objects.
68+
**Note:** `map` and `set` are not supported.
5969

6070
This is an example of how to use dynamic blocks in `region_configs`:
6171
```hcl
@@ -81,6 +91,7 @@ replication_specs {
8191
### Dynamic blocks in replication_specs
8292

8393
You can use `dynamic` blocks for `replication_specs`. The plugin assumes that the value of `for_each` is an expression which evaluates to a `list` of objects.
94+
**Note:** `map` and `set` are not supported.
8495

8596
This is an example of how to use dynamic blocks in `replication_specs`:
8697
```hcl

docs/command_clu2adv.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ atlas tf clu2adv -f in.tf -o out.tf
2626
- `--watch` or `-w`: Keep the plugin running and watching for changes in the input file
2727
- `--includeMoved` or `-m`: Include the `moved blocks` in the output file
2828

29+
## Comments and formatting
30+
31+
During the conversion process, some formatting elements may not be preserved:
32+
- Some comments from the original resources may not be preserved in the output
33+
- Custom blank lines and spacing may be modified
34+
- The output file will have standardized formatting
35+
36+
We recommend reviewing the converted output and re-adding any important comments or documentation that you need to maintain.
37+
2938
## Examples
3039

3140
You can find [here](https://github.com/mongodb-labs/atlas-cli-plugin-terraform/tree/main/internal/convert/testdata/clu2adv) some examples of input files (suffix .in.tf) and the corresponding output files (suffix .out.tf).
@@ -56,6 +65,7 @@ dynamic "tags" {
5665
### Dynamic blocks in regions_config
5766

5867
You can use `dynamic` blocks for `regions_config`. The plugin assumes that the value of `for_each` is an expression which evaluates to a `list` of objects.
68+
**Note:** `map` and `set` are not supported.
5969

6070
This is an example of how to use dynamic blocks in `regions_config`:
6171
```hcl
@@ -77,6 +87,7 @@ replication_specs {
7787
### Dynamic blocks in replication_specs
7888

7989
You can use `dynamic` blocks for `replication_specs`. The plugin assumes that the value of `for_each` is an expression which evaluates to a `list` of objects.
90+
**Note:** `map` and `set` are not supported.
8091

8192
This is an example of how to use dynamic blocks in `replication_specs`:
8293
```hcl

internal/convert/adv2v2.go

Lines changed: 64 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ import (
88
"github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/hcl"
99
)
1010

11+
var (
12+
specsWithDisk = []string{nElectableSpecs, nReadOnlySpecs, nAnalyticsSpecs}
13+
specsWithoutDisk = []string{nAutoScaling, nAnalyticsAutoScaling}
14+
)
15+
1116
// AdvancedClusterToV2 transforms all mongodbatlas_advanced_cluster resource definitions in a
1217
// Terraform configuration file from SDKv2 schema to TPF (Terraform Plugin Framework) schema.
1318
// All other resources and data sources are left untouched.
@@ -18,20 +23,18 @@ func AdvancedClusterToV2(config []byte) ([]byte, error) {
1823
}
1924
parserb := parser.Body()
2025
for _, block := range parserb.Blocks() {
21-
updated, err := updateResource(block)
26+
updated, err := processResource(block)
2227
if err != nil {
2328
return nil, err
2429
}
25-
if updated { // If the resource was converted, add a comment at the end so user knows the resource was updated
26-
blockb := block.Body()
27-
blockb.AppendNewline()
28-
hcl.AppendComment(blockb, commentUpdatedBy)
30+
if updated {
31+
addComments(block, true)
2932
}
3033
}
3134
return parser.Bytes(), nil
3235
}
3336

34-
func updateResource(resource *hclwrite.Block) (bool, error) {
37+
func processResource(resource *hclwrite.Block) (bool, error) {
3538
if resource.Type() != resourceType || getResourceName(resource) != advCluster {
3639
return false, nil
3740
}
@@ -43,24 +46,17 @@ func updateResource(resource *hclwrite.Block) (bool, error) {
4346
return false, nil
4447
}
4548
diskSizeGB, _ := hcl.PopAttr(resourceb, nDiskSizeGB, errRoot) // ok to fail as it's optional
46-
if err := convertRepSpecs(resourceb, diskSizeGB); err != nil {
47-
return false, err
48-
}
49-
if err := fillTagsLabelsOpt(resourceb, nTags); err != nil {
49+
if err := processRepSpecs(resourceb, diskSizeGB); err != nil {
5050
return false, err
5151
}
52-
if err := fillTagsLabelsOpt(resourceb, nLabels); err != nil {
52+
if err := processCommonOptionalBlocks(resourceb); err != nil {
5353
return false, err
5454
}
55-
fillAdvConfigOpt(resourceb)
56-
fillBlockOpt(resourceb, nBiConnector)
57-
fillBlockOpt(resourceb, nPinnedFCV)
58-
fillBlockOpt(resourceb, nTimeouts)
5955
return true, nil
6056
}
6157

62-
func convertRepSpecs(resourceb *hclwrite.Body, diskSizeGB hclwrite.Tokens) error {
63-
d, err := convertRepSpecsWithDynamicBlock(resourceb, diskSizeGB)
58+
func processRepSpecs(resourceb *hclwrite.Body, diskSizeGB hclwrite.Tokens) error {
59+
d, err := processRepSpecsWithDynamicBlock(resourceb, diskSizeGB)
6460
if err != nil {
6561
return err
6662
}
@@ -80,7 +76,7 @@ func convertRepSpecs(resourceb *hclwrite.Body, diskSizeGB hclwrite.Tokens) error
8076
blockb := block.Body()
8177
shardsAttr := blockb.GetAttribute(nNumShards)
8278
blockb.RemoveAttribute(nNumShards)
83-
dConfig, err := convertConfigsWithDynamicBlock(blockb, diskSizeGB, false)
79+
dConfig, err := processConfigsWithDynamicBlock(blockb, diskSizeGB, false)
8480
if err != nil {
8581
return err
8682
}
@@ -119,41 +115,50 @@ func convertRepSpecs(resourceb *hclwrite.Body, diskSizeGB hclwrite.Tokens) error
119115
return nil
120116
}
121117

122-
func convertRepSpecsWithDynamicBlock(resourceb *hclwrite.Body, diskSizeGB hclwrite.Tokens) (dynamicBlock, error) {
118+
func processRepSpecsWithDynamicBlock(resourceb *hclwrite.Body, diskSizeGB hclwrite.Tokens) (dynamicBlock, error) {
123119
dSpec, err := getDynamicBlock(resourceb, nRepSpecs, true)
124120
if err != nil || !dSpec.IsPresent() {
125121
return dynamicBlock{}, err
126122
}
127123
transformReferences(dSpec.content.Body(), nRepSpecs, nSpec)
128-
dConfig, err := convertConfigsWithDynamicBlock(dSpec.content.Body(), diskSizeGB, true)
124+
dConfig, err := processConfigsWithDynamicBlock(dSpec.content.Body(), diskSizeGB, true)
129125
if err != nil {
130126
return dynamicBlock{}, err
131127
}
128+
if dConfig.tokens != nil {
129+
forSpec := hcl.TokensFromExpr(buildForExpr(nSpec, hcl.GetAttrExpr(dSpec.forEach), true))
130+
dSpec.tokens = hcl.TokensFuncFlatten(append(forSpec, dConfig.tokens...))
131+
return dSpec, nil
132+
}
133+
134+
// Handle static region_configs blocks inside dynamic replication_specs
135+
specBody := dSpec.content.Body()
136+
staticConfigs := collectBlocks(specBody, nConfig)
137+
repSpecb := hclwrite.NewEmptyFile().Body()
138+
handleZoneName(repSpecb, specBody, nRepSpecs, nSpec)
139+
var configs []*hclwrite.Body
140+
for _, configBlock := range staticConfigs {
141+
configBlockb := configBlock.Body()
142+
newConfigBody := processConfigForDynamicBlock(configBlockb, diskSizeGB)
143+
configs = append(configs, newConfigBody)
144+
}
145+
repSpecb.SetAttributeRaw(nConfig, hcl.TokensArray(configs))
146+
numShardsAttr := specBody.GetAttribute(nNumShards)
132147
forSpec := hcl.TokensFromExpr(buildForExpr(nSpec, hcl.GetAttrExpr(dSpec.forEach), true))
133-
dSpec.tokens = hcl.TokensFuncFlatten(append(forSpec, dConfig.tokens...))
148+
numShardsTokens := buildNumShardsTokens(numShardsAttr, repSpecb, nRepSpecs, nSpec)
149+
dSpec.tokens = hcl.TokensFuncFlatten(append(forSpec, numShardsTokens...))
134150
return dSpec, nil
135151
}
136152

137-
func convertConfigsWithDynamicBlock(specbSrc *hclwrite.Body, diskSizeGB hclwrite.Tokens,
153+
func processConfigsWithDynamicBlock(specbSrc *hclwrite.Body, diskSizeGB hclwrite.Tokens,
138154
insideDynamicRepSpec bool) (dynamicBlock, error) {
139155
d, err := getDynamicBlock(specbSrc, nConfig, true)
140156
if err != nil || !d.IsPresent() {
141157
return dynamicBlock{}, err
142158
}
143159
configBody := d.content.Body()
144160
transformReferences(configBody, getResourceName(d.block), nRegion)
145-
regionConfigBody := hclwrite.NewEmptyFile().Body()
146-
copyAttributesSorted(regionConfigBody, configBody.Attributes())
147-
for _, block := range configBody.Blocks() {
148-
blockType := block.Type()
149-
blockBody := hclwrite.NewEmptyFile().Body()
150-
copyAttributesSorted(blockBody, block.Body().Attributes())
151-
if diskSizeGB != nil &&
152-
(blockType == nElectableSpecs || blockType == nReadOnlySpecs || blockType == nAnalyticsSpecs) {
153-
blockBody.SetAttributeRaw(nDiskSizeGB, diskSizeGB)
154-
}
155-
regionConfigBody.SetAttributeRaw(blockType, hcl.TokensObject(blockBody))
156-
}
161+
regionConfigBody := processConfigForDynamicBlock(configBody, diskSizeGB)
157162
forEach := hcl.GetAttrExpr(d.forEach)
158163
if insideDynamicRepSpec {
159164
forEach = fmt.Sprintf("%s.%s", nSpec, nConfig)
@@ -165,18 +170,11 @@ func convertConfigsWithDynamicBlock(specbSrc *hclwrite.Body, diskSizeGB hclwrite
165170
return d, nil
166171
}
167172
repSpecb := hclwrite.NewEmptyFile().Body()
168-
if zoneNameAttr := specbSrc.GetAttribute(nZoneName); zoneNameAttr != nil {
169-
zoneNameExpr := transformReference(hcl.GetAttrExpr(zoneNameAttr), nRepSpecs, nSpec)
170-
repSpecb.SetAttributeRaw(nZoneName, hcl.TokensFromExpr(zoneNameExpr))
171-
}
173+
handleZoneName(repSpecb, specbSrc, nRepSpecs, nSpec)
172174
repSpecb.SetAttributeRaw(nConfig, hcl.EncloseBracketsNewLines(regionTokens))
173-
if numShardsAttr := specbSrc.GetAttribute(nNumShards); numShardsAttr != nil {
174-
numShardsExpr := transformReference(hcl.GetAttrExpr(numShardsAttr), nRepSpecs, nSpec)
175-
tokens := hcl.TokensFromExpr(buildForExpr("i", fmt.Sprintf("range(%s)", numShardsExpr), false))
176-
tokens = append(tokens, hcl.TokensObject(repSpecb)...)
177-
return dynamicBlock{tokens: hcl.EncloseBracketsNewLines(tokens)}, nil
178-
}
179-
return dynamicBlock{tokens: hcl.TokensArraySingle(repSpecb)}, nil
175+
numShardsAttr := specbSrc.GetAttribute(nNumShards)
176+
tokens := buildNumShardsTokens(numShardsAttr, repSpecb, nRepSpecs, nSpec)
177+
return dynamicBlock{tokens: tokens}, nil
180178
}
181179

182180
// hasExpectedBlocksAsAttributes checks if any of the expected block names
@@ -205,11 +203,27 @@ func copyAttributesSorted(targetBody *hclwrite.Body, sourceAttrs map[string]*hcl
205203
}
206204

207205
func processAllSpecs(body *hclwrite.Body, diskSizeGB hclwrite.Tokens) {
208-
fillSpecOpt(body, nElectableSpecs, diskSizeGB)
209-
fillSpecOpt(body, nReadOnlySpecs, diskSizeGB)
210-
fillSpecOpt(body, nAnalyticsSpecs, diskSizeGB)
211-
fillSpecOpt(body, nAutoScaling, nil)
212-
fillSpecOpt(body, nAnalyticsAutoScaling, nil)
206+
for _, spec := range specsWithDisk {
207+
fillSpecOpt(body, spec, diskSizeGB)
208+
}
209+
for _, spec := range specsWithoutDisk {
210+
fillSpecOpt(body, spec, nil)
211+
}
212+
}
213+
214+
func processConfigForDynamicBlock(configBlockb *hclwrite.Body, diskSizeGB hclwrite.Tokens) *hclwrite.Body {
215+
newConfigBody := hclwrite.NewEmptyFile().Body()
216+
copyAttributesSorted(newConfigBody, configBlockb.Attributes())
217+
for _, block := range configBlockb.Blocks() {
218+
blockType := block.Type()
219+
blockBody := hclwrite.NewEmptyFile().Body()
220+
copyAttributesSorted(blockBody, block.Body().Attributes())
221+
if diskSizeGB != nil && slices.Contains(specsWithDisk, blockType) {
222+
blockBody.SetAttributeRaw(nDiskSizeGB, diskSizeGB)
223+
}
224+
newConfigBody.SetAttributeRaw(blockType, hcl.TokensObject(blockBody))
225+
}
226+
return newConfigBody
213227
}
214228

215229
func fillSpecOpt(resourceb *hclwrite.Body, name string, diskSizeGBTokens hclwrite.Tokens) {

0 commit comments

Comments
 (0)