From b52a8b8d380324f48379e4e4f405c1e5373a21ed Mon Sep 17 00:00:00 2001 From: Kevin Masseix Date: Fri, 1 Jul 2022 12:38:07 +0200 Subject: [PATCH] [Plugin elasticache_info] includes the description of each replication group (#646) [Plugin elasticache_info] includes the description of each replication group SUMMARY This pull request add the description of the corresponding Elasticache replication group into each described cache cluster. ISSUE TYPE Feature Pull Request COMPONENT NAME elasticache_info ADDITIONAL INFORMATION Actually the only way to describe Elasticache replication groups with Ansible is to use the dynamic inventory. However, exposing the informations about each replication group could be useful in other contexts. In my case, I am generating a few .env files and I need to include some Elasticache replication group primary endpoint and reader endpoint. Since the elasticache_info plugin already exist and already retrieves the appropriate replication_group_id, I believe that this plugin should also retrieve the replication group description when it exists. OTHER REMARKS CONCERNING THIS PULL REQUEST Each provided description is copy/pasted from the official boto3 documentation which is distributed under an Apache 2.0 licence. I did it for consistency reason between and also because I am not a native speaker. Also the provided description is incomplete : I only documented the fields that I was able to retrieve from my current AWS clusters. Boto3 documentation of the "describe_replication_groups" function : https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/elasticache.html#ElastiCache.Client.describe_replication_groups Moreover this pull request might violates the "integration tests" rule : I didn't find any integration test for the elasticache_info plugin and I don't know why no integration test have been made. Reviewed-by: Mark Chappell (cherry picked from commit d7e36178c674dc902af4925726a38483d7293f0a) --- ...646-elasticache_info-replication_group.yml | 2 + plugins/modules/elasticache_info.py | 300 +++++++++++++++--- tests/integration/targets/elasticache/aliases | 1 + .../targets/elasticache/tasks/main.yml | 75 +++++ .../targets/legacy_missing_tests/aliases | 1 - 5 files changed, 329 insertions(+), 50 deletions(-) create mode 100644 changelogs/fragments/646-elasticache_info-replication_group.yml diff --git a/changelogs/fragments/646-elasticache_info-replication_group.yml b/changelogs/fragments/646-elasticache_info-replication_group.yml new file mode 100644 index 00000000000..3c96fa231c3 --- /dev/null +++ b/changelogs/fragments/646-elasticache_info-replication_group.yml @@ -0,0 +1,2 @@ +minor_changes: +- elasticache_info - added ``replication_group`` to the returned information for an elasticache cluster (https://github.com/ansible-collections/community.aws/pull/646). diff --git a/plugins/modules/elasticache_info.py b/plugins/modules/elasticache_info.py index c9fa9cc4502..154567ac581 100644 --- a/plugins/modules/elasticache_info.py +++ b/plugins/modules/elasticache_info.py @@ -11,19 +11,17 @@ short_description: Retrieve information for AWS ElastiCache clusters version_added: 1.0.0 description: - - Retrieve information from AWS ElastiCache clusters + - Retrieve information from AWS ElastiCache clusters. options: name: description: - The name of an ElastiCache cluster. type: str - author: - Will Thames (@willthames) extends_documentation_fragment: -- amazon.aws.aws -- amazon.aws.ec2 - + - amazon.aws.aws + - amazon.aws.ec2 ''' EXAMPLES = ''' @@ -37,170 +35,354 @@ RETURN = ''' elasticache_clusters: - description: List of ElastiCache clusters + description: List of ElastiCache clusters. returned: always - type: complex + type: list + elements: dict contains: + arn: + description: ARN of the cache cluster. + returned: always + type: str + sample: 'arn:aws:elasticache:us-east-1:123456789012:cluster:ansible-test' auto_minor_version_upgrade: - description: Whether to automatically upgrade to minor versions + description: Whether to automatically upgrade to minor versions. returned: always type: bool sample: true cache_cluster_create_time: - description: Date and time cluster was created + description: Date and time cluster was created. returned: always type: str sample: '2017-09-15T05:43:46.038000+00:00' cache_cluster_id: - description: ID of the cache cluster + description: ID of the cache cluster. returned: always type: str sample: abcd-1234-001 cache_cluster_status: - description: Status of ElastiCache cluster + description: Status of ElastiCache cluster. returned: always type: str sample: available cache_node_type: - description: Instance type of ElastiCache nodes + description: Instance type of ElastiCache nodes. returned: always type: str sample: cache.t2.micro cache_nodes: - description: List of ElastiCache nodes in the cluster + description: List of ElastiCache nodes in the cluster. returned: always - type: complex + type: list + elements: dict contains: cache_node_create_time: - description: Date and time node was created + description: Date and time node was created. returned: always type: str sample: '2017-09-15T05:43:46.038000+00:00' cache_node_id: - description: ID of the cache node + description: ID of the cache node. returned: always type: str sample: '0001' cache_node_status: - description: Status of the cache node + description: Status of the cache node. returned: always type: str sample: available customer_availability_zone: - description: Availability Zone in which the cache node was created + description: Availability Zone in which the cache node was created. returned: always type: str sample: ap-southeast-2b endpoint: - description: Connection details for the cache node + description: Connection details for the cache node. returned: always - type: complex + type: dict contains: address: - description: URL of the cache node endpoint + description: URL of the cache node endpoint. returned: always type: str sample: abcd-1234-001.bgiz2p.0001.apse2.cache.amazonaws.com port: - description: Port of the cache node endpoint + description: Port of the cache node endpoint. returned: always type: int sample: 6379 parameter_group_status: - description: Status of the Cache Parameter Group + description: Status of the Cache Parameter Group. returned: always type: str sample: in-sync cache_parameter_group: - description: Contents of the Cache Parameter Group + description: Contents of the Cache Parameter Group. returned: always - type: complex + type: dict contains: cache_node_ids_to_reboot: - description: Cache nodes which need to be rebooted for parameter changes to be applied + description: Cache nodes which need to be rebooted for parameter changes to be applied. returned: always type: list + elements: str sample: [] cache_parameter_group_name: - description: Name of the cache parameter group + description: Name of the cache parameter group. returned: always type: str sample: default.redis3.2 parameter_apply_status: - description: Status of parameter updates + description: Status of parameter updates. returned: always type: str sample: in-sync cache_security_groups: - description: Security Groups used by the cache + description: Security Groups used by the cache. returned: always type: list + elements: str sample: - 'sg-abcd1234' cache_subnet_group_name: - description: ElastiCache Subnet Group used by the cache + description: ElastiCache Subnet Group used by the cache. returned: always type: str sample: abcd-subnet-group client_download_landing_page: - description: URL of client download web page + description: URL of client download web page. returned: always type: str sample: 'https://console.aws.amazon.com/elasticache/home#client-download:' engine: - description: Engine used by ElastiCache + description: Engine used by ElastiCache. returned: always type: str sample: redis engine_version: - description: Version of ElastiCache engine + description: Version of ElastiCache engine. returned: always type: str sample: 3.2.4 notification_configuration: - description: Configuration of notifications + description: Configuration of notifications. returned: if notifications are enabled - type: complex + type: dict contains: topic_arn: - description: ARN of notification destination topic + description: ARN of notification destination topic. returned: if notifications are enabled type: str sample: arn:aws:sns:*:123456789012:my_topic topic_name: - description: Name of notification destination topic + description: Name of notification destination topic. returned: if notifications are enabled type: str sample: MyTopic num_cache_nodes: - description: Number of Cache Nodes + description: Number of Cache Nodes. returned: always type: int sample: 1 pending_modified_values: - description: Values that are pending modification + description: Values that are pending modification. returned: always - type: complex - contains: {} + type: dict preferred_availability_zone: - description: Preferred Availability Zone + description: Preferred Availability Zone. returned: always type: str sample: ap-southeast-2b preferred_maintenance_window: - description: Time slot for preferred maintenance window + description: Time slot for preferred maintenance window. returned: always type: str sample: sat:12:00-sat:13:00 + replication_group: + description: Informations about the associated replication group. + version_added: 4.1.0 + returned: if replication is enabled + type: dict + contains: + arn: + description: The ARN (Amazon Resource Name) of the replication group. + returned: always + type: str + at_rest_encryption_enabled: + description: A flag that enables encryption at-rest when set to true. + returned: always + type: bool + auth_token_enabled: + description: A flag that enables using an AuthToken (password) when issuing Redis commands. + returned: always + type: bool + automatic_failover: + description: Indicates the status of automatic failover for this Redis replication group. + returned: always + type: str + sample: enabled + cache_node_type: + description: The name of the compute and memory capacity node type for each node in the replication group. + returned: always + type: str + sample: cache.t3.medium + cluster_enabled: + description: A flag indicating whether or not this replication group is cluster enabled. + returned: always + type: bool + description: + description: The user supplied description of the replication group. + returned: always + type: str + global_replication_group_info: + description: The name of the Global datastore and role of this replication group in the Global datastore. + returned: always + type: dict + contains: + global_replication_group_id: + description: The name of the Global datastore. + returned: always + type: str + global_replication_group_member_role: + description: The role of the replication group in a Global datastore. Can be primary or secondary. + returned: always + type: str + kms_key_id: + description: The ID of the KMS key used to encrypt the disk in the cluster. + returned: always + type: str + member_clusters: + description: The names of all the cache clusters that are part of this replication group. + returned: always + type: list + elements: str + multi_az: + description: A flag indicating if you have Multi-AZ enabled to enhance fault tolerance. + returned: always + type: str + sample: enabled + node_groups: + description: A list of node groups in this replication group. + returned: always + type: list + elements: dict + contains: + node_group_id: + description: The identifier for the node group (shard). + returned: always + type: str + node_group_members: + description: A list containing information about individual nodes within the node group (shard). + returned: always + type: list + elements: dict + contains: + cache_cluster_id: + description: The ID of the cluster to which the node belongs. + returned: always + type: str + cache_node_id: + description: The ID of the node within its cluster. + returned: always + type: str + current_role: + description: The role that is currently assigned to the node - primary or replica. + returned: always + type: str + sample: primary + preferred_availability_zone: + description: The name of the Availability Zone in which the node is located. + returned: always + type: str + read_endpoint: + description: The information required for client programs to connect to a node for read operations. + returned: always + type: list + elements: dict + contains: + address: + description: The DNS hostname of the cache node. + returned: always + type: str + port: + description: The port number that the cache engine is listening on. + returned: always + type: int + sample: 6379 + primary_endpoint: + description: The endpoint of the primary node in this node group (shard). + returned: always + type: list + elements: dict + contains: + address: + description: The DNS hostname of the cache node. + returned: always + type: str + port: + description: The port number that the cache engine is listening on. + returned: always + type: int + sample: 6379 + reader_endpoint: + description: The endpoint of the cache node. + returned: always + type: dict + contains: + address: + description: The DNS hostname of the cache node. + returned: always + type: str + port: + description: The port number that the cache engine is listening on. + returned: always + type: int + sample: 6379 + status: + description: The current state of this replication group - C(creating), C(available), C(modifying), C(deleting). + returned: always + type: str + sample: available + pending_modified_values: + description: A group of settings to be applied to the replication group, either immediately or during the next maintenance window. + returned: always + type: dict + replication_group_id: + description: Replication Group Id. + returned: always + type: str + sample: replication-001 + snapshot_retention_limit: + description: The number of days for which ElastiCache retains automatic cluster snapshots before deleting them. + returned: always + type: int + snapshot_window: + description: The daily time range (in UTC) during which ElastiCache begins taking a daily snapshot of your node group (shard). + returned: always + type: str + sample: 07:00-09:00 + snapshotting_cluster_id: + description: The cluster ID that is used as the daily snapshot source for the replication group. + returned: always + type: str + status: + description: The current state of this replication group - C(creating), C(available), C(modifying), C(deleting), C(create-failed), C(snapshotting) + returned: always + type: str + transit_encryption_enabled: + description: A flag that enables in-transit encryption when set to C(true). + returned: always + type: bool replication_group_id: - description: Replication Group Id - returned: always + description: Replication Group Id. + returned: if replication is enabled type: str sample: replication-001 security_groups: - description: List of Security Groups associated with ElastiCache + description: List of Security Groups associated with ElastiCache. returned: always - type: complex + type: list + elements: dict contains: security_group_id: description: Security Group ID @@ -215,8 +397,7 @@ tags: description: Tags applied to the ElastiCache cluster returned: always - type: complex - contains: {} + type: dict sample: Application: web Environment: test @@ -248,6 +429,16 @@ def describe_cache_clusters_with_backoff(client, cluster_id=None): return response['CacheClusters'] +@AWSRetry.exponential_backoff() +def describe_replication_group_with_backoff(client, replication_group_id): + try: + response = client.describe_replication_groups(ReplicationGroupId=replication_group_id) + except is_boto3_error_code('ReplicationGroupNotFoundFault'): + return None + + return response['ReplicationGroups'][0] + + @AWSRetry.exponential_backoff() def get_elasticache_tags_with_backoff(client, cluster_id): return client.list_tags_for_resource(ResourceName=cluster_id)['TagList'] @@ -284,6 +475,17 @@ def get_elasticache_clusters(client, module): module.fail_json_aws(e, msg="Couldn't get tags for cluster %s") cluster['tags'] = boto3_tag_list_to_ansible_dict(tags) + + if cluster.get('replication_group_id', None): + try: + replication_group = describe_replication_group_with_backoff(client, cluster['replication_group_id']) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, msg="Couldn't obtain replication group info") + + if replication_group is not None: + replication_group = camel_dict_to_snake_dict(replication_group) + cluster['replication_group'] = replication_group + results.append(cluster) return results diff --git a/tests/integration/targets/elasticache/aliases b/tests/integration/targets/elasticache/aliases index 5ee1d22add8..2d3ad74d70e 100644 --- a/tests/integration/targets/elasticache/aliases +++ b/tests/integration/targets/elasticache/aliases @@ -3,3 +3,4 @@ unstable cloud/aws +elasticache_info diff --git a/tests/integration/targets/elasticache/tasks/main.yml b/tests/integration/targets/elasticache/tasks/main.yml index 915c9ddf03c..31ae3d9cf77 100644 --- a/tests/integration/targets/elasticache/tasks/main.yml +++ b/tests/integration/targets/elasticache/tasks/main.yml @@ -95,6 +95,81 @@ - elasticache_redis_new.elasticache.data.Engine == "redis" - elasticache_redis_new.elasticache.data.SecurityGroups.0.SecurityGroupId == elasticache_redis_sg.group_id + - name: Describe all Elasticache clusters + elasticache_info: {} + register: elasticache_info + + - assert: + that: + - '"elasticache_clusters" in elasticache_info' + - elasticache_info.elasticache_clusters | length >= 1 + + - name: Describe Elasticache Redis cluster + elasticache_info: + name: "{{ elasticache_redis.elasticache.name }}" + register: elasticache_info + + - assert: + that: + - '"elasticache_clusters" in elasticache_info' + - elasticache_info.elasticache_clusters | length == 1 + - '"arn" in elasticache_info.elasticache_clusters[0]' + - '"at_rest_encryption_enabled" in elasticache_info.elasticache_clusters[0]' + - '"auth_token_enabled" in elasticache_info.elasticache_clusters[0]' + - '"auto_minor_version_upgrade" in elasticache_info.elasticache_clusters[0]' + - '"cache_cluster_create_time" in elasticache_info.elasticache_clusters[0]' + - '"cache_cluster_id" in elasticache_info.elasticache_clusters[0]' + - '"cache_cluster_status" in elasticache_info.elasticache_clusters[0]' + - '"cache_node_type" in elasticache_info.elasticache_clusters[0]' + - '"cache_nodes" in elasticache_info.elasticache_clusters[0]' + - '"cache_parameter_group" in elasticache_info.elasticache_clusters[0]' + - '"cache_security_groups" in elasticache_info.elasticache_clusters[0]' + - '"cache_subnet_group_name" in elasticache_info.elasticache_clusters[0]' + - '"client_download_landing_page" in elasticache_info.elasticache_clusters[0]' + - '"engine" in elasticache_info.elasticache_clusters[0]' + - '"engine_version" in elasticache_info.elasticache_clusters[0]' + - '"num_cache_nodes" in elasticache_info.elasticache_clusters[0]' + - '"pending_modified_values" in elasticache_info.elasticache_clusters[0]' + - '"preferred_availability_zone" in elasticache_info.elasticache_clusters[0]' + - '"preferred_maintenance_window" in elasticache_info.elasticache_clusters[0]' + - '"security_groups" in elasticache_info.elasticache_clusters[0]' + - '"snapshot_retention_limit" in elasticache_info.elasticache_clusters[0]' + - '"snapshot_window" in elasticache_info.elasticache_clusters[0]' + - '"tags" in elasticache_info.elasticache_clusters[0]' + - '"transit_encryption_enabled" in elasticache_info.elasticache_clusters[0]' + - elasticache_info.elasticache_clusters[0].arn.startswith("arn:aws") + - elasticache_info.elasticache_clusters[0].at_rest_encryption_enabled == False + - elasticache_info.elasticache_clusters[0].auth_token_enabled == False + - elasticache_info.elasticache_clusters[0].auto_minor_version_upgrade == True + - elasticache_info.elasticache_clusters[0].cache_cluster_status == "available" + - elasticache_info.elasticache_clusters[0].cache_node_type == "cache.t3.micro" + - elasticache_info.elasticache_clusters[0].cache_subnet_group_name == elasticache_subnet_group_name + - elasticache_info.elasticache_clusters[0].engine == "redis" + - elasticache_info.elasticache_clusters[0].num_cache_nodes == 1 + - elasticache_info.elasticache_clusters[0].snapshot_retention_limit == 0 + - elasticache_info.elasticache_clusters[0].transit_encryption_enabled == False + - elasticache_info.elasticache_clusters[0].cache_nodes | length == 1 + - '"cache_node_create_time" in elasticache_info.elasticache_clusters[0].cache_nodes[0]' + - '"cache_node_id" in elasticache_info.elasticache_clusters[0].cache_nodes[0]' + - '"cache_node_status" in elasticache_info.elasticache_clusters[0].cache_nodes[0]' + - '"customer_availability_zone" in elasticache_info.elasticache_clusters[0].cache_nodes[0]' + - '"endpoint" in elasticache_info.elasticache_clusters[0].cache_nodes[0]' + - '"parameter_group_status" in elasticache_info.elasticache_clusters[0].cache_nodes[0]' + - elasticache_info.elasticache_clusters[0].cache_nodes[0].cache_node_status == "available" + - elasticache_info.elasticache_clusters[0].cache_nodes[0].parameter_group_status == "in-sync" + - '"address" in elasticache_info.elasticache_clusters[0].cache_nodes[0].endpoint' + - '"port" in elasticache_info.elasticache_clusters[0].cache_nodes[0].endpoint' + - '"cache_node_ids_to_reboot" in elasticache_info.elasticache_clusters[0].cache_parameter_group' + - '"cache_parameter_group_name" in elasticache_info.elasticache_clusters[0].cache_parameter_group' + - '"parameter_apply_status" in elasticache_info.elasticache_clusters[0].cache_parameter_group' + - elasticache_info.elasticache_clusters[0].cache_parameter_group.cache_node_ids_to_reboot | length == 0 + - elasticache_info.elasticache_clusters[0].cache_parameter_group.parameter_apply_status == "in-sync" + - elasticache_info.elasticache_clusters[0].security_groups | length == 1 + - '"security_group_id" in elasticache_info.elasticache_clusters[0].security_groups[0]' + - '"status" in elasticache_info.elasticache_clusters[0].security_groups[0]' + - elasticache_info.elasticache_clusters[0].security_groups[0].security_group_id == elasticache_redis_sg.group_id + - elasticache_info.elasticache_clusters[0].security_groups[0].status == "active" + always: # == Cleanup == diff --git a/tests/integration/targets/legacy_missing_tests/aliases b/tests/integration/targets/legacy_missing_tests/aliases index 7cd81fbc77a..a7c0840a2b7 100644 --- a/tests/integration/targets/legacy_missing_tests/aliases +++ b/tests/integration/targets/legacy_missing_tests/aliases @@ -25,7 +25,6 @@ ec2_customer_gateway_info ec2_snapshot_copy ec2_win_password ecs_attribute -elasticache_info elasticache_parameter_group elasticache_snapshot iam_mfa_device_info