cytopia.cloudformation

Ansible Role: Create CloudFormation Stacks

Motivation | Installation | Features | Variables | Usage | Templates | Diff | Dependencies | Requirements | License

Build Status
Ansible Galaxy
Release

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.

  1. 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.
  2. 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.
  3. 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

MIT License

Copyright (c) 2017 cytopia

Informazioni sul progetto

Ansible role to render an arbitrary number of Jinja2 templates into cloudformation files and create any number of stacks.

Installa
ansible-galaxy install cytopia.cloudformation
Licenza
mit
Download
5.2k
Proprietario
DevOps Engineer