#!/usr/bin/python # -*- coding: utf-8 -*- # 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) DOCUMENTATION = r""" --- module: rds_instance_snapshot version_added: 5.0.0 short_description: Manage Amazon RDS instance snapshots description: - Creates or deletes RDS snapshots. - This module was originally added to C(community.aws) in release 1.0.0. 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 creating a snapshot. aliases: - instance_id type: str source_db_snapshot_identifier: description: - The identifier of the source DB snapshot. - Required when copying a snapshot. - If the source snapshot is in the same AWS region as the copy, specify the snapshot's identifier. - If the source snapshot is in a different AWS region as the copy, specify the snapshot's ARN. aliases: - source_id - source_snapshot_id type: str version_added: 3.3.0 version_added_collection: community.aws source_region: description: - The region that contains the snapshot to be copied. type: str version_added: 3.3.0 version_added_collection: community.aws copy_tags: description: - Whether to copy all tags from I(source_db_snapshot_identifier) to I(db_instance_identifier). type: bool default: False version_added: 3.3.0 version_added_collection: community.aws wait: description: - Whether or not to wait for snapshot creation or deletion. type: bool default: False wait_timeout: description: - how long before wait gives up, in seconds. default: 300 type: int author: - "Will Thames (@willthames)" - "Michael De La Rue (@mikedlr)" - "Alina Buzachis (@alinabuzachis)" - "Joseph Torcasso (@jatorcasso)" extends_documentation_fragment: - amazon.aws.common.modules - amazon.aws.region.modules - amazon.aws.tags - amazon.aws.boto3 """ EXAMPLES = r""" - name: Create snapshot amazon.aws.rds_instance_snapshot: db_instance_identifier: new-database db_snapshot_identifier: new-database-snapshot register: snapshot - name: Copy snapshot from a different region and copy its tags amazon.aws.rds_instance_snapshot: id: new-database-snapshot-copy region: us-east-1 source_id: "{{ snapshot.db_snapshot_arn }}" source_region: us-east-2 copy_tags: true - name: Delete snapshot amazon.aws.rds_instance_snapshot: db_snapshot_identifier: new-database-snapshot state: absent """ RETURN = r""" 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: [] source_db_snapshot_identifier: description: The DB snapshot ARN that the DB snapshot was copied from. returned: when snapshot is a copy type: str sample: arn:aws:rds:us-west-2:123456789012:snapshot:ansible-test-16638696-test-snapshot-source version_added: 3.3.0 version_added_collection: community.aws 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 from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict from ansible_collections.amazon.aws.plugins.module_utils.botocore import get_boto3_client_method_parameters from ansible_collections.amazon.aws.plugins.module_utils.botocore import is_boto3_error_code # import module snippets from ansible_collections.amazon.aws.plugins.module_utils.modules import AnsibleAWSModule from ansible_collections.amazon.aws.plugins.module_utils.rds import arg_spec_to_rds_params from ansible_collections.amazon.aws.plugins.module_utils.rds import call_method from ansible_collections.amazon.aws.plugins.module_utils.rds import ensure_tags from ansible_collections.amazon.aws.plugins.module_utils.rds import get_rds_method_attribute from ansible_collections.amazon.aws.plugins.module_utils.rds import get_tags from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry from ansible_collections.amazon.aws.plugins.module_utils.tagging import ansible_dict_to_boto3_tag_list def get_snapshot(snapshot_id): try: snapshot = client.describe_db_snapshots(DBSnapshotIdentifier=snapshot_id)["DBSnapshots"][0] snapshot["Tags"] = get_tags(client, module, snapshot["DBSnapshotArn"]) except is_boto3_error_code("DBSnapshotNotFound"): return {} except ( botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError, ) as e: # pylint: disable=duplicate-except module.fail_json_aws(e, msg=f"Couldn't get snapshot {snapshot_id}") return snapshot def get_parameters(parameters, method_name): if method_name == "copy_db_snapshot": parameters["TargetDBSnapshotIdentifier"] = module.params["db_snapshot_identifier"] required_options = get_boto3_client_method_parameters(client, method_name, required=True) if any(parameters.get(k) is None for k in required_options): method_description = get_rds_method_attribute(method_name, module).operation_description module.fail_json(msg=f"To {method_description} requires the parameters: {*required_options, }") options = get_boto3_client_method_parameters(client, method_name) parameters = dict((k, v) for k, v in parameters.items() if k in options and v is not None) return parameters def ensure_snapshot_absent(): snapshot_name = module.params.get("db_snapshot_identifier") params = {"DBSnapshotIdentifier": snapshot_name} changed = False snapshot = get_snapshot(snapshot_name) if not snapshot: module.exit_json(changed=changed) elif snapshot and snapshot["Status"] != "deleting": snapshot, changed = call_method(client, module, "delete_db_snapshot", params) module.exit_json(changed=changed) def ensure_snapshot_present(params): source_id = module.params.get("source_db_snapshot_identifier") snapshot_name = module.params.get("db_snapshot_identifier") changed = False snapshot = get_snapshot(snapshot_name) # Copy snapshot if source_id: changed |= copy_snapshot(params) # Create snapshot elif not snapshot: changed |= create_snapshot(params) # Snapshot exists and we're not creating a copy - modify exising snapshot else: changed |= modify_snapshot() snapshot = get_snapshot(snapshot_name) module.exit_json(changed=changed, **camel_dict_to_snake_dict(snapshot, ignore_list=["Tags"])) def create_snapshot(params): method_params = get_parameters(params, "create_db_snapshot") if method_params.get("Tags"): method_params["Tags"] = ansible_dict_to_boto3_tag_list(method_params["Tags"]) _snapshot, changed = call_method(client, module, "create_db_snapshot", method_params) return changed def copy_snapshot(params): changed = False snapshot_id = module.params.get("db_snapshot_identifier") snapshot = get_snapshot(snapshot_id) if not snapshot: method_params = get_parameters(params, "copy_db_snapshot") if method_params.get("Tags"): method_params["Tags"] = ansible_dict_to_boto3_tag_list(method_params["Tags"]) _result, changed = call_method(client, module, "copy_db_snapshot", method_params) return changed def modify_snapshot(): # TODO - add other modifications aside from purely tags changed = False snapshot_id = module.params.get("db_snapshot_identifier") snapshot = get_snapshot(snapshot_id) if module.params.get("tags"): changed |= ensure_tags( client, module, snapshot["DBSnapshotArn"], snapshot["Tags"], module.params["tags"], module.params["purge_tags"], ) return changed def main(): global client global module 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"]), source_db_snapshot_identifier=dict(aliases=["source_id", "source_snapshot_id"]), wait=dict(type="bool", default=False), wait_timeout=dict(type="int", default=300), tags=dict(type="dict", aliases=["resource_tags"]), purge_tags=dict(type="bool", default=True), copy_tags=dict(type="bool", default=False), source_region=dict(type="str"), ) module = AnsibleAWSModule( argument_spec=argument_spec, supports_check_mode=True, ) retry_decorator = AWSRetry.jittered_backoff(retries=10) try: client = module.client("rds", retry_decorator=retry_decorator) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Failed to connect to AWS.") state = module.params.get("state") if state == "absent": ensure_snapshot_absent() elif state == "present": params = arg_spec_to_rds_params(dict((k, module.params[k]) for k in module.params if k in argument_spec)) ensure_snapshot_present(params) if __name__ == "__main__": main()