Skip to content

Commit 67308ea

Browse files
lantoliEspenAlbert
andauthored
feat: Convert tags, labels, timeouts, advanced_configuration, bi_connector_config and pinned_fcv (#21)
* basic tags * improve example * simple timeouts * add labels * fix quotes * fix timeout empty * arrays brackets in new lines * add advanced configuration support and refactor timeout handling * Block always as first param * bi_connector * pinn_fcv * allow unresolved expressions in keys in tags and labels * doc clarification * readme --------- Co-authored-by: Espen Albert <[email protected]>
1 parent 5e717b7 commit 67308ea

13 files changed

+502
-136
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22

33
[![Code Health](https://github.com/mongodb-labs/atlas-cli-plugin-terraform/actions/workflows/code-health.yml/badge.svg)](https://github.com/mongodb-labs/atlas-cli-plugin-terraform/actions/workflows/code-health.yml)
44

5-
This repository contains the Atlas CLI plugin for Terraform's MongoDB Atlas Provider.
5+
This repository contains the Atlas CLI plugin for [Terraform's MongoDB Atlas Provider](https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs).
66

7-
WIP
7+
It has the following commands to help with your Terraform configurations:
8+
- **clusterToAdvancedCluster**: Convert a `mongodbatlas_cluster` Terraform configuration to `mongodbatlas_advanced_cluster` (preview provider v2).
89

9-
## Installing
10+
## Installation
1011

1112
Install the [Atlas CLI](https://github.com/mongodb/mongodb-atlas-cli) if you haven't done it yet.
1213

@@ -37,7 +38,6 @@ If you want to overwrite the output file if it exists, or even use the same outp
3738
- `priority` is required in `regions_config` and must be a resolved number 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`.
3839
- `dynamic` blocks to generate `replication_specs`, `regions_config`, etc. are not supported.
3940

40-
4141
## Contributing
4242

4343
See our [CONTRIBUTING.md](CONTRIBUTING.md) guide.

internal/convert/const_names.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ const (
44
nRepSpecs = "replication_specs"
55
nConfig = "region_configs"
66
nConfigSrc = "regions_config"
7+
nTags = "tags"
8+
nLabels = "labels"
9+
nTimeouts = "timeouts"
10+
nAdvConf = "advanced_configuration"
11+
nPinnedFCV = "pinned_fcv"
12+
nBiConnector = "bi_connector_config"
713
nElectableSpecs = "electable_specs"
814
nAutoScaling = "auto_scaling"
915
nReadOnlySpecs = "read_only_specs"
@@ -38,4 +44,6 @@ const (
3844
nElectableNodes = "electable_nodes"
3945
nReadOnlyNodes = "read_only_nodes"
4046
nAnalyticsNodes = "analytics_nodes"
47+
nKey = "key"
48+
nValue = "value"
4149
)

internal/convert/convert.go

Lines changed: 85 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import (
44
"errors"
55
"fmt"
66
"sort"
7+
"strconv"
8+
"strings"
79

10+
"github.com/hashicorp/hcl/v2/hclsyntax"
811
"github.com/hashicorp/hcl/v2/hclwrite"
912
"github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/hcl"
1013
"github.com/zclconf/go-cty/cty"
@@ -86,11 +89,11 @@ func fillFreeTier(resourceb *hclwrite.Body) error {
8689
if err := hcl.MoveAttr(resourceb, electableSpec.Body(), nInstanceSizeSrc, nInstanceSize, errFreeCluster); err != nil {
8790
return err
8891
}
89-
configb.SetAttributeRaw(nElectableSpecs, hcl.TokensObject(electableSpec))
92+
configb.SetAttributeRaw(nElectableSpecs, hcl.TokensObject(electableSpec.Body()))
9093

9194
repSpecs := hclwrite.NewEmptyFile()
92-
repSpecs.Body().SetAttributeRaw(nConfig, hcl.TokensArraySingle(config))
93-
resourceb.SetAttributeRaw(nRepSpecs, hcl.TokensArraySingle(repSpecs))
95+
repSpecs.Body().SetAttributeRaw(nConfig, hcl.TokensArraySingle(configb))
96+
resourceb.SetAttributeRaw(nRepSpecs, hcl.TokensArraySingle(repSpecs.Body()))
9497
return nil
9598
}
9699

@@ -115,14 +118,31 @@ func fillReplicationSpecs(resourceb *hclwrite.Body) error {
115118
}
116119
repSpecs := hclwrite.NewEmptyFile()
117120
repSpecs.Body().SetAttributeRaw(nConfig, configs)
118-
119-
resourceb.SetAttributeRaw(nRepSpecs, hcl.TokensArraySingle(repSpecs))
121+
resourceb.SetAttributeRaw(nRepSpecs, hcl.TokensArraySingle(repSpecs.Body()))
122+
tags, errTags := getTagsLabelsOpt(resourceb, nTags)
123+
if errTags != nil {
124+
return errTags
125+
}
126+
if tags != nil {
127+
resourceb.SetAttributeRaw(nTags, tags)
128+
}
129+
labels, errLabels := getTagsLabelsOpt(resourceb, nLabels)
130+
if errLabels != nil {
131+
return errLabels
132+
}
133+
if labels != nil {
134+
resourceb.SetAttributeRaw(nLabels, labels)
135+
}
136+
fillBlockOpt(resourceb, nTimeouts)
137+
fillBlockOpt(resourceb, nAdvConf)
138+
fillBlockOpt(resourceb, nBiConnector)
139+
fillBlockOpt(resourceb, nPinnedFCV)
120140
resourceb.RemoveBlock(repSpecsSrc)
121141
return nil
122142
}
123143

124144
func getRegionConfigs(repSpecsSrc *hclwrite.Block, root attrVals) (hclwrite.Tokens, error) {
125-
var configs []*hclwrite.File
145+
var configs []*hclwrite.Body
126146
for {
127147
configSrc := repSpecsSrc.Body().FirstMatchingBlock(nConfigSrc, nil)
128148
if configSrc == nil {
@@ -132,15 +152,15 @@ func getRegionConfigs(repSpecsSrc *hclwrite.Block, root attrVals) (hclwrite.Toke
132152
if err != nil {
133153
return nil, err
134154
}
135-
configs = append(configs, config)
155+
configs = append(configs, config.Body())
136156
repSpecsSrc.Body().RemoveBlock(configSrc)
137157
}
138158
if len(configs) == 0 {
139159
return nil, fmt.Errorf("%s: %s not found", errRepSpecs, nConfigSrc)
140160
}
141161
sort.Slice(configs, func(i, j int) bool {
142-
pi, _ := hcl.GetAttrInt(configs[i].Body().GetAttribute(nPriority), errPriority)
143-
pj, _ := hcl.GetAttrInt(configs[j].Body().GetAttribute(nPriority), errPriority)
162+
pi, _ := hcl.GetAttrInt(configs[i].GetAttribute(nPriority), errPriority)
163+
pj, _ := hcl.GetAttrInt(configs[j].GetAttribute(nPriority), errPriority)
144164
return pi > pj
145165
})
146166
return hcl.TokensArray(configs), nil
@@ -156,15 +176,15 @@ func getRegionConfig(configSrc *hclwrite.Block, root attrVals) (*hclwrite.File,
156176
if err := setPriority(fileb, configSrc.Body().GetAttribute(nPriority)); err != nil {
157177
return nil, err
158178
}
159-
electableSpecs, errElec := getSpecs(nElectableNodes, configSrc, root)
179+
electableSpecs, errElec := getSpecs(configSrc, nElectableNodes, root)
160180
if errElec != nil {
161181
return nil, errElec
162182
}
163183
fileb.SetAttributeRaw(nElectableSpecs, electableSpecs)
164-
if readOnly, _ := getSpecs(nReadOnlyNodes, configSrc, root); readOnly != nil {
184+
if readOnly, _ := getSpecs(configSrc, nReadOnlyNodes, root); readOnly != nil {
165185
fileb.SetAttributeRaw(nReadOnlySpecs, readOnly)
166186
}
167-
if analytics, _ := getSpecs(nAnalyticsNodes, configSrc, root); analytics != nil {
187+
if analytics, _ := getSpecs(configSrc, nAnalyticsNodes, root); analytics != nil {
168188
fileb.SetAttributeRaw(nAnalyticsSpecs, analytics)
169189
}
170190
if autoScaling := getAutoScalingOpt(root.opt); autoScaling != nil {
@@ -173,7 +193,7 @@ func getRegionConfig(configSrc *hclwrite.Block, root attrVals) (*hclwrite.File,
173193
return file, nil
174194
}
175195

176-
func getSpecs(countName string, configSrc *hclwrite.Block, root attrVals) (hclwrite.Tokens, error) {
196+
func getSpecs(configSrc *hclwrite.Block, countName string, root attrVals) (hclwrite.Tokens, error) {
177197
var (
178198
file = hclwrite.NewEmptyFile()
179199
fileb = file.Body()
@@ -196,7 +216,7 @@ func getSpecs(countName string, configSrc *hclwrite.Block, root attrVals) (hclwr
196216
if root.opt[nDiskIOPSSrc] != nil {
197217
fileb.SetAttributeRaw(nDiskIOPS, root.opt[nDiskIOPSSrc])
198218
}
199-
return hcl.TokensObject(file), nil
219+
return hcl.TokensObject(fileb), nil
200220
}
201221

202222
func getAutoScalingOpt(opt map[string]hclwrite.Tokens) hclwrite.Tokens {
@@ -209,19 +229,55 @@ func getAutoScalingOpt(opt map[string]hclwrite.Tokens) hclwrite.Tokens {
209229
{nComputeScaleDownEnabledSrc, nComputeScaleDownEnabled},
210230
}
211231
file = hclwrite.NewEmptyFile()
232+
fileb = file.Body()
212233
found = false
213234
)
214235
for _, tuple := range names {
215236
src, dst := tuple[0], tuple[1]
216237
if tokens := opt[src]; tokens != nil {
217-
file.Body().SetAttributeRaw(dst, tokens)
238+
fileb.SetAttributeRaw(dst, tokens)
218239
found = true
219240
}
220241
}
221242
if !found {
222243
return nil
223244
}
224-
return hcl.TokensObject(file)
245+
return hcl.TokensObject(fileb)
246+
}
247+
248+
func getTagsLabelsOpt(resourceb *hclwrite.Body, name string) (hclwrite.Tokens, error) {
249+
var (
250+
file = hclwrite.NewEmptyFile()
251+
fileb = file.Body()
252+
found = false
253+
)
254+
for {
255+
block := resourceb.FirstMatchingBlock(name, nil)
256+
if block == nil {
257+
break
258+
}
259+
key := block.Body().GetAttribute(nKey)
260+
value := block.Body().GetAttribute(nValue)
261+
if key == nil || value == nil {
262+
return nil, fmt.Errorf("%s: %s or %s not found", name, nKey, nValue)
263+
}
264+
setKeyValue(fileb, key, value)
265+
resourceb.RemoveBlock(block)
266+
found = true
267+
}
268+
if !found {
269+
return nil, nil
270+
}
271+
return hcl.TokensObject(fileb), nil
272+
}
273+
274+
func fillBlockOpt(resourceb *hclwrite.Body, name string) {
275+
block := resourceb.FirstMatchingBlock(name, nil)
276+
if block == nil {
277+
return
278+
}
279+
resourceb.RemoveBlock(block)
280+
resourceb.SetAttributeRaw(name, hcl.TokensObject(block.Body()))
225281
}
226282

227283
func checkDynamicBlock(body *hclwrite.Body) error {
@@ -233,6 +289,19 @@ func checkDynamicBlock(body *hclwrite.Body) error {
233289
return nil
234290
}
235291

292+
func setKeyValue(body *hclwrite.Body, key, value *hclwrite.Attribute) {
293+
keyStr, err := hcl.GetAttrString(key, "")
294+
if err == nil {
295+
if !hclsyntax.ValidIdentifier(keyStr) {
296+
keyStr = strconv.Quote(keyStr) // wrap in quotes so invalid identifiers (e.g. with blanks) can be used as attribute names
297+
}
298+
} else {
299+
keyStr = strings.TrimSpace(string(key.Expr().BuildTokens(nil).Bytes()))
300+
keyStr = "(" + keyStr + ")" // wrap in parentheses so unresolved expressions can be used as attribute names
301+
}
302+
body.SetAttributeRaw(keyStr, value.Expr().BuildTokens(nil))
303+
}
304+
236305
func setPriority(body *hclwrite.Body, priority *hclwrite.Attribute) error {
237306
if priority == nil {
238307
return fmt.Errorf("%s: %s not found", errRepSpecs, nPriority)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
resource "mongodbatlas_cluster" "this" {
2+
project_id = var.project_id
3+
name = var.cluster_name
4+
cluster_type = "REPLICASET"
5+
provider_name = "AWS"
6+
provider_instance_size_name = var.instance_size
7+
advanced_configuration {
8+
# comments in advanced_configuration are kept
9+
javascript_enabled = true
10+
}
11+
bi_connector_config {
12+
# comments in bi_connector_config are kept
13+
enabled = true
14+
read_preference = "secondary"
15+
}
16+
pinned_fcv {
17+
# comments in pinned_fcv are kept
18+
expiration_date = var.fcv_date
19+
version = var.fcv_date
20+
}
21+
replication_specs {
22+
num_shards = 1
23+
regions_config {
24+
region_name = "US_WEST_1"
25+
electable_nodes = 2
26+
priority = 7
27+
}
28+
}
29+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
resource "mongodbatlas_advanced_cluster" "this" {
2+
project_id = var.project_id
3+
name = var.cluster_name
4+
cluster_type = "REPLICASET"
5+
replication_specs = [
6+
{
7+
region_configs = [
8+
{
9+
provider_name = "AWS"
10+
region_name = "US_WEST_1"
11+
priority = 7
12+
electable_specs = {
13+
node_count = 2
14+
instance_size = var.instance_size
15+
}
16+
}
17+
]
18+
}
19+
]
20+
advanced_configuration = {
21+
# comments in advanced_configuration are kept
22+
javascript_enabled = true
23+
}
24+
bi_connector_config = {
25+
# comments in bi_connector_config are kept
26+
enabled = true
27+
read_preference = "secondary"
28+
}
29+
pinned_fcv = {
30+
# comments in pinned_fcv are kept
31+
expiration_date = var.fcv_date
32+
version = var.fcv_date
33+
}
34+
35+
# Generated by atlas-cli-plugin-terraform.
36+
# Please confirm that all references to this resource are updated.
37+
}

internal/convert/testdata/clu2adv/analytics_read_only_all_params.out.tf

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,38 @@ resource "mongodbatlas_advanced_cluster" "ar" {
22
project_id = var.project_id
33
name = "ar"
44
cluster_type = "REPLICASET"
5-
replication_specs = [{
6-
region_configs = [{
7-
provider_name = "AWS"
8-
region_name = "US_EAST_1"
9-
priority = 7
10-
electable_specs = {
11-
node_count = 3
12-
instance_size = "M10"
13-
disk_size_gb = 90
14-
ebs_volume_type = "PROVISIONED"
15-
disk_iops = 100
16-
}
17-
read_only_specs = {
18-
node_count = 1
19-
instance_size = "M10"
20-
disk_size_gb = 90
21-
ebs_volume_type = "PROVISIONED"
22-
disk_iops = 100
23-
}
24-
analytics_specs = {
25-
node_count = 2
26-
instance_size = "M10"
27-
disk_size_gb = 90
28-
ebs_volume_type = "PROVISIONED"
29-
disk_iops = 100
30-
}
31-
}]
32-
}]
5+
replication_specs = [
6+
{
7+
region_configs = [
8+
{
9+
provider_name = "AWS"
10+
region_name = "US_EAST_1"
11+
priority = 7
12+
electable_specs = {
13+
node_count = 3
14+
instance_size = "M10"
15+
disk_size_gb = 90
16+
ebs_volume_type = "PROVISIONED"
17+
disk_iops = 100
18+
}
19+
read_only_specs = {
20+
node_count = 1
21+
instance_size = "M10"
22+
disk_size_gb = 90
23+
ebs_volume_type = "PROVISIONED"
24+
disk_iops = 100
25+
}
26+
analytics_specs = {
27+
node_count = 2
28+
instance_size = "M10"
29+
disk_size_gb = 90
30+
ebs_volume_type = "PROVISIONED"
31+
disk_iops = 100
32+
}
33+
}
34+
]
35+
}
36+
]
3337

3438
# Generated by atlas-cli-plugin-terraform.
3539
# Please confirm that all references to this resource are updated.

0 commit comments

Comments
 (0)