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

rd/iam_policy - tagging support #18276

Merged
merged 11 commits into from
Mar 26, 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
11 changes: 11 additions & 0 deletions .changelog/18276.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
```release-note:enhancement
resource/aws_iam_policy: Add tagging support
```

```release-note:enhancement
resource/aws_iam_policy: Add `policy_id` attribute
```

```release-note:enhancement
data-source/aws_iam_policy: Add `policy_id` and `tags` attributes
```
5 changes: 5 additions & 0 deletions aws/data_source_aws_iam_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ func dataSourceAwsIAMPolicy() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"policy_id": {
Type: schema.TypeString,
Computed: true,
},
"tags": tagsSchemaComputed(),
},
}
}
Expand Down
19 changes: 11 additions & 8 deletions aws/data_source_aws_iam_policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import (
)

func TestAccAWSDataSourceIAMPolicy_basic(t *testing.T) {
resourceName := "data.aws_iam_policy.test"
datasourceName := "data.aws_iam_policy.test"
resourceName := "aws_iam_policy.test"
policyName := fmt.Sprintf("test-policy-%s", acctest.RandString(10))

resource.ParallelTest(t, resource.TestCase{
Expand All @@ -21,11 +22,13 @@ func TestAccAWSDataSourceIAMPolicy_basic(t *testing.T) {
{
Config: testAccAwsDataSourceIamPolicyConfig(policyName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "name", policyName),
resource.TestCheckResourceAttr(resourceName, "description", "My test policy"),
resource.TestCheckResourceAttr(resourceName, "path", "/"),
resource.TestCheckResourceAttrSet(resourceName, "policy"),
resource.TestCheckResourceAttrPair(resourceName, "arn", "aws_iam_policy.test_policy", "arn"),
resource.TestCheckResourceAttrPair(datasourceName, "name", resourceName, "name"),
resource.TestCheckResourceAttrPair(datasourceName, "description", resourceName, "description"),
resource.TestCheckResourceAttrPair(datasourceName, "path", resourceName, "path"),
resource.TestCheckResourceAttrPair(datasourceName, "policy", resourceName, "policy"),
resource.TestCheckResourceAttrPair(datasourceName, "policy_id", resourceName, "policy_id"),
resource.TestCheckResourceAttrPair(datasourceName, "arn", resourceName, "arn"),
resource.TestCheckResourceAttrPair(datasourceName, "tags", resourceName, "tags"),
),
},
},
Expand All @@ -35,7 +38,7 @@ func TestAccAWSDataSourceIAMPolicy_basic(t *testing.T) {

func testAccAwsDataSourceIamPolicyConfig(policyName string) string {
return fmt.Sprintf(`
resource "aws_iam_policy" "test_policy" {
resource "aws_iam_policy" "test" {
name = "%s"
path = "/"
description = "My test policy"
Expand All @@ -57,7 +60,7 @@ EOF
}

data "aws_iam_policy" "test" {
arn = aws_iam_policy.test_policy.arn
arn = aws_iam_policy.test.arn
}
`, policyName)
}
35 changes: 35 additions & 0 deletions aws/internal/keyvaluetags/iam_tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,41 @@ func IamOpenIDConnectProviderUpdateTags(conn *iam.IAM, identifier string, oldTag
return nil
}

// IamPolicyUpdateTags updates IAM Policy tags.
// The identifier is the Policy ARN.
func IamPolicyUpdateTags(conn *iam.IAM, identifier string, oldTagsMap interface{}, newTagsMap interface{}) error {
oldTags := New(oldTagsMap)
newTags := New(newTagsMap)

if removedTags := oldTags.Removed(newTags); len(removedTags) > 0 {
input := &iam.UntagPolicyInput{
PolicyArn: aws.String(identifier),
TagKeys: aws.StringSlice(removedTags.Keys()),
}

_, err := conn.UntagPolicy(input)

if err != nil {
return fmt.Errorf("error untagging resource (%s): %w", identifier, err)
}
}

if updatedTags := oldTags.Updated(newTags); len(updatedTags) > 0 {
input := &iam.TagPolicyInput{
PolicyArn: aws.String(identifier),
Tags: updatedTags.IgnoreAws().IamTags(),
}

_, err := conn.TagPolicy(input)

if err != nil {
return fmt.Errorf("error tagging resource (%s): %w", identifier, err)
}
}

return nil
}

// IamSAMLProviderUpdateTags updates IAM SAML Provider tags.
// The identifier is the SAML Provider ARN.
func IamSAMLProviderUpdateTags(conn *iam.IAM, identifier string, oldTagsMap interface{}, newTagsMap interface{}) error {
Expand Down
109 changes: 67 additions & 42 deletions aws/resource_aws_iam_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ 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/service/iam/waiter"
)

Expand Down Expand Up @@ -67,12 +68,17 @@ func resourceAwsIamPolicy() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"policy_id": {
Type: schema.TypeString,
Computed: true,
},
"tags": tagsSchema(),
},
}
}

