Development

Preparing Environment

  1. Get the source code
git clone https://github.com/CloudSnorkel/cfm-reslib.git``
  1. Switch to the code directory
cd cfm-reslib
  1. Install requirements
pip install -r requirements.txt
  1. Create a virtual environment with all of the requirements
poetry install

Run Tests

Unit tests can be executed using py.test or simply with:

poetry run py.test tests

Building

The building process creates a CloudFormation template that can be deployed and expose cfm-reslib to be imported by other CloudFormation stacks. This template uses Lambda and its source code needs to be uploaded to a bucket. The build script will create both a ZIP file and a template and will upload it to a given S3 bucket.

BUCKET=my-bucket-name
poetry run python build.py $BUCKET

And just like when deploying the released versions of cfm-reslib, you can deploy this with aws CLI tool.

BUCKET=my-bucket-name
aws cloudformation create-stack --stack-name cfm-reslib --template-url https://s3.amazonaws.com/$BUCKET/cfm-reslib-latest.template --capabilities CAPABILITY_IAM

Or when updating:

BUCKET=my-bucket-name
aws cloudformation update-stack --stack-name cfm-reslib --template-url https://s3.amazonaws.com/$BUCKET/cfm-reslib-latest.template --capabilities CAPABILITY_IAM

Note that you won’t be able to deploy multiple stacks of cfm-reslib in the same region because the exported name has to be unique across all stacks in a certain region.

Adding Custom Resources

There are two methods to implement a new custom resource. You will need to create a class for your resource in both.

  1. If the custom resource uses just one boto3 call to create, update and delete a resource, you can inherit from cfmreslib.boto.BotoResourceHandler. Simply override all of the constants with the names of the methods that need to be called and you’re done. Check out ElasticTranscoderPipeline for an example.
  2. If you need more control of the process, inherit from cfmreslib.base.CustomResourceHandler. You will have to implement some methods that will be called for requests coming from CloudFormation. Check out Route53Certificate for an example.

Once you’ve added your custom resource, make sure to add it to ALL_RESOURCES at the end of resources.py.

Classes

class cfmreslib.base.CustomResourceHandler

Abstract base class for all custom resources. Implement this class for new resources. Check the documentation for each method. Not all methods are always required.

NAME = '<not set>'

Custom resource name to be used in CloudFormation with Custom:: prefix.

DESCRIPTION = '<not set>'

Resource description for automatically generated documentation.

EXAMPLES = []

Optional resource examples to be used in documentation. Each example needs “title”, “description” and “template”.

REPLACEMENT_REQUIRED_ATTRIBUTES = {}

set of properties that require a replacement on update value changes.

exists() → bool

Checks if the resource specified in self.physical_id exists.

  • Must always be implemented
Returns:True if the resource exists, False if not
ready() → bool

Checks if the resource specified in self.physical_id is ready.

  • Must always be implemented
  • Can just return True if a resource existing means it’s ready
Returns:True if the resource exists, False if not
data() → Optional[Dict[str, object]]

Retrieves the current data that should be returned for this resource.

  • Only required if _wait_ready() is used
Returns:resource data, can be None
create(args: Dict[str, object]) → None

Creates a new resource with supplied arguments.

  • Must set self.physical_id
  • Must call _success(), _fail() or _wait_ready()
  • Must always be implemented
Parameters:args – arguments as passed from CloudFormation
can_update(old_args: Dict[str, object], new_args: Dict[str, object], diff: List[str]) → bool

Checks if a resource can safely be updated or whether a new one has to be created.

  • Must always be implemented, but can just return False if needed.
Parameters:
  • old_args – existing arguments as passed from CloudFormation for the current resource
  • new_args – requested arguments as passed from CloudFormation
  • diff – a list of argument names that have changed value
Returns:

True if the resource can be updated or False if it needs to be recreated

update(old_args: Dict[str, object], new_args: Dict[str, object], diff: List[str]) → None

Updates the resource specified in self.physical_id based on the old and new arguments.

  • Must call _success(), _fail() or _wait_ready()
  • Only required if can_update() ever returns True.
Parameters:
  • old_args – existing arguments as passed from CloudFormation for the current resource
  • new_args – requested arguments as passed from CloudFormation
  • diff – a list of argument names that have changed value
delete() → None

Deletes the resource specified in self.physical_id .

  • Must call _success(), _fail() or _wait_delete()
  • Must always be implemented
get_iam_actions() → List[str]

Returns a list of required IAM permissions for all operations.

  • Must always be implemented
class cfmreslib.boto.BotoResourceHandler
NAME = None

Custom resource name to be used in CloudFormation with Custom:: prefix.

SERVICE = None

boto3 service name that will be used to create the client (e.g. s3, acm, ec2).

CREATE_METHOD = {}

Descriptor for method used to create resource. Requires “name” with the name of the method, and “physical_id_query” used to query for the physical id of the newly created resource from the method return value.

UPDATE_METHODS = []

Optional list of descriptor for methods used to update an existing resource. Each item requires “name” with the name of the method, and “physical_id_argument” with the name of the method argument that needs to have the physical id of the updated resource.

EXISTS_METHOD = {}

Descriptor for method used to check if resource exists. Requires “name” with the name of the method, and “physical_id_argument” with the name of the method argument that needs to have the physical id of the checked resource. This method will raise the exception set in NOT_FOUND_EXCEPTION when the resource does not exist.

EXIST_READY_QUERY = {}

Optional descriptor of query to check against the result of EXISTS_METHOD. When set we will wait until the resource is ready before finishing with create and update operations. Requires “query” with the query to run over the exists method result, “expected_value” with the expected value (e.g. READY), and “failed_values” with values that denote failure and should stop the operation.

DELETE_METHOD = {}

Descriptor for method used to delete an existing resource. Requires “name” with the name of the method, and “physical_id_argument” with the name of the method argument that needs to have the physical id of the resource.

NOT_FOUND_EXCEPTION = ''

Name of exception thrown by the exists method if the resource doesn’t exist.

EXTRA_PERMISSIONS = []

A list of extra permissions required by any operations for this resource. Most permissions will be deduced by method names, but sometimes extra IAM permissions are required.