Skip to content

Commit a1f7bd4

Browse files
committed
extract convert package
1 parent b7e4ee3 commit a1f7bd4

12 files changed

+262
-249
lines changed

internal/cli/clu2adv/opts.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ package clu2adv
33
import (
44
"fmt"
55

6+
"github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/convert"
67
"github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/file"
7-
"github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/hcl"
88
"github.com/spf13/afero"
99
)
1010

@@ -30,7 +30,7 @@ func (o *opts) Run() error {
3030
if err != nil {
3131
return fmt.Errorf("failed to read file %s: %w", o.file, err)
3232
}
33-
outConfig, err := hcl.ClusterToAdvancedCluster(inConfig)
33+
outConfig, err := convert.ClusterToAdvancedCluster(inConfig)
3434
if err != nil {
3535
return err
3636
}

internal/convert/convert.go

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
package convert
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/hashicorp/hcl/v2/hclwrite"
7+
"github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/hcl"
8+
"github.com/zclconf/go-cty/cty"
9+
)
10+
11+
// ClusterToAdvancedCluster transforms all mongodbatlas_cluster definitions in a
12+
// Terraform configuration file into mongodbatlas_advanced_cluster schema v2 definitions.
13+
// All other resources and data sources are left untouched.
14+
// Note: hclwrite.Tokens are used instead of cty.Value so expressions like var.region can be preserved.
15+
// cty.Value only supports resolved values.
16+
func ClusterToAdvancedCluster(config []byte) ([]byte, error) {
17+
parser, err := hcl.GetParser(config)
18+
if err != nil {
19+
return nil, err
20+
}
21+
for _, resource := range parser.Body().Blocks() {
22+
labels := resource.Labels()
23+
resourceName := labels[0]
24+
if resource.Type() != resourceType || resourceName != cluster {
25+
continue
26+
}
27+
resourceb := resource.Body()
28+
labels[0] = advCluster
29+
resource.SetLabels(labels)
30+
31+
if resourceb.FirstMatchingBlock(nRepSpecs, nil) != nil {
32+
err = fillReplicationSpecs(resourceb)
33+
} else {
34+
err = fillFreeTier(resourceb)
35+
}
36+
if err != nil {
37+
return nil, err
38+
}
39+
40+
resourceb.AppendNewline()
41+
hcl.AppendComment(resourceb, "Generated by atlas-cli-plugin-terraform.")
42+
hcl.AppendComment(resourceb, "Please confirm that all references to this resource are updated.")
43+
}
44+
return parser.Bytes(), nil
45+
}
46+
47+
// fillFreeTier is the entry point to convert clusters in free tier
48+
func fillFreeTier(resourceb *hclwrite.Body) error {
49+
resourceb.SetAttributeValue(nClusterType, cty.StringVal(valClusterType))
50+
config := hclwrite.NewEmptyFile()
51+
configb := config.Body()
52+
hcl.SetAttrInt(configb, "priority", valPriority)
53+
if err := hcl.MoveAttr(resourceb, configb, nRegionNameSrc, nRegionName, errFreeCluster); err != nil {
54+
return err
55+
}
56+
if err := hcl.MoveAttr(resourceb, configb, nProviderName, nProviderName, errFreeCluster); err != nil {
57+
return err
58+
}
59+
if err := hcl.MoveAttr(resourceb, configb, nBackingProviderName, nBackingProviderName, errFreeCluster); err != nil {
60+
return err
61+
}
62+
electableSpec := hclwrite.NewEmptyFile()
63+
if err := hcl.MoveAttr(resourceb, electableSpec.Body(), nInstanceSizeSrc, nInstanceSize, errFreeCluster); err != nil {
64+
return err
65+
}
66+
configb.SetAttributeRaw(nElectableSpecs, hcl.TokensObject(electableSpec))
67+
68+
repSpecs := hclwrite.NewEmptyFile()
69+
repSpecs.Body().SetAttributeRaw(nConfig, hcl.TokensArrayObject(config))
70+
resourceb.SetAttributeRaw(nRepSpecs, hcl.TokensArrayObject(repSpecs))
71+
return nil
72+
}
73+
74+
// fillReplicationSpecs is the entry point to convert clusters with replications_specs (all but free tier)
75+
func fillReplicationSpecs(resourceb *hclwrite.Body) error {
76+
root, errRoot := popRootAttrs(resourceb, errRepSpecs)
77+
if errRoot != nil {
78+
return errRoot
79+
}
80+
repSpecsSrc := resourceb.FirstMatchingBlock(nRepSpecs, nil)
81+
configSrc := repSpecsSrc.Body().FirstMatchingBlock(nConfigSrc, nil)
82+
if configSrc == nil {
83+
return fmt.Errorf("%s: %s not found", errRepSpecs, nConfigSrc)
84+
}
85+
86+
resourceb.RemoveAttribute(nNumShards) // num_shards in root is not relevant, only in replication_specs
87+
// ok to fail as cloud_backup is optional
88+
_ = hcl.MoveAttr(resourceb, resourceb, nCloudBackup, nBackupEnabled, errRepSpecs)
89+
90+
config, errConfig := getRegionConfigs(configSrc, root)
91+
if errConfig != nil {
92+
return errConfig
93+
}
94+
repSpecs := hclwrite.NewEmptyFile()
95+
repSpecs.Body().SetAttributeRaw(nConfig, config)
96+
resourceb.SetAttributeRaw(nRepSpecs, hcl.TokensArrayObject(repSpecs))
97+
98+
resourceb.RemoveBlock(repSpecsSrc)
99+
return nil
100+
}
101+
102+
func getRegionConfigs(configSrc *hclwrite.Block, root attrVals) (hclwrite.Tokens, error) {
103+
file := hclwrite.NewEmptyFile()
104+
fileb := file.Body()
105+
fileb.SetAttributeRaw(nProviderName, root.req[nProviderName])
106+
if err := hcl.MoveAttr(configSrc.Body(), fileb, nRegionName, nRegionName, errRepSpecs); err != nil {
107+
return nil, err
108+
}
109+
if err := hcl.MoveAttr(configSrc.Body(), fileb, nPriority, nPriority, errRepSpecs); err != nil {
110+
return nil, err
111+
}
112+
autoScaling := getAutoScalingOpt(root.opt)
113+
if autoScaling != nil {
114+
fileb.SetAttributeRaw(nAutoScaling, autoScaling)
115+
}
116+
electableSpecs, errElect := getElectableSpecs(configSrc, root)
117+
if errElect != nil {
118+
return nil, errElect
119+
}
120+
fileb.SetAttributeRaw(nElectableSpecs, electableSpecs)
121+
return hcl.TokensArrayObject(file), nil
122+
}
123+
124+
func getElectableSpecs(configSrc *hclwrite.Block, root attrVals) (hclwrite.Tokens, error) {
125+
file := hclwrite.NewEmptyFile()
126+
fileb := file.Body()
127+
if err := hcl.MoveAttr(configSrc.Body(), fileb, nElectableNodes, nNodeCount, errRepSpecs); err != nil {
128+
return nil, err
129+
}
130+
fileb.SetAttributeRaw(nInstanceSize, root.req[nInstanceSizeSrc])
131+
if root.opt[nDiskSizeGB] != nil {
132+
fileb.SetAttributeRaw(nDiskSizeGB, root.opt[nDiskSizeGB])
133+
}
134+
return hcl.TokensObject(file), nil
135+
}
136+
137+
func getAutoScalingOpt(opt map[string]hclwrite.Tokens) hclwrite.Tokens {
138+
var (
139+
names = [][2]string{ // use slice instead of map to preserve order
140+
{nDiskGBEnabledSrc, nDiskGBEnabled},
141+
{nComputeEnabledSrc, nComputeEnabled},
142+
{nComputeMinInstanceSizeSrc, nComputeMinInstanceSize},
143+
{nComputeMaxInstanceSizeSrc, nComputeMaxInstanceSize},
144+
{nComputeScaleDownEnabledSrc, nComputeScaleDownEnabled},
145+
}
146+
file = hclwrite.NewEmptyFile()
147+
found = false
148+
)
149+
for _, tuple := range names {
150+
src, dst := tuple[0], tuple[1]
151+
if tokens := opt[src]; tokens != nil {
152+
file.Body().SetAttributeRaw(dst, tokens)
153+
found = true
154+
}
155+
}
156+
if !found {
157+
return nil
158+
}
159+
return hcl.TokensObject(file)
160+
}
161+
162+
// popRootAttrs deletes the attributes common to all replication_specs/regions_config and returns them.
163+
func popRootAttrs(body *hclwrite.Body, errPrefix string) (attrVals, error) {
164+
var (
165+
reqNames = []string{
166+
nProviderName,
167+
nInstanceSizeSrc,
168+
}
169+
optNames = []string{
170+
nDiskSizeGB,
171+
nDiskGBEnabledSrc,
172+
nComputeEnabledSrc,
173+
nComputeMinInstanceSizeSrc,
174+
nComputeMaxInstanceSizeSrc,
175+
nComputeScaleDownEnabledSrc,
176+
}
177+
req = make(map[string]hclwrite.Tokens)
178+
opt = make(map[string]hclwrite.Tokens)
179+
)
180+
for _, name := range reqNames {
181+
tokens, err := hcl.PopAttr(body, name, errPrefix)
182+
if err != nil {
183+
return attrVals{}, err
184+
}
185+
req[name] = tokens
186+
}
187+
for _, name := range optNames {
188+
tokens, _ := hcl.PopAttr(body, name, errPrefix)
189+
if tokens != nil {
190+
opt[name] = tokens
191+
}
192+
}
193+
return attrVals{req: req, opt: opt}, nil
194+
}
195+
196+
type attrVals struct {
197+
req map[string]hclwrite.Tokens
198+
opt map[string]hclwrite.Tokens
199+
}
200+
201+
const (
202+
resourceType = "resource"
203+
cluster = "mongodbatlas_cluster"
204+
advCluster = "mongodbatlas_advanced_cluster"
205+
206+
nRepSpecs = "replication_specs"
207+
nConfig = "region_configs"
208+
nConfigSrc = "regions_config"
209+
nElectableSpecs = "electable_specs"
210+
nAutoScaling = "auto_scaling"
211+
nRegionNameSrc = "provider_region_name"
212+
nRegionName = "region_name"
213+
nProviderName = "provider_name"
214+
nBackingProviderName = "backing_provider_name"
215+
nInstanceSizeSrc = "provider_instance_size_name"
216+
nInstanceSize = "instance_size"
217+
nClusterType = "cluster_type"
218+
nPriority = "priority"
219+
nNumShards = "num_shards"
220+
nBackupEnabled = "backup_enabled"
221+
nCloudBackup = "cloud_backup"
222+
nDiskSizeGB = "disk_size_gb"
223+
nDiskGBEnabledSrc = "auto_scaling_disk_gb_enabled"
224+
nComputeEnabledSrc = "auto_scaling_compute_enabled"
225+
nComputeScaleDownEnabledSrc = "auto_scaling_compute_scale_down_enabled"
226+
nComputeMinInstanceSizeSrc = "provider_auto_scaling_compute_min_instance_size"
227+
nComputeMaxInstanceSizeSrc = "provider_auto_scaling_compute_max_instance_size"
228+
nDiskGBEnabled = "disk_gb_enabled"
229+
nComputeEnabled = "compute_enabled"
230+
nComputeScaleDownEnabled = "compute_scale_down_enabled"
231+
nComputeMinInstanceSize = "compute_min_instance_size"
232+
nComputeMaxInstanceSize = "compute_max_instance_size"
233+
nNodeCount = "node_count"
234+
nElectableNodes = "electable_nodes"
235+
236+
valClusterType = "REPLICASET"
237+
valPriority = 7
238+
239+
errFreeCluster = "free cluster (because no " + nRepSpecs + ")"
240+
errRepSpecs = "setting " + nRepSpecs
241+
)
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
package hcl_test
1+
package convert_test
22

