diff --git a/go.mod b/go.mod index e0ce4b7f40e6..5ac1d89d65f1 100644 --- a/go.mod +++ b/go.mod @@ -282,17 +282,17 @@ require ( github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.7.0 github.com/hashicorp/hcl/v2 v2.23.0 - github.com/hashicorp/terraform-json v0.24.0 - github.com/hashicorp/terraform-plugin-framework v1.14.1 + github.com/hashicorp/terraform-json v0.24.1-0.20250314103308-f86d5e36f4ab + github.com/hashicorp/terraform-plugin-framework v1.15.0-beta.1 github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0 github.com/hashicorp/terraform-plugin-framework-timeouts v0.5.0 github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0 github.com/hashicorp/terraform-plugin-framework-validators v0.17.0 - github.com/hashicorp/terraform-plugin-go v0.26.0 + github.com/hashicorp/terraform-plugin-go v0.27.0-alpha.1.0.20250325210248-fa8d1fe4306b github.com/hashicorp/terraform-plugin-log v0.9.0 - github.com/hashicorp/terraform-plugin-mux v0.18.0 - github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1 - github.com/hashicorp/terraform-plugin-testing v1.12.0 + github.com/hashicorp/terraform-plugin-mux v0.19.0-alpha.1 + github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0-beta.1 + github.com/hashicorp/terraform-plugin-testing v1.13.0-beta.1 github.com/jmespath/go-jmespath v0.4.0 github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38 github.com/mitchellh/copystructure v1.2.0 diff --git a/go.sum b/go.sum index c84c556de4a2..7edde3cdf9b8 100644 --- a/go.sum +++ b/go.sum @@ -652,10 +652,10 @@ github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/terraform-exec v0.23.0 h1:MUiBM1s0CNlRFsCLJuM5wXZrzA3MnPYEsiXmzATMW/I= github.com/hashicorp/terraform-exec v0.23.0/go.mod h1:mA+qnx1R8eePycfwKkCRk3Wy65mwInvlpAeOwmA7vlY= -github.com/hashicorp/terraform-json v0.24.0 h1:rUiyF+x1kYawXeRth6fKFm/MdfBS6+lW4NbeATsYz8Q= -github.com/hashicorp/terraform-json v0.24.0/go.mod h1:Nfj5ubo9xbu9uiAoZVBsNOjvNKB66Oyrvtit74kC7ow= -github.com/hashicorp/terraform-plugin-framework v1.14.1 h1:jaT1yvU/kEKEsxnbrn4ZHlgcxyIfjvZ41BLdlLk52fY= -github.com/hashicorp/terraform-plugin-framework v1.14.1/go.mod h1:xNUKmvTs6ldbwTuId5euAtg37dTxuyj3LHS3uj7BHQ4= +github.com/hashicorp/terraform-json v0.24.1-0.20250314103308-f86d5e36f4ab h1:5Qpuprk76zkVEdTCtfoPjUc+1AeUxlgkF6sWTr7qLDs= +github.com/hashicorp/terraform-json v0.24.1-0.20250314103308-f86d5e36f4ab/go.mod h1:sMKS8fiRDX4rVlR6EJUMudg1WcanxCMoWwTLkgZP/vc= +github.com/hashicorp/terraform-plugin-framework v1.15.0-beta.1 h1:lX4qacaJc8dqUzEaOALeUW0Gvv0ACs9myvN1WQ4rRgU= +github.com/hashicorp/terraform-plugin-framework v1.15.0-beta.1/go.mod h1:SNnBQzWTh3ydNHBJF8eLVHlm/2gu+RBG508LCfCSVwI= github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0 h1:SJXL5FfJJm17554Kpt9jFXngdM6fXbnUnZ6iT2IeiYA= github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0/go.mod h1:p0phD0IYhsu9bR4+6OetVvvH59I6LwjXGnTVEr8ox6E= github.com/hashicorp/terraform-plugin-framework-timeouts v0.5.0 h1:I/N0g/eLZ1ZkLZXUQ0oRSXa8YG/EF0CEuQP1wXdrzKw= @@ -664,14 +664,14 @@ github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0 h1:v3DapR8gsp3E github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0/go.mod h1:c3PnGE9pHBDfdEVG9t1S1C9ia5LW+gkFR0CygXlM8ak= github.com/hashicorp/terraform-plugin-framework-validators v0.17.0 h1:0uYQcqqgW3BMyyve07WJgpKorXST3zkpzvrOnf3mpbg= github.com/hashicorp/terraform-plugin-framework-validators v0.17.0/go.mod h1:VwdfgE/5Zxm43flraNa0VjcvKQOGVrcO4X8peIri0T0= -github.com/hashicorp/terraform-plugin-go v0.26.0 h1:cuIzCv4qwigug3OS7iKhpGAbZTiypAfFQmw8aE65O2M= -github.com/hashicorp/terraform-plugin-go v0.26.0/go.mod h1:+CXjuLDiFgqR+GcrM5a2E2Kal5t5q2jb0E3D57tTdNY= -github.com/hashicorp/terraform-plugin-mux v0.18.0 h1:7491JFSpWyAe0v9YqBT+kel7mzHAbO5EpxxT0cUL/Ms= -github.com/hashicorp/terraform-plugin-mux v0.18.0/go.mod h1:Ho1g4Rr8qv0qTJlcRKfjjXTIO67LNbDtM6r+zHUNHJQ= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1 h1:WNMsTLkZf/3ydlgsuXePa3jvZFwAJhruxTxP/c1Viuw= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1/go.mod h1:P6o64QS97plG44iFzSM6rAn6VJIC/Sy9a9IkEtl79K4= -github.com/hashicorp/terraform-plugin-testing v1.12.0 h1:tpIe+T5KBkA1EO6aT704SPLedHUo55RenguLHcaSBdI= -github.com/hashicorp/terraform-plugin-testing v1.12.0/go.mod h1:jbDQUkT9XRjAh1Bvyufq+PEH1Xs4RqIdpOQumSgSXBM= +github.com/hashicorp/terraform-plugin-go v0.27.0-alpha.1.0.20250325210248-fa8d1fe4306b h1:JCAO+OdLztQ6F2bZ8lU93u986UVQl2Y/HNz18/jg3b0= +github.com/hashicorp/terraform-plugin-go v0.27.0-alpha.1.0.20250325210248-fa8d1fe4306b/go.mod h1:HFPb73wivXPZy5wMuE7T3WqFbpIj6R6q1svKnZsnMZo= +github.com/hashicorp/terraform-plugin-mux v0.19.0-alpha.1 h1:WCzSBsp719WKEV/+j+4/o742paM0twYm7B84y7x8pOM= +github.com/hashicorp/terraform-plugin-mux v0.19.0-alpha.1/go.mod h1:iKph9LFBiD4a33AJLgqg7IKSVg2kdlYvx0IRd+ys3Ig= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0-beta.1 h1:Ia0jU/ZLzyfReSg4TMHq6ffYGCNCREzpSMBqswM71a0= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0-beta.1/go.mod h1:fVJWDD6/eNOK0aG55CK5g8vTv3Ph9UD/dZztPPvFDgw= +github.com/hashicorp/terraform-plugin-testing v1.13.0-beta.1 h1:YpdITO9pgpSVSBoxL9DqiOG/2/rUQtcnP6encYAtKd0= +github.com/hashicorp/terraform-plugin-testing v1.13.0-beta.1/go.mod h1:2fJBV6Eim03FqxyaPbPW2qZadDbfD1+yj/tRnDHBjjI= github.com/hashicorp/terraform-registry-address v0.2.5 h1:2GTftHqmUhVOeuu9CW3kwDkRe4pcBDq0uuK5VJngU1M= github.com/hashicorp/terraform-registry-address v0.2.5/go.mod h1:PpzXWINwB5kuVS5CA7m1+eO2f1jKb5ZDIxrOPfpnGkg= github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= diff --git a/internal/acctest/configs.go b/internal/acctest/configs.go index f898b0f523de..0674d8864f23 100644 --- a/internal/acctest/configs.go +++ b/internal/acctest/configs.go @@ -294,6 +294,11 @@ func ConfigAvailableAZsNoOptInDefaultExclude() string { return ConfigAvailableAZsNoOptInExclude("usw2-az4", "usgw1-az2") } +func ConfigAvailableAZsNoOptInDefaultExclude_RegionOverride(region string) string { + // Exclude usw2-az4 (us-west-2d) as it has limited instance types. + return ConfigAvailableAZsNoOptInExclude_RegionOverride(region, "usw2-az4", "usgw1-az2") +} + func ConfigAvailableAZsNoOptInExclude(excludeZoneIds ...string) string { return fmt.Sprintf(` data "aws_availability_zones" "available" { @@ -308,6 +313,22 @@ data "aws_availability_zones" "available" { `, strings.Join(excludeZoneIds, "\", \"")) } +func ConfigAvailableAZsNoOptInExclude_RegionOverride(region string, excludeZoneIds ...string) string { + return fmt.Sprintf(` +data "aws_availability_zones" "available" { + region = %[2]q + + exclude_zone_ids = ["%[1]s"] + state = "available" + + filter { + name = "opt-in-status" + values = ["opt-in-not-required"] + } +} +`, strings.Join(excludeZoneIds, "\", \""), region) +} + // AvailableEC2InstanceTypeForAvailabilityZone returns the configuration for a data source that describes // the first available EC2 instance type offering in the specified availability zone from a list of preferred instance types. // The first argument is either an Availability Zone name or Terraform configuration reference to one, e.g. @@ -565,6 +586,29 @@ resource "aws_subnet" "test" { ) } +func ConfigVPCWithSubnets_RegionOverride(rName string, subnetCount int, region string) string { + return ConfigCompose( + ConfigAvailableAZsNoOptInDefaultExclude_RegionOverride(region), + fmt.Sprintf(` +resource "aws_vpc" "test" { + region = %[3]q + + cidr_block = "10.0.0.0/16" +} + +resource "aws_subnet" "test" { + count = %[2]d + + region = %[3]q + + vpc_id = aws_vpc.test.id + availability_zone = data.aws_availability_zones.available.names[count.index] + cidr_block = cidrsubnet(aws_vpc.test.cidr_block, 8, count.index) +} +`, rName, subnetCount, region), + ) +} + func ConfigVPCWithSubnetsEnableDNSHostnames(rName string, subnetCount int) string { return ConfigCompose( ConfigAvailableAZsNoOptInDefaultExclude(), diff --git a/internal/acctest/knownvalue/account_id.go b/internal/acctest/knownvalue/account_id.go new file mode 100644 index 000000000000..8266c6b09314 --- /dev/null +++ b/internal/acctest/knownvalue/account_id.go @@ -0,0 +1,42 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package statecheck + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" +) + +var _ knownvalue.Check = accountID{} + +type accountID struct { +} + +// CheckValue determines whether the passed value is of type string, and +// contains a matching sequence of bytes. +func (v accountID) CheckValue(other any) error { + otherVal, ok := other.(string) + + if !ok { + return fmt.Errorf("expected string value for AccountID check, got: %T", other) + } + + if a, e := otherVal, acctest.AccountID(context.Background()); a != e { + return fmt.Errorf("expected value %s for AccountID check, got: %s", e, a) + } + + return nil +} + +// String returns the string representation of the value. +func (v accountID) String() string { + return "Who Knows" +} + +func AccountID() knownvalue.Check { + return accountID{} +} diff --git a/internal/acctest/statecheck/expect_attribute_format.go b/internal/acctest/statecheck/expect_attribute_format.go new file mode 100644 index 000000000000..0c87e80aed40 --- /dev/null +++ b/internal/acctest/statecheck/expect_attribute_format.go @@ -0,0 +1,60 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package statecheck + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +var _ statecheck.StateCheck = expectAttributeFormatCheck{} + +type expectAttributeFormatCheck struct { + base Base + attributePath tfjsonpath.Path + format string +} + +func (e expectAttributeFormatCheck) CheckState(ctx context.Context, request statecheck.CheckStateRequest, response *statecheck.CheckStateResponse) { + resource, ok := e.base.ResourceFromState(request, response) + if !ok { + return + } + + value, err := tfjsonpath.Traverse(resource.AttributeValues, e.attributePath) + if err != nil { + response.Error = err + return + } + + otherVal, ok := value.(string) + if !ok { + response.Error = fmt.Errorf("expected string value for ExpectAttributeFormat check, got: %T", value) + return + } + + expectedValue, err := populateFromResourceState(e.format, resource) + if err != nil { + response.Error = err + return + } + + if otherVal != expectedValue { + response.Error = fmt.Errorf("expected value %s for ExpectAttributeFormat check, got: %s", expectedValue, otherVal) + return + } + + return +} + +func ExpectAttributeFormat(resourceAddress string, attributePath tfjsonpath.Path, format string) statecheck.StateCheck { + return expectAttributeFormatCheck{ + base: NewBase(resourceAddress), + attributePath: attributePath, + format: format, + } +} diff --git a/internal/acctest/statecheck/expect_identity_regional_arn_format.go b/internal/acctest/statecheck/expect_identity_regional_arn_format.go new file mode 100644 index 000000000000..e46cb32467fe --- /dev/null +++ b/internal/acctest/statecheck/expect_identity_regional_arn_format.go @@ -0,0 +1,109 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package statecheck + +import ( + "context" + "fmt" + "maps" + "slices" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + tfknownvalue "github.com/hashicorp/terraform-provider-aws/internal/acctest/knownvalue" +) + +var _ statecheck.StateCheck = expectIdentityRegionalARNFormatCheck{} + +type expectIdentityRegionalARNFormatCheck struct { + base Base + arnService string + arnFormat string + checkFactory func(service string, arn string) knownvalue.Check +} + +func (e expectIdentityRegionalARNFormatCheck) 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()) + return + } + + if len(resource.IdentityValues) > 1 { + deltaMsg := createDeltaString(resource.IdentityValues, map[string]bool{"arn": true}, "actual identity has extra attribute(s): ") + + response.Error = fmt.Errorf("%s - Expected %d attribute(s) in the actual identity object, got %d attribute(s): %s", e.base.ResourceAddress(), 1, len(resource.IdentityValues), deltaMsg) + return + } + + attrPath := tfjsonpath.New("arn") + value, err := tfjsonpath.Traverse(resource.AttributeValues, attrPath) + if err != nil { + response.Error = err + return + } + + arnString, err := populateFromResourceState(e.arnFormat, resource) + if err != nil { + response.Error = err + return + } + + knownCheck := e.checkFactory(e.arnService, arnString) + if err = knownCheck.CheckValue(value); err != nil { + response.Error = fmt.Errorf("checking value for attribute at path: %s.%s, err: %s", e.base.ResourceAddress(), attrPath, err) + return + } +} + +func ExpectIdentityRegionalARNFormat(resourceAddress string, arnService, arnFormat string) statecheck.StateCheck { + return expectIdentityRegionalARNFormatCheck{ + base: NewBase(resourceAddress), + arnService: arnService, + arnFormat: arnFormat, + checkFactory: func(service string, arn string) knownvalue.Check { + return tfknownvalue.RegionalARNExact(service, arn) + }, + } +} + +func ExpectIdentityRegionalARNAlternateRegionFormat(resourceAddress string, arnService, arnFormat string) statecheck.StateCheck { + return expectIdentityRegionalARNFormatCheck{ + base: NewBase(resourceAddress), + arnService: arnService, + arnFormat: arnFormat, + checkFactory: func(service string, arn string) knownvalue.Check { + return tfknownvalue.RegionalARNAlternateRegionExact(service, arn) + }, + } +} + +// createDeltaString prints the map keys that are present in mapA and not present in mapB +func createDeltaString[T any, V any](mapA map[string]T, mapB map[string]V, msgPrefix string) string { + deltaMsg := "" + + deltaMap := make(map[string]T, len(mapA)) + maps.Copy(deltaMap, mapA) + for key := range mapB { + delete(deltaMap, key) + } + + deltaKeys := slices.Sorted(maps.Keys(deltaMap)) + + for i, k := range deltaKeys { + if i == 0 { + deltaMsg += msgPrefix + } else { + deltaMsg += ", " + } + deltaMsg += fmt.Sprintf("%q", k) + } + + return deltaMsg +} diff --git a/internal/generate/servicepackage/main.go b/internal/generate/servicepackage/main.go index 38eee5b36ec0..e823a0dfb3fb 100644 --- a/internal/generate/servicepackage/main.go +++ b/internal/generate/servicepackage/main.go @@ -13,7 +13,9 @@ import ( "go/ast" "go/parser" "go/token" + "maps" "os" + "slices" "strconv" "strings" "text/template" @@ -85,6 +87,32 @@ func main() { SDKDataSources: v.sdkDataSources, SDKResources: v.sdkResources, } + + var imports []goImport + for resource := range maps.Values(v.ephemeralResources) { + imports = append(imports, resource.goImports...) + } + for resource := range maps.Values(v.frameworkDataSources) { + imports = append(imports, resource.goImports...) + } + for resource := range maps.Values(v.frameworkResources) { + imports = append(imports, resource.goImports...) + } + for resource := range maps.Values(v.sdkDataSources) { + imports = append(imports, resource.goImports...) + } + for resource := range maps.Values(v.sdkResources) { + imports = append(imports, resource.goImports...) + } + slices.SortFunc(imports, func(a, b goImport) int { + if n := strings.Compare(a.Path, b.Path); n != 0 { + return n + } + return strings.Compare(a.Alias, b.Alias) + }) + imports = slices.Compact(imports) + s.GoImports = imports + templateFuncMap := template.FuncMap{ "Camel": names.ToCamelCase, } @@ -125,6 +153,22 @@ type ResourceDatum struct { TagsIdentifierAttribute string TagsResourceType string ValidateRegionOverrideInPartition bool + IdentityAttributes []identityAttribute + ARNIdentity bool + SingletonIdentity bool + MutableIdentity bool + WrappedImport bool + goImports []goImport +} + +type identityAttribute struct { + Name string + Optional bool +} + +type goImport struct { + Path string + Alias string } func (d ResourceDatum) RegionOverrideEnabled() bool { @@ -143,9 +187,10 @@ type ServiceDatum struct { FrameworkResources map[string]ResourceDatum SDKDataSources map[string]ResourceDatum SDKResources map[string]ResourceDatum + GoImports []goImport } -//go:embed file.gtpl +//go:embed service_package_gen.go.gtpl var tmpl string //go:embed endpoint_resolver.go.gtpl @@ -211,13 +256,27 @@ func (v *visitor) processFile(file *ast.File) { func (v *visitor) processFuncDecl(funcDecl *ast.FuncDecl) { v.functionName = funcDecl.Name.Name - // Look first for per-resource annotations such as tagging and Region. d := ResourceDatum{ IsGlobal: false, regionOverrideEnabled: true, ValidateRegionOverrideInPartition: true, } + annotations := make(map[string]bool) + for _, line := range funcDecl.Doc.List { + line := line.Text + + if m := annotation.FindStringSubmatch(line); len(m) > 0 { + annotationName := m[1] + annotations[annotationName] = true + } + } + keys := slices.Collect(maps.Keys(annotations)) + if slices.Contains(keys, "IdentityAttribute") && slices.Contains(keys, "ArnIdentity") { + v.errs = append(v.errs, fmt.Errorf(`only one of "IdentityAttribute" and "ArnIdentity" can be specified: %s`, fmt.Sprintf("%s.%s", v.packageName, v.functionName))) + } + + // Look first for per-resource annotations such as tagging and Region. for _, line := range funcDecl.Doc.List { line := line.Text @@ -229,6 +288,10 @@ func (v *visitor) processFuncDecl(funcDecl *ast.FuncDecl) { v.errs = append(v.errs, fmt.Errorf("invalid Region/global value (%s): %s: %w", attr, fmt.Sprintf("%s.%s", v.packageName, v.functionName), err)) } else { d.IsGlobal = global + if global { + d.regionOverrideEnabled = false + d.ValidateRegionOverrideInPartition = false + } } } if attr, ok := args.Keyword["overrideEnabled"]; ok { @@ -245,6 +308,7 @@ func (v *visitor) processFuncDecl(funcDecl *ast.FuncDecl) { d.ValidateRegionOverrideInPartition = validate } } + case "Tags": d.TransparentTagging = true @@ -259,6 +323,41 @@ func (v *visitor) processFuncDecl(funcDecl *ast.FuncDecl) { if attr, ok := args.Keyword["resourceType"]; ok { d.TagsResourceType = attr } + + case "IdentityAttribute": + args := common.ParseArgs(m[3]) + + if len(args.Positional) == 0 { + v.errs = append(v.errs, fmt.Errorf("no Identity attribute name: %s", fmt.Sprintf("%s.%s", v.packageName, v.functionName))) + continue + } + + identityAttribute := identityAttribute{ + Name: namesgen.ConstOrQuote(args.Positional[0]), + } + + if attr, ok := args.Keyword["optional"]; ok { + if b, err := strconv.ParseBool(attr); err != nil { + v.errs = append(v.errs, fmt.Errorf("invalid optional value: %q at %s. Should be boolean value.", attr, fmt.Sprintf("%s.%s", v.packageName, v.functionName))) + continue + } else { + identityAttribute.Optional = b + } + } + + d.IdentityAttributes = append(d.IdentityAttributes, identityAttribute) + + case "WrappedImport": + d.WrappedImport = true + + case "ArnIdentity": + d.ARNIdentity = true + + case "MutableIdentity": + d.MutableIdentity = true + + case "SingletonIdentity": + d.SingletonIdentity = true } } } @@ -392,7 +491,8 @@ func (v *visitor) processFuncDecl(funcDecl *ast.FuncDecl) { } else { v.sdkResources[typeName] = d } - case "Region", "Tags": + + case "IdentityAttribute", "ArnIdentity", "MutableIdentity", "SingletonIdentity", "Region", "Tags": // Handled above. case "Testing": // Ignored. diff --git a/internal/generate/servicepackage/file.gtpl b/internal/generate/servicepackage/service_package_gen.go.gtpl similarity index 81% rename from internal/generate/servicepackage/file.gtpl rename to internal/generate/servicepackage/service_package_gen.go.gtpl index 211a0075ede4..a42eb7601d2d 100644 --- a/internal/generate/servicepackage/file.gtpl +++ b/internal/generate/servicepackage/service_package_gen.go.gtpl @@ -1,5 +1,12 @@ // Code generated by internal/generate/servicepackage/main.go; DO NOT EDIT. +{{ define "IdentifierAttribute" -}} +inttypes.StringIdentityAttribute( + {{- .Name }}, + {{- if .Optional }}false{{ else }}true{{ end -}} +), +{{- end }} + package {{ .ProviderPackage }} import ( @@ -19,6 +26,9 @@ import ( {{- if ne .ProviderPackage "meta" }} "github.com/hashicorp/terraform-provider-aws/names" {{- end }} + {{ range .GoImports -}} + {{ if .Alias }}{{ .Alias }} {{ end }}"{{ .Path }}" + {{ end }} ) type servicePackage struct {} @@ -109,6 +119,31 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*inttypes.Ser IsValidateOverrideInPartition: {{ $value.ValidateRegionOverrideInPartition }}, }), {{- end }} + {{- if not $value.MutableIdentity }} + {{- if gt (len $value.IdentityAttributes) 0 }} + {{- if or $.IsGlobal $value.IsGlobal }} + Identity: inttypes.GlobalParameterizedIdentity( + {{- range $value.IdentityAttributes }} + {{ template "IdentifierAttribute" . }} + {{- end }} + ), + {{ else }} + Identity: inttypes.ParameterizedIdentity( + {{- range $value.IdentityAttributes }} + {{ template "IdentifierAttribute" . }} + {{- end }} + ), + {{- end }} + {{- else if $value.ARNIdentity }} + Identity: inttypes.ARNIdentity(), + {{- else if $value.SingletonIdentity }} + {{- if or $.IsGlobal $value.IsGlobal }} + Identity: inttypes.GlobalSingletonIdentity(), + {{ else }} + Identity: inttypes.RegionalSingletonIdentity(), + {{- end }} + {{- end }} + {{- end }} }, {{- end }} } @@ -175,6 +210,36 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*inttypes.ServicePa IsValidateOverrideInPartition: {{ $value.ValidateRegionOverrideInPartition }}, }), {{- end }} + {{- if not $value.MutableIdentity }} + {{- if gt (len $value.IdentityAttributes) 0 }} + {{- if or $.IsGlobal $value.IsGlobal }} + Identity: inttypes.GlobalParameterizedIdentity( + {{- range $value.IdentityAttributes }} + {{ template "IdentifierAttribute" . }} + {{- end }} + ), + {{- else }} + Identity: inttypes.ParameterizedIdentity( + {{- range $value.IdentityAttributes }} + {{ template "IdentifierAttribute" . }} + {{- end }} + ), + {{- end }} + {{- else if $value.ARNIdentity }} + Identity: inttypes.ARNIdentity(), + {{- else if $value.SingletonIdentity }} + {{- if or $.IsGlobal $value.IsGlobal }} + Identity: inttypes.GlobalSingletonIdentity(), + {{- else }} + Identity: inttypes.RegionalSingletonIdentity(), + {{- end }} + {{- end }} + {{- end }} + {{- if $value.WrappedImport }} + Import: inttypes.Import{ + WrappedImport: true, + }, + {{- end }} }, {{- end }} } diff --git a/internal/provider/fwprovider/identity_interceptor.go b/internal/provider/fwprovider/identity_interceptor.go new file mode 100644 index 000000000000..1e32a5a7db75 --- /dev/null +++ b/internal/provider/fwprovider/identity_interceptor.go @@ -0,0 +1,88 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package fwprovider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" + inttypes "github.com/hashicorp/terraform-provider-aws/internal/types" + "github.com/hashicorp/terraform-provider-aws/names" +) + +var _ resourceCRUDInterceptor = identityInterceptor{} + +type identityInterceptor struct { + attributes []string +} + +func (r identityInterceptor) create(ctx context.Context, opts interceptorOptions[resource.CreateRequest, resource.CreateResponse]) diag.Diagnostics { + var diags diag.Diagnostics + awsClient := opts.c + + switch response, when := opts.response, opts.when; when { + case After: + identity := response.Identity + if identity == nil { + break + } + + for _, attrName := range r.attributes { + switch attrName { + case names.AttrAccountID: + diags.Append(identity.SetAttribute(ctx, path.Root(attrName), awsClient.AccountID(ctx))...) + if diags.HasError() { + return diags + } + + case names.AttrRegion: + diags.Append(identity.SetAttribute(ctx, path.Root(attrName), awsClient.Region(ctx))...) + if diags.HasError() { + return diags + } + + default: + var attrVal attr.Value + diags.Append(response.State.GetAttribute(ctx, path.Root(attrName), &attrVal)...) + if diags.HasError() { + return diags + } + + diags.Append(identity.SetAttribute(ctx, path.Root(attrName), attrVal)...) + if diags.HasError() { + return diags + } + } + } + } + + return diags +} + +func (r identityInterceptor) read(ctx context.Context, opts interceptorOptions[resource.ReadRequest, resource.ReadResponse]) diag.Diagnostics { + var diags diag.Diagnostics + return diags +} + +func (r identityInterceptor) update(ctx context.Context, opts interceptorOptions[resource.UpdateRequest, resource.UpdateResponse]) diag.Diagnostics { + var diags diag.Diagnostics + return diags +} + +func (r identityInterceptor) delete(ctx context.Context, opts interceptorOptions[resource.DeleteRequest, resource.DeleteResponse]) diag.Diagnostics { + var diags diag.Diagnostics + return diags +} + +func newIdentityInterceptor(attributes []inttypes.IdentityAttribute) identityInterceptor { + return identityInterceptor{ + attributes: tfslices.ApplyToAll(attributes, func(v inttypes.IdentityAttribute) string { + return v.Name + }), + } +} diff --git a/internal/provider/fwprovider/provider.go b/internal/provider/fwprovider/provider.go index a4cb2a88936f..e099ad0d7d1c 100644 --- a/internal/provider/fwprovider/provider.go +++ b/internal/provider/fwprovider/provider.go @@ -579,6 +579,11 @@ func (p *fwprovider) initialize(ctx context.Context) error { interceptors: interceptors, typeName: typeName, } + if len(v.Identity.Attributes) > 0 { + opts.identity = v.Identity + opts.interceptors = append(opts.interceptors, newIdentityInterceptor(v.Identity.Attributes)) + } + p.resources = append(p.resources, func() resource.Resource { return newWrappedResource(inner, opts) }) diff --git a/internal/provider/fwprovider/wrap.go b/internal/provider/fwprovider/wrap.go index 3622148ed26c..9f49a6588dc8 100644 --- a/internal/provider/fwprovider/wrap.go +++ b/internal/provider/fwprovider/wrap.go @@ -11,10 +11,12 @@ import ( "github.com/hashicorp/terraform-plugin-framework/ephemeral" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/identityschema" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" "github.com/hashicorp/terraform-provider-aws/internal/framework" + "github.com/hashicorp/terraform-provider-aws/internal/types" ) // Implemented by (Config|Plan|State).GetAttribute(). @@ -281,6 +283,7 @@ type wrappedResourceOptions struct { bootstrapContext contextFunc interceptors interceptorInvocations typeName string + identity types.Identity } // wrappedResource represents an interceptor dispatcher for a Plugin Framework resource. @@ -505,3 +508,29 @@ func (w *wrappedResource) MoveState(ctx context.Context) []resource.StateMover { return nil } + +func (w *wrappedResource) IdentitySchema(ctx context.Context, req resource.IdentitySchemaRequest, resp *resource.IdentitySchemaResponse) { + if len(w.opts.identity.Attributes) > 0 { + resp.IdentitySchema = newIdentitySchema(w.opts.identity.Attributes) + } +} + +func newIdentitySchema(attributes []types.IdentityAttribute) identityschema.Schema { + schemaAttrs := make(map[string]identityschema.Attribute, len(attributes)) + for _, attr := range attributes { + schemaAttrs[attr.Name] = newIdentityAttribute(attr) + } + return identityschema.Schema{ + Attributes: schemaAttrs, + } +} + +func newIdentityAttribute(attribute types.IdentityAttribute) identityschema.Attribute { + attr := identityschema.StringAttribute{} + if attribute.Required { + attr.RequiredForImport = true + } else { + attr.OptionalForImport = true + } + return attr +} diff --git a/internal/provider/identity_interceptor.go b/internal/provider/identity_interceptor.go new file mode 100644 index 000000000000..27b754df028e --- /dev/null +++ b/internal/provider/identity_interceptor.go @@ -0,0 +1,269 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" + inttypes "github.com/hashicorp/terraform-provider-aws/internal/types" + "github.com/hashicorp/terraform-provider-aws/names" +) + +var _ crudInterceptor = identityInterceptor{} + +type identityInterceptor struct { + attributes []string +} + +func (r identityInterceptor) run(ctx context.Context, opts crudInterceptorOptions) diag.Diagnostics { + var diags diag.Diagnostics + awsClient := opts.c + + switch d, when, why := opts.d, opts.when, opts.why; when { + case After: + switch why { + case Create: + identity, err := d.Identity() + if err != nil { + return sdkdiag.AppendFromErr(diags, err) + } + + for _, attr := range r.attributes { + switch attr { + case names.AttrAccountID: + if err := identity.Set(attr, awsClient.AccountID(ctx)); err != nil { + return sdkdiag.AppendFromErr(diags, err) + } + + case names.AttrRegion: + if err := identity.Set(attr, awsClient.Region(ctx)); err != nil { + return sdkdiag.AppendFromErr(diags, err) + } + + default: + val, ok := getAttributeOk(d, attr) + if !ok { + continue + } + if err := identity.Set(attr, val); err != nil { + return sdkdiag.AppendFromErr(diags, err) + } + } + } + } + } + + return diags +} + +func getAttributeOk(d schemaResourceData, name string) (string, bool) { + if name == "id" { + return d.Id(), true + } + v, ok := d.GetOk(name) + return v.(string), ok +} + +func newIdentityInterceptor(attributes []inttypes.IdentityAttribute) interceptorInvocation { + return interceptorInvocation{ + when: After, + why: Create, // TODO: probably need to do this after Read and Update as well + interceptor: identityInterceptor{ + attributes: tfslices.ApplyToAll(attributes, func(v inttypes.IdentityAttribute) string { + return v.Name + }), + }, + } +} + +func newResourceIdentity(v inttypes.Identity) *schema.ResourceIdentity { + return &schema.ResourceIdentity{ + SchemaFunc: func() map[string]*schema.Schema { + return newIdentitySchema(v.Attributes) + }, + } +} + +func newIdentitySchema(attributes []inttypes.IdentityAttribute) map[string]*schema.Schema { + identitySchema := make(map[string]*schema.Schema, len(attributes)) + for _, attr := range attributes { + identitySchema[attr.Name] = newIdentityAttribute(attr) + } + return identitySchema +} + +func newIdentityAttribute(attribute inttypes.IdentityAttribute) *schema.Schema { + attr := &schema.Schema{ + Type: schema.TypeString, + } + if attribute.Required { + attr.RequiredForImport = true + } else { + attr.OptionalForImport = true + } + return attr +} + +func newIdentityImporter(v inttypes.Identity) *schema.ResourceImporter { + if v.Singleton { + if v.Global { + return &schema.ResourceImporter{ + StateContext: globalSingletonImporter, + } + } else { + return &schema.ResourceImporter{ + StateContext: regionalSingletonImporter, + } + } + } + + importer := &schema.ResourceImporter{ + StateContext: func(ctx context.Context, rd *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + if rd.Id() != "" { + if v.IDAttrShadowsAttr != "id" { + rd.Set(v.IDAttrShadowsAttr, rd.Id()) + } + return []*schema.ResourceData{rd}, nil + } + + identity, err := rd.Identity() + if err != nil { + return nil, err + } + + for _, attr := range v.Attributes { + var val string + switch attr.Name { + case names.AttrAccountID: + accountIDRaw, ok := identity.GetOk(names.AttrAccountID) + if ok { + accountID, ok := accountIDRaw.(string) + if !ok { + return nil, fmt.Errorf("identity attribute %q: expected string, got %T", names.AttrAccountID, accountIDRaw) + } + client := meta.(*conns.AWSClient) + if accountID != client.AccountID(ctx) { + return nil, fmt.Errorf("Unable to import\n\nidentity attribute %q: Provider configured with Account ID %q, got %q", names.AttrAccountID, client.AccountID(ctx), accountID) + } + } + + case names.AttrRegion: + regionRaw, ok := identity.GetOk(names.AttrRegion) + if ok { + val, ok = regionRaw.(string) + if !ok { + return nil, fmt.Errorf("identity attribute %q: expected string, got %T", names.AttrRegion, regionRaw) + } + rd.Set(names.AttrRegion, val) + } + + default: + valRaw, ok := identity.GetOk(attr.Name) + if attr.Required && !ok { + return nil, fmt.Errorf("identity attribute %q is required", attr.Name) + } + val, ok = valRaw.(string) + if !ok { + return nil, fmt.Errorf("identity attribute %q: expected string, got %T", attr.Name, valRaw) + } + setAttribute(rd, attr.Name, val) + } + + if attr.Name == v.IDAttrShadowsAttr { + rd.SetId(val) + } + } + + return []*schema.ResourceData{rd}, nil + }, + } + return importer +} + +func globalSingletonImporter(ctx context.Context, rd *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + if rd.Id() != "" { + rd.Set(names.AttrAccountID, rd.Id()) + return []*schema.ResourceData{rd}, nil + } + + identity, err := rd.Identity() + if err != nil { + return nil, err + } + + client := meta.(*conns.AWSClient) + + accountIDRaw, ok := identity.GetOk(names.AttrAccountID) + var accountID string + if ok { + accountID, ok = accountIDRaw.(string) + if !ok { + return nil, fmt.Errorf("identity attribute %q: expected string, got %T", names.AttrAccountID, accountIDRaw) + } + if accountID != client.AccountID(ctx) { + return nil, fmt.Errorf("Unable to import\n\nidentity attribute %q: Provider configured with Account ID %q, got %q", names.AttrAccountID, client.AccountID(ctx), accountID) + } + } else { + accountID = client.AccountID(ctx) + } + rd.Set(names.AttrAccountID, accountID) + rd.SetId(accountID) + + return []*schema.ResourceData{rd}, nil +} + +func regionalSingletonImporter(ctx context.Context, rd *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + if rd.Id() != "" { + rd.Set("region", rd.Id()) + return []*schema.ResourceData{rd}, nil + } + + identity, err := rd.Identity() + if err != nil { + return nil, err + } + + client := meta.(*conns.AWSClient) + + accountIDRaw, ok := identity.GetOk(names.AttrAccountID) + var accountID string + if ok { + accountID, ok = accountIDRaw.(string) + if !ok { + return nil, fmt.Errorf("identity attribute %q: expected string, got %T", names.AttrAccountID, accountIDRaw) + } + if accountID != client.AccountID(ctx) { + return nil, fmt.Errorf("Unable to import\n\nidentity attribute %q: Provider configured with Account ID %q, got %q", names.AttrAccountID, client.AccountID(ctx), accountID) + } + } + + regionRaw, ok := identity.GetOk("region") + var region string + if ok { + region, ok = regionRaw.(string) + if !ok { + return nil, fmt.Errorf("identity attribute %q: expected string, got %T", "region", regionRaw) + } + } else { + region = client.Region(ctx) + } + rd.Set("region", region) + rd.SetId(region) + + return []*schema.ResourceData{rd}, nil +} + +func setAttribute(d *schema.ResourceData, name, value string) { + if name == "id" { + d.SetId(value) + } else { + d.Set(name, value) + } +} diff --git a/internal/provider/intercept.go b/internal/provider/intercept.go index 7013bbf9a393..54f35d95629a 100644 --- a/internal/provider/intercept.go +++ b/internal/provider/intercept.go @@ -19,6 +19,7 @@ import ( type schemaResourceData interface { sdkv2.ResourceDiffer Set(string, any) error + Identity() (*schema.IdentityData, error) } type interceptorOptions[D any] struct { diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 53a5cdf94be5..44dde34b523b 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -526,6 +526,16 @@ func initialize(ctx context.Context, provider *schema.Provider) (map[string]conn }) } + if len(v.Identity.Attributes) > 0 { + r.Identity = newResourceIdentity(v.Identity) + + if v.Import.WrappedImport { + r.Importer = newIdentityImporter(v.Identity) + } + + interceptors = append(interceptors, newIdentityInterceptor(v.Identity.Attributes)) + } + opts := wrappedResourceOptions{ // bootstrapContext is run on all wrapped methods before any interceptors. bootstrapContext: func(ctx context.Context, getAttribute getAttributeFunc, meta any) (context.Context, error) { diff --git a/internal/provider/tags_interceptor_test.go b/internal/provider/tags_interceptor_test.go index 2cf1408a1929..1e863d02c9bb 100644 --- a/internal/provider/tags_interceptor_test.go +++ b/internal/provider/tags_interceptor_test.go @@ -10,6 +10,7 @@ import ( "unique" "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" inttypes "github.com/hashicorp/terraform-provider-aws/internal/types" @@ -159,3 +160,7 @@ func (d *resourceData) HasChange(key string) bool { func (d *resourceData) HasChanges(keys ...string) bool { return false } + +func (d *resourceData) Identity() (*schema.IdentityData, error) { + return nil, nil +} diff --git a/internal/service/batch/job_definition.go b/internal/service/batch/job_definition.go index e408db2d53e7..fc44b1b6129c 100644 --- a/internal/service/batch/job_definition.go +++ b/internal/service/batch/job_definition.go @@ -12,6 +12,7 @@ import ( "github.com/YakDriver/regexache" "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/aws/arn" "github.com/aws/aws-sdk-go-v2/service/batch" awstypes "github.com/aws/aws-sdk-go-v2/service/batch/types" "github.com/hashicorp/go-cty/cty" @@ -33,7 +34,9 @@ import ( // @SDKResource("aws_batch_job_definition", name="Job Definition") // @Tags(identifierAttribute="arn") -// @Testing(existsType="github.com/aws/aws-sdk-go-v2/service/batch/types;types.JobDefinition", importIgnore="deregister_on_new_revision") +// @ArnIdentity +// @MutableIdentity +// @Testing(existsType="github.com/aws/aws-sdk-go-v2/service/batch/types;types.JobDefinition") func resourceJobDefinition() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceJobDefinitionCreate, @@ -42,7 +45,43 @@ func resourceJobDefinition() *schema.Resource { DeleteWithoutTimeout: resourceJobDefinitionDelete, Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, + StateContext: func(_ context.Context, rd *schema.ResourceData, _ any) ([]*schema.ResourceData, error) { + // Import-by-id case + if rd.Id() != "" { + rd.Set("deregister_on_new_revision", true) + + return []*schema.ResourceData{rd}, nil + } + + identity, err := rd.Identity() + if err != nil { + return nil, err + } + + arnRaw, ok := identity.GetOk(names.AttrARN) + if !ok { + return nil, fmt.Errorf("identity attribute %q is required", names.AttrARN) + } + + arnVal, ok := arnRaw.(string) + if !ok { + return nil, fmt.Errorf("identity attribute %q: expected string, got %T", names.AttrARN, arnRaw) + } + + arnARN, err := arn.Parse(arnVal) + if err != nil { + return nil, fmt.Errorf("identity attribute %q: could not parse %q as ARN: %s", names.AttrARN, arnVal, err) + } + + rd.Set(names.AttrRegion, arnARN.Region) + + rd.Set(names.AttrARN, arnVal) + rd.SetId(arnVal) + + rd.Set("deregister_on_new_revision", true) + + return []*schema.ResourceData{rd}, nil + }, }, Schema: map[string]*schema.Schema{ diff --git a/internal/service/batch/job_definition_tags_gen_test.go b/internal/service/batch/job_definition_tags_gen_test.go index 2dda1b437147..de0cc49d0c28 100644 --- a/internal/service/batch/job_definition_tags_gen_test.go +++ b/internal/service/batch/job_definition_tags_gen_test.go @@ -71,9 +71,6 @@ func TestAccBatchJobDefinition_tags(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, { ConfigDirectory: config.StaticDirectory("testdata/JobDefinition/tags/"), @@ -123,9 +120,6 @@ func TestAccBatchJobDefinition_tags(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, { ConfigDirectory: config.StaticDirectory("testdata/JobDefinition/tags/"), @@ -169,9 +163,6 @@ func TestAccBatchJobDefinition_tags(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, { ConfigDirectory: config.StaticDirectory("testdata/JobDefinition/tags/"), @@ -203,9 +194,6 @@ func TestAccBatchJobDefinition_tags(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, }, }) @@ -258,9 +246,6 @@ func TestAccBatchJobDefinition_tags_null(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, { ConfigDirectory: config.StaticDirectory("testdata/JobDefinition/tags/"), @@ -318,9 +303,6 @@ func TestAccBatchJobDefinition_tags_EmptyMap(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, { ConfigDirectory: config.StaticDirectory("testdata/JobDefinition/tags/"), @@ -411,9 +393,6 @@ func TestAccBatchJobDefinition_tags_AddOnUpdate(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, }, }) @@ -472,9 +451,6 @@ func TestAccBatchJobDefinition_tags_EmptyTag_OnCreate(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, { ConfigDirectory: config.StaticDirectory("testdata/JobDefinition/tags/"), @@ -506,9 +482,6 @@ func TestAccBatchJobDefinition_tags_EmptyTag_OnCreate(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, }, }) @@ -603,9 +576,6 @@ func TestAccBatchJobDefinition_tags_EmptyTag_OnUpdate_Add(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, { ConfigDirectory: config.StaticDirectory("testdata/JobDefinition/tags/"), @@ -649,9 +619,6 @@ func TestAccBatchJobDefinition_tags_EmptyTag_OnUpdate_Add(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, }, }) @@ -741,9 +708,6 @@ func TestAccBatchJobDefinition_tags_EmptyTag_OnUpdate_Replace(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, }, }) @@ -802,9 +766,6 @@ func TestAccBatchJobDefinition_tags_DefaultTags_providerOnly(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, { ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -852,9 +813,6 @@ func TestAccBatchJobDefinition_tags_DefaultTags_providerOnly(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, { ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -898,9 +856,6 @@ func TestAccBatchJobDefinition_tags_DefaultTags_providerOnly(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, { ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -934,9 +889,6 @@ func TestAccBatchJobDefinition_tags_DefaultTags_providerOnly(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, }, }) @@ -1005,9 +957,6 @@ func TestAccBatchJobDefinition_tags_DefaultTags_nonOverlapping(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, { ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -1067,9 +1016,6 @@ func TestAccBatchJobDefinition_tags_DefaultTags_nonOverlapping(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, { ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -1103,9 +1049,6 @@ func TestAccBatchJobDefinition_tags_DefaultTags_nonOverlapping(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, }, }) @@ -1172,9 +1115,6 @@ func TestAccBatchJobDefinition_tags_DefaultTags_overlapping(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, { ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -1234,9 +1174,6 @@ func TestAccBatchJobDefinition_tags_DefaultTags_overlapping(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, { ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -1288,9 +1225,6 @@ func TestAccBatchJobDefinition_tags_DefaultTags_overlapping(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, }, }) @@ -1381,9 +1315,6 @@ func TestAccBatchJobDefinition_tags_DefaultTags_updateToProviderOnly(t *testing. ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, }, }) @@ -1473,9 +1404,6 @@ func TestAccBatchJobDefinition_tags_DefaultTags_updateToResourceOnly(t *testing. ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, }, }) @@ -1541,9 +1469,6 @@ func TestAccBatchJobDefinition_tags_DefaultTags_emptyResourceTag(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, }, }) @@ -1601,9 +1526,6 @@ func TestAccBatchJobDefinition_tags_DefaultTags_emptyProviderOnlyTag(t *testing. ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, }, }) @@ -1666,9 +1588,6 @@ func TestAccBatchJobDefinition_tags_DefaultTags_nullOverlappingResourceTag(t *te ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, }, }) @@ -1731,9 +1650,6 @@ func TestAccBatchJobDefinition_tags_DefaultTags_nullNonOverlappingResourceTag(t ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, }, }) @@ -1789,9 +1705,6 @@ func TestAccBatchJobDefinition_tags_ComputedTag_OnCreate(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, }, }) @@ -1889,9 +1802,6 @@ func TestAccBatchJobDefinition_tags_ComputedTag_OnUpdate_Add(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, }, }) @@ -1979,9 +1889,6 @@ func TestAccBatchJobDefinition_tags_ComputedTag_OnUpdate_Replace(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, }, }) diff --git a/internal/service/batch/job_definition_test.go b/internal/service/batch/job_definition_test.go index 6e04bd9ece5e..e3f38a2c998c 100644 --- a/internal/service/batch/job_definition_test.go +++ b/internal/service/batch/job_definition_test.go @@ -14,6 +14,7 @@ import ( "github.com/YakDriver/regexache" "github.com/aws/aws-sdk-go-v2/aws" awstypes "github.com/aws/aws-sdk-go-v2/service/batch/types" + "github.com/hashicorp/terraform-plugin-testing/compare" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/knownvalue" @@ -21,6 +22,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/statecheck" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfbatch "github.com/hashicorp/terraform-provider-aws/internal/service/batch" @@ -44,8 +46,8 @@ func TestAccBatchJobDefinition_basic(t *testing.T) { Config: testAccJobDefinitionConfig_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckJobDefinitionExists(ctx, resourceName, &jd), - acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "batch", regexache.MustCompile(fmt.Sprintf(`job-definition/%s:\d+`, rName))), - acctest.MatchResourceAttrRegionalARN(ctx, resourceName, "arn_prefix", "batch", regexache.MustCompile(fmt.Sprintf(`job-definition/%s`, rName))), + acctest.CheckResourceAttrRegionalARNFormat(ctx, resourceName, names.AttrARN, "batch", "job-definition/{name}:{revision}"), + acctest.CheckResourceAttrRegionalARNFormat(ctx, resourceName, "arn_prefix", "batch", "job-definition/{name}"), acctest.CheckResourceAttrEquivalentJSON(resourceName, "container_properties", `{ "command": ["echo", "test"], "image": "busybox", @@ -76,10 +78,206 @@ func TestAccBatchJobDefinition_basic(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", + }, + }, + }) +} + +func TestAccBatchJobDefinition_Identity_Basic(t *testing.T) { + ctx := acctest.Context(t) + var jd awstypes.JobDefinition + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_batch_job_definition.test" + + resource.ParallelTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.BatchServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckJobDefinitionDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccJobDefinitionConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckJobDefinitionExists(ctx, resourceName, &jd), + ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValuePairs(resourceName, tfjsonpath.New(names.AttrID), resourceName, tfjsonpath.New(names.AttrARN), compare.ValuesSame()), + // tfstatecheck.ExpectIdentityRegionalARNFormat(ctx, resourceName, "batch", "job-definition/{name}:{revision}"), + }, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportCommandWithID, + ImportStateVerify: true, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithID, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.Region())), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("deregister_on_new_revision"), knownvalue.Bool(true)), + }, + }, + }, + // { + // ResourceName: resourceName, + // ImportState: true, + // ImportStateKind: resource.ImportBlockWithResourceIdentity, + // ImportPlanChecks: resource.ImportPlanChecks{ + // PreApply: []plancheck.PlanCheck{ + // plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("arn"), knownvalue.NotNull()), + // plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("id"), knownvalue.NotNull()), + // plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("region"), knownvalue.StringExact(acctest.Region())), + // plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("deregister_on_new_revision"), knownvalue.Bool(true)), + // }, + // }, + // }, + }, + }) +} + +// func TestAccBatchJobDefinition_Identity_Update(t *testing.T) { +// ctx := acctest.Context(t) +// var jd awstypes.JobDefinition +// rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) +// resourceName := "aws_batch_job_definition.test" + +// resource.ParallelTest(t, resource.TestCase{ +// TerraformVersionChecks: []tfversion.TerraformVersionCheck{ +// tfversion.SkipBelow(tfversion.Version1_12_0), +// }, +// PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, +// ErrorCheck: acctest.ErrorCheck(t, names.BatchServiceID), +// ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, +// CheckDestroy: testAccCheckJobDefinitionDestroy(ctx), +// Steps: []resource.TestStep{ +// { +// Config: testAccJobDefinitionConfig_containerProperties(rName, "-one"), +// Check: resource.ComposeAggregateTestCheckFunc( +// testAccCheckJobDefinitionExists(ctx, resourceName, &jd), +// ), +// ConfigStateChecks: []statecheck.StateCheck{ +// statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("revision"), knownvalue.Int64Exact(1)), +// tfstatecheck.ExpectIdentityRegionalARNFormat(ctx, resourceName, "batch", "job-definition/{name}:{revision}"), +// }, +// }, +// { +// Config: testAccJobDefinitionConfig_containerProperties(rName, "-two"), +// Check: resource.ComposeAggregateTestCheckFunc( +// testAccCheckJobDefinitionExists(ctx, resourceName, &jd), +// ), +// ConfigStateChecks: []statecheck.StateCheck{ +// statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("revision"), knownvalue.Int64Exact(2)), +// tfstatecheck.ExpectIdentityRegionalARNFormat(ctx, resourceName, "batch", "job-definition/{name}:{revision}"), +// }, +// }, +// { +// ResourceName: resourceName, +// ImportState: true, +// ImportStateKind: resource.ImportCommandWithID, +// ImportStateVerify: true, +// ImportPlanChecks: resource.ImportPlanChecks{ +// PreApply: []plancheck.PlanCheck{ +// plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("arn"), knownvalue.NotNull()), +// plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("id"), knownvalue.NotNull()), +// plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("region"), knownvalue.StringExact(acctest.Region())), +// plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("deregister_on_new_revision"), knownvalue.Bool(true)), +// }, +// }, +// }, +// { +// ResourceName: resourceName, +// ImportState: true, +// ImportStateKind: resource.ImportBlockWithID, +// ImportPlanChecks: resource.ImportPlanChecks{ +// PreApply: []plancheck.PlanCheck{ +// plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("arn"), knownvalue.NotNull()), +// plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("id"), knownvalue.NotNull()), +// plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("region"), knownvalue.StringExact(acctest.Region())), +// plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("deregister_on_new_revision"), knownvalue.Bool(true)), +// }, +// }, +// }, +// { +// ResourceName: resourceName, +// ImportState: true, +// ImportStateKind: resource.ImportBlockWithResourceIdentity, +// ImportPlanChecks: resource.ImportPlanChecks{ +// PreApply: []plancheck.PlanCheck{ +// plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("arn"), knownvalue.NotNull()), +// plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("id"), knownvalue.NotNull()), +// plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("region"), knownvalue.StringExact(acctest.Region())), +// plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("deregister_on_new_revision"), knownvalue.Bool(true)), +// }, +// }, +// }, +// }, +// }) +// } + +func TestAccBatchJobDefinition_Identity_RegionOverride(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_batch_job_definition.test" + + resource.ParallelTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.BatchServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckJobDefinitionDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccJobDefinitionConfig_regionOverride(rName), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValuePairs(resourceName, tfjsonpath.New(names.AttrID), resourceName, tfjsonpath.New(names.AttrARN), compare.ValuesSame()), + // tfstatecheck.ExpectIdentityRegionalARNAlternateRegionFormat(ctx, resourceName, "batch", "job-definition/{name}:{revision}"), + }, + }, + { + ResourceName: resourceName, + ImportStateIdFunc: acctest.CrossRegionAttrImportStateIdFunc(resourceName, names.AttrARN), + ImportState: true, + ImportStateKind: resource.ImportCommandWithID, + ImportStateVerify: true, + }, + { + ResourceName: resourceName, + ImportStateIdFunc: acctest.CrossRegionAttrImportStateIdFunc(resourceName, names.AttrARN), + ImportState: true, + ImportStateKind: resource.ImportBlockWithID, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.AlternateRegion())), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("deregister_on_new_revision"), knownvalue.Bool(true)), + }, }, }, + // { + // ResourceName: resourceName, + // ImportState: true, + // ImportStateKind: resource.ImportBlockWithResourceIdentity, + // ImportPlanChecks: resource.ImportPlanChecks{ + // PreApply: []plancheck.PlanCheck{ + // plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("arn"), knownvalue.NotNull()), + // plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("id"), knownvalue.NotNull()), + // plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("region"), knownvalue.StringExact(acctest.AlternateRegion())), + // plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("deregister_on_new_revision"), knownvalue.Bool(true)), + // }, + // }, + // }, }, }) } @@ -99,8 +297,8 @@ func TestAccBatchJobDefinition_attributes(t *testing.T) { Config: testAccJobDefinitionConfig_attributes(rName, 2, true, 3, 120, false), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckJobDefinitionExists(ctx, resourceName, &jd), - acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "batch", regexache.MustCompile(fmt.Sprintf(`job-definition/%s:1`, rName))), - acctest.MatchResourceAttrRegionalARN(ctx, resourceName, "arn_prefix", "batch", regexache.MustCompile(fmt.Sprintf(`job-definition/%s`, rName))), + acctest.CheckResourceAttrRegionalARNFormat(ctx, resourceName, names.AttrARN, "batch", "job-definition/{name}:{revision}"), + acctest.CheckResourceAttrRegionalARNFormat(ctx, resourceName, "arn_prefix", "batch", "job-definition/{name}"), resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), resource.TestCheckResourceAttr(resourceName, "parameters.%", "0"), resource.TestCheckResourceAttr(resourceName, "platform_capabilities.#", "1"), @@ -120,7 +318,7 @@ func TestAccBatchJobDefinition_attributes(t *testing.T) { Config: testAccJobDefinitionConfig_attributes(rName, 2, true, 4, 120, false), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckJobDefinitionExists(ctx, resourceName, &jd), - acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "batch", regexache.MustCompile(fmt.Sprintf(`job-definition/%s:2`, rName))), + acctest.CheckResourceAttrRegionalARNFormat(ctx, resourceName, names.AttrARN, "batch", "job-definition/{name}:{revision}"), testAccCheckJobDefinitionPreviousRegistered(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "revision", "2"), ), @@ -130,8 +328,8 @@ func TestAccBatchJobDefinition_attributes(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( testAccCheckJobDefinitionExists(ctx, resourceName, &jd), testAccCheckJobDefinitionPreviousDeregistered(ctx, resourceName), - acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "batch", regexache.MustCompile(fmt.Sprintf(`job-definition/%s:3`, rName))), - acctest.MatchResourceAttrRegionalARN(ctx, resourceName, "arn_prefix", "batch", regexache.MustCompile(fmt.Sprintf(`job-definition/%s`, rName))), + acctest.CheckResourceAttrRegionalARNFormat(ctx, resourceName, names.AttrARN, "batch", "job-definition/{name}:{revision}"), + acctest.CheckResourceAttrRegionalARNFormat(ctx, resourceName, "arn_prefix", "batch", "job-definition/{name}"), resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), resource.TestCheckResourceAttr(resourceName, "parameters.%", "0"), resource.TestCheckResourceAttr(resourceName, "platform_capabilities.#", "0"), @@ -146,17 +344,14 @@ func TestAccBatchJobDefinition_attributes(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, { Config: testAccJobDefinitionConfig_attributes(rName, 1, false, 1, 60, true), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckJobDefinitionExists(ctx, resourceName, &jd), testAccCheckJobDefinitionPreviousDeregistered(ctx, resourceName), - acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "batch", regexache.MustCompile(fmt.Sprintf(`job-definition/%s:\d+`, rName))), - acctest.MatchResourceAttrRegionalARN(ctx, resourceName, "arn_prefix", "batch", regexache.MustCompile(fmt.Sprintf(`job-definition/%s`, rName))), + acctest.CheckResourceAttrRegionalARNFormat(ctx, resourceName, names.AttrARN, "batch", "job-definition/{name}:{revision}"), + acctest.CheckResourceAttrRegionalARNFormat(ctx, resourceName, "arn_prefix", "batch", "job-definition/{name}"), resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), resource.TestCheckResourceAttr(resourceName, "parameters.%", "0"), resource.TestCheckResourceAttr(resourceName, "platform_capabilities.#", "1"), @@ -216,7 +411,7 @@ func TestAccBatchJobDefinition_PlatformCapabilities_ec2(t *testing.T) { Config: testAccJobDefinitionConfig_capabilitiesEC2(rName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckJobDefinitionExists(ctx, resourceName, &jd), - acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "batch", regexache.MustCompile(fmt.Sprintf(`job-definition/%s:\d+`, rName))), + acctest.CheckResourceAttrRegionalARNFormat(ctx, resourceName, names.AttrARN, "batch", "job-definition/{name}:{revision}"), acctest.CheckResourceAttrEquivalentJSON(resourceName, "container_properties", `{ "command": ["echo", "test"], "image": "busybox", @@ -245,9 +440,6 @@ func TestAccBatchJobDefinition_PlatformCapabilities_ec2(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, }, }) @@ -269,7 +461,7 @@ func TestAccBatchJobDefinition_PlatformCapabilitiesFargate_containerPropertiesDe Config: testAccJobDefinitionConfig_capabilitiesFargateContainerPropertiesDefaults(rName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckJobDefinitionExists(ctx, resourceName, &jd), - acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "batch", regexache.MustCompile(fmt.Sprintf(`job-definition/%s:\d+`, rName))), + acctest.CheckResourceAttrRegionalARNFormat(ctx, resourceName, names.AttrARN, "batch", "job-definition/{name}:{revision}"), acctest.CheckResourceAttrJMES(resourceName, "container_properties", "length(command)", "0"), acctest.CheckResourceAttrJMESPair(resourceName, "container_properties", "executionRoleArn", "aws_iam_role.ecs_task_execution_role", names.AttrARN), acctest.CheckResourceAttrJMES(resourceName, "container_properties", "fargatePlatformConfiguration.platformVersion", "LATEST"), @@ -292,9 +484,6 @@ func TestAccBatchJobDefinition_PlatformCapabilitiesFargate_containerPropertiesDe ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, }, }) @@ -316,7 +505,7 @@ func TestAccBatchJobDefinition_PlatformCapabilities_fargate(t *testing.T) { Config: testAccJobDefinitionConfig_capabilitiesFargate(rName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckJobDefinitionExists(ctx, resourceName, &jd), - acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "batch", regexache.MustCompile(fmt.Sprintf(`job-definition/%s:\d+`, rName))), + acctest.CheckResourceAttrRegionalARNFormat(ctx, resourceName, names.AttrARN, "batch", "job-definition/{name}:{revision}"), acctest.CheckResourceAttrJMESPair(resourceName, "container_properties", "executionRoleArn", "aws_iam_role.ecs_task_execution_role", names.AttrARN), acctest.CheckResourceAttrJMES(resourceName, "container_properties", "fargatePlatformConfiguration.platformVersion", "LATEST"), acctest.CheckResourceAttrJMES(resourceName, "container_properties", "networkConfiguration.assignPublicIp", "DISABLED"), @@ -339,9 +528,6 @@ func TestAccBatchJobDefinition_PlatformCapabilities_fargate(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, }, }) @@ -409,9 +595,6 @@ func TestAccBatchJobDefinition_ContainerProperties_advanced(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, { Config: testAccJobDefinitionConfig_containerPropertiesAdvanced(rName, "val3", 1, 60), @@ -476,15 +659,14 @@ func TestAccBatchJobDefinition_ContainerProperties_minorUpdate(t *testing.T) { Config: testAccJobDefinitionConfig_containerProperties(rName, "-la"), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckJobDefinitionExists(ctx, resourceName, &jd), - acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "batch", regexache.MustCompile(fmt.Sprintf(`job-definition/%s:1`, rName))), + acctest.CheckResourceAttrRegionalARNFormat(ctx, resourceName, names.AttrARN, "batch", "job-definition/{name}:{revision}"), resource.TestCheckResourceAttr(resourceName, "revision", "1"), ), }, { Config: testAccJobDefinitionConfig_containerProperties(rName, "-lah"), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckJobDefinitionExists(ctx, resourceName, &jd), - acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "batch", regexache.MustCompile(fmt.Sprintf(`job-definition/%s:2`, rName))), - testAccCheckJobDefinitionPreviousDeregistered(ctx, resourceName), + acctest.CheckResourceAttrRegionalARNFormat(ctx, resourceName, names.AttrARN, "batch", "job-definition/{name}:{revision}"), testAccCheckJobDefinitionPreviousDeregistered(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "revision", "2"), ), }, @@ -492,8 +674,7 @@ func TestAccBatchJobDefinition_ContainerProperties_minorUpdate(t *testing.T) { Config: testAccJobDefinitionConfig_containerProperties(rName, "-hal"), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckJobDefinitionExists(ctx, resourceName, &jd), - acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "batch", regexache.MustCompile(fmt.Sprintf(`job-definition/%s:3`, rName))), - testAccCheckJobDefinitionPreviousDeregistered(ctx, resourceName), + acctest.CheckResourceAttrRegionalARNFormat(ctx, resourceName, names.AttrARN, "batch", "job-definition/{name}:{revision}"), testAccCheckJobDefinitionPreviousDeregistered(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "revision", "3"), ), }, @@ -517,7 +698,7 @@ func TestAccBatchJobDefinition_propagateTags(t *testing.T) { Config: testAccJobDefinitionConfig_propagateTags(rName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckJobDefinitionExists(ctx, resourceName, &jd), - acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "batch", regexache.MustCompile(fmt.Sprintf(`job-definition/%s:\d+`, rName))), + acctest.CheckResourceAttrRegionalARNFormat(ctx, resourceName, names.AttrARN, "batch", "job-definition/{name}:{revision}"), acctest.CheckResourceAttrEquivalentJSON(resourceName, "container_properties", `{ "command": ["echo", "test"], "image": "busybox", @@ -572,9 +753,6 @@ func TestAccBatchJobDefinition_ContainerProperties_EmptyField(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, }, }) @@ -596,7 +774,7 @@ func TestAccBatchJobDefinition_NodeProperties_basic(t *testing.T) { Config: testAccJobDefinitionConfig_nodeProperties(rName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckJobDefinitionExists(ctx, resourceName, &jd), - acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "batch", regexache.MustCompile(fmt.Sprintf(`job-definition/%s:\d+`, rName))), + acctest.CheckResourceAttrRegionalARNFormat(ctx, resourceName, names.AttrARN, "batch", "job-definition/{name}:{revision}"), acctest.CheckResourceAttrEquivalentJSON(resourceName, "node_properties", `{ "mainNode": 0, "nodeRangeProperties": [ @@ -651,9 +829,6 @@ func TestAccBatchJobDefinition_NodeProperties_basic(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, }, }) @@ -675,7 +850,7 @@ func TestAccBatchJobDefinition_NodeProperties_advanced(t *testing.T) { Config: testAccJobDefinitionConfig_nodePropertiesAdvanced(rName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckJobDefinitionExists(ctx, resourceName, &jd), - acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "batch", regexache.MustCompile(fmt.Sprintf(`job-definition/%s:\d+`, rName))), + acctest.CheckResourceAttrRegionalARNFormat(ctx, resourceName, names.AttrARN, "batch", "job-definition/{name}:{revision}"), acctest.CheckResourceAttrEquivalentJSON(resourceName, "node_properties", `{ "mainNode": 1, "nodeRangeProperties": [ @@ -720,15 +895,12 @@ func TestAccBatchJobDefinition_NodeProperties_advanced(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, { Config: testAccJobDefinitionConfig_nodePropertiesAdvancedUpdate(rName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckJobDefinitionExists(ctx, resourceName, &jd), - acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "batch", regexache.MustCompile(fmt.Sprintf(`job-definition/%s:\d+`, rName))), + acctest.CheckResourceAttrRegionalARNFormat(ctx, resourceName, names.AttrARN, "batch", "job-definition/{name}:{revision}"), acctest.CheckResourceAttrEquivalentJSON(resourceName, "node_properties", `{ "mainNode": 1, "nodeRangeProperties": [ @@ -791,7 +963,7 @@ func TestAccBatchJobDefinition_NodeProperties_withEKS(t *testing.T) { Config: testAccJobDefinitionConfig_nodePropertiesEKS(rName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckJobDefinitionExists(ctx, resourceName, &jd), - acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "batch", regexache.MustCompile(fmt.Sprintf(`job-definition/%s:\d+`, rName))), + acctest.CheckResourceAttrRegionalARNFormat(ctx, resourceName, names.AttrARN, "batch", "job-definition/{name}:{revision}"), acctest.CheckResourceAttrEquivalentJSON(resourceName, "node_properties", `{ "mainNode": 0, "nodeRangeProperties": [ @@ -858,16 +1030,13 @@ func TestAccBatchJobDefinition_NodeProperties_withECS(t *testing.T) { Config: testAccJobDefinitionConfig_nodePropertiesECS(rName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckJobDefinitionExists(ctx, resourceName, &jd), - acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "batch", regexache.MustCompile(fmt.Sprintf(`job-definition/%s:\d+`, rName))), + acctest.CheckResourceAttrRegionalARNFormat(ctx, resourceName, names.AttrARN, "batch", "job-definition/{name}:{revision}"), ), }, { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, }, }) @@ -900,9 +1069,6 @@ func TestAccBatchJobDefinition_EKSProperties_basic(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, }, }) @@ -941,9 +1107,6 @@ func TestAccBatchJobDefinition_EKSProperties_update(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, }, }) @@ -982,9 +1145,6 @@ func TestAccBatchJobDefinition_EKSProperties_imagePullSecrets(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, }, }) @@ -1013,9 +1173,6 @@ func TestAccBatchJobDefinition_EKSProperties_multiContainers(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, }, }) @@ -1080,9 +1237,6 @@ func TestAccBatchJobDefinition_schedulingPriority(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "deregister_on_new_revision", - }, }, }, }) @@ -1104,7 +1258,7 @@ func TestAccBatchJobDefinition_emptyRetryStrategy(t *testing.T) { Config: testAccJobDefinitionConfig_emptyRetryStrategy(rName), Check: resource.ComposeTestCheckFunc( testAccCheckJobDefinitionExists(ctx, resourceName, &jd), - acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "batch", regexache.MustCompile(fmt.Sprintf(`job-definition/%s:\d+`, rName))), + acctest.CheckResourceAttrRegionalARNFormat(ctx, resourceName, names.AttrARN, "batch", "job-definition/{name}:{revision}"), ), ExpectNonEmptyPlan: true, }, @@ -1363,6 +1517,23 @@ resource "aws_batch_job_definition" "test" { `, rName) } +func testAccJobDefinitionConfig_regionOverride(rName string) string { + return fmt.Sprintf(` +resource "aws_batch_job_definition" "test" { + region = %[2]q + + container_properties = jsonencode({ + command = ["echo", "test"] + image = "busybox" + memory = 128 + vcpus = 1 + }) + name = %[1]q + type = "container" +} +`, rName, acctest.AlternateRegion()) +} + func testAccJobDefinitionConfig_containerPropertiesAdvanced(rName, param string, retries, timeout int) string { return fmt.Sprintf(` resource "aws_batch_job_definition" "test" { diff --git a/internal/service/batch/job_queue.go b/internal/service/batch/job_queue.go index 4a9227f58bc8..8cb00b57b8a4 100644 --- a/internal/service/batch/job_queue.go +++ b/internal/service/batch/job_queue.go @@ -11,6 +11,7 @@ import ( "github.com/YakDriver/regexache" "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/aws/arn" "github.com/aws/aws-sdk-go-v2/service/batch" awstypes "github.com/aws/aws-sdk-go-v2/service/batch/types" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" @@ -37,6 +38,7 @@ import ( // @FrameworkResource("aws_batch_job_queue", name="Job Queue") // @Tags(identifierAttribute="arn") +// @ArnIdentity // @Testing(existsType="github.com/aws/aws-sdk-go-v2/service/batch/types;types.JobQueueDetail") func newJobQueueResource(_ context.Context) (resource.ResourceWithConfigure, error) { r := jobQueueResource{} @@ -50,7 +52,6 @@ func newJobQueueResource(_ context.Context) (resource.ResourceWithConfigure, err type jobQueueResource struct { framework.ResourceWithModel[jobQueueResourceModel] - framework.WithImportByID framework.WithTimeouts } @@ -59,7 +60,7 @@ func (r *jobQueueResource) Schema(ctx context.Context, request resource.SchemaRe Version: 2, Attributes: map[string]schema.Attribute{ names.AttrARN: framework.ARNAttributeComputedOnly(), - names.AttrID: framework.IDAttribute(), + names.AttrID: framework.IDAttributeDeprecatedWithAlternate(path.Root(names.AttrARN)), names.AttrName: schema.StringAttribute{ Required: true, PlanModifiers: []planmodifier.String{ @@ -173,7 +174,6 @@ func (r *jobQueueResource) Create(ctx context.Context, request resource.CreateRe return } - // Set values for unknowns. data.JobQueueARN = fwflex.StringToFramework(ctx, output.JobQueueArn) data.setID() @@ -194,12 +194,6 @@ func (r *jobQueueResource) Read(ctx context.Context, request resource.ReadReques return } - if err := data.InitFromID(); err != nil { - response.Diagnostics.AddError("parsing resource ID", err.Error()) - - return - } - conn := r.Meta().BatchClient(ctx) jobQueue, err := findJobQueueByID(ctx, conn, data.ID.ValueString()) @@ -513,12 +507,6 @@ type jobQueueResourceModel struct { Timeouts timeouts.Value `tfsdk:"timeouts"` } -func (model *jobQueueResourceModel) InitFromID() error { - model.JobQueueARN = model.ID - - return nil -} - func (model *jobQueueResourceModel) setID() { model.ID = model.JobQueueARN } @@ -534,3 +522,32 @@ type jobStateTimeLimitActionModel struct { Reason types.String `tfsdk:"reason"` State fwtypes.StringEnum[awstypes.JobStateTimeLimitActionsState] `tfsdk:"state"` } + +func (r *jobQueueResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { + // Import-by-id case + if request.ID != "" { + resource.ImportStatePassthroughID(ctx, path.Root(names.AttrARN), request, response) + response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root(names.AttrID), request.ID)...) + return + } + + if identity := request.Identity; identity != nil { + arnPath := path.Root(names.AttrARN) + var arnVal string + identity.GetAttribute(ctx, arnPath, &arnVal) + + arnARN, err := arn.Parse(arnVal) + if err != nil { + response.Diagnostics.AddAttributeError( + arnPath, + "Invalid Import Attribute Value", + fmt.Sprintf("Attribute %q is not a valid ARN, got: %s", arnPath, arnVal), + ) + return + } + response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root(names.AttrRegion), arnARN.Region)...) + + response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root(names.AttrARN), arnVal)...) + response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root(names.AttrID), arnVal)...) + } +} diff --git a/internal/service/batch/job_queue_test.go b/internal/service/batch/job_queue_test.go index ec0e2ab2e738..160ad79dddb2 100644 --- a/internal/service/batch/job_queue_test.go +++ b/internal/service/batch/job_queue_test.go @@ -11,11 +11,17 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/batch" awstypes "github.com/aws/aws-sdk-go-v2/service/batch/types" + "github.com/hashicorp/terraform-plugin-testing/compare" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/statecheck" "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" "github.com/hashicorp/terraform-provider-aws/internal/acctest" + tfstatecheck "github.com/hashicorp/terraform-provider-aws/internal/acctest/statecheck" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfbatch "github.com/hashicorp/terraform-provider-aws/internal/service/batch" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" @@ -38,7 +44,7 @@ func TestAccBatchJobQueue_basic(t *testing.T) { Config: testAccJobQueueConfig_state(rName, string(awstypes.JQStateEnabled)), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckJobQueueExists(ctx, resourceName, &jobQueue1), - acctest.CheckResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "batch", fmt.Sprintf("job-queue/%s", rName)), + acctest.CheckResourceAttrRegionalARNFormat(ctx, resourceName, names.AttrARN, "batch", "job-queue/{name}"), resource.TestCheckResourceAttr(resourceName, "compute_environment_order.#", "1"), resource.TestCheckResourceAttrPair(resourceName, "compute_environment_order.0.compute_environment", "aws_batch_compute_environment.test", names.AttrARN), resource.TestCheckResourceAttr(resourceName, "job_state_time_limit_action.#", "0"), @@ -49,9 +55,125 @@ func TestAccBatchJobQueue_basic(t *testing.T) { ), }, { - ResourceName: resourceName, - RefreshState: true, - PlanOnly: true, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccBatchJobQueue_Identity_Basic(t *testing.T) { + ctx := acctest.Context(t) + var jobQueue1 awstypes.JobQueueDetail + resourceName := "aws_batch_job_queue.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.BatchServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckJobQueueDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccJobQueueConfig_state(rName, string(awstypes.JQStateEnabled)), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckJobQueueExists(ctx, resourceName, &jobQueue1), + ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValuePairs(resourceName, tfjsonpath.New(names.AttrID), resourceName, tfjsonpath.New(names.AttrARN), compare.ValuesSame()), + tfstatecheck.ExpectIdentityRegionalARNFormat(resourceName, "batch", "job-queue/{name}"), + }, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportCommandWithID, + ImportStateVerify: true, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithID, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("region"), knownvalue.StringExact(acctest.Region())), + }, + }, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithResourceIdentity, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("region"), knownvalue.StringExact(acctest.Region())), + }, + }, + }, + }, + }) +} + +func TestAccBatchJobQueue_Identity_RegionOverride(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_batch_job_queue.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.BatchServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckJobQueueDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccJobQueueConfig_regionOverride(rName), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValuePairs(resourceName, tfjsonpath.New(names.AttrID), resourceName, tfjsonpath.New(names.AttrARN), compare.ValuesSame()), + tfstatecheck.ExpectIdentityRegionalARNAlternateRegionFormat(resourceName, "batch", "job-queue/{name}"), + }, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: acctest.CrossRegionAttrImportStateIdFunc(resourceName, names.AttrARN), + ImportStateKind: resource.ImportCommandWithID, + ImportStateVerify: true, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: acctest.CrossRegionAttrImportStateIdFunc(resourceName, names.AttrARN), + ImportStateKind: resource.ImportBlockWithID, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("region"), knownvalue.StringExact(acctest.AlternateRegion())), + }, + }, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithResourceIdentity, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("region"), knownvalue.StringExact(acctest.AlternateRegion())), + }, + }, }, }, }) @@ -647,6 +769,25 @@ resource "aws_batch_job_queue" "test" { `, rName, state)) } +func testAccJobQueueConfig_regionOverride(rName string) string { + return acctest.ConfigCompose( + testAccJobQueueConfig_base_regionOverride(rName, acctest.AlternateRegion()), + fmt.Sprintf(` +resource "aws_batch_job_queue" "test" { + region = %[2]q + + compute_environment_order { + compute_environment = aws_batch_compute_environment.test.arn + order = 1 + } + + name = %[1]q + priority = 1 + state = "ENABLED" +} +`, rName, acctest.AlternateRegion())) +} + func testAccJobQueueConfig_ComputeEnvironments_multiple(rName string, state string) string { return acctest.ConfigCompose( testAccJobQueueConfig_base(rName), @@ -969,3 +1110,72 @@ resource "aws_batch_job_queue" "test" { } `, rName)) } + +func testAccJobQueueConfig_base_regionOverride(rName, region string) string { + return fmt.Sprintf(` +resource "aws_batch_compute_environment" "test" { + region = %[2]q + + name = %[1]q + service_role = aws_iam_role.batch_service.arn + type = "UNMANAGED" + + depends_on = [aws_iam_role_policy_attachment.batch_service] +} + +data "aws_partition" "current" {} + +resource "aws_iam_role" "batch_service" { + name = "%[1]s-batch-service" + + assume_role_policy = <