Skip to content

[Bug]: aws_cloudformation_stack_set_instance operation error when targeting OU #40675

Open
@nomeelnoj

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

  1. Create a state with the above code, commenting out the operation_preferences section of the stack_set_instance
  2. Make sure to target an OU, NOT an account!
  3. Run terraform apply to create the stack set and stack set instance
  4. Uncomment the operation_preferences (or make any other change to the stack_set_instance)
  5. Run terraform apply
  6. 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

Metadata

Assignees

No one assigned

    Labels

    bugAddresses a defect in current functionality.service/cloudformationIssues and PRs that pertain to the cloudformation service.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions