Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
861e565
Adds basic support for versioned Resource Identity schemas
gdavison Oct 24, 2025
c969c74
Adds version to SDKv2 Resource Identity schema
gdavison Oct 24, 2025
6703194
Refactors setting `Identity` in template
gdavison Oct 24, 2025
bbe7b9a
Fixes missing format verb
gdavison Oct 24, 2025
d892667
Sets `Identity` once per template
gdavison Oct 24, 2025
4bae981
Handles `@IdentityVersion` for SDKv2 resource types in registration
gdavison Oct 24, 2025
6998041
Adds `RegionalCustomInherentRegionIdentity`
gdavison Oct 24, 2025
56d0feb
Handles `@CustomInherentRegionIdentity` in service registration
gdavison Oct 24, 2025
43403e8
Handles `@CustomInherentRegionIdentity` in generated tests
gdavison Oct 24, 2025
ad43f73
Adds missing values to `PlannableImportCrossRegionPlanChecks`
gdavison Oct 27, 2025
fa66a5c
Adds `statecheck.ExpectHasIdentity`
gdavison Oct 27, 2025
3fd333f
Adds `common.TestVersionDecrementMinor` for generators
gdavison Oct 27, 2025
7ca1832
Generates tests for multiple versions of Resource Identity
gdavison Oct 27, 2025
da6961c
Updates `aws_sqs_queue`
gdavison Oct 27, 2025
e67deec
Renames `v` to `resource`
gdavison Oct 28, 2025
63f0c0d
Adds `sdkv2/importer.RegionalInherentRegion`
gdavison Oct 28, 2025
85038b5
Adds `customInherentRegionResourceImporter` for SDKv2
gdavison Oct 28, 2025
20d95bc
Adds inherent region parser in service registration
gdavison Oct 28, 2025
1a00f6b
Updates `aws_sqs_queue`
gdavison Oct 28, 2025
700fc94
Supports `IdentityDuplicateAttrs` for Custom Inherent Region in Resou…
gdavison Oct 28, 2025
157ad28
Updates `aws_sqs_queue`
gdavison Oct 28, 2025
513119d
Updates `aws_sqs_queue_policy`
gdavison Oct 28, 2025
bd68b38
Updates Resource Identity documentation for `aws_sqs_queue`
gdavison Oct 28, 2025
26917ab
Limits visibility for `queuePolicyMigrateState`
gdavison Oct 28, 2025
c94ac00
Updates `aws_sqs_queue_redrive_allow_policy`
gdavison Oct 28, 2025
1096f99
Updates `aws_sqs_queue_redrive_policy`
gdavison Oct 28, 2025
411d936
Adds CHANGELOG entry
gdavison Oct 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .changelog/44846.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
```release-note:enhancement
resource/aws_sqs_queue: Remove `account_id` and `region` from Resource Identity schema
```

```release-note:enhancement
resource/aws_sqs_queue_policy: Remove `account_id` and `region` from Resource Identity schema
```

```release-note:enhancement
resource/aws_sqs_queue_redrive_allow_policy: Remove `account_id` and `region` from Resource Identity schema
```

```release-note:enhancement
resource/aws_sqs_queue_redrive_policy: Remove `account_id` and `region` from Resource Identity schema
```
34 changes: 34 additions & 0 deletions internal/acctest/statecheck/expect_has_identity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package statecheck

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-testing/statecheck"
)

var _ statecheck.StateCheck = expectHasIdentity{}

type expectHasIdentity struct {
base Base
}

func (e expectHasIdentity) CheckState(ctx context.Context, request statecheck.CheckStateRequest, response *statecheck.CheckStateResponse) {
resource, ok := e.base.ResourceFromState(request, response)
if !ok {
return
}

if resource.IdentitySchemaVersion == nil || len(resource.IdentityValues) == 0 {
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)
}
}

func ExpectHasIdentity(resourceAddress string) statecheck.StateCheck {
return expectHasIdentity{
base: NewBase(resourceAddress),
}
}
22 changes: 22 additions & 0 deletions internal/generate/common/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package common

import (
"fmt"

"github.com/hashicorp/go-version"
)

func VersionDecrementMinor(v *version.Version) (*version.Version, error) {
segments := v.Segments()
if segments[1] == 0 {
return nil, fmt.Errorf("minor version is zero, cannot decrement: %s", v.String())
}

newSegments := []int{segments[0], segments[1] - 1}
newVersionStr := fmt.Sprintf("%d.%d", newSegments[0], newSegments[1])

return version.NewVersion(newVersionStr)
}
53 changes: 53 additions & 0 deletions internal/generate/common/version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package common

