Skip to content

Commit

Permalink
ec2_asg_instance_refresh and ec2_asg_instance_refresh_info modules (a…
Browse files Browse the repository at this point in the history
…nsible-collections#973)

ec2_asg_instance_refresh and ec2_asg_instance_refresh_info modules

SUMMARY

Reviving original PR that adds Autoscaling instance refresh API support as the author has yet not updated PR based on review feedback.
Issue: ansible-collections#135
PR being revived: ansible-collections#795
Fixes ansible-collections#135

ISSUE TYPE


New Module Pull Request

COMPONENT NAME

ec2_asg_instance_refresh
ec2_asg_instance_refreshes_info
ADDITIONAL INFORMATION



More about the feature: https://aws.amazon.com/blogs/compute/introducing-instance-refresh-for-ec2-auto-scaling/
Boto3 documentation: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/autoscaling.html#AutoScaling.Client.start_instance_refresh

Reviewed-by: Alina Buzachis <None>
Reviewed-by: Mandar Kulkarni <[email protected]>
Reviewed-by: Mark Woolley <[email protected]>
Reviewed-by: Jill R <None>
Reviewed-by: Joseph Torcasso <None>
  • Loading branch information
mandar242 authored Mar 24, 2022
1 parent dd17472 commit 01b6f73
Show file tree
Hide file tree
Showing 2 changed files with 486 additions and 0 deletions.
267 changes: 267 additions & 0 deletions ec2_asg_instance_refresh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
#!/usr/bin/python
# Copyright: Ansible Project
# 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 = '''
---
module: ec2_asg_instance_refresh
version_added: 3.2.0
short_description: Start or cancel an EC2 Auto Scaling Group (ASG) instance refresh in AWS
description:
- Start or cancel an EC2 Auto Scaling Group instance refresh in AWS.
- Can be used with M(community.aws.ec2_asg_instance_refresh_info) to track the subsequent progress.
author: "Dan Khersonsky (@danquixote)"
options:
state:
description:
- Desired state of the ASG.
type: str
required: true
choices: [ 'started', 'cancelled' ]
name:
description:
- The name of the auto scaling group you are searching for.
type: str
required: true
strategy:
description:
- The strategy to use for the instance refresh. The only valid value is C(Rolling).
- A rolling update is an update that is applied to all instances in an Auto Scaling group until all instances have been updated.
- A rolling update can fail due to failed health checks or if instances are on standby or are protected from scale in.
- If the rolling update process fails, any instances that were already replaced are not rolled back to their previous configuration.
type: str
default: 'Rolling'
preferences:
description:
- Set of preferences associated with the instance refresh request.
- If not provided, the default values are used.
- For I(min_healthy_percentage), the default value is C(90).
- For I(instance_warmup), the default is to use the value specified for the health check grace period for the Auto Scaling group.
- Can not be specified when I(state) is set to 'cancelled'.
required: false
suboptions:
min_healthy_percentage:
description:
- Total percent of capacity in ASG that must remain healthy during instance refresh to allow operation to continue.
- It is rounded up to the nearest integer.
type: int
default: 90
instance_warmup:
description:
- The number of seconds until a newly launched instance is configured and ready to use.
- During this time, Amazon EC2 Auto Scaling does not immediately move on to the next replacement.
- The default is to use the value for the health check grace period defined for the group.
type: int
type: dict
extends_documentation_fragment:
- amazon.aws.aws
- amazon.aws.ec2
'''

EXAMPLES = '''
# Note: These examples do not set authentication details, see the AWS Guide for details.
- name: Start a refresh
community.aws.ec2_asg_instance_refresh:
name: some-asg
state: started
- name: Cancel a refresh
community.aws.ec2_asg_instance_refresh:
name: some-asg
state: cancelled
- name: Start a refresh and pass preferences
community.aws.ec2_asg_instance_refresh:
name: some-asg
state: started
preferences:
min_healthy_percentage: 91
instance_warmup: 60
'''

RETURN = '''
---
instance_refresh_id:
description: instance refresh id
returned: success
type: str
sample: "08b91cf7-8fa6-48af-b6a6-d227f40f1b9b"
auto_scaling_group_name:
description: Name of autoscaling group
returned: success
type: str
sample: "public-webapp-production-1"
status:
description:
- The current state of the group when DeleteAutoScalingGroup is in progress.
- The following are the possible statuses
- Pending -- The request was created, but the operation has not started.
- InProgress -- The operation is in progress.
- Successful -- The operation completed successfully.
- Failed -- The operation failed to complete. You can troubleshoot using the status reason and the scaling activities.
- Cancelling --
- An ongoing operation is being cancelled.
- Cancellation does not roll back any replacements that have already been completed,
- but it prevents new replacements from being started.
- Cancelled -- The operation is cancelled.
returned: success
type: str
sample: "Pending"
start_time:
description: The date and time this ASG was created, in ISO 8601 format.
returned: success
type: str
sample: "2015-11-25T00:05:36.309Z"
end_time:
description: The date and time this ASG was created, in ISO 8601 format.
returned: success
type: str
sample: "2015-11-25T00:05:36.309Z"
percentage_complete:
description: the % of completeness
returned: success
type: int
sample: 100
instances_to_update:
description: num. of instance to update
returned: success
type: int
sample: 5
'''

try:
from botocore.exceptions import BotoCoreError, ClientError
except ImportError:
pass # caught by AnsibleAWSModule


from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import camel_dict_to_snake_dict
from ansible_collections.amazon.aws.plugins.module_utils.core import scrub_none_parameters
from ansible.module_utils.common.dict_transformations import snake_dict_to_camel_dict


def start_or_cancel_instance_refresh(conn, module):
"""
Args:
conn (boto3.AutoScaling.Client): Valid Boto3 ASG client.
module: AnsibleAWSModule object
Returns:
{
"instance_refreshes": [
{
'auto_scaling_group_name': 'ansible-test-hermes-63642726-asg',
'instance_refresh_id': '6507a3e5-4950-4503-8978-e9f2636efc09',
'instances_to_update': 1,
'percentage_complete': 0,
"preferences": {
"instance_warmup": 60,
"min_healthy_percentage": 90,
"skip_matching": false
},
'start_time': '2021-02-04T03:39:40+00:00',
'status': 'Cancelling',
'status_reason': 'Replacing instances before cancelling.',
}
]
}
"""

asg_state = module.params.get('state')
asg_name = module.params.get('name')
preferences = module.params.get('preferences')

args = {}
args['AutoScalingGroupName'] = asg_name
if asg_state == 'started':
args['Strategy'] = module.params.get('strategy')
if preferences:
if asg_state == 'cancelled':
module.fail_json(msg='can not pass preferences dict when canceling a refresh')
_prefs = scrub_none_parameters(preferences)
args['Preferences'] = snake_dict_to_camel_dict(_prefs, capitalize_first=True)
cmd_invocations = {
'cancelled': conn.cancel_instance_refresh,
'started': conn.start_instance_refresh,
}
try:
if module.check_mode:
if asg_state == 'started':
ongoing_refresh = conn.describe_instance_refreshes(AutoScalingGroupName=asg_name).get('InstanceRefreshes', '[]')
if ongoing_refresh:
module.exit_json(changed=False, msg='In check_mode - Instance Refresh is already in progress, can not start new instance refresh.')
else:
module.exit_json(changed=True, msg='Would have started instance refresh if not in check mode.')
elif asg_state == 'cancelled':
ongoing_refresh = conn.describe_instance_refreshes(AutoScalingGroupName=asg_name).get('InstanceRefreshes', '[]')[0]
if ongoing_refresh.get('Status', '') in ['Cancelling', 'Cancelled']:
module.exit_json(changed=False, msg='In check_mode - Instance Refresh already cancelled or is pending cancellation.')
elif not ongoing_refresh:
module.exit_json(chaned=False, msg='In check_mode - No active referesh found, nothing to cancel.')
else:
module.exit_json(changed=True, msg='Would have cancelled instance refresh if not in check mode.')
result = cmd_invocations[asg_state](aws_retry=True, **args)
instance_refreshes = conn.describe_instance_refreshes(AutoScalingGroupName=asg_name, InstanceRefreshIds=[result['InstanceRefreshId']])
result = dict(
instance_refreshes=camel_dict_to_snake_dict(instance_refreshes['InstanceRefreshes'][0])
)
return module.exit_json(**result)
except (BotoCoreError, ClientError) as e:
module.fail_json_aws(
e,
msg='Failed to {0} InstanceRefresh'.format(
asg_state.replace('ed', '')
)
)


def main():

argument_spec = dict(
state=dict(
type='str',
required=True,
choices=['started', 'cancelled'],
),
name=dict(required=True),
strategy=dict(
type='str',
default='Rolling',
required=False
),
preferences=dict(
type='dict',
required=False,
options=dict(
min_healthy_percentage=dict(type='int', default=90),
instance_warmup=dict(type='int'),
)
),
)

module = AnsibleAWSModule(
argument_spec=argument_spec,
supports_check_mode=True,
)
autoscaling = module.client(
'autoscaling',
retry_decorator=AWSRetry.jittered_backoff(
retries=10,
catch_extra_error_codes=['InstanceRefreshInProgress']
)
)

start_or_cancel_instance_refresh(autoscaling, module)


if __name__ == '__main__':
main()
Loading

0 comments on commit 01b6f73

Please sign in to comment.