Skip to content

Commit 89c6ed5

Browse files
authored
feat: Added default tags functionality (#489)
* feat: Added default tags functionality * feat: Removed deprecated funcions - Removed deprecated runner.EnsureNoError calls - Removed explicit pass of types to EvaluateExpr calls * fix: Changed runner.EvaluateExpr to gohcl.DecodeExpression * fix: Removed uneccesary runner.EnsureNoError when validating tags * fix: Chaged providerAlias var to "provider" in aws.DecodeProviderConfigRef * fix: Added RaiseErr field in tests to allow checking for errors - Added the RaiseErr field in the test table struct, this is due to having the ability of checking errors apart of helper issues, also checked if the provider existed using the mentioned mechanism - Erased unecessary error checking when decoding provider config
1 parent 409b250 commit 89c6ed5

File tree

6 files changed

+295
-16
lines changed

6 files changed

+295
-16
lines changed

aws/decode.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ type ProviderConfigRef struct {
2525
}
2626

2727
// original code: https://github.com/hashicorp/terraform/blob/3fbedf25430ead97eb42575d344427db3c32d524/internal/configs/resource.go#L498-L569
28-
func decodeProviderConfigRef(expr hcl.Expression, argName string) (*ProviderConfigRef, hcl.Diagnostics) {
28+
func DecodeProviderConfigRef(expr hcl.Expression, argName string) (*ProviderConfigRef, hcl.Diagnostics) {
2929
var diags hcl.Diagnostics
3030

3131
var shimDiags hcl.Diagnostics

aws/runner.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func NewRunner(runner tflint.Runner, config *Config) (*Runner, error) {
4848
func (r *Runner) AwsClient(attributes hclext.Attributes) (*Client, error) {
4949
provider := "aws"
5050
if attr, exists := attributes["provider"]; exists {
51-
providerConfigRef, diags := decodeProviderConfigRef(attr.Expr, "provider")
51+
providerConfigRef, diags := DecodeProviderConfigRef(attr.Expr, "provider")
5252
if diags.HasErrors() {
5353
logger.Error("parse resource provider attribute: %s", diags)
5454
return nil, diags

go.mod

+8-1
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,15 @@ require (
2828
gopkg.in/yaml.v2 v2.4.0 // indirect
2929
)
3030

31-
require golang.org/x/net v0.10.0
31+
require (
32+
github.com/stretchr/testify v1.7.2
33+
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53
34+
golang.org/x/net v0.10.0
35+
)
3236

3337
require (
3438
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
39+
github.com/davecgh/go-spew v1.1.1 // indirect
3540
github.com/golang/protobuf v1.5.2 // indirect
3641
github.com/hashicorp/errwrap v1.0.0 // indirect
3742
github.com/hashicorp/go-hclog v1.5.0 // indirect
@@ -41,10 +46,12 @@ require (
4146
github.com/kr/pretty v0.2.1 // indirect
4247
github.com/mattn/go-isatty v0.0.14 // indirect
4348
github.com/oklog/run v1.0.0 // indirect
49+
github.com/pmezard/go-difflib v1.0.0 // indirect
4450
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
4551
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
4652
golang.org/x/mod v0.10.0 // indirect
4753
golang.org/x/sys v0.8.0 // indirect
4854
golang.org/x/text v0.9.0 // indirect
4955
golang.org/x/tools v0.8.0 // indirect
56+
gopkg.in/yaml.v3 v3.0.1 // indirect
5057
)

go.sum

+3
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ github.com/zclconf/go-cty v1.13.2/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4
9494
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
9595
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
9696
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
97+
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o=
98+
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
9799
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
98100
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
99101
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
@@ -154,6 +156,7 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
154156
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
155157
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
156158
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
159+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
157160
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
158161
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
159162
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

rules/aws_resource_missing_tags.go

+127-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package rules
22

33
import (
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

2730
const (
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
58124
func (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

Comments
 (0)