import (
"fmt"
"testing"

"github.com/hashicorp/go-version"
)

func TestVersionDecrementMinor(t *testing.T) {
t.Parallel()

testcases := map[string]struct {
input *version.Version
expected *version.Version
expectedError error
}{
"valid": {
input: version.Must(version.NewVersion("6.10.3")),
expected: version.Must(version.NewVersion("6.9.0")),
},
"minor is zero": {
input: version.Must(version.NewVersion("6.0.2")),
expectedError: fmt.Errorf("minor version is zero, cannot decrement: %s", "6.0.2"),
},
}

for name, tc := range testcases {
t.Run(name, func(t *testing.T) {
t.Parallel()

result, err := VersionDecrementMinor(tc.input)
if tc.expectedError == nil {
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
} else {
if err == nil {
t.Fatalf("expected error but got none")
} else if err.Error() != tc.expectedError.Error() {
t.Fatalf("unexpected error: got %s, want %s", err, tc.expectedError)
}
}

if !result.Equal(tc.expected) {
t.Errorf("unexpected result: got %v, want %v", result, tc.expected)
}
})
}
}
156 changes: 111 additions & 45 deletions internal/generate/identitytests/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"go/ast"
"go/parser"
"go/token"
"maps"
"os"
"path"
"path/filepath"
Expand Down Expand Up @@ -124,7 +125,8 @@ func main() {
"inc": func(i int) int {
return i + 1
},
"NewVersion": version.NewVersion,
"NewVersion": version.NewVersion,
"VersionDecrementMinor": common.VersionDecrementMinor,
}
templates := template.New("identitytests").Funcs(templateFuncMap)

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

common := commonConfig{
commonConfig := commonConfig{
AdditionalTfVars: additionalTfVars,
RequiredEnvVars: resource.RequiredEnvVars,
WithRName: (resource.Generator != ""),
}

generateTestConfig(g, testDirPath, "basic", tfTemplates, common)
generateTestConfig(g, testDirPath, "basic", tfTemplates, commonConfig)

var versions []*version.Version

if resource.PreIdentityVersion != nil {
if resource.PreIdentityVersion.Equal(v5_100_0) {
Expand All @@ -234,33 +238,39 @@ func main() {
g.Fatalf("parsing config template %q: %s", configTmplV5Path, err)
}
}
commonV5 := common
commonV5.ExternalProviders = map[string]requiredProvider{
commonConfigV5 := commonConfig
commonConfigV5.ExternalProviders = map[string]requiredProvider{
"aws": {
Source: "hashicorp/aws",
Version: "5.100.0",
},
}
generateTestConfig(g, testDirPath, "basic_v5.100.0", tfTemplatesV5, commonV5)
generateTestConfig(g, testDirPath, "basic_v5.100.0", tfTemplatesV5, commonConfigV5)

commonV6 := common
commonV6.ExternalProviders = map[string]requiredProvider{
"aws": {
Source: "hashicorp/aws",
Version: "6.0.0",
},
}
generateTestConfig(g, testDirPath, "basic_v6.0.0", tfTemplates, commonV6)
versions = append(versions, version.Must(version.NewVersion("6.0.0")))
} else {
commonPreIdentity := common
commonPreIdentity.ExternalProviders = map[string]requiredProvider{
"aws": {
Source: "hashicorp/aws",
Version: resource.PreIdentityVersion.String(),
},
}
generateTestConfig(g, testDirPath, fmt.Sprintf("basic_v%s", resource.PreIdentityVersion.String()), tfTemplates, commonPreIdentity)
versions = append(versions, resource.PreIdentityVersion)
}
}

if len(resource.IdentityVersions) > 1 {
v := resource.IdentityVersions[1]
v, err := common.VersionDecrementMinor(v)
if err != nil {
g.Fatalf("generating versioned configurations: %s", err)
}
versions = append(versions, v)
}

for _, version := range versions {
common := commonConfig
common.ExternalProviders = map[string]requiredProvider{
"aws": {
Source: "hashicorp/aws",
Version: version.String(),
},
}
generateTestConfig(g, testDirPath, fmt.Sprintf("basic_v%s", version.String()), tfTemplates, common)
}

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

if resource.GenerateRegionOverrideTest() {
common.WithRegion = true
commonConfig.WithRegion = true

generateTestConfig(g, testDirPath, "region_override", tfTemplates, common)
generateTestConfig(g, testDirPath, "region_override", tfTemplates, commonConfig)
}
}
}
Expand Down Expand Up @@ -364,26 +374,29 @@ const (
)

