Lono

ReadmeCI Gem Version CircleCI Code Climate Dependency Status Coverage Status

Lono is a tool for managing CloudFormation templates.

  • Lono generates CloudFormation templates based on ERB ruby templates in either yaml or json format.
  • Lono takes simple env-like files to and generates the CloudFormation parameter files.
  • Lono wraps the CloudFormation api calls in a simple interface using the generated files to launch the CloudFormation stacks.

Lono documentation is at lono.cloud.

These blog posts also cover lono:

Usage


$ lono new infra

This sets up a starter lono project called infra with example templates. Next you cd into the folder and generate the template and parameter files.


$ cd infra
$ lono generate

This CloudFormation template and parameter files are now available under output and output/params.

Generate Template Files

The CloudFormation templates files that were generated from the config and templates folders and written to the output folder.

The starter lono template project config files looks like this and this. Here's a snippet from one of the config files: config/lono.rb.

template "api-web-prod.yml" do
  app,role,env = name.sub('.yml','').split('-')
  source "web.yml.erb"
  variables(
    env: env,
    app: app,
    role: role,
    ami: "ami-123",
    instance_type: "m1.small",
    port: "80",
    high_threshold: "15",
    high_periods: "4",
    low_threshold: "5",
    low_periods: "10",
    max_size: "24",
    min_size: "6",
    down_adjustment: "-3",
    up_adjustment: "3",
    ssl_cert: "arn:aws:iam::12345:server-certificate/wildcard"
  )
end

Here is the corresponding ERB template templates/web.yml.erb. Note that some of the source code has been shorten for brevity.

<% @app,@role,@env = name.sub('.yml','').split('-') -%>
---
AWSTemplateFormatVersion: '2010-09-09'
Description: <%= @app.capitalize %> Stack
Mappings:
...
Outputs:
...
Parameters:
  Application:
    Default: <%= @app %>
    Description: Application name
    Type: String
...
Resources:
  CPUAlarmHigh:
    Properties:
      AlarmActions:
      - Ref: WebServerScaleUpPolicy
      AlarmDescription: Scale-up if CPU > <%= @high_threshold %>% for <%= @high_mins %>
...
<%= partial("host_record.yml.erb", domain: "mydomain.com") %>
  LaunchConfig:
    Properties:
      BlockDeviceMappings:
      - DeviceName: "/dev/sdb"
        VirtualName: ephemeral0
      ImageId:
...
      UserData:
        Fn::Base64: !Sub | # No more Fn::Join needed
          #!/bin/bash -lexv
          <% stack_name = "#{@env}-#{@app}-#{@role}" %>
          exec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1
          echo <%= stack_name %> > /tmp/stack_name
          cat /proc/uptime | cut -f1 -d'.' > /tmp/time-to-boot
    Type: AWS::AutoScaling::LaunchConfiguration

The generated output/blog-web-prod.yml CloudFormation template looks like this:

---
AWSTemplateFormatVersion: '2010-09-09'
Description: Api Stack
Mappings:
...
Outputs:
...
Parameters:
  Application:
    Default: api
    Description: Application name
    Type: String
...
Resources:
  CPUAlarmHigh:
    Properties:
      AlarmActions:
      - Ref: WebServerScaleUpPolicy
      AlarmDescription: Scale-up if CPU > 15% for
...
  HostRecord:
    Properties:
      Comment: DNS name for mydomain.com
      HostedZoneName: ".mydomain.net."
      Name:
        Fn::Join:
        - ''
        - - Ref: AWS::StackName
          - mydomain.com
      ResourceRecords:
      - Fn::GetAtt:
        - elb
        - DNSName
      TTL: '60'
      Type: CNAME
    Type: AWS::Route53::RecordSet
  LaunchConfig:
    Properties:
      BlockDeviceMappings:
      - DeviceName: "/dev/sdb"
        VirtualName: ephemeral0
      ImageId:
...
      UserData:
        Fn::Base64: |
          #!/bin/bash -lexv

          exec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1
          echo api-web-prod > /tmp/stack_name
          cat /proc/uptime | cut -f1 -d'.' > /tmp/time-to-boot
    Type: AWS::AutoScaling::LaunchConfiguration

You can use the generated CloudFormation templates in the output folder just as you would a normal CloudFormation template. Here's a flow chart of the overall process.

Lono flowchart

Generate Parameter Files

With lono you write simpler params file that is formatted with a simple key=value env-like file. The params/mystack.txt is shorter format versus the verbose standard CloudFormation parameters json file. Here is an example of a simple params/mystack.txt param file.

Param1=1
Param2=2

