CloudFormation, the way AWS advertises its usage, is way suboptimal, driving people into writing hundreds if not thousands of lines of JSON, by hand, e.g. https://github.com/widdix/aws-cf-templates/blob/master/wordpress/wordpress-ha.yaml .
Whenever JSON is short of logic (of course it's short of logic, it's a structure format and a serializing standard!), CloudFormation comes up with parameters, conditions, mappings and functions (see link in the "Refs" section) to allow people to join strings, do substitutions, etc.
Whenever that is not enough, they push users into using Custom resources
i.e. deploying lambdas out-of-band, and calling them without any tracing,
literally making you build your own AWS CloudFormation layer.
The latest "improvement" from AWS is that they have added YAML support, making me to conclude that AWS doesn't want to, or just cannot see the elephant in the room.
The AWS users are also in the same room, because they don't see the forest behind the trees. They only thing they seem to do is come up with DSLs that add syntactic sugar.
The Google Cloud Platform has the solution, which is crazily simple: run code to generate the "infra as code" configuration. They do that via a templating framework called Jinja, or via vanilla Python code that outputs YAML. It's not that they have invented electricity, but they advertise it on their frontpage, while AWS doesn't even mention it.
And this is what we do with CloudFormation as well. We run JavaScript modules, that output partials, which are then deep merged to form the final CloudFormation Stack template.
This has a few striking benefits, like:
- code always runs locally before CloudFormation is executed
- validation (syntax and some semantics) happens before CloudFormation is executed
- manual validation and inspection can be done via local JSON diffs
- ability to reference other stacks' variables, even if they are in other AWS accounts
- ability to reference any AWS data like AMI ID, or AWS Managed policy
- no need for CloudFormation parameters, conditions, mappings, functions
- exception:
RefandGetAttfunctions, which is sensible
- exception:
.
+-- cfn
+-- env-web
+-- index.js ~ ../../yplatform/aws-cfn.mk/tpl.stack-stem.cfn.js
+-- s3.web.cfn.js
+-- kms.web.cfn.js
+-- infra
+-- index.js ~ ../../yplatform/aws-cfn.mk/tpl.stack-stem.cfn.js
+-- iam.cfn.js
+-- .gitignore ~ ../yplatform/aws-cfn.mk/tpl.gitignore
+-- Makefile ~ ../yplatform/aws-cfn.mk/tpl.Makefile
+-- env-web.inc.mk ~ ../yplatform/aws-cfn.mk/tpl.stack-stem.inc.mk
+-- infra.inc.mk ~ ../yplatform/aws-cfn.mk/tpl.stack-stem.inc.mk
Above is an example of a folder structure in a repo with a cfn folder,
holding 2 stack templates: env-web and infra.
Each subfolder has *.cfn.js files that export partial CloudFormation templates.
By default, thanks to the index.js,
they will all get deep merged to produce the final template.
It is important to highlight that you don't need to use JavaScript to generate your templates.
You can use Python instead for example, just by setting CFN_INDEX_FILE := main.py in cfn/Makefile.
The difference is that the infra stack is universal, 1 per AWS account,
while the env-web stack is per environment, n per AWS account.
yplatform/bin/repo-cfn-boostrap --stack-stem env-web
yplatform/bin/repo-cfn-boostrap --stack-stem infra/Makefiledrives the build in an abstract manner/<stack-stem>.inc.mkimplements a few specific things required by the abstract Makefile, as well as<stack-stem>-lintextra linting function called before generating the template/change-set<stack-stem>-prefunction called before generating the template/change-set<stack-stem>-pre-execfunction called after generating the template/change-set, but before executing it<stack-stem>-post-execfunction called after executing the template/change-set<stack-stem>-pre-rmfunction called before tearing down the stack<stack-stem>-post-rmfunction called after tearing down the stack- these targets allow us to execute actions outside of CloudFormation limitations/flaws
- e.g. Amazon Elastic Transcoder doesn't have CloudFormation support, but we can make calls
to create/update or delete one in the
<stack-stem>-post-execand<stack-stem>-post-rmfunctions
- e.g. Amazon Elastic Transcoder doesn't have CloudFormation support, but we can make calls
to create/update or delete one in the
/<stack-stem>.cfn.jsis the JavaScript module that gets called to output the CloudFormation template as JSON/<stack-stem>/*.cfn.jsare JavaScript modules that get called to output partial CloudFormation templates as JSON It depends on the/<stack-stem>cfn.jscode, but by convention, each of these partials are called to produce partial JSON, that are then deeply merged into the final output.
-
/<stack-stem>.cfn.json.errwould be the latest failed CloudFormation template- failed = invalid JSON or CloudFormation semantics
-
/<stack-stem>.cfn.json.bakwould be the live CloudFormation template of the current stack -
/<stack-stem>.cfn.policy.json.bakwould be the live CloudFormation policy of the current stack -
/<stack-stem>.drift.json.bakwould be the live CloudFormation drift results of the current stack -
/<stack-stem>.cfn.jsonwould be the latest generated CloudFormation template -
/<stack-stem>.cfn.policy.jsonwould be the latest generated CloudFormation policy
For stacks that are being updated:
/<stack-stem>.change-set.jsonwould be the CloudFormation changeset, as retrieved from AWS/<stack-stem>.change-set.json.diffwould be the diff between the live CloudFormation template/<stack-stem>.cfn.json.bakand the newly generated/<stack-stem>.cfn.json/<stack-stem>.drift.jsonwould be the CloudFormation drift results, after executing the change-set
Running make help lists the important targets along with descriptions,
but here's a common workflow:
- new stack
make <stack-stem>.cfn.jsonto generate and validate the templatemake <stack-stem>.cfn.json/execto execute the template, and create the stack NOTE: if the stack already exists, this will actually end up creating and executing a change-set.
- existing stack
make <stack-stem>.change-set.jsonto generate the template changeset and the diff/<stack-stem>.cfn.json.diffis tremendously useful for manual inspection!!!
make <stack-stem>.change-set.json/execto execute the changeset, and update the stackmake <stack-stem>.cfn.json/rmto remove the stack
NOTE: by far the most handy resource is the printed CloudFormation manual available in our team. Make use of it, or else if you create a new stack you can easily drown yourself in the AWS websites/PDFs.