Write unit and functional tests for AWS Cloudformation.
Report Bug
·
Request Feature
·
Guide
Table of Contents
Cloud-Radar is a python module that allows testing of Cloudformation Templates/Stacks using Python.
You can now unit test the logic contained inside your Cloudformation template. Cloud-Radar takes your template, the desired region and some parameters. We render the template into its final state and pass it back to you.
You can Test:
- That Conditionals in your template evaluate to the correct value.
- Conditional resources were created or not.
- That resources have the correct properties.
- That resources are named as expected because of
!Sub
.
You can test all this locally without worrying about AWS Credentials.
This project is a wrapper around Taskcat. Taskcat is a great tool for ensuring your Cloudformation Template can be deployed in multiple AWS Regions. Cloud-Radar enhances Taskcat by making it easier to write more complete functional tests.
Here's How:
- You can interact with the deployed resources directly with tools you already know like boto3.
- You can control the lifecycle of the stack. This allows testing if resources were retained after the stacks were deleted.
- You can run tests without hardcoding them in a taskcat config file.
This project is new and it's possible not all features or functionality of Taskcat/Cloudformation are supported (see Roadmap). If you find something missing or have a use case that isn't covered then please let me know =)
Cloud-Radar is available as an easy to install pip package.
Cloud-Radar requires python >= 3.8
- Install with pip.
pip install cloud-radar
Unit Testing (Click to expand)
Using Cloud-Radar starts by importing it into your test file or framework. We will use this Template as an example.
from pathlib import Path
from cloud_radar.cf.unit import Template
template_path = Path("tests/templates/log_bucket/log_bucket.yaml")
# template_path can be a str or a Path object
template = Template.from_yaml(template_path.resolve())
params = {"BucketPrefix": "testing", "KeepBucket": "TRUE"}
# parameters and region are optional arguments.
stack = template.create_stack(params, region="us-west-2")
stack.no_resource("LogsBucket")
bucket = stack.get_resource("RetainLogsBucket")
assert "DeletionPolicy" in bucket
assert bucket["DeletionPolicy"] == "Retain"
bucket_name = bucket.get_property_value("BucketName")
assert "us-west-2" in bucket_name
The AWS pseudo variables are all class attributes and can be modified before rendering a template.
# The value of 'AWS::AccountId' in !Sub "My AccountId is ${AWS::AccountId}" can be changed:
Template.AccountId = '8675309'
Note: Region should only be changed to change the default value. To change the region during testing pass the desired region to render(region='us-west-2')
The default values for psedo variables:
Name | Default Value |
---|---|
AccountId | "555555555555" |
NotificationARNs | [] |
NoValue | "" |
Partition | "aws" |
Region | "us-east-1" |
StackId | "" |
StackName | "" |
URLSuffix | "amazonaws.com" |
Note: Bold variables are not fully impletmented yet see the Roadmap |
A real unit testing example using Pytest can be seen here
Functional Testing (Click to expand)
Using Cloud-Radar starts by importing it into your test file or framework.from pathlib import Path
from cloud_radar.cf.e2e import Stack
# Stack is a context manager that makes sure your stacks are deleted after testing.
template_path = Path("tests/templates/log_bucket/log_bucket.yaml")
params = {"BucketPrefix": "testing", "KeepBucket": "False"}
regions = ['us-west-2']
# template_path can be a string or a Path object.
# params can be optional if all your template params have default values
# regions can be optional, default region is 'us-east-1'
with Stack(template_path, params, regions) as stacks:
# Stacks will be created and returned as a list in the stacks variable.
for stack in stacks:
# stack will be an instance of Taskcat's Stack class.
# It has all the expected properties like parameters, outputs and resources
print(f"Testing {stack.name}")
bucket_name = ""
for output in stack.outputs:
if output.key == "LogsBucketName":
bucket_name = output.value
break
assert "logs" in bucket_name
assert stack.region.name in bucket_name
print(f"Created bucket: {bucket_name}")
# Once the test is over then all resources will be deleted from your AWS account.
You can use taskcat tokens in your parameter values.
parameters = {
"BucketPrefix": "taskcat-$[taskcat_random-string]",
"KeepBucket": "FALSE",
}
You can skip the context manager. Here is an example for unittest
import unittest
from cloud-radar.cf.e2e import Stack
class TestLogBucket(unittest.TestCase):
@classmethod
def setUpClass(cls):
template_path = Path("tests/templates/log_bucket/log_bucket.yaml")
cls.test = Stack(template_path)
cls.test.create()
@classmethod
def tearDownClass(cls):
cls.test.delete()
def test_bucket(self):
stacks = self.__class__.test.stacks
for stack in stacks:
# Test
All the properties and methods of a stack instance.
A real functional testing example using Pytest can be seen here
- Python 3.7 support
- Add Logging
- Add Logo
- Make it easier to interact with stack resources.
- Getting a resource for testing should be as easy as
stack.Resources('MyResource)
ortemplate.Resources('MyResource')
- Getting a resource for testing should be as easy as
- Easier to pick regions for testing
- Add full functionality to pseudo variables.
- Variables like
Partition
,URLSuffix
should change if the region changes. - Variables like
StackName
andStackId
should have a better default than ""
- Variables like
- Handle References to resources that shouldn't exist.
- It's currently possible that a
!Ref
to a Resource stays in the final template even if that resource is later removed because of a conditional.
- It's currently possible that a
- Handle function order
- Some functions are only allowed in certain parts of the template.
- Add the ability to update a stack instance to Taskcat.
See the open issues for a list of proposed features (and known issues).
Contributions are what make the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
This project uses poetry to manage dependencies and pre-commit to run formatting, linting and tests. You will need to have both installed to your system as well as python 3.9.
- Fork the Project
- Setup environment (
poetry install
) - Setup commit hooks (
pre-commit install
) - Create your Feature Branch (
git checkout -b feature/AmazingFeature
) - Commit your Changes (
git commit -m 'Add some AmazingFeature'
) - Push to the Branch (
git push origin feature/AmazingFeature
) - Open a Pull Request
Distributed under the Apache-2.0 License. See LICENSE.txt for more information.
Levi - @shady_cuz