The lono generate command generates the CloudFormation parameter file in output/params/mystack.json which looks like this:

[
  {
    "ParameterKey": "Param1",
    "ParameterValue": "1"
  },
  {
    "ParameterKey": "Param2",
    "ParameterValue": "2"
  }
]

Template helper methods

There are helper methods that are available in templates.

  • partial(relative_path, variables, options) - Can be use to embed other files in a template. The partial view should be placed in the templates/partial folder of the project. So:

    • partial('launch_config.json.erb') -> templates/partial/launch_config.json.erb
    • partial('launch_config.json.erb', foo: "bar", hello: "world") - variables can be passed to the partial helper method are available to the partial as instance variables. So, in this case @foo and @hello will be available in the launch_config.json.erb partial.
    • partial('user_data/bootstrap.sh.erb', {}, indent: 10) - Indent the result partial by 10 spaces. Useful for yaml format.
  • user_data - Helpful if you are using CloudFormation json format. More info on the wiki page.

Breaking up config/lono.rb

If you have a lot of templates, the config/lono.rb file can get unwieldy long. You can break up the lono.rb file and put template defintions in the config/lono directory. Any file in this directory will be automatically loaded. An example is in the starter project.

lono generate

You can generate the CloudFormation templates by running:


$ lono generate
$ lono g -c # shortcut

The lono init command also sets up guard-lono. Guard-lono continuously generates the cloudformation templates. Just run guard.


$ guard

lono cfn

Summary

Lono also provides a lono cfn management command that allows you to launch stacks from the lono templates. The lono cfn tool automatically runs lono generate internally and then launches the CloudFormation stack all in one command. Provided that you are in a lono project and have a my-stack lono template definition. To create a stack you can simply run:

$ lono cfn create my-stack

The above command will generate files to output/my-stack.json and output/params/my-stack.txt and use them to create a CloudFormation stack. Here are some more examples of cfn commands:

$ lono cfn create mystack-$(date +%Y%m%d%H%M%S) --template mystack --params mystack
$ lono cfn create mystack-$(date +%Y%m%d%H%M%S) # shorthand if template and params file matches.
$ lono cfn diff mystack-1493859659
$ lono cfn preview mystack-1493859659
$ lono cfn update mystack-1493859659
$ lono cfn delete mystack-1493859659
$ lono cfn create -h # getting help

Conventions

The template by convention defaults to the name of the stack. In turn, the params by convention defaults to the name of the template.

  • stack - This is a required parameter and is the CLI first parameter.
  • template - By convention matches the stack name but can be overriden with --template.
  • params - By convention matches the template name but can be overriden with --params.

The conventions allows the command to be a very nice short command that can be easily remembered. For example, these 2 commands are the same:

lono cfn create

Long form:

$ lono cfn create my-stack --template my-stack --params --my-stack

Short form:

$ lono cfn create my-stack

Both template and params conventions can be overridden. Here are examples of overriding the template and params name conventions.

$ lono cfn create my-stack --template different-name1

The template that will be use is output/different-name1.json and the parameters will use params/different-name1.json.

$ lono cfn create my-stack --params different-name2

The template that will be use is output/different-name2.json and the parameters will use params/different-name2.json.

$ lono cfn create my-stack --template different-name3 --params different-name4

The template that will be use is output/different-name3.json and the parameters will use params/different-name4.json.

lono cfn update

To update stacks you can use lono cfn update:

$ lono cfn update my-stack --template template-name --params params-name

By default the update command will display a preview of the stack changes before applying the update and prompt to check if you are sure. If you want to bypass the are you sure prompt, use the --sure option.

$ lono cfn update my-stack --template template-name --params params-name --sure

lono cfn delete

$ lono cfn delete my-stack --sure

lono cfn preview

If you want to see the CloudFormation preview without updating the stack you can also use the lono cfn preview command. The preview command is also covered in this blog post: AWS CloudFormation dry-run with lono cfn preview

$ lono cfn preview example --template single_instance --params single_instance
Using template: output/single_instance.yml
Using parameters: params/single_instance.txt
Generating CloudFormation templates:
  ./output/single_instance.yml
Params file generated for example at ./output/params/example.json
Generating CloudFormation Change Set for preview.....
CloudFormation preview for 'example' stack update. Changes:
Remove AWS::Route53::RecordSet: DnsRecord testsubdomain.sub.tongueroo.com
$

Contributing

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

Developing

There is a submodule in this project, so when you check out remember to sync the submodule.

$ git clone [email protected]:yourfork/lono.git
$ git submodule sync
$ git submodule update --init