99import json
1010import logging
1111
12- from boto import cloudformation
13- from boto . exception import BotoServerError
12+ import boto3
13+ from botocore . exceptions import ClientError
1414
1515from troposphere import Template , Ref , Output , Tags
1616
@@ -114,14 +114,49 @@ def __init__(self, **kwargs):
114114 self .should_run = False
115115 self .input_wiring = {}
116116 self .last_heartbeat_id = None
117- self .boto_conn = None
117+ self ._cfn_client = None
118118 self .stack_outputs = {}
119119 self .extra_outputs = {}
120120 self .stack_name = self .get_stack_name ()
121121 self .aws_region = kwargs .get ("aws_region" , "us-east-1" )
122122 self .aws_profile = kwargs .get ("aws_profile" , None )
123123 self .stack = None
124124
125+ def _get_cfn_client (self ):
126+ if self ._cfn_client is not None :
127+ return self ._cfn_client
128+
129+ session = boto3 .session .Session (
130+ profile_name = self .aws_profile , region_name = self .aws_region
131+ )
132+ self ._cfn_client = session .client ("cloudformation" )
133+ return self ._cfn_client
134+
135+ @staticmethod
136+ def _to_cfn_parameters (parameters ):
137+ return [
138+ {"ParameterKey" : key , "ParameterValue" : str (value )}
139+ for key , value in parameters
140+ ]
141+
142+ @staticmethod
143+ def _to_cfn_tags (tags ):
144+ return [{"Key" : key , "Value" : str (value )} for key , value in tags .items ()]
145+
146+ def _describe_stack (self ):
147+ cfn = self ._get_cfn_client ()
148+ try :
149+ response = cfn .describe_stacks (StackName = self .stack_name )
150+ return response ["Stacks" ][0 ]
151+ except ClientError as e :
152+ error = e .response .get ("Error" , {})
153+ if error .get ("Code" ) != "ValidationError" :
154+ raise
155+ message = (error .get ("Message" ) or "" ).lower ()
156+ if "does not exist" not in message :
157+ raise
158+ return None
159+
125160 def connect_from (self , stack , name = None ):
126161 """
127162 Connects a node's outputs to this node's inputs.
@@ -227,7 +262,10 @@ def set_up_stack(self):
227262 """
228263 This method should be overridden to set up the stack.
229264 """
230- self .add_version ("2010-09-09" )
265+ if hasattr (self , "set_version" ):
266+ self .set_version ("2010-09-09" )
267+ else :
268+ self .add_version ("2010-09-09" )
231269
232270 def add_parameter (self , parameter , source = None ):
233271 """
@@ -316,25 +354,25 @@ def _launch_cfn(self):
316354 Sets up stack and launches it.
317355 """
318356 self .set_up_stack ()
319- self .boto_conn = cloudformation .connect_to_region (
320- region_name = self .aws_region , profile_name = self .aws_profile
321- )
357+ cfn = self ._get_cfn_client ()
322358 parameters = []
323359 for param , input_name in list (self .input_wiring .items ()):
324360 try :
325361 parameters .append ((param , self .get_input (input_name )))
326362 except MKInputError :
327363 pass
328- # check to see if stack exists
329- try :
330- self .stack = self .boto_conn .describe_stacks (self .stack_name )[0 ]
331- except BotoServerError :
332- # it would be great if we could more granularly check the error
333- self .boto_conn .create_stack (
334- self .stack_name ,
335- tags = self .get_raw_tags (),
336- template_body = self .to_json (),
337- parameters = parameters ,
364+
365+ cfn_parameters = self ._to_cfn_parameters (parameters )
366+ cfn_tags = self ._to_cfn_tags (self .get_raw_tags ())
367+
368+ # Check to see if stack exists.
369+ self .stack = self ._describe_stack ()
370+ if self .stack is None :
371+ cfn .create_stack (
372+ StackName = self .stack_name ,
373+ Tags = cfn_tags ,
374+ TemplateBody = self .to_json (),
375+ Parameters = cfn_parameters ,
338376 )
339377 self .logger .info ("Stack %s created" , self .stack_name )
340378
@@ -343,17 +381,20 @@ def _check_cfn(self):
343381 Checks the status of the stack
344382 """
345383
346- self .stack = self .boto_conn .describe_stacks (self .stack_name )[0 ]
347- self .logger .debug ("%s %s" , self .stack_name , self .stack .stack_status )
348- return self .stack .stack_status
384+ self .stack = self ._describe_stack ()
385+ if self .stack is None :
386+ raise MKNoSuchStackError
387+ status = self .stack .get ("StackStatus" )
388+ self .logger .debug ("%s %s" , self .stack_name , status )
389+ return status
349390
350391 def _assign_outputs (self ):
351392 """
352- Moves keys and values from boto output objects into dict.
393+ Moves keys and values from CloudFormation outputs into a dict.
353394 """
354395 self .stack_outputs = {}
355- for output in self .stack .outputs :
356- self .stack_outputs [output . key ] = output . value
396+ for output in self .stack .get ( "Outputs" , []) or [] :
397+ self .stack_outputs [output [ "OutputKey" ]] = output [ "OutputValue" ]
357398 self ._custom_output_transform ()
358399
359400 def _custom_output_transform (self ):
0 commit comments