README.md

Summary

Maintainability
Test Coverage
# Cfoo

[![Build Status](https://travis-ci.org/drrb/cfoo.svg)](https://travis-ci.org/drrb/cfoo)
[![Coverage Status](https://img.shields.io/coveralls/drrb/cfoo.svg)](https://coveralls.io/r/drrb/cfoo)
[![Code Climate](https://img.shields.io/codeclimate/github/drrb/cfoo.svg)](https://codeclimate.com/github/drrb/cfoo)

[![Gem Version](https://badge.fury.io/rb/cfoo.svg)](https://badge.fury.io/rb/cfoo)
[![Dependency Status](https://gemnasium.com/drrb/cfoo.svg)](https://gemnasium.com/drrb/cfoo)

Cfoo (pronounced "sifu") lets you write your [CloudFormation](https://aws.amazon.com/cloudformation)
templates [in YAML](#templates), and makes it easier with some [helpers](#shortcuts).

## Installation

Cfoo can be installed as a Ruby Gem

    $ gem install cfoo

## Usage

1. Write your CloudFormation templates using Cfoo YAML 

2. Turn your Cfoo templates into normal CloudFormation templates
```terminal
$ cfoo web-server.template.yml database.template.yml > web-stack.template.json
```

3. Create your stack with CloudFormation
```terminal
$ cfn-create-stack --stack-name WebStack -f web-stack.template.json
```

## Templates

### Comparison with standard CloudFormation templates

Snippet from a CloudFormation template (based on [this example](https://s3.amazonaws.com/cloudformation-templates-us-east-1/Rails_Single_Instance.template)):

```json
"Properties": {
  "ImageId" : { "Fn::FindInMap" : [ "AWSRegion2AMI", { "Ref" : "AWS::Region" }, "AMI" ] },
  "InstanceType"   : { "Ref" : "InstanceType" },
  "SecurityGroups" : [ {"Ref" : "FrontendGroup"} ],
  "KeyName"        : { "Ref" : "KeyName" },
  "UserData"       : { "Fn::Base64" : { "Fn::Join" : ["", [
    "#!/bin/bash -v\n",
    "yum update -y aws-cfn-bootstrap\n",

    "function error_exit\n",
    "{\n",
    "  /opt/aws/bin/cfn-signal -e 1 -r \"$1\" '", { "Ref" : "WaitHandle" }, "'\n",
    "  exit 1\n",
    "}\n",

    "/opt/aws/bin/cfn-init -s ", { "Ref" : "AWS::StackId" }, " -r WebServer ",
    "    --region ", { "Ref" : "AWS::Region" }, " || error_exit 'Failed to run cfn-init'\n",

    "/opt/aws/bin/cfn-signal -e 0 -r \"cfn-init complete\" '", { "Ref" : "WaitHandle" }, "'\n"
  ]]}}
}
```

Equivalent Cfoo template snippet:

```yaml
Properties:
  ImageId : AWSRegion2AMI[$(AWS::Region)][AMI]
  InstanceType: $(InstanceType)
  SecurityGroups:
     - $(FrontendGroup)
  KeyName: $(KeyName)
  UserData: !Base64 |
    #!/bin/bash -v
    yum update -y aws-cfn-bootstrap

    function error_exit
    {
      /opt/aws/bin/cfn-signal -e 1 -r "\$1" '$(WaitHandle)'
      exit 1
    }

    /opt/aws/bin/cfn-init -s $(AWS::StackId) -r WebServer --region $(AWS::Region) || error_exit 'Failed to run cfn-init'

    /opt/aws/bin/cfn-signal -e 0 -r "cfn-init completed" '$(WaitHandle)'
```

## Projects

Using Cfoo, it is possible to split your templates up into logical components that will
combined to form your CloudFormation template.

First, create a directory in your project directory called `modules`. For each module,
create some Cfoo templates defining the different parts of your app. Your project
structure will look like this:

```
my-web-app
└── modules
    ├── application
    │   ├── app_servers.yml
    │   ├── database.yml
    │   └── public_load_balancer.yml
    ├── instances
    │   └── instance_mappings.yml
    └── network
        ├── bastion.yml
        ├── cfn_user.yml
        ├── dns.yml
        ├── nat.yml
        ├── private_subnet.yml
        ├── public_subnet.yml
        └── vpc.yml
```

Use Cfoo to generate your project's CloudFormation template

```terminal
$ cfoo > web-app.template.json
```

## Shortcuts

Cfoo allows you to simplify CloudFormation [intrinsic function](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html)
references using its own shorthand

##### Reference

CloudFormation: `{ "Ref" : "InstanceType" }`

Cfoo Shortcut: `$(InstanceType)`

##### Mapping Reference

CloudFormation: `{ "FindInMap" : [ "SubnetConfig", "VPC", "CIDR" ] }`

Cfoo Shortcut: `$(SubnetConfig[VPC][CIDR])`

##### Attribute Reference

CloudFormation: `{ "Fn::GetAtt" : [ "Ec2Instance", "PublicIp" ] }`

Cfoo Shortcut: `$(Ec2Instance[PublicIp])`

##### Embedded Reference

CloudFormation: `{ "Fn::Join" : [ "", [{"Ref" : "HostedZone"}, "." ]]}`

Cfoo Shortcut: `$(HostedZone).`

### YAML Types

Cfoo gives you the option of using YAML custom data-types where it helps to make your templates easier to read.

##### Reference

CloudFormation:
```json
{ "Ref" : "InstanceType" }
```

YAML Type:
```yaml
!Ref InstanceType
```

##### Mapping Reference

CloudFormation:
```json
{ "FindInMap" : [ "SubnetConfig", "VPC", "CIDR" ] }
```

YAML Type:
```yaml
!FindInMap [ SubnetConfig, VPC, CIDR ]
```

##### Attribute Reference

CloudFormation:
```json
{ "Fn::GetAtt" : [ "Ec2Instance", "PublicIp" ] }
```

YAML Type:
```yaml
!GetAtt [ Ec2Instance, PublicIp ]
```

##### Base64 String

CloudFormation:
```json
{ "Fn::Base64" : "#!/bin/bash\necho 'Running script...'" }
```

YAML Type:
```yaml
!Base64 "#!/bin/bash\necho 'running script...'"
```

Alternative YAML Type:
```yaml
!Base64 |
    #!/bin/bash
    echo 'running script...'
```

##### Condition Function

CloudFormation:
```json
{ "Fn::Equals": [ "sg-mysggroup", {"Ref": "ASecurityGroup"} ] },
```

YAML Type:
```yaml
!Equals [ "sg-mysggroup", $(ASecurityGroup) ]
```

## Goals

### Primary Goals

Cfoo aims to let developers simplify CloudFormation templates by:

- allowing them to write templates in YAML
- providing an expression language to simplify CF Ref/Attr/etc expressions
- allowing templates to be split up into logical components (to simplify and share)

### Secondary Goals

Cfoo also aims (subject to Primary Goals) to:

- allow inclusion existing JSON templates (so you don't have to switch all at once)

### Non-goals

Cfoo does not (yet) aim to:

- provide commandline utilities for interacting directly with CloudFormation (it just generates the templates for now)
- resolve/validate references (the CloudFormation API already does this)

## Contributing

1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Make your changes (with tests please)
4. Commit your changes (`git commit -am 'Add some feature'`)
5. Push to the branch (`git push origin my-new-feature`)
6. Create new Pull Request