From 2f972baa976c45bf8ed95001723408fd293f3f00 Mon Sep 17 00:00:00 2001 From: Mandar Kulkarni Date: Fri, 25 Feb 2022 08:49:13 -0800 Subject: [PATCH 01/11] WIP: describe asg tags --- plugins/modules/ec2_asg.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/plugins/modules/ec2_asg.py b/plugins/modules/ec2_asg.py index 8dc7cd783f2..b499e5924e5 100644 --- a/plugins/modules/ec2_asg.py +++ b/plugins/modules/ec2_asg.py @@ -645,6 +645,7 @@ from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry from ansible_collections.amazon.aws.plugins.module_utils.ec2 import snake_dict_to_camel_dict from ansible_collections.amazon.aws.plugins.module_utils.ec2 import camel_dict_to_snake_dict +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import ansible_dict_to_boto3_filter_list ASG_ATTRIBUTES = ('AvailabilityZones', 'DefaultCooldown', 'DesiredCapacity', 'HealthCheckGracePeriod', 'HealthCheckType', 'LaunchConfigurationName', @@ -713,6 +714,23 @@ def describe_launch_templates(connection, launch_template): module.fail_json(msg="No launch template found matching: %s" % launch_template) +@AWSRetry.jittered_backoff(**backoff_params) +def describe_autoscaling_tags(connection, asg_name): + pg = connection.get_paginator('describe_tags') + filters = ansible_dict_to_boto3_filter_list({'auto-scaling-group': asg_name}) + + try: + response = pg.paginate(Filters=filters).build_full_result().get('Tags', []) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, msg="Failed to describe AutoScalingGroup tags.") + + asg_tags_dict = {} + for item in response: + asg_tags_dict[item['Key']] = item['Value'] + + return False, asg_tags_dict + + @AWSRetry.jittered_backoff(**backoff_params) def create_asg(connection, **params): connection.create_auto_scaling_group(**params) @@ -1790,6 +1808,7 @@ def main(): load_balancers=dict(type='list', elements='str'), target_group_arns=dict(type='list', elements='str'), availability_zones=dict(type='list', elements='str'), + describe_tags=dict(type='bool', required=False), launch_config_name=dict(type='str'), launch_template=dict( type='dict', @@ -1892,6 +1911,10 @@ def main(): changed = create_changed = replace_changed = detach_changed = False exists = asg_exists(connection) + if exists and module.params.get('describe_tags'): + changed, asg_tags_list = describe_autoscaling_tags(connection, module.params.get('name')) + module.exit_json(changed=changed, ASG_TAGS=asg_tags_list) + if state == 'present': create_changed, asg_properties = create_autoscaling_group(connection) elif state == 'absent': From d7181359ac0c38f9622ef83c8762fc70969d215c Mon Sep 17 00:00:00 2001 From: Mandar Kulkarni Date: Fri, 25 Feb 2022 22:11:18 -0800 Subject: [PATCH 02/11] WIP: handle basic add, delete, list tags cases --- plugins/modules/ec2_asg.py | 68 ++++++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 6 deletions(-) diff --git a/plugins/modules/ec2_asg.py b/plugins/modules/ec2_asg.py index b499e5924e5..823db631bf4 100644 --- a/plugins/modules/ec2_asg.py +++ b/plugins/modules/ec2_asg.py @@ -722,13 +722,31 @@ def describe_autoscaling_tags(connection, asg_name): try: response = pg.paginate(Filters=filters).build_full_result().get('Tags', []) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Failed to describe AutoScalingGroup tags.") + module.fail_json_aws(e, msg="Failed to describe AutoScalingGroup tags.") asg_tags_dict = {} for item in response: asg_tags_dict[item['Key']] = item['Value'] - return False, asg_tags_dict + return [asg_tags_dict] + + +@AWSRetry.jittered_backoff(**backoff_params) +def update_autoscaling_tags(connection, new_tags): + try: + response = connection.create_or_update_tags(Tags=new_tags) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, msg="Failed to describe AutoScalingGroup tags.") + + return True + +@AWSRetry.jittered_backoff(**backoff_params) +def delete_autoscaling_tags(connection, new_tags): + try: + response = connection.delete_tags(Tags=new_tags) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, msg="Failed to describe AutoScalingGroup tags.") + return True, new_tags @AWSRetry.jittered_backoff(**backoff_params) @@ -1802,13 +1820,26 @@ def asg_exists(connection): return bool(len(as_group)) +def to_boto3_asg_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) if v else v, + PropagateAtLaunch=bool(tag.get('propagate_at_launch', True)), + ResourceType='auto-scaling-group', + ResourceId=group_name)) + return tag_list + + def main(): argument_spec = dict( name=dict(required=True, type='str'), load_balancers=dict(type='list', elements='str'), target_group_arns=dict(type='list', elements='str'), availability_zones=dict(type='list', elements='str'), - describe_tags=dict(type='bool', required=False), launch_config_name=dict(type='str'), launch_template=dict( type='dict', @@ -1857,6 +1888,8 @@ def main(): wait_timeout=dict(type='int', default=300), state=dict(default='present', choices=['present', 'absent']), tags=dict(type='list', default=[], elements='dict'), + tags_operation=dict(type='str', choices=['list', 'add', 'remove', 'purge']), #only do tag operations if this set + purge_tags=dict(type='bool', default=False), #replace existing tags or not health_check_period=dict(type='int', default=300), health_check_type=dict(default='EC2', choices=['EC2', 'ELB']), default_cooldown=dict(type='int', default=300), @@ -1911,9 +1944,32 @@ def main(): changed = create_changed = replace_changed = detach_changed = False exists = asg_exists(connection) - if exists and module.params.get('describe_tags'): - changed, asg_tags_list = describe_autoscaling_tags(connection, module.params.get('name')) - module.exit_json(changed=changed, ASG_TAGS=asg_tags_list) + tags_operation = module.params.get('tags_operation') + + if tags_operation: + group_name = module.params.get('name') + tags = module.params.get('tags') + purge_tags = module.params.get('purge_tags') + existing_tags_list = describe_autoscaling_tags(connection, group_name) + # convert to a dict keyed by the tag Key to simplify existence checks + existing_tags_list = to_boto3_asg_tag_list(existing_tags_list, group_name) + new_tags = to_boto3_asg_tag_list(tags, group_name) + + if tags_operation == 'list': + module.exit_json(changed=changed, ASG_TAGS=existing_tags_list) + + if tags_operation == 'add': + changed = update_autoscaling_tags(connection, new_tags) + updated_tags = describe_autoscaling_tags(connection, group_name) + module.exit_json(changed=changed, updated_tags=updated_tags) + + if tags_operation == 'remove': + tags_to_remove = [] + for tag in existing_tags_list: + if tag not in new_tags: + tags_to_remove.append(tag) + changed, removed_tags = delete_autoscaling_tags(connection, new_tags) + module.exit_json(changed=changed, removed_tags=removed_tags) if state == 'present': create_changed, asg_properties = create_autoscaling_group(connection) From 4de12e4e39a528ea1512095b766b34ed083ff2e8 Mon Sep 17 00:00:00 2001 From: Mandar Kulkarni Date: Mon, 28 Feb 2022 12:23:52 -0800 Subject: [PATCH 03/11] Fix redundant code for add/remove tags --- plugins/modules/ec2_asg.py | 83 +++++++++----------------------------- 1 file changed, 20 insertions(+), 63 deletions(-) diff --git a/plugins/modules/ec2_asg.py b/plugins/modules/ec2_asg.py index 823db631bf4..39b6e40f5bf 100644 --- a/plugins/modules/ec2_asg.py +++ b/plugins/modules/ec2_asg.py @@ -715,7 +715,8 @@ def describe_launch_templates(connection, launch_template): @AWSRetry.jittered_backoff(**backoff_params) -def describe_autoscaling_tags(connection, asg_name): +def describe_autoscaling_tags(connection): + asg_name = module.params.get('name') pg = connection.get_paginator('describe_tags') filters = ansible_dict_to_boto3_filter_list({'auto-scaling-group': asg_name}) @@ -731,24 +732,6 @@ def describe_autoscaling_tags(connection, asg_name): return [asg_tags_dict] -@AWSRetry.jittered_backoff(**backoff_params) -def update_autoscaling_tags(connection, new_tags): - try: - response = connection.create_or_update_tags(Tags=new_tags) - except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Failed to describe AutoScalingGroup tags.") - - return True - -@AWSRetry.jittered_backoff(**backoff_params) -def delete_autoscaling_tags(connection, new_tags): - try: - response = connection.delete_tags(Tags=new_tags) - except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Failed to describe AutoScalingGroup tags.") - return True, new_tags - - @AWSRetry.jittered_backoff(**backoff_params) def create_asg(connection, **params): connection.create_auto_scaling_group(**params) @@ -1133,6 +1116,8 @@ def create_autoscaling_group(connection): desired_capacity = module.params.get('desired_capacity') vpc_zone_identifier = module.params.get('vpc_zone_identifier') set_tags = module.params.get('tags') + purge_tags = module.params.get('purge_tags') + remove_tags = module.params.get('remove_tags') health_check_period = module.params.get('health_check_period') health_check_type = module.params.get('health_check_type') default_cooldown = module.params.get('default_cooldown') @@ -1254,9 +1239,16 @@ def create_autoscaling_group(connection): for dead_tag in set(have_tag_keyvals).difference(want_tag_keyvals): changed = True - dead_tags.append(dict(ResourceId=as_group['AutoScalingGroupName'], - ResourceType='auto-scaling-group', Key=dead_tag)) + if purge_tags: + dead_tags.append(dict(ResourceId=as_group['AutoScalingGroupName'], + ResourceType='auto-scaling-group', Key=dead_tag)) have_tags = [have_tag for have_tag in have_tags if have_tag['Key'] != dead_tag] + + if remove_tags: + dead_tags = [] + for dead_tag in want_tag_keyvals: + dead_tags.append(dict(ResourceId=as_group['AutoScalingGroupName'], + ResourceType='auto-scaling-group', Key=dead_tag)) if dead_tags: connection.delete_tags(Tags=dead_tags) @@ -1820,20 +1812,6 @@ def asg_exists(connection): return bool(len(as_group)) -def to_boto3_asg_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) if v else v, - PropagateAtLaunch=bool(tag.get('propagate_at_launch', True)), - ResourceType='auto-scaling-group', - ResourceId=group_name)) - return tag_list - - def main(): argument_spec = dict( name=dict(required=True, type='str'), @@ -1888,8 +1866,9 @@ def main(): wait_timeout=dict(type='int', default=300), state=dict(default='present', choices=['present', 'absent']), tags=dict(type='list', default=[], elements='dict'), - tags_operation=dict(type='str', choices=['list', 'add', 'remove', 'purge']), #only do tag operations if this set - purge_tags=dict(type='bool', default=False), #replace existing tags or not + list_tags=dict(type='bool', default=False), + purge_tags=dict(type='bool', default=True), + remove_tags=dict(type='bool', default=False), health_check_period=dict(type='int', default=300), health_check_type=dict(default='EC2', choices=['EC2', 'ELB']), default_cooldown=dict(type='int', default=300), @@ -1932,6 +1911,7 @@ def main(): ['replace_all_instances', 'replace_instances'], ['replace_all_instances', 'detach_instances'], ['launch_config_name', 'launch_template'], + ['purge_tags', 'remove_tags'] ] ) @@ -1944,32 +1924,9 @@ def main(): changed = create_changed = replace_changed = detach_changed = False exists = asg_exists(connection) - tags_operation = module.params.get('tags_operation') - - if tags_operation: - group_name = module.params.get('name') - tags = module.params.get('tags') - purge_tags = module.params.get('purge_tags') - existing_tags_list = describe_autoscaling_tags(connection, group_name) - # convert to a dict keyed by the tag Key to simplify existence checks - existing_tags_list = to_boto3_asg_tag_list(existing_tags_list, group_name) - new_tags = to_boto3_asg_tag_list(tags, group_name) - - if tags_operation == 'list': - module.exit_json(changed=changed, ASG_TAGS=existing_tags_list) - - if tags_operation == 'add': - changed = update_autoscaling_tags(connection, new_tags) - updated_tags = describe_autoscaling_tags(connection, group_name) - module.exit_json(changed=changed, updated_tags=updated_tags) - - if tags_operation == 'remove': - tags_to_remove = [] - for tag in existing_tags_list: - if tag not in new_tags: - tags_to_remove.append(tag) - changed, removed_tags = delete_autoscaling_tags(connection, new_tags) - module.exit_json(changed=changed, removed_tags=removed_tags) + if module.params.get('list_tags'): + existing_tags_list = describe_autoscaling_tags(connection) + module.exit_json(changed=False, AutoScalingGroup_Tags=existing_tags_list) if state == 'present': create_changed, asg_properties = create_autoscaling_group(connection) From 2da909605ed06e646a51cd174deda73d9b3e22cd Mon Sep 17 00:00:00 2001 From: Mandar Kulkarni Date: Tue, 1 Mar 2022 13:55:12 -0800 Subject: [PATCH 04/11] Add integration tests to test tag operations --- plugins/modules/ec2_asg.py | 2 +- .../targets/ec2_asg/tasks/instance_detach.yml | 4 +- .../targets/ec2_asg/tasks/main.yml | 2 + .../targets/ec2_asg/tasks/tag_operations.yml | 182 ++++++++++++++++++ 4 files changed, 187 insertions(+), 3 deletions(-) create mode 100644 tests/integration/targets/ec2_asg/tasks/tag_operations.yml diff --git a/plugins/modules/ec2_asg.py b/plugins/modules/ec2_asg.py index 39b6e40f5bf..5aca91eda3d 100644 --- a/plugins/modules/ec2_asg.py +++ b/plugins/modules/ec2_asg.py @@ -729,7 +729,7 @@ def describe_autoscaling_tags(connection): for item in response: asg_tags_dict[item['Key']] = item['Value'] - return [asg_tags_dict] + return asg_tags_dict @AWSRetry.jittered_backoff(**backoff_params) diff --git a/tests/integration/targets/ec2_asg/tasks/instance_detach.yml b/tests/integration/targets/ec2_asg/tasks/instance_detach.yml index da574c2ebf2..fbd29d39640 100644 --- a/tests/integration/targets/ec2_asg/tasks/instance_detach.yml +++ b/tests/integration/targets/ec2_asg/tasks/instance_detach.yml @@ -83,7 +83,7 @@ - '{{ init_instance_2 }}' # pause to allow completion of instance replacement - - name: Pause for 1 minute + - name: Pause for 30 seconds pause: seconds: 30 @@ -137,7 +137,7 @@ - '{{ instance_replace_1 }}' - '{{ instance_replace_2 }}' - - name: Pause for 1 minute to allow completion of above task + - name: Pause for 30 seconds to allow completion of above task pause: seconds: 30 diff --git a/tests/integration/targets/ec2_asg/tasks/main.yml b/tests/integration/targets/ec2_asg/tasks/main.yml index 68d2f9175a2..85b69bf10a3 100644 --- a/tests/integration/targets/ec2_asg/tasks/main.yml +++ b/tests/integration/targets/ec2_asg/tasks/main.yml @@ -97,6 +97,8 @@ cidr_ip: 0.0.0.0/0 register: sg + - include_tasks: tag_operations.yml + - include_tasks: instance_detach.yml - name: ensure launch configs exist diff --git a/tests/integration/targets/ec2_asg/tasks/tag_operations.yml b/tests/integration/targets/ec2_asg/tasks/tag_operations.yml new file mode 100644 index 00000000000..62d4924c8d8 --- /dev/null +++ b/tests/integration/targets/ec2_asg/tasks/tag_operations.yml @@ -0,0 +1,182 @@ +- name: Running AutoScalingGroup Tag operations test + block: + #---------------------------------------------------------------------- + - name: create a launch configuration + ec2_lc: + name: "{{ resource_prefix }}-lc-tag-test" + image_id: "{{ ec2_ami_image }}" + region: "{{ aws_region }}" + instance_type: t2.micro + assign_public_ip: yes + register: create_lc + + - name: ensure that lc is created + assert: + that: + - create_lc is changed + - create_lc.failed is false + - '"autoscaling:CreateLaunchConfiguration" in create_lc.resource_actions' + + #---------------------------------------------------------------------- + - name: create a AutoScalingGroup to be used for tag_operations test + ec2_asg: + name: "{{ resource_prefix }}-asg-tag-test" + launch_config_name: "{{ resource_prefix }}-lc-tag-test" + health_check_period: 60 + health_check_type: ELB + replace_all_instances: yes + min_size: 1 + max_size: 1 + desired_capacity: 1 + region: "{{ aws_region }}" + register: create_asg + + - name: ensure that AutoScalingGroup is created + assert: + that: + - create_asg is changed + - create_asg.failed is false + - '"autoscaling:CreateAutoScalingGroup" in create_asg.resource_actions' + + #---------------------------------------------------------------------- + + - name: get asg tags existing + ec2_asg: + name: "{{ resource_prefix }}-asg-tag-test" + list_tags: true + register: info_result + + - assert: + that: + - info_result.AutoScalingGroup_Tags | length == 0 + - '"autoscaling:DescribeTags" in info_result.resource_actions' + + - name: Add 2 new tags + ec2_asg: + name: "{{ resource_prefix }}-asg-tag-test" + tags: + - key1: 'val1' + propagate_at_launch: no + - key2: 'val2' + propagate_at_launch: yes + purge_tags: false + register: add_result + + - name: get asg tags existing + ec2_asg: + name: "{{ resource_prefix }}-asg-tag-test" + list_tags: true + register: info_result + + - assert: + that: + - add_result is changed + - info_result.AutoScalingGroup_Tags | length == 2 + - '"autoscaling:CreateOrUpdateTags" in add_result.resource_actions' + - '"autoscaling:DescribeTags" in info_result.resource_actions' + + - name: Add 2 new tags - purge existing tags + ec2_asg: + name: "{{ resource_prefix }}-asg-tag-test" + tags: + - key3: 'val3' + propagate_at_launch: no + - key4: 'val4' + propagate_at_launch: yes + purge_tags: true + register: add_purge_result + + - name: get updated asg tags + ec2_asg: + name: "{{ resource_prefix }}-asg-tag-test" + list_tags: true + register: info_result + + - assert: + that: + - add_purge_result is changed + - info_result.AutoScalingGroup_Tags | length == 2 + - '"key1" not in info_result.AutoScalingGroup_Tags' + - '"key2" not in info_result.AutoScalingGroup_Tags' + - '"key3" in info_result.AutoScalingGroup_Tags' + - '"key4" in info_result.AutoScalingGroup_Tags' + - '"autoscaling:CreateOrUpdateTags" in add_purge_result.resource_actions' + - '"autoscaling:DescribeTags" in info_result.resource_actions' + + - name: Add 2 new tags + ec2_asg: + name: "{{ resource_prefix }}-asg-tag-test" + tags: + - key1: 'val1' + propagate_at_launch: no + - key2: 'val2' + propagate_at_launch: yes + purge_tags: false + register: add_result + + - name: get asg tags existing + ec2_asg: + name: "{{ resource_prefix }}-asg-tag-test" + list_tags: true + register: info_result + + - assert: + that: + - add_result is changed + - info_result.AutoScalingGroup_Tags | length == 4 + - '"key1" in info_result.AutoScalingGroup_Tags' + - '"key2" in info_result.AutoScalingGroup_Tags' + - '"key3" in info_result.AutoScalingGroup_Tags' + - '"key4" in info_result.AutoScalingGroup_Tags' + - '"autoscaling:CreateOrUpdateTags" in add_result.resource_actions' + - '"autoscaling:DescribeTags" in info_result.resource_actions' + + + - name: Remove 2 tags - key2, key4 + ec2_asg: + name: "{{ resource_prefix }}-asg-tag-test" + tags: + - key2: 'val2' + - key4: 'val4' + remove_tags: true + register: remove_result + + - name: get asg tags existing + ec2_asg: + name: "{{ resource_prefix }}-asg-tag-test" + list_tags: true + register: info_result + + - assert: + that: + - remove_result is changed + - info_result.AutoScalingGroup_Tags | length == 2 + - '"key1" in info_result.AutoScalingGroup_Tags' + - '"key2" not in info_result.AutoScalingGroup_Tags' + - '"key3" in info_result.AutoScalingGroup_Tags' + - '"key4" not in info_result.AutoScalingGroup_Tags' + - '"autoscaling:DeleteTags" in remove_result.resource_actions' + - '"autoscaling:DescribeTags" in info_result.resource_actions' + + + #---------------------------------------------------------------------- + + always: + + - name: kill asg created in this test + ec2_asg: + name: "{{ resource_prefix }}-asg-tag-test" + state: absent + register: removed + until: removed is not failed + ignore_errors: yes + retries: 10 + + - name: remove launch config created in this test + ec2_lc: + name: "{{ resource_prefix }}-lc-tag-test" + state: absent + register: removed + until: removed is not failed + ignore_errors: yes + retries: 10 From 3cbda286542c565f499b3ed02d5adb2d6beb9839 Mon Sep 17 00:00:00 2001 From: Mandar Kulkarni Date: Tue, 1 Mar 2022 19:55:50 -0800 Subject: [PATCH 05/11] Sanity fixes, changelogs fragment --- .../fragments/960-ec2_asg-manage-tags.yml | 2 ++ plugins/modules/ec2_asg.py | 35 ++++++++++++++----- 2 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 changelogs/fragments/960-ec2_asg-manage-tags.yml diff --git a/changelogs/fragments/960-ec2_asg-manage-tags.yml b/changelogs/fragments/960-ec2_asg-manage-tags.yml new file mode 100644 index 00000000000..cae4bd8950b --- /dev/null +++ b/changelogs/fragments/960-ec2_asg-manage-tags.yml @@ -0,0 +1,2 @@ +minor_changes: +- ec2_asg - add support for managing tags on AutoScalingGroups (https://github.com/ansible-collections/community.aws/pull/960). diff --git a/plugins/modules/ec2_asg.py b/plugins/modules/ec2_asg.py index 5aca91eda3d..7a22803b88b 100644 --- a/plugins/modules/ec2_asg.py +++ b/plugins/modules/ec2_asg.py @@ -220,6 +220,25 @@ - When I(propagate_at_launch) is true the tags will be propagated to the Instances created. type: list elements: dict + list_tags: + description: + - If C(true), returns a list of key-value pairs of existing tags on a AutoScalingGroup. + type: bool + required: false + version_added: 3.2.0 + purge_tags: + description: + - If C(true), existing tags will be purged from the resource to match exactly what is defined by I(tags) parameter. + - If the I(tags) parameter is not set then tags will not be modified. + default: true + type: bool + version_added: 3.2.0 + remove_tags: + description: + - If C(true), the tags specified by I(tags) parameter will be removed/deleted from AutoScalingGroup. + - If the I(tags) parameter is not set then tags will not be modified. + type: bool + version_added: 3.2.0 health_check_period: description: - Length of time in seconds after a new EC2 instance comes into service that Auto Scaling starts checking its health. @@ -1240,15 +1259,15 @@ def create_autoscaling_group(connection): for dead_tag in set(have_tag_keyvals).difference(want_tag_keyvals): changed = True if purge_tags: - dead_tags.append(dict(ResourceId=as_group['AutoScalingGroupName'], - ResourceType='auto-scaling-group', Key=dead_tag)) + dead_tags.append(dict( + ResourceId=as_group['AutoScalingGroupName'], ResourceType='auto-scaling-group', Key=dead_tag)) have_tags = [have_tag for have_tag in have_tags if have_tag['Key'] != dead_tag] if remove_tags: dead_tags = [] for dead_tag in want_tag_keyvals: - dead_tags.append(dict(ResourceId=as_group['AutoScalingGroupName'], - ResourceType='auto-scaling-group', Key=dead_tag)) + dead_tags.append(dict( + ResourceId=as_group['AutoScalingGroupName'], ResourceType='auto-scaling-group', Key=dead_tag)) if dead_tags: connection.delete_tags(Tags=dead_tags) @@ -1866,9 +1885,9 @@ def main(): wait_timeout=dict(type='int', default=300), state=dict(default='present', choices=['present', 'absent']), tags=dict(type='list', default=[], elements='dict'), - list_tags=dict(type='bool', default=False), + list_tags=dict(type='bool', required=False), purge_tags=dict(type='bool', default=True), - remove_tags=dict(type='bool', default=False), + remove_tags=dict(type='bool', required=False), health_check_period=dict(type='int', default=300), health_check_type=dict(default='EC2', choices=['EC2', 'ELB']), default_cooldown=dict(type='int', default=300), @@ -1925,8 +1944,8 @@ def main(): exists = asg_exists(connection) if module.params.get('list_tags'): - existing_tags_list = describe_autoscaling_tags(connection) - module.exit_json(changed=False, AutoScalingGroup_Tags=existing_tags_list) + existing_tags_list = describe_autoscaling_tags(connection) + module.exit_json(changed=False, AutoScalingGroup_Tags=existing_tags_list) if state == 'present': create_changed, asg_properties = create_autoscaling_group(connection) From 046c44029d97391cd53203eb4f924da14766754c Mon Sep 17 00:00:00 2001 From: Mandar Kulkarni Date: Wed, 2 Mar 2022 10:45:13 -0800 Subject: [PATCH 06/11] Handle non-existing tags removal --- plugins/modules/ec2_asg.py | 31 +++++++--- .../targets/ec2_asg/tasks/tag_operations.yml | 60 +++++++++++++------ 2 files changed, 66 insertions(+), 25 deletions(-) diff --git a/plugins/modules/ec2_asg.py b/plugins/modules/ec2_asg.py index 7a22803b88b..9cc48e8d578 100644 --- a/plugins/modules/ec2_asg.py +++ b/plugins/modules/ec2_asg.py @@ -751,6 +751,15 @@ def describe_autoscaling_tags(connection): return asg_tags_dict +@AWSRetry.jittered_backoff(**backoff_params) +def delete_asg_tags(connection, tags_to_remove): + try: + connection.delete_tags(Tags=tags_to_remove) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, msg="Failed to delete AutoScalingGroup tags.") + return True + + @AWSRetry.jittered_backoff(**backoff_params) def create_asg(connection, **params): connection.create_auto_scaling_group(**params) @@ -1264,17 +1273,25 @@ def create_autoscaling_group(connection): have_tags = [have_tag for have_tag in have_tags if have_tag['Key'] != dead_tag] if remove_tags: - dead_tags = [] + changed = False + existing_tags = describe_autoscaling_tags(connection) + tags_to_remove = [] for dead_tag in want_tag_keyvals: - dead_tags.append(dict( - ResourceId=as_group['AutoScalingGroupName'], ResourceType='auto-scaling-group', Key=dead_tag)) + if dead_tag in existing_tags: + tags_to_remove.append(dict( + ResourceId=as_group['AutoScalingGroupName'], ResourceType='auto-scaling-group', Key=dead_tag, Value=existing_tags[dead_tag])) + if len(tags_to_remove) != 0: + changed = delete_asg_tags(connection, tags_to_remove) + module.exit_json(changed=changed, asg_tags_removed=tags_to_remove) + if dead_tags: - connection.delete_tags(Tags=dead_tags) + delete_asg_tags(connection, dead_tags) zipped = zip(have_tags, want_tags) if len(have_tags) != len(want_tags) or not all(x == y for x, y in zipped): - changed = True - connection.create_or_update_tags(Tags=asg_tags) + if not remove_tags: + changed = True + connection.create_or_update_tags(Tags=asg_tags) # Handle load balancer attachments/detachments # Attach load balancers if they are specified but none currently exist @@ -1945,7 +1962,7 @@ def main(): if module.params.get('list_tags'): existing_tags_list = describe_autoscaling_tags(connection) - module.exit_json(changed=False, AutoScalingGroup_Tags=existing_tags_list) + module.exit_json(changed=False, asg_tags=existing_tags_list) if state == 'present': create_changed, asg_properties = create_autoscaling_group(connection) diff --git a/tests/integration/targets/ec2_asg/tasks/tag_operations.yml b/tests/integration/targets/ec2_asg/tasks/tag_operations.yml index 62d4924c8d8..e649739c661 100644 --- a/tests/integration/targets/ec2_asg/tasks/tag_operations.yml +++ b/tests/integration/targets/ec2_asg/tasks/tag_operations.yml @@ -46,9 +46,11 @@ list_tags: true register: info_result + - debug: msg="{{ info_result }}" + - assert: that: - - info_result.AutoScalingGroup_Tags | length == 0 + - info_result.asg_tags | length == 0 - '"autoscaling:DescribeTags" in info_result.resource_actions' - name: Add 2 new tags @@ -71,7 +73,7 @@ - assert: that: - add_result is changed - - info_result.AutoScalingGroup_Tags | length == 2 + - info_result.asg_tags | length == 2 - '"autoscaling:CreateOrUpdateTags" in add_result.resource_actions' - '"autoscaling:DescribeTags" in info_result.resource_actions' @@ -95,11 +97,11 @@ - assert: that: - add_purge_result is changed - - info_result.AutoScalingGroup_Tags | length == 2 - - '"key1" not in info_result.AutoScalingGroup_Tags' - - '"key2" not in info_result.AutoScalingGroup_Tags' - - '"key3" in info_result.AutoScalingGroup_Tags' - - '"key4" in info_result.AutoScalingGroup_Tags' + - info_result.asg_tags | length == 2 + - '"key1" not in info_result.asg_tags' + - '"key2" not in info_result.asg_tags' + - '"key3" in info_result.asg_tags' + - '"key4" in info_result.asg_tags' - '"autoscaling:CreateOrUpdateTags" in add_purge_result.resource_actions' - '"autoscaling:DescribeTags" in info_result.resource_actions' @@ -123,21 +125,43 @@ - assert: that: - add_result is changed - - info_result.AutoScalingGroup_Tags | length == 4 - - '"key1" in info_result.AutoScalingGroup_Tags' - - '"key2" in info_result.AutoScalingGroup_Tags' - - '"key3" in info_result.AutoScalingGroup_Tags' - - '"key4" in info_result.AutoScalingGroup_Tags' + - info_result.asg_tags | length == 4 + - '"key1" in info_result.asg_tags' + - '"key2" in info_result.asg_tags' + - '"key3" in info_result.asg_tags' + - '"key4" in info_result.asg_tags' - '"autoscaling:CreateOrUpdateTags" in add_result.resource_actions' - '"autoscaling:DescribeTags" in info_result.resource_actions' + - name: Remove Non-existing tags + ec2_asg: + name: "{{ resource_prefix }}-asg-tag-test" + tags: + - key10: 'val_a' + - key20: 'val_b' + remove_tags: true + register: remove_result + - name: get asg tags existing + ec2_asg: + name: "{{ resource_prefix }}-asg-tag-test" + list_tags: true + register: info_result + + - assert: + that: + - remove_result is not changed + - info_result.asg_tags | length == 4 + - '"autoscaling:DeleteTags" not in remove_result.resource_actions' + - '"autoscaling:DescribeTags" in info_result.resource_actions' + + # can provide a key-val pair or just the tag key of the tag to be delete - name: Remove 2 tags - key2, key4 ec2_asg: name: "{{ resource_prefix }}-asg-tag-test" tags: - key2: 'val2' - - key4: 'val4' + - key4: remove_tags: true register: remove_result @@ -150,11 +174,11 @@ - assert: that: - remove_result is changed - - info_result.AutoScalingGroup_Tags | length == 2 - - '"key1" in info_result.AutoScalingGroup_Tags' - - '"key2" not in info_result.AutoScalingGroup_Tags' - - '"key3" in info_result.AutoScalingGroup_Tags' - - '"key4" not in info_result.AutoScalingGroup_Tags' + - info_result.asg_tags | length == 2 + - '"key1" in info_result.asg_tags' + - '"key2" not in info_result.asg_tags' + - '"key3" in info_result.asg_tags' + - '"key4" not in info_result.asg_tags' - '"autoscaling:DeleteTags" in remove_result.resource_actions' - '"autoscaling:DescribeTags" in info_result.resource_actions' From 8452d4a27c82c25826cccc4ac2f64613b19fc03f Mon Sep 17 00:00:00 2001 From: Mandar Kulkarni Date: Sat, 5 Mar 2022 11:58:25 -0500 Subject: [PATCH 07/11] Remove list_tags, remove_tags --- plugins/modules/ec2_asg.py | 66 +---------- .../targets/ec2_asg/tasks/tag_operations.yml | 103 ++++-------------- 2 files changed, 23 insertions(+), 146 deletions(-) diff --git a/plugins/modules/ec2_asg.py b/plugins/modules/ec2_asg.py index 9cc48e8d578..dfe5936feba 100644 --- a/plugins/modules/ec2_asg.py +++ b/plugins/modules/ec2_asg.py @@ -220,12 +220,6 @@ - When I(propagate_at_launch) is true the tags will be propagated to the Instances created. type: list elements: dict - list_tags: - description: - - If C(true), returns a list of key-value pairs of existing tags on a AutoScalingGroup. - type: bool - required: false - version_added: 3.2.0 purge_tags: description: - If C(true), existing tags will be purged from the resource to match exactly what is defined by I(tags) parameter. @@ -233,12 +227,6 @@ default: true type: bool version_added: 3.2.0 - remove_tags: - description: - - If C(true), the tags specified by I(tags) parameter will be removed/deleted from AutoScalingGroup. - - If the I(tags) parameter is not set then tags will not be modified. - type: bool - version_added: 3.2.0 health_check_period: description: - Length of time in seconds after a new EC2 instance comes into service that Auto Scaling starts checking its health. @@ -733,33 +721,6 @@ def describe_launch_templates(connection, launch_template): module.fail_json(msg="No launch template found matching: %s" % launch_template) -@AWSRetry.jittered_backoff(**backoff_params) -def describe_autoscaling_tags(connection): - asg_name = module.params.get('name') - pg = connection.get_paginator('describe_tags') - filters = ansible_dict_to_boto3_filter_list({'auto-scaling-group': asg_name}) - - try: - response = pg.paginate(Filters=filters).build_full_result().get('Tags', []) - except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Failed to describe AutoScalingGroup tags.") - - asg_tags_dict = {} - for item in response: - asg_tags_dict[item['Key']] = item['Value'] - - return asg_tags_dict - - -@AWSRetry.jittered_backoff(**backoff_params) -def delete_asg_tags(connection, tags_to_remove): - try: - connection.delete_tags(Tags=tags_to_remove) - except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Failed to delete AutoScalingGroup tags.") - return True - - @AWSRetry.jittered_backoff(**backoff_params) def create_asg(connection, **params): connection.create_auto_scaling_group(**params) @@ -1145,7 +1106,6 @@ def create_autoscaling_group(connection): vpc_zone_identifier = module.params.get('vpc_zone_identifier') set_tags = module.params.get('tags') purge_tags = module.params.get('purge_tags') - remove_tags = module.params.get('remove_tags') health_check_period = module.params.get('health_check_period') health_check_type = module.params.get('health_check_type') default_cooldown = module.params.get('default_cooldown') @@ -1272,26 +1232,13 @@ def create_autoscaling_group(connection): ResourceId=as_group['AutoScalingGroupName'], ResourceType='auto-scaling-group', Key=dead_tag)) have_tags = [have_tag for have_tag in have_tags if have_tag['Key'] != dead_tag] - if remove_tags: - changed = False - existing_tags = describe_autoscaling_tags(connection) - tags_to_remove = [] - for dead_tag in want_tag_keyvals: - if dead_tag in existing_tags: - tags_to_remove.append(dict( - ResourceId=as_group['AutoScalingGroupName'], ResourceType='auto-scaling-group', Key=dead_tag, Value=existing_tags[dead_tag])) - if len(tags_to_remove) != 0: - changed = delete_asg_tags(connection, tags_to_remove) - module.exit_json(changed=changed, asg_tags_removed=tags_to_remove) - if dead_tags: - delete_asg_tags(connection, dead_tags) + connection.delete_tags(Tags=dead_tags) zipped = zip(have_tags, want_tags) if len(have_tags) != len(want_tags) or not all(x == y for x, y in zipped): - if not remove_tags: - changed = True - connection.create_or_update_tags(Tags=asg_tags) + changed = True + connection.create_or_update_tags(Tags=asg_tags) # Handle load balancer attachments/detachments # Attach load balancers if they are specified but none currently exist @@ -1902,9 +1849,7 @@ def main(): wait_timeout=dict(type='int', default=300), state=dict(default='present', choices=['present', 'absent']), tags=dict(type='list', default=[], elements='dict'), - list_tags=dict(type='bool', required=False), purge_tags=dict(type='bool', default=True), - remove_tags=dict(type='bool', required=False), health_check_period=dict(type='int', default=300), health_check_type=dict(default='EC2', choices=['EC2', 'ELB']), default_cooldown=dict(type='int', default=300), @@ -1947,7 +1892,6 @@ def main(): ['replace_all_instances', 'replace_instances'], ['replace_all_instances', 'detach_instances'], ['launch_config_name', 'launch_template'], - ['purge_tags', 'remove_tags'] ] ) @@ -1960,10 +1904,6 @@ def main(): changed = create_changed = replace_changed = detach_changed = False exists = asg_exists(connection) - if module.params.get('list_tags'): - existing_tags_list = describe_autoscaling_tags(connection) - module.exit_json(changed=False, asg_tags=existing_tags_list) - if state == 'present': create_changed, asg_properties = create_autoscaling_group(connection) elif state == 'absent': diff --git a/tests/integration/targets/ec2_asg/tasks/tag_operations.yml b/tests/integration/targets/ec2_asg/tasks/tag_operations.yml index e649739c661..ed4f2646ef6 100644 --- a/tests/integration/targets/ec2_asg/tasks/tag_operations.yml +++ b/tests/integration/targets/ec2_asg/tasks/tag_operations.yml @@ -40,20 +40,18 @@ #---------------------------------------------------------------------- - - name: get asg tags existing - ec2_asg: + - name: Get asg info + ec2_asg_info: name: "{{ resource_prefix }}-asg-tag-test" - list_tags: true register: info_result - debug: msg="{{ info_result }}" - assert: that: - - info_result.asg_tags | length == 0 - - '"autoscaling:DescribeTags" in info_result.resource_actions' + - info_result.results[0].tags | length == 0 - - name: Add 2 new tags + - name: Add 4 new tags - do not purge existing tags ec2_asg: name: "{{ resource_prefix }}-asg-tag-test" tags: @@ -61,51 +59,46 @@ propagate_at_launch: no - key2: 'val2' propagate_at_launch: yes + - key3: 'val3' + propagate_at_launch: yes + - key4: 'val4' + propagate_at_launch: no purge_tags: false register: add_result - - name: get asg tags existing - ec2_asg: + - name: Get asg info + ec2_asg_info: name: "{{ resource_prefix }}-asg-tag-test" - list_tags: true register: info_result - assert: that: - add_result is changed - - info_result.asg_tags | length == 2 + - info_result.results[0].tags | length == 4 - '"autoscaling:CreateOrUpdateTags" in add_result.resource_actions' - - '"autoscaling:DescribeTags" in info_result.resource_actions' - name: Add 2 new tags - purge existing tags ec2_asg: name: "{{ resource_prefix }}-asg-tag-test" tags: - - key3: 'val3' + - tag_a: 'val_a' propagate_at_launch: no - - key4: 'val4' + - tag_b: 'val_b' propagate_at_launch: yes - purge_tags: true register: add_purge_result - - name: get updated asg tags - ec2_asg: + - name: Get asg info + ec2_asg_info: name: "{{ resource_prefix }}-asg-tag-test" - list_tags: true register: info_result - assert: that: - add_purge_result is changed - - info_result.asg_tags | length == 2 - - '"key1" not in info_result.asg_tags' - - '"key2" not in info_result.asg_tags' - - '"key3" in info_result.asg_tags' - - '"key4" in info_result.asg_tags' + - info_result.results[0].tags | length == 2 - '"autoscaling:CreateOrUpdateTags" in add_purge_result.resource_actions' - - '"autoscaling:DescribeTags" in info_result.resource_actions' - - name: Add 2 new tags + - name: Add 2 more tags - do not purge existing tags ec2_asg: name: "{{ resource_prefix }}-asg-tag-test" tags: @@ -116,72 +109,16 @@ purge_tags: false register: add_result - - name: get asg tags existing - ec2_asg: + - name: Get asg info + ec2_asg_info: name: "{{ resource_prefix }}-asg-tag-test" - list_tags: true register: info_result - assert: that: - add_result is changed - - info_result.asg_tags | length == 4 - - '"key1" in info_result.asg_tags' - - '"key2" in info_result.asg_tags' - - '"key3" in info_result.asg_tags' - - '"key4" in info_result.asg_tags' + - info_result.results[0].tags | length == 4 - '"autoscaling:CreateOrUpdateTags" in add_result.resource_actions' - - '"autoscaling:DescribeTags" in info_result.resource_actions' - - - name: Remove Non-existing tags - ec2_asg: - name: "{{ resource_prefix }}-asg-tag-test" - tags: - - key10: 'val_a' - - key20: 'val_b' - remove_tags: true - register: remove_result - - - name: get asg tags existing - ec2_asg: - name: "{{ resource_prefix }}-asg-tag-test" - list_tags: true - register: info_result - - - assert: - that: - - remove_result is not changed - - info_result.asg_tags | length == 4 - - '"autoscaling:DeleteTags" not in remove_result.resource_actions' - - '"autoscaling:DescribeTags" in info_result.resource_actions' - - # can provide a key-val pair or just the tag key of the tag to be delete - - name: Remove 2 tags - key2, key4 - ec2_asg: - name: "{{ resource_prefix }}-asg-tag-test" - tags: - - key2: 'val2' - - key4: - remove_tags: true - register: remove_result - - - name: get asg tags existing - ec2_asg: - name: "{{ resource_prefix }}-asg-tag-test" - list_tags: true - register: info_result - - - assert: - that: - - remove_result is changed - - info_result.asg_tags | length == 2 - - '"key1" in info_result.asg_tags' - - '"key2" not in info_result.asg_tags' - - '"key3" in info_result.asg_tags' - - '"key4" not in info_result.asg_tags' - - '"autoscaling:DeleteTags" in remove_result.resource_actions' - - '"autoscaling:DescribeTags" in info_result.resource_actions' - #---------------------------------------------------------------------- From 6d606d0d2181b150cea6f34549594476909791b1 Mon Sep 17 00:00:00 2001 From: Mandar Kulkarni Date: Sat, 5 Mar 2022 12:00:30 -0500 Subject: [PATCH 08/11] Fix changelogs fragment --- changelogs/fragments/960-ec2_asg-manage-tags.yml | 2 -- changelogs/fragments/960-ec2_asg-purge-tags.yml | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 changelogs/fragments/960-ec2_asg-manage-tags.yml create mode 100644 changelogs/fragments/960-ec2_asg-purge-tags.yml diff --git a/changelogs/fragments/960-ec2_asg-manage-tags.yml b/changelogs/fragments/960-ec2_asg-manage-tags.yml deleted file mode 100644 index cae4bd8950b..00000000000 --- a/changelogs/fragments/960-ec2_asg-manage-tags.yml +++ /dev/null @@ -1,2 +0,0 @@ -minor_changes: -- ec2_asg - add support for managing tags on AutoScalingGroups (https://github.com/ansible-collections/community.aws/pull/960). diff --git a/changelogs/fragments/960-ec2_asg-purge-tags.yml b/changelogs/fragments/960-ec2_asg-purge-tags.yml new file mode 100644 index 00000000000..064264dfb6a --- /dev/null +++ b/changelogs/fragments/960-ec2_asg-purge-tags.yml @@ -0,0 +1,2 @@ +minor_changes: +- ec2_asg - add support for ``purge_tags`` to ec2_asg (https://github.com/ansible-collections/community.aws/pull/960). From 3d01096c3040a5fb14036f54ae89fc6dd8ac5cb7 Mon Sep 17 00:00:00 2001 From: Mandar Kulkarni Date: Mon, 7 Mar 2022 13:44:02 -0800 Subject: [PATCH 09/11] Modified tests based on feedback --- .../targets/ec2_asg/tasks/tag_operations.yml | 63 ++++++++++++++++--- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/tests/integration/targets/ec2_asg/tasks/tag_operations.yml b/tests/integration/targets/ec2_asg/tasks/tag_operations.yml index ed4f2646ef6..a939a1dacab 100644 --- a/tests/integration/targets/ec2_asg/tasks/tag_operations.yml +++ b/tests/integration/targets/ec2_asg/tasks/tag_operations.yml @@ -45,8 +45,6 @@ name: "{{ resource_prefix }}-asg-tag-test" register: info_result - - debug: msg="{{ info_result }}" - - assert: that: - info_result.results[0].tags | length == 0 @@ -55,13 +53,13 @@ ec2_asg: name: "{{ resource_prefix }}-asg-tag-test" tags: - - key1: 'val1' + - lowercase spaced: "hello cruel world" propagate_at_launch: no - - key2: 'val2' + - Title Case: "Hello Cruel World" propagate_at_launch: yes - - key3: 'val3' + - CamelCase: "SimpleCamelCase" propagate_at_launch: yes - - key4: 'val4' + - snake_case: "simple_snake_case" propagate_at_launch: no purge_tags: false register: add_result @@ -70,13 +68,46 @@ ec2_asg_info: name: "{{ resource_prefix }}-asg-tag-test" register: info_result + # create a list of tag_keys from info result + - set_fact: + tag_keys: "{{ info_result.results[0].tags | map(attribute='key') | list }}" - assert: that: - add_result is changed - info_result.results[0].tags | length == 4 + - '"lowercase spaced" in tag_keys' + - '"Title Case" in tag_keys' + - '"CamelCase" in tag_keys' + - '"snake_case" in tag_keys' - '"autoscaling:CreateOrUpdateTags" in add_result.resource_actions' + - name: Add 4 new tags - do not purge existing tags - idempotency + ec2_asg: + name: "{{ resource_prefix }}-asg-tag-test" + tags: + - lowercase spaced: "hello cruel world" + propagate_at_launch: no + - Title Case: "Hello Cruel World" + propagate_at_launch: yes + - CamelCase: "SimpleCamelCase" + propagate_at_launch: yes + - snake_case: "simple_snake_case" + propagate_at_launch: no + purge_tags: false + register: add_result + + - name: Get asg info + ec2_asg_info: + name: "{{ resource_prefix }}-asg-tag-test" + register: info_result + + - assert: + that: + - add_result is not changed + - info_result.results[0].tags | length == 4 + - '"autoscaling:CreateOrUpdateTags" not in add_result.resource_actions' + - name: Add 2 new tags - purge existing tags ec2_asg: name: "{{ resource_prefix }}-asg-tag-test" @@ -91,20 +122,29 @@ ec2_asg_info: name: "{{ resource_prefix }}-asg-tag-test" register: info_result + # create a list of tag_keys from info result + - set_fact: + tag_keys: "{{ info_result.results[0].tags | map(attribute='key') | list }}" - assert: that: - add_purge_result is changed - info_result.results[0].tags | length == 2 + - '"tag_a" in tag_keys' + - '"tag_b" in tag_keys' + - '"lowercase spaced" not in tag_keys' + - '"Title Case" not in tag_keys' + - '"CamelCase" not in tag_keys' + - '"snake_case" not in tag_keys' - '"autoscaling:CreateOrUpdateTags" in add_purge_result.resource_actions' - name: Add 2 more tags - do not purge existing tags ec2_asg: name: "{{ resource_prefix }}-asg-tag-test" tags: - - key1: 'val1' + - lowercase spaced: "hello cruel world" propagate_at_launch: no - - key2: 'val2' + - Title Case: "Hello Cruel World" propagate_at_launch: yes purge_tags: false register: add_result @@ -113,11 +153,18 @@ ec2_asg_info: name: "{{ resource_prefix }}-asg-tag-test" register: info_result + # create a list of tag_keys from info result + - set_fact: + tag_keys: "{{ info_result.results[0].tags | map(attribute='key') | list }}" - assert: that: - add_result is changed - info_result.results[0].tags | length == 4 + - '"tag_a" in tag_keys' + - '"tag_b" in tag_keys' + - '"lowercase spaced" in tag_keys' + - '"Title Case" in tag_keys' - '"autoscaling:CreateOrUpdateTags" in add_result.resource_actions' #---------------------------------------------------------------------- From 43d67eb63fc07cbec70a68ac57624b3e0ba66e93 Mon Sep 17 00:00:00 2001 From: Mandar Kulkarni Date: Mon, 7 Mar 2022 17:01:51 -0800 Subject: [PATCH 10/11] Add more tests --- .../targets/ec2_asg/tasks/tag_operations.yml | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/tests/integration/targets/ec2_asg/tasks/tag_operations.yml b/tests/integration/targets/ec2_asg/tasks/tag_operations.yml index a939a1dacab..f917e9224a6 100644 --- a/tests/integration/targets/ec2_asg/tasks/tag_operations.yml +++ b/tests/integration/targets/ec2_asg/tasks/tag_operations.yml @@ -138,6 +138,41 @@ - '"snake_case" not in tag_keys' - '"autoscaling:CreateOrUpdateTags" in add_purge_result.resource_actions' + - name: Re-tag ASG - modify values + ec2_asg: + name: "{{ resource_prefix }}-asg-tag-test" + tags: + - tag_a: 'new_val_a' + propagate_at_launch: no + - tag_b: 'new_val_b' + propagate_at_launch: yes + register: add_purge_result + + - name: Get asg info + ec2_asg_info: + name: "{{ resource_prefix }}-asg-tag-test" + register: info_result + # create a list of tag_keys and tag_values from info result + - set_fact: + tag_keys: "{{ info_result.results[0].tags | map(attribute='key') | list }}" + - set_fact: + tag_values: "{{ info_result.results[0].tags | map(attribute='value') | list }}" + + + - assert: + that: + - add_purge_result is changed + - info_result.results[0].tags | length == 2 + - '"tag_a" in tag_keys' + - '"tag_b" in tag_keys' + - '"new_val_a" in tag_values' + - '"new_val_b" in tag_values' + - '"lowercase spaced" not in tag_keys' + - '"Title Case" not in tag_keys' + - '"CamelCase" not in tag_keys' + - '"snake_case" not in tag_keys' + - '"autoscaling:CreateOrUpdateTags" in add_purge_result.resource_actions' + - name: Add 2 more tags - do not purge existing tags ec2_asg: name: "{{ resource_prefix }}-asg-tag-test" @@ -167,6 +202,31 @@ - '"Title Case" in tag_keys' - '"autoscaling:CreateOrUpdateTags" in add_result.resource_actions' + - name: Add empty tags with purge set to false to assert that existing tags are retained + ec2_asg: + name: "{{ resource_prefix }}-asg-tag-test" + tags: [] + purge_tags: false + register: add_empty + + - name: Get asg info + ec2_asg_info: + name: "{{ resource_prefix }}-asg-tag-test" + register: info_result + # create a list of tag_keys from info result + - set_fact: + tag_keys: "{{ info_result.results[0].tags | map(attribute='key') | list }}" + + - assert: + that: + - add_empty is not changed + - info_result.results[0].tags | length == 4 + - '"tag_a" in tag_keys' + - '"tag_b" in tag_keys' + - '"lowercase spaced" in tag_keys' + - '"Title Case" in tag_keys' + - '"autoscaling:CreateOrUpdateTags" not in add_empty.resource_actions' + #---------------------------------------------------------------------- always: From 4c32a8f7dc764fe8b51e9099a5ce7372c7188168 Mon Sep 17 00:00:00 2001 From: Mandar Kulkarni Date: Wed, 9 Mar 2022 08:47:31 -0800 Subject: [PATCH 11/11] Handle remove all tags, move tag related tests to single file --- plugins/modules/ec2_asg.py | 7 +- .../targets/ec2_asg/tasks/main.yml | 56 ---------- .../targets/ec2_asg/tasks/tag_operations.yml | 102 ++++++++++++++++++ 3 files changed, 107 insertions(+), 58 deletions(-) diff --git a/plugins/modules/ec2_asg.py b/plugins/modules/ec2_asg.py index dfe5936feba..fa91232cbe6 100644 --- a/plugins/modules/ec2_asg.py +++ b/plugins/modules/ec2_asg.py @@ -1214,9 +1214,12 @@ def create_autoscaling_group(connection): changed = True # process tag changes + have_tags = as_group.get('Tags') + want_tags = asg_tags + if purge_tags and not want_tags and have_tags: + connection.delete_tags(Tags=list(have_tags)) + if len(set_tags) > 0: - have_tags = as_group.get('Tags') - want_tags = asg_tags if have_tags: have_tags.sort(key=lambda x: x["Key"]) if want_tags: diff --git a/tests/integration/targets/ec2_asg/tasks/main.yml b/tests/integration/targets/ec2_asg/tasks/main.yml index 85b69bf10a3..4188f245379 100644 --- a/tests/integration/targets/ec2_asg/tasks/main.yml +++ b/tests/integration/targets/ec2_asg/tasks/main.yml @@ -138,62 +138,6 @@ that: - "output.viable_instances == 1" - - name: Tag asg - ec2_asg: - name: "{{ resource_prefix }}-asg" - tags: - - tag_a: 'value 1' - propagate_at_launch: no - - tag_b: 'value 2' - propagate_at_launch: yes - register: output - - - assert: - that: - - "output.tags | length == 2" - - output is changed - - - name: Re-Tag asg (different order) - ec2_asg: - name: "{{ resource_prefix }}-asg" - tags: - - tag_b: 'value 2' - propagate_at_launch: yes - - tag_a: 'value 1' - propagate_at_launch: no - register: output - - - assert: - that: - - "output.tags | length == 2" - - output is not changed - - - name: Re-Tag asg new tags - ec2_asg: - name: "{{ resource_prefix }}-asg" - tags: - - tag_c: 'value 3' - propagate_at_launch: no - register: output - - - assert: - that: - - "output.tags | length == 1" - - output is changed - - - name: Re-Tag asg update propagate_at_launch - ec2_asg: - name: "{{ resource_prefix }}-asg" - tags: - - tag_c: 'value 3' - propagate_at_launch: yes - register: output - - - assert: - that: - - "output.tags | length == 1" - - output is changed - - name: Enable metrics collection ec2_asg: name: "{{ resource_prefix }}-asg" diff --git a/tests/integration/targets/ec2_asg/tasks/tag_operations.yml b/tests/integration/targets/ec2_asg/tasks/tag_operations.yml index f917e9224a6..2f9cc118c4f 100644 --- a/tests/integration/targets/ec2_asg/tasks/tag_operations.yml +++ b/tests/integration/targets/ec2_asg/tasks/tag_operations.yml @@ -49,6 +49,83 @@ that: - info_result.results[0].tags | length == 0 + - name: Tag asg + ec2_asg: + name: "{{ resource_prefix }}-asg-tag-test" + tags: + - tag_a: 'value 1' + propagate_at_launch: no + - tag_b: 'value 2' + propagate_at_launch: yes + register: output + + - assert: + that: + - "output.tags | length == 2" + - output is changed + + - name: Re-Tag asg (different order) + ec2_asg: + name: "{{ resource_prefix }}-asg-tag-test" + tags: + - tag_b: 'value 2' + propagate_at_launch: yes + - tag_a: 'value 1' + propagate_at_launch: no + register: output + + - assert: + that: + - "output.tags | length == 2" + - output is not changed + + - name: Re-Tag asg new tags + ec2_asg: + name: "{{ resource_prefix }}-asg-tag-test" + tags: + - tag_c: 'value 3' + propagate_at_launch: no + register: output + + - assert: + that: + - "output.tags | length == 1" + - output is changed + + - name: Re-Tag asg update propagate_at_launch + ec2_asg: + name: "{{ resource_prefix }}-asg-tag-test" + tags: + - tag_c: 'value 3' + propagate_at_launch: yes + register: output + + - assert: + that: + - "output.tags | length == 1" + - output is changed + + - name: Remove all tags + ec2_asg: + name: "{{ resource_prefix }}-asg-tag-test" + tags: [] + register: add_empty + + - name: Get asg info + ec2_asg_info: + name: "{{ resource_prefix }}-asg-tag-test" + register: info_result + # create a list of tag_keys from info result + - set_fact: + tag_keys: "{{ info_result.results[0].tags | map(attribute='key') | list }}" + + - assert: + that: + - add_empty is changed + - info_result.results[0].tags | length == 0 + - '"autoscaling:CreateOrUpdateTags" not in add_empty.resource_actions' + - '"autoscaling:DeleteTags" in add_empty.resource_actions' + - name: Add 4 new tags - do not purge existing tags ec2_asg: name: "{{ resource_prefix }}-asg-tag-test" @@ -227,6 +304,31 @@ - '"Title Case" in tag_keys' - '"autoscaling:CreateOrUpdateTags" not in add_empty.resource_actions' + - name: Add empty tags with purge set to true to assert that existing tags are removed + ec2_asg: + name: "{{ resource_prefix }}-asg-tag-test" + tags: [] + register: add_empty + + - name: Get asg info + ec2_asg_info: + name: "{{ resource_prefix }}-asg-tag-test" + register: info_result + # create a list of tag_keys from info result + - set_fact: + tag_keys: "{{ info_result.results[0].tags | map(attribute='key') | list }}" + + - assert: + that: + - add_empty is changed + - info_result.results[0].tags | length == 0 + - '"tag_a" not in tag_keys' + - '"tag_b" not in tag_keys' + - '"lowercase spaced" not in tag_keys' + - '"Title Case" not in tag_keys' + - '"autoscaling:CreateOrUpdateTags" not in add_empty.resource_actions' + - '"autoscaling:DeleteTags" in add_empty.resource_actions' + #---------------------------------------------------------------------- always: