Skip to content

Commit

Permalink
autoscaling_instance_refresh - prepare modules for promotion (#2150)
Browse files Browse the repository at this point in the history
SUMMARY
Closes #2120
Closes #2019
Closes #2016
Prepare modules autoscaling_instance_refresh and autoscaling_instance_refresh_info for promotion:

Refactor modules to use common code from ansible_collections.amazon.aws.plugins.module_utils.autoscaling
Add type hinting
Update integration tests


ISSUE TYPE


Feature Pull Request

Reviewed-by: GomathiselviS
Reviewed-by: Bikouo Aubin
Reviewed-by: Alina Buzachis
  • Loading branch information
abikouo authored Sep 25, 2024
1 parent f54383b commit d59fa93
Show file tree
Hide file tree
Showing 9 changed files with 625 additions and 553 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bugfixes:
- autoscaling_instance_refresh - Fix typo in module ``exit_json`` (https://github.com/ansible-collections/community.aws/issues/2019).
minor_changes:
- autoscaling_instance_refresh - refactor module to use shared code from ``ansible_collections.amazon.aws.plugins.module_utils.autoscaling`` and add type hinting (https://github.com/ansible-collections/community.aws/pull/2150).
- autoscaling_instance_refresh - Add support for ``skip_matching`` and ``max_healthy_percentage`` in ``preference`` (https://github.com/ansible-collections/community.aws/pull/2150).
- autoscaling_instance_refresh_info - refactor module to use shared code from ``ansible_collections.amazon.aws.plugins.module_utils.autoscaling`` and add type hinting (https://github.com/ansible-collections/community.aws/pull/2150).
238 changes: 135 additions & 103 deletions plugins/modules/autoscaling_instance_refresh.py

Large diffs are not rendered by default.

230 changes: 118 additions & 112 deletions plugins/modules/autoscaling_instance_refresh_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
short_description: Gather information about EC2 Auto Scaling Group (ASG) Instance Refreshes in AWS
description:
- Describes one or more instance refreshes.
- You can determine the status of a request by looking at the I(status) parameter.
- Prior to release 5.0.0 this module was called C(community.aws.ec2_asg_instance_refresh_info).
- You can determine the status of a request by looking at the RV(instance_refreshes.status) return value.
- Prior to release 5.0.0 this module was called M(community.aws.ec2_asg_instance_refresh_info).
The usage did not change.
author:
- "Dan Khersonsky (@danquixote)"
Expand All @@ -34,7 +34,7 @@
type: str
max_records:
description:
- The maximum number of items to return with this call. The default value is 50 and the maximum value is 100.
- The maximum number of items to return with this call. The default value is V(50) and the maximum value is V(100).
type: int
required: false
extends_documentation_fragment:
Expand Down Expand Up @@ -70,131 +70,137 @@
"""

RETURN = r"""
---
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
- C(Pending) - The request was created, but the operation has not started.
- C(InProgress) - The operation is in progress.
- C(Successful) - The operation completed successfully.
- C(Failed) - The operation failed to complete.
You can troubleshoot using the status reason and the scaling activities.
- C(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.
- C(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
next_token:
description: A string that indicates that the response contains more items than can be returned in a single response.
returned: always
type: str
instance_refreshes:
description: A list of instance refreshes.
returned: always
type: complex
contains:
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"
preferences:
description: The preferences for an instance refresh.
returned: always
type: dict
sample: {
'AlarmSpecification': {
'Alarms': [
'my-alarm',
],
},
'AutoRollback': True,
'InstanceWarmup': 200,
'MinHealthyPercentage': 90,
'ScaleInProtectedInstances': 'Ignore',
'SkipMatching': False,
'StandbyInstances': 'Ignore',
}
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: number of instances to update.
returned: success
type: int
sample: 5
"""

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

from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict

from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry
from ansible_collections.amazon.aws.plugins.module_utils.autoscaling import AnsibleAutoScalingError
from ansible_collections.amazon.aws.plugins.module_utils.autoscaling import describe_instance_refreshes

from ansible_collections.community.aws.plugins.module_utils.modules import AnsibleCommunityAWSModule as AnsibleAWSModule


def find_asg_instance_refreshes(conn, module):
def format_response(response: Dict[str, Any]) -> Dict[str, Any]:
result = {}
if "InstanceRefreshes" in response:
instance_refreshes_dict = {
"instance_refreshes": response["InstanceRefreshes"],
"next_token": response.get("NextToken", ""),
}
result = camel_dict_to_snake_dict(instance_refreshes_dict)
return result


def find_asg_instance_refreshes(client, module: AnsibleAWSModule) -> None:
"""
Args:
conn (boto3.AutoScaling.Client): Valid Boto3 ASG client.
client (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': 'Cancelled',
'status_reason': 'Cancelled due to user request.',
}
],
'next_token': 'string'
}
"""

asg_name = module.params.get("name")
asg_ids = module.params.get("ids")
asg_next_token = module.params.get("next_token")
asg_max_records = module.params.get("max_records")

args = {}
args["AutoScalingGroupName"] = asg_name
if asg_ids:
args["InstanceRefreshIds"] = asg_ids
if asg_next_token:
args["NextToken"] = asg_next_token
if asg_max_records:
args["MaxRecords"] = asg_max_records

try:
instance_refreshes_result = {}
response = conn.describe_instance_refreshes(**args)
if "InstanceRefreshes" in response:
instance_refreshes_dict = dict(
instance_refreshes=response["InstanceRefreshes"], next_token=response.get("next_token", "")
)
instance_refreshes_result = camel_dict_to_snake_dict(instance_refreshes_dict)

while "NextToken" in response:
args["NextToken"] = response["NextToken"]
response = conn.describe_instance_refreshes(**args)
if "InstanceRefreshes" in response:
instance_refreshes_dict = camel_dict_to_snake_dict(
dict(instance_refreshes=response["InstanceRefreshes"], next_token=response.get("next_token", ""))
max_records = module.params.get("max_records")
response = describe_instance_refreshes(
client,
auto_scaling_group_name=module.params.get("name"),
instance_refresh_ids=module.params.get("ids"),
next_token=module.params.get("next_token"),
max_records=max_records,
)
instance_refreshes_result = format_response(response)

if max_records is None:
while "NextToken" in response:
response = describe_instance_refreshes(
client,
auto_scaling_group_name=module.params.get("name"),
instance_refresh_ids=module.params.get("ids"),
next_token=response["NextToken"],
max_records=max_records,
)
instance_refreshes_result.update(instance_refreshes_dict)
f_response = format_response(response)
if "instance_refreshes" in f_response:
instance_refreshes_result["instance_refreshes"].extend(f_response["instance_refreshes"])
instance_refreshes_result["next_token"] = f_response["next_token"]

return module.exit_json(**instance_refreshes_result)
except (BotoCoreError, ClientError) as e:
module.fail_json_aws(e, msg="Failed to describe InstanceRefreshes")
module.exit_json(changed=False, **instance_refreshes_result)
except AnsibleAutoScalingError as e:
module.fail_json_aws(e, msg=f"Failed to describe InstanceRefreshes: {e}")


def main():
Expand All @@ -210,7 +216,7 @@ def main():
supports_check_mode=True,
)

autoscaling = module.client("autoscaling", retry_decorator=AWSRetry.jittered_backoff(retries=10))
autoscaling = module.client("autoscaling")
find_asg_instance_refreshes(autoscaling, module)


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
---
- name: Test getting info for an ASG name
autoscaling_instance_refresh_info:
name: "{{ asg_name }}"
register: output

- name: Assert that the correct number of records are returned
assert:
that:
- output.instance_refreshes | map(attribute='instance_refresh_id') | unique | length == 7

- name: Test using fake refresh ID
autoscaling_instance_refresh_info:
name: "{{ asg_name }}"
ids: ['0e367f58-blabla-bla-bla-ca870dc5dbfe']
register: output

- name: Assert that no record is returned
assert:
that:
- output.instance_refreshes | length == 0

- name: Test using a real refresh ID
autoscaling_instance_refresh_info:
name: "{{ asg_name }}"
ids: [ '{{ refreshout.instance_refreshes.instance_refresh_id }}' ]
register: output

- name: Assert that the correct record is returned
assert:
that:
- output.instance_refreshes | length == 1

- name: Test getting info for an ASG name which doesn't exist
autoscaling_instance_refresh_info:
name: n0n3x1stentname27b
ignore_errors: true
register: output

- name: Assert that module failed to return record
assert:
that:
- "'Failed to describe InstanceRefreshes: An error occurred (ValidationError) when calling the DescribeInstanceRefreshes operation: AutoScalingGroup name not found - AutoScalingGroup n0n3x1stentname27b not found' in output.msg"

- name: Retrieve instance refresh info
autoscaling_instance_refresh_info:
name: "{{ asg_name }}"
register: output

- name: Assert that the correct number of records are returned
assert:
that:
- output.instance_refreshes | length == 7

- name: Retrieve instance refresh info using next_token
autoscaling_instance_refresh_info:
name: "{{ asg_name }}"
next_token: "fake-token-123"
ignore_errors: true
register: output

- name: Assert that valid message with fake-token is returned
assert:
that:
- '"Failed to describe InstanceRefreshes: An error occurred (InvalidNextToken) when calling the DescribeInstanceRefreshes operation: The token ''********'' is invalid." in output.msg'

- name: Retrieve instance refresh info using max_records
autoscaling_instance_refresh_info:
name: "{{ asg_name }}"
max_records: 1
register: output_with_token

- name: Assert that max records=1 returns no more than one record
assert:
that:
- output_with_token.instance_refreshes | length == 1

- name: Retrieve instance refresh using valid token
autoscaling_instance_refresh_info:
name: "{{ asg_name }}"
next_token: "{{ output_with_token.next_token }}"
register: output

- name: Assert that valid message with real-token is returned
assert:
that:
- output.instance_refreshes | length == 6

- name: Test using both real nextToken and max_records=1
autoscaling_instance_refresh_info:
name: "{{ asg_name }}"
max_records: 1
next_token: "{{ output_with_token.next_token }}"
register: output

- name: Assert that only one instance refresh is returned
assert:
that:
- output.instance_refreshes | length == 1
Loading

0 comments on commit d59fa93

Please sign in to comment.