Adam Pippin
3 years ago
1 changed files with 11 additions and 309 deletions
@ -1,318 +1,20 @@ |
|||||
# cfnpp: cloudformation plus plus |
# 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? |
## TODO |
||||
|
|
||||
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>` |
|
||||
|
|
||||
### 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` |
No license applied yet. No permission is granted to use, redestribute, modify, or |
||||
* Run `./cfnpp app:build`; outputs phar at builds/ |
otherwise do anything with this software. |
||||
|
|
||||
## TODO |
## Author |
||||
|
|
||||
* Provision rendered templates directly to CloudFormation |
* Adam Pippin <hello@adampippin.ca> |
||||
* Add flow control: `!if`, `!foreach`, etc |
|
||||
* Some sort of macro system would be good. |
|
||||
|
Loading…
Reference in new issue