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

Add tags argument to resource/aws_aws_organizations_organizational_unit (#15476) #18861

Merged
merged 7 commits into from
Apr 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/18861.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_organizations_organizational_unit: Add `tags` argument
```
90 changes: 63 additions & 27 deletions aws/resource_aws_organizations_organizational_unit.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource"
)

func resourceAwsOrganizationsOrganizationalUnit() *schema.Resource {
Expand Down Expand Up @@ -64,55 +66,60 @@ func resourceAwsOrganizationsOrganizationalUnit() *schema.Resource {
Required: true,
ValidateFunc: validation.StringMatch(regexp.MustCompile("^(r-[0-9a-z]{4,32})|(ou-[0-9a-z]{4,32}-[a-z0-9]{8,32})$"), "see https://docs.aws.amazon.com/organizations/latest/APIReference/API_CreateOrganizationalUnit.html#organizations-CreateOrganizationalUnit-request-ParentId"),
},
"tags": tagsSchema(),
"tags_all": tagsSchemaComputed(),
},

CustomizeDiff: SetTagsDiff,
}
}

func resourceAwsOrganizationsOrganizationalUnitCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).organizationsconn
defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig
tags := defaultTagsConfig.MergeTags(keyvaluetags.New(d.Get("tags").(map[string]interface{})))

// Create the organizational unit
createOpts := &organizations.CreateOrganizationalUnitInput{
Name: aws.String(d.Get("name").(string)),
ParentId: aws.String(d.Get("parent_id").(string)),
Tags: tags.IgnoreAws().OrganizationsTags(),
}

log.Printf("[DEBUG] Organizational Unit create config: %#v", createOpts)

var err error
var resp *organizations.CreateOrganizationalUnitOutput
err = resource.Retry(4*time.Minute, func() *resource.RetryError {
resp, err = conn.CreateOrganizationalUnit(createOpts)

if err != nil {
if isAWSErr(err, organizations.ErrCodeFinalizingOrganizationException, "") {
log.Printf("[DEBUG] Trying to create organizational unit again: %q", err.Error())
return resource.RetryableError(err)
}
if tfawserr.ErrCodeEquals(err, organizations.ErrCodeFinalizingOrganizationException) {
return resource.RetryableError(err)
}

if err != nil {
return resource.NonRetryableError(err)
}

return nil
})
if isResourceTimeoutError(err) {
if tfresource.TimedOut(err) {
resp, err = conn.CreateOrganizationalUnit(createOpts)
}

if err != nil {
return fmt.Errorf("Error creating organizational unit: %s", err)
return fmt.Errorf("error creating Organizations Organizational Unit: %w", err)
}
log.Printf("[DEBUG] Organizational Unit create response: %#v", resp)

// Store the ID
ouId := resp.OrganizationalUnit.Id
d.SetId(aws.StringValue(ouId))
d.SetId(aws.StringValue(resp.OrganizationalUnit.Id))

return resourceAwsOrganizationsOrganizationalUnitRead(d, meta)
}

func resourceAwsOrganizationsOrganizationalUnitRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).organizationsconn
defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig
ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig

describeOpts := &organizations.DescribeOrganizationalUnitInput{
OrganizationalUnitId: aws.String(d.Id()),
}
Expand Down Expand Up @@ -149,8 +156,6 @@ func resourceAwsOrganizationsOrganizationalUnitRead(d *schema.ResourceData, meta
return fmt.Errorf("error listing Organizations Organizational Unit (%s) parents: %w", d.Id(), err)
}

log.Printf("[INFO] Listing Accounts for Organizational Unit: %s", d.Id())

var accounts []*organizations.Account
input := &organizations.ListAccountsForParentInput{
ParentId: aws.String(d.Id()),
Expand All @@ -163,34 +168,58 @@ func resourceAwsOrganizationsOrganizationalUnitRead(d *schema.ResourceData, meta
})

if err != nil {
return fmt.Errorf("error listing AWS Organizations Organizational Unit (%s) accounts: %s", d.Id(), err)
return fmt.Errorf("error listing Organizations Organizational Unit (%s) accounts: %w", d.Id(), err)
}

if err := d.Set("accounts", flattenOrganizationsOrganizationalUnitAccounts(accounts)); err != nil {
return fmt.Errorf("error setting accounts: %s", err)
return fmt.Errorf("error setting accounts: %w", err)
}

d.Set("arn", ou.Arn)
d.Set("name", ou.Name)
d.Set("parent_id", parentId)

tags, err := keyvaluetags.OrganizationsListTags(conn, d.Id())

if err != nil {
return fmt.Errorf("error listing tags for Organizations Organizational Unit (%s): %w", d.Id(), err)
}

tags = tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig)

//lintignore:AWSR002
if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil {
return fmt.Errorf("error setting tags: %w", err)
}

if err := d.Set("tags_all", tags.Map()); err != nil {
return fmt.Errorf("error setting tags_all: %w", err)
}

return nil
}

func resourceAwsOrganizationsOrganizationalUnitUpdate(d *schema.ResourceData, meta interface{}) error {
if d.HasChange("name") {
conn := meta.(*AWSClient).organizationsconn
conn := meta.(*AWSClient).organizationsconn

if d.HasChange("name") {
updateOpts := &organizations.UpdateOrganizationalUnitInput{
Name: aws.String(d.Get("name").(string)),
OrganizationalUnitId: aws.String(d.Id()),
}

log.Printf("[DEBUG] Organizational Unit update config: %#v", updateOpts)
resp, err := conn.UpdateOrganizationalUnit(updateOpts)
_, err := conn.UpdateOrganizationalUnit(updateOpts)
if err != nil {
return fmt.Errorf("Error creating organizational unit: %s", err)
return fmt.Errorf("error updating Organizations Organizational Unit (%s): %w", d.Id(), err)
}
}

if d.HasChange("tags_all") {
o, n := d.GetChange("tags_all")

if err := keyvaluetags.OrganizationsUpdateTags(conn, d.Id(), o, n); err != nil {
return fmt.Errorf("error updating Organizations Organizational Unit (%s) tags: %w", d.Id(), err)
}
log.Printf("[DEBUG] Organizational Unit update response: %#v", resp)
}

return nil
Expand All @@ -202,14 +231,17 @@ func resourceAwsOrganizationsOrganizationalUnitDelete(d *schema.ResourceData, me
input := &organizations.DeleteOrganizationalUnitInput{
OrganizationalUnitId: aws.String(d.Id()),
}
log.Printf("[DEBUG] Removing AWS organizational unit from organization: %s", input)

_, err := conn.DeleteOrganizationalUnit(input)

if tfawserr.ErrCodeEquals(err, organizations.ErrCodeOrganizationalUnitNotFoundException) {
return nil
}

if err != nil {
if isAWSErr(err, organizations.ErrCodeOrganizationalUnitNotFoundException, "") {
return nil
}
return err
return fmt.Errorf("error deleting Organizations Organizational Unit (%s): %w", d.Id(), err)
}

return nil
}

Expand All @@ -220,6 +252,10 @@ func resourceAwsOrganizationsOrganizationalUnitGetParentId(conn *organizations.O
var parents []*organizations.Parent

err := conn.ListParentsPages(input, func(page *organizations.ListParentsOutput, lastPage bool) bool {
if page == nil {
return !lastPage
}

parents = append(parents, page.Parents...)

return !lastPage
Expand Down
78 changes: 78 additions & 0 deletions aws/resource_aws_organizations_organizational_unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func testAccAwsOrganizationsOrganizationalUnit_basic(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "accounts.#", "0"),
testAccMatchResourceAttrGlobalARN(resourceName, "arn", "organizations", regexp.MustCompile(`ou/o-.+/ou-.+`)),
resource.TestCheckResourceAttr(resourceName, "name", name),
resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
),
},
{
Expand Down Expand Up @@ -104,6 +105,52 @@ func testAccAwsOrganizationsOrganizationalUnit_Name(t *testing.T) {
})
}

func testAccAwsOrganizationsOrganizationalUnit_Tags(t *testing.T) {
var unit organizations.OrganizationalUnit

rInt := acctest.RandInt()
name := fmt.Sprintf("tf_outest_%d", rInt)
resourceName := "aws_organizations_organizational_unit.test"

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t); testAccOrganizationsAccountPreCheck(t) },
ErrorCheck: testAccErrorCheck(t, organizations.EndpointsID),
Providers: testAccProviders,
CheckDestroy: testAccCheckAwsOrganizationsOrganizationalUnitDestroy,
Steps: []resource.TestStep{
{
Config: testAccAwsOrganizationsOrganizationalUnitConfigTags1(name, "key1", "value1"),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsOrganizationsOrganizationalUnitExists(resourceName, &unit),
resource.TestCheckResourceAttr(resourceName, "tags.%", "1"),
resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
{
Config: testAccAwsOrganizationsOrganizationalUnitConfigTags2(name, "key1", "value1updated", "key2", "value2"),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsOrganizationsOrganizationalUnitExists(resourceName, &unit),
resource.TestCheckResourceAttr(resourceName, "tags.%", "2"),
resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"),
resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"),
),
},
{
Config: testAccAwsOrganizationsOrganizationalUnitConfig(name),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsOrganizationsOrganizationalUnitExists(resourceName, &unit),
resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
),
},
},
})
}

func testAccCheckAwsOrganizationsOrganizationalUnitDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).organizationsconn

Expand Down Expand Up @@ -178,3 +225,34 @@ resource "aws_organizations_organizational_unit" "test" {
}
`, name)
}

