@@ -14,7 +14,7 @@ import (
14
14
"github.com/terraform-linters/tflint-ruleset-aws/project"
15
15
"github.com/terraform-linters/tflint-ruleset-aws/rules/tags"
16
16
"github.com/zclconf/go-cty/cty"
17
- "golang.org/x/exp/maps "
17
+ "golang.org/x/exp/slices "
18
18
)
19
19
20
20
// AwsResourceMissingTagsRule checks whether resources are tagged correctly
@@ -59,7 +59,7 @@ func (r *AwsResourceMissingTagsRule) Link() string {
59
59
return project .ReferenceLink (r .Name ())
60
60
}
61
61
62
- func (r * AwsResourceMissingTagsRule ) getProviderLevelTags (runner tflint.Runner ) (map [string ]map [ string ]string , error ) {
62
+ func (r * AwsResourceMissingTagsRule ) getProviderLevelTags (runner tflint.Runner ) (map [string ][ ]string , error ) {
63
63
providerSchema := & hclext.BodySchema {
64
64
Attributes : []hclext.AttributeSchema {
65
65
{
@@ -81,7 +81,7 @@ func (r *AwsResourceMissingTagsRule) getProviderLevelTags(runner tflint.Runner)
81
81
}
82
82
83
83
// Get provider default tags
84
- allProviderTags := make (map [string ]map [ string ]string )
84
+ allProviderTags := make (map [string ][ ]string )
85
85
var providerAlias string
86
86
for _ , provider := range providerBody .Blocks .OfType (providerAttributeName ) {
87
87
// Get the alias attribute, in terraform when there is a single aws provider its called "default"
@@ -102,29 +102,22 @@ func (r *AwsResourceMissingTagsRule) getProviderLevelTags(runner tflint.Runner)
102
102
}
103
103
104
104
for _ , block := range provider .Body .Blocks {
105
- providerTags := make ( map [ string ]string )
105
+ var providerTags [ ]string
106
106
attr , ok := block .Body .Attributes [tagsAttributeName ]
107
107
if ! ok {
108
108
continue
109
109
}
110
110
111
111
err := runner .EvaluateExpr (attr .Expr , func (val cty.Value ) error {
112
- // Skip unknown values
113
- if ! val .IsKnown () || val .IsNull () {
112
+ keys , known := getKeysForValue (val )
113
+
114
+ if ! known {
114
115
logger .Warn ("The missing aws tags rule can only evaluate provided variables, skipping %s." , provider .Labels [0 ]+ "." + providerAlias + "." + defaultTagsBlockName + "." + tagsAttributeName )
115
116
return nil
116
117
}
117
118
118
- valMap := val .AsValueMap ()
119
- var tags map [string ]string = make (map [string ]string , len (valMap ))
120
- for k , v := range valMap {
121
- tags [k ] = ""
122
- if ! v .IsNull () && v .Type ().FriendlyName () == "string" {
123
- tags [k ] = v .AsString ()
124
- }
125
- }
126
- logger .Debug ("Walk `%s` provider with tags `%v`" , providerAlias , tags )
127
- providerTags = tags
119
+ logger .Debug ("Walk `%s` provider with tags `%v`" , providerAlias , keys )
120
+ providerTags = keys
128
121
return nil
129
122
}, nil )
130
123
@@ -193,12 +186,6 @@ func (r *AwsResourceMissingTagsRule) Check(runner tflint.Runner) error {
193
186
}
194
187
}
195
188
196
- resourceTags := make (map [string ]string )
197
-
198
- // The provider tags are to be overriden
199
- // https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags
200
- maps .Copy (resourceTags , providerTagsMap [providerAlias ])
201
-
202
189
// If the resource has a tags attribute
203
190
if attribute , okResource := resource .Body .Attributes [tagsAttributeName ]; okResource {
204
191
logger .Debug (
@@ -207,22 +194,13 @@ func (r *AwsResourceMissingTagsRule) Check(runner tflint.Runner) error {
207
194
)
208
195
209
196
err := runner .EvaluateExpr (attribute .Expr , func (val cty.Value ) error {
210
- // Skip unknown values
211
- if ! val . IsKnown () || val . IsNull () {
197
+ keys , known := getKeysForValue ( val )
198
+ if ! known {
212
199
logger .Warn ("The missing aws tags rule can only evaluate provided variables, skipping %s." , resource .Labels [0 ]+ "." + resource .Labels [1 ]+ "." + tagsAttributeName )
213
200
return nil
214
201
}
215
- valMap := val .AsValueMap ()
216
- // Since the evlaluateExpr, overrides k/v pairs, we need to re-copy the tags
217
- var evaluatedTags map [string ]string = make (map [string ]string , len (valMap ))
218
- for k , v := range valMap {
219
- evaluatedTags [k ] = ""
220
- if ! v .IsNull () && v .Type ().FriendlyName () == "string" {
221
- evaluatedTags [k ] = v .AsString ()
222
- }
223
- }
224
- maps .Copy (resourceTags , evaluatedTags )
225
- r .emitIssue (runner , resourceTags , config , attribute .Expr .Range ())
202
+
203
+ r .emitIssue (runner , append (providerTagsMap [providerAlias ], keys ... ), config , attribute .Expr .Range ())
226
204
return nil
227
205
}, nil )
228
206
@@ -231,7 +209,7 @@ func (r *AwsResourceMissingTagsRule) Check(runner tflint.Runner) error {
231
209
}
232
210
} else {
233
211
logger .Debug ("Walk `%s` resource" , resource .Labels [0 ]+ "." + resource .Labels [1 ])
234
- r .emitIssue (runner , resourceTags , config , resource .DefRange )
212
+ r .emitIssue (runner , providerTagsMap [ providerAlias ] , config , resource .DefRange )
235
213
}
236
214
}
237
215
}
@@ -283,7 +261,7 @@ func (r *AwsResourceMissingTagsRule) checkAwsAutoScalingGroups(runner tflint.Run
283
261
case len (asgTagBlockTags ) > 0 && len (asgTagsAttributeTags ) > 0 :
284
262
runner .EmitIssue (r , "Only tag block or tags attribute may be present, but found both" , resource .DefRange )
285
263
case len (asgTagBlockTags ) == 0 && len (asgTagsAttributeTags ) == 0 :
286
- r .emitIssue (runner , map [ string ]string {}, config , resource .DefRange )
264
+ r .emitIssue (runner , [ ]string {}, config , resource .DefRange )
287
265
case len (asgTagBlockTags ) > 0 && len (asgTagsAttributeTags ) == 0 :
288
266
tags := asgTagBlockTags
289
267
location := tagBlockLocation
@@ -299,8 +277,8 @@ func (r *AwsResourceMissingTagsRule) checkAwsAutoScalingGroups(runner tflint.Run
299
277
}
300
278
301
279
// checkAwsAutoScalingGroupsTag checks tag{} blocks on aws_autoscaling_group resources
302
- func (r * AwsResourceMissingTagsRule ) checkAwsAutoScalingGroupsTag (runner tflint.Runner , config awsResourceTagsRuleConfig , resourceBlock * hclext.Block ) (map [ string ]string , hcl.Range , error ) {
303
- tags := make (map [ string ]string )
280
+ func (r * AwsResourceMissingTagsRule ) checkAwsAutoScalingGroupsTag (runner tflint.Runner , config awsResourceTagsRuleConfig , resourceBlock * hclext.Block ) ([ ]string , hcl.Range , error ) {
281
+ tags := make ([ ]string , 0 )
304
282
305
283
resources , err := runner .GetResourceContent ("aws_autoscaling_group" , & hclext.BodySchema {
306
284
Blocks : []hclext.BlockSchema {
@@ -330,7 +308,7 @@ func (r *AwsResourceMissingTagsRule) checkAwsAutoScalingGroupsTag(runner tflint.
330
308
}
331
309
332
310
err := runner .EvaluateExpr (attribute .Expr , func (key string ) error {
333
- tags [ key ] = ""
311
+ tags = append ( tags , key )
334
312
return nil
335
313
}, nil )
336
314
if err != nil {
@@ -343,8 +321,8 @@ func (r *AwsResourceMissingTagsRule) checkAwsAutoScalingGroupsTag(runner tflint.
343
321
}
344
322
345
323
// checkAwsAutoScalingGroupsTag checks the tags attribute on aws_autoscaling_group resources
346
- func (r * AwsResourceMissingTagsRule ) checkAwsAutoScalingGroupsTags (runner tflint.Runner , config awsResourceTagsRuleConfig , resourceBlock * hclext.Block ) (map [ string ]string , hcl.Range , error ) {
347
- tags := make (map [ string ]string )
324
+ func (r * AwsResourceMissingTagsRule ) checkAwsAutoScalingGroupsTags (runner tflint.Runner , config awsResourceTagsRuleConfig , resourceBlock * hclext.Block ) ([ ]string , hcl.Range , error ) {
325
+ tags := make ([ ]string , 0 )
348
326
349
327
resources , err := runner .GetResourceContent ("aws_autoscaling_group" , & hclext.BodySchema {
350
328
Attributes : []hclext.AttributeSchema {
@@ -369,7 +347,7 @@ func (r *AwsResourceMissingTagsRule) checkAwsAutoScalingGroupsTags(runner tflint
369
347
}))
370
348
err := runner .EvaluateExpr (attribute .Expr , func (asgTags []awsAutoscalingGroupTag ) error {
371
349
for _ , tag := range asgTags {
372
- tags [ tag . Key ] = tag .Value
350
+ tags = append ( tags , tag .Key )
373
351
}
374
352
return nil
375
353
}, & tflint.EvaluateExprOption {WantType : & wantType })
@@ -383,11 +361,11 @@ func (r *AwsResourceMissingTagsRule) checkAwsAutoScalingGroupsTags(runner tflint
383
361
return tags , resourceBlock .DefRange , nil
384
362
}
385
363
386
- func (r * AwsResourceMissingTagsRule ) emitIssue (runner tflint.Runner , tags map [ string ]string , config awsResourceTagsRuleConfig , location hcl.Range ) {
364
+ func (r * AwsResourceMissingTagsRule ) emitIssue (runner tflint.Runner , tags [ ]string , config awsResourceTagsRuleConfig , location hcl.Range ) {
387
365
var missing []string
388
366
for _ , tag := range config .Tags {
389
- if _ , ok := tags [ tag ]; ! ok {
390
- missing = append (missing , fmt .Sprintf ("\" %s \" " , tag ))
367
+ if ! slices . Contains ( tags , tag ) {
368
+ missing = append (missing , fmt .Sprintf ("%q " , tag ))
391
369
}
392
370
}
393
371
if len (missing ) > 0 {
@@ -406,3 +384,24 @@ func stringInSlice(a string, list []string) bool {
406
384
}
407
385
return false
408
386
}
387
+
388
+ // getKeysForValue returns a list of keys from a cty.Value, which is assumed to be a map (or unknown).
389
+ // It returns a boolean indicating whether the keys were known.
390
+ // If _any_ key is unknown, the entire value is considered unknown, since we can't know if a required tag might be matched by the unknown key.
391
+ // Values are entirely ignored and can be unknown.
392
+ func getKeysForValue (value cty.Value ) (keys []string , known bool ) {
393
+ if ! value .CanIterateElements () {
394
+ return nil , false
395
+ }
396
+
397
+ return keys , ! value .ForEachElement (func (key , _ cty.Value ) bool {
398
+ // If any key is unknown or sensitive, return early as any missing tag could be this unknown key.
399
+ if ! key .IsKnown () || key .IsNull () || key .IsMarked () {
400
+ return true
401
+ }
402
+
403
+ keys = append (keys , key .AsString ())
404
+
405
+ return false
406
+ })
407
+ }
0 commit comments