1
\$\begingroup\$

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.

\$\endgroup\$
5
  • \$\begingroup\$ I don't get it. Why do you expect to get a ClientError when calling self.get_stack_info(stack_name)? It's already handled in there. \$\endgroup\$ Commented Jul 22, 2019 at 12:12
  • \$\begingroup\$ I am using AWS python SDK called boto3. If any AWS related exceptions/errors caught, then it ll falls under ClientError. 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 called get_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\$ Commented Jul 22, 2019 at 14:25
  • \$\begingroup\$ Except the ClientError is caught in get_stack_info and never get a chance to bubble up to create_cfn_stack (which makes the first solution broken, ihmo). \$\endgroup\$ Commented Jul 22, 2019 at 14:47
  • \$\begingroup\$ Please include the relevant import statements for clarity. \$\endgroup\$ Commented Jul 22, 2019 at 16:10
  • \$\begingroup\$ @MathiasEttinger, I agree with you. But the challenge here is, I have to avert the blast would be created by the get_stack_info method and proceed to create a new resource in the case of no resource with a given name is present. \$\endgroup\$ Commented Jul 22, 2019 at 17:10

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.