Skip to content

Commit

Permalink
feat(tflint-ruleset): add terraform_required_version
Browse files Browse the repository at this point in the history
  • Loading branch information
apeabody committed Aug 2, 2024
1 parent e88be7c commit 9ace8cd
Show file tree
Hide file tree
Showing 8 changed files with 400 additions and 10 deletions.
12 changes: 6 additions & 6 deletions go.work
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
go 1.22
go 1.22.2

use (
./cli
./infra/blueprint-test
./infra/module-swapper
./infra/utils/fbf
./tflint-ruleset-blueprint
./cli
./infra/blueprint-test
./infra/module-swapper
./infra/utils/fbf
./tflint-ruleset-blueprint
)
10 changes: 6 additions & 4 deletions tflint-ruleset-blueprint/go.mod
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
module github.com/cloud-foundation-toolkit/tflint-ruleset-blueprint

go 1.22
go 1.22.2

toolchain go1.22.5

require github.com/terraform-linters/tflint-plugin-sdk v0.21.0
require (
github.com/hashicorp/go-version v1.7.0
github.com/hashicorp/hcl/v2 v2.21.0
github.com/terraform-linters/tflint-plugin-sdk v0.21.0
)

require (
github.com/agext/levenshtein v1.2.1 // indirect
Expand All @@ -14,8 +18,6 @@ require (
github.com/google/go-cmp v0.6.0 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-plugin v1.6.1 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/hashicorp/hcl/v2 v2.21.0 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
Expand Down
138 changes: 138 additions & 0 deletions tflint-ruleset-blueprint/rules/terraform_required_version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package rules

import (
"fmt"
"strconv"
"strings"

"github.com/hashicorp/go-version"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/terraform-linters/tflint-plugin-sdk/hclext"
"github.com/terraform-linters/tflint-plugin-sdk/tflint"
)

// TerraformRequiredVersion checks if a module has a terraform required_version within valid range.
type TerraformRequiredVersion struct {
tflint.DefaultRule
}

// NewTerraformRequiredVersion returns a new rule.
func NewTerraformRequiredVersion() *TerraformRequiredVersion {
return &TerraformRequiredVersion{}
}

// Name returns the rule name.
func (r *TerraformRequiredVersion) Name() string {
return "terraform_required_version"
}

// Enabled returns whether the rule is enabled by default.
func (r *TerraformRequiredVersion) Enabled() bool {
return false
}

// Severity returns the rule severity.
func (r *TerraformRequiredVersion) Severity() tflint.Severity {
return tflint.ERROR
}

// Link returns the rule reference link
func (r *TerraformRequiredVersion) Link() string {
return "https://googlecloudplatform.github.io/samples-style-guide/#language-specific"
}

const (
minimum_terraform_required_version = "1.3"
maximum_terraform_required_version = "1.5"
)

// Check checks whether config contains restricted resource types.
func (r *TerraformRequiredVersion) Check(runner tflint.Runner) error {
splitVersion := strings.Split(minimum_terraform_required_version, ".")
majorVersion, err := strconv.Atoi(splitVersion[0])
if err != nil {
return err
}
minorVersion, err := strconv.Atoi(splitVersion[1])
if err != nil {
return err
}

var terraform_below_minimum_required_version string
if minorVersion > 0 {
terraform_below_minimum_required_version = fmt.Sprintf(
"v%d.%d.999",
majorVersion,
minorVersion - 1,
)
} else {
terraform_below_minimum_required_version = fmt.Sprintf(
"v%d.%d.999",
majorVersion - 1,
999,
)
}

below_required_version, err := version.NewVersion(terraform_below_minimum_required_version)
if err != nil {
return err
}

minimum_required_version, err := version.NewVersion(minimum_terraform_required_version)
if err != nil {
return err
}

maximum_required_version, err := version.NewVersion(maximum_terraform_required_version)
if err != nil {
return err
}

path, err := runner.GetModulePath()
if err != nil {
return err
}

if !path.IsRoot() {
return nil
}

content, err := runner.GetModuleContent(&hclext.BodySchema{
Blocks: []hclext.BlockSchema{
{
Type: "terraform",
Body: &hclext.BodySchema{
Attributes: []hclext.AttributeSchema{{Name: "required_version"}},
},
},
},
}, &tflint.GetModuleContentOption{ExpandMode: tflint.ExpandModeNone})
if err != nil {
return err
}

for _, block := range content.Blocks {
var raw_terraform_required_version string
diags := gohcl.DecodeExpression(block.Body.Attributes["required_version"].Expr, nil, &raw_terraform_required_version)
if diags.HasErrors() {
return fmt.Errorf("failed to decode terraform required_version %q: %v", block.Labels[0], diags.Error())
}

constraints, err := version.NewConstraint(raw_terraform_required_version)
if err != nil {
return err
}

//TODO: add option for exemptions
if !((constraints.Check(minimum_required_version) || constraints.Check(maximum_required_version)) && !constraints.Check(below_required_version)) {
//TODO: use EmitIssueWithFix()
err := runner.EmitIssue(r, fmt.Sprintf("required_version is not inclusive of the the minimum %q and maximum %q terraform required_version: %q", minimum_terraform_required_version, maximum_terraform_required_version, constraints.String()), block.DefRange)
if err != nil {
return err
}
}

}

return nil
}
29 changes: 29 additions & 0 deletions tflint-ruleset-blueprint/rules/terraform_required_version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package rules

import (
"path"
"testing"
)

const (
terraformRequiredVersionTestDir = "terraform_required_version"
)

func TestTerraformMinimumRequiredVersion(t *testing.T) {
tests := []ruleTC{
{
dir: path.Join(terraformRequiredVersionTestDir, "multiple-valid"),
},
{
dir: path.Join(terraformRequiredVersionTestDir, "multiple-invalid"),
},
}

rule := NewTerraformRequiredVersion()

for _, tc := range tests {
t.Run(tc.dir, func(t *testing.T) {
ruleTest(t, rule, tc)
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
[
{
"Message": "required_version is not inclusive of the the minimum \"1.3\" and maximum \"1.5\" terraform required_version: \">= 1\"",
"Range": {
"Filename": "main.tf",
"Start": {
"Line": 1,
"Column": 1
},
"End": {
"Line": 1,
"Column": 10
}
}
},
{
"Message": "required_version is not inclusive of the the minimum \"1.3\" and maximum \"1.5\" terraform required_version: \">= 1.1\"",
"Range": {
"Filename": "main.tf",
"Start": {
"Line": 5,
"Column": 1
},
"End": {
"Line": 5,
"Column": 10
}
}
},
{
"Message": "required_version is not inclusive of the the minimum \"1.3\" and maximum \"1.5\" terraform required_version: \">= 1.1.0\"",
"Range": {
"Filename": "main.tf",
"Start": {
"Line": 9,
"Column": 1
},
"End": {
"Line": 9,
"Column": 10
}
}
},
{
"Message": "required_version is not inclusive of the the minimum \"1.3\" and maximum \"1.5\" terraform required_version: \">=1.1.0\"",
"Range": {
"Filename": "main.tf",
"Start": {
"Line": 13,
"Column": 1
},
"End": {
"Line": 13,
"Column": 10
}
}
},
{
"Message": "required_version is not inclusive of the the minimum \"1.3\" and maximum \"1.5\" terraform required_version: \">= 1.1.0, < 2.0\"",
"Range": {
"Filename": "main.tf",
"Start": {
"Line": 17,
"Column": 1
},
"End": {
"Line": 17,
"Column": 10
}
}
},
{
"Message": "required_version is not inclusive of the the minimum \"1.3\" and maximum \"1.5\" terraform required_version: \">=0.13.0\"",
"Range": {
"Filename": "main.tf",
"Start": {
"Line": 21,
"Column": 1
},
"End": {
"Line": 21,
"Column": 10
}
}
},
{
"Message": "required_version is not inclusive of the the minimum \"1.3\" and maximum \"1.5\" terraform required_version: \"=0.13.0\"",
"Range": {
"Filename": "main.tf",
"Start": {
"Line": 25,
"Column": 1
},
"End": {
"Line": 25,
"Column": 10
}
}
},
{
"Message": "required_version is not inclusive of the the minimum \"1.3\" and maximum \"1.5\" terraform required_version: \"0.13.0\"",
"Range": {
"Filename": "main.tf",
"Start": {
"Line": 29,
"Column": 1
},
"End": {
"Line": 29,
"Column": 10
}
}
},
{
"Message": "required_version is not inclusive of the the minimum \"1.3\" and maximum \"1.5\" terraform required_version: \">= 1.6.0\"",
"Range": {
"Filename": "main.tf",
"Start": {
"Line": 33,
"Column": 1
},
"End": {
"Line": 33,
"Column": 10
}
}
},
{
"Message": "required_version is not inclusive of the the minimum \"1.3\" and maximum \"1.5\" terraform required_version: \">= 1.6.0, < 2.0\"",
"Range": {
"Filename": "main.tf",
"Start": {
"Line": 37,
"Column": 1
},
"End": {
"Line": 37,
"Column": 10
}
}
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
terraform {
required_version = ">= 1"
}

terraform {
required_version = ">= 1.1"
}

terraform {
required_version = ">= 1.1.0"
}

terraform {
required_version = ">=1.1.0"
}

terraform {
required_version = ">= 1.1.0, < 2.0"
}

terraform {
required_version = ">=0.13.0"
}

terraform {
required_version = "=0.13.0"
}

terraform {
required_version = "0.13.0"
}

terraform {
required_version = ">= 1.6.0"
}

terraform {
required_version = ">= 1.6.0, < 2.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
Loading

0 comments on commit 9ace8cd

Please sign in to comment.