diff --git a/changelogs/fragments/2207_scale_in_protection_auto_scaling_group.yml b/changelogs/fragments/2207_scale_in_protection_auto_scaling_group.yml new file mode 100644 index 0000000000..ca7c40c740 --- /dev/null +++ b/changelogs/fragments/2207_scale_in_protection_auto_scaling_group.yml @@ -0,0 +1,2 @@ +minor_changes: + - autoscaling_group - Added a boolean parameter ``protected_from_scale_in`` to toggle protection from scale-in. This allows users to enable or disable scale-in protection for instances in an autoscaling group. (https://github.com/ansible-collections/amazon.aws/pull/2207) diff --git a/plugins/modules/autoscaling_group.py b/plugins/modules/autoscaling_group.py index 0efd38cc48..f1bd7e17e9 100644 --- a/plugins/modules/autoscaling_group.py +++ b/plugins/modules/autoscaling_group.py @@ -228,6 +228,12 @@ - List of VPC subnets to use type: list elements: str + protected_from_scale_in: + description: + - If V(true), instances will have scale-in protection enabled. + type: bool + default: false + version_added: 8.2.0 tags: description: - A list of tags to add to the Auto Scale Group. @@ -870,6 +876,7 @@ def get_properties(autoscaling_group): properties["termination_policies"] = autoscaling_group.get("TerminationPolicies") properties["target_group_arns"] = autoscaling_group.get("TargetGroupARNs") properties["vpc_zone_identifier"] = autoscaling_group.get("VPCZoneIdentifier") + properties['protected_from_scale_in'] = autoscaling_group.get('NewInstancesProtectedFromScaleIn') raw_mixed_instance_object = autoscaling_group.get("MixedInstancesPolicy") if raw_mixed_instance_object: properties["mixed_instances_policy_full"] = camel_dict_to_snake_dict(raw_mixed_instance_object) @@ -1128,6 +1135,7 @@ 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") + protected_from_scale_in = module.params.get("protected_from_scale_in") 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") @@ -1186,6 +1194,7 @@ def create_autoscaling_group(connection): HealthCheckType=health_check_type, DefaultCooldown=default_cooldown, TerminationPolicies=termination_policies, + NewInstancesProtectedFromScaleIn=protected_from_scale_in, ) if vpc_zone_identifier: ag["VPCZoneIdentifier"] = vpc_zone_identifier @@ -1199,7 +1208,8 @@ def create_autoscaling_group(connection): ag["TargetGroupARNs"] = target_group_arns if max_instance_lifetime: ag["MaxInstanceLifetime"] = max_instance_lifetime - + if protected_from_scale_in: + ag['NewInstancesProtectedFromScaleIn'] launch_object = get_launch_object(connection, ec2_connection) if "LaunchConfigurationName" in launch_object: ag["LaunchConfigurationName"] = launch_object["LaunchConfigurationName"] @@ -1363,6 +1373,7 @@ def create_autoscaling_group(connection): MinSize=min_size, MaxSize=max_size, DesiredCapacity=desired_capacity, + NewInstancesProtectedFromScaleIn=protected_from_scale_in, HealthCheckGracePeriod=health_check_period, HealthCheckType=health_check_type, DefaultCooldown=default_cooldown, @@ -1483,7 +1494,7 @@ def get_chunks(l, n): yield l[i:i + n] # fmt: skip -def update_size(connection, group, max_size, min_size, dc): +def update_size(connection, group, max_size, min_size, dc, protected_from_scale_in): module.debug("setting ASG sizes") module.debug(f"minimum size: {min_size}, desired_capacity: {dc}, max size: {max_size}") updated_group = dict() @@ -1491,6 +1502,7 @@ def update_size(connection, group, max_size, min_size, dc): updated_group["MinSize"] = min_size updated_group["MaxSize"] = max_size updated_group["DesiredCapacity"] = dc + updated_group["NewInstancesProtectedFromScaleIn"] = protected_from_scale_in update_asg(connection, **updated_group) @@ -1501,6 +1513,7 @@ def replace(connection): group_name = module.params.get("name") max_size = module.params.get("max_size") min_size = module.params.get("min_size") + protected_from_scale_in = module.params.get("protected_from_scale_in") desired_capacity = module.params.get("desired_capacity") launch_config_name = module.params.get("launch_config_name") @@ -1570,7 +1583,14 @@ def replace(connection): # This should get overwritten if the number of instances left is less than the batch size. as_group = describe_autoscaling_groups(connection, group_name)[0] - update_size(connection, as_group, max_size + batch_size, min_size + batch_size, desired_capacity + batch_size) + update_size( + connection, + as_group, + max_size + batch_size, + min_size + batch_size, + desired_capacity + batch_size, + protected_from_scale_in, + ) if wait_for_instances: wait_for_new_inst(connection, group_name, wait_timeout, as_group["MinSize"] + batch_size, "viable_instances") @@ -1598,7 +1618,7 @@ def replace(connection): module.debug("breaking loop") break - update_size(connection, as_group, max_size, min_size, desired_capacity) + update_size(connection, as_group, max_size, min_size, desired_capacity, protected_from_scale_in) as_group = describe_autoscaling_groups(connection, group_name)[0] asg_properties = get_properties(as_group) module.debug("Rolling update complete.") @@ -1902,6 +1922,7 @@ def main(): state=dict(default="present", choices=["present", "absent"]), tags=dict(type="list", default=[], elements="dict"), purge_tags=dict(type="bool", default=False), + protected_from_scale_in=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), diff --git a/tests/integration/targets/autoscaling_group/roles/ec2_asg/tasks/create_update_delete.yml b/tests/integration/targets/autoscaling_group/roles/ec2_asg/tasks/create_update_delete.yml index bc8373af94..0c11b55ec9 100644 --- a/tests/integration/targets/autoscaling_group/roles/ec2_asg/tasks/create_update_delete.yml +++ b/tests/integration/targets/autoscaling_group/roles/ec2_asg/tasks/create_update_delete.yml @@ -580,3 +580,25 @@ - output.target_group_arns | length == 1 - output.target_group_arns[0] == out_tg1.target_group_arn - output.changed == false + # ============================================================ + - name: Create autosclaing group with instance protection enabled + amazon.aws.autoscaling_group: + name: "{{ resource_prefix }}-asg" + protected_from_scale_in: true + register: output + + - name: Assert ASG created + ansible.builtin.assert: + that: + - output.changed == true or (output.changed == false and output.protected_from_scale_in == true) + + - name: Disable instance protection from asg + amazon.aws.autoscaling_group: + name: "{{ resource_prefix }}-asg" + protected_from_scale_in: false + register: modified_output + + - name: Assert ASG modification + ansible.builtin.assert: + that: + - protected_from_scale_in.changed == true