diff --git a/CHANGELOG.md b/CHANGELOG.md index 65a778f..6d6920c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ENHANCEMENTS: -* Supports `dynamic` block for `tags` and `labels` +* Supports `dynamic` block for `tags`, `labels` and `regions_config` ## 1.0.0 (Mar 6, 2025) diff --git a/README.md b/README.md index 71e0378..69fd104 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,8 @@ Given the different ways of using dynamic blocks, we recommend reviewing the out #### Dynamic blocks in tags and labels -You can use `dynamic` blocks for `tags` and `labels`. You can also combine the use of dynamic blocks in `tags` and `labels` with individual blocks in the same cluster definition, e.g.: +You can use `dynamic` blocks for `tags` and `labels`. The plugin assumes that `for_each` has an expression which is evaluated to a `map` of strings. +You can also combine the use of dynamic blocks in `tags` and `labels` with individual blocks in the same cluster definition, e.g.: ```hcl tags { key = "environment" @@ -72,12 +73,37 @@ dynamic "tags" { } ``` +#### Dynamic blocks in regions_config + +You can use `dynamic` blocks for `regions_config`. The plugin assumes that `for_each` has an expression which is evaluated to a `list` or `set` of objects. +This is an example of how to use dynamic blocks in `regions_config`: +```hcl + replication_specs { + num_shards = var.replication_specs.num_shards + zone_name = var.replication_specs.zone_name # only needed if you're using zones + dynamic "regions_config" { + for_each = var.replication_specs.regions_config + content { + priority = regions_config.value.priority + region_name = regions_config.value.region_name + electable_nodes = regions_config.value.electable_nodes + read_only_nodes = regions_config.value.read_only_nodes + } + } + } +``` +Dynamic block and individual blocks for `regions_config` are not supported at the same time. If you need this use case, please send us [feedback](https://github.com/mongodb-labs/atlas-cli-plugin-terraform/issues). There are currently two main approaches to handle this: +- (Recommended) Remove the individual `regions_config` blocks and add their information to the variable you're using in the `for_each` expression, e.g. using [concat](https://developer.hashicorp.com/terraform/language/functions/concat) if you're using a list or [setunion](https://developer.hashicorp.com/terraform/language/functions/setunion) for sets. In this way, you don't need to change the generated `mongodb_advanced_cluster` configuration. +- Change the generated `mongodb_advanced_cluster` configuration to join the individual blocks to the code generated for the `dynamic` block. This approach is more error-prone. + ### Limitations -- The plugin doesn't support `regions_config` without `electable_nodes` as there can be some issues with `priority` when they only have `analytics_nodes` and/or `electable_nodes`. -- [`priority`](https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs/resources/cluster#priority-1) is required in `regions_config` and must be a numeric [literal expression](https://developer.hashicorp.com/nomad/docs/job-specification/hcl2/expressions#literal-expressions) between 7 and 1, e.g. `var.priority` is not supported. This is to allow reordering them by descending priority as this is expected in `mongodbatlas_advanced_cluster`. -- [`num_shards`](https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs/resources/cluster#num_shards-2) in `replication_specs` must be a numeric [literal expression](https://developer.hashicorp.com/nomad/docs/job-specification/hcl2/expressions#literal-expressions), e.g. `var.num_shards` is not supported. This is to allow creating a `replication_specs` element per shard in `mongodbatlas_advanced_cluster`. -- `dynamic` blocks are currently supported only for `tags` and `labels`. **Coming soon**: support for `replication_specs` and `regions_config`. +- [`num_shards`](https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs/resources/cluster#num_shards-2) in `replication_specs` must be a numeric [literal expression](https://developer.hashicorp.com/nomad/docs/job-specification/hcl2/expressions#literal-expressions), e.g. `var.num_shards` is not supported. This is to allow creating a `replication_specs` element per shard in `mongodbatlas_advanced_cluster`. This limitation doesn't apply if you're using `dynamic` blocks in `regions_config` or `replication_specs`. +- `dynamic` blocks are currently supported only for `tags`, `labels` and `regions_config`. See limitations for `regions_config` support in [its section](#dynamic-blocks-in-regions_config) above. **Coming soon**: support for `replication_specs`. + +## Feedback + +If you find any issues or have any suggestions, please open an [issue](https://github.com/mongodb-labs/atlas-cli-plugin-terraform/issues) in this repository. ## Contributing diff --git a/internal/convert/const_names.go b/internal/convert/const_names.go index 7352d18..c2adc20 100644 --- a/internal/convert/const_names.go +++ b/internal/convert/const_names.go @@ -54,4 +54,5 @@ const ( nDynamic = "dynamic" nForEach = "for_each" nContent = "content" + nRegion = "region" ) diff --git a/internal/convert/convert.go b/internal/convert/convert.go index 74ff298..8482b2c 100644 --- a/internal/convert/convert.go +++ b/internal/convert/convert.go @@ -22,22 +22,22 @@ const ( advClusterPlural = "mongodbatlas_advanced_clusters" valClusterType = "REPLICASET" valMaxPriority = 7 - valMinPriority = 1 - - errFreeCluster = "free cluster (because no " + nRepSpecs + ")" - errRepSpecs = "setting " + nRepSpecs - errConfigs = "setting " + nConfig - errPriority = "setting " + nPriority - errNumShards = "setting " + nNumShards + valMinPriority = 0 + errFreeCluster = "free cluster (because no " + nRepSpecs + ")" + errRepSpecs = "setting " + nRepSpecs + errConfigs = "setting " + nConfig + errPriority = "setting " + nPriority + errNumShards = "setting " + nNumShards commentGeneratedBy = "Generated by atlas-cli-plugin-terraform." - commentConfirmReferences = "Please confirm that all references to this resource are updated." + commentConfirmReferences = "Please review the changes and confirm that references to this resource are updated." commentMovedBlock = "Moved blocks" commentRemovedOld = "Note: Remember to remove or comment out the old cluster definitions." + commentPriorityFor = "Regions must be sorted by priority in descending order." ) var ( - dynamicBlockAllowList = []string{nTags, nLabels} + dynamicBlockAllowList = []string{nTags, nLabels, nConfigSrc} ) type attrVals struct { @@ -129,8 +129,8 @@ func fillMovedBlocks(body *hclwrite.Body, moveLabels []string) { for i, moveLabel := range moveLabels { block := body.AppendNewBlock(nMoved, nil) blockb := block.Body() - hcl.SetAttrExpr(blockb, nFrom, fmt.Sprintf("%s.%s", cluster, moveLabel)) - hcl.SetAttrExpr(blockb, nTo, fmt.Sprintf("%s.%s", advCluster, moveLabel)) + blockb.SetAttributeRaw(nFrom, hcl.TokensFromExpr(fmt.Sprintf("%s.%s", cluster, moveLabel))) + blockb.SetAttributeRaw(nTo, hcl.TokensFromExpr(fmt.Sprintf("%s.%s", advCluster, moveLabel))) if i < len(moveLabels)-1 { body.AppendNewline() } @@ -202,9 +202,15 @@ func fillReplicationSpecs(resourceb *hclwrite.Body, root attrVals) error { break } specbSrc := specSrc.Body() - if err := checkDynamicBlock(specbSrc); err != nil { + d, err := fillReplicationSpecsWithDynamicRegionConfigs(specbSrc, root) + if err != nil { return err } + if d.IsPresent() { + resourceb.RemoveBlock(specSrc) + resourceb.SetAttributeRaw(nRepSpecs, d.tokens) + return nil + } // ok to fail as zone_name is optional _ = hcl.MoveAttr(specbSrc, specb, nZoneName, nZoneName, errRepSpecs) shards := specbSrc.GetAttribute(nNumShards) @@ -251,7 +257,7 @@ func fillTagsLabelsOpt(resourceb *hclwrite.Body, name string) error { func extractTagsLabelsDynamicBlock(resourceb *hclwrite.Body, name string) (hclwrite.Tokens, error) { d, err := getDynamicBlock(resourceb, name) - if err != nil || d.forEach == nil { + if err != nil || !d.IsPresent() { return nil, err } key := d.content.Body().GetAttribute(nKey) @@ -306,6 +312,36 @@ func fillBlockOpt(resourceb *hclwrite.Body, name string) { resourceb.SetAttributeRaw(name, hcl.TokensObject(block.Body())) } +// fillReplicationSpecsWithDynamicRegionConfigs is used for dynamic blocks in region_configs +func fillReplicationSpecsWithDynamicRegionConfigs(specbSrc *hclwrite.Body, root attrVals) (dynamicBlock, error) { + d, err := getDynamicBlock(specbSrc, nConfigSrc) + if err != nil || !d.IsPresent() { + return dynamicBlock{}, err + } + repSpec := hclwrite.NewEmptyFile() + repSpecb := repSpec.Body() + if zoneName := hcl.GetAttrExpr(specbSrc.GetAttribute(nZoneName)); zoneName != "" { + repSpecb.SetAttributeRaw(nZoneName, hcl.TokensFromExpr(zoneName)) + } + regionFor, err := getDynamicBlockRegionConfigsRegionArray(d, root) + if err != nil { + return dynamicBlock{}, err + } + priorityFor := hcl.TokensComment(commentPriorityFor) + priorityFor = append(priorityFor, hcl.TokensFromExpr(fmt.Sprintf("for %s in range(%d, %d, -1) : ", nPriority, valMaxPriority, valMinPriority))...) + priorityFor = append(priorityFor, regionFor...) + repSpecb.SetAttributeRaw(nConfig, hcl.TokensFuncFlatten(priorityFor)) + + shards := specbSrc.GetAttribute(nNumShards) + if shards == nil { + return dynamicBlock{}, fmt.Errorf("%s: %s not found", errRepSpecs, nNumShards) + } + tokens := hcl.TokensFromExpr(fmt.Sprintf("for i in range(%s) :", hcl.GetAttrExpr(shards))) + tokens = append(tokens, hcl.EncloseBraces(repSpec.BuildTokens(nil), true)...) + d.tokens = hcl.EncloseBracketsNewLines(tokens) + return d, nil +} + func fillRegionConfigs(specb, specbSrc *hclwrite.Body, root attrVals) error { var configs []*hclwrite.Body for { @@ -313,7 +349,7 @@ func fillRegionConfigs(specb, specbSrc *hclwrite.Body, root attrVals) error { if configSrc == nil { break } - config, err := getRegionConfig(configSrc, root) + config, err := getRegionConfig(configSrc, root, false) if err != nil { return err } @@ -323,34 +359,28 @@ func fillRegionConfigs(specb, specbSrc *hclwrite.Body, root attrVals) error { if len(configs) == 0 { return fmt.Errorf("%s: %s not found", errRepSpecs, nConfigSrc) } - sort.Slice(configs, func(i, j int) bool { - pi, _ := hcl.GetAttrInt(configs[i].GetAttribute(nPriority), errPriority) - pj, _ := hcl.GetAttrInt(configs[j].GetAttribute(nPriority), errPriority) - return pi > pj - }) + configs = sortConfigsByPriority(configs) specb.SetAttributeRaw(nConfig, hcl.TokensArray(configs)) return nil } -func getRegionConfig(configSrc *hclwrite.Block, root attrVals) (*hclwrite.File, error) { +func getRegionConfig(configSrc *hclwrite.Block, root attrVals, isDynamicBlock bool) (*hclwrite.File, error) { file := hclwrite.NewEmptyFile() fileb := file.Body() fileb.SetAttributeRaw(nProviderName, root.req[nProviderName]) if err := hcl.MoveAttr(configSrc.Body(), fileb, nRegionName, nRegionName, errRepSpecs); err != nil { return nil, err } - if err := setPriority(fileb, configSrc.Body().GetAttribute(nPriority)); err != nil { + if err := hcl.MoveAttr(configSrc.Body(), fileb, nPriority, nPriority, errRepSpecs); err != nil { return nil, err } - electableSpecs, errElec := getSpecs(configSrc, nElectableNodes, root) - if errElec != nil { - return nil, errElec + if electable, _ := getSpecs(configSrc, nElectableNodes, root, isDynamicBlock); electable != nil { + fileb.SetAttributeRaw(nElectableSpecs, electable) } - fileb.SetAttributeRaw(nElectableSpecs, electableSpecs) - if readOnly, _ := getSpecs(configSrc, nReadOnlyNodes, root); readOnly != nil { + if readOnly, _ := getSpecs(configSrc, nReadOnlyNodes, root, isDynamicBlock); readOnly != nil { fileb.SetAttributeRaw(nReadOnlySpecs, readOnly) } - if analytics, _ := getSpecs(configSrc, nAnalyticsNodes, root); analytics != nil { + if analytics, _ := getSpecs(configSrc, nAnalyticsNodes, root, isDynamicBlock); analytics != nil { fileb.SetAttributeRaw(nAnalyticsSpecs, analytics) } if autoScaling := getAutoScalingOpt(root.opt); autoScaling != nil { @@ -359,7 +389,7 @@ func getRegionConfig(configSrc *hclwrite.Block, root attrVals) (*hclwrite.File, return file, nil } -func getSpecs(configSrc *hclwrite.Block, countName string, root attrVals) (hclwrite.Tokens, error) { +func getSpecs(configSrc *hclwrite.Block, countName string, root attrVals, isDynamicBlock bool) (hclwrite.Tokens, error) { var ( file = hclwrite.NewEmptyFile() fileb = file.Body() @@ -382,7 +412,11 @@ func getSpecs(configSrc *hclwrite.Block, countName string, root attrVals) (hclwr if root.opt[nDiskIOPSSrc] != nil { fileb.SetAttributeRaw(nDiskIOPS, root.opt[nDiskIOPSSrc]) } - return hcl.TokensObject(fileb), nil + tokens := hcl.TokensObject(fileb) + if isDynamicBlock { + tokens = encloseDynamicBlockRegionSpec(tokens, countName) + } + return tokens, nil } func getAutoScalingOpt(opt map[string]hclwrite.Tokens) hclwrite.Tokens { @@ -440,6 +474,17 @@ func getResourceLabel(resource *hclwrite.Block) string { return labels[1] } +type dynamicBlock struct { + block *hclwrite.Block + forEach *hclwrite.Attribute + content *hclwrite.Block + tokens hclwrite.Tokens +} + +func (d dynamicBlock) IsPresent() bool { + return d.block != nil +} + func checkDynamicBlock(body *hclwrite.Body) error { for _, block := range body.Blocks() { name := getResourceName(block) @@ -451,12 +496,6 @@ func checkDynamicBlock(body *hclwrite.Body) error { return nil } -type dynamicBlock struct { - block *hclwrite.Block - forEach *hclwrite.Attribute - content *hclwrite.Block -} - func getDynamicBlock(body *hclwrite.Body, name string) (dynamicBlock, error) { for _, block := range body.Blocks() { if block.Type() != nDynamic || name != getResourceName(block) { @@ -481,6 +520,55 @@ func replaceDynamicBlockExpr(attr *hclwrite.Attribute, blockName, attrName strin return strings.ReplaceAll(expr, fmt.Sprintf("%s.%s", blockName, attrName), attrName) } +func encloseDynamicBlockRegionSpec(specTokens hclwrite.Tokens, countName string) hclwrite.Tokens { + tokens := hcl.TokensFromExpr(fmt.Sprintf("%s.%s > 0 ?", nRegion, countName)) + tokens = append(tokens, specTokens...) + return append(tokens, hcl.TokensFromExpr(": null")...) +} + +// getDynamicBlockRegionConfigsRegionArray returns the region array for a dynamic block in replication_specs. +// e.g. [ for region in var.replication_specs.regions_config : { ... } if priority == region.priority ] +func getDynamicBlockRegionConfigsRegionArray(d dynamicBlock, root attrVals) (hclwrite.Tokens, error) { + transformDynamicBlockReferences(d.content.Body()) + priorityStr := hcl.GetAttrExpr(d.content.Body().GetAttribute(nPriority)) + if priorityStr == "" { + return nil, fmt.Errorf("%s: %s not found", errRepSpecs, nPriority) + } + region, err := getRegionConfig(d.content, root, true) + if err != nil { + return nil, err + } + tokens := hcl.TokensFromExpr(fmt.Sprintf("for %s in %s :", nRegion, hcl.GetAttrExpr(d.forEach))) + tokens = append(tokens, hcl.EncloseBraces(region.BuildTokens(nil), true)...) + tokens = append(tokens, hcl.TokensFromExpr(fmt.Sprintf("if %s == %s", nPriority, priorityStr))...) + return hcl.EncloseBracketsNewLines(tokens), nil +} + +// transformDynamicBlockReferences changes value references in all attributes, e.g. regions_config.value.electable_nodes to region.electable_nodes +func transformDynamicBlockReferences(configSrcb *hclwrite.Body) { + for name, attr := range configSrcb.Attributes() { + expr := hcl.GetAttrExpr(attr) + expr = strings.ReplaceAll(expr, + fmt.Sprintf("%s.%s.", nConfigSrc, nValue), + fmt.Sprintf("%s.", nRegion)) + configSrcb.SetAttributeRaw(name, hcl.TokensFromExpr(expr)) + } +} + +func sortConfigsByPriority(configs []*hclwrite.Body) []*hclwrite.Body { + for _, config := range configs { + if _, err := hcl.GetAttrInt(config.GetAttribute(nPriority), errPriority); err != nil { + return configs // don't sort priorities if any is not a numerical literal + } + } + sort.Slice(configs, func(i, j int) bool { + pi, _ := hcl.GetAttrInt(configs[i].GetAttribute(nPriority), errPriority) + pj, _ := hcl.GetAttrInt(configs[j].GetAttribute(nPriority), errPriority) + return pi > pj + }) + return configs +} + func setKeyValue(body *hclwrite.Body, key, value *hclwrite.Attribute) { keyStr, err := hcl.GetAttrString(key, "") if err == nil { @@ -494,21 +582,6 @@ func setKeyValue(body *hclwrite.Body, key, value *hclwrite.Attribute) { body.SetAttributeRaw(keyStr, value.Expr().BuildTokens(nil)) } -func setPriority(body *hclwrite.Body, priority *hclwrite.Attribute) error { - if priority == nil { - return fmt.Errorf("%s: %s not found", errRepSpecs, nPriority) - } - valPriority, err := hcl.GetAttrInt(priority, errPriority) - if err != nil { - return err - } - if valPriority < valMinPriority || valPriority > valMaxPriority { - return fmt.Errorf("%s: %s is %d but must be between %d and %d", errPriority, nPriority, valPriority, valMinPriority, valMaxPriority) - } - hcl.SetAttrInt(body, nPriority, valPriority) - return nil -} - // popRootAttrs deletes the attributes common to all replication_specs/regions_config and returns them. func popRootAttrs(body *hclwrite.Body) (attrVals, error) { var ( diff --git a/internal/convert/testdata/clu2adv/adv_config_bi_connector_pinned_fcv.out.tf b/internal/convert/testdata/clu2adv/adv_config_bi_connector_pinned_fcv.out.tf index 603b42f..f1f7239 100644 --- a/internal/convert/testdata/clu2adv/adv_config_bi_connector_pinned_fcv.out.tf +++ b/internal/convert/testdata/clu2adv/adv_config_bi_connector_pinned_fcv.out.tf @@ -32,5 +32,5 @@ resource "mongodbatlas_advanced_cluster" "this" { } # Generated by atlas-cli-plugin-terraform. - # Please confirm that all references to this resource are updated. + # Please review the changes and confirm that references to this resource are updated. } diff --git a/internal/convert/testdata/clu2adv/analytics_read_only_all_params.in.tf b/internal/convert/testdata/clu2adv/analytics_read_only_all_params.in.tf index 101c90e..8d4a84b 100644 --- a/internal/convert/testdata/clu2adv/analytics_read_only_all_params.in.tf +++ b/internal/convert/testdata/clu2adv/analytics_read_only_all_params.in.tf @@ -18,3 +18,24 @@ resource "mongodbatlas_cluster" "ar" { } } } + +resource "mongodbatlas_cluster" "ar_not_electable" { + project_id = var.project_id + name = "ar" + cluster_type = "REPLICASET" + provider_name = "AWS" + provider_instance_size_name = "M10" + disk_size_gb = 90 + provider_volume_type = "PROVISIONED" + provider_disk_iops = 100 + replication_specs { + num_shards = 1 + regions_config { + region_name = "US_EAST_1" + priority = 7 + electable_nodes = 0 + analytics_nodes = 2 + read_only_nodes = 1 + } + } +} diff --git a/internal/convert/testdata/clu2adv/analytics_read_only_all_params.out.tf b/internal/convert/testdata/clu2adv/analytics_read_only_all_params.out.tf index b8f0db7..85b8eef 100644 --- a/internal/convert/testdata/clu2adv/analytics_read_only_all_params.out.tf +++ b/internal/convert/testdata/clu2adv/analytics_read_only_all_params.out.tf @@ -36,5 +36,39 @@ resource "mongodbatlas_advanced_cluster" "ar" { ] # Generated by atlas-cli-plugin-terraform. - # Please confirm that all references to this resource are updated. + # Please review the changes and confirm that references to this resource are updated. +} + +resource "mongodbatlas_advanced_cluster" "ar_not_electable" { + project_id = var.project_id + name = "ar" + cluster_type = "REPLICASET" + replication_specs = [ + { + region_configs = [ + { + provider_name = "AWS" + region_name = "US_EAST_1" + priority = 7 + read_only_specs = { + node_count = 1 + instance_size = "M10" + disk_size_gb = 90 + ebs_volume_type = "PROVISIONED" + disk_iops = 100 + } + analytics_specs = { + node_count = 2 + instance_size = "M10" + disk_size_gb = 90 + ebs_volume_type = "PROVISIONED" + disk_iops = 100 + } + } + ] + } + ] + + # Generated by atlas-cli-plugin-terraform. + # Please review the changes and confirm that references to this resource are updated. } diff --git a/internal/convert/testdata/clu2adv/analytics_read_only_min_params.in.tf b/internal/convert/testdata/clu2adv/analytics_read_only_min_params.in.tf index 8aae1e1..d3b4cab 100644 --- a/internal/convert/testdata/clu2adv/analytics_read_only_min_params.in.tf +++ b/internal/convert/testdata/clu2adv/analytics_read_only_min_params.in.tf @@ -15,3 +15,20 @@ resource "mongodbatlas_cluster" "ar" { } } } + +resource "mongodbatlas_cluster" "ar_not_electable" { + project_id = var.project_id + name = "ar" + cluster_type = "REPLICASET" + provider_name = "AWS" + provider_instance_size_name = "M10" + replication_specs { + num_shards = 1 + regions_config { + region_name = "US_EAST_1" + priority = 7 + analytics_nodes = 2 + read_only_nodes = 1 + } + } +} diff --git a/internal/convert/testdata/clu2adv/analytics_read_only_min_params.out.tf b/internal/convert/testdata/clu2adv/analytics_read_only_min_params.out.tf index cb8c9ea..2699ea9 100644 --- a/internal/convert/testdata/clu2adv/analytics_read_only_min_params.out.tf +++ b/internal/convert/testdata/clu2adv/analytics_read_only_min_params.out.tf @@ -27,5 +27,33 @@ resource "mongodbatlas_advanced_cluster" "ar" { ] # Generated by atlas-cli-plugin-terraform. - # Please confirm that all references to this resource are updated. + # Please review the changes and confirm that references to this resource are updated. +} + +resource "mongodbatlas_advanced_cluster" "ar_not_electable" { + project_id = var.project_id + name = "ar" + cluster_type = "REPLICASET" + replication_specs = [ + { + region_configs = [ + { + provider_name = "AWS" + region_name = "US_EAST_1" + priority = 7 + read_only_specs = { + node_count = 1 + instance_size = "M10" + } + analytics_specs = { + node_count = 2 + instance_size = "M10" + } + } + ] + } + ] + + # Generated by atlas-cli-plugin-terraform. + # Please review the changes and confirm that references to this resource are updated. } diff --git a/internal/convert/testdata/clu2adv/autoscaling.out.tf b/internal/convert/testdata/clu2adv/autoscaling.out.tf index c1bbddb..97ab2e6 100644 --- a/internal/convert/testdata/clu2adv/autoscaling.out.tf +++ b/internal/convert/testdata/clu2adv/autoscaling.out.tf @@ -36,5 +36,5 @@ resource "mongodbatlas_advanced_cluster" "autoscaling" { ] # Generated by atlas-cli-plugin-terraform. - # Please confirm that all references to this resource are updated. + # Please review the changes and confirm that references to this resource are updated. } diff --git a/internal/convert/testdata/clu2adv/data_sources.out.tf b/internal/convert/testdata/clu2adv/data_sources.out.tf index fb8561d..2ba949f 100644 --- a/internal/convert/testdata/clu2adv/data_sources.out.tf +++ b/internal/convert/testdata/clu2adv/data_sources.out.tf @@ -6,7 +6,7 @@ data "mongodbatlas_advanced_cluster" "singular" { use_replication_spec_per_shard = true # Generated by atlas-cli-plugin-terraform. - # Please confirm that all references to this resource are updated. + # Please review the changes and confirm that references to this resource are updated. } data "mongodbatlas_advanced_clusters" "plural" { @@ -15,7 +15,7 @@ data "mongodbatlas_advanced_clusters" "plural" { use_replication_spec_per_shard = true # Generated by atlas-cli-plugin-terraform. - # Please confirm that all references to this resource are updated. + # Please review the changes and confirm that references to this resource are updated. } data "mongodbatlas_advanced_cluster" "adv_singular" { diff --git a/internal/convert/testdata/clu2adv/dynamic_regions_config_auto_scaling.in.tf b/internal/convert/testdata/clu2adv/dynamic_regions_config_auto_scaling.in.tf new file mode 100644 index 0000000..bbc64bc --- /dev/null +++ b/internal/convert/testdata/clu2adv/dynamic_regions_config_auto_scaling.in.tf @@ -0,0 +1,67 @@ +resource "mongodbatlas_cluster" "cluster" { + project_id = var.project_id + name = var.cluster_name + cloud_backup = var.backup_enabled + pit_enabled = var.pit_enabled + retain_backups_enabled = var.retain_backups_enabled + auto_scaling_disk_gb_enabled = var.auto_scaling_disk_gb_enabled + mongo_db_major_version = var.mongodb_version + cluster_type = var.cluster_type + termination_protection_enabled = var.termination_protection_enabled + num_shards = var.replication_specs.num_shards + paused = var.paused + disk_size_gb = var.disk_size_gb + provider_volume_type = var.provider_volume_type + provider_disk_iops = var.provider_disk_iops + redact_client_log_data = true + provider_name = var.provider_name + provider_instance_size_name = var.provider_instance_size_name + encryption_at_rest_provider = var.encryption_at_rest_provider + + 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 + analytics_nodes = regions_config.value.analytics_nodes + priority = regions_config.value.priority + read_only_nodes = regions_config.value.read_only_nodes + } + } + } + + advanced_configuration { + oplog_size_mb = var.oplog_size_mb + transaction_lifetime_limit_seconds = var.transaction_lifetime_limit_seconds + minimum_enabled_tls_protocol = "TLS1_2" + javascript_enabled = false + tls_cipher_config_mode = "CUSTOM" + custom_openssl_cipher_config_tls12 = ["TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"] + } + + labels { + key = "label1" + value = var.label1val + } + dynamic "labels" { + for_each = local.labels + content { + key = labels.key + value = labels.value + } + } + + tags { + key = "tag1" + value = var.tag1val + } + dynamic "tags" { + for_each = local.tags + content { + key = tags.key + value = replace(tags.value, "/", "_") + } + } +} diff --git a/internal/convert/testdata/clu2adv/dynamic_regions_config_auto_scaling.out.tf b/internal/convert/testdata/clu2adv/dynamic_regions_config_auto_scaling.out.tf new file mode 100644 index 0000000..62bbd5f --- /dev/null +++ b/internal/convert/testdata/clu2adv/dynamic_regions_config_auto_scaling.out.tf @@ -0,0 +1,80 @@ +resource "mongodbatlas_advanced_cluster" "cluster" { + project_id = var.project_id + name = var.cluster_name + pit_enabled = var.pit_enabled + retain_backups_enabled = var.retain_backups_enabled + mongo_db_major_version = var.mongodb_version + cluster_type = var.cluster_type + termination_protection_enabled = var.termination_protection_enabled + paused = var.paused + redact_client_log_data = true + encryption_at_rest_provider = var.encryption_at_rest_provider + + + + + backup_enabled = var.backup_enabled + replication_specs = [ + for i in range(var.replication_specs.num_shards) : { + region_configs = flatten([ + # Regions must be sorted by priority in descending order. + for priority in range(7, 0, -1) : [ + for region in var.replication_specs.regions_config : { + provider_name = var.provider_name + region_name = region.region_name + priority = region.priority + electable_specs = region.electable_nodes > 0 ? { + node_count = region.electable_nodes + instance_size = var.provider_instance_size_name + disk_size_gb = var.disk_size_gb + ebs_volume_type = var.provider_volume_type + disk_iops = var.provider_disk_iops + } : null + read_only_specs = region.read_only_nodes > 0 ? { + node_count = region.read_only_nodes + instance_size = var.provider_instance_size_name + disk_size_gb = var.disk_size_gb + ebs_volume_type = var.provider_volume_type + disk_iops = var.provider_disk_iops + } : null + analytics_specs = region.analytics_nodes > 0 ? { + node_count = region.analytics_nodes + instance_size = var.provider_instance_size_name + disk_size_gb = var.disk_size_gb + ebs_volume_type = var.provider_volume_type + disk_iops = var.provider_disk_iops + } : null + auto_scaling = { + disk_gb_enabled = var.auto_scaling_disk_gb_enabled + } + } if priority == region.priority + ] + ]) + } + ] + tags = merge( + { + for key, value in local.tags : key => replace(value, "/", "_") + }, + { + tag1 = var.tag1val + } + ) + labels = merge( + local.labels, + { + label1 = var.label1val + } + ) + advanced_configuration = { + oplog_size_mb = var.oplog_size_mb + transaction_lifetime_limit_seconds = var.transaction_lifetime_limit_seconds + minimum_enabled_tls_protocol = "TLS1_2" + javascript_enabled = false + tls_cipher_config_mode = "CUSTOM" + custom_openssl_cipher_config_tls12 = ["TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"] + } + + # Generated by atlas-cli-plugin-terraform. + # Please review the changes and confirm that references to this resource are updated. +} diff --git a/internal/convert/testdata/clu2adv/dynamic_regions_config_basic.in.tf b/internal/convert/testdata/clu2adv/dynamic_regions_config_basic.in.tf new file mode 100644 index 0000000..5c5265b --- /dev/null +++ b/internal/convert/testdata/clu2adv/dynamic_regions_config_basic.in.tf @@ -0,0 +1,50 @@ +resource "mongodbatlas_cluster" "dynamic_regions_config" { + 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 + zone_name = var.zone_name + 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 + } + } + } +} + +# example of variable for demostration purposes, not used in the conversion +variable "replication_specs" { + type = object({ + num_shards = number + regions_config = set(object({ + region_name = string + electable_nodes = number + prio = number + read_only_nodes = number + })) + }) + default = { + num_shards = 3 + regions_config = [ + { + region_name = "US_EAST_1" + electable_nodes = 3 + prio = 7 + read_only_nodes = 0 + }, + { + region_name = "US_WEST_2" + electable_nodes = 2 + prio = 6 + read_only_nodes = 1 + } + ] + } +} diff --git a/internal/convert/testdata/clu2adv/dynamic_regions_config_basic.out.tf b/internal/convert/testdata/clu2adv/dynamic_regions_config_basic.out.tf new file mode 100644 index 0000000..a2f1050 --- /dev/null +++ b/internal/convert/testdata/clu2adv/dynamic_regions_config_basic.out.tf @@ -0,0 +1,61 @@ +resource "mongodbatlas_advanced_cluster" "dynamic_regions_config" { + project_id = var.project_id + name = "cluster" + cluster_type = "SHARDED" + replication_specs = [ + for i in range(var.replication_specs.num_shards) : { + zone_name = var.zone_name + region_configs = flatten([ + # Regions must be sorted by priority in descending order. + for priority in range(7, 0, -1) : [ + for region in var.replication_specs.regions_config : { + provider_name = "AWS" + region_name = region.region_name + priority = region.prio + electable_specs = region.electable_nodes > 0 ? { + node_count = region.electable_nodes + instance_size = "M10" + } : null + read_only_specs = region.read_only_nodes > 0 ? { + node_count = region.read_only_nodes + instance_size = "M10" + } : null + } if priority == region.prio + ] + ]) + } + ] + + # Generated by atlas-cli-plugin-terraform. + # Please review the changes and confirm that references to this resource are updated. +} + +# example of variable for demostration purposes, not used in the conversion +variable "replication_specs" { + type = object({ + num_shards = number + regions_config = set(object({ + region_name = string + electable_nodes = number + prio = number + read_only_nodes = number + })) + }) + default = { + num_shards = 3 + regions_config = [ + { + region_name = "US_EAST_1" + electable_nodes = 3 + prio = 7 + read_only_nodes = 0 + }, + { + region_name = "US_WEST_2" + electable_nodes = 2 + prio = 6 + read_only_nodes = 1 + } + ] + } +} diff --git a/internal/convert/testdata/clu2adv/dynamic_tags_labels.out.tf b/internal/convert/testdata/clu2adv/dynamic_tags_labels.out.tf index 3e78a8f..471ccf7 100644 --- a/internal/convert/testdata/clu2adv/dynamic_tags_labels.out.tf +++ b/internal/convert/testdata/clu2adv/dynamic_tags_labels.out.tf @@ -20,7 +20,7 @@ resource "mongodbatlas_advanced_cluster" "simplified" { tags = var.tags # Generated by atlas-cli-plugin-terraform. - # Please confirm that all references to this resource are updated. + # Please review the changes and confirm that references to this resource are updated. } resource "mongodbatlas_advanced_cluster" "expression" { @@ -47,7 +47,7 @@ resource "mongodbatlas_advanced_cluster" "expression" { } # Generated by atlas-cli-plugin-terraform. - # Please confirm that all references to this resource are updated. + # Please review the changes and confirm that references to this resource are updated. } resource "mongodbatlas_advanced_cluster" "simplified_individual" { @@ -78,7 +78,7 @@ resource "mongodbatlas_advanced_cluster" "simplified_individual" { ) # Generated by atlas-cli-plugin-terraform. - # Please confirm that all references to this resource are updated. + # Please review the changes and confirm that references to this resource are updated. } resource "mongodbatlas_advanced_cluster" "expression_individual" { @@ -111,7 +111,7 @@ resource "mongodbatlas_advanced_cluster" "expression_individual" { ) # Generated by atlas-cli-plugin-terraform. - # Please confirm that all references to this resource are updated. + # Please review the changes and confirm that references to this resource are updated. } resource "mongodbatlas_advanced_cluster" "full_example" { @@ -157,5 +157,5 @@ resource "mongodbatlas_advanced_cluster" "full_example" { ) # Generated by atlas-cli-plugin-terraform. - # Please confirm that all references to this resource are updated. + # Please review the changes and confirm that references to this resource are updated. } diff --git a/internal/convert/testdata/clu2adv/errors.json b/internal/convert/testdata/clu2adv/errors.json index 7a04706..fc81442 100644 --- a/internal/convert/testdata/clu2adv/errors.json +++ b/internal/convert/testdata/clu2adv/errors.json @@ -3,12 +3,8 @@ "free_cluster_missing_attribute": "free cluster (because no replication_specs): attribute backing_provider_name not found", "autoscaling_missing_attribute": "setting replication_specs: attribute provider_instance_size_name not found", "replication_specs_missing_regions_config": "setting replication_specs: regions_config not found", - "regions_config_missing_electable_nodes": "setting replication_specs: attribute electable_nodes not found", - "regions_config_missing_priority": "setting replication_specs: priority not found", - "regions_config_out_of_range_priority": "setting priority: priority is 0 but must be between 1 and 7", - "regions_config_non_literal_priority": "setting priority: failed to evaluate number", + "regions_config_missing_priority": "setting replication_specs: attribute priority not found", "replication_specs_unsupported_dynamic": "dynamic blocks are not supported", - "regions_config_unsupported_dynamic": "dynamic blocks are not supported", "replication_specs_non_literal_num_shards": "setting num_shards: failed to evaluate number", "replication_specs_missing_num_shards": "num_shards not found" } diff --git a/internal/convert/testdata/clu2adv/free_cluster_with_count.out.tf b/internal/convert/testdata/clu2adv/free_cluster_with_count.out.tf index c24b56f..c7dbdbf 100644 --- a/internal/convert/testdata/clu2adv/free_cluster_with_count.out.tf +++ b/internal/convert/testdata/clu2adv/free_cluster_with_count.out.tf @@ -26,5 +26,5 @@ resource "mongodbatlas_advanced_cluster" "free_cluster" { # comment in the resou ] # Generated by atlas-cli-plugin-terraform. - # Please confirm that all references to this resource are updated. + # Please review the changes and confirm that references to this resource are updated. } diff --git a/internal/convert/testdata/clu2adv/includeMoved_multiple.out.tf b/internal/convert/testdata/clu2adv/includeMoved_multiple.out.tf index 8a11ff4..436ca65 100644 --- a/internal/convert/testdata/clu2adv/includeMoved_multiple.out.tf +++ b/internal/convert/testdata/clu2adv/includeMoved_multiple.out.tf @@ -19,7 +19,7 @@ resource "mongodbatlas_advanced_cluster" "cluster1" { ] # Generated by atlas-cli-plugin-terraform. - # Please confirm that all references to this resource are updated. + # Please review the changes and confirm that references to this resource are updated. } resource "mongodbatlas_advanced_cluster" "cluster2" { @@ -43,7 +43,7 @@ resource "mongodbatlas_advanced_cluster" "cluster2" { ] # Generated by atlas-cli-plugin-terraform. - # Please confirm that all references to this resource are updated. + # Please review the changes and confirm that references to this resource are updated. } resource "mongodbatlas_advanced_cluster" "count" { @@ -69,7 +69,7 @@ resource "mongodbatlas_advanced_cluster" "count" { ] # Generated by atlas-cli-plugin-terraform. - # Please confirm that all references to this resource are updated. + # Please review the changes and confirm that references to this resource are updated. } resource "mongodbatlas_advanced_cluster" "forEach" { @@ -95,7 +95,7 @@ resource "mongodbatlas_advanced_cluster" "forEach" { ] # Generated by atlas-cli-plugin-terraform. - # Please confirm that all references to this resource are updated. + # Please review the changes and confirm that references to this resource are updated. } resource "another_resource" "another" { diff --git a/internal/convert/testdata/clu2adv/includeMoved_single.out.tf b/internal/convert/testdata/clu2adv/includeMoved_single.out.tf index 5a49d34..fc8c497 100644 --- a/internal/convert/testdata/clu2adv/includeMoved_single.out.tf +++ b/internal/convert/testdata/clu2adv/includeMoved_single.out.tf @@ -19,7 +19,7 @@ resource "mongodbatlas_advanced_cluster" "cluster" { ] # Generated by atlas-cli-plugin-terraform. - # Please confirm that all references to this resource are updated. + # Please review the changes and confirm that references to this resource are updated. } # Moved blocks diff --git a/internal/convert/testdata/clu2adv/multi_region.out.tf b/internal/convert/testdata/clu2adv/multi_region.out.tf index facda3c..b7bbd54 100644 --- a/internal/convert/testdata/clu2adv/multi_region.out.tf +++ b/internal/convert/testdata/clu2adv/multi_region.out.tf @@ -43,5 +43,5 @@ resource "mongodbatlas_advanced_cluster" "multi_region" { ] # Generated by atlas-cli-plugin-terraform. - # Please confirm that all references to this resource are updated. + # Please review the changes and confirm that references to this resource are updated. } diff --git a/internal/convert/testdata/clu2adv/multi_replicaction_specs.out.tf b/internal/convert/testdata/clu2adv/multi_replicaction_specs.out.tf index 290011d..cbb3526 100644 --- a/internal/convert/testdata/clu2adv/multi_replicaction_specs.out.tf +++ b/internal/convert/testdata/clu2adv/multi_replicaction_specs.out.tf @@ -37,7 +37,7 @@ resource "mongodbatlas_advanced_cluster" "multirep" { ] # Generated by atlas-cli-plugin-terraform. - # Please confirm that all references to this resource are updated. + # Please review the changes and confirm that references to this resource are updated. } resource "mongodbatlas_advanced_cluster" "geo" { @@ -124,5 +124,5 @@ resource "mongodbatlas_advanced_cluster" "geo" { ] # Generated by atlas-cli-plugin-terraform. - # Please confirm that all references to this resource are updated. + # Please review the changes and confirm that references to this resource are updated. } diff --git a/internal/convert/testdata/clu2adv/regions_config_missing_electable_nodes.in.tf b/internal/convert/testdata/clu2adv/regions_config_missing_electable_nodes.in.tf deleted file mode 100644 index f35c870..0000000 --- a/internal/convert/testdata/clu2adv/regions_config_missing_electable_nodes.in.tf +++ /dev/null @@ -1,17 +0,0 @@ -resource "mongodbatlas_cluster" "clu" { - project_id = "1234" - name = "clu" - cluster_type = "REPLICASET" - provider_name = "AWS" - provider_instance_size_name = "M10" - - replication_specs { - num_shards = 1 - regions_config { - # missing electable_nodes - region_name = "US_WEST_2" - priority = 7 - read_only_nodes = 2 - } - } -} diff --git a/internal/convert/testdata/clu2adv/regions_config_non_literal_priority.in.tf b/internal/convert/testdata/clu2adv/regions_config_non_literal_priority.in.tf deleted file mode 100644 index 590febe..0000000 --- a/internal/convert/testdata/clu2adv/regions_config_non_literal_priority.in.tf +++ /dev/null @@ -1,16 +0,0 @@ -resource "mongodbatlas_cluster" "clu" { - project_id = "1234" - name = "clu" - cluster_type = "REPLICASET" - provider_name = "AWS" - provider_instance_size_name = "M10" - - replication_specs { - num_shards = 1 - regions_config { - region_name = "US_WEST_2" - priority = var.priority # unresolved - electable_nodes = 2 - } - } -} diff --git a/internal/convert/testdata/clu2adv/regions_config_out_of_range_priority.in.tf b/internal/convert/testdata/clu2adv/regions_config_out_of_range_priority.in.tf deleted file mode 100644 index e1fe04b..0000000 --- a/internal/convert/testdata/clu2adv/regions_config_out_of_range_priority.in.tf +++ /dev/null @@ -1,16 +0,0 @@ -resource "mongodbatlas_cluster" "clu" { - project_id = "1234" - name = "clu" - cluster_type = "REPLICASET" - provider_name = "AWS" - provider_instance_size_name = "M10" - - replication_specs { - num_shards = 1 - regions_config { - region_name = "US_WEST_2" - priority = 0 # range 1-7 - electable_nodes = 2 - } - } -} diff --git a/internal/convert/testdata/clu2adv/regions_config_unsupported_dynamic.in.tf b/internal/convert/testdata/clu2adv/regions_config_unsupported_dynamic.in.tf deleted file mode 100644 index 7934936..0000000 --- a/internal/convert/testdata/clu2adv/regions_config_unsupported_dynamic.in.tf +++ /dev/null @@ -1,38 +0,0 @@ -resource "mongodbatlas_cluster" "dynamic_region" { - project_id = var.project_id - name = "dynamic" - num_shards = 1 - cluster_type = "REPLICASET" - provider_name = "AWS" - provider_instance_size_name = "M10" - - replication_specs { - num_shards = 1 - dynamic "regions_config" { - for_each = { - US_WEST_2 = { - electable_nodes = 3 - priority = 6 - read_only_nodes = 0 - } - US_WEST_1 = { - electable_nodes = 1 - priority = 5 - read_only_nodes = 0 - } - US_EAST_1 = { - electable_nodes = 3 - priority = 7 - read_only_nodes = 0 - } - } - content { - region_name = regions_config.key - electable_nodes = regions_config.value.electable_nodes - priority = regions_config.value.priority - read_only_nodes = regions_config.value.read_only_nodes - } - - } - } -} diff --git a/internal/convert/testdata/clu2adv/tags_labels_timeouts.out.tf b/internal/convert/testdata/clu2adv/tags_labels_timeouts.out.tf index 94bf061..9127502 100644 --- a/internal/convert/testdata/clu2adv/tags_labels_timeouts.out.tf +++ b/internal/convert/testdata/clu2adv/tags_labels_timeouts.out.tf @@ -22,7 +22,7 @@ resource "mongodbatlas_advanced_cluster" "basictags" { } # Generated by atlas-cli-plugin-terraform. - # Please confirm that all references to this resource are updated. + # Please review the changes and confirm that references to this resource are updated. } resource "mongodbatlas_advanced_cluster" "basictimeouts" { @@ -49,7 +49,7 @@ resource "mongodbatlas_advanced_cluster" "basictimeouts" { } # Generated by atlas-cli-plugin-terraform. - # Please confirm that all references to this resource are updated. + # Please review the changes and confirm that references to this resource are updated. } resource "mongodbatlas_advanced_cluster" "all" { @@ -88,5 +88,5 @@ resource "mongodbatlas_advanced_cluster" "all" { } # Generated by atlas-cli-plugin-terraform. - # Please confirm that all references to this resource are updated. + # Please review the changes and confirm that references to this resource are updated. } diff --git a/internal/hcl/hcl.go b/internal/hcl/hcl.go index 46f661c..8b71cfb 100644 --- a/internal/hcl/hcl.go +++ b/internal/hcl/hcl.go @@ -31,14 +31,11 @@ func PopAttr(body *hclwrite.Body, attrName, errPrefix string) (hclwrite.Tokens, return tokens, nil } -// SetAttrExpr sets an attribute to an expression (possibly with interpolations) without quotes. -func SetAttrExpr(body *hclwrite.Body, attrName, expresion string) { - tokens := hclwrite.Tokens{{Type: hclsyntax.TokenIdent, Bytes: []byte(expresion)}} - body.SetAttributeRaw(attrName, tokens) -} - // GetAttrExpr returns the expression of an attribute as a string. func GetAttrExpr(attr *hclwrite.Attribute) string { + if attr == nil { + return "" + } return strings.TrimSpace(string(attr.Expr().BuildTokens(nil).Bytes())) } @@ -85,14 +82,11 @@ func GetAttrString(attr *hclwrite.Attribute, errPrefix string) (string, error) { // TokensArray creates an array of objects. func TokensArray(bodies []*hclwrite.Body) hclwrite.Tokens { - ret := hclwrite.Tokens{tokenNewLine} tokens := make([]hclwrite.Tokens, 0) for i := range bodies { tokens = append(tokens, TokensObject(bodies[i])) } - ret = append(ret, joinTokens(tokens...)...) - ret = append(ret, tokenNewLine) - return encloseBrackets(ret) + return EncloseBracketsNewLines(joinTokens(tokens...)) } // TokensArraySingle creates an array of one object. @@ -104,7 +98,7 @@ func TokensArraySingle(body *hclwrite.Body) hclwrite.Tokens { func TokensObject(body *hclwrite.Body) hclwrite.Tokens { tokens := hclwrite.Tokens{tokenNewLine} tokens = append(tokens, RemoveLeadingNewline(body.BuildTokens(nil))...) - return encloseBraces(tokens) + return EncloseBraces(tokens, false) } // TokensFromExpr creates the tokens for an expression provided as a string. @@ -114,19 +108,20 @@ func TokensFromExpr(expr string) hclwrite.Tokens { // TokensObjectFromExpr creates an object with an expression. func TokensObjectFromExpr(expr string) hclwrite.Tokens { - tokens := hclwrite.Tokens{tokenNewLine} - tokens = append(tokens, TokensFromExpr(expr)...) - tokens = append(tokens, tokenNewLine) - return encloseBraces(tokens) + return EncloseBraces(EncloseNewLines(TokensFromExpr(expr)), false) } // TokensFuncMerge creates the tokens for the HCL merge function. func TokensFuncMerge(tokens ...hclwrite.Tokens) hclwrite.Tokens { - params := hclwrite.Tokens{tokenNewLine} - params = append(params, joinTokens(tokens...)...) - params = append(params, tokenNewLine) - ret := hclwrite.Tokens{{Type: hclsyntax.TokenIdent, Bytes: []byte("merge")}} - return append(ret, encloseParens(params)...) + params := EncloseNewLines(joinTokens(tokens...)) + ret := TokensFromExpr("merge") + return append(ret, EncloseParens(params)...) +} + +// TokensFuncFlatten creates the tokens for the HCL flatten function. +func TokensFuncFlatten(tokens hclwrite.Tokens) hclwrite.Tokens { + ret := TokensFromExpr("flatten") + return append(ret, EncloseParens(EncloseBracketsNewLines(tokens))...) } // RemoveLeadingNewline removes the first newline if it exists to make the output prettier. @@ -139,10 +134,14 @@ func RemoveLeadingNewline(tokens hclwrite.Tokens) hclwrite.Tokens { // AppendComment adds a comment at the end of the body. func AppendComment(body *hclwrite.Body, comment string) { - tokens := hclwrite.Tokens{ + body.AppendUnstructuredTokens(TokensComment(comment)) +} + +// TokensComment returns the tokens for a comment. +func TokensComment(comment string) hclwrite.Tokens { + return hclwrite.Tokens{ &hclwrite.Token{Type: hclsyntax.TokenComment, Bytes: []byte("# " + comment + "\n")}, } - body.AppendUnstructuredTokens(tokens) } // GetParser returns a parser for the given config and checks HCL syntax is valid @@ -154,39 +153,52 @@ func GetParser(config []byte) (*hclwrite.File, error) { return parser, nil } -var tokenNewLine = &hclwrite.Token{Type: hclsyntax.TokenNewline, Bytes: []byte("\n")} - // joinTokens joins multiple tokens with commas and newlines. func joinTokens(tokens ...hclwrite.Tokens) hclwrite.Tokens { ret := hclwrite.Tokens{} for i := range tokens { ret = append(ret, tokens[i]...) if i < len(tokens)-1 { - ret = append(ret, - &hclwrite.Token{Type: hclsyntax.TokenComma, Bytes: []byte(",")}, - tokenNewLine) + ret = append(ret, &hclwrite.Token{Type: hclsyntax.TokenComma, Bytes: []byte(",")}, tokenNewLine) } } return ret } -// encloseParens encloses tokens with parentheses, ( ). -func encloseParens(tokens hclwrite.Tokens) hclwrite.Tokens { +// EncloseParens encloses tokens with parentheses, ( ). +func EncloseParens(tokens hclwrite.Tokens) hclwrite.Tokens { ret := hclwrite.Tokens{{Type: hclsyntax.TokenOParen, Bytes: []byte("(")}} ret = append(ret, tokens...) return append(ret, &hclwrite.Token{Type: hclsyntax.TokenCParen, Bytes: []byte(")")}) } -// encloseBraces encloses tokens with curly braces, { }. -func encloseBraces(tokens hclwrite.Tokens) hclwrite.Tokens { +// EncloseBraces encloses tokens with curly braces, { }. +func EncloseBraces(tokens hclwrite.Tokens, initialNewLine bool) hclwrite.Tokens { ret := hclwrite.Tokens{{Type: hclsyntax.TokenOBrace, Bytes: []byte("{")}} + if initialNewLine { + ret = append(ret, tokenNewLine) + } ret = append(ret, tokens...) return append(ret, &hclwrite.Token{Type: hclsyntax.TokenCBrace, Bytes: []byte("}")}) } -// encloseBrackets encloses tokens with square brackets, [ ]. -func encloseBrackets(tokens hclwrite.Tokens) hclwrite.Tokens { +// EncloseBrackets encloses tokens with square brackets, [ ]. +func EncloseBrackets(tokens hclwrite.Tokens) hclwrite.Tokens { ret := hclwrite.Tokens{{Type: hclsyntax.TokenOBrack, Bytes: []byte("[")}} ret = append(ret, tokens...) return append(ret, &hclwrite.Token{Type: hclsyntax.TokenCBrack, Bytes: []byte("]")}) } + +// EncloseNewLines encloses tokens with newlines at the beginning and end. +func EncloseNewLines(tokens hclwrite.Tokens) hclwrite.Tokens { + ret := hclwrite.Tokens{tokenNewLine} + ret = append(ret, tokens...) + return append(ret, tokenNewLine) +} + +// EncloseBracketsNewLines encloses tokens with square brackets and newlines, [ \n ... \n ]. +func EncloseBracketsNewLines(tokens hclwrite.Tokens) hclwrite.Tokens { + return EncloseBrackets(EncloseNewLines(tokens)) +} + +var tokenNewLine = &hclwrite.Token{Type: hclsyntax.TokenNewline, Bytes: []byte("\n")} diff --git a/test/e2e/testdata/clu2adv.expected.tf b/test/e2e/testdata/clu2adv.expected.tf index 684dafe..0544aee 100644 --- a/test/e2e/testdata/clu2adv.expected.tf +++ b/test/e2e/testdata/clu2adv.expected.tf @@ -22,5 +22,5 @@ resource "mongodbatlas_advanced_cluster" "cluster" { } # Generated by atlas-cli-plugin-terraform. - # Please confirm that all references to this resource are updated. + # Please review the changes and confirm that references to this resource are updated. } diff --git a/test/e2e/testdata/clu2adv.expected_moved.tf b/test/e2e/testdata/clu2adv.expected_moved.tf index 6ef84c3..5fbfb91 100644 --- a/test/e2e/testdata/clu2adv.expected_moved.tf +++ b/test/e2e/testdata/clu2adv.expected_moved.tf @@ -22,7 +22,7 @@ resource "mongodbatlas_advanced_cluster" "cluster" { } # Generated by atlas-cli-plugin-terraform. - # Please confirm that all references to this resource are updated. + # Please review the changes and confirm that references to this resource are updated. } # Moved blocks