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

r/aws_cloudformation_stack_set_instance: add support for additional deployment_targets config and refactor update deployment_targets config #37898

67 changes: 40 additions & 27 deletions internal/service/cloudformation/stack_set_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"context"
"fmt"
"log"
"regexp"
"slices"
"strings"
"time"
Expand Down Expand Up @@ -76,14 +77,41 @@ func resourceStackSetInstance() *schema.Resource {
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"organizational_unit_ids": {
Type: schema.TypeSet,
Optional: true,
MinItems: 1,
Type: schema.TypeSet,
Optional: true,
ForceNew: true,
MinItems: 1,
ConflictsWith: []string{"account_id"},
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.StringMatch(regexache.MustCompile(`^(ou-[0-9a-z]{4,32}-[0-9a-z]{8,32}|r-[0-9a-z]{4,32})$`), ""),
},
},
"account_filter_type": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice(enum.Slice(awstypes.AccountFilterType.Values("")...), false),
ConflictsWith: []string{"account_id"},
},
"accounts": {
Type: schema.TypeSet,
Optional: true,
ForceNew: true,
ConflictsWith: []string{"account_id"},
MinItems: 1,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: verify.ValidAccountID,
},
},
"accounts_url": {
Type: schema.TypeString,
ForceNew: true,
Optional: true,
ConflictsWith: []string{"account_id"},
ValidateFunc: validation.StringMatch(regexp.MustCompile(`(s3://|http(s?)://).+`), ""),
},
},
},
ConflictsWith: []string{names.AttrAccountID},
Expand Down Expand Up @@ -357,7 +385,6 @@ func resourceStackSetInstanceRead(ctx context.Context, d *schema.ResourceData, m
return sdkdiag.AppendErrorf(diags, "finding CloudFormation StackSet Instance (%s): %s", d.Id(), err)
}

d.Set("deployment_targets", flattenDeploymentTargetsFromSlice(orgIDs))
d.Set("stack_instance_summaries", flattenStackInstanceSummaries(summaries))
}

Expand All @@ -368,7 +395,7 @@ func resourceStackSetInstanceUpdate(ctx context.Context, d *schema.ResourceData,
var diags diag.Diagnostics
conn := meta.(*conns.AWSClient).CloudFormationClient(ctx)

if d.HasChanges("deployment_targets", "parameter_overrides", "operation_preferences") {
if d.HasChanges("parameter_overrides", "operation_preferences") {
parts, err := flex.ExpandResourceId(d.Id(), stackSetInstanceResourceIDPartCount, false)
if err != nil {
return sdkdiag.AppendFromErr(diags, err)
Expand All @@ -388,13 +415,6 @@ func resourceStackSetInstanceUpdate(ctx context.Context, d *schema.ResourceData,
input.CallAs = awstypes.CallAs(v.(string))
}

if v, ok := d.GetOk("deployment_targets"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil {
dt := expandDeploymentTargets(v.([]interface{}))
// reset input Accounts as the API accepts only 1 of Accounts and DeploymentTargets
input.Accounts = nil
input.DeploymentTargets = dt
}

if v, ok := d.GetOk("parameter_overrides"); ok {
input.ParameterOverrides = expandParameters(v.(map[string]interface{}))
}
Expand Down Expand Up @@ -560,24 +580,17 @@ func expandDeploymentTargets(tfList []interface{}) *awstypes.DeploymentTargets {
if v, ok := tfMap["organizational_unit_ids"].(*schema.Set); ok && v.Len() > 0 {
dt.OrganizationalUnitIds = flex.ExpandStringValueSet(v)
}

return dt
}

// flattenDeployment targets converts a list of organizational units (typically
// parsed from the resource ID) into the Terraform representation of the
// deployment_targets attribute.
func flattenDeploymentTargetsFromSlice(orgIDs []string) []interface{} {
tfList := []interface{}{}
for _, ou := range orgIDs {
tfList = append(tfList, ou)
if v, ok := tfMap["account_filter_type"].(string); ok && len(v) > 0 {
dt.AccountFilterType = awstypes.AccountFilterType(v)
}

m := map[string]interface{}{
"organizational_unit_ids": tfList,
if v, ok := tfMap["accounts"].(*schema.Set); ok && v.Len() > 0 {
dt.Accounts = flex.ExpandStringValueSet(v)
}
if v, ok := tfMap["accounts_url"].(string); ok && len(v) > 0 {
dt.AccountsUrl = aws.String(v)
}

return []interface{}{m}
return dt
}

func flattenStackInstanceSummaries(apiObject []awstypes.StackInstanceSummary) []interface{} {
Expand Down
7 changes: 7 additions & 0 deletions internal/service/cloudformation/stack_set_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,9 @@ func TestAccCloudFormationStackSetInstance_deploymentTargets(t *testing.T) {
testAccCheckStackSetInstanceForOrganizationalUnitExists(ctx, resourceName, stackInstanceSummaries),
resource.TestCheckResourceAttr(resourceName, "deployment_targets.#", acctest.Ct1),
resource.TestCheckResourceAttr(resourceName, "deployment_targets.0.organizational_unit_ids.#", acctest.Ct1),
resource.TestCheckResourceAttr(resourceName, "deployment_targets.0.account_filter_type", "INTERSECTION"),
resource.TestCheckResourceAttr(resourceName, "deployment_targets.0.accounts.#", acctest.Ct1),
resource.TestCheckResourceAttr(resourceName, "deployment_targets.0.accounts_url", ""),
),
},
{
Expand All @@ -228,6 +231,7 @@ func TestAccCloudFormationStackSetInstance_deploymentTargets(t *testing.T) {
ImportStateVerifyIgnore: []string{
"retain_stack",
"call_as",
"deployment_targets",
},
},
{
Expand Down Expand Up @@ -273,6 +277,7 @@ func TestAccCloudFormationStackSetInstance_DeploymentTargets_emptyOU(t *testing.
ImportStateVerifyIgnore: []string{
"retain_stack",
"call_as",
"deployment_targets",
},
},
{
Expand Down Expand Up @@ -812,6 +817,8 @@ resource "aws_cloudformation_stack_set_instance" "test" {

deployment_targets {
organizational_unit_ids = [data.aws_organizations_organization.test.roots[0].id]
account_filter_type = "INTERSECTION"
accounts = [data.aws_organizations_organization.test.non_master_accounts[0].id]
}

stack_set_name = aws_cloudformation_stack_set.test.name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ This resource supports the following arguments:
The `deployment_targets` configuration block supports the following arguments:

* `organizational_unit_ids` - (Optional) The organization root ID or organizational unit (OU) IDs to which StackSets deploys.

* `account_filter_type` - (Optional) Limit deployment targets to individual accounts or include additional accounts with provided OUs. Valid values: `INTERSECTION`, `DIFFERENCE`, `UNION`, `NONE`.
* `accounts` - (Optional) The list of accounts to deploy stack set updates.
* `accounts_url` - (Optional) The S3 URL of the file containing the list of accounts.
### `operation_preferences` Argument Reference

The `operation_preferences` configuration block supports the following arguments:
Expand Down