@@ -3,6 +3,7 @@ package convert
33import (
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
6061func 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+
91356func 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+
114433func fillSpecOpt (resourceb * hclwrite.Body , name string , diskSizeGBTokens hclwrite.Tokens ) {
115434 block := resourceb .FirstMatchingBlock (name , nil )
116435 if block == nil {
0 commit comments