Repository: ansible-collections/amazon.aws

Issue:
route53_info HealthCheck key error
### Summary

When getting the status of a health check ansible returns KeyError: 'HealthCheck'
```
community.aws.route53_info: 
      query: health_check
      hosted_zone_id: ZXXXXXXX
      health_check_method: status
      health_check_id: <redacted Id>
```

Boto3 client.get_health_check_status() doesn't appear to return HealthCheck

Commenting out the line results['health_check'] = camel_dict_to_snake_dict(results['HealthCheck']) returns a HealthCheckObservations dict

### Issue Type

Bug Report

### Component Name

route53_info

### Ansible Version

```console (paste below)
$ ansible --version

```
ansible [core 2.14.3]
  python version = 3.10.6
  jinja version = 3.1.2
  libyaml = True

### Collection Versions

```console (paste below)
$ ansible-galaxy collection list
```
Collection        Version
----------------- -------
community.general 5.8.0

# 
Collection                    Version
----------------------------- -------
amazon.aws                    5.2.0
ansible.netcommon             4.1.0
ansible.posix                 1.5.1
ansible.utils                 2.9.0
ansible.windows               1.13.0
arista.eos                    6.0.0
awx.awx                       21.12.0
azure.azcollection            1.14.0
check_point.mgmt              4.0.0
chocolatey.chocolatey         1.4.0
cisco.aci                     2.4.0
cisco.asa                     4.0.0
cisco.dnac                    6.6.3
cisco.intersight              1.0.23
cisco.ios                     4.3.1
cisco.iosxr                   4.1.0
cisco.ise                     2.5.12
cisco.meraki                  2.15.1
cisco.mso                     2.2.1
cisco.nso                     1.0.3
cisco.nxos                    4.1.0
cisco.ucs                     1.8.0
cloud.common                  2.1.2
cloudscale_ch.cloud           2.2.4
community.aws                 5.2.0
community.azure               2.0.0
community.ciscosmb            1.0.5
community.crypto              2.11.0
community.digitalocean        1.23.0
community.dns                 2.5.1
community.docker              3.4.2
community.fortios             1.0.0
community.general             6.4.0
community.google              1.0.0
community.grafana             1.5.4
community.hashi_vault         4.1.0
community.hrobot              1.7.0
community.libvirt             1.2.0
community.mongodb             1.5.1
community.mysql               3.6.0
community.network             5.0.0
community.okd                 2.3.0
community.postgresql          2.3.2
community.proxysql            1.5.1
community.rabbitmq            1.2.3
community.routeros            2.7.0
community.sap                 1.0.0
community.sap_libs            1.4.0
community.skydive             1.0.0
community.sops                1.6.1
community.vmware              3.4.0
community.windows             1.12.0
community.zabbix              1.9.2
containers.podman             1.10.1
cyberark.conjur               1.2.0
cyberark.pas                  1.0.17
dellemc.enterprise_sonic      2.0.0
dellemc.openmanage            6.3.0
dellemc.os10                  1.1.1
dellemc.os6                   1.0.7
dellemc.os9                   1.0.4
dellemc.powerflex             1.5.0
dellemc.unity                 1.5.0
f5networks.f5_modules         1.22.1
fortinet.fortimanager         2.1.7
fortinet.fortios              2.2.2
frr.frr                       2.0.0
gluster.gluster               1.0.2
google.cloud                  1.1.2
grafana.grafana               1.1.1
hetzner.hcloud                1.10.0
hpe.nimble                    1.1.4
ibm.qradar                    2.1.0
ibm.spectrum_virtualize       1.11.0
infinidat.infinibox           1.3.12
infoblox.nios_modules         1.4.1
inspur.ispim                  1.3.0
inspur.sm                     2.3.0
junipernetworks.junos         4.1.0
kubernetes.core               2.4.0
lowlydba.sqlserver            1.3.1
mellanox.onyx                 1.0.0
netapp.aws                    21.7.0
netapp.azure                  21.10.0
netapp.cloudmanager           21.22.0
netapp.elementsw              21.7.0
netapp.ontap                  22.3.0
netapp.storagegrid            21.11.1
netapp.um_info                21.8.0
netapp_eseries.santricity     1.4.0
netbox.netbox                 3.11.0
ngine_io.cloudstack           2.3.0
ngine_io.exoscale             1.0.0
ngine_io.vultr                1.1.3
openstack.cloud               1.10.0
openvswitch.openvswitch       2.1.0
ovirt.ovirt                   2.4.1
purestorage.flasharray        1.17.0
purestorage.flashblade        1.10.0
purestorage.fusion            1.3.0
sensu.sensu_go                1.13.2
splunk.es                     2.1.0
t_systems_mms.icinga_director 1.32.0
theforeman.foreman            3.9.0
vmware.vmware_rest            2.2.0
vultr.cloud                   1.7.0
vyos.vyos                     4.0.0
wti.remote                    1.0.4

