From 2d76e9f03984085afb17194e15b57148f7c3d148 Mon Sep 17 00:00:00 2001 From: Yusuke Hamano <72236227+yhamano0312@users.noreply.github.com> Date: Mon, 10 Jun 2024 17:47:40 +0900 Subject: [PATCH 1/7] add additional deploymenttargets config and changes are made to the deployment_targets parameter, it will be recreated. --- .../cloudformation/stack_set_instance.go | 67 +++++++++++-------- .../cloudformation/stack_set_instance_test.go | 7 ++ 2 files changed, 47 insertions(+), 27 deletions(-) diff --git a/internal/service/cloudformation/stack_set_instance.go b/internal/service/cloudformation/stack_set_instance.go index a7edde6fbfa..02dfe22c42c 100644 --- a/internal/service/cloudformation/stack_set_instance.go +++ b/internal/service/cloudformation/stack_set_instance.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "log" + "regexp" "slices" "strings" "time" @@ -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}, @@ -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)) } @@ -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) @@ -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{})) } @@ -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{} { diff --git a/internal/service/cloudformation/stack_set_instance_test.go b/internal/service/cloudformation/stack_set_instance_test.go index 08ee3a87120..8d79f9b963d 100644 --- a/internal/service/cloudformation/stack_set_instance_test.go +++ b/internal/service/cloudformation/stack_set_instance_test.go @@ -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", ""), ), }, { @@ -228,6 +231,7 @@ func TestAccCloudFormationStackSetInstance_deploymentTargets(t *testing.T) { ImportStateVerifyIgnore: []string{ "retain_stack", "call_as", + "deployment_targets", }, }, { @@ -273,6 +277,7 @@ func TestAccCloudFormationStackSetInstance_DeploymentTargets_emptyOU(t *testing. ImportStateVerifyIgnore: []string{ "retain_stack", "call_as", + "deployment_targets", }, }, { @@ -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 From c66b5d5e683e0accf3842600dab4a9cab498049b Mon Sep 17 00:00:00 2001 From: Yusuke Hamano <72236227+yhamano0312@users.noreply.github.com> Date: Mon, 10 Jun 2024 18:00:57 +0900 Subject: [PATCH 2/7] update docs --- .../docs/r/cloudformation_stack_set_instance.html.markdown | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/docs/r/cloudformation_stack_set_instance.html.markdown b/website/docs/r/cloudformation_stack_set_instance.html.markdown index 9fed412cb58..fbc20083522 100644 --- a/website/docs/r/cloudformation_stack_set_instance.html.markdown +++ b/website/docs/r/cloudformation_stack_set_instance.html.markdown @@ -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: From ab2f7e085125dc7ac379cb1862c3bbf41c0e6f00 Mon Sep 17 00:00:00 2001 From: Yusuke Hamano <72236227+yhamano0312@users.noreply.github.com> Date: Mon, 10 Jun 2024 21:57:06 +0900 Subject: [PATCH 3/7] add changelog --- .changelog/37898.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changelog/37898.txt diff --git a/.changelog/37898.txt b/.changelog/37898.txt new file mode 100644 index 00000000000..34c17b17d91 --- /dev/null +++ b/.changelog/37898.txt @@ -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: Fix to ensure a new resource is recreated when the deployment_targets argument is changed, which was not the case previously. +``` From e6b4baa716ffe22624206f3d6b9891726aa7e1cd Mon Sep 17 00:00:00 2001 From: Sharon Nam Date: Tue, 2 Jul 2024 12:13:18 -0700 Subject: [PATCH 4/7] Doc fixes --- ...formation_stack_set_instance.html.markdown | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/website/docs/r/cloudformation_stack_set_instance.html.markdown b/website/docs/r/cloudformation_stack_set_instance.html.markdown index fbc20083522..46c24a1699e 100644 --- a/website/docs/r/cloudformation_stack_set_instance.html.markdown +++ b/website/docs/r/cloudformation_stack_set_instance.html.markdown @@ -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`. @@ -98,27 +98,27 @@ 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) The list of accounts to deploy stack set updates. -* `accounts_url` - (Optional) The S3 URL of the file containing the list of accounts. +* `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). From d3e60009ef5a945176d561b4e813fbb94f2954cb Mon Sep 17 00:00:00 2001 From: Sharon Nam Date: Tue, 2 Jul 2024 14:37:08 -0700 Subject: [PATCH 5/7] Linter fixes --- internal/service/cloudformation/stack_set_instance.go | 8 ++++---- .../service/cloudformation/stack_set_instance_test.go | 4 ++-- .../r/cloudformation_stack_set_instance.html.markdown | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/internal/service/cloudformation/stack_set_instance.go b/internal/service/cloudformation/stack_set_instance.go index 02dfe22c42c..1cfdf51f52f 100644 --- a/internal/service/cloudformation/stack_set_instance.go +++ b/internal/service/cloudformation/stack_set_instance.go @@ -81,7 +81,7 @@ func resourceStackSetInstance() *schema.Resource { Optional: true, ForceNew: true, MinItems: 1, - ConflictsWith: []string{"account_id"}, + 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})$`), ""), @@ -92,13 +92,13 @@ func resourceStackSetInstance() *schema.Resource { Optional: true, ForceNew: true, ValidateFunc: validation.StringInSlice(enum.Slice(awstypes.AccountFilterType.Values("")...), false), - ConflictsWith: []string{"account_id"}, + ConflictsWith: []string{names.AttrAccountID}, }, "accounts": { Type: schema.TypeSet, Optional: true, ForceNew: true, - ConflictsWith: []string{"account_id"}, + ConflictsWith: []string{names.AttrAccountID}, MinItems: 1, Elem: &schema.Schema{ Type: schema.TypeString, @@ -109,7 +109,7 @@ func resourceStackSetInstance() *schema.Resource { Type: schema.TypeString, ForceNew: true, Optional: true, - ConflictsWith: []string{"account_id"}, + ConflictsWith: []string{names.AttrAccountID}, ValidateFunc: validation.StringMatch(regexp.MustCompile(`(s3://|http(s?)://).+`), ""), }, }, diff --git a/internal/service/cloudformation/stack_set_instance_test.go b/internal/service/cloudformation/stack_set_instance_test.go index 8d79f9b963d..255935248e4 100644 --- a/internal/service/cloudformation/stack_set_instance_test.go +++ b/internal/service/cloudformation/stack_set_instance_test.go @@ -817,8 +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] + account_filter_type = "INTERSECTION" + accounts = [data.aws_organizations_organization.test.non_master_accounts[0].id] } stack_set_name = aws_cloudformation_stack_set.test.name diff --git a/website/docs/r/cloudformation_stack_set_instance.html.markdown b/website/docs/r/cloudformation_stack_set_instance.html.markdown index 46c24a1699e..e180b086d57 100644 --- a/website/docs/r/cloudformation_stack_set_instance.html.markdown +++ b/website/docs/r/cloudformation_stack_set_instance.html.markdown @@ -102,6 +102,7 @@ The `deployment_targets` configuration block supports the following arguments: * `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: From 51547bf346f7c1ddc0ead727ae18d4d7dfe625c2 Mon Sep 17 00:00:00 2001 From: Sharon Nam Date: Tue, 2 Jul 2024 15:03:56 -0700 Subject: [PATCH 6/7] use regexcache --- internal/service/cloudformation/stack_set_instance.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/service/cloudformation/stack_set_instance.go b/internal/service/cloudformation/stack_set_instance.go index 1cfdf51f52f..de0f381e6e5 100644 --- a/internal/service/cloudformation/stack_set_instance.go +++ b/internal/service/cloudformation/stack_set_instance.go @@ -7,7 +7,6 @@ import ( "context" "fmt" "log" - "regexp" "slices" "strings" "time" @@ -110,7 +109,7 @@ func resourceStackSetInstance() *schema.Resource { ForceNew: true, Optional: true, ConflictsWith: []string{names.AttrAccountID}, - ValidateFunc: validation.StringMatch(regexp.MustCompile(`(s3://|http(s?)://).+`), ""), + ValidateFunc: validation.StringMatch(regexache.MustCompile(`(s3://|http(s?)://).+`), ""), }, }, }, From 32a7ce46252bda5522791130259f739ff2d91ff1 Mon Sep 17 00:00:00 2001 From: Sharon Nam Date: Fri, 5 Jul 2024 12:29:29 -0700 Subject: [PATCH 7/7] Add to changelog --- .changelog/37898.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changelog/37898.txt b/.changelog/37898.txt index 34c17b17d91..5b228b6e1ed 100644 --- a/.changelog/37898.txt +++ b/.changelog/37898.txt @@ -3,5 +3,5 @@ resource/aws_cloudformation_stack_set_instance: Extend `deployment_targets` argu ``` ```release-note:bug -resource/aws_cloudformation_stack_set_instance: Fix to ensure a new resource is recreated when the deployment_targets argument is changed, which was not the case previously. +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. ```