mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Improve iam_group exception handling (#45599)
* Improve iam_group exception handling Use AnsibleAWSModule for iam_group and handle BotoCoreErrors as well as ClientErrors. Use fail_json_aws to improve error messages * Add minimal iam_group test suite Update some of the read-only IAM permissions (this is not sufficient to run the test suite but it gets further than it did until it tries to add a (non-existent) user) * Clean up after tests
This commit is contained in:
parent
5c49641798
commit
d2569a3f7d
4 changed files with 115 additions and 78 deletions
|
@ -3,12 +3,16 @@
|
||||||
"Statement": [
|
"Statement": [
|
||||||
{
|
{
|
||||||
"Action": [
|
"Action": [
|
||||||
|
"iam:GetGroup",
|
||||||
"iam:GetInstanceProfile",
|
"iam:GetInstanceProfile",
|
||||||
"iam:GetPolicy",
|
"iam:GetPolicy",
|
||||||
"iam:GetPolicyVersion",
|
"iam:GetPolicyVersion",
|
||||||
"iam:GetRole",
|
"iam:GetRole",
|
||||||
"iam:GetRolePolicy",
|
"iam:GetRolePolicy",
|
||||||
|
"iam:GetUser",
|
||||||
|
"iam:ListAttachedGroupPolicies",
|
||||||
"iam:ListAttachedRolePolicies",
|
"iam:ListAttachedRolePolicies",
|
||||||
|
"iam:ListAttachedUserPolicies",
|
||||||
"iam:ListGroups",
|
"iam:ListGroups",
|
||||||
"iam:ListInstanceProfiles",
|
"iam:ListInstanceProfiles",
|
||||||
"iam:ListInstanceProfilesForRole",
|
"iam:ListInstanceProfilesForRole",
|
||||||
|
|
|
@ -161,14 +161,12 @@ users:
|
||||||
sample: /
|
sample: /
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.aws.core import AnsibleAWSModule
|
||||||
from ansible.module_utils.ec2 import camel_dict_to_snake_dict, ec2_argument_spec, get_aws_connection_info, boto3_conn
|
from ansible.module_utils.ec2 import camel_dict_to_snake_dict
|
||||||
from ansible.module_utils.ec2 import HAS_BOTO3, AWSRetry
|
from ansible.module_utils.ec2 import AWSRetry
|
||||||
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from botocore.exceptions import ClientError, ParamValidationError
|
from botocore.exceptions import BotoCoreError, ClientError, ParamValidationError
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass # caught by imported HAS_BOTO3
|
pass # caught by imported HAS_BOTO3
|
||||||
|
|
||||||
|
@ -232,9 +230,8 @@ def create_or_update_group(connection, module):
|
||||||
# Get group
|
# Get group
|
||||||
try:
|
try:
|
||||||
group = get_group(connection, module, params['GroupName'])
|
group = get_group(connection, module, params['GroupName'])
|
||||||
except ClientError as e:
|
except (BotoCoreError, ClientError) as e:
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(),
|
module.fail_json_aws(e, msg="Couldn't get group")
|
||||||
**camel_dict_to_snake_dict(e.response))
|
|
||||||
|
|
||||||
# If group is None, create it
|
# If group is None, create it
|
||||||
if group is None:
|
if group is None:
|
||||||
|
@ -245,11 +242,8 @@ def create_or_update_group(connection, module):
|
||||||
try:
|
try:
|
||||||
group = connection.create_group(**params)
|
group = connection.create_group(**params)
|
||||||
changed = True
|
changed = True
|
||||||
except ClientError as e:
|
except (BotoCoreError, ClientError) as e:
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(),
|
module.fail_json_aws(e, msg="Couldn't create group")
|
||||||
**camel_dict_to_snake_dict(e.response))
|
|
||||||
except ParamValidationError as e:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc())
|
|
||||||
|
|
||||||
# Manage managed policies
|
# Manage managed policies
|
||||||
current_attached_policies = get_attached_policy_list(connection, module, params['GroupName'])
|
current_attached_policies = get_attached_policy_list(connection, module, params['GroupName'])
|
||||||
|
@ -266,11 +260,8 @@ def create_or_update_group(connection, module):
|
||||||
if not module.check_mode:
|
if not module.check_mode:
|
||||||
try:
|
try:
|
||||||
connection.detach_group_policy(GroupName=params['GroupName'], PolicyArn=policy_arn)
|
connection.detach_group_policy(GroupName=params['GroupName'], PolicyArn=policy_arn)
|
||||||
except ClientError as e:
|
except (BotoCoreError, ClientError) as e:
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(),
|
module.fail_json_aws(e, msg="Couldn't detach policy from group %s" % params['GroupName'])
|
||||||
**camel_dict_to_snake_dict(e.response))
|
|
||||||
except ParamValidationError as e:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc())
|
|
||||||
# If there are policies to adjust that aren't in the current list, then things have changed
|
# If there are policies to adjust that aren't in the current list, then things have changed
|
||||||
# Otherwise the only changes were in purging above
|
# Otherwise the only changes were in purging above
|
||||||
if set(managed_policies) - set(current_attached_policies_arn_list):
|
if set(managed_policies) - set(current_attached_policies_arn_list):
|
||||||
|
@ -280,18 +271,14 @@ def create_or_update_group(connection, module):
|
||||||
for policy_arn in managed_policies:
|
for policy_arn in managed_policies:
|
||||||
try:
|
try:
|
||||||
connection.attach_group_policy(GroupName=params['GroupName'], PolicyArn=policy_arn)
|
connection.attach_group_policy(GroupName=params['GroupName'], PolicyArn=policy_arn)
|
||||||
except ClientError as e:
|
except (BotoCoreError, ClientError) as e:
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(),
|
module.fail_json_aws(e, msg="Couldn't attach policy to group %s" % params['GroupName'])
|
||||||
**camel_dict_to_snake_dict(e.response))
|
|
||||||
except ParamValidationError as e:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc())
|
|
||||||
|
|
||||||
# Manage group memberships
|
# Manage group memberships
|
||||||
try:
|
try:
|
||||||
current_group_members = get_group(connection, module, params['GroupName'])['Users']
|
current_group_members = get_group(connection, module, params['GroupName'])['Users']
|
||||||
except ClientError as e:
|
except (BotoCoreError, ClientError) as e:
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(),
|
module.fail_json_aws(e, "Couldn't get group %s" % params['GroupName'])
|
||||||
**camel_dict_to_snake_dict(e.response))
|
|
||||||
|
|
||||||
current_group_members_list = []
|
current_group_members_list = []
|
||||||
for member in current_group_members:
|
for member in current_group_members:
|
||||||
|
@ -307,11 +294,8 @@ def create_or_update_group(connection, module):
|
||||||
if not module.check_mode:
|
if not module.check_mode:
|
||||||
try:
|
try:
|
||||||
connection.remove_user_from_group(GroupName=params['GroupName'], UserName=user)
|
connection.remove_user_from_group(GroupName=params['GroupName'], UserName=user)
|
||||||
except ClientError as e:
|
except (BotoCoreError, ClientError) as e:
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(),
|
module.fail_json_aws(e, msg="Couldn't remove user %s from group %s" % (user, params['GroupName']))
|
||||||
**camel_dict_to_snake_dict(e.response))
|
|
||||||
except ParamValidationError as e:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc())
|
|
||||||
# If there are users to adjust that aren't in the current list, then things have changed
|
# If there are users to adjust that aren't in the current list, then things have changed
|
||||||
# Otherwise the only changes were in purging above
|
# Otherwise the only changes were in purging above
|
||||||
if set(users) - set(current_group_members_list):
|
if set(users) - set(current_group_members_list):
|
||||||
|
@ -321,20 +305,16 @@ def create_or_update_group(connection, module):
|
||||||
for user in users:
|
for user in users:
|
||||||
try:
|
try:
|
||||||
connection.add_user_to_group(GroupName=params['GroupName'], UserName=user)
|
connection.add_user_to_group(GroupName=params['GroupName'], UserName=user)
|
||||||
except ClientError as e:
|
except (BotoCoreError, ClientError) as e:
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(),
|
module.fail_json_aws(e, msg="Couldn't add user %s to group %s" % (user, params['GroupName']))
|
||||||
**camel_dict_to_snake_dict(e.response))
|
|
||||||
except ParamValidationError as e:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc())
|
|
||||||
if module.check_mode:
|
if module.check_mode:
|
||||||
module.exit_json(changed=changed)
|
module.exit_json(changed=changed)
|
||||||
|
|
||||||
# Get the group again
|
# Get the group again
|
||||||
try:
|
try:
|
||||||
group = get_group(connection, module, params['GroupName'])
|
group = get_group(connection, module, params['GroupName'])
|
||||||
except ClientError as e:
|
except (BotoCoreError, ClientError) as e:
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(),
|
module.fail_json_aws(e, "Couldn't get group %s" % params['GroupName'])
|
||||||
**camel_dict_to_snake_dict(e.response))
|
|
||||||
|
|
||||||
module.exit_json(changed=changed, iam_group=camel_dict_to_snake_dict(group))
|
module.exit_json(changed=changed, iam_group=camel_dict_to_snake_dict(group))
|
||||||
|
|
||||||
|
@ -346,9 +326,8 @@ def destroy_group(connection, module):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
group = get_group(connection, module, params['GroupName'])
|
group = get_group(connection, module, params['GroupName'])
|
||||||
except ClientError as e:
|
except (BotoCoreError, ClientError) as e:
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(),
|
module.fail_json_aws(e, "Couldn't get group %s" % params['GroupName'])
|
||||||
**camel_dict_to_snake_dict(e.response))
|
|
||||||
if group:
|
if group:
|
||||||
# Check mode means we would remove this group
|
# Check mode means we would remove this group
|
||||||
if module.check_mode:
|
if module.check_mode:
|
||||||
|
@ -358,37 +337,27 @@ def destroy_group(connection, module):
|
||||||
try:
|
try:
|
||||||
for policy in get_attached_policy_list(connection, module, params['GroupName']):
|
for policy in get_attached_policy_list(connection, module, params['GroupName']):
|
||||||
connection.detach_group_policy(GroupName=params['GroupName'], PolicyArn=policy['PolicyArn'])
|
connection.detach_group_policy(GroupName=params['GroupName'], PolicyArn=policy['PolicyArn'])
|
||||||
except ClientError as e:
|
except (BotoCoreError, ClientError) as e:
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(),
|
module.fail_json_aws(e, msg="Couldn't remove policy from group %s" % params['GroupName'])
|
||||||
**camel_dict_to_snake_dict(e.response))
|
|
||||||
except ParamValidationError as e:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc())
|
|
||||||
|
|
||||||
# Remove any users in the group otherwise deletion fails
|
# Remove any users in the group otherwise deletion fails
|
||||||
current_group_members_list = []
|
current_group_members_list = []
|
||||||
try:
|
try:
|
||||||
current_group_members = get_group(connection, module, params['GroupName'])['Users']
|
current_group_members = get_group(connection, module, params['GroupName'])['Users']
|
||||||
except ClientError as e:
|
except (BotoCoreError, ClientError) as e:
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(),
|
module.fail_json_aws(e, "Couldn't get group %s" % params['GroupName'])
|
||||||
**camel_dict_to_snake_dict(e.response))
|
|
||||||
for member in current_group_members:
|
for member in current_group_members:
|
||||||
current_group_members_list.append(member['UserName'])
|
current_group_members_list.append(member['UserName'])
|
||||||
for user in current_group_members_list:
|
for user in current_group_members_list:
|
||||||
try:
|
try:
|
||||||
connection.remove_user_from_group(GroupName=params['GroupName'], UserName=user)
|
connection.remove_user_from_group(GroupName=params['GroupName'], UserName=user)
|
||||||
except ClientError as e:
|
except (BotoCoreError, ClientError) as e:
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(),
|
module.fail_json_aws(e, "Couldn't remove user %s from group %s" % (user, params['GroupName']))
|
||||||
**camel_dict_to_snake_dict(e.response))
|
|
||||||
except ParamValidationError as e:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc())
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
connection.delete_group(**params)
|
connection.delete_group(**params)
|
||||||
except ClientError as e:
|
except (BotoCoreError, ClientError) as e:
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc(),
|
module.fail_json_aws(e, "Couldn't delete group %s" % params['GroupName'])
|
||||||
**camel_dict_to_snake_dict(e.response))
|
|
||||||
except ParamValidationError as e:
|
|
||||||
module.fail_json(msg=e.message, exception=traceback.format_exc())
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
module.exit_json(changed=False)
|
module.exit_json(changed=False)
|
||||||
|
@ -423,29 +392,21 @@ def get_attached_policy_list(connection, module, name):
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
||||||
argument_spec = ec2_argument_spec()
|
argument_spec = dict(
|
||||||
argument_spec.update(
|
name=dict(required=True),
|
||||||
dict(
|
|
||||||
name=dict(required=True, type='str'),
|
|
||||||
managed_policy=dict(default=[], type='list'),
|
managed_policy=dict(default=[], type='list'),
|
||||||
users=dict(default=[], type='list'),
|
users=dict(default=[], type='list'),
|
||||||
state=dict(choices=['present', 'absent'], required=True),
|
state=dict(choices=['present', 'absent'], required=True),
|
||||||
purge_users=dict(default=False, type='bool'),
|
purge_users=dict(default=False, type='bool'),
|
||||||
purge_policy=dict(default=False, type='bool')
|
purge_policy=dict(default=False, type='bool')
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
module = AnsibleModule(
|
module = AnsibleAWSModule(
|
||||||
argument_spec=argument_spec,
|
argument_spec=argument_spec,
|
||||||
supports_check_mode=True
|
supports_check_mode=True
|
||||||
)
|
)
|
||||||
|
|
||||||
if not HAS_BOTO3:
|
connection = module.client('iam')
|
||||||
module.fail_json(msg='boto3 required for this module')
|
|
||||||
|
|
||||||
region, ec2_url, aws_connect_params = get_aws_connection_info(module, boto3=True)
|
|
||||||
|
|
||||||
connection = boto3_conn(module, conn_type='client', resource='iam', region=region, endpoint=ec2_url, **aws_connect_params)
|
|
||||||
|
|
||||||
state = module.params.get("state")
|
state = module.params.get("state")
|
||||||
|
|
||||||
|
|
2
test/integration/targets/iam_group/aliases
Normal file
2
test/integration/targets/iam_group/aliases
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
unsupported
|
||||||
|
cloud/aws
|
70
test/integration/targets/iam_group/tasks/main.yml
Normal file
70
test/integration/targets/iam_group/tasks/main.yml
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
- name: set up aws connection info
|
||||||
|
set_fact:
|
||||||
|
aws_connection_info: &aws_connection_info
|
||||||
|
aws_access_key: "{{ aws_access_key }}"
|
||||||
|
aws_secret_key: "{{ aws_secret_key }}"
|
||||||
|
security_token: "{{ security_token }}"
|
||||||
|
region: "{{ aws_region }}"
|
||||||
|
no_log: yes
|
||||||
|
|
||||||
|
- name: ensure ansible user exists
|
||||||
|
iam_user:
|
||||||
|
name: AnsibleTestUser
|
||||||
|
state: present
|
||||||
|
<<: *aws_connection_info
|
||||||
|
|
||||||
|
- name: ensure group exists
|
||||||
|
iam_group:
|
||||||
|
name: ansible_test
|
||||||
|
users:
|
||||||
|
- AnsibleTestUser
|
||||||
|
state: present
|
||||||
|
<<: *aws_connection_info
|
||||||
|
register: iam_group
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- iam_group.users
|
||||||
|
|
||||||
|
- name: add non existent user to group
|
||||||
|
iam_group:
|
||||||
|
name: ansible_test
|
||||||
|
users:
|
||||||
|
- AnsibleTestUser
|
||||||
|
- NonExistentUser
|
||||||
|
state: present
|
||||||
|
<<: *aws_connection_info
|
||||||
|
ignore_errors: yes
|
||||||
|
register: iam_group
|
||||||
|
|
||||||
|
- name: assert that adding non existent user to group fails with helpful message
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- iam_group is failed
|
||||||
|
- iam_group.msg.startswith("Couldn't add user NonExistentUser to group ansible_test")
|
||||||
|
|
||||||
|
- name: remove a user
|
||||||
|
iam_group:
|
||||||
|
name: ansible_test
|
||||||
|
purge_users: True
|
||||||
|
users: []
|
||||||
|
state: present
|
||||||
|
<<: *aws_connection_info
|
||||||
|
register: iam_group
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- iam_group.changed
|
||||||
|
- not iam_group.users
|
||||||
|
|
||||||
|
- name: remove group
|
||||||
|
iam_group:
|
||||||
|
name: ansible_test
|
||||||
|
state: absent
|
||||||
|
<<: *aws_connection_info
|
||||||
|
|
||||||
|
- name: remove ansible user
|
||||||
|
iam_user:
|
||||||
|
name: AnsibleTestUser
|
||||||
|
state: absent
|
||||||
|
<<: *aws_connection_info
|
Loading…
Reference in a new issue