@@ -9,9 +9,11 @@ import (
99 "github.com/hashicorp/hcl/v2"
1010 "github.com/hashicorp/hcl/v2/hclparse"
1111 "github.com/hashicorp/hcl/v2/hclsyntax"
12+ "github.com/zclconf/go-cty/cty"
13+ "github.com/zclconf/go-cty/cty/gocty"
1214)
1315
14- func parseHCLBytes (src []byte , filePath string ) (map [string ]map [string ]struct {} , error ) {
16+ func parseHCLBytes (src []byte , filePath string ) (map [string ]map [string ]any , error ) {
1517 parser := hclparse .NewParser ()
1618 hclFile , diags := parser .ParseHCL (src , filePath )
1719 if diags .HasErrors () {
@@ -22,7 +24,7 @@ func parseHCLBytes(src []byte, filePath string) (map[string]map[string]struct{},
2224 return nil , fmt .Errorf ("parsed HCL file %s is nil cannot proceed" , filePath )
2325 }
2426
25- parsed := make (map [string ]map [string ]struct {} )
27+ parsed := make (map [string ]map [string ]any )
2628
2729 for _ , block := range hclFile .Body .(* hclsyntax.Body ).Blocks {
2830 if block .Type == "resource" {
@@ -43,7 +45,7 @@ func parseHCLBytes(src []byte, filePath string) (map[string]map[string]struct{},
4345 }
4446 }
4547
46- flattenedAttrs := make (map [string ]struct {} )
48+ flattenedAttrs := make (map [string ]any )
4749 flatten (attrs , "" , flattenedAttrs )
4850 parsed [addr ] = flattenedAttrs
4951 }
@@ -61,7 +63,7 @@ func parseHCLBody(body hcl.Body) (
6163
6264 if syntaxBody , ok := body .(* hclsyntax.Body ); ok {
6365 for _ , attr := range syntaxBody .Attributes {
64- insert (struct {}{} , attr .Name , attributes )
66+ insert (getValue ( attr . Expr ) , attr .Name , attributes )
6567 }
6668
6769 for _ , block := range syntaxBody .Blocks {
@@ -96,39 +98,43 @@ func insert(data any, key string, parent map[string]any) {
9698 }
9799}
98100
99- func flatten (data any , prefix string , result map [string ]struct {} ) {
101+ func flatten (data any , prefix string , result map [string ]any ) {
100102 switch v := data .(type ) {
101103 case map [string ]any :
102- for key , value := range v {
103- newPrefix := key
104- if prefix != "" {
105- newPrefix = prefix + "." + key
104+ if len (v ) == 0 && prefix != "" {
105+ result [prefix ] = v
106+ } else {
107+ for key , value := range v {
108+ newPrefix := key
109+ if prefix != "" {
110+ newPrefix = prefix + "." + key
111+ }
112+ flatten (value , newPrefix , result )
106113 }
107- flatten (value , newPrefix , result )
108114 }
109115 case []any :
110116 flattenSlice (prefix , v , result )
111117 default :
112118 if prefix != "" {
113- result [prefix ] = struct {}{}
119+ result [prefix ] = v
114120 }
115121 }
116122}
117123
118- func flattenSlice (prefix string , v []any , result map [string ]struct {} ) {
124+ func flattenSlice (prefix string , v []any , result map [string ]any ) {
119125 if len (v ) == 0 && prefix != "" {
120126 result [prefix ] = struct {}{}
121127 return
122128 }
123129
124130 type sortableElement struct {
125131 flatKeys string
126- flattened map [string ]struct {}
132+ flattened map [string ]any
127133 }
128134
129135 sortable := make ([]sortableElement , len (v ))
130136 for i , value := range v {
131- flattened := make (map [string ]struct {} )
137+ flattened := make (map [string ]any )
132138 flatten (value , "" , flattened )
133139 keys := make ([]string , 0 , len (flattened ))
134140 for k := range flattened {
@@ -152,13 +158,88 @@ func flattenSlice(prefix string, v []any, result map[string]struct{}) {
152158 result [newPrefix ] = struct {}{}
153159 }
154160 } else {
155- for k := range element .flattened {
161+ for k , v := range element .flattened {
156162 newKey := newPrefix
157163 if k != "" {
158164 newKey = newPrefix + "." + k
159165 }
160- result [newKey ] = struct {}{}
166+ result [newKey ] = v
161167 }
162168 }
163169 }
164170}
171+
172+ // Gets the value of the expression of an attribute
173+ func getValue (expr hcl.Expression ) any {
174+ switch expr := expr .(type ) {
175+ case * hclsyntax.ScopeTraversalExpr :
176+ // Example: id = google_instance.web.id
177+ return getTraveralExprVal (expr .Traversal )
178+ case * hclsyntax.LiteralValueExpr :
179+ // Example: region = "us-west1"
180+ return convertValue (expr .Val )
181+ case * hclsyntax.TemplateExpr :
182+ // Example: ip_address = "IP: ${var.ip}"
183+ vStr := ""
184+ parts := expr .Parts
185+ for _ , part := range parts {
186+ vStr += getValue (part ).(string )
187+ }
188+ return vStr
189+ case * hclsyntax.TupleConsExpr :
190+ // Example: methods = ["GET", "POST", "DELETE"]
191+ exprV := make ([]string , 0 , len (expr .Exprs ))
192+ for _ , elem := range expr .Exprs {
193+ exprV = append (exprV , fmt .Sprint (getValue (elem )))
194+ }
195+ return strings .Join (exprV , "," )
196+ case * hclsyntax.ObjectConsKeyExpr :
197+ // Example: labels = {(local.key_name) = "value"}
198+ return getValue (expr .Wrapped ).(string )
199+ case * hclsyntax.ObjectConsExpr :
200+ // Example: tags = { Env = "dev", Owner = var.user }
201+ return map [string ]any {}
202+ case * hclsyntax.ParenthesesExpr :
203+ // Example: (local.key_name)
204+ return getValue (expr .Expression )
205+ default :
206+ log .Printf ("Unsupported expression type: %T" , expr )
207+ return nil
208+ }
209+ }
210+
211+ // Converts a literal cty.Value to a standard Go value
212+ func convertValue (val cty.Value ) any {
213+ switch val .Type () {
214+ case cty .Number :
215+ f , _ := val .AsBigFloat ().Float64 ()
216+ return f
217+ case cty .String :
218+ return val .AsString ()
219+ case cty .Bool :
220+ var v bool
221+ _ = gocty .FromCtyValue (val , & v )
222+ return v
223+ default :
224+ return ""
225+ }
226+ }
227+
228+ // Gets the value for the traveral expression (e.g. "google_pubsub_topic.example.id")
229+ func getTraveralExprVal (traversal hcl.Traversal ) string {
230+ exprV := make ([]string , 0 , len (traversal ))
231+
232+ for _ , v := range traversal {
233+ switch v := v .(type ) {
234+ // The starting point of the traversal (e.g. "google_pubsub_topic)
235+ case hcl.TraverseRoot :
236+ exprV = append (exprV , v .Name )
237+ // A list of hclsyntax.Traversal objects, which represent each step in the path.
238+ // In "google_pubsub_topic.example.id", the steps are "example", "id"
239+ case hcl.TraverseAttr :
240+ exprV = append (exprV , v .Name )
241+ }
242+ }
243+
244+ return strings .Join (exprV , "." )
245+ }
0 commit comments