Skip to content

Commit cc82ae2

Browse files
authored
Fix issues caused by multiple terraform blocks (#216)
See also #205 Some rules that reference terraform blocks do not assume that blocks can be defined multiple times and can lead to inconsistent results in that case.
1 parent ac15058 commit cc82ae2

6 files changed

+206
-12
lines changed

rules/terraform_required_providers.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,9 @@ func (r *TerraformRequiredProvidersRule) Check(rr tflint.Runner) error {
151151
requiredProviders := hclext.Attributes{}
152152
for _, terraform := range body.Blocks {
153153
for _, requiredProvidersBlock := range terraform.Body.Blocks {
154-
requiredProviders = requiredProvidersBlock.Body.Attributes
154+
for name, attr := range requiredProvidersBlock.Body.Attributes {
155+
requiredProviders[name] = attr
156+
}
155157
}
156158
}
157159

rules/terraform_required_providers_test.go

Lines changed: 134 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package rules
22

33
import (
4+
"reflect"
45
"testing"
56

7+
"github.com/google/go-cmp/cmp"
8+
"github.com/google/go-cmp/cmp/cmpopts"
69
"github.com/hashicorp/hcl/v2"
710
"github.com/terraform-linters/tflint-plugin-sdk/helper"
11+
"github.com/terraform-linters/tflint-plugin-sdk/tflint"
812
)
913

1014
func Test_TerraformRequiredProvidersRule(t *testing.T) {
@@ -570,6 +574,107 @@ output "foo" {
570574
},
571575
},
572576
},
577+
{
578+
Name: "multiple required providers",
579+
Content: `
580+
terraform {
581+
required_providers {
582+
template = "~> 2"
583+
}
584+
585+
required_providers {
586+
aws = "~> 5.0"
587+
}
588+
}
589+
590+
provider "template" {}
591+
provider "aws" {}
592+
provider "google" {}
593+
594+
terraform {
595+
required_providers {
596+
google = "~> 6.0"
597+
}
598+
}
599+
`,
600+
Expected: helper.Issues{
601+
{
602+
Rule: NewTerraformRequiredProvidersRule(),
603+
Message: "Legacy version constraint for provider \"template\" in `required_providers`",
604+
Range: hcl.Range{
605+
Filename: "module.tf",
606+
Start: hcl.Pos{
607+
Line: 4,
608+
Column: 16,
609+
},
610+
End: hcl.Pos{
611+
Line: 4,
612+
Column: 22,
613+
},
614+
},
615+
},
616+
{
617+
Rule: NewTerraformRequiredProvidersRule(),
618+
Message: "Legacy version constraint for provider \"aws\" in `required_providers`",
619+
Range: hcl.Range{
620+
Filename: "module.tf",
621+
Start: hcl.Pos{
622+
Line: 8,
623+
Column: 11,
624+
},
625+
End: hcl.Pos{
626+
Line: 8,
627+
Column: 19,
628+
},
629+
},
630+
},
631+
{
632+
Rule: NewTerraformRequiredProvidersRule(),
633+
Message: "Legacy version constraint for provider \"google\" in `required_providers`",
634+
Range: hcl.Range{
635+
Filename: "module.tf",
636+
Start: hcl.Pos{
637+
Line: 18,
638+
Column: 14,
639+
},
640+
End: hcl.Pos{
641+
Line: 18,
642+
Column: 22,
643+
},
644+
},
645+
},
646+
},
647+
Fixed: `
648+
terraform {
649+
required_providers {
650+
template = {
651+
source = "hashicorp/template"
652+
version = "~> 2"
653+
}
654+
}
655+
656+
required_providers {
657+
aws = {
658+
source = "hashicorp/aws"
659+
version = "~> 5.0"
660+
}
661+
}
662+
}
663+
664+
provider "template" {}
665+
provider "aws" {}
666+
provider "google" {}
667+
668+
terraform {
669+
required_providers {
670+
google = {
671+
source = "hashicorp/google"
672+
version = "~> 6.0"
673+
}
674+
}
675+
}
676+
`,
677+
},
573678
}
574679

575680
rule := NewTerraformRequiredProvidersRule()
@@ -590,7 +695,35 @@ output "foo" {
590695
t.Fatalf("Unexpected error occurred: %s", err)
591696
}
592697

