tflint-ruleset-blueprint/rules/terraform_required_version_range.go (139 lines of code) (raw):

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/logger" "github.com/terraform-linters/tflint-plugin-sdk/tflint" ) // TerraformRequiredVersionRange checks if a module has a terraform required_version within valid range. type TerraformRequiredVersionRange struct { tflint.DefaultRule } // TerraformRequiredVersionRangeConfig is a config of TerraformRequiredVersionRange type TerraformRequiredVersionRangeConfig struct { MinVersion string `hclext:"min_version,optional"` MaxVersion string `hclext:"max_version,optional"` } // NewTerraformRequiredVersionRange returns a new rule. func NewTerraformRequiredVersionRange() *TerraformRequiredVersionRange { return &TerraformRequiredVersionRange{} } // Name returns the rule name. func (r *TerraformRequiredVersionRange) Name() string { return "terraform_required_version_range" } // Enabled returns whether the rule is enabled by default. func (r *TerraformRequiredVersionRange) Enabled() bool { return false } // Severity returns the rule severity. func (r *TerraformRequiredVersionRange) Severity() tflint.Severity { return tflint.ERROR } // Link returns the rule reference link func (r *TerraformRequiredVersionRange) Link() string { return "https://googlecloudplatform.github.io/samples-style-guide/#language-specific" } const ( minimumTerraformRequiredVersionRange = "1.3" maximumTerraformRequiredVersionRange = "1.5" ) // Checks if a module has a terraform required_version within valid range. func (r *TerraformRequiredVersionRange) Check(runner tflint.Runner) error { config := &TerraformRequiredVersionRangeConfig{} if err := runner.DecodeRuleConfig(r.Name(), config); err != nil { return err } minVersion := minimumTerraformRequiredVersionRange if config.MinVersion != "" { if _, err := version.NewSemver(config.MinVersion); err != nil { return err } minVersion = config.MinVersion } maxVersion := maximumTerraformRequiredVersionRange if config.MaxVersion != "" { if _, err := version.NewSemver(config.MaxVersion); err != nil { return err } maxVersion = config.MaxVersion } logger.Info(fmt.Sprintf("Running with min_version: %q max_version: %q", minVersion, maxVersion)) splitVersion := strings.Split(minVersion, ".") 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 { if majorVersion == 0 { return fmt.Errorf("Error: minimum version test constraint would be below zero: v%d.%d.999", majorVersion - 1, 999) } 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(minVersion) if err != nil { return err } maximum_required_version, err := version.NewVersion(maxVersion) 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 { requiredVersion, exists := block.Body.Attributes["required_version"] if !exists { logger.Info(fmt.Sprintf("terraform block does not contain required_version: %s", block.DefRange)) continue } var raw_terraform_required_version string diags := gohcl.DecodeExpression(requiredVersion.Expr, nil, &raw_terraform_required_version) if diags.HasErrors() { return fmt.Errorf("failed to decode terraform block required_version: %v", diags.Error()) } constraints, err := version.NewConstraint(raw_terraform_required_version) if err != nil { return err } 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", minVersion, maxVersion, constraints.String()), block.DefRange) if err != nil { return err } } } return nil }