CloudFormation::DSL - A Domain Specific Language for creating CloudFormation templates
package MyStack {
use CloudFormation::DSL;
resource Instance1 => 'AWS::EC2::Instance', {
ImageId => 'ami-12345',
};
output IP => GetAtt('Instance1', 'PublicIp');
}
my $s1 = MyStack->new;
say "Resource Count: " . $s1->ResourceCount;
print $s1->as_hashref;
CloudFormation is a great AWS service for automating infrastructure creation. You can get a better grasp of what CloudFormation does and tries to solve here: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html
CloudFormation::DSL is a "framework" for writing CloudFormation and addressing some of its shortcomings. It lets you express CloudFormation templates in an easier fashion, with a more forgiving syntax than the standard JSON or YAML syntaxes that CloudFormation supports. It also eases authoring some complex CloudFormation patterns.
You can think of it as a preprocessor that generates CloudFormation documents.
CloudFormation::DSL builds on the idea that the information in a CloudFormation template can be
expressed as a class. After all, a class is a template for an object! Since we represent templates
as classes, we can instance those classes, manipulate and query them. An instance of a class has
an as_json
method that once called generates the CloudFormation document that can be sent to
the CloudFormation service.
CloudFormation::DSL builds upon existing layers:
Cfn: Each CloudDeploy::DSL
class is a Cfn subclass. This means that the object model that
Cfn provides is accessible from CloudDeploy::DSL
.
Moose and Perl: CloudDeploy::DSL
builds upon Moose's (An Object Orientation framework for Perl)
and Perl's ability to add syntax to the language. This let's us declare keywords like resource
or paramter
so you can write your CloudFormation templates in a faster way. This is just an implementation
detail: you don't need to know Moose or Perl to use CloudFormation::DSL
.
CloudFormation::DSL brings you full object orientation to your CloudFormation template authoring: you can create base classes, inherit, override and specialize.
Enough chit-chat: let's see the action:
Start a file named `MyCfn.pm`
package MyCfn {
use CloudFormation::DSL;
}
1;
You now have a class that represents a template without resources. Now we'll add stuff with the following keywords:
package MyCfn {
use CloudFormation::DSL;
resource User1 => 'AWS::IAM::User', {
Path => "/",
};
}
The resource keyword declares a CloudFormation resource of type TYPE
. The supported types
are available in the Cfn::Resource
namespace. This piece of code generates the following
CloudFormation:
{
"Resources": {
"User1": {
"Type": "AWS::IAM::User",
"Properties": {
"Path": "/"
}
}
}
}
Note a couple of things that the DSL is doing for us:
User1
doesn't have to be quoted. Perls "fat comma" automatically quotes it for us!
You could write:
resource 'User1' => 'AWS::IAM::User', { ... };
if you wanted. You could also use double quotes:
resource "User1" => 'AWS::IAM::User', { ... };
You actually need to do this if the resource name has special characters.
Note that we specify the properties just after the object in a Key / Value fashion.
We use Perls Hashrefs to represent these Key/Value structures. The Keys are the same
keys that we would use in the "Properties"
object in a CloudFormation template. The
values can be Perl strings "myvalue"
, Perl numbers 42
, bareword booleans true
or false
and also CloudFormation functions like { Ref =
'LogicalId' }> and
{ 'Fn::GetAtt' =
[ 'LogicalId', 'AttributeName' ] }> as Perl HashRefs. If you think
this is typing too much, please read the "shortcuts" section. You don't have to type
"Properties"
, and these go first, since 99% of the time we write resources, we
write their properties.
Note that in HashRefs we can leave trailing commas:
{ Path => '/', }
is valid, while in JSON
{ "Path": "/", }
isn't valid.
The DSL is also helping us assuring that AWS::IAM::User is a valid resource type. If we
don't use a supported resource type, we will get an error. This is also true for its
properties. The AWS::IAM::User
object has a property called Path
. If we had a
typo in the Path
property, we would get an error. If we didn't define a required property
we get an error. If we used a wrong value: we get an error.
If we want to configure resource properties like DependsOn
or DeletionPolicy
we can do so passing a fourth element to our resource
statement:
resource "User1" => 'AWS::IAM::User', { ... }, { DeletionPolicy => 'Retain' };
The DSL will verify that the DeletionPolicy is a permitted value in CloudFormation.
This will declare an output in our CloudFormation template.
package MyCfn {
use CloudFormation::DSL;
resource User1 => 'AWS::IAM::User', {
Path => "/"
};
output IAMUser => Ref('User1');
}
Note that the name of the output doesn't need to be quoted (just like in with the
resource
keyword. The value for an output is the same ones that CloudFormation supports.
In the example we're using a shortcut to specify a CloudFormation Ref function. We could
have wrote:
output IAMUser => { Ref => 'User1' };
The two are equivalent to the following CloudFormation JSON:
{
"Outputs": {
"IAMUser": {
"Value": { "Ref": "User1" }
}
}
}
We can also specify extra properties for the output Description
,
output IAMUser => { Ref => 'User1' }, {
Description => 'The name of the IAM user',
};
Will generate:
{
"Outputs": {
"IAMUser": {
"Value": { "Ref": "User1" },
"Description": "The name of the IAM user"
}
}
}
The parameter
keyword adds a CloudFormation parameter to the template.
parameter IAMPath => 'String',
Generates:
{
"Parameters": {
"IAMPath": {
"Type": "String"
}
}
}
We can also specify parameter properties like Default
,
NoEcho
, MaxLength
, etc.
parameter IAMPath => 'String', {
Default => '/',
MazLength => 32,
};
Generates:
{
"Parameters": {
"IAMPath": {
"Type": "String",
"Default": "/",
"MaxLength": 32
}
}
}
Adds a condition to the template
Adds a CloudFormation mapping with a specific name
Adds a metadata key to the template
Adds a transform to the template
is a shorthand way to write { Ref =
'LogicalId' }>
It writes {"Ref":"LogicalId"}
in the CloudFormation template.
is a shorthand way to write
is a shorthand way of referencing a parameter that doesn't get passed to CloudFormation.
is a shorhand way of writing the values that most Tag
attributes of resources expect:
Tag('Owner', 'me')
gets converted to
{ "Key": "Owner", "Value": "me" }
in CloudFormation
An example of usage would be:
resource Subnet1 => 'AWS::EC2::Subnet', {
VpcId => Ref('Vpc'),
CidrBlock => '10.0.0.0/24',
Tags => [ Tag('Owner', 'me'), Tag('BU', 'sales') ],
};
is a shorthand way of referencing an instance attribute. This is for advanced use. See the "Instance Attributes" section.
is a shorthand way to import the contents of a file in "UserData" format:
Tiefighters are sequences of #-#...#-#
that can be found inside the files
that the UserData keyword converts to the
Inserts a Ref('LogicalId')
Inserts a GetAtt('LogicalId', 'Attribute')
Inserts a Parameter
Inserts an Attribute value
is a shorthand way to write a string that has gets tiefighters interpreted
will convert a JSON string into a HashRef
is a shorthand way of writing an ELB Listener. It can be used in many ways:
ELBListener(80, 'HTTP')
will forward traffic from port 80 of the ELB to port 80 on the backends
ELBListener(80, 'HTTP', 3000)
will forward traffic from port 80 of the ELB to port 3000 on the
backends
ELBListener(443, 'HTTPS', 5000, 'HTTP')
will do SSL offloading on the ELB, forwarding to port
5000 HTTP on the backends
You can use inheritance primitives to structure your infrastructure into reusable modules
package MyBaseClass {
use CloudFormation::DSL;
resource I1 => 'AWS::EC2::Instance', {
ImageId => SpecifyInSubClass,
SecurityGroups => [ Ref('SG' ],
};
resource SG => 'AWS::EC2::SecurityGroup, {
...
};
}
package SubClass1 {
use CloudFormation::DSL;
extends 'MyBaseClass';
resource I1 => 'AWS::IAM::User', {
ImageId => 'ami-XXXX',
};
}
Use SpecifyInSubClass
to force the user to overwrite this value in a subclass:
package MyBase {
use CloudFormation::DSL;
resource User1 => 'AWS::IAM::User', {
Path => SpecifyInSubclass,
};
}
If MyBase is instanced, an error will be thrown. The value can be overwritten in subclasses:
package MySubClass {
use CloudFormation::DSL;
extends 'MyBase';
resource '+User1' => 'AWS::IAM::User', {
Path => '/',
};
}
Adds an attachment to the template
https://docs.aws.amazon.com/es_es/AWSCloudFormation/latest/UserGuide/Welcome.html
Jose Luis Martinez
CAPSiDE
jlmartinez@capside.com
Thanks to Sergi Pruneda, Miquel Ruiz, Luis Alberto Gimenez, Eleatzar Colomer, Oriol Soriano, Roi Vazquez for years of work on this module.
The source code is located here: https://github.com/pplu/cloudformation-dsl
Please report bugs to: https://github.com/pplu/cfn-perl/cloudformation-dsl
Copyright (c) 2013 by CAPSiDE This code is distributed under the Apache 2 License. The full text of the license can be found in the LICENSE file included with this module.