From ee7d2c12c7eca4bc06519ae1413a64ee76b5423c Mon Sep 17 00:00:00 2001 From: saravanan30erd Date: Sat, 6 Oct 2018 20:18:43 +0400 Subject: [PATCH 1/9] datasource/aws_iam_policy: lookup by name --- aws/data_source_aws_iam_policy.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/aws/data_source_aws_iam_policy.go b/aws/data_source_aws_iam_policy.go index 938af05e46f..713e5015874 100644 --- a/aws/data_source_aws_iam_policy.go +++ b/aws/data_source_aws_iam_policy.go @@ -20,12 +20,14 @@ func dataSourceAwsIAMPolicy() *schema.Resource { Schema: map[string]*schema.Schema{ "arn": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"name"}, }, "name": { - Type: schema.TypeString, - Computed: true, + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"arn"}, }, "policy": { Type: schema.TypeString, From 302791dc37c2bc4777827a73a827d4b27025df22 Mon Sep 17 00:00:00 2001 From: saravanan30erd Date: Sat, 6 Oct 2018 20:31:03 +0400 Subject: [PATCH 2/9] update acceptance test --- aws/data_source_aws_iam_policy_test.go | 51 +++++++++++++++++++++++++ website/docs/d/iam_policy.html.markdown | 10 ++++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/aws/data_source_aws_iam_policy_test.go b/aws/data_source_aws_iam_policy_test.go index e3246cac11e..b4624d5f13e 100644 --- a/aws/data_source_aws_iam_policy_test.go +++ b/aws/data_source_aws_iam_policy_test.go @@ -36,6 +36,29 @@ func TestAccAWSDataSourceIAMPolicy_basic(t *testing.T) { } +func TestAccAWSDataSourceIAMPolicy_withName(t *testing.T) { + policyName := fmt.Sprintf("test-policy-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccAwsDataSourceIamPolicyConfig_withName(policyName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.aws_iam_policy.test", "name", policyName), + resource.TestCheckResourceAttr("data.aws_iam_policy.test", "description", "My test policy"), + resource.TestCheckResourceAttr("data.aws_iam_policy.test", "path", "/"), + resource.TestCheckResourceAttrSet("data.aws_iam_policy.test", "policy"), + resource.TestMatchResourceAttr("data.aws_iam_policy.test", "arn", + regexp.MustCompile(`^arn:[\w-]+:([a-zA-Z0-9\-])+:([a-z]{2}-(gov-)?[a-z]+-\d{1})?:(\d{12})?:(.*)$`)), + ), + }, + }, + }) + +} + func testAccAwsDataSourceIamPolicyConfig(policyName string) string { return fmt.Sprintf(` resource "aws_iam_policy" "test" { @@ -64,3 +87,31 @@ data "aws_iam_policy" "test" { } `, policyName) } + +func testAccAwsDataSourceIamPolicyConfig_withName(policyName string) string { + return fmt.Sprintf(` +resource "aws_iam_policy" "test_policy" { + name = "%s" + path = "/" + description = "My test policy" + policy = < Date: Fri, 12 Oct 2018 22:17:20 +0400 Subject: [PATCH 3/9] cover all the policy types --- aws/data_source_aws_iam_policy.go | 123 +++++++----------------- website/docs/d/iam_policy.html.markdown | 1 + 2 files changed, 34 insertions(+), 90 deletions(-) diff --git a/aws/data_source_aws_iam_policy.go b/aws/data_source_aws_iam_policy.go index 713e5015874..3f33e903436 100644 --- a/aws/data_source_aws_iam_policy.go +++ b/aws/data_source_aws_iam_policy.go @@ -29,6 +29,12 @@ func dataSourceAwsIAMPolicy() *schema.Resource { Optional: true, ConflictsWith: []string{"arn"}, }, + "path_prefix": { + Type: schema.TypeString, + Optional: true, + Default: "/", + ConflictsWith: []string{"arn"}, + }, "policy": { Type: schema.TypeString, Computed: true, @@ -52,105 +58,42 @@ func dataSourceAwsIAMPolicy() *schema.Resource { } func dataSourceAwsIAMPolicyRead(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).iamconn - ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig - - arn := d.Get("arn").(string) + iamconn := meta.(*AWSClient).iamconn - input := &iam.GetPolicyInput{ - PolicyArn: aws.String(arn), - } - - // Handle IAM eventual consistency - var output *iam.GetPolicyOutput - err := resource.Retry(waiter.PropagationTimeout, func() *resource.RetryError { - var err error - output, err = conn.GetPolicy(input) - - if tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { - return resource.RetryableError(err) + var iamPolicyARN string + if v, ok := d.GetOk("name"); ok { + input := &iam.ListPoliciesInput{ + PathPrefix: aws.String(d.Get("path_prefix").(string)), } - if err != nil { - return resource.NonRetryableError(err) + policyArns := make([]string, 0) + if err := iamconn.ListPoliciesPages(input, func(res *iam.ListPoliciesOutput, lastPage bool) bool { + for _, policy := range res.Policies { + if v.(string) == aws.StringValue(policy.PolicyName) { + policyArns = append(policyArns, aws.StringValue(policy.Arn)) + } + } + return !lastPage + }); err != nil { + return err } - return nil - }) - - if tfresource.TimedOut(err) { - output, err = conn.GetPolicy(input) - } - - if err != nil { - return fmt.Errorf("error reading IAM policy %s: %w", arn, err) - } - - if output == nil || output.Policy == nil { - return fmt.Errorf("error reading IAM policy %s: empty output", arn) - } - - policy := output.Policy - - d.SetId(aws.StringValue(policy.Arn)) - - d.Set("arn", policy.Arn) - d.Set("description", policy.Description) - d.Set("name", policy.PolicyName) - d.Set("path", policy.Path) - d.Set("policy_id", policy.PolicyId) - - if err := d.Set("tags", keyvaluetags.IamKeyValueTags(policy.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { - return fmt.Errorf("error setting tags: %w", err) - } - - // Retrieve policy - - policyVersionInput := &iam.GetPolicyVersionInput{ - PolicyArn: aws.String(arn), - VersionId: policy.DefaultVersionId, - } - - // Handle IAM eventual consistency - var policyVersionOutput *iam.GetPolicyVersionOutput - err = resource.Retry(waiter.PropagationTimeout, func() *resource.RetryError { - var err error - policyVersionOutput, err = conn.GetPolicyVersion(policyVersionInput) - - if tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { - return resource.RetryableError(err) + if len(policyArns) == 0 { + return fmt.Errorf("no matching IAM policy found") } - - if err != nil { - return resource.NonRetryableError(err) + if len(policyArns) > 1 { + return fmt.Errorf("multiple IAM policies matched; use additional constraints to reduce matches to a single IAM policy") } - return nil - }) + iamPolicyARN = policyArns[0] - if tfresource.TimedOut(err) { - policyVersionOutput, err = conn.GetPolicyVersion(policyVersionInput) + } else if v, ok := d.GetOk("arn"); ok { + iamPolicyARN = v.(string) + } else { + return fmt.Errorf( + "ARN or name must be set to query IAM policy") } - if err != nil { - return fmt.Errorf("error reading IAM Policy (%s) version: %w", arn, err) - } - - if policyVersionOutput == nil || policyVersionOutput.PolicyVersion == nil { - return fmt.Errorf("error reading IAM Policy (%s) version: empty output", arn) - } - - policyVersion := policyVersionOutput.PolicyVersion - - var policyDocument string - if policyVersion != nil { - policyDocument, err = url.QueryUnescape(aws.StringValue(policyVersion.Document)) - if err != nil { - return fmt.Errorf("error parsing IAM Policy (%s) document: %w", arn, err) - } - } - - d.Set("policy", policyDocument) - - return nil + d.SetId(iamPolicyARN) + return resourceAwsIamPolicyRead(d, meta) } diff --git a/website/docs/d/iam_policy.html.markdown b/website/docs/d/iam_policy.html.markdown index 83da9412f5b..0b752a00d06 100644 --- a/website/docs/d/iam_policy.html.markdown +++ b/website/docs/d/iam_policy.html.markdown @@ -30,6 +30,7 @@ data "aws_iam_policy" "example" { * `arn` - (Optional) The ARN of the IAM policy. * `name` - (Optional) The name of the IAM policy. You must use either ARN or name to fetch information about IAM policy. +* `path_prefix` - (Optional) The path to the IAM policy. This parameter allows a string of characters consisting of either a forward slash (/) by itself or a string that must begin and end with forward slashes. E.G. `/service-role/` ## Attributes Reference From 042e7d21ad6db4271c9f43e732bc0c7d5cba3ac2 Mon Sep 17 00:00:00 2001 From: saravanan30erd Date: Sat, 13 Oct 2018 18:49:44 +0400 Subject: [PATCH 4/9] add acceptance test with paths --- aws/data_source_aws_iam_policy_test.go | 53 ++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/aws/data_source_aws_iam_policy_test.go b/aws/data_source_aws_iam_policy_test.go index b4624d5f13e..fca71d82072 100644 --- a/aws/data_source_aws_iam_policy_test.go +++ b/aws/data_source_aws_iam_policy_test.go @@ -59,6 +59,30 @@ func TestAccAWSDataSourceIAMPolicy_withName(t *testing.T) { } +func TestAccAWSDataSourceIAMPolicy_withPath(t *testing.T) { + policyName := fmt.Sprintf("test-policy-%s", acctest.RandString(10)) + policyPath := "/describe/" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccAwsDataSourceIamPolicyConfig_withPath(policyName, policyPath), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.aws_iam_policy.test", "name", policyName), + resource.TestCheckResourceAttr("data.aws_iam_policy.test", "description", "My test policy"), + resource.TestCheckResourceAttr("data.aws_iam_policy.test", "path", policyPath), + resource.TestCheckResourceAttrSet("data.aws_iam_policy.test", "policy"), + resource.TestMatchResourceAttr("data.aws_iam_policy.test", "arn", + regexp.MustCompile(`^arn:[\w-]+:([a-zA-Z0-9\-])+:([a-z]{2}-(gov-)?[a-z]+-\d{1})?:(\d{12})?:(.*)$`)), + ), + }, + }, + }) + +} + func testAccAwsDataSourceIamPolicyConfig(policyName string) string { return fmt.Sprintf(` resource "aws_iam_policy" "test" { @@ -115,3 +139,32 @@ data "aws_iam_policy" "test" { } `, policyName) } + +func testAccAwsDataSourceIamPolicyConfig_withPath(policyName, policyPath string) string { + return fmt.Sprintf(` +resource "aws_iam_policy" "test_policy" { + name = "%s" + path = "%s" + description = "My test policy" + policy = < Date: Thu, 22 Apr 2021 00:01:59 -0400 Subject: [PATCH 5/9] refactor and update to current contribution guidelines --- .changelog/6084.txt | 3 + aws/data_source_aws_iam_policy.go | 180 ++++++++++++++++------ aws/data_source_aws_iam_policy_test.go | 132 +++++++--------- aws/internal/service/iam/finder/finder.go | 37 +++++ website/docs/d/iam_policy.html.markdown | 14 +- 5 files changed, 239 insertions(+), 127 deletions(-) create mode 100644 .changelog/6084.txt diff --git a/.changelog/6084.txt b/.changelog/6084.txt new file mode 100644 index 00000000000..175c2ea43c2 --- /dev/null +++ b/.changelog/6084.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_iam_policy: Add support for lookup by `arn`, `name`, and/or `path_prefix` +``` \ No newline at end of file diff --git a/aws/data_source_aws_iam_policy.go b/aws/data_source_aws_iam_policy.go index 3f33e903436..805b08f7623 100644 --- a/aws/data_source_aws_iam_policy.go +++ b/aws/data_source_aws_iam_policy.go @@ -3,6 +3,7 @@ package aws import ( "fmt" "net/url" + "strings" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/iam" @@ -10,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/iam/finder" "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/iam/waiter" "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) @@ -20,31 +22,28 @@ func dataSourceAwsIAMPolicy() *schema.Resource { Schema: map[string]*schema.Schema{ "arn": { - Type: schema.TypeString, - Optional: true, - ConflictsWith: []string{"name"}, - }, - "name": { - Type: schema.TypeString, - Optional: true, - ConflictsWith: []string{"arn"}, + Type: schema.TypeString, + Optional: true, + Computed: true, }, - "path_prefix": { - Type: schema.TypeString, - Optional: true, - Default: "/", - ConflictsWith: []string{"arn"}, + "description": { + Type: schema.TypeString, + Computed: true, }, - "policy": { + "name": { Type: schema.TypeString, + Optional: true, Computed: true, }, "path": { Type: schema.TypeString, Computed: true, }, - - "description": { + "path_prefix": { + Type: schema.TypeString, + Optional: true, + }, + "policy": { Type: schema.TypeString, Computed: true, }, @@ -58,42 +57,137 @@ func dataSourceAwsIAMPolicy() *schema.Resource { } func dataSourceAwsIAMPolicyRead(d *schema.ResourceData, meta interface{}) error { - iamconn := meta.(*AWSClient).iamconn + conn := meta.(*AWSClient).iamconn + ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig + + arn := d.Get("arn").(string) + name := d.Get("name").(string) + pathPrefix := d.Get("path_prefix").(string) + + var results []*iam.Policy + + // Handle IAM eventual consistency + err := resource.Retry(waiter.PropagationTimeout, func() *resource.RetryError { + var err error + results, err = finder.Policies(conn, arn, name, pathPrefix) - var iamPolicyARN string - if v, ok := d.GetOk("name"); ok { - input := &iam.ListPoliciesInput{ - PathPrefix: aws.String(d.Get("path_prefix").(string)), + if tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { + return resource.RetryableError(err) } - policyArns := make([]string, 0) - if err := iamconn.ListPoliciesPages(input, func(res *iam.ListPoliciesOutput, lastPage bool) bool { - for _, policy := range res.Policies { - if v.(string) == aws.StringValue(policy.PolicyName) { - policyArns = append(policyArns, aws.StringValue(policy.Arn)) - } - } - return !lastPage - }); err != nil { - return err + if err != nil { + return resource.NonRetryableError(err) } - if len(policyArns) == 0 { - return fmt.Errorf("no matching IAM policy found") + return nil + }) + + if tfresource.TimedOut(err) { + results, err = finder.Policies(conn, arn, name, pathPrefix) + } + + if err != nil { + return fmt.Errorf("error reading IAM policy (%s): %w", PolicySearchDetails(arn, name, pathPrefix), err) + } + + if len(results) == 0 { + return fmt.Errorf("no IAM policy found matching criteria (%s); try different search", PolicySearchDetails(arn, name, pathPrefix)) + } + + if len(results) > 1 { + return fmt.Errorf("multiple IAM policies found matching criteria (%s); try different search", PolicySearchDetails(arn, name, pathPrefix)) + } + + policy := results[0] + policyArn := aws.StringValue(policy.Arn) + + d.SetId(policyArn) + + d.Set("arn", policyArn) + d.Set("description", policy.Description) + d.Set("name", policy.PolicyName) + d.Set("path", policy.Path) + d.Set("policy_id", policy.PolicyId) + + if err := d.Set("tags", keyvaluetags.IamKeyValueTags(policy.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + return fmt.Errorf("error setting tags: %w", err) + } + + // Retrieve policy description + + policyOutput, err := conn.GetPolicy(&iam.GetPolicyInput{PolicyArn: policy.Arn}) + + if err != nil { + return fmt.Errorf("error getting IAM policy (%s): %w", d.Id(), err) + } + + if policyOutput == nil || policyOutput.Policy == nil { + return fmt.Errorf("error getting IAM policy (%s): empty output", d.Id()) + } + + d.Set("description", policyOutput.Policy.Description) + + // Retrieve policy + policyVersionInput := &iam.GetPolicyVersionInput{ + PolicyArn: policy.Arn, + VersionId: policy.DefaultVersionId, + } + + // Handle IAM eventual consistency + var policyVersionOutput *iam.GetPolicyVersionOutput + err = resource.Retry(waiter.PropagationTimeout, func() *resource.RetryError { + var err error + policyVersionOutput, err = conn.GetPolicyVersion(policyVersionInput) + + if tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { + return resource.RetryableError(err) } - if len(policyArns) > 1 { - return fmt.Errorf("multiple IAM policies matched; use additional constraints to reduce matches to a single IAM policy") + + if err != nil { + return resource.NonRetryableError(err) } - iamPolicyARN = policyArns[0] + return nil + }) - } else if v, ok := d.GetOk("arn"); ok { - iamPolicyARN = v.(string) - } else { - return fmt.Errorf( - "ARN or name must be set to query IAM policy") + if tfresource.TimedOut(err) { + policyVersionOutput, err = conn.GetPolicyVersion(policyVersionInput) + } + + if err != nil { + return fmt.Errorf("error reading IAM Policy (%s) version: %w", policyArn, err) + } + + if policyVersionOutput == nil || policyVersionOutput.PolicyVersion == nil { + return fmt.Errorf("error reading IAM Policy (%s) version: empty output", policyArn) + } + + policyVersion := policyVersionOutput.PolicyVersion + + var policyDocument string + if policyVersion != nil { + policyDocument, err = url.QueryUnescape(aws.StringValue(policyVersion.Document)) + if err != nil { + return fmt.Errorf("error parsing IAM Policy (%s) document: %w", policyArn, err) + } + } + + d.Set("policy", policyDocument) + + return nil +} + +func PolicySearchDetails(arn, name, pathPrefix string) string { + var policyDetails []string + if arn != "" { + policyDetails = append(policyDetails, fmt.Sprintf("ARN: %s", arn)) + } + if name != "" { + policyDetails = append(policyDetails, fmt.Sprintf("Name: %s", name)) + } + if pathPrefix != "" { + policyDetails = append(policyDetails, fmt.Sprintf("PathPrefix: %s", pathPrefix)) } - d.SetId(iamPolicyARN) - return resourceAwsIamPolicyRead(d, meta) + return strings.Join(policyDetails, ",") } diff --git a/aws/data_source_aws_iam_policy_test.go b/aws/data_source_aws_iam_policy_test.go index fca71d82072..a0c87bffd46 100644 --- a/aws/data_source_aws_iam_policy_test.go +++ b/aws/data_source_aws_iam_policy_test.go @@ -20,7 +20,7 @@ func TestAccAWSDataSourceIAMPolicy_basic(t *testing.T) { Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: testAccAwsDataSourceIamPolicyConfig(policyName), + Config: testAccAwsDataSourceIamPolicyConfig(policyName, "/"), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrPair(datasourceName, "name", resourceName, "name"), resource.TestCheckResourceAttrPair(datasourceName, "description", resourceName, "description"), @@ -36,22 +36,26 @@ func TestAccAWSDataSourceIAMPolicy_basic(t *testing.T) { } -func TestAccAWSDataSourceIAMPolicy_withName(t *testing.T) { +func TestAccAWSDataSourceIAMPolicy_Name(t *testing.T) { + datasourceName := "data.aws_iam_policy.test" + resourceName := "aws_iam_policy.test" policyName := fmt.Sprintf("test-policy-%s", acctest.RandString(10)) - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, iam.EndpointsID), + Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: testAccAwsDataSourceIamPolicyConfig_withName(policyName), + Config: testAccAwsDataSourceIamPolicyConfig_Name(policyName, "/"), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("data.aws_iam_policy.test", "name", policyName), - resource.TestCheckResourceAttr("data.aws_iam_policy.test", "description", "My test policy"), - resource.TestCheckResourceAttr("data.aws_iam_policy.test", "path", "/"), - resource.TestCheckResourceAttrSet("data.aws_iam_policy.test", "policy"), - resource.TestMatchResourceAttr("data.aws_iam_policy.test", "arn", - regexp.MustCompile(`^arn:[\w-]+:([a-zA-Z0-9\-])+:([a-z]{2}-(gov-)?[a-z]+-\d{1})?:(\d{12})?:(.*)$`)), + resource.TestCheckResourceAttr(datasourceName, "name", policyName), + resource.TestCheckResourceAttr(datasourceName, "description", "My test policy"), + 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"), ), }, }, @@ -59,23 +63,28 @@ func TestAccAWSDataSourceIAMPolicy_withName(t *testing.T) { } -func TestAccAWSDataSourceIAMPolicy_withPath(t *testing.T) { +func TestAccAWSDataSourceIAMPolicy_PathPrefix(t *testing.T) { + datasourceName := "data.aws_iam_policy.test" + resourceName := "aws_iam_policy.test" + policyName := fmt.Sprintf("test-policy-%s", acctest.RandString(10)) - policyPath := "/describe/" + policyPath := "/test-path/" - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, iam.EndpointsID), + Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: testAccAwsDataSourceIamPolicyConfig_withPath(policyName, policyPath), + Config: testAccAwsDataSourceIamPolicyConfig_PathPrefix(policyName, policyPath), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("data.aws_iam_policy.test", "name", policyName), - resource.TestCheckResourceAttr("data.aws_iam_policy.test", "description", "My test policy"), - resource.TestCheckResourceAttr("data.aws_iam_policy.test", "path", policyPath), - resource.TestCheckResourceAttrSet("data.aws_iam_policy.test", "policy"), - resource.TestMatchResourceAttr("data.aws_iam_policy.test", "arn", - regexp.MustCompile(`^arn:[\w-]+:([a-zA-Z0-9\-])+:([a-z]{2}-(gov-)?[a-z]+-\d{1})?:(\d{12})?:(.*)$`)), + resource.TestCheckResourceAttr(datasourceName, "name", policyName), + resource.TestCheckResourceAttr(datasourceName, "description", "My test policy"), + 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"), ), }, }, @@ -83,11 +92,11 @@ func TestAccAWSDataSourceIAMPolicy_withPath(t *testing.T) { } -func testAccAwsDataSourceIamPolicyConfig(policyName string) string { +func testAccAwsDataSourceIamPolicyBaseConfig(policyName, policyPath string) string { return fmt.Sprintf(` resource "aws_iam_policy" "test" { - name = "%s" - path = "/" + name = %q + path = %q description = "My test policy" policy = < Date: Thu, 22 Apr 2021 00:03:20 -0400 Subject: [PATCH 6/9] Update 6084.txt --- .changelog/6084.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changelog/6084.txt b/.changelog/6084.txt index 175c2ea43c2..d9dc3388815 100644 --- a/.changelog/6084.txt +++ b/.changelog/6084.txt @@ -1,3 +1,3 @@ ```release-note:enhancement -resource/aws_iam_policy: Add support for lookup by `arn`, `name`, and/or `path_prefix` -``` \ No newline at end of file +data-source/aws_iam_policy: Add support for lookup by `arn`, `name`, and/or `path_prefix` +``` From 327820cdda67e520a91c54f1d60323baf311694d Mon Sep 17 00:00:00 2001 From: Angie Pinilla Date: Thu, 22 Apr 2021 00:13:23 -0400 Subject: [PATCH 7/9] update GetPolicy usage --- aws/data_source_aws_iam_policy.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/aws/data_source_aws_iam_policy.go b/aws/data_source_aws_iam_policy.go index 805b08f7623..1cfbbf19715 100644 --- a/aws/data_source_aws_iam_policy.go +++ b/aws/data_source_aws_iam_policy.go @@ -114,15 +114,18 @@ func dataSourceAwsIAMPolicyRead(d *schema.ResourceData, meta interface{}) error } // Retrieve policy description + policyInput := &iam.GetPolicyInput{ + PolicyArn: policy.Arn, + } - policyOutput, err := conn.GetPolicy(&iam.GetPolicyInput{PolicyArn: policy.Arn}) + policyOutput, err := conn.GetPolicy(policyInput) if err != nil { - return fmt.Errorf("error getting IAM policy (%s): %w", d.Id(), err) + return fmt.Errorf("error reading IAM policy (%s): %w", policyArn, err) } if policyOutput == nil || policyOutput.Policy == nil { - return fmt.Errorf("error getting IAM policy (%s): empty output", d.Id()) + return fmt.Errorf("error reading IAM policy (%s): empty output", policyArn) } d.Set("description", policyOutput.Policy.Description) From 0dcd309deb2205da87ec8740ad03bf834acf4216 Mon Sep 17 00:00:00 2001 From: Angie Pinilla Date: Thu, 22 Apr 2021 10:44:17 -0400 Subject: [PATCH 8/9] update tests and add plan-time validation to name arg; docs --- aws/data_source_aws_iam_policy.go | 11 +-- aws/data_source_aws_iam_policy_test.go | 106 ++++++++++++++++++++++-- website/docs/d/iam_policy.html.markdown | 8 +- 3 files changed, 111 insertions(+), 14 deletions(-) diff --git a/aws/data_source_aws_iam_policy.go b/aws/data_source_aws_iam_policy.go index 1cfbbf19715..b4177d16c91 100644 --- a/aws/data_source_aws_iam_policy.go +++ b/aws/data_source_aws_iam_policy.go @@ -22,9 +22,10 @@ func dataSourceAwsIAMPolicy() *schema.Resource { Schema: map[string]*schema.Schema{ "arn": { - Type: schema.TypeString, - Optional: true, - Computed: true, + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validateArn, }, "description": { Type: schema.TypeString, @@ -104,7 +105,6 @@ func dataSourceAwsIAMPolicyRead(d *schema.ResourceData, meta interface{}) error d.SetId(policyArn) d.Set("arn", policyArn) - d.Set("description", policy.Description) d.Set("name", policy.PolicyName) d.Set("path", policy.Path) d.Set("policy_id", policy.PolicyId) @@ -180,6 +180,7 @@ func dataSourceAwsIAMPolicyRead(d *schema.ResourceData, meta interface{}) error return nil } +// PolicySearchDetails returns the configured search criteria as a printable string func PolicySearchDetails(arn, name, pathPrefix string) string { var policyDetails []string if arn != "" { @@ -192,5 +193,5 @@ func PolicySearchDetails(arn, name, pathPrefix string) string { policyDetails = append(policyDetails, fmt.Sprintf("PathPrefix: %s", pathPrefix)) } - return strings.Join(policyDetails, ",") + return strings.Join(policyDetails, ", ") } diff --git a/aws/data_source_aws_iam_policy_test.go b/aws/data_source_aws_iam_policy_test.go index a0c87bffd46..eb7c9b811a4 100644 --- a/aws/data_source_aws_iam_policy_test.go +++ b/aws/data_source_aws_iam_policy_test.go @@ -2,6 +2,7 @@ package aws import ( "fmt" + "regexp" "testing" "github.com/aws/aws-sdk-go/service/iam" @@ -9,7 +10,75 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) -func TestAccAWSDataSourceIAMPolicy_basic(t *testing.T) { +func TestPolicySearchDetails(t *testing.T) { + testCases := []struct { + Arn string + Name string + PathPrefix string + Expected string + }{ + { + Arn: "", + Name: "", + PathPrefix: "", + Expected: "", + }, + { + Arn: "arn:aws:iam::aws:policy/TestPolicy", //lintignore:AWSAT005 + Name: "", + PathPrefix: "", + Expected: "ARN: arn:aws:iam::aws:policy/TestPolicy", //lintignore:AWSAT005 + }, + { + Arn: "", + Name: "tf-acc-test-policy", + PathPrefix: "", + Expected: "Name: tf-acc-test-policy", + }, + { + Arn: "", + Name: "", + PathPrefix: "/test-prefix/", + Expected: "PathPrefix: /test-prefix/", + }, + { + Arn: "arn:aws:iam::aws:policy/TestPolicy", //lintignore:AWSAT005 + Name: "tf-acc-test-policy", + PathPrefix: "", + Expected: "ARN: arn:aws:iam::aws:policy/TestPolicy, Name: tf-acc-test-policy", //lintignore:AWSAT005 + }, + { + Arn: "arn:aws:iam::aws:policy/TestPolicy", //lintignore:AWSAT005 + Name: "", + PathPrefix: "/test-prefix/", + Expected: "ARN: arn:aws:iam::aws:policy/TestPolicy, PathPrefix: /test-prefix/", //lintignore:AWSAT005 + }, + { + Arn: "", + Name: "tf-acc-test-policy", + PathPrefix: "/test-prefix/", + Expected: "Name: tf-acc-test-policy, PathPrefix: /test-prefix/", + }, + { + Arn: "arn:aws:iam::aws:policy/TestPolicy", //lintignore:AWSAT005 + Name: "tf-acc-test-policy", + PathPrefix: "/test-prefix/", + Expected: "ARN: arn:aws:iam::aws:policy/TestPolicy, Name: tf-acc-test-policy, PathPrefix: /test-prefix/", //lintignore:AWSAT005 + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + got := PolicySearchDetails(testCase.Arn, testCase.Name, testCase.PathPrefix) + + if got != testCase.Expected { + t.Errorf("got %s, expected %s", got, testCase.Expected) + } + }) + } +} + +func TestAccAWSDataSourceIAMPolicy_Arn(t *testing.T) { datasourceName := "data.aws_iam_policy.test" resourceName := "aws_iam_policy.test" policyName := fmt.Sprintf("test-policy-%s", acctest.RandString(10)) @@ -20,7 +89,7 @@ func TestAccAWSDataSourceIAMPolicy_basic(t *testing.T) { Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: testAccAwsDataSourceIamPolicyConfig(policyName, "/"), + Config: testAccAwsDataSourceIamPolicyConfig_Arn(policyName, "/"), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrPair(datasourceName, "name", resourceName, "name"), resource.TestCheckResourceAttrPair(datasourceName, "description", resourceName, "description"), @@ -33,7 +102,6 @@ func TestAccAWSDataSourceIAMPolicy_basic(t *testing.T) { }, }, }) - } func TestAccAWSDataSourceIAMPolicy_Name(t *testing.T) { @@ -60,10 +128,9 @@ func TestAccAWSDataSourceIAMPolicy_Name(t *testing.T) { }, }, }) - } -func TestAccAWSDataSourceIAMPolicy_PathPrefix(t *testing.T) { +func TestAccAWSDataSourceIAMPolicy_NameAndPathPrefix(t *testing.T) { datasourceName := "data.aws_iam_policy.test" resourceName := "aws_iam_policy.test" @@ -89,7 +156,23 @@ func TestAccAWSDataSourceIAMPolicy_PathPrefix(t *testing.T) { }, }, }) +} + +func TestAccAWSDataSourceIAMPolicy_NonExistent(t *testing.T) { + policyName := fmt.Sprintf("test-policy-%s", acctest.RandString(10)) + policyPath := "/test-path/" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, iam.EndpointsID), + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccAwsDataSourceIamPolicyConfig_NonExistent(policyName, policyPath), + ExpectError: regexp.MustCompile(`no IAM policy found matching criteria`), + }, + }, + }) } func testAccAwsDataSourceIamPolicyBaseConfig(policyName, policyPath string) string { @@ -116,7 +199,7 @@ EOF }`, policyName, policyPath) } -func testAccAwsDataSourceIamPolicyConfig(policyName, policyPath string) string { +func testAccAwsDataSourceIamPolicyConfig_Arn(policyName, policyPath string) string { return composeConfig( testAccAwsDataSourceIamPolicyBaseConfig(policyName, policyPath), ` @@ -146,3 +229,14 @@ data "aws_iam_policy" "test" { } `, policyPath)) } + +func testAccAwsDataSourceIamPolicyConfig_NonExistent(policyName, policyPath string) string { + return composeConfig( + testAccAwsDataSourceIamPolicyBaseConfig(policyName, policyPath), + fmt.Sprintf(` +data "aws_iam_policy" "test" { + name = "non-existent" + path_prefix = %q +} +`, policyPath)) +} diff --git a/website/docs/d/iam_policy.html.markdown b/website/docs/d/iam_policy.html.markdown index 4e25a1aebfe..b03749e1cb0 100644 --- a/website/docs/d/iam_policy.html.markdown +++ b/website/docs/d/iam_policy.html.markdown @@ -6,7 +6,7 @@ description: |- Get information on a Amazon IAM policy --- -# aws_iam_policy +# Data Source: aws_iam_policy This data source can be used to fetch information about a specific IAM policy. @@ -33,12 +33,14 @@ data "aws_iam_policy" "example" { * `arn` - (Optional) The ARN of the IAM policy. * `name` - (Optional) The name of the IAM policy. -* `path_prefix` - (Optional) The prefix of the IAM policy's path. +* `path_prefix` - (Optional) The prefix of the path to the IAM policy. Defaults to a slash (`/`). ## Attributes Reference +In addition to all arguments above, the following attributes are exported: + * `path` - The path to the policy. * `description` - The description of the policy. * `policy` - The policy document of the policy. * `policy_id` - The policy's ID. -* `tags` - Key-value mapping of tags for the IAM Policy +* `tags` - Key-value mapping of tags for the IAM Policy. From ebcb910d2531f922f59aa2c5ab0cf5cfaea22b8d Mon Sep 17 00:00:00 2001 From: Angie Pinilla Date: Thu, 22 Apr 2021 11:10:18 -0400 Subject: [PATCH 9/9] use AttrPair test checks for name and description --- aws/data_source_aws_iam_policy_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/aws/data_source_aws_iam_policy_test.go b/aws/data_source_aws_iam_policy_test.go index eb7c9b811a4..f14a6b469eb 100644 --- a/aws/data_source_aws_iam_policy_test.go +++ b/aws/data_source_aws_iam_policy_test.go @@ -117,8 +117,8 @@ func TestAccAWSDataSourceIAMPolicy_Name(t *testing.T) { { Config: testAccAwsDataSourceIamPolicyConfig_Name(policyName, "/"), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(datasourceName, "name", policyName), - resource.TestCheckResourceAttr(datasourceName, "description", "My test policy"), + 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"), @@ -145,8 +145,8 @@ func TestAccAWSDataSourceIAMPolicy_NameAndPathPrefix(t *testing.T) { { Config: testAccAwsDataSourceIamPolicyConfig_PathPrefix(policyName, policyPath), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(datasourceName, "name", policyName), - resource.TestCheckResourceAttr(datasourceName, "description", "My test policy"), + 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"),