Skip to content

Commit 45f7f06

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

File tree

2 files changed

+170
-7
lines changed

2 files changed

+170
-7
lines changed

rules/terraform_required_version.go

Lines changed: 51 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,55 @@ 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+
End: hcl.InitialPos,
104+
}, runner)
105+
}
106+
107+
moduleDirectory := filepath.Dir(file)
108+
109+
// If there are multiple files, look for terraform.tf or main.tf (in that order)
110+
for _, basename := range []string{"terraform.tf", "main.tf"} {
111+
filename := filepath.Join(moduleDirectory, basename)
112+
if _, ok := files[filename]; ok {
113+
return r.emitIssue(hcl.Range{
114+
Filename: filename,
115+
Start: hcl.InitialPos,
116+
End: hcl.InitialPos,
117+
}, runner)
118+
}
119+
}
120+
121+
// If none of those are found, point to a nonexistent terraform.tf per the style guide
122+
return r.emitIssue(hcl.Range{
123+
Filename: filepath.Join(moduleDirectory, "terraform.tf"),
124+
}, runner)
125+
}
126+
127+
// emitIssue emits issue for missing terraform require version
128+
func (r *TerraformRequiredVersionRule) emitIssue(missingRange hcl.Range, runner tflint.Runner) error {
129+
return runner.EmitIssue(
130+
r,
131+
`terraform "required_version" attribute is required`,
132+
missingRange,
133+
)
90134
}

rules/terraform_required_version_test.go

Lines changed: 119 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,31 @@ 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+
End: hcl.Pos{
87+
Line: 1,
88+
Column: 1,
89+
},
90+
},
91+
},
92+
},
93+
},
5694
}
5795

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

0 commit comments

Comments
 (0)