Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PR #1078/d04ab427 backport][stable-3] rds_instance_snapshot - add copy snapshot functionality #1156

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
minor_changes:
- rds_instance_snapshot - add copy_db_snapshot functionality (https://github.com/ansible-collections/community.aws/pull/1078).

bugfixes:
- don't require `db_instance_identifier` on state = present (https://github.com/ansible-collections/community.aws/pull/1078).
163 changes: 121 additions & 42 deletions plugins/modules/rds_instance_snapshot.py
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
# defaults file for rds_instance_snapshot

# Create RDS instance
instance_id: 'ansible-test-instance-{{ resource_prefix }}'
instance_id: '{{ resource_prefix }}-instance'
username: 'testrdsusername'
password: 'test-rds_password'
password: "{{ lookup('password', '/dev/null') }}"
db_instance_class: db.t3.micro
allocated_storage: 10
engine: 'mariadb'
mariadb_engine_version: 10.3.31

# Create snapshot
snapshot_id: 'ansible-test-instance-snapshot-{{ resource_prefix }}'
snapshot_id: '{{ instance_id }}-snapshot'
Loading