func testAccAwsOrganizationsOrganizationalUnitConfigTags1(name, tagKey1, tagValue1 string) string {
return fmt.Sprintf(`
resource "aws_organizations_organization" "test" {}

resource "aws_organizations_organizational_unit" "test" {
name = %[1]q
parent_id = aws_organizations_organization.test.roots[0].id

tags = {
%[2]q = %[3]q
}
}
`, name, tagKey1, tagValue1)
}

func testAccAwsOrganizationsOrganizationalUnitConfigTags2(name, tagKey1, tagValue1, tagKey2, tagValue2 string) string {
return fmt.Sprintf(`
resource "aws_organizations_organization" "test" {}

resource "aws_organizations_organizational_unit" "test" {
name = %[1]q
parent_id = aws_organizations_organization.test.roots[0].id

tags = {
%[2]q = %[3]q
%[4]q = %[5]q
}
}
`, name, tagKey1, tagValue1, tagKey2, tagValue2)
}
1 change: 1 addition & 0 deletions aws/resource_aws_organizations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func TestAccAWSOrganizations_serial(t *testing.T) {
"basic": testAccAwsOrganizationsOrganizationalUnit_basic,
"disappears": testAccAwsOrganizationsOrganizationalUnit_disappears,
"Name": testAccAwsOrganizationsOrganizationalUnit_Name,
"Tags": testAccAwsOrganizationsOrganizationalUnit_Tags,
},
"OrganizationalUnits": {
"DataSource": testAccDataSourceAwsOrganizationsOrganizationalUnits_basic,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ The following arguments are supported:

* `name` - The name for the organizational unit
* `parent_id` - ID of the parent organizational unit, which may be the root
* `tags` - (Optional) Key-value map of resource tags. If configured with a provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level.

## Attributes Reference

Expand All @@ -37,6 +38,7 @@ In addition to all arguments above, the following attributes are exported:
* `name` - Name of the account
* `arn` - ARN of the organizational unit
* `id` - Identifier of the organization unit
* `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block).

## Import

Expand Down