cytopia.cloudformation
Ansible Role: Create CloudFormation Stacks
Motivation | Installation | Features | Variables | Usage | Templates | Diff | Dependencies | Requirements | License
This Ansible role helps you convert multiple Jinja2 templates into CloudFormation files and deploy various stacks.
Motivation
This role solves issues found in CloudFormation templates while using many useful features of Ansible.
- CloudFormation Limits - CloudFormation has limited syntax for programming logic (like conditions and loops). By using Ansible, you can leverage Jinja2 directives, giving you more flexibility in your templates.
- Environment Agnostic - You can create CloudFormation templates that are not tied to a specific environment, making it easy to use the same templates for production, testing, staging, etc.
- Dry Run - Ansible allows you to do a dry run (
--check
mode) when deploying CloudFormation templates. You'll be able to see what changes would happen without actually applying them, allowing you to test safely.
You can either generate templates only using cloudformation_generate_only
or deploy them right after generating.
A temporary build/
directory will be created to store rendered templates. You can choose to keep it or clear it whenever this role runs by setting cloudformation_clean_build_env
.
Installation
To install the role, you can use Ansible Galaxy:
$ ansible-galaxy install cytopia.cloudformation
Or clone the repository into your roles directory:
$ git clone https://github.com/cytopia/ansible-role-cloudformation /path/to/ansible/roles
Features
- Deploy any number of CloudFormation templates.
- Use Jinja2 for template creation.
- Render templates only for use with your existing infrastructure.
- Perform a dry run to see changes without applying them.
- Compare local and deployed templates with a detailed diff.
- Store sensitive information securely using Ansible Vault.
Variables
Overview
The following variables can be configured in defaults/main.yml
to set up your infrastructure.
Variable | Type | Default | Description |
---|---|---|---|
cloudformation_clean_build_env |
bool | False |
Clean up the build/ directory on each run. |
cloudformation_generate_only |
bool | False |
Generate templates only without deploying them. |
cloudformation_run_diff |
bool | False |
Enable comparison between local and deployed templates. |
cloudformation_diff_output |
string | json |
Output format for the diff (json or yaml). |
cloudformation_required |
list | [] |
Keys that are required for CloudFormation stacks. |
cloudformation_defaults |
dict | {} |
Default values for every CloudFormation stack. |
cloudformation_stacks |
list | [] |
Lists of CloudFormation stacks to deploy. |
Details
Details of certain variables are as follows:
cloudformation_defaults
Key | Type | Required | Description |
---|---|---|---|
aws_access_key |
string | optional | AWS access key. |
aws_secret_key |
string | optional | AWS secret key. |
security_token |
string | optional | AWS security token. |
profile |
string | optional | AWS profile to use. |
notification_arns |
string | optional | ARNs for stack notifications. |
termination_protection |
bool | optional | Enable termination protection on stack. |
region |
string | optional | Target AWS region for the stack. |
cloudformation_stacks
Key | Type | Required | Description |
---|---|---|---|
stack_name |
string | required | Name of your CloudFormation stack. |
template |
string | required | Path to the CloudFormation template. |
aws_access_key |
string | optional | Overrides default AWS access key. |
aws_secret_key |
string | optional | Overrides default AWS secret key. |
security_token |
string | optional | Overrides default AWS security token. |
profile |
string | optional | Overrides default AWS profile. |
notification_arns |
string | optional | Overrides default ARN for notifications. |
termination_protection |
bool | optional | Overrides termination protection settings. |
region |
string | optional | Overrides default AWS region. |
template_parameters |
dict | optional | Required parameters for the CloudFormation stack. |
tags |
dict | optional | Tags for the CloudFormation stack. |
Examples
This example enforces that 'profile' must be set for each CloudFormation stack:
cloudformation_required:
- profile
cloudformation_defaults:
region: eu-central-1
Defining CloudFormation stacks to render and deploy:
cloudformation_stacks:
- stack_name: stack-s3
template: files/cloudformation/s3.yml.j2
profile: production
template_parameters:
bucketName: my-bucket
tags:
env: production
- stack_name: stack-lambda
template: files/cloudformation/lambda.yml.j2
profile: production
termination_protection: True
template_parameters:
lambdaFunctionName: lambda
handler: lambda.run_handler
runtime: python2.7
s3Bucket: my-bucket
s3Key: lambda.py.zip
tags:
env: production
If you only want to render your Jinja2 templates and not deploy them, you can do it like this:
$ ansible-playbook play.yml -e cloudformation_generate_only=True
Usage
Simple
A basic usage example:
playbook.yml
- hosts: localhost
connection: local
roles:
- cloudformation
group_vars/all.yml
cloudformation_stacks:
- stack_name: stack-s3
profile: testing
region: eu-central-1
template: files/cloudformation/s3.yml.j2
template_parameters:
bucketName: my-bucket
tags:
env: testing
- stack_name: stack-lambda
profile: testing
termination_protection: True
region: eu-central-1
template: files/cloudformation/lambda.yml.j2
template_parameters:
lambdaFunctionName: lambda
handler: lambda.run_handler
runtime: python2.7
s3Bucket: my-bucket
s3Key: lambda.py.zip
tags:
env: testing
Advanced
For advanced usage, you can call the role in different virtual hosts:
inventory
[my-group]
infrastructure ansible_connection=local
application ansible_connection=local
playbook.yml
- hosts: infrastructure
roles:
- cloudformation
tags:
- infrastructure
- hosts: application
roles:
- some-role
tags:
- some-role
- application
- hosts: application
roles:
- cloudformation
tags:
- application
group_vars/my-group.yml
stack_prefix: testing
boto_profile: testing
s3_bucket: awesome-lambda
cloudformation_defaults:
profile: "{{ boto_profile }}"
region: eu-central-1
host_vars/infrastructure.yml
cloudformation_stacks:
- stack_name: "{{ stack_prefix }}-s3"
template: files/cloudformation/s3.yml.j2
template_parameters:
bucketName: "{{ s3_bucket }}"
tags:
env: "{{ stack_prefix }}"
host_vars/application.yml
cloudformation_stacks:
- stack_name: "{{ stack_prefix }}-lambda"
template: files/cloudformation/lambda.yml.j2
template_parameters:
lambdaFunctionName: lambda
handler: lambda.run_handler
runtime: python2.7
s3Bucket: "{{ s3_bucket }}"
s3Key: lambda.py.zip
tags:
env: "{{ stack_prefix }}"
Templates
This section shows what you can do with CloudFormation templates using Jinja2.
Example: Subnet Definitions
Define different subnets for staging and production environments using Ansible variables:
# file: staging.yml
vpc_subnets:
- directive: subnetA
az: a
cidr: 10.0.10.0/24
tags:
- name: Name
value: staging-subnet-a
- directive: subnetB
az: b
cidr: 10.0.20.0/24
tags:
- name: Name
value: staging-subnet-b
# file: production.yml
vpc_subnets:
- directive: subnetA
az: a
cidr: 10.0.10.0/24
tags:
- name: Name
value: prod-subnet-a
- directive: subnetB
az: b
cidr: 10.0.20.0/24
tags:
- name: Name
value: prod-subnet-b
- directive: subnetC
az: b
cidr: 10.0.30.0/24
tags:
- name: Name
value: prod-subnet-c
CloudFormation template:
AWSTemplateFormatVersion: '2010-09-09'
Description: VPC Template
Resources:
vpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: {{ vpc_cidr_block }}
EnableDnsSupport: true
EnableDnsHostnames: true
{% if vpc_tags %}
Tags:
{% for tag in vpc_tags %}
- Key: {{ tag.name }}
Value: {{ tag.value }}
{% endfor %}
{% endif %}
{% for subnet in vpc_subnets %}
{{ subnet.directive }}:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: {{ subnet.az }}
CidrBlock: {{ subnet.cidr }}
VpcId: !Ref vpc
{% if subnet.tags %}
Tags:
{% for tag in subnet.tags %}
- Key: {{ tag.name }}
Value: {{ tag.value }}
{% endfor %}
{% endif %}
Example: Security Groups
Defining security groups with specific IP rules can be simplified with Jinja2 to keep templates consistent across environments:
Ansible variables for staging:
# file: staging.yml
security_groups:
- protocol: tcp
from_port: 3306
to_port: 3306
cidr_ip: 10.0.0.1/32
- protocol: tcp
from_port: 3306
to_port: 3306
cidr_ip: 192.168.0.15/32
Ansible variables for production:
# file: production.yml
security_groups:
- protocol: tcp
from_port: 3306
to_port: 3306
cidr_ip: 10.0.15.1/32
CloudFormation template:
AWSTemplateFormatVersion: '2010-09-09'
Description: VPC Template
Resources:
rdsSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: RDS security group
{% if security_groups %}
SecurityGroupIngress:
{% for rule in security_groups %}
- IpProtocol: "{{ rule.protocol }}"
FromPort: "{{ rule.from_port }}"
ToPort: "{{ rule.to_port }}"
CidrIp: "{{ rule.cidr_ip }}"
{% endfor %}
{% endif %}
Diff
When you enable cloudformation_run_diff
, you'll see a line-by-line comparison between your local template and the one currently deployed on AWS. You can run Ansible with --diff
to see this:
$ ansible-playbook play.yml --diff
Json diff
Set cloudformation_diff_output
to json
for JSON output:
TASK [cloudformation : diff cloudformation template file] *********************************************
--- before
+++ after
@@ -38,7 +38,6 @@
"Type": "AWS::S3::BucketPolicy"
},
"s3Bucket": {
- "DeletionPolicy": "Retain",
"Properties": {
"BucketName": {
"Ref": "bucketName"
Yaml diff
Set cloudformation_diff_output
to yaml
for YAML output:
TASK [cloudformation : diff cloudformation template file] *********************************************
--- before
+++ after
@@ -14,7 +14,6 @@
Service: !Sub 'logs.${AWS::Region}.amazonaws.com'
Bucket: !Ref 's3Bucket'
s3Bucket:
- DeletionPolicy: Retain
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref 'bucketName'
Dependencies
This role does not depend on any other roles.
Requirements
Make sure you have at least Ansible 2.5 to use --check
mode for CloudFormation.
You will need the cfn_flip
Python module if you want to use line-by-line diffing between local and remote CloudFormation templates:
$ pip install cfn_flip
License
Copyright (c) 2017 cytopia
Ansible role to render an arbitrary number of Jinja2 templates into cloudformation files and create any number of stacks.
ansible-galaxy install cytopia.cloudformation