### AWS SDK versions

```console (paste below)
$ pip show boto boto3 botocore
```
Name: boto
Version: 2.49.0
Summary: Amazon Web Services Library
Home-page: https://github.com/boto/boto/
Author: Mitch Garnaat
Author-email: mitch@garnaat.com
License: MIT
Location: redacted
Requires:
Required-by:
---
Name: boto3
Version: 1.26.81
Summary: The AWS SDK for Python
Home-page: https://github.com/boto/boto3
Author: Amazon Web Services
Author-email:
License: Apache License 2.0
Location: redacted
Requires: botocore, jmespath, s3transfer
Required-by:
---
Name: botocore
Version: 1.29.81
Summary: Low-level, data-driven core of boto 3.
Home-page: https://github.com/boto/botocore
Author: Amazon Web Services
Author-email:
License: Apache License 2.0
Location: redacted
Requires: jmespath, python-dateutil, urllib3
Required-by: boto3, s3transfer

### Configuration

```console (paste below)
$ ansible-config dump --only-changed
CONFIG_FILE() = None
```


### OS / Environment

Ubuntu 20.04 and AWX 21.12.0 (installed via awx-operator) 

### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)
Commenting out the line results['health_check'] = camel_dict_to_snake_dict(results['HealthCheck']) returns a HealthCheckObservations dict
```


### Expected Results

Expected health http status code to be returned

### Actual Results

```console (paste below)

