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 #789/872f6e31 backport][stable-3] Update rds_instance_snaphot to use handlers defined in rds.py #1139

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
4 changes: 4 additions & 0 deletions changelogs/fragments/789-rds_instance_snapshot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
minor_changes:
- rds_instance_snapshot - update module to use handlers defined in module_utils/rds.py (https://github.com/ansible-collections/community.aws/pull/789).
- rds_instance_snapshot - add ``check_mode`` (https://github.com/ansible-collections/community.aws/pull/789).
- rds_instance_snapshot - add integration tests (https://github.com/ansible-collections/community.aws/pull/789).
180 changes: 72 additions & 108 deletions plugins/modules/rds_instance_snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
__metaclass__ = type


DOCUMENTATION = '''
DOCUMENTATION = r'''
---
module: rds_instance_snapshot
version_added: 1.0.0
short_description: manage Amazon RDS snapshots.
short_description: Manage Amazon RDS instance snapshots
description:
- Creates or deletes RDS snapshots.
options:
Expand Down Expand Up @@ -58,13 +58,14 @@
author:
- "Will Thames (@willthames)"
- "Michael De La Rue (@mikedlr)"
- "Alina Buzachis (@alinabuzachis)"
extends_documentation_fragment:
- amazon.aws.aws
- amazon.aws.ec2

'''

EXAMPLES = '''
EXAMPLES = r'''
- name: Create snapshot
community.aws.rds_instance_snapshot:
db_instance_identifier: new-database
Expand All @@ -76,7 +77,7 @@
state: absent
'''

RETURN = '''
RETURN = r'''
allocated_storage:
description: How much storage is allocated in GB.
returned: always
Expand Down Expand Up @@ -201,143 +202,106 @@

# 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
from ansible_collections.amazon.aws.plugins.module_utils.core import is_boto3_error_code
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 call_method


def get_snapshot(client, module, snapshot_id):
def get_snapshot(snapshot_id):
try:
response = client.describe_db_snapshots(DBSnapshotIdentifier=snapshot_id)
except client.exceptions.DBSnapshotNotFoundFault:
except is_boto3_error_code("DBSnapshotNotFoundFault"):
return None
except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e:
except is_boto3_error_code("DBSnapshotNotFound"): # pylint: disable=duplicate-except
return None
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]


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))
def fetch_tags(snapshot):
snapshot["Tags"] = get_tags(client, module, snapshot["DBSnapshotArn"])

return camel_dict_to_snake_dict(snapshot, ignore_list=['Tags'])
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')
def ensure_snapshot_absent():
snapshot_name = module.params.get("db_snapshot_identifier")
params = {"DBSnapshotIdentifier": snapshot_name}
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:
snapshot = get_snapshot(snapshot_name)
if not snapshot:
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):
elif snapshot and snapshot["Status"] != "deleting":
snapshot, changed = call_method(client, module, "delete_db_snapshot", params)

return dict(changed=changed)


def ensure_snapshot_present():
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)
snapshot = get_snapshot(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")
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)

if module.params.get('wait'):
wait_for_snapshot_status(client, module, snapshot_name, 'db_snapshot_available')
if module.check_mode:
return dict(changed=changed)

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)
return dict(changed=changed, **fetch_tags(get_snapshot(snapshot_name)))

snapshot = get_snapshot(client, module, 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"])

return dict(changed=changed, **snapshot_to_facts(client, module, snapshot))
if module.check_mode:
return dict(changed=changed)

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


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']),
wait=dict(type='bool', default=False),
wait_timeout=dict(type='int', default=300),
tags=dict(type='dict'),
purge_tags=dict(type='bool', default=True),
)

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']]]
argument_spec=argument_spec,
required_if=[['state', 'present', ['db_instance_identifier']]],
supports_check_mode=True,
)

client = module.client('rds', retry_decorator=AWSRetry.jittered_backoff(retries=10, catch_extra_error_codes=['DBSnapshotNotFound']))
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.")

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

module.exit_json(**ret_dict)

Expand Down
3 changes: 3 additions & 0 deletions tests/integration/targets/rds_instance_snapshot/aliases
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
cloud/aws

rds_snapshot_info
14 changes: 14 additions & 0 deletions tests/integration/targets/rds_instance_snapshot/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
# defaults file for rds_instance_snapshot

# Create RDS instance
instance_id: 'ansible-test-instance-{{ resource_prefix }}'
username: 'testrdsusername'
password: 'test-rds_password'
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 }}'
Loading