Skip to content

Commit

Permalink
rds_instance_snapshot - add copy snapshot functionality (ansible-coll…
Browse files Browse the repository at this point in the history
…ections#1078)

rds_instance_snapshot - add copy snapshot functionality

Depends-On: ansible-collections#776
Depends-On: ansible-collections#1116
SUMMARY

Add support for copying a snapshot
Fixes ansible-collections#210
Don't require db_instance_identifier on state = present (only required for creation)

ISSUE TYPE

Feature Pull Request

COMPONENT NAME
rds_instance_snapshot

Reviewed-by: Markus Bergholz <git@osuv.de>
Reviewed-by: Joseph Torcasso <None>
Reviewed-by: Alina Buzachis <None>
  • Loading branch information
jatorcasso authored and abikouo committed Sep 18, 2023
1 parent 758f573 commit 7a544fe
Showing 1 changed file with 121 additions and 42 deletions.
163 changes: 121 additions & 42 deletions rds_instance_snapshot.py
Expand Up @@ -32,15 +32,37 @@
type: str
db_instance_identifier:
description:
- Database instance identifier. Required when state is present.
- 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
source_region:
description:
- The region that contains the snapshot to be copied.
type: str
version_added: 3.3.0
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
wait:
description:
- Whether or not to wait for snapshot creation or deletion.
type: bool
default: 'no'
default: False
wait_timeout:
description:
- how long before wait gives up, in seconds.
Expand All @@ -52,13 +74,14 @@
type: dict
purge_tags:
description:
- whether to remove tags not present in the C(tags) parameter.
- whether to remove tags not present in the I(tags) parameter.
default: True
type: bool
author:
- "Will Thames (@willthames)"
- "Michael De La Rue (@mikedlr)"
- "Alina Buzachis (@alinabuzachis)"
- "Joseph Torcasso (@jatorcasso)"
extends_documentation_fragment:
- amazon.aws.aws
- amazon.aws.ec2
Expand All @@ -70,6 +93,15 @@
community.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
community.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: yes
- name: Delete snapshot
community.aws.rds_instance_snapshot:
Expand Down Expand Up @@ -163,6 +195,12 @@
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
snapshot_create_time:
description: Creation time of the snapshot.
returned: always
Expand Down Expand Up @@ -202,31 +240,41 @@

# import module snippets
from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule
from ansible_collections.amazon.aws.plugins.module_utils.core import get_boto3_client_method_parameters
from ansible_collections.amazon.aws.plugins.module_utils.core import is_boto3_error_code
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import ansible_dict_to_boto3_tag_list
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import camel_dict_to_snake_dict
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import ansible_dict_to_boto3_tag_list
from ansible_collections.amazon.aws.plugins.module_utils.rds import get_tags
from ansible_collections.amazon.aws.plugins.module_utils.rds import ensure_tags
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


def get_snapshot(snapshot_id):
try:
response = client.describe_db_snapshots(DBSnapshotIdentifier=snapshot_id)
except is_boto3_error_code("DBSnapshotNotFoundFault"):
return None
except is_boto3_error_code("DBSnapshotNotFound"): # pylint: disable=duplicate-except
return None
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="Couldn't get snapshot {0}".format(snapshot_id))
return response['DBSnapshots'][0]
return snapshot


def fetch_tags(snapshot):
snapshot["Tags"] = get_tags(client, module, snapshot["DBSnapshotArn"])
def get_parameters(parameters, method_name):
if method_name == 'copy_db_snapshot':
parameters['TargetDBSnapshotIdentifier'] = module.params['db_snapshot_identifier']

return camel_dict_to_snake_dict(snapshot, ignore_list=["Tags"])
required_options = get_boto3_client_method_parameters(client, method_name, required=True)
if any(parameters.get(k) is None for k in required_options):
module.fail_json(msg='To {0} requires the parameters: {1}'.format(
get_rds_method_attribute(method_name, module).operation_description, 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():
Expand All @@ -236,40 +284,68 @@ def ensure_snapshot_absent():

snapshot = get_snapshot(snapshot_name)
if not snapshot:
return dict(changed=changed)
module.exit_json(changed=changed)
elif snapshot and snapshot["Status"] != "deleting":
snapshot, changed = call_method(client, module, "delete_db_snapshot", params)

return dict(changed=changed)
module.exit_json(changed=changed)


def ensure_snapshot_present():
db_instance_identifier = module.params.get('db_instance_identifier')
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:
params = {
"DBSnapshotIdentifier": snapshot_name,
"DBInstanceIdentifier": db_instance_identifier
}
if module.params.get("tags"):
params['Tags'] = ansible_dict_to_boto3_tag_list(module.params.get("tags"))
_result, changed = call_method(client, module, "create_db_snapshot", params)
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)

if module.check_mode:
return dict(changed=changed)
return changed

return dict(changed=changed, **fetch_tags(get_snapshot(snapshot_name)))

existing_tags = get_tags(client, module, snapshot["DBSnapshotArn"])
changed |= ensure_tags(client, module, snapshot["DBSnapshotArn"], existing_tags,
module.params["tags"], module.params["purge_tags"])
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.check_mode:
return dict(changed=changed)
if module.params.get('tags'):
changed |= ensure_tags(client, module, snapshot['DBSnapshotArn'], snapshot['Tags'], module.params['tags'], module.params['purge_tags'])

return dict(changed=changed, **fetch_tags(get_snapshot(snapshot_name)))
return changed


def main():
Expand All @@ -280,16 +356,18 @@ def main():
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'),
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,
required_if=[['state', 'present', ['db_instance_identifier']]],
supports_check_mode=True,
supports_check_mode=True
)

retry_decorator = AWSRetry.jittered_backoff(retries=10)
Expand All @@ -298,12 +376,13 @@ def main():
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Failed to connect to AWS.")

if module.params['state'] == 'absent':
ret_dict = ensure_snapshot_absent()
else:
ret_dict = ensure_snapshot_present()
state = module.params.get("state")
if state == 'absent':
ensure_snapshot_absent()

module.exit_json(**ret_dict)
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__':
Expand Down

0 comments on commit 7a544fe

Please sign in to comment.