@ -1,19 +1,8 @@
#!/usr/bin/python
#!/usr/bin/python
# This file is part of Ansible
#
#
# Ansible is free software: you can redistribute it and/or modify
# Copyright (c) 2017 Ansible Project
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
#
# Ansible is distributed in the hope that it will be useful,
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
ANSIBLE_METADATA = { ' metadata_version ' : ' 1.0 ' ,
ANSIBLE_METADATA = { ' metadata_version ' : ' 1.0 ' ,
' status ' : [ ' preview ' ] ,
' status ' : [ ' preview ' ] ,
' supported_by ' : ' community ' }
' supported_by ' : ' community ' }
@ -140,19 +129,20 @@ EXAMPLES = """
state : rebooted
state : rebooted
"""
"""
from time import sleep
import sys
from traceback import format_exc
import time
from ansible . module_utils . basic import AnsibleModule
from ansible . module_utils . ec2 import ec2_argument_spec , get_aws_connection_info , boto3_conn , HAS_BOTO3 , camel_dict_to_snake_dict
try :
try :
import boto
import boto3
from boto . elasticache import connect_to_region
import botocore
HAS_BOTO = True
except ImportError :
except ImportError :
HAS_BOTO = False
pass # will be detected by imported HAS_BOTO3
class ElastiCacheManager ( object ) :
class ElastiCacheManager ( object ) :
""" Handles elasticache creation and destruction """
""" Handles elasticache creation and destruction """
EXIST_STATUSES = [ ' available ' , ' creating ' , ' rebooting ' , ' modifying ' ]
EXIST_STATUSES = [ ' available ' , ' creating ' , ' rebooting ' , ' modifying ' ]
@ -163,7 +153,7 @@ class ElastiCacheManager(object):
hard_modify , region , * * aws_connect_kwargs ) :
hard_modify , region , * * aws_connect_kwargs ) :
self . module = module
self . module = module
self . name = name
self . name = name
self . engine = engine
self . engine = engine . lower ( )
self . cache_engine_version = cache_engine_version
self . cache_engine_version = cache_engine_version
self . node_type = node_type
self . node_type = node_type
self . num_nodes = num_nodes
self . num_nodes = num_nodes
@ -219,20 +209,25 @@ class ElastiCacheManager(object):
msg = " ' %s ' is currently deleting. Cannot create. "
msg = " ' %s ' is currently deleting. Cannot create. "
self . module . fail_json ( msg = msg % self . name )
self . module . fail_json ( msg = msg % self . name )
kwargs = dict ( CacheClusterId = self . name ,
NumCacheNodes = self . num_nodes ,
CacheNodeType = self . node_type ,
Engine = self . engine ,
EngineVersion = self . cache_engine_version ,
CacheSecurityGroupNames = self . cache_security_groups ,
SecurityGroupIds = self . security_group_ids ,
CacheParameterGroupName = self . cache_parameter_group ,
CacheSubnetGroupName = self . cache_subnet_group ,
PreferredAvailabilityZone = self . zone )
if self . cache_port is not None :
kwargs [ ' Port ' ] = self . cache_port
try :
try :
response = self . conn . create_cache_cluster ( cache_cluster_id = self . name ,
self . conn . create_cache_cluster ( * * kwargs )
num_cache_nodes = self . num_nodes ,
cache_node_type = self . node_type ,
except botocore . exceptions . ClientError as e :
engine = self . engine ,
self . module . fail_json ( msg = e . message , exception = format_exc ( ) ,
engine_version = self . cache_engine_version ,
* * camel_dict_to_snake_dict ( e . response ) )
cache_security_group_names = self . cache_security_groups ,
security_group_ids = self . security_group_ids ,
cache_parameter_group_name = self . cache_parameter_group ,
cache_subnet_group_name = self . cache_subnet_group ,
preferred_availability_zone = self . zone ,
port = self . cache_port )
except boto . exception . BotoServerError as e :
self . module . fail_json ( msg = e . message )
self . _refresh_data ( )
self . _refresh_data ( )
@ -257,10 +252,12 @@ class ElastiCacheManager(object):
self . module . fail_json ( msg = msg % ( self . name , self . status ) )
self . module . fail_json ( msg = msg % ( self . name , self . status ) )
try :
try :
response = self . conn . delete_cache_cluster ( cache_cluster_id = self . name )
response = self . conn . delete_cache_cluster ( CacheClusterId = self . name )
except boto . exception . BotoServerError as e :
except botocore . exceptions . ClientError as e :
self . module . fail_json ( msg = e . message )
self . module . fail_json ( msg = e . message , exception = format_exc ( ) ,
cache_cluster_data = response [ ' DeleteCacheClusterResponse ' ] [ ' DeleteCacheClusterResult ' ] [ ' CacheCluster ' ]
* * camel_dict_to_snake_dict ( e . response ) )
cache_cluster_data = response [ ' CacheCluster ' ]
self . _refresh_data ( cache_cluster_data )
self . _refresh_data ( cache_cluster_data )
self . changed = True
self . changed = True
@ -299,16 +296,17 @@ class ElastiCacheManager(object):
""" Modify the cache cluster. Note it ' s only possible to modify a few select options. """
""" Modify the cache cluster. Note it ' s only possible to modify a few select options. """
nodes_to_remove = self . _get_nodes_to_remove ( )
nodes_to_remove = self . _get_nodes_to_remove ( )
try :
try :
response = self . conn . modify_cache_cluster ( cache_cluster_id = self . name ,
self . conn . modify_cache_cluster ( CacheClusterId = self . name ,
num_cache_nodes = self . num_nodes ,
NumCacheNodes = self . num_nodes ,
cache_node_ids_to_remove = nodes_to_remove ,
CacheNodeIdsToRemove = nodes_to_remove ,
cache_security_group_names = self . cache_security_groups ,
CacheSecurityGroupNames = self . cache_security_groups ,
cache_parameter_group_name = self . cache_parameter_group ,
CacheParameterGroupName = self . cache_parameter_group ,
security_group_ids = self . security_group_ids ,
SecurityGroupIds = self . security_group_ids ,
apply_immediately = True ,
ApplyImmediately = True ,
engine_version = self . cache_engine_version )
EngineVersion = self . cache_engine_version )
except boto . exception . BotoServerError as e :
except botocore . exceptions . ClientError as e :
self . module . fail_json ( msg = e . message )
self . module . fail_json ( msg = e . message , exception = format_exc ( ) ,
* * camel_dict_to_snake_dict ( e . response ) )
self . _refresh_data ( )
self . _refresh_data ( )
@ -333,10 +331,11 @@ class ElastiCacheManager(object):
# Collect ALL nodes for reboot
# Collect ALL nodes for reboot
cache_node_ids = [ cn [ ' CacheNodeId ' ] for cn in self . data [ ' CacheNodes ' ] ]
cache_node_ids = [ cn [ ' CacheNodeId ' ] for cn in self . data [ ' CacheNodes ' ] ]
try :
try :
response = self . conn . reboot_cache_cluster ( cache_cluster_id = self . name ,
self . conn . reboot_cache_cluster ( CacheClusterId = self . name ,
cache_node_ids_to_reboot = cache_node_ids )
CacheNodeIdsToReboot = cache_node_ids )
except boto . exception . BotoServerError as e :
except botocore . exceptions . ClientError as e :
self . module . fail_json ( msg = e . message )
self . module . fail_json ( msg = e . message , exception = format_exc ( ) ,
* * camel_dict_to_snake_dict ( e . response ) )
self . _refresh_data ( )
self . _refresh_data ( )
@ -354,7 +353,6 @@ class ElastiCacheManager(object):
info [ ' data ' ] = self . data
info [ ' data ' ] = self . data
return info
return info
def _wait_for_status ( self , awaited_status ) :
def _wait_for_status ( self , awaited_status ) :
""" Wait for status to change from present status to awaited_status """
""" Wait for status to change from present status to awaited_status """
status_map = {
status_map = {
@ -375,7 +373,7 @@ class ElastiCacheManager(object):
self . module . fail_json ( msg = msg % awaited_status )
self . module . fail_json ( msg = msg % awaited_status )
while True :
while True :
time. sleep( 1 )
sleep( 1 )
self . _refresh_data ( )
self . _refresh_data ( )
if self . status == awaited_status :
if self . status == awaited_status :
break
break
@ -399,7 +397,7 @@ class ElastiCacheManager(object):
return True
return True
# check vpc security groups
# check vpc security groups
if len ( self . security_group_ids ) > 0 :
if self . security_group_ids :
vpc_security_groups = [ ]
vpc_security_groups = [ ]
security_groups = self . data [ ' SecurityGroups ' ] or [ ]
security_groups = self . data [ ' SecurityGroups ' ] or [ ]
for sg in security_groups :
for sg in security_groups :
@ -428,13 +426,12 @@ class ElastiCacheManager(object):
def _get_elasticache_connection ( self ) :
def _get_elasticache_connection ( self ) :
""" Get an elasticache connection """
""" Get an elasticache connection """
try :
region , ec2_url , aws_connect_params = get_aws_connection_info ( self . module , boto3 = True )
return connect_to_region (
if region :
region_name = self . region ,
return boto3_conn ( self . module , conn_type = ' client ' , resource = ' elasticache ' ,
* * self . aws_connect_kwargs
region = region , endpoint = ec2_url , * * aws_connect_params )
)
else :
except boto . exception . NoAuthHandlerFound as e :
self . module . fail_json ( msg = " region must be specified " )
self . module . fail_json ( msg = e . message )
def _get_port ( self ) :
def _get_port ( self ) :
""" Get the port. Where this information is retrieved from is engine dependent. """
""" Get the port. Where this information is retrieved from is engine dependent. """
@ -450,13 +447,16 @@ class ElastiCacheManager(object):
if cache_cluster_data is None :
if cache_cluster_data is None :
try :
try :
response = self . conn . describe_cache_clusters ( cache_cluster_i d= self . name ,
response = self . conn . describe_cache_clusters ( CacheClusterI d= self . name , ShowCacheNodeInfo = True )
show_cache_node_info = True )
except botocore . exceptions . ClientError as e :
except boto . exception . BotoServerError :
if e . response [ ' Error ' ] [ ' Code ' ] == ' CacheClusterNotFound ' :
self . data = None
self . data = None
self . status = ' gone '
self . status = ' gone '
return
return
cache_cluster_data = response [ ' DescribeCacheClustersResponse ' ] [ ' DescribeCacheClustersResult ' ] [ ' CacheClusters ' ] [ 0 ]
else :
self . module . fail_json ( msg = e . message , exception = format_exc ( ) ,
* * camel_dict_to_snake_dict ( e . response ) )
cache_cluster_data = response [ ' CacheClusters ' ] [ 0 ]
self . data = cache_cluster_data
self . data = cache_cluster_data
self . status = self . data [ ' CacheClusterStatus ' ]
self . status = self . data [ ' CacheClusterStatus ' ]
@ -470,7 +470,7 @@ class ElastiCacheManager(object):
""" If there are nodes to remove, it figures out which need to be removed """
""" If there are nodes to remove, it figures out which need to be removed """
num_nodes_to_remove = self . data [ ' NumCacheNodes ' ] - self . num_nodes
num_nodes_to_remove = self . data [ ' NumCacheNodes ' ] - self . num_nodes
if num_nodes_to_remove < = 0 :
if num_nodes_to_remove < = 0 :
return None
return [ ]
if not self . hard_modify :
if not self . hard_modify :
msg = " ' %s ' requires removal of cache nodes. ' hard_modify ' must be set to true to proceed. "
msg = " ' %s ' requires removal of cache nodes. ' hard_modify ' must be set to true to proceed. "
@ -481,32 +481,32 @@ class ElastiCacheManager(object):
def main ( ) :
def main ( ) :
""" elasticache ansible module """
argument_spec = ec2_argument_spec ( )
argument_spec = ec2_argument_spec ( )
argument_spec . update ( dict (
argument_spec . update ( dict (
state = { ' required ' : True , ' choices ' : [ ' present ' , ' absent ' , ' rebooted ' ] } ,
state = { ' required ' : True , ' choices ' : [ ' present ' , ' absent ' , ' rebooted ' ] } ,
name = { ' required ' : True } ,
name = { ' required ' : True } ,
engine = { ' required ' : False , ' default ' : ' memcached ' } ,
engine = { ' required ' : False , ' default ' : ' memcached ' } ,
cache_engine_version = { ' required ' : False } ,
cache_engine_version = { ' required ' : False , ' default ' : " " } ,
node_type = { ' required ' : False , ' default ' : ' cache. m1 .small' } ,
node_type = { ' required ' : False , ' default ' : ' cache. t2 .small' } ,
num_nodes = { ' required ' : False , ' default ' : None , ' type ' : ' int ' } ,
num_nodes = { ' required ' : False , ' default ' : 1 , ' type ' : ' int ' } ,
# alias for compat with the original PR 1950
# alias for compat with the original PR 1950
cache_parameter_group = { ' required ' : False , ' default ' : None , ' aliases ' : [ ' parameter_group ' ] } ,
cache_parameter_group = { ' required ' : False , ' default ' : " " , ' aliases ' : [ ' parameter_group ' ] } ,
cache_port = { ' required ' : False , ' type ' : ' int ' } ,
cache_port = { ' required ' : False , ' type ' : ' int ' , ' default ' : None } ,
cache_subnet_group = { ' required ' : False , ' default ' : None } ,
cache_subnet_group = { ' required ' : False , ' default ' : " " } ,
cache_security_groups = { ' required ' : False , ' default ' : [ ] , ' type ' : ' list ' } ,
cache_security_groups = { ' required ' : False , ' default ' : [ ] , ' type ' : ' list ' } ,
security_group_ids = { ' required ' : False , ' default ' : [ ] , ' type ' : ' list ' } ,
security_group_ids = { ' required ' : False , ' default ' : [ ] , ' type ' : ' list ' } ,
zone = { ' required ' : False , ' default ' : None } ,
zone = { ' required ' : False , ' default ' : " " } ,
wait = { ' required ' : False , ' type ' : ' bool ' , ' default ' : True } ,
wait = { ' required ' : False , ' default ' : True , ' type ' : ' bool ' } ,
hard_modify = { ' required ' : False , ' type ' : ' bool ' , ' default ' : False }
hard_modify = { ' required ' : False , ' default ' : False , ' type ' : ' bool ' }
)
) )
)
module = AnsibleModule (
module = AnsibleModule (
argument_spec = argument_spec ,
argument_spec = argument_spec ,
)
)
if not HAS_BOTO :
if not HAS_BOTO 3 :
module . fail_json ( msg = ' boto required for this module' )
module . fail_json ( msg = ' boto 3 required for this module' )
region , ec2_url , aws_connect_kwargs = get_aws_connection_info ( module )
region , ec2_url , aws_connect_kwargs = get_aws_connection_info ( module )
@ -531,9 +531,6 @@ def main():
if state == ' present ' and not num_nodes :
if state == ' present ' and not num_nodes :
module . fail_json ( msg = " ' num_nodes ' is a required parameter. Please specify num_nodes > 0 " )
module . fail_json ( msg = " ' num_nodes ' is a required parameter. Please specify num_nodes > 0 " )
if not region :
module . fail_json ( msg = str ( " Either region or AWS_REGION or EC2_REGION environment variable or boto config aws_region or ec2_region must be set. " ) )
elasticache_manager = ElastiCacheManager ( module , name , engine ,
elasticache_manager = ElastiCacheManager ( module , name , engine ,
cache_engine_version , node_type ,
cache_engine_version , node_type ,
num_nodes , cache_port ,
num_nodes , cache_port ,
@ -555,9 +552,5 @@ def main():
module . exit_json ( * * facts_result )
module . exit_json ( * * facts_result )
# import module snippets
from ansible . module_utils . basic import *
from ansible . module_utils . ec2 import *
if __name__ == ' __main__ ' :
if __name__ == ' __main__ ' :
main ( )
main ( )