```
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: KeyError: 'HealthCheck'

### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:

Resolving patch:
diff --git a/plugins/modules/route53_info.py b/plugins/modules/route53_info.py
--- a/plugins/modules/route53_info.py
+++ b/plugins/modules/route53_info.py
@@ -381,7 +381,7 @@
     version_added: 4.1.0
     version_added_collection: community.aws
 health_check:
-    description: A dict of Route53 health check details returned by get_health_check_status in boto3.
+    description: A dict of Route53 health check details returned by get_health_check in boto3.
     type: dict
     returned: when I(query=health_check) and I(health_check_method=details)
     contains:
@@ -451,6 +451,33 @@
                     sample: HTTPS
     version_added: 4.1.0
     version_added_collection: community.aws
+health_check_observations:
+    description: A dict of Route53 health check details returned by get_health_check_status and get_health_check_last_failure_reason in boto3.
+    type: list
+    elements: dict
+    returned: when I(query=health_check) and I(health_check_method=status) or I(health_check_method=failure_reason)
+    contains:
+        ip_address:
+            description: The IP address of the Amazon Route 53 health checker that provided the failure reason in StatusReport.
+            type: str
+            sample: '12.345.67.89'
+        region:
+            description: The region of the Amazon Route 53 health checker that provided the status in StatusReport.
+            type: str
+            sample: 'us-west-1'
+        status_report:
+            description: A complex type that contains the last failure reason and the time of the failed health check.
+            type: dict
+            contains:
+                checked_time:
+                    description: The date and time that the health checker performed the health check in ISO 8601 format and Coordinated Universal Time (UTC).
+                    type: str
+                    sample: '2023-03-08T23:10:08.452000+00:00'
+                status:
+                    description: A description of the status of the health check endpoint as reported by one of the Amazon Route 53 health checkers.
+                    type: str
+                    sample: 'Failure: Resolved IP: 12.345.67.89. The connection was closed by the endpoint.'
+    version_added: 5.4.0
 ResourceRecordSets:
     description: A deprecated CamelCased list of resource record sets returned by list_resource_record_sets in boto3. \
                  This list contains same elements/parameters as it's snake_cased version mentioned above. \
@@ -487,7 +514,7 @@
     elements: dict
     returned: when I(query=reusable_delegation_set)
 HealthCheck:
-    description: A deprecated CamelCased dict of Route53 health check details returned by get_health_check_status in boto3. \
+    description: A deprecated CamelCased dict of Route53 health check details returned by get_health_check in boto3. \
                  This dict contains same elements/parameters as it's snake_cased version mentioned above. \
                  This field is deprecated and will be removed in 6.0.0 version release.
     type: dict
@@ -627,6 +654,7 @@ def get_count():
 
 def get_health_check():
     params = dict()
+    results = dict()
 
     if not module.params.get('health_check_id'):
         module.fail_json(msg="health_check_id is required")
@@ -635,16 +663,26 @@ def get_health_check():
 
     if module.params.get('health_check_method') == 'details':
         results = client.get_health_check(**params)
+        results["health_check"] = camel_dict_to_snake_dict(results["HealthCheck"])
+        module.deprecate(
+            "The 'CamelCase' return values with key 'HealthCheck' is deprecated \
+                and will be replaced by 'snake_case' return values with key 'health_check'. \
+                Both case values are returned for now.",
+            date="2025-01-01",
+            collection_name="amazon.aws",
+        )
+
     elif module.params.get('health_check_method') == 'failure_reason':
-        results = client.get_health_check_last_failure_reason(**params)
-    elif module.params.get('health_check_method') == 'status':
-        results = client.get_health_check_status(**params)
+        response = client.get_health_check_last_failure_reason(**params)
+        results["health_check_observations"] = [
+            camel_dict_to_snake_dict(health_check) for health_check in response["HealthCheckObservations"]
+        ]
 
-    results['health_check'] = camel_dict_to_snake_dict(results['HealthCheck'])
-    module.deprecate("The 'CamelCase' return values with key 'HealthCheck' is deprecated and \
-                    will be replaced by 'snake_case' return values with key 'health_check'. \
-                    Both case values are returned for now.",
-                     date='2025-01-01', collection_name='amazon.aws')
+    elif module.params.get('health_check_method') == 'status':
+        response = client.get_health_check_status(**params)
+        results["health_check_observations"] = [
+            camel_dict_to_snake_dict(health_check) for health_check in response["HealthCheckObservations"]
+        ]
 
     return results
 
@@ -827,7 +865,7 @@ def main():
     try:
         results = invocations[module.params.get('query')]()
     except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
-        module.fail_json(msg=to_native(e))
+        module.fail_json_aws(e, msg="Query failed")
 
     module.exit_json(**results)
 


Test patch:
diff --git a/tests/integration/targets/route53_health_check/tasks/main.yml b/tests/integration/targets/route53_health_check/tasks/main.yml
--- a/tests/integration/targets/route53_health_check/tasks/main.yml
+++ b/tests/integration/targets/route53_health_check/tasks/main.yml
@@ -1722,6 +1722,36 @@
       - create_check is changed
       - health_check_info.health_check.health_check_config.measure_latency == true
 
+  - pause:
+      seconds: 20
+
+  # test route53_info for health_check_method=status
+  - name: Get health check status
+    amazon.aws.route53_info:
+      query: health_check
+      health_check_id: "{{ create_check.health_check.id }}"
+      health_check_method: status
+    register: health_check_status_info
+
+  - assert:
+      that:
+      - health_check_status_info is not failed
+      - '"health_check_observations" in health_check_status_info'
+
+  # test route53_info for health_check_method=failure_reason
+  - name: Get health check failure_reason
+    amazon.aws.route53_info:
+      query: health_check
+      health_check_id: "{{ create_check.health_check.id }}"
+      health_check_method: failure_reason
+    register: health_check_failure_reason_info
+
+  - assert:
+      that:
+      - health_check_failure_reason_info is not failed
+      - '"health_check_observations" in health_check_failure_reason_info'
+
+
   - name: 'Update above health check to disable latency graphs - immutable, no change'
     route53_health_check:
       state: present