Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Proposal to modify provider meta to enable specifying the region at each resource #31517

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5f90b66
Initial Proof Of Concept: Allowing region to be defined at the resource
brittandeyoung May 19, 2023
c19ae66
provider: Amend intercept, update Tagging intercept to support region…
brittandeyoung May 21, 2023
15e81ac
provider: Amend defaultTags type assertion
brittandeyoung May 21, 2023
cc25797
provider: Amend tags_interceptor_test, Add GetOk method to support re…
brittandeyoung May 21, 2023
b42430e
lightsail: Amend find, remove id split on FindBucketById
brittandeyoung May 21, 2023
89ce86b
lightsail: Amend bucket, add support for region at resource
brittandeyoung May 21, 2023
ec84c96
ec2: Amend ec2_availability_zones_data_source, add support for new pr…
brittandeyoung May 21, 2023
4065091
lightsail: Amend disk, add support for new provider meta.
brittandeyoung May 21, 2023
6f244f4
lightsail: Amend disk_test, add support for new provider meta.
brittandeyoung May 21, 2023
acbdae2
verify: Modify diff, add RegionDiffSuppress function
brittandeyoung May 21, 2023
a65f635
lightsail: Amend bucket, add RegionDiffSuppress function for region
brittandeyoung May 21, 2023
db53397
provider: Amend provider, remove comments, add allowed_regions descri…
brittandeyoung May 23, 2023
847264e
provider: Amend tags_interceptor, update type assertion meta
brittandeyoung May 23, 2023
6790522
provider: Amend tags_interceptor_test, update type assertion meta
brittandeyoung May 23, 2023
96b95a5
fwprovider: Amend provider, Add description to match provider config
brittandeyoung May 23, 2023
4224da0
lightsail: Amend bucket_test, testacc-lint-fix
brittandeyoung May 23, 2023
db24eef
create: Amend errors, add ErrActionExpandingResourceRegion
brittandeyoung May 24, 2023
bc74fb1
flex: Amend flex, add ExpandResourceRegion to allow for central setti…
brittandeyoung May 24, 2023
339ac19
flex: Amend, flex_test, add ExpandResourceRegion tests
brittandeyoung May 24, 2023
46f45c2
lightsail: Amend bucket, Update region to use central function
brittandeyoung May 24, 2023
8b4cad3
lightsail: Amend bucket_test, remove unneeded new line
brittandeyoung May 24, 2023
e283980
lightsail: Amend disk, move to new ExpandResourceRegion function
brittandeyoung May 24, 2023
14dc3c7
flex: Amend flex, update ExpandResourceRegion to also check for empty…
brittandeyoung May 24, 2023
0dcf3fe
flex: Amend flex_test, fix imports
brittandeyoung May 24, 2023
bdc7c3e
flex: Amend flex_test, resolve golanglint-ci and add test names
brittandeyoung May 24, 2023
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
36 changes: 18 additions & 18 deletions internal/acctest/acctest.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ func ProviderAccountID(provo *schema.Provider) string {
log.Print("[DEBUG] Unable to read account ID from test provider: unconfigured provider")
return ""
}
client, ok := provo.Meta().(*conns.AWSClient)
client, ok := provo.Meta().(*conns.ProviderMeta).AWSClients[Provider.Meta().(*conns.ProviderMeta).Region]
if !ok {
log.Print("[DEBUG] Unable to read account ID from test provider: non-AWS or unconfigured AWS provider")
return ""
Expand Down Expand Up @@ -884,7 +884,7 @@ func PreCheckPartitionNot(t *testing.T, partitions ...string) {
}

func PreCheckOrganizationsAccount(ctx context.Context, t *testing.T) {
_, err := tforganizations.FindOrganization(ctx, Provider.Meta().(*conns.AWSClient).OrganizationsConn())
_, err := tforganizations.FindOrganization(ctx, Provider.Meta().(*conns.ProviderMeta).AWSClients[Provider.Meta().(*conns.ProviderMeta).Region].OrganizationsConn())

if tfresource.NotFound(err) {
return
Expand All @@ -898,7 +898,7 @@ func PreCheckOrganizationsAccount(ctx context.Context, t *testing.T) {
}

func PreCheckOrganizationsEnabled(ctx context.Context, t *testing.T) {
_, err := tforganizations.FindOrganization(ctx, Provider.Meta().(*conns.AWSClient).OrganizationsConn())
_, err := tforganizations.FindOrganization(ctx, Provider.Meta().(*conns.ProviderMeta).AWSClients[Provider.Meta().(*conns.ProviderMeta).Region].OrganizationsConn())

if tfresource.NotFound(err) {
t.Skip("this AWS account must be an existing member of an AWS Organization")
Expand All @@ -910,13 +910,13 @@ func PreCheckOrganizationsEnabled(ctx context.Context, t *testing.T) {
}

func PreCheckOrganizationManagementAccount(ctx context.Context, t *testing.T) {
organization, err := tforganizations.FindOrganization(ctx, Provider.Meta().(*conns.AWSClient).OrganizationsConn())
organization, err := tforganizations.FindOrganization(ctx, Provider.Meta().(*conns.ProviderMeta).AWSClients[Provider.Meta().(*conns.ProviderMeta).Region].OrganizationsConn())

if err != nil {
t.Fatalf("describing AWS Organization: %s", err)
}

callerIdentity, err := tfsts.FindCallerIdentity(ctx, Provider.Meta().(*conns.AWSClient).STSConn())
callerIdentity, err := tfsts.FindCallerIdentity(ctx, Provider.Meta().(*conns.ProviderMeta).AWSClients[Provider.Meta().(*conns.ProviderMeta).Region].STSConn())

if err != nil {
t.Fatalf("getting current identity: %s", err)
Expand All @@ -928,7 +928,7 @@ func PreCheckOrganizationManagementAccount(ctx context.Context, t *testing.T) {
}

func PreCheckSSOAdminInstances(ctx context.Context, t *testing.T) {
conn := Provider.Meta().(*conns.AWSClient).SSOAdminConn()
conn := Provider.Meta().(*conns.ProviderMeta).AWSClients[Provider.Meta().(*conns.ProviderMeta).Region].SSOAdminConn()
input := &ssoadmin.ListInstancesInput{}
var instances []*ssoadmin.InstanceMetadata

Expand Down Expand Up @@ -956,7 +956,7 @@ func PreCheckSSOAdminInstances(ctx context.Context, t *testing.T) {
}

func PreCheckHasIAMRole(ctx context.Context, t *testing.T, roleName string) {
conn := Provider.Meta().(*conns.AWSClient).IAMConn()
conn := Provider.Meta().(*conns.ProviderMeta).AWSClients[Provider.Meta().(*conns.ProviderMeta).Region].IAMConn()
input := &iam.GetRoleInput{
RoleName: aws.String(roleName),
}
Expand All @@ -977,7 +977,7 @@ func PreCheckHasIAMRole(ctx context.Context, t *testing.T, roleName string) {
}

func PreCheckIAMServiceLinkedRole(ctx context.Context, t *testing.T, pathPrefix string) {
conn := Provider.Meta().(*conns.AWSClient).IAMConn()
conn := Provider.Meta().(*conns.ProviderMeta).AWSClients[Provider.Meta().(*conns.ProviderMeta).Region].IAMConn()
input := &iam.ListRolesInput{
PathPrefix: aws.String(pathPrefix),
}
Expand Down Expand Up @@ -1006,7 +1006,7 @@ func PreCheckIAMServiceLinkedRole(ctx context.Context, t *testing.T, pathPrefix
}

func PreCheckDirectoryService(ctx context.Context, t *testing.T) {
conn := Provider.Meta().(*conns.AWSClient).DSConn()
conn := Provider.Meta().(*conns.ProviderMeta).AWSClients[Provider.Meta().(*conns.ProviderMeta).Region].DSConn()
input := &directoryservice.DescribeDirectoriesInput{}

_, err := conn.DescribeDirectoriesWithContext(ctx, input)
Expand All @@ -1024,7 +1024,7 @@ func PreCheckDirectoryService(ctx context.Context, t *testing.T) {
// and we do not have a good read-only way to determine this situation. Here we
// opt to perform a creation that will fail so we can determine Simple AD support.
func PreCheckDirectoryServiceSimpleDirectory(ctx context.Context, t *testing.T) {
conn := Provider.Meta().(*conns.AWSClient).DSConn()
conn := Provider.Meta().(*conns.ProviderMeta).AWSClients[Provider.Meta().(*conns.ProviderMeta).Region].DSConn()
input := &directoryservice.CreateDirectoryInput{
Name: aws.String("corp.example.com"),
Password: aws.String("PreCheck123"),
Expand All @@ -1043,7 +1043,7 @@ func PreCheckDirectoryServiceSimpleDirectory(ctx context.Context, t *testing.T)
}

func PreCheckOutpostsOutposts(ctx context.Context, t *testing.T) {
conn := Provider.Meta().(*conns.AWSClient).OutpostsConn()
conn := Provider.Meta().(*conns.ProviderMeta).AWSClients[Provider.Meta().(*conns.ProviderMeta).Region].OutpostsConn()
input := &outposts.ListOutpostsInput{}

output, err := conn.ListOutpostsWithContext(ctx, input)
Expand Down Expand Up @@ -1233,7 +1233,7 @@ func RegionProviderFunc(region string, providers *[]*schema.Provider) func() *sc
}

// Ignore if Meta is not conns.AWSClient, this will happen for other providers
client, ok := provo.Meta().(*conns.AWSClient)
client, ok := provo.Meta().(*conns.ProviderMeta).AWSClients[Provider.Meta().(*conns.ProviderMeta).Region]
if !ok {
log.Printf("[DEBUG] Skipping non-AWS provider")
continue
Expand Down Expand Up @@ -1672,7 +1672,7 @@ func ACMCertificateRandomSubDomain(rootDomain string) string {

func CheckACMPCACertificateAuthorityActivateRootCA(ctx context.Context, certificateAuthority *acmpca.CertificateAuthority) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := Provider.Meta().(*conns.AWSClient).ACMPCAConn()
conn := Provider.Meta().(*conns.ProviderMeta).AWSClients[Provider.Meta().(*conns.ProviderMeta).Region].ACMPCAConn()

if v := aws.StringValue(certificateAuthority.Type); v != acmpca.CertificateAuthorityTypeRoot {
return fmt.Errorf("attempting to activate ACM PCA %s Certificate Authority", v)
Expand Down Expand Up @@ -1738,7 +1738,7 @@ func CheckACMPCACertificateAuthorityActivateRootCA(ctx context.Context, certific

func CheckACMPCACertificateAuthorityActivateSubordinateCA(ctx context.Context, rootCertificateAuthority, certificateAuthority *acmpca.CertificateAuthority) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := Provider.Meta().(*conns.AWSClient).ACMPCAConn()
conn := Provider.Meta().(*conns.ProviderMeta).AWSClients[Provider.Meta().(*conns.ProviderMeta).Region].ACMPCAConn()

if v := aws.StringValue(certificateAuthority.Type); v != acmpca.CertificateAuthorityTypeSubordinate {
return fmt.Errorf("attempting to activate ACM PCA %s Certificate Authority", v)
Expand Down Expand Up @@ -1807,7 +1807,7 @@ func CheckACMPCACertificateAuthorityActivateSubordinateCA(ctx context.Context, r

func CheckACMPCACertificateAuthorityDisableCA(ctx context.Context, certificateAuthority *acmpca.CertificateAuthority) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := Provider.Meta().(*conns.AWSClient).ACMPCAConn()
conn := Provider.Meta().(*conns.ProviderMeta).AWSClients[Provider.Meta().(*conns.ProviderMeta).Region].ACMPCAConn()

_, err := conn.UpdateCertificateAuthorityWithContext(ctx, &acmpca.UpdateCertificateAuthorityInput{
CertificateAuthorityArn: certificateAuthority.Arn,
Expand All @@ -1829,7 +1829,7 @@ func CheckACMPCACertificateAuthorityExists(ctx context.Context, n string, certif
return fmt.Errorf("no ACM PCA Certificate Authority ID is set")
}

conn := Provider.Meta().(*conns.AWSClient).ACMPCAConn()
conn := Provider.Meta().(*conns.ProviderMeta).AWSClients[Provider.Meta().(*conns.ProviderMeta).Region].ACMPCAConn()

output, err := tfacmpca.FindCertificateAuthorityByARN(ctx, conn, rs.Primary.ID)

Expand Down Expand Up @@ -2193,7 +2193,7 @@ func CheckVPCExists(ctx context.Context, n string, v *ec2.Vpc) resource.TestChec
return fmt.Errorf("no VPC ID is set")
}

conn := Provider.Meta().(*conns.AWSClient).EC2Conn()
conn := Provider.Meta().(*conns.ProviderMeta).AWSClients[Provider.Meta().(*conns.ProviderMeta).Region].EC2Conn()

output, err := tfec2.FindVPCByID(ctx, conn, rs.Primary.ID)

Expand All @@ -2218,7 +2218,7 @@ func CheckCallerIdentityAccountID(n string) resource.TestCheckFunc {
return fmt.Errorf("account Id resource ID not set.")
}

expected := Provider.Meta().(*conns.AWSClient).AccountID
expected := Provider.Meta().(*conns.ProviderMeta).AWSClients[Provider.Meta().(*conns.ProviderMeta).Region].AccountID
if rs.Primary.Attributes["account_id"] != expected {
return fmt.Errorf("incorrect Account ID: expected %q, got %q", expected, rs.Primary.Attributes["account_id"])
}
Expand Down
14 changes: 14 additions & 0 deletions internal/conns/providermeta.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package conns

import tftags "github.com/hashicorp/terraform-provider-aws/internal/tags"

type ProviderMeta struct {
AccountID string
AllowedRegions []string
AWSClients map[string]*AWSClient
DefaultTagsConfig *tftags.DefaultConfig
IgnoreTagsConfig *tftags.IgnoreConfig
Partition string
Region string
ServicePackages map[string]ServicePackage
}
33 changes: 17 additions & 16 deletions internal/create/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,23 @@ import (
)

const (
ErrActionChecking = "checking"
ErrActionCheckingDestroyed = "checking destroyed"
ErrActionCheckingExistence = "checking existence"
ErrActionCheckingNotRecreated = "checking not recreated"
ErrActionCheckingRecreated = "checking recreated"
ErrActionCreating = "creating"
ErrActionDeleting = "deleting"
ErrActionImporting = "importing"
ErrActionReading = "reading"
ErrActionSetting = "setting"
ErrActionUpdating = "updating"
ErrActionWaitingForCreation = "waiting for creation"
ErrActionWaitingForDeletion = "waiting for delete"
ErrActionWaitingForUpdate = "waiting for update"
ErrActionExpandingResourceId = "expanding resource id"
ErrActionFlatteningResourceId = "flattening resource id"
ErrActionChecking = "checking"
ErrActionCheckingDestroyed = "checking destroyed"
ErrActionCheckingExistence = "checking existence"
ErrActionCheckingNotRecreated = "checking not recreated"
ErrActionCheckingRecreated = "checking recreated"
ErrActionCreating = "creating"
ErrActionDeleting = "deleting"
ErrActionImporting = "importing"
ErrActionReading = "reading"
ErrActionSetting = "setting"
ErrActionUpdating = "updating"
ErrActionWaitingForCreation = "waiting for creation"
ErrActionWaitingForDeletion = "waiting for delete"
ErrActionWaitingForUpdate = "waiting for update"
ErrActionExpandingResourceId = "expanding resource id"
ErrActionFlatteningResourceId = "flattening resource id"
ErrActionExpandingResourceRegion = "expanding resource region"
)

// ProblemStandardMessage is a standardized message for reporting errors and warnings
Expand Down
17 changes: 17 additions & 0 deletions internal/flex/flex.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/aws/aws-sdk-go/aws"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"golang.org/x/exp/slices"
)

const (
Expand Down Expand Up @@ -286,3 +287,19 @@ func ResourceIdPartCount(id string) int {
idParts := strings.Split(id, ResourceIdSeparator)
return len(idParts)
}

// Takes an interface that contains region as a string value, a list of allowed regions, and a default Region
// Returns a string of the region name to be used for the connection and an error if there is an issue returning the region
func ExpandResourceRegion(r interface{}, allowedRegions []string, defaultRegion string) (region string, err error) {
if r != nil && r.(string) != "" {
region = r.(string)
} else {
region = defaultRegion
}

if !slices.Contains(allowedRegions, region) {
return "", fmt.Errorf("provided resource region is not an allowed region in the provider configuration. To deploy to this region, add it to the 'allowed_regions' provider setting, or remove the list of 'allowed_regions' from your provider configuration. Provided region %s, provider allowed regions: %v", region, allowedRegions)
}

return region, nil
}
67 changes: 67 additions & 0 deletions internal/flex/flex_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package flex

import (
"fmt"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -241,3 +242,69 @@ func TestFlattenTimeStringList(t *testing.T) {
t.Errorf("expanded = %v, want = %v", got, want)
}
}

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

cases := []struct {
name string
region interface{}
defaultRegion string
allowedRegions []string
expectedRegion string
expectedError error
}{
{
name: "resourceRegionPositive",
region: "us-east-1",
defaultRegion: "us-west-2",
allowedRegions: []string{"us-west-2", "us-east-1"},
expectedRegion: "us-east-1",
expectedError: nil,
},
{
name: "resourceRegionNilPositive",
region: nil,
defaultRegion: "us-west-2",
allowedRegions: []string{"us-west-2", "us-east-1"},
expectedRegion: "us-west-2",
expectedError: nil,
},
{
name: "resourceRegionNilNegative",
region: nil,
defaultRegion: "us-west-2",
allowedRegions: []string{"us-east-1"},
expectedRegion: "",
expectedError: fmt.Errorf("provided resource region is not an allowed region in the provider configuration. To deploy to this region, add it to the 'allowed_regions' provider setting, or remove the list of 'allowed_regions' from your provider configuration. Provided region us-west-2, provider allowed regions: [us-east-1]"),
},
{
name: "resourceRegionNegative",
region: "us-east-1",
defaultRegion: "us-west-2",
allowedRegions: []string{"us-west-2", "ap-south-1"},
expectedRegion: "",
expectedError: fmt.Errorf("provided resource region is not an allowed region in the provider configuration. To deploy to this region, add it to the 'allowed_regions' provider setting, or remove the list of 'allowed_regions' from your provider configuration. Provided region us-east-1, provider allowed regions: [us-west-2 ap-south-1]"),
},
}

for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
out, err := ExpandResourceRegion(tc.region, tc.allowedRegions, tc.defaultRegion)

if tc.expectedError != nil {
if err != nil && !strings.Contains(err.Error(), tc.expectedError.Error()) {
t.Fatalf("expected = %s, want = %s", tc.expectedError.Error(), err.Error())
} else if err != nil && tc.expectedError == nil {
t.Errorf("unexpected error returned: %s", err)
}
}

if !cmp.Equal(out, tc.expectedRegion) {
t.Errorf("expanded = %s, want = %s", out, tc.expectedRegion)
}
})
}
}
9 changes: 7 additions & 2 deletions internal/provider/fwprovider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ func (p *fwprovider) Schema(ctx context.Context, req provider.SchemaRequest, res
ElementType: types.StringType,
Optional: true,
},
"allowed_regions": schema.SetAttribute{
ElementType: types.StringType,
Optional: true,
Description: "A list of aws regions that are allowed to be used as a `region` input at the resource level.",
},
"custom_ca_bundle": schema.StringAttribute{
Optional: true,
Description: "File containing custom root and intermediate certificates. Can also be configured using the `AWS_CA_BUNDLE` environment variable. (Setting `ca_bundle` in the shared config file is not supported.)",
Expand Down Expand Up @@ -280,7 +285,7 @@ func (p *fwprovider) Configure(ctx context.Context, request provider.ConfigureRe
func (p *fwprovider) DataSources(ctx context.Context) []func() datasource.DataSource {
var dataSources []func() datasource.DataSource

for n, sp := range p.Primary.Meta().(*conns.AWSClient).ServicePackages {
for n, sp := range p.Primary.Meta().(*conns.ProviderMeta).ServicePackages {
servicePackageName := sp.ServicePackageName()

for _, v := range sp.FrameworkDataSources(ctx) {
Expand Down Expand Up @@ -324,7 +329,7 @@ func (p *fwprovider) Resources(ctx context.Context) []func() resource.Resource {
var errs *multierror.Error
var resources []func() resource.Resource

for _, sp := range p.Primary.Meta().(*conns.AWSClient).ServicePackages {
for _, sp := range p.Primary.Meta().(*conns.ProviderMeta).ServicePackages {
servicePackageName := sp.ServicePackageName()

for _, v := range sp.FrameworkResources(ctx) {
Expand Down
Loading