Skip to content

Add range to terraform_required_version errors #178

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 51 additions & 7 deletions rules/terraform_required_version.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package rules

import (
"path/filepath"

"github.com/hashicorp/hcl/v2"
"github.com/terraform-linters/tflint-plugin-sdk/hclext"
"github.com/terraform-linters/tflint-plugin-sdk/tflint"
Expand Down Expand Up @@ -78,13 +80,55 @@ func (r *TerraformRequiredVersionRule) Check(runner tflint.Runner) error {
exists = exists || ok
}

if !exists {
return runner.EmitIssue(
r,
`terraform "required_version" attribute is required`,
hcl.Range{},
)
if exists {
return nil
}

if len(body.Blocks) > 0 {
return r.emitIssue(body.Blocks[0].DefRange, runner)
}

return nil
// If there are no "terraform" blocks, create a hcl.Range from the files
var file string
for k := range files {
file = k
break
}

// If there is only one file, use that
if len(files) == 1 {
return r.emitIssue(hcl.Range{
Filename: file,
Start: hcl.InitialPos,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may need to specify the End as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should that also be hcl.InitialPos?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, a range where the End is less than the Start may not be valid.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, done!

End: hcl.InitialPos,
}, runner)
}

moduleDirectory := filepath.Dir(file)

// If there are multiple files, look for terraform.tf or main.tf (in that order)
for _, basename := range []string{"terraform.tf", "main.tf"} {
filename := filepath.Join(moduleDirectory, basename)
if _, ok := files[filename]; ok {
return r.emitIssue(hcl.Range{
Filename: filename,
Start: hcl.InitialPos,
End: hcl.InitialPos,
}, runner)
}
}

// If none of those are found, point to a nonexistent terraform.tf per the style guide
return r.emitIssue(hcl.Range{
Filename: filepath.Join(moduleDirectory, "terraform.tf"),
}, runner)
}

// emitIssue emits issue for missing terraform require version
func (r *TerraformRequiredVersionRule) emitIssue(missingRange hcl.Range, runner tflint.Runner) error {
return runner.EmitIssue(
r,
`terraform "required_version" attribute is required`,
missingRange,
)
}
119 changes: 119 additions & 0 deletions rules/terraform_required_version_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package rules

import (
"path/filepath"
"testing"

"github.com/hashicorp/hcl/v2"
"github.com/terraform-linters/tflint-plugin-sdk/helper"
)

Expand All @@ -25,6 +27,17 @@ terraform {}
{
Rule: NewTerraformRequiredVersionRule(),
Message: "terraform \"required_version\" attribute is required",
Range: hcl.Range{
Filename: "module.tf",
Start: hcl.Pos{
Line: 2,
Column: 1,
},
End: hcl.Pos{
Line: 2,
Column: 10,
},
},
},
},
},
Expand Down Expand Up @@ -53,6 +66,31 @@ terraform {
`,
Expected: helper.Issues{},
},
{
Name: "no terraform block",
Content: `
locals {
foo = "bar"
}
`,
Expected: helper.Issues{
{
Rule: NewTerraformRequiredVersionRule(),
Message: "terraform \"required_version\" attribute is required",
Range: hcl.Range{
Filename: "module.tf",
Start: hcl.Pos{
Line: 1,
Column: 1,
},
End: hcl.Pos{
Line: 1,
Column: 1,
},
},
},
},
},
}

rule := NewTerraformRequiredVersionRule()
Expand All @@ -73,3 +111,84 @@ terraform {
})
}
}

func Test_TerraformRequiredVersionRuleMultipleFiles(t *testing.T) {
cases := []struct {
Name string
Files []string
Expected helper.Issues
}{
{
Name: "has terraform.tf and main.tf",
Files: []string{"modules/foo/main.tf", "modules/foo/terraform.tf"},
Expected: helper.Issues{
{
Rule: NewTerraformRequiredVersionRule(),
Message: "terraform \"required_version\" attribute is required",
Range: hcl.Range{
Filename: filepath.FromSlash("modules/foo/terraform.tf"),
Start: hcl.Pos{
Line: 1,
Column: 1,
},
End: hcl.Pos{
Line: 1,
Column: 1,
},
},
},
},
},
{
Name: "has main.tf",
Files: []string{"modules/foo/outputs.tf", "modules/foo/main.tf", "modules/foo/variables.tf"},
Expected: helper.Issues{
{
Rule: NewTerraformRequiredVersionRule(),
Message: "terraform \"required_version\" attribute is required",
Range: hcl.Range{
Filename: filepath.FromSlash("modules/foo/main.tf"),
Start: hcl.Pos{
Line: 1,
Column: 1,
},
End: hcl.Pos{
Line: 1,
Column: 1,
},
},
},
},
},
{
Name: "has neither terraform.tf or main.tf",
Files: []string{"modules/foo/variables.tf", "modules/foo/outputs.tf"},
Expected: helper.Issues{
{
Rule: NewTerraformRequiredVersionRule(),
Message: "terraform \"required_version\" attribute is required",
Range: hcl.Range{
Filename: filepath.FromSlash("modules/foo/terraform.tf"),
},
},
},
},
}

rule := NewTerraformRequiredVersionRule()

for _, tc := range cases {
t.Run(tc.Name, func(t *testing.T) {
var files = map[string]string{}
for _, filename := range tc.Files {
files[filepath.FromSlash(filename)] = ""
}
runner := helper.TestRunner(t, files)
if err := rule.Check(runner); err != nil {
t.Fatal(err)
}

helper.AssertIssues(t, tc.Expected, runner.Issues)
})
}
}