-
Notifications
You must be signed in to change notification settings - Fork 9.2k
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
resource/aws_iam_user_group_membership: allow non-exclusive group memberships #3365
resource/aws_iam_user_group_membership: allow non-exclusive group memberships #3365
Conversation
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.
Hi @devonbleak 👋 thanks for this contribution! This is a nice enhancement, but I think the implementation is more complex than it needs to be. I may be missing the use case that requires non-exclusively managing a list of users in one resource though, please let me know!
To simplify this, I would prefer to introduce a singular user
attribute that manages a single user-to-group membership (almost like how it used to be 2 years ago). Everything becomes much simpler afterwards:
- No
CustomizeDiff
- No additional flag attribute (
remove_unmanaged_members
) - No sidecar storage attribute (
group_members
)
The implementation would entail something like:
- Set
user
toForceNew: true
- Set
users
toOptional: true
- Set
users
toConflictsWith: []string{"user"}
- Use
d.GetOk()
to switch between the user list of 1 item or the full user lists for addition/removal in Create/Delete - Use
d.GetOk()
to switch to only checking if the singular user is a member in Read - No update logic necessary
What do you think?
"remove_unmanaged_members": &schema.Schema{ | ||
Type: schema.TypeBool, | ||
Optional: true, | ||
Default: false, |
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.
This is a breaking change for the resource as written. It should default to true
to keep backwards compatibility.
@@ -35,6 +36,18 @@ func resourceAwsIamGroupMembership() *schema.Resource { | |||
Required: true, | |||
ForceNew: true, | |||
}, | |||
|
|||
"remove_unmanaged_members": &schema.Schema{ |
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.
Minor nitpick: As of Go 1.7+, the &schema.Schema
after the attribute name declarations are unnecessary
@@ -242,3 +324,14 @@ resource "aws_iam_user" "user" { | |||
} | |||
`, groupName, membershipName, userNamePrefix) | |||
} | |||
|
|||
func testAccAWSGroupMemberAddUnmanagedUser(groupName string, userName string) func() { |
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.
We should probably create a complimentary testAccAWSGroupMemberRemoveManagedUser
Thanks @bflad, this makes sense and I'll get the feedback incorporated.
…On Mon, Feb 26, 2018 at 6:46 PM, Brian Flad ***@***.***> wrote:
***@***.**** commented on this pull request.
Hi @devonbleak <https://github.com/devonbleak> 👋 thanks for this
contribution! This is a nice enhancement, but I think the implementation is
more complex than it needs to be. I may be missing the use case that
requires non-exclusively managing a *list* of users in one resource
though, please let me know!
To simplify this, I would prefer to introduce a singular user attribute
that manages a single user-to-group membership (almost like how it used to
be 2 years ago). Everything becomes much simpler afterwards:
- No CustomizeDiff
- No additional flag attribute (remove_unmanaged_members)
- No sidecar storage attribute (group_members)
The implementation would entail something like:
- Set user to ForceNew: true
- Set users to Optional: true
- Set users to ConflictsWith: []string{"user"}
- Use d.GetOk() to switch between the user list of 1 item or the full
user lists for addition/removal in Create/Delete
- Use d.GetOk() to switch to only checking if the singular user is a
member in Read
- No update logic necessary
What do you think?
------------------------------
In aws/resource_aws_iam_group_membership.go
<#3365 (comment)>
:
> @@ -35,6 +36,18 @@ func resourceAwsIamGroupMembership() *schema.Resource {
Required: true,
ForceNew: true,
},
+
+ "remove_unmanaged_members": &schema.Schema{
+ Type: schema.TypeBool,
+ Optional: true,
+ Default: false,
This is a breaking change for the resource as written. It should default
to true to keep backwards compatibility.
------------------------------
In aws/resource_aws_iam_group_membership.go
<#3365 (comment)>
:
> @@ -35,6 +36,18 @@ func resourceAwsIamGroupMembership() *schema.Resource {
Required: true,
ForceNew: true,
},
+
+ "remove_unmanaged_members": &schema.Schema{
Minor nitpick: As of Go 1.7+, the &schema.Schema after the attribute name
declarations are unnecessary
------------------------------
In aws/resource_aws_iam_group_membership_test.go
<#3365 (comment)>
:
> @@ -242,3 +324,14 @@ resource "aws_iam_user" "user" {
}
`, groupName, membershipName, userNamePrefix)
}
+
+func testAccAWSGroupMemberAddUnmanagedUser(groupName string, userName string) func() {
We should probably create a complimentary testAccAWSGroupMemberRemoveMan
agedUser
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#3365 (review)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AA3vI2TKEJblfpKR4KtT0zPuqAd3Rwbfks5tY2yegaJpZM4SEpYu>
.
|
@bflad thinking about this more, would it make sense to just create a new |
@bflad the more I think about having a 1:1 user-group membership resource the more it breaks down. Consider the case where we have a list of users and a list of groups - there wouldn't be a way to add all the users to all the groups if the lengths of those lists has a common factor. I'm going to go ahead with creating a new resource that will take a list of users and a single group and will be non-exclusive with group memberships. |
@devonbleak sorry for my lack of reply earlier. Do you need any help? Just to confirm the use case you're thinking about, it is something like this? variable "users" {
default = [
"a",
"b",
]
}
variable "groups" {
default = [
"z",
"y",
"x",
]
}
resource "aws_iam_user" "example" {
count = "${length(var.users)}"
# ... other configuration ...
}
resource "aws_iam_group" "example" {
count = "${length(var.groups)}"
# ... other configuration ...
}
resource "aws_iam_user_group_membership" "example" {
# yikes what would we put here? :)
} What do you think about a variable "users" {
default = [
"a",
"b",
]
}
variable "groups" {
default = [
"z",
"y",
"x",
]
}
resource "aws_iam_user" "example" {
count = "${length(var.users)}"
# ... other configuration ...
# add user to all groups
groups = ["${aws_iam_group.example.*.id}"]
}
resource "aws_iam_group" "example" {
count = "${length(var.groups)}"
# ... other configuration ...
} |
@bflad The Adding a groups argument to |
@devonbleak I guess we have come full circle. 😄 Do you think you could implement this PR without the sidecar and defaulting the flag attribute to keep existing behavior (exclusive management)? |
@bflad been thinking about this again the past couple days and thinking about implementing something like an My original implementation of this basically just ignored things it didn't know about in the I'll start working on this approach, but let me know if you'd like to see something different. resource "aws_iam_user_group_memberships" "test" {
user = "${aws_iam_user.test.name}"
groups = [
"${aws_iam_group.test1.name}",
"${aws_iam_group.test2.name}"
]
} |
I'd drop the |
e87df1d
to
7805065
Compare
7805065
to
d898afa
Compare
d898afa
to
79fe763
Compare
@bflad implementation is done on resource/aws_iam_user_group_membership. I also added a note to aws_iam_group_membership about the conflicts behavior so new users aren't caught by surprise with it. Let me know if you'd like any additional changes! |
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.
Hi @devonbleak 👋 This is looking pretty good -- can you see my comments below and let me know if you have any questions or do not have time to implement the feedback? Thanks!
Delete: resourceAwsIamUserGroupMembershipDelete, | ||
|
||
Schema: map[string]*schema.Schema{ | ||
"name": &schema.Schema{ |
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.
What is the purpose of this attribute? It seems to only be necessary for the resource ID. We can instead have Terraform generate a unique ID as it serves no useful value:
d.SetId(resource.UniqueId())
Other nitpick: The &schema.Schema
declarations for the attributes are extraneous as of Go 1.7 as its already declared in map[string]*schema.Schema
Type: schema.TypeSet, | ||
Required: true, | ||
Elem: &schema.Schema{Type: schema.TypeString}, | ||
Set: schema.HashString, |
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.
You can omit Set: schema.HashString
}) | ||
if err != nil { | ||
// unwrap aws-specific error | ||
if awsErr, ok := err.(awserr.Error); ok { |
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.
We have a helper for these:
if isAWSErr(err, iam.ErrCodeNoSuchEntityException, "") {
// ...
}
if awsErr, ok := err.(awserr.Error); ok { | ||
if awsErr.Code() == "NoSuchEntity" { | ||
// no such user | ||
d.SetId("") |
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.
We should log a WARN
message when removing something from the Terraform state, e.g.
log.Printf("[WARN] Groups not found for user (%s), removing from state", user)
GroupName: group, | ||
}) | ||
if err != nil { | ||
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoSuchEntity" { |
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.
Same here about using isAWSErr()
helper
func testAccAWSUserGroupMembershipDestroy(s *terraform.State) error { | ||
conn := testAccProvider.Meta().(*AWSClient).iamconn | ||
|
||
// check that all users and groups have been destroyed |
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.
The below code seems to be checking that the physical IAM users and groups are destroyed. We should only be checking for the removal of group attachments to users defined by aws_iam_user_group_membership
resources. Checking the opposite would mask coding errors in this resource fully deleting users/groups.
for _, group := range groupsNeg { | ||
for _, groupFound := range userGroupList.Groups { | ||
if group == *groupFound.GroupName { | ||
return fmt.Errorf("Required negative group found for %s: %s", userName, group) |
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.
This might be easier to understand with Unexpected group found for
|
||
Provides a resource for adding an [IAM User][2] to [IAM Groups][1]. This | ||
resource will not conflict with itself when used multiple times for the same | ||
user. |
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.
user unless there are overlapping groups.
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.
The resource description here and vice-versa with aws_iam_group_membership
should link to each other and mention why to use one over the other (exclusive management vs non-exclusive).
Config: usersAndGroupsConfig + testAccAWSUserGroupMembershipConfigInit(membershipName), | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttr("aws_iam_user_group_membership.user1_test1", "user", userName1), | ||
testAccAWSUserGroupMembershipCheckGroupListForUser(userName1, []string{groupName1}, []string{groupName2, groupName3}), |
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.
It'd probably be good to also test that userName2
is not attached to anything 👍
79fe763
to
01a3425
Compare
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.
Excellent work here @devonbleak! Let's get this in. 🚀
I'll make the minor testing/documentation updates on merge.
1 test passed (all tests)
=== RUN TestAccAWSUserGroupMembership_basic
--- PASS: TestAccAWSUserGroupMembership_basic (46.68s)
This has been released in version 1.17.0 of the AWS provider. Please see the Terraform documentation on provider versioning or reach out if you need any assistance upgrading. |
Now that the aws_iam_user_group_membership resource exists[1], we can add these users to the relevant groups without clobbering existing members of the group, meaning that our workaround is no longer necessary. [1]hashicorp/terraform-provider-aws#3365
Now that the aws_iam_user_group_membership resource exists[1], we can add these users to the relevant groups without clobbering existing members of the group, meaning that our workaround is no longer necessary. [1]hashicorp/terraform-provider-aws#3365
Now that the aws_iam_user_group_membership resource exists[1], we can add these users to the relevant groups without clobbering existing members of the group, meaning that our workaround is no longer necessary. [1]hashicorp/terraform-provider-aws#3365
Now that the aws_iam_user_group_membership resource exists[1], we can add these users to the relevant groups without clobbering existing members of the group, meaning that our workaround is no longer necessary. [1]hashicorp/terraform-provider-aws#3365
Now that the aws_iam_user_group_membership resource exists[1], we can add these users to the relevant groups without clobbering existing members of the group, meaning that our workaround is no longer necessary. [1]hashicorp/terraform-provider-aws#3365
Now that the aws_iam_user_group_membership resource exists[1], we can add these users to the relevant groups without clobbering existing members of the group, meaning that our workaround is no longer necessary. [1]hashicorp/terraform-provider-aws#3365
Now that the aws_iam_user_group_membership resource exists[1], we can add these users to the relevant groups without clobbering existing members of the group, meaning that our workaround is no longer necessary. [1]hashicorp/terraform-provider-aws#3365
Now that the aws_iam_user_group_membership resource exists[1], we can add these users to the relevant groups without clobbering existing members of the group, meaning that our workaround is no longer necessary. [1]hashicorp/terraform-provider-aws#3365
Now that the aws_iam_user_group_membership resource exists[1], we can add these users to the relevant groups without clobbering existing members of the group, meaning that our workaround is no longer necessary. [1]hashicorp/terraform-provider-aws#3365
I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues. If you feel this issue should be reopened, we encourage creating a new issue linking back to this one for added context. Thanks! |
Resolves #2881 - allows multiple aws_iam_group_memberships to co-exist peacefully by adding a new computed field
group_members
to manage adding/removing users.Adds a
remove_unmanaged_members
property that can be set to true to restore previous functionality and documentation caveat about flapping with that setting.