Skip to content

Commit 5406ce5

Browse files
authored
resource_missing_tags: rewrite to only consider keys (#517)
* `resource_missing_tags`: rewrite to only consider keys * refactor get keys func * handle sensitive values
1 parent bcd0c2d commit 5406ce5

File tree

1 file changed

+45
-46
lines changed

1 file changed

+45
-46
lines changed

rules/aws_resource_missing_tags.go

Lines changed: 45 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)