Skip to content

Commit f33ed40

Browse files
committed
implementation for basic tests
1 parent 8fc0daa commit f33ed40

File tree

2 files changed

+336
-18
lines changed

2 files changed

+336
-18
lines changed

internal/convert/adv2v2.go

Lines changed: 329 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package convert
33
import (
44
"fmt"
55
"slices"
6+
"strings"
67

78
"github.com/hashicorp/hcl/v2/hclwrite"
89
"github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/hcl"
@@ -58,6 +59,15 @@ func updateResource(resource *hclwrite.Block) (bool, error) {
5859
}
5960

6061
func convertRepSpecs(resourceb *hclwrite.Body, diskSizeGB hclwrite.Tokens) error {
62+
// Handle dynamic blocks for replication_specs
63+
dSpec, err := getDynamicBlock(resourceb, nRepSpecs)
64+
if err != nil {
65+
return err
66+
}
67+
if dSpec.IsPresent() {
68+
return convertDynamicRepSpecs(resourceb, dSpec, diskSizeGB)
69+
}
70+
6171
var repSpecs []*hclwrite.Body
6272
for {
6373
block := resourceb.FirstMatchingBlock(nRepSpecs, nil)
@@ -66,18 +76,42 @@ func convertRepSpecs(resourceb *hclwrite.Body, diskSizeGB hclwrite.Tokens) error
6676
}
6777
resourceb.RemoveBlock(block)
6878
blockb := block.Body()
69-
numShardsVal := 1 // default to 1 if num_shards not present
70-
if numShardsAttr := blockb.GetAttribute(nNumShards); numShardsAttr != nil {
71-
var err error
72-
if numShardsVal, err = hcl.GetAttrInt(numShardsAttr, errNumShards); err != nil {
79+
80+
// Check if num_shards is a variable/expression
81+
numShardsAttr := blockb.GetAttribute(nNumShards)
82+
if numShardsAttr != nil {
83+
numShardsVal, err := hcl.GetAttrInt(numShardsAttr, errNumShards)
84+
if err != nil {
85+
// num_shards is not a literal number, it's a variable/expression
86+
// Need to use range() in the output
87+
numShardsExpr := hcl.GetAttrExpr(numShardsAttr)
88+
blockb.RemoveAttribute(nNumShards)
89+
90+
if err := convertConfig(blockb, diskSizeGB); err != nil {
91+
return err
92+
}
93+
94+
// Create a for expression with range
95+
forExpr := fmt.Sprintf("for i in range(%s) :", numShardsExpr)
96+
tokens := hcl.TokensFromExpr(forExpr)
97+
tokens = append(tokens, hcl.TokensObject(blockb)...)
98+
resourceb.SetAttributeRaw(nRepSpecs, hcl.EncloseBracketsNewLines(tokens))
99+
return nil
100+
} else {
101+
// num_shards is a literal number
102+
blockb.RemoveAttribute(nNumShards)
103+
if err := convertConfig(blockb, diskSizeGB); err != nil {
104+
return err
105+
}
106+
for range numShardsVal {
107+
repSpecs = append(repSpecs, blockb)
108+
}
109+
}
110+
} else {
111+
// No num_shards, default to 1
112+
if err := convertConfig(blockb, diskSizeGB); err != nil {
73113
return err
74114
}
75-
blockb.RemoveAttribute(nNumShards)
76-
}
77-
if err := convertConfig(blockb, diskSizeGB); err != nil {
78-
return err
79-
}
80-
for range numShardsVal {
81115
repSpecs = append(repSpecs, blockb)
82116
}
83117
}
@@ -88,7 +122,255 @@ func convertRepSpecs(resourceb *hclwrite.Body, diskSizeGB hclwrite.Tokens) error
88122
return nil
89123
}
90124

125+
func convertDynamicRepSpecs(resourceb *hclwrite.Body, dSpec dynamicBlock, diskSizeGB hclwrite.Tokens) error {
126+
// Transform references from replication_specs.value.* to spec.*
127+
transformDynamicBlockReferences(dSpec.content.Body(), nRepSpecs, nSpec)
128+
129+
// Check for dynamic region_configs within this dynamic replication_specs
130+
dConfig, err := getDynamicBlock(dSpec.content.Body(), nConfig)
131+
if err != nil {
132+
return err
133+
}
134+
if !dConfig.IsPresent() {
135+
dConfig, err = getDynamicBlock(dSpec.content.Body(), nConfigSrc)
136+
if err != nil {
137+
return err
138+
}
139+
}
140+
141+
if dConfig.IsPresent() {
142+
// Handle nested dynamic block for region_configs
143+
return convertDynamicRepSpecsWithDynamicConfig(resourceb, dSpec, dConfig, diskSizeGB)
144+
}
145+
146+
// Get num_shards from the dynamic block content
147+
numShardsAttr := dSpec.content.Body().GetAttribute(nNumShards)
148+
if numShardsAttr != nil {
149+
numShardsExpr := replaceDynamicBlockReferences(hcl.GetAttrExpr(numShardsAttr), nRepSpecs, nSpec)
150+
dSpec.content.Body().RemoveAttribute(nNumShards)
151+
152+
// Convert region_configs inside the dynamic block
153+
if err := convertConfig(dSpec.content.Body(), diskSizeGB); err != nil {
154+
return err
155+
}
156+
157+
// Create the for expression for the flattened replication_specs
158+
forExpr := fmt.Sprintf("for %s in %s : [\n for i in range(%s) : ",
159+
nSpec, hcl.GetAttrExpr(dSpec.forEach), numShardsExpr)
160+
tokens := hcl.TokensFromExpr(forExpr)
161+
tokens = append(tokens, hcl.TokensObject(dSpec.content.Body())...)
162+
tokens = append(tokens, hcl.TokensFromExpr("\n ]\n]")...)
163+
164+
resourceb.RemoveBlock(dSpec.block)
165+
resourceb.SetAttributeRaw(nRepSpecs, hcl.TokensFuncFlatten(tokens))
166+
return nil
167+
} else {
168+
// No num_shards, default to 1
169+
dSpec.content.Body().RemoveAttribute(nNumShards)
170+
171+
// Convert region_configs inside the dynamic block
172+
if err := convertConfig(dSpec.content.Body(), diskSizeGB); err != nil {
173+
return err
174+
}
175+
176+
// Create the for expression without num_shards
177+
forExpr := fmt.Sprintf("for %s in %s :", nSpec, hcl.GetAttrExpr(dSpec.forEach))
178+
tokens := hcl.TokensFromExpr(forExpr)
179+
tokens = append(tokens, hcl.TokensObject(dSpec.content.Body())...)
180+
tokens = hcl.EncloseBracketsNewLines(tokens)
181+
182+
resourceb.RemoveBlock(dSpec.block)
183+
resourceb.SetAttributeRaw(nRepSpecs, tokens)
184+
return nil
185+
}
186+
}
187+
188+
func convertDynamicRepSpecsWithDynamicConfig(resourceb *hclwrite.Body, dSpec, dConfig dynamicBlock, diskSizeGB hclwrite.Tokens) error {
189+
// Get the block name from the dynamic block
190+
configBlockName := getResourceName(dConfig.block)
191+
192+
// Get num_shards expression
193+
numShardsAttr := dSpec.content.Body().GetAttribute(nNumShards)
194+
if numShardsAttr != nil {
195+
numShardsExpr := replaceDynamicBlockReferences(hcl.GetAttrExpr(numShardsAttr), nRepSpecs, nSpec)
196+
197+
// Transform references in place for the dynamic config content
198+
transformDynamicBlockReferencesRecursive(dConfig.content.Body(), configBlockName, nRegion)
199+
// Also transform outer references
200+
for name, attr := range dConfig.content.Body().Attributes() {
201+
expr := hcl.GetAttrExpr(attr)
202+
expr = replaceDynamicBlockReferences(expr, nRepSpecs, nSpec)
203+
dConfig.content.Body().SetAttributeRaw(name, hcl.TokensFromExpr(expr))
204+
}
205+
for _, block := range dConfig.content.Body().Blocks() {
206+
for name, attr := range block.Body().Attributes() {
207+
expr := hcl.GetAttrExpr(attr)
208+
expr = replaceDynamicBlockReferences(expr, nRepSpecs, nSpec)
209+
block.Body().SetAttributeRaw(name, hcl.TokensFromExpr(expr))
210+
}
211+
}
212+
213+
// Don't convert specs blocks to attributes - we'll handle them manually in the string building
214+
215+
// Build the expression using HCL functions
216+
configForEach := replaceDynamicBlockReferences(hcl.GetAttrExpr(dConfig.forEach), nRepSpecs, nSpec)
217+
zoneNameExpr := ""
218+
if zoneNameAttr := dSpec.content.Body().GetAttribute(nZoneName); zoneNameAttr != nil {
219+
zoneNameExpr = replaceDynamicBlockReferences(hcl.GetAttrExpr(zoneNameAttr), nRepSpecs, nSpec)
220+
}
221+
222+
// Build the nested expression string
223+
var exprStr strings.Builder
224+
exprStr.WriteString("flatten([\n")
225+
exprStr.WriteString(fmt.Sprintf(" for %s in %s : [\n", nSpec, hcl.GetAttrExpr(dSpec.forEach)))
226+
exprStr.WriteString(fmt.Sprintf(" for i in range(%s) : {\n", numShardsExpr))
227+
if zoneNameExpr != "" {
228+
exprStr.WriteString(fmt.Sprintf(" zone_name = %s\n", zoneNameExpr))
229+
}
230+
exprStr.WriteString(" region_configs = [\n")
231+
exprStr.WriteString(fmt.Sprintf(" for %s in %s : {\n", nRegion, configForEach))
232+
233+
// Add regular attributes first (provider_name, region_name, priority)
234+
attrs := dConfig.content.Body().Attributes()
235+
if providerName := attrs["provider_name"]; providerName != nil {
236+
exprStr.WriteString(fmt.Sprintf(" provider_name = %s\n", hcl.GetAttrExpr(providerName)))
237+
}
238+
if regionName := attrs["region_name"]; regionName != nil {
239+
exprStr.WriteString(fmt.Sprintf(" region_name = %s\n", hcl.GetAttrExpr(regionName)))
240+
}
241+
if priority := attrs["priority"]; priority != nil {
242+
exprStr.WriteString(fmt.Sprintf(" priority = %s\n", hcl.GetAttrExpr(priority)))
243+
}
244+
245+
// Add spec blocks as objects with correct formatting and ordering
246+
// Look for the original blocks before they were converted
247+
for _, block := range dConfig.content.Body().Blocks() {
248+
if block.Type() == "electable_specs" {
249+
exprStr.WriteString(" electable_specs = {\n")
250+
blockAttrs := block.Body().Attributes()
251+
// Write in specific order: instance_size first, then node_count
252+
if instanceSize := blockAttrs["instance_size"]; instanceSize != nil {
253+
exprStr.WriteString(fmt.Sprintf(" instance_size = %s\n", hcl.GetAttrExpr(instanceSize)))
254+
}
255+
if nodeCount := blockAttrs["node_count"]; nodeCount != nil {
256+
exprStr.WriteString(fmt.Sprintf(" node_count = %s\n", hcl.GetAttrExpr(nodeCount)))
257+
}
258+
exprStr.WriteString(" }\n")
259+
}
260+
if block.Type() == "read_only_specs" {
261+
exprStr.WriteString(" read_only_specs = {\n")
262+
blockAttrs := block.Body().Attributes()
263+
// Write in specific order: instance_size first, then node_count
264+
if instanceSize := blockAttrs["instance_size"]; instanceSize != nil {
265+
exprStr.WriteString(fmt.Sprintf(" instance_size = %s\n", hcl.GetAttrExpr(instanceSize)))
266+
}
267+
if nodeCount := blockAttrs["node_count"]; nodeCount != nil {
268+
exprStr.WriteString(fmt.Sprintf(" node_count = %s\n", hcl.GetAttrExpr(nodeCount)))
269+
}
270+
exprStr.WriteString(" }\n")
271+
}
272+
}
273+
274+
exprStr.WriteString(" }\n")
275+
exprStr.WriteString(" ]\n")
276+
exprStr.WriteString(" }\n")
277+
exprStr.WriteString(" ]\n")
278+
exprStr.WriteString(" ])")
279+
280+
tokens := hcl.TokensFromExpr(exprStr.String())
281+
282+
resourceb.RemoveBlock(dSpec.block)
283+
resourceb.SetAttributeRaw(nRepSpecs, tokens)
284+
return nil
285+
} else {
286+
// No num_shards, handle like without nested shards
287+
// Get the block name from the dynamic block
288+
configBlockName := getResourceName(dConfig.block)
289+
290+
// Get zone_name if present
291+
repSpecFile := hclwrite.NewEmptyFile()
292+
repSpecb := repSpecFile.Body()
293+
294+
if zoneNameAttr := dSpec.content.Body().GetAttribute(nZoneName); zoneNameAttr != nil {
295+
zoneNameExpr := replaceDynamicBlockReferences(hcl.GetAttrExpr(zoneNameAttr), nRepSpecs, nSpec)
296+
repSpecb.SetAttributeRaw(nZoneName, hcl.TokensFromExpr(zoneNameExpr))
297+
}
298+
299+
// Create config content with transformed references
300+
configFile := hclwrite.NewEmptyFile()
301+
configb := configFile.Body()
302+
303+
// Copy and transform attributes
304+
for name, attr := range dConfig.content.Body().Attributes() {
305+
expr := hcl.GetAttrExpr(attr)
306+
// First replace nested dynamic block references
307+
expr = replaceDynamicBlockReferences(expr, configBlockName, nRegion)
308+
// Then replace outer dynamic block references
309+
expr = replaceDynamicBlockReferences(expr, nRepSpecs, nSpec)
310+
configb.SetAttributeRaw(name, hcl.TokensFromExpr(expr))
311+
}
312+
313+
// Process blocks and transform their references
314+
for _, block := range dConfig.content.Body().Blocks() {
315+
newBlock := configb.AppendNewBlock(block.Type(), block.Labels())
316+
newBlockb := newBlock.Body()
317+
for name, attr := range block.Body().Attributes() {
318+
expr := hcl.GetAttrExpr(attr)
319+
// First replace nested dynamic block references
320+
expr = replaceDynamicBlockReferences(expr, configBlockName, nRegion)
321+
// Then replace outer dynamic block references
322+
expr = replaceDynamicBlockReferences(expr, nRepSpecs, nSpec)
323+
newBlockb.SetAttributeRaw(name, hcl.TokensFromExpr(expr))
324+
}
325+
}
326+
327+
// Process specs
328+
fillSpecOpt(configb, nElectableSpecs, diskSizeGB)
329+
fillSpecOpt(configb, nReadOnlySpecs, diskSizeGB)
330+
fillSpecOpt(configb, nAnalyticsSpecs, diskSizeGB)
331+
fillSpecOpt(configb, nAutoScaling, nil)
332+
fillSpecOpt(configb, nAnalyticsAutoScaling, nil)
333+
334+
// Build the nested for expression for region_configs
335+
configForEach := replaceDynamicBlockReferences(hcl.GetAttrExpr(dConfig.forEach), nRepSpecs, nSpec)
336+
var regionTokens2 hclwrite.Tokens
337+
regionTokens2 = append(regionTokens2, hcl.TokensFromExpr("[\n")...)
338+
regionTokens2 = append(regionTokens2, hcl.TokensFromExpr(fmt.Sprintf(" for %s in %s : ", nRegion, configForEach))...)
339+
regionTokens2 = append(regionTokens2, hcl.TokensObject(configb)...)
340+
regionTokens2 = append(regionTokens2, hcl.TokensFromExpr("\n ]")...)
341+
repSpecb.SetAttributeRaw(nConfig, regionTokens2)
342+
343+
// Build the for expression without range
344+
var tokens hclwrite.Tokens
345+
tokens = append(tokens, hcl.TokensFromExpr("[\n")...)
346+
tokens = append(tokens, hcl.TokensFromExpr(fmt.Sprintf(" for %s in %s : ", nSpec, hcl.GetAttrExpr(dSpec.forEach)))...)
347+
tokens = append(tokens, hcl.TokensObject(repSpecb)...)
348+
tokens = append(tokens, hcl.TokensFromExpr("\n ]")...)
349+
350+
resourceb.RemoveBlock(dSpec.block)
351+
resourceb.SetAttributeRaw(nRepSpecs, tokens)
352+
return nil
353+
}
354+
}
355+
91356
func convertConfig(repSpecs *hclwrite.Body, diskSizeGB hclwrite.Tokens) error {
357+
// Check for dynamic region_configs block (can be either "region_configs" or "regions_config")
358+
dConfig, err := getDynamicBlock(repSpecs, nConfig)
359+
if err != nil {
360+
return err
361+
}
362+
if !dConfig.IsPresent() {
363+
dConfig, err = getDynamicBlock(repSpecs, nConfigSrc)
364+
if err != nil {
365+
return err
366+
}
367+
}
368+
369+
if dConfig.IsPresent() {
370+
// Handle dynamic region_configs
371+
return convertDynamicConfig(repSpecs, dConfig, diskSizeGB)
372+
}
373+
92374
var configs []*hclwrite.Body
93375
for {
94376
block := repSpecs.FirstMatchingBlock(nConfig, nil)
@@ -111,6 +393,43 @@ func convertConfig(repSpecs *hclwrite.Body, diskSizeGB hclwrite.Tokens) error {
111393
return nil
112394
}
113395

396+
func convertDynamicConfig(repSpecs *hclwrite.Body, dConfig dynamicBlock, diskSizeGB hclwrite.Tokens) error {
397+
// Get the block name from the dynamic block itself
398+
blockName := getResourceName(dConfig.block)
399+
400+
// Transform the references in attributes and blocks
401+
transformDynamicBlockReferencesRecursive(dConfig.content.Body(), blockName, nRegion)
402+
403+
// Process specs
404+
fillSpecOpt(dConfig.content.Body(), nElectableSpecs, diskSizeGB)
405+
fillSpecOpt(dConfig.content.Body(), nReadOnlySpecs, diskSizeGB)
406+
fillSpecOpt(dConfig.content.Body(), nAnalyticsSpecs, diskSizeGB)
407+
fillSpecOpt(dConfig.content.Body(), nAutoScaling, nil)
408+
fillSpecOpt(dConfig.content.Body(), nAnalyticsAutoScaling, nil)
409+
410+
// Build the for expression
411+
forExpr := fmt.Sprintf("for %s in %s :", nRegion, hcl.GetAttrExpr(dConfig.forEach))
412+
tokens := hcl.TokensFromExpr(forExpr)
413+
tokens = append(tokens, hcl.TokensObject(dConfig.content.Body())...)
414+
tokens = hcl.EncloseBracketsNewLines(tokens)
415+
416+
repSpecs.RemoveBlock(dConfig.block)
417+
repSpecs.SetAttributeRaw(nConfig, tokens)
418+
return nil
419+
}
420+
421+
func transformDynamicBlockReferencesRecursive(body *hclwrite.Body, blockName, varName string) {
422+
// Transform attributes
423+
for name, attr := range body.Attributes() {
424+
expr := replaceDynamicBlockReferences(hcl.GetAttrExpr(attr), blockName, varName)
425+
body.SetAttributeRaw(name, hcl.TokensFromExpr(expr))
426+
}
427+
// Transform nested blocks
428+
for _, block := range body.Blocks() {
429+
transformDynamicBlockReferencesRecursive(block.Body(), blockName, varName)
430+
}
431+
}
432+
114433
func fillSpecOpt(resourceb *hclwrite.Body, name string, diskSizeGBTokens hclwrite.Tokens) {
115434
block := resourceb.FirstMatchingBlock(name, nil)
116435
if block == nil {

0 commit comments

Comments
 (0)