@@ -2,6 +2,8 @@ package helper
2
2
3
3
import (
4
4
"github.com/hashicorp/hcl/v2"
5
+ "github.com/hashicorp/hcl/v2/gohcl"
6
+ "github.com/terraform-linters/tflint-plugin-sdk/terraform"
5
7
"github.com/terraform-linters/tflint-plugin-sdk/tflint"
6
8
"github.com/zclconf/go-cty/cty/gocty"
7
9
)
@@ -59,7 +61,7 @@ func (r *Runner) WalkResourceAttributes(resourceType, attributeName string, walk
59
61
}
60
62
61
63
// WalkResources searches for resources with a specific type and passes to the walker function
62
- func (r * Runner ) WalkResources (resourceType string , walker func (* tflint .Resource ) error ) error {
64
+ func (r * Runner ) WalkResources (resourceType string , walker func (* terraform .Resource ) error ) error {
63
65
for _ , file := range r .Files {
64
66
resources , _ , diags := file .Body .PartialContent (& hcl.BodySchema {
65
67
Blocks : []hcl.BlockHeaderSchema {
@@ -73,16 +75,17 @@ func (r *Runner) WalkResources(resourceType string, walker func(*tflint.Resource
73
75
return diags
74
76
}
75
77
76
- for _ , resource := range resources .Blocks {
77
- if resource .Labels [0 ] != resourceType {
78
+ for _ , block := range resources .Blocks {
79
+ resource , diags := simpleDecodeResouceBlock (block )
80
+ if diags .HasErrors () {
81
+ return diags
82
+ }
83
+
84
+ if resource .Type != resourceType {
78
85
continue
79
86
}
80
- err := walker (& tflint.Resource {
81
- Name : resource .Labels [1 ],
82
- Type : resource .Labels [0 ],
83
- DeclRange : resource .DefRange ,
84
- TypeRange : resource .LabelRanges [0 ],
85
- })
87
+
88
+ err := walker (resource )
86
89
if err != nil {
87
90
return err
88
91
}
@@ -119,3 +122,174 @@ func (r *Runner) EnsureNoError(err error, proc func() error) error {
119
122
}
120
123
return err
121
124
}
125
+
126
+ // simpleDecodeResourceBlock decodes the data equivalent to configs.Resource from hcl.Block
127
+ // without depending on Terraform. Some operations have been omitted for ease of implementation.
128
+ // As such, it is expected to parse the minimal code needed for testing.
129
+ // https://github.com/hashicorp/terraform/blob/v0.12.26/configs/resource.go#L78-L288
130
+ func simpleDecodeResouceBlock (resource * hcl.Block ) (* terraform.Resource , hcl.Diagnostics ) {
131
+ content , resourceRemain , diags := resource .Body .PartialContent (& hcl.BodySchema {
132
+ Attributes : []hcl.AttributeSchema {
133
+ {
134
+ Name : "count" ,
135
+ },
136
+ {
137
+ Name : "for_each" ,
138
+ },
139
+ {
140
+ Name : "provider" ,
141
+ },
142
+ {
143
+ Name : "depends_on" ,
144
+ },
145
+ },
146
+ Blocks : []hcl.BlockHeaderSchema {
147
+ {Type : "lifecycle" },
148
+ {Type : "connection" },
149
+ {Type : "provisioner" , LabelNames : []string {"type" }},
150
+ },
151
+ })
152
+ if diags .HasErrors () {
153
+ return nil , diags
154
+ }
155
+
156
+ var count hcl.Expression
157
+ if attr , exists := content .Attributes ["count" ]; exists {
158
+ count = attr .Expr
159
+ }
160
+
161
+ var forEach hcl.Expression
162
+ if attr , exists := content .Attributes ["for_each" ]; exists {
163
+ forEach = attr .Expr
164
+ }
165
+
166
+ var ref * terraform.ProviderConfigRef
167
+ if attr , exists := content .Attributes ["provider" ]; exists {
168
+ traversal , diags := hcl .AbsTraversalForExpr (attr .Expr )
169
+ if diags .HasErrors () {
170
+ return nil , diags
171
+ }
172
+
173
+ ref = & terraform.ProviderConfigRef {
174
+ Name : traversal .RootName (),
175
+ NameRange : traversal [0 ].SourceRange (),
176
+ }
177
+
178
+ if len (traversal ) > 1 {
179
+ aliasStep := traversal [1 ].(hcl.TraverseAttr )
180
+ ref .Alias = aliasStep .Name
181
+ ref .AliasRange = aliasStep .SourceRange ().Ptr ()
182
+ }
183
+ }
184
+
185
+ managed := & terraform.ManagedResource {}
186
+ for _ , block := range content .Blocks {
187
+ switch block .Type {
188
+ case "lifecycle" :
189
+ content , _ , diags := block .Body .PartialContent (& hcl.BodySchema {
190
+ Attributes : []hcl.AttributeSchema {
191
+ {
192
+ Name : "create_before_destroy" ,
193
+ },
194
+ {
195
+ Name : "prevent_destroy" ,
196
+ },
197
+ {
198
+ Name : "ignore_changes" ,
199
+ },
200
+ },
201
+ })
202
+ if diags .HasErrors () {
203
+ return nil , diags
204
+ }
205
+
206
+ if attr , exists := content .Attributes ["create_before_destroy" ]; exists {
207
+ if diags := gohcl .DecodeExpression (attr .Expr , nil , & managed .CreateBeforeDestroy ); diags .HasErrors () {
208
+ return nil , diags
209
+ }
210
+ managed .CreateBeforeDestroySet = true
211
+ }
212
+ if attr , exists := content .Attributes ["prevent_destroy" ]; exists {
213
+ if diags := gohcl .DecodeExpression (attr .Expr , nil , & managed .PreventDestroy ); diags .HasErrors () {
214
+ return nil , diags
215
+ }
216
+ managed .PreventDestroySet = true
217
+ }
218
+ if attr , exists := content .Attributes ["ignore_changes" ]; exists {
219
+ if hcl .ExprAsKeyword (attr .Expr ) == "all" {
220
+ managed .IgnoreAllChanges = true
221
+ }
222
+ }
223
+ case "connection" :
224
+ managed .Connection = & terraform.Connection {
225
+ Config : block .Body ,
226
+ DeclRange : block .DefRange ,
227
+ }
228
+ case "provisioner" :
229
+ pv := & terraform.Provisioner {
230
+ Type : block .Labels [0 ],
231
+ TypeRange : block .LabelRanges [0 ],
232
+ DeclRange : block .DefRange ,
233
+ When : terraform .ProvisionerWhenCreate ,
234
+ OnFailure : terraform .ProvisionerOnFailureFail ,
235
+ }
236
+
237
+ content , config , diags := block .Body .PartialContent (& hcl.BodySchema {
238
+ Attributes : []hcl.AttributeSchema {
239
+ {Name : "when" },
240
+ {Name : "on_failure" },
241
+ },
242
+ Blocks : []hcl.BlockHeaderSchema {
243
+ {Type : "connection" },
244
+ },
245
+ })
246
+ if diags .HasErrors () {
247
+ return nil , diags
248
+ }
249
+ pv .Config = config
250
+
251
+ if attr , exists := content .Attributes ["when" ]; exists {
252
+ switch hcl .ExprAsKeyword (attr .Expr ) {
253
+ case "create" :
254
+ pv .When = terraform .ProvisionerWhenCreate
255
+ case "destroy" :
256
+ pv .When = terraform .ProvisionerWhenDestroy
257
+ }
258
+ }
259
+
260
+ if attr , exists := content .Attributes ["on_failure" ]; exists {
261
+ switch hcl .ExprAsKeyword (attr .Expr ) {
262
+ case "continue" :
263
+ pv .OnFailure = terraform .ProvisionerOnFailureContinue
264
+ case "fail" :
265
+ pv .OnFailure = terraform .ProvisionerOnFailureFail
266
+ }
267
+ }
268
+
269
+ for _ , block := range content .Blocks {
270
+ pv .Connection = & terraform.Connection {
271
+ Config : block .Body ,
272
+ DeclRange : block .DefRange ,
273
+ }
274
+ }
275
+
276
+ managed .Provisioners = append (managed .Provisioners , pv )
277
+ }
278
+ }
279
+
280
+ return & terraform.Resource {
281
+ Mode : terraform .ManagedResourceMode ,
282
+ Name : resource .Labels [1 ],
283
+ Type : resource .Labels [0 ],
284
+ Config : resourceRemain ,
285
+ Count : count ,
286
+ ForEach : forEach ,
287
+
288
+ ProviderConfigRef : ref ,
289
+
290
+ Managed : managed ,
291
+
292
+ DeclRange : resource .DefRange ,
293
+ TypeRange : resource .LabelRanges [0 ],
294
+ }, nil
295
+ }
0 commit comments