func resourceAwsIamPolicyCreate(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
conn := meta.(*AWSClient).iamconn

var name string
if v, ok := d.GetOk("name"); ok {
Expand All @@ -88,11 +94,12 @@ func resourceAwsIamPolicyCreate(d *schema.ResourceData, meta interface{}) error
Path: aws.String(d.Get("path").(string)),
PolicyDocument: aws.String(d.Get("policy").(string)),
PolicyName: aws.String(name),
Tags: keyvaluetags.New(d.Get("tags").(map[string]interface{})).IgnoreAws().IamTags(),
}

response, err := iamconn.CreatePolicy(request)
response, err := conn.CreatePolicy(request)
if err != nil {
return fmt.Errorf("Error creating IAM policy %s: %s", name, err)
return fmt.Errorf("Error creating IAM policy %s: %w", name, err)
}

d.SetId(aws.StringValue(response.Policy.Arn))
Expand All @@ -101,7 +108,8 @@ func resourceAwsIamPolicyCreate(d *schema.ResourceData, meta interface{}) error
}

func resourceAwsIamPolicyRead(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
conn := meta.(*AWSClient).iamconn
ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig

getPolicyRequest := &iam.GetPolicyInput{
PolicyArn: aws.String(d.Id()),
Expand All @@ -112,7 +120,7 @@ func resourceAwsIamPolicyRead(d *schema.ResourceData, meta interface{}) error {
var getPolicyResponse *iam.GetPolicyOutput
err := resource.Retry(waiter.PropagationTimeout, func() *resource.RetryError {
var err error
getPolicyResponse, err = iamconn.GetPolicy(getPolicyRequest)
getPolicyResponse, err = conn.GetPolicy(getPolicyRequest)

if d.IsNewResource() && isAWSErr(err, iam.ErrCodeNoSuchEntityException, "") {
return resource.RetryableError(err)
Expand All @@ -125,7 +133,7 @@ func resourceAwsIamPolicyRead(d *schema.ResourceData, meta interface{}) error {
return nil
})
if isResourceTimeoutError(err) {
getPolicyResponse, err = iamconn.GetPolicy(getPolicyRequest)
getPolicyResponse, err = conn.GetPolicy(getPolicyRequest)
}
if isAWSErr(err, iam.ErrCodeNoSuchEntityException, "") {
log.Printf("[WARN] IAM Policy (%s) not found, removing from state", d.Id())
Expand All @@ -134,7 +142,7 @@ func resourceAwsIamPolicyRead(d *schema.ResourceData, meta interface{}) error {
}

if err != nil {
return fmt.Errorf("Error reading IAM policy %s: %s", d.Id(), err)
return fmt.Errorf("Error reading IAM policy %s: %w", d.Id(), err)
}

if getPolicyResponse == nil || getPolicyResponse.Policy == nil {
Expand All @@ -143,24 +151,30 @@ func resourceAwsIamPolicyRead(d *schema.ResourceData, meta interface{}) error {
return nil
}

d.Set("arn", getPolicyResponse.Policy.Arn)
d.Set("description", getPolicyResponse.Policy.Description)
d.Set("name", getPolicyResponse.Policy.PolicyName)
d.Set("path", getPolicyResponse.Policy.Path)
policyRes := getPolicyResponse.Policy
d.Set("arn", policyRes.Arn)
d.Set("description", policyRes.Description)
d.Set("name", policyRes.PolicyName)
d.Set("path", policyRes.Path)
d.Set("policy_id", policyRes.PolicyId)

if err := d.Set("tags", keyvaluetags.IamKeyValueTags(policyRes.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil {
return fmt.Errorf("error setting tags: %w", err)
}

// Retrieve policy

getPolicyVersionRequest := &iam.GetPolicyVersionInput{
PolicyArn: aws.String(d.Id()),
VersionId: getPolicyResponse.Policy.DefaultVersionId,
VersionId: policyRes.DefaultVersionId,
}
log.Printf("[DEBUG] Getting IAM Policy Version: %s", getPolicyVersionRequest)

// Handle IAM eventual consistency
var getPolicyVersionResponse *iam.GetPolicyVersionOutput
err = resource.Retry(waiter.PropagationTimeout, func() *resource.RetryError {
var err error
getPolicyVersionResponse, err = iamconn.GetPolicyVersion(getPolicyVersionRequest)
getPolicyVersionResponse, err = conn.GetPolicyVersion(getPolicyVersionRequest)

if isAWSErr(err, iam.ErrCodeNoSuchEntityException, "") {
return resource.RetryableError(err)
Expand All @@ -173,7 +187,7 @@ func resourceAwsIamPolicyRead(d *schema.ResourceData, meta interface{}) error {
return nil
})
if isResourceTimeoutError(err) {
getPolicyVersionResponse, err = iamconn.GetPolicyVersion(getPolicyVersionRequest)
getPolicyVersionResponse, err = conn.GetPolicyVersion(getPolicyVersionRequest)
}
if isAWSErr(err, iam.ErrCodeNoSuchEntityException, "") {
log.Printf("[WARN] IAM Policy (%s) not found, removing from state", d.Id())
Expand All @@ -182,15 +196,15 @@ func resourceAwsIamPolicyRead(d *schema.ResourceData, meta interface{}) error {
}

if err != nil {
return fmt.Errorf("Error reading IAM policy version %s: %s", d.Id(), err)
return fmt.Errorf("Error reading IAM policy version %s: %w", d.Id(), err)
}

policy := ""
if getPolicyVersionResponse != nil && getPolicyVersionResponse.PolicyVersion != nil {
var err error
policy, err = url.QueryUnescape(aws.StringValue(getPolicyVersionResponse.PolicyVersion.Document))
if err != nil {
return fmt.Errorf("error parsing policy: %s", err)
return fmt.Errorf("error parsing policy: %w", err)
}
}

Expand All @@ -200,41 +214,52 @@ func resourceAwsIamPolicyRead(d *schema.ResourceData, meta interface{}) error {
}

func resourceAwsIamPolicyUpdate(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
conn := meta.(*AWSClient).iamconn

if err := iamPolicyPruneVersions(d.Id(), iamconn); err != nil {
return err
}
if d.HasChangeExcept("tags") {

request := &iam.CreatePolicyVersionInput{
PolicyArn: aws.String(d.Id()),
PolicyDocument: aws.String(d.Get("policy").(string)),
SetAsDefault: aws.Bool(true),
if err := iamPolicyPruneVersions(d.Id(), conn); err != nil {
return err
}

request := &iam.CreatePolicyVersionInput{
PolicyArn: aws.String(d.Id()),
PolicyDocument: aws.String(d.Get("policy").(string)),
SetAsDefault: aws.Bool(true),
}

if _, err := conn.CreatePolicyVersion(request); err != nil {
return fmt.Errorf("Error updating IAM policy %s: %w", d.Id(), err)
}
}

if _, err := iamconn.CreatePolicyVersion(request); err != nil {
return fmt.Errorf("Error updating IAM policy %s: %s", d.Id(), err)
if d.HasChange("tags") {
o, n := d.GetChange("tags")

if err := keyvaluetags.IamPolicyUpdateTags(conn, d.Id(), o, n); err != nil {
return fmt.Errorf("error updating tags for IAM Policy (%s): %w", d.Id(), err)
}
}

return resourceAwsIamPolicyRead(d, meta)
}

func resourceAwsIamPolicyDelete(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
conn := meta.(*AWSClient).iamconn

if err := iamPolicyDeleteNondefaultVersions(d.Id(), iamconn); err != nil {
if err := iamPolicyDeleteNondefaultVersions(d.Id(), conn); err != nil {
return err
}

request := &iam.DeletePolicyInput{
PolicyArn: aws.String(d.Id()),
}

if _, err := iamconn.DeletePolicy(request); err != nil {
if _, err := conn.DeletePolicy(request); err != nil {
if isAWSErr(err, iam.ErrCodeNoSuchEntityException, "") {
return nil
}
return fmt.Errorf("Error deleting IAM policy %s: %s", d.Id(), err)
return fmt.Errorf("Error deleting IAM policy %s: %w", d.Id(), err)
}

return nil
Expand All @@ -247,8 +272,8 @@ func resourceAwsIamPolicyDelete(d *schema.ResourceData, meta interface{}) error
//
// The default version is never deleted.

func iamPolicyPruneVersions(arn string, iamconn *iam.IAM) error {
versions, err := iamPolicyListVersions(arn, iamconn)
func iamPolicyPruneVersions(arn string, conn *iam.IAM) error {
versions, err := iamPolicyListVersions(arn, conn)
if err != nil {
return err
}
Expand All @@ -268,12 +293,12 @@ func iamPolicyPruneVersions(arn string, iamconn *iam.IAM) error {
}
}

err1 := iamPolicyDeleteVersion(arn, *oldestVersion.VersionId, iamconn)
err1 := iamPolicyDeleteVersion(arn, aws.StringValue(oldestVersion.VersionId), conn)
return err1
}

func iamPolicyDeleteNondefaultVersions(arn string, iamconn *iam.IAM) error {
versions, err := iamPolicyListVersions(arn, iamconn)
func iamPolicyDeleteNondefaultVersions(arn string, conn *iam.IAM) error {
versions, err := iamPolicyListVersions(arn, conn)
if err != nil {
return err
}
Expand All @@ -282,35 +307,35 @@ func iamPolicyDeleteNondefaultVersions(arn string, iamconn *iam.IAM) error {
if *version.IsDefaultVersion {
continue
}
if err := iamPolicyDeleteVersion(arn, *version.VersionId, iamconn); err != nil {
if err := iamPolicyDeleteVersion(arn, aws.StringValue(version.VersionId), conn); err != nil {
return err
}
}

return nil
}

func iamPolicyDeleteVersion(arn, versionID string, iamconn *iam.IAM) error {
func iamPolicyDeleteVersion(arn, versionID string, conn *iam.IAM) error {
request := &iam.DeletePolicyVersionInput{
PolicyArn: aws.String(arn),
VersionId: aws.String(versionID),
}

_, err := iamconn.DeletePolicyVersion(request)
_, err := conn.DeletePolicyVersion(request)
if err != nil {
return fmt.Errorf("Error deleting version %s from IAM policy %s: %s", versionID, arn, err)
return fmt.Errorf("Error deleting version %s from IAM policy %s: %w", versionID, arn, err)
}
return nil
}

func iamPolicyListVersions(arn string, iamconn *iam.IAM) ([]*iam.PolicyVersion, error) {
func iamPolicyListVersions(arn string, conn *iam.IAM) ([]*iam.PolicyVersion, error) {
request := &iam.ListPolicyVersionsInput{
PolicyArn: aws.String(arn),
}

response, err := iamconn.ListPolicyVersions(request)
response, err := conn.ListPolicyVersions(request)
if err != nil {
return nil, fmt.Errorf("Error listing versions for IAM policy %s: %s", arn, err)
return nil, fmt.Errorf("Error listing versions for IAM policy %s: %w", arn, err)
}
return response.Versions, nil
}
Loading