diff --git a/plugins/modules/ec2_asg_tag.py b/plugins/modules/ec2_asg_tag.py new file mode 100644 index 00000000000..4a4b2cb61cd --- /dev/null +++ b/plugins/modules/ec2_asg_tag.py @@ -0,0 +1,183 @@ +#!/usr/bin/python +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +DOCUMENTATION = r''' +--- +module: ec2_asg_tag +version_added: 1.0.0 +short_description: Create and remove tags on AWS AutoScaling Groups (ASGs) +description: + - Creates, modifies and removes tags for AutoScaling Groups +author: "Jonathan Sokolowski (@jsok)" +requirements: [ "boto3", "botocore" ] +options: + name: + description: + - The ASG name. + required: true + type: str + state: + description: + - Whether the tags should be present or absent on the ASG. + default: present + choices: ['present', 'absent'] + type: str + tags: + description: + - A list of tags to add or remove from the ASG. + - If the value provided for a key is not set and I(state=absent), the tag will be removed regardless of its current value. + - Optional key is I(propagate_at_launch), which defaults to true. + type: list + elements: dict +extends_documentation_fragment: +- amazon.aws.aws +- amazon.aws.ec2 +''' + +EXAMPLES = r''' +- name: Ensure tags are present on an ASG + community.aws.ec2_asg_tag: + name: my-auto-scaling-group + state: present + tags: + - environment: production + propagate_at_launch: true + - role: webserver + propagate_at_launch: true + +- name: Ensure tag is absent on an ASG + community.aws.ec2_asg_tag: + name: my-auto-scaling-group + state: absent + tags: + - environment: development +''' + +RETURN = r''' +--- +tags: + description: A list containing the tags on the resource + returned: always + type: list + sample: [ + { + "key": "Name", + "value": "public-webapp-production-1", + "resource_id": "public-webapp-production-1", + "resource_type": "auto-scaling-group", + "propagate_at_launch": "true" + }, + { + "key": "env", + "value": "production", + "resource_id": "public-webapp-production-1", + "resource_type": "auto-scaling-group", + "propagate_at_launch": "true" + } + ] +added_tags: + description: A list of tags that were added to the ASG + returned: If tags were added + type: list +removed_tags: + description: A list of tags that were removed from the ASG + returned: If tags were removed + type: list +''' + +try: + from botocore.exceptions import BotoCoreError, ClientError +except ImportError: + pass # Handled by AnsibleAWSModule + +from ansible.module_utils._text import to_native + +from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry + +backoff_params = dict(tries=10, delay=3, backoff=1.5) + + +def to_boto3_tag_list(tags, group_name): + tag_list = [] + for tag in tags: + for k, v in tag.items(): + if k == 'propagate_at_launch': + continue + tag_list.append(dict(Key=k, + Value=to_native(v), + PropagateAtLaunch=bool(tag.get('propagate_at_launch', True)), + ResourceType='auto-scaling-group', + ResourceId=group_name)) + return tag_list + + +def get_tags(autoscaling, module, group_name): + filters = [{'Name': 'auto-scaling-group', 'Values': [group_name]}] + try: + result = AWSRetry.jittered_backoff()(autoscaling.describe_tags)(Filters=filters) + return result['Tags'] + except (BotoCoreError, ClientError) as e: + module.fail_json_aws(e, msg='Failed to fetch tags for ASG {0}'.format(group_name)) + + +def main(): + argument_spec = dict( + name=dict(required=True, type='str'), + tags=dict(type='list', default=[], elements='dict'), + state=dict(default='present', choices=['present', 'absent']), + ) + + module = AnsibleAWSModule(argument_spec=argument_spec, supports_check_mode=True) + + group_name = module.params['name'] + state = module.params['state'] + tags = module.params['tags'] + + result = {'changed': False} + + autoscaling = module.client('autoscaling') + + change_tags = to_boto3_tag_list(tags, group_name) + current_tags = get_tags(autoscaling, module, group_name) + remove_tags = [] + add_tags = [] + + for tag in change_tags: + if tag in current_tags: + if state == 'absent': + remove_tags.append(tag) + else: + if state == 'present': + add_tags.append(tag) + + if remove_tags: + result['changed'] = True + result['removed_tags'] = remove_tags + if not module.check_mode: + try: + AWSRetry.jittered_backoff()(autoscaling.delete_tags)(Tags=remove_tags) + except (BotoCoreError, ClientError) as e: + module.fail_json_aws(e, msg='Failed to remove tags {0} from ASG {1}'.format(remove_tags, group_name)) + + if add_tags: + result['changed'] = True + result['added_tags'] = add_tags + if not module.check_mode: + try: + AWSRetry.jittered_backoff()(autoscaling.create_or_update_tags)(Tags=add_tags) + except (BotoCoreError, ClientError) as e: + module.fail_json_aws(e, msg='Failed to remove tags {0} from ASG {1}'.format(remove_tags, group_name)) + + result['tags'] = get_tags(autoscaling, module, group_name) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/tests/integration/targets/ec2_asg/tasks/main.yml b/tests/integration/targets/ec2_asg/tasks/main.yml index aa53e9688ea..912a7bc9a44 100644 --- a/tests/integration/targets/ec2_asg/tasks/main.yml +++ b/tests/integration/targets/ec2_asg/tasks/main.yml @@ -247,6 +247,60 @@ - "output.tags | length == 1" - output is changed + - name: Add a tag to the asg + ec2_asg_tag: + name: "{{ resource_prefix }}-asg" + state: present + tags: + - tag_d: 'value 4' + propagate_at_launch: yes + register: output + + - assert: + that: + - "output.added_tags | length == 1" + - output is changed + + - name: Add an existing tag to the asg + ec2_asg_tag: + name: "{{ resource_prefix }}-asg" + state: present + tags: + - tag_d: 'value 4' + propagate_at_launch: yes + register: output + + - assert: + that: + - output is not changed + + - name: Remove a tag from the asg + ec2_asg_tag: + name: "{{ resource_prefix }}-asg" + state: absent + tags: + - tag_d: 'value 4' + propagate_at_launch: yes + register: output + + - assert: + that: + - "output.removed_tags | length == 1" + - output is changed + + - name: Remove a non-existent tag from the asg + ec2_asg_tag: + name: "{{ resource_prefix }}-asg" + state: absent + tags: + - tag_d: 'value 4' + propagate_at_launch: yes + register: output + + - assert: + that: + - output is not changed + - name: Enable metrics collection ec2_asg: name: "{{ resource_prefix }}-asg"