Skip to content

Commit c5fcef3

Browse files
authored
Merge pull request #37 from terraform-linters/meta_args_for_plugin
tflint: Support meta-arguments for terraform.Resource
2 parents c1d3caf + aa281aa commit c5fcef3

File tree

10 files changed

+872
-29
lines changed

10 files changed

+872
-29
lines changed

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQ
6666
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
6767
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
6868
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
69+
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
6970
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
7071
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
7172
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

helper/runner.go

Lines changed: 183 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package helper
22

33
import (
44
"github.com/hashicorp/hcl/v2"
5+
"github.com/hashicorp/hcl/v2/gohcl"
6+
"github.com/terraform-linters/tflint-plugin-sdk/terraform"
57
"github.com/terraform-linters/tflint-plugin-sdk/tflint"
68
"github.com/zclconf/go-cty/cty/gocty"
79
)
@@ -59,7 +61,7 @@ func (r *Runner) WalkResourceAttributes(resourceType, attributeName string, walk
5961
}
6062

6163
// 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 {
6365
for _, file := range r.Files {
6466
resources, _, diags := file.Body.PartialContent(&hcl.BodySchema{
6567
Blocks: []hcl.BlockHeaderSchema{
@@ -73,16 +75,17 @@ func (r *Runner) WalkResources(resourceType string, walker func(*tflint.Resource
7375
return diags
7476
}
7577

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 {
7885
continue
7986
}
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)
8689
if err != nil {
8790
return err
8891
}
@@ -119,3 +122,174 @@ func (r *Runner) EnsureNoError(err error, proc func() error) error {
119122
}
120123
return err
121124
}
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

Comments
 (0)