1
1
package rules
2
2
3
3
import (
4
+ "errors"
4
5
"fmt"
5
6
"sort"
6
7
"strings"
@@ -9,9 +10,11 @@ import (
9
10
"github.com/terraform-linters/tflint-plugin-sdk/hclext"
10
11
"github.com/terraform-linters/tflint-plugin-sdk/logger"
11
12
"github.com/terraform-linters/tflint-plugin-sdk/tflint"
13
+ "github.com/terraform-linters/tflint-ruleset-aws/aws"
12
14
"github.com/terraform-linters/tflint-ruleset-aws/project"
13
15
"github.com/terraform-linters/tflint-ruleset-aws/rules/tags"
14
16
"github.com/zclconf/go-cty/cty"
17
+ "golang.org/x/exp/maps"
15
18
)
16
19
17
20
// AwsResourceMissingTagsRule checks whether resources are tagged correctly
@@ -25,8 +28,9 @@ type awsResourceTagsRuleConfig struct {
25
28
}
26
29
27
30
const (
28
- tagsAttributeName = "tags"
29
- tagBlockName = "tag"
31
+ tagsAttributeName = "tags"
32
+ tagBlockName = "tag"
33
+ providerAttributeName = "provider"
30
34
)
31
35
32
36
// NewAwsResourceMissingTagsRule returns new rules for all resources that support tags
@@ -54,13 +58,81 @@ func (r *AwsResourceMissingTagsRule) Link() string {
54
58
return project .ReferenceLink (r .Name ())
55
59
}
56
60
61
+ func (r * AwsResourceMissingTagsRule ) getProviderLevelTags (runner tflint.Runner ) (map [string ]map [string ]string , error ) {
62
+ providerSchema := & hclext.BodySchema {
63
+ Attributes : []hclext.AttributeSchema {
64
+ {
65
+ Name : "alias" ,
66
+ Required : false ,
67
+ },
68
+ },
69
+ Blocks : []hclext.BlockSchema {
70
+ {
71
+ Type : "default_tags" ,
72
+ Body : & hclext.BodySchema {Attributes : []hclext.AttributeSchema {{Name : tagsAttributeName }}},
73
+ },
74
+ },
75
+ }
76
+
77
+ providerBody , err := runner .GetProviderContent ("aws" , providerSchema , nil )
78
+ if err != nil {
79
+ return nil , err
80
+ }
81
+
82
+ // Get provider default tags
83
+ allProviderTags := make (map [string ]map [string ]string )
84
+ var providerAlias string
85
+ for _ , provider := range providerBody .Blocks .OfType (providerAttributeName ) {
86
+ providerTags := make (map [string ]string )
87
+ for _ , block := range provider .Body .Blocks {
88
+ attr , ok := block .Body .Attributes [tagsAttributeName ]
89
+ if ! ok {
90
+ continue
91
+ }
92
+
93
+ err := runner .EvaluateExpr (attr .Expr , func (tags map [string ]string ) error {
94
+ providerTags = tags
95
+ return nil
96
+ }, nil )
97
+
98
+ if err != nil {
99
+ return nil , err
100
+ }
101
+
102
+ // Get the alias attribute, in terraform when there is a single aws provider its called "default"
103
+ providerAttr , ok := provider .Body .Attributes ["alias" ]
104
+ if ! ok {
105
+ providerAlias = "default"
106
+ allProviderTags [providerAlias ] = providerTags
107
+ } else {
108
+ err := runner .EvaluateExpr (providerAttr .Expr , func (alias string ) error {
109
+ providerAlias = alias
110
+ return nil
111
+ }, nil )
112
+ // Assign default provider
113
+ allProviderTags [providerAlias ] = providerTags
114
+ if err != nil {
115
+ return nil , err
116
+ }
117
+ }
118
+ }
119
+ }
120
+ return allProviderTags , nil
121
+ }
122
+
57
123
// Check checks resources for missing tags
58
124
func (r * AwsResourceMissingTagsRule ) Check (runner tflint.Runner ) error {
59
125
config := awsResourceTagsRuleConfig {}
60
126
if err := runner .DecodeRuleConfig (r .Name (), & config ); err != nil {
61
127
return err
62
128
}
63
129
130
+ providerTagsMap , err := r .getProviderLevelTags (runner )
131
+
132
+ if err != nil {
133
+ return err
134
+ }
135
+
64
136
for _ , resourceType := range tags .Resources {
65
137
// Skip this resource if its type is excluded in configuration
66
138
if stringInSlice (resourceType , config .Exclude ) {
@@ -77,29 +149,74 @@ func (r *AwsResourceMissingTagsRule) Check(runner tflint.Runner) error {
77
149
}
78
150
79
151
resources , err := runner .GetResourceContent (resourceType , & hclext.BodySchema {
80
- Attributes : []hclext.AttributeSchema {{Name : tagsAttributeName }},
152
+ Attributes : []hclext.AttributeSchema {
153
+ {Name : tagsAttributeName },
154
+ {Name : providerAttributeName },
155
+ },
81
156
}, nil )
82
157
if err != nil {
83
158
return err
84
159
}
85
160
161
+ if resources .IsEmpty () {
162
+ continue
163
+ }
164
+
86
165
for _ , resource := range resources .Blocks {
87
- if attribute , ok := resource .Body .Attributes [tagsAttributeName ]; ok {
88
- logger .Debug ("Walk `%s` attribute" , resource .Labels [0 ]+ "." + resource .Labels [1 ]+ "." + tagsAttributeName )
89
- wantType := cty .Map (cty .String )
90
- err := runner .EvaluateExpr (attribute .Expr , func (resourceTags map [string ]string ) error {
91
- r .emitIssue (runner , resourceTags , config , attribute .Expr .Range ())
166
+ providerAlias := "default"
167
+ // Override the provider alias if defined
168
+ if val , ok := resource .Body .Attributes [providerAttributeName ]; ok {
169
+ provider , diagnostics := aws .DecodeProviderConfigRef (val .Expr , "provider" )
170
+ providerAlias = provider .Alias
171
+
172
+ if _ , hasProvider := providerTagsMap [providerAlias ]; ! hasProvider {
173
+ errString := fmt .Sprintf (
174
+ "The aws provider with alias \" %s\" doesn't exist." ,
175
+ providerAlias ,
176
+ )
177
+ logger .Error ("Error querying provider tags: %s" , errString )
178
+ return errors .New (errString )
179
+ }
180
+
181
+ if diagnostics .HasErrors () {
182
+ logger .Error ("error decoding provider: %w" , diagnostics )
183
+ return diagnostics
184
+ }
185
+ }
186
+
187
+ resourceTags := make (map [string ]string )
188
+
189
+ // The provider tags are to be overriden
190
+ // https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags
191
+ maps .Copy (resourceTags , providerTagsMap [providerAlias ])
192
+
193
+ // If the resource has a tags attribute
194
+ if attribute , okResource := resource .Body .Attributes [tagsAttributeName ]; okResource {
195
+ logger .Debug (
196
+ "Walk `%s` attribute" ,
197
+ resource .Labels [0 ]+ "." + resource .Labels [1 ]+ "." + tagsAttributeName ,
198
+ )
199
+ // Since the evlaluateExpr, overrides k/v pairs, we need to re-copy the tags
200
+ resourceTagsAux := make (map [string ]string )
201
+
202
+ err := runner .EvaluateExpr (attribute .Expr , func (val map [string ]string ) error {
203
+ resourceTagsAux = val
92
204
return nil
93
- }, & tflint.EvaluateExprOption {WantType : & wantType })
205
+ }, nil )
206
+
207
+ maps .Copy (resourceTags , resourceTagsAux )
208
+ r .emitIssue (runner , resourceTags , config , attribute .Expr .Range ())
209
+
94
210
if err != nil {
95
211
return err
96
212
}
97
213
} else {
98
214
logger .Debug ("Walk `%s` resource" , resource .Labels [0 ]+ "." + resource .Labels [1 ])
99
- r .emitIssue (runner , map [ string ] string {} , config , resource .DefRange )
215
+ r .emitIssue (runner , resourceTags , config , resource .DefRange )
100
216
}
101
217
}
102
218
}
219
+
103
220
return nil
104
221
}
105
222
0 commit comments