-
Notifications
You must be signed in to change notification settings - Fork 9.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
provider/aws: Add IAMGroupMembership resource #2273
Changes from all commits
f31891f
6b57f29
96a28a0
9891523
5f1ab2a
4d59019
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here - looks like this is just examining the IAM group? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My reasoning here is this:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahhh sure I get it now. So this method is really more like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suppose that's true; I was following a convention of I can rename if you think it's necessary |
||
|
||
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}" | ||
} | ||
` |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -89,6 +89,10 @@ | |
<a href="/docs/providers/aws/r/iam_group_policy.html">aws_iam_group_policy</a> | ||
</li> | ||
|
||
<li<%= sidebar_current("docs-aws-resource-iam-group-membership") %>> | ||
<a href="/docs/providers/aws/r/iam_group_membership.html">aws_iam_group_membership</a> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing a file here? Can't find the page this links to. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. derp, added |
||
</li> | ||
|
||
<li<%= sidebar_current("docs-aws-resource-iam-instance-profile") %>> | ||
<a href="/docs/providers/aws/r/iam_instance_profile.html">aws_iam_instance_profile</a> | ||
</li> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these API calls idempotent? Should make sure we properly handle the scenario where we add some users, then crash, then need to add the rest of the users in a follow up run.
Might be as simple as catching an "already in that group" error code here, or perhaps its already considered an AWS success. Either way, worth checking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are idempotent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yay! 💃