Skip to content

Commit

Permalink
rds_cluster_snapshot - new module to handle RDS cluster snapshotting (a…
Browse files Browse the repository at this point in the history
…nsible-collections#788)

rds_cluster_snapshot -  new module to handle RDS cluster snapshotting

SUMMARY

rds_cluster_snapshot -  new module to handle RDS cluster snapshotting
Requires rds_cluster to be merged first ansible-collections#687
Depends-On: ansible-collections#840
Requires: mattclay/aws-terminator#212
Requires also mattclay/aws-terminator#184

ISSUE TYPE


New Module Pull Request

COMPONENT NAME

rds_cluster_snapshot

Reviewed-by: Markus Bergholz <git@osuv.de>
Reviewed-by: Alina Buzachis <None>
Reviewed-by: Mark Chappell <None>
Reviewed-by: Joseph Torcasso <None>
  • Loading branch information
alinabuzachis committed Jun 14, 2022
1 parent 3df423a commit 5e7acbd
Show file tree
Hide file tree
Showing 6 changed files with 871 additions and 0 deletions.
1 change: 1 addition & 0 deletions meta/runtime.yml
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ action_groups:
- rds_subnet_group
- rds_cluster
- rds_cluster_info
- rds_cluster_snapshot
- redshift
- redshift_cross_region_snapshots
- redshift_info
Expand Down
372 changes: 372 additions & 0 deletions plugins/modules/rds_cluster_snapshot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,372 @@
#!/usr/bin/python
# Copyright (c) 2014 Ansible Project
# Copyright (c) 2021 Alina Buzachis (@alinabuzachis)
# 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 = r'''
---
module: rds_cluster_snapshot
version_added: 4.0.0
short_description: Manage Amazon RDS snapshots of DB clusters
description:
- Create, modify and delete RDS snapshots of DB clusters.
options:
state:
description:
- Specify the desired state of the snapshot.
default: present
choices: [ 'present', 'absent']
type: str
db_cluster_snapshot_identifier:
description:
- The identifier of the DB cluster snapshot.
required: true
aliases:
- snapshot_id
- id
- snapshot_name
type: str
db_cluster_identifier:
description:
- The identifier of the DB cluster to create a snapshot for.
- Required when I(state=present).
aliases:
- cluster_id
- cluster_name
type: str
source_db_cluster_snapshot_identifier:
description:
- The identifier of the DB cluster snapshot to copy.
- 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
source_region:
description:
- The region that contains the snapshot to be copied.
type: str
copy_tags:
description:
- Whether to copy all tags from I(source_db_cluster_snapshot_identifier) to I(db_cluster_snapshot_identifier).
type: bool
default: False
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
notes:
- Retrieve the information about a specific DB cluster or list the DB cluster snapshots for a specific DB cluster
can de done using M(community.aws.rds_snapshot_info).
author:
- Alina Buzachis (@alinabuzachis)
extends_documentation_fragment:
- amazon.aws.aws
- amazon.aws.ec2
- amazon.aws.tags
'''

EXAMPLES = r'''
- name: Create a DB cluster snapshot
community.aws.rds_cluster_snapshot:
db_cluster_identifier: "{{ cluster_id }}"
db_cluster_snapshot_identifier: new-cluster-snapshot
- name: Delete a DB cluster snapshot
community.aws.rds_cluster_snapshot:
db_cluster_snapshot_identifier: new-cluster-snapshot
state: absent
- name: Copy snapshot from a different region and copy its tags
community.aws.rds_cluster_snapshot:
id: new-database-snapshot-copy
region: us-east-1
source_id: "{{ snapshot.db_snapshot_arn }}"
source_region: us-east-2
copy_tags: yes
'''

RETURN = r'''
availability_zone:
description: Availability zone of the database from which the snapshot was created.
returned: always
type: str
sample: us-west-2a
db_cluster_snapshot_identifier:
description: Specifies the identifier for the DB cluster snapshot.
returned: always
type: str
sample: ansible-test-16638696-test-snapshot
db_cluster_identifier:
description: Specifies the DB cluster identifier of the DB cluster that this DB cluster snapshot was created from.
returned: always
type: str
sample: ansible-test-16638696
snapshot_create_time:
description: Provides the time when the snapshot was taken, in Universal Coordinated Time (UTC).
returned: always
type: str
sample: '2019-06-15T10:46:23.776000+00:00'
engine:
description: Specifies the name of the database engine for this DB cluster snapshot.
returned: always
type: str
sample: "aurora"
engine_mode:
description: Provides the engine mode of the database engine for this DB cluster snapshot.
returned: always
type: str
sample: "5.6.mysql_aurora.1.22.5"
allocated_storage:
description: Specifies the allocated storage size in gibibytes (GiB).
returned: always
type: int
sample: 20
status:
description: Specifies the status of this DB cluster snapshot.
returned: always
type: str
sample: available
port:
description: Port on which the database is listening.
returned: always
type: int
sample: 3306
vpc_id:
description: ID of the VPC in which the DB lives.
returned: always
type: str
sample: vpc-09ff232e222710ae0
cluster_create_time:
description: Specifies the time when the DB cluster was created, in Universal Coordinated Time (UTC).
returned: always
type: str
sample: '2019-06-15T10:15:56.221000+00:00'
master_username:
description: Provides the master username for this DB cluster snapshot.
returned: always
type: str
sample: test
engine_version:
description: Version of the cluster from which the snapshot was created.
returned: always
type: str
sample: "5.6.mysql_aurora.1.22.5"
license_model:
description: Provides the license model information for this DB cluster snapshot.
returned: always
type: str
sample: general-public-license
snapshot_type:
description: How the snapshot was created (always manual for this module!).
returned: always
type: str
sample: manual
percent_progress:
description: Specifies the percentage of the estimated data that has been transferred.
returned: always
type: int
sample: 100
storage_encrypted:
description: Specifies whether the DB cluster snapshot is encrypted.
returned: always
type: bool
sample: false
kms_key_id:
description: The Amazon Web Services KMS key identifier is the key ARN, key ID, alias ARN, or alias name for the KMS key.
returned: always
type: str
db_cluster_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
source_db_cluster_snapshot_arn:
description: If the DB cluster snapshot was copied from a source DB cluster snapshot, the ARN for the source DB cluster snapshot, otherwise, null.
returned: always
type: str
sample: null
iam_database_authentication_enabled:
description: Whether IAM database authentication is enabled.
returned: always
type: bool
sample: false
tag_list:
description: A list of tags.
returned: always
type: list
sample: []
tags:
description: Tags applied to the snapshot.
returned: always
type: complex
contains: {}
'''

try:
import botocore
except ImportError:
pass # caught by AnsibleAWSModule

from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule
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
from ansible_collections.amazon.aws.plugins.module_utils.core import get_boto3_client_method_parameters
from ansible_collections.amazon.aws.plugins.module_utils.rds import get_rds_method_attribute
from ansible_collections.amazon.aws.plugins.module_utils.rds import arg_spec_to_rds_params


def get_snapshot(snapshot_id):
try:
snapshot = client.describe_db_cluster_snapshots(DBClusterSnapshotIdentifier=snapshot_id, aws_retry=True)["DBClusterSnapshots"][0]
snapshot["Tags"] = get_tags(client, module, snapshot["DBClusterSnapshotArn"])
except is_boto3_error_code("DBClusterSnapshotNotFound"):
return {}
except is_boto3_error_code("DBClusterSnapshotNotFoundFault"): # pylint: disable=duplicate-except
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 snapshot


def get_parameters(parameters, method_name):
if method_name == 'copy_db_cluster_snapshot':
parameters['TargetDBClusterSnapshotIdentifier'] = module.params['db_cluster_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):
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():
snapshot_name = module.params.get("db_cluster_snapshot_identifier")
params = {"DBClusterSnapshotIdentifier": 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_cluster_snapshot", params)

module.exit_json(changed=changed)


def copy_snapshot(params):
changed = False
snapshot_id = module.params.get('db_cluster_snapshot_identifier')
snapshot = get_snapshot(snapshot_id)

if not snapshot:
method_params = get_parameters(params, 'copy_db_cluster_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_cluster_snapshot', method_params)

return changed


def ensure_snapshot_present(params):
source_id = module.params.get('source_db_cluster_snapshot_identifier')
snapshot_name = module.params.get("db_cluster_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_cluster_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_cluster_snapshot', method_params)

return changed


def modify_snapshot():
# TODO - add other modifications aside from purely tags
changed = False
snapshot_id = module.params.get('db_cluster_snapshot_identifier')
snapshot = get_snapshot(snapshot_id)

if module.params.get('tags'):
changed |= ensure_tags(client, module, snapshot['DBClusterSnapshotArn'], snapshot['Tags'], module.params['tags'], module.params['purge_tags'])

return changed


def main():
global client
global module

argument_spec = dict(
state=dict(type='str', choices=['present', 'absent'], default='present'),
db_cluster_snapshot_identifier=dict(type='str', aliases=['id', 'snapshot_id', 'snapshot_name'], required=True),
db_cluster_identifier=dict(type='str', aliases=['cluster_id', 'cluster_name']),
source_db_cluster_snapshot_identifier=dict(type='str', 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()
3 changes: 3 additions & 0 deletions tests/integration/targets/rds_cluster_snapshot/aliases
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
cloud/aws

rds_snapshot_info
13 changes: 13 additions & 0 deletions tests/integration/targets/rds_cluster_snapshot/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
# defaults file for rds_cluster_snapshot
_resource_prefix: 'ansible-test-{{ tiny_prefix }}'

# Create RDS cluster
cluster_id: '{{ _resource_prefix }}-rds-cluster'
username: 'testrdsusername'
password: "{{ lookup('password', 'dev/null length=12 chars=ascii_letters,digits') }}"
engine: 'aurora'
port: 3306

# Create snapshot
snapshot_id: '{{ _resource_prefix }}-rds-cluster-snapshot'
Loading

0 comments on commit 5e7acbd

Please sign in to comment.