rules/terraform_variable_order.go (116 lines of code) (raw):
package rules
import (
"fmt"
"github.com/terraform-linters/tflint-plugin-sdk/logger"
"reflect"
"sort"
"strings"
"github.com/Azure/tflint-ruleset-basic-ext/project"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/hcl/v2/hclwrite"
"github.com/terraform-linters/tflint-plugin-sdk/tflint"
)
// TerraformVariableOrderRule checks whether the variables are sorted in expected order
type TerraformVariableOrderRule struct {
tflint.DefaultRule
}
// NewTerraformVariableOrderRule returns a new rule
func NewTerraformVariableOrderRule() *TerraformVariableOrderRule {
return &TerraformVariableOrderRule{}
}
// Name returns the rule name
func (r *TerraformVariableOrderRule) Name() string {
return "terraform_variable_order"
}
// Enabled returns whether the rule is enabled by default
func (r *TerraformVariableOrderRule) Enabled() bool {
return false
}
// Severity returns the rule severity
func (r *TerraformVariableOrderRule) Severity() tflint.Severity {
return tflint.NOTICE
}
// Link returns the rule reference link
func (r *TerraformVariableOrderRule) Link() string {
return project.ReferenceLink(r.Name())
}
// Check checks whether the variables are sorted in expected order
func (r *TerraformVariableOrderRule) Check(runner tflint.Runner) error {
files, err := runner.GetFiles()
if err != nil {
return err
}
for _, file := range files {
if subErr := r.checkVariableOrder(runner, file); subErr != nil {
err = multierror.Append(err, subErr)
}
}
return err
}
func (r *TerraformVariableOrderRule) checkVariableOrder(runner tflint.Runner, file *hcl.File) error {
body, ok := file.Body.(*hclsyntax.Body)
if !ok {
logger.Debug("skip terraform_variable_order check since it's not hcl file")
return nil
}
blocks := body.Blocks
requiredVars := r.getSortedVariableNames(blocks, false)
optionalVars := r.getSortedVariableNames(blocks, true)
sortedVariableNames := append(requiredVars, optionalVars...)
variableNames := r.getVariableNames(blocks)
if reflect.DeepEqual(variableNames, sortedVariableNames) {
return nil
}
firstRange := r.firstVariableRange(blocks)
sortedVariableHclTxts := r.sortedVariableCodeTxts(blocks, file, sortedVariableNames)
sortedVariableHclBytes := hclwrite.Format([]byte(strings.Join(sortedVariableHclTxts, "\n\n")))
return runner.EmitIssue(
r,
fmt.Sprintf("Recommended variable order:\n%s", sortedVariableHclBytes),
*firstRange,
)
}
func (r *TerraformVariableOrderRule) sortedVariableCodeTxts(blocks hclsyntax.Blocks, file *hcl.File, sortedVariableNames []string) []string {
variableHclTxts := r.variableCodeTxts(blocks, file)
var sortedVariableHclTxts []string
for _, name := range sortedVariableNames {
sortedVariableHclTxts = append(sortedVariableHclTxts, variableHclTxts[name])
}
return sortedVariableHclTxts
}
func (r *TerraformVariableOrderRule) variableCodeTxts(blocks hclsyntax.Blocks, file *hcl.File) map[string]string {
variableHclTxts := make(map[string]string)
r.forVariables(blocks, func(v *hclsyntax.Block) {
name := v.Labels[0]
variableHclTxts[name] = string(v.Range().SliceBytes(file.Bytes))
})
return variableHclTxts
}
func (r *TerraformVariableOrderRule) firstVariableRange(blocks hclsyntax.Blocks) *hcl.Range {
var firstRange *hcl.Range
r.forVariables(blocks, func(v *hclsyntax.Block) {
if firstRange == nil {
firstRange = ref(v.DefRange())
}
})
return firstRange
}
func (r *TerraformVariableOrderRule) getVariableNames(blocks hclsyntax.Blocks) []string {
var variableNames []string
r.forVariables(blocks, func(v *hclsyntax.Block) {
variableNames = append(variableNames, v.Labels[0])
})
return variableNames
}
func (r *TerraformVariableOrderRule) getSortedVariableNames(blocks hclsyntax.Blocks, defaultWanted bool) []string {
var sortedVariableNames []string
r.forVariables(blocks, func(v *hclsyntax.Block) {
if _, hasDefault := v.Body.Attributes["default"]; hasDefault == defaultWanted {
sortedVariableNames = append(sortedVariableNames, v.Labels[0])
}
})
sort.Strings(sortedVariableNames)
return sortedVariableNames
}
func (r *TerraformVariableOrderRule) forVariables(blocks hclsyntax.Blocks, action func(v *hclsyntax.Block)) {
for _, block := range blocks {
if block.Type == "variable" {
action(block)
}
}
}