rules/terraform_output_order.go (93 lines of code) (raw):
package rules
import (
"fmt"
"github.com/terraform-linters/tflint-plugin-sdk/logger"
"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"
)
// TerraformOutputOrderRule checks whether the outputs are sorted in expected order
type TerraformOutputOrderRule struct {
tflint.DefaultRule
}
// NewTerraformOutputOrderRule returns a new rule
func NewTerraformOutputOrderRule() *TerraformOutputOrderRule {
return &TerraformOutputOrderRule{}
}
// Name returns the rule name
func (r *TerraformOutputOrderRule) Name() string {
return "terraform_output_order"
}
// Enabled returns whether the rule is enabled by default
func (r *TerraformOutputOrderRule) Enabled() bool {
return false
}
// Severity returns the rule severity
func (r *TerraformOutputOrderRule) Severity() tflint.Severity {
return tflint.NOTICE
}
// Link returns the rule reference link
func (r *TerraformOutputOrderRule) Link() string {
return project.ReferenceLink(r.Name())
}
// Check checks whether the outputs are sorted in expected order
func (r *TerraformOutputOrderRule) Check(runner tflint.Runner) error {
files, err := runner.GetFiles()
if err != nil {
return err
}
for _, file := range files {
if subErr := r.checkOutputOrder(runner, file); subErr != nil {
err = multierror.Append(err, subErr)
}
}
return err
}
func (r *TerraformOutputOrderRule) checkOutputOrder(runner tflint.Runner, file *hcl.File) error {
body, ok := file.Body.(*hclsyntax.Body)
if !ok {
logger.Debug("skip terraform_output_order check since it's not hcl file")
return nil
}
blocks := body.Blocks
firstOutputBlockRange := r.firstOutputRange(blocks)
if firstOutputBlockRange == nil {
return nil
}
if r.sorted(blocks) {
return nil
}
return r.suggestedOrder(runner, file, blocks, firstOutputBlockRange)
}
func (r *TerraformOutputOrderRule) suggestedOrder(runner tflint.Runner, file *hcl.File, blocks hclsyntax.Blocks, firstOutputBlockRange *hcl.Range) error {
sort.Slice(blocks, func(i, j int) bool {
return blocks[i].Labels[0] < blocks[j].Labels[0]
})
var sortedOutputHclTxts []string
for _, b := range blocks {
sortedOutputHclTxts = append(sortedOutputHclTxts, string(b.Range().SliceBytes(file.Bytes)))
}
sortedOutputHclBytes := hclwrite.Format([]byte(strings.Join(sortedOutputHclTxts, "\n\n")))
return runner.EmitIssue(
r,
fmt.Sprintf("Recommended output order:\n%s", sortedOutputHclBytes),
*firstOutputBlockRange,
)
}
func (r *TerraformOutputOrderRule) sorted(blocks hclsyntax.Blocks) bool {
var outputNames []string
for _, block := range blocks {
switch block.Type {
case "output":
outputNames = append(outputNames, block.Labels[0])
}
}
return sort.StringsAreSorted(outputNames)
}
func (r *TerraformOutputOrderRule) firstOutputRange(blocks hclsyntax.Blocks) *hcl.Range {
for _, b := range blocks {
switch b.Type {
case "output":
return ref(b.DefRange())
}
}
return nil
}