685 lines
24 KiB
Python
685 lines
24 KiB
Python
#!/usr/bin/python
|
|
|
|
# Copyright 2014 Jens Carl, Hothead Games Inc.
|
|
# 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'''
|
|
---
|
|
author:
|
|
- "Jens Carl (@j-carl), Hothead Games Inc."
|
|
- "Rafael Driutti (@rafaeldriutti)"
|
|
module: redshift
|
|
version_added: 1.0.0
|
|
short_description: create, delete, or modify an Amazon Redshift instance
|
|
description:
|
|
- Creates, deletes, or modifies Amazon Redshift cluster instances.
|
|
options:
|
|
command:
|
|
description:
|
|
- Specifies the action to take.
|
|
required: true
|
|
choices: [ 'create', 'facts', 'delete', 'modify' ]
|
|
type: str
|
|
identifier:
|
|
description:
|
|
- Redshift cluster identifier.
|
|
required: true
|
|
type: str
|
|
node_type:
|
|
description:
|
|
- The node type of the cluster.
|
|
- Require when I(command=create).
|
|
choices: ['ds1.xlarge', 'ds1.8xlarge', 'ds2.xlarge', 'ds2.8xlarge', 'dc1.large','dc2.large',
|
|
'dc1.8xlarge', 'dw1.xlarge', 'dw1.8xlarge', 'dw2.large', 'dw2.8xlarge']
|
|
type: str
|
|
username:
|
|
description:
|
|
- Master database username.
|
|
- Used only when I(command=create).
|
|
type: str
|
|
password:
|
|
description:
|
|
- Master database password.
|
|
- Used only when I(command=create).
|
|
type: str
|
|
cluster_type:
|
|
description:
|
|
- The type of cluster.
|
|
choices: ['multi-node', 'single-node' ]
|
|
default: 'single-node'
|
|
type: str
|
|
db_name:
|
|
description:
|
|
- Name of the database.
|
|
type: str
|
|
availability_zone:
|
|
description:
|
|
- Availability zone in which to launch cluster.
|
|
aliases: ['zone', 'aws_zone']
|
|
type: str
|
|
number_of_nodes:
|
|
description:
|
|
- Number of nodes.
|
|
- Only used when I(cluster_type=multi-node).
|
|
type: int
|
|
cluster_subnet_group_name:
|
|
description:
|
|
- Which subnet to place the cluster.
|
|
aliases: ['subnet']
|
|
type: str
|
|
cluster_security_groups:
|
|
description:
|
|
- In which security group the cluster belongs.
|
|
type: list
|
|
elements: str
|
|
aliases: ['security_groups']
|
|
vpc_security_group_ids:
|
|
description:
|
|
- VPC security group
|
|
aliases: ['vpc_security_groups']
|
|
type: list
|
|
elements: str
|
|
skip_final_cluster_snapshot:
|
|
description:
|
|
- Skip a final snapshot before deleting the cluster.
|
|
- Used only when I(command=delete).
|
|
aliases: ['skip_final_snapshot']
|
|
default: false
|
|
type: bool
|
|
final_cluster_snapshot_identifier:
|
|
description:
|
|
- Identifier of the final snapshot to be created before deleting the cluster.
|
|
- If this parameter is provided, I(skip_final_cluster_snapshot) must be C(false).
|
|
- Used only when I(command=delete).
|
|
aliases: ['final_snapshot_id']
|
|
type: str
|
|
preferred_maintenance_window:
|
|
description:
|
|
- 'Maintenance window in format of C(ddd:hh24:mi-ddd:hh24:mi). (Example: C(Mon:22:00-Mon:23:15))'
|
|
- Times are specified in UTC.
|
|
- If not specified then a random 30 minute maintenance window is assigned.
|
|
aliases: ['maintance_window', 'maint_window']
|
|
type: str
|
|
cluster_parameter_group_name:
|
|
description:
|
|
- Name of the cluster parameter group.
|
|
aliases: ['param_group_name']
|
|
type: str
|
|
automated_snapshot_retention_period:
|
|
description:
|
|
- The number of days that automated snapshots are retained.
|
|
aliases: ['retention_period']
|
|
type: int
|
|
port:
|
|
description:
|
|
- Which port the cluster is listening on.
|
|
type: int
|
|
cluster_version:
|
|
description:
|
|
- Which version the cluster should have.
|
|
aliases: ['version']
|
|
choices: ['1.0']
|
|
type: str
|
|
allow_version_upgrade:
|
|
description:
|
|
- When I(allow_version_upgrade=true) the cluster may be automatically
|
|
upgraded during the maintenance window.
|
|
aliases: ['version_upgrade']
|
|
default: true
|
|
type: bool
|
|
publicly_accessible:
|
|
description:
|
|
- If the cluster is accessible publicly or not.
|
|
default: false
|
|
type: bool
|
|
encrypted:
|
|
description:
|
|
- If the cluster is encrypted or not.
|
|
default: false
|
|
type: bool
|
|
elastic_ip:
|
|
description:
|
|
- An Elastic IP to use for the cluster.
|
|
type: str
|
|
new_cluster_identifier:
|
|
description:
|
|
- Only used when command=modify.
|
|
aliases: ['new_identifier']
|
|
type: str
|
|
wait:
|
|
description:
|
|
- When I(command=create), I(command=modify) or I(command=restore) then wait for the database to enter the 'available' state.
|
|
- When I(command=delete) wait for the database to be terminated.
|
|
type: bool
|
|
default: false
|
|
wait_timeout:
|
|
description:
|
|
- When I(wait=true) defines how long in seconds before giving up.
|
|
default: 300
|
|
type: int
|
|
enhanced_vpc_routing:
|
|
description:
|
|
- Whether the cluster should have enhanced VPC routing enabled.
|
|
default: false
|
|
type: bool
|
|
tags:
|
|
description:
|
|
- A dictionary of resource tags.
|
|
type: dict
|
|
aliases: ['resource_tags']
|
|
version_added: "1.3.0"
|
|
purge_tags:
|
|
description:
|
|
- Purge existing tags that are not found in the cluster
|
|
type: bool
|
|
default: 'yes'
|
|
version_added: "1.3.0"
|
|
extends_documentation_fragment:
|
|
- amazon.aws.aws
|
|
- amazon.aws.ec2
|
|
'''
|
|
|
|
EXAMPLES = r'''
|
|
- name: Basic cluster provisioning example
|
|
community.aws.redshift:
|
|
command: create
|
|
node_type: ds1.xlarge
|
|
identifier: new_cluster
|
|
username: cluster_admin
|
|
password: 1nsecure
|
|
|
|
- name: Cluster delete example
|
|
community.aws.redshift:
|
|
command: delete
|
|
identifier: new_cluster
|
|
skip_final_cluster_snapshot: true
|
|
wait: true
|
|
'''
|
|
|
|
RETURN = r'''
|
|
cluster:
|
|
description: dictionary containing all the cluster information
|
|
returned: success
|
|
type: complex
|
|
contains:
|
|
identifier:
|
|
description: Id of the cluster.
|
|
returned: success
|
|
type: str
|
|
sample: "new_redshift_cluster"
|
|
create_time:
|
|
description: Time of the cluster creation as timestamp.
|
|
returned: success
|
|
type: float
|
|
sample: 1430158536.308
|
|
status:
|
|
description: Status of the cluster.
|
|
returned: success
|
|
type: str
|
|
sample: "available"
|
|
db_name:
|
|
description: Name of the database.
|
|
returned: success
|
|
type: str
|
|
sample: "new_db_name"
|
|
availability_zone:
|
|
description: Amazon availability zone where the cluster is located. "None" until cluster is available.
|
|
returned: success
|
|
type: str
|
|
sample: "us-east-1b"
|
|
maintenance_window:
|
|
description: Time frame when maintenance/upgrade are done.
|
|
returned: success
|
|
type: str
|
|
sample: "sun:09:30-sun:10:00"
|
|
private_ip_address:
|
|
description: Private IP address of the main node.
|
|
returned: success
|
|
type: str
|
|
sample: "10.10.10.10"
|
|
public_ip_address:
|
|
description: Public IP address of the main node. "None" when enhanced_vpc_routing is enabled.
|
|
returned: success
|
|
type: str
|
|
sample: "0.0.0.0"
|
|
port:
|
|
description: Port of the cluster. "None" until cluster is available.
|
|
returned: success
|
|
type: int
|
|
sample: 5439
|
|
url:
|
|
description: FQDN of the main cluster node. "None" until cluster is available.
|
|
returned: success
|
|
type: str
|
|
sample: "new-redshift_cluster.jfkdjfdkj.us-east-1.redshift.amazonaws.com"
|
|
enhanced_vpc_routing:
|
|
description: status of the enhanced vpc routing feature.
|
|
returned: success
|
|
type: bool
|
|
tags:
|
|
description: aws tags for cluster.
|
|
returned: success
|
|
type: dict
|
|
'''
|
|
|
|
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 ansible_dict_to_boto3_tag_list
|
|
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import boto3_tag_list_to_ansible_dict
|
|
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import compare_aws_tags
|
|
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import snake_dict_to_camel_dict
|
|
from ansible_collections.amazon.aws.plugins.module_utils.iam import get_aws_account_id
|
|
|
|
|
|
def _ensure_tags(redshift, identifier, existing_tags, module):
|
|
"""Compares and update resource tags"""
|
|
|
|
account_id = get_aws_account_id(module)
|
|
region = module.params.get('region')
|
|
resource_arn = "arn:aws:redshift:{0}:{1}:cluster:{2}" .format(region, account_id, identifier)
|
|
tags = module.params.get('tags')
|
|
purge_tags = module.params.get('purge_tags')
|
|
|
|
tags_to_add, tags_to_remove = compare_aws_tags(boto3_tag_list_to_ansible_dict(existing_tags), tags, purge_tags)
|
|
|
|
if tags_to_add:
|
|
try:
|
|
redshift.create_tags(ResourceName=resource_arn, Tags=ansible_dict_to_boto3_tag_list(tags_to_add))
|
|
except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e:
|
|
module.fail_json_aws(e, msg="Failed to add tags to cluster")
|
|
|
|
if tags_to_remove:
|
|
try:
|
|
redshift.delete_tags(ResourceName=resource_arn, TagKeys=tags_to_remove)
|
|
except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e:
|
|
module.fail_json_aws(e, msg="Failed to delete tags on cluster")
|
|
|
|
changed = bool(tags_to_add or tags_to_remove)
|
|
return changed
|
|
|
|
|
|
def _collect_facts(resource):
|
|
"""Transform cluster information to dict."""
|
|
facts = {
|
|
'identifier': resource['ClusterIdentifier'],
|
|
'status': resource['ClusterStatus'],
|
|
'username': resource['MasterUsername'],
|
|
'db_name': resource['DBName'],
|
|
'maintenance_window': resource['PreferredMaintenanceWindow'],
|
|
'enhanced_vpc_routing': resource['EnhancedVpcRouting']
|
|
|
|
}
|
|
|
|
for node in resource['ClusterNodes']:
|
|
if node['NodeRole'] in ('SHARED', 'LEADER'):
|
|
facts['private_ip_address'] = node['PrivateIPAddress']
|
|
if facts['enhanced_vpc_routing'] is False:
|
|
facts['public_ip_address'] = node['PublicIPAddress']
|
|
else:
|
|
facts['public_ip_address'] = None
|
|
break
|
|
|
|
# Some parameters are not ready instantly if you don't wait for available
|
|
# cluster status
|
|
facts['create_time'] = None
|
|
facts['url'] = None
|
|
facts['port'] = None
|
|
facts['availability_zone'] = None
|
|
facts['tags'] = {}
|
|
|
|
if resource['ClusterStatus'] != "creating":
|
|
facts['create_time'] = resource['ClusterCreateTime']
|
|
facts['url'] = resource['Endpoint']['Address']
|
|
facts['port'] = resource['Endpoint']['Port']
|
|
facts['availability_zone'] = resource['AvailabilityZone']
|
|
facts['tags'] = boto3_tag_list_to_ansible_dict(resource['Tags'])
|
|
|
|
return facts
|
|
|
|
|
|
@AWSRetry.jittered_backoff()
|
|
def _describe_cluster(redshift, identifier):
|
|
'''
|
|
Basic wrapper around describe_clusters with a retry applied
|
|
'''
|
|
return redshift.describe_clusters(ClusterIdentifier=identifier)['Clusters'][0]
|
|
|
|
|
|
@AWSRetry.jittered_backoff()
|
|
def _create_cluster(redshift, **kwargs):
|
|
'''
|
|
Basic wrapper around create_cluster with a retry applied
|
|
'''
|
|
return redshift.create_cluster(**kwargs)
|
|
|
|
|
|
# Simple wrapper around delete, try to avoid throwing an error if some other
|
|
# operation is in progress
|
|
@AWSRetry.jittered_backoff(catch_extra_error_codes=['InvalidClusterState'])
|
|
def _delete_cluster(redshift, **kwargs):
|
|
'''
|
|
Basic wrapper around delete_cluster with a retry applied.
|
|
Explicitly catches 'InvalidClusterState' (~ Operation in progress) so that
|
|
we can still delete a cluster if some kind of change operation was in
|
|
progress.
|
|
'''
|
|
return redshift.delete_cluster(**kwargs)
|
|
|
|
|
|
@AWSRetry.jittered_backoff(catch_extra_error_codes=['InvalidClusterState'])
|
|
def _modify_cluster(redshift, **kwargs):
|
|
'''
|
|
Basic wrapper around modify_cluster with a retry applied.
|
|
Explicitly catches 'InvalidClusterState' (~ Operation in progress) for cases
|
|
where another modification is still in progress
|
|
'''
|
|
return redshift.modify_cluster(**kwargs)
|
|
|
|
|
|
def create_cluster(module, redshift):
|
|
"""
|
|
Create a new cluster
|
|
|
|
module: AnsibleAWSModule object
|
|
redshift: authenticated redshift connection object
|
|
|
|
Returns:
|
|
"""
|
|
|
|
identifier = module.params.get('identifier')
|
|
node_type = module.params.get('node_type')
|
|
username = module.params.get('username')
|
|
password = module.params.get('password')
|
|
d_b_name = module.params.get('db_name')
|
|
wait = module.params.get('wait')
|
|
wait_timeout = module.params.get('wait_timeout')
|
|
tags = module.params.get('tags')
|
|
|
|
changed = True
|
|
# Package up the optional parameters
|
|
params = {}
|
|
for p in ('cluster_type', 'cluster_security_groups',
|
|
'vpc_security_group_ids', 'cluster_subnet_group_name',
|
|
'availability_zone', 'preferred_maintenance_window',
|
|
'cluster_parameter_group_name',
|
|
'automated_snapshot_retention_period', 'port',
|
|
'cluster_version', 'allow_version_upgrade',
|
|
'number_of_nodes', 'publicly_accessible', 'encrypted',
|
|
'elastic_ip', 'enhanced_vpc_routing'):
|
|
# https://github.com/boto/boto3/issues/400
|
|
if module.params.get(p) is not None:
|
|
params[p] = module.params.get(p)
|
|
|
|
if d_b_name:
|
|
params['d_b_name'] = d_b_name
|
|
if tags:
|
|
tags = ansible_dict_to_boto3_tag_list(tags)
|
|
params['tags'] = tags
|
|
|
|
try:
|
|
_describe_cluster(redshift, identifier)
|
|
changed = False
|
|
except is_boto3_error_code('ClusterNotFound'):
|
|
try:
|
|
_create_cluster(redshift,
|
|
ClusterIdentifier=identifier,
|
|
NodeType=node_type,
|
|
MasterUsername=username,
|
|
MasterUserPassword=password,
|
|
**snake_dict_to_camel_dict(params, capitalize_first=True))
|
|
except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e:
|
|
module.fail_json_aws(e, msg="Failed to create cluster")
|
|
except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: # pylint: disable=duplicate-except
|
|
module.fail_json_aws(e, msg="Failed to describe cluster")
|
|
if wait:
|
|
attempts = wait_timeout // 60
|
|
waiter = redshift.get_waiter('cluster_available')
|
|
try:
|
|
waiter.wait(
|
|
ClusterIdentifier=identifier,
|
|
WaiterConfig=dict(MaxAttempts=attempts)
|
|
)
|
|
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
|
module.fail_json_aws(e, msg="Timeout waiting for the cluster creation")
|
|
try:
|
|
resource = _describe_cluster(redshift, identifier)
|
|
except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e:
|
|
module.fail_json_aws(e, msg="Failed to describe cluster")
|
|
|
|
if tags:
|
|
if _ensure_tags(redshift, identifier, resource['Tags'], module):
|
|
changed = True
|
|
resource = _describe_cluster(redshift, identifier)
|
|
|
|
return(changed, _collect_facts(resource))
|
|
|
|
|
|
def describe_cluster(module, redshift):
|
|
"""
|
|
Collect data about the cluster.
|
|
|
|
module: Ansible module object
|
|
redshift: authenticated redshift connection object
|
|
"""
|
|
identifier = module.params.get('identifier')
|
|
|
|
try:
|
|
resource = _describe_cluster(redshift, identifier)
|
|
except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e:
|
|
module.fail_json_aws(e, msg="Error describing cluster")
|
|
|
|
return(True, _collect_facts(resource))
|
|
|
|
|
|
def delete_cluster(module, redshift):
|
|
"""
|
|
Delete a cluster.
|
|
|
|
module: Ansible module object
|
|
redshift: authenticated redshift connection object
|
|
"""
|
|
|
|
identifier = module.params.get('identifier')
|
|
wait = module.params.get('wait')
|
|
wait_timeout = module.params.get('wait_timeout')
|
|
|
|
params = {}
|
|
for p in ('skip_final_cluster_snapshot',
|
|
'final_cluster_snapshot_identifier'):
|
|
if p in module.params:
|
|
# https://github.com/boto/boto3/issues/400
|
|
if module.params.get(p) is not None:
|
|
params[p] = module.params.get(p)
|
|
|
|
try:
|
|
_delete_cluster(
|
|
redshift,
|
|
ClusterIdentifier=identifier,
|
|
**snake_dict_to_camel_dict(params, capitalize_first=True))
|
|
except is_boto3_error_code('ClusterNotFound'):
|
|
return(False, {})
|
|
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except
|
|
module.fail_json_aws(e, msg="Failed to delete cluster")
|
|
|
|
if wait:
|
|
attempts = wait_timeout // 60
|
|
waiter = redshift.get_waiter('cluster_deleted')
|
|
try:
|
|
waiter.wait(
|
|
ClusterIdentifier=identifier,
|
|
WaiterConfig=dict(MaxAttempts=attempts)
|
|
)
|
|
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
|
module.fail_json_aws(e, msg="Timeout deleting the cluster")
|
|
|
|
return(True, {})
|
|
|
|
|
|
def modify_cluster(module, redshift):
|
|
"""
|
|
Modify an existing cluster.
|
|
|
|
module: Ansible module object
|
|
redshift: authenticated redshift connection object
|
|
"""
|
|
|
|
identifier = module.params.get('identifier')
|
|
wait = module.params.get('wait')
|
|
wait_timeout = module.params.get('wait_timeout')
|
|
tags = module.params.get('tags')
|
|
purge_tags = module.params.get('purge_tags')
|
|
region = region = module.params.get('region')
|
|
|
|
# Package up the optional parameters
|
|
params = {}
|
|
for p in ('cluster_type', 'cluster_security_groups',
|
|
'vpc_security_group_ids', 'cluster_subnet_group_name',
|
|
'availability_zone', 'preferred_maintenance_window',
|
|
'cluster_parameter_group_name',
|
|
'automated_snapshot_retention_period', 'port', 'cluster_version',
|
|
'allow_version_upgrade', 'number_of_nodes', 'new_cluster_identifier'):
|
|
# https://github.com/boto/boto3/issues/400
|
|
if module.params.get(p) is not None:
|
|
params[p] = module.params.get(p)
|
|
|
|
# enhanced_vpc_routing parameter change needs an exclusive request
|
|
if module.params.get('enhanced_vpc_routing') is not None:
|
|
try:
|
|
_modify_cluster(
|
|
redshift,
|
|
ClusterIdentifier=identifier,
|
|
EnhancedVpcRouting=module.params.get('enhanced_vpc_routing'))
|
|
except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e:
|
|
module.fail_json_aws(e, msg="Couldn't modify redshift cluster %s " % identifier)
|
|
if wait:
|
|
attempts = wait_timeout // 60
|
|
waiter = redshift.get_waiter('cluster_available')
|
|
try:
|
|
waiter.wait(
|
|
ClusterIdentifier=identifier,
|
|
WaiterConfig=dict(MaxAttempts=attempts))
|
|
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
|
module.fail_json_aws(e,
|
|
msg="Timeout waiting for cluster enhanced vpc routing modification")
|
|
|
|
# change the rest
|
|
try:
|
|
_modify_cluster(
|
|
redshift,
|
|
ClusterIdentifier=identifier,
|
|
**snake_dict_to_camel_dict(params, capitalize_first=True))
|
|
except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e:
|
|
module.fail_json_aws(e, msg="Couldn't modify redshift cluster %s " % identifier)
|
|
|
|
if module.params.get('new_cluster_identifier'):
|
|
identifier = module.params.get('new_cluster_identifier')
|
|
|
|
if wait:
|
|
attempts = wait_timeout // 60
|
|
waiter2 = redshift.get_waiter('cluster_available')
|
|
try:
|
|
waiter2.wait(
|
|
ClusterIdentifier=identifier,
|
|
WaiterConfig=dict(MaxAttempts=attempts)
|
|
)
|
|
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
|
module.fail_json_aws(e, msg="Timeout waiting for cluster modification")
|
|
try:
|
|
resource = _describe_cluster(redshift, identifier)
|
|
except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e:
|
|
module.fail_json_aws(e, msg="Couldn't modify redshift cluster %s " % identifier)
|
|
|
|
if _ensure_tags(redshift, identifier, resource['Tags'], module):
|
|
resource = redshift.describe_clusters(ClusterIdentifier=identifier)['Clusters'][0]
|
|
|
|
return(True, _collect_facts(resource))
|
|
|
|
|
|
def main():
|
|
argument_spec = dict(
|
|
command=dict(choices=['create', 'facts', 'delete', 'modify'], required=True),
|
|
identifier=dict(required=True),
|
|
node_type=dict(choices=['ds1.xlarge', 'ds1.8xlarge', 'ds2.xlarge',
|
|
'ds2.8xlarge', 'dc1.large', 'dc2.large',
|
|
'dc1.8xlarge', 'dw1.xlarge', 'dw1.8xlarge',
|
|
'dw2.large', 'dw2.8xlarge'], required=False),
|
|
username=dict(required=False),
|
|
password=dict(no_log=True, required=False),
|
|
db_name=dict(required=False),
|
|
cluster_type=dict(choices=['multi-node', 'single-node'], default='single-node'),
|
|
cluster_security_groups=dict(aliases=['security_groups'], type='list', elements='str'),
|
|
vpc_security_group_ids=dict(aliases=['vpc_security_groups'], type='list', elements='str'),
|
|
skip_final_cluster_snapshot=dict(aliases=['skip_final_snapshot'],
|
|
type='bool', default=False),
|
|
final_cluster_snapshot_identifier=dict(aliases=['final_snapshot_id'], required=False),
|
|
cluster_subnet_group_name=dict(aliases=['subnet']),
|
|
availability_zone=dict(aliases=['aws_zone', 'zone']),
|
|
preferred_maintenance_window=dict(aliases=['maintance_window', 'maint_window']),
|
|
cluster_parameter_group_name=dict(aliases=['param_group_name']),
|
|
automated_snapshot_retention_period=dict(aliases=['retention_period'], type='int'),
|
|
port=dict(type='int'),
|
|
cluster_version=dict(aliases=['version'], choices=['1.0']),
|
|
allow_version_upgrade=dict(aliases=['version_upgrade'], type='bool', default=True),
|
|
number_of_nodes=dict(type='int'),
|
|
publicly_accessible=dict(type='bool', default=False),
|
|
encrypted=dict(type='bool', default=False),
|
|
elastic_ip=dict(required=False),
|
|
new_cluster_identifier=dict(aliases=['new_identifier']),
|
|
enhanced_vpc_routing=dict(type='bool', default=False),
|
|
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)
|
|
)
|
|
|
|
required_if = [
|
|
('command', 'delete', ['skip_final_cluster_snapshot']),
|
|
('command', 'create', ['node_type',
|
|
'username',
|
|
'password'])
|
|
]
|
|
|
|
module = AnsibleAWSModule(
|
|
argument_spec=argument_spec,
|
|
required_if=required_if
|
|
)
|
|
|
|
command = module.params.get('command')
|
|
skip_final_cluster_snapshot = module.params.get('skip_final_cluster_snapshot')
|
|
final_cluster_snapshot_identifier = module.params.get('final_cluster_snapshot_identifier')
|
|
# can't use module basic required_if check for this case
|
|
if command == 'delete' and skip_final_cluster_snapshot is False and final_cluster_snapshot_identifier is None:
|
|
module.fail_json(msg="Need to specify final_cluster_snapshot_identifier if skip_final_cluster_snapshot is False")
|
|
|
|
conn = module.client('redshift')
|
|
|
|
changed = True
|
|
if command == 'create':
|
|
(changed, cluster) = create_cluster(module, conn)
|
|
|
|
elif command == 'facts':
|
|
(changed, cluster) = describe_cluster(module, conn)
|
|
|
|
elif command == 'delete':
|
|
(changed, cluster) = delete_cluster(module, conn)
|
|
|
|
elif command == 'modify':
|
|
(changed, cluster) = modify_cluster(module, conn)
|
|
|
|
module.exit_json(changed=changed, cluster=cluster)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|