rules/provider_version.go (112 lines of code) (raw):
package rules
import (
"fmt"
"strings"
goverison "github.com/hashicorp/go-version"
"github.com/terraform-linters/tflint-plugin-sdk/hclext"
"github.com/terraform-linters/tflint-plugin-sdk/tflint"
"github.com/zclconf/go-cty/cty"
)
var _ tflint.Rule = new(ProviderVersionRule)
type ProviderVersionRule struct {
tflint.DefaultRule
ProviderName string
ProviderSource string
Version string
RecommendedConstraint string
MustExist bool
}
func NewProviderVersionRule(providerName, providerSource, ver, recConstr string, mustExist bool) *ProviderVersionRule {
return &ProviderVersionRule{
ProviderName: providerName,
ProviderSource: providerSource,
Version: ver,
MustExist: mustExist,
RecommendedConstraint: recConstr,
}
}
func (m *ProviderVersionRule) Name() string {
return fmt.Sprintf("provider_%s_version_constraint", m.ProviderName)
}
func (m *ProviderVersionRule) Enabled() bool {
return true
}
func (m *ProviderVersionRule) Severity() tflint.Severity {
return tflint.ERROR
}
func (m *ProviderVersionRule) Check(r tflint.Runner) error {
ver, err := goverison.NewVersion(m.Version)
if err != nil {
return fmt.Errorf("invalid version constraint: %s", err)
}
content, err := r.GetModuleContent(&hclext.BodySchema{
Blocks: []hclext.BlockSchema{
{
Type: "terraform",
Body: &hclext.BodySchema{
Blocks: []hclext.BlockSchema{
{
Type: "required_providers",
Body: &hclext.BodySchema{
Attributes: []hclext.AttributeSchema{
{
Name: m.ProviderName,
},
},
},
},
},
},
},
},
}, &tflint.GetModuleContentOption{ExpandMode: tflint.ExpandModeNone})
if err != nil {
return err
}
if len(content.Blocks) == 0 {
return nil
}
providerFound := false
requiredProviderFound := false
for _, tb := range content.Blocks {
for _, rpb := range tb.Body.Blocks {
requiredProviderFound = true
providerAttr, ok := rpb.Body.Attributes[m.ProviderName]
if !ok {
continue
}
providerFound = true
provider := struct {
Source string `cty:"source"`
Version string `cty:"version"`
}{}
wantType := cty.Object(map[string]cty.Type{
"source": cty.String,
"version": cty.String,
})
if err = r.EvaluateExpr(providerAttr.Expr, &provider, &tflint.EvaluateExprOption{WantType: &wantType}); err != nil {
return err
}
if !strings.EqualFold(provider.Source, m.ProviderSource) {
return r.EmitIssue(m, fmt.Sprintf("provider `%s`'s source should be %s, got %s", m.ProviderName, m.ProviderSource, provider.Source), providerAttr.Range)
}
constraint, err := goverison.NewConstraint(provider.Version)
if err != nil {
return fmt.Errorf("invalid version constraint: %s", err)
}
if constraint.Check(ver) {
continue
}
if err = r.EmitIssue(m, fmt.Sprintf("provider `%s`'s version should satisfy %s, got %s. Recommended version constraint `%s`", m.ProviderName, m.Version, provider.Version, m.RecommendedConstraint), providerAttr.Range); err != nil {
return err
}
}
}
if !requiredProviderFound {
return nil
}
if !providerFound && m.MustExist {
return r.EmitIssue(m, fmt.Sprintf("`%s` provider should be declared in the `required_providers` block", m.ProviderName), content.Blocks[0].DefRange)
}
return nil
}