Adam Pippin
3 years ago
1 changed files with 11 additions and 309 deletions
@ -1,318 +1,20 @@ |
|||
# cfnpp: cloudformation plus plus |
|||
|
|||
## Goal |
|||
A tool for making it easier to write, re-use, and template CloudFormation stacks. |
|||
|
|||
Make it easier to write, re-use and template CloudFormation templates |
|||
For more information, see the [wiki](wiki). |
|||
|
|||
## Why did you do this? |
|||
|
|||
Writing and maintaining CloudFormation templates is tedious, but using tools that |
|||
take you _too_ far away from the CloudFormation templates gives more opportunity |
|||
for bugs to creep in and leaves you operating several months behind updates to |
|||
AWS services and CloudFormation. |
|||
|
|||
But mainly I needed something to fill up some free time. |
|||
|
|||
## What it does |
|||
|
|||
* Works with existing CloudFormation templates. (Some possible issues with the Symfony YAML parser notwithstanding.) |
|||
* Adds support for a `cfnpp:` meta block which provides some extended functionality detailed below. |
|||
* Adds several additional `!function`s detailed below. |
|||
* More planned, detailed at the end of the README. |
|||
|
|||
### Meta Block |
|||
|
|||
At the top level, a `cfnpp` block can be specified to make use of some extended functionality: |
|||
|
|||
* Includes |
|||
* Variables |
|||
|
|||
For example: |
|||
|
|||
```yaml |
|||
cfnpp: |
|||
stack: |
|||
- parent.yaml |
|||
variables: |
|||
A: 1 |
|||
B: 2 |
|||
foo: bar |
|||
``` |
|||
|
|||
#### Stack |
|||
|
|||
Any files listed will be processed in the order given _before_ the current document. If those documents specify |
|||
any documents in their stack, those will be processed before those documents in the order given and so on. |
|||
|
|||
There is nothing preventing you from including a file more than once, nor from creating a loop. It's undecided at |
|||
the time being whether this is desirable or whether we need some more complicated dependency graph or something to |
|||
process these. |
|||
|
|||
The first document is loaded, then each subsequent document is merged with it, and then any functions present processed. |
|||
|
|||
Merging follows some simple rules: |
|||
|
|||
* Maps are merged, with duplicate keys being overwritten. |
|||
* Arrays are appended. |
|||
* Scalars overwrite. |
|||
* Any type mismatch, and the value in the template being merged in simply overwrites whatever's in the original. |
|||
|
|||
For example, starting with: |
|||
|
|||
```yaml |
|||
Resources: |
|||
Resource1: |
|||
Name: My Resource Name |
|||
VpcId: vpc-1234 |
|||
AvailabilityZones: |
|||
- us-east-1a |
|||
- us-east-1b |
|||
ValidUsers: |
|||
Alice: 'aws:arn:1234:alice' |
|||
Bob: 'aws:arn:1234:bob' |
|||
AllowPorts: |
|||
- 80 |
|||
- 443 |
|||
``` |
|||
|
|||
And merging in: |
|||
|
|||
```yaml |
|||
Resources: |
|||
Resource1: |
|||
Name: New Name |
|||
AvailabilityZones: |
|||
- us-east-1c |
|||
ValidUsers: |
|||
Charlie: 'aws:arn:1234:charlie' |
|||
AllowPorts: False |
|||
``` |
|||
|
|||
Will result in: |
|||
|
|||
```yaml |
|||
Resources: |
|||
Resource1: |
|||
Name: New Name |
|||
VpcId: vpc-1234 |
|||
AvailabilityZones: |
|||
- us-east-1a |
|||
- us-east-1b |
|||
- us-east-1c |
|||
ValidUsers: |
|||
Alice: 'aws:arn:1234:alice' |
|||
Bob: 'aws:arn:1234:bob' |
|||
Charlie: 'aws:arn:1234:charlie' |
|||
AllowPorts: False |
|||
``` |
|||
|
|||
#### Variables |
|||
|
|||
Variables listed are set from all templates before the first one is processed. Variables are processed in the same |
|||
order that the documents will be compiled, and later values will overwrite earlier ones. |
|||
|
|||
That is, a base template can define some default values, and a dependent one can specify new values and they will |
|||
be visible to the base template. |
|||
|
|||
### Functions |
|||
|
|||
#### !replace |
|||
|
|||
Tells the merging process to _not_ attempt to perform any merging, but simply replace whatever's in the target document |
|||
with these values. For example, given a base template: |
|||
|
|||
```yaml |
|||
Resources; |
|||
Resource1: |
|||
AvailabilityZones: |
|||
- us-east-1a |
|||
- us-east-1b |
|||
- us-east-1c |
|||
``` |
|||
|
|||
Merging in: |
|||
|
|||
```yaml |
|||
Resources: |
|||
Resource1: |
|||
AvailabilityZones: !replace |
|||
- us-east-1c |
|||
``` |
|||
|
|||
Will result in: |
|||
|
|||
```yaml |
|||
Resources: |
|||
Resource1: |
|||
AvailabilityZones: |
|||
- us-east-1c |
|||
``` |
|||
|
|||
#### !unset |
|||
|
|||
Removes a property completely, rather than simply overriding its value or something. |
|||
The YAML parser requires that tags have a value, so simply pass '~' (null) or some |
|||
other garbage. |
|||
|
|||
Given a base template: |
|||
|
|||
```yaml |
|||
Resources: |
|||
Resource1: |
|||
Name: Test |
|||
SuperSecurityLevel: 5 |
|||
``` |
|||
|
|||
Merging in: |
|||
|
|||
```yaml |
|||
Resources: |
|||
Resource1: |
|||
SuperSecurityLevel: !unset ~ |
|||
``` |
|||
|
|||
Will result in: |
|||
|
|||
```yaml |
|||
Resources: |
|||
Resource1: |
|||
Name: Test |
|||
``` |
|||
|
|||
#### !var |
|||
|
|||
Replaced with the value of a variable. |
|||
|
|||
Given: |
|||
|
|||
```yaml |
|||
cfnpp: |
|||
variables: |
|||
A: 1 |
|||
B: |
|||
- 1 |
|||
- 2 |
|||
- 3 |
|||
|
|||
MyFavouriteNumber: !var A |
|||
SomeOtherNumbers: !var B |
|||
``` |
|||
|
|||
Will result in: |
|||
|
|||
```yaml |
|||
MyFavouriteNumber: 1 |
|||
SomeOtherNumbers: |
|||
- 1 |
|||
- 2 |
|||
- 3 |
|||
``` |
|||
|
|||
#### !if |
|||
|
|||
Conditionally include portions of the document. |
|||
|
|||
Expects to receive an array where the first element is a condition, the second is |
|||
the value if the condition is true, and the third is the value if the condition |
|||
is false (this one is optional). |
|||
|
|||
Expressions use postfix notation because this is a play project right now and |
|||
it's both fun and easy to implement and my HP calculator demanded it. Bright side, |
|||
you never have to worry about operator precedence! |
|||
|
|||
Example: |
|||
|
|||
``` |
|||
cnfpp: |
|||
variables: |
|||
LoadBalancerType: internal |
|||
|
|||
Resources: |
|||
MyLoadBalancer: |
|||
Subnet: !if |
|||
- LoadBalancerType "internal" eq |
|||
- subnet-1234 |
|||
- subnet-6789 |
|||
``` |
|||
|
|||
The true/false values can include maps/arrays/any other valid YAML. |
|||
|
|||
#### !expr |
|||
|
|||
Evaluate an expression and insert the result. |
|||
|
|||
Expects a scalar representing an expression using the same notatation as !if (and |
|||
outlined below). |
|||
|
|||
If the expression evaluates down to a single value, it will be inserted as a |
|||
scalar. If it evaluates down to multiple values, it will be inserted as an array. |
|||
|
|||
Example: |
|||
|
|||
``` |
|||
cfnpp: |
|||
variables: |
|||
A: 1 |
|||
B: 2 |
|||
C: !expr 1 2 gt |
|||
|
|||
Math: |
|||
IsALessThanB: !expr A B lt |
|||
AllMyFavouriteVariables: !expr A B |
|||
ValueOfC: !var C |
|||
``` |
|||
|
|||
Result: |
|||
|
|||
``` |
|||
Math: |
|||
IsALessThanB: True |
|||
AllMyFavouriteVariables: |
|||
- 1 |
|||
- 2 |
|||
ValueOfC: False |
|||
``` |
|||
|
|||
#### Expressions |
|||
|
|||
Expressions are composed of space separated values and operators. Valid components |
|||
are: |
|||
|
|||
* Numeric Literals: Any integer or float value. |
|||
* String Literals: Any value enclosed in double quotes (`"`). A backslash can be |
|||
used to quote/escape literal quotes within the string. |
|||
* Variables: Any non-quoted string consisting of letters and numbers (no leading |
|||
digit) is interpreted as a reference to one of the variables. |
|||
* Operator: Currently supported comparison/logical operators are: |
|||
- eq: equal |
|||
- neq: not equal |
|||
- gt: greater than |
|||
- gte: greater than or equal |
|||
- lt: less than |
|||
- lte: less than or equal |
|||
- and: boolean and |
|||
- or: boolean or |
|||
* Function: Currently supported functions are: |
|||
- concat: Concatenate as strings two values off of the stack |
|||
- concat\*: Concatenate all values currently on the stack down to a single value. |
|||
|
|||
## Usage |
|||
|
|||
### Running |
|||
|
|||
* Install dependencies with `composer install` |
|||
* Execute `cfnpp` with PHP 8, it will either complain about any missing PHP extensions or present a list of commands. |
|||
* Run `./cfnpp <infile.yaml> <outfile.yaml>` |
|||
## TODO |
|||
|
|||
### Building |
|||
* Provision rendered templates directly to CloudFormation |
|||
* More flow control (e.g., foreach, etc) |
|||
* Some sort of macro system would be good. |
|||
|
|||
Note: Building is not required to run. |
|||
## License |
|||
|
|||
* Install dependencies with `composer install` |
|||
* Run `./cfnpp app:build`; outputs phar at builds/ |
|||
No license applied yet. No permission is granted to use, redestribute, modify, or |
|||
otherwise do anything with this software. |
|||
|
|||
## TODO |
|||
## Author |
|||
|
|||
* Provision rendered templates directly to CloudFormation |
|||
* Add flow control: `!if`, `!foreach`, etc |
|||
* Some sort of macro system would be good. |
|||
* Adam Pippin <hello@adampippin.ca> |
|||
|
Loading…
Reference in new issue