33
import (
44
"encoding/json"
55
"path/filepath"
66
"strings"
77
"testing"
88

9-
"github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/hcl"
9+
"github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/convert"
1010
"github.com/sebdah/goldie/v2"
1111
"github.com/spf13/afero"
1212
"github.com/stretchr/testify/assert"
@@ -38,7 +38,7 @@ func TestClusterToAdvancedCluster(t *testing.T) {
3838
t.Run(testName, func(t *testing.T) {
3939
inConfig, err := afero.ReadFile(fs, inputFile)
4040
require.NoError(t, err)
41-
outConfig, err := hcl.ClusterToAdvancedCluster(inConfig)
41+
outConfig, err := convert.ClusterToAdvancedCluster(inConfig)
4242
if err == nil {
4343
g.Assert(t, testName, outConfig)
4444
} else {
File renamed without changes.
File renamed without changes.

internal/hcl/testdata/clu2adv/autoscaling_missing_attribute.in.tf renamed to internal/convert/testdata/clu2adv/autoscaling_missing_attribute.in.tf

File renamed without changes.

internal/hcl/testdata/clu2adv/configuration_file_error.in.tf renamed to internal/convert/testdata/clu2adv/configuration_file_error.in.tf

File renamed without changes.
File renamed without changes.

internal/hcl/testdata/clu2adv/free_cluster_missing_attribute.in.tf renamed to internal/convert/testdata/clu2adv/free_cluster_missing_attribute.in.tf

File renamed without changes.

internal/hcl/testdata/clu2adv/free_cluster_with_count.in.tf renamed to internal/convert/testdata/clu2adv/free_cluster_with_count.in.tf

File renamed without changes.

0 commit comments

Comments
 (0)