rules/terraform_heredoc_usage.go (90 lines of code) (raw):
package rules
import (
"encoding/json"
"fmt"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/terraform-linters/tflint-plugin-sdk/logger"
"github.com/terraform-linters/tflint-plugin-sdk/tflint"
"gopkg.in/yaml.v3"
"strings"
)
var _ tflint.Rule = &TerraformHeredocUsageRule{}
// TerraformHeredocUsageRule checks whether HEREDOC is used for JSON/YAML
type TerraformHeredocUsageRule struct {
tflint.DefaultRule
}
// NewTerraformHeredocUsageRule returns a new rule
func NewTerraformHeredocUsageRule() *TerraformHeredocUsageRule {
return &TerraformHeredocUsageRule{}
}
func (r *TerraformHeredocUsageRule) Enabled() bool {
return false
}
func (r *TerraformHeredocUsageRule) Severity() tflint.Severity {
return tflint.NOTICE
}
func (r *TerraformHeredocUsageRule) Check(runner tflint.Runner) error {
return ForFiles(runner, r.CheckFile)
}
// Name returns the rule name
func (r *TerraformHeredocUsageRule) Name() string {
return "terraform_heredoc_usage"
}
func (r *TerraformHeredocUsageRule) CheckFile(runner tflint.Runner, file *hcl.File) error {
body, ok := file.Body.(*hclsyntax.Body)
if !ok {
logger.Debug("skip terraform_heredoc_usage since it's not hcl file")
return nil
}
fileName := body.Range().Filename
tokens, diags := hclsyntax.LexConfig(file.Bytes, fileName, hcl.InitialPos)
if diags.HasErrors() {
return diags
}
var err error
var hereDocStartRange hcl.Range
var heredoc string
var inHeredoc bool
for _, token := range tokens {
switch token.Type {
case hclsyntax.TokenOHeredoc:
inHeredoc = true
heredoc = ""
hereDocStartRange = token.Range
case hclsyntax.TokenCHeredoc:
inHeredoc = false
if subErr := r.checkHeredocIsJSONOrYAML(runner, heredoc, hereDocStartRange); subErr != nil {
err = multierror.Append(err, subErr)
}
case hclsyntax.TokenStringLit:
if inHeredoc {
heredoc = fmt.Sprintf("%s%s", heredoc, string(token.Bytes))
}
}
}
return err
}
func (r *TerraformHeredocUsageRule) checkHeredocIsJSONOrYAML(runner tflint.Runner, heredoc string, heredocStartRange hcl.Range) error {
prunedHereDoc := strings.ReplaceAll(heredoc, "\t", "")
prunedHereDoc = strings.ReplaceAll(prunedHereDoc, " ", "")
prunedHereDoc = strings.ReplaceAll(prunedHereDoc, "\n", "")
if prunedHereDoc == "" {
return nil
}
bytes := []byte(heredoc)
if json.Valid(bytes) {
return runner.EmitIssue(
r,
"for JSON, instead of HEREDOC, use a combination of a `local` and the `jsonencode` function",
heredocStartRange,
)
}
temp := map[string]interface{}{}
if yaml.Unmarshal(bytes, &temp) == nil {
return runner.EmitIssue(
r,
"for YAML, instead of HEREDOC, use a combination of a `local` and the `yamlencode` function",
heredocStartRange,
)
}
return nil
}