593-
helper.AssertIssues(t, tc.Expected, runner.Runner.(*helper.Runner).Issues)
698+
// TODO: replace the following assertions without ordering by AssertIssues
699+
// helper.AssertIssues(t, tc.Expected, runner.Runner.(*helper.Runner).Issues)
700+
opts := []cmp.Option{
701+
cmpopts.IgnoreFields(hcl.Pos{}, "Byte"),
702+
cmp.Comparer(func(x, y tflint.Rule) bool {
703+
return reflect.TypeOf(x) == reflect.TypeOf(y)
704+
}),
705+
cmpopts.SortSlices(func(i, j *helper.Issue) bool {
706+
if i.Range.Filename != j.Range.Filename {
707+
return i.Range.Filename < j.Range.Filename
708+
}
709+
if i.Range.Start.Line != j.Range.Start.Line {
710+
return i.Range.Start.Line < j.Range.Start.Line
711+
}
712+
if i.Range.Start.Column != j.Range.Start.Column {
713+
return i.Range.Start.Column < j.Range.Start.Column
714+
}
715+
if i.Range.End.Line != j.Range.End.Line {
716+
return i.Range.End.Line > j.Range.End.Line
717+
}
718+
if i.Range.End.Column != j.Range.End.Column {
719+
return i.Range.End.Column > j.Range.End.Column
720+
}
721+
return i.Message < j.Message
722+
}),
723+
}
724+
if diff := cmp.Diff(tc.Expected, runner.Runner.(*helper.Runner).Issues, opts...); diff != "" {
725+
t.Fatalf("Expected issues are not matched:\n %s\n", diff)
726+
}
594727
want := map[string]string{}
595728
if tc.Fixed != "" {
596729
want[filename] = tc.Fixed

rules/terraform_unused_required_providers.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,10 @@ func (r *TerraformUnusedRequiredProvidersRule) Check(rr tflint.Runner) error {
8282
}
8383

8484
for _, block := range content.Blocks {
85-
var attrDiags hcl.Diagnostics
86-
requiredProviders, attrDiags = block.Body.JustAttributes()
85+
attributes, attrDiags := block.Body.JustAttributes()
86+
for k, v := range attributes {
87+
requiredProviders[k] = v
88+
}
8789
diags = diags.Extend(attrDiags)
8890
if diags.HasErrors() {
8991
continue

rules/terraform_unused_required_providers_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,43 @@ func Test_TerraformUnusedRequiredProvidersRule(t *testing.T) {
260260
`,
261261
Expected: helper.Issues{},
262262
},
263+
{
264+
Name: "unused - in multiple terraform blocks",
265+
Content: `
266+
terraform {
267+
required_providers {
268+
aws = {
269+
source = "hashicorp/aws"
270+
}
271+
}
272+
}
273+
terraform {
274+
required_providers {
275+
null = {
276+
source = "hashicorp/null"
277+
}
278+
}
279+
}
280+
resource "null_resource" "foo" {}
281+
`,
282+
Expected: helper.Issues{
283+
{
284+
Rule: NewTerraformUnusedRequiredProvidersRule(),
285+
Message: "provider 'aws' is declared in required_providers but not used by the module",
286+
Range: hcl.Range{
287+
Filename: "module.tf",
288+
Start: hcl.Pos{
289+
Line: 4,
290+
Column: 7,
291+
},
292+
End: hcl.Pos{
293+
Line: 6,
294+
Column: 8,
295+
},
296+
},
297+
},
298+
},
299+
},
263300
}
264301

265302
rule := NewTerraformUnusedRequiredProvidersRule()

rules/terraform_workspace_remote.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,20 +94,15 @@ func (r *TerraformWorkspaceRemoteRule) Check(runner tflint.Runner) error {
9494
}
9595

9696
var remoteBackend bool
97-
var tf10Support bool
97+
constraints := version.Constraints{}
9898
for _, terraform := range body.Blocks {
9999
for _, requiredVersion := range terraform.Body.Attributes {
100100
err := runner.EvaluateExpr(requiredVersion.Expr, func(v string) error {
101-
constraints, err := version.NewConstraint(v)
101+
c, err := version.NewConstraint(v)
102102
if err != nil {
103103
return err
104104
}
105-
106-
for _, tf10Version := range tf10Versions {
107-
if constraints.Check(tf10Version) {
108-
tf10Support = true
109-
}
110-
}
105+
constraints = append(constraints, c...)
111106
return nil
112107
}, nil)
113108
if err != nil {
@@ -121,6 +116,14 @@ func (r *TerraformWorkspaceRemoteRule) Check(runner tflint.Runner) error {
121116
}
122117
}
123118
}
119+
var tf10Support bool
120+
if constraints.Len() > 0 {
121+
for _, tf10Version := range tf10Versions {
122+
if constraints.Check(tf10Version) {
123+
tf10Support = true
124+
}
125+
}
126+
}
124127
if !remoteBackend || !tf10Support {
125128
return nil
126129
}

rules/terraform_workspace_remote_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,23 @@ resource "kubernetes_secret" "my_secret" {
296296
data
297297
]
298298
}
299+
}`,
300+
Expected: helper.Issues{},
301+
},
302+
{
303+
Name: "required_version does not support 1.0.x by multiple setting",
304+
Content: `
305+
terraform {
306+
required_version = "< 1.9"
307+
backend "remote" {}
308+
}
309+
terraform {
310+
required_version = ">= 1.1"
311+
}
312+
resource "null_resource" "a" {
313+
triggers = {
314+
w = terraform.workspace
315+
}
299316
}`,
300317
Expected: helper.Issues{},
301318
},

0 commit comments

Comments
 (0)