type ResourceDatum struct {
service *serviceRecords
FileName string
idAttrDuplicates string // TODO: Remove. Still needed for Parameterized Identity
GenerateConfig bool
ARNFormat string
arnAttribute string
isARNFormatGlobal triBoolean
ArnIdentity bool
MutableIdentity bool
IsGlobal bool
isSingleton bool
HasRegionOverrideTest bool
identityAttributes []identityAttribute
identityAttribute string
IdentityDuplicateAttrs []string
IDAttrFormat string
HasV6_0NullValuesError bool
HasV6_0RefreshError bool
HasNoPreExistingResource bool
PreIdentityVersion *version.Version
service *serviceRecords
FileName string
idAttrDuplicates string // TODO: Remove. Still needed for Parameterized Identity
GenerateConfig bool
ARNFormat string
arnAttribute string
isARNFormatGlobal triBoolean
ArnIdentity bool
MutableIdentity bool
IsGlobal bool
isSingleton bool
HasRegionOverrideTest bool
identityAttributes []identityAttribute
identityAttribute string
IdentityDuplicateAttrs []string
IDAttrFormat string
HasV6_0NullValuesError bool
HasV6_0RefreshError bool
HasNoPreExistingResource bool
PreIdentityVersion *version.Version
IsCustomInherentRegionIdentity bool
customIdentityAttribute string
IdentityVersions map[int64]*version.Version
tests.CommonArgs
}

Expand Down Expand Up @@ -436,7 +449,7 @@ func (d ResourceDatum) GenerateRegionOverrideTest() bool {
}

func (d ResourceDatum) HasInherentRegion() bool {
return d.IsARNIdentity() || d.IsRegionalSingleton()
return d.IsARNIdentity() || d.IsRegionalSingleton() || d.IsCustomInherentRegionIdentity
}

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

func (r ResourceDatum) CustomIdentityAttribute() string {
return namesgen.ConstOrQuote(r.customIdentityAttribute)
}

func (r ResourceDatum) LatestIdentityVersion() int64 {
if len(r.IdentityVersions) == 0 {
return 0
}
return slices.Max(slices.Collect(maps.Keys(r.IdentityVersions)))
}

type identityAttribute struct {
name string
Optional bool
Expand Down Expand Up @@ -552,6 +576,7 @@ func (v *visitor) processFuncDecl(funcDecl *ast.FuncDecl) {
CommonArgs: tests.InitCommonArgs(),
IsGlobal: false,
HasRegionOverrideTest: true,
IdentityVersions: make(map[int64]*version.Version, 0),
}
hasIdentity := false
skip := false
Expand Down Expand Up @@ -719,6 +744,27 @@ func (v *visitor) processFuncDecl(funcDecl *ast.FuncDecl) {
}
}

case "CustomInherentRegionIdentity":
hasIdentity = true
d.IsCustomInherentRegionIdentity = true

args := common.ParseArgs(m[3])
d.customIdentityAttribute = args.Positional[0]
d.identityAttribute = args.Positional[0]

var attrs []string
if attr, ok := args.Keyword["identityDuplicateAttributes"]; ok {
attrs = strings.Split(attr, ";")
}
if d.Implementation == tests.ImplementationSDK {
attrs = append(attrs, "id")
}
slices.Sort(attrs)
attrs = slices.Compact(attrs)
d.IdentityDuplicateAttrs = tfslices.ApplyToAll(attrs, func(s string) string {
return namesgen.ConstOrQuote(s)
})

case "Testing":
args := common.ParseArgs(m[3])

Expand Down Expand Up @@ -812,6 +858,26 @@ func (v *visitor) processFuncDecl(funcDecl *ast.FuncDecl) {
if attr, ok := args.Keyword["tlsKeyDomain"]; ok {
tlsKeyCN = attr
}
if attr, ok := args.Keyword["identityVersion"]; ok {
parts := strings.Split(attr, ";")
if len(parts) != 2 {
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)))
continue
}
var identityVersion int64
if i, err := strconv.ParseInt(parts[0], 10, 64); err != nil {
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)))
continue
} else {
identityVersion = i
}
providerVersion, err := version.NewVersion(parts[1])
if err != nil {
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)))
continue
}
d.IdentityVersions[identityVersion] = providerVersion
}
}
}
}
Expand Down
Loading
Loading