diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index 7e2de224d1..3e517d71f1 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -103,6 +103,7 @@ func Provider() terraform.ResourceProvider { "aws_iam_access_key": resourceAwsIamAccessKey(), "aws_iam_group_policy": resourceAwsIamGroupPolicy(), "aws_iam_group": resourceAwsIamGroup(), + "aws_iam_group_membership": resourceAwsIamGroupMembership(), "aws_iam_instance_profile": resourceAwsIamInstanceProfile(), "aws_iam_policy": resourceAwsIamPolicy(), "aws_iam_role_policy": resourceAwsIamRolePolicy(), diff --git a/builtin/providers/aws/resource_aws_iam_group_membership.go b/builtin/providers/aws/resource_aws_iam_group_membership.go new file mode 100644 index 0000000000..c90511cd62 --- /dev/null +++ b/builtin/providers/aws/resource_aws_iam_group_membership.go @@ -0,0 +1,156 @@ +package aws + +import ( + "fmt" + + "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/schema" +) + +func resourceAwsIamGroupMembership() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsIamGroupMembershipCreate, + Read: resourceAwsIamGroupMembershipRead, + Update: resourceAwsIamGroupMembershipUpdate, + Delete: resourceAwsIamGroupMembershipDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "users": &schema.Schema{ + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + + "group": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceAwsIamGroupMembershipCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).iamconn + + group := d.Get("group").(string) + userList := expandStringList(d.Get("users").(*schema.Set).List()) + + if err := addUsersToGroup(conn, userList, group); err != nil { + return err + } + + d.SetId(d.Get("name").(string)) + return resourceAwsIamGroupMembershipRead(d, meta) +} + +func resourceAwsIamGroupMembershipRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).iamconn + group := d.Get("group").(string) + resp, err := conn.GetGroup(&iam.GetGroupInput{ + GroupName: aws.String(group), + }) + + if err != nil { + if awsErr, ok := err.(awserr.Error); ok { + // aws specific error + if awsErr.Code() == "NoSuchEntity" { + // group not found + d.SetId("") + return nil + } + } + return err + } + + ul := make([]string, 0, len(resp.Users)) + for _, u := range resp.Users { + ul = append(ul, *u.UserName) + } + + if err := d.Set("users", ul); err != nil { + return fmt.Errorf("[WARN] Error setting user list from IAM Group Membership (%s), error: %s", group, err) + } + + return nil +} + +func resourceAwsIamGroupMembershipUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).iamconn + + if d.HasChange("users") { + group := d.Get("group").(string) + + o, n := d.GetChange("users") + 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 := removeUsersFromGroup(conn, remove, group); err != nil { + return err + } + + if err := addUsersToGroup(conn, add, group); err != nil { + return err + } + } + + return resourceAwsIamGroupMembershipRead(d, meta) +} + +func resourceAwsIamGroupMembershipDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).iamconn + userList := expandStringList(d.Get("users").(*schema.Set).List()) + group := d.Get("group").(string) + + if err := removeUsersFromGroup(conn, userList, group); err != nil { + return err + } + + return nil +} + +func removeUsersFromGroup(conn *iam.IAM, users []*string, group string) error { + for _, u := range users { + _, err := conn.RemoveUserFromGroup(&iam.RemoveUserFromGroupInput{ + UserName: u, + GroupName: aws.String(group), + }) + + if err != nil { + return err + } + } + return nil +} + +func addUsersToGroup(conn *iam.IAM, users []*string, group string) error { + for _, u := range users { + _, err := conn.AddUserToGroup(&iam.AddUserToGroupInput{ + UserName: u, + GroupName: aws.String(group), + }) + + if err != nil { + return err + } + } + return nil +} diff --git a/builtin/providers/aws/resource_aws_iam_group_membership_test.go b/builtin/providers/aws/resource_aws_iam_group_membership_test.go new file mode 100644 index 0000000000..211ceebd40 --- /dev/null +++ b/builtin/providers/aws/resource_aws_iam_group_membership_test.go @@ -0,0 +1,169 @@ +package aws + +import ( + "fmt" + "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 TestAccAWSGroupMembership_basic(t *testing.T) { + var group iam.GetGroupOutput + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSGroupMembershipDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSGroupMemberConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSGroupMembershipExists("aws_iam_group_membership.team", &group), + testAccCheckAWSGroupMembershipAttributes(&group, []string{"test-user"}), + ), + }, + + resource.TestStep{ + Config: testAccAWSGroupMemberConfigUpdate, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSGroupMembershipExists("aws_iam_group_membership.team", &group), + testAccCheckAWSGroupMembershipAttributes(&group, []string{"test-user-two", "test-user-three"}), + ), + }, + }, + }) +} + +func testAccCheckAWSGroupMembershipDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).iamconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_iam_group_membership" { + continue + } + + group := rs.Primary.Attributes["group"] + + resp, err := conn.GetGroup(&iam.GetGroupInput{ + GroupName: aws.String(group), + }) + if err != nil { + // might error here + return err + } + + users := []string{"test-user", "test-user-two", "test-user-three"} + for _, u := range resp.Users { + for _, i := range users { + if i == *u.UserName { + return fmt.Errorf("Error: User (s) still a member of Group (%s)", i, *resp.Group.GroupName) + } + } + } + + } + + return nil +} + +func testAccCheckAWSGroupMembershipExists(n string, g *iam.GetGroupOutput) 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 User name is set") + } + + conn := testAccProvider.Meta().(*AWSClient).iamconn + gn := rs.Primary.Attributes["group"] + + resp, err := conn.GetGroup(&iam.GetGroupInput{ + GroupName: aws.String(gn), + }) + + if err != nil { + return fmt.Errorf("Error: Group (%s) not found", gn) + } + + *g = *resp + + return nil + } +} + +func testAccCheckAWSGroupMembershipAttributes(group *iam.GetGroupOutput, users []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if *group.Group.GroupName != "test-group" { + return fmt.Errorf("Bad group membership: expected %s, got %s", "test-group", *group.Group.GroupName) + } + + uc := len(users) + for _, u := range users { + for _, gu := range group.Users { + if u == *gu.UserName { + uc-- + } + } + } + + if uc > 0 { + return fmt.Errorf("Bad group membership count, expected (%d), but only (%d) found", len(users), uc) + } + return nil + } +} + +const testAccAWSGroupMemberConfig = ` +resource "aws_iam_group" "group" { + name = "test-group" + path = "/" +} + +resource "aws_iam_user" "user" { + name = "test-user" + path = "/" +} + +resource "aws_iam_group_membership" "team" { + name = "tf-testing-group-membership" + users = ["${aws_iam_user.user.name}"] + group = "${aws_iam_group.group.name}" +} +` + +const testAccAWSGroupMemberConfigUpdate = ` +resource "aws_iam_group" "group" { + name = "test-group" + path = "/" +} + +resource "aws_iam_user" "user" { + name = "test-user" + path = "/" +} + +resource "aws_iam_user" "user_two" { + name = "test-user-two" + path = "/" +} + +resource "aws_iam_user" "user_three" { + name = "test-user-three" + path = "/" +} + +resource "aws_iam_group_membership" "team" { + name = "tf-testing-group-membership" + users = [ + "${aws_iam_user.user_two.name}", + "${aws_iam_user.user_three.name}", + ] + group = "${aws_iam_group.group.name}" +} +` diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index aaf2e49a32..6cab799b18 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -89,6 +89,10 @@ aws_iam_group_policy + > + aws_iam_group_membership + + > aws_iam_instance_profile