[Bug]: aws_cloudformation_stack_set_instance operation error when targeting OU #40675
Description
Terraform Core Version
1.9.8
AWS Provider Version
5.82.2
Affected Resource(s)
- aws_cloudformation_stack_set_instance
Expected Behavior
When updating parameters in the resource with deployment_targets set to an OU rather than an account, it should succeed to update.
Actual Behavior
If deployment_targets
is set only to an OU and not an account, there is a validation error as the provider is sending the OU ID through as an account ID.
Relevant Error/Panic Output Snippet
aws_cloudformation_stack_set_instance.default: Modifying... [id=TestStackSet,ou-1abc-abcdefg1,us-east-1]
╷
│ Error: updating CloudFormation StackSet Instance (TestStackSet,ou-1abc-abcdefg1,us-east-1): operation error CloudFormation: UpdateStackInstances, https response error StatusCode: 400, RequestID: fa70f28e-........, api error ValidationError: 1 validation error detected: Value '[ou-1abc-abcdefg1]' at 'accounts' failed to satisfy constraint: Member must satisfy constraint: [Member must have length less than or equal to 12, Member must have length greater than or equal to 12, Member must satisfy regular expression pattern: ^[0-9]{12}$]
│
│ with aws_cloudformation_stack_set_instance.default,
│ on cf.tf line 63, in resource "aws_cloudformation_stack_set_instance" "default":
│ 63: resource "aws_cloudformation_stack_set_instance" "default" {
│
╵
Terraform Configuration Files
resource "aws_cloudformation_stack_set" "default" {
name = "TestStackSet"
description = "Testing"
permission_model = "SERVICE_MANAGED"
capabilities = [
"CAPABILITY_NAMED_IAM"
]
auto_deployment {
enabled = true
retain_stacks_on_account_removal = false
}
managed_execution {
active = true
}
tags = {
Name = "TestStackSet"
}
lifecycle {
ignore_changes = [
administration_role_arn
]
}
template_body = jsonencode(
{
AWSTemplateFormatVersion = "2010-09-09"
Description = "Testing"
Resources = {
Role = {
Type = "AWS::IAM::Role"
Properties = {
RoleName = "TestRole"
AssumeRolePolicyDocument = jsonencode(
{
Statement = [
{
Sid = "Foobar"
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
}
]
}
)
}
}
}
}
)
}
resource "aws_cloudformation_stack_set_instance" "default" {
deployment_targets {
organizational_unit_ids = [
"ou-1abc-abcdefg1"
]
}
operation_preferences {
failure_tolerance_percentage = 100
max_concurrent_percentage = 100
concurrency_mode = "SOFT_FAILURE_TOLERANCE"
region_concurrency_type = "PARALLEL"
}
region = local.region
stack_set_name = aws_cloudformation_stack_set.default.name
}
Steps to Reproduce
- Create a state with the above code, commenting out the
operation_preferences
section of thestack_set_instance
- Make sure to target an OU, NOT an account!
- Run
terraform apply
to create the stack set and stack set instance - Uncomment the
operation_preferences
(or make any other change to thestack_set_instance
) - Run
terraform apply
- See error message in account validation
Debug Output
No response
Panic Output
No response
Important Factoids
This appears to be an issue with the resourceStackSetInstanceUpdate function in the code, as the only option is to pass in Accounts
: https://github.com/hashicorp/terraform-provider-aws/blob/main/internal/service/cloudformation/stack_set_instance.go#L418-L424
The value getting passed into accounts is being pulled from the resource ID, but if you target an OU or two, the resource ID does not have an account in it.
Based on the golang spec, it appears that simply changing from Accounts
to DeploymentTargets should suffice.
Part of me wonders if there is a larger issue with how the resource ID is being created and used to figure out the targets, as it assumes that there will only ever be 1 value passed into the input, but if more than one account or OU is provided, the object includes them /
separated, but the input validation does not split them back into a list.
References
No response
Would you like to implement a fix?
Yes