Skip to content

Commit

Permalink
cloudtrail - add support for purge_tags
Browse files Browse the repository at this point in the history
  • Loading branch information
tremble committed Jun 5, 2022
1 parent 1eef1c6 commit a1932c7
Show file tree
Hide file tree
Showing 5 changed files with 313 additions and 219 deletions.
4 changes: 4 additions & 0 deletions changelogs/fragments/1219-cloudtrail-taging.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
minor_changes:
- cloudtrail - the default value for ``tags`` has been updated, to remove all tags the ``tags`` parameter must be explicitly set to the empty dict ``{}`` (https://github.com/ansible-collections/community.aws/pull/1219).
- cloudtrail - ``resource_tags`` has been added as an alias for the ``tags`` parameter (https://github.com/ansible-collections/community.aws/pull/1219).
- cloudtrail - updated to pass tags as part of the create API call rather than tagging the trail after creation (https://github.com/ansible-collections/community.aws/pull/1219).
115 changes: 54 additions & 61 deletions plugins/modules/cloudtrail.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
description:
- Creates, deletes, or updates CloudTrail configuration. Ensures logging is also enabled.
author:
- Ansible Core Team
- Ted Timmons (@tedder)
- Daniel Shepherd (@shepdelacreme)
- Ansible Core Team
- Ted Timmons (@tedder)
- Daniel Shepherd (@shepdelacreme)
options:
state:
description:
Expand Down Expand Up @@ -88,16 +88,13 @@
- The value can be an alias name prefixed by "alias/", a fully specified ARN to an alias, a fully specified ARN to a key, or a globally unique identifier.
- See U(https://docs.aws.amazon.com/awscloudtrail/latest/userguide/encrypting-cloudtrail-log-files-with-aws-kms.html).
type: str
tags:
description:
- A hash/dictionary of tags to be applied to the CloudTrail resource.
- Remove completely or specify an empty dictionary to remove all tags.
default: {}
type: dict
notes:
- The I(purge_tags) option was added in release 4.0.0
extends_documentation_fragment:
- amazon.aws.aws
- amazon.aws.ec2
- amazon.aws.aws
- amazon.aws.ec2
- amazon.aws.tags
'''

Expand Down Expand Up @@ -251,11 +248,12 @@
except ImportError:
pass # Handled by AnsibleAWSModule

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.ec2 import (camel_dict_to_snake_dict,
ansible_dict_to_boto3_tag_list,
boto3_tag_list_to_ansible_dict,
)
from ansible_collections.amazon.aws.plugins.module_utils.tagging import ansible_dict_to_boto3_tag_list
from ansible_collections.amazon.aws.plugins.module_utils.tagging import boto3_tag_list_to_ansible_dict
from ansible_collections.amazon.aws.plugins.module_utils.tagging import compare_aws_tags


def get_kms_key_aliases(module, client, keyId):
Expand Down Expand Up @@ -293,7 +291,7 @@ def create_trail(module, client, ct_params):
return resp


def tag_trail(module, client, tags, trail_arn, curr_tags=None, dry_run=False):
def tag_trail(module, client, tags, trail_arn, curr_tags=None, purge_tags=True):
"""
Creates, updates, removes tags on a CloudTrail resource
Expand All @@ -304,45 +302,35 @@ def tag_trail(module, client, tags, trail_arn, curr_tags=None, dry_run=False):
curr_tags : Dict of the current tags on resource, if any
dry_run : true/false to determine if changes will be made if needed
"""
adds = []
removes = []
updates = []
changed = False

if curr_tags is None:
# No current tags so just convert all to a tag list
adds = ansible_dict_to_boto3_tag_list(tags)
else:
curr_keys = set(curr_tags.keys())
new_keys = set(tags.keys())
add_keys = new_keys - curr_keys
remove_keys = curr_keys - new_keys
update_keys = dict()
for k in curr_keys.intersection(new_keys):
if curr_tags[k] != tags[k]:
update_keys.update({k: tags[k]})

adds = get_tag_list(add_keys, tags)
removes = get_tag_list(remove_keys, curr_tags)
updates = get_tag_list(update_keys, tags)

if removes or updates:
changed = True
if not dry_run:
try:
client.remove_tags(ResourceId=trail_arn, TagsList=removes + updates)
except (BotoCoreError, ClientError) as err:
module.fail_json_aws(err, msg="Failed to remove tags from Trail")

if updates or adds:
changed = True
if not dry_run:
try:
client.add_tags(ResourceId=trail_arn, TagsList=updates + adds)
except (BotoCoreError, ClientError) as err:
module.fail_json_aws(err, msg="Failed to add tags to Trail")
if tags is None:
return False

curr_tags = curr_tags or {}

return changed
tags_to_add, tags_to_remove = compare_aws_tags(curr_tags, tags, purge_tags=purge_tags)
if not tags_to_add and not tags_to_remove:
return False

if module.check_mode:
return True

if tags_to_remove:
remove = {k: curr_tags[k] for k in tags_to_remove}
tags_to_remove = ansible_dict_to_boto3_tag_list(remove)
try:
client.remove_tags(ResourceId=trail_arn, TagsList=tags_to_remove)
except (BotoCoreError, ClientError) as err:
module.fail_json_aws(err, msg="Failed to remove tags from Trail")

if tags_to_add:
tags_to_add = ansible_dict_to_boto3_tag_list(tags_to_add)
try:
client.add_tags(ResourceId=trail_arn, TagsList=tags_to_add)
except (BotoCoreError, ClientError) as err:
module.fail_json_aws(err, msg="Failed to add tags to Trail")

return True


def get_tag_list(keys, tags):
Expand Down Expand Up @@ -461,7 +449,8 @@ def main():
cloudwatch_logs_role_arn=dict(),
cloudwatch_logs_log_group_arn=dict(),
kms_key_id=dict(),
tags=dict(default={}, type='dict'),
tags=dict(type='dict', aliases=['resource_tags']),
purge_tags=dict(default=True, type='bool')
)

required_if = [('state', 'present', ['s3_bucket_name']), ('state', 'enabled', ['s3_bucket_name'])]
Expand All @@ -475,6 +464,7 @@ def main():
elif module.params['state'] in ('absent', 'disabled'):
state = 'absent'
tags = module.params['tags']
purge_tags = module.params['purge_tags']
enable_logging = module.params['enable_logging']
ct_params = dict(
Name=module.params['name'],
Expand Down Expand Up @@ -586,13 +576,16 @@ def main():
set_logging(module, client, name=ct_params['Name'], action='stop')

# Check if we need to update tags on resource
tag_dry_run = False
if module.check_mode:
tag_dry_run = True
tags_changed = tag_trail(module, client, tags=tags, trail_arn=trail['TrailARN'], curr_tags=trail['tags'], dry_run=tag_dry_run)
tags_changed = tag_trail(module, client, tags=tags, trail_arn=trail['TrailARN'], curr_tags=trail['tags'],
purge_tags=purge_tags)
if tags_changed:
updated_tags = dict()
if not purge_tags:
updated_tags = trail['tags']
updated_tags.update(tags)
results['changed'] = True
trail['tags'] = tags
trail['tags'] = updated_tags

# Populate trail facts in output
results['trail'] = camel_dict_to_snake_dict(trail, ignore_list=['tags'])

Expand All @@ -601,10 +594,10 @@ def main():
results['changed'] = True
results['exists'] = True
if not module.check_mode:
if tags:
ct_params['TagList'] = ansible_dict_to_boto3_tag_list(tags)
# If we aren't in check_mode then actually create it
created_trail = create_trail(module, client, ct_params)
# Apply tags
tag_trail(module, client, tags=tags, trail_arn=created_trail['TrailARN'])
# Get the trail status
try:
status_resp = client.get_trail_status(Name=created_trail['Name'])
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/targets/cloudtrail/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ cloudtrail_name: '{{ resource_prefix }}-cloudtrail'
s3_bucket_name: '{{ resource_prefix }}-cloudtrail-bucket'
kms_alias: '{{ resource_prefix }}-cloudtrail'
sns_topic: '{{ resource_prefix }}-cloudtrail-notifications'
cloudtrail_prefix: 'test-prefix'
cloudtrail_prefix: 'ansible-test-prefix'
cloudwatch_log_group: '{{ resource_prefix }}-cloudtrail'
cloudwatch_role: '{{ resource_prefix }}-cloudtrail'
cloudwatch_no_kms_role: '{{ resource_prefix }}-cloudtrail2'
159 changes: 2 additions & 157 deletions tests/integration/targets/cloudtrail/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
security_token: '{{ security_token | default(omit) }}'
region: '{{ aws_region }}'
# Add this as a default because we (almost) always need it
cloudtrail:
community.aws.cloudtrail:
s3_bucket_name: '{{ s3_bucket_name }}'
collections:
- amazon.aws
Expand Down Expand Up @@ -361,162 +361,7 @@

# ============================================================

- name: 'Add Tag (CHECK MODE)'
cloudtrail:
state: present
name: '{{ cloudtrail_name }}'
tags:
tag1: Value1
register: output
check_mode: yes
- assert:
that:
- output is changed

- name: 'Add Tag'
cloudtrail:
state: present
name: '{{ cloudtrail_name }}'
tags:
tag1: Value1
register: output
- assert:
that:
- output is changed
- output.trail.name == cloudtrail_name
- output.trail.tags | length == 1
- '("tag1" in output.trail.tags) and (output.trail.tags["tag1"] == "Value1")'

- name: 'Add Tag (no change)'
cloudtrail:
state: present
name: '{{ cloudtrail_name }}'
tags:
tag1: Value1
register: output
- assert:
that:
- output is not changed
- output.trail.name == cloudtrail_name
- output.trail.tags | length == 1
- '("tag1" in output.trail.tags) and (output.trail.tags["tag1"] == "Value1")'

- name: 'Change tags (CHECK MODE)'
cloudtrail:
state: present
name: '{{ cloudtrail_name }}'
tags:
tag2: Value2
register: output
check_mode: yes
- assert:
that:
- output is changed

- name: 'Change tags'
cloudtrail:
state: present
name: '{{ cloudtrail_name }}'
tags:
tag2: Value2
register: output
- assert:
that:
- output is changed
- output.trail.name == cloudtrail_name
- output.trail.tags | length == 1
- '("tag2" in output.trail.tags) and (output.trail.tags["tag2"] == "Value2")'

- name: 'Change tags (no change)'
cloudtrail:
state: present
name: '{{ cloudtrail_name }}'
tags:
tag2: Value2
register: output
- assert:
that:
- output is not changed
- output.trail.name == cloudtrail_name
- output.trail.tags | length == 1
- '("tag2" in output.trail.tags) and (output.trail.tags["tag2"] == "Value2")'

- name: 'Change tags (CHECK MODE)'
cloudtrail:
state: present
name: '{{ cloudtrail_name }}'
tags:
tag2: Value2
Tag3: Value3
register: output
check_mode: yes
- assert:
that:
- output is changed

- name: 'Change tags'
cloudtrail:
state: present
name: '{{ cloudtrail_name }}'
tags:
tag2: Value2
Tag3: Value3
register: output
- assert:
that:
- output is changed
- output.trail.name == cloudtrail_name
- output.trail.tags | length == 2
- '("tag2" in output.trail.tags) and (output.trail.tags["tag2"] == "Value2")'
- '("Tag3" in output.trail.tags) and (output.trail.tags["Tag3"] == "Value3")'

- name: 'Change tags (no change)'
cloudtrail:
state: present
name: '{{ cloudtrail_name }}'
tags:
tag2: Value2
Tag3: Value3
register: output
- assert:
that:
- output is not changed
- output.trail.name == cloudtrail_name
- output.trail.tags | length == 2
- '("tag2" in output.trail.tags) and (output.trail.tags["tag2"] == "Value2")'
- '("Tag3" in output.trail.tags) and (output.trail.tags["Tag3"] == "Value3")'

- name: 'Remove tags (CHECK MODE)'
cloudtrail:
state: present
name: '{{ cloudtrail_name }}'
register: output
check_mode: yes
- assert:
that:
- output is changed

- name: 'Remove tags'
cloudtrail:
state: present
name: '{{ cloudtrail_name }}'
register: output
- assert:
that:
- output is changed
- output.trail.name == cloudtrail_name
- output.trail.tags | length == 0

- name: 'Remove tags (no change)'
cloudtrail:
state: present
name: '{{ cloudtrail_name }}'
register: output
- assert:
that:
- output is not changed
- output.trail.name == cloudtrail_name
- output.trail.tags | length == 0
- include_tasks: 'tagging.yml'

# ============================================================

Expand Down
Loading

0 comments on commit a1932c7

Please sign in to comment.