Skip to content

Commit e5e7452

Browse files
authored
Merge pull request #44846 from hashicorp/b-sqs-identity
Updates Resource Identity for SQS resource typess with inherent regional identity
2 parents 7ef0001 + 411d936 commit e5e7452

29 files changed

+2072
-373
lines changed

.changelog/44846.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
```release-note:enhancement
2+
resource/aws_sqs_queue: Remove `account_id` and `region` from Resource Identity schema
3+
```
4+
5+
```release-note:enhancement
6+
resource/aws_sqs_queue_policy: Remove `account_id` and `region` from Resource Identity schema
7+
```
8+
9+
```release-note:enhancement
10+
resource/aws_sqs_queue_redrive_allow_policy: Remove `account_id` and `region` from Resource Identity schema
11+
```
12+
13+
```release-note:enhancement
14+
resource/aws_sqs_queue_redrive_policy: Remove `account_id` and `region` from Resource Identity schema
15+
```
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package statecheck
5+
6+
import (
7+
"context"
8+
"fmt"
9+
10+
"github.com/hashicorp/terraform-plugin-testing/statecheck"
11+
)
12+
13+
var _ statecheck.StateCheck = expectHasIdentity{}
14+
15+
type expectHasIdentity struct {
16+
base Base
17+
}
18+
19+
func (e expectHasIdentity) CheckState(ctx context.Context, request statecheck.CheckStateRequest, response *statecheck.CheckStateResponse) {
20+
resource, ok := e.base.ResourceFromState(request, response)
21+
if !ok {
22+
return
23+
}
24+
25+
if resource.IdentitySchemaVersion == nil || len(resource.IdentityValues) == 0 {
26+
response.Error = fmt.Errorf("%s - Identity not found in state. Either the resource does not support identity or the Terraform version running the test does not support identity. (must be v1.12+)", e.base.resourceAddress)
27+
}
28+
}
29+
30+
func ExpectHasIdentity(resourceAddress string) statecheck.StateCheck {
31+
return expectHasIdentity{
32+
base: NewBase(resourceAddress),
33+
}
34+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package common
5+
6+
import (
7+
"fmt"
8+
9+
"github.com/hashicorp/go-version"
10+
)
11+
12+
func VersionDecrementMinor(v *version.Version) (*version.Version, error) {
13+
segments := v.Segments()
14+
if segments[1] == 0 {
15+
return nil, fmt.Errorf("minor version is zero, cannot decrement: %s", v.String())
16+
}
17+
18+
newSegments := []int{segments[0], segments[1] - 1}
19+
newVersionStr := fmt.Sprintf("%d.%d", newSegments[0], newSegments[1])
20+
21+
return version.NewVersion(newVersionStr)
22+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package common
5+
6+
import (
7+
"fmt"
8+
"testing"
9+
10+
"github.com/hashicorp/go-version"
11+
)
12+
13+
func TestVersionDecrementMinor(t *testing.T) {
14+
t.Parallel()
15+
16+
testcases := map[string]struct {
17+
input *version.Version
18+
expected *version.Version
19+
expectedError error
20+
}{
21+
"valid": {
22+
input: version.Must(version.NewVersion("6.10.3")),
23+
expected: version.Must(version.NewVersion("6.9.0")),
24+
},
25+
"minor is zero": {
26+
input: version.Must(version.NewVersion("6.0.2")),
27+
expectedError: fmt.Errorf("minor version is zero, cannot decrement: %s", "6.0.2"),
28+
},
29+
}
30+
31+
for name, tc := range testcases {
32+
t.Run(name, func(t *testing.T) {
33+
t.Parallel()
34+
35+
result, err := VersionDecrementMinor(tc.input)
36+
if tc.expectedError == nil {
37+
if err != nil {
38+
t.Fatalf("unexpected error: %s", err)
39+
}
40+
} else {
41+
if err == nil {
42+
t.Fatalf("expected error but got none")
43+
} else if err.Error() != tc.expectedError.Error() {
44+
t.Fatalf("unexpected error: got %s, want %s", err, tc.expectedError)
45+
}
46+
}
47+
48+
if !result.Equal(tc.expected) {
49+
t.Errorf("unexpected result: got %v, want %v", result, tc.expected)
50+
}
51+
})
52+
}
53+
}

internal/generate/identitytests/main.go

Lines changed: 111 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"go/ast"
1414
"go/parser"
1515
"go/token"
16+
"maps"
1617
"os"
1718
"path"
1819
"path/filepath"
@@ -124,7 +125,8 @@ func main() {
124125
"inc": func(i int) int {
125126
return i + 1
126127
},
127-
"NewVersion": version.NewVersion,
128+
"NewVersion": version.NewVersion,
129+
"VersionDecrementMinor": common.VersionDecrementMinor,
128130
}
129131
templates := template.New("identitytests").Funcs(templateFuncMap)
130132

@@ -205,13 +207,15 @@ func main() {
205207
g.Fatalf("parsing config template: %s", err)
206208
}
207209

208-
common := commonConfig{
210+
commonConfig := commonConfig{
209211
AdditionalTfVars: additionalTfVars,
210212
RequiredEnvVars: resource.RequiredEnvVars,
211213
WithRName: (resource.Generator != ""),
212214
}
213215

214-
generateTestConfig(g, testDirPath, "basic", tfTemplates, common)
216+
generateTestConfig(g, testDirPath, "basic", tfTemplates, commonConfig)
217+
218+
var versions []*version.Version
215219

216220
if resource.PreIdentityVersion != nil {
217221
if resource.PreIdentityVersion.Equal(v5_100_0) {
@@ -234,33 +238,39 @@ func main() {
234238
g.Fatalf("parsing config template %q: %s", configTmplV5Path, err)
235239
}
236240
}
237-
commonV5 := common
238-
commonV5.ExternalProviders = map[string]requiredProvider{
241+
commonConfigV5 := commonConfig
242+
commonConfigV5.ExternalProviders = map[string]requiredProvider{
239243
"aws": {
240244
Source: "hashicorp/aws",
241245
Version: "5.100.0",
242246
},
243247
}
244-
generateTestConfig(g, testDirPath, "basic_v5.100.0", tfTemplatesV5, commonV5)
248+
generateTestConfig(g, testDirPath, "basic_v5.100.0", tfTemplatesV5, commonConfigV5)
245249

246-
commonV6 := common
247-
commonV6.ExternalProviders = map[string]requiredProvider{
248-
"aws": {
249-
Source: "hashicorp/aws",
250-
Version: "6.0.0",
251-
},
252-
}
253-
generateTestConfig(g, testDirPath, "basic_v6.0.0", tfTemplates, commonV6)
250+
versions = append(versions, version.Must(version.NewVersion("6.0.0")))
254251
} else {
255-
commonPreIdentity := common
256-
commonPreIdentity.ExternalProviders = map[string]requiredProvider{
257-
"aws": {
258-
Source: "hashicorp/aws",
259-
Version: resource.PreIdentityVersion.String(),
260-
},
261-
}
262-
generateTestConfig(g, testDirPath, fmt.Sprintf("basic_v%s", resource.PreIdentityVersion.String()), tfTemplates, commonPreIdentity)
252+
versions = append(versions, resource.PreIdentityVersion)
253+
}
254+
}
255+
256+
if len(resource.IdentityVersions) > 1 {
257+
v := resource.IdentityVersions[1]
258+
v, err := common.VersionDecrementMinor(v)
259+
if err != nil {
260+
g.Fatalf("generating versioned configurations: %s", err)
263261
}
262+
versions = append(versions, v)
263+
}
264+
265+
for _, version := range versions {
266+
common := commonConfig
267+
common.ExternalProviders = map[string]requiredProvider{
268+
"aws": {
269+
Source: "hashicorp/aws",
270+
Version: version.String(),
271+
},
272+
}
273+
generateTestConfig(g, testDirPath, fmt.Sprintf("basic_v%s", version.String()), tfTemplates, common)
264274
}
265275

266276
_, err = tfTemplates.New("region").Parse("\n region = var.region\n")
@@ -269,9 +279,9 @@ func main() {
269279
}
270280

271281
if resource.GenerateRegionOverrideTest() {
272-
common.WithRegion = true
282+
commonConfig.WithRegion = true
273283

274-
generateTestConfig(g, testDirPath, "region_override", tfTemplates, common)
284+
generateTestConfig(g, testDirPath, "region_override", tfTemplates, commonConfig)
275285
}
276286
}
277287
}
@@ -364,26 +374,29 @@ const (
364374
)
365375

366376
type ResourceDatum struct {
367-
service *serviceRecords
368-
FileName string
369-
idAttrDuplicates string // TODO: Remove. Still needed for Parameterized Identity
370-
GenerateConfig bool
371-
ARNFormat string
372-
arnAttribute string
373-
isARNFormatGlobal triBoolean
374-
ArnIdentity bool
375-
MutableIdentity bool
376-
IsGlobal bool
377-
isSingleton bool
378-
HasRegionOverrideTest bool
379-
identityAttributes []identityAttribute
380-
identityAttribute string
381-
IdentityDuplicateAttrs []string
382-
IDAttrFormat string
383-
HasV6_0NullValuesError bool
384-
HasV6_0RefreshError bool
385-
HasNoPreExistingResource bool
386-
PreIdentityVersion *version.Version
377+
service *serviceRecords
378+
FileName string
379+
idAttrDuplicates string // TODO: Remove. Still needed for Parameterized Identity
380+
GenerateConfig bool
381+
ARNFormat string
382+
arnAttribute string
383+
isARNFormatGlobal triBoolean
384+
ArnIdentity bool
385+
MutableIdentity bool
386+
IsGlobal bool
387+
isSingleton bool
388+
HasRegionOverrideTest bool
389+
identityAttributes []identityAttribute
390+
identityAttribute string
391+
IdentityDuplicateAttrs []string
392+
IDAttrFormat string
393+
HasV6_0NullValuesError bool
394+
HasV6_0RefreshError bool
395+
HasNoPreExistingResource bool
396+
PreIdentityVersion *version.Version
397+
IsCustomInherentRegionIdentity bool
398+
customIdentityAttribute string
399+
IdentityVersions map[int64]*version.Version
387400
tests.CommonArgs
388401
}
389402

@@ -436,7 +449,7 @@ func (d ResourceDatum) GenerateRegionOverrideTest() bool {
436449
}
437450

438451
func (d ResourceDatum) HasInherentRegion() bool {
439-
return d.IsARNIdentity() || d.IsRegionalSingleton()
452+
return d.IsARNIdentity() || d.IsRegionalSingleton() || d.IsCustomInherentRegionIdentity
440453
}
441454

442455
func (d ResourceDatum) IdentityAttribute() string {
@@ -455,6 +468,17 @@ func (r ResourceDatum) IdentityAttributes() []identityAttribute {
455468
return r.identityAttributes
456469
}
457470

471+
func (r ResourceDatum) CustomIdentityAttribute() string {
472+
return namesgen.ConstOrQuote(r.customIdentityAttribute)
473+
}
474+
475+
func (r ResourceDatum) LatestIdentityVersion() int64 {
476+
if len(r.IdentityVersions) == 0 {
477+
return 0
478+
}
479+
return slices.Max(slices.Collect(maps.Keys(r.IdentityVersions)))
480+
}
481+
458482
type identityAttribute struct {
459483
name string
460484
Optional bool
@@ -552,6 +576,7 @@ func (v *visitor) processFuncDecl(funcDecl *ast.FuncDecl) {
552576
CommonArgs: tests.InitCommonArgs(),
553577
IsGlobal: false,
554578
HasRegionOverrideTest: true,
579+
IdentityVersions: make(map[int64]*version.Version, 0),
555580
}
556581
hasIdentity := false
557582
skip := false
@@ -719,6 +744,27 @@ func (v *visitor) processFuncDecl(funcDecl *ast.FuncDecl) {
719744
}
720745
}
721746

747+
case "CustomInherentRegionIdentity":
748+
hasIdentity = true
749+
d.IsCustomInherentRegionIdentity = true
750+
751+
args := common.ParseArgs(m[3])
752+
d.customIdentityAttribute = args.Positional[0]
753+
d.identityAttribute = args.Positional[0]
754+
755+
var attrs []string
756+
if attr, ok := args.Keyword["identityDuplicateAttributes"]; ok {
757+
attrs = strings.Split(attr, ";")
758+
}
759+
if d.Implementation == tests.ImplementationSDK {
760+
attrs = append(attrs, "id")
761+
}
762+
slices.Sort(attrs)
763+
attrs = slices.Compact(attrs)
764+
d.IdentityDuplicateAttrs = tfslices.ApplyToAll(attrs, func(s string) string {
765+
return namesgen.ConstOrQuote(s)
766+
})
767+
722768
case "Testing":
723769
args := common.ParseArgs(m[3])
724770

@@ -812,6 +858,26 @@ func (v *visitor) processFuncDecl(funcDecl *ast.FuncDecl) {
812858
if attr, ok := args.Keyword["tlsKeyDomain"]; ok {
813859
tlsKeyCN = attr
814860
}
861+
if attr, ok := args.Keyword["identityVersion"]; ok {
862+
parts := strings.Split(attr, ";")
863+
if len(parts) != 2 {
864+
v.errs = append(v.errs, fmt.Errorf("invalid identityVersion value: %q at %s. Should be in format <identity version>;<provider version>.", attr, fmt.Sprintf("%s.%s", v.packageName, v.functionName)))
865+
continue
866+
}
867+
var identityVersion int64
868+
if i, err := strconv.ParseInt(parts[0], 10, 64); err != nil {
869+
v.errs = append(v.errs, fmt.Errorf("invalid identity version value: %q at %s. Should be integer value.", parts[0], fmt.Sprintf("%s.%s", v.packageName, v.functionName)))
870+
continue
871+
} else {
872+
identityVersion = i
873+
}
874+
providerVersion, err := version.NewVersion(parts[1])
875+
if err != nil {
876+
v.errs = append(v.errs, fmt.Errorf("invalid provider version value: %q at %s. Should be version value.", parts[1], fmt.Sprintf("%s.%s", v.packageName, v.functionName)))
877+
continue
878+
}
879+
d.IdentityVersions[identityVersion] = providerVersion
880+
}
815881
}
816882
}
817883
}

0 commit comments

Comments
 (0)