Skip to content

Commit 9dfba73

Browse files
authored
required_providers: warn on legacy version syntax, missing source (#64)
1 parent 896da1d commit 9dfba73

File tree

3 files changed

+295
-34
lines changed

3 files changed

+295
-34
lines changed

docs/rules/terraform_required_providers.md

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# terraform_required_providers
22

3-
Require that all providers have version constraints through `required_providers`.
3+
Require that all providers specify a `source` and `version` constraint through `required_providers`.
44

55
> This rule is enabled by "recommended" preset.
66
@@ -9,6 +9,10 @@ Require that all providers have version constraints through `required_providers`
99
```hcl
1010
rule "terraform_required_providers" {
1111
enabled = true
12+
13+
# defaults
14+
source = true
15+
version = true
1216
}
1317
```
1418

@@ -22,7 +26,7 @@ provider "template" {}
2226
$ tflint
2327
1 issue(s) found:
2428
25-
Warning: Missing version constraint for provider "template" in "required_providers" (terraform_required_providers)
29+
Warning: Missing version constraint for provider "template" in `required_providers` (terraform_required_providers)
2630
2731
on main.tf line 1:
2832
1: provider "template" {}
@@ -49,28 +53,89 @@ Warning: provider.template: version constraint should be specified via "required
4953
5054
Reference: https://github.com/terraform-linters/tflint-ruleset-terraform/blob/v0.1.0/docs/rules/terraform_required_providers.md
5155
52-
Warning: Missing version constraint for provider "template" in "required_providers" (terraform_required_providers)
56+
Warning: Missing version constraint for provider "template" in `required_providers` (terraform_required_providers)
5357
5458
on main.tf line 1:
5559
1: provider "template" {
5660
5761
Reference: https://github.com/terraform-linters/tflint-ruleset-terraform/blob/v0.1.0/docs/rules/terraform_required_providers.md
5862
```
5963

64+
<hr>
65+
66+
```hcl
67+
provider "template" {}
68+
69+
terraform {
70+
required_providers {
71+
template = {
72+
version = "~> 2"
73+
}
74+
}
75+
}
76+
```
77+
78+
```
79+
$ tflint
80+
1 issue(s) found:
81+
82+
Warning: Legacy version constraint for provider "template" in `required_providers` (terraform_required_providers)
83+
84+
on main.tf line 5:
85+
5: template = "~> 2"
86+
87+
Reference: https://github.com/terraform-linters/tflint-ruleset-terraform/blob/v0.1.0/docs/rules/terraform_required_providers.md
88+
```
89+
90+
<hr>
91+
92+
```hcl
93+
provider "template" {}
94+
95+
terraform {
96+
required_providers {
97+
template = {
98+
version = "~> 2"
99+
}
100+
}
101+
}
102+
```
103+
104+
```
105+
$ tflint
106+
1 issue(s) found:
107+
108+
Warning: Missing `source` for provider "template" in `required_providers` (terraform_required_providers)
109+
110+
on main.tf line 5:
111+
5: template = {
112+
6: version = "~> 2"
113+
7: }
114+
115+
Reference: https://github.com/terraform-linters/tflint-ruleset-terraform/blob/v0.1.0/docs/rules/terraform_required_providers.md
116+
```
117+
60118
## Why
61119

62120
Providers are plugins released on a separate rhythm from Terraform itself, and so they have their own version numbers. For production use, you should constrain the acceptable provider versions via configuration, to ensure that new versions with breaking changes will not be automatically installed by `terraform init` in future.
63121

122+
Terraform supports multiple provider registries/namespaces through the [`source` address](https://developer.hashicorp.com/terraform/language/providers/requirements#source-addresses) attribute. While this is optional for providers in `registry.terraform.io` under the `hashicorp` namespace (the defaults), it is required for all other providers. Omitting `source` is a common error when using third-party providers and using explicit source addresses for all providers is recommended.
123+
64124
## How To Fix
65125

66-
Add the [`required_providers`](https://www.terraform.io/docs/configuration/terraform.html#specifying-required-provider-versions) block to the `terraform` configuration block and include current versions for all providers. For example:
126+
Add the [`required_providers`](https://developer.hashicorp.com/terraform/language/providers/requirements#requiring-providers) block to the `terraform` configuration block and include current versions for all providers. For example:
67127

68128
```tf
69129
terraform {
70130
required_providers {
71-
template = "~> 2.0"
131+
template = {
132+
source = "hashicorp/template"
133+
version = "~> 2"
134+
}
72135
}
73136
}
74137
```
75138

76-
Provider version constraints can be specified using a [version argument within a provider block](https://www.terraform.io/docs/configuration/providers.html#provider-versions) for backwards compatability. This approach is now discouraged, particularly for child modules.
139+
Provider version constraints can be specified using a [version argument within a provider block](https://www.terraform.io/docs/configuration/providers.html#provider-versions) for backwards compatibility. This approach is now discouraged, particularly for child modules.
140+
141+
Optionally, you can disable enforcement of either `source` or `version` by setting the corresponding attribute in the rule configuration to `false`.

rules/terraform_required_providers.go

Lines changed: 72 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ type TerraformRequiredProvidersRule struct {
1717
tflint.DefaultRule
1818
}
1919

20+
type terraformRequiredProvidersRuleConfig struct {
21+
// Source specifies whether the rule should assert the presence of a `source` attribute
22+
Source *bool `hclext:"source,optional"`
23+
// Version specifies whether the rule should assert the presence of a `version` attribute
24+
Version *bool `hclext:"version,optional"`
25+
}
26+
2027
// NewTerraformRequiredProvidersRule returns new rule with default attributes
2128
func NewTerraformRequiredProvidersRule() *TerraformRequiredProvidersRule {
2229
return &TerraformRequiredProvidersRule{}
@@ -42,6 +49,26 @@ func (r *TerraformRequiredProvidersRule) Link() string {
4249
return project.ReferenceLink(r.Name())
4350
}
4451

52+
// config returns the rule config, with defaults
53+
func (r *TerraformRequiredProvidersRule) config(runner tflint.Runner) (*terraformRequiredProvidersRuleConfig, error) {
54+
config := &terraformRequiredProvidersRuleConfig{}
55+
56+
if err := runner.DecodeRuleConfig(r.Name(), config); err != nil {
57+
return nil, err
58+
}
59+
60+
dv := true
61+
if config.Source == nil {
62+
config.Source = &dv
63+
}
64+
65+
if config.Version == nil {
66+
config.Version = &dv
67+
}
68+
69+
return config, nil
70+
}
71+
4572
// Check Checks whether provider required version is set
4673
func (r *TerraformRequiredProvidersRule) Check(rr tflint.Runner) error {
4774
runner := rr.(*terraform.Runner)
@@ -55,6 +82,11 @@ func (r *TerraformRequiredProvidersRule) Check(rr tflint.Runner) error {
5582
return nil
5683
}
5784

85+
config, err := r.config(runner)
86+
if err != nil {
87+
return fmt.Errorf("failed to parse rule config: %w", err)
88+
}
89+
5890
body, err := runner.GetModuleContent(&hclext.BodySchema{
5991
Blocks: []hclext.BlockSchema{
6092
{
@@ -76,7 +108,7 @@ func (r *TerraformRequiredProvidersRule) Check(rr tflint.Runner) error {
76108
if _, exists := provider.Body.Attributes["version"]; exists {
77109
if err := runner.EmitIssue(
78110
r,
79-
`provider version constraint should be specified via "required_providers"`,
111+
"provider version constraint should be specified via `required_providers`",
80112
provider.DefRange,
81113
); err != nil {
82114
return err
@@ -131,7 +163,11 @@ func (r *TerraformRequiredProvidersRule) Check(rr tflint.Runner) error {
131163

132164
requiredProvider, exists := requiredProviders[name]
133165
if !exists {
134-
if err := runner.EmitIssue(r, fmt.Sprintf(`Missing version constraint for provider "%s" in "required_providers"`, name), ref.DefRange); err != nil {
166+
if err := runner.EmitIssue(
167+
r,
168+
fmt.Sprintf("Missing version constraint for provider %q in `required_providers`", name),
169+
ref.DefRange,
170+
); err != nil {
135171
return err
136172
}
137173
continue
@@ -148,25 +184,46 @@ func (r *TerraformRequiredProvidersRule) Check(rr tflint.Runner) error {
148184
if diags.HasErrors() {
149185
return diags
150186
}
151-
// Look for a single static string, in case we have the legacy version-only
152-
// format in the configuration.
187+
153188
if val.Type() == cty.String {
189+
if err := runner.EmitIssue(
190+
r,
191+
fmt.Sprintf("Legacy version constraint for provider %q in `required_providers`", name),
192+
requiredProvider.Expr.Range(),
193+
); err != nil {
194+
return err
195+
}
196+
154197
continue
155198
}
156199

157200
vm := val.AsValueMap()
158-
if _, exists := vm["version"]; !exists {
159-
if source, exists := vm["source"]; exists {
160-
p, err := tfaddr.ParseProviderSource(source.AsString())
161-
if err != nil {
162-
return err
163-
}
164-
165-
if p.IsBuiltIn() {
166-
continue
167-
}
201+
202+
if source, exists := vm["source"]; exists {
203+
p, err := tfaddr.ParseProviderSource(source.AsString())
204+
if err != nil {
205+
return err
168206
}
169-
if err := runner.EmitIssue(r, fmt.Sprintf(`Missing version constraint for provider "%s" in "required_providers"`, name), requiredProvider.Expr.Range()); err != nil {
207+
208+
if p.IsBuiltIn() {
209+
continue
210+
}
211+
} else if *config.Source {
212+
if err := runner.EmitIssue(
213+
r,
214+
fmt.Sprintf("Missing `source` for provider %q in `required_providers`", name),
215+
requiredProvider.Expr.Range(),
216+
); err != nil {
217+
return err
218+
}
219+
}
220+
221+
if _, exists := vm["version"]; !exists && *config.Version {
222+
if err := runner.EmitIssue(
223+
r,
224+
fmt.Sprintf("Missing version constraint for provider %q in `required_providers`", name),
225+
requiredProvider.Expr.Range(),
226+
); err != nil {
170227
return err
171228
}
172229
}

0 commit comments

Comments
 (0)