Skip to content

Commit

Permalink
resource/aws_cloudformation_stack_set: Add support for SERVICE_MANAGE…
Browse files Browse the repository at this point in the history
…D permission model
  • Loading branch information
srikanthchelluri committed Mar 25, 2020
1 parent 04d24f8 commit 966cf04
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 18 deletions.
126 changes: 110 additions & 16 deletions aws/resource_aws_cloudformation_stack_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,38 @@ func resourceAwsCloudFormationStackSet() *schema.Resource {

Schema: map[string]*schema.Schema{
"administration_role_arn": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validateArn,
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"auto_deployment"},
ValidateFunc: validateArn,
},
"arn": {
Type: schema.TypeString,
Computed: true,
},
"auto_deployment": {
Type: schema.TypeList,
MinItems: 1,
MaxItems: 1,
Optional: true,
ForceNew: true,
ConflictsWith: []string{
"administration_role_arn",
"execution_role_name",
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"enabled": {
Type: schema.TypeBool,
Optional: true,
},
"retain_stacks_on_account_removal": {
Type: schema.TypeBool,
Optional: true,
},
},
},
},
"capabilities": {
Type: schema.TypeSet,
Optional: true,
Expand All @@ -57,9 +81,9 @@ func resourceAwsCloudFormationStackSet() *schema.Resource {
ValidateFunc: validation.StringLenBetween(0, 1024),
},
"execution_role_name": {
Type: schema.TypeString,
Optional: true,
Default: "AWSCloudFormationStackSetExecutionRole",
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"auto_deployment"},
},
"name": {
Type: schema.TypeString,
Expand All @@ -76,6 +100,15 @@ func resourceAwsCloudFormationStackSet() *schema.Resource {
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"permission_model": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{
cloudformation.PermissionModelsServiceManaged,
cloudformation.PermissionModelsSelfManaged,
}, false),
Default: cloudformation.PermissionModelsSelfManaged,
},
"stack_set_id": {
Type: schema.TypeString,
Computed: true,
Expand Down Expand Up @@ -103,10 +136,16 @@ func resourceAwsCloudFormationStackSetCreate(d *schema.ResourceData, meta interf
name := d.Get("name").(string)

input := &cloudformation.CreateStackSetInput{
AdministrationRoleARN: aws.String(d.Get("administration_role_arn").(string)),
ClientRequestToken: aws.String(resource.UniqueId()),
ExecutionRoleName: aws.String(d.Get("execution_role_name").(string)),
StackSetName: aws.String(name),
ClientRequestToken: aws.String(resource.UniqueId()),
StackSetName: aws.String(name),
}

if v, ok := d.GetOk("administration_role_arn"); ok {
input.AdministrationRoleARN = aws.String(v.(string))
}

if v, ok := d.GetOk("auto_deployment"); ok {
input.AutoDeployment = expandAutoDeployment(v.([]interface{}))
}

if v, ok := d.GetOk("capabilities"); ok {
Expand All @@ -117,10 +156,18 @@ func resourceAwsCloudFormationStackSetCreate(d *schema.ResourceData, meta interf
input.Description = aws.String(v.(string))
}

if v, ok := d.GetOk("execution_role_name"); ok {
input.ExecutionRoleName = aws.String(v.(string))
}

if v, ok := d.GetOk("parameters"); ok {
input.Parameters = expandCloudFormationParameters(v.(map[string]interface{}))
}

if v, ok := d.GetOk("permission_model"); ok {
input.PermissionModel = aws.String(v.(string))
}

if v, ok := d.GetOk("tags"); ok {
input.Tags = keyvaluetags.New(v.(map[string]interface{})).IgnoreAws().CloudformationTags()
}
Expand Down Expand Up @@ -174,13 +221,18 @@ func resourceAwsCloudFormationStackSetRead(d *schema.ResourceData, meta interfac
d.Set("administration_role_arn", stackSet.AdministrationRoleARN)
d.Set("arn", stackSet.StackSetARN)

if err := d.Set("auto_deployment", flattenStackSetAutoDeploymentResponse(stackSet.AutoDeployment)); err != nil {
return fmt.Errorf("error setting auto_deployment: %s", err)
}

if err := d.Set("capabilities", aws.StringValueSlice(stackSet.Capabilities)); err != nil {
return fmt.Errorf("error setting capabilities: %s", err)
}

d.Set("description", stackSet.Description)
d.Set("execution_role_name", stackSet.ExecutionRoleName)
d.Set("name", stackSet.StackSetName)
d.Set("permission_model", stackSet.PermissionModel)

if err := d.Set("parameters", flattenAllCloudFormationParameters(stackSet.Parameters)); err != nil {
return fmt.Errorf("error setting parameters: %s", err)
Expand All @@ -201,12 +253,18 @@ func resourceAwsCloudFormationStackSetUpdate(d *schema.ResourceData, meta interf
conn := meta.(*AWSClient).cfconn

input := &cloudformation.UpdateStackSetInput{
AdministrationRoleARN: aws.String(d.Get("administration_role_arn").(string)),
ExecutionRoleName: aws.String(d.Get("execution_role_name").(string)),
OperationId: aws.String(resource.UniqueId()),
StackSetName: aws.String(d.Id()),
Tags: []*cloudformation.Tag{},
TemplateBody: aws.String(d.Get("template_body").(string)),
OperationId: aws.String(resource.UniqueId()),
StackSetName: aws.String(d.Id()),
Tags: []*cloudformation.Tag{},
TemplateBody: aws.String(d.Get("template_body").(string)),
}

if v, ok := d.GetOk("administration_role_arn"); ok {
input.AdministrationRoleARN = aws.String(v.(string))
}

if v, ok := d.GetOk("auto_deployment"); ok {
input.AutoDeployment = expandAutoDeployment(v.([]interface{}))
}

if v, ok := d.GetOk("capabilities"); ok {
Expand All @@ -217,10 +275,18 @@ func resourceAwsCloudFormationStackSetUpdate(d *schema.ResourceData, meta interf
input.Description = aws.String(v.(string))
}

if v, ok := d.GetOk("execution_role_name"); ok {
input.ExecutionRoleName = aws.String(v.(string))
}

if v, ok := d.GetOk("parameters"); ok {
input.Parameters = expandCloudFormationParameters(v.(map[string]interface{}))
}

if v, ok := d.GetOk("permission_model"); ok {
input.PermissionModel = aws.String(v.(string))
}

if v, ok := d.GetOk("tags"); ok {
input.Tags = keyvaluetags.New(v.(map[string]interface{})).IgnoreAws().CloudformationTags()
}
Expand Down Expand Up @@ -291,3 +357,31 @@ func listCloudFormationStackSets(conn *cloudformation.CloudFormation) ([]*cloudf

return result, nil
}

func expandAutoDeployment(l []interface{}) *cloudformation.AutoDeployment {
if len(l) == 0 {
return nil
}

m := l[0].(map[string]interface{})

autoDeployment := &cloudformation.AutoDeployment{
Enabled: aws.Bool(m["enabled"].(bool)),
RetainStacksOnAccountRemoval: aws.Bool(m["retain_stacks_on_account_removal"].(bool)),
}

return autoDeployment
}

func flattenStackSetAutoDeploymentResponse(autoDeployment *cloudformation.AutoDeployment) []map[string]interface{} {
if autoDeployment == nil {
return []map[string]interface{}{}
}

m := map[string]interface{}{
"enabled": aws.BoolValue(autoDeployment.Enabled),
"retain_stacks_on_account_removal": aws.BoolValue(autoDeployment.RetainStacksOnAccountRemoval),
}

return []map[string]interface{}{m}
}
54 changes: 54 additions & 0 deletions aws/resource_aws_cloudformation_stack_set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ func TestAccAWSCloudFormationStackSet_basic(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "execution_role_name", "AWSCloudFormationStackSetExecutionRole"),
resource.TestCheckResourceAttr(resourceName, "name", rName),
resource.TestCheckResourceAttr(resourceName, "parameters.%", "0"),
resource.TestCheckResourceAttr(resourceName, "permission_model", "SELF_MANAGED"),
resource.TestMatchResourceAttr(resourceName, "stack_set_id", regexp.MustCompile(fmt.Sprintf("%s:.+", rName))),
resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
resource.TestCheckResourceAttr(resourceName, "template_body", testAccAWSCloudFormationStackSetTemplateBodyVpc(rName)+"\n"),
Expand Down Expand Up @@ -447,6 +448,40 @@ func TestAccAWSCloudFormationStackSet_Parameters_NoEcho(t *testing.T) {
})
}

func TestAccAWSCloudFormationStackSet_PermissionModel_ServiceManaged(t *testing.T) {
var stackSet1 cloudformation.StackSet
rName := acctest.RandomWithPrefix("tf-acc-test")
resourceName := "aws_cloudformation_stack_set.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSCloudFormationStackSet(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSCloudFormationStackSetDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSCloudFormationStackSetConfigPermissionModel(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudFormationStackSetExists(resourceName, &stackSet1),
testAccMatchResourceAttrRegionalARN(resourceName, "arn", "cloudformation", regexp.MustCompile(`stackset/.+`)),
resource.TestCheckResourceAttr(resourceName, "permission_model", "SERVICE_MANAGED"),
resource.TestCheckResourceAttr(resourceName, "auto_deployment.#", "1"),
resource.TestCheckResourceAttr(resourceName, "auto_deployment.0.enabled", "true"),
resource.TestCheckResourceAttr(resourceName, "auto_deployment.0.retain_stacks_on_account_removal", "false"),
resource.TestMatchResourceAttr(resourceName, "stack_set_id", regexp.MustCompile(fmt.Sprintf("%s:.+", rName))),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
"template_url",
},
},
},
})
}

