The following Python class is what I am trying to refactor in the case where I have to add validation of the existence of an AWS cloudformation stack before creating it in the method create_cfn_stack :
import boto3
import botocore
from botocore.exceptions import ClientError
class AWSPlugin(object):
""" AWS Plugin class """
def __init__(**kwargs):
""" setting kwargs to class object variables"""
pass
def get_stack_info(self, stack_name):
""" returns the cloudformation stack details """
client_response = None
try:
cfn_client = self.get_client(AWS_SERVICES.CLOUDFORMATION)
response = cfn_client.describe_stacks(StackName=stack_name)
client_response = response['Stacks'].pop()
except ClientError as ce:
self.log.error("Failed to retrieve cloudformation stack object : %s", ce)
except Exception as e:
self.log.exception("Exception occurred while retrieving cloudformation stack object : %s", e)
return client_response
def create_cfn_stack(self, stack_name, cfn_template_file, parameters):
""" creates a cloudformation stack """
client_response = None
try:
with open(cfn_template_file, 'r') as jfp:
template_body = jfp.read()
cfn_client = self.get_client(AWS_SERVICES.CLOUDFORMATION)
cfn_response = cfn_client.create_stack(
StackName=stack_name,
TemplateBody=template_body,
Parameters=parameters,
DisableRollback=False,
Tags=[
{
'Key': 'Name',
'Value': stack_name
},
]
)
client_response = cfn_response['StackId']
except ClientError as ce:
self.log.error("Failed to create cloudformation stack : %s", ce)
except Exception as e:
self.log.exception("Exception occurred while creating cloudformation stack : %s", e)
return client_response
There are 3 ways the code can could be refactored as shown here :
Way 1:
def create_cfn_stack(self, stack_name, cfn_template_file, parameters):
""" creates a cloudformation stack """
client_response = None
try:
existing_stack = self.get_stack_info(stack_name)
if existing_stack is not None:
raise Exception("Stack %s is already exists and in %s state" %(stack_name, existing_stack['Status']))
except ClientError as ce:
print "Stack is not present and its good to create with name %s" % (stack_name)
with open(cfn_template_file, 'r') as jfp:
template_body = jfp.read()
cfn_client = self.get_client(AWS_SERVICES.CLOUDFORMATION)
cfn_response = cfn_client.create_stack(
StackName=stack_name,
TemplateBody=template_body,
Parameters=parameters,
DisableRollback=False,
Tags=[
{
'Key': 'Name',
'Value': stack_name
},
]
)
client_response = response['StackId']
except Exception as e:
self.log.exception("Exception occurred while creating cloudformation stack : %s", e)
return client_response
Way 2
def create_cfn_stack(self, stack_name, cfn_template_file, parameters):
""" creates a cloudformation stack """
client_response = None
try:
try:
existing_stack = self.get_stack_info(stack_name)
if existing_stack is not None:
raise Exception("Stack %s is already exists and in %s state" %(stack_name, existing_stack['Status']))
except ClientError as ce:
print "Stack is not present and its good to create with name %s" % (stack_name)
with open(cfn_template_file, 'r') as jfp:
template_body = jfp.read()
cfn_client = self.get_client(AWS_SERVICES.CLOUDFORMATION)
cfn_response = cfn_client.create_stack(
StackName=stack_name,
TemplateBody=template_body,
Parameters=parameters,
DisableRollback=False,
Tags=[
{
'Key': 'Name',
'Value': stack_name
},
]
)
client_response = cfn_response['StackId']
except ClientError as ce:
self.log.error("Failed to create cloudformation stack : %s", ce)
except Exception as e:
self.log.exception("Exception occurred while creating cloudformation stack : %s", e)
return client_response
Way 3
def create_cfn_stack(self, stack_name, cfn_template_file, parameters):
""" creates a cloudformation stack """
client_response = None
try:
existing_stack = self.get_stack_info(stack_name)
if existing_stack is not None:
raise Exception("Stack %s is already exists and in %s state" %(stack_name, existing_stack['Status']))
except ClientError as ce:
print "Stack is not present and its good to create with name %s" % (stack_name)
try:
with open(cfn_template_file, 'r') as jfp:
template_body = jfp.read()
cfn_client = self.get_client(AWS_SERVICES.CLOUDFORMATION)
cfn_response = cfn_client.create_stack(
StackName=stack_name,
TemplateBody=template_body,
Parameters=parameters,
DisableRollback=False,
Tags=[
{
'Key': 'Name',
'Value': stack_name
},
]
)
client_response = cfn_response['StackId']
except ClientError as ce:
self.log.error("Failed to create cloudformation stack : %s", ce)
except Exception as e:
self.log.exception("Exception occurred while creating cloudformation stack : %s", e)
return client_response
Among 3 or apart from what I have shared here, I am not sure what is the standardized way to handle such a scenario. Helping hands are very much appreciated.
ClientError
when callingself.get_stack_info(stack_name)
? It's already handled in there. \$\endgroup\$boto3
. If any AWS related exceptions/errors caught, then it ll falls underClientError
. In this scenario, I wanted to create an AWS resource in the specified name which should not exist when I call this method. To validate that, I called another method calledget_stack_info()
which returns detailed info about stack if exists else it'll throw ClientError. I have to react to both the situation and act accordingly. \$\endgroup\$ClientError
is caught inget_stack_info
and never get a chance to bubble up tocreate_cfn_stack
(which makes the first solution broken, ihmo). \$\endgroup\$import
statements for clarity. \$\endgroup\$