|
|
@ -2,29 +2,218 @@ |
|
|
|
|
|
|
|
## Goal |
|
|
|
|
|
|
|
Make it easier to re-use and template CloudFormation templates |
|
|
|
Make it easier to write, re-use and template CloudFormation templates |
|
|
|
|
|
|
|
## Functionality |
|
|
|
## What it does |
|
|
|
|
|
|
|
### Current |
|
|
|
* 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. |
|
|
|
|
|
|
|
* Supports serializing/deserializing yaml |
|
|
|
* Can specify an input file; will pull in all referencing files and merge them and write to an output file |
|
|
|
* Supports `!Unset` and `!Replace` |
|
|
|
### Meta Block |
|
|
|
|
|
|
|
### Planned |
|
|
|
At the top level, a `cfnpp` block can be specified to make use of some extended functionality: |
|
|
|
|
|
|
|
* Provision rendered templates directly to CloudFormation |
|
|
|
* 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 (scalar) value of a variable. |
|
|
|
|
|
|
|
Given: |
|
|
|
|
|
|
|
```yaml |
|
|
|
cfnpp: |
|
|
|
variables: |
|
|
|
A: 1 |
|
|
|
|
|
|
|
MyFavouriteNumber: !var A |
|
|
|
``` |
|
|
|
|
|
|
|
Will result in: |
|
|
|
|
|
|
|
```yaml |
|
|
|
MyFavouriteNumber: 1 |
|
|
|
``` |
|
|
|
|
|
|
|
## Usage |
|
|
|
|
|
|
|
### Running |
|
|
|
|
|
|
|
* Install dependencies with `composer install` |
|
|
|
* Execute `cfnpp` with PHP. |
|
|
|
* 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 |
|
|
|
|
|
|
|
Note: Building is not required to run. |
|
|
|
|
|
|
|
* Install dependencies with `composer install` |
|
|
|
* Run `./cfnpp app:build`; outputs phar at builds/ |
|
|
|
|
|
|
|
## TODO |
|
|
|
|
|
|
|
* Provision rendered templates directly to CloudFormation |
|
|
|
* Maybe look at implementing an actual dependency graph to make stuff like "multiple inheritance" a little cleaner. |
|
|
|
E.g., if a single app stack pulls in multiple resources which all depend on `base.yaml`, don't reprocess base.yaml a |
|
|
|
bunch of times but instead just realize that's the root and process it once. |
|
|
|
* Add flow control: `!if`, `!foreach`, etc |
|
|
|
* Extend variables to support arrays/maps. Probably write a helper or add a toArray on Node since we're having to |
|
|
|
convert to/from dom nodes and arrays/maps all over the place. |
|
|
|
* Build an expression parser. Just to make everyone's life difficult, because it's fun and this is a toy project, and |
|
|
|
also to avoid having to care about operator precedence let's do it all postfix? My HP50G would be proud. |
|
|
|
* Some sort of macro system would be good. |
|
|
|