diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index 1ef643553b..d80f18c5a8 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -183,13 +183,16 @@ func Provider() terraform.ResourceProvider { "aws_iam_group_policy": resourceAwsIamGroupPolicy(), "aws_iam_group": resourceAwsIamGroup(), "aws_iam_group_membership": resourceAwsIamGroupMembership(), + "aws_iam_group_policy_attachment": resourceAwsIamGroupPolicyAttachment(), "aws_iam_instance_profile": resourceAwsIamInstanceProfile(), "aws_iam_policy": resourceAwsIamPolicy(), "aws_iam_policy_attachment": resourceAwsIamPolicyAttachment(), + "aws_iam_role_policy_attachment": resourceAwsIamRolePolicyAttachment(), "aws_iam_role_policy": resourceAwsIamRolePolicy(), "aws_iam_role": resourceAwsIamRole(), "aws_iam_saml_provider": resourceAwsIamSamlProvider(), "aws_iam_server_certificate": resourceAwsIAMServerCertificate(), + "aws_iam_user_policy_attachment": resourceAwsIamUserPolicyAttachment(), "aws_iam_user_policy": resourceAwsIamUserPolicy(), "aws_iam_user_ssh_key": resourceAwsIamUserSshKey(), "aws_iam_user": resourceAwsIamUser(), diff --git a/builtin/providers/aws/resource_aws_iam_group_policy_attachment.go b/builtin/providers/aws/resource_aws_iam_group_policy_attachment.go new file mode 100644 index 0000000000..cf9595232a --- /dev/null +++ b/builtin/providers/aws/resource_aws_iam_group_policy_attachment.go @@ -0,0 +1,124 @@ +package aws + +import ( + "fmt" + "log" + + "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/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsIamGroupPolicyAttachment() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsIamGroupPolicyAttachmentCreate, + Read: resourceAwsIamGroupPolicyAttachmentRead, + Delete: resourceAwsIamGroupPolicyAttachmentDelete, + + Schema: map[string]*schema.Schema{ + "group": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "policy_arn": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceAwsIamGroupPolicyAttachmentCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).iamconn + + group := d.Get("group").(string) + arn := d.Get("policy_arn").(string) + + err := attachPolicyToGroup(conn, group, arn) + if err != nil { + return fmt.Errorf("[WARN] Error attaching policy %s to IAM group %s: %v", arn, group, err) + } + + d.SetId(resource.PrefixedUniqueId(fmt.Sprintf("%s-", group))) + return resourceAwsIamGroupPolicyAttachmentRead(d, meta) +} + +func resourceAwsIamGroupPolicyAttachmentRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).iamconn + group := d.Get("group").(string) + arn := d.Get("policy_arn").(string) + + _, err := conn.GetGroup(&iam.GetGroupInput{ + GroupName: aws.String(group), + }) + + if err != nil { + if awsErr, ok := err.(awserr.Error); ok { + if awsErr.Code() == "NoSuchEntity" { + log.Printf("[WARN] No such entity found for Policy Attachment (%s)", group) + d.SetId("") + return nil + } + } + return err + } + + attachedPolicies, err := conn.ListAttachedGroupPolicies(&iam.ListAttachedGroupPoliciesInput{ + GroupName: aws.String(group), + }) + if err != nil { + return err + } + + var policy string + for _, p := range attachedPolicies.AttachedPolicies { + if *p.PolicyArn == arn { + policy = *p.PolicyArn + } + } + + if policy == "" { + log.Printf("[WARN] No such policy found for Group Policy Attachment (%s)", group) + d.SetId("") + } + + return nil +} + +func resourceAwsIamGroupPolicyAttachmentDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).iamconn + group := d.Get("group").(string) + arn := d.Get("policy_arn").(string) + + err := detachPolicyFromGroup(conn, group, arn) + if err != nil { + return fmt.Errorf("[WARN] Error removing policy %s from IAM Group %s: %v", arn, group, err) + } + return nil +} + +func attachPolicyToGroup(conn *iam.IAM, group string, arn string) error { + _, err := conn.AttachGroupPolicy(&iam.AttachGroupPolicyInput{ + GroupName: aws.String(group), + PolicyArn: aws.String(arn), + }) + if err != nil { + return err + } + return nil +} + +func detachPolicyFromGroup(conn *iam.IAM, group string, arn string) error { + _, err := conn.DetachGroupPolicy(&iam.DetachGroupPolicyInput{ + GroupName: aws.String(group), + PolicyArn: aws.String(arn), + }) + if err != nil { + return err + } + return nil +} diff --git a/builtin/providers/aws/resource_aws_iam_group_policy_attachment_test.go b/builtin/providers/aws/resource_aws_iam_group_policy_attachment_test.go new file mode 100644 index 0000000000..a63bd20797 --- /dev/null +++ b/builtin/providers/aws/resource_aws_iam_group_policy_attachment_test.go @@ -0,0 +1,192 @@ +package aws + +import ( + "fmt" + "strings" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/iam" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSIamGroupPolicyAttachment_basic(t *testing.T) { + var out iam.ListAttachedGroupPoliciesOutput + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSGroupPolicyAttachmentDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSGroupPolicyAttachConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSGroupPolicyAttachmentExists("aws_iam_group_policy_attachment.test-attach", 1, &out), + testAccCheckAWSGroupPolicyAttachmentAttributes([]string{"test-policy"}, &out), + ), + }, + resource.TestStep{ + Config: testAccAWSGroupPolicyAttachConfigUpdate, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSGroupPolicyAttachmentExists("aws_iam_group_policy_attachment.test-attach", 2, &out), + testAccCheckAWSGroupPolicyAttachmentAttributes([]string{"test-policy2", "test-policy3"}, &out), + ), + }, + }, + }) +} +func testAccCheckAWSGroupPolicyAttachmentDestroy(s *terraform.State) error { + return nil +} + +func testAccCheckAWSGroupPolicyAttachmentExists(n string, c int, out *iam.ListAttachedGroupPoliciesOutput) 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 policy name is set") + } + + conn := testAccProvider.Meta().(*AWSClient).iamconn + group := rs.Primary.Attributes["group"] + + attachedPolicies, err := conn.ListAttachedGroupPolicies(&iam.ListAttachedGroupPoliciesInput{ + GroupName: aws.String(group), + }) + if err != nil { + return fmt.Errorf("Error: Failed to get attached policies for group %s (%s)", group, n) + } + if c != len(attachedPolicies.AttachedPolicies) { + return fmt.Errorf("Error: Group (%s) has wrong number of policies attached on initial creation", n) + } + + *out = *attachedPolicies + return nil + } +} +func testAccCheckAWSGroupPolicyAttachmentAttributes(policies []string, out *iam.ListAttachedGroupPoliciesOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + matched := 0 + + for _, p := range policies { + for _, ap := range out.AttachedPolicies { + // *ap.PolicyArn like arn:aws:iam::111111111111:policy/test-policy + parts := strings.Split(*ap.PolicyArn, "/") + if len(parts) == 2 && p == parts[1] { + matched++ + } + } + } + if matched != len(policies) || matched != len(out.AttachedPolicies) { + return fmt.Errorf("Error: Number of attached policies was incorrect: expected %d matched policies, matched %d of %d", len(policies), matched, len(out.AttachedPolicies)) + } + return nil + } +} + +const testAccAWSGroupPolicyAttachConfig = ` +resource "aws_iam_group" "group" { + name = "test-group" +} + +resource "aws_iam_policy" "policy" { + name = "test-policy" + description = "A test policy" + policy = <aws_iam_group_policy + > + aws_iam_group_policy_attachment + + > aws_iam_group_membership @@ -407,6 +411,10 @@ aws_iam_role_policy + > + aws_iam_role_policy_attachment + + > aws_iam_saml_provider @@ -423,6 +431,10 @@ aws_iam_user_policy + > + aws_iam_user_policy_attachment + + > aws_iam_user_ssh_key