From d94640ee333e122f6237e2ea81f05fe0cae5fc53 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Tue, 11 Sep 2018 20:24:35 -0400 Subject: [PATCH 01/20] resource/aws_iam_role: Add exclusive lists of policies to role This provides a way to maintain an exhaustive list of policies associated with a given role. When applied, any changes made outside of Terraform, are reverted back to the configuration provided to Terraform. See #4426 --- aws/resource_aws_iam_role.go | 461 ++++++++++++++++++++++++++++++++--- 1 file changed, 426 insertions(+), 35 deletions(-) diff --git a/aws/resource_aws_iam_role.go b/aws/resource_aws_iam_role.go index 2c25f6a8357..d58abdc9958 100644 --- a/aws/resource_aws_iam_role.go +++ b/aws/resource_aws_iam_role.go @@ -5,9 +5,11 @@ import ( "log" "net/url" "regexp" + "strings" "time" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/iam" "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -15,6 +17,7 @@ import ( "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" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/hashcode" ) func resourceAwsIamRole() *schema.Resource { @@ -26,7 +29,7 @@ func resourceAwsIamRole() *schema.Resource { Importer: &schema.ResourceImporter{ State: resourceAwsIamRoleImport, }, - + CustomizeDiff: resourceAwsIamRoleInlineCustDiff, Schema: map[string]*schema.Schema{ "arn": { Type: schema.TypeString, @@ -110,6 +113,44 @@ func resourceAwsIamRole() *schema.Resource { }, "tags": tagsSchema(), + + "inline_policy": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "policy": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateIAMPolicyJson, + DiffSuppressFunc: suppressEquivalentAwsPolicyDiffs, + }, + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validateIamRolePolicyName, + // ConflictsWith: []string{"inline_policy.0.name_prefix"}, + // Not working: prevents two separate policies with + // one having a name and the other a prefix + }, + "name_prefix": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateIamRolePolicyNamePrefix, + }, + }, + }, + }, + + "managed_policy_arns": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, }, } } @@ -172,8 +213,23 @@ func resourceAwsIamRoleCreate(d *schema.ResourceData, meta interface{}) error { createResp, err = iamconn.CreateRole(request) } if err != nil { - return fmt.Errorf("Error creating IAM Role %s: %s", name, err) + return fmt.Errorf("failed to create IAM role %s, error: %s", name, err) } + + if policyData, ok := d.GetOk("inline_policy"); ok { + inlinePolicies := policyData.(*schema.Set).List() + if err := addInlinePoliciesToRole(iamconn, inlinePolicies, name); err != nil { + return fmt.Errorf("failed to add inline policies to IAM role %s, error: %s", name, err) + } + } + + if policies, ok := d.GetOk("managed_policy_arns"); ok { + managedPolicies := expandStringList(policies.(*schema.Set).List()) + if err := addManagedPoliciesToRole(iamconn, managedPolicies, name); err != nil { + return fmt.Errorf("failed to add managed policies to IAM role %s, error: %s", name, err) + } + } + d.SetId(aws.StringValue(createResp.Role.RoleName)) return resourceAwsIamRoleRead(d, meta) } @@ -189,15 +245,15 @@ func resourceAwsIamRoleRead(d *schema.ResourceData, meta interface{}) error { getResp, err := iamconn.GetRole(request) if err != nil { if isAWSErr(err, iam.ErrCodeNoSuchEntityException, "") { - log.Printf("[WARN] IAM Role %q not found, removing from state", d.Id()) + log.Printf("[WARN] IAM role %q not found, removing from state", d.Id()) d.SetId("") return nil } - return fmt.Errorf("Error reading IAM Role %s: %s", d.Id(), err) + return fmt.Errorf("failed to read IAM role %s, error: %s", d.Id(), err) } if getResp == nil || getResp.Role == nil { - log.Printf("[WARN] IAM Role %q not found, removing from state", d.Id()) + log.Printf("[WARN] IAM role %q not found, removing from state", d.Id()) d.SetId("") return nil } @@ -206,7 +262,7 @@ func resourceAwsIamRoleRead(d *schema.ResourceData, meta interface{}) error { d.Set("arn", role.Arn) if err := d.Set("create_date", role.CreateDate.Format(time.RFC3339)); err != nil { - return err + return fmt.Errorf("failed to set create date (RFC3339) for IAM role %s, error: %s", d.Id(), err) } d.Set("description", role.Description) d.Set("max_session_duration", role.MaxSessionDuration) @@ -223,11 +279,28 @@ func resourceAwsIamRoleRead(d *schema.ResourceData, meta interface{}) error { assumRolePolicy, err := url.QueryUnescape(*role.AssumeRolePolicyDocument) if err != nil { - return err + return fmt.Errorf("failed to unescape assume role policy for IAM role %s, error: %s", d.Id(), err) } if err := d.Set("assume_role_policy", assumRolePolicy); err != nil { - return err + return fmt.Errorf("failed to set assume role policy for IAM role %s, error: %s", d.Id(), err) + } + + inlinePolicies, err := readInlinePoliciesForRole(iamconn, *role.RoleName) + if err != nil { + return fmt.Errorf("failed to read inline policies for IAM role %s, error: %s", d.Id(), err) + } + if err = d.Set("inline_policy", inlinePolicies); err != nil { + return fmt.Errorf("failed to set inline policies for IAM role %s, error: %s", d.Id(), err) + } + + managedPolicies, err := readManagedPoliciesForRole(iamconn, *role.RoleName) + if err != nil { + return fmt.Errorf("failed to read managed policy list for IAM role %s, error: %s", *role.RoleName, err) + } + if err := d.Set("managed_policy_arns", managedPolicies); err != nil { + return fmt.Errorf("failed to set managed policy list for IAM role (%s), error: %s", *role.RoleName, err) } + return nil } @@ -245,7 +318,7 @@ func resourceAwsIamRoleUpdate(d *schema.ResourceData, meta interface{}) error { d.SetId("") return nil } - return fmt.Errorf("Error Updating IAM Role (%s) Assume Role Policy: %s", d.Id(), err) + return fmt.Errorf("failed to update assume role policy for IAM role %s, error: %s", d.Id(), err) } } @@ -260,7 +333,7 @@ func resourceAwsIamRoleUpdate(d *schema.ResourceData, meta interface{}) error { d.SetId("") return nil } - return fmt.Errorf("Error Updating IAM Role (%s) Assume Role Policy: %s", d.Id(), err) + return fmt.Errorf("failed to update role description for IAM role %s, error: %s", d.Id(), err) } } @@ -275,7 +348,7 @@ func resourceAwsIamRoleUpdate(d *schema.ResourceData, meta interface{}) error { d.SetId("") return nil } - return fmt.Errorf("Error Updating IAM Role (%s) Max Session Duration: %s", d.Id(), err) + return fmt.Errorf("failed to update max session duration for IAM role %s, error: %s", d.Id(), err) } } @@ -288,7 +361,7 @@ func resourceAwsIamRoleUpdate(d *schema.ResourceData, meta interface{}) error { } _, err := iamconn.PutRolePermissionsBoundary(input) if err != nil { - return fmt.Errorf("error updating IAM Role permissions boundary: %s", err) + return fmt.Errorf("failed to update permission boundary for IAM role %s, error: %s", d.Id(), err) } } else { input := &iam.DeleteRolePermissionsBoundaryInput{ @@ -296,7 +369,7 @@ func resourceAwsIamRoleUpdate(d *schema.ResourceData, meta interface{}) error { } _, err := iamconn.DeleteRolePermissionsBoundary(input) if err != nil { - return fmt.Errorf("error deleting IAM Role permissions boundary: %s", err) + return fmt.Errorf("failed to delete permission boundary for IAM role %s, error: %s", d.Id(), err) } } } @@ -308,6 +381,55 @@ func resourceAwsIamRoleUpdate(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("error updating IAM Role (%s) tags: %s", d.Id(), err) } } + if d.HasChange("inline_policy") { + roleName := d.Get("name").(string) + o, n := d.GetChange("inline_policy") + if o == nil { + o = new(schema.Set) + } + if n == nil { + n = new(schema.Set) + } + + os := o.(*schema.Set) + ns := n.(*schema.Set) + remove := os.Difference(ns).List() + add := ns.Difference(os).List() + + if err := removeInlinePoliciesFromRole(iamconn, remove, roleName); err != nil { + return fmt.Errorf("failed to remove inline policies for IAM role %s, error: %s", roleName, err) + } + + if err := addInlinePoliciesToRole(iamconn, add, roleName); err != nil { + return fmt.Errorf("failed to add inline policies for IAM role %s, error: %s", roleName, err) + } + } + + if d.HasChange("managed_policy_arns") { + roleName := d.Get("name").(string) + + o, n := d.GetChange("managed_policy_arns") + if o == nil { + o = new(schema.Set) + } + if n == nil { + n = new(schema.Set) + } + + os := o.(*schema.Set) + ns := n.(*schema.Set) + remove := expandStringList(os.Difference(ns).List()) + add := expandStringList(ns.Difference(os).List()) + + if err := removeManagedPoliciesFromRole(iamconn, remove, roleName); err != nil { + return fmt.Errorf("failed to detach managed policies for IAM role %s, error: %s", roleName, err) + } + + if err := addManagedPoliciesToRole(iamconn, add, roleName); err != nil { + return fmt.Errorf("failed to attach managed policies for IAM role %s, error: %s", roleName, err) + } + + } return resourceAwsIamRoleRead(d, meta) } @@ -338,6 +460,53 @@ func deleteAwsIamRole(conn *iam.IAM, rolename string, forceDetach bool) error { if err := deleteAwsIamRolePolicies(conn, rolename); err != nil { return fmt.Errorf("unable to delete inline policies: %w", err) + // Roles cannot be destroyed when attached to an existing Instance Profile + resp, err := iamconn.ListInstanceProfilesForRole(&iam.ListInstanceProfilesForRoleInput{ + RoleName: aws.String(d.Id()), + }) + if err != nil { + return fmt.Errorf("failed to list profiles for IAM role %s, error: %s", d.Id(), err) + } + + // Loop and remove this Role from any Profiles + if len(resp.InstanceProfiles) > 0 { + for _, i := range resp.InstanceProfiles { + _, err := iamconn.RemoveRoleFromInstanceProfile(&iam.RemoveRoleFromInstanceProfileInput{ + InstanceProfileName: i.InstanceProfileName, + RoleName: aws.String(d.Id()), + }) + if err != nil { + return fmt.Errorf("failed to remove IAM role %s from instance profile, error: %s", d.Id(), err) + } + } + } + + if d.Get("force_detach_policies").(bool) || d.Get("inline_policy.#").(int) > 0 { + inlinePolicies, err := readInlinePoliciesForRole(iamconn, d.Id()) + if err != nil { + return fmt.Errorf("failed to read inline policies for IAM role %s, error: %s", d.Id(), err) + } + + if err := removeInlinePoliciesFromRole(iamconn, inlinePolicies, d.Id()); err != nil { + return fmt.Errorf("failed to delete inline policies from IAM role %s, error: %s", d.Id(), err) + } + } + + if d.Get("force_detach_policies").(bool) || d.Get("managed_policy_arns.#").(int) > 0 { + managedPolicies, err := readManagedPoliciesForRole(iamconn, d.Id()) + if err != nil { + return fmt.Errorf("failed to read managed policies for IAM role %s, error: %s", d.Id(), err) + } + + // convert []string to []*string + managedPolicyPtrs := []*string{} + for _, policy := range managedPolicies { + newVar := policy // necessary to get a new pointer + managedPolicyPtrs = append(managedPolicyPtrs, &newVar) + } + + if err := removeManagedPoliciesFromRole(iamconn, managedPolicyPtrs, d.Id()); err != nil { + return fmt.Errorf("failed to detach managed policies from IAM role %s, error: %s", d.Id(), err) } } @@ -372,12 +541,25 @@ func deleteAwsIamRoleInstanceProfiles(conn *iam.IAM, rolename string) error { if err != nil { return err } + return nil - // Loop and remove this Role from any Profiles - for _, i := range resp.InstanceProfiles { - input := &iam.RemoveRoleFromInstanceProfileInput{ - InstanceProfileName: i.InstanceProfileName, - RoleName: aws.String(rolename), +} + +func resourceAwsIamRoleInlineCustDiff(diff *schema.ResourceDiff, v interface{}) error { + + // Avoids diffs resulting when inline policies are configured without either + // name or name prefix, or with a name prefix. In these cases, Terraform + // generates some or all of the name. Without a customized diff function, + // comparing the config to the state will always generate a diff since the + // config has no information about the policy's generated name. + if diff.HasChange("inline_policy") { + + o, n := diff.GetChange("inline_policy") + if o == nil { + o = new(schema.Set) + } + if n == nil { + n = new(schema.Set) } _, err := conn.RemoveRoleFromInstanceProfile(input) @@ -386,21 +568,147 @@ func deleteAwsIamRoleInstanceProfiles(conn *iam.IAM, rolename string) error { } if err != nil { return err + os := o.(*schema.Set) + ns := n.(*schema.Set) + + // a single empty inline_policy in the config produces a diff with + // inline_policy.# = 0 and subattributes all blank + if len(os.List()) == 0 && len(ns.List()) == 1 { + data := (ns.List())[0].(map[string]interface{}) + if data["name"].(string) == "" && data["name_prefix"].(string) == "" && data["policy"].(string) == "" { + if err := diff.Clear("inline_policy"); err != nil { + return fmt.Errorf("failed to clear diff for IAM role %s, error: %s", diff.Id(), err) + } + } + } + + // if there's no old or new set, nothing to do - can't match up + // equivalents between the lists + if len(os.List()) > 0 && len(ns.List()) > 0 { + + // fast O(n) comparison in case of thousands of policies + + // current state lookup map: + // key: inline policy doc hash + // value: string slice with policy names (slice in case of dupes) + statePolicies := make(map[int]interface{}) + for _, policy := range os.List() { + data := policy.(map[string]interface{}) + name := data["name"].(string) + + // condition probably not needed, will have been assigned name + if name != "" { + docHash := hashcode.String(data["policy"].(string)) + if _, ok := statePolicies[docHash]; !ok { + statePolicies[docHash] = []string{name} + } else { + statePolicies[docHash] = append(statePolicies[docHash].([]string), name) + } + } + } + + // construct actual changes by going through incoming config changes + configSet := make([]interface{}, 0) + for _, policy := range ns.List() { + appended := false + data := policy.(map[string]interface{}) + namePrefix := data["name_prefix"].(string) + name := data["name"].(string) + + if namePrefix != "" || (namePrefix == "" && name == "") { + docHash := hashcode.String(data["policy"].(string)) + if namesFromState, ok := statePolicies[docHash]; ok { + for i, nameFromState := range namesFromState.([]string) { + if (namePrefix == "" && name == "") || strings.HasPrefix(nameFromState, namePrefix) { + // match - we want the state value + pair := make(map[string]interface{}) + pair["name"] = nameFromState + pair["policy"] = data["policy"] + configSet = append(configSet, pair) + appended = true + + // remove - in case of duplicate policies + stateSlice := namesFromState.([]string) + stateSlice = append(stateSlice[:i], stateSlice[i+1:]...) + if len(stateSlice) == 0 { + delete(statePolicies, docHash) + } else { + statePolicies[docHash] = stateSlice + } + break + } + } + } + } + + if !appended { + pair := make(map[string]interface{}) + pair["name"] = name + pair["name_prefix"] = namePrefix + pair["policy"] = data["policy"] + configSet = append(configSet, pair) + } + } + if err := diff.SetNew("inline_policy", configSet); err != nil { + return fmt.Errorf("failed to set new inline policies for IAM role %s, error: %s", diff.Id(), err) + } } } return nil } -func deleteAwsIamRolePolicyAttachments(conn *iam.IAM, rolename string) error { - managedPolicies := make([]*string, 0) - input := &iam.ListAttachedRolePoliciesInput{ - RoleName: aws.String(rolename), +func readInlinePoliciesForRole(iamconn *iam.IAM, roleName string) ([]interface{}, error) { + inlinePolicies := make([]interface{}, 0) + var marker *string + for { + resp, err := iamconn.ListRolePolicies(&iam.ListRolePoliciesInput{ + RoleName: aws.String(roleName), + Marker: marker, + }) + + if err != nil { + return nil, err + } + + for _, policyName := range resp.PolicyNames { + policyResp, err := iamconn.GetRolePolicy(&iam.GetRolePolicyInput{ + RoleName: aws.String(roleName), + PolicyName: policyName, + }) + if err != nil { + return nil, err + } + + json, err := url.QueryUnescape(*policyResp.PolicyDocument) + if err != nil { + return nil, err + } + pair := make(map[string]interface{}) + pair["name"] = *policyName + pair["policy"] = json + inlinePolicies = append(inlinePolicies, pair) + } + + if !*resp.IsTruncated { + break + } + marker = resp.Marker } - err := conn.ListAttachedRolePoliciesPages(input, func(page *iam.ListAttachedRolePoliciesOutput, lastPage bool) bool { - for _, v := range page.AttachedPolicies { - managedPolicies = append(managedPolicies, v.PolicyArn) + return inlinePolicies, nil +} + +func readManagedPoliciesForRole(iamconn *iam.IAM, roleName string) ([]string, error) { + var managedPolicyList []string + var marker *string + for { + resp, err := iamconn.ListAttachedRolePolicies(&iam.ListAttachedRolePoliciesInput{ + RoleName: aws.String(roleName), + Marker: marker, + }) + if err != nil { + return nil, err } return !lastPage }) @@ -415,29 +723,71 @@ func deleteAwsIamRolePolicyAttachments(conn *iam.IAM, rolename string) error { input := &iam.DetachRolePolicyInput{ PolicyArn: parn, RoleName: aws.String(rolename), + + for _, ap := range resp.AttachedPolicies { + managedPolicyList = append(managedPolicyList, *ap.PolicyArn) } + if !*resp.IsTruncated { + break + } + marker = resp.Marker + } + return managedPolicyList, nil +} + +func addInlinePoliciesToRole(iamconn *iam.IAM, inlinePolicies []interface{}, roleName string) error { + + if len(inlinePolicies) == 1 { + // check for special case: one empty inline policy + data := inlinePolicies[0].(map[string]interface{}) + if data["name"].(string) == "" && data["name_prefix"].(string) == "" && data["policy"].(string) == "" { + return nil + } + } + _, err = conn.DetachRolePolicy(input) if tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { continue } if err != nil { return err + for _, policy := range inlinePolicies { + data := policy.(map[string]interface{}) + + policyDoc := data["policy"].(string) + if policyDoc == "" { + return fmt.Errorf("policy is required") + } + + var policyName string + + if v, ok := data["name"]; ok && v.(string) != "" { + policyName = v.(string) + } else if v, ok := data["name_prefix"]; ok && v.(string) != "" { + policyName = resource.PrefixedUniqueId(v.(string)) + } else { + policyName = resource.UniqueId() + } + + _, err := iamconn.PutRolePolicy(&iam.PutRolePolicyInput{ + PolicyName: aws.String(policyName), + RoleName: aws.String(roleName), + PolicyDocument: aws.String(policyDoc), + }) + + if err != nil { + return fmt.Errorf("failed to add inline policy to IAM role %s, error: %s", roleName, err) } } return nil } -func deleteAwsIamRolePolicies(conn *iam.IAM, rolename string) error { - inlinePolicies := make([]*string, 0) - input := &iam.ListRolePoliciesInput{ - RoleName: aws.String(rolename), - } +func removeInlinePoliciesFromRole(iamconn *iam.IAM, inlinePolicies []interface{}, roleName string) error { err := conn.ListRolePoliciesPages(input, func(page *iam.ListRolePoliciesOutput, lastPage bool) bool { inlinePolicies = append(inlinePolicies, page.PolicyNames...) - return !lastPage }) if tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { return nil @@ -445,12 +795,27 @@ func deleteAwsIamRolePolicies(conn *iam.IAM, rolename string) error { if err != nil { return err } + policyName := "" + if ok { + policyName = data["name"].(string) + } else { + dataS := policy.(map[string]string) + policyName = dataS["name"] + } - for _, pname := range inlinePolicies { - input := &iam.DeleteRolePolicyInput{ - PolicyName: pname, - RoleName: aws.String(rolename), + _, err := iamconn.DeleteRolePolicy(&iam.DeleteRolePolicyInput{ + PolicyName: aws.String(policyName), + RoleName: aws.String(roleName), + }) + + log.Printf("[WARN] Inline role policy (%s) was already removed from role (%s)", policyName, roleName) + continue + } + return fmt.Errorf("failed to delete inline policy of IAM role %s, error: %s", roleName, err) } + } + return nil +} _, err := conn.DeleteRolePolicy(input) if tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { @@ -458,8 +823,34 @@ func deleteAwsIamRolePolicies(conn *iam.IAM, rolename string) error { } if err != nil { return err +func addManagedPoliciesToRole(iamconn *iam.IAM, managedPolicies []*string, roleName string) error { + for _, arn := range managedPolicies { + _, err := iamconn.AttachRolePolicy(&iam.AttachRolePolicyInput{ + PolicyArn: aws.String(*arn), + RoleName: aws.String(roleName), + }) + + if err != nil { + return fmt.Errorf("failed to attach managed policy to IAM role %s, error: %s", roleName, err) } } + return nil +} + +func removeManagedPoliciesFromRole(iamconn *iam.IAM, managedPolicies []*string, roleName string) error { + for _, arn := range managedPolicies { + _, err := iamconn.DetachRolePolicy(&iam.DetachRolePolicyInput{ + PolicyArn: aws.String(*arn), + RoleName: aws.String(roleName), + }) + if err != nil { + if iamerr, ok := err.(awserr.Error); ok && iamerr.Code() == "NoSuchEntity" { + log.Printf("[WARN] Managed role policy (%s) was already detached from role (%s)", *arn, roleName) + continue + } + return fmt.Errorf("failed to detach managed policy of IAM role %s, error: %s", roleName, err) + } + } return nil } From 25b47c01104637b71849161b3b1f062c755f8fb7 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Mon, 17 Sep 2018 14:23:30 -0400 Subject: [PATCH 02/20] resource/aws_iam_role: Update documentation --- website/docs/r/iam_role.html.markdown | 142 ++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/website/docs/r/iam_role.html.markdown b/website/docs/r/iam_role.html.markdown index dada55200f6..6da08bb2f45 100644 --- a/website/docs/r/iam_role.html.markdown +++ b/website/docs/r/iam_role.html.markdown @@ -57,7 +57,27 @@ The following arguments are supported: * `max_session_duration` - (Optional) The maximum session duration (in seconds) that you want to set for the specified role. If you do not specify a value for this setting, the default maximum of one hour is applied. This setting can have a value from 1 hour to 12 hours. * `permissions_boundary` - (Optional) The ARN of the policy that is used to set the permissions boundary for the role. +<<<<<<< HEAD * `tags` - Key-value map of tags for the IAM role +======= +<<<<<<< HEAD +* `tags` - Key-value mapping of tags for the IAM role +======= +* `managed_policy_arns` - (Optional) An exclusive set of IAM managed policy ARNs to attach to the IAM role. If the attribute is not used, the resource will not attach or detach the role's managed policies on the next `apply`. If the set is empty, all managed policies that are attached out of band, will be detached on the next `apply`. + +~> **NOTE:** The `managed_policy_arns` attribute, which provides an _exclusive_ set of managed policies for an IAM role, will conflict with using the `iam_role_policy_attachment` resource, which provides non-exclusive, managed policy-role attachment. See [`iam_role_policy_attachment`](/docs/providers/aws/r/iam_role_policy_attachment.html). + +* `inline_policy` - (Optional) An exclusive set of IAM inline policies associated with the IAM role. If the attribute is not used, the resource will not add or remove the role's inline policies on the next `apply`. If one empty `inline_policy` attribute is used, any inline policies that are added outside of Terraform will be removed on the next `apply`. + +~> **NOTE:** The `inline_policy` attribute, which provides an _exclusive_ set of inline policies for an IAM role, will conflict with the `iam_role_policy` resource, which provides non-exclusive, inline policy-role association. See [`iam_role_policy`](/docs/providers/aws/r/iam_role_policy.html). + +### inline_policy + +The following arguments are supported: + +* `policy` - (Required) The policy document. This is a JSON formatted string. For more information about building IAM policy documents with Terraform, see the [AWS IAM Policy Document Guide](https://www.terraform.io/docs/providers/aws/guides/iam-policy-documents.html). +* `name` - (Optional) The name of the role policy. If omitted, Terraform will assign a random, unique name. +* `name_prefix` - (Optional) Creates a unique name beginning with the specified prefix. If both `name` and `name_prefix` are used, `name_prefix` will be ignored. ## Attributes Reference @@ -91,6 +111,128 @@ resource "aws_iam_role" "instance" { } ``` +## Example of Using Exclusive Inline Policies + +This example will create an IAM role with two inline IAM policies. If a third policy were added out of band, on the next apply, that policy would be removed. If one of the two original policies were removed, out of band, on the next apply, the policy would be recreated. + +```hcl +resource "aws_iam_role" "example" { + name = "yak_role" + assume_role_policy = "${data.aws_iam_policy_document.instance_assume_role_policy.json}" # (not shown) + + inline_policy { + name = "my_inline_policy" + policy = < Date: Wed, 19 Sep 2018 07:59:52 -0400 Subject: [PATCH 03/20] resource/aws_iam_role: Add new acceptance tests --- aws/resource_aws_iam_role_test.go | 1341 +++++++++++++++++++++++-- website/docs/r/iam_role.html.markdown | 4 +- 2 files changed, 1274 insertions(+), 71 deletions(-) diff --git a/aws/resource_aws_iam_role_test.go b/aws/resource_aws_iam_role_test.go index 8a4be8ec92f..0e9b4e5bb33 100644 --- a/aws/resource_aws_iam_role_test.go +++ b/aws/resource_aws_iam_role_test.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "regexp" + "sort" "strings" "testing" @@ -312,7 +313,7 @@ func TestAccAWSIAMRole_disappears(t *testing.T) { }) } -func TestAccAWSIAMRole_force_detach_policies(t *testing.T) { +func TestAccAWSIAMRole_forceDetachPolicies(t *testing.T) { var conf iam.GetRoleOutput rName := acctest.RandString(10) resourceName := "aws_iam_role.test" @@ -339,7 +340,7 @@ func TestAccAWSIAMRole_force_detach_policies(t *testing.T) { }) } -func TestAccAWSIAMRole_MaxSessionDuration(t *testing.T) { +func TestAccAWSIAMRole_maxSessionDuration(t *testing.T) { var conf iam.GetRoleOutput rName := acctest.RandString(10) resourceName := "aws_iam_role.test" @@ -385,7 +386,7 @@ func TestAccAWSIAMRole_MaxSessionDuration(t *testing.T) { }) } -func TestAccAWSIAMRole_PermissionsBoundary(t *testing.T) { +func TestAccAWSIAMRole_permissionsBoundary(t *testing.T) { var role iam.GetRoleOutput rName := acctest.RandString(10) @@ -419,10 +420,12 @@ func TestAccAWSIAMRole_PermissionsBoundary(t *testing.T) { }, // Test import { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"force_destroy"}, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "force_destroy", + }, }, // Test removal { @@ -491,6 +494,429 @@ func TestAccAWSIAMRole_tags(t *testing.T) { }) } +func TestAccAWSIAMRole_policyBasicInline(t *testing.T) { + var role iam.GetRoleOutput + + rString := acctest.RandString(5) + roleName := fmt.Sprintf("tf-acc-role-policy-basic-%s", rString) + ilPolicyName1 := fmt.Sprintf("tf-acc-ipolicy-basic-1-%s", rString) + ilPolicyName2 := fmt.Sprintf("tf-acc-ipolicy-basic-2-%s", rString) + ilPolicyName3 := fmt.Sprintf("tf-acc-ipolicy-basic-3-%s", rString) + resourceAddr := "aws_iam_role.acc_role" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRoleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSRolePolicyInlineConfig(roleName, ilPolicyName1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRoleExists(resourceAddr, &role), + testAccCheckAWSRolePolicyCheckInline(&role, roleName, []string{ilPolicyName1}), + resource.TestCheckResourceAttr(resourceAddr, "inline_policy.#", "1"), + resource.TestCheckResourceAttr(resourceAddr, "name", roleName), + resource.TestCheckResourceAttr(resourceAddr, "managed_policy_arns.#", "0"), + ), + }, + + { + Config: testAccAWSRolePolicyInlineConfigUpdate(roleName, ilPolicyName2, ilPolicyName3), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRoleExists(resourceAddr, &role), + testAccCheckAWSRolePolicyCheckInline(&role, roleName, []string{ilPolicyName2, ilPolicyName3}), + resource.TestCheckResourceAttr(resourceAddr, "inline_policy.#", "2"), + resource.TestCheckResourceAttr(resourceAddr, "managed_policy_arns.#", "0"), + ), + }, + + { + Config: testAccAWSRolePolicyInlineConfigUpdateDown(roleName, ilPolicyName3), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRoleExists(resourceAddr, &role), + testAccCheckAWSRolePolicyCheckInline(&role, roleName, []string{ilPolicyName3}), + resource.TestCheckResourceAttr(resourceAddr, "inline_policy.#", "1"), + resource.TestCheckResourceAttr(resourceAddr, "managed_policy_arns.#", "0"), + ), + }, + + { + ResourceName: resourceAddr, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSIAMRole_policyInlinePrefix(t *testing.T) { + var role iam.GetRoleOutput + + rString := acctest.RandString(5) + roleName := fmt.Sprintf("tf-acc-role-policy-prefix-%s", rString) + ilPolicyPrefix := fmt.Sprintf("tf-acc-%s", rString) + resourceAddr := "aws_iam_role.acc_role" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRoleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSRolePolicyInlinePrefix(roleName, ilPolicyPrefix), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRoleExists(resourceAddr, &role), + testAccCheckAWSRolePolicyCheckInlinePrefix(&role, roleName, ilPolicyPrefix), + resource.TestCheckResourceAttr(resourceAddr, "inline_policy.#", "1"), + ), + }, + }, + }) +} + +func TestAccAWSIAMRole_policyInlineNoName(t *testing.T) { + var role iam.GetRoleOutput + + rString := acctest.RandString(5) + roleName := fmt.Sprintf("tf-acc-role-policy-prefix-%s", rString) + resourceAddr := "aws_iam_role.acc_role" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRoleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSRolePolicyInlineNoName(roleName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRoleExists(resourceAddr, &role), + resource.TestCheckResourceAttr(resourceAddr, "inline_policy.#", "1"), + ), + }, + }, + }) +} + +func TestAccAWSIAMRole_policyBasicManaged(t *testing.T) { + var role iam.GetRoleOutput + + rString := acctest.RandString(5) + roleName := fmt.Sprintf("tf-acc-role-policy-basic-%s", rString) + mgPolicyName1 := fmt.Sprintf("tf-acc-mpolicy-basic-1-%s", rString) + mgPolicyName2 := fmt.Sprintf("tf-acc-mpolicy-basic-2-%s", rString) + mgPolicyName3 := fmt.Sprintf("tf-acc-mpolicy-basic-3-%s", rString) + resourceAddr := "aws_iam_role.acc_role" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRoleDestroy, + Steps: []resource.TestStep{ + + { + Config: testAccAWSRolePolicyManagedConfig(roleName, mgPolicyName1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRoleExists(resourceAddr, &role), + testAccCheckAWSRolePolicyCheckManaged(&role, roleName, []string{mgPolicyName1}), + resource.TestCheckResourceAttr(resourceAddr, "name", roleName), + resource.TestCheckResourceAttr(resourceAddr, "managed_policy_arns.#", "1"), + ), + }, + + { + Config: testAccAWSRolePolicyManagedConfigUpdate(roleName, mgPolicyName1, mgPolicyName2, mgPolicyName3), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRoleExists(resourceAddr, &role), + testAccCheckAWSRolePolicyCheckManaged(&role, roleName, []string{mgPolicyName2, mgPolicyName3}), + resource.TestCheckResourceAttr(resourceAddr, "managed_policy_arns.#", "2"), + ), + }, + + { + Config: testAccAWSRolePolicyManagedConfigUpdateDown(roleName, mgPolicyName1, mgPolicyName2, mgPolicyName3), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRoleExists(resourceAddr, &role), + testAccCheckAWSRolePolicyCheckManaged(&role, roleName, []string{mgPolicyName3}), + resource.TestCheckResourceAttr(resourceAddr, "managed_policy_arns.#", "1"), + ), + }, + + { + ResourceName: resourceAddr, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +// TestAccAWSIAMRole_policyManagedReattached: if a policy is detached +// externally, it should be reattached. +func TestAccAWSIAMRole_policyManagedReattached(t *testing.T) { + var role iam.GetRoleOutput + + rString := acctest.RandString(5) + roleName := fmt.Sprintf("tf-acc-role-policy-reattach-%s", rString) + mgPolicyName1 := fmt.Sprintf("tf-acc-rpl-mpolicy-detach-1-%s", rString) + resourceAddr := "aws_iam_role.acc_role" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRoleDestroy, + Steps: []resource.TestStep{ + + { + Config: testAccAWSRolePolicyManagedConfig(roleName, mgPolicyName1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRoleExists(resourceAddr, &role), + testAccCheckAWSRolePolicyDetachManagedPolicy(&role, mgPolicyName1), + testAccCheckAWSRolePolicyCheckManaged(&role, roleName, []string{}), + ), + ExpectNonEmptyPlan: true, + }, + + { + Config: testAccAWSRolePolicyManagedConfig(roleName, mgPolicyName1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRoleExists(resourceAddr, &role), + testAccCheckAWSRolePolicyCheckManaged(&role, roleName, []string{mgPolicyName1}), + resource.TestCheckResourceAttr(resourceAddr, "managed_policy_arns.#", "1"), + ), + }, + }, + }) +} + +// TestAccAWSIAMRole_policyExtraManagedAdded: remove externally added +// managed policies. +func TestAccAWSIAMRole_policyExtraManagedAdded(t *testing.T) { + var role iam.GetRoleOutput + + rString := acctest.RandString(5) + roleName := fmt.Sprintf("tf-acc-role-policy-extra-%s", rString) + mgPolicyName1 := fmt.Sprintf("tf-acc-rpl-mpolicy-extra-good-%s", rString) + mgPolicyName2 := fmt.Sprintf("tf-acc-rpl-mpolicy-extra-bad-%s", rString) + resourceAddr := "aws_iam_role.acc_role" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRoleDestroy, + Steps: []resource.TestStep{ + + { + Config: testAccAWSRolePolicyExtraManagedConfig(roleName, mgPolicyName1, mgPolicyName2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRoleExists(resourceAddr, &role), + testAccCheckAWSRolePolicyAttachManagedPolicy(&role, mgPolicyName2), + testAccCheckAWSRolePolicyCheckManaged(&role, roleName, []string{mgPolicyName1, mgPolicyName2}), + ), + ExpectNonEmptyPlan: true, + }, + + { + Config: testAccAWSRolePolicyExtraManagedConfig(roleName, mgPolicyName1, mgPolicyName2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRoleExists(resourceAddr, &role), + testAccCheckAWSRolePolicyCheckManaged(&role, roleName, []string{mgPolicyName1}), + resource.TestCheckResourceAttr(resourceAddr, "managed_policy_arns.#", "1"), + ), + }, + }, + }) +} + +// TestAccAWSIAMRole_policyExtraInlineAdded: remove externally added inline +// policies. +func TestAccAWSIAMRole_policyExtraInlineAdded(t *testing.T) { + var role iam.GetRoleOutput + + rString := acctest.RandString(5) + roleName := fmt.Sprintf("tf-acc-role-policy-extra-%s", rString) + ilPolicyName1 := fmt.Sprintf("tf-acc-rpl-ipolicy-extra-good-%s", rString) + ilPolicyName2 := fmt.Sprintf("tf-acc-rpl-ipolicy-extra-bad-%s", rString) + resourceAddr := "aws_iam_role.acc_role" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRoleDestroy, + Steps: []resource.TestStep{ + + { + Config: testAccAWSRolePolicyInlineConfig(roleName, ilPolicyName1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRoleExists(resourceAddr, &role), + testAccCheckAWSRolePolicyAddInlinePolicy(&role, ilPolicyName2), + testAccCheckAWSRolePolicyCheckInline(&role, roleName, []string{ilPolicyName1, ilPolicyName2}), + ), + ExpectNonEmptyPlan: true, + }, + + { + Config: testAccAWSRolePolicyInlineConfig(roleName, ilPolicyName1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRoleExists(resourceAddr, &role), + testAccCheckAWSRolePolicyCheckInline(&role, roleName, []string{ilPolicyName1}), + resource.TestCheckResourceAttr(resourceAddr, "inline_policy.#", "1"), + resource.TestCheckResourceAttr(resourceAddr, "managed_policy_arns.#", "0"), + ), + }, + }, + }) +} + +// TestAccAWSIAMRole_policyNoInlineListExtraInlineAdded: when there is no +// inline_policy attribute, resource should not do anything if policies +// are added externally +func TestAccAWSIAMRole_policyNoInlineListExtraInlineAdded(t *testing.T) { + var role iam.GetRoleOutput + + rString := acctest.RandString(5) + roleName := fmt.Sprintf("tf-acc-role-policy-no-ilist-%s", rString) + ilPolicyName1 := fmt.Sprintf("tf-acc-rpl-ipolicy-extra-good-%s", rString) + resourceAddr := "aws_iam_role.acc_role" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRoleDestroy, + Steps: []resource.TestStep{ + + { + Config: testAccAWSRolePolicyNoInlineConfig(roleName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRoleExists(resourceAddr, &role), + testAccCheckAWSRolePolicyAddInlinePolicy(&role, ilPolicyName1), + testAccCheckAWSRolePolicyCheckInline(&role, roleName, []string{ilPolicyName1}), + ), + }, + + { + Config: testAccAWSRolePolicyNoInlineConfig(roleName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRoleExists(resourceAddr, &role), + testAccCheckAWSRolePolicyCheckInline(&role, roleName, []string{ilPolicyName1}), + testAccCheckAWSRolePolicyRemoveInlinePolicy(&role, ilPolicyName1), + ), + }, + }, + }) +} + +// TestAccAWSIAMRole_policyNoManagedListExtraManagedAdded: if there is no +// managed_policies attribute, resource should not do anything if one is attached. +func TestAccAWSIAMRole_policyNoManagedListExtraManagedAdded(t *testing.T) { + var role iam.GetRoleOutput + + rString := acctest.RandString(5) + roleName := fmt.Sprintf("tf-acc-role-policy-no-mlist-%s", rString) + mgPolicyName1 := fmt.Sprintf("tf-acc-rpl-mpolicy-extra-good-%s", rString) + resourceAddr := "aws_iam_role.acc_role" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRoleDestroy, + Steps: []resource.TestStep{ + + { + Config: testAccAWSRolePolicyNoManagedConfig(roleName, mgPolicyName1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRoleExists(resourceAddr, &role), + testAccCheckAWSRolePolicyAttachManagedPolicy(&role, mgPolicyName1), + testAccCheckAWSRolePolicyCheckManaged(&role, roleName, []string{mgPolicyName1}), + ), + }, + + { + Config: testAccAWSRolePolicyNoManagedConfig(roleName, mgPolicyName1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRoleExists(resourceAddr, &role), + testAccCheckAWSRolePolicyCheckManaged(&role, roleName, []string{mgPolicyName1}), + testAccCheckAWSRolePolicyDetachManagedPolicy(&role, mgPolicyName1), + ), + }, + }, + }) +} + +// TestAccAWSIAMRole_policyEmptyInlineListExtraInlineAdded: when there is an +// empty inline_policy attribute, resource should remove policies that +// are added externally +func TestAccAWSIAMRole_policyEmptyInlineListExtraInlineAdded(t *testing.T) { + var role iam.GetRoleOutput + + rString := acctest.RandString(5) + roleName := fmt.Sprintf("tf-acc-role-policy-empty-%s", rString) + ilPolicyName1 := fmt.Sprintf("tf-acc-rpl-ipolicy-extra-bad-%s", rString) + resourceAddr := "aws_iam_role.acc_role" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRoleDestroy, + Steps: []resource.TestStep{ + + { + Config: testAccAWSRolePolicyEmptyInlineConfig(roleName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRoleExists(resourceAddr, &role), + testAccCheckAWSRolePolicyAddInlinePolicy(&role, ilPolicyName1), + testAccCheckAWSRolePolicyCheckInline(&role, roleName, []string{ilPolicyName1}), + ), + ExpectNonEmptyPlan: true, + }, + + { + Config: testAccAWSRolePolicyEmptyInlineConfig(roleName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRoleExists(resourceAddr, &role), + testAccCheckAWSRolePolicyCheckInline(&role, roleName, []string{}), + ), + }, + }, + }) +} + +// TestAccAWSIAMRole_policyEmptyManagedListExtraManagedAdded: if there is no +// managed_policies attribute, resource should not do anything if one is attached. +func TestAccAWSIAMRole_policyEmptyManagedListExtraManagedAdded(t *testing.T) { + var role iam.GetRoleOutput + + rString := acctest.RandString(5) + roleName := fmt.Sprintf("tf-acc-role-policy-empty-%s", rString) + mgPolicyName1 := fmt.Sprintf("tf-acc-rpl-mpolicy-extra-bad-%s", rString) + resourceAddr := "aws_iam_role.acc_role" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRoleDestroy, + Steps: []resource.TestStep{ + + { + Config: testAccAWSRolePolicyEmptyManagedConfig(roleName, mgPolicyName1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRoleExists(resourceAddr, &role), + testAccCheckAWSRolePolicyAttachManagedPolicy(&role, mgPolicyName1), + testAccCheckAWSRolePolicyCheckManaged(&role, roleName, []string{mgPolicyName1}), + ), + ExpectNonEmptyPlan: true, + }, + + { + Config: testAccAWSRolePolicyEmptyManagedConfig(roleName, mgPolicyName1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRoleExists(resourceAddr, &role), + testAccCheckAWSRolePolicyCheckManaged(&role, roleName, []string{}), + ), + }, + }, + }) +} + func testAccCheckAWSRoleDestroy(s *terraform.State) error { iamconn := testAccProvider.Meta().(*AWSClient).iamconn @@ -499,100 +925,437 @@ func testAccCheckAWSRoleDestroy(s *terraform.State) error { continue } - // Try to get role - _, err := iamconn.GetRole(&iam.GetRoleInput{ - RoleName: aws.String(rs.Primary.ID), - }) - if err == nil { - return fmt.Errorf("still exist.") + // Try to get role + _, err := iamconn.GetRole(&iam.GetRoleInput{ + RoleName: aws.String(rs.Primary.ID), + }) + if err == nil { + return fmt.Errorf("still exist.") + } + + // Verify the error is what we want + ec2err, ok := err.(awserr.Error) + if !ok { + return err + } + if ec2err.Code() != "NoSuchEntity" { + return err + } + } + + return nil +} + +func testAccCheckAWSRoleExists(n string, res *iam.GetRoleOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Role name is set") + } + + iamconn := testAccProvider.Meta().(*AWSClient).iamconn + + resp, err := iamconn.GetRole(&iam.GetRoleInput{ + RoleName: aws.String(rs.Primary.ID), + }) + if err != nil { + return err + } + + *res = *resp + + return nil + } +} + +func testAccCheckAWSRoleDisappears(getRoleOutput *iam.GetRoleOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + iamconn := testAccProvider.Meta().(*AWSClient).iamconn + + roleName := aws.StringValue(getRoleOutput.Role.RoleName) + + _, err := iamconn.DeleteRole(&iam.DeleteRoleInput{ + RoleName: aws.String(roleName), + }) + if err != nil { + return fmt.Errorf("error deleting role %q: %s", roleName, err) + } + + return nil + } +} + +func testAccCheckAWSRoleGeneratedNamePrefix(resource, prefix string) resource.TestCheckFunc { + return func(s *terraform.State) error { + r, ok := s.RootModule().Resources[resource] + if !ok { + return fmt.Errorf("Resource not found") + } + name, ok := r.Primary.Attributes["name"] + if !ok { + return fmt.Errorf("Name attr not found: %#v", r.Primary.Attributes) + } + if !strings.HasPrefix(name, prefix) { + return fmt.Errorf("Name: %q, does not have prefix: %q", name, prefix) + } + return nil + } +} + +// Attach inline policy outside of terraform CRUD. +func testAccAddAwsIAMRolePolicy(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Resource not found") + } + if rs.Primary.ID == "" { + return fmt.Errorf("No Role name is set") + } + + iamconn := testAccProvider.Meta().(*AWSClient).iamconn + + input := &iam.PutRolePolicyInput{ + RoleName: aws.String(rs.Primary.ID), + PolicyDocument: aws.String(`{ + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "*", + "Resource": "*" + } + }`), + PolicyName: aws.String(resource.UniqueId()), + } + + _, err := iamconn.PutRolePolicy(input) + return err + } +} + +func testAccCheckAWSRolePermissionsBoundary(getRoleOutput *iam.GetRoleOutput, expectedPermissionsBoundaryArn string) resource.TestCheckFunc { + return func(s *terraform.State) error { + actualPermissionsBoundaryArn := "" + + if getRoleOutput.Role.PermissionsBoundary != nil { + actualPermissionsBoundaryArn = *getRoleOutput.Role.PermissionsBoundary.PermissionsBoundaryArn + } + + if actualPermissionsBoundaryArn != expectedPermissionsBoundaryArn { + return fmt.Errorf("PermissionsBoundary: '%q', expected '%q'.", actualPermissionsBoundaryArn, expectedPermissionsBoundaryArn) + } + + return nil + } +} + +func testAccCheckAWSRolePolicyCheckInline(role *iam.GetRoleOutput, roleName string, inlinePolicies []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if !strings.Contains(*role.Role.RoleName, roleName) { + return fmt.Errorf("bad role: expected %s, got %s", roleName, *role.Role.RoleName) + } + + conn := testAccProvider.Meta().(*AWSClient).iamconn + + //inline policies + var inlinePolicyList []string + var marker *string + for { + //inline is ListRolePolicies + resp, err := conn.ListRolePolicies(&iam.ListRolePoliciesInput{ + RoleName: aws.String(roleName), + Marker: marker, + }) + + if err != nil { + if awsErr, ok := err.(awserr.Error); ok { + // aws specific error + if awsErr.Code() == "NoSuchEntity" { + // policies not found + break + } + } + return err + } + + for _, policyName := range resp.PolicyNames { + inlinePolicyList = append(inlinePolicyList, *policyName) + } + + if !*resp.IsTruncated { + break + } + marker = resp.Marker + } + + if !compareStringSlices(inlinePolicyList, inlinePolicies) { + return fmt.Errorf("inline policies did not match: %s (from AWS) to %s (expected)", strings.Join(inlinePolicyList, ","), strings.Join(inlinePolicies, ",")) + } + + return nil + } +} + +func testAccCheckAWSRolePolicyCheckManaged(role *iam.GetRoleOutput, roleName string, managedPolicies []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if !strings.Contains(*role.Role.RoleName, roleName) { + return fmt.Errorf("bad role: expected %s, got %s", roleName, *role.Role.RoleName) + } + + conn := testAccProvider.Meta().(*AWSClient).iamconn + + // managed policies + var managedPolicyList []string + var marker *string + for { + resp, err := conn.ListAttachedRolePolicies(&iam.ListAttachedRolePoliciesInput{ + RoleName: aws.String(roleName), + Marker: marker, + }) + + if err != nil { + if awsErr, ok := err.(awserr.Error); ok { + // aws specific error + if awsErr.Code() == "NoSuchEntity" { + // role not found + break + } + } + return err + } + + for _, ap := range resp.AttachedPolicies { + managedPolicyList = append(managedPolicyList, *ap.PolicyName) //PolicyName also available + } + + if !*resp.IsTruncated { + break + } + marker = resp.Marker + } + + if !compareStringSlices(managedPolicyList, managedPolicies) { + return fmt.Errorf("managed policies did not match: %s (from AWS) to %s (expected)", strings.Join(managedPolicyList, ","), strings.Join(managedPolicies, ",")) + } + + return nil + } +} + +func testAccCheckAWSRolePolicyDetachManagedPolicy(role *iam.GetRoleOutput, managedPolicy string) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).iamconn + + // find in managed policies + var policyARN string + var marker *string + for { + resp, err := conn.ListAttachedRolePolicies(&iam.ListAttachedRolePoliciesInput{ + RoleName: aws.String(*role.Role.RoleName), + Marker: marker, + }) + + if err != nil { + return err + } + + for _, ap := range resp.AttachedPolicies { + if *ap.PolicyName == managedPolicy { + policyARN = *ap.PolicyArn + break + } + } + + if !*resp.IsTruncated { + break + } + marker = resp.Marker + } + + if policyARN == "" { + return fmt.Errorf("managed policy %s not found", managedPolicy) } - // Verify the error is what we want - ec2err, ok := err.(awserr.Error) - if !ok { - return err - } - if ec2err.Code() != "NoSuchEntity" { + _, err := conn.DetachRolePolicy(&iam.DetachRolePolicyInput{ + PolicyArn: aws.String(policyARN), + RoleName: aws.String(*role.Role.RoleName), + }) + + if err != nil { return err } - } - return nil + return nil + } } -func testAccCheckAWSRoleExists(n string, res *iam.GetRoleOutput) resource.TestCheckFunc { +func testAccCheckAWSRolePolicyAttachManagedPolicy(role *iam.GetRoleOutput, managedPolicy string) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } + conn := testAccProvider.Meta().(*AWSClient).iamconn + + // find in managed policies + var policyARN string + var marker *string + for { + pathPrefix := "/tf-testing/" + policyUsageFilter := "PermissionsPolicy" + scope := "Local" + resp, err := conn.ListPolicies(&iam.ListPoliciesInput{ + PathPrefix: &pathPrefix, + PolicyUsageFilter: &policyUsageFilter, + Scope: &scope, + Marker: marker, + }) + + if err != nil { + return err + } - if rs.Primary.ID == "" { - return fmt.Errorf("No Role name is set") + for _, ap := range resp.Policies { + if *ap.PolicyName == managedPolicy { + policyARN = *ap.Arn + break + } + } + + if !*resp.IsTruncated { + break + } + marker = resp.Marker } - iamconn := testAccProvider.Meta().(*AWSClient).iamconn + if policyARN == "" { + return fmt.Errorf("managed policy %s not found", managedPolicy) + } - resp, err := iamconn.GetRole(&iam.GetRoleInput{ - RoleName: aws.String(rs.Primary.ID), + _, err := conn.AttachRolePolicy(&iam.AttachRolePolicyInput{ + PolicyArn: aws.String(policyARN), + RoleName: aws.String(*role.Role.RoleName), }) + if err != nil { return err } - *res = *resp - return nil } } -func testAccCheckAWSRoleDisappears(getRoleOutput *iam.GetRoleOutput) resource.TestCheckFunc { +func testAccCheckAWSRolePolicyAddInlinePolicy(role *iam.GetRoleOutput, inlinePolicy string) resource.TestCheckFunc { return func(s *terraform.State) error { - iamconn := testAccProvider.Meta().(*AWSClient).iamconn - - roleName := aws.StringValue(getRoleOutput.Role.RoleName) + conn := testAccProvider.Meta().(*AWSClient).iamconn - _, err := iamconn.DeleteRole(&iam.DeleteRoleInput{ - RoleName: aws.String(roleName), + _, err := conn.PutRolePolicy(&iam.PutRolePolicyInput{ + PolicyDocument: aws.String(testAccAWSRolePolicyExtraInlineConfig()), + PolicyName: aws.String(inlinePolicy), + RoleName: aws.String(*role.Role.RoleName), }) + if err != nil { - return fmt.Errorf("error deleting role %q: %s", roleName, err) + return err } + return nil + } +} + +func testAccCheckAWSRolePolicyRemoveInlinePolicy(role *iam.GetRoleOutput, inlinePolicy string) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).iamconn + + _, err := conn.DeleteRolePolicy(&iam.DeleteRolePolicyInput{ + PolicyName: aws.String(inlinePolicy), + RoleName: aws.String(*role.Role.RoleName), + }) + if err != nil { + return err + } return nil } } -func testAccCheckAWSRoleGeneratedNamePrefix(resource, prefix string) resource.TestCheckFunc { +func testAccCheckAWSRolePolicyCheckInlinePrefix(role *iam.GetRoleOutput, roleName string, prefix string) resource.TestCheckFunc { return func(s *terraform.State) error { - r, ok := s.RootModule().Resources[resource] - if !ok { - return fmt.Errorf("Resource not found") + if *role.Role.RoleName != roleName { + return fmt.Errorf("bad role: expected %s, got %s", roleName, *role.Role.RoleName) } - name, ok := r.Primary.Attributes["name"] - if !ok { - return fmt.Errorf("Name attr not found: %#v", r.Primary.Attributes) + + conn := testAccProvider.Meta().(*AWSClient).iamconn + + //inline policies + var inlinePolicyList []string + var marker *string + for { + //inline is ListRolePolicies + resp, err := conn.ListRolePolicies(&iam.ListRolePoliciesInput{ + RoleName: aws.String(roleName), + Marker: marker, + }) + + if err != nil { + return err + } + + for _, policyName := range resp.PolicyNames { + inlinePolicyList = append(inlinePolicyList, *policyName) + } + + if !*resp.IsTruncated { + break + } + marker = resp.Marker } - if !strings.HasPrefix(name, prefix) { - return fmt.Errorf("Name: %q, does not have prefix: %q", name, prefix) + + match := false + r := regexp.MustCompile(fmt.Sprintf("^%s(.*)$", prefix)) + + for _, policyName := range inlinePolicyList { + if r.MatchString(policyName) { + match = true + break + } + } + + if !match { + return fmt.Errorf( + "%s didn't match any inline policies", + prefix) } + return nil } } -// Attach inline policy outside of terraform CRUD. -func testAccAddAwsIAMRolePolicy(n string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Resource not found") +func deleteAwsIamRoleInstanceProfiles(conn *iam.IAM, rolename string) error { + resp, err := conn.ListInstanceProfilesForRole(&iam.ListInstanceProfilesForRoleInput{ + RoleName: aws.String(rolename), + }) + if err != nil { + return fmt.Errorf("Error listing Profiles for IAM Role (%s) when trying to delete: %s", rolename, err) + } + + // Loop and remove this Role from any Profiles + for _, i := range resp.InstanceProfiles { + input := &iam.RemoveRoleFromInstanceProfileInput{ + InstanceProfileName: i.InstanceProfileName, + RoleName: aws.String(rolename), } - if rs.Primary.ID == "" { - return fmt.Errorf("No Role name is set") + + _, err := conn.RemoveRoleFromInstanceProfile(input) + + if err != nil { + return fmt.Errorf("Error deleting IAM Role %s: %s", rolename, err) } + } - iamconn := testAccProvider.Meta().(*AWSClient).iamconn + return nil +} +<<<<<<< HEAD input := &iam.PutRolePolicyInput{ RoleName: aws.String(rs.Primary.ID), PolicyDocument: aws.String(`{ @@ -604,27 +1367,90 @@ func testAccAddAwsIAMRolePolicy(n string) resource.TestCheckFunc { } }`), PolicyName: aws.String(resource.UniqueId()), +======= +func deleteAwsIamRolePolicyAttachments(conn *iam.IAM, rolename string) error { + managedPolicies := make([]*string, 0) + input := &iam.ListAttachedRolePoliciesInput{ + RoleName: aws.String(rolename), + } + + err := conn.ListAttachedRolePoliciesPages(input, func(page *iam.ListAttachedRolePoliciesOutput, lastPage bool) bool { + for _, v := range page.AttachedPolicies { + managedPolicies = append(managedPolicies, v.PolicyArn) + } + return !lastPage + }) + if err != nil { + return fmt.Errorf("Error listing Policies for IAM Role (%s) when trying to delete: %s", rolename, err) + } + for _, parn := range managedPolicies { + input := &iam.DetachRolePolicyInput{ + PolicyArn: parn, + RoleName: aws.String(rolename), +>>>>>>> 558baf839 (resource/aws_iam_role: Add new acceptance tests) } - _, err := iamconn.PutRolePolicy(input) - return err + _, err = conn.DetachRolePolicy(input) + + if isAWSErr(err, iam.ErrCodeNoSuchEntityException, "") { + continue + } + + if err != nil { + return fmt.Errorf("Error deleting IAM Role %s: %s", rolename, err) + } } + + return nil } -func testAccCheckAWSRolePermissionsBoundary(getRoleOutput *iam.GetRoleOutput, expectedPermissionsBoundaryArn string) resource.TestCheckFunc { - return func(s *terraform.State) error { - actualPermissionsBoundaryArn := "" +func deleteAwsIamRolePolicies(conn *iam.IAM, rolename string) error { + inlinePolicies := make([]*string, 0) + input := &iam.ListRolePoliciesInput{ + RoleName: aws.String(rolename), + } - if getRoleOutput.Role.PermissionsBoundary != nil { - actualPermissionsBoundaryArn = *getRoleOutput.Role.PermissionsBoundary.PermissionsBoundaryArn + err := conn.ListRolePoliciesPages(input, func(page *iam.ListRolePoliciesOutput, lastPage bool) bool { + inlinePolicies = append(inlinePolicies, page.PolicyNames...) + return !lastPage + }) + + if err != nil { + return fmt.Errorf("Error listing inline Policies for IAM Role (%s) when trying to delete: %s", rolename, err) + } + + for _, pname := range inlinePolicies { + input := &iam.DeleteRolePolicyInput{ + PolicyName: pname, + RoleName: aws.String(rolename), } - if actualPermissionsBoundaryArn != expectedPermissionsBoundaryArn { - return fmt.Errorf("PermissionsBoundary: '%q', expected '%q'.", actualPermissionsBoundaryArn, expectedPermissionsBoundaryArn) + _, err := conn.DeleteRolePolicy(input) + + if isAWSErr(err, iam.ErrCodeNoSuchEntityException, "") { + continue } - return nil + if err != nil { + return fmt.Errorf("Error deleting inline policy of IAM Role %s: %s", rolename, err) + } } + + return nil +} + +func compareStringSlices(a []string, b []string) bool { + if len(a) != len(b) { + return false + } + sort.Strings(a) + sort.Strings(b) + for i := range b { + if a[i] != b[i] { + return false + } + } + return true } func testAccCheckIAMRoleConfig_MaxSessionDuration(rName string, maxSessionDuration int) string { @@ -1057,3 +1883,382 @@ EOF } `, rName) } + +func testAccAWSRoleAssumeRolePolicy() string { + return ` +data "aws_iam_policy_document" "assume-role" { + statement { + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["ec2.amazonaws.com"] + } + } +} + ` +} + +func testAccAWSRolePolicyInlineConfig(roleName, ilPolicyName1 string) string { + return fmt.Sprintf(` +resource "aws_iam_role" "acc_role" { + name = "%s" + assume_role_policy = "${data.aws_iam_policy_document.assume-role.json}" + inline_policy { + name = "%s" + policy = < **NOTE:** The `managed_policy_arns` attribute, which provides an _exclusive_ set of managed policies for an IAM role, will conflict with using the `iam_role_policy_attachment` resource, which provides non-exclusive, managed policy-role attachment. See [`iam_role_policy_attachment`](/docs/providers/aws/r/iam_role_policy_attachment.html). From a11bf99bacc814fa309a77fdd4505d18b88658f9 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Thu, 28 Jan 2021 16:40:18 -0500 Subject: [PATCH 04/20] resource/iam_role: Revert less significant changes --- aws/resource_aws_iam_role.go | 24 ++++++++++++------------ aws/resource_aws_iam_role_test.go | 9 +++------ website/docs/r/iam_role.html.markdown | 2 -- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/aws/resource_aws_iam_role.go b/aws/resource_aws_iam_role.go index d58abdc9958..e6768e96cc7 100644 --- a/aws/resource_aws_iam_role.go +++ b/aws/resource_aws_iam_role.go @@ -213,7 +213,7 @@ func resourceAwsIamRoleCreate(d *schema.ResourceData, meta interface{}) error { createResp, err = iamconn.CreateRole(request) } if err != nil { - return fmt.Errorf("failed to create IAM role %s, error: %s", name, err) + return fmt.Errorf("Error creating IAM Role %s: %s", name, err) } if policyData, ok := d.GetOk("inline_policy"); ok { @@ -245,15 +245,15 @@ func resourceAwsIamRoleRead(d *schema.ResourceData, meta interface{}) error { getResp, err := iamconn.GetRole(request) if err != nil { if isAWSErr(err, iam.ErrCodeNoSuchEntityException, "") { - log.Printf("[WARN] IAM role %q not found, removing from state", d.Id()) + log.Printf("[WARN] IAM Role %q not found, removing from state", d.Id()) d.SetId("") return nil } - return fmt.Errorf("failed to read IAM role %s, error: %s", d.Id(), err) + return fmt.Errorf("Error reading IAM Role %s: %s", d.Id(), err) } if getResp == nil || getResp.Role == nil { - log.Printf("[WARN] IAM role %q not found, removing from state", d.Id()) + log.Printf("[WARN] IAM Role %q not found, removing from state", d.Id()) d.SetId("") return nil } @@ -262,7 +262,7 @@ func resourceAwsIamRoleRead(d *schema.ResourceData, meta interface{}) error { d.Set("arn", role.Arn) if err := d.Set("create_date", role.CreateDate.Format(time.RFC3339)); err != nil { - return fmt.Errorf("failed to set create date (RFC3339) for IAM role %s, error: %s", d.Id(), err) + return err } d.Set("description", role.Description) d.Set("max_session_duration", role.MaxSessionDuration) @@ -279,10 +279,10 @@ func resourceAwsIamRoleRead(d *schema.ResourceData, meta interface{}) error { assumRolePolicy, err := url.QueryUnescape(*role.AssumeRolePolicyDocument) if err != nil { - return fmt.Errorf("failed to unescape assume role policy for IAM role %s, error: %s", d.Id(), err) + return err } if err := d.Set("assume_role_policy", assumRolePolicy); err != nil { - return fmt.Errorf("failed to set assume role policy for IAM role %s, error: %s", d.Id(), err) + return err } inlinePolicies, err := readInlinePoliciesForRole(iamconn, *role.RoleName) @@ -318,7 +318,7 @@ func resourceAwsIamRoleUpdate(d *schema.ResourceData, meta interface{}) error { d.SetId("") return nil } - return fmt.Errorf("failed to update assume role policy for IAM role %s, error: %s", d.Id(), err) + return fmt.Errorf("Error Updating IAM Role (%s) Assume Role Policy: %s", d.Id(), err) } } @@ -333,7 +333,7 @@ func resourceAwsIamRoleUpdate(d *schema.ResourceData, meta interface{}) error { d.SetId("") return nil } - return fmt.Errorf("failed to update role description for IAM role %s, error: %s", d.Id(), err) + return fmt.Errorf("Error Updating IAM Role (%s) Assume Role Policy: %s", d.Id(), err) } } @@ -348,7 +348,7 @@ func resourceAwsIamRoleUpdate(d *schema.ResourceData, meta interface{}) error { d.SetId("") return nil } - return fmt.Errorf("failed to update max session duration for IAM role %s, error: %s", d.Id(), err) + return fmt.Errorf("Error Updating IAM Role (%s) Max Session Duration: %s", d.Id(), err) } } @@ -361,7 +361,7 @@ func resourceAwsIamRoleUpdate(d *schema.ResourceData, meta interface{}) error { } _, err := iamconn.PutRolePermissionsBoundary(input) if err != nil { - return fmt.Errorf("failed to update permission boundary for IAM role %s, error: %s", d.Id(), err) + return fmt.Errorf("error updating IAM Role permissions boundary: %s", err) } } else { input := &iam.DeleteRolePermissionsBoundaryInput{ @@ -369,7 +369,7 @@ func resourceAwsIamRoleUpdate(d *schema.ResourceData, meta interface{}) error { } _, err := iamconn.DeleteRolePermissionsBoundary(input) if err != nil { - return fmt.Errorf("failed to delete permission boundary for IAM role %s, error: %s", d.Id(), err) + return fmt.Errorf("error deleting IAM Role permissions boundary: %s", err) } } } diff --git a/aws/resource_aws_iam_role_test.go b/aws/resource_aws_iam_role_test.go index 0e9b4e5bb33..fb0d9ad64b6 100644 --- a/aws/resource_aws_iam_role_test.go +++ b/aws/resource_aws_iam_role_test.go @@ -313,7 +313,7 @@ func TestAccAWSIAMRole_disappears(t *testing.T) { }) } -func TestAccAWSIAMRole_forceDetachPolicies(t *testing.T) { +func TestAccAWSIAMRole_force_detach_policies(t *testing.T) { var conf iam.GetRoleOutput rName := acctest.RandString(10) resourceName := "aws_iam_role.test" @@ -340,7 +340,7 @@ func TestAccAWSIAMRole_forceDetachPolicies(t *testing.T) { }) } -func TestAccAWSIAMRole_maxSessionDuration(t *testing.T) { +func TestAccAWSIAMRole_MaxSessionDuration(t *testing.T) { var conf iam.GetRoleOutput rName := acctest.RandString(10) resourceName := "aws_iam_role.test" @@ -386,7 +386,7 @@ func TestAccAWSIAMRole_maxSessionDuration(t *testing.T) { }) } -func TestAccAWSIAMRole_permissionsBoundary(t *testing.T) { +func TestAccAWSIAMRole_PermissionsBoundary(t *testing.T) { var role iam.GetRoleOutput rName := acctest.RandString(10) @@ -1355,7 +1355,6 @@ func deleteAwsIamRoleInstanceProfiles(conn *iam.IAM, rolename string) error { return nil } -<<<<<<< HEAD input := &iam.PutRolePolicyInput{ RoleName: aws.String(rs.Primary.ID), PolicyDocument: aws.String(`{ @@ -1367,7 +1366,6 @@ func deleteAwsIamRoleInstanceProfiles(conn *iam.IAM, rolename string) error { } }`), PolicyName: aws.String(resource.UniqueId()), -======= func deleteAwsIamRolePolicyAttachments(conn *iam.IAM, rolename string) error { managedPolicies := make([]*string, 0) input := &iam.ListAttachedRolePoliciesInput{ @@ -1387,7 +1385,6 @@ func deleteAwsIamRolePolicyAttachments(conn *iam.IAM, rolename string) error { input := &iam.DetachRolePolicyInput{ PolicyArn: parn, RoleName: aws.String(rolename), ->>>>>>> 558baf839 (resource/aws_iam_role: Add new acceptance tests) } _, err = conn.DetachRolePolicy(input) diff --git a/website/docs/r/iam_role.html.markdown b/website/docs/r/iam_role.html.markdown index aada428d532..c6b898fa996 100644 --- a/website/docs/r/iam_role.html.markdown +++ b/website/docs/r/iam_role.html.markdown @@ -57,8 +57,6 @@ The following arguments are supported: * `max_session_duration` - (Optional) The maximum session duration (in seconds) that you want to set for the specified role. If you do not specify a value for this setting, the default maximum of one hour is applied. This setting can have a value from 1 hour to 12 hours. * `permissions_boundary` - (Optional) The ARN of the policy that is used to set the permissions boundary for the role. -<<<<<<< HEAD -<<<<<<< HEAD * `tags` - Key-value map of tags for the IAM role * `tags` - Key-value mapping of tags for the IAM role * `managed_policy_arns` - (Optional) An exclusive set of IAM managed policy ARNs to attach to the IAM role. If the attribute is not used, the resource will not attach or detach the role's managed policies on the next `apply`. If the set is empty, all managed policies that are attached out of band, will be detached on the next `apply`. From bc6986447fd51dcb144102b16a7f670dc5164d3f Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Thu, 4 Feb 2021 10:32:26 -0500 Subject: [PATCH 05/20] resource/iam_role: Modernize old patterns --- aws/resource_aws_iam_role.go | 580 +++++++++---------- aws/resource_aws_iam_role_test.go | 924 ++++++++++++++++++------------ 2 files changed, 843 insertions(+), 661 deletions(-) diff --git a/aws/resource_aws_iam_role.go b/aws/resource_aws_iam_role.go index e6768e96cc7..081753478c0 100644 --- a/aws/resource_aws_iam_role.go +++ b/aws/resource_aws_iam_role.go @@ -5,19 +5,17 @@ import ( "log" "net/url" "regexp" - "strings" "time" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/iam" "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/go-multierror" "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" - "github.com/terraform-providers/terraform-provider-aws/aws/internal/hashcode" ) func resourceAwsIamRole() *schema.Resource { @@ -29,7 +27,7 @@ func resourceAwsIamRole() *schema.Resource { Importer: &schema.ResourceImporter{ State: resourceAwsIamRoleImport, }, - CustomizeDiff: resourceAwsIamRoleInlineCustDiff, + //CustomizeDiff: resourceAwsIamRoleInlineCustDiff, Schema: map[string]*schema.Schema{ "arn": { Type: schema.TypeString, @@ -120,26 +118,23 @@ func resourceAwsIamRole() *schema.Resource { Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "policy": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validateIAMPolicyJson, - DiffSuppressFunc: suppressEquivalentAwsPolicyDiffs, - }, "name": { Type: schema.TypeString, Optional: true, Computed: true, ValidateFunc: validateIamRolePolicyName, - // ConflictsWith: []string{"inline_policy.0.name_prefix"}, - // Not working: prevents two separate policies with - // one having a name and the other a prefix }, "name_prefix": { Type: schema.TypeString, Optional: true, ValidateFunc: validateIamRolePolicyNamePrefix, }, + "policy": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateIAMPolicyJson, + DiffSuppressFunc: suppressEquivalentAwsPolicyDiffs, + }, }, }, }, @@ -216,21 +211,23 @@ func resourceAwsIamRoleCreate(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error creating IAM Role %s: %s", name, err) } - if policyData, ok := d.GetOk("inline_policy"); ok { - inlinePolicies := policyData.(*schema.Set).List() - if err := addInlinePoliciesToRole(iamconn, inlinePolicies, name); err != nil { - return fmt.Errorf("failed to add inline policies to IAM role %s, error: %s", name, err) + roleName := aws.StringValue(createResp.Role.RoleName) + + if v, ok := d.GetOk("inline_policy"); ok && v.(*schema.Set).Len() > 0 { + policies := expandIamInlinePolicies(roleName, v.(*schema.Set).List()) + if err := resourceAwsIamRoleCreateInlinePolicies(policies, meta); err != nil { + return err } } - if policies, ok := d.GetOk("managed_policy_arns"); ok { - managedPolicies := expandStringList(policies.(*schema.Set).List()) - if err := addManagedPoliciesToRole(iamconn, managedPolicies, name); err != nil { - return fmt.Errorf("failed to add managed policies to IAM role %s, error: %s", name, err) + if v, ok := d.GetOk("managed_policy_arns"); ok && v.(*schema.Set).Len() > 0 { + managedPolicies := expandStringSet(v.(*schema.Set)) + if err := resourceAwsIamRoleAttachManagedPolicies(roleName, managedPolicies, meta); err != nil { + return err } } - d.SetId(aws.StringValue(createResp.Role.RoleName)) + d.SetId(roleName) return resourceAwsIamRoleRead(d, meta) } @@ -285,21 +282,19 @@ func resourceAwsIamRoleRead(d *schema.ResourceData, meta interface{}) error { return err } - inlinePolicies, err := readInlinePoliciesForRole(iamconn, *role.RoleName) + inlinePolicies, err := resourceAwsIamRoleListInlinePolicies(*role.RoleName, meta) if err != nil { - return fmt.Errorf("failed to read inline policies for IAM role %s, error: %s", d.Id(), err) + return fmt.Errorf("reading inline policies for IAM role %s, error: %s", d.Id(), err) } - if err = d.Set("inline_policy", inlinePolicies); err != nil { - return fmt.Errorf("failed to set inline policies for IAM role %s, error: %s", d.Id(), err) + if err := d.Set("inline_policy", flattenIamInlinePolicies(inlinePolicies)); err != nil { + return fmt.Errorf("setting attribute_name: %w", err) } - managedPolicies, err := readManagedPoliciesForRole(iamconn, *role.RoleName) + managedPolicies, err := readAwsIamRolePolicyAttachments(iamconn, *role.RoleName) if err != nil { - return fmt.Errorf("failed to read managed policy list for IAM role %s, error: %s", *role.RoleName, err) - } - if err := d.Set("managed_policy_arns", managedPolicies); err != nil { - return fmt.Errorf("failed to set managed policy list for IAM role (%s), error: %s", *role.RoleName, err) + return fmt.Errorf("reading managed policies for IAM role %s, error: %s", d.Id(), err) } + d.Set("managed_policy_arns", managedPolicies) return nil } @@ -381,6 +376,7 @@ func resourceAwsIamRoleUpdate(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("error updating IAM Role (%s) tags: %s", d.Id(), err) } } + if d.HasChange("inline_policy") { roleName := d.Get("name").(string) o, n := d.GetChange("inline_policy") @@ -396,12 +392,23 @@ func resourceAwsIamRoleUpdate(d *schema.ResourceData, meta interface{}) error { remove := os.Difference(ns).List() add := ns.Difference(os).List() - if err := removeInlinePoliciesFromRole(iamconn, remove, roleName); err != nil { - return fmt.Errorf("failed to remove inline policies for IAM role %s, error: %s", roleName, err) + var policyNames []*string + for _, policy := range remove { + tfMap, ok := policy.(map[string]interface{}) + + if !ok { + continue + } + + policyNames = append(policyNames, aws.String(tfMap["name"].(string))) + } + if err := deleteAwsIamRolePolicies(iamconn, roleName, policyNames); err != nil { + return fmt.Errorf("unable to delete inline policies: %w", err) } - if err := addInlinePoliciesToRole(iamconn, add, roleName); err != nil { - return fmt.Errorf("failed to add inline policies for IAM role %s, error: %s", roleName, err) + policies := expandIamInlinePolicies(roleName, add) + if err := resourceAwsIamRoleCreateInlinePolicies(policies, meta); err != nil { + return err } } @@ -421,14 +428,13 @@ func resourceAwsIamRoleUpdate(d *schema.ResourceData, meta interface{}) error { remove := expandStringList(os.Difference(ns).List()) add := expandStringList(ns.Difference(os).List()) - if err := removeManagedPoliciesFromRole(iamconn, remove, roleName); err != nil { - return fmt.Errorf("failed to detach managed policies for IAM role %s, error: %s", roleName, err) + if err := deleteAwsIamRolePolicyAttachments(iamconn, roleName, remove); err != nil { + return fmt.Errorf("unable to detach policies: %w", err) } - if err := addManagedPoliciesToRole(iamconn, add, roleName); err != nil { - return fmt.Errorf("failed to attach managed policies for IAM role %s, error: %s", roleName, err) + if err := resourceAwsIamRoleAttachManagedPolicies(roleName, add, meta); err != nil { + return err } - } return resourceAwsIamRoleRead(d, meta) @@ -454,59 +460,22 @@ func deleteAwsIamRole(conn *iam.IAM, rolename string, forceDetach bool) error { } if forceDetach { - if err := deleteAwsIamRolePolicyAttachments(conn, rolename); err != nil { - return fmt.Errorf("unable to detach policies: %w", err) - } - - if err := deleteAwsIamRolePolicies(conn, rolename); err != nil { - return fmt.Errorf("unable to delete inline policies: %w", err) - // Roles cannot be destroyed when attached to an existing Instance Profile - resp, err := iamconn.ListInstanceProfilesForRole(&iam.ListInstanceProfilesForRoleInput{ - RoleName: aws.String(d.Id()), - }) - if err != nil { - return fmt.Errorf("failed to list profiles for IAM role %s, error: %s", d.Id(), err) - } - - // Loop and remove this Role from any Profiles - if len(resp.InstanceProfiles) > 0 { - for _, i := range resp.InstanceProfiles { - _, err := iamconn.RemoveRoleFromInstanceProfile(&iam.RemoveRoleFromInstanceProfileInput{ - InstanceProfileName: i.InstanceProfileName, - RoleName: aws.String(d.Id()), - }) - if err != nil { - return fmt.Errorf("failed to remove IAM role %s from instance profile, error: %s", d.Id(), err) - } - } - } - - if d.Get("force_detach_policies").(bool) || d.Get("inline_policy.#").(int) > 0 { - inlinePolicies, err := readInlinePoliciesForRole(iamconn, d.Id()) + managedPolicies, err := readAwsIamRolePolicyAttachments(conn, rolename) if err != nil { - return fmt.Errorf("failed to read inline policies for IAM role %s, error: %s", d.Id(), err) + return err } - if err := removeInlinePoliciesFromRole(iamconn, inlinePolicies, d.Id()); err != nil { - return fmt.Errorf("failed to delete inline policies from IAM role %s, error: %s", d.Id(), err) + if err := deleteAwsIamRolePolicyAttachments(conn, rolename, managedPolicies); err != nil { + return fmt.Errorf("unable to detach policies: %w", err) } - } - if d.Get("force_detach_policies").(bool) || d.Get("managed_policy_arns.#").(int) > 0 { - managedPolicies, err := readManagedPoliciesForRole(iamconn, d.Id()) + inlinePolicies, err := readAwsIamRolePolicyNames(conn, rolename) if err != nil { - return fmt.Errorf("failed to read managed policies for IAM role %s, error: %s", d.Id(), err) - } - - // convert []string to []*string - managedPolicyPtrs := []*string{} - for _, policy := range managedPolicies { - newVar := policy // necessary to get a new pointer - managedPolicyPtrs = append(managedPolicyPtrs, &newVar) + return err } - if err := removeManagedPoliciesFromRole(iamconn, managedPolicyPtrs, d.Id()); err != nil { - return fmt.Errorf("failed to detach managed policies from IAM role %s, error: %s", d.Id(), err) + if err := deleteAwsIamRolePolicies(conn, rolename, inlinePolicies); err != nil { + return fmt.Errorf("unable to delete inline policies: %w", err) } } @@ -541,12 +510,247 @@ func deleteAwsIamRoleInstanceProfiles(conn *iam.IAM, rolename string) error { if err != nil { return err } + + // Loop and remove this Role from any Profiles + for _, i := range resp.InstanceProfiles { + input := &iam.RemoveRoleFromInstanceProfileInput{ + InstanceProfileName: i.InstanceProfileName, + RoleName: aws.String(rolename), + } + + _, err := conn.RemoveRoleFromInstanceProfile(input) + if tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { + continue + } + if err != nil { + return err + } + } + + return nil +} + +func readAwsIamRolePolicyAttachments(conn *iam.IAM, rolename string) ([]*string, error) { + managedPolicies := make([]*string, 0) + input := &iam.ListAttachedRolePoliciesInput{ + RoleName: aws.String(rolename), + } + + err := conn.ListAttachedRolePoliciesPages(input, func(page *iam.ListAttachedRolePoliciesOutput, lastPage bool) bool { + for _, v := range page.AttachedPolicies { + managedPolicies = append(managedPolicies, v.PolicyArn) + } + return !lastPage + }) + if err != nil && !tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { + return nil, err + } + + return managedPolicies, nil +} + +func deleteAwsIamRolePolicyAttachments(conn *iam.IAM, rolename string, managedPolicies []*string) error { + for _, parn := range managedPolicies { + input := &iam.DetachRolePolicyInput{ + PolicyArn: parn, + RoleName: aws.String(rolename), + } + + _, err := conn.DetachRolePolicy(input) + if tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { + continue + } + if err != nil { + return err + } + } + + return nil +} + +func readAwsIamRolePolicyNames(conn *iam.IAM, rolename string) ([]*string, error) { + inlinePolicies := make([]*string, 0) + input := &iam.ListRolePoliciesInput{ + RoleName: aws.String(rolename), + } + + err := conn.ListRolePoliciesPages(input, func(page *iam.ListRolePoliciesOutput, lastPage bool) bool { + inlinePolicies = append(inlinePolicies, page.PolicyNames...) + return !lastPage + }) + + if err != nil && !tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { + return nil, err + } + + return inlinePolicies, nil +} + +func deleteAwsIamRolePolicies(conn *iam.IAM, rolename string, policyNames []*string) error { + for _, name := range policyNames { + input := &iam.DeleteRolePolicyInput{ + PolicyName: name, + RoleName: aws.String(rolename), + } + + _, err := conn.DeleteRolePolicy(input) + if tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { + return nil + } + if err != nil { + return err + } + } + return nil +} + +func flattenIamInlinePolicy(apiObject *iam.PutRolePolicyInput) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + tfMap["name"] = aws.StringValue(apiObject.PolicyName) + tfMap["policy"] = aws.StringValue(apiObject.PolicyDocument) + + return tfMap +} + +func flattenIamInlinePolicies(apiObjects []*iam.PutRolePolicyInput) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []interface{} + + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfList = append(tfList, flattenIamInlinePolicy(apiObject)) + } + + return tfList +} + +func expandIamInlinePolicy(roleName string, tfMap map[string]interface{}) *iam.PutRolePolicyInput { + if tfMap == nil { + return nil + } + + apiObject := &iam.PutRolePolicyInput{ + RoleName: aws.String(roleName), + PolicyDocument: aws.String(tfMap["policy"].(string)), + } + + var policyName string + if v, ok := tfMap["name"]; ok { + policyName = v.(string) + } else if v, ok := tfMap["name_prefix"]; ok { + policyName = resource.PrefixedUniqueId(v.(string)) + } else { + policyName = resource.UniqueId() + } + apiObject.PolicyName = aws.String(policyName) + + return apiObject +} + +func expandIamInlinePolicies(roleName string, tfList []interface{}) []*iam.PutRolePolicyInput { + if len(tfList) == 0 { + return nil + } + + var apiObjects []*iam.PutRolePolicyInput + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + apiObject := expandIamInlinePolicy(roleName, tfMap) + + if apiObject == nil { + continue + } + + apiObjects = append(apiObjects, apiObject) + } + return apiObjects } -func resourceAwsIamRoleInlineCustDiff(diff *schema.ResourceDiff, v interface{}) error { +func resourceAwsIamRoleCreateInlinePolicies(policies []*iam.PutRolePolicyInput, meta interface{}) error { + conn := meta.(*AWSClient).iamconn + + var errs *multierror.Error + for _, policy := range policies { + if _, err := conn.PutRolePolicy(policy); err != nil { + newErr := fmt.Errorf("creating inline policy (%s): %w", aws.StringValue(policy.PolicyName), err) + log.Printf("[ERROR] %s", newErr) + errs = multierror.Append(errs, newErr) + } + } + + return errs.ErrorOrNil() +} + +func resourceAwsIamRoleAttachManagedPolicies(roleName string, policies []*string, meta interface{}) error { + conn := meta.(*AWSClient).iamconn + + var errs *multierror.Error + for _, arn := range policies { + if err := attachPolicyToRole(conn, roleName, aws.StringValue(arn)); err != nil { + newErr := fmt.Errorf("attaching managed policy (%s): %w", aws.StringValue(arn), err) + log.Printf("[ERROR] %s", newErr) + errs = multierror.Append(errs, newErr) + } + } + + return errs.ErrorOrNil() +} + +func resourceAwsIamRoleListInlinePolicies(roleName string, meta interface{}) ([]*iam.PutRolePolicyInput, error) { + conn := meta.(*AWSClient).iamconn + + policyNames, err := readAwsIamRolePolicyNames(conn, roleName) + if err != nil { + return nil, err + } + + var apiObjects []*iam.PutRolePolicyInput + for _, policyName := range policyNames { + policyResp, err := conn.GetRolePolicy(&iam.GetRolePolicyInput{ + RoleName: aws.String(roleName), + PolicyName: policyName, + }) + if err != nil { + return nil, err + } + + policy, err := url.QueryUnescape(*policyResp.PolicyDocument) + if err != nil { + return nil, err + } + + apiObject := &iam.PutRolePolicyInput{ + RoleName: aws.String(roleName), + PolicyDocument: aws.String(policy), + } + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects, nil +} +/* +func resourceAwsIamRoleInlineCustDiff(_ context.Context, diff *schema.ResourceDiff, meta interface{}) error { // Avoids diffs resulting when inline policies are configured without either // name or name prefix, or with a name prefix. In these cases, Terraform // generates some or all of the name. Without a customized diff function, @@ -562,12 +766,6 @@ func resourceAwsIamRoleInlineCustDiff(diff *schema.ResourceDiff, v interface{}) n = new(schema.Set) } - _, err := conn.RemoveRoleFromInstanceProfile(input) - if tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { - continue - } - if err != nil { - return err os := o.(*schema.Set) ns := n.(*schema.Set) @@ -657,200 +855,4 @@ func resourceAwsIamRoleInlineCustDiff(diff *schema.ResourceDiff, v interface{}) return nil } - -func readInlinePoliciesForRole(iamconn *iam.IAM, roleName string) ([]interface{}, error) { - inlinePolicies := make([]interface{}, 0) - var marker *string - for { - resp, err := iamconn.ListRolePolicies(&iam.ListRolePoliciesInput{ - RoleName: aws.String(roleName), - Marker: marker, - }) - - if err != nil { - return nil, err - } - - for _, policyName := range resp.PolicyNames { - policyResp, err := iamconn.GetRolePolicy(&iam.GetRolePolicyInput{ - RoleName: aws.String(roleName), - PolicyName: policyName, - }) - if err != nil { - return nil, err - } - - json, err := url.QueryUnescape(*policyResp.PolicyDocument) - if err != nil { - return nil, err - } - pair := make(map[string]interface{}) - pair["name"] = *policyName - pair["policy"] = json - inlinePolicies = append(inlinePolicies, pair) - } - - if !*resp.IsTruncated { - break - } - marker = resp.Marker - } - - return inlinePolicies, nil -} - -func readManagedPoliciesForRole(iamconn *iam.IAM, roleName string) ([]string, error) { - var managedPolicyList []string - var marker *string - for { - resp, err := iamconn.ListAttachedRolePolicies(&iam.ListAttachedRolePoliciesInput{ - RoleName: aws.String(roleName), - Marker: marker, - }) - if err != nil { - return nil, err - } - return !lastPage - }) - if tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { - return nil - } - if err != nil { - return err - } - - for _, parn := range managedPolicies { - input := &iam.DetachRolePolicyInput{ - PolicyArn: parn, - RoleName: aws.String(rolename), - - for _, ap := range resp.AttachedPolicies { - managedPolicyList = append(managedPolicyList, *ap.PolicyArn) - } - - if !*resp.IsTruncated { - break - } - marker = resp.Marker - } - return managedPolicyList, nil -} - -func addInlinePoliciesToRole(iamconn *iam.IAM, inlinePolicies []interface{}, roleName string) error { - - if len(inlinePolicies) == 1 { - // check for special case: one empty inline policy - data := inlinePolicies[0].(map[string]interface{}) - if data["name"].(string) == "" && data["name_prefix"].(string) == "" && data["policy"].(string) == "" { - return nil - } - } - - _, err = conn.DetachRolePolicy(input) - if tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { - continue - } - if err != nil { - return err - for _, policy := range inlinePolicies { - data := policy.(map[string]interface{}) - - policyDoc := data["policy"].(string) - if policyDoc == "" { - return fmt.Errorf("policy is required") - } - - var policyName string - - if v, ok := data["name"]; ok && v.(string) != "" { - policyName = v.(string) - } else if v, ok := data["name_prefix"]; ok && v.(string) != "" { - policyName = resource.PrefixedUniqueId(v.(string)) - } else { - policyName = resource.UniqueId() - } - - _, err := iamconn.PutRolePolicy(&iam.PutRolePolicyInput{ - PolicyName: aws.String(policyName), - RoleName: aws.String(roleName), - PolicyDocument: aws.String(policyDoc), - }) - - if err != nil { - return fmt.Errorf("failed to add inline policy to IAM role %s, error: %s", roleName, err) - } - } - - return nil -} - -func removeInlinePoliciesFromRole(iamconn *iam.IAM, inlinePolicies []interface{}, roleName string) error { - - err := conn.ListRolePoliciesPages(input, func(page *iam.ListRolePoliciesOutput, lastPage bool) bool { - inlinePolicies = append(inlinePolicies, page.PolicyNames...) - }) - if tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { - return nil - } - if err != nil { - return err - } - policyName := "" - if ok { - policyName = data["name"].(string) - } else { - dataS := policy.(map[string]string) - policyName = dataS["name"] - } - - _, err := iamconn.DeleteRolePolicy(&iam.DeleteRolePolicyInput{ - PolicyName: aws.String(policyName), - RoleName: aws.String(roleName), - }) - - log.Printf("[WARN] Inline role policy (%s) was already removed from role (%s)", policyName, roleName) - continue - } - return fmt.Errorf("failed to delete inline policy of IAM role %s, error: %s", roleName, err) - } - } - return nil -} - - _, err := conn.DeleteRolePolicy(input) - if tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { - return nil - } - if err != nil { - return err -func addManagedPoliciesToRole(iamconn *iam.IAM, managedPolicies []*string, roleName string) error { - for _, arn := range managedPolicies { - _, err := iamconn.AttachRolePolicy(&iam.AttachRolePolicyInput{ - PolicyArn: aws.String(*arn), - RoleName: aws.String(roleName), - }) - - if err != nil { - return fmt.Errorf("failed to attach managed policy to IAM role %s, error: %s", roleName, err) - } - } - return nil -} - -func removeManagedPoliciesFromRole(iamconn *iam.IAM, managedPolicies []*string, roleName string) error { - for _, arn := range managedPolicies { - _, err := iamconn.DetachRolePolicy(&iam.DetachRolePolicyInput{ - PolicyArn: aws.String(*arn), - RoleName: aws.String(roleName), - }) - - if err != nil { - if iamerr, ok := err.(awserr.Error); ok && iamerr.Code() == "NoSuchEntity" { - log.Printf("[WARN] Managed role policy (%s) was already detached from role (%s)", *arn, roleName) - continue - } - return fmt.Errorf("failed to detach managed policy of IAM role %s, error: %s", roleName, err) - } - } - return nil -} +*/ diff --git a/aws/resource_aws_iam_role_test.go b/aws/resource_aws_iam_role_test.go index fb0d9ad64b6..a26d938791f 100644 --- a/aws/resource_aws_iam_role_test.go +++ b/aws/resource_aws_iam_role_test.go @@ -1022,13 +1022,13 @@ func testAccAddAwsIAMRolePolicy(n string) resource.TestCheckFunc { input := &iam.PutRolePolicyInput{ RoleName: aws.String(rs.Primary.ID), PolicyDocument: aws.String(`{ - "Version": "2012-10-17", - "Statement": { - "Effect": "Allow", - "Action": "*", - "Resource": "*" - } - }`), + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "*", + "Resource": "*" + } +}`), PolicyName: aws.String(resource.UniqueId()), } @@ -1330,112 +1330,6 @@ func testAccCheckAWSRolePolicyCheckInlinePrefix(role *iam.GetRoleOutput, roleNam } } -func deleteAwsIamRoleInstanceProfiles(conn *iam.IAM, rolename string) error { - resp, err := conn.ListInstanceProfilesForRole(&iam.ListInstanceProfilesForRoleInput{ - RoleName: aws.String(rolename), - }) - if err != nil { - return fmt.Errorf("Error listing Profiles for IAM Role (%s) when trying to delete: %s", rolename, err) - } - - // Loop and remove this Role from any Profiles - for _, i := range resp.InstanceProfiles { - input := &iam.RemoveRoleFromInstanceProfileInput{ - InstanceProfileName: i.InstanceProfileName, - RoleName: aws.String(rolename), - } - - _, err := conn.RemoveRoleFromInstanceProfile(input) - - if err != nil { - return fmt.Errorf("Error deleting IAM Role %s: %s", rolename, err) - } - } - - return nil -} - - input := &iam.PutRolePolicyInput{ - RoleName: aws.String(rs.Primary.ID), - PolicyDocument: aws.String(`{ - "Version": "2012-10-17", - "Statement": { - "Effect": "Allow", - "Action": "*", - "Resource": "*" - } -}`), - PolicyName: aws.String(resource.UniqueId()), -func deleteAwsIamRolePolicyAttachments(conn *iam.IAM, rolename string) error { - managedPolicies := make([]*string, 0) - input := &iam.ListAttachedRolePoliciesInput{ - RoleName: aws.String(rolename), - } - - err := conn.ListAttachedRolePoliciesPages(input, func(page *iam.ListAttachedRolePoliciesOutput, lastPage bool) bool { - for _, v := range page.AttachedPolicies { - managedPolicies = append(managedPolicies, v.PolicyArn) - } - return !lastPage - }) - if err != nil { - return fmt.Errorf("Error listing Policies for IAM Role (%s) when trying to delete: %s", rolename, err) - } - for _, parn := range managedPolicies { - input := &iam.DetachRolePolicyInput{ - PolicyArn: parn, - RoleName: aws.String(rolename), - } - - _, err = conn.DetachRolePolicy(input) - - if isAWSErr(err, iam.ErrCodeNoSuchEntityException, "") { - continue - } - - if err != nil { - return fmt.Errorf("Error deleting IAM Role %s: %s", rolename, err) - } - } - - return nil -} - -func deleteAwsIamRolePolicies(conn *iam.IAM, rolename string) error { - inlinePolicies := make([]*string, 0) - input := &iam.ListRolePoliciesInput{ - RoleName: aws.String(rolename), - } - - err := conn.ListRolePoliciesPages(input, func(page *iam.ListRolePoliciesOutput, lastPage bool) bool { - inlinePolicies = append(inlinePolicies, page.PolicyNames...) - return !lastPage - }) - - if err != nil { - return fmt.Errorf("Error listing inline Policies for IAM Role (%s) when trying to delete: %s", rolename, err) - } - - for _, pname := range inlinePolicies { - input := &iam.DeleteRolePolicyInput{ - PolicyName: pname, - RoleName: aws.String(rolename), - } - - _, err := conn.DeleteRolePolicy(input) - - if isAWSErr(err, iam.ErrCodeNoSuchEntityException, "") { - continue - } - - if err != nil { - return fmt.Errorf("Error deleting inline policy of IAM Role %s: %s", rolename, err) - } - } - - return nil -} - func compareStringSlices(a []string, b []string) bool { if len(a) != len(b) { return false @@ -1881,285 +1775,517 @@ EOF `, rName) } -func testAccAWSRoleAssumeRolePolicy() string { - return ` -data "aws_iam_policy_document" "assume-role" { - statement { - actions = ["sts:AssumeRole"] - - principals { - type = "Service" - identifiers = ["ec2.amazonaws.com"] - } - } -} - ` -} - func testAccAWSRolePolicyInlineConfig(roleName, ilPolicyName1 string) string { return fmt.Sprintf(` -resource "aws_iam_role" "acc_role" { - name = "%s" - assume_role_policy = "${data.aws_iam_policy_document.assume-role.json}" - inline_policy { - name = "%s" - policy = < Date: Thu, 4 Feb 2021 14:21:03 -0500 Subject: [PATCH 06/20] resource/iam_role: Fix lint issues --- aws/resource_aws_iam_role.go | 4 +- aws/resource_aws_iam_role_test.go | 52 ++++++----- website/docs/r/iam_role.html.markdown | 127 +++++++++++++------------- 3 files changed, 93 insertions(+), 90 deletions(-) diff --git a/aws/resource_aws_iam_role.go b/aws/resource_aws_iam_role.go index 081753478c0..5e688021748 100644 --- a/aws/resource_aws_iam_role.go +++ b/aws/resource_aws_iam_role.go @@ -425,8 +425,8 @@ func resourceAwsIamRoleUpdate(d *schema.ResourceData, meta interface{}) error { os := o.(*schema.Set) ns := n.(*schema.Set) - remove := expandStringList(os.Difference(ns).List()) - add := expandStringList(ns.Difference(os).List()) + remove := expandStringSet(os.Difference(ns)) + add := expandStringSet(ns.Difference(os)) if err := deleteAwsIamRolePolicyAttachments(iamconn, roleName, remove); err != nil { return fmt.Errorf("unable to detach policies: %w", err) diff --git a/aws/resource_aws_iam_role_test.go b/aws/resource_aws_iam_role_test.go index a26d938791f..c3f3c4ef523 100644 --- a/aws/resource_aws_iam_role_test.go +++ b/aws/resource_aws_iam_role_test.go @@ -502,7 +502,7 @@ func TestAccAWSIAMRole_policyBasicInline(t *testing.T) { ilPolicyName1 := fmt.Sprintf("tf-acc-ipolicy-basic-1-%s", rString) ilPolicyName2 := fmt.Sprintf("tf-acc-ipolicy-basic-2-%s", rString) ilPolicyName3 := fmt.Sprintf("tf-acc-ipolicy-basic-3-%s", rString) - resourceAddr := "aws_iam_role.acc_role" + resourceAddr := "aws_iam_role.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -555,7 +555,7 @@ func TestAccAWSIAMRole_policyInlinePrefix(t *testing.T) { rString := acctest.RandString(5) roleName := fmt.Sprintf("tf-acc-role-policy-prefix-%s", rString) ilPolicyPrefix := fmt.Sprintf("tf-acc-%s", rString) - resourceAddr := "aws_iam_role.acc_role" + resourceAddr := "aws_iam_role.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -579,7 +579,7 @@ func TestAccAWSIAMRole_policyInlineNoName(t *testing.T) { rString := acctest.RandString(5) roleName := fmt.Sprintf("tf-acc-role-policy-prefix-%s", rString) - resourceAddr := "aws_iam_role.acc_role" + resourceAddr := "aws_iam_role.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -605,7 +605,7 @@ func TestAccAWSIAMRole_policyBasicManaged(t *testing.T) { mgPolicyName1 := fmt.Sprintf("tf-acc-mpolicy-basic-1-%s", rString) mgPolicyName2 := fmt.Sprintf("tf-acc-mpolicy-basic-2-%s", rString) mgPolicyName3 := fmt.Sprintf("tf-acc-mpolicy-basic-3-%s", rString) - resourceAddr := "aws_iam_role.acc_role" + resourceAddr := "aws_iam_role.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -658,7 +658,7 @@ func TestAccAWSIAMRole_policyManagedReattached(t *testing.T) { rString := acctest.RandString(5) roleName := fmt.Sprintf("tf-acc-role-policy-reattach-%s", rString) mgPolicyName1 := fmt.Sprintf("tf-acc-rpl-mpolicy-detach-1-%s", rString) - resourceAddr := "aws_iam_role.acc_role" + resourceAddr := "aws_iam_role.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -697,7 +697,7 @@ func TestAccAWSIAMRole_policyExtraManagedAdded(t *testing.T) { roleName := fmt.Sprintf("tf-acc-role-policy-extra-%s", rString) mgPolicyName1 := fmt.Sprintf("tf-acc-rpl-mpolicy-extra-good-%s", rString) mgPolicyName2 := fmt.Sprintf("tf-acc-rpl-mpolicy-extra-bad-%s", rString) - resourceAddr := "aws_iam_role.acc_role" + resourceAddr := "aws_iam_role.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -736,7 +736,7 @@ func TestAccAWSIAMRole_policyExtraInlineAdded(t *testing.T) { roleName := fmt.Sprintf("tf-acc-role-policy-extra-%s", rString) ilPolicyName1 := fmt.Sprintf("tf-acc-rpl-ipolicy-extra-good-%s", rString) ilPolicyName2 := fmt.Sprintf("tf-acc-rpl-ipolicy-extra-bad-%s", rString) - resourceAddr := "aws_iam_role.acc_role" + resourceAddr := "aws_iam_role.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -776,7 +776,7 @@ func TestAccAWSIAMRole_policyNoInlineListExtraInlineAdded(t *testing.T) { rString := acctest.RandString(5) roleName := fmt.Sprintf("tf-acc-role-policy-no-ilist-%s", rString) ilPolicyName1 := fmt.Sprintf("tf-acc-rpl-ipolicy-extra-good-%s", rString) - resourceAddr := "aws_iam_role.acc_role" + resourceAddr := "aws_iam_role.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -813,7 +813,7 @@ func TestAccAWSIAMRole_policyNoManagedListExtraManagedAdded(t *testing.T) { rString := acctest.RandString(5) roleName := fmt.Sprintf("tf-acc-role-policy-no-mlist-%s", rString) mgPolicyName1 := fmt.Sprintf("tf-acc-rpl-mpolicy-extra-good-%s", rString) - resourceAddr := "aws_iam_role.acc_role" + resourceAddr := "aws_iam_role.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -851,7 +851,7 @@ func TestAccAWSIAMRole_policyEmptyInlineListExtraInlineAdded(t *testing.T) { rString := acctest.RandString(5) roleName := fmt.Sprintf("tf-acc-role-policy-empty-%s", rString) ilPolicyName1 := fmt.Sprintf("tf-acc-rpl-ipolicy-extra-bad-%s", rString) - resourceAddr := "aws_iam_role.acc_role" + resourceAddr := "aws_iam_role.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -888,7 +888,7 @@ func TestAccAWSIAMRole_policyEmptyManagedListExtraManagedAdded(t *testing.T) { rString := acctest.RandString(5) roleName := fmt.Sprintf("tf-acc-role-policy-empty-%s", rString) mgPolicyName1 := fmt.Sprintf("tf-acc-rpl-mpolicy-extra-bad-%s", rString) - resourceAddr := "aws_iam_role.acc_role" + resourceAddr := "aws_iam_role.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -1925,6 +1925,7 @@ EOF inline_policy { name = %[2]q + policy = < **NOTE:** This `assume_role_policy` is very similar but slightly different than just a standard IAM policy and cannot use an `aws_iam_policy` resource. It _can_ however, use an `aws_iam_policy_document` [data source](/docs/providers/aws/d/iam_policy_document.html), see example below for how this could work. - -* `force_detach_policies` - (Optional) Specifies to force detaching any policies the role has before destroying it. Defaults to `false`. -* `path` - (Optional) The path to the role. - See [IAM Identifiers](https://docs.aws.amazon.com/IAM/latest/UserGuide/Using_Identifiers.html) for more information. -* `description` - (Optional) The description of the role. - -* `max_session_duration` - (Optional) The maximum session duration (in seconds) that you want to set for the specified role. If you do not specify a value for this setting, the default maximum of one hour is applied. This setting can have a value from 1 hour to 12 hours. -* `permissions_boundary` - (Optional) The ARN of the policy that is used to set the permissions boundary for the role. -* `tags` - Key-value map of tags for the IAM role -* `tags` - Key-value mapping of tags for the IAM role -* `managed_policy_arns` - (Optional) An exclusive set of IAM managed policy ARNs to attach to the IAM role. If the attribute is not used, the resource will not attach or detach the role's managed policies on the next `apply`. If the set is empty, all managed policies that are attached out of band, will be detached on the next `apply`. - -~> **NOTE:** The `managed_policy_arns` attribute, which provides an _exclusive_ set of managed policies for an IAM role, will conflict with using the `iam_role_policy_attachment` resource, which provides non-exclusive, managed policy-role attachment. See [`iam_role_policy_attachment`](/docs/providers/aws/r/iam_role_policy_attachment.html). - -* `inline_policy` - (Optional) An exclusive set of IAM inline policies associated with the IAM role. If the attribute is not used, the resource will not add or remove the role's inline policies on the next `apply`. If one empty `inline_policy` attribute is used, any inline policies that are added outside of Terraform will be removed on the next `apply`. - -~> **NOTE:** The `inline_policy` attribute, which provides an _exclusive_ set of inline policies for an IAM role, will conflict with the `iam_role_policy` resource, which provides non-exclusive, inline policy-role association. See [`iam_role_policy`](/docs/providers/aws/r/iam_role_policy.html). - -### inline_policy - -The following arguments are supported: - -* `policy` - (Required) The policy document. This is a JSON formatted string. For more information about building IAM policy documents with Terraform, see the [AWS IAM Policy Document Guide](https://www.terraform.io/docs/providers/aws/guides/iam-policy-documents.html). -* `name` - (Optional) The name of the role policy. If omitted, Terraform will assign a random, unique name. -* `name_prefix` - (Optional) Creates a unique name beginning with the specified prefix. If both `name` and `name_prefix` are used, `name_prefix` will be ignored. - -## Attributes Reference - -In addition to all arguments above, the following attributes are exported: - -* `arn` - The Amazon Resource Name (ARN) specifying the role. -* `create_date` - The creation date of the IAM role. -* `description` - The description of the role. -* `id` - The name of the role. -* `name` - The name of the role. -* `unique_id` - The stable and unique string identifying the role. - -## Example of Using Data Source for Assume Role Policy +### Example of Using Data Source for Assume Role Policy ```hcl data "aws_iam_policy_document" "instance-assume-role-policy" { @@ -107,17 +63,18 @@ resource "aws_iam_role" "instance" { } ``` -## Example of Using Exclusive Inline Policies +### Example of Using Exclusive Inline Policies This example will create an IAM role with two inline IAM policies. If a third policy were added out of band, on the next apply, that policy would be removed. If one of the two original policies were removed, out of band, on the next apply, the policy would be recreated. ```hcl resource "aws_iam_role" "example" { name = "yak_role" - assume_role_policy = "${data.aws_iam_policy_document.instance_assume_role_policy.json}" # (not shown) + assume_role_policy = data.aws_iam_policy_document.instance_assume_role_policy.json # (not shown) inline_policy { name = "my_inline_policy" + policy = < **NOTE:** This `assume_role_policy` is very similar but slightly different than just a standard IAM policy and cannot use an `aws_iam_policy` resource. It _can_ however, use an `aws_iam_policy_document` [data source](/docs/providers/aws/d/iam_policy_document.html), see example below for how this could work. + +* `force_detach_policies` - (Optional) Specifies to force detaching any policies the role has before destroying it. Defaults to `false`. +* `path` - (Optional) The path to the role. + See [IAM Identifiers](https://docs.aws.amazon.com/IAM/latest/UserGuide/Using_Identifiers.html) for more information. +* `description` - (Optional) The description of the role. + +* `max_session_duration` - (Optional) The maximum session duration (in seconds) that you want to set for the specified role. If you do not specify a value for this setting, the default maximum of one hour is applied. This setting can have a value from 1 hour to 12 hours. +* `permissions_boundary` - (Optional) The ARN of the policy that is used to set the permissions boundary for the role. +* `tags` - Key-value map of tags for the IAM role +* `tags` - Key-value mapping of tags for the IAM role +* `managed_policy_arns` - (Optional) An exclusive set of IAM managed policy ARNs to attach to the IAM role. If the attribute is not used, the resource will not attach or detach the role's managed policies on the next `apply`. If the set is empty, all managed policies that are attached out of band, will be detached on the next `apply`. + +~> **NOTE:** The `managed_policy_arns` attribute, which provides an _exclusive_ set of managed policies for an IAM role, will conflict with using the `iam_role_policy_attachment` resource, which provides non-exclusive, managed policy-role attachment. See [`iam_role_policy_attachment`](/docs/providers/aws/r/iam_role_policy_attachment.html). + +* `inline_policy` - (Optional) An exclusive set of IAM inline policies associated with the IAM role. If the attribute is not used, the resource will not add or remove the role's inline policies on the next `apply`. If one empty `inline_policy` attribute is used, any inline policies that are added outside of Terraform will be removed on the next `apply`. + +~> **NOTE:** The `inline_policy` attribute, which provides an _exclusive_ set of inline policies for an IAM role, will conflict with the `iam_role_policy` resource, which provides non-exclusive, inline policy-role association. See [`iam_role_policy`](/docs/providers/aws/r/iam_role_policy.html). + +### inline_policy + +The following arguments are supported: + +* `policy` - (Required) The policy document. This is a JSON formatted string. For more information about building IAM policy documents with Terraform, see the [AWS IAM Policy Document Guide](https://www.terraform.io/docs/providers/aws/guides/iam-policy-documents.html). +* `name` - (Optional) The name of the role policy. If omitted, Terraform will assign a random, unique name. +* `name_prefix` - (Optional) Creates a unique name beginning with the specified prefix. If both `name` and `name_prefix` are used, `name_prefix` will be ignored. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - The Amazon Resource Name (ARN) specifying the role. +* `create_date` - The creation date of the IAM role. +* `description` - The description of the role. +* `id` - The name of the role. +* `name` - The name of the role. +* `unique_id` - The stable and unique string identifying the role. + ## Import IAM Roles can be imported using the `name`, e.g. From ba5e679c34f46f3a20426a6c97c4d9576829fbfb Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Fri, 5 Feb 2021 14:22:20 -0500 Subject: [PATCH 07/20] resource/iam_role: Rework policies arguments --- aws/resource_aws_iam_role.go | 154 ++------- aws/resource_aws_iam_role_test.go | 509 +++++++++++------------------- 2 files changed, 207 insertions(+), 456 deletions(-) diff --git a/aws/resource_aws_iam_role.go b/aws/resource_aws_iam_role.go index 5e688021748..318c0cb72d2 100644 --- a/aws/resource_aws_iam_role.go +++ b/aws/resource_aws_iam_role.go @@ -27,7 +27,6 @@ func resourceAwsIamRole() *schema.Resource { Importer: &schema.ResourceImporter{ State: resourceAwsIamRoleImport, }, - //CustomizeDiff: resourceAwsIamRoleInlineCustDiff, Schema: map[string]*schema.Schema{ "arn": { Type: schema.TypeString, @@ -113,22 +112,17 @@ func resourceAwsIamRole() *schema.Resource { "tags": tagsSchema(), "inline_policy": { - Type: schema.TypeSet, - Optional: true, - Computed: true, + Type: schema.TypeSet, + Optional: true, + Computed: true, + ConfigMode: schema.SchemaConfigModeAttr, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "name": { Type: schema.TypeString, - Optional: true, - Computed: true, + Required: true, ValidateFunc: validateIamRolePolicyName, }, - "name_prefix": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validateIamRolePolicyNamePrefix, - }, "policy": { Type: schema.TypeString, Required: true, @@ -443,7 +437,17 @@ func resourceAwsIamRoleUpdate(d *schema.ResourceData, meta interface{}) error { func resourceAwsIamRoleDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).iamconn - err := deleteAwsIamRole(conn, d.Id(), d.Get("force_detach_policies").(bool)) + hasInline := false + if v, ok := d.GetOk("inline_policy"); ok && v.(*schema.Set).Len() > 0 { + hasInline = true + } + + hasManaged := false + if v, ok := d.GetOk("managed_policy_arns"); ok && v.(*schema.Set).Len() > 0 { + hasManaged = true + } + + err := deleteAwsIamRole(conn, d.Id(), d.Get("force_detach_policies").(bool), hasInline, hasManaged) if tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { return nil } @@ -454,12 +458,12 @@ func resourceAwsIamRoleDelete(d *schema.ResourceData, meta interface{}) error { return nil } -func deleteAwsIamRole(conn *iam.IAM, rolename string, forceDetach bool) error { +func deleteAwsIamRole(conn *iam.IAM, rolename string, forceDetach, hasInline, hasManaged bool) error { if err := deleteAwsIamRoleInstanceProfiles(conn, rolename); err != nil { return fmt.Errorf("unable to detach instance profiles: %w", err) } - if forceDetach { + if forceDetach || hasManaged { managedPolicies, err := readAwsIamRolePolicyAttachments(conn, rolename) if err != nil { return err @@ -468,7 +472,9 @@ func deleteAwsIamRole(conn *iam.IAM, rolename string, forceDetach bool) error { if err := deleteAwsIamRolePolicyAttachments(conn, rolename, managedPolicies); err != nil { return fmt.Errorf("unable to detach policies: %w", err) } + } + if forceDetach || hasInline { inlinePolicies, err := readAwsIamRolePolicyNames(conn, rolename) if err != nil { return err @@ -643,19 +649,10 @@ func expandIamInlinePolicy(roleName string, tfMap map[string]interface{}) *iam.P apiObject := &iam.PutRolePolicyInput{ RoleName: aws.String(roleName), + PolicyName: aws.String(tfMap["name"].(string)), PolicyDocument: aws.String(tfMap["policy"].(string)), } - var policyName string - if v, ok := tfMap["name"]; ok { - policyName = v.(string) - } else if v, ok := tfMap["name_prefix"]; ok { - policyName = resource.PrefixedUniqueId(v.(string)) - } else { - policyName = resource.UniqueId() - } - apiObject.PolicyName = aws.String(policyName) - return apiObject } @@ -741,6 +738,7 @@ func resourceAwsIamRoleListInlinePolicies(roleName string, meta interface{}) ([] apiObject := &iam.PutRolePolicyInput{ RoleName: aws.String(roleName), PolicyDocument: aws.String(policy), + PolicyName: policyName, } apiObjects = append(apiObjects, apiObject) @@ -748,111 +746,3 @@ func resourceAwsIamRoleListInlinePolicies(roleName string, meta interface{}) ([] return apiObjects, nil } - -/* -func resourceAwsIamRoleInlineCustDiff(_ context.Context, diff *schema.ResourceDiff, meta interface{}) error { - // Avoids diffs resulting when inline policies are configured without either - // name or name prefix, or with a name prefix. In these cases, Terraform - // generates some or all of the name. Without a customized diff function, - // comparing the config to the state will always generate a diff since the - // config has no information about the policy's generated name. - if diff.HasChange("inline_policy") { - - o, n := diff.GetChange("inline_policy") - if o == nil { - o = new(schema.Set) - } - if n == nil { - n = new(schema.Set) - } - - os := o.(*schema.Set) - ns := n.(*schema.Set) - - // a single empty inline_policy in the config produces a diff with - // inline_policy.# = 0 and subattributes all blank - if len(os.List()) == 0 && len(ns.List()) == 1 { - data := (ns.List())[0].(map[string]interface{}) - if data["name"].(string) == "" && data["name_prefix"].(string) == "" && data["policy"].(string) == "" { - if err := diff.Clear("inline_policy"); err != nil { - return fmt.Errorf("failed to clear diff for IAM role %s, error: %s", diff.Id(), err) - } - } - } - - // if there's no old or new set, nothing to do - can't match up - // equivalents between the lists - if len(os.List()) > 0 && len(ns.List()) > 0 { - - // fast O(n) comparison in case of thousands of policies - - // current state lookup map: - // key: inline policy doc hash - // value: string slice with policy names (slice in case of dupes) - statePolicies := make(map[int]interface{}) - for _, policy := range os.List() { - data := policy.(map[string]interface{}) - name := data["name"].(string) - - // condition probably not needed, will have been assigned name - if name != "" { - docHash := hashcode.String(data["policy"].(string)) - if _, ok := statePolicies[docHash]; !ok { - statePolicies[docHash] = []string{name} - } else { - statePolicies[docHash] = append(statePolicies[docHash].([]string), name) - } - } - } - - // construct actual changes by going through incoming config changes - configSet := make([]interface{}, 0) - for _, policy := range ns.List() { - appended := false - data := policy.(map[string]interface{}) - namePrefix := data["name_prefix"].(string) - name := data["name"].(string) - - if namePrefix != "" || (namePrefix == "" && name == "") { - docHash := hashcode.String(data["policy"].(string)) - if namesFromState, ok := statePolicies[docHash]; ok { - for i, nameFromState := range namesFromState.([]string) { - if (namePrefix == "" && name == "") || strings.HasPrefix(nameFromState, namePrefix) { - // match - we want the state value - pair := make(map[string]interface{}) - pair["name"] = nameFromState - pair["policy"] = data["policy"] - configSet = append(configSet, pair) - appended = true - - // remove - in case of duplicate policies - stateSlice := namesFromState.([]string) - stateSlice = append(stateSlice[:i], stateSlice[i+1:]...) - if len(stateSlice) == 0 { - delete(statePolicies, docHash) - } else { - statePolicies[docHash] = stateSlice - } - break - } - } - } - } - - if !appended { - pair := make(map[string]interface{}) - pair["name"] = name - pair["name_prefix"] = namePrefix - pair["policy"] = data["policy"] - configSet = append(configSet, pair) - } - } - if err := diff.SetNew("inline_policy", configSet); err != nil { - return fmt.Errorf("failed to set new inline policies for IAM role %s, error: %s", diff.Id(), err) - } - } - } - - return nil -} -*/ diff --git a/aws/resource_aws_iam_role_test.go b/aws/resource_aws_iam_role_test.go index c3f3c4ef523..ba212d0349f 100644 --- a/aws/resource_aws_iam_role_test.go +++ b/aws/resource_aws_iam_role_test.go @@ -124,7 +124,7 @@ func testSweepIamRoles(region string) error { rolename := aws.StringValue(role.RoleName) log.Printf("[DEBUG] Deleting IAM Role (%s)", rolename) - err := deleteAwsIamRole(conn, rolename, true) + err := deleteAwsIamRole(conn, rolename, true, true, true) if tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { continue } @@ -496,13 +496,11 @@ func TestAccAWSIAMRole_tags(t *testing.T) { func TestAccAWSIAMRole_policyBasicInline(t *testing.T) { var role iam.GetRoleOutput - - rString := acctest.RandString(5) - roleName := fmt.Sprintf("tf-acc-role-policy-basic-%s", rString) - ilPolicyName1 := fmt.Sprintf("tf-acc-ipolicy-basic-1-%s", rString) - ilPolicyName2 := fmt.Sprintf("tf-acc-ipolicy-basic-2-%s", rString) - ilPolicyName3 := fmt.Sprintf("tf-acc-ipolicy-basic-3-%s", rString) - resourceAddr := "aws_iam_role.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + policyName1 := acctest.RandomWithPrefix("tf-acc-test") + policyName2 := acctest.RandomWithPrefix("tf-acc-test") + policyName3 := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_iam_role.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -510,38 +508,35 @@ func TestAccAWSIAMRole_policyBasicInline(t *testing.T) { CheckDestroy: testAccCheckAWSRoleDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSRolePolicyInlineConfig(roleName, ilPolicyName1), + Config: testAccAWSRolePolicyInlineConfig(rName, policyName1), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRoleExists(resourceAddr, &role), - testAccCheckAWSRolePolicyCheckInline(&role, roleName, []string{ilPolicyName1}), - resource.TestCheckResourceAttr(resourceAddr, "inline_policy.#", "1"), - resource.TestCheckResourceAttr(resourceAddr, "name", roleName), - resource.TestCheckResourceAttr(resourceAddr, "managed_policy_arns.#", "0"), + testAccCheckAWSRoleExists(resourceName, &role), + testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{policyName1}), + resource.TestCheckResourceAttr(resourceName, "inline_policy.#", "1"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "managed_policy_arns.#", "0"), ), }, - { - Config: testAccAWSRolePolicyInlineConfigUpdate(roleName, ilPolicyName2, ilPolicyName3), + Config: testAccAWSRolePolicyInlineConfigUpdate(rName, policyName2, policyName3), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRoleExists(resourceAddr, &role), - testAccCheckAWSRolePolicyCheckInline(&role, roleName, []string{ilPolicyName2, ilPolicyName3}), - resource.TestCheckResourceAttr(resourceAddr, "inline_policy.#", "2"), - resource.TestCheckResourceAttr(resourceAddr, "managed_policy_arns.#", "0"), + testAccCheckAWSRoleExists(resourceName, &role), + testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{policyName2, policyName3}), + resource.TestCheckResourceAttr(resourceName, "inline_policy.#", "2"), + resource.TestCheckResourceAttr(resourceName, "managed_policy_arns.#", "0"), ), }, - { - Config: testAccAWSRolePolicyInlineConfigUpdateDown(roleName, ilPolicyName3), + Config: testAccAWSRolePolicyInlineConfigUpdateDown(rName, policyName3), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRoleExists(resourceAddr, &role), - testAccCheckAWSRolePolicyCheckInline(&role, roleName, []string{ilPolicyName3}), - resource.TestCheckResourceAttr(resourceAddr, "inline_policy.#", "1"), - resource.TestCheckResourceAttr(resourceAddr, "managed_policy_arns.#", "0"), + testAccCheckAWSRoleExists(resourceName, &role), + testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{policyName3}), + resource.TestCheckResourceAttr(resourceName, "inline_policy.#", "1"), + resource.TestCheckResourceAttr(resourceName, "managed_policy_arns.#", "0"), ), }, - { - ResourceName: resourceAddr, + ResourceName: resourceName, ImportState: true, ImportStateVerify: true, }, @@ -549,13 +544,13 @@ func TestAccAWSIAMRole_policyBasicInline(t *testing.T) { }) } -func TestAccAWSIAMRole_policyInlinePrefix(t *testing.T) { +func TestAccAWSIAMRole_policyBasicManaged(t *testing.T) { var role iam.GetRoleOutput - - rString := acctest.RandString(5) - roleName := fmt.Sprintf("tf-acc-role-policy-prefix-%s", rString) - ilPolicyPrefix := fmt.Sprintf("tf-acc-%s", rString) - resourceAddr := "aws_iam_role.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + policyName1 := acctest.RandomWithPrefix("tf-acc-test") + policyName2 := acctest.RandomWithPrefix("tf-acc-test") + policyName3 := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_iam_role.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -563,354 +558,304 @@ func TestAccAWSIAMRole_policyInlinePrefix(t *testing.T) { CheckDestroy: testAccCheckAWSRoleDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSRolePolicyInlinePrefix(roleName, ilPolicyPrefix), + Config: testAccAWSRolePolicyManagedConfig(rName, policyName1), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRoleExists(resourceAddr, &role), - testAccCheckAWSRolePolicyCheckInlinePrefix(&role, roleName, ilPolicyPrefix), - resource.TestCheckResourceAttr(resourceAddr, "inline_policy.#", "1"), + testAccCheckAWSRoleExists(resourceName, &role), + testAccCheckAWSRolePolicyCheckManaged(&role, rName, []string{policyName1}), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "managed_policy_arns.#", "1"), + ), + }, + { + Config: testAccAWSRolePolicyManagedConfigUpdate(rName, policyName1, policyName2, policyName3), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRoleExists(resourceName, &role), + testAccCheckAWSRolePolicyCheckManaged(&role, rName, []string{policyName2, policyName3}), + resource.TestCheckResourceAttr(resourceName, "managed_policy_arns.#", "2"), ), }, - }, - }) -} - -func TestAccAWSIAMRole_policyInlineNoName(t *testing.T) { - var role iam.GetRoleOutput - - rString := acctest.RandString(5) - roleName := fmt.Sprintf("tf-acc-role-policy-prefix-%s", rString) - resourceAddr := "aws_iam_role.test" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSRoleDestroy, - Steps: []resource.TestStep{ { - Config: testAccAWSRolePolicyInlineNoName(roleName), + Config: testAccAWSRolePolicyManagedConfigUpdateDown(rName, policyName1, policyName2, policyName3), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRoleExists(resourceAddr, &role), - resource.TestCheckResourceAttr(resourceAddr, "inline_policy.#", "1"), + testAccCheckAWSRoleExists(resourceName, &role), + testAccCheckAWSRolePolicyCheckManaged(&role, rName, []string{policyName3}), + resource.TestCheckResourceAttr(resourceName, "managed_policy_arns.#", "1"), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } -func TestAccAWSIAMRole_policyBasicManaged(t *testing.T) { +// TestAccAWSIAMRole_policyOutOfBandRemovalAddedBack_managedNonEmpty: if a policy is detached +// out of band, it should be reattached. +func TestAccAWSIAMRole_policyOutOfBandRemovalAddedBack_managedNonEmpty(t *testing.T) { var role iam.GetRoleOutput - - rString := acctest.RandString(5) - roleName := fmt.Sprintf("tf-acc-role-policy-basic-%s", rString) - mgPolicyName1 := fmt.Sprintf("tf-acc-mpolicy-basic-1-%s", rString) - mgPolicyName2 := fmt.Sprintf("tf-acc-mpolicy-basic-2-%s", rString) - mgPolicyName3 := fmt.Sprintf("tf-acc-mpolicy-basic-3-%s", rString) - resourceAddr := "aws_iam_role.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + policyName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_iam_role.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSRoleDestroy, Steps: []resource.TestStep{ - - { - Config: testAccAWSRolePolicyManagedConfig(roleName, mgPolicyName1), - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRoleExists(resourceAddr, &role), - testAccCheckAWSRolePolicyCheckManaged(&role, roleName, []string{mgPolicyName1}), - resource.TestCheckResourceAttr(resourceAddr, "name", roleName), - resource.TestCheckResourceAttr(resourceAddr, "managed_policy_arns.#", "1"), - ), - }, - { - Config: testAccAWSRolePolicyManagedConfigUpdate(roleName, mgPolicyName1, mgPolicyName2, mgPolicyName3), + Config: testAccAWSRolePolicyManagedConfig(rName, policyName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRoleExists(resourceAddr, &role), - testAccCheckAWSRolePolicyCheckManaged(&role, roleName, []string{mgPolicyName2, mgPolicyName3}), - resource.TestCheckResourceAttr(resourceAddr, "managed_policy_arns.#", "2"), + testAccCheckAWSRoleExists(resourceName, &role), + testAccCheckAWSRolePolicyDetachManagedPolicy(&role, policyName), + testAccCheckAWSRolePolicyCheckManaged(&role, rName, []string{}), ), + ExpectNonEmptyPlan: true, }, - { - Config: testAccAWSRolePolicyManagedConfigUpdateDown(roleName, mgPolicyName1, mgPolicyName2, mgPolicyName3), + Config: testAccAWSRolePolicyManagedConfig(rName, policyName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRoleExists(resourceAddr, &role), - testAccCheckAWSRolePolicyCheckManaged(&role, roleName, []string{mgPolicyName3}), - resource.TestCheckResourceAttr(resourceAddr, "managed_policy_arns.#", "1"), + testAccCheckAWSRoleExists(resourceName, &role), + testAccCheckAWSRolePolicyCheckManaged(&role, rName, []string{policyName}), + resource.TestCheckResourceAttr(resourceName, "managed_policy_arns.#", "1"), ), }, - - { - ResourceName: resourceAddr, - ImportState: true, - ImportStateVerify: true, - }, }, }) } -// TestAccAWSIAMRole_policyManagedReattached: if a policy is detached -// externally, it should be reattached. -func TestAccAWSIAMRole_policyManagedReattached(t *testing.T) { +// TestAccAWSIAMRole_policyOutOfBandRemovalAddedBack_inlineNonEmpty: if a policy is removed +// out of band, it should be recreated. +func TestAccAWSIAMRole_policyOutOfBandRemovalAddedBack_inlineNonEmpty(t *testing.T) { var role iam.GetRoleOutput - - rString := acctest.RandString(5) - roleName := fmt.Sprintf("tf-acc-role-policy-reattach-%s", rString) - mgPolicyName1 := fmt.Sprintf("tf-acc-rpl-mpolicy-detach-1-%s", rString) - resourceAddr := "aws_iam_role.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + policyName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_iam_role.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSRoleDestroy, Steps: []resource.TestStep{ - { - Config: testAccAWSRolePolicyManagedConfig(roleName, mgPolicyName1), + Config: testAccAWSRolePolicyInlineConfig(rName, policyName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRoleExists(resourceAddr, &role), - testAccCheckAWSRolePolicyDetachManagedPolicy(&role, mgPolicyName1), - testAccCheckAWSRolePolicyCheckManaged(&role, roleName, []string{}), + testAccCheckAWSRoleExists(resourceName, &role), + testAccCheckAWSRolePolicyRemoveInlinePolicy(&role, policyName), + testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{}), ), ExpectNonEmptyPlan: true, }, - { - Config: testAccAWSRolePolicyManagedConfig(roleName, mgPolicyName1), + Config: testAccAWSRolePolicyInlineConfig(rName, policyName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRoleExists(resourceAddr, &role), - testAccCheckAWSRolePolicyCheckManaged(&role, roleName, []string{mgPolicyName1}), - resource.TestCheckResourceAttr(resourceAddr, "managed_policy_arns.#", "1"), + testAccCheckAWSRoleExists(resourceName, &role), + testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{policyName}), + resource.TestCheckResourceAttr(resourceName, "inline_policy.#", "1"), ), }, }, }) } -// TestAccAWSIAMRole_policyExtraManagedAdded: remove externally added -// managed policies. -func TestAccAWSIAMRole_policyExtraManagedAdded(t *testing.T) { +// TestAccAWSIAMRole_policyOutOfBandAdditionRemoved_managedNonEmpty: if managed_policies arg +// exists and is non-empty, policy attached out of band should be removed +func TestAccAWSIAMRole_policyOutOfBandAdditionRemoved_managedNonEmpty(t *testing.T) { var role iam.GetRoleOutput - - rString := acctest.RandString(5) - roleName := fmt.Sprintf("tf-acc-role-policy-extra-%s", rString) - mgPolicyName1 := fmt.Sprintf("tf-acc-rpl-mpolicy-extra-good-%s", rString) - mgPolicyName2 := fmt.Sprintf("tf-acc-rpl-mpolicy-extra-bad-%s", rString) - resourceAddr := "aws_iam_role.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + policyName1 := acctest.RandomWithPrefix("tf-acc-test") + policyName2 := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_iam_role.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSRoleDestroy, Steps: []resource.TestStep{ - { - Config: testAccAWSRolePolicyExtraManagedConfig(roleName, mgPolicyName1, mgPolicyName2), + Config: testAccAWSRolePolicyExtraManagedConfig(rName, policyName1, policyName2), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRoleExists(resourceAddr, &role), - testAccCheckAWSRolePolicyAttachManagedPolicy(&role, mgPolicyName2), - testAccCheckAWSRolePolicyCheckManaged(&role, roleName, []string{mgPolicyName1, mgPolicyName2}), + testAccCheckAWSRoleExists(resourceName, &role), + testAccCheckAWSRolePolicyAttachManagedPolicy(&role, policyName2), + testAccCheckAWSRolePolicyCheckManaged(&role, rName, []string{policyName1, policyName2}), ), ExpectNonEmptyPlan: true, }, - { - Config: testAccAWSRolePolicyExtraManagedConfig(roleName, mgPolicyName1, mgPolicyName2), + Config: testAccAWSRolePolicyExtraManagedConfig(rName, policyName1, policyName2), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRoleExists(resourceAddr, &role), - testAccCheckAWSRolePolicyCheckManaged(&role, roleName, []string{mgPolicyName1}), - resource.TestCheckResourceAttr(resourceAddr, "managed_policy_arns.#", "1"), + testAccCheckAWSRoleExists(resourceName, &role), + testAccCheckAWSRolePolicyCheckManaged(&role, rName, []string{policyName1}), + resource.TestCheckResourceAttr(resourceName, "managed_policy_arns.#", "1"), ), }, }, }) } -// TestAccAWSIAMRole_policyExtraInlineAdded: remove externally added inline -// policies. -func TestAccAWSIAMRole_policyExtraInlineAdded(t *testing.T) { +// TestAccAWSIAMRole_policyOutOfBandAdditionRemoved_inlineNonEmpty: if inline_policy arg +// exists and is non-empty, policy added out of band should be removed +func TestAccAWSIAMRole_policyOutOfBandAdditionRemoved_inlineNonEmpty(t *testing.T) { var role iam.GetRoleOutput - - rString := acctest.RandString(5) - roleName := fmt.Sprintf("tf-acc-role-policy-extra-%s", rString) - ilPolicyName1 := fmt.Sprintf("tf-acc-rpl-ipolicy-extra-good-%s", rString) - ilPolicyName2 := fmt.Sprintf("tf-acc-rpl-ipolicy-extra-bad-%s", rString) - resourceAddr := "aws_iam_role.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + policyName1 := acctest.RandomWithPrefix("tf-acc-test") + policyName2 := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_iam_role.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSRoleDestroy, Steps: []resource.TestStep{ - { - Config: testAccAWSRolePolicyInlineConfig(roleName, ilPolicyName1), + Config: testAccAWSRolePolicyInlineConfig(rName, policyName1), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRoleExists(resourceAddr, &role), - testAccCheckAWSRolePolicyAddInlinePolicy(&role, ilPolicyName2), - testAccCheckAWSRolePolicyCheckInline(&role, roleName, []string{ilPolicyName1, ilPolicyName2}), + testAccCheckAWSRoleExists(resourceName, &role), + testAccCheckAWSRolePolicyAddInlinePolicy(&role, policyName2), + testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{policyName1, policyName2}), ), ExpectNonEmptyPlan: true, }, - { - Config: testAccAWSRolePolicyInlineConfig(roleName, ilPolicyName1), + Config: testAccAWSRolePolicyInlineConfig(rName, policyName1), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRoleExists(resourceAddr, &role), - testAccCheckAWSRolePolicyCheckInline(&role, roleName, []string{ilPolicyName1}), - resource.TestCheckResourceAttr(resourceAddr, "inline_policy.#", "1"), - resource.TestCheckResourceAttr(resourceAddr, "managed_policy_arns.#", "0"), + testAccCheckAWSRoleExists(resourceName, &role), + testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{policyName1}), + resource.TestCheckResourceAttr(resourceName, "inline_policy.#", "1"), + resource.TestCheckResourceAttr(resourceName, "managed_policy_arns.#", "0"), ), }, }, }) } -// TestAccAWSIAMRole_policyNoInlineListExtraInlineAdded: when there is no -// inline_policy attribute, resource should not do anything if policies -// are added externally -func TestAccAWSIAMRole_policyNoInlineListExtraInlineAdded(t *testing.T) { +// TestAccAWSIAMRole_policyOutOfBandAdditionIgnored_inlineNonExistent: if there is no +// inline_policy attribute, out of band changes should be ignored. +func TestAccAWSIAMRole_policyOutOfBandAdditionIgnored_inlineNonExistent(t *testing.T) { var role iam.GetRoleOutput - - rString := acctest.RandString(5) - roleName := fmt.Sprintf("tf-acc-role-policy-no-ilist-%s", rString) - ilPolicyName1 := fmt.Sprintf("tf-acc-rpl-ipolicy-extra-good-%s", rString) - resourceAddr := "aws_iam_role.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + policyName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_iam_role.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSRoleDestroy, Steps: []resource.TestStep{ - { - Config: testAccAWSRolePolicyNoInlineConfig(roleName), + Config: testAccAWSRolePolicyNoInlineConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRoleExists(resourceAddr, &role), - testAccCheckAWSRolePolicyAddInlinePolicy(&role, ilPolicyName1), - testAccCheckAWSRolePolicyCheckInline(&role, roleName, []string{ilPolicyName1}), + testAccCheckAWSRoleExists(resourceName, &role), + testAccCheckAWSRolePolicyAddInlinePolicy(&role, policyName), + testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{policyName}), ), }, - { - Config: testAccAWSRolePolicyNoInlineConfig(roleName), + Config: testAccAWSRolePolicyNoInlineConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRoleExists(resourceAddr, &role), - testAccCheckAWSRolePolicyCheckInline(&role, roleName, []string{ilPolicyName1}), - testAccCheckAWSRolePolicyRemoveInlinePolicy(&role, ilPolicyName1), + testAccCheckAWSRoleExists(resourceName, &role), + testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{policyName}), + testAccCheckAWSRolePolicyRemoveInlinePolicy(&role, policyName), ), }, }, }) } -// TestAccAWSIAMRole_policyNoManagedListExtraManagedAdded: if there is no -// managed_policies attribute, resource should not do anything if one is attached. -func TestAccAWSIAMRole_policyNoManagedListExtraManagedAdded(t *testing.T) { +// TestAccAWSIAMRole_policyOutOfBandAdditionIgnored_managedNonExistent: if there is no +// managed_policies attribute, out of band changes should be ignored. +func TestAccAWSIAMRole_policyOutOfBandAdditionIgnored_managedNonExistent(t *testing.T) { var role iam.GetRoleOutput - - rString := acctest.RandString(5) - roleName := fmt.Sprintf("tf-acc-role-policy-no-mlist-%s", rString) - mgPolicyName1 := fmt.Sprintf("tf-acc-rpl-mpolicy-extra-good-%s", rString) - resourceAddr := "aws_iam_role.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + policyName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_iam_role.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSRoleDestroy, Steps: []resource.TestStep{ - { - Config: testAccAWSRolePolicyNoManagedConfig(roleName, mgPolicyName1), + Config: testAccAWSRolePolicyNoManagedConfig(rName, policyName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRoleExists(resourceAddr, &role), - testAccCheckAWSRolePolicyAttachManagedPolicy(&role, mgPolicyName1), - testAccCheckAWSRolePolicyCheckManaged(&role, roleName, []string{mgPolicyName1}), + testAccCheckAWSRoleExists(resourceName, &role), + testAccCheckAWSRolePolicyAttachManagedPolicy(&role, policyName), + testAccCheckAWSRolePolicyCheckManaged(&role, rName, []string{policyName}), ), }, - { - Config: testAccAWSRolePolicyNoManagedConfig(roleName, mgPolicyName1), + Config: testAccAWSRolePolicyNoManagedConfig(rName, policyName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRoleExists(resourceAddr, &role), - testAccCheckAWSRolePolicyCheckManaged(&role, roleName, []string{mgPolicyName1}), - testAccCheckAWSRolePolicyDetachManagedPolicy(&role, mgPolicyName1), + testAccCheckAWSRoleExists(resourceName, &role), + testAccCheckAWSRolePolicyCheckManaged(&role, rName, []string{policyName}), + testAccCheckAWSRolePolicyDetachManagedPolicy(&role, policyName), ), }, }, }) } -// TestAccAWSIAMRole_policyEmptyInlineListExtraInlineAdded: when there is an -// empty inline_policy attribute, resource should remove policies that -// are added externally -func TestAccAWSIAMRole_policyEmptyInlineListExtraInlineAdded(t *testing.T) { +// TestAccAWSIAMRole_policyOutOfBandAdditionRemoved_inlineEmpty: if inline is added +// out of band with empty inline arg, should be removed +func TestAccAWSIAMRole_policyOutOfBandAdditionRemoved_inlineEmpty(t *testing.T) { var role iam.GetRoleOutput - - rString := acctest.RandString(5) - roleName := fmt.Sprintf("tf-acc-role-policy-empty-%s", rString) - ilPolicyName1 := fmt.Sprintf("tf-acc-rpl-ipolicy-extra-bad-%s", rString) - resourceAddr := "aws_iam_role.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + policyName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_iam_role.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSRoleDestroy, Steps: []resource.TestStep{ - { - Config: testAccAWSRolePolicyEmptyInlineConfig(roleName), + Config: testAccAWSRolePolicyEmptyInlineConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRoleExists(resourceAddr, &role), - testAccCheckAWSRolePolicyAddInlinePolicy(&role, ilPolicyName1), - testAccCheckAWSRolePolicyCheckInline(&role, roleName, []string{ilPolicyName1}), + testAccCheckAWSRoleExists(resourceName, &role), + testAccCheckAWSRolePolicyAddInlinePolicy(&role, policyName), + testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{policyName}), ), ExpectNonEmptyPlan: true, }, - { - Config: testAccAWSRolePolicyEmptyInlineConfig(roleName), + Config: testAccAWSRolePolicyEmptyInlineConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRoleExists(resourceAddr, &role), - testAccCheckAWSRolePolicyCheckInline(&role, roleName, []string{}), + testAccCheckAWSRoleExists(resourceName, &role), + testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{}), ), }, }, }) } -// TestAccAWSIAMRole_policyEmptyManagedListExtraManagedAdded: if there is no -// managed_policies attribute, resource should not do anything if one is attached. -func TestAccAWSIAMRole_policyEmptyManagedListExtraManagedAdded(t *testing.T) { +// TestAccAWSIAMRole_policyOutOfBandAdditionRemoved_managedEmpty: if managed is attached +// out of band with empty managed arg, should be detached +func TestAccAWSIAMRole_policyOutOfBandAdditionRemoved_managedEmpty(t *testing.T) { var role iam.GetRoleOutput - - rString := acctest.RandString(5) - roleName := fmt.Sprintf("tf-acc-role-policy-empty-%s", rString) - mgPolicyName1 := fmt.Sprintf("tf-acc-rpl-mpolicy-extra-bad-%s", rString) - resourceAddr := "aws_iam_role.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + policyName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_iam_role.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSRoleDestroy, Steps: []resource.TestStep{ - { - Config: testAccAWSRolePolicyEmptyManagedConfig(roleName, mgPolicyName1), + Config: testAccAWSRolePolicyEmptyManagedConfig(rName, policyName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRoleExists(resourceAddr, &role), - testAccCheckAWSRolePolicyAttachManagedPolicy(&role, mgPolicyName1), - testAccCheckAWSRolePolicyCheckManaged(&role, roleName, []string{mgPolicyName1}), + testAccCheckAWSRoleExists(resourceName, &role), + testAccCheckAWSRolePolicyAttachManagedPolicy(&role, policyName), + testAccCheckAWSRolePolicyCheckManaged(&role, rName, []string{policyName}), ), ExpectNonEmptyPlan: true, }, - { - Config: testAccAWSRolePolicyEmptyManagedConfig(roleName, mgPolicyName1), + Config: testAccAWSRolePolicyEmptyManagedConfig(rName, policyName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSRoleExists(resourceAddr, &role), - testAccCheckAWSRolePolicyCheckManaged(&role, roleName, []string{}), + testAccCheckAWSRoleExists(resourceName, &role), + testAccCheckAWSRolePolicyCheckManaged(&role, rName, []string{}), ), }, }, @@ -1775,7 +1720,7 @@ EOF `, rName) } -func testAccAWSRolePolicyInlineConfig(roleName, ilPolicyName1 string) string { +func testAccAWSRolePolicyInlineConfig(roleName, policyName string) string { return fmt.Sprintf(` resource "aws_iam_role" "test" { name = %[1]q @@ -1815,94 +1760,10 @@ EOF EOF } } -`, roleName, ilPolicyName1) +`, roleName, policyName) } -func testAccAWSRolePolicyInlinePrefix(roleName, prefix string) string { - return fmt.Sprintf(` -resource "aws_iam_role" "test" { - name = %[1]q - - assume_role_policy = < Date: Fri, 5 Feb 2021 15:46:05 -0500 Subject: [PATCH 08/20] resource/iam_role: Refactor names, modernize paged output --- aws/resource_aws_iam_role.go | 66 ++++---- aws/resource_aws_iam_role_test.go | 252 +++++++++--------------------- 2 files changed, 104 insertions(+), 214 deletions(-) diff --git a/aws/resource_aws_iam_role.go b/aws/resource_aws_iam_role.go index 318c0cb72d2..b6fe18f6252 100644 --- a/aws/resource_aws_iam_role.go +++ b/aws/resource_aws_iam_role.go @@ -209,14 +209,14 @@ func resourceAwsIamRoleCreate(d *schema.ResourceData, meta interface{}) error { if v, ok := d.GetOk("inline_policy"); ok && v.(*schema.Set).Len() > 0 { policies := expandIamInlinePolicies(roleName, v.(*schema.Set).List()) - if err := resourceAwsIamRoleCreateInlinePolicies(policies, meta); err != nil { + if err := addIamInlinePolicies(policies, meta); err != nil { return err } } if v, ok := d.GetOk("managed_policy_arns"); ok && v.(*schema.Set).Len() > 0 { managedPolicies := expandStringSet(v.(*schema.Set)) - if err := resourceAwsIamRoleAttachManagedPolicies(roleName, managedPolicies, meta); err != nil { + if err := addIamManagedPolicies(roleName, managedPolicies, meta); err != nil { return err } } @@ -268,15 +268,15 @@ func resourceAwsIamRoleRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("error setting tags: %s", err) } - assumRolePolicy, err := url.QueryUnescape(*role.AssumeRolePolicyDocument) + assumeRolePolicy, err := url.QueryUnescape(*role.AssumeRolePolicyDocument) if err != nil { return err } - if err := d.Set("assume_role_policy", assumRolePolicy); err != nil { + if err := d.Set("assume_role_policy", assumeRolePolicy); err != nil { return err } - inlinePolicies, err := resourceAwsIamRoleListInlinePolicies(*role.RoleName, meta) + inlinePolicies, err := readIamInlinePolicies(*role.RoleName, meta) if err != nil { return fmt.Errorf("reading inline policies for IAM role %s, error: %s", d.Id(), err) } @@ -284,7 +284,7 @@ func resourceAwsIamRoleRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("setting attribute_name: %w", err) } - managedPolicies, err := readAwsIamRolePolicyAttachments(iamconn, *role.RoleName) + managedPolicies, err := readIamRolePolicyAttachments(iamconn, *role.RoleName) if err != nil { return fmt.Errorf("reading managed policies for IAM role %s, error: %s", d.Id(), err) } @@ -396,12 +396,12 @@ func resourceAwsIamRoleUpdate(d *schema.ResourceData, meta interface{}) error { policyNames = append(policyNames, aws.String(tfMap["name"].(string))) } - if err := deleteAwsIamRolePolicies(iamconn, roleName, policyNames); err != nil { + if err := deleteIamRolePolicies(iamconn, roleName, policyNames); err != nil { return fmt.Errorf("unable to delete inline policies: %w", err) } policies := expandIamInlinePolicies(roleName, add) - if err := resourceAwsIamRoleCreateInlinePolicies(policies, meta); err != nil { + if err := addIamInlinePolicies(policies, meta); err != nil { return err } } @@ -422,11 +422,11 @@ func resourceAwsIamRoleUpdate(d *schema.ResourceData, meta interface{}) error { remove := expandStringSet(os.Difference(ns)) add := expandStringSet(ns.Difference(os)) - if err := deleteAwsIamRolePolicyAttachments(iamconn, roleName, remove); err != nil { + if err := deleteIamRolePolicyAttachments(iamconn, roleName, remove); err != nil { return fmt.Errorf("unable to detach policies: %w", err) } - if err := resourceAwsIamRoleAttachManagedPolicies(roleName, add, meta); err != nil { + if err := addIamManagedPolicies(roleName, add, meta); err != nil { return err } } @@ -447,7 +447,7 @@ func resourceAwsIamRoleDelete(d *schema.ResourceData, meta interface{}) error { hasManaged = true } - err := deleteAwsIamRole(conn, d.Id(), d.Get("force_detach_policies").(bool), hasInline, hasManaged) + err := deleteIamRole(conn, d.Id(), d.Get("force_detach_policies").(bool), hasInline, hasManaged) if tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { return nil } @@ -458,35 +458,35 @@ func resourceAwsIamRoleDelete(d *schema.ResourceData, meta interface{}) error { return nil } -func deleteAwsIamRole(conn *iam.IAM, rolename string, forceDetach, hasInline, hasManaged bool) error { - if err := deleteAwsIamRoleInstanceProfiles(conn, rolename); err != nil { +func deleteIamRole(conn *iam.IAM, roleName string, forceDetach, hasInline, hasManaged bool) error { + if err := deleteIamRoleInstanceProfiles(conn, roleName); err != nil { return fmt.Errorf("unable to detach instance profiles: %w", err) } if forceDetach || hasManaged { - managedPolicies, err := readAwsIamRolePolicyAttachments(conn, rolename) + managedPolicies, err := readIamRolePolicyAttachments(conn, roleName) if err != nil { return err } - if err := deleteAwsIamRolePolicyAttachments(conn, rolename, managedPolicies); err != nil { + if err := deleteIamRolePolicyAttachments(conn, roleName, managedPolicies); err != nil { return fmt.Errorf("unable to detach policies: %w", err) } } if forceDetach || hasInline { - inlinePolicies, err := readAwsIamRolePolicyNames(conn, rolename) + inlinePolicies, err := readIamRolePolicyNames(conn, roleName) if err != nil { return err } - if err := deleteAwsIamRolePolicies(conn, rolename, inlinePolicies); err != nil { + if err := deleteIamRolePolicies(conn, roleName, inlinePolicies); err != nil { return fmt.Errorf("unable to delete inline policies: %w", err) } } deleteRoleInput := &iam.DeleteRoleInput{ - RoleName: aws.String(rolename), + RoleName: aws.String(roleName), } err := resource.Retry(waiter.PropagationTimeout, func() *resource.RetryError { _, err := conn.DeleteRole(deleteRoleInput) @@ -506,9 +506,9 @@ func deleteAwsIamRole(conn *iam.IAM, rolename string, forceDetach, hasInline, ha return err } -func deleteAwsIamRoleInstanceProfiles(conn *iam.IAM, rolename string) error { +func deleteIamRoleInstanceProfiles(conn *iam.IAM, roleName string) error { resp, err := conn.ListInstanceProfilesForRole(&iam.ListInstanceProfilesForRoleInput{ - RoleName: aws.String(rolename), + RoleName: aws.String(roleName), }) if tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { return nil @@ -521,7 +521,7 @@ func deleteAwsIamRoleInstanceProfiles(conn *iam.IAM, rolename string) error { for _, i := range resp.InstanceProfiles { input := &iam.RemoveRoleFromInstanceProfileInput{ InstanceProfileName: i.InstanceProfileName, - RoleName: aws.String(rolename), + RoleName: aws.String(roleName), } _, err := conn.RemoveRoleFromInstanceProfile(input) @@ -536,10 +536,10 @@ func deleteAwsIamRoleInstanceProfiles(conn *iam.IAM, rolename string) error { return nil } -func readAwsIamRolePolicyAttachments(conn *iam.IAM, rolename string) ([]*string, error) { +func readIamRolePolicyAttachments(conn *iam.IAM, roleName string) ([]*string, error) { managedPolicies := make([]*string, 0) input := &iam.ListAttachedRolePoliciesInput{ - RoleName: aws.String(rolename), + RoleName: aws.String(roleName), } err := conn.ListAttachedRolePoliciesPages(input, func(page *iam.ListAttachedRolePoliciesOutput, lastPage bool) bool { @@ -555,11 +555,11 @@ func readAwsIamRolePolicyAttachments(conn *iam.IAM, rolename string) ([]*string, return managedPolicies, nil } -func deleteAwsIamRolePolicyAttachments(conn *iam.IAM, rolename string, managedPolicies []*string) error { +func deleteIamRolePolicyAttachments(conn *iam.IAM, roleName string, managedPolicies []*string) error { for _, parn := range managedPolicies { input := &iam.DetachRolePolicyInput{ PolicyArn: parn, - RoleName: aws.String(rolename), + RoleName: aws.String(roleName), } _, err := conn.DetachRolePolicy(input) @@ -574,10 +574,10 @@ func deleteAwsIamRolePolicyAttachments(conn *iam.IAM, rolename string, managedPo return nil } -func readAwsIamRolePolicyNames(conn *iam.IAM, rolename string) ([]*string, error) { +func readIamRolePolicyNames(conn *iam.IAM, roleName string) ([]*string, error) { inlinePolicies := make([]*string, 0) input := &iam.ListRolePoliciesInput{ - RoleName: aws.String(rolename), + RoleName: aws.String(roleName), } err := conn.ListRolePoliciesPages(input, func(page *iam.ListRolePoliciesOutput, lastPage bool) bool { @@ -592,11 +592,11 @@ func readAwsIamRolePolicyNames(conn *iam.IAM, rolename string) ([]*string, error return inlinePolicies, nil } -func deleteAwsIamRolePolicies(conn *iam.IAM, rolename string, policyNames []*string) error { +func deleteIamRolePolicies(conn *iam.IAM, roleName string, policyNames []*string) error { for _, name := range policyNames { input := &iam.DeleteRolePolicyInput{ PolicyName: name, - RoleName: aws.String(rolename), + RoleName: aws.String(roleName), } _, err := conn.DeleteRolePolicy(input) @@ -682,7 +682,7 @@ func expandIamInlinePolicies(roleName string, tfList []interface{}) []*iam.PutRo return apiObjects } -func resourceAwsIamRoleCreateInlinePolicies(policies []*iam.PutRolePolicyInput, meta interface{}) error { +func addIamInlinePolicies(policies []*iam.PutRolePolicyInput, meta interface{}) error { conn := meta.(*AWSClient).iamconn var errs *multierror.Error @@ -697,7 +697,7 @@ func resourceAwsIamRoleCreateInlinePolicies(policies []*iam.PutRolePolicyInput, return errs.ErrorOrNil() } -func resourceAwsIamRoleAttachManagedPolicies(roleName string, policies []*string, meta interface{}) error { +func addIamManagedPolicies(roleName string, policies []*string, meta interface{}) error { conn := meta.(*AWSClient).iamconn var errs *multierror.Error @@ -712,10 +712,10 @@ func resourceAwsIamRoleAttachManagedPolicies(roleName string, policies []*string return errs.ErrorOrNil() } -func resourceAwsIamRoleListInlinePolicies(roleName string, meta interface{}) ([]*iam.PutRolePolicyInput, error) { +func readIamInlinePolicies(roleName string, meta interface{}) ([]*iam.PutRolePolicyInput, error) { conn := meta.(*AWSClient).iamconn - policyNames, err := readAwsIamRolePolicyNames(conn, roleName) + policyNames, err := readIamRolePolicyNames(conn, roleName) if err != nil { return nil, err } diff --git a/aws/resource_aws_iam_role_test.go b/aws/resource_aws_iam_role_test.go index ba212d0349f..80d9f86e820 100644 --- a/aws/resource_aws_iam_role_test.go +++ b/aws/resource_aws_iam_role_test.go @@ -121,19 +121,19 @@ func testSweepIamRoles(region string) error { var sweeperErrs *multierror.Error for _, role := range roles { - rolename := aws.StringValue(role.RoleName) - log.Printf("[DEBUG] Deleting IAM Role (%s)", rolename) + roleName := aws.StringValue(role.RoleName) + log.Printf("[DEBUG] Deleting IAM Role (%s)", roleName) - err := deleteAwsIamRole(conn, rolename, true, true, true) + err := deleteIamRole(conn, roleName, true, true, true) if tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { continue } if testSweepSkipResourceError(err) { - log.Printf("[WARN] Skipping IAM Role (%s): %s", rolename, err) + log.Printf("[WARN] Skipping IAM Role (%s): %s", roleName, err) continue } if err != nil { - sweeperErr := fmt.Errorf("error deleting IAM Role (%s): %w", rolename, err) + sweeperErr := fmt.Errorf("error deleting IAM Role (%s): %w", roleName, err) log.Printf("[ERROR] %s", sweeperErr) sweeperErrs = multierror.Append(sweeperErrs, sweeperErr) continue @@ -502,7 +502,7 @@ func TestAccAWSIAMRole_policyBasicInline(t *testing.T) { policyName3 := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_iam_role.test" - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSRoleDestroy, @@ -552,7 +552,7 @@ func TestAccAWSIAMRole_policyBasicManaged(t *testing.T) { policyName3 := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_iam_role.test" - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSRoleDestroy, @@ -599,7 +599,7 @@ func TestAccAWSIAMRole_policyOutOfBandRemovalAddedBack_managedNonEmpty(t *testin policyName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_iam_role.test" - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSRoleDestroy, @@ -633,7 +633,7 @@ func TestAccAWSIAMRole_policyOutOfBandRemovalAddedBack_inlineNonEmpty(t *testing policyName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_iam_role.test" - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSRoleDestroy, @@ -668,7 +668,7 @@ func TestAccAWSIAMRole_policyOutOfBandAdditionRemoved_managedNonEmpty(t *testing policyName2 := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_iam_role.test" - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSRoleDestroy, @@ -703,7 +703,7 @@ func TestAccAWSIAMRole_policyOutOfBandAdditionRemoved_inlineNonEmpty(t *testing. policyName2 := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_iam_role.test" - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSRoleDestroy, @@ -738,7 +738,7 @@ func TestAccAWSIAMRole_policyOutOfBandAdditionIgnored_inlineNonExistent(t *testi policyName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_iam_role.test" - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSRoleDestroy, @@ -771,7 +771,7 @@ func TestAccAWSIAMRole_policyOutOfBandAdditionIgnored_managedNonExistent(t *test policyName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_iam_role.test" - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSRoleDestroy, @@ -804,7 +804,7 @@ func TestAccAWSIAMRole_policyOutOfBandAdditionRemoved_inlineEmpty(t *testing.T) policyName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_iam_role.test" - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSRoleDestroy, @@ -837,7 +837,7 @@ func TestAccAWSIAMRole_policyOutOfBandAdditionRemoved_managedEmpty(t *testing.T) policyName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_iam_role.test" - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSRoleDestroy, @@ -951,7 +951,7 @@ func testAccCheckAWSRoleGeneratedNamePrefix(resource, prefix string) resource.Te } } -// Attach inline policy outside of terraform CRUD. +// Attach inline policy out of band (outside of terraform) func testAccAddAwsIAMRolePolicy(n string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -1006,35 +1006,14 @@ func testAccCheckAWSRolePolicyCheckInline(role *iam.GetRoleOutput, roleName stri conn := testAccProvider.Meta().(*AWSClient).iamconn - //inline policies - var inlinePolicyList []string - var marker *string - for { - //inline is ListRolePolicies - resp, err := conn.ListRolePolicies(&iam.ListRolePoliciesInput{ - RoleName: aws.String(roleName), - Marker: marker, - }) - - if err != nil { - if awsErr, ok := err.(awserr.Error); ok { - // aws specific error - if awsErr.Code() == "NoSuchEntity" { - // policies not found - break - } - } - return err - } - - for _, policyName := range resp.PolicyNames { - inlinePolicyList = append(inlinePolicyList, *policyName) - } + policyNames, err := readIamRolePolicyNames(conn, roleName) + if err != nil { + return fmt.Errorf("reading role (%s): %w", roleName, err) + } - if !*resp.IsTruncated { - break - } - marker = resp.Marker + var inlinePolicyList []string + for _, name := range policyNames { + inlinePolicyList = append(inlinePolicyList, aws.StringValue(name)) } if !compareStringSlices(inlinePolicyList, inlinePolicies) { @@ -1053,34 +1032,19 @@ func testAccCheckAWSRolePolicyCheckManaged(role *iam.GetRoleOutput, roleName str conn := testAccProvider.Meta().(*AWSClient).iamconn - // managed policies var managedPolicyList []string - var marker *string - for { - resp, err := conn.ListAttachedRolePolicies(&iam.ListAttachedRolePoliciesInput{ - RoleName: aws.String(roleName), - Marker: marker, - }) - - if err != nil { - if awsErr, ok := err.(awserr.Error); ok { - // aws specific error - if awsErr.Code() == "NoSuchEntity" { - // role not found - break - } - } - return err - } - - for _, ap := range resp.AttachedPolicies { - managedPolicyList = append(managedPolicyList, *ap.PolicyName) //PolicyName also available - } + input := &iam.ListAttachedRolePoliciesInput{ + RoleName: aws.String(roleName), + } - if !*resp.IsTruncated { - break + err := conn.ListAttachedRolePoliciesPages(input, func(page *iam.ListAttachedRolePoliciesOutput, lastPage bool) bool { + for _, v := range page.AttachedPolicies { + managedPolicyList = append(managedPolicyList, aws.StringValue(v.PolicyName)) } - marker = resp.Marker + return !lastPage + }) + if err != nil && !tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { + return err } if !compareStringSlices(managedPolicyList, managedPolicies) { @@ -1091,43 +1055,34 @@ func testAccCheckAWSRolePolicyCheckManaged(role *iam.GetRoleOutput, roleName str } } -func testAccCheckAWSRolePolicyDetachManagedPolicy(role *iam.GetRoleOutput, managedPolicy string) resource.TestCheckFunc { +func testAccCheckAWSRolePolicyDetachManagedPolicy(role *iam.GetRoleOutput, policyName string) resource.TestCheckFunc { return func(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).iamconn - // find in managed policies - var policyARN string - var marker *string - for { - resp, err := conn.ListAttachedRolePolicies(&iam.ListAttachedRolePoliciesInput{ - RoleName: aws.String(*role.Role.RoleName), - Marker: marker, - }) - - if err != nil { - return err - } + var managedARN string + input := &iam.ListAttachedRolePoliciesInput{ + RoleName: role.Role.RoleName, + } - for _, ap := range resp.AttachedPolicies { - if *ap.PolicyName == managedPolicy { - policyARN = *ap.PolicyArn + err := conn.ListAttachedRolePoliciesPages(input, func(page *iam.ListAttachedRolePoliciesOutput, lastPage bool) bool { + for _, v := range page.AttachedPolicies { + if *v.PolicyName == policyName { + managedARN = *v.PolicyArn break } } - - if !*resp.IsTruncated { - break - } - marker = resp.Marker + return !lastPage + }) + if err != nil && !tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { + return fmt.Errorf("finding managed policy (%s): %w", policyName, err) } - - if policyARN == "" { - return fmt.Errorf("managed policy %s not found", managedPolicy) + if managedARN == "" { + return fmt.Errorf("managed policy (%s) not found", policyName) } - _, err := conn.DetachRolePolicy(&iam.DetachRolePolicyInput{ - PolicyArn: aws.String(policyARN), - RoleName: aws.String(*role.Role.RoleName), + _, err = conn.DetachRolePolicy(&iam.DetachRolePolicyInput{ + PolicyArn: aws.String(managedARN), + RoleName: role.Role.RoleName, }) if err != nil { @@ -1138,50 +1093,37 @@ func testAccCheckAWSRolePolicyDetachManagedPolicy(role *iam.GetRoleOutput, manag } } -func testAccCheckAWSRolePolicyAttachManagedPolicy(role *iam.GetRoleOutput, managedPolicy string) resource.TestCheckFunc { +func testAccCheckAWSRolePolicyAttachManagedPolicy(role *iam.GetRoleOutput, policyName string) resource.TestCheckFunc { return func(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).iamconn - // find in managed policies - var policyARN string - var marker *string - for { - pathPrefix := "/tf-testing/" - policyUsageFilter := "PermissionsPolicy" - scope := "Local" - resp, err := conn.ListPolicies(&iam.ListPoliciesInput{ - PathPrefix: &pathPrefix, - PolicyUsageFilter: &policyUsageFilter, - Scope: &scope, - Marker: marker, - }) - - if err != nil { - return err - } + var managedARN string + input := &iam.ListPoliciesInput{ + PathPrefix: aws.String("/tf-testing/"), + PolicyUsageFilter: aws.String("PermissionsPolicy"), + Scope: aws.String("Local"), + } - for _, ap := range resp.Policies { - if *ap.PolicyName == managedPolicy { - policyARN = *ap.Arn + err := conn.ListPoliciesPages(input, func(page *iam.ListPoliciesOutput, lastPage bool) bool { + for _, v := range page.Policies { + if *v.PolicyName == policyName { + managedARN = *v.Arn break } } - - if !*resp.IsTruncated { - break - } - marker = resp.Marker + return !lastPage + }) + if err != nil && !tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { + return fmt.Errorf("finding managed policy (%s): %w", policyName, err) } - - if policyARN == "" { - return fmt.Errorf("managed policy %s not found", managedPolicy) + if managedARN == "" { + return fmt.Errorf("managed policy (%s) not found", policyName) } - _, err := conn.AttachRolePolicy(&iam.AttachRolePolicyInput{ - PolicyArn: aws.String(policyARN), - RoleName: aws.String(*role.Role.RoleName), + _, err = conn.AttachRolePolicy(&iam.AttachRolePolicyInput{ + PolicyArn: aws.String(managedARN), + RoleName: role.Role.RoleName, }) - if err != nil { return err } @@ -1223,58 +1165,6 @@ func testAccCheckAWSRolePolicyRemoveInlinePolicy(role *iam.GetRoleOutput, inline } } -func testAccCheckAWSRolePolicyCheckInlinePrefix(role *iam.GetRoleOutput, roleName string, prefix string) resource.TestCheckFunc { - return func(s *terraform.State) error { - if *role.Role.RoleName != roleName { - return fmt.Errorf("bad role: expected %s, got %s", roleName, *role.Role.RoleName) - } - - conn := testAccProvider.Meta().(*AWSClient).iamconn - - //inline policies - var inlinePolicyList []string - var marker *string - for { - //inline is ListRolePolicies - resp, err := conn.ListRolePolicies(&iam.ListRolePoliciesInput{ - RoleName: aws.String(roleName), - Marker: marker, - }) - - if err != nil { - return err - } - - for _, policyName := range resp.PolicyNames { - inlinePolicyList = append(inlinePolicyList, *policyName) - } - - if !*resp.IsTruncated { - break - } - marker = resp.Marker - } - - match := false - r := regexp.MustCompile(fmt.Sprintf("^%s(.*)$", prefix)) - - for _, policyName := range inlinePolicyList { - if r.MatchString(policyName) { - match = true - break - } - } - - if !match { - return fmt.Errorf( - "%s didn't match any inline policies", - prefix) - } - - return nil - } -} - func compareStringSlices(a []string, b []string) bool { if len(a) != len(b) { return false From 2c13eb2e34c86ad3dd07a2a819e96a55c7f9223d Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Fri, 5 Feb 2021 16:27:30 -0500 Subject: [PATCH 09/20] resource/iam_role: Update documentation --- website/docs/r/iam_role.html.markdown | 48 ++++++++++++--------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/website/docs/r/iam_role.html.markdown b/website/docs/r/iam_role.html.markdown index ae109c2e4e7..b356458c511 100644 --- a/website/docs/r/iam_role.html.markdown +++ b/website/docs/r/iam_role.html.markdown @@ -12,6 +12,8 @@ Provides an IAM role. ~> *NOTE:* If policies are attached to the role via the [`aws_iam_policy_attachment` resource](/docs/providers/aws/r/iam_policy_attachment.html) and you are modifying the role `name` or `path`, the `force_detach_policies` argument must be set to `true` and applied before attempting the operation otherwise you will encounter a `DeleteConflict` error. The [`aws_iam_role_policy_attachment` resource (recommended)](/docs/providers/aws/r/iam_role_policy_attachment.html) does not have this requirement. +~> *NOTE:* If you use this resource's `managed_policy_arns` argument or `inline_policy` configuration blocks, this resource will take over exclusive management of the role's respective policy types (e.g., both policy types if both arguments are used). These arguments are incompatible with other ways of managing a role's policies, such as [`aws_iam_policy_attachment`](/docs/providers/aws/r/iam_policy_attachment.html), [`aws_iam_role_policy_attachment`](/docs/providers/aws/r/iam_role_policy_attachment.html), and [`aws_iam_role_policy`](/docs/providers/aws/r/iam_role_policy.html). If you attempt to manage a role's policies by multiple means, you will get resource cycling and/or errors. + ## Example Usage ### Basic Example @@ -63,9 +65,9 @@ resource "aws_iam_role" "instance" { } ``` -### Example of Using Exclusive Inline Policies +### Example of Exclusive Inline Policies -This example will create an IAM role with two inline IAM policies. If a third policy were added out of band, on the next apply, that policy would be removed. If one of the two original policies were removed, out of band, on the next apply, the policy would be recreated. +This example creates an IAM role with two inline IAM policies. If someone adds another inline policy out-of-band, on the next apply, Terraform will remove that policy. If someone deletes these policies out-of-band, Terraform will recreate them. ```hcl resource "aws_iam_role" "example" { @@ -92,8 +94,8 @@ EOF } inline_policy { - name_prefix = "inline_" - policy = data.aws_iam_policy_document.inline_policy.json + name = "policy-8675309" + policy = data.aws_iam_policy_document.inline_policy.json } } @@ -105,33 +107,32 @@ data "aws_iam_policy_document" "inline_policy" { } ``` -### Example of Not Allowing Inline Policies +### Example of Removing Inline Policies + +This example creates an IAM role with an empty IAM `inline_policy` argument. If someone were to add an inline policy out-of-band, on the next apply, Terraform will remove that policy. -This example will create an IAM role resource, which, on the next apply, will remove any out-of-band inline policies. ```hcl resource "aws_iam_role" "example" { name = "yak_role" assume_role_policy = data.aws_iam_policy_document.instance_assume_role_policy.json # (not shown) - - inline_policy = [] + inline_policy = [] } ``` -### Example of Using Exclusive Managed Policies +### Example of Exclusive Managed Policies -This example will create an IAM role resource with two attached managed IAM policies. If a third policy were attached out of band, on the next apply, that policy would be detached. If one of the two original policies were detached out of band, on the next apply, the policy would be recreated and re-attached. +This example creates an IAM role and attaches two managed IAM policies. If someone attaches another managed policy out-of-band, on the next apply, Terraform will detach that policy. If someone detaches these policies out-of-band, Terraform will attach them again. ```hcl resource "aws_iam_role" "example" { - name = "yak_role" - assume_role_policy = data.aws_iam_policy_document.instance_assume_role_policy.json # (not shown) - + name = "yak_role" + assume_role_policy = data.aws_iam_policy_document.instance_assume_role_policy.json # (not shown) managed_policy_arns = [aws_iam_policy.policy_one.arn, aws_iam_policy.policy_two.arn] } resource "aws_iam_policy" "policy_one" { - name = "managed_policy" + name = "policy-618033" policy = < **NOTE:** The `managed_policy_arns` attribute, which provides an _exclusive_ set of managed policies for an IAM role, will conflict with using the `iam_role_policy_attachment` resource, which provides non-exclusive, managed policy-role attachment. See [`iam_role_policy_attachment`](/docs/providers/aws/r/iam_role_policy_attachment.html). -* `inline_policy` - (Optional) An exclusive set of IAM inline policies associated with the IAM role. If the attribute is not used, the resource will not add or remove the role's inline policies on the next `apply`. If one empty `inline_policy` attribute is used, any inline policies that are added outside of Terraform will be removed on the next `apply`. - -~> **NOTE:** The `inline_policy` attribute, which provides an _exclusive_ set of inline policies for an IAM role, will conflict with the `iam_role_policy` resource, which provides non-exclusive, inline policy-role association. See [`iam_role_policy`](/docs/providers/aws/r/iam_role_policy.html). +* `inline_policy` - (Optional) An exclusive set of IAM inline policies associated with the IAM role. If the attribute is not used, the resource will ignore the role's inline policy changes on the next `apply`. Using one empty `inline_policy` attribute (i.e., `inline_policy = []`) will cause Terraform to remove any inline policies added out of band, on the next `apply`. ### inline_policy The following arguments are supported: * `policy` - (Required) The policy document. This is a JSON formatted string. For more information about building IAM policy documents with Terraform, see the [AWS IAM Policy Document Guide](https://www.terraform.io/docs/providers/aws/guides/iam-policy-documents.html). -* `name` - (Optional) The name of the role policy. If omitted, Terraform will assign a random, unique name. -* `name_prefix` - (Optional) Creates a unique name beginning with the specified prefix. If both `name` and `name_prefix` are used, `name_prefix` will be ignored. +* `name` - (Required) The name of the role policy. ## Attributes Reference From 303a865f6094e340809bc804b623050858a4463e Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Mon, 8 Feb 2021 12:27:07 -0500 Subject: [PATCH 10/20] docs/iam_role: Clean up arguments --- website/docs/r/iam_role.html.markdown | 52 +++++++++++++-------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/website/docs/r/iam_role.html.markdown b/website/docs/r/iam_role.html.markdown index b356458c511..d9174f0fdb3 100644 --- a/website/docs/r/iam_role.html.markdown +++ b/website/docs/r/iam_role.html.markdown @@ -109,7 +109,7 @@ data "aws_iam_policy_document" "inline_policy" { ### Example of Removing Inline Policies -This example creates an IAM role with an empty IAM `inline_policy` argument. If someone were to add an inline policy out-of-band, on the next apply, Terraform will remove that policy. +This example creates an IAM role with what appears to be empty IAM `inline_policy` argument instead of using `inline_policy` as a configuration block. The result is that if someone were to add an inline policy out-of-band, on the next apply, Terraform will remove that policy. ```hcl @@ -186,45 +186,43 @@ resource "aws_iam_role" "example" { ## Argument Reference -The following arguments are supported: +The following argument is required: -* `name` - (Optional, Forces new resource) The name of the role. If omitted, Terraform will assign a random, unique name. -* `name_prefix` - (Optional, Forces new resource) Creates a unique name beginning with the specified prefix. Conflicts with `name`. -* `assume_role_policy` - (Required) The policy that grants an entity permission to assume the role. +* `assume_role_policy` - (Required) Policy that grants an entity permission to assume the role. -~> **NOTE:** This `assume_role_policy` is very similar but slightly different than just a standard IAM policy and cannot use an `aws_iam_policy` resource. It _can_ however, use an `aws_iam_policy_document` [data source](/docs/providers/aws/d/iam_policy_document.html), see example below for how this could work. +~> **NOTE:** The `assume_role_policy` is very similar to but slightly different than a standard IAM policy and cannot use an `aws_iam_policy` resource. However, it _can_ use an `aws_iam_policy_document` [data source](/docs/providers/aws/d/iam_policy_document.html). See the example above of how this works. -* `force_detach_policies` - (Optional) Specifies to force detaching any policies the role has before destroying it. Defaults to `false`. -* `path` - (Optional) The path to the role. - See [IAM Identifiers](https://docs.aws.amazon.com/IAM/latest/UserGuide/Using_Identifiers.html) for more information. -* `description` - (Optional) The description of the role. +The following arguments are optional: -* `max_session_duration` - (Optional) The maximum session duration (in seconds) that you want to set for the specified role. If you do not specify a value for this setting, the default maximum of one hour is applied. This setting can have a value from 1 hour to 12 hours. -* `permissions_boundary` - (Optional) The ARN of the policy that is used to set the permissions boundary for the role. +* `description` - (Optional) Description of the role. +* `force_detach_policies` - (Optional) Whether to force detaching any policies the role has before destroying it. Defaults to `false`. +* `inline_policy` - (Optional) Configuration block defining an exclusive set of IAM inline policies associated with the IAM role. Defined below. +* `managed_policy_arns` - (Optional) Set of exclusive IAM managed policy ARNs to attach to the IAM role. When a configuration does not include this attribute, Terraform will ignore out-of-band policy attachments on the next `apply`. However, when you include it, Terraform will, on the next `apply`, align the role's attachments with the set by attaching or detaching managed policies. This includes detaching all out-of-band attachments when you include an empty set (i.e., `managed_policy_arns = []`). +* `max_session_duration` - (Optional) Maximum session duration (in seconds) that you want to set for the specified role. If you do not specify a value for this setting, the default maximum of one hour is applied. This setting can have a value from 1 hour to 12 hours. +* `name` - (Optional, Forces new resource) Friendly name of the role. If omitted, Terraform will assign a random, unique name. See [IAM Identifiers](https://docs.aws.amazon.com/IAM/latest/UserGuide/Using_Identifiers.html) for more information. +* `name_prefix` - (Optional, Forces new resource) Creates a unique friendly name beginning with the specified prefix. Conflicts with `name`. +* `path` - (Optional) Path to the role. See [IAM Identifiers](https://docs.aws.amazon.com/IAM/latest/UserGuide/Using_Identifiers.html) for more information. +* `permissions_boundary` - (Optional) ARN of the policy that is used to set the permissions boundary for the role. * `tags` - Key-value mapping of tags for the IAM role -* `managed_policy_arns` - (Optional) An exclusive set of IAM managed policy ARNs to attach to the IAM role. If the attribute is not used, the resource will not attach or detach the role's managed policies on the next `apply`. If the set is empty, all managed policies that are attached out of band, will be detached on the next `apply`. - -~> **NOTE:** The `managed_policy_arns` attribute, which provides an _exclusive_ set of managed policies for an IAM role, will conflict with using the `iam_role_policy_attachment` resource, which provides non-exclusive, managed policy-role attachment. See [`iam_role_policy_attachment`](/docs/providers/aws/r/iam_role_policy_attachment.html). - -* `inline_policy` - (Optional) An exclusive set of IAM inline policies associated with the IAM role. If the attribute is not used, the resource will ignore the role's inline policy changes on the next `apply`. Using one empty `inline_policy` attribute (i.e., `inline_policy = []`) will cause Terraform to remove any inline policies added out of band, on the next `apply`. ### inline_policy -The following arguments are supported: +If the `inline_policy` configuration block is not used, Terraform will ignore the role's inline policy changes on the next `apply`. Using an `inline_policy` like an empty attribute (i.e., `inline_policy = []`) will cause Terraform to remove any inline policies that were added out-of-band on the next `apply`. + +This configuration block supports the following: -* `policy` - (Required) The policy document. This is a JSON formatted string. For more information about building IAM policy documents with Terraform, see the [AWS IAM Policy Document Guide](https://www.terraform.io/docs/providers/aws/guides/iam-policy-documents.html). -* `name` - (Required) The name of the role policy. +* `name` - (Required) Name of the role policy. +* `policy` - (Required) Policy document as a JSON formatted string. For more information about building IAM policy documents with Terraform, see the [AWS IAM Policy Document Guide](https://www.terraform.io/docs/providers/aws/guides/iam-policy-documents.html). ## Attributes Reference -In addition to all arguments above, the following attributes are exported: +In addition to the arguments above, the following attributes are exported: -* `arn` - The Amazon Resource Name (ARN) specifying the role. -* `create_date` - The creation date of the IAM role. -* `description` - The description of the role. -* `id` - The name of the role. -* `name` - The name of the role. -* `unique_id` - The stable and unique string identifying the role. +* `arn` - Amazon Resource Name (ARN) specifying the role. +* `create_date` - Creation date of the IAM role. +* `id` - Name of the role. +* `name` - Name of the role. +* `unique_id` - Stable and unique string identifying the role. ## Import From 49179fef31a2ff58d95e203c59b187a6c8bf43e6 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Mon, 8 Feb 2021 12:32:27 -0500 Subject: [PATCH 11/20] resource/iam_role: Add changelog file --- .changelog/5904.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/5904.txt diff --git a/.changelog/5904.txt b/.changelog/5904.txt new file mode 100644 index 00000000000..cee17c90851 --- /dev/null +++ b/.changelog/5904.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/iam_role: Add support for exclusive IAM role policies with `inline_policy` and `managed_policy_arns` arguments +``` \ No newline at end of file From 34db37ee7594ba06105ddf05a7d57dfc024659f7 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Tue, 16 Feb 2021 14:28:42 -0500 Subject: [PATCH 12/20] resource/iam_role: Rework exclusive policy lists --- aws/resource_aws_iam_role.go | 26 ++++++++++++++----- aws/resource_aws_iam_role_test.go | 43 ++++++++++++++++++++++++++----- 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/aws/resource_aws_iam_role.go b/aws/resource_aws_iam_role.go index b6fe18f6252..9d9f5a77bf2 100644 --- a/aws/resource_aws_iam_role.go +++ b/aws/resource_aws_iam_role.go @@ -112,20 +112,19 @@ func resourceAwsIamRole() *schema.Resource { "tags": tagsSchema(), "inline_policy": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - ConfigMode: schema.SchemaConfigModeAttr, + Type: schema.TypeSet, + Optional: true, + Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "name": { Type: schema.TypeString, - Required: true, + Optional: true, ValidateFunc: validateIamRolePolicyName, }, "policy": { Type: schema.TypeString, - Required: true, + Optional: true, ValidateFunc: validateIAMPolicyJson, DiffSuppressFunc: suppressEquivalentAwsPolicyDiffs, }, @@ -594,6 +593,10 @@ func readIamRolePolicyNames(conn *iam.IAM, roleName string) ([]*string, error) { func deleteIamRolePolicies(conn *iam.IAM, roleName string, policyNames []*string) error { for _, name := range policyNames { + if len(aws.StringValue(name)) == 0 { + continue + } + input := &iam.DeleteRolePolicyInput{ PolicyName: name, RoleName: aws.String(roleName), @@ -687,6 +690,10 @@ func addIamInlinePolicies(policies []*iam.PutRolePolicyInput, meta interface{}) var errs *multierror.Error for _, policy := range policies { + if len(aws.StringValue(policy.PolicyName)) == 0 || len(aws.StringValue(policy.PolicyDocument)) == 0 { + continue + } + if _, err := conn.PutRolePolicy(policy); err != nil { newErr := fmt.Errorf("creating inline policy (%s): %w", aws.StringValue(policy.PolicyName), err) log.Printf("[ERROR] %s", newErr) @@ -744,5 +751,12 @@ func readIamInlinePolicies(roleName string, meta interface{}) ([]*iam.PutRolePol apiObjects = append(apiObjects, apiObject) } + if len(apiObjects) == 0 { + apiObjects = append(apiObjects, &iam.PutRolePolicyInput{ + PolicyDocument: aws.String(""), + PolicyName: aws.String(""), + }) + } + return apiObjects, nil } diff --git a/aws/resource_aws_iam_role_test.go b/aws/resource_aws_iam_role_test.go index 80d9f86e820..09678a29a9d 100644 --- a/aws/resource_aws_iam_role_test.go +++ b/aws/resource_aws_iam_role_test.go @@ -544,6 +544,27 @@ func TestAccAWSIAMRole_policyBasicInline(t *testing.T) { }) } +func TestAccAWSIAMRole_policyBasicInlineEmpty(t *testing.T) { + var role iam.GetRoleOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_iam_role.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRoleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSRolePolicyEmptyInlineConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRoleExists(resourceName, &role), + testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{}), + ), + }, + }, + }) +} + func TestAccAWSIAMRole_policyBasicManaged(t *testing.T) { var role iam.GetRoleOutput rName := acctest.RandomWithPrefix("tf-acc-test") @@ -735,7 +756,8 @@ func TestAccAWSIAMRole_policyOutOfBandAdditionRemoved_inlineNonEmpty(t *testing. func TestAccAWSIAMRole_policyOutOfBandAdditionIgnored_inlineNonExistent(t *testing.T) { var role iam.GetRoleOutput rName := acctest.RandomWithPrefix("tf-acc-test") - policyName := acctest.RandomWithPrefix("tf-acc-test") + policyName1 := acctest.RandomWithPrefix("tf-acc-test") + policyName2 := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_iam_role.test" resource.ParallelTest(t, resource.TestCase{ @@ -747,16 +769,25 @@ func TestAccAWSIAMRole_policyOutOfBandAdditionIgnored_inlineNonExistent(t *testi Config: testAccAWSRolePolicyNoInlineConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSRoleExists(resourceName, &role), - testAccCheckAWSRolePolicyAddInlinePolicy(&role, policyName), - testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{policyName}), + testAccCheckAWSRolePolicyAddInlinePolicy(&role, policyName1), + testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{policyName1}), ), }, { Config: testAccAWSRolePolicyNoInlineConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSRoleExists(resourceName, &role), - testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{policyName}), - testAccCheckAWSRolePolicyRemoveInlinePolicy(&role, policyName), + testAccCheckAWSRolePolicyAddInlinePolicy(&role, policyName2), + testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{policyName1, policyName2}), + ), + }, + { + Config: testAccAWSRolePolicyNoInlineConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRoleExists(resourceName, &role), + testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{policyName1, policyName2}), + testAccCheckAWSRolePolicyRemoveInlinePolicy(&role, policyName1), + testAccCheckAWSRolePolicyRemoveInlinePolicy(&role, policyName2), ), }, }, @@ -2142,7 +2173,7 @@ resource "aws_iam_role" "test" { } EOF - inline_policy = [] + inline_policy {} } `, roleName) } From 33349f1a5cd4f290750cf7fb55b2b0c7a133d07a Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Tue, 16 Feb 2021 14:33:04 -0500 Subject: [PATCH 13/20] docs/resource/iam_role: Update for new syntax --- website/docs/r/iam_role.html.markdown | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/website/docs/r/iam_role.html.markdown b/website/docs/r/iam_role.html.markdown index d9174f0fdb3..317d9eb708c 100644 --- a/website/docs/r/iam_role.html.markdown +++ b/website/docs/r/iam_role.html.markdown @@ -116,7 +116,8 @@ This example creates an IAM role with what appears to be empty IAM `inline_polic resource "aws_iam_role" "example" { name = "yak_role" assume_role_policy = data.aws_iam_policy_document.instance_assume_role_policy.json # (not shown) - inline_policy = [] + + inline_policy {} } ``` @@ -207,7 +208,7 @@ The following arguments are optional: ### inline_policy -If the `inline_policy` configuration block is not used, Terraform will ignore the role's inline policy changes on the next `apply`. Using an `inline_policy` like an empty attribute (i.e., `inline_policy = []`) will cause Terraform to remove any inline policies that were added out-of-band on the next `apply`. +If the `inline_policy` configuration block is not used, Terraform will ignore the role's inline policy changes on the next `apply`. Using an empty `inline_policy` (i.e., `inline_policy {}`) will cause Terraform to remove _all_ inline policies that were added out-of-band on the next `apply`. This configuration block supports the following: From 392c292c2763245233c2f2d2190482413381e841 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Tue, 16 Feb 2021 14:35:59 -0500 Subject: [PATCH 14/20] docs/resource/iam_role: Minor correction --- website/docs/r/iam_role.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/iam_role.html.markdown b/website/docs/r/iam_role.html.markdown index 317d9eb708c..a7f35057f65 100644 --- a/website/docs/r/iam_role.html.markdown +++ b/website/docs/r/iam_role.html.markdown @@ -198,7 +198,7 @@ The following arguments are optional: * `description` - (Optional) Description of the role. * `force_detach_policies` - (Optional) Whether to force detaching any policies the role has before destroying it. Defaults to `false`. * `inline_policy` - (Optional) Configuration block defining an exclusive set of IAM inline policies associated with the IAM role. Defined below. -* `managed_policy_arns` - (Optional) Set of exclusive IAM managed policy ARNs to attach to the IAM role. When a configuration does not include this attribute, Terraform will ignore out-of-band policy attachments on the next `apply`. However, when you include it, Terraform will, on the next `apply`, align the role's attachments with the set by attaching or detaching managed policies. This includes detaching all out-of-band attachments when you include an empty set (i.e., `managed_policy_arns = []`). +* `managed_policy_arns` - (Optional) Set of exclusive IAM managed policy ARNs to attach to the IAM role. When a configuration does not include this attribute, Terraform will ignore out-of-band policy attachments on the next `apply`. However, when you include it, Terraform will, on the next `apply`, align the role's policy attachments with this set by attaching or detaching managed policies. This includes detaching all out-of-band attachments when you include an empty set (i.e., `managed_policy_arns = []`). * `max_session_duration` - (Optional) Maximum session duration (in seconds) that you want to set for the specified role. If you do not specify a value for this setting, the default maximum of one hour is applied. This setting can have a value from 1 hour to 12 hours. * `name` - (Optional, Forces new resource) Friendly name of the role. If omitted, Terraform will assign a random, unique name. See [IAM Identifiers](https://docs.aws.amazon.com/IAM/latest/UserGuide/Using_Identifiers.html) for more information. * `name_prefix` - (Optional, Forces new resource) Creates a unique friendly name beginning with the specified prefix. Conflicts with `name`. From 387b80b86519d5b50cedc750c4c1518c50719066 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Mon, 22 Feb 2021 11:53:22 -0500 Subject: [PATCH 15/20] resource/iam_role: Adjust after review --- .changelog/5904.txt | 2 +- aws/resource_aws_iam_role.go | 31 ++- aws/resource_aws_iam_role_test.go | 193 +++++++----------- .../r/iam_policy_attachment.html.markdown | 2 + website/docs/r/iam_role.html.markdown | 10 +- website/docs/r/iam_role_policy.html.markdown | 2 + .../r/iam_role_policy_attachment.markdown | 2 + 7 files changed, 109 insertions(+), 133 deletions(-) diff --git a/.changelog/5904.txt b/.changelog/5904.txt index cee17c90851..cc516468999 100644 --- a/.changelog/5904.txt +++ b/.changelog/5904.txt @@ -1,3 +1,3 @@ ```release-note:enhancement -resource/iam_role: Add support for exclusive IAM role policies with `inline_policy` and `managed_policy_arns` arguments +resource/aws_iam_role: Add support for exclusive policy management `inline_policy` and `managed_policy_arns` arguments ``` \ No newline at end of file diff --git a/aws/resource_aws_iam_role.go b/aws/resource_aws_iam_role.go index 9d9f5a77bf2..b2451459085 100644 --- a/aws/resource_aws_iam_role.go +++ b/aws/resource_aws_iam_role.go @@ -118,9 +118,12 @@ func resourceAwsIamRole() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "name": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validateIamRolePolicyName, + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.All( + validation.StringIsNotEmpty, + validateIamRolePolicyName, + ), }, "policy": { Type: schema.TypeString, @@ -275,15 +278,15 @@ func resourceAwsIamRoleRead(d *schema.ResourceData, meta interface{}) error { return err } - inlinePolicies, err := readIamInlinePolicies(*role.RoleName, meta) + inlinePolicies, err := readIamInlinePolicies(aws.StringValue(role.RoleName), meta) if err != nil { return fmt.Errorf("reading inline policies for IAM role %s, error: %s", d.Id(), err) } if err := d.Set("inline_policy", flattenIamInlinePolicies(inlinePolicies)); err != nil { - return fmt.Errorf("setting attribute_name: %w", err) + return fmt.Errorf("error setting inline_policy: %w", err) } - managedPolicies, err := readIamRolePolicyAttachments(iamconn, *role.RoleName) + managedPolicies, err := readIamRolePolicyAttachments(iamconn, aws.StringValue(role.RoleName)) if err != nil { return fmt.Errorf("reading managed policies for IAM role %s, error: %s", d.Id(), err) } @@ -393,7 +396,9 @@ func resourceAwsIamRoleUpdate(d *schema.ResourceData, meta interface{}) error { continue } - policyNames = append(policyNames, aws.String(tfMap["name"].(string))) + if v, ok := tfMap["name"].(string); ok && v != "" { + policyNames = append(policyNames, aws.String(tfMap["name"].(string))) + } } if err := deleteIamRolePolicies(iamconn, roleName, policyNames); err != nil { return fmt.Errorf("unable to delete inline policies: %w", err) @@ -651,9 +656,15 @@ func expandIamInlinePolicy(roleName string, tfMap map[string]interface{}) *iam.P } apiObject := &iam.PutRolePolicyInput{ - RoleName: aws.String(roleName), - PolicyName: aws.String(tfMap["name"].(string)), - PolicyDocument: aws.String(tfMap["policy"].(string)), + RoleName: aws.String(roleName), + } + + if v, ok := tfMap["name"].(string); ok && v != "" { + apiObject.PolicyName = aws.String(v) + } + + if v, ok := tfMap["policy"].(string); ok && v != "" { + apiObject.PolicyDocument = aws.String(v) } return apiObject diff --git a/aws/resource_aws_iam_role_test.go b/aws/resource_aws_iam_role_test.go index 09678a29a9d..ecf53a70c9f 100644 --- a/aws/resource_aws_iam_role_test.go +++ b/aws/resource_aws_iam_role_test.go @@ -511,7 +511,6 @@ func TestAccAWSIAMRole_policyBasicInline(t *testing.T) { Config: testAccAWSRolePolicyInlineConfig(rName, policyName1), Check: resource.ComposeTestCheckFunc( testAccCheckAWSRoleExists(resourceName, &role), - testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{policyName1}), resource.TestCheckResourceAttr(resourceName, "inline_policy.#", "1"), resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "managed_policy_arns.#", "0"), @@ -521,7 +520,6 @@ func TestAccAWSIAMRole_policyBasicInline(t *testing.T) { Config: testAccAWSRolePolicyInlineConfigUpdate(rName, policyName2, policyName3), Check: resource.ComposeTestCheckFunc( testAccCheckAWSRoleExists(resourceName, &role), - testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{policyName2, policyName3}), resource.TestCheckResourceAttr(resourceName, "inline_policy.#", "2"), resource.TestCheckResourceAttr(resourceName, "managed_policy_arns.#", "0"), ), @@ -530,7 +528,6 @@ func TestAccAWSIAMRole_policyBasicInline(t *testing.T) { Config: testAccAWSRolePolicyInlineConfigUpdateDown(rName, policyName3), Check: resource.ComposeTestCheckFunc( testAccCheckAWSRoleExists(resourceName, &role), - testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{policyName3}), resource.TestCheckResourceAttr(resourceName, "inline_policy.#", "1"), resource.TestCheckResourceAttr(resourceName, "managed_policy_arns.#", "0"), ), @@ -558,7 +555,6 @@ func TestAccAWSIAMRole_policyBasicInlineEmpty(t *testing.T) { Config: testAccAWSRolePolicyEmptyInlineConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSRoleExists(resourceName, &role), - testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{}), ), }, }, @@ -582,7 +578,6 @@ func TestAccAWSIAMRole_policyBasicManaged(t *testing.T) { Config: testAccAWSRolePolicyManagedConfig(rName, policyName1), Check: resource.ComposeTestCheckFunc( testAccCheckAWSRoleExists(resourceName, &role), - testAccCheckAWSRolePolicyCheckManaged(&role, rName, []string{policyName1}), resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "managed_policy_arns.#", "1"), ), @@ -591,7 +586,6 @@ func TestAccAWSIAMRole_policyBasicManaged(t *testing.T) { Config: testAccAWSRolePolicyManagedConfigUpdate(rName, policyName1, policyName2, policyName3), Check: resource.ComposeTestCheckFunc( testAccCheckAWSRoleExists(resourceName, &role), - testAccCheckAWSRolePolicyCheckManaged(&role, rName, []string{policyName2, policyName3}), resource.TestCheckResourceAttr(resourceName, "managed_policy_arns.#", "2"), ), }, @@ -599,7 +593,6 @@ func TestAccAWSIAMRole_policyBasicManaged(t *testing.T) { Config: testAccAWSRolePolicyManagedConfigUpdateDown(rName, policyName1, policyName2, policyName3), Check: resource.ComposeTestCheckFunc( testAccCheckAWSRoleExists(resourceName, &role), - testAccCheckAWSRolePolicyCheckManaged(&role, rName, []string{policyName3}), resource.TestCheckResourceAttr(resourceName, "managed_policy_arns.#", "1"), ), }, @@ -630,7 +623,6 @@ func TestAccAWSIAMRole_policyOutOfBandRemovalAddedBack_managedNonEmpty(t *testin Check: resource.ComposeTestCheckFunc( testAccCheckAWSRoleExists(resourceName, &role), testAccCheckAWSRolePolicyDetachManagedPolicy(&role, policyName), - testAccCheckAWSRolePolicyCheckManaged(&role, rName, []string{}), ), ExpectNonEmptyPlan: true, }, @@ -638,7 +630,6 @@ func TestAccAWSIAMRole_policyOutOfBandRemovalAddedBack_managedNonEmpty(t *testin Config: testAccAWSRolePolicyManagedConfig(rName, policyName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSRoleExists(resourceName, &role), - testAccCheckAWSRolePolicyCheckManaged(&role, rName, []string{policyName}), resource.TestCheckResourceAttr(resourceName, "managed_policy_arns.#", "1"), ), }, @@ -664,7 +655,6 @@ func TestAccAWSIAMRole_policyOutOfBandRemovalAddedBack_inlineNonEmpty(t *testing Check: resource.ComposeTestCheckFunc( testAccCheckAWSRoleExists(resourceName, &role), testAccCheckAWSRolePolicyRemoveInlinePolicy(&role, policyName), - testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{}), ), ExpectNonEmptyPlan: true, }, @@ -672,7 +662,6 @@ func TestAccAWSIAMRole_policyOutOfBandRemovalAddedBack_inlineNonEmpty(t *testing Config: testAccAWSRolePolicyInlineConfig(rName, policyName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSRoleExists(resourceName, &role), - testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{policyName}), resource.TestCheckResourceAttr(resourceName, "inline_policy.#", "1"), ), }, @@ -699,7 +688,6 @@ func TestAccAWSIAMRole_policyOutOfBandAdditionRemoved_managedNonEmpty(t *testing Check: resource.ComposeTestCheckFunc( testAccCheckAWSRoleExists(resourceName, &role), testAccCheckAWSRolePolicyAttachManagedPolicy(&role, policyName2), - testAccCheckAWSRolePolicyCheckManaged(&role, rName, []string{policyName1, policyName2}), ), ExpectNonEmptyPlan: true, }, @@ -707,7 +695,6 @@ func TestAccAWSIAMRole_policyOutOfBandAdditionRemoved_managedNonEmpty(t *testing Config: testAccAWSRolePolicyExtraManagedConfig(rName, policyName1, policyName2), Check: resource.ComposeTestCheckFunc( testAccCheckAWSRoleExists(resourceName, &role), - testAccCheckAWSRolePolicyCheckManaged(&role, rName, []string{policyName1}), resource.TestCheckResourceAttr(resourceName, "managed_policy_arns.#", "1"), ), }, @@ -734,7 +721,6 @@ func TestAccAWSIAMRole_policyOutOfBandAdditionRemoved_inlineNonEmpty(t *testing. Check: resource.ComposeTestCheckFunc( testAccCheckAWSRoleExists(resourceName, &role), testAccCheckAWSRolePolicyAddInlinePolicy(&role, policyName2), - testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{policyName1, policyName2}), ), ExpectNonEmptyPlan: true, }, @@ -742,7 +728,6 @@ func TestAccAWSIAMRole_policyOutOfBandAdditionRemoved_inlineNonEmpty(t *testing. Config: testAccAWSRolePolicyInlineConfig(rName, policyName1), Check: resource.ComposeTestCheckFunc( testAccCheckAWSRoleExists(resourceName, &role), - testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{policyName1}), resource.TestCheckResourceAttr(resourceName, "inline_policy.#", "1"), resource.TestCheckResourceAttr(resourceName, "managed_policy_arns.#", "0"), ), @@ -770,7 +755,6 @@ func TestAccAWSIAMRole_policyOutOfBandAdditionIgnored_inlineNonExistent(t *testi Check: resource.ComposeTestCheckFunc( testAccCheckAWSRoleExists(resourceName, &role), testAccCheckAWSRolePolicyAddInlinePolicy(&role, policyName1), - testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{policyName1}), ), }, { @@ -778,14 +762,12 @@ func TestAccAWSIAMRole_policyOutOfBandAdditionIgnored_inlineNonExistent(t *testi Check: resource.ComposeTestCheckFunc( testAccCheckAWSRoleExists(resourceName, &role), testAccCheckAWSRolePolicyAddInlinePolicy(&role, policyName2), - testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{policyName1, policyName2}), ), }, { Config: testAccAWSRolePolicyNoInlineConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSRoleExists(resourceName, &role), - testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{policyName1, policyName2}), testAccCheckAWSRolePolicyRemoveInlinePolicy(&role, policyName1), testAccCheckAWSRolePolicyRemoveInlinePolicy(&role, policyName2), ), @@ -812,14 +794,12 @@ func TestAccAWSIAMRole_policyOutOfBandAdditionIgnored_managedNonExistent(t *test Check: resource.ComposeTestCheckFunc( testAccCheckAWSRoleExists(resourceName, &role), testAccCheckAWSRolePolicyAttachManagedPolicy(&role, policyName), - testAccCheckAWSRolePolicyCheckManaged(&role, rName, []string{policyName}), ), }, { Config: testAccAWSRolePolicyNoManagedConfig(rName, policyName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSRoleExists(resourceName, &role), - testAccCheckAWSRolePolicyCheckManaged(&role, rName, []string{policyName}), testAccCheckAWSRolePolicyDetachManagedPolicy(&role, policyName), ), }, @@ -845,7 +825,6 @@ func TestAccAWSIAMRole_policyOutOfBandAdditionRemoved_inlineEmpty(t *testing.T) Check: resource.ComposeTestCheckFunc( testAccCheckAWSRoleExists(resourceName, &role), testAccCheckAWSRolePolicyAddInlinePolicy(&role, policyName), - testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{policyName}), ), ExpectNonEmptyPlan: true, }, @@ -853,7 +832,6 @@ func TestAccAWSIAMRole_policyOutOfBandAdditionRemoved_inlineEmpty(t *testing.T) Config: testAccAWSRolePolicyEmptyInlineConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSRoleExists(resourceName, &role), - testAccCheckAWSRolePolicyCheckInline(&role, rName, []string{}), ), }, }, @@ -878,7 +856,6 @@ func TestAccAWSIAMRole_policyOutOfBandAdditionRemoved_managedEmpty(t *testing.T) Check: resource.ComposeTestCheckFunc( testAccCheckAWSRoleExists(resourceName, &role), testAccCheckAWSRolePolicyAttachManagedPolicy(&role, policyName), - testAccCheckAWSRolePolicyCheckManaged(&role, rName, []string{policyName}), ), ExpectNonEmptyPlan: true, }, @@ -886,7 +863,6 @@ func TestAccAWSIAMRole_policyOutOfBandAdditionRemoved_managedEmpty(t *testing.T) Config: testAccAWSRolePolicyEmptyManagedConfig(rName, policyName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSRoleExists(resourceName, &role), - testAccCheckAWSRolePolicyCheckManaged(&role, rName, []string{}), ), }, }, @@ -1029,63 +1005,6 @@ func testAccCheckAWSRolePermissionsBoundary(getRoleOutput *iam.GetRoleOutput, ex } } -func testAccCheckAWSRolePolicyCheckInline(role *iam.GetRoleOutput, roleName string, inlinePolicies []string) resource.TestCheckFunc { - return func(s *terraform.State) error { - if !strings.Contains(*role.Role.RoleName, roleName) { - return fmt.Errorf("bad role: expected %s, got %s", roleName, *role.Role.RoleName) - } - - conn := testAccProvider.Meta().(*AWSClient).iamconn - - policyNames, err := readIamRolePolicyNames(conn, roleName) - if err != nil { - return fmt.Errorf("reading role (%s): %w", roleName, err) - } - - var inlinePolicyList []string - for _, name := range policyNames { - inlinePolicyList = append(inlinePolicyList, aws.StringValue(name)) - } - - if !compareStringSlices(inlinePolicyList, inlinePolicies) { - return fmt.Errorf("inline policies did not match: %s (from AWS) to %s (expected)", strings.Join(inlinePolicyList, ","), strings.Join(inlinePolicies, ",")) - } - - return nil - } -} - -func testAccCheckAWSRolePolicyCheckManaged(role *iam.GetRoleOutput, roleName string, managedPolicies []string) resource.TestCheckFunc { - return func(s *terraform.State) error { - if !strings.Contains(*role.Role.RoleName, roleName) { - return fmt.Errorf("bad role: expected %s, got %s", roleName, *role.Role.RoleName) - } - - conn := testAccProvider.Meta().(*AWSClient).iamconn - - var managedPolicyList []string - input := &iam.ListAttachedRolePoliciesInput{ - RoleName: aws.String(roleName), - } - - err := conn.ListAttachedRolePoliciesPages(input, func(page *iam.ListAttachedRolePoliciesOutput, lastPage bool) bool { - for _, v := range page.AttachedPolicies { - managedPolicyList = append(managedPolicyList, aws.StringValue(v.PolicyName)) - } - return !lastPage - }) - if err != nil && !tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { - return err - } - - if !compareStringSlices(managedPolicyList, managedPolicies) { - return fmt.Errorf("managed policies did not match: %s (from AWS) to %s (expected)", strings.Join(managedPolicyList, ","), strings.Join(managedPolicies, ",")) - } - - return nil - } -} - func testAccCheckAWSRolePolicyDetachManagedPolicy(role *iam.GetRoleOutput, policyName string) resource.TestCheckFunc { return func(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).iamconn @@ -1212,6 +1131,8 @@ func compareStringSlices(a []string, b []string) bool { func testAccCheckIAMRoleConfig_MaxSessionDuration(rName string, maxSessionDuration int) string { return fmt.Sprintf(` +data "aws_partition" "current" {} + resource "aws_iam_role" "test" { name = "test-role-%s" path = "/" @@ -1225,7 +1146,7 @@ resource "aws_iam_role" "test" { "Effect": "Allow", "Principal": { "Service": [ - "ec2.amazonaws.com" + "ec2.${data.aws_partition.current.dns_suffix}" ] }, "Action": [ @@ -1241,6 +1162,8 @@ EOF func testAccCheckIAMRoleConfig_PermissionsBoundary(rName, permissionsBoundary string) string { return fmt.Sprintf(` +data "aws_partition" "current" {} + resource "aws_iam_role" "test" { assume_role_policy = < **NOTE:** The usage of this resource conflicts with the `aws_iam_group_policy_attachment`, `aws_iam_role_policy_attachment`, and `aws_iam_user_policy_attachment` resources and will permanently show a difference if both are defined. +~> **NOTE:** For a given role, this resource is incompatible with using [`aws_iam_role`'s](/docs/providers/aws/r/iam_role_policy_attachment.html) `managed_policy_arns` argument. If you use the `aws_iam_role`.`managed_policy_arns` argument, the `aws_iam_role` resource will take over exclusive management of the role's managed policy attachments. If you attempt to manage a role's policies by multiple means, you will get resource cycling and/or errors. + ## Example Usage ```hcl diff --git a/website/docs/r/iam_role.html.markdown b/website/docs/r/iam_role.html.markdown index a7f35057f65..e8a95f11bce 100644 --- a/website/docs/r/iam_role.html.markdown +++ b/website/docs/r/iam_role.html.markdown @@ -10,9 +10,9 @@ description: |- Provides an IAM role. -~> *NOTE:* If policies are attached to the role via the [`aws_iam_policy_attachment` resource](/docs/providers/aws/r/iam_policy_attachment.html) and you are modifying the role `name` or `path`, the `force_detach_policies` argument must be set to `true` and applied before attempting the operation otherwise you will encounter a `DeleteConflict` error. The [`aws_iam_role_policy_attachment` resource (recommended)](/docs/providers/aws/r/iam_role_policy_attachment.html) does not have this requirement. +~> **NOTE:** If policies are attached to the role via the [`aws_iam_policy_attachment` resource](/docs/providers/aws/r/iam_policy_attachment.html) and you are modifying the role `name` or `path`, the `force_detach_policies` argument must be set to `true` and applied before attempting the operation otherwise you will encounter a `DeleteConflict` error. The [`aws_iam_role_policy_attachment` resource (recommended)](/docs/providers/aws/r/iam_role_policy_attachment.html) does not have this requirement. -~> *NOTE:* If you use this resource's `managed_policy_arns` argument or `inline_policy` configuration blocks, this resource will take over exclusive management of the role's respective policy types (e.g., both policy types if both arguments are used). These arguments are incompatible with other ways of managing a role's policies, such as [`aws_iam_policy_attachment`](/docs/providers/aws/r/iam_policy_attachment.html), [`aws_iam_role_policy_attachment`](/docs/providers/aws/r/iam_role_policy_attachment.html), and [`aws_iam_role_policy`](/docs/providers/aws/r/iam_role_policy.html). If you attempt to manage a role's policies by multiple means, you will get resource cycling and/or errors. +~> **NOTE:** If you use this resource's `managed_policy_arns` argument or `inline_policy` configuration blocks, this resource will take over exclusive management of the role's respective policy types (e.g., both policy types if both arguments are used). These arguments are incompatible with other ways of managing a role's policies, such as [`aws_iam_policy_attachment`](/docs/providers/aws/r/iam_policy_attachment.html), [`aws_iam_role_policy_attachment`](/docs/providers/aws/r/iam_role_policy_attachment.html), and [`aws_iam_role_policy`](/docs/providers/aws/r/iam_role_policy.html). If you attempt to manage a role's policies by multiple means, you will get resource cycling and/or errors. ## Example Usage @@ -197,8 +197,8 @@ The following arguments are optional: * `description` - (Optional) Description of the role. * `force_detach_policies` - (Optional) Whether to force detaching any policies the role has before destroying it. Defaults to `false`. -* `inline_policy` - (Optional) Configuration block defining an exclusive set of IAM inline policies associated with the IAM role. Defined below. -* `managed_policy_arns` - (Optional) Set of exclusive IAM managed policy ARNs to attach to the IAM role. When a configuration does not include this attribute, Terraform will ignore out-of-band policy attachments on the next `apply`. However, when you include it, Terraform will, on the next `apply`, align the role's policy attachments with this set by attaching or detaching managed policies. This includes detaching all out-of-band attachments when you include an empty set (i.e., `managed_policy_arns = []`). +* `inline_policy` - (Optional) Configuration block defining an exclusive set of IAM inline policies associated with the IAM role. Defined below. If no blocks are configured, Terraform will ignore any managing any inline policies in this resource. Configuring one empty block (i.e., `inline_policy {}`) will cause Terraform to remove _all_ inline policies. +* `managed_policy_arns` - (Optional) Set of exclusive IAM managed policy ARNs to attach to the IAM role. If this attribute is not configured, Terraform will ignore policy attachments to this resource. When configured, Terraform will align the role's managed policy attachments with this set by attaching or detaching managed policies. Configuring an empty set (i.e., `managed_policy_arns = []`) will cause Terraform to remove _all_ managed policy attachments. * `max_session_duration` - (Optional) Maximum session duration (in seconds) that you want to set for the specified role. If you do not specify a value for this setting, the default maximum of one hour is applied. This setting can have a value from 1 hour to 12 hours. * `name` - (Optional, Forces new resource) Friendly name of the role. If omitted, Terraform will assign a random, unique name. See [IAM Identifiers](https://docs.aws.amazon.com/IAM/latest/UserGuide/Using_Identifiers.html) for more information. * `name_prefix` - (Optional, Forces new resource) Creates a unique friendly name beginning with the specified prefix. Conflicts with `name`. @@ -208,8 +208,6 @@ The following arguments are optional: ### inline_policy -If the `inline_policy` configuration block is not used, Terraform will ignore the role's inline policy changes on the next `apply`. Using an empty `inline_policy` (i.e., `inline_policy {}`) will cause Terraform to remove _all_ inline policies that were added out-of-band on the next `apply`. - This configuration block supports the following: * `name` - (Required) Name of the role policy. diff --git a/website/docs/r/iam_role_policy.html.markdown b/website/docs/r/iam_role_policy.html.markdown index dc081aa826a..bf8b105cd1e 100644 --- a/website/docs/r/iam_role_policy.html.markdown +++ b/website/docs/r/iam_role_policy.html.markdown @@ -10,6 +10,8 @@ description: |- Provides an IAM role inline policy. +~> **NOTE:** For a given role, this resource is incompatible with using the [`aws_iam_role`](/docs/providers/aws/r/iam_role.html) resource's `inline_policy` argument. If you use `aws_iam_role`.`inline_policy` configuration blocks, the `aws_iam_role` resource will take over exclusive management of the role's inline policies. Attempting to manage a role's policies by multiple means will cause resource cycling and/or errors. + ## Example Usage ```hcl diff --git a/website/docs/r/iam_role_policy_attachment.markdown b/website/docs/r/iam_role_policy_attachment.markdown index 4c3cd7845de..6a036316f90 100644 --- a/website/docs/r/iam_role_policy_attachment.markdown +++ b/website/docs/r/iam_role_policy_attachment.markdown @@ -12,6 +12,8 @@ Attaches a Managed IAM Policy to an IAM role ~> **NOTE:** The usage of this resource conflicts with the `aws_iam_policy_attachment` resource and will permanently show a difference if both are defined. +~> **NOTE:** For a given role, this resource is incompatible with using the [`aws_iam_role`](/docs/providers/aws/r/iam_role_policy_attachment.html) resource's `managed_policy_arns` argument. If you use the `aws_iam_role`.`managed_policy_arns` argument, the `aws_iam_role` resource will take over exclusive management of the role's managed policies. Attempting to manage a role's policies by multiple means will cause resource cycling and/or errors. + ## Example Usage ```hcl From 0f22b867051d80fcfc980a75c335a7c8c50b68a1 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Mon, 22 Feb 2021 12:04:33 -0500 Subject: [PATCH 16/20] tests/resource/iam_role: Remove unused function --- aws/resource_aws_iam_role_test.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/aws/resource_aws_iam_role_test.go b/aws/resource_aws_iam_role_test.go index ecf53a70c9f..3549c937777 100644 --- a/aws/resource_aws_iam_role_test.go +++ b/aws/resource_aws_iam_role_test.go @@ -4,7 +4,6 @@ import ( "fmt" "log" "regexp" - "sort" "strings" "testing" @@ -1115,20 +1114,6 @@ func testAccCheckAWSRolePolicyRemoveInlinePolicy(role *iam.GetRoleOutput, inline } } -func compareStringSlices(a []string, b []string) bool { - if len(a) != len(b) { - return false - } - sort.Strings(a) - sort.Strings(b) - for i := range b { - if a[i] != b[i] { - return false - } - } - return true -} - func testAccCheckIAMRoleConfig_MaxSessionDuration(rName string, maxSessionDuration int) string { return fmt.Sprintf(` data "aws_partition" "current" {} From 42bf4aa9096a33588b3d8ae4c21a04f65e4ff565 Mon Sep 17 00:00:00 2001 From: Dirk Avery <31492422+YakDriver@users.noreply.github.com> Date: Mon, 22 Feb 2021 19:00:03 -0500 Subject: [PATCH 17/20] Update website/docs/r/iam_policy_attachment.html.markdown Co-authored-by: Brian Flad --- website/docs/r/iam_policy_attachment.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/iam_policy_attachment.html.markdown b/website/docs/r/iam_policy_attachment.html.markdown index 15d705eee10..0a0b588ea09 100644 --- a/website/docs/r/iam_policy_attachment.html.markdown +++ b/website/docs/r/iam_policy_attachment.html.markdown @@ -14,7 +14,7 @@ Attaches a Managed IAM Policy to user(s), role(s), and/or group(s) ~> **NOTE:** The usage of this resource conflicts with the `aws_iam_group_policy_attachment`, `aws_iam_role_policy_attachment`, and `aws_iam_user_policy_attachment` resources and will permanently show a difference if both are defined. -~> **NOTE:** For a given role, this resource is incompatible with using [`aws_iam_role`'s](/docs/providers/aws/r/iam_role_policy_attachment.html) `managed_policy_arns` argument. If you use the `aws_iam_role`.`managed_policy_arns` argument, the `aws_iam_role` resource will take over exclusive management of the role's managed policy attachments. If you attempt to manage a role's policies by multiple means, you will get resource cycling and/or errors. +~> **NOTE:** For a given role, this resource is incompatible with using the [`aws_iam_role` resource](/docs/providers/aws/r/iam_role.html) `managed_policy_arns` argument. When using that argument and this resource, both will attempt to manage the role's managed policy attachments and Terraform will show a permanent difference. ## Example Usage From 50fc80647d5fa914487b5f0cb89a1073f220df58 Mon Sep 17 00:00:00 2001 From: Dirk Avery <31492422+YakDriver@users.noreply.github.com> Date: Mon, 22 Feb 2021 19:00:33 -0500 Subject: [PATCH 18/20] Update website/docs/r/iam_role_policy.html.markdown Co-authored-by: Brian Flad --- website/docs/r/iam_role_policy.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/iam_role_policy.html.markdown b/website/docs/r/iam_role_policy.html.markdown index bf8b105cd1e..1bec734beba 100644 --- a/website/docs/r/iam_role_policy.html.markdown +++ b/website/docs/r/iam_role_policy.html.markdown @@ -10,7 +10,7 @@ description: |- Provides an IAM role inline policy. -~> **NOTE:** For a given role, this resource is incompatible with using the [`aws_iam_role`](/docs/providers/aws/r/iam_role.html) resource's `inline_policy` argument. If you use `aws_iam_role`.`inline_policy` configuration blocks, the `aws_iam_role` resource will take over exclusive management of the role's inline policies. Attempting to manage a role's policies by multiple means will cause resource cycling and/or errors. +~> **NOTE:** For a given role, this resource is incompatible with using the [`aws_iam_role` resource](/docs/providers/aws/r/iam_role.html) `inline_policy` argument. When using that argument and this resource, both will attempt to manage the role's inline policies and Terraform will show a permanent difference. ## Example Usage From feb54575864309b93936dd48551cf68161c26ff4 Mon Sep 17 00:00:00 2001 From: Dirk Avery <31492422+YakDriver@users.noreply.github.com> Date: Mon, 22 Feb 2021 19:00:45 -0500 Subject: [PATCH 19/20] Update website/docs/r/iam_role_policy_attachment.markdown Co-authored-by: Brian Flad --- website/docs/r/iam_role_policy_attachment.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/iam_role_policy_attachment.markdown b/website/docs/r/iam_role_policy_attachment.markdown index 6a036316f90..b714008e1ac 100644 --- a/website/docs/r/iam_role_policy_attachment.markdown +++ b/website/docs/r/iam_role_policy_attachment.markdown @@ -12,7 +12,7 @@ Attaches a Managed IAM Policy to an IAM role ~> **NOTE:** The usage of this resource conflicts with the `aws_iam_policy_attachment` resource and will permanently show a difference if both are defined. -~> **NOTE:** For a given role, this resource is incompatible with using the [`aws_iam_role`](/docs/providers/aws/r/iam_role_policy_attachment.html) resource's `managed_policy_arns` argument. If you use the `aws_iam_role`.`managed_policy_arns` argument, the `aws_iam_role` resource will take over exclusive management of the role's managed policies. Attempting to manage a role's policies by multiple means will cause resource cycling and/or errors. +~> **NOTE:** For a given role, this resource is incompatible with using the [`aws_iam_role` resource](/docs/providers/aws/r/iam_role.html) `managed_policy_arns` argument. When using that argument and this resource, both will attempt to manage the role's managed policy attachments and Terraform will show a permanent difference. ## Example Usage From 01ec6d51c4b320ec9c3aac63c8b359f4c6370d4a Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Mon, 22 Feb 2021 20:05:44 -0500 Subject: [PATCH 20/20] docs/resource/iam_role: Switch to jsonencode --- website/docs/r/iam_role.html.markdown | 74 +++++++++++---------------- 1 file changed, 30 insertions(+), 44 deletions(-) diff --git a/website/docs/r/iam_role.html.markdown b/website/docs/r/iam_role.html.markdown index e8a95f11bce..9ac2eea8719 100644 --- a/website/docs/r/iam_role.html.markdown +++ b/website/docs/r/iam_role.html.markdown @@ -77,20 +77,16 @@ resource "aws_iam_role" "example" { inline_policy { name = "my_inline_policy" - policy = <