UNPKG

@mapbox/cloudfriend

Version:

Helper functions for assembling CloudFormation templates in JavaScript

225 lines (197 loc) 11.2 kB
'use strict'; const test = require('tape'); const cloudfriend = require('..'); const path = require('path'); const expectedTemplate = require('./fixtures/static.json'); const fixtures = path.resolve(__dirname, 'fixtures'); test('intrinsic functions', (assert) => { assert.deepEqual(cloudfriend.base64('secret'), { 'Fn::Base64': 'secret' }, 'base64'); assert.deepEqual(cloudfriend.findInMap('mapping', 'key', 'value'), { 'Fn::FindInMap': ['mapping', 'key', 'value'] }, 'lookup'); assert.deepEqual(cloudfriend.getAtt('obj', 'key'), { 'Fn::GetAtt': ['obj', 'key'] }, 'attr'); assert.deepEqual(cloudfriend.getAzs(), { 'Fn::GetAZs': '' }, 'azs (no value specified)'); assert.deepEqual(cloudfriend.getAzs('us-east-1'), { 'Fn::GetAZs': 'us-east-1' }, 'azs (value specified)'); assert.deepEqual(cloudfriend.join(['abra', 'cadabra']), { 'Fn::Join': ['', ['abra', 'cadabra']] }, 'join (no delimeter specified)'); assert.deepEqual(cloudfriend.join('-', ['abra', 'cadabra']), { 'Fn::Join': ['-', ['abra', 'cadabra']] }, 'join (delimeter specified)'); assert.deepEqual(cloudfriend.select(1, ['abra', 'cadabra']), { 'Fn::Select': ['1', ['abra', 'cadabra']] }, ''); assert.deepEqual(cloudfriend.ref('something'), { Ref: 'something' }, 'ref'); assert.deepEqual(cloudfriend.split(',', 'a,b,c,d'), { 'Fn::Split': [',', 'a,b,c,d'] }, 'split'); assert.deepEqual(cloudfriend.split(',', cloudfriend.ref('Id')), { 'Fn::Split': [',', { Ref: 'Id' }] }, 'split'); assert.deepEqual(cloudfriend.userData(['#!/usr/bin/env bash', 'set -e']), { 'Fn::Base64': { 'Fn::Join': ['\n', ['#!/usr/bin/env bash', 'set -e']] } }, 'userData'); assert.deepEqual(cloudfriend.sub('my ${thing}'), { 'Fn::Sub': 'my ${thing}' }, 'sub without variables'); assert.deepEqual(cloudfriend.sub('my ${thing}', { thing: 'stuff' }), { 'Fn::Sub': ['my ${thing}', { thing: 'stuff' }] }, 'sub with variables'); assert.deepEqual(cloudfriend.importValue('id'), { 'Fn::ImportValue': 'id' }, 'import value with string'); assert.deepEqual(cloudfriend.importValue(cloudfriend.ref('Id')), { 'Fn::ImportValue': cloudfriend.ref('Id') }, 'import value with string'); assert.deepEqual(cloudfriend.arn('s3', 'my-bucket/*'), { 'Fn::Sub': ['arn:${AWS::Partition}:${service}:::${suffix}', { service: 's3', suffix: 'my-bucket/*' }] }, 's3 arn'); assert.deepEqual(cloudfriend.arn('cloudformation', 'stack/my-stack/*'), { 'Fn::Sub': ['arn:${AWS::Partition}:${service}:${AWS::Region}:${AWS::AccountId}:${suffix}', { service: 'cloudformation', suffix: 'stack/my-stack/*' }] }, 'non-s3 arn'); assert.end(); }); test('conditions', (assert) => { assert.deepEqual(cloudfriend.and(['a', 'b']), { 'Fn::And': ['a', 'b'] }, 'and'); assert.deepEqual(cloudfriend.equals('a', 'b'), { 'Fn::Equals': ['a', 'b'] }, 'equal'); assert.deepEqual(cloudfriend.if('condition', 'a', 'b'), { 'Fn::If': ['condition', 'a', 'b'] }, 'if'); assert.deepEqual(cloudfriend.not('condition'), { 'Fn::Not': ['condition'] }, 'not'); assert.deepEqual(cloudfriend.or(['a', 'b']), { 'Fn::Or': ['a', 'b'] }, 'or'); assert.deepEqual(cloudfriend.notEquals('a', 'b'), { 'Fn::Not': [{ 'Fn::Equals': ['a', 'b'] }] }, 'notEqual'); assert.end(); }); test('pseudo', (assert) => { assert.deepEqual(cloudfriend.accountId, { Ref: 'AWS::AccountId' }, 'account'); assert.deepEqual(cloudfriend.notificationArns, { Ref: 'AWS::NotificationARNs' }, 'notificationArns'); assert.deepEqual(cloudfriend.noValue, { Ref: 'AWS::NoValue' }, 'noValue'); assert.deepEqual(cloudfriend.region, { Ref: 'AWS::Region' }, 'region'); assert.deepEqual(cloudfriend.stackId, { Ref: 'AWS::StackId' }, 'stackId'); assert.deepEqual(cloudfriend.stackName, { Ref: 'AWS::StackName' }, 'stack'); assert.deepEqual(cloudfriend.partition, { Ref: 'AWS::Partition' }, 'stack'); assert.deepEqual(cloudfriend.urlSuffix, { Ref: 'AWS::URLSuffix' }, 'stack'); assert.end(); }); test('build', (assert) => { assert.plan(8); cloudfriend.build(path.join(fixtures, 'static.json')) .then((template) => { assert.deepEqual(template, expectedTemplate, 'static.json'); return cloudfriend.build(path.join(fixtures, 'static.js')); }) .then((template) => { assert.deepEqual(template, expectedTemplate, 'static.js'); return cloudfriend.build(path.join(fixtures, 'sync.js')); }) .then((template) => { assert.deepEqual(template, expectedTemplate, 'sync.js'); return cloudfriend.build(path.join(fixtures, 'async.js')); }) .then((template) => { assert.deepEqual(template, expectedTemplate, 'async.js (success)'); return cloudfriend.build(path.join(fixtures, 'async-error.js')) .catch((err) => { assert.ok(err, 'async.js (error)'); }); }) .then(() => { return cloudfriend.build(path.join(fixtures, 'sync-args.js'), { some: 'options' }); }) .then((template) => { assert.deepEqual(template, { some: 'options' }, 'passes args (sync)'); return cloudfriend.build(path.join(fixtures, 'async-args.js'), { some: 'options' }); }) .then((template) => { assert.deepEqual(template, { some: 'options' }, 'passes args (async)'); return cloudfriend.build(path.join(fixtures, 'malformed.json')) .catch((err) => { assert.ok(err, 'malformed JSON (error)'); }); }); }); test('validate', (assert) => { assert.plan(2); cloudfriend.validate(path.join(fixtures, 'static.json')) .then(() => { assert.ok(true, 'valid'); return cloudfriend.validate(path.join(fixtures, 'invalid.json')); }) .catch((err) => { assert.ok(/Template format error: Unrecognized resource type/.test(err.message), 'invalid'); }); }); test('merge', (assert) => { const a = { Metadata: { Instances: { Description: 'Information about the instances' } }, Parameters: { InstanceCount: { Type: 'Number' } }, Mappings: { Region: { 'us-east-1': { AMI: 'ami-123456' } } }, Conditions: { WouldYouLikeBaconWithThat: cloudfriend.equals(cloudfriend.ref('InstanceCount'), 999) }, Resources: { Instance: { Type: 'AWS::EC2::Instance', Properties: { ImageId: cloudfriend.findInMap('Region', cloudfriend.region, 'AMI') } } }, Outputs: { Breakfast: { Condition: 'WouldYouLikeBaconWithThat', Value: cloudfriend.ref('Instance') } } }; let b = { Metadata: { Databases: { Description: 'Information about the databases' } }, Parameters: { DatabasePrefix: { Type: 'String' } }, Mappings: { Prefix: { eggs: { Name: 'bananas' } } }, Conditions: { TooMuch: cloudfriend.equals(cloudfriend.ref('DatabasePrefix'), 'bacon') }, Resources: { Database: { Type: 'AWS::DynamoDB::Table', Properties: { Name: cloudfriend.findInMap('Prefix', cloudfriend.ref('DatabasePrefix'), 'Name') } } }, Outputs: { GoSomewhereElse: { Condition: 'TooMuch', Value: cloudfriend.ref('Database') } } }; const c = { Parameters: { NoConsequence: { Type: 'String' } } }; assert.deepEqual(cloudfriend.merge(a, b, c), { AWSTemplateFormatVersion: '2010-09-09', Metadata: { Instances: { Description: 'Information about the instances' }, Databases: { Description: 'Information about the databases' } }, Parameters: { InstanceCount: { Type: 'Number' }, DatabasePrefix: { Type: 'String' }, NoConsequence: { Type: 'String' } }, Mappings: { Region: { 'us-east-1': { AMI: 'ami-123456' } }, Prefix: { eggs: { Name: 'bananas' } } }, Conditions: { WouldYouLikeBaconWithThat: cloudfriend.equals(cloudfriend.ref('InstanceCount'), 999), TooMuch: cloudfriend.equals(cloudfriend.ref('DatabasePrefix'), 'bacon') }, Resources: { Instance: { Type: 'AWS::EC2::Instance', Properties: { ImageId: cloudfriend.findInMap('Region', cloudfriend.region, 'AMI') } }, Database: { Type: 'AWS::DynamoDB::Table', Properties: { Name: cloudfriend.findInMap('Prefix', cloudfriend.ref('DatabasePrefix'), 'Name') } } }, Outputs: { Breakfast: { Condition: 'WouldYouLikeBaconWithThat', Value: cloudfriend.ref('Instance') }, GoSomewhereElse: { Condition: 'TooMuch', Value: cloudfriend.ref('Database') } } }, 'merge without overlap'); assert.throws(() => { b = { Metadata: { Instances: { Description: 'Information about the instances different' } } }; cloudfriend.merge(a, b); }, /Metadata name used more than once: Instances/, 'throws on .Metadata overlap'); assert.doesNotThrow(() => { b = { Metadata: { Instances: { Description: 'Information about the instances' } } }; cloudfriend.merge(a, b); }, 'allows identical .Metadata overlap'); assert.throws(() => { b = { Parameters: { InstanceCount: { Type: 'Number', Description: 'Different' } } }; cloudfriend.merge(a, b); }, /Parameters name used more than once: InstanceCount/, 'throws on .Parameters overlap'); assert.doesNotThrow(() => { b = { Parameters: { InstanceCount: { Type: 'Number' } } }; cloudfriend.merge(a, b); }, 'allows identical .Parameters overlap'); assert.throws(() => { b = { Mappings: { Region: { 'us-east-1': { AMI: 'ami-123456' }, 'us-east-4': { AMI: 'ami-123456' } } } }; cloudfriend.merge(a, b); }, /Mappings name used more than once: Region/, 'throws on .Mappings overlap'); assert.doesNotThrow(() => { b = { Mappings: { Region: { 'us-east-1': { AMI: 'ami-123456' } } } }; cloudfriend.merge(a, b); }, 'allows identical .Mappings overlap'); assert.throws(() => { b = { Conditions: { WouldYouLikeBaconWithThat: cloudfriend.equals(cloudfriend.ref('InstanceCount'), 998) } }; cloudfriend.merge(a, b); }, /Conditions name used more than once: WouldYouLikeBaconWithThat/, 'throws on .Conditions overlap'); assert.doesNotThrow(() => { b = { Conditions: { WouldYouLikeBaconWithThat: cloudfriend.equals(cloudfriend.ref('InstanceCount'), 999) } }; cloudfriend.merge(a, b); }, 'allows identical .Conditions overlap'); assert.throws(() => { b = { Resources: { Instance: { Type: 'AWS::EC2::Instance', Properties: { ImageId: cloudfriend.findInMap('Region', cloudfriend.region, 'AMIz') } } } }; cloudfriend.merge(a, b); }, /Resources name used more than once: Instance/, 'throws on .Resources overlap'); assert.doesNotThrow(() => { b = { Resources: { Instance: { Type: 'AWS::EC2::Instance', Properties: { ImageId: cloudfriend.findInMap('Region', cloudfriend.region, 'AMI') } } } }; cloudfriend.merge(a, b); }, 'allows identical .Resources overlap'); assert.throws(() => { b = { Outputs: { Breakfast: { Condition: 'WouldYouLikeBaconWithThat', Value: cloudfriend.ref('Instancez') } } }; cloudfriend.merge(a, b); }, /Outputs name used more than once: Breakfast/, 'throws on .Outputs overlap'); assert.doesNotThrow(() => { b = { Outputs: { Breakfast: { Condition: 'WouldYouLikeBaconWithThat', Value: cloudfriend.ref('Instance') } } }; cloudfriend.merge(a, b); }, 'allows identical .Outputs overlap'); assert.doesNotThrow(() => { b = { Mappings: { Instance: { 'us-east-1': { AMI: 'ami-123456' } } } }; cloudfriend.merge(a, b); }, 'does not throw on cross-property name overlap'); assert.end(); });