Skip to content

Commit

Permalink
regions for cloudformaton_stack_set_instance
Browse files Browse the repository at this point in the history
  • Loading branch information
alexknez committed Apr 9, 2024
1 parent a56bdf2 commit 83e3aaf
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 60 deletions.
8 changes: 4 additions & 4 deletions internal/service/cloudformation/find.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ func FindChangeSetByStackIDAndChangeSetName(ctx context.Context, conn *cloudform
return output, nil
}

func FindStackInstanceSummariesByOrgIDs(ctx context.Context, conn *cloudformation.CloudFormation, stackSetName, region, callAs string, orgIDs []string) ([]*cloudformation.StackInstanceSummary, error) {
func FindStackInstanceSummariesByOrgIDs(ctx context.Context, conn *cloudformation.CloudFormation, stackSetName string, region []string, callAs string, orgIDs []string) ([]*cloudformation.StackInstanceSummary, error) {
input := &cloudformation.ListStackInstancesInput{
StackInstanceRegion: aws.String(region),
StackInstanceRegion: aws.String(region[0]),
StackSetName: aws.String(stackSetName),
}

Expand Down Expand Up @@ -85,10 +85,10 @@ func FindStackInstanceSummariesByOrgIDs(ctx context.Context, conn *cloudformatio
return result, nil
}

func FindStackInstanceByName(ctx context.Context, conn *cloudformation.CloudFormation, stackSetName, accountID, region, callAs string) (*cloudformation.StackInstance, error) {
func FindStackInstanceByName(ctx context.Context, conn *cloudformation.CloudFormation, stackSetName, accountID string, region []string, callAs string) (*cloudformation.StackInstance, error) {
input := &cloudformation.DescribeStackInstanceInput{
StackInstanceAccount: aws.String(accountID),
StackInstanceRegion: aws.String(region),
StackInstanceRegion: aws.String(region[0]),
StackSetName: aws.String(stackSetName),
}

Expand Down
92 changes: 59 additions & 33 deletions internal/service/cloudformation/stack_set_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,14 @@ func ResourceStackSetInstance() *schema.Resource {
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"region": {
Type: schema.TypeString,
"regions": {
Type: schema.TypeList,
Optional: true,
Computed: true,
ForceNew: true,
MinItems: 1,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.StringMatch(regexache.MustCompile(`^[0-9A-Za-z-]{1,128}$`), ""),
},
},
"retain_stack": {
Type: schema.TypeBool,
Expand Down Expand Up @@ -189,17 +192,23 @@ func resourceStackSetInstanceCreate(ctx context.Context, d *schema.ResourceData,
var diags diag.Diagnostics
conn := meta.(*conns.AWSClient).CloudFormationConn(ctx)

region := meta.(*conns.AWSClient).Region
if v, ok := d.GetOk("region"); ok {
region = v.(string)
}

stackSetName := d.Get("stack_set_name").(string)
input := &cloudformation.CreateStackInstancesInput{
Regions: aws.StringSlice([]string{region}),
StackSetName: aws.String(stackSetName),
}

regions := []string{}
if v, ok := d.GetOk("regions"); ok {
r, err := interfaceToStringSlice(v)
if err == nil {
regions = r
}
}

if len(regions) > 0 {
input.Regions = aws.StringSlice(regions)
}

accountID := meta.(*conns.AWSClient).AccountID
if v, ok := d.GetOk("account_id"); ok {
accountID = v.(string)
Expand Down Expand Up @@ -241,7 +250,7 @@ func resourceStackSetInstanceCreate(ctx context.Context, d *schema.ResourceData,
return nil, err
}

d.SetId(StackSetInstanceCreateResourceID(stackSetName, accountOrOrgID, region))
d.SetId(StackSetInstanceCreateResourceID(stackSetName, accountOrOrgID, regions))

operation, err := WaitStackSetOperationSucceeded(ctx, conn, stackSetName, aws.StringValue(output.OperationId), callAs, d.Timeout(schema.TimeoutCreate))
if err != nil {
Expand Down Expand Up @@ -299,19 +308,19 @@ func resourceStackSetInstanceRead(ctx context.Context, d *schema.ResourceData, m
var diags diag.Diagnostics
conn := meta.(*conns.AWSClient).CloudFormationConn(ctx)

stackSetName, accountOrOrgID, region, err := StackSetInstanceParseResourceID(d.Id())
stackSetName, accountOrOrgID, regions, err := StackSetInstanceParseResourceID(d.Id())
if err != nil {
return sdkdiag.AppendFromErr(diags, err)
}

d.Set("region", region)
d.Set("regions", regions)
d.Set("stack_set_name", stackSetName)

callAs := d.Get("call_as").(string)

if accountIDRegexp.MatchString(accountOrOrgID) {
// Stack instances deployed by account ID
stackInstance, err := FindStackInstanceByName(ctx, conn, stackSetName, accountOrOrgID, region, callAs)
stackInstance, err := FindStackInstanceByName(ctx, conn, stackSetName, accountOrOrgID, regions, callAs)

if !d.IsNewResource() && tfresource.NotFound(err) {
log.Printf("[WARN] CloudFormation StackSet Instance (%s) not found, removing from state", d.Id())
Expand All @@ -335,7 +344,7 @@ func resourceStackSetInstanceRead(ctx context.Context, d *schema.ResourceData, m
// Stack instances deployed by organizational unit ID
orgIDs := strings.Split(accountOrOrgID, "/")

summaries, err := FindStackInstanceSummariesByOrgIDs(ctx, conn, stackSetName, region, callAs, orgIDs)
summaries, err := FindStackInstanceSummariesByOrgIDs(ctx, conn, stackSetName, regions, callAs, orgIDs)

if !d.IsNewResource() && tfresource.NotFound(err) {
log.Printf("[WARN] CloudFormation StackSet Instance (%s) not found, removing from state", d.Id())
Expand All @@ -359,7 +368,7 @@ func resourceStackSetInstanceUpdate(ctx context.Context, d *schema.ResourceData,
conn := meta.(*conns.AWSClient).CloudFormationConn(ctx)

if d.HasChanges("deployment_targets", "parameter_overrides", "operation_preferences") {
stackSetName, accountOrOrgID, region, err := StackSetInstanceParseResourceID(d.Id())
stackSetName, accountOrOrgID, regions, err := StackSetInstanceParseResourceID(d.Id())
if err != nil {
return sdkdiag.AppendFromErr(diags, err)
}
Expand All @@ -368,7 +377,7 @@ func resourceStackSetInstanceUpdate(ctx context.Context, d *schema.ResourceData,
Accounts: aws.StringSlice([]string{accountOrOrgID}),
OperationId: aws.String(id.UniqueId()),
ParameterOverrides: []*cloudformation.Parameter{},
Regions: aws.StringSlice([]string{region}),
Regions: aws.StringSlice(regions),
StackSetName: aws.String(stackSetName),
}

Expand Down Expand Up @@ -410,15 +419,15 @@ func resourceStackSetInstanceDelete(ctx context.Context, d *schema.ResourceData,
var diags diag.Diagnostics
conn := meta.(*conns.AWSClient).CloudFormationConn(ctx)

stackSetName, accountOrOrgID, region, err := StackSetInstanceParseResourceID(d.Id())
stackSetName, accountOrOrgID, regions, err := StackSetInstanceParseResourceID(d.Id())
if err != nil {
return sdkdiag.AppendFromErr(diags, err)
}

input := &cloudformation.DeleteStackInstancesInput{
Accounts: aws.StringSlice([]string{accountOrOrgID}),
OperationId: aws.String(id.UniqueId()),
Regions: aws.StringSlice([]string{region}),
Regions: aws.StringSlice(regions),
RetainStacks: aws.Bool(d.Get("retain_stack").(bool)),
StackSetName: aws.String(stackSetName),
}
Expand Down Expand Up @@ -457,35 +466,34 @@ func resourceStackSetInstanceDelete(ctx context.Context, d *schema.ResourceData,
}

func resourceStackSetInstanceImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
switch parts := strings.Split(d.Id(), stackSetInstanceResourceIDSeparator); len(parts) {
case 3:
case 4:
d.SetId(strings.Join([]string{parts[0], parts[1], parts[2]}, stackSetInstanceResourceIDSeparator))
d.Set("call_as", parts[3])
default:
return []*schema.ResourceData{}, fmt.Errorf("unexpected format for import ID (%[1]s), use: STACKSETNAME%[2]sACCOUNTID%[2]sREGION or STACKSETNAME%[2]sACCOUNTID%[2]sREGION%[2]sCALLAS", d.Id(), stackSetInstanceResourceIDSeparator)
parts := strings.Split(d.Id(), stackSetInstanceResourceIDSeparator)
lastElement := parts[len(parts)-1]
if lastElement == "SELF" || lastElement == "DELEGATED_ADMIN" {
d.SetId(strings.Join(parts[:len(parts)-1], stackSetInstanceResourceIDSeparator))
d.Set("call_as", lastElement)
}

return []*schema.ResourceData{d}, nil
}

const stackSetInstanceResourceIDSeparator = ","

func StackSetInstanceCreateResourceID(stackSetName, accountID, region string) string {
parts := []string{stackSetName, accountID, region}
func StackSetInstanceCreateResourceID(stackSetName string, accountID string, regions []string) string {
parts := []string{stackSetName, accountID}
parts = append(parts, regions...)
id := strings.Join(parts, stackSetInstanceResourceIDSeparator)

return id
}

func StackSetInstanceParseResourceID(id string) (string, string, string, error) {
func StackSetInstanceParseResourceID(id string) (string, string, []string, error) {
parts := strings.Split(id, stackSetInstanceResourceIDSeparator)

if len(parts) == 3 && parts[0] != "" && parts[1] != "" && parts[2] != "" {
return parts[0], parts[1], parts[2], nil
if len(parts) >= 3 && parts[0] != "" && parts[1] != "" && parts[2] != "" {
regions := parts[2:]
return parts[0], parts[1], regions, nil
}

return "", "", "", fmt.Errorf("unexpected format for ID (%[1]s), expected STACKSETNAME%[2]sACCOUNTID%[2]sREGION", id, stackSetInstanceResourceIDSeparator)
return "", "", nil, fmt.Errorf("unexpected format for ID (%[1]s), expected STACKSETNAME%[2]sACCOUNTID%[2]sREGION", id, stackSetInstanceResourceIDSeparator)
}

func expandDeploymentTargets(tfList []interface{}) *cloudformation.DeploymentTargets {
Expand Down Expand Up @@ -539,3 +547,21 @@ func flattenStackInstanceSummaries(apiObject []*cloudformation.StackInstanceSumm

return tfList
}

func interfaceToStringSlice(input interface{}) ([]string, error) {
slice, ok := input.([]interface{})
if !ok {
return nil, fmt.Errorf("input is not a slice of interfaces")
}

var result []string
for _, elem := range slice {
str, ok := elem.(string)
if !ok {
return nil, fmt.Errorf("element %v cannot be converted to string", elem)
}
result = append(result, str)
}

return result, nil
}
38 changes: 19 additions & 19 deletions internal/service/cloudformation/stack_set_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func TestAccCloudFormationStackSetInstance_basic(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "operation_preferences.#", "0"),
resource.TestCheckResourceAttr(resourceName, "organizational_unit_id", ""),
resource.TestCheckResourceAttr(resourceName, "parameter_overrides.%", "0"),
resource.TestCheckResourceAttr(resourceName, "region", acctest.Region()),
resource.TestCheckResourceAttr(resourceName, "regions.#", "2"),
resource.TestCheckResourceAttr(resourceName, "retain_stack", "false"),
resource.TestCheckResourceAttrSet(resourceName, "stack_id"),
resource.TestCheckResourceAttr(resourceName, "stack_instance_summaries.#", "0"),
Expand Down Expand Up @@ -449,13 +449,13 @@ func testAccCheckStackSetInstanceExists(ctx context.Context, resourceName string

conn := acctest.Provider.Meta().(*conns.AWSClient).CloudFormationConn(ctx)

stackSetName, accountID, region, err := tfcloudformation.StackSetInstanceParseResourceID(rs.Primary.ID)
stackSetName, accountID, regions, err := tfcloudformation.StackSetInstanceParseResourceID(rs.Primary.ID)

if err != nil {
return err
}

output, err := tfcloudformation.FindStackInstanceByName(ctx, conn, stackSetName, accountID, region, callAs)
output, err := tfcloudformation.FindStackInstanceByName(ctx, conn, stackSetName, accountID, regions, callAs)

if err != nil {
return err
Expand All @@ -481,13 +481,13 @@ func testAccCheckStackSetInstanceForOrganizationalUnitExists(ctx context.Context

conn := acctest.Provider.Meta().(*conns.AWSClient).CloudFormationConn(ctx)

stackSetName, accountOrOrgID, region, err := tfcloudformation.StackSetInstanceParseResourceID(rs.Primary.ID)
stackSetName, accountOrOrgID, regions, err := tfcloudformation.StackSetInstanceParseResourceID(rs.Primary.ID)
if err != nil {
return err
}
orgIDs := strings.Split(accountOrOrgID, "/")

output, err := tfcloudformation.FindStackInstanceSummariesByOrgIDs(ctx, conn, stackSetName, region, callAs, orgIDs)
output, err := tfcloudformation.FindStackInstanceSummariesByOrgIDs(ctx, conn, stackSetName, regions, callAs, orgIDs)

if err != nil {
return err
Expand All @@ -513,13 +513,13 @@ func testAccCheckStackSetInstanceForOrganizationalUnitDestroy(ctx context.Contex

callAs := rs.Primary.Attributes["call_as"]

stackSetName, accountOrOrgID, region, err := tfcloudformation.StackSetInstanceParseResourceID(rs.Primary.ID)
stackSetName, accountOrOrgID, regions, err := tfcloudformation.StackSetInstanceParseResourceID(rs.Primary.ID)
if err != nil {
return err
}
orgIDs := strings.Split(accountOrOrgID, "/")

output, err := tfcloudformation.FindStackInstanceSummariesByOrgIDs(ctx, conn, stackSetName, region, callAs, orgIDs)
output, err := tfcloudformation.FindStackInstanceSummariesByOrgIDs(ctx, conn, stackSetName, regions, callAs, orgIDs)

if tfresource.NotFound(err) {
continue
Expand Down Expand Up @@ -566,13 +566,13 @@ func testAccCheckStackSetInstanceDestroy(ctx context.Context) resource.TestCheck

callAs := rs.Primary.Attributes["call_as"]

stackSetName, accountID, region, err := tfcloudformation.StackSetInstanceParseResourceID(rs.Primary.ID)
stackSetName, accountID, regions, err := tfcloudformation.StackSetInstanceParseResourceID(rs.Primary.ID)

if err != nil {
return err
}

_, err = tfcloudformation.FindStackInstanceByName(ctx, conn, stackSetName, accountID, region, callAs)
_, err = tfcloudformation.FindStackInstanceByName(ctx, conn, stackSetName, accountID, regions, callAs)

if tfresource.NotFound(err) {
continue
Expand Down Expand Up @@ -732,8 +732,8 @@ TEMPLATE
func testAccStackSetInstanceConfig_basic(rName string) string {
return acctest.ConfigCompose(testAccStackSetInstanceBaseConfig(rName), `

Check failure on line 733 in internal/service/cloudformation/stack_set_instance_test.go

View workflow job for this annotation

GitHub Actions / providerlint

AWSAT003: regions should not be hardcoded, use aws_region and aws_availability_zones data sources instead
resource "aws_cloudformation_stack_set_instance" "test" {
depends_on = [aws_iam_role_policy.Administration, aws_iam_role_policy.Execution]
depends_on = [aws_iam_role_policy.Administration, aws_iam_role_policy.Execution]
regions = ["us-east-1", "us-west-2"]
stack_set_name = aws_cloudformation_stack_set.test.name
}
`)
Expand All @@ -743,7 +743,7 @@ func testAccStackSetInstanceConfig_parameterOverrides1(rName, value1 string) str
return acctest.ConfigCompose(testAccStackSetInstanceBaseConfig(rName), fmt.Sprintf(`

Check failure on line 743 in internal/service/cloudformation/stack_set_instance_test.go

View workflow job for this annotation

GitHub Actions / providerlint

AWSAT003: regions should not be hardcoded, use aws_region and aws_availability_zones data sources instead
resource "aws_cloudformation_stack_set_instance" "test" {
depends_on = [aws_iam_role_policy.Administration, aws_iam_role_policy.Execution]
regions = ["us-east-1", "us-west-2"]
parameter_overrides = {
Parameter1 = %[1]q
}
Expand All @@ -757,7 +757,7 @@ func testAccStackSetInstanceConfig_parameterOverrides2(rName, value1, value2 str
return acctest.ConfigCompose(testAccStackSetInstanceBaseConfig(rName), fmt.Sprintf(`

Check failure on line 757 in internal/service/cloudformation/stack_set_instance_test.go

View workflow job for this annotation

GitHub Actions / providerlint

AWSAT003: regions should not be hardcoded, use aws_region and aws_availability_zones data sources instead
resource "aws_cloudformation_stack_set_instance" "test" {
depends_on = [aws_iam_role_policy.Administration, aws_iam_role_policy.Execution]
regions = ["us-east-1", "us-west-2"]
parameter_overrides = {
Parameter1 = %[1]q
Parameter2 = %[2]q
Expand All @@ -771,8 +771,8 @@ resource "aws_cloudformation_stack_set_instance" "test" {
func testAccStackSetInstanceConfig_retain(rName string, retainStack bool) string {
return acctest.ConfigCompose(testAccStackSetInstanceBaseConfig(rName), fmt.Sprintf(`

Check failure on line 772 in internal/service/cloudformation/stack_set_instance_test.go

View workflow job for this annotation

GitHub Actions / providerlint

AWSAT003: regions should not be hardcoded, use aws_region and aws_availability_zones data sources instead
resource "aws_cloudformation_stack_set_instance" "test" {
depends_on = [aws_iam_role_policy.Administration, aws_iam_role_policy.Execution]
depends_on = [aws_iam_role_policy.Administration, aws_iam_role_policy.Execution]
regions = ["us-east-1", "us-west-2"]
retain_stack = %[1]t
stack_set_name = aws_cloudformation_stack_set.test.name
}
Expand Down Expand Up @@ -899,7 +899,7 @@ func testAccStackSetInstanceConfig_deploymentTargets(rName string) string {
return acctest.ConfigCompose(testAccStackSetInstanceBaseConfig_ServiceManagedStackSet(rName), `

Check failure on line 899 in internal/service/cloudformation/stack_set_instance_test.go

View workflow job for this annotation

GitHub Actions / providerlint

AWSAT003: regions should not be hardcoded, use aws_region and aws_availability_zones data sources instead
resource "aws_cloudformation_stack_set_instance" "test" {
depends_on = [aws_iam_role_policy.Administration, aws_iam_role_policy.Execution]
regions = ["us-east-1", "us-west-2"]
deployment_targets {
organizational_unit_ids = [data.aws_organizations_organization.test.roots[0].id]
}
Expand All @@ -918,7 +918,7 @@ resource "aws_organizations_organizational_unit" "test" {
resource "aws_cloudformation_stack_set_instance" "test" {
depends_on = [aws_iam_role_policy.Administration, aws_iam_role_policy.Execution]
regions = ["us-east-1", "us-west-2"]
deployment_targets {
organizational_unit_ids = [aws_organizations_organizational_unit.test.id]
}
Expand All @@ -932,7 +932,7 @@ func testAccStackSetInstanceConfig_operationPreferences(rName string) string {
return acctest.ConfigCompose(testAccStackSetInstanceBaseConfig_ServiceManagedStackSet(rName), `

Check failure on line 932 in internal/service/cloudformation/stack_set_instance_test.go

View workflow job for this annotation

GitHub Actions / providerlint

AWSAT003: regions should not be hardcoded, use aws_region and aws_availability_zones data sources instead
resource "aws_cloudformation_stack_set_instance" "test" {
depends_on = [aws_iam_role_policy.Administration, aws_iam_role_policy.Execution]
regions = ["us-east-1", "us-west-2"]
operation_preferences {
failure_tolerance_count = 1
max_concurrent_count = 10
Expand All @@ -953,7 +953,7 @@ data "aws_organizations_organization" "test" {}
resource "aws_cloudformation_stack_set_instance" "test" {
call_as = "DELEGATED_ADMIN"
regions = ["us-east-1", "us-west-2"]
deployment_targets {
organizational_unit_ids = [data.aws_organizations_organization.test.roots[0].id]
}
Expand Down
2 changes: 1 addition & 1 deletion internal/service/cloudformation/sweep.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func sweepStackSetInstances(region string) error {

r := ResourceStackSetInstance()
d := r.Data(nil)
id := StackSetInstanceCreateResourceID(stackSetID, accountOrOrgID, aws.StringValue(v.Region))
id := StackSetInstanceCreateResourceID(stackSetID, accountOrOrgID, []string{aws.StringValue(v.Region)})
d.SetId(id)
d.Set("call_as", cloudformation.CallAsSelf)
if ouID != "" {
Expand Down
Loading

0 comments on commit 83e3aaf

Please sign in to comment.