├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bin └── buildDocs.js ├── docs ├── features.md ├── getting-started.md ├── handlebars-helpers.md ├── images │ ├── condensation_logo.png │ └── condensation_logo.svg ├── intrinsic-functions.md ├── particle-helpers.md ├── tasks.md └── template-helpers.md ├── index.js ├── jsdoc2md └── templates │ └── sig-name.hbs ├── lib ├── condensation │ ├── index.js │ ├── loaders │ │ ├── all-helpers.js │ │ ├── front-loader.js │ │ ├── particle-loader.js │ │ └── template-helper-loader.js │ ├── tasks │ │ ├── build.js │ │ ├── index.js │ │ └── s3 │ │ │ ├── deleteObjects.js │ │ │ ├── ensureBucket.js │ │ │ ├── index.js │ │ │ └── writeObjects.js │ ├── template-helpers │ │ ├── arrayify.js │ │ ├── assetPath.js │ │ ├── assetS3Url.js │ │ ├── cValue.js │ │ ├── functions │ │ │ ├── fnAnd.js │ │ │ ├── fnBase64.js │ │ │ ├── fnEquals.js │ │ │ ├── fnFindInMap.js │ │ │ ├── fnGetAZs.js │ │ │ ├── fnGetArtifactAtt.js │ │ │ ├── fnGetAtt.js │ │ │ ├── fnGetParam.js │ │ │ ├── fnIf.js │ │ │ ├── fnImportValue.js │ │ │ ├── fnJoin.js │ │ │ ├── fnNot.js │ │ │ ├── fnOr.js │ │ │ ├── fnSelect.js │ │ │ ├── fnSplit.js │ │ │ ├── fnSub.js │ │ │ ├── index.js │ │ │ └── ref.js │ │ ├── helper.js │ │ ├── index.js │ │ ├── layout.js │ │ ├── objectify.js │ │ ├── partial.js │ │ ├── requireAssets.js │ │ ├── scopeId.js │ │ ├── sections │ │ │ ├── _buildHelper.js │ │ │ ├── condition.js │ │ │ ├── index.js │ │ │ ├── mapping.js │ │ │ ├── metadata.js │ │ │ ├── output.js │ │ │ ├── parameter.js │ │ │ └── resource.js │ │ ├── set.js │ │ └── templateS3Url.js │ └── util.js ├── gulp-plugins │ └── gulp-cf-validate.js └── handlebars-helpers │ ├── concat.js │ ├── index.js │ └── stringify.js ├── package.json └── test ├── fixtures ├── projects │ ├── invalid │ │ ├── package.json │ │ └── particles │ │ │ └── cftemplates │ │ │ └── invalid.template.hbs │ ├── particle-builds │ │ ├── package.json │ │ └── particles │ │ │ ├── mappings │ │ │ ├── ami.hbs │ │ │ └── ami_full_object.hbs │ │ │ ├── parameters │ │ │ ├── extend_malformed.hbs │ │ │ ├── full_object.json.hbs │ │ │ ├── generic.hbs │ │ │ └── malformed.hbs │ │ │ ├── resources │ │ │ └── generic.hbs │ │ │ └── sets │ │ │ ├── set1.hbs │ │ │ ├── set2.hbs │ │ │ └── set3.hbs │ ├── particles-common-core │ │ ├── package.json │ │ └── particles │ │ │ ├── conditions │ │ │ ├── is_false.hbs │ │ │ └── is_true.hbs │ │ │ ├── parameters │ │ │ ├── cidr_range.hbs │ │ │ └── true_false.hbs │ │ │ ├── partials │ │ │ └── parameter_base.hbs │ │ │ └── sets │ │ │ └── true_false.hbs │ ├── particles-vpc │ │ ├── package.json │ │ └── particles │ │ │ ├── cftemplates │ │ │ ├── subnet.template.json.hbs │ │ │ └── vpc.template.json.hbs │ │ │ ├── parameters │ │ │ └── vpc_cidr.hbs │ │ │ └── sets │ │ │ └── true_false_set.hbs │ ├── projectA │ │ ├── package.json │ │ ├── particles │ │ │ ├── cftemplates │ │ │ │ ├── infra.template.json.hbs │ │ │ │ ├── subnet.template.json.hbs │ │ │ │ └── vpc.template.json.hbs │ │ │ ├── helpers │ │ │ │ └── test-helper.js │ │ │ └── partials │ │ │ │ ├── ami-map │ │ │ │ ├── parameter-name-tag │ │ │ │ └── text-test │ │ └── projectA │ ├── projectB │ │ ├── package.json │ │ └── particles │ │ │ ├── assets │ │ │ ├── bootstrap.sh │ │ │ └── download.sh.hbs │ │ │ ├── cftemplates │ │ │ └── instance.template.json.hbs │ │ │ ├── helpers │ │ │ └── hello-b.js │ │ │ └── partials │ │ │ └── ami_map.partial │ ├── projectC │ │ ├── package.json │ │ └── particles │ │ │ ├── cftemplates │ │ │ └── proj.template.json.hbs │ │ │ ├── front_loaders │ │ │ └── testLoader.js │ │ │ └── resources │ │ │ ├── lambda_function.hbs │ │ │ └── lambda_function │ │ │ └── execution_role.hbs │ └── sam │ │ ├── package.json │ │ └── particles │ │ └── cftemplates │ │ └── sam.template.json.hbs └── projects_output │ ├── particle-builds │ └── sets_output1.json │ ├── particles-vpc │ └── 0 │ │ └── particles │ │ └── cftemplates │ │ ├── subnet.template.json │ │ └── vpc.template.json │ ├── projectA │ └── 0 │ │ └── particles │ │ └── cftemplates │ │ ├── infra.template.json │ │ ├── subnet.template.json │ │ └── vpc.template.json │ ├── projectB-config2 │ └── 0 │ │ └── testing-path │ │ ├── node_modules │ │ └── projectA │ │ │ └── particles │ │ │ └── cftemplates │ │ │ ├── vpc.template │ │ │ └── vpc.template.json │ │ └── particles │ │ ├── assets │ │ ├── bootstrap.sh │ │ └── download.sh │ │ └── cftemplates │ │ └── instance.template.json │ ├── projectB │ └── 0 │ │ ├── node_modules │ │ └── projectA │ │ │ └── particles │ │ │ └── cftemplates │ │ │ └── vpc.template.json │ │ └── particles │ │ ├── assets │ │ ├── bootstrap.sh │ │ └── download.sh │ │ └── cftemplates │ │ └── instance.template.json │ └── projectC │ └── 0 │ ├── node_modules │ ├── projectA │ │ └── particles │ │ │ └── cftemplates │ │ │ └── vpc.template.json │ └── projectB │ │ ├── node_modules │ │ └── projectA │ │ │ └── particles │ │ │ └── cftemplates │ │ │ └── vpc.template.json │ │ └── particles │ │ ├── assets │ │ ├── bootstrap.sh │ │ └── download.sh │ │ └── cftemplates │ │ └── instance.template.json │ └── particles │ └── cftemplates │ └── proj.template ├── integration ├── invalid.test.js ├── particles-vpc.test.js ├── project-shared.js ├── projectA.test.js ├── projectB-config2.test.js ├── projectB.test.js ├── projectC.test.js └── sam.test.js └── unit ├── handlebars-helpers ├── concat.test.js └── stringify.test.js ├── load.test.js ├── loaders ├── front-loader.test.js └── particle-loader.test.js └── template-helpers ├── arrayify.test.js ├── assetPath.test.js ├── assetS3Url.test.js ├── cValue.test.js ├── functions ├── fnAnd.test.js ├── fnBase64.test.js ├── fnEquals.test.js ├── fnFindInMap.test.js ├── fnGetAZs.test.js ├── fnGetArtifactAtt.test.js ├── fnGetAtt.test.js ├── fnGetParam.test.js ├── fnIf.test.js ├── fnImportValue.test.js ├── fnJoin.test.js ├── fnNot.test.js ├── fnOr.test.js ├── fnSelect.test.js ├── fnSplit.test.js ├── fnSub.test.js └── ref.test.js ├── objectify.test.js ├── scopeId.test.js ├── sections ├── mapping.test.js ├── parameters.test.js └── sets.test.js └── templateS3Url.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | node_modules/* 3 | test/fixtures/projects/*/node_modules/* 4 | !test/fixtures/projects/projectB/node_modules/projectD/ 5 | npm* 6 | test/dist/ 7 | condensation_errors 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - 7 5 | - 6 6 | - 5 7 | - 4 8 | matrix: 9 | after_success: NODE_ENV=test ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha 10 | --report lcovonly -- -R spec --timeout 15000 "./test/**/*.test.js" && cat ./coverage/lcov.info 11 | | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage 12 | env: 13 | global: 14 | - secure: hpxf7obBI8eUCIDDwYn0eEXtgrobdkL8EWW7A1u0Us5vuJK6R8spETU0f4qmmv+GUDm7YxM7HRPOBMeoi/uiB475IvnDOen7kA76g1rQBMXemhJWFrnB1JizKzb1bQBe15ZtKwj6RdZkTK2jpMgtehecrsFI5M/bFPBKz75nd4Q= 15 | - secure: kUaJjK1l+A/x1LnTscwzOYdBaFekJ+wv6AQtYXmOiz3SPPDTbizD+pLUs+ZRBvWi2D5e6LivvBKLoOn5mvr/KSip0hriTCBwfjK+Sx8+kreXUv+gZXX+gCd37CCpp5PuOba0nErwNn3TAV3tCnMkUp0wxFnOdREXeCFv/qYEzGo= 16 | deploy: 17 | provider: npm 18 | email: as.us.labs@sungardas.com 19 | api_key: 20 | secure: IafCsbxGASncqJ1b90lR91bbGqyUoO5ljuJRab+qvQenJafts7LAFeaYxpT0o8eRFZYwsJwAjJeqV1S8NcT4/Dz6NAbi2b3+f1qY0viXk2rWyZhHWgPjMKOQFTw2tEhbkAW2hMYf6YvAW087hnrXUVXQqlcw/p39EzN8WYxAIe8= 21 | on: 22 | tags: true 23 | repo: SungardAS/condensation 24 | node: 6 25 | notifications: 26 | slack: sgaslabs-community:9fV0wYOO1bUrVhEcGNCwlkY6 27 | webhooks: 28 | urls: 29 | - https://webhooks.gitter.im/e/fdcdbf8972142ab62e53 30 | on_success: change # options: [always|never|change] default: always 31 | on_failure: always # options: [always|never|change] default: always 32 | on_start: never # options: [always|never|change] default: always 33 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented here in 3 | accordance with [Keep a CHANGELOG][keep-changelog-url]. 4 | This project adheres to [Semantic Versioning][semver-url]. 5 | 6 | ## [0.6.10] - [2017-06-02] 7 | ### Added 8 | - fnBase64 now accepts blocks 9 | 10 | ### Fixed 11 | - removed rogue console.log within catch 12 | 13 | ## [0.6.8] - [2017-05-18] 14 | ### Fixed 15 | - enable fnGetArtifactAtt and fnGetParam 16 | 17 | ## [0.6.8] - [2017-05-14] 18 | ### Fixed 19 | - objectify - enabled and added docs 20 | 21 | ### Changed 22 | - dropped node 0.12 support 23 | 24 | ## [0.6.7] - [2017-05-14] 25 | ### Added 26 | - fnGetParam - for CodePipeline projects 27 | - fnArtifactAtt - for CodePipeline projects 28 | - objectify - to compliment arrayify 29 | 30 | 31 | ## [0.6.6] - [2017-02-13] 32 | ### Fixed 33 | - fnJoin - if single parameter use that as the array 34 | 35 | ## [0.6.5] - [2017-02-12] 36 | ### Added 37 | - front loaders - scripts that can run before a template is processed 38 | - more descriptive errors for failed particles 39 | 40 | ### Fixed 41 | - Layout compile error messages now report which template the error is 42 | in 43 | - partials always return a string and not a safestring object 44 | - fnSub will check for string before checking for first character 45 | 46 | ## [0.6.3] - [2017-01-18] 47 | ### Changed 48 | - Impoved particle path load error 49 | 50 | ### Fixed 51 | - ref helper should not scope psuedo variables that start with AWS:: 52 | 53 | ## [0.6.2] - 2017-01-17 54 | ### Changed 55 | - Only clean the S3 bucket if the build passes 56 | 57 | ## [0.6.1] - 2017-01-17 58 | ### Added 59 | - fnSplit helper 60 | 61 | ## [0.6.0] - 2017-01-16 62 | ### Added 63 | - Helpers for all CloudFormation Functions fnAnd, fnBase64, fnEquals, 64 | fnFindInMap, fnGetAtt, FnGetAZs, fnIf, fnImportValue, fnJoin, fnOr, 65 | fnSelect, fnSub, ref 66 | 67 | - New template helpers arrayify and cValue 68 | 69 | - New handlebars helper stringify 70 | 71 | ### Changed 72 | - Error handling now uses VError 73 | 74 | - Documention is now converted from jsdoc to markdown with jsdoc2md 75 | 76 | ## [0.5.9] - 2016-12-28 77 | ### Fixed 78 | - Bug where logicalIdPrefix would not work correctly with sets that 79 | extended other sets 80 | 81 | ### Added 82 | - If a `condensation.js` file accepts 2 arguments the first will be a 83 | configuration object for Condensation and the second will be a 84 | callback. For `condensation.js` files that accept one argument only 85 | the callback will be provided. 86 | 87 | - Helper particles will receive the global handlebars object as part of 88 | hOpts so that they can access other helpers and particles. 89 | 90 | 91 | ## [0.5.8] - 2016-12-14 92 | ### Added 93 | - Support **Transform** in layout 94 | 95 | ## [0.5.7] - 2016-11-16 96 | ### Fixed 97 | - stop partials being read with an extra newline 98 | 99 | ### Changed 100 | - graceful-fs hack removed with dependency upgrade 101 | 102 | ## [0.5.6] - 2016-08-25 103 | ### Fixed 104 | - warnings about graceful-fs on node 6 105 | 106 | ### Updated 107 | - package.json with latest dependencies 108 | - documentation with docker-condensation use 109 | 110 | ## [0.5.5] - 2016-05-12 111 | ### Fixed 112 | - output when errors bubble up in `_buildHelper` 113 | 114 | ## [0.5.4] - 2016-05-06 115 | ### Fixed 116 | - The particle loader should ignore directories when using glob to find 117 | particles 118 | - `_templatePath` should be the full relative path 119 | 120 | ## [0.5.3] - 2016-05-04 121 | ### Added 122 | - The template being compiled is now recorded in the Handlebars data 123 | object as `_templatePath` 124 | - s3:list now includes the prefix path 125 | 126 | ### Changed 127 | - npm releases will now happen with node 6 128 | 129 | ### Fixed 130 | - If a prefix starts with a `/` ensure `//` does not occur when 131 | written to S3 132 | 133 | ## [0.5.2] - 2016-04-30 134 | ### Fixed 135 | - `ref` helper needs to look in options.hash to follow handlebars 136 | standards 137 | 138 | ## [0.5.1] - 2016-04-29 139 | ### Added 140 | - `ref` helper 141 | - `scopeId` helper 142 | - `scope` can be set to true or false which will add or ignore logicalId 143 | prefix and suffix respectively. 144 | 145 | ### Fixed 146 | - doValidation in build.js will now validate correctly 147 | - tests will now validate if AWS Credentials are available 148 | - condensation helpers use the same parameter format as handlebars helpers 149 | 150 | ## [0.5.0] - 2016-04-21 151 | ### Added 152 | - Short module reference syntax. Use `m` instead of `module` to 153 | reference a particle module. When `m` is used `particles-` will 154 | automatically be added to the name. 155 | 156 | To reference particles in `particles-core` use `m:core` or 157 | `module:particles-core` 158 | 159 | - Better reporting for particle compile errors 160 | 161 | - Overhaul of the test suite. Now uses 162 | [condensation-particle-tests][cpt-url] to test individual particles. 163 | 164 | - Expose all helpers through class object. Used by 165 | [condensation-particle-tests][cpt-url] 166 | 167 | ### Changed 168 | - The constructor no longer calls `condense`. The function must be 169 | called after initializing the class. 170 | - Dependencies updated to latest versions 171 | 172 | ### Fixed 173 | - When a template has invalid JSON the correct error is reported and the 174 | file is dumped to `condensation_errors` 175 | - merge order in partials and sets 176 | 177 | ### Removed 178 | - jsonlint - was not adding value. Will look for better lint-er 179 | 180 | ## [0.4.12] - 2016-04-13 181 | ### Fixed 182 | - turn off HTML escaping at the highest level 183 | - merge order of Front Matter with extended templates 184 | 185 | [keep-changelog-url]: http://keepachangelog.com/ 186 | [cpt-url]: https://github.com/SungardAS/condensation-particle-tests 187 | [semver-url]: http://semver.org 188 | -------------------------------------------------------------------------------- /bin/buildDocs.js: -------------------------------------------------------------------------------- 1 | var jsdoc2md = require("jsdoc-to-markdown"); 2 | var fs = require("fs"); 3 | var path = require("path"); 4 | 5 | jsdoc2md.render({ 6 | files: ['lib/condensation/template-helpers/functions/*.js'], 7 | partial: [ 8 | path.join(__dirname,"..","jsdoc2md","templates","sig-name.hbs") 9 | ], 10 | separators: true, 11 | "name-format": false, 12 | "global-index-format": "none", 13 | "module-index-format": "none" 14 | }).then(function(doc) { 15 | fs.writeFile(path.join(__dirname,"..","docs","intrinsic-functions.md"),doc,function(err) { 16 | if (err) console.warn(err); 17 | //done 18 | }); 19 | }); 20 | 21 | jsdoc2md.render({ 22 | files: [ 23 | 'lib/condensation/template-helpers/sections/*.js', 24 | 'lib/condensation/template-helpers/helper.js', 25 | 'lib/condensation/template-helpers/partial.js', 26 | 'lib/condensation/template-helpers/set.js' 27 | ], 28 | partial: [ 29 | path.join(__dirname,"..","jsdoc2md","templates","sig-name.hbs") 30 | ], 31 | separators: true, 32 | "name-format": false, 33 | "global-index-format": "none", 34 | "module-index-format": "none" 35 | }).then(function(doc) { 36 | fs.writeFile(path.join(__dirname,"..","docs","particle-helpers.md"),doc,function(err) { 37 | //done 38 | }); 39 | }); 40 | 41 | jsdoc2md.render({ 42 | files: [ 43 | 'lib/condensation/template-helpers/index.js', 44 | 'lib/condensation/template-helpers/arrayify.js', 45 | 'lib/condensation/template-helpers/assetPath.js', 46 | 'lib/condensation/template-helpers/cValue.js', 47 | 'lib/condensation/template-helpers/layout.js', 48 | 'lib/condensation/template-helpers/objectify.js', 49 | 'lib/condensation/template-helpers/requireAssets.js', 50 | 'lib/condensation/template-helpers/scopeId.js', 51 | 'lib/condensation/template-helpers/templateS3Url.js' 52 | ], 53 | partial: [ 54 | path.join(__dirname,"..","jsdoc2md","templates","sig-name.hbs") 55 | ], 56 | separators: true, 57 | "name-format": false, 58 | "global-index-format": "none", 59 | "module-index-format": "none" 60 | }).then(function(doc) { 61 | fs.writeFile(path.join(__dirname,"..","docs","template-helpers.md"),doc,function(err) { 62 | if (err) console.warn(err); 63 | //done 64 | }); 65 | }); 66 | 67 | jsdoc2md.render({ 68 | files: [ 69 | 'lib/handlebars-helpers/*' 70 | ], 71 | partial: [ 72 | path.join(__dirname,"..","jsdoc2md","templates","sig-name.hbs") 73 | ], 74 | separators: true, 75 | "name-format": false, 76 | "global-index-format": "none", 77 | "module-index-format": "none" 78 | }).then(function(doc) { 79 | fs.writeFile(path.join(__dirname,"..","docs","handlebars-helpers.md"),doc,function(err) { 80 | if (err) console.warn(err); 81 | //done 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /docs/features.md: -------------------------------------------------------------------------------- 1 | ## Features 2 | 3 | * Write reusable CloudFormation snippets, called `particles` that can be used 4 | accross condensation projets 5 | * Package templates and assets then upload full distributions to multiple buckets across 6 | regions with one command. 7 | * Reference another template within the distribution with 8 | [AWS::CloudFormation::Stack](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-stack.html) 9 | and the `templateS3Url` helper 10 | * Upload scripts, configuration files and other assets alongside 11 | CloudFormation templates and reference them with asset helpers. 12 | 13 | ## Why? 14 | 15 | CloudFormation templates are great for creating, updating and deleting 16 | AWS resources. Reusing parts of templates, referencing other 17 | templates with `AWS::CloudFormation::Stack` and deploying cloud-init 18 | scripts can be difficult to manage. 19 | 20 | * Sections such as AMI [mappings](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/mappings-section-structure.html) 21 | are often re-used by many templates. Particles provide a way to 22 | write the mapping once and reuse it in other templates by reference. 23 | * It is common to set up resources, such as a VPC, with nearly 24 | identical attributes and structure for different applications and 25 | services. Condensation allows that definition to become a independent 26 | stack that can be referenced by other templates that are part of the 27 | same distribution. 28 | * When bootstrapping ec2 instances it is beneficial to have versioned scripts and configuration 29 | files deployed in the same bucket and path as the CloudFormation template 30 | they are associated with. 31 | * When using `AWS::CloudFormation::Authentication` to download assets from 32 | S3 buckets all resources must be in the same region. Condensation 33 | makes it easy to deploy the same templates and assets to multiple 34 | regions and ensure the referencing URLs are always pointing to the 35 | right place. 36 | 37 | For example, templates in a distribution can reference one another based on the 38 | bucket they are deployed to. 39 | 40 | Example: 41 | 42 | "TemplateURL": "{{templateS3Url 'vpc.template' }}" 43 | ... 44 | "TemplateURL": "{{templateS3Url 'subnet.template' }}" 45 | 46 | Output: 47 | 48 | "TemplateURL": "https://s3-us-west-1.amazonaws.com//cftemplates/vpc.template" 49 | ... 50 | "TemplateURL": "https://s3-us-west-1.amazonaws.com//cftemplates/subnet.template" 51 | 52 | The Handlebars helper, `templateS3Url`, creates a URL that will always reference a template deployed within the same bucket. 53 | -------------------------------------------------------------------------------- /docs/getting-started.md: -------------------------------------------------------------------------------- 1 | ## Quick Start 2 | 3 | ### With Docker 4 | 5 | The fastest way to get started with condensation: [condensation-docker][condensation-docker-url] 6 | 7 | ``` 8 | $ alias condensation="docker run -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_SESSION_TOKEN -v \"$HOME\"/.aws/credentials:/home/condensation/.aws/credentials -v \`pwd\`:/particles --rm -it sungardas/condensation" 9 | 10 | 11 | $ condensation create project particles-MYPROJECT 12 | $ cd particles-MYPROJECT 13 | $ condensation run build 14 | 15 | # Upload to S3 16 | $ condensation run deploy 17 | ``` 18 | 19 | 20 | ### With a nodejs environment 21 | 22 | Use the Yeoman 23 | [generator](https://github.com/SungardAS/generator-condensation). 24 | 25 | ``` 26 | $ npm install -g yo 27 | $ npm install -g generator-condensation 28 | $ yo condensation:project particles-MYPROJECT 29 | $ cd particles-MYPROJECT 30 | $ npm run build 31 | 32 | # Upload to S3 33 | $ npm run deploy 34 | ``` 35 | 36 | ### Example Projects 37 | 38 | * [condensation-examples](https://github.com/SungardAS/condensation-examples) 39 | * [particles-vpc](https://github.com/SungardAS/particles-vpc) 40 | * [particles-cloudsploit-scans](https://github.com/SungardAS/particles-cloudsploit-scans) 41 | * [particles-enhanced-snapshots](https://github.com/SungardAS/particles-enhanced-snapshots) 42 | 43 | Check out the growing list of particles on 44 | [npm](https://www.npmjs.com/browse/keyword/condensation-particles)! 45 | 46 | [condensation-docker-url]: https://github.com/SungardAS/condensation-docker 47 | -------------------------------------------------------------------------------- /docs/handlebars-helpers.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## HandlebarsHelpers 4 | Generic helpers that work with any handlebars project 5 | 6 | **Kind**: global namespace 7 | 8 | * [HandlebarsHelpers](#HandlebarsHelpers) : object 9 | * [.concat(...string, options)](#HandlebarsHelpers.concat) ⇒ String 10 | * [.stringify([string])](#HandlebarsHelpers.stringify) ⇒ String 11 | 12 | 13 | * * * 14 | 15 | 16 | 17 | ### concat(...string, options) 18 | Concatenates two or more strings 19 | 20 | **Kind**: static method of [HandlebarsHelpers](#HandlebarsHelpers) 21 | **Returns**: String - - One concatenated string 22 | 23 | | Param | Type | Description | 24 | | --- | --- | --- | 25 | | ...string | string | two or more strings to concatenate | 26 | | options | Object | passed by handlebars | 27 | | options.hash | Object | named key/value pairs | 28 | | options.hash.separator | string | string to separate each value | 29 | 30 | **Example** 31 | ```js 32 | {{concat "string1" "string2"}} 33 | ``` 34 | **Example** 35 | ```js 36 | {{concat "string1" "string2" separator="-"}} 37 | ``` 38 | 39 | * * * 40 | 41 | 42 | 43 | ### stringify([string]) 44 | JSON.stringify a string or block 45 | 46 | **Kind**: static method of [HandlebarsHelpers](#HandlebarsHelpers) 47 | **Returns**: String - - JSON.stringify result of block or string 48 | 49 | | Param | Type | Description | 50 | | --- | --- | --- | 51 | | [string] | string | String to use if block is not present | 52 | 53 | **Example** 54 | ```js 55 | {{stringify "a !string for {json} /end"}} 56 | ``` 57 | **Example** 58 | ```js 59 | {{#stringify}} 60 | mybash.sh -o option1 61 | continue.sh 62 | {{/stringify}} 63 | ``` 64 | **Example** 65 | ```js 66 | {{#stringify noLineBreaks}} 67 | docker run 68 | -e VAR1=VAL1 69 | -e VAR2=VAL2 70 | my/image 71 | {{/stringify}} 72 | ``` 73 | 74 | * * * 75 | 76 | -------------------------------------------------------------------------------- /docs/images/condensation_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SungardAS/condensation/4595b3d7ac7f7adbc7a8c049aa5fac18e59bf016/docs/images/condensation_logo.png -------------------------------------------------------------------------------- /docs/images/condensation_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 24 | 25 | 26 | 27 | 28 | 29 | 31 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /docs/tasks.md: -------------------------------------------------------------------------------- 1 | # Tasks 2 | 3 | ## Task List 4 | 5 | Get a full list of tasks by running: 6 | 7 | **condensation-docker** - `condensation list-tasks` 8 | 9 | **nodejs** - `npm run gulp -T` 10 | 11 | By default all tasks are prefixed with `condensation:`. This can be 12 | changed with the `taskPrefix` config option. 13 | 14 | 15 | ## Default 16 | 17 | The `default` task is an alias for `build`. It will prepare all files 18 | for deployment to s3. Templates and assets are written to the configured 19 | `dist` directory. 20 | 21 | **condensation-docker** - `condensation run build` 22 | 23 | **nodejs** - `npm run gulp` 24 | 25 | 26 | ## s3:list 27 | 28 | Will list all the configured s3 buckets and module corresponding ID. 29 | 30 | The IDs can be used to deploy to a single bucket instead of all buckets. 31 | 32 | **condensation-docker** - `condensation run-task s3:list` 33 | 34 | **nodejs** - `npm run gulp condensation:s3:list` 35 | 36 | **Example** 37 | 38 | > condensation run-task s3:list 39 | [10:21:47] Using gulpfile ~/condensation-example/gulpfile.js 40 | [10:21:47] Starting 'condensation:s3:list'... 41 | 0: a.bucket.in.us-east-1 42 | 1: a.bucket.in.us-west-2 43 | [10:21:47] Finished 'condensation:s3:list' after 153 μs 44 | 45 | 46 | ## build 47 | 48 | For the `build` task to run AWS credentials must be set as environment 49 | variables: `AWS_SECRET_ACCESS_KEY` and `AWS_ACCESS_KEY_ID` 50 | 51 | 52 | This will build and verify all templates 53 | 54 | **condensation-docker** - `condensation run build` 55 | 56 | **nodejs** - `npm run gulp condensation:build` 57 | 58 | **Example** 59 | 60 | > AWS_SECRET_ACCESS_KEY=XXXX AWS_ACCESS_KEY_ID=XXXX condensation run build 61 | 62 | 63 | ## deploy 64 | 65 | For the `deploy` task to run AWS credentials must be set as environment 66 | variables: `AWS_SECRET_ACCESS_KEY` and `AWS_ACCESS_KEY_ID` 67 | 68 | 69 | This will upload templates to all cofigured S3 buckets. 70 | 71 | **condensation-docker** - `condensation run deploy` 72 | 73 | **nodejs** - `npm run gulp condensation:deploy` 74 | 75 | **Example** 76 | 77 | > AWS_SECRET_ACCESS_KEY=XXXX AWS_ACCESS_KEY_ID=XXXX condensation run deploy 78 | 79 | 80 | ## deploy:ID 81 | 82 | Deploy templates to a specific S3 bucket. 83 | 84 | **condensation-docker** - `condensation run-task deploy:0` 85 | 86 | **nodejs** - `npm run gulp condensation:deploy:0` 87 | 88 | 89 | ## deploy:LABEL 90 | 91 | Deploy templates to all S3 buckets that contain the label, LABEL. 92 | 93 | **condensation-docker** - `condensation run-task deploy:dev` 94 | 95 | **nodejs** - `npm run gulp condensation:deploy:dev` 96 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var Condensation = require('./lib/condensation'); 2 | 3 | module.exports.buildTasks = function(gulp,options) { 4 | var condensation = new Condensation(gulp,options); 5 | return condensation.condense(); 6 | }; 7 | -------------------------------------------------------------------------------- /jsdoc2md/templates/sig-name.hbs: -------------------------------------------------------------------------------- 1 | {{#if virtual}}*{{/if}}{{#with (parentObject)}}{{#if virtual}}*{{/if~}}{{/with~}} 2 | {{#if name}}{{#sig~}} 3 | {{{@depOpen}~}} 4 | {{{@codeOpen}~}} 5 | {{#if @prefix}}{{@prefix}} {{/if~}} 6 | {{#if (isEvent)}}"{{{name}}}"{{else}}{{{name}}}{{/if~}} 7 | {{#if @methodSign}}{{#if (isEvent)}} {{@methodSign}}{{else}}{{@methodSign}}{{/if}}{{/if~}} 8 | {{{@codeClose}~}} 9 | {{!-- 10 | {{#if @returnSymbol}} {{@returnSymbol}}{{/if~}} 11 | {{#if @returnTypes}} {{>linked-type-list types=@returnTypes delimiter=" | " }}{{/if~}} 12 | --}} 13 | {{#if @suffix}} {{@suffix}}{{/if~}} 14 | {{{@depClose}~}} 15 | {{~/sig}}{{/if~}} 16 | {{#if virtual}}*{{/if}}{{#with (parentObject)}}{{#if virtual}}*{{/if~}}{{/with~}} 17 | 18 | -------------------------------------------------------------------------------- /lib/condensation/loaders/all-helpers.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var loadTemplateHelper = require('./template-helper-loader'); 3 | var sections = require('../template-helpers/sections'); 4 | 5 | module.exports = function(options) { 6 | 7 | var helpers = { 8 | arrayify: require('../template-helpers/arrayify'), 9 | assetPath: loadTemplateHelper(require('../template-helpers/assetPath'),options), 10 | assetS3Url: loadTemplateHelper(require('../template-helpers/assetS3Url'),options), 11 | concat: require('../../handlebars-helpers/concat'), 12 | cValue: require('../template-helpers/cValue'), 13 | fnAnd: require('../template-helpers/functions/fnAnd'), 14 | fnEquals: require('../template-helpers/functions/fnEquals'), 15 | fnIf: require('../template-helpers/functions/fnIf'), 16 | fnNot: require('../template-helpers/functions/fnNot'), 17 | fnOr: require('../template-helpers/functions/fnOr'), 18 | fnBase64: require('../template-helpers/functions/fnBase64'), 19 | fnFindInMap: require('../template-helpers/functions/fnFindInMap'), 20 | fnGetAZs: require('../template-helpers/functions/fnGetAZs'), 21 | fnGetAtt: require('../template-helpers/functions/fnGetAtt'), 22 | fnGetArtifactAtt: require('../template-helpers/functions/fnGetArtifactAtt'), 23 | fnGetParam: require('../template-helpers/functions/fnGetParam'), 24 | fnImportValue: require('../template-helpers/functions/fnImportValue'), 25 | fnJoin: require('../template-helpers/functions/fnJoin'), 26 | fnSelect: require('../template-helpers/functions/fnSelect'), 27 | fnSplit: require('../template-helpers/functions/fnSplit'), 28 | fnSub: require('../template-helpers/functions/fnSub'), 29 | helper: loadTemplateHelper(require('../template-helpers/helper'),options), 30 | layout: loadTemplateHelper(require('../template-helpers/layout'),options), 31 | objectify: require('../template-helpers/objectify'), 32 | partial: loadTemplateHelper(require('../template-helpers/partial'),options), 33 | ref: require('../template-helpers/functions/ref'), 34 | requireAssets: loadTemplateHelper(require('../template-helpers/requireAssets'),options), 35 | scopeId: require('../template-helpers/scopeId'), 36 | set: loadTemplateHelper(require('../template-helpers/set'),options), 37 | stringify: require('../../handlebars-helpers/stringify'), 38 | templateS3Url: loadTemplateHelper(require('../template-helpers/templateS3Url'),options) 39 | }; 40 | 41 | _.each(_.values(sections), function(v) { 42 | helpers[v.NAME] = loadTemplateHelper(v,options); 43 | }); 44 | 45 | 46 | var engine = options.handlebars; 47 | _.each(_.toPairs(helpers), function(kv) { 48 | if (!engine.helpers[kv[0]]) { 49 | engine.registerHelper(kv[0],kv[1]); 50 | } 51 | }); 52 | 53 | return helpers; 54 | }; 55 | 56 | -------------------------------------------------------------------------------- /lib/condensation/loaders/front-loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ``` 3 | * - particles 4 | * |- front_loaders 5 | * |- particle_name 6 | * ``` 7 | * 8 | * @example 9 | * --- 10 | * front_loaders: 11 | * availability_zones: particles-vpc availabilityZones 12 | * --- 13 | * @function front_loader 14 | * @memberof SpecialParticles 15 | * @param {string} [module] - module to load with either `module:` or `m:` 16 | * @param {string} path - Path to the helper, excluding the `.js` extension 17 | * @param {...kv} [options] - Key/Value pairs to pass to the particle helper 18 | * @returns {*} - The output from the front loader 19 | * 20 | */ 21 | 22 | var _ = require("lodash"); 23 | 24 | module.exports = function frontLoader(loaderConfig,templateData,cb) { 25 | 26 | var config = _.merge({},loaderConfig); 27 | 28 | var particle = this.particleLoader.loadParticle('frontLoader',config.module,config.loader,{parentFile: config._file}); 29 | var loaderFunc = require(particle.path); 30 | 31 | var args = _.isArray(config.args) ? config.args : []; 32 | args.push(config.opts); 33 | args.push(cb); 34 | 35 | return loaderFunc.apply( 36 | templateData, 37 | args 38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /lib/condensation/loaders/particle-loader.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var _ = require('lodash'); 3 | var glob = require('glob'); 4 | var path = require('path'); 5 | var slash = require('slash'); 6 | var util = require('util'); 7 | var VError = require('verror'); 8 | 9 | var ParticleLoader = function(options) { 10 | this.root = options.root; 11 | this.registry = {}; 12 | this.typeMap = { 13 | 'asset': 'assets', 14 | 'condition': 'conditions', 15 | 'frontLoader': 'front_loaders', 16 | 'helper': 'helpers', 17 | 'mapping': 'mappings', 18 | 'metadata': 'metadata', 19 | 'output': 'outputs', 20 | 'parameter': 'parameters', 21 | 'partial': 'partials', 22 | 'resource': 'resources', 23 | 'set': 'sets', 24 | 'template': 'cftemplates' 25 | }; 26 | 27 | this.typeGlobs = { 28 | 'asset': ['','?(.hbs)]'], 29 | 'condition': ['','.*'], 30 | 'frontLoader': ['.js'], 31 | 'helper': ['.js'], 32 | 'mapping': ['','.*'], 33 | 'metadata': ['','.*'], 34 | 'output': ['','.*'], 35 | 'parameter': ['','.*'], 36 | 'partial': ['','.*'], 37 | 'resource': ['','.*'], 38 | 'set': ['','.*'], 39 | 'template': ['?(.hbs)'] 40 | }; 41 | }; 42 | util.inherits(ParticleLoader,EventEmitter); 43 | 44 | ParticleLoader.prototype.loadParticle = function(type,cModule,pPath,options) { 45 | var loadError = new VError("particle load error"); 46 | 47 | var opts = _.merge({ 48 | parentFile: null, 49 | cModule: '' 50 | },_.omit(options,_.isUndefined)); 51 | 52 | if (!_.isString(pPath)) { 53 | throw new VError(loadError, '%s particlePath in "%s" must be a string', type, opts.parentFile.path); 54 | } 55 | 56 | // Make Windows Happy 57 | var particlePath = pPath.replace(/\//g,path.sep); 58 | 59 | // Used to reduce the number of times this particle is searched for. 60 | // TODO Possible to group by package instead of file? 61 | var particleId = [opts.parentFile.path,cModule,particlePath].join(':'); 62 | 63 | this.registry[type] = this.registry[type] || {}; 64 | 65 | if (this.registry[type][particleId]) { 66 | return this.registry[type][particleId]; 67 | } 68 | 69 | // Start building the particle object 70 | var particle = { modulePath: '' }; 71 | 72 | // Look for the closest particles directory in relation to the parent file making the request 73 | var particlesIndex = opts.parentFile.path.lastIndexOf(path.join(path.sep,'particles',path.sep)); 74 | if (particlesIndex > 0) { 75 | particle.modulePath = opts.parentFile.path.slice(0,particlesIndex); 76 | } 77 | 78 | // Look in node_modules is a condensation module is defined 79 | if (cModule) { 80 | particle.modulePath = path.join(particle.modulePath,'node_modules',cModule); 81 | } 82 | 83 | // This feels UGLY 84 | // Walks the tree to find particles in parent node_modules directories 85 | // TODO Got to be a better way to do this. 86 | var done = false; 87 | while (done === false) { 88 | particle = _.merge(particle,this.genParticlePaths(particle,type,particlePath)); 89 | 90 | // TODO more efficient discovery? 91 | var globFiles = _.map(this.typeGlobs[type], function(pattern) { 92 | return glob.sync(particle.path+pattern,{nodir: true}); 93 | }); 94 | var files = _.flatten(globFiles); 95 | if (files[0]) { 96 | particle.fsPath = files[0]; 97 | done = true; 98 | } 99 | else { 100 | var lastNodeModules = particle.modulePath.lastIndexOf(path.join(path.sep,'node_modules')); 101 | if (lastNodeModules < 0) { 102 | done = true; 103 | } 104 | else { 105 | particle.modulePath = particle.modulePath.slice(0,lastNodeModules); 106 | var newNodeModules = particle.modulePath.lastIndexOf(path.join(path.sep,'node_modules')); 107 | if (newNodeModules < 0) { 108 | done = true; 109 | } 110 | else { 111 | particle.modulePath = particle.modulePath.slice(0,newNodeModules+13); 112 | particle.modulePath = path.join(particle.modulePath,cModule); 113 | } 114 | } 115 | } 116 | } 117 | if (!particle.fsPath) { 118 | throw new VError(loadError, '%s %s refrenced in %s could not be found', type, particlePath, opts.parentFile.path); 119 | } 120 | 121 | this.registry[type][particleId] = particle; 122 | this.emit('particle',particle); 123 | return particle; 124 | }; 125 | 126 | ParticleLoader.prototype.genParticlePaths = function(particle,type,particlePath) { 127 | 128 | var pathObj = path.parse(path.join(particle.modulePath,'particles',this.typeMap[type],particlePath)); 129 | var realPath = path.format(pathObj); 130 | 131 | var relativePathObj = path.parse(path.relative(path.resolve(process.cwd(),this.root),realPath)); 132 | var relativePath = path.format(relativePathObj); 133 | 134 | var urlPath = slash(relativePath); 135 | 136 | return { 137 | pathObj: pathObj, 138 | path: realPath, 139 | relativePathObj: relativePathObj, 140 | relativePath: relativePath, 141 | urlPath: urlPath 142 | }; 143 | }; 144 | 145 | ParticleLoader.prototype.processablePaths = function() { 146 | var templatePaths = _.values(this.registry.template); 147 | var assetPaths = _.values(this.registry.asset); 148 | 149 | return _.map(_.flatten([templatePaths,assetPaths]),'path'); 150 | }; 151 | 152 | module.exports = ParticleLoader; 153 | 154 | -------------------------------------------------------------------------------- /lib/condensation/loaders/template-helper-loader.js: -------------------------------------------------------------------------------- 1 | var Handlebars = require('handlebars'); 2 | var _ = require('lodash'); 3 | 4 | var loadHelper = function(helperDefinition,options) { 5 | var cOpts = _.merge({ 6 | handlebars: Handlebars, 7 | projectDir: process.cwd(), 8 | particleLoader: null 9 | },_.omit(options,_.isUndefined)); 10 | 11 | var wrappedHelper = function () { 12 | var cModule, pPath, hArgs, hOpts; 13 | 14 | if (arguments.length === 1) { 15 | cModule = ''; 16 | pPath = ''; 17 | } 18 | else { 19 | var moduleCheck = _.isString(arguments[0]) ? arguments[0].split(':') : null; 20 | 21 | if (moduleCheck && moduleCheck[0] === 'module') { 22 | cModule = moduleCheck[1]; 23 | pPath = arguments[1]; 24 | } 25 | else if (moduleCheck && moduleCheck[0] === 'm') { 26 | cModule = "particles-" + moduleCheck[1]; 27 | pPath = arguments[1]; 28 | } 29 | else { 30 | cModule = ''; 31 | pPath = arguments[0]; 32 | } 33 | 34 | hArgs = _.slice(arguments,(cModule ? 2 : 1),arguments.length-1); 35 | } 36 | hOpts = arguments[arguments.length-1]; 37 | 38 | return helperDefinition.helper.apply(this,[cModule,pPath,hArgs,hOpts,cOpts]); 39 | 40 | }; 41 | 42 | return wrappedHelper; 43 | }; 44 | 45 | module.exports = loadHelper; 46 | -------------------------------------------------------------------------------- /lib/condensation/tasks/build.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | var cache = require("gulp-cached"); 3 | var cfValidate = require("../../gulp-plugins/gulp-cf-validate"); 4 | var cUtil = require("../util"); 5 | var es = require("event-stream"); 6 | var fs = require("fs-extra"); 7 | var gulp = require("gulp"); 8 | var gulpif = require("gulp-if"); 9 | var gutil = require("gulp-util"); 10 | var path = require("path"); 11 | var rename = require("gulp-rename"); 12 | var shortid = require("shortid"); 13 | var through = require("through2"); 14 | var url = require("url"); 15 | var util = require("util"); 16 | var VError = require("verror"); 17 | 18 | 19 | module.exports = function(s3opts,distPath,awsS3) { 20 | var self = this; 21 | 22 | var templateData = {}; 23 | var urlString = [awsS3.endpoint.href,path.posix.join(s3opts.aws.bucket,s3opts.prefix)].join(''); 24 | 25 | templateData.s3 = s3opts.aws; 26 | templateData.s3.prefix = s3opts.prefix; 27 | templateData.s3.condensationUrl = url.parse(urlString); 28 | 29 | templateData.buildId = shortid.generate(); 30 | 31 | var stream = es.readable(function(esCount,streamCb) { 32 | var readable = this; 33 | 34 | var totalCount = 0; 35 | var lastTotalCount = 0; 36 | 37 | var runStreams = function(globs,options) { 38 | var s = gulp.src(globs,options) 39 | .pipe(cache(self.options.projectName+distPath)) 40 | .pipe(gulpif(/\.hbs$/,through.obj(function(file,enc,cb) { 41 | cUtil.processFrontMatter.call(self, file, templateData, function(err, matter) { 42 | var fn = self.handlebars.compile(matter.content,{noEscape:true}); 43 | var templatePath = file.relative.replace(/\.hbs$/,""); 44 | try { 45 | file.contents = new Buffer(fn( 46 | _.merge(matter.data, templateData), 47 | {data: {_file: file, _templatePath: templatePath}} 48 | )); 49 | } 50 | catch(e) { 51 | var ve = new VError(e,"TemplateError %s",file.relative); 52 | throw(ve.message); 53 | } 54 | cb(null,file); 55 | }); 56 | }))) 57 | .pipe(through.obj(function(file,enc,cb) { 58 | totalCount = totalCount + 1; 59 | readable.emit('data',file); 60 | cb(null,file); 61 | })); 62 | s.on('data',function(){}); 63 | s.on('end',function(err) { 64 | if (lastTotalCount === totalCount) { 65 | readable.emit('end'); 66 | streamCb(); 67 | } 68 | else { 69 | var paths = _.invokeMap( 70 | self.particleLoader.processablePaths(), 71 | function() { 72 | return this+"?(.hbs)"; 73 | } 74 | ); 75 | 76 | lastTotalCount = totalCount; 77 | runStreams(paths,{base:self.options.root}); 78 | } 79 | }); 80 | }; 81 | 82 | runStreams( 83 | [ 84 | "particles/cftemplates/**/*.json", 85 | "particles/cftemplates/**/*.template", 86 | "particles/cftemplates/**/*.hbs" 87 | ], 88 | {cwd:self.options.root,base:self.options.root} 89 | ); 90 | 91 | }); 92 | 93 | 94 | var parseTemplate = through.obj(function(file,enc,cb) { 95 | var error; 96 | if (file.isNull()) { 97 | //Do Nothing for now 98 | } 99 | else { 100 | try { 101 | var formatted = JSON.stringify(JSON.parse(file.contents.toString()), null, 2); 102 | file.contents = new Buffer(formatted); 103 | } 104 | catch(e) { 105 | e.message = "File " + file.path + " is not valid JSON. " + e.message; 106 | fs.outputFileSync(path.join('condensation_errors',file.path),file.contents); 107 | error = new gutil.PluginError('build',e); 108 | } 109 | } 110 | cb(error,file); 111 | }); 112 | 113 | var doValidate = function(file) { 114 | return (file.path.match(/cftemplates[\/\\]/) && (process.env.FORCE_VALIDATE || s3opts.validate)) 115 | } 116 | 117 | stream = stream 118 | .pipe(gulpif(/\.hbs$/,rename({extname:""}))) 119 | .pipe(gulpif(/cftemplates[\/\\]/,parseTemplate)) 120 | .pipe(gulpif(doValidate,cfValidate({region: s3opts.aws.region}))) 121 | .pipe(gulp.dest(distPath)) 122 | 123 | return stream; 124 | }; 125 | -------------------------------------------------------------------------------- /lib/condensation/tasks/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | build: require('./build'), 3 | s3: require('./s3') 4 | }; 5 | -------------------------------------------------------------------------------- /lib/condensation/tasks/s3/deleteObjects.js: -------------------------------------------------------------------------------- 1 | var s3 = require('s3'); 2 | 3 | module.exports = function(s3opts,awsS3,cb) { 4 | var client = s3.createClient({ 5 | s3Options: { 6 | region: s3opts.aws.region 7 | } 8 | }); 9 | 10 | var deleteDir = client.deleteDir({ 11 | Bucket: s3opts.aws.bucket, 12 | Prefix: s3opts.prefix 13 | }); 14 | deleteDir.on('error',cb); 15 | deleteDir.on('end',cb); 16 | 17 | }; 18 | -------------------------------------------------------------------------------- /lib/condensation/tasks/s3/ensureBucket.js: -------------------------------------------------------------------------------- 1 | module.exports = function(s3opts,awsS3,cb) { 2 | awsS3.headBucket({ 3 | Bucket: s3opts.aws.bucket 4 | },function(err,response){ 5 | if (err && err.code === 'NotFound' && s3opts.create) { 6 | awsS3.createBucket({ 7 | Bucket: s3opts.aws.bucket 8 | },cb); 9 | } 10 | else { 11 | cb(null,response); 12 | //TODO Removed for cross account access. Need to revisit s3 bucket permissions and correct fallback 13 | // cb(err,data); 14 | } 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /lib/condensation/tasks/s3/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ensureBucket: require('./ensureBucket'), 3 | writeObjects: require('./writeObjects'), 4 | deleteObjects: require('./deleteObjects') 5 | }; 6 | -------------------------------------------------------------------------------- /lib/condensation/tasks/s3/writeObjects.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var path = require('path'); 3 | 4 | module.exports = function(s3opts,awsS3,distPath) { 5 | var stream = gulp.src("**",{cwd:distPath}).on("data",function(file) { 6 | if (file.stat.isFile()) { 7 | var newFilename = file.relative; 8 | 9 | if (s3opts.prefix) { 10 | 11 | newFilename = path.posix.join(s3opts.prefix,newFilename); 12 | 13 | // Ensure the path does not start with a '/' 14 | newFilename = newFilename.replace(/^\//,''); 15 | } 16 | 17 | awsS3.putObject({ 18 | Bucket: s3opts.aws.bucket, 19 | ACL: "bucket-owner-full-control", 20 | //ACL: "private", 21 | Key: newFilename, 22 | Body: file.contents 23 | },function(err,response) { 24 | if (err) { 25 | // TODO throw error 26 | console.warn(err,response); 27 | } 28 | }); 29 | } 30 | }); 31 | return stream; 32 | }; 33 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/arrayify.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | var cValue = require("./cValue"); 3 | 4 | /** 5 | * Turn a string into a JSON parseable array. 6 | * Each value is processed with `cValue` 7 | * @function arrayify 8 | * @memberof TemplateHelpers 9 | * @example 10 | * {{arrayify "string" (ref "Parameter1") }} 11 | * @example 12 | * {{arrayify (ref "Parameter1")}} 13 | * @param {...string} str - String to use if block is not present 14 | * @returns {String} - When parsed will be an array 15 | * 16 | */ 17 | module.exports= function arrayify () { 18 | var joinValues = _.slice(arguments, 0, arguments.length-1); 19 | var options = arguments[arguments.length-1]; 20 | 21 | joinValues = _.map(joinValues, function(v) { 22 | return cValue(v) 23 | }); 24 | 25 | return '['+joinValues.join(",")+']'; 26 | 27 | }; 28 | 29 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/assetPath.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Build the S3 path to an asset particle within the project 3 | * @function assetPath 4 | * @memberof TemplateHelpers 5 | * @example 6 | * {{assetPath "myasset.zip"}} 7 | * @example 8 | * {{assetPath "path/to/myasset.sh"}} 9 | * @param {string} path - the path to the particle relative to the `assets` directory 10 | * @returns {String} - S3 path to the asset 11 | * 12 | */ 13 | 14 | var url = require('url'); 15 | var path = require('path'); 16 | 17 | var helper = function assetPath(cModule,pPath,hArgs,hOpts,cOpts) { 18 | 19 | var particle = cOpts.particleLoader.loadParticle('asset',cModule,pPath,{parentFile: hOpts.data._file}); 20 | 21 | return path.posix.join(hOpts.data.root.s3.prefix,particle.urlPath); 22 | 23 | }; 24 | 25 | module.exports.NAME = 'assetPath'; 26 | module.exports.helper = helper; 27 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/assetS3Url.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Build full S3 URL an asset particle within the project 3 | * @function assetS3Url 4 | * @memberof TemplateHelpers 5 | * @example 6 | * {{assetS3Url "myasset.zip"}} 7 | * @example 8 | * {{assetS3Url "myasset.zip" protocol="s3"}} 9 | * @example 10 | * {{assetS3Url "path/to/myasset.sh"}} 11 | * @param {string} path - the path to the particle relative to the `assets` directory 12 | * @param {Object} options - options used to generate the URL 13 | * @param {Object} options.protocol - [s3|https] s3:// or https:// 14 | * @returns {String} - Full S3 URL to the asset 15 | * 16 | */ 17 | 18 | var url = require('url'); 19 | var path = require('path'); 20 | 21 | var helper = function assetS3Url(cModule,pPath,hArgs,hOpts,cOpts) { 22 | //Assumption: The relative path returned never starts with a leading slash. This appears to be the behavior 23 | var particle = cOpts.particleLoader.loadParticle('asset',cModule,pPath,{parentFile: hOpts.data._file}); 24 | 25 | var protocol = hOpts.hash.protocol; 26 | 27 | switch(protocol){ 28 | case 's3': 29 | return ['s3:/',path.posix.join(hOpts.data.root.s3.condensationUrl.path,particle.urlPath)].join(''); 30 | 31 | default: 32 | return [url.format(hOpts.data.root.s3.condensationUrl),particle.urlPath].join('/'); 33 | } 34 | }; 35 | 36 | module.exports.NAME = 'assetS3Url'; 37 | module.exports.helper = helper; 38 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/cValue.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Coerce Value 3 | * 4 | * Will check to see if a string is a parsable object. If it is, 5 | * then it will be left alone and simply returned back. 6 | * 7 | * If the string is not a parseable object it will be made JSON compliant and returned. 8 | * 9 | * If the string is empty, null, undefined or otherwise `falsey` then an empty string will be returned. 10 | * @summary Coerce value 11 | * @example 12 | * --- 13 | * foo: bar 14 | * baz: {"Ref": "LogicalId"} 15 | * faz: "5" 16 | * --- 17 | * {{cValue foo}} 18 | * {{cValue baz}} 19 | * {{cValue faz forceNumber=true}} 20 | * @function cValue 21 | * @memberof TemplateHelpers 22 | * @param {string} str - The string to evaluate 23 | * @param {Object} options - options object from Handlebars 24 | * @param {boolean} options.forceNumber - return a number at all costs 25 | * @returns {string|Number} 26 | * 27 | */ 28 | 29 | var _ = require('lodash'); 30 | 31 | module.exports = function cValue(str,options) { 32 | options = options || {}; 33 | var isObject = false; 34 | 35 | if (_.isPlainObject(str) || _.isArray(str) || _.isNumber(str)) { 36 | return JSON.stringify(str); 37 | } 38 | else { 39 | try { 40 | var check = JSON.parse(str) 41 | if (_.isBoolean(check)) { 42 | throw("boolean"); 43 | } 44 | if (_.isNumber(check) && !options.hash.forceNumber) { 45 | throw("number"); 46 | } 47 | 48 | return str; 49 | } 50 | catch(e) { 51 | var returnStr = JSON.stringify(str) || '""'; 52 | return returnStr; 53 | } 54 | } 55 | 56 | }; 57 | 58 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/functions/fnAnd.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | var cValue = require("../cValue"); 3 | 4 | /** 5 | * Fn::And definition 6 | * @function fnAnd 7 | * @memberof "IntrinsicFunctions" 8 | * @param {...string} condition - Any number of conditions 9 | * @returns {string} 10 | */ 11 | module.exports = function fnAnd() { 12 | var joinValues = _.slice(arguments, 0, arguments.length-1); 13 | 14 | joinValues = _.map(joinValues, function(v) { 15 | v = cValue(v); 16 | if (v.charAt(0) === '"') { 17 | v = '{"Condition": '+v+'}'; 18 | } 19 | return v; 20 | }); 21 | 22 | return '{"Fn::And": ['+joinValues.join(",")+']}'; 23 | 24 | }; 25 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/functions/fnBase64.js: -------------------------------------------------------------------------------- 1 | var cValue = require("../cValue"); 2 | 3 | /** 4 | * Fn::Base64 definition 5 | * @function fnBase64 6 | * @memberof "IntrinsicFunctions" 7 | * @type {function} 8 | * @param {string} str - The string to evaluate 9 | * @param {Object} options - Passed in by Handlebars 10 | * @returns {string} 11 | */ 12 | module.exports = function fnBase64() { 13 | var str = ""; 14 | var options = arguments[arguments.length-1]; 15 | 16 | if (options.fn) { 17 | str = options.fn(this); 18 | } 19 | else { 20 | str = arguments[0]; 21 | } 22 | 23 | return '{"Fn::Base64": '+cValue(str,options)+'}'; 24 | }; 25 | 26 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/functions/fnEquals.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | var cValue = require("../cValue"); 3 | 4 | /** 5 | * Fn::Equals definition 6 | * @function fnEquals 7 | * @memberof "IntrinsicFunctions" 8 | * @type {function} 9 | * @param {string} v1 - first value to compare 10 | * @param {string} v2 - second value to compare 11 | * @returns {string} A JSON compliant string for CloudFormation 12 | */ 13 | module.exports = function fnEquals(v1,v2,options) { 14 | v1 = cValue(v1); 15 | v2 = cValue(v2); 16 | options = _.merge({hash: {}}, options); 17 | 18 | return '{"Fn::Equals": ['+v1+','+v2+']}'; 19 | 20 | }; 21 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/functions/fnFindInMap.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | var cValue = require("../cValue"); 3 | 4 | /** 5 | * Fn::FindInMap definition 6 | * @function fnFindInMap 7 | * @memberof "IntrinsicFunctions" 8 | * @type {function} 9 | * @param {string} mapName - logicalId of the map in the template 10 | * @param {string} topLevelKey - The top-level key name. Its value is a list of key-value pairs 11 | * @param {string} secondLevelKey - The second-level key name, which is set to one of the keys from the list assigned to TopLevelKey 12 | * @param {Object} options - options for creting the logicalId reference 13 | * @return {string} A JSON compliant string for CloudFormation 14 | */ 15 | module.exports = function fnFindInMap(mapName, topLevelKey, secondLevelKey, options) { 16 | options = _.merge({hash: {}}, options); 17 | 18 | mapName = cValue(mapName, options); 19 | topLevelKey = cValue(topLevelKey, options); 20 | secondLevelKey = cValue(secondLevelKey, options); 21 | 22 | return '{"Fn::FindInMap": ['+[mapName,topLevelKey,secondLevelKey].join(",")+']}'; 23 | 24 | }; 25 | 26 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/functions/fnGetAZs.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | var cValue = require("../cValue"); 3 | 4 | /** 5 | * Fn::GetAZs definition 6 | * @function fnGetAZs 7 | * @memberof "IntrinsicFunctions" 8 | * @type {function} 9 | * @param {string} region - name of the region 10 | * @param {Object} options - options passed by handlebars 11 | * @returns {string} 12 | */ 13 | module.exports = function fnGetAZs(region, options) { 14 | options = _.merge({hash: {}}, options); 15 | 16 | region = cValue(region, options); 17 | 18 | return '{"Fn::GetAZs": '+region+'}'; 19 | 20 | }; 21 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/functions/fnGetArtifactAtt.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | var cValue = require("../cValue"); 3 | 4 | /** 5 | * Fn::GetArtifactAtt definition 6 | * @function fnGetArtifactAtt 7 | * @memberof "IntrinsicFunctions" 8 | * @type {function} 9 | * @param {string} artifactName - The name of the input artifact. You must declare this artifact as input for the associated action. 10 | * @param {string} attributeName - The name of the artifact attribute whose value you want to retrieve. For details about each artifact attribute, see the following Attributes section. 11 | * @param {Object} options - options for creting the logicalId reference 12 | * @return {string} A JSON compliant string for CloudFormation 13 | */ 14 | module.exports = function fnGetArtifactAtt(artifactName, attributeName, options) { 15 | options = _.merge({hash: {}}, options); 16 | 17 | artifactName = cValue(artifactName, options); 18 | attributeName = cValue(attributeName, options); 19 | 20 | return '{"Fn::GetArtifactAtt": ['+[artifactName,attributeName].join(",")+']}'; 21 | 22 | }; 23 | 24 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/functions/fnGetAtt.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | var cValue = require("../cValue"); 3 | 4 | /** 5 | * Fn::GetAtt definition 6 | * @function fnGetAtt 7 | * @memberof "IntrinsicFunctions" 8 | * @type {function} 9 | * @param {string} logicalId - resource that contains the attribute you want 10 | * @param {string} attributeName - name of the resource-specific attribute whose value you want 11 | * @param {Object} options - options for creting the logicalId reference 12 | * @returns {string} A JSON compliant string for CloudFormation 13 | */ 14 | module.exports = function fnGetAtt(logicalId, attributeName, options) { 15 | logicalId = cValue(logicalId); 16 | attributeName = cValue(attributeName); 17 | 18 | options = _.merge({hash: {}}, options); 19 | 20 | return '{"Fn::GetAtt": ['+[logicalId,attributeName].join(",")+']}'; 21 | 22 | }; 23 | 24 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/functions/fnGetParam.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | var cValue = require("../cValue"); 3 | 4 | /** 5 | * Fn::GetParam definition 6 | * @function fnGetParam 7 | * @memberof "IntrinsicFunctions" 8 | * @type {function} 9 | * @param {string} artifactName - The name of the artifact, which must be included as an input artifact for the associated action 10 | * @param {string} JSONFileName - The name of a JSON file that is contained in the artifact 11 | * @param {string} keyName - The name of the key whose value you want to retrieve 12 | * @param {Object} options - options for creting the logicalId reference 13 | * @return {string} A JSON compliant string for CloudFormation 14 | */ 15 | module.exports = function fnGetParam(artifactName, JSONFileName, keyName, options) { 16 | options = _.merge({hash: {}}, options); 17 | 18 | artifactName = cValue(artifactName, options); 19 | JSONFileName = cValue(JSONFileName, options); 20 | keyName = cValue(keyName, options); 21 | 22 | return '{"Fn::GetParam": ['+[artifactName,JSONFileName,keyName].join(",")+']}'; 23 | 24 | }; 25 | 26 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/functions/fnIf.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | var cValue = require("../cValue"); 3 | 4 | /** 5 | * Fn::If definition 6 | * @function fnIf 7 | * @memberof "IntrinsicFunctions" 8 | * @type {function} 9 | * @param {string} conditionName - Name of the condition to reference 10 | * @param {string} trueValue - value to use if condition is true 11 | * @param {string} falseValue - value to use if condition is false 12 | * @param {Object} options - options passed by handlebars 13 | * @returns {string} 14 | */ 15 | module.exports = function fnIf(conditionName, trueValue, falseValue, options) { 16 | trueValue = cValue(trueValue); 17 | falseValue = cValue(falseValue); 18 | options = _.merge({hash: {}}, options); 19 | 20 | return '{"Fn::If": ["'+conditionName+'",'+trueValue+','+falseValue+']}'; 21 | 22 | }; 23 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/functions/fnImportValue.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | var cValue = require("../cValue"); 3 | 4 | /** 5 | * Fn::ImportValue definition 6 | * @function fnImportValue 7 | * @memberof "IntrinsicFunctions" 8 | * @type {function} 9 | * @param {string} sharedValue - name of the shared value 10 | * @param {Object} options - options passed by handlebars 11 | * @returns {string} 12 | */ 13 | module.exports = function fnImportValue(sharedValue, options) { 14 | options = _.merge({hash: {}}, options); 15 | 16 | sharedValue = cValue(sharedValue, options); 17 | 18 | return '{"Fn::ImportValue": '+sharedValue+'}'; 19 | 20 | }; 21 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/functions/fnJoin.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | var cValue = require("../cValue"); 3 | 4 | /** 5 | * Fn::Join definition 6 | * @function fnJoin 7 | * @memberof "IntrinsicFunctions" 8 | * @type {function} 9 | * @param {array|string|...string} arr - if one parameter, it will be used as the array. If multiple, they will be joined to form the array. 10 | * @param {Object} options - options passed by handlebars 11 | * @returns {string} 12 | * @example 13 | * {{fnJoin "," (ref "Parameter1") }} 14 | * @example 15 | * {{fnJoin "," (fnGetAZs (ref "AWS::Region")) }} 16 | * @example 17 | * {{fnJoin "," "one" (ref "Parameter") "three"}} 18 | */ 19 | module.exports = function fnJoin() { 20 | var delimiter = cValue(arguments[0]); 21 | var joinValues = _.slice(arguments, 1, arguments.length-1); 22 | var options = arguments[arguments.length-1]; 23 | 24 | var arr; 25 | if (joinValues.length === 1) 26 | arr = cValue(joinValues[0]); 27 | else { 28 | var joinValues = _.map(joinValues, function(v) { return cValue(v) }); 29 | arr = '['+joinValues.join(",")+"]"; 30 | } 31 | 32 | options = _.merge({hash: {}}, options); 33 | 34 | return '{"Fn::Join": ['+delimiter+','+arr+']}'; 35 | 36 | }; 37 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/functions/fnNot.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | var cValue = require("../cValue"); 3 | 4 | /** 5 | * Fn::Not definition 6 | * @example 7 | * {{fnNot "Condition1"}} 8 | * @example 9 | * {{fnNot (fnEquals (ref "ParameterName") "value") }} 10 | * @function fnNot 11 | * @memberof "IntrinsicFunctions" 12 | * @type {function} 13 | * @param {string} condition - condition to evaluate 14 | * @param {Object} options - options passed by handlebars 15 | * @returns {string} 16 | */ 17 | module.exports = function fnNot(v1,options) { 18 | v1 = cValue(v1); 19 | options = _.merge({hash: {}}, options); 20 | 21 | if (v1.charAt(0) === '"') { 22 | v1 = '{"Condition": '+v1+'}'; 23 | } 24 | 25 | return '{"Fn::Not": ['+v1+']}'; 26 | 27 | }; 28 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/functions/fnOr.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | var cValue = require("../cValue"); 3 | 4 | /** 5 | * Fn::Or definition 6 | * @function fnOr 7 | * @memberof "IntrinsicFunctions" 8 | * @type {function} 9 | * @param {...string} condition - One to many conditions 10 | * @returns {string} 11 | */ 12 | module.exports = function fnOr() { 13 | var joinValues = _.slice(arguments, 0, arguments.length-1); 14 | 15 | joinValues = _.map(joinValues, function(v) { 16 | v = cValue(v); 17 | if (v.charAt(0) === '"') { 18 | v = '{"Condition": '+v+'}'; 19 | } 20 | return v; 21 | }); 22 | 23 | return '{"Fn::Or": ['+joinValues.join(",")+']}'; 24 | 25 | }; 26 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/functions/fnSelect.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | var cValue = require("../cValue"); 3 | 4 | /** 5 | * Fn::Select definition 6 | * @example 7 | * {{fnSelect 0 (ref "ParameterList")}} 8 | * @example 9 | * {{fnSelect 0 "value1" "value2"}} 10 | * @function fnSelect 11 | * @memberof "IntrinsicFunctions" 12 | * @type {function} 13 | * @param {Number} index - array member to pick 14 | * @param {...string} str - strings to select from 15 | * @param {Object} options - options passed by handlebars 16 | * @returns {string} 17 | */ 18 | module.exports = function fnSelect() { 19 | var index = cValue(arguments[0]); 20 | var joinValues = _.slice(arguments, 1, arguments.length-1); 21 | var options = arguments[arguments.length-1]; 22 | 23 | joinValues = _.map(joinValues, function(v) { return cValue(v) }); 24 | 25 | var list; 26 | if (joinValues.length === 1) { 27 | list = joinValues[0]; 28 | } 29 | else { 30 | list = '['+joinValues.join(",")+']'; 31 | } 32 | 33 | options = _.merge({hash: {}}, options); 34 | 35 | return '{"Fn::Select": ['+index+','+list+']}'; 36 | 37 | }; 38 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/functions/fnSplit.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | var cValue = require("../cValue"); 3 | 4 | /** 5 | * Fn::Split definition 6 | * @example 7 | * {{fnSplit ":" "split:me"}} 8 | * @function fnSplit 9 | * @memberof "IntrinsicFunctions" 10 | * @type {function} 11 | * @param {string} delimiter - A string value that determines where the source string is divided 12 | * @param {string} str - A string 13 | * @param {Object} options - options passed by handlebars 14 | * @returns {string} 15 | */ 16 | module.exports = function fnSplit(delimiter,str) { 17 | 18 | return '{"Fn::Split": ['+cValue(delimiter)+','+cValue(str)+']}'; 19 | 20 | }; 21 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/functions/fnSub.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | var cValue = require("../cValue"); 3 | 4 | /** 5 | * Fn::Sub definition 6 | * @example 7 | * {{fnSub "The current region is ${AWS::Region"}} 8 | * @example 9 | * {{fnSub "Use this URL ${Url}" Url=(partial "buildUrl") }} 10 | * @function fnSub 11 | * @memberof "IntrinsicFunctions" 12 | * @type {function} 13 | * @param {string} str - String with replacement variables defined 14 | * @param {Object} options - options passed by handlebars 15 | * @param {Object} options.hash - all named parameters will be used for the variableMap 16 | * @returns {string} 17 | */ 18 | module.exports = function fnSub() { 19 | var str = ""; 20 | var options = arguments[arguments.length-1]; 21 | var variableMap = _.mapValues(options.hash, function(v) { 22 | var r = cValue(v); 23 | if (_.isString(r) && r.charAt(0) === '"') { 24 | return v; 25 | } 26 | return JSON.parse(r); 27 | }); 28 | 29 | if (options.fn) { 30 | str = options.fn(this); 31 | } 32 | else { 33 | str = arguments[0]; 34 | } 35 | 36 | return '{"Fn::Sub": ['+cValue(str)+','+JSON.stringify(variableMap)+']}'; 37 | 38 | }; 39 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/functions/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Helpers that will create AWS Intrinsic Functions 3 | * @namespace "IntrinsicFunctions" 4 | */ 5 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/functions/ref.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | var scopeId = require("../scopeId"); 3 | 4 | /** 5 | * Ref definition 6 | * @example 7 | * {{ref "Parameter1"}} 8 | * @example 9 | * {{ref "Parameter2" scope=false}} 10 | * @function ref 11 | * @memberof "IntrinsicFunctions" 12 | * @type {function} 13 | * @param {string} logicalId - The logicalId to reference 14 | * @param {Object} options - passed by handlebars 15 | * @param {Object} [options.hash] - Named parameters used for ref options 16 | * @param {Boolean} [options.hash.scope=true] - Whether to scope the logicalId or not 17 | * @return {String} 18 | */ 19 | module.exports = function ref(str,options) { 20 | options = _.merge({hash: {}}, options); 21 | if (options.hash.scope !== false && !(str.indexOf("AWS::") == 0)) { 22 | str = scopeId.apply(this,[str]); 23 | } 24 | return ['{"Ref": "',str,'"}'].join(''); 25 | }; 26 | 27 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/helper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ``` 3 | * - particles 4 | * |- helpers 5 | * |- particle_name 6 | * ``` 7 | * 8 | * @example 9 | * {{helper "particle_name"}} 10 | * @example 11 | * {{helper "particle_name" foo="bar"}} 12 | * @example 13 | * {{helper "module:" 'particle_name'}} 14 | * @example 15 | * {{!-- to load modules with format `particles-NAME` --}} 16 | * {{helper "m:" "particle_name"}} 17 | * @function helper 18 | * @memberof ParticleHelpers 19 | * @param {string} [module] - module to load with either `module:` or `m:` 20 | * @param {string} path - Path to the helper, excluding the `.js` extension 21 | * @param {...kv} [options] - Key/Value pairs to pass to the particle helper 22 | * @returns {*} - The output from the particle helper 23 | * 24 | */ 25 | 26 | var _ = require("lodash"); 27 | 28 | var helper = function helper(cModule,pPath,hArgs,hOpts,cOpts) { 29 | 30 | var particle = cOpts.particleLoader.loadParticle('helper',cModule,pPath,{parentFile: hOpts.data._file}); 31 | var helperFunc = require(particle.path); 32 | 33 | return helperFunc.apply( 34 | this, 35 | _.flatten( 36 | [ 37 | hArgs, 38 | _.merge(hOpts,{handlebars: cOpts.handlebars}) 39 | ] 40 | ) 41 | ); 42 | }; 43 | 44 | module.exports.NAME = 'helper'; 45 | module.exports.helper = helper; 46 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Condensation specific helpers for common tasks 3 | * @namespace TemplateHelpers 4 | */ 5 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/layout.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Start a layout 3 | * @example 4 | * --- 5 | * things: 6 | * - 7 | * name: subnet1 8 | * cidr: "10.0.0.0/24" 9 | * - 10 | * name subnet2 11 | * cidr: "10.0.1.0/24" 12 | * --- 13 | * 14 | * {{#layout templateDescription="condensation rocks!"}} 15 | * {{parameter 'my_parameter' logicalId="MyParameter"}} 16 | * {{condition 'my_condition' logicalId="MyCondition"}} 17 | * 18 | * {{! helpers can occur in any order, allowing you to group related section parts together }} 19 | * 20 | * {{#each things}} 21 | * {{parameter 'repeate_me' logicalId="RepeateMe" logicalIdSuffix=@index}} 22 | * {{condition 'repeate_me' logicalId="RepeateMeCond" logicalIdSuffix=@index}} 23 | * {{resource 'repeate_me' logicalId="RepeateMeResource" logicalIdSuffix=@index}} 24 | * {{output 'repeate_me' logicalId="RepeateMeOutput" logicalIdSuffix=@index}} 25 | * {{/each}} 26 | * {{/layout}} 27 | * @function layout 28 | * @memberof TemplateHelpers 29 | * @param {Object} [options] - options object from Handlebars 30 | * @param {string} [options.AWSTemplateFormatVersion=2010-09-09] - AWS Format Version 31 | * @param {string} [options.TemplateDescription] - Description for the template 32 | * @param {string} [options.Transform] - AWS Transform type for the template 33 | * @returns {string} - CloudFormation Template 34 | * 35 | */ 36 | 37 | var _ = require('lodash'); 38 | var sections = require('./sections'); 39 | var VError = require('verror'); 40 | 41 | var helper = function layout(cModule,pPath,hArgs,hOpts,cOpts) { 42 | 43 | var engine = cOpts.handlebars; 44 | var data = engine.createFrame(hOpts.data || {}); 45 | data._layoutContentPusher = {}; 46 | 47 | var sectionMap = {}; 48 | _.each(_.values(sections), function(v) { 49 | if (v.SECTION_NAME) { 50 | var s = sectionMap[v.SECTION_NAME] = {}; 51 | s.items = []; 52 | data._layoutContentPusher[v.PARTICLE_NAME] = s.items.push.bind(s.items); 53 | } 54 | }); 55 | 56 | var compileOpts = _.merge(hOpts.data.root,this,hOpts.hash); 57 | try { 58 | hOpts.fn(compileOpts,{data:data}); 59 | } 60 | catch(e) { 61 | var ve = new VError(e,"Layout Parse Error in file %s",hOpts.data._file.path); 62 | throw(ve.message); 63 | } 64 | 65 | var template = {}; 66 | template.AWSTemplateFormatVersion = hOpts.hash.AWSTemplateFormatVersion || "2010-09-09"; 67 | 68 | if (compileOpts.TemplateDescription) { 69 | template.Description = compileOpts.TemplateDescription + ""; 70 | } 71 | 72 | if (compileOpts.Transform) { 73 | template.Transform = compileOpts.Transform + ""; 74 | } 75 | 76 | _.each(_.toPairs(sectionMap), function(kv) { 77 | if (kv[1].items.length) { 78 | try { 79 | template[kv[0]] = JSON.parse('{'+kv[1].items.join(',')+'}'); 80 | } 81 | catch(e) { 82 | var ve = new VError(e,"Section Parse Error: %s in file %s\n%s",kv[0],hOpts.data._file.path,kv[1].items.join(',')); 83 | throw(ve.message); 84 | } 85 | } 86 | }); 87 | 88 | return JSON.stringify(template); 89 | }; 90 | 91 | module.exports.NAME = 'layout'; 92 | module.exports.helper = helper; 93 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/objectify.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | var cValue = require("./cValue"); 3 | 4 | /** 5 | * Return options as a stringified object 6 | * @function objectify 7 | * @memberof TemplateHelpers 8 | * @example 9 | * {{objectify Param1="Value1" Param2=(ref "AWS::Region")}} 10 | * @param {...kv} [options] - Key/Value pairs to pass to the particle helper 11 | * @returns {String} - When parsed will be an object 12 | * 13 | */ 14 | module.exports= function objectify () { 15 | var options = arguments[arguments.length-1]; 16 | 17 | var hash = options.hash; 18 | 19 | var propertiesBlock = []; 20 | 21 | _.each(_.toPairs(hash), function(kv) { 22 | if (!_.isNil(hash[kv[0]])) { 23 | 24 | // cValue was written to return strings so that it is also compatible 25 | // as a direct call from a template. We need to account for that here. 26 | var property = ['"',kv[0],'":',cValue(hash[kv[0]],options)].join(""); 27 | 28 | propertiesBlock.push(property); 29 | } 30 | }); 31 | 32 | var propertiesBlock = propertiesBlock.join(","); 33 | return ["{",propertiesBlock,"}"].join(""); 34 | }; 35 | 36 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/partial.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ``` 3 | * - particles 4 | * |- partials 5 | * |- particle_name 6 | * ``` 7 | * 8 | * @example 9 | * {{partial "particle_name"}} 10 | * @example 11 | * {{partial "particle_name" foo="bar"}} 12 | * @example 13 | * {{partial "module:" 'particle_name'}} 14 | * @example 15 | * {{!-- to load modules with format `particles-NAME` --}} 16 | * {{partial "m:" "particle_name"}} 17 | * @function partial 18 | * @memberof ParticleHelpers 19 | * @param {string} [module] - module to load with either `module:` or `m:` 20 | * @param {string} path - Path to the partial (file extensions optional) 21 | * @param {...kv} [options] - Key/Value pairs to pass to the particle partial 22 | * @returns {string} 23 | * 24 | */ 25 | 26 | var File = require('vinyl'); 27 | var _ = require('lodash'); 28 | var fs = require('fs'); 29 | var matter = require('gray-matter'); 30 | 31 | var helper = function partial(cModule,pPath,hArgs,hOpts,cOpts) { 32 | 33 | var engine = cOpts.handlebars; 34 | var particle = cOpts.particleLoader.loadParticle('partial',cModule,pPath,{parentFile: hOpts.data._file}); 35 | var file = new File({path: particle.path}); 36 | var m = matter(fs.readFileSync(particle.fsPath,{encoding:'utf8'})); 37 | m.content = m.content.replace(/\n$/,''); 38 | 39 | if (!engine.partials[particle.path]) { 40 | engine.registerPartial(particle.path,m.content); 41 | } 42 | 43 | var fn = engine.compile(m.content,{noEscape:true}); 44 | var data = cOpts.handlebars.createFrame(hOpts.data || {}); 45 | var extended = data._partialExtended || false; 46 | data._file = file; 47 | data._partialExtended = true; 48 | 49 | var templateContent = ''; 50 | if (extended === true) { 51 | // If extending another partial merge options in reverse 52 | templateContent = new engine.SafeString(fn(_.merge(m.data,hOpts.hash,this),{data:data})); 53 | } 54 | else { 55 | templateContent = new engine.SafeString(fn(_.merge(m.data,this,hOpts.hash),{data:data})); 56 | } 57 | return templateContent.string; 58 | }; 59 | 60 | module.exports.name = 'partial'; 61 | module.exports.helper = helper; 62 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/requireAssets.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Include a glob of assets 3 | * 4 | * Only needed for assets that are not directly referenced by another particle 5 | * @example 6 | * {{requireAssets "all_of_these/**"}} 7 | * @example 8 | * {{requireAssets "module:" 'all_from_module/**'}} 9 | * @example 10 | * {{!-- to load modules with format `particles-NAME` --}} 11 | * {{requireAssets "m:" "all_from_module/**"}} 12 | * @function requireAssets 13 | * @memberof TemplateHelpers 14 | * @param {string} globPath - Glob patter of assets to package with the project 15 | * @returns {string} 16 | * 17 | */ 18 | var helper = function (cModule,pPath,hArgs,hOpts,cOpts) { 19 | 20 | cOpts.particleLoader.loadParticle('asset',cModule,pPath,{parentFile: hOpts.data._file}); 21 | 22 | return ''; 23 | }; 24 | 25 | module.exports.NAME = 'requireAssets'; 26 | module.exports.helper = helper; 27 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/scopeId.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Used within sets to add the correct logicalIdPrefix and/or logicalIdSuffix to a logicalId 3 | * @example 4 | * {{scopeId "LogicalId"}} 5 | * @example 6 | * {{ref (scopeId "LogicalId")}} 7 | * 8 | * @function scopeId 9 | * @memberof TemplateHelpers 10 | * @param {string} - The logicalId 11 | * @return {string} - The logical with the correct prefix and suffix for the current scope 12 | */ 13 | 14 | module.exports = function scopeId(str,options) { 15 | var scopeStr = [this.logicalIdPrefix,str,this.logicalIdSuffix].join(''); 16 | return scopeStr; 17 | }; 18 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/sections/_buildHelper.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var fs = require('fs'); 3 | var File = require('vinyl'); 4 | var matter = require('gray-matter'); 5 | var VError = require('verror'); 6 | 7 | 8 | /** 9 | * The build_helper function. 10 | * Returns a helper function that is able to process a particle section 11 | * 12 | * 13 | * @method buildHelper 14 | * @param {String} particleName The name of the particle section (parameter|resource|...) 15 | * @param {Object} options A config object 16 | * @return {Function} A function that helps compile section particles 17 | */ 18 | module.exports = function buildHelper(particleName,options) { 19 | var opts = _.merge({ 20 | wrapLogicalId: true 21 | },options); 22 | 23 | /** 24 | * @param {String} cModule The condensation module being processed 25 | * @param {String} pPath The particle path 26 | * @param {Object} hArgs Handlebars arguments 27 | * @param {Object} hOpts Handlebars options 28 | * @param {Object} cOpts Condensation options 29 | * @return {String} Finalized content for the particle section 30 | */ 31 | var helper = function(cModule,pPath,hArgs,hOpts,cOpts) { 32 | 33 | // If this particle is extended by another and both have content blocks, merge them 34 | // This variable holds the extended output to be merged. 35 | var deepMerge = {}; 36 | 37 | var engine = cOpts.handlebars; 38 | var data = cOpts.handlebars.createFrame(hOpts.data || {}); 39 | 40 | var wrapLogicalId = _.isUndefined(data.wrapLogicalId) ? opts.wrapLogicalId : data.wrapLogicalId; 41 | var extended = data.extended || false; 42 | var callerFile = data._file; 43 | 44 | // Once a section has been started any subsequent calls should not wrap a logical id. 45 | // Allows a particle to 'extend' another particle 46 | data.extended = true; 47 | data.wrapLogicalId = false; 48 | 49 | var mergeParticleTemplate = '{}'; 50 | var templateContent = '{}'; 51 | 52 | if (hOpts.fn) { 53 | templateContent = processTemplate.call(this,hOpts.fn,hOpts,cOpts,{data:{}},data,extended,particleName); 54 | } 55 | 56 | if (pPath) { 57 | var particle = cOpts.particleLoader.loadParticle(particleName,cModule,pPath,{parentFile: hOpts.data._file}); 58 | var file = new File({path: particle.path}); 59 | var m = matter(fs.readFileSync(particle.fsPath,{encoding:'utf8'})); 60 | 61 | var pData = _.cloneDeep(data); 62 | pData._file = file; 63 | 64 | var fn = engine.compile(m.content,{noEscape:true,data:pData}); 65 | mergeParticleTemplate = processTemplate.call(this,fn,hOpts,cOpts,m,pData,true,particleName); 66 | } 67 | 68 | templateContent = JSON.stringify(_.merge(JSON.parse(mergeParticleTemplate),JSON.parse(templateContent))); 69 | 70 | var finalContent = templateContent; 71 | if (wrapLogicalId === true) { 72 | 73 | var logicalId = hOpts.hash.logicalId; 74 | 75 | if (hOpts.hash.scope !== false) { 76 | var logicalIdPrefix = [hOpts.hash.logicalIdPrefix,this.logicalIdPrefix].join(''); 77 | var logicalIdSuffix = [this.logicalIdSuffix,hOpts.hash.logicalIdSuffix].join(''); 78 | logicalId = [logicalIdPrefix,logicalId,logicalIdSuffix].join(''); 79 | } 80 | 81 | finalContent = ['"',logicalId,'":',finalContent].join(''); 82 | 83 | if (!logicalId) { 84 | var message = [ 85 | "LogicalId undefined for ", 86 | particleName, 87 | " ", 88 | (cModule ? cModule + ":" : ""), 89 | pPath, 90 | " called from ", 91 | callerFile.path, 92 | "\n\n", 93 | finalContent 94 | ].join(''); 95 | throw new Error(message); 96 | } 97 | } 98 | 99 | /* Check to see if this is being added to a layout. 100 | * If so, add to the respective section. 101 | * 102 | * If not, return the string 103 | */ 104 | if (data._layoutContentPusher && data._layoutContentPusher[particleName] && !extended) { 105 | data._layoutContentPusher[particleName](finalContent); 106 | } 107 | else { 108 | return finalContent; 109 | } 110 | }; 111 | 112 | return helper; 113 | }; 114 | 115 | var processTemplate = function(fn,hOpts,cOpts,m,data,extended,particleName) { 116 | 117 | var engine = cOpts.handlebars; 118 | var templateContent = ''; 119 | 120 | try { 121 | if (extended) { 122 | // If extending another particle of the same type merge `hOpts.hash` before `this` 123 | templateContent = new engine.SafeString(fn(_.merge(m.data,hOpts.hash,this),{data:data})); 124 | } 125 | else { 126 | templateContent = new engine.SafeString(fn(_.merge(m.data,this,hOpts.hash),{data:data})); 127 | } 128 | } 129 | catch(e) { 130 | var ve = new VError(e,"Template Process Error"); 131 | if (VError.findCauseByName(ve,"SyntaxError")) { 132 | throw(ve.message); 133 | } 134 | ve = new VError(ve,'%s\n\nParticle "%s included by %s"',m.orig,data._file.path,data._parent._file.path); 135 | throw(ve.message); 136 | } 137 | 138 | /* For backwards compatiblity with older partials check to see if the 139 | * content is a valid JSON object. If it is, don't wrap the content in braces 140 | * 141 | */ 142 | try { 143 | var testContent = JSON.parse(templateContent); 144 | if (!_.isObject(testContent)) { 145 | throw new VError("Must be an Object"); 146 | } 147 | } 148 | catch(e) { 149 | /* If the try block does not work, no need to catch the error. 150 | * Wrap the string in braces and move on to the next try 151 | */ 152 | templateContent = '{'+templateContent+'}'; 153 | } 154 | 155 | /* Ensure the manufactured string is JSON compliant. 156 | * If not, trow an error for this inclusion. 157 | */ 158 | try { 159 | JSON.parse(templateContent); 160 | } 161 | catch(e) { 162 | var ve = new VError(e,'Section Parse Error: particle "%s" in file %s\n%s\n\n',particleName,hOpts.data._file.path,templateContent); 163 | throw(ve); 164 | } 165 | return templateContent; 166 | }; 167 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/sections/condition.js: -------------------------------------------------------------------------------- 1 | var buildHelper = require('./_buildHelper'); 2 | 3 | module.exports.NAME = 'condition'; 4 | module.exports.PARTICLE_NAME = 'condition'; 5 | module.exports.PARTICLE_DIR = 'conditions'; 6 | module.exports.SECTION_NAME = 'Conditions'; 7 | 8 | /** 9 | * ``` 10 | * - particles 11 | * |- conditions 12 | * |- particle_name 13 | * ``` 14 | * 15 | * @example 16 | * {{condition "particle_name"}} 17 | * @example 18 | * {{condition "particle_name" foo="bar"}} 19 | * @example 20 | * {{condition "module:" 'particle_name'}} 21 | * @example 22 | * {{!-- to load modules with format `particles-NAME` --}} 23 | * {{condition "m:" "particle_name"}} 24 | * @function condition 25 | * @memberof ParticleHelpers 26 | * @param {string} [module] - module to load with either `module:` or `m:` 27 | * @param {string} path - Path to the particle (file extensions optional) 28 | * @param {...kv} [options] - Key/Value pairs to pass to the particle 29 | * @returns {string} 30 | */ 31 | module.exports.helper = buildHelper('condition'); 32 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/sections/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Particle Helpers load particles from their respective paths within the project. 3 | * 4 | * ``` 5 | * - particles 6 | * |-- conditions 7 | * |-- helpers 8 | * |-- mappings 9 | * |-- metadata 10 | * |-- outputs 11 | * |-- parameters 12 | * |-- partials 13 | * |-- resources 14 | * |-- sets 15 | * ``` 16 | * 17 | * All helpers follow the same pattern 18 | * 19 | * `{{ [module:] '' [OPTIONS...]}}` 20 | * 21 | * When loading a particle from a module that starts with `particles-` the short form 22 | * can also be used, where is the name of the module without `particles-` 23 | * 24 | * `{{ [m:] '' [OPTIONS...]}}` 25 | * 26 | * @namespace ParticleHelpers 27 | * 28 | */ 29 | 30 | module.exports = { 31 | condition: require('./condition'), 32 | mapping: require('./mapping'), 33 | metadata: require('./metadata'), 34 | output: require('./output'), 35 | parameter: require('./parameter'), 36 | resource: require('./resource') 37 | }; 38 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/sections/mapping.js: -------------------------------------------------------------------------------- 1 | var buildHelper = require('./_buildHelper'); 2 | 3 | module.exports.NAME = 'mapping'; 4 | module.exports.PARTICLE_NAME = 'mapping'; 5 | module.exports.PARTICLE_DIR = 'mappings'; 6 | module.exports.SECTION_NAME = 'Mappings'; 7 | 8 | /** 9 | * ``` 10 | * - particles 11 | * |- mappings 12 | * |- particle_name 13 | * ``` 14 | * 15 | * @example 16 | * {{mapping "particle_name"}} 17 | * @example 18 | * {{mapping "particle_name" foo="bar"}} 19 | * @example 20 | * {{mapping "module:" 'particle_name'}} 21 | * @example 22 | * {{!-- to load modules with format `particles-NAME` --}} 23 | * {{mapping "m:" "particle_name"}} 24 | * @memberof ParticleHelpers 25 | * @function mapping 26 | * @param {string} [module] - module to load with either `module:` or `m:` 27 | * @param {string} path - Path to the particle (file extensions optional) 28 | * @param {...kv} [options] - Key/Value pairs to pass to the particle 29 | * @returns {string} 30 | */ 31 | module.exports.helper = buildHelper('mapping'); 32 | 33 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/sections/metadata.js: -------------------------------------------------------------------------------- 1 | var buildHelper = require('./_buildHelper'); 2 | 3 | module.exports.NAME = 'metadata'; 4 | module.exports.PARTICLE_NAME = 'metadata'; 5 | module.exports.PARTICLE_DIR = 'metadata'; 6 | module.exports.SECTION_NAME = 'Metadata'; 7 | 8 | /** 9 | * ``` 10 | * - particles 11 | * |- metadata 12 | * |- particle_name 13 | * ``` 14 | * 15 | * @example 16 | * {{metadata "particle_name"}} 17 | * @example 18 | * {{metadata "particle_name" foo="bar"}} 19 | * @example 20 | * {{metadata "module:" 'particle_name'}} 21 | * @example 22 | * {{!-- to load modules with format `particles-NAME` --}} 23 | * {{metadata "m:" "particle_name"}} 24 | * @memberof ParticleHelpers 25 | * @function metadata 26 | * @param {string} [module] - module to load with either `module:` or `m:` 27 | * @param {string} path - Path to the particle (file extensions optional) 28 | * @param {...kv} [options] - Key/Value pairs to pass to the particle 29 | * @returns {string} 30 | */ 31 | module.exports.helper = buildHelper('metadata'); 32 | 33 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/sections/output.js: -------------------------------------------------------------------------------- 1 | var buildHelper = require('./_buildHelper'); 2 | 3 | module.exports.NAME = 'output'; 4 | module.exports.PARTICLE_NAME = 'output'; 5 | module.exports.PARTICLE_DIR = 'outputs'; 6 | module.exports.SECTION_NAME = 'Outputs'; 7 | 8 | /** 9 | * ``` 10 | * - particles 11 | * |- outputs 12 | * |- particle_name 13 | * ``` 14 | * 15 | * @example 16 | * {{output "particle_name"}} 17 | * @example 18 | * {{output "particle_name" foo="bar"}} 19 | * @example 20 | * {{output "module:" 'particle_name'}} 21 | * @example 22 | * {{!-- to load modules with format `particles-NAME` --}} 23 | * {{output "m:" "particle_name"}} 24 | * @memberof ParticleHelpers 25 | * @function output 26 | * @param {string} [module] - module to load with either `module:` or `m:` 27 | * @param {string} path - Path to the particle (file extensions optional) 28 | * @param {...kv} [options] - Key/Value pairs to pass to the particle 29 | * @returns {string} 30 | */ 31 | module.exports.helper = buildHelper('output'); 32 | 33 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/sections/parameter.js: -------------------------------------------------------------------------------- 1 | var buildHelper = require('./_buildHelper'); 2 | 3 | module.exports.NAME = 'parameter'; 4 | module.exports.PARTICLE_NAME = 'parameter'; 5 | module.exports.PARTICLE_DIR = 'parameters'; 6 | module.exports.SECTION_NAME = 'Parameters'; 7 | 8 | /** 9 | * ``` 10 | * - particles 11 | * |- parameters 12 | * |- particle_name 13 | * ``` 14 | * 15 | * @example 16 | * {{parameter "particle_name"}} 17 | * @example 18 | * {{parameter "particle_name" foo="bar"}} 19 | * @example 20 | * {{parameter "module:" 'particle_name'}} 21 | * @example 22 | * {{!-- to load modules with format `particles-NAME` --}} 23 | * {{parameter "m:" "particle_name"}} 24 | * @memberof ParticleHelpers 25 | * @function parameter 26 | * @param {string} [module] - module to load with either `module:` or `m:` 27 | * @param {string} path - Path to the particle (file extensions optional) 28 | * @param {...kv} [options] - Key/Value pairs to pass to the particle 29 | * @returns {string} 30 | */ 31 | module.exports.helper = buildHelper('parameter'); 32 | 33 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/sections/resource.js: -------------------------------------------------------------------------------- 1 | var buildHelper = require('./_buildHelper'); 2 | 3 | module.exports.NAME = 'resource'; 4 | module.exports.PARTICLE_NAME = 'resource'; 5 | module.exports.PARTICLE_DIR = 'resources'; 6 | module.exports.SECTION_NAME = 'Resources'; 7 | 8 | /** 9 | * ``` 10 | * - particles 11 | * |- resources 12 | * |- particle_name 13 | * ``` 14 | * 15 | * @example 16 | * {{resource "particle_name"}} 17 | * @example 18 | * {{resource "particle_name" foo="bar"}} 19 | * @example 20 | * {{resource "module:" 'particle_name'}} 21 | * @example 22 | * {{!-- to load modules with format `particles-NAME` --}} 23 | * {{resource "m:" "particle_name"}} 24 | * @memberof ParticleHelpers 25 | * @function resource 26 | * @param {string} [module] - module to load with either `module:` or `m:` 27 | * @param {string} path - Path to the particle (file extensions optional) 28 | * @param {...kv} [options] - Key/Value pairs to pass to the particle 29 | * @returns {string} 30 | */ 31 | module.exports.helper = buildHelper('resource'); 32 | 33 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/set.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ``` 3 | * - particles 4 | * |- sets 5 | * |- particle_name 6 | * ``` 7 | * 8 | * @example 9 | * {{set "particle_name"}} 10 | * @example 11 | * {{set "particle_name" foo="bar"}} 12 | * @example 13 | * {{set "module:" 'particle_name'}} 14 | * @example 15 | * {{!-- to load modules with format `particles-NAME` --}} 16 | * {{set "m:" "particle_name"}} 17 | * @function set 18 | * @memberof ParticleHelpers 19 | * @param {string} [module] - module to load with either `module:` or `m:` 20 | * @param {string} path - Path to the set (file extensions optional) 21 | * @param {Object} [options] - options for the set 22 | * @param {string} [options.logicalIdPrefix] - Add a prefix to the set's scope 23 | * @param {string} [options.logicalIdSuffix] - Add a suffix to the set's scope 24 | * @returns {string} 25 | * 26 | */ 27 | 28 | var File = require('vinyl'); 29 | var _ = require('lodash'); 30 | var fs = require('fs'); 31 | var matter = require('gray-matter'); 32 | 33 | // A set and a partial are nearly the same and should be looked at 34 | // to shsre more code. At this time the following is almost an exact copy of partial 35 | var helper = function(cModule,pPath,hArgs,hOpts,cOpts) { 36 | 37 | var engine = cOpts.handlebars; 38 | var particle = cOpts.particleLoader.loadParticle('set',cModule,pPath,{parentFile: hOpts.data._file}); 39 | var file = new File({path: particle.path}); 40 | var m = matter(fs.readFileSync(particle.fsPath,{encoding:'utf8'})); 41 | 42 | var fn = engine.compile(m.content,{noEscape:true}); 43 | var data = cOpts.handlebars.createFrame(hOpts.data || {}); 44 | var extended = data._setExtended || false; 45 | data._file = file; 46 | data._setExtended = true; 47 | 48 | var templateContent = ''; 49 | if (extended === true) { 50 | var self = cOpts.handlebars.createFrame(this); 51 | 52 | self.logicalIdPrefix = [self.logicalIdPrefix,(hOpts.hash.logicalIdPrefix || m.data.logicalIdPrefix)].join(''); 53 | 54 | // If extending another set this has priority 55 | templateContent = new engine.SafeString(fn(_.merge(m.data,hOpts.hash,self),{data:data})); 56 | } 57 | else { 58 | templateContent = new engine.SafeString(fn(_.merge(m.data,this,hOpts.hash),{data:data})); 59 | } 60 | return templateContent; 61 | }; 62 | 63 | module.exports.name = 'set'; 64 | module.exports.helper = helper; 65 | -------------------------------------------------------------------------------- /lib/condensation/template-helpers/templateS3Url.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Generate an S3 URL for another template in the project 3 | * @example 4 | * {{templateUrl "another.template.json"}} 5 | * @example 6 | * {{templateUrl "module:" 'another.template.json'}} 7 | * @example 8 | * {{!-- to load modules with format `particles-NAME` --}} 9 | * {{set "m:" "another.template.json"}} 10 | * @function templateUrl 11 | * @memberof TemplateHelpers 12 | * @param {string} path - Path to the template (file extensions optional) 13 | * @returns {string} 14 | * 15 | */ 16 | 17 | var url = require('url'); 18 | 19 | var helper = function (cModule,pPath,hArgs,hOpts,cOpts) { 20 | 21 | var particle = cOpts.particleLoader.loadParticle('template',cModule,pPath,{parentFile: hOpts.data._file}); 22 | 23 | return [url.format(hOpts.data.root.s3.condensationUrl),particle.urlPath].join('/'); 24 | }; 25 | 26 | module.exports.NAME = 'templateS3Url'; 27 | module.exports.helper = helper; 28 | -------------------------------------------------------------------------------- /lib/condensation/util.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | var async = require("async"); 3 | var path = require("path"); 4 | var frontLoader = require("./loaders/front-loader"); 5 | var matter = require("gray-matter"); 6 | 7 | /** 8 | * Generate a TaskNme based on condensation configuration 9 | * @this Condensation 10 | * @param {Object} options - All options 11 | * @param {string} [options.separator=":"] - The separator to use when joining the parts of the name 12 | * @returns {genTaskNameFunc~taskNameFunc} - Function that generates task names 13 | */ 14 | exports.genTaskNameFunc = function genTaskNameFunc(options) { 15 | options = _.merge({ 16 | separator: ':' 17 | },options); 18 | 19 | 20 | /** 21 | * Make a task name 22 | * @param {...string} str - Parts of the task name that will be joined together 23 | * @returns {string} - The full task name 24 | */ 25 | var taskNameFunc = function taskNameFunc() { 26 | var validParts = _.dropWhile(_.flatten([this.prefix,arguments]),function(item) { 27 | return !item; 28 | }); 29 | return validParts.join(this.separator); 30 | }; 31 | 32 | return taskNameFunc.bind(options); 33 | }; 34 | 35 | /** 36 | * Generates a distribution path 37 | * @this Condensation 38 | * @param {Object} options - All options 39 | * @param {string} [options.root="dist"] - The root distribution name 40 | * @param {string} [options.s3prefix=""] - Prefix to add to all s3 paths 41 | * @param {string} options.id - The unique ID for this distribution 42 | * @returns {String} - The distribution path 43 | */ 44 | exports.genDistPath = function genDistPath(options) { 45 | var opts = _.merge({ 46 | root: 'dist', 47 | s3prefix: '', 48 | id: null 49 | },options); 50 | 51 | return( 52 | path.join.apply( 53 | null, 54 | _.flatten([ 55 | opts.root, 56 | opts.id, 57 | opts.s3prefix 58 | ]) 59 | ) 60 | ); 61 | 62 | }; 63 | 64 | /** 65 | * 66 | */ 67 | exports.processFrontMatter = function processFrontMatter(file, templateData, cb) { 68 | var self = this; 69 | 70 | var m = matter(file.contents.toString()); 71 | 72 | async.mapValues( 73 | (m.data.frontload || {}), 74 | function(value, key, cb) { 75 | var moduleConfig = _.merge({},value); 76 | moduleConfig._file = file; 77 | frontLoader.call(self, moduleConfig, templateData, cb); 78 | }, 79 | function(err, result) { 80 | _.merge(m.data, result); 81 | cb(err, m); 82 | } 83 | ); 84 | 85 | }; 86 | -------------------------------------------------------------------------------- /lib/gulp-plugins/gulp-cf-validate.js: -------------------------------------------------------------------------------- 1 | var AWS = require('aws-sdk'), 2 | gutil = require('gulp-util'), 3 | through = require('through2'), 4 | backoff = require('backoff'), 5 | fs = require('fs-extra'), 6 | path = require('path'), 7 | PluginError = gutil.PluginError; 8 | 9 | 10 | const PLUGIN_NAME = 'gulp-cf-validate'; 11 | 12 | function validator(opts) { 13 | var cloudformation = new AWS.CloudFormation(opts); 14 | // creating a stream through which each file will pass 15 | var stream = through.obj(function(file, enc, cb) { 16 | var retry = backoff.exponential(); 17 | retry.failAfter(20); 18 | 19 | retry.on('ready', function(number,delay) { 20 | if (file.contents) { 21 | cloudformation.validateTemplate({ 22 | TemplateBody: file.contents.toString() 23 | },function(err,data) { 24 | if (err && err.code === "Throttling") { 25 | //console.log(new PluginError(PLUGIN_NAME,"RETRY VALIDATE (throttled) count:" + number + " delay:" + delay + " file:" + file.path)); 26 | retry.backoff(); 27 | } 28 | else if (err) { 29 | console.log(new PluginError(PLUGIN_NAME, file.path)); 30 | fs.outputFile(path.join('condensation_errors',file.path),file.contents,function(ofErr) { 31 | if (ofErr) { 32 | console.log(new PluginError(PLUGIN_NAME, file.contents)); 33 | } 34 | cb(err,file); 35 | }); 36 | } 37 | else { 38 | cb(err,file); 39 | } 40 | }); 41 | } 42 | else { 43 | cb(null,file); 44 | } 45 | }); 46 | 47 | retry.backoff(); 48 | 49 | 50 | }); 51 | 52 | // returning the file stream 53 | return stream; 54 | } 55 | 56 | 57 | module.exports = validator; 58 | -------------------------------------------------------------------------------- /lib/handlebars-helpers/concat.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | 3 | /** 4 | * Concatenates two or more strings 5 | * @function concat 6 | * @memberof HandlebarsHelpers 7 | * @example 8 | * {{concat "string1" "string2"}} 9 | * @example 10 | * {{concat "string1" "string2" separator="-"}} 11 | * @param {...string} string - two or more strings to concatenate 12 | * @param {Object} options - passed by handlebars 13 | * @param {Object} options.hash - named key/value pairs 14 | * @param {string} options.hash.separator - string to separate each value 15 | * @returns {String} - One concatenated string 16 | * 17 | */ 18 | module.exports = function concat() { 19 | var args = arguments; 20 | var options = args[args.length-1]; 21 | options = _.merge({hash:{separator:""}},options); 22 | 23 | return _.slice(args,0,args.length-1).join(options.hash.separator); 24 | }; 25 | -------------------------------------------------------------------------------- /lib/handlebars-helpers/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Generic helpers that work with any handlebars project 3 | * @namespace HandlebarsHelpers 4 | */ 5 | -------------------------------------------------------------------------------- /lib/handlebars-helpers/stringify.js: -------------------------------------------------------------------------------- 1 | /** 2 | * JSON.stringify a string or block 3 | * @function stringify 4 | * @memberof HandlebarsHelpers 5 | * @example 6 | * {{stringify "a !string for {json} /end"}} 7 | * @example 8 | * {{#stringify}} 9 | * mybash.sh -o option1 10 | * continue.sh 11 | * {{/stringify}} 12 | * @example 13 | * {{#stringify noLineBreaks}} 14 | * docker run 15 | * -e VAR1=VAL1 16 | * -e VAR2=VAL2 17 | * my/image 18 | * {{/stringify}} 19 | * @param {string=} string - String to use if block is not present 20 | * @returns {String} - JSON.stringify result of block or string 21 | * 22 | */ 23 | module.exports= function stringify (content) { 24 | var options = arguments[arguments.length-1]; 25 | if (options.fn) { 26 | content = options.fn(this); 27 | } 28 | 29 | if (options.hash.noLineBreaks) { 30 | content = content.replace(/(\r\n|\n|\r)/gm, ''); 31 | } 32 | return JSON.stringify(content) 33 | }; 34 | 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "condensation", 3 | "version": "0.6.10", 4 | "publishConfig": { 5 | "tag": "latest" 6 | }, 7 | "description": "Package, reuse and share particles for CloudFormation projects", 8 | "main": "index.js", 9 | "scripts": { 10 | "test": "mocha \"./test/**/*.test.js\" --timeout 45000", 11 | "build-docs": "node ./bin/buildDocs.js" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/sungardas/condensation.git" 16 | }, 17 | "author": "Kevin McGrath ", 18 | "dependencies": { 19 | "async": "^2.4.0", 20 | "aws-sdk": "^2.50.0", 21 | "backlog": "0.0.3", 22 | "backoff": "^2.5.0", 23 | "del": "^2.2.2", 24 | "event-stream": "^3.3.4", 25 | "fs-extra": "^3.0.1", 26 | "glob": "^7.1.1", 27 | "graceful-fs": "^4.1.11", 28 | "gray-matter": "^2.1.1", 29 | "gulp": "^3.9.1", 30 | "gulp-cached": "^1.1.1", 31 | "gulp-if": "^2.0.2", 32 | "gulp-rename": "^1.2.2", 33 | "gulp-util": "^3.0.8", 34 | "handlebars": "^4.0.8", 35 | "lodash": "^4.17.4", 36 | "merge-stream": "^1.0.1", 37 | "rimraf": "^2.6.1", 38 | "s3": "^4.4.0", 39 | "shortid": "^2.2.8", 40 | "slash": "^1.0.0", 41 | "through2": "^2.0.3", 42 | "verror": "^1.10.0", 43 | "vinyl": "^2.0.2" 44 | }, 45 | "license": "Apache-2.0", 46 | "keywords": [ 47 | "condensation", 48 | "aws", 49 | "cloudformation" 50 | ], 51 | "bugs": { 52 | "url": "https://github.com/sungardas/condensation/issues" 53 | }, 54 | "homepage": "https://github.com/sungardas/condensation", 55 | "devDependencies": { 56 | "clone": "^2.1.1", 57 | "condensation-particle-tests": "^0.5.4", 58 | "coveralls": "^2.13.1", 59 | "istanbul": "^0.4.5", 60 | "jsdoc-to-markdown": "^3.0.0", 61 | "mocha": "^3.4.1", 62 | "semver": "^5.3.0" 63 | }, 64 | "engines": { 65 | "node": ">4", 66 | "iojs": "*" 67 | }, 68 | "engineStrict": true, 69 | "engine-strict": true, 70 | "directories": { 71 | "doc": "docs", 72 | "test": "test" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /test/fixtures/projects/invalid/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "invalid", 3 | "version": "0.0.0", 4 | "description": "", 5 | "keywords": ["condensation-particles"] 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/projects/invalid/particles/cftemplates/invalid.template.hbs: -------------------------------------------------------------------------------- 1 | { 2 | {{#if neverTrue}}{{else}}/{{/if}} 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/projects/particle-builds/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "particle-builds", 3 | "version": "0.0.0", 4 | "description": "", 5 | "keywords": ["condensation-particles"] 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/projects/particle-builds/particles/mappings/ami.hbs: -------------------------------------------------------------------------------- 1 | "ap-northeast-1":{"ami":"ami-ab1df7ab"} 2 | -------------------------------------------------------------------------------- /test/fixtures/projects/particle-builds/particles/mappings/ami_full_object.hbs: -------------------------------------------------------------------------------- 1 | {"ap-northeast-1":{"ami":"ami-ab1df7ab"}} 2 | -------------------------------------------------------------------------------- /test/fixtures/projects/particle-builds/particles/parameters/extend_malformed.hbs: -------------------------------------------------------------------------------- 1 | {{#parameter "malformed"}} 2 | "NoEcho": "true" 3 | {{/parameter}} 4 | 5 | -------------------------------------------------------------------------------- /test/fixtures/projects/particle-builds/particles/parameters/full_object.json.hbs: -------------------------------------------------------------------------------- 1 | { 2 | "Type": "String" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/projects/particle-builds/particles/parameters/generic.hbs: -------------------------------------------------------------------------------- 1 | "Type": "{{type}}" 2 | -------------------------------------------------------------------------------- /test/fixtures/projects/particle-builds/particles/parameters/malformed.hbs: -------------------------------------------------------------------------------- 1 | { { 2 | } 3 | -------------------------------------------------------------------------------- /test/fixtures/projects/particle-builds/particles/resources/generic.hbs: -------------------------------------------------------------------------------- 1 | "Type": "{{type}}", 2 | "Properties": {} 3 | -------------------------------------------------------------------------------- /test/fixtures/projects/particle-builds/particles/sets/set1.hbs: -------------------------------------------------------------------------------- 1 | {{#layout}} 2 | {{#resource logicalId="Resource1"}} 3 | "Type": "Custom::TEST", 4 | "Properties": {} 5 | {{/resource}} 6 | 7 | {{set "set2" }} 8 | {{/layout}} 9 | -------------------------------------------------------------------------------- /test/fixtures/projects/particle-builds/particles/sets/set2.hbs: -------------------------------------------------------------------------------- 1 | --- 2 | logicalIdPrefix: "Set2" 3 | --- 4 | {{#resource logicalId="Resource2"}} 5 | "Type": "Custom::TEST", 6 | "Properties": {} 7 | {{/resource}} 8 | 9 | {{set "set3"}} 10 | -------------------------------------------------------------------------------- /test/fixtures/projects/particle-builds/particles/sets/set3.hbs: -------------------------------------------------------------------------------- 1 | --- 2 | logicalIdPrefix: "Set3" 3 | --- 4 | {{#resource logicalId="Resource3"}} 5 | "Type": "Custom:TEST", 6 | "Properties": {} 7 | {{/resource}} 8 | -------------------------------------------------------------------------------- /test/fixtures/projects/particles-common-core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "particles-common-core", 3 | "keywords": ["condensation-particles"] 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/projects/particles-common-core/particles/conditions/is_false.hbs: -------------------------------------------------------------------------------- 1 | "Fn::Equals": [ 2 | { 3 | "Ref": "{{parameterLogicalId}}" 4 | }, 5 | "false" 6 | ] 7 | 8 | -------------------------------------------------------------------------------- /test/fixtures/projects/particles-common-core/particles/conditions/is_true.hbs: -------------------------------------------------------------------------------- 1 | "Fn::Equals": [ 2 | { 3 | "Ref": "{{parameterLogicalId}}" 4 | }, 5 | "true" 6 | ] 7 | 8 | -------------------------------------------------------------------------------- /test/fixtures/projects/particles-common-core/particles/parameters/cidr_range.hbs: -------------------------------------------------------------------------------- 1 | --- 2 | description: "Valid CIDR Range. Format: x.x.x.x/x" 3 | --- 4 | {{{ 5 | partial 'parameter_base' 6 | default="10.1.0.0/16" 7 | type="String" 8 | minLength=9 9 | maxLength=18 10 | constraintDescription="must be a valid CIDR range formatted as x.x.x.x/x" 11 | allowedPattern="(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})" 12 | }}} 13 | -------------------------------------------------------------------------------- /test/fixtures/projects/particles-common-core/particles/parameters/true_false.hbs: -------------------------------------------------------------------------------- 1 | {{ 2 | partial 'parameter_base' 3 | type="String" 4 | minLength=4 5 | maxLength=5 6 | constraintDescription="[true|false]" 7 | allowedValues=["true","false"] 8 | }} 9 | -------------------------------------------------------------------------------- /test/fixtures/projects/particles-common-core/particles/partials/parameter_base.hbs: -------------------------------------------------------------------------------- 1 | "Type": "{{type}}" 2 | {{#if default}},"Default": "{{default}}"{{/if}} 3 | {{#if noEcho}},"NoEcho": "true"{{/if}} 4 | {{#if allowedValues}},"AllowedValues": {{allowedValues}}{{/if}} 5 | {{#if allowedPattern}},"AllowedPattern": "{{allowedPattern}}"{{/if}} 6 | {{#if maxLength}},"MaxLength": "{{maxLength}}"{{/if}} 7 | {{#if minLength}},"MinLength": "{{minLength}}"{{/if}} 8 | {{#if maxValue}},"MaxValue": "{{maxValue}}"{{/if}} 9 | {{#if minValue}},"MinValue": "{{minValue}}"{{/if}} 10 | {{#if description}},"Description": "{{description}}"{{/if}} 11 | {{#if constraintDescription}},"ConstraintDescription": "{{constraintDescription}}"{{/if}} 12 | -------------------------------------------------------------------------------- /test/fixtures/projects/particles-common-core/particles/sets/true_false.hbs: -------------------------------------------------------------------------------- 1 | {{parameter 'true_false' logicalId=parameterLogicalId}} 2 | {{#if conditionLogicalId}} 3 | {{condition 'is_true' logicalId=conditionLogicalId }} 4 | {{condition 'is_false' logicalId=(concat 'Not' conditionLogicalId)}} 5 | {{else}} 6 | {{condition 'is_true' logicalId=(concat parameterLogicalId 'IsTrueCondition') }} 7 | {{condition 'is_false' logicalId=(concat parameterLogicalId 'IsFalseCondition') }} 8 | {{/if}} 9 | -------------------------------------------------------------------------------- /test/fixtures/projects/particles-vpc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "particles-vpc", 3 | "keywords": ["condensation-particles"] 4 | 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/projects/particles-vpc/particles/cftemplates/subnet.template.json.hbs: -------------------------------------------------------------------------------- 1 | {{#layout TemplateDescription="A Subnet"}} 2 | {{parameter 'vpc_cidr' logicalId="SubnetCidr" description="Subnet CIDR [x.x.x.x/x]"}} 3 | {{#parameter logicalId="AvailabilityZone"}} 4 | "Default": "random", 5 | "Description": "Availability Zone", 6 | "Type": "String", 7 | "ConstraintDescription": "Must be a valid AZ for the region." 8 | {{/parameter}} 9 | {{#parameter logicalId="VpcId"}} 10 | "Description": "ID of an existing VPC to create the subnet in", 11 | "Type": "AWS::EC2::VPC::Id" 12 | {{/parameter}} 13 | {{#parameter logicalId="RouteTableId"}} 14 | "Description": "Route table that the subnet will attach to.", 15 | "Default": "default", 16 | "Type": "String", 17 | "MinLength": "1", 18 | "MaxLength": "255", 19 | "AllowedPattern": "[\\x20-\\x7E]*", 20 | "ConstraintDescription": "can contain only ASCII characters." 21 | {{/parameter}} 22 | {{#parameter logicalId="NameTag"}} 23 | "Description": "Set the name tag for all created resources.", 24 | "Default": "default", 25 | "Type": "String", 26 | "MinLength": "1", 27 | "MaxLength": "255", 28 | "AllowedPattern": "[\\x20-\\x7E]*", 29 | "ConstraintDescription": "can contain only ASCII characters." 30 | {{/parameter}} 31 | 32 | {{set 'true_false_set' parameterLogicalId="tf"}} 33 | 34 | {{#condition logicalId="condNonDefaultRouteTable"}} 35 | "Fn::Not": [ 36 | { 37 | "Fn::Equals": [ 38 | { 39 | "Ref": "RouteTableId" 40 | }, 41 | "default" 42 | ] 43 | } 44 | ] 45 | {{/condition}} 46 | {{#condition logicalId="condNonDefaultAZ"}} 47 | "Fn::Not": [ 48 | { 49 | "Fn::Equals": [ 50 | { 51 | "Ref": "AvailabilityZone" 52 | }, 53 | "random" 54 | ] 55 | } 56 | ] 57 | {{/condition}} 58 | {{#condition logicalId="condDefaultAZ"}} 59 | "Fn::Equals": [ 60 | { 61 | "Ref": "AvailabilityZone" 62 | }, 63 | "random" 64 | ] 65 | {{/condition}} 66 | 67 | {{#resource logicalId="Subnet"}} 68 | "Type": "AWS::EC2::Subnet", 69 | "Properties": { 70 | "CidrBlock": { 71 | "Ref": "SubnetCidr" 72 | }, 73 | "AvailabilityZone": { 74 | "Fn::If": [ 75 | "condNonDefaultAZ", 76 | { 77 | "Ref": "AvailabilityZone" 78 | }, 79 | { 80 | "Ref": "AWS::NoValue" 81 | } 82 | ] 83 | }, 84 | "VpcId": { 85 | "Ref": "VpcId" 86 | }, 87 | "Tags": [ 88 | { 89 | "Key": "Name", 90 | "Value": { 91 | "Ref": "NameTag" 92 | } 93 | } 94 | ] 95 | } 96 | {{/resource}} 97 | 98 | {{#resource logicalId="RouteTableAssociation"}} 99 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 100 | "Condition": "condNonDefaultRouteTable", 101 | "Properties": { 102 | "RouteTableId": { 103 | "Ref": "RouteTableId" 104 | }, 105 | "SubnetId": { 106 | "Ref": "Subnet" 107 | } 108 | } 109 | {{/resource}} 110 | 111 | {{#output logicalId="SubnetId"}} 112 | "Value": { 113 | "Ref": "Subnet" 114 | } 115 | {{/output}} 116 | 117 | {{#output logicalId="AvailabilityZone"}} 118 | "Value": { "Fn::GetAtt" : [ "Subnet", "AvailabilityZone" ] } 119 | {{/output}} 120 | 121 | {{/layout}} 122 | -------------------------------------------------------------------------------- /test/fixtures/projects/particles-vpc/particles/cftemplates/vpc.template.json.hbs: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Parameters": { 4 | {{parameter 'vpc_cidr' logicalId="VpcCidr"}}, 5 | {{parameter 'm:common-core' 'true_false' logicalId="CreateVpcSecurityGroup" default="false" description="[true|false] Create a security group assigned to the new VPC"}}, 6 | {{parameter 'm:common-core' 'true_false' logicalId="OpenVpcCommunication" default="false" description="[true|false] Open all communication within the VPC"}}, 7 | {{parameter 'm:common-core' 'true_false' logicalId="CreateInternetGateway" default="false" description="[true|false] Create an Internet Gateway for the VPC"}}, 8 | "NameTag": { 9 | "Description": "Will set the name tag on all resources created", 10 | "Type": "String" 11 | } 12 | }, 13 | "Conditions": { 14 | {{condition 'm:common-core' 'is_true' logicalId="CondCreateVpcSecurityGroup" parameterLogicalId="CreateVpcSecurityGroup"}}, 15 | {{condition 'm:common-core' 'is_true' logicalId="CondOpenVpcCommunication" parameterLogicalId="OpenVpcCommunication"}}, 16 | {{condition 'm:common-core' 'is_true' logicalId="CondCreateInternetGateway" parameterLogicalId="CreateInternetGateway"}} 17 | }, 18 | "Resources": { 19 | "vpc": { 20 | "Type": "AWS::EC2::VPC", 21 | "Properties": { 22 | "CidrBlock": { 23 | "Ref": "VpcCidr" 24 | }, 25 | "InstanceTenancy": "default", 26 | "EnableDnsSupport": "true", 27 | "EnableDnsHostnames": "true", 28 | "Tags": [ 29 | { 30 | "Key": "Name", 31 | "Value": { 32 | "Ref": "NameTag" 33 | } 34 | } 35 | ] 36 | } 37 | }, 38 | "internetGateway": { 39 | "Type": "AWS::EC2::InternetGateway", 40 | "Condition": "CondCreateInternetGateway", 41 | "Properties": { 42 | "Tags": [ 43 | { 44 | "Key": "Name", 45 | "Value": { 46 | "Ref": "NameTag" 47 | } 48 | } 49 | ] 50 | } 51 | }, 52 | "networkACL": { 53 | "Type": "AWS::EC2::NetworkAcl", 54 | "Properties": { 55 | "VpcId": { 56 | "Ref": "vpc" 57 | } 58 | } 59 | }, 60 | "routeTable": { 61 | "Type": "AWS::EC2::RouteTable", 62 | "Properties": { 63 | "VpcId": { 64 | "Ref": "vpc" 65 | }, 66 | "Tags": [ 67 | { 68 | "Key": "Name", 69 | "Value": { 70 | "Ref": "NameTag" 71 | } 72 | } 73 | ] 74 | } 75 | }, 76 | "VpcSecurityGroup": { 77 | "Type": "AWS::EC2::SecurityGroup", 78 | "Condition": "CondCreateVpcSecurityGroup", 79 | "Properties": { 80 | "GroupDescription": "VPC Default Security Group", 81 | "VpcId": { 82 | "Ref": "vpc" 83 | }, 84 | "SecurityGroupEgress": [ 85 | { 86 | "IpProtocol": "-1", 87 | "CidrIp": "0.0.0.0/0" 88 | } 89 | ], 90 | "Tags": [ 91 | { 92 | "Key": "Name", 93 | "Value": { 94 | "Ref": "NameTag" 95 | } 96 | } 97 | ] 98 | } 99 | }, 100 | "SecurityGroupIngress": { 101 | "Type": "AWS::EC2::SecurityGroupIngress", 102 | "Condition": "CondOpenVpcCommunication", 103 | "Properties": { 104 | "GroupId": { 105 | "Fn::GetAtt": ["VpcSecurityGroup","GroupId"] 106 | }, 107 | "IpProtocol": "-1", 108 | "SourceSecurityGroupId": { 109 | "Fn::GetAtt": ["VpcSecurityGroup","GroupId"] 110 | } 111 | } 112 | }, 113 | "acl1": { 114 | "Type": "AWS::EC2::NetworkAclEntry", 115 | "Properties": { 116 | "CidrBlock": "0.0.0.0/0", 117 | "Egress": true, 118 | "Protocol": "-1", 119 | "RuleAction": "allow", 120 | "RuleNumber": "100", 121 | "NetworkAclId": { 122 | "Ref": "networkACL" 123 | } 124 | } 125 | }, 126 | "acl2": { 127 | "Type": "AWS::EC2::NetworkAclEntry", 128 | "Properties": { 129 | "CidrBlock": "0.0.0.0/0", 130 | "Protocol": "-1", 131 | "RuleAction": "allow", 132 | "RuleNumber": "100", 133 | "NetworkAclId": { 134 | "Ref": "networkACL" 135 | } 136 | } 137 | }, 138 | "gw1": { 139 | "Type": "AWS::EC2::VPCGatewayAttachment", 140 | "Condition": "CondCreateInternetGateway", 141 | "Properties": { 142 | "VpcId": { 143 | "Ref": "vpc" 144 | }, 145 | "InternetGatewayId": { 146 | "Ref": "internetGateway" 147 | } 148 | } 149 | }, 150 | "route1": { 151 | "Type": "AWS::EC2::Route", 152 | "Condition": "CondCreateInternetGateway", 153 | "Properties": { 154 | "DestinationCidrBlock": "0.0.0.0/0", 155 | "RouteTableId": { 156 | "Ref": "routeTable" 157 | }, 158 | "GatewayId": { 159 | "Ref": "internetGateway" 160 | } 161 | }, 162 | "DependsOn": "gw1" 163 | } 164 | }, 165 | "Description": "", 166 | "Outputs": { 167 | "VpcId": { 168 | "Value": { 169 | "Ref": "vpc" 170 | } 171 | }, 172 | "RouteTableId": { 173 | "Value": { 174 | "Ref": "routeTable" 175 | } 176 | }, 177 | "SecurityGroupId": { 178 | "Value": { 179 | "Ref": "VpcSecurityGroup" 180 | }, 181 | "Condition": "CondCreateVpcSecurityGroup" 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /test/fixtures/projects/particles-vpc/particles/parameters/vpc_cidr.hbs: -------------------------------------------------------------------------------- 1 | {{parameter 'module:particles-common-core' 'cidr_range'}} 2 | -------------------------------------------------------------------------------- /test/fixtures/projects/particles-vpc/particles/sets/true_false_set.hbs: -------------------------------------------------------------------------------- 1 | {{set 'module:particles-common-core' 'true_false'}} 2 | -------------------------------------------------------------------------------- /test/fixtures/projects/projectA/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "projectA", 3 | "version": "0.0.0", 4 | "description": "", 5 | "keywords": ["condensation-particles"] 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/projects/projectA/particles/cftemplates/infra.template.json.hbs: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Parameters": { 4 | "VpcCidr": { 5 | "Description": "The VPC Network Range", 6 | "Type": "String", 7 | "MinLength": "9", 8 | "MaxLength": "18", 9 | "Default": "10.0.0.0/16", 10 | "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})", 11 | "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x." 12 | }, 13 | "SubnetCidr": { 14 | "Description": "The Subnet Network Range", 15 | "Type": "String", 16 | "MinLength": "9", 17 | "MaxLength": "18", 18 | "Default": "10.0.0.0/24", 19 | "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})", 20 | "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x." 21 | }, 22 | "SubnetAvailabilityZone": { 23 | "Description": "Availability Zone", 24 | "Type": "String", 25 | "Default": "auto", 26 | "ConstraintDescription": "Must be a valid AZ for the region or 'auto'." 27 | }, 28 | {{partial 'parameter-name-tag'}} 29 | }, 30 | "Conditions": { 31 | "SubnetAutoSelection": {"Fn::Equals": [{"Ref": "SubnetAvailabilityZone"}, "auto"]} 32 | }, 33 | "Resources": { 34 | "Vpc": { 35 | "Type": "AWS::CloudFormation::Stack", 36 | "Properties": { 37 | "Parameters": { 38 | "VpcCidr": { 39 | "Ref": "VpcCidr" 40 | }, 41 | "CreateVpcSecurityGroup": "true", 42 | "CreateInternetGateway": "true", 43 | "OpenVpcCommunication": "true", 44 | "NameTag": { 45 | "Ref": "NameTag" 46 | } 47 | }, 48 | "TemplateURL": "{{templateS3Url 'vpc.template.json'}}" 49 | } 50 | }, 51 | "Subnet": { 52 | "Type": "AWS::CloudFormation::Stack", 53 | "Properties": { 54 | "Parameters": { 55 | "SubnetCidr": { 56 | "Ref": "SubnetCidr" 57 | }, 58 | "AvailabilityZone": { 59 | "Fn::If": [ 60 | "SubnetAutoSelection", 61 | {"Fn::Select": ["0",{"Fn::GetAZs" : "" }]}, 62 | {"Ref": "SubnetAvailabilityZone"} 63 | ] 64 | }, 65 | "VpcId": { "Fn::GetAtt": [ "Vpc", "Outputs.VpcId" ] }, 66 | "RouteTableId": { 67 | "Fn::GetAtt": [ 68 | "Vpc", 69 | "Outputs.RouteTableId" 70 | ] 71 | }, 72 | "NameTag": { "Ref": "NameTag" } 73 | }, 74 | "TemplateURL": "{{templateS3Url 'subnet.template.json'}}" 75 | }, 76 | "DependsOn": "Vpc" 77 | } 78 | }, 79 | "Outputs": { 80 | "SubnetId" : { 81 | "Value" : {"Fn::GetAtt": ["Subnet","Outputs.SubnetId"]}, 82 | "Description" : "Subnet" 83 | }, 84 | "VpcId" : { 85 | "Value" : {"Fn::GetAtt": ["Vpc","Outputs.VpcId"]}, 86 | "Description" : "VPC ID" 87 | }, 88 | "SubnetAZ": { 89 | "Value": { 90 | "Fn::If": [ 91 | "SubnetAutoSelection", 92 | {"Fn::Select": ["0",{"Fn::GetAZs" : "" }]}, 93 | {"Ref": "SubnetAvailabilityZone"} 94 | ] 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /test/fixtures/projects/projectA/particles/cftemplates/subnet.template.json.hbs: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "{{helper 'test-helper'}}", 4 | "Parameters": { 5 | "SubnetCidr": { 6 | "Type": "String" 7 | }, 8 | "AvailabilityZone": { 9 | "Type": "String" 10 | }, 11 | "VpcId": { 12 | "Type": "String" 13 | }, 14 | {{partial 'parameter-name-tag'}} 15 | }, 16 | "Resources": { 17 | "Subnet": { 18 | "Type": "AWS::EC2::Subnet", 19 | "Properties": { 20 | "CidrBlock": { 21 | "Ref": "SubnetCidr" 22 | }, 23 | "AvailabilityZone": { 24 | "Fn::If": [ 25 | "condNonDefaultAZ", 26 | { 27 | "Ref": "AvailabilityZone" 28 | }, 29 | { 30 | "Ref": "AWS::NoValue" 31 | } 32 | ] 33 | }, 34 | "VpcId": { 35 | "Ref": "VpcId" 36 | }, 37 | "Tags": [ 38 | { 39 | "Key": "Name", 40 | "Value": { 41 | "Ref": "NameTag" 42 | } 43 | } 44 | ] 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test/fixtures/projects/projectA/particles/cftemplates/vpc.template.json.hbs: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "{{helper 'test-helper'}}", 4 | "Parameters": { 5 | "VpcCidr": { 6 | "Description": "The VPC Network Range", 7 | "Type": "String", 8 | "MinLength": "9", 9 | "MaxLength": "18", 10 | "Default": "0.0.0.0/0", 11 | "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})", 12 | "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x." 13 | }, 14 | {{partial 'parameter-name-tag'}} 15 | }, 16 | "Resources": { 17 | "vpc": { 18 | "Type": "AWS::EC2::VPC", 19 | "Properties": { 20 | "CidrBlock": { 21 | "Ref": "VpcCidr" 22 | }, 23 | "InstanceTenancy": "default", 24 | "EnableDnsSupport": "true", 25 | "EnableDnsHostnames": "true", 26 | "Tags": [ 27 | { 28 | "Key": "Name", 29 | "Value": { 30 | "Ref": "NameTag" 31 | } 32 | } 33 | ] 34 | } 35 | } 36 | }, 37 | "Outputs": { 38 | "VpcId": { 39 | "Value": { 40 | "Ref": "vpc" 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/fixtures/projects/projectA/particles/helpers/test-helper.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | return "This test text was generated for region " + this.s3.region 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/projects/projectA/particles/partials/ami-map: -------------------------------------------------------------------------------- 1 | { 2 | "us-east-1" : { "32" : "ami-6411e20d"}, 3 | "us-west-1" : { "32" : "ami-c9c7978c"}, 4 | "eu-west-1" : { "32" : "ami-37c2f643"}, 5 | "ap-southeast-1" : { "32" : "ami-66f28c34"}, 6 | "ap-northeast-1" : { "32" : "ami-9c03a89d"} 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/projects/projectA/particles/partials/parameter-name-tag: -------------------------------------------------------------------------------- 1 | "NameTag": { 2 | "Description": "Will set the name tag on all resources created", 3 | "Type": "String" 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/projects/projectA/particles/partials/text-test: -------------------------------------------------------------------------------- 1 | --- 2 | text: Text from project A 3 | --- 4 | {{text}} 5 | -------------------------------------------------------------------------------- /test/fixtures/projects/projectA/projectA: -------------------------------------------------------------------------------- 1 | projectA -------------------------------------------------------------------------------- /test/fixtures/projects/projectB/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "projectB", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "projectA": "../projectA" 6 | }, 7 | "keywords": ["condensation-particles"] 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/projects/projectB/particles/assets/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "I bootstrap!" 4 | -------------------------------------------------------------------------------- /test/fixtures/projects/projectB/particles/assets/download.sh.hbs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | wget {{{assetS3Url 'bootstrap.sh'}}} 4 | -------------------------------------------------------------------------------- /test/fixtures/projects/projectB/particles/cftemplates/instance.template.json.hbs: -------------------------------------------------------------------------------- 1 | {{requireAssets '*'}} 2 | { 3 | "AWSTemplateFormatVersion": "2010-09-09", 4 | "Description": "Create a Instance with it's own VPC", 5 | "Parameters": { 6 | "InstanceType": { 7 | "Description": "EC2 instance type", 8 | "Type": "String", 9 | "Default": "t2.medium", 10 | "ConstraintDescription": "must be a valid EC2 instance type." 11 | }, 12 | "SecurityGroupId": { 13 | "Description": "Existing security group that has access to a Sky appliance", 14 | "Type": "String" 15 | }, 16 | "VpcCidr": { 17 | "Description": "The VPC Network Range", 18 | "Type": "String", 19 | "MinLength": "9", 20 | "MaxLength": "18", 21 | "Default": "10.0.0.0/16", 22 | "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})", 23 | "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x." 24 | }, 25 | "SubnetId": { 26 | "Type": "String" 27 | }, 28 | "KeyName": { 29 | "Type": "String" 30 | }, 31 | {{partial 'module:projectA' 'parameter-name-tag'}} 32 | }, 33 | "Resources": { 34 | "Vpc": { 35 | "Type": "AWS::CloudFormation::Stack", 36 | "Properties": { 37 | "Parameters": { 38 | "VpcCidr": { 39 | "Ref": "VpcCidr" 40 | }, 41 | "NameTag": { 42 | "Ref": "NameTag" 43 | } 44 | }, 45 | "TemplateURL": "{{templateS3Url 'module:projectA' 'vpc.template.json'}}" 46 | } 47 | }, 48 | "CfnUser": { 49 | "Type": "AWS::IAM::User", 50 | "Properties": { 51 | "Path": "/", 52 | "Policies": [{ 53 | "PolicyName": "root", 54 | "PolicyDocument": { "Statement":[{ 55 | "Effect" : "Allow", 56 | "Action" : [ 57 | "cloudformation:DescribeStackResource", 58 | "s3:GetObject" 59 | ], 60 | "Resource" :"*" 61 | },{ 62 | "Sid" : "CloudFormationReadAccess", 63 | "Action" : ["s3:GetObject"], 64 | "Effect" : "Allow", 65 | "Resource" : { "Fn::Join" : ["", ["arn:aws:s3:::{{s3.bucket}}", "/*"]]} 66 | }]} 67 | }] 68 | } 69 | }, 70 | "CfnKeys" : { 71 | "Type" : "AWS::IAM::AccessKey", 72 | "Properties" : { 73 | "UserName" : {"Ref": "CfnUser"} 74 | } 75 | }, 76 | 77 | "WaitHandle" : { 78 | "Type" : "AWS::CloudFormation::WaitConditionHandle" 79 | }, 80 | 81 | "WaitCondition" : { 82 | "Type": "AWS::CloudFormation::WaitCondition", 83 | "DependsOn": "Client", 84 | "Properties": { 85 | "Handle": { "Ref": "WaitHandle" }, 86 | "Timeout": "6000" 87 | } 88 | }, 89 | 90 | "Client": { 91 | "Type": "AWS::EC2::Instance", 92 | "Metadata": { 93 | "AWS::CloudFormation::Init": { 94 | "configSets": { 95 | "default": ["enableRepos","runScripts"] 96 | }, 97 | "enableRepos": { 98 | "commands": { 99 | "001": { 100 | "command": "yum-config-manager --enable epel" 101 | }, 102 | "002": { 103 | "command": "yum update -y" 104 | } 105 | } 106 | }, 107 | "runScripts": { 108 | "files": { 109 | "/root/gen_data.sh" : { 110 | "source": "{{{assetS3Url 'bootstrap.sh'}}}", 111 | "mode": "000755" 112 | } 113 | }, 114 | "commands": { 115 | "001": { 116 | "command": "/root/bootstrap.sh" 117 | } 118 | } 119 | } 120 | }, 121 | "AWS::CloudFormation::Authentication" : { 122 | "S3AccessCreds" : { 123 | "type" : "S3", 124 | "accessKeyId" : { "Ref" : "CfnKeys" }, 125 | "secretKey" : {"Fn::GetAtt": ["CfnKeys", "SecretAccessKey"]}, 126 | "buckets" : [ "{{s3.bucket}}" ] 127 | } 128 | } 129 | }, 130 | "Properties": { 131 | "KeyName": { "Ref": "KeyName" }, 132 | "ImageId": { "Fn::FindInMap": [ "RegionMap", { "Ref": "AWS::Region" }, "AMI"] }, 133 | "InstanceType": { "Ref": "InstanceType" }, 134 | "Tags":[ { "Key": "Name", "Value": {"Ref": "NameTag"} } ], 135 | "NetworkInterfaces": [{ 136 | "DeviceIndex": "0", 137 | "AssociatePublicIpAddress": true, 138 | "GroupSet": [{"Ref": "SecurityGroupId"}], 139 | "SubnetId": {"Ref": "SubnetId"} 140 | }], 141 | "BlockDeviceMappings": [ 142 | { "DeviceName" : "/dev/xvda", "Ebs" : { "VolumeSize" : "8", "VolumeType": "gp2" } }, 143 | { "DeviceName" : "/dev/xvdf", "Ebs" : { "VolumeSize" : "51", "VolumeType": "gp2" } } 144 | ], 145 | "UserData": { "Fn::Base64": { "Fn::Join": ["", [ 146 | "#!/bin/bash -ex\n", 147 | 148 | "# Helper function\n", 149 | "function error_exit\n", 150 | "{\n", 151 | " /opt/aws/bin/cfn-signal -e 1 -r \"$1\" ", 152 | " --stack ", { "Ref" : "AWS::StackName" }, 153 | " --resource ServerGroup ", 154 | " --region ", { "Ref" : "AWS::Region" }, "\n", 155 | " exit 1\n", 156 | "}\n", 157 | 158 | "# Process CloudFormation init definitions\n", 159 | "/opt/aws/bin/cfn-init -s ", { "Ref": "AWS::StackName" }, " -r Client ", 160 | " --region ", { "Ref": "AWS::Region" }, " || error_exit 'Failed to run cfn-init'\n", 161 | 162 | "# All is well so signal success\n", 163 | "/opt/aws/bin/cfn-signal -e 0 -d \"`cut -f2 -d= /etc/iscsi/initiatorname.iscsi`\" ", 164 | " --region ", { "Ref" : "AWS::Region" }, " \"", 165 | { "Ref": "WaitHandle" }, "\" \n", 166 | 167 | "#EOF" 168 | ]] } } 169 | } 170 | } 171 | }, 172 | 173 | "Outputs": { 174 | "HostIqn": { 175 | "Value": { "Fn::GetAtt": ["WaitCondition", "Data"] } 176 | }, 177 | "PrivateIp": { 178 | "Value": { "Fn::GetAtt": ["Client", "PrivateIp"] } 179 | }, 180 | "PublicIp": { 181 | "Value": { "Fn::GetAtt": ["Client", "PublicIp"] } 182 | }, 183 | "PrivateDnsName": { 184 | "Value": { "Fn::GetAtt": ["Client", "PrivateDnsName"] } 185 | }, 186 | "PublicDnsName": { 187 | "Value": { "Fn::GetAtt": ["Client", "PublicDnsName"] } 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /test/fixtures/projects/projectB/particles/helpers/hello-b.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | return "Hello from project B"; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/projects/projectB/particles/partials/ami_map.partial: -------------------------------------------------------------------------------- 1 | {{partial 'module:projectA' 'ami-map'}} 2 | -------------------------------------------------------------------------------- /test/fixtures/projects/projectC/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "projectC", 3 | "dependencies": { 4 | "projectB": "../projectB" 5 | }, 6 | "keywords": ["condensation-particles"] 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/projects/projectC/particles/cftemplates/proj.template.json.hbs: -------------------------------------------------------------------------------- 1 | --- 2 | frontload: 3 | someString: 4 | loader: testLoader 5 | args: "hello" 6 | --- 7 | { 8 | "Description": "{{helper 'module:projectB' 'hello-b'}}", 9 | "Metadata": { 10 | "SayHello": {{cValue someString}} 11 | }, 12 | "Mappings": { 13 | "myAMImap": {{partial 'module:projectB' 'ami_map.partial'}} 14 | }, 15 | "Resources": { 16 | "ProjectBStack": { 17 | "Type": "AWS::CloudFormation::Stack", 18 | "Properties": { 19 | "TemplateURL": "{{templateS3Url 'module:projectB' 'instance.template.json'}}" 20 | } 21 | }, 22 | {{resource "lambda_function" logicalId="LambdaFunction"}} 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/fixtures/projects/projectC/particles/front_loaders/testLoader.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | var cb = arguments[arguments.length-1]; 3 | cb(null,"a random string"); 4 | }; 5 | -------------------------------------------------------------------------------- /test/fixtures/projects/projectC/particles/resources/lambda_function.hbs: -------------------------------------------------------------------------------- 1 | "Type": "AWS::Lambda::Function", 2 | "Properties": { 3 | "Code": "", 4 | "Handler": "index.handler", 5 | "MemorySize": 128, 6 | "Role": "arn::", 7 | "Runtime": "nodejs", 8 | "Timeout": 3 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/projects/projectC/particles/resources/lambda_function/execution_role.hbs: -------------------------------------------------------------------------------- 1 | "Type": "AWS::IAM::Role" 2 | "Properties": { 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/projects/sam/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sam", 3 | "dependencies": { }, 4 | "keywords": ["condensation-particles"] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/projects/sam/particles/cftemplates/sam.template.json.hbs: -------------------------------------------------------------------------------- 1 | {{#layout Transform="AWS::Serverless-2016-10-31"}} 2 | 3 | {{/layout}} 4 | -------------------------------------------------------------------------------- /test/fixtures/projects_output/particle-builds/sets_output1.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Resources": { 4 | "Resource1": { 5 | "Properties": {}, 6 | "Type": "Custom::TEST" 7 | }, 8 | "Set2Resource2": { 9 | "Properties": {}, 10 | "Type": "Custom::TEST" 11 | }, 12 | "Set2Set3Resource3": { 13 | "Properties": {}, 14 | "Type": "Custom:TEST" 15 | } 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /test/fixtures/projects_output/particles-vpc/0/particles/cftemplates/subnet.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "A Subnet", 4 | "Conditions": { 5 | "tfIsTrueCondition": { 6 | "Fn::Equals": [ 7 | { 8 | "Ref": "tf" 9 | }, 10 | "true" 11 | ] 12 | }, 13 | "tfIsFalseCondition": { 14 | "Fn::Equals": [ 15 | { 16 | "Ref": "tf" 17 | }, 18 | "false" 19 | ] 20 | }, 21 | "condNonDefaultRouteTable": { 22 | "Fn::Not": [ 23 | { 24 | "Fn::Equals": [ 25 | { 26 | "Ref": "RouteTableId" 27 | }, 28 | "default" 29 | ] 30 | } 31 | ] 32 | }, 33 | "condNonDefaultAZ": { 34 | "Fn::Not": [ 35 | { 36 | "Fn::Equals": [ 37 | { 38 | "Ref": "AvailabilityZone" 39 | }, 40 | "random" 41 | ] 42 | } 43 | ] 44 | }, 45 | "condDefaultAZ": { 46 | "Fn::Equals": [ 47 | { 48 | "Ref": "AvailabilityZone" 49 | }, 50 | "random" 51 | ] 52 | } 53 | }, 54 | "Outputs": { 55 | "SubnetId": { 56 | "Value": { 57 | "Ref": "Subnet" 58 | } 59 | }, 60 | "AvailabilityZone": { 61 | "Value": { 62 | "Fn::GetAtt": [ 63 | "Subnet", 64 | "AvailabilityZone" 65 | ] 66 | } 67 | } 68 | }, 69 | "Parameters": { 70 | "SubnetCidr": { 71 | "Type": "String", 72 | "Default": "10.1.0.0/16", 73 | "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})", 74 | "MaxLength": "18", 75 | "MinLength": "9", 76 | "Description": "Subnet CIDR [x.x.x.x/x]", 77 | "ConstraintDescription": "must be a valid CIDR range formatted as x.x.x.x/x" 78 | }, 79 | "AvailabilityZone": { 80 | "Default": "random", 81 | "Description": "Availability Zone", 82 | "Type": "String", 83 | "ConstraintDescription": "Must be a valid AZ for the region." 84 | }, 85 | "VpcId": { 86 | "Description": "ID of an existing VPC to create the subnet in", 87 | "Type": "AWS::EC2::VPC::Id" 88 | }, 89 | "RouteTableId": { 90 | "Description": "Route table that the subnet will attach to.", 91 | "Default": "default", 92 | "Type": "String", 93 | "MinLength": "1", 94 | "MaxLength": "255", 95 | "AllowedPattern": "[\\x20-\\x7E]*", 96 | "ConstraintDescription": "can contain only ASCII characters." 97 | }, 98 | "NameTag": { 99 | "Description": "Set the name tag for all created resources.", 100 | "Default": "default", 101 | "Type": "String", 102 | "MinLength": "1", 103 | "MaxLength": "255", 104 | "AllowedPattern": "[\\x20-\\x7E]*", 105 | "ConstraintDescription": "can contain only ASCII characters." 106 | }, 107 | "tf": { 108 | "Type": "String", 109 | "MaxLength": "5", 110 | "MinLength": "4", 111 | "ConstraintDescription": "[true|false]" 112 | } 113 | }, 114 | "Resources": { 115 | "Subnet": { 116 | "Type": "AWS::EC2::Subnet", 117 | "Properties": { 118 | "CidrBlock": { 119 | "Ref": "SubnetCidr" 120 | }, 121 | "AvailabilityZone": { 122 | "Fn::If": [ 123 | "condNonDefaultAZ", 124 | { 125 | "Ref": "AvailabilityZone" 126 | }, 127 | { 128 | "Ref": "AWS::NoValue" 129 | } 130 | ] 131 | }, 132 | "VpcId": { 133 | "Ref": "VpcId" 134 | }, 135 | "Tags": [ 136 | { 137 | "Key": "Name", 138 | "Value": { 139 | "Ref": "NameTag" 140 | } 141 | } 142 | ] 143 | } 144 | }, 145 | "RouteTableAssociation": { 146 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 147 | "Condition": "condNonDefaultRouteTable", 148 | "Properties": { 149 | "RouteTableId": { 150 | "Ref": "RouteTableId" 151 | }, 152 | "SubnetId": { 153 | "Ref": "Subnet" 154 | } 155 | } 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /test/fixtures/projects_output/particles-vpc/0/particles/cftemplates/vpc.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Parameters": { 4 | "VpcCidr": { 5 | "Type": "String", 6 | "Default": "10.1.0.0/16", 7 | "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})", 8 | "MaxLength": "18", 9 | "MinLength": "9", 10 | "Description": "Valid CIDR Range. Format: x.x.x.x/x", 11 | "ConstraintDescription": "must be a valid CIDR range formatted as x.x.x.x/x" 12 | }, 13 | "CreateVpcSecurityGroup": { 14 | "Type": "String", 15 | "Default": "false", 16 | "MaxLength": "5", 17 | "MinLength": "4", 18 | "Description": "[true|false] Create a security group assigned to the new VPC", 19 | "ConstraintDescription": "[true|false]" 20 | }, 21 | "OpenVpcCommunication": { 22 | "Type": "String", 23 | "Default": "false", 24 | "MaxLength": "5", 25 | "MinLength": "4", 26 | "Description": "[true|false] Open all communication within the VPC", 27 | "ConstraintDescription": "[true|false]" 28 | }, 29 | "CreateInternetGateway": { 30 | "Type": "String", 31 | "Default": "false", 32 | "MaxLength": "5", 33 | "MinLength": "4", 34 | "Description": "[true|false] Create an Internet Gateway for the VPC", 35 | "ConstraintDescription": "[true|false]" 36 | }, 37 | "NameTag": { 38 | "Description": "Will set the name tag on all resources created", 39 | "Type": "String" 40 | } 41 | }, 42 | "Conditions": { 43 | "CondCreateVpcSecurityGroup": { 44 | "Fn::Equals": [ 45 | { 46 | "Ref": "CreateVpcSecurityGroup" 47 | }, 48 | "true" 49 | ] 50 | }, 51 | "CondOpenVpcCommunication": { 52 | "Fn::Equals": [ 53 | { 54 | "Ref": "OpenVpcCommunication" 55 | }, 56 | "true" 57 | ] 58 | }, 59 | "CondCreateInternetGateway": { 60 | "Fn::Equals": [ 61 | { 62 | "Ref": "CreateInternetGateway" 63 | }, 64 | "true" 65 | ] 66 | } 67 | }, 68 | "Resources": { 69 | "vpc": { 70 | "Type": "AWS::EC2::VPC", 71 | "Properties": { 72 | "CidrBlock": { 73 | "Ref": "VpcCidr" 74 | }, 75 | "InstanceTenancy": "default", 76 | "EnableDnsSupport": "true", 77 | "EnableDnsHostnames": "true", 78 | "Tags": [ 79 | { 80 | "Key": "Name", 81 | "Value": { 82 | "Ref": "NameTag" 83 | } 84 | } 85 | ] 86 | } 87 | }, 88 | "internetGateway": { 89 | "Type": "AWS::EC2::InternetGateway", 90 | "Condition": "CondCreateInternetGateway", 91 | "Properties": { 92 | "Tags": [ 93 | { 94 | "Key": "Name", 95 | "Value": { 96 | "Ref": "NameTag" 97 | } 98 | } 99 | ] 100 | } 101 | }, 102 | "networkACL": { 103 | "Type": "AWS::EC2::NetworkAcl", 104 | "Properties": { 105 | "VpcId": { 106 | "Ref": "vpc" 107 | } 108 | } 109 | }, 110 | "routeTable": { 111 | "Type": "AWS::EC2::RouteTable", 112 | "Properties": { 113 | "VpcId": { 114 | "Ref": "vpc" 115 | }, 116 | "Tags": [ 117 | { 118 | "Key": "Name", 119 | "Value": { 120 | "Ref": "NameTag" 121 | } 122 | } 123 | ] 124 | } 125 | }, 126 | "VpcSecurityGroup": { 127 | "Type": "AWS::EC2::SecurityGroup", 128 | "Condition": "CondCreateVpcSecurityGroup", 129 | "Properties": { 130 | "GroupDescription": "VPC Default Security Group", 131 | "VpcId": { 132 | "Ref": "vpc" 133 | }, 134 | "SecurityGroupEgress": [ 135 | { 136 | "IpProtocol": "-1", 137 | "CidrIp": "0.0.0.0/0" 138 | } 139 | ], 140 | "Tags": [ 141 | { 142 | "Key": "Name", 143 | "Value": { 144 | "Ref": "NameTag" 145 | } 146 | } 147 | ] 148 | } 149 | }, 150 | "SecurityGroupIngress": { 151 | "Type": "AWS::EC2::SecurityGroupIngress", 152 | "Condition": "CondOpenVpcCommunication", 153 | "Properties": { 154 | "GroupId": { 155 | "Fn::GetAtt": [ 156 | "VpcSecurityGroup", 157 | "GroupId" 158 | ] 159 | }, 160 | "IpProtocol": "-1", 161 | "SourceSecurityGroupId": { 162 | "Fn::GetAtt": [ 163 | "VpcSecurityGroup", 164 | "GroupId" 165 | ] 166 | } 167 | } 168 | }, 169 | "acl1": { 170 | "Type": "AWS::EC2::NetworkAclEntry", 171 | "Properties": { 172 | "CidrBlock": "0.0.0.0/0", 173 | "Egress": true, 174 | "Protocol": "-1", 175 | "RuleAction": "allow", 176 | "RuleNumber": "100", 177 | "NetworkAclId": { 178 | "Ref": "networkACL" 179 | } 180 | } 181 | }, 182 | "acl2": { 183 | "Type": "AWS::EC2::NetworkAclEntry", 184 | "Properties": { 185 | "CidrBlock": "0.0.0.0/0", 186 | "Protocol": "-1", 187 | "RuleAction": "allow", 188 | "RuleNumber": "100", 189 | "NetworkAclId": { 190 | "Ref": "networkACL" 191 | } 192 | } 193 | }, 194 | "gw1": { 195 | "Type": "AWS::EC2::VPCGatewayAttachment", 196 | "Condition": "CondCreateInternetGateway", 197 | "Properties": { 198 | "VpcId": { 199 | "Ref": "vpc" 200 | }, 201 | "InternetGatewayId": { 202 | "Ref": "internetGateway" 203 | } 204 | } 205 | }, 206 | "route1": { 207 | "Type": "AWS::EC2::Route", 208 | "Condition": "CondCreateInternetGateway", 209 | "Properties": { 210 | "DestinationCidrBlock": "0.0.0.0/0", 211 | "RouteTableId": { 212 | "Ref": "routeTable" 213 | }, 214 | "GatewayId": { 215 | "Ref": "internetGateway" 216 | } 217 | }, 218 | "DependsOn": "gw1" 219 | } 220 | }, 221 | "Description": "", 222 | "Outputs": { 223 | "VpcId": { 224 | "Value": { 225 | "Ref": "vpc" 226 | } 227 | }, 228 | "RouteTableId": { 229 | "Value": { 230 | "Ref": "routeTable" 231 | } 232 | }, 233 | "SecurityGroupId": { 234 | "Value": { 235 | "Ref": "VpcSecurityGroup" 236 | }, 237 | "Condition": "CondCreateVpcSecurityGroup" 238 | } 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /test/fixtures/projects_output/projectA/0/particles/cftemplates/infra.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Parameters": { 4 | "VpcCidr": { 5 | "Description": "The VPC Network Range", 6 | "Type": "String", 7 | "MinLength": "9", 8 | "MaxLength": "18", 9 | "Default": "10.0.0.0/16", 10 | "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})", 11 | "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x." 12 | }, 13 | "SubnetCidr": { 14 | "Description": "The Subnet Network Range", 15 | "Type": "String", 16 | "MinLength": "9", 17 | "MaxLength": "18", 18 | "Default": "10.0.0.0/24", 19 | "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})", 20 | "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x." 21 | }, 22 | "SubnetAvailabilityZone": { 23 | "Description": "Availability Zone", 24 | "Type": "String", 25 | "Default": "auto", 26 | "ConstraintDescription": "Must be a valid AZ for the region or 'auto'." 27 | }, 28 | "NameTag": { 29 | "Description": "Will set the name tag on all resources created", 30 | "Type": "String" 31 | } 32 | }, 33 | "Conditions": { 34 | "SubnetAutoSelection": { 35 | "Fn::Equals": [ 36 | { 37 | "Ref": "SubnetAvailabilityZone" 38 | }, 39 | "auto" 40 | ] 41 | } 42 | }, 43 | "Resources": { 44 | "Vpc": { 45 | "Type": "AWS::CloudFormation::Stack", 46 | "Properties": { 47 | "Parameters": { 48 | "VpcCidr": { 49 | "Ref": "VpcCidr" 50 | }, 51 | "CreateVpcSecurityGroup": "true", 52 | "CreateInternetGateway": "true", 53 | "OpenVpcCommunication": "true", 54 | "NameTag": { 55 | "Ref": "NameTag" 56 | } 57 | }, 58 | "TemplateURL": "https://s3.amazonaws.com/./particles/cftemplates/vpc.template.json" 59 | } 60 | }, 61 | "Subnet": { 62 | "Type": "AWS::CloudFormation::Stack", 63 | "Properties": { 64 | "Parameters": { 65 | "SubnetCidr": { 66 | "Ref": "SubnetCidr" 67 | }, 68 | "AvailabilityZone": { 69 | "Fn::If": [ 70 | "SubnetAutoSelection", 71 | { 72 | "Fn::Select": [ 73 | "0", 74 | { 75 | "Fn::GetAZs": "" 76 | } 77 | ] 78 | }, 79 | { 80 | "Ref": "SubnetAvailabilityZone" 81 | } 82 | ] 83 | }, 84 | "VpcId": { 85 | "Fn::GetAtt": [ 86 | "Vpc", 87 | "Outputs.VpcId" 88 | ] 89 | }, 90 | "RouteTableId": { 91 | "Fn::GetAtt": [ 92 | "Vpc", 93 | "Outputs.RouteTableId" 94 | ] 95 | }, 96 | "NameTag": { 97 | "Ref": "NameTag" 98 | } 99 | }, 100 | "TemplateURL": "https://s3.amazonaws.com/./particles/cftemplates/subnet.template.json" 101 | }, 102 | "DependsOn": "Vpc" 103 | } 104 | }, 105 | "Outputs": { 106 | "SubnetId": { 107 | "Value": { 108 | "Fn::GetAtt": [ 109 | "Subnet", 110 | "Outputs.SubnetId" 111 | ] 112 | }, 113 | "Description": "Subnet" 114 | }, 115 | "VpcId": { 116 | "Value": { 117 | "Fn::GetAtt": [ 118 | "Vpc", 119 | "Outputs.VpcId" 120 | ] 121 | }, 122 | "Description": "VPC ID" 123 | }, 124 | "SubnetAZ": { 125 | "Value": { 126 | "Fn::If": [ 127 | "SubnetAutoSelection", 128 | { 129 | "Fn::Select": [ 130 | "0", 131 | { 132 | "Fn::GetAZs": "" 133 | } 134 | ] 135 | }, 136 | { 137 | "Ref": "SubnetAvailabilityZone" 138 | } 139 | ] 140 | } 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /test/fixtures/projects_output/projectA/0/particles/cftemplates/subnet.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "This test text was generated for region us-east-1", 4 | "Parameters": { 5 | "SubnetCidr": { 6 | "Type": "String" 7 | }, 8 | "AvailabilityZone": { 9 | "Type": "String" 10 | }, 11 | "VpcId": { 12 | "Type": "String" 13 | }, 14 | "NameTag": { 15 | "Description": "Will set the name tag on all resources created", 16 | "Type": "String" 17 | } 18 | }, 19 | "Resources": { 20 | "Subnet": { 21 | "Type": "AWS::EC2::Subnet", 22 | "Properties": { 23 | "CidrBlock": { 24 | "Ref": "SubnetCidr" 25 | }, 26 | "AvailabilityZone": { 27 | "Fn::If": [ 28 | "condNonDefaultAZ", 29 | { 30 | "Ref": "AvailabilityZone" 31 | }, 32 | { 33 | "Ref": "AWS::NoValue" 34 | } 35 | ] 36 | }, 37 | "VpcId": { 38 | "Ref": "VpcId" 39 | }, 40 | "Tags": [ 41 | { 42 | "Key": "Name", 43 | "Value": { 44 | "Ref": "NameTag" 45 | } 46 | } 47 | ] 48 | } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /test/fixtures/projects_output/projectA/0/particles/cftemplates/vpc.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "This test text was generated for region us-east-1", 4 | "Parameters": { 5 | "VpcCidr": { 6 | "Description": "The VPC Network Range", 7 | "Type": "String", 8 | "MinLength": "9", 9 | "MaxLength": "18", 10 | "Default": "0.0.0.0/0", 11 | "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})", 12 | "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x." 13 | }, 14 | "NameTag": { 15 | "Description": "Will set the name tag on all resources created", 16 | "Type": "String" 17 | } 18 | }, 19 | "Resources": { 20 | "vpc": { 21 | "Type": "AWS::EC2::VPC", 22 | "Properties": { 23 | "CidrBlock": { 24 | "Ref": "VpcCidr" 25 | }, 26 | "InstanceTenancy": "default", 27 | "EnableDnsSupport": "true", 28 | "EnableDnsHostnames": "true", 29 | "Tags": [ 30 | { 31 | "Key": "Name", 32 | "Value": { 33 | "Ref": "NameTag" 34 | } 35 | } 36 | ] 37 | } 38 | } 39 | }, 40 | "Outputs": { 41 | "VpcId": { 42 | "Value": { 43 | "Ref": "vpc" 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /test/fixtures/projects_output/projectB-config2/0/testing-path/node_modules/projectA/particles/cftemplates/vpc.template: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "This test text was generated for region us-east-1", 4 | "Parameters": { 5 | "VpcCidr": { 6 | "Description": "The VPC Network Range", 7 | "Type": "String", 8 | "MinLength": "9", 9 | "MaxLength": "18", 10 | "Default": "0.0.0.0/0", 11 | "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})", 12 | "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x." 13 | }, 14 | "NameTag": { 15 | "Description": "Will set the name tag on all resources created", 16 | "Type": "String" 17 | } 18 | }, 19 | "Resources": { 20 | "vpc": { 21 | "Type": "AWS::EC2::VPC", 22 | "Properties": { 23 | "CidrBlock": { 24 | "Ref": "VpcCidr" 25 | }, 26 | "InstanceTenancy": "default", 27 | "EnableDnsSupport": "true", 28 | "EnableDnsHostnames": "true", 29 | "Tags": [ 30 | { 31 | "Key": "Name", 32 | "Value": { 33 | "Ref": "NameTag" 34 | } 35 | } 36 | ] 37 | } 38 | } 39 | }, 40 | "Outputs": { 41 | "VpcId": { 42 | "Value": { 43 | "Ref": "vpc" 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /test/fixtures/projects_output/projectB-config2/0/testing-path/node_modules/projectA/particles/cftemplates/vpc.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "This test text was generated for region us-east-1", 4 | "Parameters": { 5 | "VpcCidr": { 6 | "Description": "The VPC Network Range", 7 | "Type": "String", 8 | "MinLength": "9", 9 | "MaxLength": "18", 10 | "Default": "0.0.0.0/0", 11 | "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})", 12 | "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x." 13 | }, 14 | "NameTag": { 15 | "Description": "Will set the name tag on all resources created", 16 | "Type": "String" 17 | } 18 | }, 19 | "Resources": { 20 | "vpc": { 21 | "Type": "AWS::EC2::VPC", 22 | "Properties": { 23 | "CidrBlock": { 24 | "Ref": "VpcCidr" 25 | }, 26 | "InstanceTenancy": "default", 27 | "EnableDnsSupport": "true", 28 | "EnableDnsHostnames": "true", 29 | "Tags": [ 30 | { 31 | "Key": "Name", 32 | "Value": { 33 | "Ref": "NameTag" 34 | } 35 | } 36 | ] 37 | } 38 | } 39 | }, 40 | "Outputs": { 41 | "VpcId": { 42 | "Value": { 43 | "Ref": "vpc" 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /test/fixtures/projects_output/projectB-config2/0/testing-path/particles/assets/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "I bootstrap!" 4 | -------------------------------------------------------------------------------- /test/fixtures/projects_output/projectB-config2/0/testing-path/particles/assets/download.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | wget https://s3.amazonaws.com/testing-path/particles/assets/bootstrap.sh 4 | -------------------------------------------------------------------------------- /test/fixtures/projects_output/projectB/0/node_modules/projectA/particles/cftemplates/vpc.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "This test text was generated for region us-east-1", 4 | "Parameters": { 5 | "VpcCidr": { 6 | "Description": "The VPC Network Range", 7 | "Type": "String", 8 | "MinLength": "9", 9 | "MaxLength": "18", 10 | "Default": "0.0.0.0/0", 11 | "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})", 12 | "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x." 13 | }, 14 | "NameTag": { 15 | "Description": "Will set the name tag on all resources created", 16 | "Type": "String" 17 | } 18 | }, 19 | "Resources": { 20 | "vpc": { 21 | "Type": "AWS::EC2::VPC", 22 | "Properties": { 23 | "CidrBlock": { 24 | "Ref": "VpcCidr" 25 | }, 26 | "InstanceTenancy": "default", 27 | "EnableDnsSupport": "true", 28 | "EnableDnsHostnames": "true", 29 | "Tags": [ 30 | { 31 | "Key": "Name", 32 | "Value": { 33 | "Ref": "NameTag" 34 | } 35 | } 36 | ] 37 | } 38 | } 39 | }, 40 | "Outputs": { 41 | "VpcId": { 42 | "Value": { 43 | "Ref": "vpc" 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /test/fixtures/projects_output/projectB/0/particles/assets/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "I bootstrap!" 4 | -------------------------------------------------------------------------------- /test/fixtures/projects_output/projectB/0/particles/assets/download.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | wget https://s3.amazonaws.com/./particles/assets/bootstrap.sh 4 | -------------------------------------------------------------------------------- /test/fixtures/projects_output/projectC/0/node_modules/projectA/particles/cftemplates/vpc.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "This test text was generated for region us-east-1", 4 | "Parameters": { 5 | "VpcCidr": { 6 | "Description": "The VPC Network Range", 7 | "Type": "String", 8 | "MinLength": "9", 9 | "MaxLength": "18", 10 | "Default": "0.0.0.0/0", 11 | "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})", 12 | "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x." 13 | }, 14 | "NameTag": { 15 | "Description": "Will set the name tag on all resources created", 16 | "Type": "String" 17 | } 18 | }, 19 | "Resources": { 20 | "vpc": { 21 | "Type": "AWS::EC2::VPC", 22 | "Properties": { 23 | "CidrBlock": { 24 | "Ref": "VpcCidr" 25 | }, 26 | "InstanceTenancy": "default", 27 | "EnableDnsSupport": "true", 28 | "EnableDnsHostnames": "true", 29 | "Tags": [ 30 | { 31 | "Key": "Name", 32 | "Value": { 33 | "Ref": "NameTag" 34 | } 35 | } 36 | ] 37 | } 38 | } 39 | }, 40 | "Outputs": { 41 | "VpcId": { 42 | "Value": { 43 | "Ref": "vpc" 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /test/fixtures/projects_output/projectC/0/node_modules/projectB/node_modules/projectA/particles/cftemplates/vpc.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "This test text was generated for region us-east-1", 4 | "Parameters": { 5 | "VpcCidr": { 6 | "Description": "The VPC Network Range", 7 | "Type": "String", 8 | "MinLength": "9", 9 | "MaxLength": "18", 10 | "Default": "0.0.0.0/0", 11 | "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})", 12 | "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x." 13 | }, 14 | "NameTag": { 15 | "Description": "Will set the name tag on all resources created", 16 | "Type": "String" 17 | } 18 | }, 19 | "Resources": { 20 | "vpc": { 21 | "Type": "AWS::EC2::VPC", 22 | "Properties": { 23 | "CidrBlock": { 24 | "Ref": "VpcCidr" 25 | }, 26 | "InstanceTenancy": "default", 27 | "EnableDnsSupport": "true", 28 | "EnableDnsHostnames": "true", 29 | "Tags": [ 30 | { 31 | "Key": "Name", 32 | "Value": { 33 | "Ref": "NameTag" 34 | } 35 | } 36 | ] 37 | } 38 | } 39 | }, 40 | "Outputs": { 41 | "VpcId": { 42 | "Value": { 43 | "Ref": "vpc" 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /test/fixtures/projects_output/projectC/0/node_modules/projectB/particles/assets/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "I bootstrap!" 4 | -------------------------------------------------------------------------------- /test/fixtures/projects_output/projectC/0/node_modules/projectB/particles/assets/download.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | wget https://s3.amazonaws.com/my-test-bucket/node_modules/projectB/particles/assets/bootstrap.sh 4 | -------------------------------------------------------------------------------- /test/fixtures/projects_output/projectC/0/particles/cftemplates/proj.template: -------------------------------------------------------------------------------- 1 | { 2 | "Description": "Hello from project B", 3 | "Mappings": { 4 | "myAMImap": { 5 | "us-east-1": { 6 | "32": "ami-6411e20d" 7 | }, 8 | "us-west-1": { 9 | "32": "ami-c9c7978c" 10 | }, 11 | "eu-west-1": { 12 | "32": "ami-37c2f643" 13 | }, 14 | "ap-southeast-1": { 15 | "32": "ami-66f28c34" 16 | }, 17 | "ap-northeast-1": { 18 | "32": "ami-9c03a89d" 19 | } 20 | } 21 | }, 22 | "Resources": { 23 | "ProjectBStack": { 24 | "Type": "AWS::CloudFormation::Stack", 25 | "Properties": { 26 | "TemplateURL": "https://s3.amazonaws.com/my-test-bucket/node_modules/projectB/particles/cftemplates/instance.template.json" 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /test/integration/invalid.test.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var assert = require('assert'); 3 | var clone = require('clone'); 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | var shared = require('./project-shared'); 7 | 8 | describe.skip('invalid', function(){ 9 | var gulp; 10 | 11 | var projectName = 'invalid'; 12 | 13 | var config = shared.generateConfig(projectName, {gulp: gulp}); 14 | 15 | beforeEach('create new gulp object', function() { 16 | gulp = clone(require('gulp')); 17 | require('../../').buildTasks( 18 | gulp, 19 | config.projectConfig 20 | ); 21 | }); 22 | 23 | after('clean the project', function(done) { 24 | var afterGulp = clone(require('gulp')); 25 | require('../../').buildTasks(afterGulp,config.projectConfig); 26 | afterGulp.start('clean'); 27 | afterGulp.on('stop',function(){ 28 | fs.lstat(config.projectConfig.dist, function(err, stats) { 29 | assert(err,config.projectConfig.dist + " not clean"); 30 | done(); 31 | }); 32 | }); 33 | }); 34 | 35 | it('should fail to build the project', function(done){ 36 | gulp.start('build'); 37 | 38 | gulp.on('err', function(err) { 39 | assert(err); 40 | }); 41 | gulp.on('stop', function() { 42 | done(); 43 | }); 44 | 45 | }); 46 | 47 | }); 48 | -------------------------------------------------------------------------------- /test/integration/particles-vpc.test.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var exec = require('child_process').exec; 3 | var shared = require('./project-shared'); 4 | 5 | describe('particles-vpc', function(){ 6 | var gulp; 7 | 8 | var projectName = 'particles-vpc'; 9 | 10 | var distributionFiles = [ 11 | '0/particles/cftemplates/subnet.template.json', 12 | '0/particles/cftemplates/vpc.template.json' 13 | ]; 14 | 15 | var config = shared.generateConfig( 16 | projectName, 17 | {distributionFiles:distributionFiles} 18 | ); 19 | 20 | before(function(done) { 21 | var p = exec("npm link ../particles-common-core",{cwd: config.projectConfig.root},done); 22 | }); 23 | 24 | after(function(done) { 25 | var p = exec("npm unlink ../particles-common-core",{cwd: config.projectConfig.root},done); 26 | }); 27 | 28 | shared.shouldBehaveLikeAProject(config); 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /test/integration/project-shared.js: -------------------------------------------------------------------------------- 1 | var AWS = require('aws-sdk'); 2 | var _ = require('lodash'); 3 | var assert = require('assert'); 4 | var async = require('async'); 5 | var clone = require('clone'); 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | 9 | exports.generateConfig = function(projectName,overrides) { 10 | var config = { 11 | tasks: [ 12 | 'build', 13 | 'build:0', 14 | 'clean', 15 | 'default', 16 | 'deploy', 17 | 'deploy:0', 18 | 's3:bucket:ensure:0', 19 | 's3:list', 20 | 's3:objects:write:0' 21 | ], 22 | distributionFiles: [], 23 | projectConfig: { 24 | s3: [ 25 | { 26 | aws: { 27 | region: 'us-east-1', 28 | bucket: '', 29 | }, 30 | validate: false, 31 | create: false 32 | } 33 | ], 34 | projectName: projectName, 35 | root: path.join('test','fixtures','projects',projectName), 36 | taskPrefix: '', 37 | dist: path.join('test','dist',projectName) 38 | } 39 | }; 40 | return _.merge(config,overrides); 41 | }; 42 | 43 | exports.shouldBehaveLikeAProject = function(options){ 44 | var gulp = options.gulp; 45 | 46 | before('check for AWS credentials', function(cb) { 47 | var self = this; 48 | var awsCreds = new AWS.CredentialProviderChain(); 49 | awsCreds.resolve(function(err) { 50 | if (!err) { 51 | process.env.FORCE_VALIDATE=true 52 | } 53 | cb(); 54 | }); 55 | }); 56 | 57 | beforeEach('create new gulp object', function() { 58 | gulp = clone(require('gulp')); 59 | require('../../').buildTasks( 60 | gulp, 61 | options.projectConfig 62 | ); 63 | }); 64 | 65 | after('clean the project', function(done) { 66 | var afterGulp = clone(require('gulp')); 67 | require('../../').buildTasks(afterGulp,options.projectConfig); 68 | afterGulp.start('clean'); 69 | afterGulp.on('stop',function(){ 70 | fs.lstat(options.projectConfig.dist, function(err, stats) { 71 | assert(err); 72 | done(); 73 | }); 74 | }); 75 | }); 76 | 77 | 78 | options.tasks.forEach(function(task) { 79 | it('should have a task named \''+task+'\'', function(done){ 80 | assert(_.indexOf(_.keys(gulp.tasks),task)>=0); 81 | done(); 82 | }); 83 | }); 84 | 85 | 86 | 87 | it('should build the project', function(done){ 88 | gulp.start('build'); 89 | gulp.on('stop',function(){ 90 | async.each( 91 | options.distributionFiles, 92 | function(file,cb) { 93 | fs.lstat(path.join(options.projectConfig.dist,file), function(err, stats) { 94 | assert.ifError(err); 95 | 96 | fs.readFile(path.join('test','fixtures','projects_output',options.projectConfig.projectName,file), function(err, validate) { 97 | assert.ifError(err); 98 | var source = fs.readFileSync(path.join(options.projectConfig.dist,file)); 99 | var isJson = true; 100 | try { 101 | source = JSON.parse(source); 102 | } 103 | catch(e) { 104 | isJson = false; 105 | } 106 | 107 | if (isJson) { 108 | assert.deepEqual(source,JSON.parse(validate)); 109 | } 110 | else { 111 | assert.equal(source.toString(),validate.toString()); 112 | } 113 | cb(); 114 | }); 115 | }); 116 | }, 117 | done 118 | ); 119 | }); 120 | }); 121 | 122 | }; 123 | -------------------------------------------------------------------------------- /test/integration/projectA.test.js: -------------------------------------------------------------------------------- 1 | var shared = require('./project-shared'); 2 | 3 | 4 | describe('projectA', function(){ 5 | 6 | var projectName = 'projectA'; 7 | 8 | var distributionFiles = [ 9 | '0/particles/cftemplates/vpc.template.json', 10 | '0/particles/cftemplates/subnet.template.json', 11 | '0/particles/cftemplates/infra.template.json' 12 | ]; 13 | 14 | var config = shared.generateConfig( 15 | projectName, 16 | {distributionFiles:distributionFiles} 17 | ); 18 | 19 | shared.shouldBehaveLikeAProject(config); 20 | 21 | }); 22 | -------------------------------------------------------------------------------- /test/integration/projectB-config2.test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var exec = require('child_process').exec; 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var shared = require('./project-shared'); 6 | 7 | var projectName = 'projectB-config2'; 8 | 9 | 10 | describe('projectB-config2', function(){ 11 | 12 | var config = shared.generateConfig( 13 | projectName, 14 | { 15 | distributionFiles: [ 16 | '0/testing-path/particles/cftemplates/instance.template.json' 17 | ], 18 | projectConfig: { 19 | root: path.join('test','fixtures','projects','projectB'), 20 | s3: [ 21 | { 22 | aws: { 23 | region: 'us-east-1', 24 | bucket: '', 25 | }, 26 | validate: false, 27 | create: false, 28 | prefix: 'testing-path' 29 | } 30 | ] 31 | } 32 | } 33 | ); 34 | 35 | before(function(done) { 36 | var pA = exec("npm link ../projectA",{cwd: config.projectConfig.root},done); 37 | }); 38 | 39 | after(function(done) { 40 | var pA = exec("npm unlink ../projectA",{cwd: config.projectConfig.root},done); 41 | }); 42 | 43 | shared.shouldBehaveLikeAProject(config); 44 | 45 | it("should prefix all file paths with 'testing-path' for the distribution", function(done){ 46 | fs.lstat(path.join('./',config.projectConfig.dist,'0','testing-path'), function(err, stats) { 47 | assert((!err && stats.isDirectory()),err); 48 | done(); 49 | }); 50 | }); 51 | 52 | }); 53 | -------------------------------------------------------------------------------- /test/integration/projectB.test.js: -------------------------------------------------------------------------------- 1 | var exec = require('child_process').exec; 2 | var shared = require('./project-shared'); 3 | 4 | 5 | describe('projectB', function(){ 6 | 7 | var projectName = 'projectB'; 8 | 9 | var distributionFiles = [ 10 | '0/particles/cftemplates/instance.template.json', 11 | '0/particles/assets/bootstrap.sh', 12 | '0/particles/assets/download.sh', 13 | '0/node_modules/projectA/particles/cftemplates/vpc.template.json' 14 | ]; 15 | 16 | var config = shared.generateConfig( 17 | projectName, 18 | { 19 | tasks: ['build:east', 'deploy:east'], 20 | distributionFiles:distributionFiles, 21 | projectConfig: { 22 | s3: [ 23 | { 24 | aws: { 25 | region: 'us-east-1', 26 | bucket: '', 27 | }, 28 | labels: ['east'], 29 | validate: false, 30 | create: false 31 | } 32 | ] 33 | } 34 | } 35 | ); 36 | 37 | before(function(done) { 38 | var pA = exec("npm link ../projectA",{cwd: config.projectConfig.root},done); 39 | }); 40 | 41 | after(function(done) { 42 | var pA = exec("npm unlink ../projectA",{cwd: config.projectConfig.root},done); 43 | }); 44 | 45 | shared.shouldBehaveLikeAProject(config); 46 | 47 | }); 48 | -------------------------------------------------------------------------------- /test/integration/projectC.test.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var assert = require('assert'); 3 | var async = require('async'); 4 | var clone = require('clone'); 5 | var exec = require('child_process').exec; 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | var semver = require('semver'); 9 | var shared = require('./project-shared'); 10 | 11 | describe('projectC', function(){ 12 | var projectName = 'projectC'; 13 | 14 | var config = shared.generateConfig( 15 | projectName, 16 | {distributionFiles:distributionFiles} 17 | ); 18 | 19 | var distributionFiles = [ 20 | 'particles/cftemplates/proj.template', 21 | 'node_modules/projectB/particles/assets/bootstrap.sh', 22 | 'node_modules/projectB/particles/assets/download.sh' 23 | ]; 24 | 25 | if (semver.gte(process.versions.node,"5.0.0")) { 26 | distributionFiles.push('node_modules/projectA/particles/cftemplates/vpc.template'); 27 | } 28 | else { 29 | distributionFiles.push('node_modules/projectB/node_modules/projectA/particles/cftemplates/vpc.template'); 30 | } 31 | 32 | before(function(done) { 33 | var pB = exec("npm install",{cwd: config.projectConfig.root},done); 34 | }); 35 | 36 | after(function(done) { 37 | exec("rm -rf node_modules/*",{cwd: config.projectConfig.root},done); 38 | }); 39 | 40 | shared.shouldBehaveLikeAProject(config); 41 | 42 | }); 43 | -------------------------------------------------------------------------------- /test/integration/sam.test.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var assert = require('assert'); 3 | var async = require('async'); 4 | var clone = require('clone'); 5 | var exec = require('child_process').exec; 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | var semver = require('semver'); 9 | var shared = require('./project-shared'); 10 | 11 | describe('sam', function(){ 12 | var projectName = 'sam'; 13 | 14 | var config = shared.generateConfig( 15 | projectName, 16 | {distributionFiles:distributionFiles} 17 | ); 18 | 19 | var distributionFiles = [ 20 | 'particles/cftemplates/sam.template.json' 21 | ]; 22 | 23 | shared.shouldBehaveLikeAProject(config); 24 | 25 | it("set the transform on the template", function(done){ 26 | var template = require(path.join('../../',config.projectConfig.dist,'0','particles','cftemplates','sam.template.json')) 27 | assert(template.Transform,"AWS::Serverless-2016-10-31") 28 | done(); 29 | }); 30 | 31 | }); 32 | -------------------------------------------------------------------------------- /test/unit/handlebars-helpers/concat.test.js: -------------------------------------------------------------------------------- 1 | var concat = require('../../../lib/handlebars-helpers/concat'), 2 | assert = require('assert'); 3 | 4 | describe('concat', function() { 5 | it('should concatenate strings', function() { 6 | var string = concat('one','two',{}); 7 | assert.equal(string,'onetwo'); 8 | }); 9 | 10 | it('should concatenate strings with a separator', function() { 11 | var string = concat('one','two',{hash: {separator: "-"}}); 12 | assert.equal(string,'one-two'); 13 | }); 14 | 15 | it('should return with a single string', function() { 16 | var string = concat('one',{}); 17 | assert.equal(string,'one'); 18 | }); 19 | 20 | it('should return an empty string if no strings are passed', function() { 21 | var string = concat({}); 22 | assert.equal(string,''); 23 | }); 24 | 25 | }); 26 | -------------------------------------------------------------------------------- /test/unit/handlebars-helpers/stringify.test.js: -------------------------------------------------------------------------------- 1 | var stringify = require('../../../lib/handlebars-helpers/stringify'), 2 | assert = require('assert'); 3 | 4 | describe('stringify', function() { 5 | it('should JSON.stringify a string', function() { 6 | var string = stringify('${/^"',{hash:{}}); 7 | assert.equal(string,'"${/^\\""'); 8 | }); 9 | 10 | }); 11 | -------------------------------------------------------------------------------- /test/unit/load.test.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'), 2 | clone = require('clone'), 3 | assert = require('assert'), 4 | gulp = clone(require('gulp')); 5 | 6 | require('../../').buildTasks(gulp); 7 | 8 | describe('load', function(done){ 9 | it('should populate gulp tasks with correct prefix', function(tDone){ 10 | assert(_.keys(gulp.tasks).length); 11 | _.each(_.keys(gulp.tasks),function(taskName) { 12 | assert(taskName.match(/^condensation:/)); 13 | }); 14 | tDone(); 15 | }); 16 | }); 17 | 18 | -------------------------------------------------------------------------------- /test/unit/loaders/front-loader.test.js: -------------------------------------------------------------------------------- 1 | var ParticleLoader = require("../../../lib/condensation/loaders/particle-loader"); 2 | var frontLoader = require("../../../lib/condensation/loaders/front-loader"); 3 | var assert = require("assert"); 4 | var path = require("path"); 5 | 6 | describe('frontLoader', function(){ 7 | 8 | it("should run a loader", function(done) { 9 | var particleLoader = new ParticleLoader({root:path.join('test','fixtures','projectC')}); 10 | var processCwd = process.cwd(); 11 | 12 | var templateData = { 13 | }; 14 | var loaderConfig = { 15 | loader: "testLoader", 16 | _file: { 17 | path: path.join(processCwd,"test","fixtures","projects","projectC","particles","cftemplates","fake.template") 18 | } 19 | }; 20 | 21 | frontLoader.call({particleLoader: particleLoader}, loaderConfig, templateData, done); 22 | 23 | }) 24 | 25 | }); 26 | -------------------------------------------------------------------------------- /test/unit/loaders/particle-loader.test.js: -------------------------------------------------------------------------------- 1 | var ParticleLoader = require('../../../lib/condensation/loaders/particle-loader'), 2 | assert = require("assert"), 3 | async = require('async'), 4 | path = require('path'); 5 | 6 | describe('genParticlePaths', function(){ 7 | 8 | var processCwd = process.cwd(); 9 | 10 | async.each([ 11 | { 12 | description: 'should generate paths for an asset', 13 | particle: { 14 | modulePath: path.join(processCwd,'test','fixtures','projectC') 15 | }, 16 | type: 'asset', 17 | particlePath: 'my_asset.thing', 18 | expected: { 19 | path: path.join(processCwd,'test','fixtures','projectC','particles','assets','my_asset.thing'), 20 | urlPath: 'particles/assets/my_asset.thing' 21 | } 22 | }, 23 | { 24 | description: 'should generate paths for a template', 25 | particle: { 26 | modulePath: path.join(processCwd,'test','fixtures','projectC') 27 | }, 28 | type: 'template', 29 | particlePath: 'my.template.json', 30 | expected: { 31 | path: path.join(processCwd,'test','fixtures','projectC','particles','cftemplates','my.template.json'), 32 | urlPath: 'particles/cftemplates/my.template.json' 33 | } 34 | } 35 | ], function(config){ 36 | 37 | it(config.description, function(done){ 38 | 39 | //Arrange 40 | var options = { 41 | parentFile: "" 42 | }; 43 | var particleLoader = new ParticleLoader({root:path.join('test','fixtures','projectC')}); 44 | 45 | //Act 46 | var result = particleLoader.genParticlePaths(config.particle,config.type,config.particlePath); 47 | 48 | //Assert 49 | assert.equal(result.path, config.expected.path); 50 | assert.equal(result.urlPath, config.expected.urlPath); 51 | done(); 52 | }); 53 | 54 | }); 55 | 56 | 57 | 58 | 59 | }); 60 | -------------------------------------------------------------------------------- /test/unit/template-helpers/arrayify.test.js: -------------------------------------------------------------------------------- 1 | var arrayify = require('../../../lib/condensation/template-helpers/arrayify'), 2 | assert = require('assert'); 3 | 4 | describe('arrayify', function() { 5 | it('should accept two strings', function() { 6 | var string = arrayify('one','two',{}); 7 | assert.equal(string,'["one","two"]'); 8 | }); 9 | 10 | it('should recgonize objects', function() { 11 | var string = arrayify('one','two','{"Ref": "Parameter1"}', {}); 12 | assert.equal(string,'["one","two",{"Ref": "Parameter1"}]'); 13 | }); 14 | 15 | it('should return with a single string', function() { 16 | var string = arrayify('one',{}); 17 | assert.equal(string,'["one"]'); 18 | }); 19 | 20 | it('should not double quote strings', function() { 21 | var string = arrayify('"one"',{}); 22 | assert.equal(string,'["one"]'); 23 | }); 24 | 25 | 26 | it('should return an empty array if no strings are passed', function() { 27 | var string = arrayify({}); 28 | assert.equal(string,'[]'); 29 | }); 30 | 31 | }); 32 | -------------------------------------------------------------------------------- /test/unit/template-helpers/assetPath.test.js: -------------------------------------------------------------------------------- 1 | var ParticleLoader = require('../../../lib/condensation/loaders/particle-loader'), 2 | assert = require("assert"), 3 | async = require('async'), 4 | helper = require('../../../lib/condensation/template-helpers/assetPath'), 5 | path = require('path'); 6 | 7 | 8 | 9 | describe('assetPath', function(){ 10 | 11 | /* 12 | * particlePath - As would be written in the template 13 | * filePath - Relative filesystem path to the file 14 | * prefix - prefix for the key path as given in condensation config 15 | */ 16 | 17 | async.each([ 18 | { 19 | description: 'should resolve path with no prefix', 20 | particlePath: 'bootstrap.sh', 21 | filePath: path.join('test','fixtures','projects','projectB','particles','cftemplates','fake.template'), 22 | prefix: '', 23 | expected: 'particles/assets/bootstrap.sh' 24 | }, 25 | { 26 | description: 'should resolve path with prefix', 27 | particlePath: 'bootstrap.sh', 28 | filePath: path.join('test','fixtures','projects','projectB','particles','cftemplates','fake.template'), 29 | prefix: 'aNewPrefix', 30 | expected: 'aNewPrefix/particles/assets/bootstrap.sh' 31 | } 32 | ], function(config){ 33 | 34 | it(config.description, function(done){ 35 | //Arrange 36 | var hOpts = { 37 | data: { 38 | _file: { 39 | path: config.filePath, 40 | }, 41 | root: { 42 | s3: { 43 | prefix: config.prefix 44 | } 45 | } 46 | }, 47 | hash: {protocol: config.protocol} 48 | }; 49 | var cOpts = { 50 | particleLoader: new ParticleLoader({root:path.join('test','fixtures','projects','projectB')}) 51 | }; 52 | 53 | 54 | 55 | //Act 56 | var result = helper.helper.apply(root, [null, config.particlePath, null, hOpts, cOpts]); 57 | 58 | //Assert 59 | assert.equal(result, config.expected); 60 | done(); 61 | }); 62 | 63 | }); 64 | 65 | }); 66 | -------------------------------------------------------------------------------- /test/unit/template-helpers/assetS3Url.test.js: -------------------------------------------------------------------------------- 1 | var ParticleLoader = require('../../../lib/condensation/loaders/particle-loader'), 2 | assert = require("assert"), 3 | async = require('async'), 4 | helper = require('../../../lib/condensation/template-helpers/assetS3Url'), 5 | path = require('path'); 6 | 7 | 8 | 9 | describe('assetS3Url', function(){ 10 | 11 | /* 12 | * particlePath - As would be written in the template 13 | * filePath - Relative filesystem path to the file 14 | * protocol - https | s3 15 | */ 16 | 17 | async.each([ 18 | { 19 | description: 'should resolve https path', 20 | particlePath: 'bootstrap.sh', 21 | filePath: path.join('test','fixtures','projects','projectB','particles','cftemplates','fake.template'), 22 | protocol: 'https', 23 | expected: 'https://s3-eu-west-1.amazonaws.com/bucket/particles/assets/bootstrap.sh' 24 | }, 25 | { 26 | description: 'should resolve s3 path', 27 | particlePath: 'bootstrap.sh', 28 | filePath: path.join('test','fixtures','projects','projectB','particles','cftemplates','fake.template'), 29 | protocol: 's3', 30 | expected: 's3://bucket/particles/assets/bootstrap.sh' 31 | }, 32 | { 33 | description: 'should default to https if no protocol specified', 34 | particlePath: 'bootstrap.sh', 35 | filePath: path.join('test','fixtures','projects','projectB','particles','cftemplate','fake.template'), 36 | protocol: null, 37 | expected: 'https://s3-eu-west-1.amazonaws.com/bucket/particles/assets/bootstrap.sh' 38 | } 39 | ], function(config){ 40 | 41 | it(config.description, function(done){ 42 | //Arrange 43 | var hOpts = { 44 | data: { 45 | _file: { 46 | path: config.filePath, 47 | }, 48 | root: { 49 | s3: { 50 | condensationUrl: { 51 | protocol: 'https:', 52 | slashes: true, 53 | auth: null, 54 | host: 's3-eu-west-1.amazonaws.com', 55 | port: null, 56 | hostname: 's3-eu-west-1.amazonaws.com', 57 | hash: null, 58 | search: null, 59 | query: null, 60 | pathname: '/bucket', 61 | path: '/bucket/', 62 | href: 'https://s3-eu-west-1.amazonaws.com/bucket' 63 | } 64 | } 65 | } 66 | }, 67 | hash: {protocol: config.protocol} 68 | }; 69 | var cOpts = { 70 | particleLoader: new ParticleLoader({root:path.join('test','fixtures','projects','projectB')}) 71 | }; 72 | 73 | 74 | 75 | //Act 76 | var result = helper.helper.apply(root, [null, config.particlePath, null, hOpts, cOpts]); 77 | 78 | //Assert 79 | assert.equal(result, config.expected); 80 | done(); 81 | }); 82 | 83 | }); 84 | 85 | }); 86 | -------------------------------------------------------------------------------- /test/unit/template-helpers/cValue.test.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"); 2 | var cValue = require("../../../lib/condensation/template-helpers/cValue"); 3 | 4 | describe("helpers", function() { 5 | describe("cValue", function() { 6 | 7 | it("should return a string that is json withough quotes", function() { 8 | var result = cValue('{"Ref": "LogicalId"}'); 9 | assert.deepEqual(result,'{"Ref": "LogicalId"}'); 10 | }); 11 | 12 | it("should return a string that is not json with quotes", function() { 13 | var result = cValue('LogicalId'); 14 | assert.deepEqual(result,'"LogicalId"'); 15 | }); 16 | 17 | it.skip("should convert objects with a prototype to strings with quotes", function() { 18 | var date = new Date(); 19 | var result = cValue(date); 20 | assert.equal(result,'"'+date+'"'); 21 | }); 22 | 23 | it("should handle undefined values as empty strings", function() { 24 | var result = cValue(undefined); 25 | assert.equal(result,'""'); 26 | }); 27 | 28 | it("should treat a string with a number as a string by default ", function() { 29 | var result = cValue("30"); 30 | assert.equal(result,'"30"'); 31 | }); 32 | 33 | it("will treat a number as a number", function() { 34 | var result = cValue(30); 35 | assert.equal(result,30); 36 | }); 37 | 38 | it("will force a string to a number", function() { 39 | var result = cValue("30", {hash:{forceNumber:true}}); 40 | assert.equal(result,30); 41 | }); 42 | 43 | }) 44 | }); 45 | 46 | -------------------------------------------------------------------------------- /test/unit/template-helpers/functions/fnAnd.test.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"); 2 | var fnAnd = require("../../../../lib/condensation/template-helpers/functions/fnAnd"); 3 | 4 | describe("helpers", function() { 5 | describe("fnAnd", function() { 6 | 7 | it("should accept a condition", function() { 8 | var result = fnAnd('{"Condition": "TestCondition"}',{}); 9 | assert.deepEqual( 10 | JSON.parse(result), 11 | { 12 | "Fn::And": [ 13 | { 14 | "Condition": "TestCondition" 15 | } 16 | ] 17 | } 18 | ) 19 | }); 20 | 21 | it("should accept 2 conditions", function() { 22 | var result = fnAnd('{"Condition": "TestCondition"}','{"Condition": "TestCondition2"}',{}); 23 | assert.deepEqual( 24 | JSON.parse(result), 25 | { 26 | "Fn::And": [ 27 | {"Condition": "TestCondition"}, 28 | {"Condition": "TestCondition2"} 29 | ] 30 | } 31 | ) 32 | }); 33 | 34 | it("should convert a string to a condition reference", function() { 35 | var result = fnAnd("TestCondition",'{"Condition": "TestCondition2"}',{}); 36 | assert.deepEqual( 37 | JSON.parse(result), 38 | { 39 | "Fn::And": [ 40 | {"Condition": "TestCondition"}, 41 | {"Condition": "TestCondition2"} 42 | ] 43 | } 44 | ) 45 | }); 46 | 47 | it("should convert all strings to a condition reference", function() { 48 | var result = fnAnd("TestCondition", "TestCondition2",{}); 49 | assert.deepEqual( 50 | JSON.parse(result), 51 | { 52 | "Fn::And": [ 53 | {"Condition": "TestCondition"}, 54 | {"Condition": "TestCondition2"} 55 | ] 56 | } 57 | ) 58 | }); 59 | 60 | it("should convert the last string to a condition reference", function() { 61 | var result = fnAnd('{"Condition": "TestCondition"}',"TestCondition2",{}); 62 | assert.deepEqual( 63 | JSON.parse(result), 64 | { 65 | "Fn::And": [ 66 | {"Condition": "TestCondition"}, 67 | {"Condition": "TestCondition2"} 68 | ] 69 | } 70 | ) 71 | }); 72 | 73 | it("should accept 3 conditions", function() { 74 | var result = fnAnd('{"Condition": "TestCondition"}',"TestCondition2","TestCondition3",{}); 75 | assert.deepEqual( 76 | JSON.parse(result), 77 | { 78 | "Fn::And": [ 79 | {"Condition": "TestCondition"}, 80 | {"Condition": "TestCondition2"}, 81 | {"Condition": "TestCondition3"} 82 | ] 83 | } 84 | ) 85 | }); 86 | 87 | 88 | }) 89 | }); 90 | 91 | -------------------------------------------------------------------------------- /test/unit/template-helpers/functions/fnBase64.test.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"); 2 | var fnBase64 = require("../../../../lib/condensation/template-helpers/functions/fnBase64"); 3 | 4 | describe("helpers", function() { 5 | describe("fnBase64", function() { 6 | 7 | it("should accept a string", function() { 8 | var result = fnBase64("a string",{}); 9 | assert.deepEqual(JSON.parse(result), {"Fn::Base64": "a string"}) 10 | }); 11 | 12 | it("should accept a ref", function() { 13 | var result = fnBase64('{"Ref": "SomethingElse"}',{}); 14 | assert.deepEqual( 15 | JSON.parse(result), 16 | {"Fn::Base64": {"Ref": "SomethingElse"}} 17 | ) 18 | }); 19 | 20 | }) 21 | }); 22 | 23 | -------------------------------------------------------------------------------- /test/unit/template-helpers/functions/fnEquals.test.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"); 2 | var fnEquals = require("../../../../lib/condensation/template-helpers/functions/fnEquals"); 3 | 4 | describe("helpers", function() { 5 | describe("fnEquals", function() { 6 | 7 | it("should accept two strings", function() { 8 | var result = fnEquals("a string","a string"); 9 | assert.deepEqual( 10 | JSON.parse(result), 11 | {"Fn::Equals": ["a string", "a string"]} 12 | ) 13 | }); 14 | 15 | it("should accept a ref", function() { 16 | var result = fnEquals("a string",'{"Ref": "SomethingElse"}',{}); 17 | assert.deepEqual( 18 | JSON.parse(result), 19 | {"Fn::Equals": ["a string", {"Ref": "SomethingElse"}]} 20 | ) 21 | }); 22 | 23 | }) 24 | }); 25 | 26 | -------------------------------------------------------------------------------- /test/unit/template-helpers/functions/fnFindInMap.test.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"); 2 | var fnFindInMap = require("../../../../lib/condensation/template-helpers/functions/fnFindInMap"); 3 | 4 | describe("helpers", function() { 5 | describe("fnFindInMap", function() { 6 | 7 | it("should work with all strings", function() { 8 | var result = fnFindInMap("MapName","TopLevelKey","SecondLevelKey"); 9 | assert.deepEqual( 10 | JSON.parse(result), 11 | {"Fn::FindInMap": ["MapName", "TopLevelKey", "SecondLevelKey"]} 12 | ) 13 | }); 14 | 15 | it("should work with a ref", function() { 16 | var result = fnFindInMap("MapName",'{"Ref": "TopLevelKey"}',"SecondLevelKey"); 17 | assert.deepEqual( 18 | JSON.parse(result), 19 | {"Fn::FindInMap": ["MapName", {"Ref": "TopLevelKey"}, "SecondLevelKey"]} 20 | ) 21 | }); 22 | 23 | }) 24 | }); 25 | 26 | -------------------------------------------------------------------------------- /test/unit/template-helpers/functions/fnGetAZs.test.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"); 2 | var fnGetAZs = require("../../../../lib/condensation/template-helpers/functions/fnGetAZs"); 3 | 4 | describe("helpers", function() { 5 | describe("fnGetAZs", function() { 6 | 7 | it("accept a region as a string", function() { 8 | var result = fnGetAZs("us-east-1"); 9 | assert.deepEqual( 10 | JSON.parse(result), 11 | {"Fn::GetAZs": "us-east-1"} 12 | ) 13 | }); 14 | 15 | it("accept the region psuedo parameter", function() { 16 | var result = fnGetAZs('{"Ref": "AWS::Region"}'); 17 | assert.deepEqual( 18 | JSON.parse(result), 19 | {"Fn::GetAZs": { 20 | "Ref": "AWS::Region" 21 | }} 22 | ) 23 | }); 24 | 25 | }) 26 | }); 27 | 28 | -------------------------------------------------------------------------------- /test/unit/template-helpers/functions/fnGetArtifactAtt.test.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"); 2 | var fnGetArtifactAtt = require("../../../../lib/condensation/template-helpers/functions/fnGetArtifactAtt"); 3 | 4 | describe("helpers", function() { 5 | describe("fnGetArtifactAtt", function() { 6 | 7 | it("should work with all strings", function() { 8 | var result = fnGetArtifactAtt("ArtifactName","AttributeName"); 9 | assert.deepEqual( 10 | JSON.parse(result), 11 | {"Fn::GetArtifactAtt": ["ArtifactName", "AttributeName"]} 12 | ) 13 | }); 14 | 15 | it("should work with a ref", function() { 16 | var result = fnGetArtifactAtt("ArtifactName",'{"Ref": "AttributeName"}'); 17 | assert.deepEqual( 18 | JSON.parse(result), 19 | {"Fn::GetArtifactAtt": ["ArtifactName", {"Ref": "AttributeName"}]} 20 | ) 21 | }); 22 | 23 | }) 24 | }); 25 | 26 | -------------------------------------------------------------------------------- /test/unit/template-helpers/functions/fnGetAtt.test.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"); 2 | var fnGetAtt = require("../../../../lib/condensation/template-helpers/functions/fnGetAtt"); 3 | 4 | describe("helpers", function() { 5 | describe("fnGetAtt", function() { 6 | 7 | it("should build the function definition", function() { 8 | var result = fnGetAtt("LogicalId","AttributeName"); 9 | assert.deepEqual( 10 | JSON.parse(result), 11 | {"Fn::GetAtt": ["LogicalId", "AttributeName"]} 12 | ) 13 | }); 14 | 15 | }) 16 | }); 17 | 18 | -------------------------------------------------------------------------------- /test/unit/template-helpers/functions/fnGetParam.test.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"); 2 | var fnGetParam = require("../../../../lib/condensation/template-helpers/functions/fnGetParam"); 3 | 4 | describe("helpers", function() { 5 | describe("fnGetParam", function() { 6 | 7 | it("should work with all strings", function() { 8 | var result = fnGetParam("ArtifactName","JSONFileName","KeyName"); 9 | assert.deepEqual( 10 | JSON.parse(result), 11 | {"Fn::GetParam": ["ArtifactName", "JSONFileName", "KeyName"]} 12 | ) 13 | }); 14 | 15 | it("should work with a ref", function() { 16 | var result = fnGetParam("ArtifactName",'{"Ref": "JSONFileName"}',"KeyName"); 17 | assert.deepEqual( 18 | JSON.parse(result), 19 | {"Fn::GetParam": ["ArtifactName", {"Ref": "JSONFileName"}, "KeyName"]} 20 | ) 21 | }); 22 | 23 | }) 24 | }); 25 | 26 | -------------------------------------------------------------------------------- /test/unit/template-helpers/functions/fnIf.test.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"); 2 | var fnIf = require("../../../../lib/condensation/template-helpers/functions/fnIf"); 3 | 4 | describe("helpers", function() { 5 | describe("fnIf", function() { 6 | 7 | it("accepts strings", function() { 8 | var result = fnIf("ConditionName","TrueValue","FalseValue"); 9 | assert.deepEqual( 10 | JSON.parse(result), 11 | {"Fn::If": ["ConditionName", "TrueValue", "FalseValue"]} 12 | ) 13 | }); 14 | 15 | it("accepts a ref for the trueValue", function() { 16 | var result = fnIf("ConditionName", {"Ref": "TrueValue"}, "FalseValue"); 17 | assert.deepEqual( 18 | JSON.parse(result), 19 | {"Fn::If": ["ConditionName", {"Ref": "TrueValue"}, "FalseValue"]} 20 | ) 21 | }); 22 | 23 | it("accepts a ref for the falseValue", function() { 24 | var result = fnIf("ConditionName", {"Ref": "TrueValue"}, {"Ref": "AWS::NoValue"}); 25 | assert.deepEqual( 26 | JSON.parse(result), 27 | {"Fn::If": ["ConditionName", {"Ref": "TrueValue"}, {"Ref": "AWS::NoValue"}]} 28 | ) 29 | }); 30 | 31 | }) 32 | }); 33 | 34 | -------------------------------------------------------------------------------- /test/unit/template-helpers/functions/fnImportValue.test.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"); 2 | var fnImportValue = require("../../../../lib/condensation/template-helpers/functions/fnImportValue"); 3 | 4 | describe("helpers", function() { 5 | describe("fnImportValue", function() { 6 | 7 | it("accepts shared value name", function() { 8 | var result = fnImportValue("SharedValue"); 9 | assert.deepEqual( 10 | JSON.parse(result), 11 | {"Fn::ImportValue": "SharedValue"} 12 | ) 13 | }); 14 | 15 | it("accepts shared value ref object", function() { 16 | var result = fnImportValue('{"Ref": "SharedValue"}'); 17 | assert.deepEqual( 18 | JSON.parse(result), 19 | {"Fn::ImportValue": {"Ref": "SharedValue"}} 20 | ) 21 | }); 22 | 23 | }) 24 | }); 25 | 26 | -------------------------------------------------------------------------------- /test/unit/template-helpers/functions/fnJoin.test.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"); 2 | var fnJoin = require("../../../../lib/condensation/template-helpers/functions/fnJoin"); 3 | 4 | describe("helpers", function() { 5 | describe("fnJoin", function() { 6 | 7 | it("accepts one value as the full array", function() { 8 | var result = fnJoin(",", '["one","two"]', {}); 9 | assert.deepEqual( 10 | JSON.parse(result), 11 | {"Fn::Join": [",", ["one", "two"]]} 12 | ) 13 | }); 14 | 15 | it("accepts one value as the full array that is a ref", function() { 16 | var result = fnJoin(",", {"Ref": "SomeParam"}, {}); 17 | assert.deepEqual( 18 | JSON.parse(result), 19 | {"Fn::Join": [",", {"Ref": "SomeParam"}]} 20 | ) 21 | }); 22 | 23 | it("accepts two values", function() { 24 | var result = fnJoin(",", "Value1", "Value2", {}); 25 | assert.deepEqual( 26 | JSON.parse(result), 27 | {"Fn::Join": [",", ["Value1", "Value2"]]} 28 | ) 29 | }); 30 | 31 | it("accepts three values", function() { 32 | var result = fnJoin(",", "Value1", "Value2", "Value3", {}); 33 | assert.deepEqual( 34 | JSON.parse(result), 35 | {"Fn::Join": [",", ["Value1", "Value2", "Value3"]]} 36 | ) 37 | }); 38 | 39 | it("accepts a value that is a ref", function() { 40 | var result = fnJoin(",", "Value1", {"Ref": "Value2"}, "Value3", {}); 41 | assert.deepEqual( 42 | JSON.parse(result), 43 | {"Fn::Join": [",", ["Value1", {"Ref": "Value2"}, "Value3"]]} 44 | ) 45 | }); 46 | 47 | }) 48 | }); 49 | 50 | -------------------------------------------------------------------------------- /test/unit/template-helpers/functions/fnNot.test.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"); 2 | var fnNot = require("../../../../lib/condensation/template-helpers/functions/fnNot"); 3 | 4 | describe("helpers", function() { 5 | describe("fnNot", function() { 6 | 7 | it("accepts a condition", function() { 8 | var result = fnNot('{"Condition": "TestCondition"}', {}); 9 | assert.deepEqual( 10 | JSON.parse(result), 11 | {"Fn::Not": [{"Condition": "TestCondition"}]} 12 | ) 13 | }); 14 | 15 | it("accepts condition name as a string", function() { 16 | var result = fnNot("TestCondition", {}); 17 | assert.deepEqual( 18 | JSON.parse(result), 19 | {"Fn::Not": [{"Condition": "TestCondition"}]} 20 | ) 21 | }); 22 | 23 | }) 24 | }); 25 | 26 | -------------------------------------------------------------------------------- /test/unit/template-helpers/functions/fnOr.test.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"); 2 | var fnOr = require("../../../../lib/condensation/template-helpers/functions/fnOr"); 3 | 4 | describe("helpers", function() { 5 | describe("fnOr", function() { 6 | 7 | it("should accept a condition", function() { 8 | var result = fnOr('{"Condition": "TestCondition"}',{}); 9 | assert.deepEqual( 10 | JSON.parse(result), 11 | { 12 | "Fn::Or": [ 13 | { 14 | "Condition": "TestCondition" 15 | } 16 | ] 17 | } 18 | ) 19 | }); 20 | 21 | it("should accept 2 conditions", function() { 22 | var result = fnOr('{"Condition": "TestCondition"}','{"Condition": "TestCondition2"}',{}); 23 | assert.deepEqual( 24 | JSON.parse(result), 25 | { 26 | "Fn::Or": [ 27 | {"Condition": "TestCondition"}, 28 | {"Condition": "TestCondition2"} 29 | ] 30 | } 31 | ) 32 | }); 33 | 34 | it("should convert a string to a condition reference", function() { 35 | var result = fnOr("TestCondition",'{"Condition": "TestCondition2"}',{}); 36 | assert.deepEqual( 37 | JSON.parse(result), 38 | { 39 | "Fn::Or": [ 40 | {"Condition": "TestCondition"}, 41 | {"Condition": "TestCondition2"} 42 | ] 43 | } 44 | ) 45 | }); 46 | 47 | it("should convert all strings to a condition reference", function() { 48 | var result = fnOr("TestCondition", "TestCondition2",{}); 49 | assert.deepEqual( 50 | JSON.parse(result), 51 | { 52 | "Fn::Or": [ 53 | {"Condition": "TestCondition"}, 54 | {"Condition": "TestCondition2"} 55 | ] 56 | } 57 | ) 58 | }); 59 | 60 | it("should convert the last string to a condition reference", function() { 61 | var result = fnOr('{"Condition": "TestCondition"}',"TestCondition2",{}); 62 | assert.deepEqual( 63 | JSON.parse(result), 64 | { 65 | "Fn::Or": [ 66 | {"Condition": "TestCondition"}, 67 | {"Condition": "TestCondition2"} 68 | ] 69 | } 70 | ) 71 | }); 72 | 73 | it("should accept 3 conditions", function() { 74 | var result = fnOr('{"Condition": "TestCondition"}',"TestCondition2","TestCondition3",{}); 75 | assert.deepEqual( 76 | JSON.parse(result), 77 | { 78 | "Fn::Or": [ 79 | {"Condition": "TestCondition"}, 80 | {"Condition": "TestCondition2"}, 81 | {"Condition": "TestCondition3"} 82 | ] 83 | } 84 | ) 85 | }); 86 | 87 | 88 | }) 89 | }); 90 | 91 | -------------------------------------------------------------------------------- /test/unit/template-helpers/functions/fnSelect.test.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"); 2 | var fnSelect = require("../../../../lib/condensation/template-helpers/functions/fnSelect"); 3 | 4 | describe("helpers", function() { 5 | describe("fnSelect", function() { 6 | 7 | it("accepts two values", function() { 8 | var result = fnSelect(0, "Value1", "Value2", {}); 9 | assert.deepEqual( 10 | JSON.parse(result), 11 | {"Fn::Select": [0, ["Value1", "Value2"]]} 12 | ) 13 | }); 14 | 15 | it("accepts three values", function() { 16 | var result = fnSelect(1, "Value1", "Value2", "Value3", {}); 17 | assert.deepEqual( 18 | JSON.parse(result), 19 | {"Fn::Select": [1, ["Value1", "Value2", "Value3"]]} 20 | ) 21 | }); 22 | 23 | it("accepts a value that is a ref", function() { 24 | var result = fnSelect(2, "Value1", {"Ref": "Value2"}, "Value3", {}); 25 | assert.deepEqual( 26 | JSON.parse(result), 27 | {"Fn::Select": [2, ["Value1", {"Ref": "Value2"}, "Value3"]]} 28 | ) 29 | }); 30 | 31 | }) 32 | }); 33 | -------------------------------------------------------------------------------- /test/unit/template-helpers/functions/fnSplit.test.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"); 2 | var fnSplit = require("../../../../lib/condensation/template-helpers/functions/fnSplit"); 3 | 4 | describe("helpers", function() { 5 | describe("fnSplit", function() { 6 | 7 | it("accepts a delimiter and a string", function() { 8 | var result = fnSplit(":","split:me", {}); 9 | assert.deepEqual( 10 | JSON.parse(result), 11 | {"Fn::Split": [":", "split:me"]} 12 | ) 13 | }); 14 | 15 | it("accepts a delimiter and a ref", function() { 16 | var result = fnSplit(":", '{"Ref": "Parameter1"}',{}); 17 | assert.deepEqual( 18 | JSON.parse(result), 19 | {"Fn::Split": [":", {"Ref": "Parameter1"}]} 20 | ) 21 | }); 22 | 23 | }) 24 | }); 25 | -------------------------------------------------------------------------------- /test/unit/template-helpers/functions/fnSub.test.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"); 2 | var fnSub = require("../../../../lib/condensation/template-helpers/functions/fnSub"); 3 | 4 | describe("helpers", function() { 5 | describe("fnSub", function() { 6 | 7 | it("accepts a string", function() { 8 | var result = fnSub("The Region Is ${AWS::StackName}", {}); 9 | assert.deepEqual( 10 | JSON.parse(result), 11 | {"Fn::Sub": ["The Region Is ${AWS::StackName}", {}]} 12 | ) 13 | }); 14 | 15 | it("accepts a string with a map", function() { 16 | var result = fnSub("The Region Is ${Region}", {hash: {Region: '{"Ref": "AWS::Region"}'}}); 17 | assert.deepEqual( 18 | JSON.parse(result), 19 | {"Fn::Sub": ["The Region Is ${Region}", {"Region": {"Ref": "AWS::Region"}}]} 20 | ) 21 | }); 22 | 23 | it("processed a map with string values", function() { 24 | var result = fnSub("The Region Is ${Region}", {hash: {Region: 'blah blah $"/^'}}); 25 | assert.deepEqual( 26 | JSON.parse(result), 27 | {"Fn::Sub": ["The Region Is ${Region}", {"Region": "blah blah $\"/^"}]} 28 | ) 29 | }); 30 | 31 | }) 32 | }); 33 | -------------------------------------------------------------------------------- /test/unit/template-helpers/functions/ref.test.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"); 2 | var ref = require("../../../../lib/condensation/template-helpers/functions/ref"); 3 | 4 | describe("helpers", function() { 5 | describe("ref", function() { 6 | 7 | it("should accept a string and return it as a Ref", function() { 8 | var result = ref("LogicalId"); 9 | assert.deepEqual(result,'{"Ref": "LogicalId"}'); 10 | }); 11 | 12 | it("should add a prefix if defined by the scope", function() { 13 | var result = ref.apply({logicalIdPrefix: "My"}, ["LogicalId"]); 14 | assert.deepEqual(result,'{"Ref": "MyLogicalId"}'); 15 | }); 16 | 17 | it("should add a suffix if defined by the scope", function() { 18 | var result = ref.apply({logicalIdSuffix: "2"}, ["LogicalId"]); 19 | assert.deepEqual(result,'{"Ref": "LogicalId2"}'); 20 | }); 21 | 22 | it("should add a prefix and a suffix if defined by the scope", function() { 23 | var result = ref.apply({logicalIdPrefix: "My", logicalIdSuffix: "2"}, ["LogicalId"]); 24 | assert.deepEqual(result,'{"Ref": "MyLogicalId2"}'); 25 | }); 26 | 27 | it("should ignore prefix and suffix if scope is false", function() { 28 | var result = ref.apply({logicalIdPrefix: "My", logicalIdSuffix: "2"}, ["LogicalId",{hash: {scope:false}}]); 29 | assert.deepEqual(result,'{"Ref": "LogicalId"}'); 30 | }); 31 | 32 | it("should not scope psuedo parameters that start with AWS::", function() { 33 | var result = ref.apply({logicalIdPrefix: "My", logicalIdSuffix: "2"}, ["AWS::NoValue",{hash: {scope:true}}]); 34 | assert.deepEqual(result,'{"Ref": "AWS::NoValue"}'); 35 | }); 36 | 37 | }) 38 | }); 39 | 40 | -------------------------------------------------------------------------------- /test/unit/template-helpers/objectify.test.js: -------------------------------------------------------------------------------- 1 | var objectify = require('../../../lib/condensation/template-helpers/objectify'), 2 | assert = require('assert'); 3 | 4 | describe('objectify', function() { 5 | 6 | it('should work with multiple parameters', function() { 7 | var string = objectify({hash:{Param1: "Value2", Param2: '{"Ref": "Parameter1"}'}}); 8 | assert.deepEqual(JSON.parse(string),{"Param1": "Value2", "Param2": {"Ref": "Parameter1"}}); 9 | }); 10 | 11 | }); 12 | -------------------------------------------------------------------------------- /test/unit/template-helpers/scopeId.test.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"); 2 | var scopeId = require("../../../lib/condensation/template-helpers/scopeId"); 3 | 4 | describe("helpers", function() { 5 | describe("scopeId", function() { 6 | 7 | it("should do nothing when no prefix or suffix is defined", function() { 8 | var result = scopeId("LogicalId"); 9 | assert.equal(result,"LogicalId"); 10 | }); 11 | 12 | it("should add a prefix", function() { 13 | var result = scopeId.apply({logicalIdPrefix: "My"}, ["LogicalId"]); 14 | assert.equal(result,"MyLogicalId"); 15 | }); 16 | 17 | it("should add a suffix", function() { 18 | var result = scopeId.apply({logicalIdSuffix: "2"}, ["LogicalId"]); 19 | assert.equal(result,"LogicalId2"); 20 | }); 21 | 22 | it("should add a prefix and a suffix", function() { 23 | var result = scopeId.apply({logicalIdPrefix: "My", logicalIdSuffix: "2"}, ["LogicalId"]); 24 | assert.equal(result,"MyLogicalId2"); 25 | }); 26 | 27 | }) 28 | }); 29 | -------------------------------------------------------------------------------- /test/unit/template-helpers/sections/mapping.test.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var assert = require('assert'); 3 | var Condensation = require('../../../../lib/condensation'); 4 | var CPT = require('condensation-particle-tests'); 5 | var path = require('path'); 6 | 7 | var projectDir = 'test/fixtures/projects/particle-builds'; 8 | var distDir = 'test/dist/particle-builds'; 9 | 10 | var projectConfig = { 11 | s3: [ 12 | { 13 | aws: { 14 | region: 'us-east-1', 15 | bucket: '', 16 | }, 17 | validate: false, 18 | create: false 19 | } 20 | ], 21 | projectName: 'particle-builds', 22 | root: projectDir, 23 | taskPrefix: '', 24 | dist: distDir, 25 | }; 26 | 27 | 28 | describe('mapping', function(){ 29 | var condensation = new Condensation(null, projectConfig); 30 | var cTests = new CPT({condensation: condensation}); 31 | 32 | it("creates a mapping", function() { 33 | cTests.testParticle( 34 | "mapping", 35 | "ami", 36 | {Mapping1: {"ap-northeast-1":{"ami":"ami-ab1df7ab"}}}, 37 | {logicalId: "Mapping1"} 38 | ); 39 | }); 40 | 41 | it("works with older full object particle definitions", function() { 42 | cTests.testParticle( 43 | "mapping", 44 | "ami_full_object", 45 | {Mapping1: {"ap-northeast-1":{"ami":"ami-ab1df7ab"}}}, 46 | {logicalId: "Mapping1"} 47 | ); 48 | }); 49 | 50 | }); 51 | -------------------------------------------------------------------------------- /test/unit/template-helpers/sections/parameters.test.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var assert = require('assert'); 3 | var Condensation = require('../../../../lib/condensation'); 4 | var CPT = require('condensation-particle-tests'); 5 | var path = require('path'); 6 | 7 | var projectDir = 'test/fixtures/projects/particle-builds'; 8 | var distDir = 'test/dist/particle-builds'; 9 | 10 | var projectConfig = { 11 | s3: [ 12 | { 13 | aws: { 14 | region: 'us-east-1', 15 | bucket: '', 16 | }, 17 | validate: false, 18 | create: false 19 | } 20 | ], 21 | projectName: 'particle-builds', 22 | root: projectDir, 23 | taskPrefix: '', 24 | dist: distDir, 25 | }; 26 | 27 | 28 | describe('parameters', function(){ 29 | var condensation = new Condensation(null, projectConfig); 30 | 31 | var cTests = new CPT({condensation: condensation}); 32 | 33 | it("creates a parameter", function() { 34 | cTests.testParticle( 35 | "parameter", 36 | "generic", 37 | {Parameter1: {Type: ""}}, 38 | {logicalId: "Parameter1"} 39 | ); 40 | }); 41 | 42 | it("works with older full object particle definitions", function() { 43 | cTests.testParticle( 44 | "parameter", 45 | "full_object", 46 | {Parameter1: {Type: "String"}}, 47 | {logicalId: "Parameter1"} 48 | ); 49 | }); 50 | 51 | it("fails when a parameter has no logicalId", function() { 52 | cTests.testParticle( 53 | "parameter", 54 | "generic", 55 | null, 56 | {expectError: true} 57 | ); 58 | }); 59 | 60 | it("fails when a parameter has invalid JSON", function() { 61 | cTests.testParticle( 62 | "parameter", 63 | "malformed", 64 | null, 65 | {logicalId: "Parameter1", expectError: true} 66 | ); 67 | }); 68 | 69 | it("fails when a parameter extends malformed", function() { 70 | cTests.testParticle( 71 | "parameter", 72 | "extend_malformed", 73 | null, 74 | {logicalId: "Parameter1", expectError: true} 75 | ); 76 | }); 77 | 78 | }); 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /test/unit/template-helpers/sections/sets.test.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var assert = require('assert'); 3 | var Condensation = require('../../../../lib/condensation'); 4 | var CPT = require('condensation-particle-tests'); 5 | var path = require('path'); 6 | 7 | var projectDir = 'test/fixtures/projects/particle-builds'; 8 | var distDir = 'test/dist/particle-builds'; 9 | 10 | var projectConfig = { 11 | s3: [ 12 | { 13 | aws: { 14 | region: 'us-east-1', 15 | bucket: '', 16 | }, 17 | validate: false, 18 | create: false 19 | } 20 | ], 21 | projectName: 'particle-builds', 22 | root: projectDir, 23 | taskPrefix: '', 24 | dist: distDir, 25 | }; 26 | 27 | 28 | describe('sets', function(){ 29 | var condensation = new Condensation(null, projectConfig); 30 | 31 | var cTests = new CPT({condensation: condensation}); 32 | 33 | it("ceates a set", function() { 34 | cTests.testParticle( 35 | "set", 36 | "set1", 37 | require("../../../fixtures/projects_output/particle-builds/sets_output1"), 38 | {logicalId: "Parameter1"} 39 | ); 40 | }); 41 | 42 | }); 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /test/unit/template-helpers/templateS3Url.test.js: -------------------------------------------------------------------------------- 1 | var ParticleLoader = require('../../../lib/condensation/loaders/particle-loader'), 2 | assert = require("assert"), 3 | async = require('async'), 4 | helper = require('../../../lib/condensation/template-helpers/templateS3Url'), 5 | path = require('path'); 6 | 7 | describe('templateS3Url', function(){ 8 | 9 | /* 10 | * particlePath - As would be written in the template 11 | * filePath - Relative filesystem path to the file 12 | * protocol - https | s3 13 | */ 14 | 15 | async.each([ 16 | { 17 | description: 'should resolve http path', 18 | particlePath: 'instance.template.json', 19 | filePath: path.join('test','fixtures','projects','projectB','particles','cftemplates','fake.template'), 20 | expected: 'https://s3-eu-west-1.amazonaws.com/bucket/particles/cftemplates/instance.template.json' 21 | } 22 | ], function(config){ 23 | 24 | it(config.description, function(done){ 25 | 26 | //Arrange 27 | var hOpts = { 28 | data: { 29 | _file: { 30 | path: config.filePath, 31 | }, 32 | root: { 33 | s3: { 34 | condensationUrl: { 35 | protocol: 'https:', 36 | slashes: true, 37 | auth: null, 38 | host: 's3-eu-west-1.amazonaws.com', 39 | port: null, 40 | hostname: 's3-eu-west-1.amazonaws.com', 41 | hash: null, 42 | search: null, 43 | query: null, 44 | pathname: '/bucket', 45 | path: '/bucket/', 46 | href: 'https://s3-eu-west-1.amazonaws.com/bucket' 47 | } 48 | } 49 | } 50 | }, 51 | hash: {protocol: config.protocol} 52 | }; 53 | var cOpts = { 54 | particleLoader: new ParticleLoader({root:path.join('test','fixtures','projects','projectB')}) 55 | }; 56 | 57 | //Act 58 | var result = helper.helper.apply(root, [null, config.particlePath, null, hOpts, cOpts]); 59 | 60 | //Assert 61 | assert.equal(result, config.expected); 62 | done(); 63 | }); 64 | 65 | }); 66 | 67 | 68 | 69 | 70 | }); 71 | --------------------------------------------------------------------------------