Skip to content

Commit 1d00e6d

Browse files
committed
Add ranges to terraform_required_version errors
1 parent 5382486 commit 1d00e6d

File tree

2 files changed

+156
-7
lines changed

2 files changed

+156
-7
lines changed

rules/terraform_required_version.go

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package rules
22

33
import (
4+
"path/filepath"
5+
46
"github.com/hashicorp/hcl/v2"
57
"github.com/terraform-linters/tflint-plugin-sdk/hclext"
68
"github.com/terraform-linters/tflint-plugin-sdk/tflint"
@@ -78,13 +80,53 @@ func (r *TerraformRequiredVersionRule) Check(runner tflint.Runner) error {
7880
exists = exists || ok
7981
}
8082

81-
if !exists {
82-
return runner.EmitIssue(
83-
r,
84-
`terraform "required_version" attribute is required`,
85-
hcl.Range{},
86-
)
83+
if exists {
84+
return nil
85+
}
86+
87+
if len(body.Blocks) > 0 {
88+
return r.emitIssue(body.Blocks[0].DefRange, runner)
8789
}
8890

89-
return nil
91+
// If there are no "terraform" blocks, create a hcl.Range from the files
92+
var file string
93+
for k := range files {
94+
file = k
95+
break
96+
}
97+
98+
// If there is only one file, use that
99+
if len(files) == 1 {
100+
return r.emitIssue(hcl.Range{
101+
Filename: file,
102+
Start: hcl.InitialPos,
103+
}, runner)
104+
}
105+
106+
moduleDirectory := filepath.Dir(file)
107+
108+
// If there are multiple files, look for terraform.tf or main.tf (in that order)
109+
for _, basename := range []string{"terraform.tf", "main.tf"} {
110+
filename := filepath.Join(moduleDirectory, basename)
111+
if _, ok := files[filename]; ok {
112+
return r.emitIssue(hcl.Range{
113+
Filename: filename,
114+
Start: hcl.InitialPos,
115+
}, runner)
116+
}
117+
}
118+
119+
// If none of those are found, point to a nonexistent terraform.tf per the style guide
120+
return r.emitIssue(hcl.Range{
121+
Filename: filepath.Join(moduleDirectory, "terraform.tf"),
122+
}, runner)
123+
}
124+
125+
// emitIssue emits issue for missing terraform require version
126+
func (r *TerraformRequiredVersionRule) emitIssue(missingRange hcl.Range, runner tflint.Runner) error {
127+
return runner.EmitIssue(
128+
r,
129+
`terraform "required_version" attribute is required`,
130+
missingRange,
131+
)
90132
}

rules/terraform_required_version_test.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package rules
22

33
import (
4+
"path/filepath"
45
"testing"
56

7+
"github.com/hashicorp/hcl/v2"
68
"github.com/terraform-linters/tflint-plugin-sdk/helper"
79
)
810

@@ -25,6 +27,17 @@ terraform {}
2527
{
2628
Rule: NewTerraformRequiredVersionRule(),
2729
Message: "terraform \"required_version\" attribute is required",
30+
Range: hcl.Range{
31+
Filename: "module.tf",
32+
Start: hcl.Pos{
33+
Line: 2,
34+
Column: 1,
35+
},
36+
End: hcl.Pos{
37+
Line: 2,
38+
Column: 10,
39+
},
40+
},
2841
},
2942
},
3043
},
@@ -53,6 +66,27 @@ terraform {
5366
`,
5467
Expected: helper.Issues{},
5568
},
69+
{
70+
Name: "no terraform block",
71+
Content: `
72+
locals {
73+
foo = "bar"
74+
}
75+
`,
76+
Expected: helper.Issues{
77+
{
78+
Rule: NewTerraformRequiredVersionRule(),
79+
Message: "terraform \"required_version\" attribute is required",
80+
Range: hcl.Range{
81+
Filename: "module.tf",
82+
Start: hcl.Pos{
83+
Line: 1,
84+
Column: 1,
85+
},
86+
},
87+
},
88+
},
89+
},
5690
}
5791

5892
rule := NewTerraformRequiredVersionRule()
@@ -73,3 +107,76 @@ terraform {
73107
})
74108
}
75109
}
110+
111+
func Test_TerraformRequiredVersionRuleMultipleFiles(t *testing.T) {
112+
cases := []struct {
113+
Name string
114+
Files []string
115+
Expected helper.Issues
116+
}{
117+
{
118+
Name: "has terraform.tf and main.tf",
119+
Files: []string{"modules/foo/main.tf", "modules/foo/terraform.tf"},
120+
Expected: helper.Issues{
121+
{
122+
Rule: NewTerraformRequiredVersionRule(),
123+
Message: "terraform \"required_version\" attribute is required",
124+
Range: hcl.Range{
125+
Filename: filepath.FromSlash("modules/foo/terraform.tf"),
126+
Start: hcl.Pos{
127+
Line: 1,
128+
Column: 1,
129+
},
130+
},
131+
},
132+
},
133+
},
134+
{
135+
Name: "has main.tf",
136+
Files: []string{"modules/foo/outputs.tf", "modules/foo/main.tf", "modules/foo/variables.tf"},
137+
Expected: helper.Issues{
138+
{
139+
Rule: NewTerraformRequiredVersionRule(),
140+
Message: "terraform \"required_version\" attribute is required",
141+
Range: hcl.Range{
142+
Filename: filepath.FromSlash("modules/foo/main.tf"),
143+
Start: hcl.Pos{
144+
Line: 1,
145+
Column: 1,
146+
},
147+
},
148+
},
149+
},
150+
},
151+
{
152+
Name: "has neither terraform.tf or main.tf",
153+
Files: []string{"modules/foo/variables.tf", "modules/foo/outputs.tf"},
154+
Expected: helper.Issues{
155+
{
156+
Rule: NewTerraformRequiredVersionRule(),
157+
Message: "terraform \"required_version\" attribute is required",
158+
Range: hcl.Range{
159+
Filename: filepath.FromSlash("modules/foo/terraform.tf"),
160+
},
161+
},
162+
},
163+
},
164+
}
165+
166+
rule := NewTerraformRequiredVersionRule()
167+
168+
for _, tc := range cases {
169+
t.Run(tc.Name, func(t *testing.T) {
170+
var files = map[string]string{}
171+
for _, filename := range tc.Files {
172+
files[filepath.FromSlash(filename)] = ""
173+
}
174+
runner := helper.TestRunner(t, files)
175+
if err := rule.Check(runner); err != nil {
176+
t.Fatal(err)
177+
}
178+
179+
helper.AssertIssues(t, tc.Expected, runner.Issues)
180+
})
181+
}
182+
}

0 commit comments

Comments
 (0)