11package rules
22
33import (
4+ "errors"
45 "fmt"
56 "sort"
67 "strings"
@@ -9,9 +10,11 @@ import (
910 "github.com/terraform-linters/tflint-plugin-sdk/hclext"
1011 "github.com/terraform-linters/tflint-plugin-sdk/logger"
1112 "github.com/terraform-linters/tflint-plugin-sdk/tflint"
13+ "github.com/terraform-linters/tflint-ruleset-aws/aws"
1214 "github.com/terraform-linters/tflint-ruleset-aws/project"
1315 "github.com/terraform-linters/tflint-ruleset-aws/rules/tags"
1416 "github.com/zclconf/go-cty/cty"
17+ "golang.org/x/exp/maps"
1518)
1619
1720// AwsResourceMissingTagsRule checks whether resources are tagged correctly
@@ -25,8 +28,9 @@ type awsResourceTagsRuleConfig struct {
2528}
2629
2730const (
28- tagsAttributeName = "tags"
29- tagBlockName = "tag"
31+ tagsAttributeName = "tags"
32+ tagBlockName = "tag"
33+ providerAttributeName = "provider"
3034)
3135
3236// NewAwsResourceMissingTagsRule returns new rules for all resources that support tags
@@ -54,13 +58,81 @@ func (r *AwsResourceMissingTagsRule) Link() string {
5458 return project .ReferenceLink (r .Name ())
5559}
5660
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+
57123// Check checks resources for missing tags
58124func (r * AwsResourceMissingTagsRule ) Check (runner tflint.Runner ) error {
59125 config := awsResourceTagsRuleConfig {}
60126 if err := runner .DecodeRuleConfig (r .Name (), & config ); err != nil {
61127 return err
62128 }
63129
130+ providerTagsMap , err := r .getProviderLevelTags (runner )
131+
132+ if err != nil {
133+ return err
134+ }
135+
64136 for _ , resourceType := range tags .Resources {
65137 // Skip this resource if its type is excluded in configuration
66138 if stringInSlice (resourceType , config .Exclude ) {
@@ -77,29 +149,74 @@ func (r *AwsResourceMissingTagsRule) Check(runner tflint.Runner) error {
77149 }
78150
79151 resources , err := runner .GetResourceContent (resourceType , & hclext.BodySchema {
80- Attributes : []hclext.AttributeSchema {{Name : tagsAttributeName }},
152+ Attributes : []hclext.AttributeSchema {
153+ {Name : tagsAttributeName },
154+ {Name : providerAttributeName },
155+ },
81156 }, nil )
82157 if err != nil {
83158 return err
84159 }
85160
161+ if resources .IsEmpty () {
162+ continue
163+ }
164+
86165 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
92204 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+
94210 if err != nil {
95211 return err
96212 }
97213 } else {
98214 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 )
100216 }
101217 }
102218 }
219+
103220 return nil
104221}
105222
0 commit comments