Skip to content

Commit

Permalink
iam_user - stabilize for migration to amazon.aws (#1059)
Browse files Browse the repository at this point in the history
iam_user - stabilize for migration to amazon.aws

SUMMARY
Stabilize for migration to amazon.aws

dont delete user login profile on check mode
add extra return value user to deprecate iam_user
gracefully handle iam_user_info when no users returned

ISSUE TYPE

Feature Pull Request

COMPONENT NAME
iam_user

Reviewed-by: Mark Chappell <None>
Reviewed-by: Joseph Torcasso <None>
Reviewed-by: Jill R <None>
Reviewed-by: Markus Bergholz <[email protected]>
Reviewed-by: Alina Buzachis <None>
Reviewed-by: Mark Woolley <[email protected]>
Reviewed-by: Mike Graves <[email protected]>
(cherry picked from commit a11e66a)
  • Loading branch information
jatorcasso authored and patchback[bot] committed May 5, 2022
1 parent 44596b8 commit cdc7507
Show file tree
Hide file tree
Showing 5 changed files with 287 additions and 32 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
minor_changes:
- iam_user - add `user` value to return data structure to deprecate old `iam_user` (https://github.com/ansible-collections/community.aws/pull/1059).
bugfixes:
- iam_user - don't delete user login profile on check mode (https://github.com/ansible-collections/community.aws/pull/1059).
- iam_user_info - gracefully handle when no users are found (https://github.com/ansible-collections/community.aws/pull/1059).
64 changes: 49 additions & 15 deletions plugins/modules/iam_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,20 +161,20 @@
user_id:
description: the stable and unique string identifying the user
type: str
sample: AGPAIDBWE12NSFINE55TM
sample: "AGPAIDBWE12NSFINE55TM"
user_name:
description: the friendly name that identifies the user
type: str
sample: testuser1
sample: "testuser1"
path:
description: the path to the user
type: str
sample: /
sample: "/"
tags:
description: user tags
type: dict
returned: always
sample: '{"Env": "Prod"}'
sample: {"Env": "Prod"}
'''

try:
Expand Down Expand Up @@ -228,10 +228,6 @@ def convert_friendly_names_to_arns(connection, module, policy_names):


def wait_iam_exists(connection, module):
if module.check_mode:
return
if not module.params.get('wait'):
return

user_name = module.params.get('name')
wait_timeout = module.params.get('wait_timeout')
Expand Down Expand Up @@ -263,6 +259,7 @@ def create_or_update_login_profile(connection, module):
try:
retval = connection.update_login_profile(**user_params)
except is_boto3_error_code('NoSuchEntity'):
# Login profile does not yet exist - create it
try:
retval = connection.create_login_profile(**user_params)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
Expand All @@ -274,14 +271,26 @@ def create_or_update_login_profile(connection, module):


def delete_login_profile(connection, module):

'''
Deletes a users login profile.
Parameters:
connection: IAM client
module: AWSModule
Returns:
(bool): True if login profile deleted, False if no login profile found to delete
'''
user_params = dict()
user_params['UserName'] = module.params.get('name')

try:
connection.delete_login_profile(**user_params)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Unable to delete user login profile")
# User does not have login profile - nothing to delete
if not user_has_login_profile(connection, module, user_params['UserName']):
return False

if not module.check_mode:
try:
connection.delete_login_profile(**user_params)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except
module.fail_json_aws(e, msg="Unable to delete user login profile")

return True

Expand Down Expand Up @@ -331,6 +340,9 @@ def create_or_update_user(connection, module):
update_result = update_user_tags(connection, module, params, user)

if module.params['update_password'] == "always" and module.params.get('password') is not None:
# Can't compare passwords, so just return changed on check mode runs
if module.check_mode:
module.exit_json(changed=True)
login_profile_result, login_profile_data = create_or_update_login_profile(connection, module)

if login_profile_data.get('LoginProfile', {}).get('PasswordResetRequired', False):
Expand Down Expand Up @@ -382,7 +394,7 @@ def create_or_update_user(connection, module):
# `LoginProfile` is only returned on `create_login_profile` method
user['user']['password_reset_required'] = login_profile_data.get('LoginProfile', {}).get('PasswordResetRequired', False)

module.exit_json(changed=changed, iam_user=user)
module.exit_json(changed=changed, iam_user=user, user=user['user'])


def destroy_user(connection, module):
Expand Down Expand Up @@ -412,7 +424,7 @@ def destroy_user(connection, module):
connection.delete_access_key(UserName=user_name, AccessKeyId=access_key["AccessKeyId"])

# Remove user's login profile (console password)
delete_user_login_profile(connection, module, user_name)
delete_login_profile(connection, module)

# Remove user's ssh public keys
ssh_public_keys = connection.list_ssh_public_keys(UserName=user_name)["SSHPublicKeys"]
Expand Down Expand Up @@ -495,6 +507,25 @@ def delete_user_login_profile(connection, module, user_name):
module.fail_json_aws(e, msg="Unable to delete login profile for user {0}".format(user_name))


def user_has_login_profile(connection, module, name):
'''
Returns whether or not given user has a login profile.
Parameters:
connection: IAM client
module: AWSModule
name (str): Username of user
Returns:
(bool): True if user had login profile, False if not
'''
try:
connection.get_login_profile(UserName=name)
except is_boto3_error_code('NoSuchEntity'):
return False
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except
module.fail_json_aws(e, msg="Unable to get login profile for user {0}".format(name))
return True


def update_user_tags(connection, module, params, user):
user_name = params['UserName']
existing_tags = user['user']['tags']
Expand Down Expand Up @@ -543,6 +574,9 @@ def main():
mutually_exclusive=[['password', 'remove_password']],
)

module.deprecate("The 'iam_user' return key is deprecated and will be replaced by 'user'. Both values are returned for now.",
date='2024-05-01', collection_name='community.aws')

connection = module.client('iam')

state = module.params.get("state")
Expand Down
13 changes: 10 additions & 3 deletions plugins/modules/iam_user_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict

from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule
from ansible_collections.amazon.aws.plugins.module_utils.core import is_boto3_error_code
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import boto3_tag_list_to_ansible_dict

Expand Down Expand Up @@ -142,14 +143,18 @@ def list_iam_users(connection, module):
params['UserName'] = name
try:
iam_users.append(connection.get_user(**params)['User'])
except (ClientError, BotoCoreError) as e:
except is_boto3_error_code('NoSuchEntity'):
pass
except (ClientError, BotoCoreError) as e: # pylint: disable=duplicate-except
module.fail_json_aws(e, msg="Couldn't get IAM user info for user %s" % name)

if group:
params['GroupName'] = group
try:
iam_users = list_iam_users_with_backoff(connection, 'get_group', **params)['Users']
except (ClientError, BotoCoreError) as e:
except is_boto3_error_code('NoSuchEntity'):
pass
except (ClientError, BotoCoreError) as e: # pylint: disable=duplicate-except
module.fail_json_aws(e, msg="Couldn't get IAM user info for group %s" % group)
if name:
iam_users = [user for user in iam_users if user['UserName'] == name]
Expand All @@ -158,7 +163,9 @@ def list_iam_users(connection, module):
params['PathPrefix'] = path
try:
iam_users = list_iam_users_with_backoff(connection, 'list_users', **params)['Users']
except (ClientError, BotoCoreError) as e:
except is_boto3_error_code('NoSuchEntity'):
pass
except (ClientError, BotoCoreError) as e: # pylint: disable=duplicate-except
module.fail_json_aws(e, msg="Couldn't get IAM user info for path %s" % path)
if name:
iam_users = [user for user in iam_users if user['UserName'] == name]
Expand Down
Loading

0 comments on commit cdc7507

Please sign in to comment.