From abdcca84e5e59911e7a8cc53614be24b86a8ce55 Mon Sep 17 00:00:00 2001 From: Alina Buzachis Date: Thu, 28 Oct 2021 12:58:02 +0200 Subject: [PATCH] Rename rds_snapshot to rds_instance_snapshot (#783) Rename rds_snapshot to rds_instance_snapshot SUMMARY Rename rds_snapshot to rds_instance_snapshot since rds_snapshot only handles snapshotting of DB instances. A new module for snapshotting RDS clusters will be added in a future PR. ISSUE TYPE New Module Pull Request COMPONENT NAME rds_snapshot Reviewed-by: Mark Chappell Reviewed-by: None This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/4c443723d0c9116caae6298473d3687b3f46c742 --- plugins/modules/rds_instance_snapshot.py | 346 ++++++++++++++++++ .../roles/rds_instance/tasks/test_states.yml | 2 +- 2 files changed, 347 insertions(+), 1 deletion(-) create mode 100644 plugins/modules/rds_instance_snapshot.py diff --git a/plugins/modules/rds_instance_snapshot.py b/plugins/modules/rds_instance_snapshot.py new file mode 100644 index 00000000000..45915082739 --- /dev/null +++ b/plugins/modules/rds_instance_snapshot.py @@ -0,0 +1,346 @@ +#!/usr/bin/python +# Copyright (c) 2014 Ansible Project +# Copyright (c) 2017, 2018, 2019 Will Thames +# Copyright (c) 2017, 2018 Michael De La Rue +# 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: rds_instance_snapshot +version_added: 1.0.0 +short_description: manage Amazon RDS snapshots. +description: + - Creates or deletes RDS snapshots. +options: + state: + description: + - Specify the desired state of the snapshot. + default: present + choices: [ 'present', 'absent'] + type: str + db_snapshot_identifier: + description: + - The snapshot to manage. + required: true + aliases: + - id + - snapshot_id + type: str + db_instance_identifier: + description: + - Database instance identifier. Required when state is present. + aliases: + - instance_id + type: str + wait: + description: + - Whether or not to wait for snapshot creation or deletion. + type: bool + default: 'no' + wait_timeout: + description: + - how long before wait gives up, in seconds. + default: 300 + type: int + tags: + description: + - tags dict to apply to a snapshot. + type: dict + purge_tags: + description: + - whether to remove tags not present in the C(tags) parameter. + default: True + type: bool +author: + - "Will Thames (@willthames)" + - "Michael De La Rue (@mikedlr)" +extends_documentation_fragment: +- amazon.aws.aws +- amazon.aws.ec2 + +''' + +EXAMPLES = ''' +- name: Create snapshot + community.aws.rds_instance_snapshot: + db_instance_identifier: new-database + db_snapshot_identifier: new-database-snapshot + +- name: Delete snapshot + community.aws.rds_instance_snapshot: + db_snapshot_identifier: new-database-snapshot + state: absent +''' + +RETURN = ''' +allocated_storage: + description: How much storage is allocated in GB. + returned: always + type: int + sample: 20 +availability_zone: + description: Availability zone of the database from which the snapshot was created. + returned: always + type: str + sample: us-west-2a +db_instance_identifier: + description: Database from which the snapshot was created. + returned: always + type: str + sample: ansible-test-16638696 +db_snapshot_arn: + description: Amazon Resource Name for the snapshot. + returned: always + type: str + sample: arn:aws:rds:us-west-2:123456789012:snapshot:ansible-test-16638696-test-snapshot +db_snapshot_identifier: + description: Name of the snapshot. + returned: always + type: str + sample: ansible-test-16638696-test-snapshot +dbi_resource_id: + description: The identifier for the source DB instance, which can't be changed and which is unique to an AWS Region. + returned: always + type: str + sample: db-MM4P2U35RQRAMWD3QDOXWPZP4U +encrypted: + description: Whether the snapshot is encrypted. + returned: always + type: bool + sample: false +engine: + description: Engine of the database from which the snapshot was created. + returned: always + type: str + sample: mariadb +engine_version: + description: Version of the database from which the snapshot was created. + returned: always + type: str + sample: 10.2.21 +iam_database_authentication_enabled: + description: Whether IAM database authentication is enabled. + returned: always + type: bool + sample: false +instance_create_time: + description: Creation time of the instance from which the snapshot was created. + returned: always + type: str + sample: '2019-06-15T10:15:56.221000+00:00' +license_model: + description: License model of the database. + returned: always + type: str + sample: general-public-license +master_username: + description: Master username of the database. + returned: always + type: str + sample: test +option_group_name: + description: Option group of the database. + returned: always + type: str + sample: default:mariadb-10-2 +percent_progress: + description: How much progress has been made taking the snapshot. Will be 100 for an available snapshot. + returned: always + type: int + sample: 100 +port: + description: Port on which the database is listening. + returned: always + type: int + sample: 3306 +processor_features: + description: List of processor features of the database. + returned: always + type: list + sample: [] +snapshot_create_time: + description: Creation time of the snapshot. + returned: always + type: str + sample: '2019-06-15T10:46:23.776000+00:00' +snapshot_type: + description: How the snapshot was created (always manual for this module!). + returned: always + type: str + sample: manual +status: + description: Status of the snapshot. + returned: always + type: str + sample: available +storage_type: + description: Storage type of the database. + returned: always + type: str + sample: gp2 +tags: + description: Tags applied to the snapshot. + returned: always + type: complex + contains: {} +vpc_id: + description: ID of the VPC in which the DB lives. + returned: always + type: str + sample: vpc-09ff232e222710ae0 +''' + +try: + import botocore +except ImportError: + pass # protected by AnsibleAWSModule + +# import module snippets +from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import camel_dict_to_snake_dict, AWSRetry, compare_aws_tags +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import boto3_tag_list_to_ansible_dict, ansible_dict_to_boto3_tag_list + + +def get_snapshot(client, module, snapshot_id): + try: + response = client.describe_db_snapshots(DBSnapshotIdentifier=snapshot_id) + except client.exceptions.DBSnapshotNotFoundFault: + return None + except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: + module.fail_json_aws(e, msg="Couldn't get snapshot {0}".format(snapshot_id)) + return response['DBSnapshots'][0] + + +def snapshot_to_facts(client, module, snapshot): + try: + snapshot['Tags'] = boto3_tag_list_to_ansible_dict(client.list_tags_for_resource(ResourceName=snapshot['DBSnapshotArn'], + aws_retry=True)['TagList']) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, "Couldn't get tags for snapshot %s" % snapshot['DBSnapshotIdentifier']) + except KeyError: + module.fail_json(msg=str(snapshot)) + + return camel_dict_to_snake_dict(snapshot, ignore_list=['Tags']) + + +def wait_for_snapshot_status(client, module, db_snapshot_id, waiter_name): + if not module.params['wait']: + return + timeout = module.params['wait_timeout'] + try: + client.get_waiter(waiter_name).wait(DBSnapshotIdentifier=db_snapshot_id, + WaiterConfig=dict( + Delay=5, + MaxAttempts=int((timeout + 2.5) / 5) + )) + except botocore.exceptions.WaiterError as e: + if waiter_name == 'db_snapshot_deleted': + msg = "Failed to wait for DB snapshot {0} to be deleted".format(db_snapshot_id) + else: + msg = "Failed to wait for DB snapshot {0} to be available".format(db_snapshot_id) + module.fail_json_aws(e, msg=msg) + except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: + module.fail_json_aws(e, msg="Failed with an unexpected error while waiting for the DB cluster {0}".format(db_snapshot_id)) + + +def ensure_snapshot_absent(client, module): + snapshot_name = module.params.get('db_snapshot_identifier') + changed = False + + snapshot = get_snapshot(client, module, snapshot_name) + if snapshot and snapshot['Status'] != 'deleting': + try: + client.delete_db_snapshot(DBSnapshotIdentifier=snapshot_name) + changed = True + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, msg="trying to delete snapshot") + + # If we're not waiting for a delete to complete then we're all done + # so just return + if not snapshot or not module.params.get('wait'): + return dict(changed=changed) + try: + wait_for_snapshot_status(client, module, snapshot_name, 'db_snapshot_deleted') + return dict(changed=changed) + except client.exceptions.DBSnapshotNotFoundFault: + return dict(changed=changed) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, "awaiting snapshot deletion") + + +def ensure_tags(client, module, resource_arn, existing_tags, tags, purge_tags): + if tags is None: + return False + tags_to_add, tags_to_remove = compare_aws_tags(existing_tags, tags, purge_tags) + changed = bool(tags_to_add or tags_to_remove) + if tags_to_add: + try: + client.add_tags_to_resource(ResourceName=resource_arn, Tags=ansible_dict_to_boto3_tag_list(tags_to_add)) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, "Couldn't add tags to snapshot {0}".format(resource_arn)) + if tags_to_remove: + try: + client.remove_tags_from_resource(ResourceName=resource_arn, TagKeys=tags_to_remove) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, "Couldn't remove tags from snapshot {0}".format(resource_arn)) + return changed + + +def ensure_snapshot_present(client, module): + db_instance_identifier = module.params.get('db_instance_identifier') + snapshot_name = module.params.get('db_snapshot_identifier') + changed = False + snapshot = get_snapshot(client, module, snapshot_name) + if not snapshot: + try: + snapshot = client.create_db_snapshot(DBSnapshotIdentifier=snapshot_name, + DBInstanceIdentifier=db_instance_identifier)['DBSnapshot'] + changed = True + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, msg="trying to create db snapshot") + + if module.params.get('wait'): + wait_for_snapshot_status(client, module, snapshot_name, 'db_snapshot_available') + + existing_tags = boto3_tag_list_to_ansible_dict(client.list_tags_for_resource(ResourceName=snapshot['DBSnapshotArn'], + aws_retry=True)['TagList']) + desired_tags = module.params['tags'] + purge_tags = module.params['purge_tags'] + changed |= ensure_tags(client, module, snapshot['DBSnapshotArn'], existing_tags, desired_tags, purge_tags) + + snapshot = get_snapshot(client, module, snapshot_name) + + return dict(changed=changed, **snapshot_to_facts(client, module, snapshot)) + + +def main(): + + module = AnsibleAWSModule( + argument_spec=dict( + state=dict(choices=['present', 'absent'], default='present'), + db_snapshot_identifier=dict(aliases=['id', 'snapshot_id'], required=True), + db_instance_identifier=dict(aliases=['instance_id']), + wait=dict(type='bool', default=False), + wait_timeout=dict(type='int', default=300), + tags=dict(type='dict'), + purge_tags=dict(type='bool', default=True), + ), + required_if=[['state', 'present', ['db_instance_identifier']]] + ) + + client = module.client('rds', retry_decorator=AWSRetry.jittered_backoff(retries=10, catch_extra_error_codes=['DBSnapshotNotFound'])) + + if module.params['state'] == 'absent': + ret_dict = ensure_snapshot_absent(client, module) + else: + ret_dict = ensure_snapshot_present(client, module) + + module.exit_json(**ret_dict) + + +if __name__ == '__main__': + main() diff --git a/tests/integration/targets/rds_instance/roles/rds_instance/tasks/test_states.yml b/tests/integration/targets/rds_instance/roles/rds_instance/tasks/test_states.yml index 0321dc99d7c..dd13d55e164 100644 --- a/tests/integration/targets/rds_instance/roles/rds_instance/tasks/test_states.yml +++ b/tests/integration/targets/rds_instance/roles/rds_instance/tasks/test_states.yml @@ -144,7 +144,7 @@ always: - name: remove snapshot - rds_snapshot: + rds_instance_snapshot: db_snapshot_identifier: "{{ resource_prefix }}-test-snapshot" state: absent wait: false