rules/terraform_required_providers_declaration.go (129 lines of code) (raw):

package rules import ( "fmt" "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/helper" "github.com/terraform-linters/tflint-plugin-sdk/logger" "github.com/terraform-linters/tflint-plugin-sdk/tflint" "sort" "strings" ) var _ tflint.Rule = &TerraformRequiredProvidersDeclarationRule{} // TerraformRequiredProvidersDeclarationRule checks whether the required_providers block is declared in terraform block and whether the args of it are sorted in alphabetic order type TerraformRequiredProvidersDeclarationRule struct { tflint.DefaultRule } func (r *TerraformRequiredProvidersDeclarationRule) Enabled() bool { return false } func (r *TerraformRequiredProvidersDeclarationRule) Severity() tflint.Severity { return tflint.NOTICE } func (r *TerraformRequiredProvidersDeclarationRule) Check(runner tflint.Runner) error { return ForFiles(runner, r.CheckFile) } // NewTerraformRequiredProvidersDeclarationRule returns a new rule func NewTerraformRequiredProvidersDeclarationRule() *TerraformRequiredProvidersDeclarationRule { return &TerraformRequiredProvidersDeclarationRule{} } // Name returns the rule name func (r *TerraformRequiredProvidersDeclarationRule) Name() string { return "terraform_required_providers_declaration" } func (r *TerraformRequiredProvidersDeclarationRule) CheckFile(runner tflint.Runner, file *hcl.File) error { var err error body, ok := file.Body.(*hclsyntax.Body) if !ok { logger.Debug("skip terraform_required_providers_declaration check since it's not hcl file") return nil } filename := body.Range().Filename if isOverrideTfFile(filename) { logger.Debug("skip terraform_required_version_declaration check since it's override file") return nil } blocks := body.Blocks for _, block := range blocks { switch block.Type { case "terraform": if subErr := r.checkBlock(runner, block); subErr != nil { err = multierror.Append(err, subErr) } } } return err } func (r *TerraformRequiredProvidersDeclarationRule) checkBlock(runner tflint.Runner, block *hclsyntax.Block) error { isRequiredProvidersDeclared := false var err error for _, nestedBlock := range block.Body.Blocks { switch nestedBlock.Type { case "required_providers": isRequiredProvidersDeclared = true err = multierror.Append(err, r.checkRequiredProvidersArgOrder(runner, nestedBlock)) } } if isRequiredProvidersDeclared { return nil } return runner.EmitIssue( r, "The `required_providers` field should be declared in `terraform` block", block.DefRange(), ) } func (r *TerraformRequiredProvidersDeclarationRule) checkRequiredProvidersArgOrder(runner tflint.Runner, providerBlock *hclsyntax.Block) error { file, _ := runner.GetFile(providerBlock.Range().Filename) var providerNames []string providerParamTxts := make(map[string]string) providerParamIssues := helper.Issues{} providers := providerBlock.Body.Attributes for _, config := range attributesByLines(providers) { sortedMap, sorted := PrintSortedAttrTxt(file.Bytes, config) name := config.Name providerParamTxts[name] = sortedMap providerNames = append(providerNames, name) if !sorted { providerParamIssues = append(providerParamIssues, &helper.Issue{ Rule: r, Message: fmt.Sprintf("Parameters of provider `%s` are expected to be sorted as follows:\n%s", name, sortedMap), Range: config.NameRange, }) } } sort.Slice(providerNames, func(x, y int) bool { providerX := providers[providerNames[x]] providerY := providers[providerNames[y]] if providerX.Range().Start.Line == providerY.Range().Start.Line { return providerX.Range().Start.Column < providerY.Range().Start.Column } return providerX.Range().Start.Line < providerY.Range().Start.Line }) if !sort.StringsAreSorted(providerNames) { sort.Strings(providerNames) var sortedProviderParamTxts []string for _, providerName := range providerNames { sortedProviderParamTxts = append(sortedProviderParamTxts, providerParamTxts[providerName]) } sortedProviderParamTxt := strings.Join(sortedProviderParamTxts, "\n") var sortedRequiredProviderTxt string if RemoveSpaceAndLine(sortedProviderParamTxt) == "" { sortedRequiredProviderTxt = fmt.Sprintf("%s {}", providerBlock.Type) } else { sortedRequiredProviderTxt = fmt.Sprintf("%s {\n%s\n}", providerBlock.Type, sortedProviderParamTxt) } sortedRequiredProviderTxt = string(hclwrite.Format([]byte(sortedRequiredProviderTxt))) return runner.EmitIssue( r, fmt.Sprintf("The arguments of `required_providers` are expected to be sorted as follows:\n%s", sortedRequiredProviderTxt), providerBlock.DefRange(), ) } var err error for _, issue := range providerParamIssues { if subErr := runner.EmitIssue(issue.Rule, issue.Message, issue.Range); subErr != nil { err = multierror.Append(err, subErr) } } return err }