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

Merged
7 changes: 7 additions & 0 deletions .changelog/37898.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```release-note:enhancement
resource/aws_cloudformation_stack_set_instance: Extend `deployment_targets` argument.
```

```release-note:bug
resource/aws_cloudformation_stack_set_instance: Add `ForceNew` to deployment_targets attributes to ensure a new resource is recreated when the deployment_targets argument is changed, which was not the case previously.
```
66 changes: 39 additions & 27 deletions internal/service/cloudformation/stack_set_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,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{names.AttrAccountID},
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{names.AttrAccountID},
},
"accounts": {
Type: schema.TypeSet,
Optional: true,
ForceNew: true,
ConflictsWith: []string{names.AttrAccountID},
MinItems: 1,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: verify.ValidAccountID,
},
},
"accounts_url": {
Type: schema.TypeString,
ForceNew: true,
Optional: true,
ConflictsWith: []string{names.AttrAccountID},
ValidateFunc: validation.StringMatch(regexache.MustCompile(`(s3://|http(s?)://).+`), ""),
},
},
},
ConflictsWith: []string{names.AttrAccountID},
Expand Down Expand Up @@ -357,7 +384,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 +394,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 +414,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 +579,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
21 changes: 12 additions & 9 deletions website/docs/r/cloudformation_stack_set_instance.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ This resource supports the following arguments:

* `stack_set_name` - (Required) Name of the StackSet.
* `account_id` - (Optional) Target AWS Account ID to create a Stack based on the StackSet. Defaults to current account.
* `deployment_targets` - (Optional) The AWS Organizations accounts to which StackSets deploys. StackSets doesn't deploy stack instances to the organization management account, even if the organization management account is in your organization or in an OU in your organization. Drift detection is not possible for this argument. See [deployment_targets](#deployment_targets-argument-reference) below.
* `deployment_targets` - (Optional) AWS Organizations accounts to which StackSets deploys. StackSets doesn't deploy stack instances to the organization management account, even if the organization management account is in your organization or in an OU in your organization. Drift detection is not possible for this argument. See [deployment_targets](#deployment_targets-argument-reference) below.
* `parameter_overrides` - (Optional) Key-value map of input parameters to override from the StackSet for this Instance.
* `region` - (Optional) Target AWS Region to create a Stack based on the StackSet. Defaults to current region.
* `retain_stack` - (Optional) During Terraform resource destroy, remove Instance from StackSet while keeping the Stack and its associated resources. Must be enabled in Terraform state _before_ destroy operation to take effect. You cannot reassociate a retained Stack or add an existing, saved Stack to a new StackSet. Defaults to `false`.
Expand All @@ -98,25 +98,28 @@ 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.
* `organizational_unit_ids` - (Optional) 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) List of accounts to deploy stack set updates.
* `accounts_url` - (Optional) S3 URL of the file containing the list of accounts.

### `operation_preferences` Argument Reference

The `operation_preferences` configuration block supports the following arguments:

* `failure_tolerance_count` - (Optional) The number of accounts, per Region, for which this operation can fail before AWS CloudFormation stops the operation in that Region.
* `failure_tolerance_percentage` - (Optional) The percentage of accounts, per Region, for which this stack operation can fail before AWS CloudFormation stops the operation in that Region.
* `max_concurrent_count` - (Optional) The maximum number of accounts in which to perform this operation at one time.
* `max_concurrent_percentage` - (Optional) The maximum percentage of accounts in which to perform this operation at one time.
* `region_concurrency_type` - (Optional) The concurrency type of deploying StackSets operations in Regions, could be in parallel or one Region at a time. Valid values are `SEQUENTIAL` and `PARALLEL`.
* `region_order` - (Optional) The order of the Regions in where you want to perform the stack operation.
* `failure_tolerance_count` - (Optional) Number of accounts, per Region, for which this operation can fail before AWS CloudFormation stops the operation in that Region.
* `failure_tolerance_percentage` - (Optional) Percentage of accounts, per Region, for which this stack operation can fail before AWS CloudFormation stops the operation in that Region.
* `max_concurrent_count` - (Optional) Maximum number of accounts in which to perform this operation at one time.
* `max_concurrent_percentage` - (Optional) Maximum percentage of accounts in which to perform this operation at one time.
* `region_concurrency_type` - (Optional) Concurrency type of deploying StackSets operations in Regions, could be in parallel or one Region at a time. Valid values are `SEQUENTIAL` and `PARALLEL`.
* `region_order` - (Optional) Order of the Regions in where you want to perform the stack operation.

## Attribute Reference

This resource exports the following attributes in addition to the arguments above:

* `id` - Unique identifier for the resource. If `deployment_targets` is set, this is a comma-delimited string combining stack set name, organizational unit IDs (`/`-delimited), and region (ie. `mystack,ou-123/ou-456,us-east-1`). Otherwise, this is a comma-delimited string combining stack set name, AWS account ID, and region (ie. `mystack,123456789012,us-east-1`).
* `organizational_unit_id` - The organization root ID or organizational unit (OU) ID in which the stack is deployed.
* `organizational_unit_id` - Organization root ID or organizational unit (OU) ID in which the stack is deployed.
* `stack_id` - Stack identifier.
* `stack_instance_summaries` - List of stack instances created from an organizational unit deployment target. This will only be populated when `deployment_targets` is set. See [`stack_instance_summaries`](#stack_instance_summaries-attribute-reference).

Expand Down
Loading