func TestAccAWSCloudFormationStackSet_Tags(t *testing.T) {
var stackSet1, stackSet2 cloudformation.StackSet
rName := acctest.RandomWithPrefix("tf-acc-test")
Expand Down Expand Up @@ -1141,3 +1176,22 @@ resource "aws_cloudformation_stack_set" "test" {
}
`, rName, testAccAWSCloudFormationStackSetTemplateBodyVpc(rName+"2"))
}

func testAccAWSCloudFormationStackSetConfigPermissionModel(rName string) string {
return fmt.Sprintf(`
resource "aws_cloudformation_stack_set" "test" {
name = %[1]q
permission_model = "SERVICE_MANAGED"
auto_deployment {
enabled = true
retain_stacks_on_account_removal = false
}
template_body = <<TEMPLATE
%[2]s
TEMPLATE
}
`, rName, testAccAWSCloudFormationStackSetTemplateBodyVpc(rName))
}
8 changes: 6 additions & 2 deletions website/docs/r/cloudformation_stack_set.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,16 @@ resource "aws_iam_role_policy" "AWSCloudFormationStackSetAdministrationRole_Exec

The following arguments are supported:

* `administration_role_arn` - (Required) Amazon Resource Number (ARN) of the IAM Role in the administrator account.
* `administration_role_arn` - (Optional) Amazon Resource Number (ARN) of the IAM Role in the administrator account. This must be defined when using the `SELF_MANAGED` permission model.
* `auto_deployment` - (Optional) Nested attribute containing the auto-deployment model for your StackSet. This can only be defined when using the `SERVICE_MANAGED` permission model.
* `enabled` - Whether or not auto-deployment is enabled.
* `retain_stacks_on_account_removal` - Whether or not to retain stacks when the account is removed.
* `name` - (Required) Name of the StackSet. The name must be unique in the region where you create your StackSet. The name can contain only alphanumeric characters (case-sensitive) and hyphens. It must start with an alphabetic character and cannot be longer than 128 characters.
* `capabilities` - (Optional) A list of capabilities. Valid values: `CAPABILITY_IAM`, `CAPABILITY_NAMED_IAM`, `CAPABILITY_AUTO_EXPAND`.
* `description` - (Optional) Description of the StackSet.
* `execution_role_name` - (Optional) Name of the IAM Role in all target accounts for StackSet operations. Defaults to `AWSCloudFormationStackSetExecutionRole`.
* `execution_role_name` - (Optional) Name of the IAM Role in all target accounts for StackSet operations. Defaults to `AWSCloudFormationStackSetExecutionRole` when using the `SELF_MANAGED` permission model. This should not be defined when using the `SERVICE_MANAGED` permission model.
* `parameters` - (Optional) Key-value map of input parameters for the StackSet template. All template parameters, including those with a `Default`, must be configured or ignored with `lifecycle` configuration block `ignore_changes` argument. All `NoEcho` template parameters must be ignored with the `lifecycle` configuration block `ignore_changes` argument.
* `permission_model` - (Optional) Describes how the IAM roles required for your StackSet are created. Valid values: `SELF_MANAGED` (default), `SERVICE_MANAGED`.
* `tags` - (Optional) Key-value map of tags to associate with this StackSet and the Stacks created from it. AWS CloudFormation also propagates these tags to supported resources that are created in the Stacks. A maximum number of 50 tags can be specified.
* `template_body` - (Optional) String containing the CloudFormation template body. Maximum size: 51,200 bytes. Conflicts with `template_url`.
* `template_url` - (Optional) String containing the location of a file containing the CloudFormation template body. The URL must point to a template that is located in an Amazon S3 bucket. Maximum location file size: 460,800 bytes. Conflicts with `template_body`.
Expand Down

0 comments on commit 966cf04

Please sign in to comment.