@@ -2,22 +2,38 @@ package hcl
22
33import (
44 "fmt"
5+ "strconv"
56
67 "github.com/hashicorp/hcl/v2"
78 "github.com/hashicorp/hcl/v2/hclsyntax"
89 "github.com/hashicorp/hcl/v2/hclwrite"
10+ "github.com/zclconf/go-cty/cty"
911)
1012
1113const (
12- resourceType = "resource"
13- cluster = "mongodbatlas_cluster"
14- advCluster = "mongodbatlas_advanced_cluster"
14+ resourceType = "resource"
15+ cluster = "mongodbatlas_cluster"
16+ advCluster = "mongodbatlas_advanced_cluster"
17+ nameReplicationSpecs = "replication_specs"
18+ nameRegionConfigs = "region_configs"
19+ nameElectableSpecs = "electable_specs"
20+ nameProviderRegionName = "provider_region_name"
21+ nameRegionName = "region_name"
22+ nameProviderName = "provider_name"
23+ nameBackingProviderName = "backing_provider_name"
24+ nameProviderInstanceSizeName = "provider_instance_size_name"
25+ nameInstanceSize = "instance_size"
26+ nameClusterType = "cluster_type"
27+ namePriority = "priority"
28+
29+ errFreeCluster = "free cluster (because no " + nameReplicationSpecs + ")"
1530)
1631
1732// ClusterToAdvancedCluster transforms all mongodbatlas_cluster definitions in a
1833// Terraform configuration file into mongodbatlas_advanced_cluster schema v2 definitions.
1934// All other resources and data sources are left untouched.
20- // TODO: at the moment it just changes the resource type.
35+ // Note: hclwrite.Tokens are used instead of cty.Value so expressions like var.region can be preserved.
36+ // cty.Value only supports resolved values.
2137func ClusterToAdvancedCluster (config []byte ) ([]byte , error ) {
2238 parser , err := getParser (config )
2339 if err != nil {
@@ -30,31 +46,105 @@ func ClusterToAdvancedCluster(config []byte) ([]byte, error) {
3046 continue
3147 }
3248 resourceBody := resource .Body ()
33-
34- // TODO: Do the full transformation
3549 labels [0 ] = advCluster
3650 resource .SetLabels (labels )
51+
52+ if isFreeTier (resourceBody ) {
53+ if err := fillFreeTier (resourceBody ); err != nil {
54+ return nil , err
55+ }
56+ }
57+
3758 resourceBody .AppendNewline ()
3859 appendComment (resourceBody , "Generated by atlas-cli-plugin-terraform." )
3960 appendComment (resourceBody , "Please confirm that all references to this resource are updated." )
4061 }
4162 return parser .Bytes (), nil
4263}
4364
44- func getParser (config []byte ) (* hclwrite.File , error ) {
45- parser , diags := hclwrite .ParseConfig (config , "" , hcl.Pos {Line : 1 , Column : 1 })
46- if diags .HasErrors () {
47- return nil , fmt .Errorf ("failed to parse Terraform config file: %s" , diags .Error ())
65+ func isFreeTier (body * hclwrite.Body ) bool {
66+ return body .FirstMatchingBlock (nameReplicationSpecs , nil ) == nil
67+ }
68+
69+ func fillFreeTier (body * hclwrite.Body ) error {
70+ const (
71+ valClusterType = "REPLICASET"
72+ valPriority = 7
73+ )
74+ body .SetAttributeValue (nameClusterType , cty .StringVal (valClusterType ))
75+ regionConfig := hclwrite .NewEmptyFile ()
76+ regionConfigBody := regionConfig .Body ()
77+ setAttrInt (regionConfigBody , "priority" , valPriority )
78+ if err := moveAttribute (nameProviderRegionName , nameRegionName , body , regionConfigBody , errFreeCluster ); err != nil {
79+ return err
4880 }
49- return parser , nil
81+ if err := moveAttribute (nameProviderName , nameProviderName , body , regionConfigBody , errFreeCluster ); err != nil {
82+ return err
83+ }
84+ if err := moveAttribute (nameBackingProviderName , nameBackingProviderName , body , regionConfigBody , errFreeCluster ); err != nil {
85+ return err
86+ }
87+ electableSpec := hclwrite .NewEmptyFile ()
88+ if err := moveAttribute (nameProviderInstanceSizeName , nameInstanceSize , body , electableSpec .Body (), errFreeCluster ); err != nil {
89+ return err
90+ }
91+ regionConfigBody .SetAttributeRaw (nameElectableSpecs , tokensObject (electableSpec ))
92+
93+ replicationSpec := hclwrite .NewEmptyFile ()
94+ replicationSpec .Body ().SetAttributeRaw (nameRegionConfigs , tokensArrayObject (regionConfig ))
95+ body .SetAttributeRaw (nameReplicationSpecs , tokensArrayObject (replicationSpec ))
96+ return nil
97+ }
98+
99+ func moveAttribute (fromAttrName , toAttrName string , fromBody , toBody * hclwrite.Body , errPrefix string ) error {
100+ attr := fromBody .GetAttribute (fromAttrName )
101+ if attr == nil {
102+ return fmt .Errorf ("%s: attribute %s not found" , errPrefix , fromAttrName )
103+ }
104+ fromBody .RemoveAttribute (fromAttrName )
105+ toBody .SetAttributeRaw (toAttrName , attr .Expr ().BuildTokens (nil ))
106+ return nil
107+ }
108+
109+ func setAttrInt (body * hclwrite.Body , attrName string , number int ) {
110+ tokens := hclwrite.Tokens {
111+ {Type : hclsyntax .TokenNumberLit , Bytes : []byte (strconv .Itoa (number ))},
112+ }
113+ body .SetAttributeRaw (attrName , tokens )
114+ }
115+
116+ func tokensArrayObject (file * hclwrite.File ) hclwrite.Tokens {
117+ ret := hclwrite.Tokens {
118+ {Type : hclsyntax .TokenOBrack , Bytes : []byte ("[" )},
119+ }
120+ ret = append (ret , tokensObject (file )... )
121+ ret = append (ret ,
122+ & hclwrite.Token {Type : hclsyntax .TokenCBrack , Bytes : []byte ("]" )})
123+ return ret
124+ }
125+
126+ func tokensObject (file * hclwrite.File ) hclwrite.Tokens {
127+ ret := hclwrite.Tokens {
128+ {Type : hclsyntax .TokenOBrack , Bytes : []byte ("{" )},
129+ {Type : hclsyntax .TokenNewline , Bytes : []byte ("\n " )},
130+ }
131+ ret = append (ret , file .BuildTokens (nil )... )
132+ ret = append (ret ,
133+ & hclwrite.Token {Type : hclsyntax .TokenCBrack , Bytes : []byte ("}" )})
134+ return ret
50135}
51136
52137func appendComment (body * hclwrite.Body , comment string ) {
53138 tokens := hclwrite.Tokens {
54- & hclwrite.Token {
55- Type : hclsyntax .TokenComment ,
56- Bytes : []byte ("# " + comment + "\n " ),
57- },
139+ & hclwrite.Token {Type : hclsyntax .TokenComment , Bytes : []byte ("# " + comment + "\n " )},
58140 }
59141 body .AppendUnstructuredTokens (tokens )
60142}
143+
144+ func getParser (config []byte ) (* hclwrite.File , error ) {
145+ parser , diags := hclwrite .ParseConfig (config , "" , hcl.Pos {Line : 1 , Column : 1 })
146+ if diags .HasErrors () {
147+ return nil , fmt .Errorf ("failed to parse Terraform config file: %s" , diags .Error ())
148+ }
149+ return parser , nil
150+ }
0 commit comments