Skip to content

Commit

Permalink
rds_subnet_group: Add tags feature and enable check_mode (#562)
Browse files Browse the repository at this point in the history
rds_subnet_group: Add tags feature and enable check_mode

SUMMARY

rds_subnet_group: Add tags feature and enable check_mode
Fixes: #552

Depends-On: ansible-collections/amazon.aws#553
ISSUE TYPE


Feature Pull Request

COMPONENT NAME

rds_subnet_group

Reviewed-by: Jill R <None>
Reviewed-by: Mark Chappell <None>
Reviewed-by: Alina Buzachis <None>
Reviewed-by: Mark Woolley <[email protected]>
Reviewed-by: Markus Bergholz <[email protected]>
  • Loading branch information
alinabuzachis authored Mar 29, 2022
1 parent 10137a2 commit ce599bd
Show file tree
Hide file tree
Showing 4 changed files with 661 additions and 52 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
minor_changes:
- rds_subnet_group - add ``tags`` feature (https://github.com/ansible-collections/community.aws/pull/562).
- rds_subnet_group - add ``check_mode`` (https://github.com/ansible-collections/community.aws/pull/562).
249 changes: 200 additions & 49 deletions plugins/modules/rds_subnet_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,21 @@
- Required when I(state=present).
type: list
elements: str
author: "Scott Anderson (@tastychutney)"
tags:
description:
- A hash/dictionary of tags to add to the new RDS subnet group or to add/remove from an existing one.
type: dict
version_added: 3.2.0
purge_tags:
description:
- Whether or not to remove tags assigned to the RDS subnet group if not specified in the playbook.
- To remove all tags set I(tags) to an empty dictionary in conjunction with this.
default: True
type: bool
version_added: 3.2.0
author:
- "Scott Anderson (@tastychutney)"
- "Alina Buzachis (@alinabuzachis)"
extends_documentation_fragment:
- amazon.aws.aws
- amazon.aws.ec2
Expand All @@ -56,13 +70,30 @@
- subnet-aaaaaaaa
- subnet-bbbbbbbb
- name: Add or change a subnet group and associate tags
community.aws.rds_subnet_group:
state: present
name: norwegian-blue
description: My Fancy Ex Parrot Subnet Group
subnets:
- subnet-aaaaaaaa
- subnet-bbbbbbbb
tags:
tag1: Tag1
tag2: Tag2
- name: Remove a subnet group
community.aws.rds_subnet_group:
state: absent
name: norwegian-blue
'''

RETURN = r'''
changed:
description: True if listing the RDS subnet group succeeds.
type: bool
returned: always
sample: "false"
subnet_group:
description: Dictionary of DB subnet group values
returned: I(state=present)
Expand All @@ -72,46 +103,95 @@
description: The name of the DB subnet group (maintained for backward compatibility)
returned: I(state=present)
type: str
sample: "ansible-test-mbp-13950442"
db_subnet_group_name:
description: The name of the DB subnet group
returned: I(state=present)
type: str
sample: "ansible-test-mbp-13950442"
description:
description: The description of the DB subnet group (maintained for backward compatibility)
returned: I(state=present)
type: str
sample: "Simple description."
db_subnet_group_description:
description: The description of the DB subnet group
returned: I(state=present)
type: str
sample: "Simple description."
vpc_id:
description: The VpcId of the DB subnet group
returned: I(state=present)
type: str
sample: "vpc-0acb0ba033ff2119c"
subnet_ids:
description: Contains a list of Subnet IDs
returned: I(state=present)
type: list
sample:
"subnet-08c94870f4480797e"
subnets:
description: Contains a list of Subnet elements (@see https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/rds.html#RDS.Client.describe_db_subnet_groups) # noqa
returned: I(state=present)
type: list
contains:
subnet_availability_zone:
description: Contains Availability Zone information.
returned: I(state=present)
type: dict
version_added: 3.2.0
sample:
name: "eu-north-1b"
subnet_identifier:
description: The identifier of the subnet.
returned: I(state=present)
type: str
version_added: 3.2.0
sample: "subnet-08c94870f4480797e"
subnet_outpost:
description: This value specifies the Outpost.
returned: I(state=present)
type: dict
version_added: 3.2.0
sample: {}
subnet_status:
description: The status of the subnet.
returned: I(state=present)
type: str
version_added: 3.2.0
sample: "Active"
status:
description: The status of the DB subnet group (maintained for backward compatibility)
returned: I(state=present)
type: str
sample: "Complete"
subnet_group_status:
description: The status of the DB subnet group
returned: I(state=present)
type: str
sample: "Complete"
db_subnet_group_arn:
description: The ARN of the DB subnet group
returned: I(state=present)
type: str
sample: "arn:aws:rds:eu-north-1:721066863947:subgrp:ansible-test-13950442"
tags:
description: The tags associated with the subnet group
returned: I(state=present)
type: dict
version_added: 3.2.0
sample:
tag1: Tag1
tag2: Tag2
'''

from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict
from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule, is_boto3_error_code
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 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.rds import get_tags
from ansible_collections.amazon.aws.plugins.module_utils.rds import ensure_tags


try:
Expand All @@ -125,23 +205,47 @@ def create_result(changed, subnet_group=None):
return dict(
changed=changed
)
result_subnet_group = dict(camel_dict_to_snake_dict(subnet_group))
result_subnet_group = dict(subnet_group)
result_subnet_group['name'] = result_subnet_group.get(
'db_subnet_group_name')
result_subnet_group['description'] = result_subnet_group.get(
'db_subnet_group_description')
result_subnet_group['status'] = result_subnet_group.get(
'subnet_group_status')
result_subnet_group['subnet_ids'] = create_subnet_list(
subnet_group.get('Subnets'))
subnet_group.get('subnets'))
return dict(
changed=changed,
subnet_group=result_subnet_group
)


@AWSRetry.jittered_backoff()
def _describe_db_subnet_groups_with_backoff(client, **kwargs):
paginator = client.get_paginator('describe_db_subnet_groups')
return paginator.paginate(**kwargs).build_full_result()


def get_subnet_group(client, module):
params = dict()
params['DBSubnetGroupName'] = module.params.get('name').lower()

try:
_result = _describe_db_subnet_groups_with_backoff(client, **params)
except is_boto3_error_code('DBSubnetGroupNotFoundFault'):
return None
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except
module.fail_json_aws(e, msg="Couldn't describe subnet groups.")

if _result:
result = camel_dict_to_snake_dict(_result['DBSubnetGroups'][0])
result['tags'] = get_tags(client, module, result['db_subnet_group_arn'])

return result


def create_subnet_list(subnets):
'''
r'''
Construct a list of subnet ids from a list of subnets dicts returned by boto3.
Parameters:
subnets (list): A list of subnets definitions.
Expand All @@ -151,7 +255,7 @@ def create_subnet_list(subnets):
'''
subnets_ids = []
for subnet in subnets:
subnets_ids.append(subnet.get('SubnetIdentifier'))
subnets_ids.append(subnet.get('subnet_identifier'))
return subnets_ids


Expand All @@ -161,64 +265,111 @@ def main():
name=dict(required=True),
description=dict(required=False),
subnets=dict(required=False, type='list', elements='str'),
tags=dict(required=False, type='dict'),
purge_tags=dict(type='bool', default=True),
)
required_if = [('state', 'present', ['description', 'subnets'])]

module = AnsibleAWSModule(
argument_spec=argument_spec, required_if=required_if)
argument_spec=argument_spec,
required_if=required_if,
supports_check_mode=True
)

state = module.params.get('state')
group_name = module.params.get('name').lower()
group_description = module.params.get('description')
group_subnets = module.params.get('subnets') or []

try:
conn = module.client('rds')
connection = module.client('rds', retry_decorator=AWSRetry.jittered_backoff())
except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e:
module.fail_json_aws(e, 'Failed to instantiate AWS connection')
module.fail_json_aws(e, 'Failed to instantiate AWS connection.')

# Default.
changed = None
result = create_result(False)
tags_update = False
subnet_update = False

try:
matching_groups = conn.describe_db_subnet_groups(
DBSubnetGroupName=group_name, MaxRecords=100).get('DBSubnetGroups')
except is_boto3_error_code('DBSubnetGroupNotFoundFault'):
# No existing subnet, create it if needed, else we can just exit.
if state == 'present':
if module.params.get("tags") is not None:
_tags = ansible_dict_to_boto3_tag_list(module.params.get("tags"))
else:
_tags = list()

matching_groups = get_subnet_group(connection, module)

if state == 'present':
if matching_groups:
# We have one or more subnets at this point.

# Check if there is any tags update
tags_update = ensure_tags(
connection,
module,
matching_groups['db_subnet_group_arn'],
matching_groups['tags'],
module.params.get("tags"),
module.params['purge_tags']
)

# Sort the subnet groups before we compare them
existing_subnets = create_subnet_list(matching_groups['subnets'])
existing_subnets.sort()
group_subnets.sort()

# See if anything changed.
if (
matching_groups['db_subnet_group_name'] != group_name or
matching_groups['db_subnet_group_description'] != group_description or
existing_subnets != group_subnets
):
if not module.check_mode:
# Modify existing group.
try:
connection.modify_db_subnet_group(
aws_retry=True,
DBSubnetGroupName=group_name,
DBSubnetGroupDescription=group_description,
SubnetIds=group_subnets
)
except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e:
module.fail_json_aws(e, 'Failed to update a subnet group.')
subnet_update = True
else:
if not module.check_mode:
try:
connection.create_db_subnet_group(
aws_retry=True,
DBSubnetGroupName=group_name,
DBSubnetGroupDescription=group_description,
SubnetIds=group_subnets,
Tags=_tags
)
except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e:
module.fail_json_aws(e, 'Failed to create a new subnet group.')
subnet_update = True
elif state == 'absent':
if not module.check_mode:
try:
new_group = conn.create_db_subnet_group(
DBSubnetGroupName=group_name, DBSubnetGroupDescription=group_description, SubnetIds=group_subnets)
result = create_result(True, new_group.get('DBSubnetGroup'))
except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e:
module.fail_json_aws(e, 'Failed to create a new subnet group')
module.exit_json(**result)
except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: # pylint: disable=duplicate-except
module.fail_json_aws(e, 'Failed to get subnet groups description')
# We have one or more subnets at this point.
if state == 'absent':
try:
conn.delete_db_subnet_group(DBSubnetGroupName=group_name)
result = create_result(True)
connection.delete_db_subnet_group(aws_retry=True, DBSubnetGroupName=group_name)
except is_boto3_error_code('DBSubnetGroupNotFoundFault'):
module.exit_json(**result)
except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: # pylint: disable=duplicate-except
module.fail_json_aws(e, 'Failed to delete a subnet group.')
else:
subnet_group = get_subnet_group(connection, module)
if subnet_group:
subnet_update = True
result = create_result(subnet_update, subnet_group)
module.exit_json(**result)
except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e:
module.fail_json_aws(e, 'Failed to delete a subnet group')

# Sort the subnet groups before we compare them
existing_subnets = create_subnet_list(matching_groups[0].get('Subnets'))
existing_subnets.sort()
group_subnets.sort()
# See if anything changed.
if (matching_groups[0].get('DBSubnetGroupName') == group_name and
matching_groups[0].get('DBSubnetGroupDescription') == group_description and
existing_subnets == group_subnets):
result = create_result(False, matching_groups[0])
module.exit_json(**result)
# Modify existing group.
try:
changed_group = conn.modify_db_subnet_group(
DBSubnetGroupName=group_name, DBSubnetGroupDescription=group_description, SubnetIds=group_subnets)
result = create_result(True, changed_group.get('DBSubnetGroup'))
module.exit_json(**result)
except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e:
module.fail_json_aws(e, 'Failed to update a subnet group')

subnet_update = True

subnet_group = get_subnet_group(connection, module)
changed = tags_update or subnet_update
result = create_result(changed, subnet_group)
module.exit_json(**result)


if __name__ == '__main__':
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/targets/rds_subnet_group/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
register: subnets

- set_fact:
subnet_ids: '{{ subnets | community.general.json_query("results[].subnet.id") | list }}'
subnet_ids: '{{ subnets.results | map(attribute="subnet.id") | list }}'

# ============================================================

Expand Down
Loading

0 comments on commit ce599bd

Please sign in to comment.