From f71b5f3582683456bf6952274bc2cc4cd6171771 Mon Sep 17 00:00:00 2001 From: Adam Pippin Date: Sun, 14 Feb 2021 00:38:05 -0800 Subject: [PATCH] Rewrite README to explain functionality, move forward TODO list --- README.md | 207 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 198 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 1d59ea1..3da035b 100644 --- a/README.md +++ b/README.md @@ -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 ` ### 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.