@@ -14,7 +14,7 @@ import (
1414 "github.com/terraform-linters/tflint-ruleset-aws/project"
1515 "github.com/terraform-linters/tflint-ruleset-aws/rules/tags"
1616 "github.com/zclconf/go-cty/cty"
17- "golang.org/x/exp/maps "
17+ "golang.org/x/exp/slices "
1818)
1919
2020// AwsResourceMissingTagsRule checks whether resources are tagged correctly
@@ -59,7 +59,7 @@ func (r *AwsResourceMissingTagsRule) Link() string {
5959 return project .ReferenceLink (r .Name ())
6060}
6161
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 ) {
6363 providerSchema := & hclext.BodySchema {
6464 Attributes : []hclext.AttributeSchema {
6565 {
@@ -81,7 +81,7 @@ func (r *AwsResourceMissingTagsRule) getProviderLevelTags(runner tflint.Runner)
8181 }
8282
8383 // Get provider default tags
84- allProviderTags := make (map [string ]map [ string ]string )
84+ allProviderTags := make (map [string ][ ]string )
8585 var providerAlias string
8686 for _ , provider := range providerBody .Blocks .OfType (providerAttributeName ) {
8787 // 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)
102102 }
103103
104104 for _ , block := range provider .Body .Blocks {
105- providerTags := make ( map [ string ]string )
105+ var providerTags [ ]string
106106 attr , ok := block .Body .Attributes [tagsAttributeName ]
107107 if ! ok {
108108 continue
109109 }
110110
111111 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 {
114115 logger .Warn ("The missing aws tags rule can only evaluate provided variables, skipping %s." , provider .Labels [0 ]+ "." + providerAlias + "." + defaultTagsBlockName + "." + tagsAttributeName )
115116 return nil
116117 }
117118
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
128121 return nil
129122 }, nil )
130123
@@ -193,12 +186,6 @@ func (r *AwsResourceMissingTagsRule) Check(runner tflint.Runner) error {
193186 }
194187 }
195188
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-
202189 // If the resource has a tags attribute
203190 if attribute , okResource := resource .Body .Attributes [tagsAttributeName ]; okResource {
204191 logger .Debug (
@@ -207,22 +194,13 @@ func (r *AwsResourceMissingTagsRule) Check(runner tflint.Runner) error {
207194 )
208195
209196 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 {
212199 logger .Warn ("The missing aws tags rule can only evaluate provided variables, skipping %s." , resource .Labels [0 ]+ "." + resource .Labels [1 ]+ "." + tagsAttributeName )
213200 return nil
214201 }
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 ())
226204 return nil
227205 }, nil )
228206
@@ -231,7 +209,7 @@ func (r *AwsResourceMissingTagsRule) Check(runner tflint.Runner) error {
231209 }
232210 } else {
233211 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 )
235213 }
236214 }
237215 }
@@ -283,7 +261,7 @@ func (r *AwsResourceMissingTagsRule) checkAwsAutoScalingGroups(runner tflint.Run
283261 case len (asgTagBlockTags ) > 0 && len (asgTagsAttributeTags ) > 0 :
284262 runner .EmitIssue (r , "Only tag block or tags attribute may be present, but found both" , resource .DefRange )
285263 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 )
287265 case len (asgTagBlockTags ) > 0 && len (asgTagsAttributeTags ) == 0 :
288266 tags := asgTagBlockTags
289267 location := tagBlockLocation
@@ -299,8 +277,8 @@ func (r *AwsResourceMissingTagsRule) checkAwsAutoScalingGroups(runner tflint.Run
299277}
300278
301279// 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 )
304282
305283 resources , err := runner .GetResourceContent ("aws_autoscaling_group" , & hclext.BodySchema {
306284 Blocks : []hclext.BlockSchema {
@@ -330,7 +308,7 @@ func (r *AwsResourceMissingTagsRule) checkAwsAutoScalingGroupsTag(runner tflint.
330308 }
331309
332310 err := runner .EvaluateExpr (attribute .Expr , func (key string ) error {
333- tags [ key ] = ""
311+ tags = append ( tags , key )
334312 return nil
335313 }, nil )
336314 if err != nil {
@@ -343,8 +321,8 @@ func (r *AwsResourceMissingTagsRule) checkAwsAutoScalingGroupsTag(runner tflint.
343321}
344322
345323// 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 )
348326
349327 resources , err := runner .GetResourceContent ("aws_autoscaling_group" , & hclext.BodySchema {
350328 Attributes : []hclext.AttributeSchema {
@@ -369,7 +347,7 @@ func (r *AwsResourceMissingTagsRule) checkAwsAutoScalingGroupsTags(runner tflint
369347 }))
370348 err := runner .EvaluateExpr (attribute .Expr , func (asgTags []awsAutoscalingGroupTag ) error {
371349 for _ , tag := range asgTags {
372- tags [ tag . Key ] = tag .Value
350+ tags = append ( tags , tag .Key )
373351 }
374352 return nil
375353 }, & tflint.EvaluateExprOption {WantType : & wantType })
@@ -383,11 +361,11 @@ func (r *AwsResourceMissingTagsRule) checkAwsAutoScalingGroupsTags(runner tflint
383361 return tags , resourceBlock .DefRange , nil
384362}
385363
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 ) {
387365 var missing []string
388366 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 ))
391369 }
392370 }
393371 if len (missing ) > 0 {
@@ -406,3 +384,24 @@ func stringInSlice(a string, list []string) bool {
406384 }
407385 return false
408386}
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