From 3796389e5d571dbd89fc0cda4dfd4f4c8cdc2eee Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Tue, 7 Aug 2018 14:51:12 +0200 Subject: [PATCH 01/17] Framework: Tokens can be converted to strings Tokens (such as resource attributes) can now be implicitly converted into strings. Parameters and attributes can now be combined into larger strings using the native string facilities of the host language. The process will be reversed by the resolve() function during synthesis, which will split the string up again into string literals and CloudFormation intrinsics. The same transformation is applied during JSON.stringify(), so that Tokens in a complex JSON structure are preserved. Fixes #24 and #168. --- .../@aws-cdk/aws-cloudwatch/lib/dashboard.ts | 4 +- .../integ.alarm-and-dashboard.expected.json | 53 +- .../aws-cloudwatch/test/test.dashboard.ts | 15 +- .../@aws-cdk/cdk/lib/cloudformation/pseudo.ts | 6 +- .../cdk/lib/cloudformation/resource.ts | 4 +- .../@aws-cdk/cdk/lib/cloudformation/stack.ts | 4 +- .../lib/cloudformation/token-aware-jsonify.ts | 112 - packages/@aws-cdk/cdk/lib/core/tokens.ts | 171 + .../@aws-cdk/cdk/test/core/test.tokens.ts | 29 + .../cdk/test/test.token-aware-jsonify.ts | 74 - packages/@aws-cdk/custom-resources/.jsii | 3109 +++++++++++++++++ .../@aws-cdk/custom-resources/lib/index.d.ts | 2 + .../@aws-cdk/custom-resources/lib/index.js | 8 + .../custom-resources/lib/resource.d.ts | 50 + .../@aws-cdk/custom-resources/lib/resource.js | 45 + .../custom-resources/lib/singletonlambda.d.ts | 31 + .../custom-resources/lib/singletonlambda.js | 38 + .../test/integ.trivial-lambda-resource.d.ts | 1 + .../test/integ.trivial-lambda-resource.js | 43 + .../custom-resources/test/test.resource.d.ts | 5 + .../custom-resources/test/test.resource.js | 101 + .../test/test.singletonlambda.d.ts | 5 + .../test/test.singletonlambda.js | 62 + .../@aws-cdk/custom-resources/tsconfig.json | 27 + .../@aws-cdk/custom-resources/tslint.json | 38 + 25 files changed, 3816 insertions(+), 221 deletions(-) delete mode 100644 packages/@aws-cdk/cdk/lib/cloudformation/token-aware-jsonify.ts delete mode 100644 packages/@aws-cdk/cdk/test/test.token-aware-jsonify.ts create mode 100644 packages/@aws-cdk/custom-resources/.jsii create mode 100644 packages/@aws-cdk/custom-resources/lib/index.d.ts create mode 100644 packages/@aws-cdk/custom-resources/lib/index.js create mode 100644 packages/@aws-cdk/custom-resources/lib/resource.d.ts create mode 100644 packages/@aws-cdk/custom-resources/lib/resource.js create mode 100644 packages/@aws-cdk/custom-resources/lib/singletonlambda.d.ts create mode 100644 packages/@aws-cdk/custom-resources/lib/singletonlambda.js create mode 100644 packages/@aws-cdk/custom-resources/test/integ.trivial-lambda-resource.d.ts create mode 100644 packages/@aws-cdk/custom-resources/test/integ.trivial-lambda-resource.js create mode 100644 packages/@aws-cdk/custom-resources/test/test.resource.d.ts create mode 100644 packages/@aws-cdk/custom-resources/test/test.resource.js create mode 100644 packages/@aws-cdk/custom-resources/test/test.singletonlambda.d.ts create mode 100644 packages/@aws-cdk/custom-resources/test/test.singletonlambda.js create mode 100644 packages/@aws-cdk/custom-resources/tsconfig.json create mode 100644 packages/@aws-cdk/custom-resources/tslint.json diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts b/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts index 77cac5955be08..3cc70b7e01268 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts @@ -1,4 +1,4 @@ -import { Construct, Stack, Token, tokenAwareJsonify } from "@aws-cdk/cdk"; +import { Construct, Stack, Token } from "@aws-cdk/cdk"; import { cloudformation } from './cloudwatch.generated'; import { Column, Row } from "./layout"; import { IWidget } from "./widget"; @@ -33,7 +33,7 @@ export class Dashboard extends Construct { dashboardBody: new Token(() => { const column = new Column(...this.rows); column.position(0, 0); - return tokenAwareJsonify({ widgets: column.toJson() }); + return JSON.stringify({ widgets: column.toJson() }); }) }); } diff --git a/packages/@aws-cdk/aws-cloudwatch/test/integ.alarm-and-dashboard.expected.json b/packages/@aws-cdk/aws-cloudwatch/test/integ.alarm-and-dashboard.expected.json index 64feeab61fc77..bc6a19679b430 100644 --- a/packages/@aws-cdk/aws-cloudwatch/test/integ.alarm-and-dashboard.expected.json +++ b/packages/@aws-cdk/aws-cloudwatch/test/integ.alarm-and-dashboard.expected.json @@ -7,6 +7,11 @@ "Type": "AWS::CloudWatch::Alarm", "Properties": { "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 3, + "MetricName": "ApproximateNumberOfMessagesVisible", + "Namespace": "AWS/SQS", + "Period": 300, + "Threshold": 100, "Dimensions": [ { "Name": "QueueName", @@ -18,41 +23,55 @@ } } ], - "EvaluationPeriods": 3, - "MetricName": "ApproximateNumberOfMessagesVisible", - "Namespace": "AWS/SQS", - "Period": 300, - "Statistic": "Average", - "Threshold": 100 + "Statistic": "Average" } }, "DashCCD7F836": { "Type": "AWS::CloudWatch::Dashboard", "Properties": { - "DashboardName": "aws-cdk-cloudwatch-DashCCD7F836", "DashboardBody": { - "Fn::Sub": [ - "{\"widgets\":[{\"type\":\"text\",\"width\":6,\"height\":2,\"x\":0,\"y\":0,\"properties\":{\"markdown\":\"# This is my dashboard\"}},{\"type\":\"text\",\"width\":6,\"height\":2,\"x\":6,\"y\":0,\"properties\":{\"markdown\":\"you like?\"}},{\"type\":\"metric\",\"width\":6,\"height\":6,\"x\":0,\"y\":2,\"properties\":{\"view\":\"timeSeries\",\"title\":\"Messages in queue\",\"region\":\"${ref0}\",\"annotations\":{\"alarms\":[\"${ref1}\"]},\"yAxis\":{\"left\":{\"min\":0}}}},{\"type\":\"metric\",\"width\":6,\"height\":6,\"x\":0,\"y\":8,\"properties\":{\"view\":\"timeSeries\",\"title\":\"More messages in queue with alarm annotation\",\"region\":\"${ref0}\",\"metrics\":[[\"AWS/SQS\",\"ApproximateNumberOfMessagesVisible\",\"QueueName\",\"${ref2}\",{\"yAxis\":\"left\",\"period\":300,\"stat\":\"Average\"}]],\"annotations\":{\"horizontal\":[{\"label\":\"ApproximateNumberOfMessagesVisible >= 100 for 3 datapoints within 15 minutes\",\"value\":100,\"yAxis\":\"left\"}]},\"yAxis\":{\"left\":{\"min\":0},\"right\":{\"min\":0}}}},{\"type\":\"metric\",\"width\":6,\"height\":3,\"x\":0,\"y\":14,\"properties\":{\"view\":\"singleValue\",\"title\":\"Current messages in queue\",\"region\":\"${ref0}\",\"metrics\":[[\"AWS/SQS\",\"ApproximateNumberOfMessagesVisible\",\"QueueName\",\"${ref2}\",{\"yAxis\":\"left\",\"period\":300,\"stat\":\"Average\"}]]}}]}", - { - "ref0": { + "Fn::Join": [ + "", + [ + "{\"widgets\":[{\"type\":\"text\",\"width\":6,\"height\":2,\"x\":0,\"y\":0,\"properties\":{\"markdown\":\"# This is my dashboard\"}},{\"type\":\"text\",\"width\":6,\"height\":2,\"x\":6,\"y\":0,\"properties\":{\"markdown\":\"you like?\"}},{\"type\":\"metric\",\"width\":6,\"height\":6,\"x\":0,\"y\":2,\"properties\":{\"view\":\"timeSeries\",\"title\":\"Messages in queue\",\"region\":\"", + { "Ref": "AWS::Region" }, - "ref1": { + "\",\"annotations\":{\"alarms\":[\"", + { "Fn::GetAtt": [ "Alarm7103F465", "Arn" ] }, - "ref2": { + "\"]},\"yAxis\":{\"left\":{\"min\":0}}}},{\"type\":\"metric\",\"width\":6,\"height\":6,\"x\":0,\"y\":8,\"properties\":{\"view\":\"timeSeries\",\"title\":\"More messages in queue with alarm annotation\",\"region\":\"", + { + "Ref": "AWS::Region" + }, + "\",\"metrics\":[[\"AWS/SQS\",\"ApproximateNumberOfMessagesVisible\",\"QueueName\",\"", + { "Fn::GetAtt": [ "queue", "QueueName" ] - } - } + }, + "\",{\"yAxis\":\"left\",\"period\":300,\"stat\":\"Average\"}]],\"annotations\":{\"horizontal\":[{\"label\":\"ApproximateNumberOfMessagesVisible >= 100 for 3 datapoints within 15 minutes\",\"value\":100,\"yAxis\":\"left\"}]},\"yAxis\":{\"left\":{\"min\":0},\"right\":{\"min\":0}}}},{\"type\":\"metric\",\"width\":6,\"height\":3,\"x\":0,\"y\":14,\"properties\":{\"view\":\"singleValue\",\"title\":\"Current messages in queue\",\"region\":\"", + { + "Ref": "AWS::Region" + }, + "\",\"metrics\":[[\"AWS/SQS\",\"ApproximateNumberOfMessagesVisible\",\"QueueName\",\"", + { + "Fn::GetAtt": [ + "queue", + "QueueName" + ] + }, + "\",{\"yAxis\":\"left\",\"period\":300,\"stat\":\"Average\"}]]}}]}" + ] ] - } + }, + "DashboardName": "aws-cdk-cloudwatch-DashCCD7F836" } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudwatch/test/test.dashboard.ts b/packages/@aws-cdk/aws-cloudwatch/test/test.dashboard.ts index 30a21e379e714..8a62530bed35f 100644 --- a/packages/@aws-cdk/aws-cloudwatch/test/test.dashboard.ts +++ b/packages/@aws-cdk/aws-cloudwatch/test/test.dashboard.ts @@ -70,7 +70,7 @@ export = { test.done(); }, - 'tokens in widgets are retained through FnSub'(test: Test) { + 'tokens in widgets are retained'(test: Test) { // GIVEN const stack = new Stack(); const dashboard = new Dashboard(stack, 'Dash'); @@ -82,14 +82,11 @@ export = { // THEN expect(stack).to(haveResource('AWS::CloudWatch::Dashboard', { - DashboardBody: { "Fn::Sub": [ - // tslint:disable-next-line:max-line-length - "{\"widgets\":[{\"type\":\"metric\",\"width\":1,\"height\":1,\"x\":0,\"y\":0,\"properties\":{\"view\":\"timeSeries\",\"region\":\"${ref0}\",\"metrics\":[],\"annotations\":{\"horizontal\":[]},\"yAxis\":{\"left\":{\"min\":0},\"right\":{\"min\":0}}}}]}", - { - ref0: { Ref: "AWS::Region" } - } - ] - } + DashboardBody: { "Fn::Join": [ "", [ + "{\"widgets\":[{\"type\":\"metric\",\"width\":1,\"height\":1,\"x\":0,\"y\":0,\"properties\":{\"view\":\"timeSeries\",\"region\":\"", + { Ref: "AWS::Region" }, + "\",\"metrics\":[],\"annotations\":{\"horizontal\":[]},\"yAxis\":{\"left\":{\"min\":0},\"right\":{\"min\":0}}}}]}" + ]]} })); test.done(); diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts b/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts index 59b6edd4c90ee..79f1490f75e02 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts @@ -1,8 +1,8 @@ -import { Token } from '../core/tokens'; +import { HintedToken } from '../core/tokens'; -export class PseudoParameter extends Token { +export class PseudoParameter extends HintedToken { constructor(name: string) { - super(() => ({ Ref: name })); + super(name, () => ({ Ref: name })); } } diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts b/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts index ae19bc1090e7d..1d9a1b77136a0 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts @@ -1,5 +1,5 @@ import { Construct } from '../core/construct'; -import { Token } from '../core/tokens'; +import { HintedToken, Token } from '../core/tokens'; import { capitalizePropertyNames, ignoreEmpty } from '../core/util'; import { Condition } from './condition'; import { CreationPolicy, DeletionPolicy, UpdatePolicy } from './resource-policy'; @@ -82,7 +82,7 @@ export class Resource extends Referenceable { * @param attributeName The name of the attribute. */ public getAtt(attributeName: string): Token { - return new Token(() => ({ 'Fn::GetAtt': [this.logicalId, attributeName] })); + return new HintedToken(`${this.logicalId}.${attributeName}`, () => ({ 'Fn::GetAtt': [this.logicalId, attributeName] })); } /** diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts b/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts index 81ee55645eb2a..3e4c7e333436f 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts @@ -1,7 +1,7 @@ import cxapi = require('@aws-cdk/cx-api'); import { App } from '../app'; import { Construct, PATH_SEP } from '../core/construct'; -import { resolve, Token } from '../core/tokens'; +import { HintedToken, resolve, Token } from '../core/tokens'; import { Environment } from '../environment'; import { HashedAddressingScheme, IAddressingScheme, LogicalIDs } from './logical-id'; import { Resource } from './resource'; @@ -392,7 +392,7 @@ export abstract class Referenceable extends StackElement { * Returns a token to a CloudFormation { Ref } that references this entity based on it's logical ID. */ public get ref() { - return new Token(() => ({ Ref: this.logicalId })); + return new HintedToken(`${this.logicalId}`, () => ({ Ref: this.logicalId })); } } diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/token-aware-jsonify.ts b/packages/@aws-cdk/cdk/lib/cloudformation/token-aware-jsonify.ts deleted file mode 100644 index af36eaad9c4c9..0000000000000 --- a/packages/@aws-cdk/cdk/lib/cloudformation/token-aware-jsonify.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { istoken, resolve, Token } from '../core/tokens'; -import { FnSub } from './fn'; - -/** - * Jsonify a deep structure to a string while preserving tokens - * - * Sometimes we have JSON structures that contain CloudFormation intrinsics like - * { Ref } and { Fn::GetAtt }, but the model requires that we stringify the JSON - * structure and pass it into the parameter. - * - * Doing this makes it so that CloudFormation does not resolve the intrinsics - * anymore, since it does not look into every string. To resolve this, we - * stringify into a string and put placeholders in wich we substitute with the - * resolved references using { Fn::Sub }. - * - * Since the result is expected to be a stringified JSON, we need to make sure - * any textual values resolved from tokens are also stringified, so we also - * stringify any string values in resolved tokens (for example, "\n" will be - * replaced by "\\n", quotes will be escaped, etc). This might not be needed (or - * even could be harmful) for certain tokens (e.g. Fn::GetAtt), but we prefer to - * make the common case fool-proof, and hope for the best. - * - * Will only work correctly for intrinsics that return a string value. - */ -export function tokenAwareJsonify(structure: any): any { - // Our strategy is as follows: - // - // - Find all tokens, replace each of them with a string - // token that's highly unlikely to occur naturally. - // Attempt deduplication of the same intrinsic into the same - // string token. - // - JSONify the entire structure. - // - Replace things that LOOK like FnSub references - // with the escape string ${!NotSubstituted}. - // - Replace the special tokens with FnSub references, ${LikeThis}. - let counter = 0; - const tokenId: {[key: string]: string} = {}; - const substitutionMap: {[key: string]: any} = {}; - - function stringifyStrings(x: any): any { - if (typeof(x) === 'string') { - const jsonS = JSON.stringify(x); - return jsonS.substr(1, jsonS.length - 2); // trim quotes - } - - if (Array.isArray(x)) { - return x.map(stringifyStrings); - } - - if (typeof(x) === 'object') { - const result: any = {}; - for (const key of Object.keys(x)) { - result[key] = stringifyStrings(x[key]); - } - - return result; - } - - return x; - } - - function rememberToken(x: Token) { - // Get a representation of the resolved Token that we can use as a hash key. - const resolved = resolve(x); - const reprKey = JSON.stringify(resolved); - if (!(reprKey in tokenId)) { - tokenId[reprKey] = `ref${counter}`; - substitutionMap[tokenId[reprKey]] = stringifyStrings(resolved); - counter += 1; - } - return `<<>>`; - } - - function replaceTokens(x: any): any { - if (Array.isArray(x)) { - return x.map(replaceTokens); - } - - if (typeof x === 'object' && x !== null) { - if (istoken(x)) { - // This a token, remember and replace it. - return rememberToken(x); - } else { - // Recurse into regular object - for (const key of Object.keys(x)) { - x[key] = replaceTokens(x[key]); - } - return x; - } - } - - return x; - } - - structure = replaceTokens(structure); - - let stringified = JSON.stringify(structure); - - if (counter === 0) { - // No replacements - return stringified; - } - - // Escape things that shouldn't be substituted - // Translate ${Oops} -> ${!Oops} - stringified = stringified.replace(/\$\{([^}]+)\}/g, '$${!$1}'); - - // Now substitute our magic pattern with actual references - stringified = stringified.replace(/<<]+)>>>/g, '$${$1}'); - - return new FnSub(stringified, substitutionMap); -} diff --git a/packages/@aws-cdk/cdk/lib/core/tokens.ts b/packages/@aws-cdk/cdk/lib/core/tokens.ts index b0f953ee2544b..37ad7fcb5fa9b 100644 --- a/packages/@aws-cdk/cdk/lib/core/tokens.ts +++ b/packages/@aws-cdk/cdk/lib/core/tokens.ts @@ -11,6 +11,8 @@ export const RESOLVE_METHOD = 'resolve'; * in case, for example, that it requires some context or late-bound data. */ export class Token { + private stringRepr?: string; + /** * Creates a token that resolves to `value`. If value is a function, * the function is evaluated upon resolution and the value it returns will be @@ -29,6 +31,43 @@ export class Token { return value; } + + /** + * Return a string representation of this token + * + * This string representation can be embedded into strings and + * will be turned into the actual representation of the token + * when resolve() is called on the string. + */ + public toString(): string { + if (this.stringRepr === undefined) { + this.stringRepr = TOKEN_STRING_MAP.register(this); + } + return this.stringRepr; + } + + /** + * Return the JSON representation of this Token + * + * Implements JSON.stringify()-compatibility for this Token. Note that it + * returns a string representation for the Token pre-resolution, NOT the + * resolved value of the Token. + */ + public toJSON(): any { + return this.toString(); + } +} + +/** + * A Token that has a human-readable representation hint + * + * This can be used to give Tokens a string representation that makes + * sense to humans. + */ +export class HintedToken extends Token { + constructor(public readonly representationHint: string, valueOrFunction?: any) { + super(valueOrFunction); + } } /** @@ -79,6 +118,13 @@ export function resolve(obj: any, prefix?: string[]): any { throw new Error(`Trying to resolve a non-data object. Only token are supported for lazy evaluation. Path: ${pathName}. Object: ${obj}`); } + // + // string - potentially replace all stringified Tokens + // + if (typeof(obj) === 'string') { + return TOKEN_STRING_MAP.resolveMarkers(obj as string); + } + // // primitives - as-is // @@ -133,3 +179,128 @@ export function resolve(obj: any, prefix?: string[]): any { return result; } + +import { FnConcat } from "../cloudformation/fn"; + +/** + * Central place where we keep a mapping from Tokens to their String representation + * + * The string representation is used to embed token into strings, + * and stored to be able to + * + * All instances of TokenStringMap share the same storage, so that this process + * works even when different copies of the library are loaded. + */ +class TokenStringMap { + private readonly tokenMap: {[key: string]: Token}; + + constructor() { + const glob = global as any; + this.tokenMap = glob.__cdkTokenMap = glob.__cdkTokenMap || {}; + } + + /** + * Return a unique string for this Token + * + * Every call for the same Token will produce a new unique string, no + * attempt is made to deduplicate. Token objects should cache the + * value themselves, if required. + * + * If the token is an IRepresentableToken, it chooses its own representation + * string. This may be used to produce aesthetically pleasing and recognizable + * token representations for humans. + */ + public register(token: Token | HintedToken): string { + const counter = Object.keys(this.tokenMap).length; + const representation = isHintedToken(token) ? token.representationHint : `TOKEN`; + + const key = `${representation}.${counter}`; + if (new RegExp(`[^${VALID_KEY_CHARS}]`).exec(key)) { + throw new Error(`Invalid characters in token representation: ${key}`); + } + + this.tokenMap[key] = token; + + return `${BEGIN_TOKEN_MARKER}${key}${END_TOKEN_MARKER}`; + } + + /** + * Replace any Token markers in this string with their resolved values + */ + public resolveMarkers(s: string): any { + const spans = this.split(s); + const resolved = spans.map(this.resolveSpan.bind(this)); + + switch (resolved.length) { + case 0: return ''; + case 1: return resolved[0]; + default: return resolve(new FnConcat(...resolved)); + } + } + + /** + * Split a string on Token markers + */ + private split(s: string): Span[] { + const re = new RegExp(`${BEGIN_TOKEN_MARKER}([${VALID_KEY_CHARS}]+)${END_TOKEN_MARKER}`, 'gu'); + const ret: Span[] = []; + + let rest = 0; + let m = re.exec(s); + while (m) { + if (m.index > rest) { + ret.push({ type: 'string', value: s.substring(rest, m.index) }); + } + + ret.push({ type: 'token', key: m[1] }); + + rest = re.lastIndex; + m = re.exec(s); + } + + if (rest < s.length) { + ret.push({ type: 'string', value: s.substring(rest) }); + } + + return ret; + } + + /** + * Resolve a single Span + */ + private resolveSpan(span: Span): any { + if (span.type === 'string') { + return span.value; + } + if (!(span.key in this.tokenMap)) { + throw new Error(`Unrecognized token representation: ${span.key}`); + } + + return resolve(this.tokenMap[span.key]); + } +} + +interface StringSpan { + type: 'string'; + value: string; +} + +interface TokenSpan { + type: 'token'; + key: string; +} + +type Span = StringSpan | TokenSpan; + +const BEGIN_TOKEN_MARKER = '🔸🔹'; +const END_TOKEN_MARKER = '🔹🔸'; +const VALID_KEY_CHARS = 'a-zA-Z0-9:._-'; + +/** + * Singleton instance of the token string map + */ +const TOKEN_STRING_MAP = new TokenStringMap(); + +function isHintedToken(token: any): token is HintedToken { + return 'representationHint' in token; +} diff --git a/packages/@aws-cdk/cdk/test/core/test.tokens.ts b/packages/@aws-cdk/cdk/test/core/test.tokens.ts index 2330f1672410f..16c7bc4a8e911 100644 --- a/packages/@aws-cdk/cdk/test/core/test.tokens.ts +++ b/packages/@aws-cdk/cdk/test/core/test.tokens.ts @@ -141,6 +141,35 @@ export = { test.equal(date.toString(), resolved.toString()); test.done(); + }, + + 'tokens can be stringified and stringification can be reversed'(test: Test) { + // GIVEN + const token = new Token(() => 'woof woof'); + + // WHEN + const stringified = `The dog says: ${token}`; + const resolved = resolve(stringified); + + // THEN + test.deepEqual(resolved, {'Fn::Join': ['', ['The dog says: ', 'woof woof']]}); + test.done(); + }, + + 'tokens can be JSON.stringified and stringification can be reversed'(test: Test) { + // GIVEN + const fido = { + name: 'Fido', + speaks: new Token(() => 'woof woof') + }; + + // WHEN + const resolved = resolve(JSON.stringify(fido)); + + // THEN + test.deepEqual(resolved, {'Fn::Join': ['', + ['{"name":"Fido","speaks":"', 'woof woof', '"}']]}); + test.done(); } }; diff --git a/packages/@aws-cdk/cdk/test/test.token-aware-jsonify.ts b/packages/@aws-cdk/cdk/test/test.token-aware-jsonify.ts deleted file mode 100644 index 1608c774bb8f1..0000000000000 --- a/packages/@aws-cdk/cdk/test/test.token-aware-jsonify.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Test } from 'nodeunit'; -import { AwsRegion, FnConcat, resolve, tokenAwareJsonify } from '../lib'; - -export = { - 'substitutes tokens'(test: Test) { - // WHEN - const result = tokenAwareJsonify({ - 'the region': new AwsRegion(), - 'the king': 'me', - }); - - // THEN - test.deepEqual(resolve(result), { - 'Fn::Sub': [ - '{"the region":"${ref0}","the king":"me"}', - { ref0: { Ref: 'AWS::Region' } } - ] - }); - - test.done(); - }, - - 'reuse token substitutions'(test: Test) { - // WHEN - const result = tokenAwareJsonify({ - 'the region': new AwsRegion(), - 'other region': new AwsRegion(), - 'the king': 'me', - }); - - // THEN - test.deepEqual(resolve(result), { - 'Fn::Sub': [ - '{"the region":"${ref0}","other region":"${ref0}","the king":"me"}', - { ref0: { Ref: 'AWS::Region' } } - ] - }); - - test.done(); - }, - - 'escape things that look like FnSub values'(test: Test) { - // WHEN - const result = tokenAwareJsonify({ - 'the region': new AwsRegion(), - 'the king': '${Me}', - }); - - // THEN - test.deepEqual(resolve(result), { - 'Fn::Sub': [ - '{"the region":"${ref0}","the king":"${!Me}"}', - { ref0: { Ref: 'AWS::Region' } } - ] - }); - - test.done(); - }, - - 'string values in resolved tokens should be represented as stringified strings'(test: Test) { - // WHEN - const result = tokenAwareJsonify({ - test1: new FnConcat('Hello', 'This\nIs', 'Very "cool"'), - }); - - // THEN - test.deepEqual(resolve(result), { 'Fn::Sub': - [ '{"test1":"${ref0}"}', - { ref0: - { 'Fn::Join': [ '', [ 'Hello', 'This\\nIs', 'Very \\"cool\\"' ] ] } } ] }); - - test.done(); - } -}; diff --git a/packages/@aws-cdk/custom-resources/.jsii b/packages/@aws-cdk/custom-resources/.jsii new file mode 100644 index 0000000000000..4a4b16aa1367b --- /dev/null +++ b/packages/@aws-cdk/custom-resources/.jsii @@ -0,0 +1,3109 @@ +{ + "fingerprint": "FrhACMViy4m74joZ6LnVKtaQBmKEVsLwOmGHm4enaWI=", + "author": { + "name": "Amazon Web Services", + "organization": true, + "roles": [ + "author" + ], + "url": "https://aws.amazon.com" + }, + "dependencies": { + "@aws-cdk/aws-cloudformation": { + "dependencies": { + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.CloudFormation" + }, + "java": { + "maven": { + "artifactId": "aws-cloudformation", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.cloudformation" + }, + "js": { + "npm": "@aws-cdk/aws-cloudformation" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/aws-iam": { + "dependencies": { + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.IAM" + }, + "java": { + "maven": { + "artifactId": "aws-iam", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.iam" + }, + "js": { + "npm": "@aws-cdk/aws-iam" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/aws-lambda": { + "dependencies": { + "@aws-cdk/assets": { + "dependencies": { + "@aws-cdk/aws-iam": { + "dependencies": { + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.IAM" + }, + "java": { + "maven": { + "artifactId": "aws-iam", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.iam" + }, + "js": { + "npm": "@aws-cdk/aws-iam" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/aws-s3": { + "dependencies": { + "@aws-cdk/aws-iam": { + "dependencies": { + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.IAM" + }, + "java": { + "maven": { + "artifactId": "aws-iam", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.iam" + }, + "js": { + "npm": "@aws-cdk/aws-iam" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/aws-kms": { + "dependencies": { + "@aws-cdk/aws-iam": { + "dependencies": { + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.IAM" + }, + "java": { + "maven": { + "artifactId": "aws-iam", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.iam" + }, + "js": { + "npm": "@aws-cdk/aws-iam" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.KMS" + }, + "java": { + "maven": { + "artifactId": "aws-kms", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.kms" + }, + "js": { + "npm": "@aws-cdk/aws-kms" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.S3" + }, + "java": { + "maven": { + "artifactId": "aws-s3", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.s3" + }, + "js": { + "npm": "@aws-cdk/aws-s3" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.Assets" + }, + "java": { + "maven": { + "artifactId": "assets", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.assets" + }, + "js": { + "npm": "@aws-cdk/assets" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/aws-cloudwatch": { + "dependencies": { + "@aws-cdk/aws-iam": { + "dependencies": { + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.IAM" + }, + "java": { + "maven": { + "artifactId": "aws-iam", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.iam" + }, + "js": { + "npm": "@aws-cdk/aws-iam" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.CloudWatch" + }, + "java": { + "maven": { + "artifactId": "aws-cloudwatch", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.cloudwatch" + }, + "js": { + "npm": "@aws-cdk/aws-cloudwatch" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/aws-events": { + "dependencies": { + "@aws-cdk/aws-iam": { + "dependencies": { + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.IAM" + }, + "java": { + "maven": { + "artifactId": "aws-iam", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.iam" + }, + "js": { + "npm": "@aws-cdk/aws-iam" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.Events" + }, + "java": { + "maven": { + "artifactId": "aws-events", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.events" + }, + "js": { + "npm": "@aws-cdk/aws-events" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/aws-iam": { + "dependencies": { + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.IAM" + }, + "java": { + "maven": { + "artifactId": "aws-iam", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.iam" + }, + "js": { + "npm": "@aws-cdk/aws-iam" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/aws-logs": { + "dependencies": { + "@aws-cdk/aws-iam": { + "dependencies": { + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.IAM" + }, + "java": { + "maven": { + "artifactId": "aws-iam", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.iam" + }, + "js": { + "npm": "@aws-cdk/aws-iam" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.Logs" + }, + "java": { + "maven": { + "artifactId": "aws-logs", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.logs" + }, + "js": { + "npm": "@aws-cdk/aws-logs" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/aws-s3": { + "dependencies": { + "@aws-cdk/aws-iam": { + "dependencies": { + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.IAM" + }, + "java": { + "maven": { + "artifactId": "aws-iam", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.iam" + }, + "js": { + "npm": "@aws-cdk/aws-iam" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/aws-kms": { + "dependencies": { + "@aws-cdk/aws-iam": { + "dependencies": { + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.IAM" + }, + "java": { + "maven": { + "artifactId": "aws-iam", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.iam" + }, + "js": { + "npm": "@aws-cdk/aws-iam" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.KMS" + }, + "java": { + "maven": { + "artifactId": "aws-kms", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.kms" + }, + "js": { + "npm": "@aws-cdk/aws-kms" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.S3" + }, + "java": { + "maven": { + "artifactId": "aws-s3", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.s3" + }, + "js": { + "npm": "@aws-cdk/aws-s3" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.Lambda" + }, + "java": { + "maven": { + "artifactId": "aws-lambda", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.lambda" + }, + "js": { + "npm": "@aws-cdk/aws-lambda" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/aws-sns": { + "dependencies": { + "@aws-cdk/aws-cloudwatch": { + "dependencies": { + "@aws-cdk/aws-iam": { + "dependencies": { + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.IAM" + }, + "java": { + "maven": { + "artifactId": "aws-iam", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.iam" + }, + "js": { + "npm": "@aws-cdk/aws-iam" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.CloudWatch" + }, + "java": { + "maven": { + "artifactId": "aws-cloudwatch", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.cloudwatch" + }, + "js": { + "npm": "@aws-cdk/aws-cloudwatch" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/aws-events": { + "dependencies": { + "@aws-cdk/aws-iam": { + "dependencies": { + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.IAM" + }, + "java": { + "maven": { + "artifactId": "aws-iam", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.iam" + }, + "js": { + "npm": "@aws-cdk/aws-iam" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.Events" + }, + "java": { + "maven": { + "artifactId": "aws-events", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.events" + }, + "js": { + "npm": "@aws-cdk/aws-events" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/aws-iam": { + "dependencies": { + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.IAM" + }, + "java": { + "maven": { + "artifactId": "aws-iam", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.iam" + }, + "js": { + "npm": "@aws-cdk/aws-iam" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/aws-lambda": { + "dependencies": { + "@aws-cdk/assets": { + "dependencies": { + "@aws-cdk/aws-iam": { + "dependencies": { + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.IAM" + }, + "java": { + "maven": { + "artifactId": "aws-iam", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.iam" + }, + "js": { + "npm": "@aws-cdk/aws-iam" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/aws-s3": { + "dependencies": { + "@aws-cdk/aws-iam": { + "dependencies": { + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.IAM" + }, + "java": { + "maven": { + "artifactId": "aws-iam", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.iam" + }, + "js": { + "npm": "@aws-cdk/aws-iam" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/aws-kms": { + "dependencies": { + "@aws-cdk/aws-iam": { + "dependencies": { + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.IAM" + }, + "java": { + "maven": { + "artifactId": "aws-iam", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.iam" + }, + "js": { + "npm": "@aws-cdk/aws-iam" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.KMS" + }, + "java": { + "maven": { + "artifactId": "aws-kms", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.kms" + }, + "js": { + "npm": "@aws-cdk/aws-kms" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.S3" + }, + "java": { + "maven": { + "artifactId": "aws-s3", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.s3" + }, + "js": { + "npm": "@aws-cdk/aws-s3" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.Assets" + }, + "java": { + "maven": { + "artifactId": "assets", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.assets" + }, + "js": { + "npm": "@aws-cdk/assets" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/aws-cloudwatch": { + "dependencies": { + "@aws-cdk/aws-iam": { + "dependencies": { + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.IAM" + }, + "java": { + "maven": { + "artifactId": "aws-iam", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.iam" + }, + "js": { + "npm": "@aws-cdk/aws-iam" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.CloudWatch" + }, + "java": { + "maven": { + "artifactId": "aws-cloudwatch", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.cloudwatch" + }, + "js": { + "npm": "@aws-cdk/aws-cloudwatch" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/aws-events": { + "dependencies": { + "@aws-cdk/aws-iam": { + "dependencies": { + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.IAM" + }, + "java": { + "maven": { + "artifactId": "aws-iam", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.iam" + }, + "js": { + "npm": "@aws-cdk/aws-iam" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.Events" + }, + "java": { + "maven": { + "artifactId": "aws-events", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.events" + }, + "js": { + "npm": "@aws-cdk/aws-events" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/aws-iam": { + "dependencies": { + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.IAM" + }, + "java": { + "maven": { + "artifactId": "aws-iam", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.iam" + }, + "js": { + "npm": "@aws-cdk/aws-iam" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/aws-logs": { + "dependencies": { + "@aws-cdk/aws-iam": { + "dependencies": { + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.IAM" + }, + "java": { + "maven": { + "artifactId": "aws-iam", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.iam" + }, + "js": { + "npm": "@aws-cdk/aws-iam" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.Logs" + }, + "java": { + "maven": { + "artifactId": "aws-logs", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.logs" + }, + "js": { + "npm": "@aws-cdk/aws-logs" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/aws-s3": { + "dependencies": { + "@aws-cdk/aws-iam": { + "dependencies": { + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.IAM" + }, + "java": { + "maven": { + "artifactId": "aws-iam", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.iam" + }, + "js": { + "npm": "@aws-cdk/aws-iam" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/aws-kms": { + "dependencies": { + "@aws-cdk/aws-iam": { + "dependencies": { + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.IAM" + }, + "java": { + "maven": { + "artifactId": "aws-iam", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.iam" + }, + "js": { + "npm": "@aws-cdk/aws-iam" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.KMS" + }, + "java": { + "maven": { + "artifactId": "aws-kms", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.kms" + }, + "js": { + "npm": "@aws-cdk/aws-kms" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.S3" + }, + "java": { + "maven": { + "artifactId": "aws-s3", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.s3" + }, + "js": { + "npm": "@aws-cdk/aws-s3" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.Lambda" + }, + "java": { + "maven": { + "artifactId": "aws-lambda", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.lambda" + }, + "js": { + "npm": "@aws-cdk/aws-lambda" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/aws-sqs": { + "dependencies": { + "@aws-cdk/aws-kms": { + "dependencies": { + "@aws-cdk/aws-iam": { + "dependencies": { + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.IAM" + }, + "java": { + "maven": { + "artifactId": "aws-iam", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.iam" + }, + "js": { + "npm": "@aws-cdk/aws-iam" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.KMS" + }, + "java": { + "maven": { + "artifactId": "aws-kms", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.kms" + }, + "js": { + "npm": "@aws-cdk/aws-kms" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.SQS" + }, + "java": { + "maven": { + "artifactId": "aws-sqs", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.sqs" + }, + "js": { + "npm": "@aws-cdk/aws-sqs" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.SNS" + }, + "java": { + "maven": { + "artifactId": "aws-sns", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.aws.sns" + }, + "js": { + "npm": "@aws-cdk/aws-sns" + } + }, + "version": "0.8.0" + }, + "@aws-cdk/cdk": { + "dependencies": { + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CXAPI" + }, + "java": { + "maven": { + "artifactId": "cxapi", + "groupId": "com.amazonaws.cdk" + }, + "package": "com.amazonaws.cdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + } + }, + "version": "0.8.0" + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK" + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + } + }, + "version": "0.8.0" + } + }, + "description": "CDK Constructs to make it easier to build custom resources", + "homepage": "https://github.com/awslabs/aws-cdk", + "license": "Apache-2.0", + "name": "@aws-cdk/custom-resources", + "readme": { + "markdown": "## CDK Custom Resources\n\nCustom Resources are CloudFormation resources that are implemented by\narbitrary user code. They can do arbitrary lookups or modifications\nduring a CloudFormation synthesis run.\n\nYou will typically use Lambda to implement a Construct implemented as a\nCustom Resource (though SNS topics can be used as well). Your Lambda function\nwill be sent a `CREATE`, `UPDATE` or `DELETE` message, depending on the\nCloudFormation life cycle, and can return any number of output values which\nwill be available as attributes of your Construct. In turn, those can\nbe used as input to other Constructs in your model.\n\nIn general, consumers of your Construct will not need to care whether\nit is implemented in term of other CloudFormation resources or as a\ncustom resource.\n\nNote: when implementing your Custom Resource using a Lambda, use\na `SingletonLambda` so that even if your custom resource is instantiated\nmultiple times, the Lambda will only get uploaded once.\n\n### Example\n\nSample of a Custom Resource that copies files into an S3 bucket during deployment\n(implementation of actual `copy.py` operation elided).\n\n```ts\ninterface CopyOperationProps {\n sourceBucket: IBucket;\n targetBucket: IBucket;\n}\n\nclass CopyOperation extends Construct {\n constructor(parent: Construct, name: string, props: DemoResourceProps) {\n super(parent, name);\n\n const lambdaProvider = new SingletonLambda(this, 'Provider', {\n uuid: 'f7d4f730-4ee1-11e8-9c2d-fa7ae01bbebc',\n code: new LambdaInlineCode(resources['copy.py']),\n handler: 'index.handler',\n timeout: 60,\n runtime: LambdaRuntime.Python3,\n });\n\n new CustomResource(this, 'Resource', {\n lambdaProvider,\n properties: {\n sourceBucketArn: props.sourceBucket.bucketArn,\n targetBucketArn: props.targetBucket.bucketArn,\n }\n });\n }\n}\n```\n\nMore examples are in the `example` directory, including an example of how to use\nthe `cfnresponse` module that is provided for you by CloudFormation.\n\n### References\n\nSee the following section of the docs on details to write Custom Resources:\n\n* [Introduction](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html)\n* [Reference](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref.html)\n* [Code Reference](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html)\n" + }, + "repository": { + "type": "git", + "url": "https://github.com/awslabs/aws-cdk.git" + }, + "schema": "jsii/1.0", + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.CustomResources" + }, + "java": { + "maven": { + "artifactId": "cdk-custom-resources", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk.customresources" + }, + "js": { + "npm": "@aws-cdk/custom-resources" + } + }, + "types": { + "@aws-cdk/custom-resources.CustomResource": { + "assembly": "@aws-cdk/custom-resources", + "base": { + "fqn": "@aws-cdk/aws-cloudformation.cloudformation.CustomResource" + }, + "docs": { + "comment": "Custom resource that is implemented using a Lambda\n\nAs a custom resource author, you should be publishing a subclass of this class\nthat hides the choice of provider, and accepts a strongly-typed properties\nobject with the properties your provider accepts." + }, + "fqn": "@aws-cdk/custom-resources.CustomResource", + "initializer": { + "initializer": true, + "parameters": [ + { + "name": "parent", + "type": { + "fqn": "@aws-cdk/cdk.Construct" + } + }, + { + "name": "name", + "type": { + "primitive": "string" + } + }, + { + "name": "props", + "type": { + "fqn": "@aws-cdk/custom-resources.CustomResourceProps" + } + } + ] + }, + "kind": "class", + "methods": [ + { + "docs": { + "comment": "Override renderProperties to mix in the user-defined properties" + }, + "name": "renderProperties", + "protected": true, + "returns": { + "collection": { + "elementtype": { + "primitive": "any" + }, + "kind": "map" + } + } + } + ], + "name": "CustomResource", + "namespace": "@aws-cdk/custom-resources" + }, + "@aws-cdk/custom-resources.CustomResourceProps": { + "assembly": "@aws-cdk/custom-resources", + "datatype": true, + "docs": { + "comment": "Properties to provide a Lambda-backed custom resource" + }, + "fqn": "@aws-cdk/custom-resources.CustomResourceProps", + "kind": "interface", + "name": "CustomResourceProps", + "namespace": "@aws-cdk/custom-resources", + "properties": [ + { + "docs": { + "comment": "The Lambda provider that implements this custom resource.\n\nWe recommend using a SingletonLambda for this.\n\nOptional, exactly one of lamdaProvider or topicProvider must be set." + }, + "name": "lambdaProvider", + "type": { + "fqn": "@aws-cdk/aws-lambda.LambdaRef", + "optional": true + } + }, + { + "docs": { + "comment": "The SNS Topic for the provider that implements this custom resource.\n\nOptional, exactly one of lamdaProvider or topicProvider must be set." + }, + "name": "topicProvider", + "type": { + "fqn": "@aws-cdk/aws-sns.TopicRef", + "optional": true + } + }, + { + "docs": { + "comment": "Properties to pass to the Lambda" + }, + "name": "properties", + "type": { + "collection": { + "elementtype": { + "primitive": "any" + }, + "kind": "map" + }, + "optional": true + } + } + ] + }, + "@aws-cdk/custom-resources.SingletonLambda": { + "assembly": "@aws-cdk/custom-resources", + "base": { + "fqn": "@aws-cdk/aws-lambda.LambdaRef" + }, + "docs": { + "comment": "A Lambda that will only ever be added to a stack once.\n\nThe lambda is identified using the value of 'uuid'. Run 'uuidgen'\nfor every SingletonLambda you create." + }, + "fqn": "@aws-cdk/custom-resources.SingletonLambda", + "initializer": { + "initializer": true, + "parameters": [ + { + "name": "parent", + "type": { + "fqn": "@aws-cdk/cdk.Construct" + } + }, + { + "name": "name", + "type": { + "primitive": "string" + } + }, + { + "name": "props", + "type": { + "fqn": "@aws-cdk/custom-resources.SingletonLambdaProps" + } + } + ] + }, + "kind": "class", + "methods": [ + { + "docs": { + "comment": "Adds a permission to the Lambda resource policy." + }, + "name": "addPermission", + "parameters": [ + { + "name": "name", + "type": { + "primitive": "string" + } + }, + { + "name": "permission", + "type": { + "fqn": "@aws-cdk/aws-lambda.LambdaPermission" + } + } + ] + } + ], + "name": "SingletonLambda", + "namespace": "@aws-cdk/custom-resources", + "properties": [ + { + "docs": { + "comment": "The name of the function." + }, + "immutable": true, + "name": "functionName", + "type": { + "fqn": "@aws-cdk/aws-lambda.FunctionName" + } + }, + { + "docs": { + "comment": "The ARN fo the function." + }, + "immutable": true, + "name": "functionArn", + "type": { + "fqn": "@aws-cdk/aws-lambda.FunctionArn" + } + }, + { + "docs": { + "comment": "The IAM role associated with this function." + }, + "immutable": true, + "name": "role", + "type": { + "fqn": "@aws-cdk/aws-iam.Role", + "optional": true + } + }, + { + "docs": { + "comment": "Whether the addPermission() call adds any permissions\n\nTrue for new Lambdas, false for imported Lambdas (they might live in different accounts)." + }, + "immutable": true, + "name": "canCreatePermissions", + "protected": true, + "type": { + "primitive": "boolean" + } + } + ] + }, + "@aws-cdk/custom-resources.SingletonLambdaProps": { + "assembly": "@aws-cdk/custom-resources", + "datatype": true, + "docs": { + "comment": "Properties for a newly created singleton Lambda" + }, + "fqn": "@aws-cdk/custom-resources.SingletonLambdaProps", + "interfaces": [ + { + "fqn": "@aws-cdk/aws-lambda.LambdaProps" + } + ], + "kind": "interface", + "name": "SingletonLambdaProps", + "namespace": "@aws-cdk/custom-resources", + "properties": [ + { + "docs": { + "comment": "A unique identifier to identify this lambda\n\nThe identifier should be unique across all custom resource providers.\nWe recommend generating a UUID per provider." + }, + "name": "uuid", + "type": { + "primitive": "string" + } + } + ] + } + }, + "version": "0.8.0" +} diff --git a/packages/@aws-cdk/custom-resources/lib/index.d.ts b/packages/@aws-cdk/custom-resources/lib/index.d.ts new file mode 100644 index 0000000000000..a8ace2f074f49 --- /dev/null +++ b/packages/@aws-cdk/custom-resources/lib/index.d.ts @@ -0,0 +1,2 @@ +export * from './resource'; +export * from './singletonlambda'; diff --git a/packages/@aws-cdk/custom-resources/lib/index.js b/packages/@aws-cdk/custom-resources/lib/index.js new file mode 100644 index 0000000000000..e30746f2f9ce5 --- /dev/null +++ b/packages/@aws-cdk/custom-resources/lib/index.js @@ -0,0 +1,8 @@ +"use strict"; +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +Object.defineProperty(exports, "__esModule", { value: true }); +__export(require("./resource")); +__export(require("./singletonlambda")); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQUFBLGdDQUEyQjtBQUMzQix1Q0FBa0MifQ== \ No newline at end of file diff --git a/packages/@aws-cdk/custom-resources/lib/resource.d.ts b/packages/@aws-cdk/custom-resources/lib/resource.d.ts new file mode 100644 index 0000000000000..006ee5f7f00b2 --- /dev/null +++ b/packages/@aws-cdk/custom-resources/lib/resource.d.ts @@ -0,0 +1,50 @@ +import { cloudformation } from '@aws-cdk/aws-cloudformation'; +import lambda = require('@aws-cdk/aws-lambda'); +import sns = require('@aws-cdk/aws-sns'); +import cdk = require('@aws-cdk/cdk'); +/** + * Collection of arbitrary properties + */ +export declare type Properties = { + [key: string]: any; +}; +/** + * Properties to provide a Lambda-backed custom resource + */ +export interface CustomResourceProps { + /** + * The Lambda provider that implements this custom resource. + * + * We recommend using a SingletonLambda for this. + * + * Optional, exactly one of lamdaProvider or topicProvider must be set. + */ + lambdaProvider?: lambda.LambdaRef; + /** + * The SNS Topic for the provider that implements this custom resource. + * + * Optional, exactly one of lamdaProvider or topicProvider must be set. + */ + topicProvider?: sns.TopicRef; + /** + * Properties to pass to the Lambda + */ + properties?: Properties; +} +/** + * Custom resource that is implemented using a Lambda + * + * As a custom resource author, you should be publishing a subclass of this class + * that hides the choice of provider, and accepts a strongly-typed properties + * object with the properties your provider accepts. + */ +export declare class CustomResource extends cloudformation.CustomResource { + private readonly userProperties?; + constructor(parent: cdk.Construct, name: string, props: CustomResourceProps); + /** + * Override renderProperties to mix in the user-defined properties + */ + protected renderProperties(): { + [key: string]: any; + }; +} diff --git a/packages/@aws-cdk/custom-resources/lib/resource.js b/packages/@aws-cdk/custom-resources/lib/resource.js new file mode 100644 index 0000000000000..9e3139746a5f5 --- /dev/null +++ b/packages/@aws-cdk/custom-resources/lib/resource.js @@ -0,0 +1,45 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const aws_cloudformation_1 = require("@aws-cdk/aws-cloudformation"); +/** + * Custom resource that is implemented using a Lambda + * + * As a custom resource author, you should be publishing a subclass of this class + * that hides the choice of provider, and accepts a strongly-typed properties + * object with the properties your provider accepts. + */ +class CustomResource extends aws_cloudformation_1.cloudformation.CustomResource { + constructor(parent, name, props) { + if (!!props.lambdaProvider === !!props.topicProvider) { + throw new Error('Exactly one of "lambdaProvider" or "topicProvider" must be set.'); + } + super(parent, name, { + serviceToken: props.lambdaProvider ? props.lambdaProvider.functionArn : props.topicProvider.topicArn + }); + this.userProperties = props.properties; + } + /** + * Override renderProperties to mix in the user-defined properties + */ + renderProperties() { + const props = super.renderProperties(); + return Object.assign(props, uppercaseProperties(this.userProperties || {})); + } +} +exports.CustomResource = CustomResource; +/** + * Uppercase the first letter of every property name + * + * It's customary for CloudFormation properties to start with capitals, and our + * properties to start with lowercase, so this function translates from one + * to the other + */ +function uppercaseProperties(props) { + const ret = {}; + Object.keys(props).forEach(key => { + const upper = key.substr(0, 1).toUpperCase() + key.substr(1); + ret[upper] = props[key]; + }); + return ret; +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzb3VyY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJyZXNvdXJjZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLG9FQUE2RDtBQW9DN0Q7Ozs7OztHQU1HO0FBQ0gsb0JBQTRCLFNBQVEsbUNBQWMsQ0FBQyxjQUFjO0lBTTdELFlBQVksTUFBcUIsRUFBRSxJQUFZLEVBQUUsS0FBMEI7UUFDdkUsSUFBSSxDQUFDLENBQUMsS0FBSyxDQUFDLGNBQWMsS0FBSyxDQUFDLENBQUMsS0FBSyxDQUFDLGFBQWEsRUFBRTtZQUNsRCxNQUFNLElBQUksS0FBSyxDQUFDLGlFQUFpRSxDQUFDLENBQUM7U0FDdEY7UUFFRCxLQUFLLENBQUMsTUFBTSxFQUFFLElBQUksRUFBRTtZQUNoQixZQUFZLEVBQUUsS0FBSyxDQUFDLGNBQWMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLGNBQWMsQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxhQUFjLENBQUMsUUFBUTtTQUN4RyxDQUFDLENBQUM7UUFFSCxJQUFJLENBQUMsY0FBYyxHQUFHLEtBQUssQ0FBQyxVQUFVLENBQUM7SUFDM0MsQ0FBQztJQUVEOztPQUVHO0lBQ08sZ0JBQWdCO1FBQ3RCLE1BQU0sS0FBSyxHQUFHLEtBQUssQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDO1FBQ3ZDLE9BQU8sTUFBTSxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsbUJBQW1CLENBQUMsSUFBSSxDQUFDLGNBQWMsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQ2hGLENBQUM7Q0FFSjtBQTFCRCx3Q0EwQkM7QUFFRDs7Ozs7O0dBTUc7QUFDSCw2QkFBNkIsS0FBaUI7SUFDMUMsTUFBTSxHQUFHLEdBQWUsRUFBRSxDQUFDO0lBQzNCLE1BQU0sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxFQUFFO1FBQzdCLE1BQU0sS0FBSyxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLFdBQVcsRUFBRSxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDN0QsR0FBRyxDQUFDLEtBQUssQ0FBQyxHQUFHLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUM1QixDQUFDLENBQUMsQ0FBQztJQUNILE9BQU8sR0FBRyxDQUFDO0FBQ2YsQ0FBQyJ9 \ No newline at end of file diff --git a/packages/@aws-cdk/custom-resources/lib/singletonlambda.d.ts b/packages/@aws-cdk/custom-resources/lib/singletonlambda.d.ts new file mode 100644 index 0000000000000..601052a66fd60 --- /dev/null +++ b/packages/@aws-cdk/custom-resources/lib/singletonlambda.d.ts @@ -0,0 +1,31 @@ +import iam = require('@aws-cdk/aws-iam'); +import lambda = require('@aws-cdk/aws-lambda'); +import cdk = require('@aws-cdk/cdk'); +/** + * Properties for a newly created singleton Lambda + */ +export interface SingletonLambdaProps extends lambda.LambdaProps { + /** + * A unique identifier to identify this lambda + * + * The identifier should be unique across all custom resource providers. + * We recommend generating a UUID per provider. + */ + uuid: string; +} +/** + * A Lambda that will only ever be added to a stack once. + * + * The lambda is identified using the value of 'uuid'. Run 'uuidgen' + * for every SingletonLambda you create. + */ +export declare class SingletonLambda extends lambda.LambdaRef { + readonly functionName: lambda.FunctionName; + readonly functionArn: lambda.FunctionArn; + readonly role?: iam.Role | undefined; + protected readonly canCreatePermissions: boolean; + private lambdaFunction; + constructor(parent: cdk.Construct, name: string, props: SingletonLambdaProps); + addPermission(name: string, permission: lambda.LambdaPermission): void; + private ensureLambda; +} diff --git a/packages/@aws-cdk/custom-resources/lib/singletonlambda.js b/packages/@aws-cdk/custom-resources/lib/singletonlambda.js new file mode 100644 index 0000000000000..b0bcdeca55bfb --- /dev/null +++ b/packages/@aws-cdk/custom-resources/lib/singletonlambda.js @@ -0,0 +1,38 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const lambda = require("@aws-cdk/aws-lambda"); +const cdk = require("@aws-cdk/cdk"); +/** + * A Lambda that will only ever be added to a stack once. + * + * The lambda is identified using the value of 'uuid'. Run 'uuidgen' + * for every SingletonLambda you create. + */ +class SingletonLambda extends lambda.LambdaRef { + constructor(parent, name, props) { + super(parent, name); + this.lambdaFunction = this.ensureLambda(props.uuid, props); + this.functionArn = this.lambdaFunction.functionArn; + this.functionName = this.lambdaFunction.functionName; + this.role = this.lambdaFunction.role; + this.canCreatePermissions = true; // Doesn't matter, addPermission is overriden anyway + } + addPermission(name, permission) { + return this.lambdaFunction.addPermission(name, permission); + } + ensureLambda(uuid, props) { + const constructName = 'SingletonLambda' + slugify(uuid); + const stack = cdk.Stack.find(this); + const existing = stack.tryFindChild(constructName); + if (existing) { + // Just assume this is true + return existing; + } + return new lambda.Lambda(stack, constructName, props); + } +} +exports.SingletonLambda = SingletonLambda; +function slugify(x) { + return x.replace(/[^a-zA-Z0-9]/g, ''); +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2luZ2xldG9ubGFtYmRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsic2luZ2xldG9ubGFtYmRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQ0EsOENBQStDO0FBQy9DLG9DQUFxQztBQWVyQzs7Ozs7R0FLRztBQUNILHFCQUE2QixTQUFRLE1BQU0sQ0FBQyxTQUFTO0lBT2pELFlBQVksTUFBcUIsRUFBRSxJQUFZLEVBQUUsS0FBMkI7UUFDeEUsS0FBSyxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsQ0FBQztRQUVwQixJQUFJLENBQUMsY0FBYyxHQUFHLElBQUksQ0FBQyxZQUFZLENBQUMsS0FBSyxDQUFDLElBQUksRUFBRSxLQUFLLENBQUMsQ0FBQztRQUUzRCxJQUFJLENBQUMsV0FBVyxHQUFHLElBQUksQ0FBQyxjQUFjLENBQUMsV0FBVyxDQUFDO1FBQ25ELElBQUksQ0FBQyxZQUFZLEdBQUcsSUFBSSxDQUFDLGNBQWMsQ0FBQyxZQUFZLENBQUM7UUFDckQsSUFBSSxDQUFDLElBQUksR0FBRyxJQUFJLENBQUMsY0FBYyxDQUFDLElBQUksQ0FBQztRQUVyQyxJQUFJLENBQUMsb0JBQW9CLEdBQUcsSUFBSSxDQUFDLENBQUMsb0RBQW9EO0lBQzFGLENBQUM7SUFFTSxhQUFhLENBQUMsSUFBWSxFQUFFLFVBQW1DO1FBQ2xFLE9BQU8sSUFBSSxDQUFDLGNBQWMsQ0FBQyxhQUFhLENBQUMsSUFBSSxFQUFFLFVBQVUsQ0FBQyxDQUFDO0lBQy9ELENBQUM7SUFFTyxZQUFZLENBQUMsSUFBWSxFQUFFLEtBQXlCO1FBQ3hELE1BQU0sYUFBYSxHQUFHLGlCQUFpQixHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUN4RCxNQUFNLEtBQUssR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUNuQyxNQUFNLFFBQVEsR0FBRyxLQUFLLENBQUMsWUFBWSxDQUFDLGFBQWEsQ0FBQyxDQUFDO1FBQ25ELElBQUksUUFBUSxFQUFFO1lBQ1YsMkJBQTJCO1lBQzNCLE9BQU8sUUFBNEIsQ0FBQztTQUN2QztRQUVELE9BQU8sSUFBSSxNQUFNLENBQUMsTUFBTSxDQUFDLEtBQUssRUFBRSxhQUFhLEVBQUUsS0FBSyxDQUFDLENBQUM7SUFDMUQsQ0FBQztDQUNKO0FBbENELDBDQWtDQztBQUVELGlCQUFpQixDQUFTO0lBQ3RCLE9BQU8sQ0FBQyxDQUFDLE9BQU8sQ0FBQyxlQUFlLEVBQUUsRUFBRSxDQUFDLENBQUM7QUFDMUMsQ0FBQyJ9 \ No newline at end of file diff --git a/packages/@aws-cdk/custom-resources/test/integ.trivial-lambda-resource.d.ts b/packages/@aws-cdk/custom-resources/test/integ.trivial-lambda-resource.d.ts new file mode 100644 index 0000000000000..cb0ff5c3b541f --- /dev/null +++ b/packages/@aws-cdk/custom-resources/test/integ.trivial-lambda-resource.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/@aws-cdk/custom-resources/test/integ.trivial-lambda-resource.js b/packages/@aws-cdk/custom-resources/test/integ.trivial-lambda-resource.js new file mode 100644 index 0000000000000..c2e4b8d6264a1 --- /dev/null +++ b/packages/@aws-cdk/custom-resources/test/integ.trivial-lambda-resource.js @@ -0,0 +1,43 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const lambda = require("@aws-cdk/aws-lambda"); +const cdk = require("@aws-cdk/cdk"); +const fs = require("fs"); +const customResource = require("../lib"); +class DemoResource extends cdk.Construct { + constructor(parent, name, props) { + super(parent, name); + const resource = new customResource.CustomResource(this, 'Resource', { + lambdaProvider: new customResource.SingletonLambda(this, 'Singleton', { + uuid: 'f7d4f730-4ee1-11e8-9c2d-fa7ae01bbebc', + // This makes the demo only work as top-level TypeScript program, but that's fine for now + code: new lambda.LambdaInlineCode(fs.readFileSync('integ.trivial-lambda-provider.py', { encoding: 'utf-8' })), + handler: 'index.main', + timeout: 300, + runtime: lambda.LambdaRuntime.Python27, + }), + properties: props + }); + this.response = resource.getAtt('Response'); + } +} +/** + * A stack that only sets up the CustomResource and shows how to get an attribute from it + */ +class SucceedingStack extends cdk.Stack { + constructor(parent, name, props) { + super(parent, name, props); + const resource = new DemoResource(this, 'DemoResource', { + message: 'CustomResource says hello', + }); + // Publish the custom resource output + new cdk.Output(this, 'ResponseMessage', { + description: 'The message that came back from the Custom Resource', + value: resource.response + }); + } +} +const app = new cdk.App(process.argv); +new SucceedingStack(app, 'SucceedingStack'); +process.stdout.write(app.run()); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZWcudHJpdmlhbC1sYW1iZGEtcmVzb3VyY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbnRlZy50cml2aWFsLWxhbWJkYS1yZXNvdXJjZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLDhDQUErQztBQUMvQyxvQ0FBcUM7QUFDckMseUJBQTBCO0FBQzFCLHlDQUEwQztBQWMxQyxrQkFBbUIsU0FBUSxHQUFHLENBQUMsU0FBUztJQUdwQyxZQUFZLE1BQXFCLEVBQUUsSUFBWSxFQUFFLEtBQXdCO1FBQ3JFLEtBQUssQ0FBQyxNQUFNLEVBQUUsSUFBSSxDQUFDLENBQUM7UUFFcEIsTUFBTSxRQUFRLEdBQUcsSUFBSSxjQUFjLENBQUMsY0FBYyxDQUFDLElBQUksRUFBRSxVQUFVLEVBQUU7WUFDakUsY0FBYyxFQUFFLElBQUksY0FBYyxDQUFDLGVBQWUsQ0FBQyxJQUFJLEVBQUUsV0FBVyxFQUFFO2dCQUNsRSxJQUFJLEVBQUUsc0NBQXNDO2dCQUM1Qyx5RkFBeUY7Z0JBQ3pGLElBQUksRUFBRSxJQUFJLE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxFQUFFLENBQUMsWUFBWSxDQUFDLGtDQUFrQyxFQUFFLEVBQUUsUUFBUSxFQUFFLE9BQU8sRUFBRSxDQUFDLENBQUM7Z0JBQzdHLE9BQU8sRUFBRSxZQUFZO2dCQUNyQixPQUFPLEVBQUUsR0FBRztnQkFDWixPQUFPLEVBQUUsTUFBTSxDQUFDLGFBQWEsQ0FBQyxRQUFRO2FBQ3pDLENBQUM7WUFDRixVQUFVLEVBQUUsS0FBSztTQUNwQixDQUFDLENBQUM7UUFFSCxJQUFJLENBQUMsUUFBUSxHQUFHLFFBQVEsQ0FBQyxNQUFNLENBQUMsVUFBVSxDQUFDLENBQUM7SUFDaEQsQ0FBQztDQUNKO0FBRUQ7O0dBRUc7QUFDSCxxQkFBc0IsU0FBUSxHQUFHLENBQUMsS0FBSztJQUNuQyxZQUFZLE1BQWUsRUFBRSxJQUFZLEVBQUUsS0FBc0I7UUFDN0QsS0FBSyxDQUFDLE1BQU0sRUFBRSxJQUFJLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFFM0IsTUFBTSxRQUFRLEdBQUcsSUFBSSxZQUFZLENBQUMsSUFBSSxFQUFFLGNBQWMsRUFBRTtZQUNwRCxPQUFPLEVBQUUsMkJBQTJCO1NBQ3ZDLENBQUMsQ0FBQztRQUVILHFDQUFxQztRQUNyQyxJQUFJLEdBQUcsQ0FBQyxNQUFNLENBQUMsSUFBSSxFQUFFLGlCQUFpQixFQUFFO1lBQ3BDLFdBQVcsRUFBRSxxREFBcUQ7WUFDbEUsS0FBSyxFQUFFLFFBQVEsQ0FBQyxRQUFRO1NBQzNCLENBQUMsQ0FBQztJQUNQLENBQUM7Q0FDSjtBQUNELE1BQU0sR0FBRyxHQUFHLElBQUksR0FBRyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUM7QUFFdEMsSUFBSSxlQUFlLENBQUMsR0FBRyxFQUFFLGlCQUFpQixDQUFDLENBQUM7QUFFNUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLEdBQUcsRUFBRSxDQUFDLENBQUMifQ== \ No newline at end of file diff --git a/packages/@aws-cdk/custom-resources/test/test.resource.d.ts b/packages/@aws-cdk/custom-resources/test/test.resource.d.ts new file mode 100644 index 0000000000000..7a7413d4d7613 --- /dev/null +++ b/packages/@aws-cdk/custom-resources/test/test.resource.d.ts @@ -0,0 +1,5 @@ +import { Test } from 'nodeunit'; +declare const _default: { + 'custom resource is added twice, lambda is added once'(test: Test): void; +}; +export = _default; diff --git a/packages/@aws-cdk/custom-resources/test/test.resource.js b/packages/@aws-cdk/custom-resources/test/test.resource.js new file mode 100644 index 0000000000000..853f993a97386 --- /dev/null +++ b/packages/@aws-cdk/custom-resources/test/test.resource.js @@ -0,0 +1,101 @@ +"use strict"; +const assert_1 = require("@aws-cdk/assert"); +const lambda = require("@aws-cdk/aws-lambda"); +const cdk = require("@aws-cdk/cdk"); +const lib_1 = require("../lib"); +class TestCustomResource extends cdk.Construct { + constructor(parent, name) { + super(parent, name); + const singletonLambda = new lib_1.SingletonLambda(this, 'Lambda', { + uuid: 'TestCustomResourceProvider', + code: new lambda.LambdaInlineCode('def hello(): pass'), + runtime: lambda.LambdaRuntime.Python27, + handler: 'index.hello', + timeout: 300, + }); + new lib_1.CustomResource(this, 'Resource', { + lambdaProvider: singletonLambda + }); + } +} +module.exports = { + 'custom resource is added twice, lambda is added once'(test) { + // GIVEN + const stack = new cdk.Stack(); + // WHEN + new TestCustomResource(stack, 'Custom1'); + new TestCustomResource(stack, 'Custom2'); + // THEN + assert_1.expect(stack).toMatch({ + "Resources": { + "SingletonLambdaTestCustomResourceProviderServiceRole81FEAB5C": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { "Fn::Join": ["", [ + "arn", ":", { "Ref": "AWS::Partition" }, ":", "iam", ":", "", ":", "aws", ":", "policy", "/", + "service-role/AWSLambdaBasicExecutionRole" + ]] } + ] + } + }, + "SingletonLambdaTestCustomResourceProviderA9255269": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "def hello(): pass" + }, + "Handler": "index.hello", + "Role": { + "Fn::GetAtt": [ + "SingletonLambdaTestCustomResourceProviderServiceRole81FEAB5C", + "Arn" + ] + }, + "Runtime": "python2.7", + "Timeout": 300 + }, + "DependsOn": [ + "SingletonLambdaTestCustomResourceProviderServiceRole81FEAB5C" + ] + }, + "Custom1D319B237": { + "Type": "AWS::CloudFormation::CustomResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonLambdaTestCustomResourceProviderA9255269", + "Arn" + ] + } + } + }, + "Custom2DD5FB44D": { + "Type": "AWS::CloudFormation::CustomResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonLambdaTestCustomResourceProviderA9255269", + "Arn" + ] + } + } + } + } + }); + test.done(); + } +}; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVzdC5yZXNvdXJjZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInRlc3QucmVzb3VyY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLDRDQUF5QztBQUN6Qyw4Q0FBK0M7QUFDL0Msb0NBQXFDO0FBRXJDLGdDQUF5RDtBQXNGekQsd0JBQXlCLFNBQVEsR0FBRyxDQUFDLFNBQVM7SUFDNUMsWUFBWSxNQUFxQixFQUFFLElBQVk7UUFDN0MsS0FBSyxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsQ0FBQztRQUVwQixNQUFNLGVBQWUsR0FBRyxJQUFJLHFCQUFlLENBQUMsSUFBSSxFQUFFLFFBQVEsRUFBRTtZQUMxRCxJQUFJLEVBQUUsNEJBQTRCO1lBQ2xDLElBQUksRUFBRSxJQUFJLE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxtQkFBbUIsQ0FBQztZQUN0RCxPQUFPLEVBQUUsTUFBTSxDQUFDLGFBQWEsQ0FBQyxRQUFRO1lBQ3RDLE9BQU8sRUFBRSxhQUFhO1lBQ3RCLE9BQU8sRUFBRSxHQUFHO1NBQ2IsQ0FBQyxDQUFDO1FBRUgsSUFBSSxvQkFBYyxDQUFDLElBQUksRUFBRSxVQUFVLEVBQUU7WUFDbkMsY0FBYyxFQUFFLGVBQWU7U0FDaEMsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztDQUNGO0FBbEdELGlCQUFTO0lBQ0wsc0RBQXNELENBQUMsSUFBVTtRQUM3RCxRQUFRO1FBQ1IsTUFBTSxLQUFLLEdBQUcsSUFBSSxHQUFHLENBQUMsS0FBSyxFQUFFLENBQUM7UUFFOUIsT0FBTztRQUNQLElBQUksa0JBQWtCLENBQUMsS0FBSyxFQUFFLFNBQVMsQ0FBQyxDQUFDO1FBQ3pDLElBQUksa0JBQWtCLENBQUMsS0FBSyxFQUFFLFNBQVMsQ0FBQyxDQUFDO1FBRXpDLE9BQU87UUFDUCxlQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsT0FBTyxDQUFDO1lBQ2xCLFdBQVcsRUFBRTtnQkFDWCw4REFBOEQsRUFBRTtvQkFDOUQsTUFBTSxFQUFFLGdCQUFnQjtvQkFDeEIsWUFBWSxFQUFFO3dCQUNaLDBCQUEwQixFQUFFOzRCQUMxQixXQUFXLEVBQUU7Z0NBQ1g7b0NBQ0UsUUFBUSxFQUFFLGdCQUFnQjtvQ0FDMUIsUUFBUSxFQUFFLE9BQU87b0NBQ2pCLFdBQVcsRUFBRTt3Q0FDWCxTQUFTLEVBQUUsc0JBQXNCO3FDQUNsQztpQ0FDRjs2QkFDRjs0QkFDRCxTQUFTLEVBQUUsWUFBWTt5QkFDeEI7d0JBQ0QsbUJBQW1CLEVBQUU7NEJBQ2pCLEVBQUUsVUFBVSxFQUFFLENBQUUsRUFBRSxFQUFFO3dDQUNoQixLQUFLLEVBQUUsR0FBRyxFQUFFLEVBQUUsS0FBSyxFQUFFLGdCQUFnQixFQUFFLEVBQUUsR0FBRyxFQUFFLEtBQUssRUFBRSxHQUFHLEVBQUUsRUFBRSxFQUFFLEdBQUcsRUFBRSxLQUFLLEVBQUUsR0FBRyxFQUFFLFFBQVEsRUFBRSxHQUFHO3dDQUM1RiwwQ0FBMEM7cUNBQUUsQ0FBRSxFQUFDO3lCQUN0RDtxQkFDRjtpQkFDRjtnQkFDRCxtREFBbUQsRUFBRTtvQkFDbkQsTUFBTSxFQUFFLHVCQUF1QjtvQkFDL0IsWUFBWSxFQUFFO3dCQUNaLE1BQU0sRUFBRTs0QkFDTixTQUFTLEVBQUUsbUJBQW1CO3lCQUMvQjt3QkFDRCxTQUFTLEVBQUUsYUFBYTt3QkFDeEIsTUFBTSxFQUFFOzRCQUNOLFlBQVksRUFBRTtnQ0FDWiw4REFBOEQ7Z0NBQzlELEtBQUs7NkJBQ047eUJBQ0Y7d0JBQ0QsU0FBUyxFQUFFLFdBQVc7d0JBQ3RCLFNBQVMsRUFBRSxHQUFHO3FCQUNmO29CQUNELFdBQVcsRUFBRTt3QkFDWCw4REFBOEQ7cUJBQy9EO2lCQUNGO2dCQUNELGlCQUFpQixFQUFFO29CQUNqQixNQUFNLEVBQUUscUNBQXFDO29CQUM3QyxZQUFZLEVBQUU7d0JBQ1osY0FBYyxFQUFFOzRCQUNkLFlBQVksRUFBRTtnQ0FDWixtREFBbUQ7Z0NBQ25ELEtBQUs7NkJBQ047eUJBQ0Y7cUJBQ0Y7aUJBQ0Y7Z0JBQ0QsaUJBQWlCLEVBQUU7b0JBQ2pCLE1BQU0sRUFBRSxxQ0FBcUM7b0JBQzdDLFlBQVksRUFBRTt3QkFDWixjQUFjLEVBQUU7NEJBQ2QsWUFBWSxFQUFFO2dDQUNaLG1EQUFtRDtnQ0FDbkQsS0FBSzs2QkFDTjt5QkFDRjtxQkFDRjtpQkFDRjthQUNGO1NBQ0osQ0FBQyxDQUFDO1FBQ0gsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDO0lBQ2hCLENBQUM7Q0FDSixDQUFDIn0= \ No newline at end of file diff --git a/packages/@aws-cdk/custom-resources/test/test.singletonlambda.d.ts b/packages/@aws-cdk/custom-resources/test/test.singletonlambda.d.ts new file mode 100644 index 0000000000000..8277fa23a5f84 --- /dev/null +++ b/packages/@aws-cdk/custom-resources/test/test.singletonlambda.d.ts @@ -0,0 +1,5 @@ +import { Test } from 'nodeunit'; +declare const _default: { + 'can add same singleton Lambda multiple times, only instantiated once in template'(test: Test): void; +}; +export = _default; diff --git a/packages/@aws-cdk/custom-resources/test/test.singletonlambda.js b/packages/@aws-cdk/custom-resources/test/test.singletonlambda.js new file mode 100644 index 0000000000000..e062decd66642 --- /dev/null +++ b/packages/@aws-cdk/custom-resources/test/test.singletonlambda.js @@ -0,0 +1,62 @@ +"use strict"; +const assert_1 = require("@aws-cdk/assert"); +const lambda = require("@aws-cdk/aws-lambda"); +const cdk = require("@aws-cdk/cdk"); +const lib_1 = require("../lib"); +module.exports = { + 'can add same singleton Lambda multiple times, only instantiated once in template'(test) { + // GIVEN + const stack = new cdk.Stack(); + // WHEN + for (let i = 0; i < 5; i++) { + new lib_1.SingletonLambda(stack, `Singleton${i}`, { + uuid: '84c0de93-353f-4217-9b0b-45b6c993251a', + code: new lambda.LambdaInlineCode('def hello(): pass'), + runtime: lambda.LambdaRuntime.Python27, + handler: 'index.hello', + timeout: 300, + }); + } + // THEN + assert_1.expect(stack).to(assert_1.matchTemplate({ + Resources: { + SingletonLambda84c0de93353f42179b0b45b6c993251aServiceRole26D59235: { + Type: "AWS::IAM::Role", + Properties: { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: "sts:AssumeRole", + Effect: "Allow", + Principal: { Service: "lambda.amazonaws.com" } + } + ], + Version: "2012-10-17" + }, + ManagedPolicyArns: [ + { + "Fn::Join": ["", ["arn", ":", { Ref: "AWS::Partition" }, ":", "iam", ":", "", + ":", "aws", ":", "policy", "/", "service-role/AWSLambdaBasicExecutionRole"]] + } + ] + } + }, + SingletonLambda84c0de93353f42179b0b45b6c993251a840BCC38: { + Type: "AWS::Lambda::Function", + Properties: { + Code: { + ZipFile: "def hello(): pass" + }, + Handler: "index.hello", + Role: { "Fn::GetAtt": ["SingletonLambda84c0de93353f42179b0b45b6c993251aServiceRole26D59235", "Arn"] }, + Runtime: "python2.7", + Timeout: 300 + }, + DependsOn: ["SingletonLambda84c0de93353f42179b0b45b6c993251aServiceRole26D59235"] + } + } + })); + test.done(); + } +}; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVzdC5zaW5nbGV0b25sYW1iZGEuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJ0ZXN0LnNpbmdsZXRvbmxhbWJkYS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsNENBQXdEO0FBQ3hELDhDQUErQztBQUMvQyxvQ0FBcUM7QUFFckMsZ0NBQXlDO0FBRXpDLGlCQUFTO0lBQ1Asa0ZBQWtGLENBQUMsSUFBVTtRQUMzRixRQUFRO1FBQ1IsTUFBTSxLQUFLLEdBQUcsSUFBSSxHQUFHLENBQUMsS0FBSyxFQUFFLENBQUM7UUFFOUIsT0FBTztRQUNQLEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxFQUFFLEVBQUU7WUFDMUIsSUFBSSxxQkFBZSxDQUFDLEtBQUssRUFBRSxZQUFZLENBQUMsRUFBRSxFQUFFO2dCQUMxQyxJQUFJLEVBQUUsc0NBQXNDO2dCQUM1QyxJQUFJLEVBQUUsSUFBSSxNQUFNLENBQUMsZ0JBQWdCLENBQUMsbUJBQW1CLENBQUM7Z0JBQ3RELE9BQU8sRUFBRSxNQUFNLENBQUMsYUFBYSxDQUFDLFFBQVE7Z0JBQ3RDLE9BQU8sRUFBRSxhQUFhO2dCQUN0QixPQUFPLEVBQUUsR0FBRzthQUNiLENBQUMsQ0FBQztTQUNKO1FBRUQsT0FBTztRQUNQLGVBQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLENBQUMsc0JBQWEsQ0FBQztZQUM3QixTQUFTLEVBQUU7Z0JBQ1Qsa0VBQWtFLEVBQUU7b0JBQ2xFLElBQUksRUFBRSxnQkFBZ0I7b0JBQ3RCLFVBQVUsRUFBRTt3QkFDVix3QkFBd0IsRUFBRTs0QkFDeEIsU0FBUyxFQUFFO2dDQUNUO29DQUNFLE1BQU0sRUFBRSxnQkFBZ0I7b0NBQ3hCLE1BQU0sRUFBRSxPQUFPO29DQUNmLFNBQVMsRUFBRSxFQUFFLE9BQU8sRUFBRSxzQkFBc0IsRUFBRTtpQ0FDL0M7NkJBQ0Y7NEJBQ0QsT0FBTyxFQUFFLFlBQVk7eUJBQ3RCO3dCQUNELGlCQUFpQixFQUFFOzRCQUNqQjtnQ0FDRSxVQUFVLEVBQUUsQ0FBRSxFQUFFLEVBQUUsQ0FBRSxLQUFLLEVBQUUsR0FBRyxFQUFFLEVBQUUsR0FBRyxFQUFFLGdCQUFnQixFQUFFLEVBQUUsR0FBRyxFQUFFLEtBQUssRUFBRSxHQUFHLEVBQUUsRUFBRTt3Q0FDMUUsR0FBRyxFQUFFLEtBQUssRUFBRSxHQUFHLEVBQUUsUUFBUSxFQUFFLEdBQUcsRUFBRSwwQ0FBMEMsQ0FBRSxDQUFFOzZCQUNuRjt5QkFDRjtxQkFDRjtpQkFDRjtnQkFDRCx1REFBdUQsRUFBRTtvQkFDdkQsSUFBSSxFQUFFLHVCQUF1QjtvQkFDN0IsVUFBVSxFQUFFO3dCQUNWLElBQUksRUFBRTs0QkFDSixPQUFPLEVBQUUsbUJBQW1CO3lCQUM3Qjt3QkFDRCxPQUFPLEVBQUUsYUFBYTt3QkFDdEIsSUFBSSxFQUFFLEVBQUUsWUFBWSxFQUFFLENBQUUsb0VBQW9FLEVBQUUsS0FBSyxDQUFFLEVBQUU7d0JBQ3ZHLE9BQU8sRUFBRSxXQUFXO3dCQUNwQixPQUFPLEVBQUUsR0FBRztxQkFDYjtvQkFDRCxTQUFTLEVBQUUsQ0FBRSxvRUFBb0UsQ0FBRTtpQkFDcEY7YUFDRjtTQUNGLENBQUMsQ0FBQyxDQUFDO1FBRUosSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDO0lBQ2QsQ0FBQztDQUNGLENBQUMifQ== \ No newline at end of file diff --git a/packages/@aws-cdk/custom-resources/tsconfig.json b/packages/@aws-cdk/custom-resources/tsconfig.json new file mode 100644 index 0000000000000..5ae4f32b6ed34 --- /dev/null +++ b/packages/@aws-cdk/custom-resources/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "es2018", + "lib": [ + "es2016", + "es2017.object", + "es2017.string" + ], + "module": "commonjs", + "declaration": true, + "strict": true, + "strictPropertyInitialization": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "inlineSourceMap": true, + "experimentalDecorators": true, + "jsx": "react", + "jsxFactory": "jsx.create" + }, + "_generated_by_jsii_": "generated by jsii - you can delete, and ideally add to your .gitignore" +} diff --git a/packages/@aws-cdk/custom-resources/tslint.json b/packages/@aws-cdk/custom-resources/tslint.json new file mode 100644 index 0000000000000..ddd9bc8e0f437 --- /dev/null +++ b/packages/@aws-cdk/custom-resources/tslint.json @@ -0,0 +1,38 @@ +{ + "extends": "tslint:recommended", + "rules": { + "semicolon": [ + true, + "always", + "ignore-interfaces" + ], + "no-invalid-template-strings": false, + "quotemark": false, + "interface-name": false, + "max-classes-per-file": false, + "member-access": { + "severity": "warning" + }, + "interface-over-type-literal": false, + "eofline": false, + "arrow-parens": false, + "no-namespace": false, + "max-line-length": [ + true, + 150 + ], + "object-literal-sort-keys": false, + "trailing-comma": false, + "no-unused-expression": [ + true, + "allow-new" + ], + "variable-name": [ + true, + "ban-keywords", + "check-format", + "allow-leading-underscore", + "allow-pascal-case" + ] + } +} From 9469a7109e7ecca88bdbadf861ea7d99d6faf11d Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 8 Aug 2018 15:32:30 +0200 Subject: [PATCH 02/17] Remove incorrectly staged custom-resources directory --- packages/@aws-cdk/custom-resources/.jsii | 3109 ----------------- .../@aws-cdk/custom-resources/lib/index.d.ts | 2 - .../@aws-cdk/custom-resources/lib/index.js | 8 - .../custom-resources/lib/resource.d.ts | 50 - .../@aws-cdk/custom-resources/lib/resource.js | 45 - .../custom-resources/lib/singletonlambda.d.ts | 31 - .../custom-resources/lib/singletonlambda.js | 38 - .../test/integ.trivial-lambda-resource.d.ts | 1 - .../test/integ.trivial-lambda-resource.js | 43 - .../custom-resources/test/test.resource.d.ts | 5 - .../custom-resources/test/test.resource.js | 101 - .../test/test.singletonlambda.d.ts | 5 - .../test/test.singletonlambda.js | 62 - .../@aws-cdk/custom-resources/tsconfig.json | 27 - .../@aws-cdk/custom-resources/tslint.json | 38 - 15 files changed, 3565 deletions(-) delete mode 100644 packages/@aws-cdk/custom-resources/.jsii delete mode 100644 packages/@aws-cdk/custom-resources/lib/index.d.ts delete mode 100644 packages/@aws-cdk/custom-resources/lib/index.js delete mode 100644 packages/@aws-cdk/custom-resources/lib/resource.d.ts delete mode 100644 packages/@aws-cdk/custom-resources/lib/resource.js delete mode 100644 packages/@aws-cdk/custom-resources/lib/singletonlambda.d.ts delete mode 100644 packages/@aws-cdk/custom-resources/lib/singletonlambda.js delete mode 100644 packages/@aws-cdk/custom-resources/test/integ.trivial-lambda-resource.d.ts delete mode 100644 packages/@aws-cdk/custom-resources/test/integ.trivial-lambda-resource.js delete mode 100644 packages/@aws-cdk/custom-resources/test/test.resource.d.ts delete mode 100644 packages/@aws-cdk/custom-resources/test/test.resource.js delete mode 100644 packages/@aws-cdk/custom-resources/test/test.singletonlambda.d.ts delete mode 100644 packages/@aws-cdk/custom-resources/test/test.singletonlambda.js delete mode 100644 packages/@aws-cdk/custom-resources/tsconfig.json delete mode 100644 packages/@aws-cdk/custom-resources/tslint.json diff --git a/packages/@aws-cdk/custom-resources/.jsii b/packages/@aws-cdk/custom-resources/.jsii deleted file mode 100644 index 4a4b16aa1367b..0000000000000 --- a/packages/@aws-cdk/custom-resources/.jsii +++ /dev/null @@ -1,3109 +0,0 @@ -{ - "fingerprint": "FrhACMViy4m74joZ6LnVKtaQBmKEVsLwOmGHm4enaWI=", - "author": { - "name": "Amazon Web Services", - "organization": true, - "roles": [ - "author" - ], - "url": "https://aws.amazon.com" - }, - "dependencies": { - "@aws-cdk/aws-cloudformation": { - "dependencies": { - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.CloudFormation" - }, - "java": { - "maven": { - "artifactId": "aws-cloudformation", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.cloudformation" - }, - "js": { - "npm": "@aws-cdk/aws-cloudformation" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/aws-iam": { - "dependencies": { - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.IAM" - }, - "java": { - "maven": { - "artifactId": "aws-iam", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.iam" - }, - "js": { - "npm": "@aws-cdk/aws-iam" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/aws-lambda": { - "dependencies": { - "@aws-cdk/assets": { - "dependencies": { - "@aws-cdk/aws-iam": { - "dependencies": { - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.IAM" - }, - "java": { - "maven": { - "artifactId": "aws-iam", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.iam" - }, - "js": { - "npm": "@aws-cdk/aws-iam" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/aws-s3": { - "dependencies": { - "@aws-cdk/aws-iam": { - "dependencies": { - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.IAM" - }, - "java": { - "maven": { - "artifactId": "aws-iam", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.iam" - }, - "js": { - "npm": "@aws-cdk/aws-iam" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/aws-kms": { - "dependencies": { - "@aws-cdk/aws-iam": { - "dependencies": { - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.IAM" - }, - "java": { - "maven": { - "artifactId": "aws-iam", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.iam" - }, - "js": { - "npm": "@aws-cdk/aws-iam" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.KMS" - }, - "java": { - "maven": { - "artifactId": "aws-kms", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.kms" - }, - "js": { - "npm": "@aws-cdk/aws-kms" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.S3" - }, - "java": { - "maven": { - "artifactId": "aws-s3", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.s3" - }, - "js": { - "npm": "@aws-cdk/aws-s3" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.Assets" - }, - "java": { - "maven": { - "artifactId": "assets", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.assets" - }, - "js": { - "npm": "@aws-cdk/assets" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/aws-cloudwatch": { - "dependencies": { - "@aws-cdk/aws-iam": { - "dependencies": { - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.IAM" - }, - "java": { - "maven": { - "artifactId": "aws-iam", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.iam" - }, - "js": { - "npm": "@aws-cdk/aws-iam" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.CloudWatch" - }, - "java": { - "maven": { - "artifactId": "aws-cloudwatch", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.cloudwatch" - }, - "js": { - "npm": "@aws-cdk/aws-cloudwatch" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/aws-events": { - "dependencies": { - "@aws-cdk/aws-iam": { - "dependencies": { - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.IAM" - }, - "java": { - "maven": { - "artifactId": "aws-iam", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.iam" - }, - "js": { - "npm": "@aws-cdk/aws-iam" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.Events" - }, - "java": { - "maven": { - "artifactId": "aws-events", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.events" - }, - "js": { - "npm": "@aws-cdk/aws-events" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/aws-iam": { - "dependencies": { - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.IAM" - }, - "java": { - "maven": { - "artifactId": "aws-iam", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.iam" - }, - "js": { - "npm": "@aws-cdk/aws-iam" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/aws-logs": { - "dependencies": { - "@aws-cdk/aws-iam": { - "dependencies": { - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.IAM" - }, - "java": { - "maven": { - "artifactId": "aws-iam", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.iam" - }, - "js": { - "npm": "@aws-cdk/aws-iam" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.Logs" - }, - "java": { - "maven": { - "artifactId": "aws-logs", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.logs" - }, - "js": { - "npm": "@aws-cdk/aws-logs" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/aws-s3": { - "dependencies": { - "@aws-cdk/aws-iam": { - "dependencies": { - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.IAM" - }, - "java": { - "maven": { - "artifactId": "aws-iam", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.iam" - }, - "js": { - "npm": "@aws-cdk/aws-iam" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/aws-kms": { - "dependencies": { - "@aws-cdk/aws-iam": { - "dependencies": { - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.IAM" - }, - "java": { - "maven": { - "artifactId": "aws-iam", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.iam" - }, - "js": { - "npm": "@aws-cdk/aws-iam" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.KMS" - }, - "java": { - "maven": { - "artifactId": "aws-kms", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.kms" - }, - "js": { - "npm": "@aws-cdk/aws-kms" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.S3" - }, - "java": { - "maven": { - "artifactId": "aws-s3", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.s3" - }, - "js": { - "npm": "@aws-cdk/aws-s3" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.Lambda" - }, - "java": { - "maven": { - "artifactId": "aws-lambda", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.lambda" - }, - "js": { - "npm": "@aws-cdk/aws-lambda" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/aws-sns": { - "dependencies": { - "@aws-cdk/aws-cloudwatch": { - "dependencies": { - "@aws-cdk/aws-iam": { - "dependencies": { - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.IAM" - }, - "java": { - "maven": { - "artifactId": "aws-iam", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.iam" - }, - "js": { - "npm": "@aws-cdk/aws-iam" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.CloudWatch" - }, - "java": { - "maven": { - "artifactId": "aws-cloudwatch", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.cloudwatch" - }, - "js": { - "npm": "@aws-cdk/aws-cloudwatch" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/aws-events": { - "dependencies": { - "@aws-cdk/aws-iam": { - "dependencies": { - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.IAM" - }, - "java": { - "maven": { - "artifactId": "aws-iam", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.iam" - }, - "js": { - "npm": "@aws-cdk/aws-iam" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.Events" - }, - "java": { - "maven": { - "artifactId": "aws-events", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.events" - }, - "js": { - "npm": "@aws-cdk/aws-events" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/aws-iam": { - "dependencies": { - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.IAM" - }, - "java": { - "maven": { - "artifactId": "aws-iam", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.iam" - }, - "js": { - "npm": "@aws-cdk/aws-iam" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/aws-lambda": { - "dependencies": { - "@aws-cdk/assets": { - "dependencies": { - "@aws-cdk/aws-iam": { - "dependencies": { - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.IAM" - }, - "java": { - "maven": { - "artifactId": "aws-iam", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.iam" - }, - "js": { - "npm": "@aws-cdk/aws-iam" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/aws-s3": { - "dependencies": { - "@aws-cdk/aws-iam": { - "dependencies": { - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.IAM" - }, - "java": { - "maven": { - "artifactId": "aws-iam", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.iam" - }, - "js": { - "npm": "@aws-cdk/aws-iam" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/aws-kms": { - "dependencies": { - "@aws-cdk/aws-iam": { - "dependencies": { - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.IAM" - }, - "java": { - "maven": { - "artifactId": "aws-iam", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.iam" - }, - "js": { - "npm": "@aws-cdk/aws-iam" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.KMS" - }, - "java": { - "maven": { - "artifactId": "aws-kms", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.kms" - }, - "js": { - "npm": "@aws-cdk/aws-kms" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.S3" - }, - "java": { - "maven": { - "artifactId": "aws-s3", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.s3" - }, - "js": { - "npm": "@aws-cdk/aws-s3" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.Assets" - }, - "java": { - "maven": { - "artifactId": "assets", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.assets" - }, - "js": { - "npm": "@aws-cdk/assets" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/aws-cloudwatch": { - "dependencies": { - "@aws-cdk/aws-iam": { - "dependencies": { - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.IAM" - }, - "java": { - "maven": { - "artifactId": "aws-iam", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.iam" - }, - "js": { - "npm": "@aws-cdk/aws-iam" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.CloudWatch" - }, - "java": { - "maven": { - "artifactId": "aws-cloudwatch", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.cloudwatch" - }, - "js": { - "npm": "@aws-cdk/aws-cloudwatch" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/aws-events": { - "dependencies": { - "@aws-cdk/aws-iam": { - "dependencies": { - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.IAM" - }, - "java": { - "maven": { - "artifactId": "aws-iam", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.iam" - }, - "js": { - "npm": "@aws-cdk/aws-iam" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.Events" - }, - "java": { - "maven": { - "artifactId": "aws-events", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.events" - }, - "js": { - "npm": "@aws-cdk/aws-events" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/aws-iam": { - "dependencies": { - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.IAM" - }, - "java": { - "maven": { - "artifactId": "aws-iam", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.iam" - }, - "js": { - "npm": "@aws-cdk/aws-iam" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/aws-logs": { - "dependencies": { - "@aws-cdk/aws-iam": { - "dependencies": { - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.IAM" - }, - "java": { - "maven": { - "artifactId": "aws-iam", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.iam" - }, - "js": { - "npm": "@aws-cdk/aws-iam" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.Logs" - }, - "java": { - "maven": { - "artifactId": "aws-logs", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.logs" - }, - "js": { - "npm": "@aws-cdk/aws-logs" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/aws-s3": { - "dependencies": { - "@aws-cdk/aws-iam": { - "dependencies": { - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.IAM" - }, - "java": { - "maven": { - "artifactId": "aws-iam", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.iam" - }, - "js": { - "npm": "@aws-cdk/aws-iam" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/aws-kms": { - "dependencies": { - "@aws-cdk/aws-iam": { - "dependencies": { - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.IAM" - }, - "java": { - "maven": { - "artifactId": "aws-iam", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.iam" - }, - "js": { - "npm": "@aws-cdk/aws-iam" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.KMS" - }, - "java": { - "maven": { - "artifactId": "aws-kms", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.kms" - }, - "js": { - "npm": "@aws-cdk/aws-kms" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.S3" - }, - "java": { - "maven": { - "artifactId": "aws-s3", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.s3" - }, - "js": { - "npm": "@aws-cdk/aws-s3" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.Lambda" - }, - "java": { - "maven": { - "artifactId": "aws-lambda", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.lambda" - }, - "js": { - "npm": "@aws-cdk/aws-lambda" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/aws-sqs": { - "dependencies": { - "@aws-cdk/aws-kms": { - "dependencies": { - "@aws-cdk/aws-iam": { - "dependencies": { - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.IAM" - }, - "java": { - "maven": { - "artifactId": "aws-iam", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.iam" - }, - "js": { - "npm": "@aws-cdk/aws-iam" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.KMS" - }, - "java": { - "maven": { - "artifactId": "aws-kms", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.kms" - }, - "js": { - "npm": "@aws-cdk/aws-kms" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.SQS" - }, - "java": { - "maven": { - "artifactId": "aws-sqs", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.sqs" - }, - "js": { - "npm": "@aws-cdk/aws-sqs" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.AWS.SNS" - }, - "java": { - "maven": { - "artifactId": "aws-sns", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.aws.sns" - }, - "js": { - "npm": "@aws-cdk/aws-sns" - } - }, - "version": "0.8.0" - }, - "@aws-cdk/cdk": { - "dependencies": { - "@aws-cdk/cx-api": { - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CXAPI" - }, - "java": { - "maven": { - "artifactId": "cxapi", - "groupId": "com.amazonaws.cdk" - }, - "package": "com.amazonaws.cdk.cxapi" - }, - "js": { - "npm": "@aws-cdk/cx-api" - } - }, - "version": "0.8.0" - } - }, - "targets": { - "dotnet": { - "namespace": "Amazon.CDK" - }, - "java": { - "maven": { - "artifactId": "cdk", - "groupId": "software.amazon.awscdk" - }, - "package": "software.amazon.awscdk" - }, - "js": { - "npm": "@aws-cdk/cdk" - } - }, - "version": "0.8.0" - } - }, - "description": "CDK Constructs to make it easier to build custom resources", - "homepage": "https://github.com/awslabs/aws-cdk", - "license": "Apache-2.0", - "name": "@aws-cdk/custom-resources", - "readme": { - "markdown": "## CDK Custom Resources\n\nCustom Resources are CloudFormation resources that are implemented by\narbitrary user code. They can do arbitrary lookups or modifications\nduring a CloudFormation synthesis run.\n\nYou will typically use Lambda to implement a Construct implemented as a\nCustom Resource (though SNS topics can be used as well). Your Lambda function\nwill be sent a `CREATE`, `UPDATE` or `DELETE` message, depending on the\nCloudFormation life cycle, and can return any number of output values which\nwill be available as attributes of your Construct. In turn, those can\nbe used as input to other Constructs in your model.\n\nIn general, consumers of your Construct will not need to care whether\nit is implemented in term of other CloudFormation resources or as a\ncustom resource.\n\nNote: when implementing your Custom Resource using a Lambda, use\na `SingletonLambda` so that even if your custom resource is instantiated\nmultiple times, the Lambda will only get uploaded once.\n\n### Example\n\nSample of a Custom Resource that copies files into an S3 bucket during deployment\n(implementation of actual `copy.py` operation elided).\n\n```ts\ninterface CopyOperationProps {\n sourceBucket: IBucket;\n targetBucket: IBucket;\n}\n\nclass CopyOperation extends Construct {\n constructor(parent: Construct, name: string, props: DemoResourceProps) {\n super(parent, name);\n\n const lambdaProvider = new SingletonLambda(this, 'Provider', {\n uuid: 'f7d4f730-4ee1-11e8-9c2d-fa7ae01bbebc',\n code: new LambdaInlineCode(resources['copy.py']),\n handler: 'index.handler',\n timeout: 60,\n runtime: LambdaRuntime.Python3,\n });\n\n new CustomResource(this, 'Resource', {\n lambdaProvider,\n properties: {\n sourceBucketArn: props.sourceBucket.bucketArn,\n targetBucketArn: props.targetBucket.bucketArn,\n }\n });\n }\n}\n```\n\nMore examples are in the `example` directory, including an example of how to use\nthe `cfnresponse` module that is provided for you by CloudFormation.\n\n### References\n\nSee the following section of the docs on details to write Custom Resources:\n\n* [Introduction](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html)\n* [Reference](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref.html)\n* [Code Reference](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html)\n" - }, - "repository": { - "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" - }, - "schema": "jsii/1.0", - "targets": { - "dotnet": { - "namespace": "Amazon.CDK.CustomResources" - }, - "java": { - "maven": { - "artifactId": "cdk-custom-resources", - "groupId": "software.amazon.awscdk" - }, - "package": "software.amazon.awscdk.customresources" - }, - "js": { - "npm": "@aws-cdk/custom-resources" - } - }, - "types": { - "@aws-cdk/custom-resources.CustomResource": { - "assembly": "@aws-cdk/custom-resources", - "base": { - "fqn": "@aws-cdk/aws-cloudformation.cloudformation.CustomResource" - }, - "docs": { - "comment": "Custom resource that is implemented using a Lambda\n\nAs a custom resource author, you should be publishing a subclass of this class\nthat hides the choice of provider, and accepts a strongly-typed properties\nobject with the properties your provider accepts." - }, - "fqn": "@aws-cdk/custom-resources.CustomResource", - "initializer": { - "initializer": true, - "parameters": [ - { - "name": "parent", - "type": { - "fqn": "@aws-cdk/cdk.Construct" - } - }, - { - "name": "name", - "type": { - "primitive": "string" - } - }, - { - "name": "props", - "type": { - "fqn": "@aws-cdk/custom-resources.CustomResourceProps" - } - } - ] - }, - "kind": "class", - "methods": [ - { - "docs": { - "comment": "Override renderProperties to mix in the user-defined properties" - }, - "name": "renderProperties", - "protected": true, - "returns": { - "collection": { - "elementtype": { - "primitive": "any" - }, - "kind": "map" - } - } - } - ], - "name": "CustomResource", - "namespace": "@aws-cdk/custom-resources" - }, - "@aws-cdk/custom-resources.CustomResourceProps": { - "assembly": "@aws-cdk/custom-resources", - "datatype": true, - "docs": { - "comment": "Properties to provide a Lambda-backed custom resource" - }, - "fqn": "@aws-cdk/custom-resources.CustomResourceProps", - "kind": "interface", - "name": "CustomResourceProps", - "namespace": "@aws-cdk/custom-resources", - "properties": [ - { - "docs": { - "comment": "The Lambda provider that implements this custom resource.\n\nWe recommend using a SingletonLambda for this.\n\nOptional, exactly one of lamdaProvider or topicProvider must be set." - }, - "name": "lambdaProvider", - "type": { - "fqn": "@aws-cdk/aws-lambda.LambdaRef", - "optional": true - } - }, - { - "docs": { - "comment": "The SNS Topic for the provider that implements this custom resource.\n\nOptional, exactly one of lamdaProvider or topicProvider must be set." - }, - "name": "topicProvider", - "type": { - "fqn": "@aws-cdk/aws-sns.TopicRef", - "optional": true - } - }, - { - "docs": { - "comment": "Properties to pass to the Lambda" - }, - "name": "properties", - "type": { - "collection": { - "elementtype": { - "primitive": "any" - }, - "kind": "map" - }, - "optional": true - } - } - ] - }, - "@aws-cdk/custom-resources.SingletonLambda": { - "assembly": "@aws-cdk/custom-resources", - "base": { - "fqn": "@aws-cdk/aws-lambda.LambdaRef" - }, - "docs": { - "comment": "A Lambda that will only ever be added to a stack once.\n\nThe lambda is identified using the value of 'uuid'. Run 'uuidgen'\nfor every SingletonLambda you create." - }, - "fqn": "@aws-cdk/custom-resources.SingletonLambda", - "initializer": { - "initializer": true, - "parameters": [ - { - "name": "parent", - "type": { - "fqn": "@aws-cdk/cdk.Construct" - } - }, - { - "name": "name", - "type": { - "primitive": "string" - } - }, - { - "name": "props", - "type": { - "fqn": "@aws-cdk/custom-resources.SingletonLambdaProps" - } - } - ] - }, - "kind": "class", - "methods": [ - { - "docs": { - "comment": "Adds a permission to the Lambda resource policy." - }, - "name": "addPermission", - "parameters": [ - { - "name": "name", - "type": { - "primitive": "string" - } - }, - { - "name": "permission", - "type": { - "fqn": "@aws-cdk/aws-lambda.LambdaPermission" - } - } - ] - } - ], - "name": "SingletonLambda", - "namespace": "@aws-cdk/custom-resources", - "properties": [ - { - "docs": { - "comment": "The name of the function." - }, - "immutable": true, - "name": "functionName", - "type": { - "fqn": "@aws-cdk/aws-lambda.FunctionName" - } - }, - { - "docs": { - "comment": "The ARN fo the function." - }, - "immutable": true, - "name": "functionArn", - "type": { - "fqn": "@aws-cdk/aws-lambda.FunctionArn" - } - }, - { - "docs": { - "comment": "The IAM role associated with this function." - }, - "immutable": true, - "name": "role", - "type": { - "fqn": "@aws-cdk/aws-iam.Role", - "optional": true - } - }, - { - "docs": { - "comment": "Whether the addPermission() call adds any permissions\n\nTrue for new Lambdas, false for imported Lambdas (they might live in different accounts)." - }, - "immutable": true, - "name": "canCreatePermissions", - "protected": true, - "type": { - "primitive": "boolean" - } - } - ] - }, - "@aws-cdk/custom-resources.SingletonLambdaProps": { - "assembly": "@aws-cdk/custom-resources", - "datatype": true, - "docs": { - "comment": "Properties for a newly created singleton Lambda" - }, - "fqn": "@aws-cdk/custom-resources.SingletonLambdaProps", - "interfaces": [ - { - "fqn": "@aws-cdk/aws-lambda.LambdaProps" - } - ], - "kind": "interface", - "name": "SingletonLambdaProps", - "namespace": "@aws-cdk/custom-resources", - "properties": [ - { - "docs": { - "comment": "A unique identifier to identify this lambda\n\nThe identifier should be unique across all custom resource providers.\nWe recommend generating a UUID per provider." - }, - "name": "uuid", - "type": { - "primitive": "string" - } - } - ] - } - }, - "version": "0.8.0" -} diff --git a/packages/@aws-cdk/custom-resources/lib/index.d.ts b/packages/@aws-cdk/custom-resources/lib/index.d.ts deleted file mode 100644 index a8ace2f074f49..0000000000000 --- a/packages/@aws-cdk/custom-resources/lib/index.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './resource'; -export * from './singletonlambda'; diff --git a/packages/@aws-cdk/custom-resources/lib/index.js b/packages/@aws-cdk/custom-resources/lib/index.js deleted file mode 100644 index e30746f2f9ce5..0000000000000 --- a/packages/@aws-cdk/custom-resources/lib/index.js +++ /dev/null @@ -1,8 +0,0 @@ -"use strict"; -function __export(m) { - for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; -} -Object.defineProperty(exports, "__esModule", { value: true }); -__export(require("./resource")); -__export(require("./singletonlambda")); -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQUFBLGdDQUEyQjtBQUMzQix1Q0FBa0MifQ== \ No newline at end of file diff --git a/packages/@aws-cdk/custom-resources/lib/resource.d.ts b/packages/@aws-cdk/custom-resources/lib/resource.d.ts deleted file mode 100644 index 006ee5f7f00b2..0000000000000 --- a/packages/@aws-cdk/custom-resources/lib/resource.d.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { cloudformation } from '@aws-cdk/aws-cloudformation'; -import lambda = require('@aws-cdk/aws-lambda'); -import sns = require('@aws-cdk/aws-sns'); -import cdk = require('@aws-cdk/cdk'); -/** - * Collection of arbitrary properties - */ -export declare type Properties = { - [key: string]: any; -}; -/** - * Properties to provide a Lambda-backed custom resource - */ -export interface CustomResourceProps { - /** - * The Lambda provider that implements this custom resource. - * - * We recommend using a SingletonLambda for this. - * - * Optional, exactly one of lamdaProvider or topicProvider must be set. - */ - lambdaProvider?: lambda.LambdaRef; - /** - * The SNS Topic for the provider that implements this custom resource. - * - * Optional, exactly one of lamdaProvider or topicProvider must be set. - */ - topicProvider?: sns.TopicRef; - /** - * Properties to pass to the Lambda - */ - properties?: Properties; -} -/** - * Custom resource that is implemented using a Lambda - * - * As a custom resource author, you should be publishing a subclass of this class - * that hides the choice of provider, and accepts a strongly-typed properties - * object with the properties your provider accepts. - */ -export declare class CustomResource extends cloudformation.CustomResource { - private readonly userProperties?; - constructor(parent: cdk.Construct, name: string, props: CustomResourceProps); - /** - * Override renderProperties to mix in the user-defined properties - */ - protected renderProperties(): { - [key: string]: any; - }; -} diff --git a/packages/@aws-cdk/custom-resources/lib/resource.js b/packages/@aws-cdk/custom-resources/lib/resource.js deleted file mode 100644 index 9e3139746a5f5..0000000000000 --- a/packages/@aws-cdk/custom-resources/lib/resource.js +++ /dev/null @@ -1,45 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const aws_cloudformation_1 = require("@aws-cdk/aws-cloudformation"); -/** - * Custom resource that is implemented using a Lambda - * - * As a custom resource author, you should be publishing a subclass of this class - * that hides the choice of provider, and accepts a strongly-typed properties - * object with the properties your provider accepts. - */ -class CustomResource extends aws_cloudformation_1.cloudformation.CustomResource { - constructor(parent, name, props) { - if (!!props.lambdaProvider === !!props.topicProvider) { - throw new Error('Exactly one of "lambdaProvider" or "topicProvider" must be set.'); - } - super(parent, name, { - serviceToken: props.lambdaProvider ? props.lambdaProvider.functionArn : props.topicProvider.topicArn - }); - this.userProperties = props.properties; - } - /** - * Override renderProperties to mix in the user-defined properties - */ - renderProperties() { - const props = super.renderProperties(); - return Object.assign(props, uppercaseProperties(this.userProperties || {})); - } -} -exports.CustomResource = CustomResource; -/** - * Uppercase the first letter of every property name - * - * It's customary for CloudFormation properties to start with capitals, and our - * properties to start with lowercase, so this function translates from one - * to the other - */ -function uppercaseProperties(props) { - const ret = {}; - Object.keys(props).forEach(key => { - const upper = key.substr(0, 1).toUpperCase() + key.substr(1); - ret[upper] = props[key]; - }); - return ret; -} -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzb3VyY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJyZXNvdXJjZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLG9FQUE2RDtBQW9DN0Q7Ozs7OztHQU1HO0FBQ0gsb0JBQTRCLFNBQVEsbUNBQWMsQ0FBQyxjQUFjO0lBTTdELFlBQVksTUFBcUIsRUFBRSxJQUFZLEVBQUUsS0FBMEI7UUFDdkUsSUFBSSxDQUFDLENBQUMsS0FBSyxDQUFDLGNBQWMsS0FBSyxDQUFDLENBQUMsS0FBSyxDQUFDLGFBQWEsRUFBRTtZQUNsRCxNQUFNLElBQUksS0FBSyxDQUFDLGlFQUFpRSxDQUFDLENBQUM7U0FDdEY7UUFFRCxLQUFLLENBQUMsTUFBTSxFQUFFLElBQUksRUFBRTtZQUNoQixZQUFZLEVBQUUsS0FBSyxDQUFDLGNBQWMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLGNBQWMsQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxhQUFjLENBQUMsUUFBUTtTQUN4RyxDQUFDLENBQUM7UUFFSCxJQUFJLENBQUMsY0FBYyxHQUFHLEtBQUssQ0FBQyxVQUFVLENBQUM7SUFDM0MsQ0FBQztJQUVEOztPQUVHO0lBQ08sZ0JBQWdCO1FBQ3RCLE1BQU0sS0FBSyxHQUFHLEtBQUssQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDO1FBQ3ZDLE9BQU8sTUFBTSxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsbUJBQW1CLENBQUMsSUFBSSxDQUFDLGNBQWMsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQ2hGLENBQUM7Q0FFSjtBQTFCRCx3Q0EwQkM7QUFFRDs7Ozs7O0dBTUc7QUFDSCw2QkFBNkIsS0FBaUI7SUFDMUMsTUFBTSxHQUFHLEdBQWUsRUFBRSxDQUFDO0lBQzNCLE1BQU0sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxFQUFFO1FBQzdCLE1BQU0sS0FBSyxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLFdBQVcsRUFBRSxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDN0QsR0FBRyxDQUFDLEtBQUssQ0FBQyxHQUFHLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUM1QixDQUFDLENBQUMsQ0FBQztJQUNILE9BQU8sR0FBRyxDQUFDO0FBQ2YsQ0FBQyJ9 \ No newline at end of file diff --git a/packages/@aws-cdk/custom-resources/lib/singletonlambda.d.ts b/packages/@aws-cdk/custom-resources/lib/singletonlambda.d.ts deleted file mode 100644 index 601052a66fd60..0000000000000 --- a/packages/@aws-cdk/custom-resources/lib/singletonlambda.d.ts +++ /dev/null @@ -1,31 +0,0 @@ -import iam = require('@aws-cdk/aws-iam'); -import lambda = require('@aws-cdk/aws-lambda'); -import cdk = require('@aws-cdk/cdk'); -/** - * Properties for a newly created singleton Lambda - */ -export interface SingletonLambdaProps extends lambda.LambdaProps { - /** - * A unique identifier to identify this lambda - * - * The identifier should be unique across all custom resource providers. - * We recommend generating a UUID per provider. - */ - uuid: string; -} -/** - * A Lambda that will only ever be added to a stack once. - * - * The lambda is identified using the value of 'uuid'. Run 'uuidgen' - * for every SingletonLambda you create. - */ -export declare class SingletonLambda extends lambda.LambdaRef { - readonly functionName: lambda.FunctionName; - readonly functionArn: lambda.FunctionArn; - readonly role?: iam.Role | undefined; - protected readonly canCreatePermissions: boolean; - private lambdaFunction; - constructor(parent: cdk.Construct, name: string, props: SingletonLambdaProps); - addPermission(name: string, permission: lambda.LambdaPermission): void; - private ensureLambda; -} diff --git a/packages/@aws-cdk/custom-resources/lib/singletonlambda.js b/packages/@aws-cdk/custom-resources/lib/singletonlambda.js deleted file mode 100644 index b0bcdeca55bfb..0000000000000 --- a/packages/@aws-cdk/custom-resources/lib/singletonlambda.js +++ /dev/null @@ -1,38 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const lambda = require("@aws-cdk/aws-lambda"); -const cdk = require("@aws-cdk/cdk"); -/** - * A Lambda that will only ever be added to a stack once. - * - * The lambda is identified using the value of 'uuid'. Run 'uuidgen' - * for every SingletonLambda you create. - */ -class SingletonLambda extends lambda.LambdaRef { - constructor(parent, name, props) { - super(parent, name); - this.lambdaFunction = this.ensureLambda(props.uuid, props); - this.functionArn = this.lambdaFunction.functionArn; - this.functionName = this.lambdaFunction.functionName; - this.role = this.lambdaFunction.role; - this.canCreatePermissions = true; // Doesn't matter, addPermission is overriden anyway - } - addPermission(name, permission) { - return this.lambdaFunction.addPermission(name, permission); - } - ensureLambda(uuid, props) { - const constructName = 'SingletonLambda' + slugify(uuid); - const stack = cdk.Stack.find(this); - const existing = stack.tryFindChild(constructName); - if (existing) { - // Just assume this is true - return existing; - } - return new lambda.Lambda(stack, constructName, props); - } -} -exports.SingletonLambda = SingletonLambda; -function slugify(x) { - return x.replace(/[^a-zA-Z0-9]/g, ''); -} -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2luZ2xldG9ubGFtYmRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsic2luZ2xldG9ubGFtYmRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQ0EsOENBQStDO0FBQy9DLG9DQUFxQztBQWVyQzs7Ozs7R0FLRztBQUNILHFCQUE2QixTQUFRLE1BQU0sQ0FBQyxTQUFTO0lBT2pELFlBQVksTUFBcUIsRUFBRSxJQUFZLEVBQUUsS0FBMkI7UUFDeEUsS0FBSyxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsQ0FBQztRQUVwQixJQUFJLENBQUMsY0FBYyxHQUFHLElBQUksQ0FBQyxZQUFZLENBQUMsS0FBSyxDQUFDLElBQUksRUFBRSxLQUFLLENBQUMsQ0FBQztRQUUzRCxJQUFJLENBQUMsV0FBVyxHQUFHLElBQUksQ0FBQyxjQUFjLENBQUMsV0FBVyxDQUFDO1FBQ25ELElBQUksQ0FBQyxZQUFZLEdBQUcsSUFBSSxDQUFDLGNBQWMsQ0FBQyxZQUFZLENBQUM7UUFDckQsSUFBSSxDQUFDLElBQUksR0FBRyxJQUFJLENBQUMsY0FBYyxDQUFDLElBQUksQ0FBQztRQUVyQyxJQUFJLENBQUMsb0JBQW9CLEdBQUcsSUFBSSxDQUFDLENBQUMsb0RBQW9EO0lBQzFGLENBQUM7SUFFTSxhQUFhLENBQUMsSUFBWSxFQUFFLFVBQW1DO1FBQ2xFLE9BQU8sSUFBSSxDQUFDLGNBQWMsQ0FBQyxhQUFhLENBQUMsSUFBSSxFQUFFLFVBQVUsQ0FBQyxDQUFDO0lBQy9ELENBQUM7SUFFTyxZQUFZLENBQUMsSUFBWSxFQUFFLEtBQXlCO1FBQ3hELE1BQU0sYUFBYSxHQUFHLGlCQUFpQixHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUN4RCxNQUFNLEtBQUssR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUNuQyxNQUFNLFFBQVEsR0FBRyxLQUFLLENBQUMsWUFBWSxDQUFDLGFBQWEsQ0FBQyxDQUFDO1FBQ25ELElBQUksUUFBUSxFQUFFO1lBQ1YsMkJBQTJCO1lBQzNCLE9BQU8sUUFBNEIsQ0FBQztTQUN2QztRQUVELE9BQU8sSUFBSSxNQUFNLENBQUMsTUFBTSxDQUFDLEtBQUssRUFBRSxhQUFhLEVBQUUsS0FBSyxDQUFDLENBQUM7SUFDMUQsQ0FBQztDQUNKO0FBbENELDBDQWtDQztBQUVELGlCQUFpQixDQUFTO0lBQ3RCLE9BQU8sQ0FBQyxDQUFDLE9BQU8sQ0FBQyxlQUFlLEVBQUUsRUFBRSxDQUFDLENBQUM7QUFDMUMsQ0FBQyJ9 \ No newline at end of file diff --git a/packages/@aws-cdk/custom-resources/test/integ.trivial-lambda-resource.d.ts b/packages/@aws-cdk/custom-resources/test/integ.trivial-lambda-resource.d.ts deleted file mode 100644 index cb0ff5c3b541f..0000000000000 --- a/packages/@aws-cdk/custom-resources/test/integ.trivial-lambda-resource.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/packages/@aws-cdk/custom-resources/test/integ.trivial-lambda-resource.js b/packages/@aws-cdk/custom-resources/test/integ.trivial-lambda-resource.js deleted file mode 100644 index c2e4b8d6264a1..0000000000000 --- a/packages/@aws-cdk/custom-resources/test/integ.trivial-lambda-resource.js +++ /dev/null @@ -1,43 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const lambda = require("@aws-cdk/aws-lambda"); -const cdk = require("@aws-cdk/cdk"); -const fs = require("fs"); -const customResource = require("../lib"); -class DemoResource extends cdk.Construct { - constructor(parent, name, props) { - super(parent, name); - const resource = new customResource.CustomResource(this, 'Resource', { - lambdaProvider: new customResource.SingletonLambda(this, 'Singleton', { - uuid: 'f7d4f730-4ee1-11e8-9c2d-fa7ae01bbebc', - // This makes the demo only work as top-level TypeScript program, but that's fine for now - code: new lambda.LambdaInlineCode(fs.readFileSync('integ.trivial-lambda-provider.py', { encoding: 'utf-8' })), - handler: 'index.main', - timeout: 300, - runtime: lambda.LambdaRuntime.Python27, - }), - properties: props - }); - this.response = resource.getAtt('Response'); - } -} -/** - * A stack that only sets up the CustomResource and shows how to get an attribute from it - */ -class SucceedingStack extends cdk.Stack { - constructor(parent, name, props) { - super(parent, name, props); - const resource = new DemoResource(this, 'DemoResource', { - message: 'CustomResource says hello', - }); - // Publish the custom resource output - new cdk.Output(this, 'ResponseMessage', { - description: 'The message that came back from the Custom Resource', - value: resource.response - }); - } -} -const app = new cdk.App(process.argv); -new SucceedingStack(app, 'SucceedingStack'); -process.stdout.write(app.run()); -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZWcudHJpdmlhbC1sYW1iZGEtcmVzb3VyY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbnRlZy50cml2aWFsLWxhbWJkYS1yZXNvdXJjZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLDhDQUErQztBQUMvQyxvQ0FBcUM7QUFDckMseUJBQTBCO0FBQzFCLHlDQUEwQztBQWMxQyxrQkFBbUIsU0FBUSxHQUFHLENBQUMsU0FBUztJQUdwQyxZQUFZLE1BQXFCLEVBQUUsSUFBWSxFQUFFLEtBQXdCO1FBQ3JFLEtBQUssQ0FBQyxNQUFNLEVBQUUsSUFBSSxDQUFDLENBQUM7UUFFcEIsTUFBTSxRQUFRLEdBQUcsSUFBSSxjQUFjLENBQUMsY0FBYyxDQUFDLElBQUksRUFBRSxVQUFVLEVBQUU7WUFDakUsY0FBYyxFQUFFLElBQUksY0FBYyxDQUFDLGVBQWUsQ0FBQyxJQUFJLEVBQUUsV0FBVyxFQUFFO2dCQUNsRSxJQUFJLEVBQUUsc0NBQXNDO2dCQUM1Qyx5RkFBeUY7Z0JBQ3pGLElBQUksRUFBRSxJQUFJLE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxFQUFFLENBQUMsWUFBWSxDQUFDLGtDQUFrQyxFQUFFLEVBQUUsUUFBUSxFQUFFLE9BQU8sRUFBRSxDQUFDLENBQUM7Z0JBQzdHLE9BQU8sRUFBRSxZQUFZO2dCQUNyQixPQUFPLEVBQUUsR0FBRztnQkFDWixPQUFPLEVBQUUsTUFBTSxDQUFDLGFBQWEsQ0FBQyxRQUFRO2FBQ3pDLENBQUM7WUFDRixVQUFVLEVBQUUsS0FBSztTQUNwQixDQUFDLENBQUM7UUFFSCxJQUFJLENBQUMsUUFBUSxHQUFHLFFBQVEsQ0FBQyxNQUFNLENBQUMsVUFBVSxDQUFDLENBQUM7SUFDaEQsQ0FBQztDQUNKO0FBRUQ7O0dBRUc7QUFDSCxxQkFBc0IsU0FBUSxHQUFHLENBQUMsS0FBSztJQUNuQyxZQUFZLE1BQWUsRUFBRSxJQUFZLEVBQUUsS0FBc0I7UUFDN0QsS0FBSyxDQUFDLE1BQU0sRUFBRSxJQUFJLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFFM0IsTUFBTSxRQUFRLEdBQUcsSUFBSSxZQUFZLENBQUMsSUFBSSxFQUFFLGNBQWMsRUFBRTtZQUNwRCxPQUFPLEVBQUUsMkJBQTJCO1NBQ3ZDLENBQUMsQ0FBQztRQUVILHFDQUFxQztRQUNyQyxJQUFJLEdBQUcsQ0FBQyxNQUFNLENBQUMsSUFBSSxFQUFFLGlCQUFpQixFQUFFO1lBQ3BDLFdBQVcsRUFBRSxxREFBcUQ7WUFDbEUsS0FBSyxFQUFFLFFBQVEsQ0FBQyxRQUFRO1NBQzNCLENBQUMsQ0FBQztJQUNQLENBQUM7Q0FDSjtBQUNELE1BQU0sR0FBRyxHQUFHLElBQUksR0FBRyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUM7QUFFdEMsSUFBSSxlQUFlLENBQUMsR0FBRyxFQUFFLGlCQUFpQixDQUFDLENBQUM7QUFFNUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLEdBQUcsRUFBRSxDQUFDLENBQUMifQ== \ No newline at end of file diff --git a/packages/@aws-cdk/custom-resources/test/test.resource.d.ts b/packages/@aws-cdk/custom-resources/test/test.resource.d.ts deleted file mode 100644 index 7a7413d4d7613..0000000000000 --- a/packages/@aws-cdk/custom-resources/test/test.resource.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Test } from 'nodeunit'; -declare const _default: { - 'custom resource is added twice, lambda is added once'(test: Test): void; -}; -export = _default; diff --git a/packages/@aws-cdk/custom-resources/test/test.resource.js b/packages/@aws-cdk/custom-resources/test/test.resource.js deleted file mode 100644 index 853f993a97386..0000000000000 --- a/packages/@aws-cdk/custom-resources/test/test.resource.js +++ /dev/null @@ -1,101 +0,0 @@ -"use strict"; -const assert_1 = require("@aws-cdk/assert"); -const lambda = require("@aws-cdk/aws-lambda"); -const cdk = require("@aws-cdk/cdk"); -const lib_1 = require("../lib"); -class TestCustomResource extends cdk.Construct { - constructor(parent, name) { - super(parent, name); - const singletonLambda = new lib_1.SingletonLambda(this, 'Lambda', { - uuid: 'TestCustomResourceProvider', - code: new lambda.LambdaInlineCode('def hello(): pass'), - runtime: lambda.LambdaRuntime.Python27, - handler: 'index.hello', - timeout: 300, - }); - new lib_1.CustomResource(this, 'Resource', { - lambdaProvider: singletonLambda - }); - } -} -module.exports = { - 'custom resource is added twice, lambda is added once'(test) { - // GIVEN - const stack = new cdk.Stack(); - // WHEN - new TestCustomResource(stack, 'Custom1'); - new TestCustomResource(stack, 'Custom2'); - // THEN - assert_1.expect(stack).toMatch({ - "Resources": { - "SingletonLambdaTestCustomResourceProviderServiceRole81FEAB5C": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { "Fn::Join": ["", [ - "arn", ":", { "Ref": "AWS::Partition" }, ":", "iam", ":", "", ":", "aws", ":", "policy", "/", - "service-role/AWSLambdaBasicExecutionRole" - ]] } - ] - } - }, - "SingletonLambdaTestCustomResourceProviderA9255269": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "ZipFile": "def hello(): pass" - }, - "Handler": "index.hello", - "Role": { - "Fn::GetAtt": [ - "SingletonLambdaTestCustomResourceProviderServiceRole81FEAB5C", - "Arn" - ] - }, - "Runtime": "python2.7", - "Timeout": 300 - }, - "DependsOn": [ - "SingletonLambdaTestCustomResourceProviderServiceRole81FEAB5C" - ] - }, - "Custom1D319B237": { - "Type": "AWS::CloudFormation::CustomResource", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "SingletonLambdaTestCustomResourceProviderA9255269", - "Arn" - ] - } - } - }, - "Custom2DD5FB44D": { - "Type": "AWS::CloudFormation::CustomResource", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "SingletonLambdaTestCustomResourceProviderA9255269", - "Arn" - ] - } - } - } - } - }); - test.done(); - } -}; -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVzdC5yZXNvdXJjZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInRlc3QucmVzb3VyY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLDRDQUF5QztBQUN6Qyw4Q0FBK0M7QUFDL0Msb0NBQXFDO0FBRXJDLGdDQUF5RDtBQXNGekQsd0JBQXlCLFNBQVEsR0FBRyxDQUFDLFNBQVM7SUFDNUMsWUFBWSxNQUFxQixFQUFFLElBQVk7UUFDN0MsS0FBSyxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsQ0FBQztRQUVwQixNQUFNLGVBQWUsR0FBRyxJQUFJLHFCQUFlLENBQUMsSUFBSSxFQUFFLFFBQVEsRUFBRTtZQUMxRCxJQUFJLEVBQUUsNEJBQTRCO1lBQ2xDLElBQUksRUFBRSxJQUFJLE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxtQkFBbUIsQ0FBQztZQUN0RCxPQUFPLEVBQUUsTUFBTSxDQUFDLGFBQWEsQ0FBQyxRQUFRO1lBQ3RDLE9BQU8sRUFBRSxhQUFhO1lBQ3RCLE9BQU8sRUFBRSxHQUFHO1NBQ2IsQ0FBQyxDQUFDO1FBRUgsSUFBSSxvQkFBYyxDQUFDLElBQUksRUFBRSxVQUFVLEVBQUU7WUFDbkMsY0FBYyxFQUFFLGVBQWU7U0FDaEMsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztDQUNGO0FBbEdELGlCQUFTO0lBQ0wsc0RBQXNELENBQUMsSUFBVTtRQUM3RCxRQUFRO1FBQ1IsTUFBTSxLQUFLLEdBQUcsSUFBSSxHQUFHLENBQUMsS0FBSyxFQUFFLENBQUM7UUFFOUIsT0FBTztRQUNQLElBQUksa0JBQWtCLENBQUMsS0FBSyxFQUFFLFNBQVMsQ0FBQyxDQUFDO1FBQ3pDLElBQUksa0JBQWtCLENBQUMsS0FBSyxFQUFFLFNBQVMsQ0FBQyxDQUFDO1FBRXpDLE9BQU87UUFDUCxlQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsT0FBTyxDQUFDO1lBQ2xCLFdBQVcsRUFBRTtnQkFDWCw4REFBOEQsRUFBRTtvQkFDOUQsTUFBTSxFQUFFLGdCQUFnQjtvQkFDeEIsWUFBWSxFQUFFO3dCQUNaLDBCQUEwQixFQUFFOzRCQUMxQixXQUFXLEVBQUU7Z0NBQ1g7b0NBQ0UsUUFBUSxFQUFFLGdCQUFnQjtvQ0FDMUIsUUFBUSxFQUFFLE9BQU87b0NBQ2pCLFdBQVcsRUFBRTt3Q0FDWCxTQUFTLEVBQUUsc0JBQXNCO3FDQUNsQztpQ0FDRjs2QkFDRjs0QkFDRCxTQUFTLEVBQUUsWUFBWTt5QkFDeEI7d0JBQ0QsbUJBQW1CLEVBQUU7NEJBQ2pCLEVBQUUsVUFBVSxFQUFFLENBQUUsRUFBRSxFQUFFO3dDQUNoQixLQUFLLEVBQUUsR0FBRyxFQUFFLEVBQUUsS0FBSyxFQUFFLGdCQUFnQixFQUFFLEVBQUUsR0FBRyxFQUFFLEtBQUssRUFBRSxHQUFHLEVBQUUsRUFBRSxFQUFFLEdBQUcsRUFBRSxLQUFLLEVBQUUsR0FBRyxFQUFFLFFBQVEsRUFBRSxHQUFHO3dDQUM1RiwwQ0FBMEM7cUNBQUUsQ0FBRSxFQUFDO3lCQUN0RDtxQkFDRjtpQkFDRjtnQkFDRCxtREFBbUQsRUFBRTtvQkFDbkQsTUFBTSxFQUFFLHVCQUF1QjtvQkFDL0IsWUFBWSxFQUFFO3dCQUNaLE1BQU0sRUFBRTs0QkFDTixTQUFTLEVBQUUsbUJBQW1CO3lCQUMvQjt3QkFDRCxTQUFTLEVBQUUsYUFBYTt3QkFDeEIsTUFBTSxFQUFFOzRCQUNOLFlBQVksRUFBRTtnQ0FDWiw4REFBOEQ7Z0NBQzlELEtBQUs7NkJBQ047eUJBQ0Y7d0JBQ0QsU0FBUyxFQUFFLFdBQVc7d0JBQ3RCLFNBQVMsRUFBRSxHQUFHO3FCQUNmO29CQUNELFdBQVcsRUFBRTt3QkFDWCw4REFBOEQ7cUJBQy9EO2lCQUNGO2dCQUNELGlCQUFpQixFQUFFO29CQUNqQixNQUFNLEVBQUUscUNBQXFDO29CQUM3QyxZQUFZLEVBQUU7d0JBQ1osY0FBYyxFQUFFOzRCQUNkLFlBQVksRUFBRTtnQ0FDWixtREFBbUQ7Z0NBQ25ELEtBQUs7NkJBQ047eUJBQ0Y7cUJBQ0Y7aUJBQ0Y7Z0JBQ0QsaUJBQWlCLEVBQUU7b0JBQ2pCLE1BQU0sRUFBRSxxQ0FBcUM7b0JBQzdDLFlBQVksRUFBRTt3QkFDWixjQUFjLEVBQUU7NEJBQ2QsWUFBWSxFQUFFO2dDQUNaLG1EQUFtRDtnQ0FDbkQsS0FBSzs2QkFDTjt5QkFDRjtxQkFDRjtpQkFDRjthQUNGO1NBQ0osQ0FBQyxDQUFDO1FBQ0gsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDO0lBQ2hCLENBQUM7Q0FDSixDQUFDIn0= \ No newline at end of file diff --git a/packages/@aws-cdk/custom-resources/test/test.singletonlambda.d.ts b/packages/@aws-cdk/custom-resources/test/test.singletonlambda.d.ts deleted file mode 100644 index 8277fa23a5f84..0000000000000 --- a/packages/@aws-cdk/custom-resources/test/test.singletonlambda.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Test } from 'nodeunit'; -declare const _default: { - 'can add same singleton Lambda multiple times, only instantiated once in template'(test: Test): void; -}; -export = _default; diff --git a/packages/@aws-cdk/custom-resources/test/test.singletonlambda.js b/packages/@aws-cdk/custom-resources/test/test.singletonlambda.js deleted file mode 100644 index e062decd66642..0000000000000 --- a/packages/@aws-cdk/custom-resources/test/test.singletonlambda.js +++ /dev/null @@ -1,62 +0,0 @@ -"use strict"; -const assert_1 = require("@aws-cdk/assert"); -const lambda = require("@aws-cdk/aws-lambda"); -const cdk = require("@aws-cdk/cdk"); -const lib_1 = require("../lib"); -module.exports = { - 'can add same singleton Lambda multiple times, only instantiated once in template'(test) { - // GIVEN - const stack = new cdk.Stack(); - // WHEN - for (let i = 0; i < 5; i++) { - new lib_1.SingletonLambda(stack, `Singleton${i}`, { - uuid: '84c0de93-353f-4217-9b0b-45b6c993251a', - code: new lambda.LambdaInlineCode('def hello(): pass'), - runtime: lambda.LambdaRuntime.Python27, - handler: 'index.hello', - timeout: 300, - }); - } - // THEN - assert_1.expect(stack).to(assert_1.matchTemplate({ - Resources: { - SingletonLambda84c0de93353f42179b0b45b6c993251aServiceRole26D59235: { - Type: "AWS::IAM::Role", - Properties: { - AssumeRolePolicyDocument: { - Statement: [ - { - Action: "sts:AssumeRole", - Effect: "Allow", - Principal: { Service: "lambda.amazonaws.com" } - } - ], - Version: "2012-10-17" - }, - ManagedPolicyArns: [ - { - "Fn::Join": ["", ["arn", ":", { Ref: "AWS::Partition" }, ":", "iam", ":", "", - ":", "aws", ":", "policy", "/", "service-role/AWSLambdaBasicExecutionRole"]] - } - ] - } - }, - SingletonLambda84c0de93353f42179b0b45b6c993251a840BCC38: { - Type: "AWS::Lambda::Function", - Properties: { - Code: { - ZipFile: "def hello(): pass" - }, - Handler: "index.hello", - Role: { "Fn::GetAtt": ["SingletonLambda84c0de93353f42179b0b45b6c993251aServiceRole26D59235", "Arn"] }, - Runtime: "python2.7", - Timeout: 300 - }, - DependsOn: ["SingletonLambda84c0de93353f42179b0b45b6c993251aServiceRole26D59235"] - } - } - })); - test.done(); - } -}; -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVzdC5zaW5nbGV0b25sYW1iZGEuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJ0ZXN0LnNpbmdsZXRvbmxhbWJkYS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsNENBQXdEO0FBQ3hELDhDQUErQztBQUMvQyxvQ0FBcUM7QUFFckMsZ0NBQXlDO0FBRXpDLGlCQUFTO0lBQ1Asa0ZBQWtGLENBQUMsSUFBVTtRQUMzRixRQUFRO1FBQ1IsTUFBTSxLQUFLLEdBQUcsSUFBSSxHQUFHLENBQUMsS0FBSyxFQUFFLENBQUM7UUFFOUIsT0FBTztRQUNQLEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxFQUFFLEVBQUU7WUFDMUIsSUFBSSxxQkFBZSxDQUFDLEtBQUssRUFBRSxZQUFZLENBQUMsRUFBRSxFQUFFO2dCQUMxQyxJQUFJLEVBQUUsc0NBQXNDO2dCQUM1QyxJQUFJLEVBQUUsSUFBSSxNQUFNLENBQUMsZ0JBQWdCLENBQUMsbUJBQW1CLENBQUM7Z0JBQ3RELE9BQU8sRUFBRSxNQUFNLENBQUMsYUFBYSxDQUFDLFFBQVE7Z0JBQ3RDLE9BQU8sRUFBRSxhQUFhO2dCQUN0QixPQUFPLEVBQUUsR0FBRzthQUNiLENBQUMsQ0FBQztTQUNKO1FBRUQsT0FBTztRQUNQLGVBQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLENBQUMsc0JBQWEsQ0FBQztZQUM3QixTQUFTLEVBQUU7Z0JBQ1Qsa0VBQWtFLEVBQUU7b0JBQ2xFLElBQUksRUFBRSxnQkFBZ0I7b0JBQ3RCLFVBQVUsRUFBRTt3QkFDVix3QkFBd0IsRUFBRTs0QkFDeEIsU0FBUyxFQUFFO2dDQUNUO29DQUNFLE1BQU0sRUFBRSxnQkFBZ0I7b0NBQ3hCLE1BQU0sRUFBRSxPQUFPO29DQUNmLFNBQVMsRUFBRSxFQUFFLE9BQU8sRUFBRSxzQkFBc0IsRUFBRTtpQ0FDL0M7NkJBQ0Y7NEJBQ0QsT0FBTyxFQUFFLFlBQVk7eUJBQ3RCO3dCQUNELGlCQUFpQixFQUFFOzRCQUNqQjtnQ0FDRSxVQUFVLEVBQUUsQ0FBRSxFQUFFLEVBQUUsQ0FBRSxLQUFLLEVBQUUsR0FBRyxFQUFFLEVBQUUsR0FBRyxFQUFFLGdCQUFnQixFQUFFLEVBQUUsR0FBRyxFQUFFLEtBQUssRUFBRSxHQUFHLEVBQUUsRUFBRTt3Q0FDMUUsR0FBRyxFQUFFLEtBQUssRUFBRSxHQUFHLEVBQUUsUUFBUSxFQUFFLEdBQUcsRUFBRSwwQ0FBMEMsQ0FBRSxDQUFFOzZCQUNuRjt5QkFDRjtxQkFDRjtpQkFDRjtnQkFDRCx1REFBdUQsRUFBRTtvQkFDdkQsSUFBSSxFQUFFLHVCQUF1QjtvQkFDN0IsVUFBVSxFQUFFO3dCQUNWLElBQUksRUFBRTs0QkFDSixPQUFPLEVBQUUsbUJBQW1CO3lCQUM3Qjt3QkFDRCxPQUFPLEVBQUUsYUFBYTt3QkFDdEIsSUFBSSxFQUFFLEVBQUUsWUFBWSxFQUFFLENBQUUsb0VBQW9FLEVBQUUsS0FBSyxDQUFFLEVBQUU7d0JBQ3ZHLE9BQU8sRUFBRSxXQUFXO3dCQUNwQixPQUFPLEVBQUUsR0FBRztxQkFDYjtvQkFDRCxTQUFTLEVBQUUsQ0FBRSxvRUFBb0UsQ0FBRTtpQkFDcEY7YUFDRjtTQUNGLENBQUMsQ0FBQyxDQUFDO1FBRUosSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDO0lBQ2QsQ0FBQztDQUNGLENBQUMifQ== \ No newline at end of file diff --git a/packages/@aws-cdk/custom-resources/tsconfig.json b/packages/@aws-cdk/custom-resources/tsconfig.json deleted file mode 100644 index 5ae4f32b6ed34..0000000000000 --- a/packages/@aws-cdk/custom-resources/tsconfig.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "compilerOptions": { - "target": "es2018", - "lib": [ - "es2016", - "es2017.object", - "es2017.string" - ], - "module": "commonjs", - "declaration": true, - "strict": true, - "strictPropertyInitialization": true, - "noImplicitAny": true, - "strictNullChecks": true, - "noImplicitThis": true, - "alwaysStrict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "inlineSourceMap": true, - "experimentalDecorators": true, - "jsx": "react", - "jsxFactory": "jsx.create" - }, - "_generated_by_jsii_": "generated by jsii - you can delete, and ideally add to your .gitignore" -} diff --git a/packages/@aws-cdk/custom-resources/tslint.json b/packages/@aws-cdk/custom-resources/tslint.json deleted file mode 100644 index ddd9bc8e0f437..0000000000000 --- a/packages/@aws-cdk/custom-resources/tslint.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "extends": "tslint:recommended", - "rules": { - "semicolon": [ - true, - "always", - "ignore-interfaces" - ], - "no-invalid-template-strings": false, - "quotemark": false, - "interface-name": false, - "max-classes-per-file": false, - "member-access": { - "severity": "warning" - }, - "interface-over-type-literal": false, - "eofline": false, - "arrow-parens": false, - "no-namespace": false, - "max-line-length": [ - true, - 150 - ], - "object-literal-sort-keys": false, - "trailing-comma": false, - "no-unused-expression": [ - true, - "allow-new" - ], - "variable-name": [ - true, - "ban-keywords", - "check-format", - "allow-leading-underscore", - "allow-pascal-case" - ] - } -} From e47741b2fd699b330f08eb747a213e1261482b3b Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 8 Aug 2018 15:40:13 +0200 Subject: [PATCH 03/17] WIP --- packages/@aws-cdk/cdk/lib/core/tokens.ts | 29 ++++++++---------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/packages/@aws-cdk/cdk/lib/core/tokens.ts b/packages/@aws-cdk/cdk/lib/core/tokens.ts index 37ad7fcb5fa9b..3208aea73ba2b 100644 --- a/packages/@aws-cdk/cdk/lib/core/tokens.ts +++ b/packages/@aws-cdk/cdk/lib/core/tokens.ts @@ -16,9 +16,9 @@ export class Token { /** * Creates a token that resolves to `value`. If value is a function, * the function is evaluated upon resolution and the value it returns will be - * uesd as the token's value. + * used as the token's value. */ - constructor(private readonly valueOrFunction?: any) { } + constructor(private readonly valueOrFunction?: any, public readonly stringRepresentation?: string) { } /** * @returns The resolved value for this token. @@ -40,6 +40,13 @@ export class Token { * when resolve() is called on the string. */ public toString(): string { + const valueType = typeof this.valueOrFunction; + if (valueType === 'string' || valueType === 'number' || valueType === 'boolean') { + return this.valueOrFunction.toString(); + } + + // In particular: 'undefined' is not stringified here, since it should + // probably evaluate to a proper "undefined" later. if (this.stringRepr === undefined) { this.stringRepr = TOKEN_STRING_MAP.register(this); } @@ -58,18 +65,6 @@ export class Token { } } -/** - * A Token that has a human-readable representation hint - * - * This can be used to give Tokens a string representation that makes - * sense to humans. - */ -export class HintedToken extends Token { - constructor(public readonly representationHint: string, valueOrFunction?: any) { - super(valueOrFunction); - } -} - /** * Returns true if obj is a token (i.e. has the resolve() method) * @param obj The object to test. @@ -299,8 +294,4 @@ const VALID_KEY_CHARS = 'a-zA-Z0-9:._-'; /** * Singleton instance of the token string map */ -const TOKEN_STRING_MAP = new TokenStringMap(); - -function isHintedToken(token: any): token is HintedToken { - return 'representationHint' in token; -} +const TOKEN_STRING_MAP = new TokenStringMap(); \ No newline at end of file From 6e79bb8249eb6f6063c1156d73edc5ee140c44bf Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 8 Aug 2018 16:59:12 +0200 Subject: [PATCH 04/17] Remove HintedToken, change markers, add quoting --- .../@aws-cdk/cdk/lib/cloudformation/pseudo.ts | 6 ++-- .../cdk/lib/cloudformation/resource.ts | 4 +-- .../@aws-cdk/cdk/lib/cloudformation/stack.ts | 4 +-- packages/@aws-cdk/cdk/lib/core/tokens.ts | 30 ++++++++++++------- .../@aws-cdk/cdk/test/core/test.tokens.ts | 24 +++++++++++++-- 5 files changed, 48 insertions(+), 20 deletions(-) diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts b/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts index 79f1490f75e02..084d8fd515fb6 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts @@ -1,8 +1,8 @@ -import { HintedToken } from '../core/tokens'; +import { Token } from '../core/tokens'; -export class PseudoParameter extends HintedToken { +export class PseudoParameter extends Token { constructor(name: string) { - super(name, () => ({ Ref: name })); + super(() => ({ Ref: name }), name); } } diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts b/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts index 1d9a1b77136a0..117aa86ad44f8 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts @@ -1,5 +1,5 @@ import { Construct } from '../core/construct'; -import { HintedToken, Token } from '../core/tokens'; +import { Token } from '../core/tokens'; import { capitalizePropertyNames, ignoreEmpty } from '../core/util'; import { Condition } from './condition'; import { CreationPolicy, DeletionPolicy, UpdatePolicy } from './resource-policy'; @@ -82,7 +82,7 @@ export class Resource extends Referenceable { * @param attributeName The name of the attribute. */ public getAtt(attributeName: string): Token { - return new HintedToken(`${this.logicalId}.${attributeName}`, () => ({ 'Fn::GetAtt': [this.logicalId, attributeName] })); + return new Token(() => ({ 'Fn::GetAtt': [this.logicalId, attributeName] }), `${this.logicalId}.${attributeName}`); } /** diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts b/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts index 3e4c7e333436f..7fa47faeff3c1 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts @@ -1,7 +1,7 @@ import cxapi = require('@aws-cdk/cx-api'); import { App } from '../app'; import { Construct, PATH_SEP } from '../core/construct'; -import { HintedToken, resolve, Token } from '../core/tokens'; +import { resolve, Token } from '../core/tokens'; import { Environment } from '../environment'; import { HashedAddressingScheme, IAddressingScheme, LogicalIDs } from './logical-id'; import { Resource } from './resource'; @@ -392,7 +392,7 @@ export abstract class Referenceable extends StackElement { * Returns a token to a CloudFormation { Ref } that references this entity based on it's logical ID. */ public get ref() { - return new HintedToken(`${this.logicalId}`, () => ({ Ref: this.logicalId })); + return new Token(() => ({ Ref: this.logicalId }), `${this.logicalId}.Ref`); } } diff --git a/packages/@aws-cdk/cdk/lib/core/tokens.ts b/packages/@aws-cdk/cdk/lib/core/tokens.ts index 3208aea73ba2b..6e18931b46c72 100644 --- a/packages/@aws-cdk/cdk/lib/core/tokens.ts +++ b/packages/@aws-cdk/cdk/lib/core/tokens.ts @@ -14,9 +14,13 @@ export class Token { private stringRepr?: string; /** - * Creates a token that resolves to `value`. If value is a function, - * the function is evaluated upon resolution and the value it returns will be - * used as the token's value. + * Creates a token that resolves to `value`. + * + * If value is a function, the function is evaluated upon resolution and + * the value it returns will be used as the token's value. + * + * @param valueOrFunction What this token will evaluate to, literal or function. + * @param stringRepresentation A human-readable string describing the token's value. */ constructor(private readonly valueOrFunction?: any, public readonly stringRepresentation?: string) { } @@ -205,9 +209,9 @@ class TokenStringMap { * string. This may be used to produce aesthetically pleasing and recognizable * token representations for humans. */ - public register(token: Token | HintedToken): string { + public register(token: Token): string { const counter = Object.keys(this.tokenMap).length; - const representation = isHintedToken(token) ? token.representationHint : `TOKEN`; + const representation = token.stringRepresentation || `TOKEN`; const key = `${representation}.${counter}`; if (new RegExp(`[^${VALID_KEY_CHARS}]`).exec(key)) { @@ -237,8 +241,8 @@ class TokenStringMap { * Split a string on Token markers */ private split(s: string): Span[] { - const re = new RegExp(`${BEGIN_TOKEN_MARKER}([${VALID_KEY_CHARS}]+)${END_TOKEN_MARKER}`, 'gu'); - const ret: Span[] = []; + const re = new RegExp(`${regexQuote(BEGIN_TOKEN_MARKER)}([${VALID_KEY_CHARS}]+)${regexQuote(END_TOKEN_MARKER)}`, 'gu'); + const ret = new Array(); let rest = 0; let m = re.exec(s); @@ -268,7 +272,7 @@ class TokenStringMap { return span.value; } if (!(span.key in this.tokenMap)) { - throw new Error(`Unrecognized token representation: ${span.key}`); + throw new Error(`Unrecognized token key: ${span.key}`); } return resolve(this.tokenMap[span.key]); @@ -287,11 +291,15 @@ interface TokenSpan { type Span = StringSpan | TokenSpan; -const BEGIN_TOKEN_MARKER = '🔸🔹'; -const END_TOKEN_MARKER = '🔹🔸'; +const BEGIN_TOKEN_MARKER = '${Token['; +const END_TOKEN_MARKER = ']}'; const VALID_KEY_CHARS = 'a-zA-Z0-9:._-'; /** * Singleton instance of the token string map */ -const TOKEN_STRING_MAP = new TokenStringMap(); \ No newline at end of file +const TOKEN_STRING_MAP = new TokenStringMap(); + +function regexQuote(s: string) { + return s.replace(/[.?*+^$[\]\\(){}|-]/g, "\\$&"); +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/test/core/test.tokens.ts b/packages/@aws-cdk/cdk/test/core/test.tokens.ts index 16c7bc4a8e911..8b1996d203870 100644 --- a/packages/@aws-cdk/cdk/test/core/test.tokens.ts +++ b/packages/@aws-cdk/cdk/test/core/test.tokens.ts @@ -1,5 +1,5 @@ import { Test } from 'nodeunit'; -import { istoken, resolve, Token } from '../../lib'; +import { FnConcat, istoken, resolve, Token } from '../../lib'; export = { 'resolve a plain old object should just return the object'(test: Test) { @@ -170,7 +170,27 @@ export = { test.deepEqual(resolved, {'Fn::Join': ['', ['{"name":"Fido","speaks":"', 'woof woof', '"}']]}); test.done(); - } + }, + + 'string literals in evaluated tokens are escaped when calling JSON.stringify()'(test: Test) { + // WHEN + const token = new FnConcat('Hello', 'This\nIs', 'Very "cool"'); + + // WHEN + const resolved = resolve(JSON.stringify({ + literal: 'I can also "contain" quotes', + token + })); + + // THEN + test.deepEqual(resolved, { 'Fn::Join': ['', [ + '{"literal": "I can also \\"contain\\" quotes","token":"', + {'Fn::Join': ['', ['Hello', 'This\\nIs', 'Very \\"cool\\"']]}, + '"}' + ]]}); + + test.done(); + } }; class Promise2 extends Token { From a72d14d1d011291e8a5b3b6cdfcc27d7a73aa8e8 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Thu, 9 Aug 2018 09:52:41 +0200 Subject: [PATCH 05/17] Update testing --- packages/@aws-cdk/cdk/lib/core/tokens.ts | 146 +++++++++++++++--- .../@aws-cdk/cdk/test/core/cfn-intrinsics.ts | 39 +++++ .../@aws-cdk/cdk/test/core/test.tokens.ts | 87 ++++++++++- 3 files changed, 250 insertions(+), 22 deletions(-) create mode 100644 packages/@aws-cdk/cdk/test/core/cfn-intrinsics.ts diff --git a/packages/@aws-cdk/cdk/lib/core/tokens.ts b/packages/@aws-cdk/cdk/lib/core/tokens.ts index 6e18931b46c72..9c3722fa2e771 100644 --- a/packages/@aws-cdk/cdk/lib/core/tokens.ts +++ b/packages/@aws-cdk/cdk/lib/core/tokens.ts @@ -11,7 +11,7 @@ export const RESOLVE_METHOD = 'resolve'; * in case, for example, that it requires some context or late-bound data. */ export class Token { - private stringRepr?: string; + private tokenKey?: string; /** * Creates a token that resolves to `value`. @@ -37,24 +37,25 @@ export class Token { } /** - * Return a string representation of this token + * Return a reversible string representation of this token * - * This string representation can be embedded into strings and - * will be turned into the actual representation of the token - * when resolve() is called on the string. + * If the Token is initialized with a literal, the stringified value of the + * literal is returned. Otherwise, a special quoted string representation + * of the Token is returned that can be embedded into other strings. + * + * Strings with quoted Tokens in them can be restored back into + * complex values with the Tokens restored by calling `resolve()` + * on the string. */ public toString(): string { const valueType = typeof this.valueOrFunction; if (valueType === 'string' || valueType === 'number' || valueType === 'boolean') { return this.valueOrFunction.toString(); } - // In particular: 'undefined' is not stringified here, since it should - // probably evaluate to a proper "undefined" later. - if (this.stringRepr === undefined) { - this.stringRepr = TOKEN_STRING_MAP.register(this); - } - return this.stringRepr; + // probably evaluate to a proper "undefined" during resolve() later. + + return this.getStringMarker(TokenContext.Scalar); } /** @@ -63,9 +64,40 @@ export class Token { * Implements JSON.stringify()-compatibility for this Token. Note that it * returns a string representation for the Token pre-resolution, NOT the * resolved value of the Token. + * + * If the Token evaluates to a string literal, the string literal will + * undergo an additional level of escaping to make sure quotes and newlines + * embed properly into a larger JSON context. + * + * In case the Token represents a value that is intrinsic to the deployment + * engine (e.g. CloudFormation), the value is unavailable at synthesis time + * and we cannot do this escaping. Typically the values returned by those + * intrinsics are alphanumeric identifiers that don't need escaping, so + * this is not an issue. + * + * This feature is only supported for Tokens that resolve to strings, or + * to intrinsics that will produce strings. */ public toJSON(): any { - return this.toString(); + const valueType = typeof this.valueOrFunction; + if (valueType === 'string' || valueType === 'number' || valueType === 'boolean') { + return this.valueOrFunction; + } + + return this.getStringMarker(TokenContext.JSON); + } + + /** + * Allocate and encode the appropriate string marker for this Token + */ + private getStringMarker(context: TokenContext) { + // Why not evaluate the function here to see if it resolves to a + // primitive? Because the value they're referring to might still change + // later on after embedding. So we always storing the function for later. + if (this.tokenKey === undefined) { + this.tokenKey = TOKEN_STRING_MAP.registerKey(this); + } + return TOKEN_STRING_MAP.makeMarker(this.tokenKey, context); } } @@ -199,7 +231,7 @@ class TokenStringMap { } /** - * Return a unique string for this Token + * Return a unique string for this Token, returning a key * * Every call for the same Token will produce a new unique string, no * attempt is made to deduplicate. Token objects should cache the @@ -209,7 +241,7 @@ class TokenStringMap { * string. This may be used to produce aesthetically pleasing and recognizable * token representations for humans. */ - public register(token: Token): string { + public registerKey(token: Token): string { const counter = Object.keys(this.tokenMap).length; const representation = token.stringRepresentation || `TOKEN`; @@ -219,8 +251,18 @@ class TokenStringMap { } this.tokenMap[key] = token; + return key; + } - return `${BEGIN_TOKEN_MARKER}${key}${END_TOKEN_MARKER}`; + /** + * From a key return a marker, encoding the processing context + */ + public makeMarker(key: string, context: TokenContext): string { + if (context === TokenContext.Scalar) { + return `${BEGIN_SCALAR_TOKEN_MARKER}${key}${END_SCALAR_TOKEN_MARKER}`; + } else { + return `${BEGIN_JSON_TOKEN_MARKER}${key}${END_JSON_TOKEN_MARKER}`; + } } /** @@ -241,7 +283,14 @@ class TokenStringMap { * Split a string on Token markers */ private split(s: string): Span[] { - const re = new RegExp(`${regexQuote(BEGIN_TOKEN_MARKER)}([${VALID_KEY_CHARS}]+)${regexQuote(END_TOKEN_MARKER)}`, 'gu'); + // In this regex: note that the JSON markers include the surrounding + // quotes which have been added by the JSON.stringify() function + // *around* the result of token.toJSON(). + + // tslint:disable-next-line:max-line-length + const re = new RegExp(`${regexQuote(BEGIN_SCALAR_TOKEN_MARKER)}([${VALID_KEY_CHARS}]+)${regexQuote(END_SCALAR_TOKEN_MARKER)}` + + `|` + + `"${regexQuote(BEGIN_JSON_TOKEN_MARKER)}([${VALID_KEY_CHARS}]+)${regexQuote(END_JSON_TOKEN_MARKER)}"`, 'gu'); const ret = new Array(); let rest = 0; @@ -251,7 +300,11 @@ class TokenStringMap { ret.push({ type: 'string', value: s.substring(rest, m.index) }); } - ret.push({ type: 'token', key: m[1] }); + ret.push({ + type: 'token', + key: m[2], + context: m[1] === BEGIN_SCALAR_TOKEN_MARKER ? TokenContext.Scalar : TokenContext.JSON + }); rest = re.lastIndex; m = re.exec(s); @@ -275,7 +328,12 @@ class TokenStringMap { throw new Error(`Unrecognized token key: ${span.key}`); } - return resolve(this.tokenMap[span.key]); + const resolved = resolve(this.tokenMap[span.key]); + + switch (span.context) { + case TokenContext.JSON: return returnForJSONContext(resolved); + default: return resolved; + } } } @@ -287,12 +345,15 @@ interface StringSpan { interface TokenSpan { type: 'token'; key: string; + context: TokenContext; } type Span = StringSpan | TokenSpan; -const BEGIN_TOKEN_MARKER = '${Token['; -const END_TOKEN_MARKER = ']}'; +const BEGIN_SCALAR_TOKEN_MARKER = '${Token['; +const END_SCALAR_TOKEN_MARKER = ']}'; +const BEGIN_JSON_TOKEN_MARKER = '#{Token['; +const END_JSON_TOKEN_MARKER = ']}'; const VALID_KEY_CHARS = 'a-zA-Z0-9:._-'; /** @@ -302,4 +363,49 @@ const TOKEN_STRING_MAP = new TokenStringMap(); function regexQuote(s: string) { return s.replace(/[.?*+^$[\]\\(){}|-]/g, "\\$&"); +} + +/** + * Escape the resolved value for use in a JSON context. + * + * Only allowed if it's a string; otherwise the Token result + * gets combined with other values in a + * + * Only if it's a primitive or array (not an intrinsic). + */ +function returnForJSONContext(x: any): any { + if (typeof(x) === 'string') { + return JSON.stringify(x); + } + + // Nonscalar values + + if (Array.isArray(x)) { + return x.map(returnForJSONContext); + } + + if (typeof(x) === 'object') { + const result: any = {}; + for (const key of Object.keys(x)) { + result[key] = returnForJSONContext(x[key]); + } + return result; + } + + return x; +} + +/** + * The context that a stringified Token is used in + */ +enum TokenContext { + /** + * Scalar string embedding + */ + Scalar = 'Simple', + + /** + * String value embedded into a JSON document + */ + JSON = 'JSON' } \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/test/core/cfn-intrinsics.ts b/packages/@aws-cdk/cdk/test/core/cfn-intrinsics.ts new file mode 100644 index 0000000000000..c49e14eda866b --- /dev/null +++ b/packages/@aws-cdk/cdk/test/core/cfn-intrinsics.ts @@ -0,0 +1,39 @@ +/** + * Simple function to evaluate CloudFormation intrinsics. + * + * Note that this function is not production quality, it exists to support tests. + */ +export function evaluateIntrinsics(obj: any): any { + if (Array.isArray(obj)) { + return obj.map(evaluateIntrinsics); + } + + if (typeof obj === 'object') { + const keys = Object.keys(obj); + if (keys.length === 1 && (keys[0].startsWith('Fn::') || keys[0] === 'Ref')) { + return evaluateIntrinsic(keys[0], obj[keys[0]]); + } + + const ret: {[key: string]: any} = {}; + for (const key of keys) { + ret[key] = evaluateIntrinsics(obj[key]); + } + return ret; + } + + return obj; +} + +const INTRINSICS: any = { + 'Fn::Join'(separator: string, args: string[]) { + return args.join(separator); + }, +}; + +function evaluateIntrinsic(name: string, args: any) { + if (!(name in INTRINSICS)) { + throw new Error(`Intrinsic ${name} not supported here`); + } + + return INTRINSICS[name].apply(INTRINSICS, args); +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/test/core/test.tokens.ts b/packages/@aws-cdk/cdk/test/core/test.tokens.ts index 8b1996d203870..9e96b4dec0ca6 100644 --- a/packages/@aws-cdk/cdk/test/core/test.tokens.ts +++ b/packages/@aws-cdk/cdk/test/core/test.tokens.ts @@ -1,5 +1,6 @@ import { Test } from 'nodeunit'; import { FnConcat, istoken, resolve, Token } from '../../lib'; +import { evaluateIntrinsics } from './cfn-intrinsics'; export = { 'resolve a plain old object should just return the object'(test: Test) { @@ -172,7 +173,43 @@ export = { test.done(); }, - 'string literals in evaluated tokens are escaped when calling JSON.stringify()'(test: Test) { + /* + 'Tokens that resolve to undefined disappear under JSON.stringification()'(test: Test) { + // GIVEN + const bob1 = { name: 'Bob', speaks: new Token(() => undefined) }; + const bob2 = { name: 'Bob', speaks: new Token(undefined) }; + + // WHEN + const resolved1 = resolve(JSON.stringify(bob1)); + const resolved2 = resolve(JSON.stringify(bob2)); + + // THEN + const expected = {'Fn::Join': ['', ['{"name":"Bob"}']]}; + test.deepEqual(resolved1, expected); + test.deepEqual(resolved2, expected); + + test.done(); + }, + */ + + 'Tokens that resolve to a number are unquoted during JSON.stringification'(test: Test) { + // GIVEN + const fido1 = { name: "Fido", age: new Token(() => 1) }; + const fido2 = { name: "Fido", age: new Token(1) }; + + // WHEN + const resolved1 = resolve(JSON.stringify(fido1)); + const resolved2 = resolve(JSON.stringify(fido2)); + + // THEN + const expected = {'Fn::Join': ['', ['{"name":"Fido","age":', 1, '}']]}; + test.deepEqual(resolved1, expected); + test.deepEqual(resolved2, expected); + + test.done(); + }, + + 'lazy string literals in evaluated tokens are escaped when calling JSON.stringify()'(test: Test) { // WHEN const token = new FnConcat('Hello', 'This\nIs', 'Very "cool"'); @@ -190,7 +227,53 @@ export = { ]]}); test.done(); - } + }, + + 'doubly nested strings evaluate correctly'(test: Test) { + // GIVEN + const token1 = new Token(() => "world"); + const token2 = new Token(() => `hello ${token1}`); + + // WHEN + const resolved1 = resolve(token2.toString()); + const resolved2 = resolve(token2); + + // THEN + test.deepEqual(evaluateIntrinsics(resolved1), "hello world"); + test.deepEqual(evaluateIntrinsics(resolved2), "hello world"); + + test.done(); + }, + + 'combined strings in JSON context end up correctly'(test: Test) { + // WHEN + const fidoSays = new Token(() => 'woof'); + + // WHEN + const resolved = resolve(JSON.stringify({ + information: `Did you know that Fido says: ${fidoSays}` + })); + + // THEN + test.deepEqual(evaluateIntrinsics(resolved), '{"information": "Did you know that Fido says: woof"}'); + + test.done(); + }, + + 'quoted strings in embedded JSON context end up correctly'(test: Test) { + // WHEN + const fidoSays = new Token(() => '"woof"'); + + // WHEN + const resolved = resolve(JSON.stringify({ + information: `Did you know that Fido says: ${fidoSays}` + })); + + // THEN + test.deepEqual(evaluateIntrinsics(resolved), '{"information": "Did you know that Fido says: \\"woof\\""}'); + + test.done(); + }, }; class Promise2 extends Token { From 42becf3568c3049742c916a2519304835975139c Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Thu, 9 Aug 2018 10:29:31 +0200 Subject: [PATCH 06/17] WIP --- .../@aws-cdk/cdk/lib/cloudformation/fn.ts | 5 ++- .../cdk/lib/cloudformation/intrinsics.ts | 4 ++ .../@aws-cdk/cdk/lib/cloudformation/pseudo.ts | 6 ++- .../cdk/lib/cloudformation/resource.ts | 6 ++- .../@aws-cdk/cdk/lib/cloudformation/stack.ts | 6 ++- packages/@aws-cdk/cdk/lib/core/tokens.ts | 43 ++++++++++++++++++- 6 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 packages/@aws-cdk/cdk/lib/cloudformation/intrinsics.ts diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/fn.ts b/packages/@aws-cdk/cdk/lib/cloudformation/fn.ts index 661d1776aeebc..936db47a86d03 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/fn.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/fn.ts @@ -1,4 +1,5 @@ import { Token } from '../core/tokens'; +import { CLOUDFORMATION_ENGINE } from './intrinsics'; // tslint:disable:max-line-length /** @@ -7,7 +8,9 @@ import { Token } from '../core/tokens'; */ export class Fn extends Token { constructor(name: string, value: any) { - super(() => ({ [name]: value })); + super(() => ({ [name]: value }), { + intrinsicEngine: CLOUDFORMATION_ENGINE + }); } } diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/intrinsics.ts b/packages/@aws-cdk/cdk/lib/cloudformation/intrinsics.ts new file mode 100644 index 0000000000000..901e2b6dbf6b4 --- /dev/null +++ b/packages/@aws-cdk/cdk/lib/cloudformation/intrinsics.ts @@ -0,0 +1,4 @@ +/** + * The default intrinsics Token engine for CloudFormation + */ +export const CLOUDFORMATION_ENGINE = 'cloudformation'; \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts b/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts index 084d8fd515fb6..d6af9df44ffcd 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts @@ -1,8 +1,12 @@ import { Token } from '../core/tokens'; +import { CLOUDFORMATION_ENGINE } from './intrinsics'; export class PseudoParameter extends Token { constructor(name: string) { - super(() => ({ Ref: name }), name); + super(() => ({ Ref: name }), { + stringRepresentation: name, + intrinsicEngine: CLOUDFORMATION_ENGINE + }); } } diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts b/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts index 117aa86ad44f8..35b152905279a 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts @@ -2,6 +2,7 @@ import { Construct } from '../core/construct'; import { Token } from '../core/tokens'; import { capitalizePropertyNames, ignoreEmpty } from '../core/util'; import { Condition } from './condition'; +import { CLOUDFORMATION_ENGINE } from './intrinsics'; import { CreationPolicy, DeletionPolicy, UpdatePolicy } from './resource-policy'; import { IDependable, Referenceable, StackElement } from './stack'; @@ -82,7 +83,10 @@ export class Resource extends Referenceable { * @param attributeName The name of the attribute. */ public getAtt(attributeName: string): Token { - return new Token(() => ({ 'Fn::GetAtt': [this.logicalId, attributeName] }), `${this.logicalId}.${attributeName}`); + return new Token(() => ({ 'Fn::GetAtt': [this.logicalId, attributeName] }), { + stringRepresentation: `${this.logicalId}.${attributeName}`, + intrinsicEngine: CLOUDFORMATION_ENGINE + }); } /** diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts b/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts index 7fa47faeff3c1..bc0ca42f219b4 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts @@ -3,6 +3,7 @@ import { App } from '../app'; import { Construct, PATH_SEP } from '../core/construct'; import { resolve, Token } from '../core/tokens'; import { Environment } from '../environment'; +import { CLOUDFORMATION_ENGINE } from './intrinsics'; import { HashedAddressingScheme, IAddressingScheme, LogicalIDs } from './logical-id'; import { Resource } from './resource'; @@ -392,7 +393,10 @@ export abstract class Referenceable extends StackElement { * Returns a token to a CloudFormation { Ref } that references this entity based on it's logical ID. */ public get ref() { - return new Token(() => ({ Ref: this.logicalId }), `${this.logicalId}.Ref`); + return new Token(() => ({ Ref: this.logicalId }), { + stringRepresentation: `${this.logicalId}.Ref`, + intrinsicEngine: CLOUDFORMATION_ENGINE + }); } } diff --git a/packages/@aws-cdk/cdk/lib/core/tokens.ts b/packages/@aws-cdk/cdk/lib/core/tokens.ts index 9c3722fa2e771..310b550246660 100644 --- a/packages/@aws-cdk/cdk/lib/core/tokens.ts +++ b/packages/@aws-cdk/cdk/lib/core/tokens.ts @@ -6,11 +6,40 @@ import { Construct } from "./construct"; */ export const RESOLVE_METHOD = 'resolve'; +/** + * Additional properties for Tokens + */ +export interface TokenProps { + /** + * A human-readable string describing the token's value. + * + * This is used in the placeholder string of stringified Tokens, so that + * if humans look at the string its purpose makes sense to them. + * + * Must contain only alphanumeric and simple separator characters + * (_.:-). + * + * @default No string representation + */ + stringRepresentation?: string; + + /** + * Mark this Token as producing an intrinsic for the given engine. + * + * The results of instrinsic Tokens are not evaluated further, + * but used as-is. + */ + intrinsicEngine?: string; +} + /** * Represents a lazy-evaluated value. Can be used to delay evaluation of a certain value * in case, for example, that it requires some context or late-bound data. */ export class Token { + public readonly stringRepresentation?: string; + public readonly intrinsicEngine?: string; + private tokenKey?: string; /** @@ -20,9 +49,12 @@ export class Token { * the value it returns will be used as the token's value. * * @param valueOrFunction What this token will evaluate to, literal or function. - * @param stringRepresentation A human-readable string describing the token's value. + * @param stringRepresentation */ - constructor(private readonly valueOrFunction?: any, public readonly stringRepresentation?: string) { } + constructor(private readonly valueOrFunction?: any, props: TokenProps = {}) { + this.stringRepresentation = props.stringRepresentation; + this.intrinsicEngine = props.intrinsicEngine; + } /** * @returns The resolved value for this token. @@ -109,6 +141,13 @@ export function istoken(obj: any) { return typeof(obj[RESOLVE_METHOD]) === 'function'; } +/** + * Return whether the given Token is an intrinsic + */ +function isIntrinsic(t: Token): boolean { + return t.intrinsicEngine !== undefined; +} + /** * Resolves an object by evaluating all tokens and removing any undefined or empty objects or arrays. * Values can only be primitives, arrays or tokens. Other objects (i.e. with methods) will be rejected. From 1b60e45882188ae5cf68b204f554f71d37dd9211 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Thu, 9 Aug 2018 16:43:13 +0200 Subject: [PATCH 07/17] After a giant slough, reintroducing tokenAwareJsonify() MAJOR CHANGES - Reintroducing tokenAwareJsonify; it needs to be there because otherwise we cannot have enough knowledge to do escaping properly; see documentation in the code. - Introducing markAsIntrinsic() which sets a hidden property on an object which is used as a hint to the rest of the engine that (a) the value doesn't need to be resolved anymore and (b) we can use it as a type hint to reject non-intrinsic complex types during stringification. A wrapper class would have been nicer but this entails fewer code changes (deepEqual everywhere...). A convenience class has been introduced in the CloudFormation section to make impact minimal. - Implement inversion-of-control on the FnConcat thing, execution engines (name?) can now register themselves to handle string combining. Still not perfect because if there are not intrinsics we can't really guess the engine (so we "default" to CFN anyway) but we have the initial trappings of CFN decoupling. ALSO - Add a run config and a helper script to debug a unit test from VSCode (breakpoints \o/) I expect this to generate a healthy amount of debate. Also I would appreciate suggestions on naming of the various pieces, I'm afraid I haven't been 100% consistent just yet. - What do we call CloudFormation/Terraform/...? - What do we call stringified Tokens? --- .../@aws-cdk/cdk/lib/cloudformation/fn.ts | 9 +- .../cdk/lib/cloudformation/intrinsics.ts | 33 +- .../@aws-cdk/cdk/lib/cloudformation/pseudo.ts | 10 +- .../cdk/lib/cloudformation/resource.ts | 7 +- .../@aws-cdk/cdk/lib/cloudformation/stack.ts | 9 +- packages/@aws-cdk/cdk/lib/core/tokens.ts | 412 +++++++++++------- packages/@aws-cdk/cdk/lib/index.ts | 2 +- .../@aws-cdk/cdk/test/core/cfn-intrinsics.ts | 12 +- .../@aws-cdk/cdk/test/core/test.tokens.ts | 83 ++-- scripts/runtest.js | 16 + 10 files changed, 379 insertions(+), 214 deletions(-) create mode 100755 scripts/runtest.js diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/fn.ts b/packages/@aws-cdk/cdk/lib/cloudformation/fn.ts index 936db47a86d03..68000e24fe904 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/fn.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/fn.ts @@ -1,16 +1,13 @@ -import { Token } from '../core/tokens'; -import { CLOUDFORMATION_ENGINE } from './intrinsics'; +import { CloudFormationIntrinsicToken } from './intrinsics'; // tslint:disable:max-line-length /** * CloudFormation intrinsic functions. * http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html */ -export class Fn extends Token { +export class Fn extends CloudFormationIntrinsicToken { constructor(name: string, value: any) { - super(() => ({ [name]: value }), { - intrinsicEngine: CLOUDFORMATION_ENGINE - }); + super(() => ({ [name]: value })); } } diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/intrinsics.ts b/packages/@aws-cdk/cdk/lib/cloudformation/intrinsics.ts index 901e2b6dbf6b4..7e105e7a0fe6e 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/intrinsics.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/intrinsics.ts @@ -1,4 +1,35 @@ +import { markAsIntrinsic } from "../core/intrinsic"; +import { DEFAULT_ENGINE_NAME, registerEngineTokenHandler, resolve, StringFragment, Token } from "../core/tokens"; + /** * The default intrinsics Token engine for CloudFormation */ -export const CLOUDFORMATION_ENGINE = 'cloudformation'; \ No newline at end of file +export const CLOUDFORMATION_ENGINE = 'cloudformation'; + +/** + * Class that tags the Token's return value as an Intrinsic. + * + * It also automatically resolves inner Token values. Simple base class so + * existing Tokens don't need to change too much. + */ +export class CloudFormationIntrinsicToken extends Token { + public resolve(): any { + // Get the inner value, and deep-resolve it to resolve further Tokens. + const resolved = resolve(super.resolve()); + return markAsIntrinsic(resolved, CLOUDFORMATION_ENGINE); + } +} + +import { FnConcat } from "./fn"; + +const cloudFormationEngine = { + /** + * In CloudFormation, we combine strings by wrapping them in FnConcat + */ + combineStringFragments(fragments: StringFragment[]) { + return new FnConcat(...fragments.map(f => f.value)); + } +}; + +registerEngineTokenHandler(CLOUDFORMATION_ENGINE, cloudFormationEngine); +registerEngineTokenHandler(DEFAULT_ENGINE_NAME, cloudFormationEngine); \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts b/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts index d6af9df44ffcd..cf6692377f3a5 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts @@ -1,12 +1,8 @@ -import { Token } from '../core/tokens'; -import { CLOUDFORMATION_ENGINE } from './intrinsics'; +import { CloudFormationIntrinsicToken } from './intrinsics'; -export class PseudoParameter extends Token { +export class PseudoParameter extends CloudFormationIntrinsicToken { constructor(name: string) { - super(() => ({ Ref: name }), { - stringRepresentation: name, - intrinsicEngine: CLOUDFORMATION_ENGINE - }); + super(() => ({ Ref: name }), name); } } diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts b/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts index 35b152905279a..fb97ce8266f0a 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts @@ -2,7 +2,7 @@ import { Construct } from '../core/construct'; import { Token } from '../core/tokens'; import { capitalizePropertyNames, ignoreEmpty } from '../core/util'; import { Condition } from './condition'; -import { CLOUDFORMATION_ENGINE } from './intrinsics'; +import { CloudFormationIntrinsicToken } from './intrinsics'; import { CreationPolicy, DeletionPolicy, UpdatePolicy } from './resource-policy'; import { IDependable, Referenceable, StackElement } from './stack'; @@ -83,10 +83,7 @@ export class Resource extends Referenceable { * @param attributeName The name of the attribute. */ public getAtt(attributeName: string): Token { - return new Token(() => ({ 'Fn::GetAtt': [this.logicalId, attributeName] }), { - stringRepresentation: `${this.logicalId}.${attributeName}`, - intrinsicEngine: CLOUDFORMATION_ENGINE - }); + return new CloudFormationIntrinsicToken(() => ({ 'Fn::GetAtt': [this.logicalId, attributeName] }), `${this.logicalId}.${attributeName}`); } /** diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts b/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts index bc0ca42f219b4..0dd71d5b5968b 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts @@ -3,7 +3,7 @@ import { App } from '../app'; import { Construct, PATH_SEP } from '../core/construct'; import { resolve, Token } from '../core/tokens'; import { Environment } from '../environment'; -import { CLOUDFORMATION_ENGINE } from './intrinsics'; +import { CloudFormationIntrinsicToken } from './intrinsics'; import { HashedAddressingScheme, IAddressingScheme, LogicalIDs } from './logical-id'; import { Resource } from './resource'; @@ -392,11 +392,8 @@ export abstract class Referenceable extends StackElement { /** * Returns a token to a CloudFormation { Ref } that references this entity based on it's logical ID. */ - public get ref() { - return new Token(() => ({ Ref: this.logicalId }), { - stringRepresentation: `${this.logicalId}.Ref`, - intrinsicEngine: CLOUDFORMATION_ENGINE - }); + public get ref(): Token { + return new CloudFormationIntrinsicToken(() => ({ Ref: this.logicalId }), `${this.logicalId}.Ref`); } } diff --git a/packages/@aws-cdk/cdk/lib/core/tokens.ts b/packages/@aws-cdk/cdk/lib/core/tokens.ts index 310b550246660..57448eda95170 100644 --- a/packages/@aws-cdk/cdk/lib/core/tokens.ts +++ b/packages/@aws-cdk/cdk/lib/core/tokens.ts @@ -7,39 +7,12 @@ import { Construct } from "./construct"; export const RESOLVE_METHOD = 'resolve'; /** - * Additional properties for Tokens - */ -export interface TokenProps { - /** - * A human-readable string describing the token's value. - * - * This is used in the placeholder string of stringified Tokens, so that - * if humans look at the string its purpose makes sense to them. - * - * Must contain only alphanumeric and simple separator characters - * (_.:-). - * - * @default No string representation - */ - stringRepresentation?: string; - - /** - * Mark this Token as producing an intrinsic for the given engine. - * - * The results of instrinsic Tokens are not evaluated further, - * but used as-is. - */ - intrinsicEngine?: string; -} - -/** - * Represents a lazy-evaluated value. Can be used to delay evaluation of a certain value - * in case, for example, that it requires some context or late-bound data. + * Represents a lazy-evaluated value. + * + * Can be used to delay evaluation of a certain value in case, for example, + * that it requires some context or late-bound data. */ export class Token { - public readonly stringRepresentation?: string; - public readonly intrinsicEngine?: string; - private tokenKey?: string; /** @@ -48,12 +21,16 @@ export class Token { * If value is a function, the function is evaluated upon resolution and * the value it returns will be used as the token's value. * + * stringRepresentation is used in the placeholder string of stringified + * Tokens, so that if humans look at the string its purpose makes sense to + * them. Must contain only alphanumeric and simple separator characters + * (_.:-). + * * @param valueOrFunction What this token will evaluate to, literal or function. - * @param stringRepresentation + * @param stringRepresentation A human-readable string describing the token's value. + * */ - constructor(private readonly valueOrFunction?: any, props: TokenProps = {}) { - this.stringRepresentation = props.stringRepresentation; - this.intrinsicEngine = props.intrinsicEngine; + constructor(private readonly valueOrFunction?: any, public readonly stringRepresentation?: string) { } /** @@ -87,7 +64,7 @@ export class Token { // In particular: 'undefined' is not stringified here, since it should // probably evaluate to a proper "undefined" during resolve() later. - return this.getStringMarker(TokenContext.Scalar); + return this.stringMarker(); } /** @@ -109,27 +86,35 @@ export class Token { * * This feature is only supported for Tokens that resolve to strings, or * to intrinsics that will produce strings. + * In case the Token represents a value that is intrinsic to the deployment + * engine (e.g. CloudFormation), the value is unavailable at synthesis time + * and we cannot do this escaping. Typically the values returned by those + * intrinsics are alphanumeric identifiers that don't need escaping, so + * this is not an issue. + * + * This feature is only supported for Tokens that resolve to strings, or + * to intrinsics that will produce strings. */ public toJSON(): any { - const valueType = typeof this.valueOrFunction; - if (valueType === 'string' || valueType === 'number' || valueType === 'boolean') { + // Return immediately if it doesn't look like this value is ever going to change. + if (!isIntrinsic(this.valueOrFunction) && typeof this.valueOrFunction !== 'function' && typeof this.valueOrFunction !== 'object') { return this.valueOrFunction; } - return this.getStringMarker(TokenContext.JSON); + // Otherwise return a marker for lazy evaluation. + return this.stringMarker(); } /** * Allocate and encode the appropriate string marker for this Token + * + * Implements caching of the key. */ - private getStringMarker(context: TokenContext) { - // Why not evaluate the function here to see if it resolves to a - // primitive? Because the value they're referring to might still change - // later on after embedding. So we always storing the function for later. + private stringMarker() { if (this.tokenKey === undefined) { - this.tokenKey = TOKEN_STRING_MAP.registerKey(this); + this.tokenKey = TOKEN_STRING_MAP.register(this); } - return TOKEN_STRING_MAP.makeMarker(this.tokenKey, context); + return this.tokenKey; } } @@ -137,15 +122,13 @@ export class Token { * Returns true if obj is a token (i.e. has the resolve() method) * @param obj The object to test. */ -export function istoken(obj: any) { +export function isToken(obj: any): obj is Token { return typeof(obj[RESOLVE_METHOD]) === 'function'; } -/** - * Return whether the given Token is an intrinsic - */ -function isIntrinsic(t: Token): boolean { - return t.intrinsicEngine !== undefined; +export function istoken(obj: any): obj is Token { + process.emitWarning('Deprecated; use isToken() instead'); + return isToken(obj); } /** @@ -203,11 +186,19 @@ export function resolve(obj: any, prefix?: string[]): any { return obj; } + // + // intrinsics - return intrinsic without further resolution + // + + if (isIntrinsic(obj)) { + return obj; + } + // // tokens - invoke 'resolve' and continue to resolve recursively // - if (istoken(obj)) { + if (isToken(obj)) { const value = obj[RESOLVE_METHOD](); return resolve(value, path); } @@ -250,8 +241,6 @@ export function resolve(obj: any, prefix?: string[]): any { return result; } -import { FnConcat } from "../cloudformation/fn"; - /** * Central place where we keep a mapping from Tokens to their String representation * @@ -270,17 +259,17 @@ class TokenStringMap { } /** - * Return a unique string for this Token, returning a key + * Generating a unique string for this Token, returning a key * * Every call for the same Token will produce a new unique string, no * attempt is made to deduplicate. Token objects should cache the * value themselves, if required. * - * If the token is an IRepresentableToken, it chooses its own representation - * string. This may be used to produce aesthetically pleasing and recognizable - * token representations for humans. + * The token can choose (part of) its own representation string with a + * hint. This may be used to produce aesthetically pleasing and + * recognizable token representations for humans. */ - public registerKey(token: Token): string { + public register(token: Token): string { const counter = Object.keys(this.tokenMap).length; const representation = token.stringRepresentation || `TOKEN`; @@ -290,47 +279,25 @@ class TokenStringMap { } this.tokenMap[key] = token; - return key; - } - - /** - * From a key return a marker, encoding the processing context - */ - public makeMarker(key: string, context: TokenContext): string { - if (context === TokenContext.Scalar) { - return `${BEGIN_SCALAR_TOKEN_MARKER}${key}${END_SCALAR_TOKEN_MARKER}`; - } else { - return `${BEGIN_JSON_TOKEN_MARKER}${key}${END_JSON_TOKEN_MARKER}`; - } + return `${BEGIN_TOKEN_MARKER}${key}${END_TOKEN_MARKER}`; } /** * Replace any Token markers in this string with their resolved values */ public resolveMarkers(s: string): any { - const spans = this.split(s); - const resolved = spans.map(this.resolveSpan.bind(this)); - - switch (resolved.length) { - case 0: return ''; - case 1: return resolved[0]; - default: return resolve(new FnConcat(...resolved)); - } + const unresolved = this.splitMarkers(s); + const resolved = resolveFragments(unresolved); + return combineStringFragments(resolved); } /** - * Split a string on Token markers + * Split a string up into string and Token Spans */ - private split(s: string): Span[] { - // In this regex: note that the JSON markers include the surrounding - // quotes which have been added by the JSON.stringify() function - // *around* the result of token.toJSON(). - + public splitMarkers(s: string): UnresolvedFragment[] { // tslint:disable-next-line:max-line-length - const re = new RegExp(`${regexQuote(BEGIN_SCALAR_TOKEN_MARKER)}([${VALID_KEY_CHARS}]+)${regexQuote(END_SCALAR_TOKEN_MARKER)}` - + `|` - + `"${regexQuote(BEGIN_JSON_TOKEN_MARKER)}([${VALID_KEY_CHARS}]+)${regexQuote(END_JSON_TOKEN_MARKER)}"`, 'gu'); - const ret = new Array(); + const re = new RegExp(`${regexQuote(BEGIN_TOKEN_MARKER)}([${VALID_KEY_CHARS}]+)${regexQuote(END_TOKEN_MARKER)}`, 'g'); + const ret = new Array(); let rest = 0; let m = re.exec(s); @@ -341,8 +308,7 @@ class TokenStringMap { ret.push({ type: 'token', - key: m[2], - context: m[1] === BEGIN_SCALAR_TOKEN_MARKER ? TokenContext.Scalar : TokenContext.JSON + token: this.lookupToken(m[1]) }); rest = re.lastIndex; @@ -353,46 +319,57 @@ class TokenStringMap { ret.push({ type: 'string', value: s.substring(rest) }); } + if (ret.length === 0) { + ret.push({ type: 'string', value: '' }); + } + return ret; } /** - * Resolve a single Span + * Find a Token by key */ - private resolveSpan(span: Span): any { - if (span.type === 'string') { - return span.value; - } - if (!(span.key in this.tokenMap)) { - throw new Error(`Unrecognized token key: ${span.key}`); + private lookupToken(key: string): Token { + if (!(key in this.tokenMap)) { + throw new Error(`Unrecognized token key: ${key}`); } - const resolved = resolve(this.tokenMap[span.key]); - - switch (span.context) { - case TokenContext.JSON: return returnForJSONContext(resolved); - default: return resolved; - } + return this.tokenMap[key]; } } -interface StringSpan { - type: 'string'; - value: string; -} +/** + * Result of the split of a string with Tokens + * + * Either a literal part of the string, or an unresolved Token. + */ +type UnresolvedFragment = { type: 'string'; value: string } | { type: 'token'; token: Token }; -interface TokenSpan { - type: 'token'; - key: string; - context: TokenContext; -} +/** + * Resolves string fragments + */ +function resolveFragments(spans: UnresolvedFragment[]): StringFragment[] { + return spans.map(span => { + if (span.type === 'string') { + return { source: FragmentSource.Literal, value: span.value }; + } -type Span = StringSpan | TokenSpan; + // If not, then it's a Token that needs resolving + const resolved = resolve(span.token); -const BEGIN_SCALAR_TOKEN_MARKER = '${Token['; -const END_SCALAR_TOKEN_MARKER = ']}'; -const BEGIN_JSON_TOKEN_MARKER = '#{Token['; -const END_JSON_TOKEN_MARKER = ']}'; + // This must resolve to a string or an intrinsic to make any sense. Let's see + // what errors crop up if we're strict and then see if we want to loosen up the + // rules later. + if (!isIntrinsic(resolved) && typeof resolved !== 'string') { + throw new Error(`Result of Token evaluation must be a string, got: ${resolved}`); + } + + return { source: FragmentSource.Token, value: resolved, intrinsicEngine: intrinsicEngine(resolved) }; + }); +} + +const BEGIN_TOKEN_MARKER = '${Token['; +const END_TOKEN_MARKER = ']}'; const VALID_KEY_CHARS = 'a-zA-Z0-9:._-'; /** @@ -405,46 +382,189 @@ function regexQuote(s: string) { } /** - * Escape the resolved value for use in a JSON context. + * The hidden marker property that marks an object as an engine-intrinsic value. + */ +const INTRINSIC_VALUE_PROPERTY = '__intrinsicValue__'; + +/** + * Mark a given object as an engine-intrinsic value. * - * Only allowed if it's a string; otherwise the Token result - * gets combined with other values in a + * This will avoid it from being resolved by resolve(). Note that if the value + * returns any Tokens, they should be resolved before marking the object + * as an intrinsic. * - * Only if it's a primitive or array (not an intrinsic). + * This could have been a wrapper class, but that breaks all test.deepEqual()s. + * So instead, it's implemented as a hidden property on the object (which is + * hidden from JSON.stringify() and test.deepEqual(). */ -function returnForJSONContext(x: any): any { - if (typeof(x) === 'string') { - return JSON.stringify(x); - } - - // Nonscalar values - - if (Array.isArray(x)) { - return x.map(returnForJSONContext); - } +export function markAsIntrinsic(x: any, engine: string): any { + Object.defineProperty(x, INTRINSIC_VALUE_PROPERTY, { + value: engine, + enumerable: false, + writable: false, + }); + return x; +} - if (typeof(x) === 'object') { - const result: any = {}; - for (const key of Object.keys(x)) { - result[key] = returnForJSONContext(x[key]); - } - return result; - } +/** + * Return whether the given value is an intrinsic + */ +export function isIntrinsic(x: any): boolean { + return x[INTRINSIC_VALUE_PROPERTY] !== undefined; +} - return x; +/** + * Return the intrinsic engine for the given intrinsic value + */ +export function intrinsicEngine(x: any): string | undefined { + return x[INTRINSIC_VALUE_PROPERTY]; } /** - * The context that a stringified Token is used in + * A (resolved) fragment of a string to be combined. + * + * The values may be string literals or intrinsics. */ -enum TokenContext { +export interface StringFragment { + /** + * Source of the fragment + */ + source: FragmentSource; + + /** + * String value + * + * Either a string literal or an intrinsic. + */ + value: any; + /** - * Scalar string embedding + * The intrinsic engine */ - Scalar = 'Simple', + intrinsicEngine?: string; +} + +/** + * Where the resolved fragment came from (a string literal or a Token) + */ +export enum FragmentSource { + Literal = 'Literal', + Token = 'Token' +} +/** + * Interface for engine-specific Token marker handlers + */ +export interface IEngineTokenHandler { /** - * String value embedded into a JSON document + * Return the language intrinsic that will combine the strings in the given engine */ - JSON = 'JSON' + combineStringFragments(fragments: StringFragment[]): any; +} + +/** + * The engine that will be used if no Tokens are found + */ +export const DEFAULT_ENGINE_NAME = 'default'; + +/** + * Global handler map + */ +const HANDLERS: {[engine: string]: IEngineTokenHandler} = {}; + +/** + * Register a handler for all intrinsics for the given engine + */ +export function registerEngineTokenHandler(engineName: string, handler: IEngineTokenHandler) { + HANDLERS[engineName] = handler; +} + +/** + * Combine resolved fragments using the appropriate engine. + * + * Resolves the result. + */ +function combineStringFragments(fragments: StringFragment[]): any { + if (fragments.length === 0) { return ''; } + if (fragments.length === 1) { return fragments[0].value; } + + const engines = Array.from(new Set(fragments.filter(f => f.intrinsicEngine !== undefined).map(f => f.intrinsicEngine!))); + if (engines.length > 1) { + throw new Error(`Combining different engines in one string fragment: ${engines.join(', ')}`); + } + + const engine = engines.length > 0 ? engines[0] : DEFAULT_ENGINE_NAME; + if (!(engine in HANDLERS)) { + throw new Error(`No Token handler registered for engine: ${engine}`); + } + + return resolve(HANDLERS[engine].combineStringFragments(fragments)); +} + +/** + * Turn an arbitrary structure potentially containing Tokens into JSON. + */ +export function tokenAwareJsonify(obj: any): any { + // This function must exist because even if we changed token.toJSON() + // to return a special marker, we couldn't handle both of the following + // cases correctly: + // + // [a] Embed into JSON structure directly, result: + // + // {"key":"#{Token[0]}"} + // + // [b] First toString() to embed into string literal, then embed in JSON + // structure, result: + // + // {"key":"larger string with ${Token[0]}"} + // + // In [a], we could detect from '#' vs the '$' that the value of Token[0] + // needs to undergo additional JSON-escaping, but we couldn't do the same in + // case [b], even though the resultant string needs to undergo JSON-escaping + // all the same. + // + // The correct way to handle this is to resolve scalars as usual, and do + // an additional layer of JSON-postprocessing afterwards. + return new Token(() => { + const unresolved = TOKEN_STRING_MAP.splitMarkers(JSON.stringify(obj)); + const fragments = resolveFragments(unresolved); + + // Here's the magic sauce: everything that used to be a string before + // has already been escaped, so we leave alone. Everything that came + // rolling fresh out of a Token could _theoretically_ be everything + // but in practice is either a string literal (escape!) or an intrinsic, + // which in this RARE case we also recurse over and escape the strings + // we find in it. This is technically cheating since we don't know which + // strings in the intrinsics are instructions and which will be rendered + // into the output, but in practice the instruction strings will not + // contain escapable characters anyway. + for (const fragment of fragments) { + if (fragment.source === FragmentSource.Token) { + fragment.value = deepEscape(fragment.value); + } + } + + return combineStringFragments(fragments); + }); + + function deepEscape(x: any): any { + if (typeof x === 'string') { + // Whenever we escape a string we strip off the outermost quotes + // since we're already in a quoted context. + const stringified = JSON.stringify(x); + return stringified.substring(1, stringified.length - 1); + } + + if (Array.isArray(x)) { + return x.map(deepEscape); + } + + if (typeof x === 'object') { + for (const key of Object.keys(x)) { + x[key] = deepEscape(x[key]); + } + } + + return x; + } } \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/index.ts b/packages/@aws-cdk/cdk/lib/index.ts index fa933eb4685ca..51ba534caf11c 100644 --- a/packages/@aws-cdk/cdk/lib/index.ts +++ b/packages/@aws-cdk/cdk/lib/index.ts @@ -5,6 +5,7 @@ export * from './core/jsx'; export * from './cloudformation/condition'; export * from './cloudformation/fn'; export * from './cloudformation/include'; +export * from './cloudformation/intrinsics'; export * from './cloudformation/logical-id'; export * from './cloudformation/mapping'; export * from './cloudformation/output'; @@ -19,7 +20,6 @@ export * from './cloudformation/tag'; export * from './cloudformation/removal-policy'; export * from './cloudformation/arn'; export * from './cloudformation/secret'; -export * from './cloudformation/token-aware-jsonify'; export * from './app'; export * from './context'; diff --git a/packages/@aws-cdk/cdk/test/core/cfn-intrinsics.ts b/packages/@aws-cdk/cdk/test/core/cfn-intrinsics.ts index c49e14eda866b..5db89bb4fedbd 100644 --- a/packages/@aws-cdk/cdk/test/core/cfn-intrinsics.ts +++ b/packages/@aws-cdk/cdk/test/core/cfn-intrinsics.ts @@ -26,8 +26,14 @@ export function evaluateIntrinsics(obj: any): any { const INTRINSICS: any = { 'Fn::Join'(separator: string, args: string[]) { - return args.join(separator); + return args.map(evaluateIntrinsics).join(separator); }, + + 'Ref'(logicalId: string) { + // We can't get the actual here, but we can at least return a marker + // that shows we would put the value here. + return `<>`; + } }; function evaluateIntrinsic(name: string, args: any) { @@ -35,5 +41,9 @@ function evaluateIntrinsic(name: string, args: any) { throw new Error(`Intrinsic ${name} not supported here`); } + if (!Array.isArray(args)) { + args = [args]; + } + return INTRINSICS[name].apply(INTRINSICS, args); } \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/test/core/test.tokens.ts b/packages/@aws-cdk/cdk/test/core/test.tokens.ts index 9e96b4dec0ca6..82fc5a0725007 100644 --- a/packages/@aws-cdk/cdk/test/core/test.tokens.ts +++ b/packages/@aws-cdk/cdk/test/core/test.tokens.ts @@ -1,5 +1,5 @@ import { Test } from 'nodeunit'; -import { FnConcat, istoken, resolve, Token } from '../../lib'; +import { CloudFormationIntrinsicToken, FnConcat, isToken, resolve, Token, tokenAwareJsonify } from '../../lib'; import { evaluateIntrinsics } from './cfn-intrinsics'; export = { @@ -122,10 +122,10 @@ export = { test.done(); }, - 'istoken(obj) can be used to determine if an object is a token'(test: Test) { - test.ok(istoken({ resolve: () => 123 })); - test.ok(istoken({ a: 1, b: 2, resolve: () => 'hello' })); - test.ok(!istoken({ a: 1, b: 2, resolve: 3 })); + 'isToken(obj) can be used to determine if an object is a token'(test: Test) { + test.ok(isToken({ resolve: () => 123 })); + test.ok(isToken({ a: 1, b: 2, resolve: () => 'hello' })); + test.ok(!isToken({ a: 1, b: 2, resolve: 3 })); test.done(); }, @@ -157,7 +157,7 @@ export = { test.done(); }, - 'tokens can be JSON.stringified and stringification can be reversed'(test: Test) { + 'tokens can be JSONified and JSONification can be reversed'(test: Test) { // GIVEN const fido = { name: 'Fido', @@ -165,7 +165,7 @@ export = { }; // WHEN - const resolved = resolve(JSON.stringify(fido)); + const resolved = resolve(tokenAwareJsonify(fido)); // THEN test.deepEqual(resolved, {'Fn::Join': ['', @@ -173,63 +173,49 @@ export = { test.done(); }, - /* - 'Tokens that resolve to undefined disappear under JSON.stringification()'(test: Test) { + 'During tokenAwareJsonify, its an error for a Token to not resolve to a string'(test: Test) { // GIVEN - const bob1 = { name: 'Bob', speaks: new Token(() => undefined) }; - const bob2 = { name: 'Bob', speaks: new Token(undefined) }; - - // WHEN - const resolved1 = resolve(JSON.stringify(bob1)); - const resolved2 = resolve(JSON.stringify(bob2)); + const fido1 = { name: "Fido", age: new Token(() => 1) }; // THEN - const expected = {'Fn::Join': ['', ['{"name":"Bob"}']]}; - test.deepEqual(resolved1, expected); - test.deepEqual(resolved2, expected); + test.throws(() => { + resolve(tokenAwareJsonify(fido1)); + }); test.done(); }, - */ - 'Tokens that resolve to a number are unquoted during JSON.stringification'(test: Test) { + 'Non-lazy integers are allowed in tokenAwareJsonify()'(test: Test) { // GIVEN - const fido1 = { name: "Fido", age: new Token(() => 1) }; - const fido2 = { name: "Fido", age: new Token(1) }; + const fido = { name: "Fido", age: new Token(1) }; // WHEN - const resolved1 = resolve(JSON.stringify(fido1)); - const resolved2 = resolve(JSON.stringify(fido2)); + const resolved = evaluateIntrinsics(resolve(tokenAwareJsonify(fido))); // THEN - const expected = {'Fn::Join': ['', ['{"name":"Fido","age":', 1, '}']]}; - test.deepEqual(resolved1, expected); - test.deepEqual(resolved2, expected); + test.deepEqual('{"name":"Fido","age":1}', resolved); test.done(); }, - 'lazy string literals in evaluated tokens are escaped when calling JSON.stringify()'(test: Test) { + 'lazy string literals in evaluated tokens are escaped when calling tokenAwareJsonify()'(test: Test) { // WHEN const token = new FnConcat('Hello', 'This\nIs', 'Very "cool"'); // WHEN - const resolved = resolve(JSON.stringify({ + const resolved = resolve(tokenAwareJsonify({ literal: 'I can also "contain" quotes', token })); // THEN - test.deepEqual(resolved, { 'Fn::Join': ['', [ - '{"literal": "I can also \\"contain\\" quotes","token":"', - {'Fn::Join': ['', ['Hello', 'This\\nIs', 'Very \\"cool\\"']]}, - '"}' - ]]}); + const expected = '{"literal":"I can also \\"contain\\" quotes","token":"HelloThis\\nIsVery \\"cool\\""}'; + test.equal(evaluateIntrinsics(resolved), expected); test.done(); }, - 'doubly nested strings evaluate correctly'(test: Test) { + 'Doubly nested strings evaluate correctly in scalar context'(test: Test) { // GIVEN const token1 = new Token(() => "world"); const token2 = new Token(() => `hello ${token1}`); @@ -245,32 +231,47 @@ export = { test.done(); }, - 'combined strings in JSON context end up correctly'(test: Test) { + 'Doubly nested strings evaluate correctly in JSON context'(test: Test) { // WHEN const fidoSays = new Token(() => 'woof'); // WHEN - const resolved = resolve(JSON.stringify({ + const resolved = resolve(tokenAwareJsonify({ + information: `Did you know that Fido says: ${fidoSays}` + })); + + // THEN + test.deepEqual(evaluateIntrinsics(resolved), '{"information":"Did you know that Fido says: woof"}'); + + test.done(); + }, + + 'Doubly nested intrinsics evaluate correctly in JSON context'(test: Test) { + // WHEN + const fidoSays = new CloudFormationIntrinsicToken(() => ({ Ref: 'Something' })); + + // WHEN + const resolved = resolve(tokenAwareJsonify({ information: `Did you know that Fido says: ${fidoSays}` })); // THEN - test.deepEqual(evaluateIntrinsics(resolved), '{"information": "Did you know that Fido says: woof"}'); + test.deepEqual(evaluateIntrinsics(resolved), '{"information":"Did you know that Fido says: <>"}'); test.done(); }, - 'quoted strings in embedded JSON context end up correctly'(test: Test) { + 'Quoted strings in embedded JSON context are escaped'(test: Test) { // WHEN const fidoSays = new Token(() => '"woof"'); // WHEN - const resolved = resolve(JSON.stringify({ + const resolved = resolve(tokenAwareJsonify({ information: `Did you know that Fido says: ${fidoSays}` })); // THEN - test.deepEqual(evaluateIntrinsics(resolved), '{"information": "Did you know that Fido says: \\"woof\\""}'); + test.deepEqual(evaluateIntrinsics(resolved), '{"information":"Did you know that Fido says: \\"woof\\""}'); test.done(); }, diff --git a/scripts/runtest.js b/scripts/runtest.js new file mode 100755 index 0000000000000..d501aca18f22c --- /dev/null +++ b/scripts/runtest.js @@ -0,0 +1,16 @@ +#!/usr/bin/env node +// Helper script to invoke 'nodeunit' on a .ts file. This should involve compilation, it doesn't right now. +const path = require('path'); + +// Unfortunately, nodeunit has no programmatic interface. Therefore, the +// easiest thing to do is rewrite the argv arguments where we change +// .ts into .js. + +for (let i = 2; i < process.argv.length; i++) { + if (process.argv[i].endsWith('.ts')) { + process.argv[i] = process.argv[i].substring(0, process.argv[i].length - 3) + '.js'; + } +} + +// Just pretend we're calling the program directly +require(path.resolve(__dirname, '..', 'node_modules', '.bin', 'nodeunit')); From d1fc358538915381e4aea3447ebe9098dadac8f7 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Thu, 9 Aug 2018 17:13:09 +0200 Subject: [PATCH 08/17] Make CloudWatch use tokenAwareJsonify again --- packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts b/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts index 3cc70b7e01268..77cac5955be08 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts @@ -1,4 +1,4 @@ -import { Construct, Stack, Token } from "@aws-cdk/cdk"; +import { Construct, Stack, Token, tokenAwareJsonify } from "@aws-cdk/cdk"; import { cloudformation } from './cloudwatch.generated'; import { Column, Row } from "./layout"; import { IWidget } from "./widget"; @@ -33,7 +33,7 @@ export class Dashboard extends Construct { dashboardBody: new Token(() => { const column = new Column(...this.rows); column.position(0, 0); - return JSON.stringify({ widgets: column.toJson() }); + return tokenAwareJsonify({ widgets: column.toJson() }); }) }); } From 58602a90108bb24a0284dda31746c3631241769b Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 13 Aug 2018 14:14:01 +0200 Subject: [PATCH 09/17] Reorganized code Changed tokenAwareJsonify() -> TokenJSON.stringify() to mimic JSON.stringify() API name. Implementation does not depend on override of toJSON() anymore, because there's no way to do that correctly. Instead, the function has a custom implementation. Split new code over more files to separate them out. --- .../@aws-cdk/aws-cloudwatch/lib/dashboard.ts | 4 +- .../cdk/lib/cloudformation/intrinsics.ts | 21 +- .../@aws-cdk/cdk/lib/cloudformation/rule.ts | 3 +- .../cdk/lib/core/engine-intrinsics.ts | 42 +++ .../@aws-cdk/cdk/lib/core/engine-strings.ts | 87 +++++ packages/@aws-cdk/cdk/lib/core/token-json.ts | 114 ++++++ packages/@aws-cdk/cdk/lib/core/tokens.ts | 344 ++---------------- packages/@aws-cdk/cdk/lib/core/util.ts | 66 +++- packages/@aws-cdk/cdk/lib/index.ts | 2 + .../@aws-cdk/cdk/test/core/cfn-intrinsics.ts | 49 --- .../@aws-cdk/cdk/test/core/evaluate-cfn.ts | 62 ++++ .../@aws-cdk/cdk/test/core/test.token-json.ts | 175 +++++++++ .../@aws-cdk/cdk/test/core/test.tokens.ts | 126 ++----- 13 files changed, 622 insertions(+), 473 deletions(-) create mode 100644 packages/@aws-cdk/cdk/lib/core/engine-intrinsics.ts create mode 100644 packages/@aws-cdk/cdk/lib/core/engine-strings.ts create mode 100644 packages/@aws-cdk/cdk/lib/core/token-json.ts delete mode 100644 packages/@aws-cdk/cdk/test/core/cfn-intrinsics.ts create mode 100644 packages/@aws-cdk/cdk/test/core/evaluate-cfn.ts create mode 100644 packages/@aws-cdk/cdk/test/core/test.token-json.ts diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts b/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts index 77cac5955be08..4fcd4a7750cac 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts @@ -1,4 +1,4 @@ -import { Construct, Stack, Token, tokenAwareJsonify } from "@aws-cdk/cdk"; +import { Construct, Stack, Token, TokenJSON } from "@aws-cdk/cdk"; import { cloudformation } from './cloudwatch.generated'; import { Column, Row } from "./layout"; import { IWidget } from "./widget"; @@ -33,7 +33,7 @@ export class Dashboard extends Construct { dashboardBody: new Token(() => { const column = new Column(...this.rows); column.position(0, 0); - return tokenAwareJsonify({ widgets: column.toJson() }); + return TokenJSON.stringify({ widgets: column.toJson() }); }) }); } diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/intrinsics.ts b/packages/@aws-cdk/cdk/lib/cloudformation/intrinsics.ts index 7e105e7a0fe6e..468c88d121ee5 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/intrinsics.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/intrinsics.ts @@ -1,5 +1,5 @@ -import { markAsIntrinsic } from "../core/intrinsic"; -import { DEFAULT_ENGINE_NAME, registerEngineTokenHandler, resolve, StringFragment, Token } from "../core/tokens"; +import { DEFAULT_ENGINE_NAME, ProvisioningEngine, StringFragment } from "../core/engine-strings"; +import { IntrinsicToken } from "../core/tokens"; /** * The default intrinsics Token engine for CloudFormation @@ -7,17 +7,10 @@ import { DEFAULT_ENGINE_NAME, registerEngineTokenHandler, resolve, StringFragmen export const CLOUDFORMATION_ENGINE = 'cloudformation'; /** - * Class that tags the Token's return value as an Intrinsic. - * - * It also automatically resolves inner Token values. Simple base class so - * existing Tokens don't need to change too much. + * Base class for CloudFormation built-ins */ -export class CloudFormationIntrinsicToken extends Token { - public resolve(): any { - // Get the inner value, and deep-resolve it to resolve further Tokens. - const resolved = resolve(super.resolve()); - return markAsIntrinsic(resolved, CLOUDFORMATION_ENGINE); - } +export class CloudFormationIntrinsicToken extends IntrinsicToken { + protected readonly engine: string = CLOUDFORMATION_ENGINE; } import { FnConcat } from "./fn"; @@ -31,5 +24,5 @@ const cloudFormationEngine = { } }; -registerEngineTokenHandler(CLOUDFORMATION_ENGINE, cloudFormationEngine); -registerEngineTokenHandler(DEFAULT_ENGINE_NAME, cloudFormationEngine); \ No newline at end of file +ProvisioningEngine.register(CLOUDFORMATION_ENGINE, cloudFormationEngine); +ProvisioningEngine.register(DEFAULT_ENGINE_NAME, cloudFormationEngine); \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/rule.ts b/packages/@aws-cdk/cdk/lib/cloudformation/rule.ts index ea58c6ad60cb1..a759f6a41659f 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/rule.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/rule.ts @@ -1,5 +1,4 @@ import { Construct } from '../core/construct'; -import { Token } from '../core/tokens'; import { capitalizePropertyNames } from '../core/util'; import { FnCondition } from './fn'; import { Referenceable } from './stack'; @@ -30,7 +29,7 @@ export interface RuleProps { * If the rule condition evaluates to false, the rule doesn't take effect. * If the function in the rule condition evaluates to true, expressions in each assert are evaluated and applied. */ - ruleCondition?: Token; + ruleCondition?: FnCondition; /** * Assertions which define the rule. diff --git a/packages/@aws-cdk/cdk/lib/core/engine-intrinsics.ts b/packages/@aws-cdk/cdk/lib/core/engine-intrinsics.ts new file mode 100644 index 0000000000000..268d6e30893d0 --- /dev/null +++ b/packages/@aws-cdk/cdk/lib/core/engine-intrinsics.ts @@ -0,0 +1,42 @@ +/** + * Mark a given object as a provisioning engine-intrinsic value. + * + * Any value that has been marked as intrinsic to a provisioning engine + * will escape all further type checks and attempts to manipulate, and be + * passed on as-is to the final provisioning engine. + * + * Note that this is separate from a Token: a Token represents a lazy value. + * The result of evaluating a Token is a value, which may or may not be an + * engine intrinsic value. If you want to combine the two, see `IntrinsicToken`. + */ +export function markAsIntrinsic(x: any, engine: string): any { + // This could have been a wrapper class, but that breaks all test.deepEqual()s. + // So instead, it's implemented as a hidden property on the object (which is + // hidden from JSON.stringify() and test.deepEqual(). + + Object.defineProperty(x, INTRINSIC_VALUE_PROPERTY, { + value: engine, + enumerable: false, + writable: false, + }); + return x; +} + +/** + * Return whether the given value is an intrinsic + */ +export function isIntrinsic(x: any): boolean { + return x[INTRINSIC_VALUE_PROPERTY] !== undefined; +} + +/** + * Return the intrinsic engine for the given intrinsic value + */ +export function intrinsicEngine(x: any): string | undefined { + return x[INTRINSIC_VALUE_PROPERTY]; +} + +/** + * The hidden marker property that marks an object as an engine-intrinsic value. + */ +const INTRINSIC_VALUE_PROPERTY = '__intrinsicValue__'; \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/core/engine-strings.ts b/packages/@aws-cdk/cdk/lib/core/engine-strings.ts new file mode 100644 index 0000000000000..68b06c9163e48 --- /dev/null +++ b/packages/@aws-cdk/cdk/lib/core/engine-strings.ts @@ -0,0 +1,87 @@ +import { resolve } from "./tokens"; + +/** + * Interface point for provisioning engines to register themselves + */ +export class ProvisioningEngine { + /** + * Register a handler for all intrinsics for the given engine + */ + public static register(engineName: string, handler: IProvisioningEngine) { + HANDLERS[engineName] = handler; + } + + /** + * Combine resolved fragments using the appropriate engine. + * + * Resolves the result. + */ + public static combineStringFragments(fragments: StringFragment[]): any { + if (fragments.length === 0) { return ''; } + if (fragments.length === 1) { return fragments[0].value; } + + const engines = Array.from(new Set(fragments.filter(f => f.intrinsicEngine !== undefined).map(f => f.intrinsicEngine!))); + if (engines.length > 1) { + throw new Error(`Combining different engines in one string fragment: ${engines.join(', ')}`); + } + + const engine = engines.length > 0 ? engines[0] : DEFAULT_ENGINE_NAME; + if (!(engine in HANDLERS)) { + throw new Error(`No Token handler registered for engine: ${engine}`); + } + + return resolve(HANDLERS[engine].combineStringFragments(fragments)); + } +} + +/** + * A (resolved) fragment of a string to be combined. + * + * The values may be string literals or intrinsics. + */ +export interface StringFragment { + /** + * Source of the fragment + */ + source: FragmentSource; + + /** + * String value + * + * Either a string literal or an intrinsic. + */ + value: any; + + /** + * The intrinsic engine + */ + intrinsicEngine?: string; +} + +/** + * Where the resolved fragment came from (a string literal or a Token) + */ +export enum FragmentSource { + Literal = 'Literal', + Token = 'Token' +} + +/** + * Interface for engine-specific Token marker handlers + */ +export interface IProvisioningEngine { + /** + * Return the language intrinsic that will combine the strings in the given engine + */ + combineStringFragments(fragments: StringFragment[]): any; +} + +/** + * The engine that will be used if no Tokens are found + */ +export const DEFAULT_ENGINE_NAME = 'default'; + +/** + * Global handler map + */ +const HANDLERS: {[engine: string]: IProvisioningEngine} = {}; diff --git a/packages/@aws-cdk/cdk/lib/core/token-json.ts b/packages/@aws-cdk/cdk/lib/core/token-json.ts new file mode 100644 index 0000000000000..f9fc80037af4a --- /dev/null +++ b/packages/@aws-cdk/cdk/lib/core/token-json.ts @@ -0,0 +1,114 @@ +import { isIntrinsic } from "./engine-intrinsics"; +import { ProvisioningEngine } from "./engine-strings"; +import { resolve, Token } from "./tokens"; +import { resolveMarkerSpans, splitOnMarkers } from "./util"; + +/** + * Class for JSON routines that are framework-aware + */ +export class TokenJSON { + /** + * Turn an arbitrary structure potentially containing Tokens into JSON. + */ + public static stringify(obj: any): any { + let counter: number = 0; + const allocatedIntrinsics: {[key: string]: JSONIntrinsic} = {}; + + return new Token(() => { + // General strategy: resolve the inner value, replace remaining intrinsics with objects + // that override toJSON() to return a special marker string, then split on the + // marker strings and restore the original intrinsics. + const resolved = resolve(obj); + const stringified = JSON.stringify(deepReplaceIntrinsics(resolved)); + const spans = splitOnMarkers(stringified, BEGIN_MARKER, "[a-zA-Z0-9.]+", END_MARKER); + const fragments = resolveMarkerSpans(spans, (id) => { + return allocatedIntrinsics[id].quotedIntrinsic(); + }); + return ProvisioningEngine.combineStringFragments(fragments); + }); + + /** + * Recurse into a structure, replace all intrinsics with + */ + function deepReplaceIntrinsics(x: any): any { + if (isIntrinsic(x)) { + return allocateIntrinsic(x); + } + + if (Array.isArray(x)) { + return x.map(deepReplaceIntrinsics); + } + + if (typeof x === 'object') { + for (const key of Object.keys(x)) { + x[key] = deepReplaceIntrinsics(x[key]); + } + } + + return x; + } + + function allocateIntrinsic(intrinsic: any): JSONIntrinsic { + counter++; + const key = `INTRINSIC.${counter}`; + return allocatedIntrinsics[key] = new JSONIntrinsic(key, intrinsic); + } + } +} + +/** + * Class to hold intrinsic values, returning special marker values in calling toJSON() + */ +class JSONIntrinsic { + constructor(private readonly marker: string, private readonly intrinsic: any) { + } + + /** + * Return the intrinsic value, quoted for use within JSON context + * + * Because we know we're in a JSON context, deep recurse into the strings + * inside the intrinsic, assume they're display strings, and escape them + * for use in a JSON context. + * + * This is not entirely correct without knowing more about the structure of + * the intrinsics themselves, but doesn't break as long as they don't + * contain functional strings that break when passed through escaping. + */ + public quotedIntrinsic(): any { + return deepQuoteStringsForJSON(this.intrinsic); + } + + /** + * Return a special serialized marker string for this value + */ + public toJSON(): any { + return `${BEGIN_MARKER}${this.marker}${END_MARKER}`; + } +} + +const BEGIN_MARKER = "#{Json["; +const END_MARKER = "]}"; + +/** + * Deep escape strings for use in a JSON context + */ +function deepQuoteStringsForJSON(x: any): any { + if (typeof x === 'string') { + // Whenever we escape a string we strip off the outermost quotes + // since we're already in a quoted context. + const stringified = JSON.stringify(x); + return stringified.substring(1, stringified.length - 1); + } + + if (Array.isArray(x)) { + return x.map(deepQuoteStringsForJSON); + } + + if (typeof x === 'object') { + for (const key of Object.keys(x)) { + x[key] = deepQuoteStringsForJSON(x[key]); + } + } + + return x; +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/core/tokens.ts b/packages/@aws-cdk/cdk/lib/core/tokens.ts index 57448eda95170..cce4298369823 100644 --- a/packages/@aws-cdk/cdk/lib/core/tokens.ts +++ b/packages/@aws-cdk/cdk/lib/core/tokens.ts @@ -1,4 +1,7 @@ import { Construct } from "./construct"; +import { isIntrinsic, markAsIntrinsic } from "./engine-intrinsics"; +import { ProvisioningEngine } from "./engine-strings"; +import { resolveMarkerSpans, splitOnMarkers } from './util'; /** * If objects has a function property by this name, they will be considered tokens, and this @@ -58,63 +61,42 @@ export class Token { */ public toString(): string { const valueType = typeof this.valueOrFunction; + // Optimization: if we can immediately resolve this, don't bother + // registering a Token. if (valueType === 'string' || valueType === 'number' || valueType === 'boolean') { return this.valueOrFunction.toString(); } - // In particular: 'undefined' is not stringified here, since it should - // probably evaluate to a proper "undefined" during resolve() later. - return this.stringMarker(); + if (this.tokenKey === undefined) { + this.tokenKey = TOKEN_STRING_MAP.register(this); + } + return this.tokenKey; } /** - * Return the JSON representation of this Token - * - * Implements JSON.stringify()-compatibility for this Token. Note that it - * returns a string representation for the Token pre-resolution, NOT the - * resolved value of the Token. - * - * If the Token evaluates to a string literal, the string literal will - * undergo an additional level of escaping to make sure quotes and newlines - * embed properly into a larger JSON context. - * - * In case the Token represents a value that is intrinsic to the deployment - * engine (e.g. CloudFormation), the value is unavailable at synthesis time - * and we cannot do this escaping. Typically the values returned by those - * intrinsics are alphanumeric identifiers that don't need escaping, so - * this is not an issue. - * - * This feature is only supported for Tokens that resolve to strings, or - * to intrinsics that will produce strings. - * In case the Token represents a value that is intrinsic to the deployment - * engine (e.g. CloudFormation), the value is unavailable at synthesis time - * and we cannot do this escaping. Typically the values returned by those - * intrinsics are alphanumeric identifiers that don't need escaping, so - * this is not an issue. + * Turn this Token into JSON * - * This feature is only supported for Tokens that resolve to strings, or - * to intrinsics that will produce strings. + * This gets called by JSON.stringify(). We want to prohibit this, because + * it's not possible to do this properly, so we just throw an error here. */ public toJSON(): any { - // Return immediately if it doesn't look like this value is ever going to change. - if (!isIntrinsic(this.valueOrFunction) && typeof this.valueOrFunction !== 'function' && typeof this.valueOrFunction !== 'object') { - return this.valueOrFunction; - } - - // Otherwise return a marker for lazy evaluation. - return this.stringMarker(); + throw new Error('JSON.stringify() cannot be applied to structure with a Token in it. Use TokenJSON.stringify() instead.'); } +} - /** - * Allocate and encode the appropriate string marker for this Token - * - * Implements caching of the key. - */ - private stringMarker() { - if (this.tokenKey === undefined) { - this.tokenKey = TOKEN_STRING_MAP.register(this); - } - return this.tokenKey; +/** + * Class that tags the Token's return value as an Intrinsic. + * + */ +export abstract class IntrinsicToken extends Token { + protected abstract readonly engine: string; + + public resolve(): any { + // Get the inner value, and deep-resolve it to resolve further Tokens. + // Necessary to do this now since an intrinsic will never be resolved + // any deeper. + const resolved = resolve(super.resolve()); + return markAsIntrinsic(resolved, this.engine); } } @@ -126,11 +108,6 @@ export function isToken(obj: any): obj is Token { return typeof(obj[RESOLVE_METHOD]) === 'function'; } -export function istoken(obj: any): obj is Token { - process.emitWarning('Deprecated; use isToken() instead'); - return isToken(obj); -} - /** * Resolves an object by evaluating all tokens and removing any undefined or empty objects or arrays. * Values can only be primitives, arrays or tokens. Other objects (i.e. with methods) will be rejected. @@ -286,44 +263,15 @@ class TokenStringMap { * Replace any Token markers in this string with their resolved values */ public resolveMarkers(s: string): any { - const unresolved = this.splitMarkers(s); - const resolved = resolveFragments(unresolved); - return combineStringFragments(resolved); - } - - /** - * Split a string up into string and Token Spans - */ - public splitMarkers(s: string): UnresolvedFragment[] { - // tslint:disable-next-line:max-line-length - const re = new RegExp(`${regexQuote(BEGIN_TOKEN_MARKER)}([${VALID_KEY_CHARS}]+)${regexQuote(END_TOKEN_MARKER)}`, 'g'); - const ret = new Array(); - - let rest = 0; - let m = re.exec(s); - while (m) { - if (m.index > rest) { - ret.push({ type: 'string', value: s.substring(rest, m.index) }); - } - - ret.push({ - type: 'token', - token: this.lookupToken(m[1]) - }); - - rest = re.lastIndex; - m = re.exec(s); - } - - if (rest < s.length) { - ret.push({ type: 'string', value: s.substring(rest) }); - } + const unresolved = splitOnMarkers(s, BEGIN_TOKEN_MARKER, `[${VALID_KEY_CHARS}]+`, END_TOKEN_MARKER); + const fragments = resolveMarkerSpans(unresolved, (id) => { + const resolved = resolve(this.lookupToken(id)); - if (ret.length === 0) { - ret.push({ type: 'string', value: '' }); - } + // Convert to string unless intrinsic + return isIntrinsic(resolved) ? resolved : `${resolved}`; + }); - return ret; + return ProvisioningEngine.combineStringFragments(fragments); } /** @@ -338,36 +286,6 @@ class TokenStringMap { } } -/** - * Result of the split of a string with Tokens - * - * Either a literal part of the string, or an unresolved Token. - */ -type UnresolvedFragment = { type: 'string'; value: string } | { type: 'token'; token: Token }; - -/** - * Resolves string fragments - */ -function resolveFragments(spans: UnresolvedFragment[]): StringFragment[] { - return spans.map(span => { - if (span.type === 'string') { - return { source: FragmentSource.Literal, value: span.value }; - } - - // If not, then it's a Token that needs resolving - const resolved = resolve(span.token); - - // This must resolve to a string or an intrinsic to make any sense. Let's see - // what errors crop up if we're strict and then see if we want to loosen up the - // rules later. - if (!isIntrinsic(resolved) && typeof resolved !== 'string') { - throw new Error(`Result of Token evaluation must be a string, got: ${resolved}`); - } - - return { source: FragmentSource.Token, value: resolved, intrinsicEngine: intrinsicEngine(resolved) }; - }); -} - const BEGIN_TOKEN_MARKER = '${Token['; const END_TOKEN_MARKER = ']}'; const VALID_KEY_CHARS = 'a-zA-Z0-9:._-'; @@ -375,196 +293,4 @@ const VALID_KEY_CHARS = 'a-zA-Z0-9:._-'; /** * Singleton instance of the token string map */ -const TOKEN_STRING_MAP = new TokenStringMap(); - -function regexQuote(s: string) { - return s.replace(/[.?*+^$[\]\\(){}|-]/g, "\\$&"); -} - -/** - * The hidden marker property that marks an object as an engine-intrinsic value. - */ -const INTRINSIC_VALUE_PROPERTY = '__intrinsicValue__'; - -/** - * Mark a given object as an engine-intrinsic value. - * - * This will avoid it from being resolved by resolve(). Note that if the value - * returns any Tokens, they should be resolved before marking the object - * as an intrinsic. - * - * This could have been a wrapper class, but that breaks all test.deepEqual()s. - * So instead, it's implemented as a hidden property on the object (which is - * hidden from JSON.stringify() and test.deepEqual(). - */ -export function markAsIntrinsic(x: any, engine: string): any { - Object.defineProperty(x, INTRINSIC_VALUE_PROPERTY, { - value: engine, - enumerable: false, - writable: false, - }); - return x; -} - -/** - * Return whether the given value is an intrinsic - */ -export function isIntrinsic(x: any): boolean { - return x[INTRINSIC_VALUE_PROPERTY] !== undefined; -} - -/** - * Return the intrinsic engine for the given intrinsic value - */ -export function intrinsicEngine(x: any): string | undefined { - return x[INTRINSIC_VALUE_PROPERTY]; -} - -/** - * A (resolved) fragment of a string to be combined. - * - * The values may be string literals or intrinsics. - */ -export interface StringFragment { - /** - * Source of the fragment - */ - source: FragmentSource; - - /** - * String value - * - * Either a string literal or an intrinsic. - */ - value: any; - - /** - * The intrinsic engine - */ - intrinsicEngine?: string; -} - -/** - * Where the resolved fragment came from (a string literal or a Token) - */ -export enum FragmentSource { - Literal = 'Literal', - Token = 'Token' -} - -/** - * Interface for engine-specific Token marker handlers - */ -export interface IEngineTokenHandler { - /** - * Return the language intrinsic that will combine the strings in the given engine - */ - combineStringFragments(fragments: StringFragment[]): any; -} - -/** - * The engine that will be used if no Tokens are found - */ -export const DEFAULT_ENGINE_NAME = 'default'; - -/** - * Global handler map - */ -const HANDLERS: {[engine: string]: IEngineTokenHandler} = {}; - -/** - * Register a handler for all intrinsics for the given engine - */ -export function registerEngineTokenHandler(engineName: string, handler: IEngineTokenHandler) { - HANDLERS[engineName] = handler; -} - -/** - * Combine resolved fragments using the appropriate engine. - * - * Resolves the result. - */ -function combineStringFragments(fragments: StringFragment[]): any { - if (fragments.length === 0) { return ''; } - if (fragments.length === 1) { return fragments[0].value; } - - const engines = Array.from(new Set(fragments.filter(f => f.intrinsicEngine !== undefined).map(f => f.intrinsicEngine!))); - if (engines.length > 1) { - throw new Error(`Combining different engines in one string fragment: ${engines.join(', ')}`); - } - - const engine = engines.length > 0 ? engines[0] : DEFAULT_ENGINE_NAME; - if (!(engine in HANDLERS)) { - throw new Error(`No Token handler registered for engine: ${engine}`); - } - - return resolve(HANDLERS[engine].combineStringFragments(fragments)); -} - -/** - * Turn an arbitrary structure potentially containing Tokens into JSON. - */ -export function tokenAwareJsonify(obj: any): any { - // This function must exist because even if we changed token.toJSON() - // to return a special marker, we couldn't handle both of the following - // cases correctly: - // - // [a] Embed into JSON structure directly, result: - // - // {"key":"#{Token[0]}"} - // - // [b] First toString() to embed into string literal, then embed in JSON - // structure, result: - // - // {"key":"larger string with ${Token[0]}"} - // - // In [a], we could detect from '#' vs the '$' that the value of Token[0] - // needs to undergo additional JSON-escaping, but we couldn't do the same in - // case [b], even though the resultant string needs to undergo JSON-escaping - // all the same. - // - // The correct way to handle this is to resolve scalars as usual, and do - // an additional layer of JSON-postprocessing afterwards. - return new Token(() => { - const unresolved = TOKEN_STRING_MAP.splitMarkers(JSON.stringify(obj)); - const fragments = resolveFragments(unresolved); - - // Here's the magic sauce: everything that used to be a string before - // has already been escaped, so we leave alone. Everything that came - // rolling fresh out of a Token could _theoretically_ be everything - // but in practice is either a string literal (escape!) or an intrinsic, - // which in this RARE case we also recurse over and escape the strings - // we find in it. This is technically cheating since we don't know which - // strings in the intrinsics are instructions and which will be rendered - // into the output, but in practice the instruction strings will not - // contain escapable characters anyway. - for (const fragment of fragments) { - if (fragment.source === FragmentSource.Token) { - fragment.value = deepEscape(fragment.value); - } - } - - return combineStringFragments(fragments); - }); - - function deepEscape(x: any): any { - if (typeof x === 'string') { - // Whenever we escape a string we strip off the outermost quotes - // since we're already in a quoted context. - const stringified = JSON.stringify(x); - return stringified.substring(1, stringified.length - 1); - } - - if (Array.isArray(x)) { - return x.map(deepEscape); - } - - if (typeof x === 'object') { - for (const key of Object.keys(x)) { - x[key] = deepEscape(x[key]); - } - } - - return x; - } -} \ No newline at end of file +const TOKEN_STRING_MAP = new TokenStringMap(); \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/core/util.ts b/packages/@aws-cdk/cdk/lib/core/util.ts index f1944f089e305..1bb1d5a3380e8 100644 --- a/packages/@aws-cdk/cdk/lib/core/util.ts +++ b/packages/@aws-cdk/cdk/lib/core/util.ts @@ -1,3 +1,5 @@ +import { intrinsicEngine } from './engine-intrinsics'; +import { FragmentSource, StringFragment } from './engine-strings'; import { resolve } from './tokens'; /** @@ -47,4 +49,66 @@ export function ignoreEmpty(o: any): any { } return o; -} \ No newline at end of file +} + +/** + * Result of the split of a string with Tokens + * + * Either a literal part of the string, or an unresolved Token. + */ +export type MarkerSpan = { type: 'string'; value: string } | { type: 'marker'; id: string }; + +/** + * Split a string up into String Spans and Marker Spans + */ +export function splitOnMarkers(s: string, beginMarker: string, idPattern: string, endMarker: string): MarkerSpan[] { + // tslint:disable-next-line:max-line-length + const re = new RegExp(`${regexQuote(beginMarker)}(${idPattern})${regexQuote(endMarker)}`, 'g'); + const ret = new Array(); + + let rest = 0; + let m = re.exec(s); + while (m) { + if (m.index > rest) { + ret.push({ type: 'string', value: s.substring(rest, m.index) }); + } + + ret.push({ type: 'marker', id: m[1] }); + + rest = re.lastIndex; + m = re.exec(s); + } + + if (rest < s.length) { + ret.push({ type: 'string', value: s.substring(rest) }); + } + + if (ret.length === 0) { + ret.push({ type: 'string', value: '' }); + } + + return ret; +} + +/** + * Resolves marker spans to string fragments + */ +export function resolveMarkerSpans(spans: MarkerSpan[], lookup: (id: string) => any): StringFragment[] { + return spans.map(span => { + + switch (span.type) { + case 'string': + return { source: FragmentSource.Literal, value: span.value }; + case 'marker': + const value = lookup(span.id); + return { source: FragmentSource.Token, value, intrinsicEngine: intrinsicEngine(value) }; + } + }); +} + +/** + * Quote a string for use in a regex + */ +function regexQuote(s: string) { + return s.replace(/[.?*+^$[\]\\(){}|-]/g, "\\$&"); +} diff --git a/packages/@aws-cdk/cdk/lib/index.ts b/packages/@aws-cdk/cdk/lib/index.ts index 51ba534caf11c..4f708b99b0ea4 100644 --- a/packages/@aws-cdk/cdk/lib/index.ts +++ b/packages/@aws-cdk/cdk/lib/index.ts @@ -1,4 +1,6 @@ export * from './core/construct'; +export * from './core/engine-intrinsics'; +export * from './core/token-json'; export * from './core/tokens'; export * from './core/jsx'; diff --git a/packages/@aws-cdk/cdk/test/core/cfn-intrinsics.ts b/packages/@aws-cdk/cdk/test/core/cfn-intrinsics.ts deleted file mode 100644 index 5db89bb4fedbd..0000000000000 --- a/packages/@aws-cdk/cdk/test/core/cfn-intrinsics.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Simple function to evaluate CloudFormation intrinsics. - * - * Note that this function is not production quality, it exists to support tests. - */ -export function evaluateIntrinsics(obj: any): any { - if (Array.isArray(obj)) { - return obj.map(evaluateIntrinsics); - } - - if (typeof obj === 'object') { - const keys = Object.keys(obj); - if (keys.length === 1 && (keys[0].startsWith('Fn::') || keys[0] === 'Ref')) { - return evaluateIntrinsic(keys[0], obj[keys[0]]); - } - - const ret: {[key: string]: any} = {}; - for (const key of keys) { - ret[key] = evaluateIntrinsics(obj[key]); - } - return ret; - } - - return obj; -} - -const INTRINSICS: any = { - 'Fn::Join'(separator: string, args: string[]) { - return args.map(evaluateIntrinsics).join(separator); - }, - - 'Ref'(logicalId: string) { - // We can't get the actual here, but we can at least return a marker - // that shows we would put the value here. - return `<>`; - } -}; - -function evaluateIntrinsic(name: string, args: any) { - if (!(name in INTRINSICS)) { - throw new Error(`Intrinsic ${name} not supported here`); - } - - if (!Array.isArray(args)) { - args = [args]; - } - - return INTRINSICS[name].apply(INTRINSICS, args); -} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/test/core/evaluate-cfn.ts b/packages/@aws-cdk/cdk/test/core/evaluate-cfn.ts new file mode 100644 index 0000000000000..4f8a419626ead --- /dev/null +++ b/packages/@aws-cdk/cdk/test/core/evaluate-cfn.ts @@ -0,0 +1,62 @@ +/** + * Simple function to evaluate CloudFormation intrinsics. + * + * Note that this function is not production quality, it exists to support tests. + */ +export function evaluateCFN(object: any, context: {[key: string]: string} = {}): any { + const intrinsics: any = { + 'Fn::Join'(separator: string, args: string[]) { + return args.map(evaluate).join(separator); + }, + + 'Ref'(logicalId: string) { + if (!(logicalId in context)) { + throw new Error(`Trying to evaluate Ref of '${logicalId}' but not in context!`); + } + return context[logicalId]; + }, + + 'Fn::GetAtt'(logicalId: string, attributeName: string) { + const key = `${logicalId}.${attributeName}`; + if (!(key in context)) { + throw new Error(`Trying to evaluate Fn::GetAtt of '${logicalId}.${attributeName}' but not in context!`); + } + return context[key]; + } + }; + + return evaluate(object); + + function evaluate(obj: any): any { + if (Array.isArray(obj)) { + return obj.map(evaluate); + } + + if (typeof obj === 'object') { + const keys = Object.keys(obj); + if (keys.length === 1 && (keys[0].startsWith('Fn::') || keys[0] === 'Ref')) { + return evaluateIntrinsic(keys[0], obj[keys[0]]); + } + + const ret: {[key: string]: any} = {}; + for (const key of keys) { + ret[key] = evaluateCFN(obj[key]); + } + return ret; + } + + return obj; + } + + function evaluateIntrinsic(name: string, args: any) { + if (!(name in intrinsics)) { + throw new Error(`Intrinsic ${name} not supported here`); + } + + if (!Array.isArray(args)) { + args = [args]; + } + + return intrinsics[name].apply(intrinsics, args); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/test/core/test.token-json.ts b/packages/@aws-cdk/cdk/test/core/test.token-json.ts new file mode 100644 index 0000000000000..cd85673f1b13a --- /dev/null +++ b/packages/@aws-cdk/cdk/test/core/test.token-json.ts @@ -0,0 +1,175 @@ +import { Test } from 'nodeunit'; +import { CloudFormationIntrinsicToken, FnConcat, resolve, Token, TokenJSON } from '../../lib'; +import { evaluateCFN } from './evaluate-cfn'; + +export = { + 'plain JSON.stringify() on a Token fails'(test: Test) { + // GIVEN + const token = new Token(() => 'value'); + + // WHEN + test.throws(() => { + JSON.stringify({ token }); + }); + + test.done(); + }, + + 'string tokens can be JSONified and JSONification can be reversed'(test: Test) { + for (const token of tokensThatResolveTo('woof woof')) { + // GIVEN + const fido = { name: 'Fido', speaks: token }; + + // WHEN + const resolved = resolve(TokenJSON.stringify(fido)); + + // THEN + test.deepEqual(evaluateCFN(resolved), '{"name":"Fido","speaks":"woof woof"}'); + } + + test.done(); + }, + + 'string tokens can be embedded while being JSONified'(test: Test) { + for (const token of tokensThatResolveTo('woof woof')) { + // GIVEN + const fido = { name: 'Fido', speaks: `deep ${token}` }; + + // WHEN + const resolved = resolve(TokenJSON.stringify(fido)); + + // THEN + test.deepEqual(evaluateCFN(resolved), '{"name":"Fido","speaks":"deep woof woof"}'); + } + + test.done(); + }, + + 'integer Tokens behave correctly in stringification and JSONification'(test: Test) { + // GIVEN + const num = new Token(() => 1); + const embedded = `the number is ${num}`; + + // WHEN + test.equal(evaluateCFN(resolve(embedded)), "the number is 1"); + test.equal(evaluateCFN(resolve(TokenJSON.stringify({ embedded }))), "{\"embedded\":\"the number is 1\"}"); + test.equal(evaluateCFN(resolve(TokenJSON.stringify({ num }))), "{\"num\":1}"); + + test.done(); + }, + + 'tokens in strings survive additional TokenJSON.stringification()'(test: Test) { + // GIVEN + for (const token of tokensThatResolveTo('pong!')) { + // WHEN + const stringified = TokenJSON.stringify(`ping? ${token}`); + + // THEN + test.equal(evaluateCFN(resolve(stringified)), '"ping? pong!"'); + } + + test.done(); + }, + + 'intrinsic Tokens embed correctly in JSONification'(test: Test) { + // GIVEN + const bucketName = new CloudFormationIntrinsicToken({ Ref: 'MyBucket' }); + + // WHEN + const resolved = resolve(TokenJSON.stringify({ theBucket: bucketName })); + + // THEN + const context = {MyBucket: 'TheName'}; + test.equal(evaluateCFN(resolved, context), '{"theBucket":"TheName"}'); + + test.done(); + }, + + 'embedded string literals in intrinsics are escaped when calling TokenJSON.stringify()'(test: Test) { + // WHEN + const token = new FnConcat('Hello', 'This\nIs', 'Very "cool"'); + + // WHEN + const resolved = resolve(TokenJSON.stringify({ + literal: 'I can also "contain" quotes', + token + })); + + // THEN + const expected = '{"literal":"I can also \\"contain\\" quotes","token":"HelloThis\\nIsVery \\"cool\\""}'; + test.equal(evaluateCFN(resolved), expected); + + test.done(); + }, + + 'Tokens in Tokens are handled correctly'(test: Test) { + // GIVEN + const bucketName = new CloudFormationIntrinsicToken({ Ref: 'MyBucket' }); + const combinedName = new FnConcat('The bucket name is ', bucketName); + + // WHEN + const resolved = resolve(TokenJSON.stringify({ theBucket: combinedName })); + + // THEN + const context = {MyBucket: 'TheName'}; + test.equal(evaluateCFN(resolved, context), '{"theBucket":"The bucket name is TheName"}'); + + test.done(); + }, + + 'Doubly nested strings evaluate correctly in JSON context'(test: Test) { + // WHEN + const fidoSays = new Token(() => 'woof'); + + // WHEN + const resolved = resolve(TokenJSON.stringify({ + information: `Did you know that Fido says: ${fidoSays}` + })); + + // THEN + test.deepEqual(evaluateCFN(resolved), '{"information":"Did you know that Fido says: woof"}'); + + test.done(); + }, + + 'Doubly nested intrinsics evaluate correctly in JSON context'(test: Test) { + // WHEN + const fidoSays = new CloudFormationIntrinsicToken(() => ({ Ref: 'Something' })); + + // WHEN + const resolved = resolve(TokenJSON.stringify({ + information: `Did you know that Fido says: ${fidoSays}` + })); + + // THEN + const context = {Something: 'woof woof'}; + test.deepEqual(evaluateCFN(resolved, context), '{"information":"Did you know that Fido says: woof woof"}'); + + test.done(); + }, + + 'Quoted strings in embedded JSON context are escaped'(test: Test) { + // WHEN + const fidoSays = new Token(() => '"woof"'); + + // WHEN + const resolved = resolve(TokenJSON.stringify({ + information: `Did you know that Fido says: ${fidoSays}` + })); + + // THEN + test.deepEqual(evaluateCFN(resolved), '{"information":"Did you know that Fido says: \\"woof\\""}'); + + test.done(); + }, +}; + +/** + * Return two Tokens, one of which evaluates to a Token directly, one which evaluates to it lazily + */ +function tokensThatResolveTo(value: any): Token[] { + return [ + new Token(value), + new Token(() => value) + ]; +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/test/core/test.tokens.ts b/packages/@aws-cdk/cdk/test/core/test.tokens.ts index 82fc5a0725007..b667d5e6f2331 100644 --- a/packages/@aws-cdk/cdk/test/core/test.tokens.ts +++ b/packages/@aws-cdk/cdk/test/core/test.tokens.ts @@ -1,6 +1,6 @@ import { Test } from 'nodeunit'; -import { CloudFormationIntrinsicToken, FnConcat, isToken, resolve, Token, tokenAwareJsonify } from '../../lib'; -import { evaluateIntrinsics } from './cfn-intrinsics'; +import { CloudFormationIntrinsicToken, isToken, resolve, Token } from '../../lib'; +import { evaluateCFN } from './evaluate-cfn'; export = { 'resolve a plain old object should just return the object'(test: Test) { @@ -144,7 +144,7 @@ export = { test.done(); }, - 'tokens can be stringified and stringification can be reversed'(test: Test) { + 'tokens can be stringified and evaluated to conceptual value'(test: Test) { // GIVEN const token = new Token(() => 'woof woof'); @@ -157,64 +157,6 @@ export = { test.done(); }, - 'tokens can be JSONified and JSONification can be reversed'(test: Test) { - // GIVEN - const fido = { - name: 'Fido', - speaks: new Token(() => 'woof woof') - }; - - // WHEN - const resolved = resolve(tokenAwareJsonify(fido)); - - // THEN - test.deepEqual(resolved, {'Fn::Join': ['', - ['{"name":"Fido","speaks":"', 'woof woof', '"}']]}); - test.done(); - }, - - 'During tokenAwareJsonify, its an error for a Token to not resolve to a string'(test: Test) { - // GIVEN - const fido1 = { name: "Fido", age: new Token(() => 1) }; - - // THEN - test.throws(() => { - resolve(tokenAwareJsonify(fido1)); - }); - - test.done(); - }, - - 'Non-lazy integers are allowed in tokenAwareJsonify()'(test: Test) { - // GIVEN - const fido = { name: "Fido", age: new Token(1) }; - - // WHEN - const resolved = evaluateIntrinsics(resolve(tokenAwareJsonify(fido))); - - // THEN - test.deepEqual('{"name":"Fido","age":1}', resolved); - - test.done(); - }, - - 'lazy string literals in evaluated tokens are escaped when calling tokenAwareJsonify()'(test: Test) { - // WHEN - const token = new FnConcat('Hello', 'This\nIs', 'Very "cool"'); - - // WHEN - const resolved = resolve(tokenAwareJsonify({ - literal: 'I can also "contain" quotes', - token - })); - - // THEN - const expected = '{"literal":"I can also \\"contain\\" quotes","token":"HelloThis\\nIsVery \\"cool\\""}'; - test.equal(evaluateIntrinsics(resolved), expected); - - test.done(); - }, - 'Doubly nested strings evaluate correctly in scalar context'(test: Test) { // GIVEN const token1 = new Token(() => "world"); @@ -225,53 +167,35 @@ export = { const resolved2 = resolve(token2); // THEN - test.deepEqual(evaluateIntrinsics(resolved1), "hello world"); - test.deepEqual(evaluateIntrinsics(resolved2), "hello world"); - - test.done(); - }, - - 'Doubly nested strings evaluate correctly in JSON context'(test: Test) { - // WHEN - const fidoSays = new Token(() => 'woof'); - - // WHEN - const resolved = resolve(tokenAwareJsonify({ - information: `Did you know that Fido says: ${fidoSays}` - })); - - // THEN - test.deepEqual(evaluateIntrinsics(resolved), '{"information":"Did you know that Fido says: woof"}'); + test.deepEqual(evaluateCFN(resolved1), "hello world"); + test.deepEqual(evaluateCFN(resolved2), "hello world"); test.done(); }, - 'Doubly nested intrinsics evaluate correctly in JSON context'(test: Test) { - // WHEN - const fidoSays = new CloudFormationIntrinsicToken(() => ({ Ref: 'Something' })); - - // WHEN - const resolved = resolve(tokenAwareJsonify({ - information: `Did you know that Fido says: ${fidoSays}` - })); - - // THEN - test.deepEqual(evaluateIntrinsics(resolved), '{"information":"Did you know that Fido says: <>"}'); + 'integer Tokens can be stringified and evaluate to conceptual value'(test: Test) { + // GIVEN + for (const token of tokensThatResolveTo(1)) { + // WHEN + const stringified = `the number is ${token}`; + const resolved = resolve(stringified); + // THEN + test.deepEqual(evaluateCFN(resolved), 'the number is 1'); + } test.done(); }, - 'Quoted strings in embedded JSON context are escaped'(test: Test) { - // WHEN - const fidoSays = new Token(() => '"woof"'); + 'intrinsic Tokens can be stringified and evaluate to conceptual value'(test: Test) { + // GIVEN + const bucketName = new CloudFormationIntrinsicToken({ Ref: 'MyBucket' }); // WHEN - const resolved = resolve(tokenAwareJsonify({ - information: `Did you know that Fido says: ${fidoSays}` - })); + const resolved = resolve(`my bucket is named ${bucketName}`); // THEN - test.deepEqual(evaluateIntrinsics(resolved), '{"information":"Did you know that Fido says: \\"woof\\""}'); + const context = {MyBucket: 'TheName'}; + test.equal(evaluateCFN(resolved, context), 'my bucket is named TheName'); test.done(); }, @@ -309,3 +233,13 @@ class DataType extends BaseDataType { super(12); } } + +/** + * Return two Tokens, one of which evaluates to a Token directly, one which evaluates to it lazily + */ +function tokensThatResolveTo(value: any): Token[] { + return [ + new Token(value), + new Token(() => value) + ]; +} \ No newline at end of file From 74f4fe7cb70de9c454014e5a23a95223888e7441 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 13 Aug 2018 14:34:59 +0200 Subject: [PATCH 10/17] SecretParameter no longer duckily implements Token, won't work in non-TypeScript languages anyway --- .../aws-codebuild-codepipeline/test/test.pipeline.ts | 2 +- packages/@aws-cdk/cdk/lib/cloudformation/secret.ts | 9 ++------- packages/@aws-cdk/cdk/test/cloudformation/test.secret.ts | 1 - 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/@aws-cdk/aws-codebuild-codepipeline/test/test.pipeline.ts b/packages/@aws-cdk/aws-codebuild-codepipeline/test/test.pipeline.ts index 7ddcfa8b706f8..02b2c1e470a93 100644 --- a/packages/@aws-cdk/aws-codebuild-codepipeline/test/test.pipeline.ts +++ b/packages/@aws-cdk/aws-codebuild-codepipeline/test/test.pipeline.ts @@ -50,7 +50,7 @@ export = { new codepipeline.GitHubSource(new codepipeline.Stage(p, 'Source'), 'GH', { artifactName: 'A', branch: 'branch', - oauthToken: secret, + oauthToken: secret.value, owner: 'foo', repo: 'bar' }); diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/secret.ts b/packages/@aws-cdk/cdk/lib/cloudformation/secret.ts index 01c2e9b2e1b55..53f2f62ae545e 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/secret.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/secret.ts @@ -72,9 +72,9 @@ export interface SecretProps { */ export class SecretParameter extends Construct { /** - * A token for the secret value. + * The value of the secret parameter. */ - public value: Token; + public value: Secret; constructor(parent: Construct, name: string, props: SecretProps) { super(parent, name); @@ -93,9 +93,4 @@ export class SecretParameter extends Construct { this.value = param.ref; } - - // implicitly implements Token, and therefore Secret. - public resolve(): any { - return this.value; - } } diff --git a/packages/@aws-cdk/cdk/test/cloudformation/test.secret.ts b/packages/@aws-cdk/cdk/test/cloudformation/test.secret.ts index 1d58495ab9e61..a28a8ede46752 100644 --- a/packages/@aws-cdk/cdk/test/cloudformation/test.secret.ts +++ b/packages/@aws-cdk/cdk/test/cloudformation/test.secret.ts @@ -44,7 +44,6 @@ export = { // value resolves to a "Ref" test.deepEqual(resolve(mySecret.value), { Ref: 'MySecretParameterBB81DE58' }); - test.deepEqual(resolve(mySecret), { Ref: 'MySecretParameterBB81DE58' }); test.done(); } From 5753e319872d7f16321239118839ecb934b7872f Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 13 Aug 2018 15:20:37 +0200 Subject: [PATCH 11/17] fix(aws-logs): resolving of policy Fix an issue where the wrong resolution method was used to resolve policy documents. --- packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts | 4 +--- packages/@aws-cdk/aws-logs/test/test.destination.ts | 3 +-- packages/@aws-cdk/cdk/lib/core/tokens.ts | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts b/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts index 8f76d231cb798..f7d9adc62b9a4 100644 --- a/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts +++ b/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts @@ -59,14 +59,12 @@ export class CrossAccountDestination extends cdk.Construct implements ILogSubscr constructor(parent: cdk.Construct, id: string, props: CrossAccountDestinationProps) { super(parent, id); - this.policyDocument = new cdk.PolicyDocument(); - // In the underlying model, the name is not optional, but we make it so anyway. const destinationName = props.destinationName || new cdk.Token(() => this.generateUniqueName()); this.resource = new cloudformation.DestinationResource(this, 'Resource', { destinationName, - destinationPolicy: new cdk.Token(() => !this.policyDocument.isEmpty ? JSON.stringify(this.policyDocument.resolve()) : ""), + destinationPolicy: new cdk.Token(() => !this.policyDocument.isEmpty ? JSON.stringify(cdk.resolve(this.policyDocument)) : ""), roleArn: props.role.roleArn, targetArn: props.targetArn }); diff --git a/packages/@aws-cdk/aws-logs/test/test.destination.ts b/packages/@aws-cdk/aws-logs/test/test.destination.ts index ebd1a6e9ca515..63b33a0e62815 100644 --- a/packages/@aws-cdk/aws-logs/test/test.destination.ts +++ b/packages/@aws-cdk/aws-logs/test/test.destination.ts @@ -48,10 +48,9 @@ export = { // THEN expect(stack).to(haveResource('AWS::Logs::Destination', (props: any) => { - // tslint:disable-next-line:no-console const pol = JSON.parse(props.DestinationPolicy); - return pol.Statement[0].action[0] === 'logs:TalkToMe'; + return pol.Statement[0].Action === 'logs:TalkToMe'; })); test.done(); diff --git a/packages/@aws-cdk/cdk/lib/core/tokens.ts b/packages/@aws-cdk/cdk/lib/core/tokens.ts index cce4298369823..9575efd6f87f0 100644 --- a/packages/@aws-cdk/cdk/lib/core/tokens.ts +++ b/packages/@aws-cdk/cdk/lib/core/tokens.ts @@ -80,7 +80,7 @@ export class Token { * it's not possible to do this properly, so we just throw an error here. */ public toJSON(): any { - throw new Error('JSON.stringify() cannot be applied to structure with a Token in it. Use TokenJSON.stringify() instead.'); + throw new Error('JSON.stringify() cannot be applied to structure with a deferred Token in it. Use TokenJSON.stringify() instead.'); } } From 4206b85da3627ba586e8d773edbda076f2c41bf7 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 13 Aug 2018 15:49:33 +0200 Subject: [PATCH 12/17] Combine string literals while resolving fragments, so we never have non-intrinsics --- .../cdk/lib/cloudformation/intrinsics.ts | 5 ++-- .../@aws-cdk/cdk/lib/core/engine-strings.ts | 29 +++++++++++++++---- packages/@aws-cdk/cdk/lib/core/util.ts | 8 +++-- .../@aws-cdk/cdk/test/core/test.tokens.ts | 2 +- 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/intrinsics.ts b/packages/@aws-cdk/cdk/lib/cloudformation/intrinsics.ts index 468c88d121ee5..49b0b8341ca05 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/intrinsics.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/intrinsics.ts @@ -1,4 +1,4 @@ -import { DEFAULT_ENGINE_NAME, ProvisioningEngine, StringFragment } from "../core/engine-strings"; +import { ProvisioningEngine, StringFragment } from "../core/engine-strings"; import { IntrinsicToken } from "../core/tokens"; /** @@ -24,5 +24,4 @@ const cloudFormationEngine = { } }; -ProvisioningEngine.register(CLOUDFORMATION_ENGINE, cloudFormationEngine); -ProvisioningEngine.register(DEFAULT_ENGINE_NAME, cloudFormationEngine); \ No newline at end of file +ProvisioningEngine.register(CLOUDFORMATION_ENGINE, cloudFormationEngine); \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/core/engine-strings.ts b/packages/@aws-cdk/cdk/lib/core/engine-strings.ts index 68b06c9163e48..198b907f661c6 100644 --- a/packages/@aws-cdk/cdk/lib/core/engine-strings.ts +++ b/packages/@aws-cdk/cdk/lib/core/engine-strings.ts @@ -17,15 +17,20 @@ export class ProvisioningEngine { * Resolves the result. */ public static combineStringFragments(fragments: StringFragment[]): any { + simplifyFragments(fragments); + if (fragments.length === 0) { return ''; } if (fragments.length === 1) { return fragments[0].value; } const engines = Array.from(new Set(fragments.filter(f => f.intrinsicEngine !== undefined).map(f => f.intrinsicEngine!))); + if (engines.length === 0) { + throw new Error('Should not happen; no intrinsics found in StringFragment list.'); + } if (engines.length > 1) { throw new Error(`Combining different engines in one string fragment: ${engines.join(', ')}`); } - const engine = engines.length > 0 ? engines[0] : DEFAULT_ENGINE_NAME; + const engine = engines[0]; if (!(engine in HANDLERS)) { throw new Error(`No Token handler registered for engine: ${engine}`); } @@ -63,7 +68,7 @@ export interface StringFragment { */ export enum FragmentSource { Literal = 'Literal', - Token = 'Token' + Intrinsic = 'Intrinsic' } /** @@ -77,11 +82,23 @@ export interface IProvisioningEngine { } /** - * The engine that will be used if no Tokens are found + * Global handler map */ -export const DEFAULT_ENGINE_NAME = 'default'; +const HANDLERS: {[engine: string]: IProvisioningEngine} = {}; /** - * Global handler map + * Combine adjacent string literals in the fragment list + * + * List is modified in-place. */ -const HANDLERS: {[engine: string]: IProvisioningEngine} = {}; +function simplifyFragments(fragments: StringFragment[]) { + let i = 0; + while (i < fragments.length - 1) { + if (fragments[i].source === FragmentSource.Literal && fragments[i + 1].source === FragmentSource.Literal) { + fragments[i].value += fragments[i + 1].value; + fragments.splice(i + 1, 1); + } else { + i++; + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/core/util.ts b/packages/@aws-cdk/cdk/lib/core/util.ts index 1bb1d5a3380e8..f8e52bf939798 100644 --- a/packages/@aws-cdk/cdk/lib/core/util.ts +++ b/packages/@aws-cdk/cdk/lib/core/util.ts @@ -1,4 +1,4 @@ -import { intrinsicEngine } from './engine-intrinsics'; +import { intrinsicEngine, isIntrinsic } from './engine-intrinsics'; import { FragmentSource, StringFragment } from './engine-strings'; import { resolve } from './tokens'; @@ -101,7 +101,11 @@ export function resolveMarkerSpans(spans: MarkerSpan[], lookup: (id: string) => return { source: FragmentSource.Literal, value: span.value }; case 'marker': const value = lookup(span.id); - return { source: FragmentSource.Token, value, intrinsicEngine: intrinsicEngine(value) }; + if (isIntrinsic(value)) { + return { source: FragmentSource.Intrinsic, value, intrinsicEngine: intrinsicEngine(value) }; + } else { + return { source: FragmentSource.Literal, value: `${value}` }; + } } }); } diff --git a/packages/@aws-cdk/cdk/test/core/test.tokens.ts b/packages/@aws-cdk/cdk/test/core/test.tokens.ts index b667d5e6f2331..86497645962c3 100644 --- a/packages/@aws-cdk/cdk/test/core/test.tokens.ts +++ b/packages/@aws-cdk/cdk/test/core/test.tokens.ts @@ -153,7 +153,7 @@ export = { const resolved = resolve(stringified); // THEN - test.deepEqual(resolved, {'Fn::Join': ['', ['The dog says: ', 'woof woof']]}); + test.deepEqual(evaluateCFN(resolved), 'The dog says: woof woof'); test.done(); }, From 85cfa81db676b91326a2b44a999d8f9da83231a1 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Tue, 14 Aug 2018 10:32:17 +0200 Subject: [PATCH 13/17] Make intrinsics carry a direct object reference to the engine, simply cross-account-destination policy usage --- .../aws-logs/lib/cross-account-destination.ts | 2 +- .../{intrinsics.ts => engine.ts} | 28 ++-- .../@aws-cdk/cdk/lib/cloudformation/fn.ts | 2 +- .../@aws-cdk/cdk/lib/cloudformation/pseudo.ts | 2 +- .../cdk/lib/cloudformation/resource.ts | 2 +- .../@aws-cdk/cdk/lib/cloudformation/stack.ts | 2 +- .../cdk/lib/core/engine-intrinsics.ts | 42 ------ .../@aws-cdk/cdk/lib/core/engine-strings.ts | 104 ------------- packages/@aws-cdk/cdk/lib/core/engine.ts | 138 ++++++++++++++++++ packages/@aws-cdk/cdk/lib/core/token-json.ts | 5 +- packages/@aws-cdk/cdk/lib/core/tokens.ts | 9 +- packages/@aws-cdk/cdk/lib/core/util.ts | 3 +- packages/@aws-cdk/cdk/lib/index.ts | 4 +- 13 files changed, 163 insertions(+), 180 deletions(-) rename packages/@aws-cdk/cdk/lib/cloudformation/{intrinsics.ts => engine.ts} (50%) delete mode 100644 packages/@aws-cdk/cdk/lib/core/engine-intrinsics.ts delete mode 100644 packages/@aws-cdk/cdk/lib/core/engine-strings.ts create mode 100644 packages/@aws-cdk/cdk/lib/core/engine.ts diff --git a/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts b/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts index f7d9adc62b9a4..7de523d836c8d 100644 --- a/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts +++ b/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts @@ -64,7 +64,7 @@ export class CrossAccountDestination extends cdk.Construct implements ILogSubscr this.resource = new cloudformation.DestinationResource(this, 'Resource', { destinationName, - destinationPolicy: new cdk.Token(() => !this.policyDocument.isEmpty ? JSON.stringify(cdk.resolve(this.policyDocument)) : ""), + destinationPolicy: this.policyDocument, roleArn: props.role.roleArn, targetArn: props.targetArn }); diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/intrinsics.ts b/packages/@aws-cdk/cdk/lib/cloudformation/engine.ts similarity index 50% rename from packages/@aws-cdk/cdk/lib/cloudformation/intrinsics.ts rename to packages/@aws-cdk/cdk/lib/cloudformation/engine.ts index 49b0b8341ca05..b44fc5492b47b 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/intrinsics.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/engine.ts @@ -1,27 +1,21 @@ -import { ProvisioningEngine, StringFragment } from "../core/engine-strings"; +import { IProvisioningEngine, StringFragment } from "../core/engine"; import { IntrinsicToken } from "../core/tokens"; -/** - * The default intrinsics Token engine for CloudFormation - */ -export const CLOUDFORMATION_ENGINE = 'cloudformation'; +import { FnConcat } from "./fn"; /** - * Base class for CloudFormation built-ins + * The default intrinsics Token engine for CloudFormation */ -export class CloudFormationIntrinsicToken extends IntrinsicToken { - protected readonly engine: string = CLOUDFORMATION_ENGINE; -} - -import { FnConcat } from "./fn"; - -const cloudFormationEngine = { - /** - * In CloudFormation, we combine strings by wrapping them in FnConcat - */ +export const CLOUDFORMATION_ENGINE = { + engineName: 'CloudFormation', combineStringFragments(fragments: StringFragment[]) { return new FnConcat(...fragments.map(f => f.value)); } }; -ProvisioningEngine.register(CLOUDFORMATION_ENGINE, cloudFormationEngine); \ No newline at end of file +/** + * Base class for CloudFormation built-ins + */ +export class CloudFormationIntrinsicToken extends IntrinsicToken { + protected readonly engine: IProvisioningEngine = CLOUDFORMATION_ENGINE; +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/fn.ts b/packages/@aws-cdk/cdk/lib/cloudformation/fn.ts index 68000e24fe904..e5d820f43b43d 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/fn.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/fn.ts @@ -1,4 +1,4 @@ -import { CloudFormationIntrinsicToken } from './intrinsics'; +import { CloudFormationIntrinsicToken } from './engine'; // tslint:disable:max-line-length /** diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts b/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts index cf6692377f3a5..a82dd9862a435 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts @@ -1,4 +1,4 @@ -import { CloudFormationIntrinsicToken } from './intrinsics'; +import { CloudFormationIntrinsicToken } from './engine'; export class PseudoParameter extends CloudFormationIntrinsicToken { constructor(name: string) { diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts b/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts index fb97ce8266f0a..3444ede31d18e 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts @@ -2,7 +2,7 @@ import { Construct } from '../core/construct'; import { Token } from '../core/tokens'; import { capitalizePropertyNames, ignoreEmpty } from '../core/util'; import { Condition } from './condition'; -import { CloudFormationIntrinsicToken } from './intrinsics'; +import { CloudFormationIntrinsicToken } from './engine'; import { CreationPolicy, DeletionPolicy, UpdatePolicy } from './resource-policy'; import { IDependable, Referenceable, StackElement } from './stack'; diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts b/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts index 0dd71d5b5968b..89e042156b07e 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts @@ -3,7 +3,7 @@ import { App } from '../app'; import { Construct, PATH_SEP } from '../core/construct'; import { resolve, Token } from '../core/tokens'; import { Environment } from '../environment'; -import { CloudFormationIntrinsicToken } from './intrinsics'; +import { CloudFormationIntrinsicToken } from './engine'; import { HashedAddressingScheme, IAddressingScheme, LogicalIDs } from './logical-id'; import { Resource } from './resource'; diff --git a/packages/@aws-cdk/cdk/lib/core/engine-intrinsics.ts b/packages/@aws-cdk/cdk/lib/core/engine-intrinsics.ts deleted file mode 100644 index 268d6e30893d0..0000000000000 --- a/packages/@aws-cdk/cdk/lib/core/engine-intrinsics.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Mark a given object as a provisioning engine-intrinsic value. - * - * Any value that has been marked as intrinsic to a provisioning engine - * will escape all further type checks and attempts to manipulate, and be - * passed on as-is to the final provisioning engine. - * - * Note that this is separate from a Token: a Token represents a lazy value. - * The result of evaluating a Token is a value, which may or may not be an - * engine intrinsic value. If you want to combine the two, see `IntrinsicToken`. - */ -export function markAsIntrinsic(x: any, engine: string): any { - // This could have been a wrapper class, but that breaks all test.deepEqual()s. - // So instead, it's implemented as a hidden property on the object (which is - // hidden from JSON.stringify() and test.deepEqual(). - - Object.defineProperty(x, INTRINSIC_VALUE_PROPERTY, { - value: engine, - enumerable: false, - writable: false, - }); - return x; -} - -/** - * Return whether the given value is an intrinsic - */ -export function isIntrinsic(x: any): boolean { - return x[INTRINSIC_VALUE_PROPERTY] !== undefined; -} - -/** - * Return the intrinsic engine for the given intrinsic value - */ -export function intrinsicEngine(x: any): string | undefined { - return x[INTRINSIC_VALUE_PROPERTY]; -} - -/** - * The hidden marker property that marks an object as an engine-intrinsic value. - */ -const INTRINSIC_VALUE_PROPERTY = '__intrinsicValue__'; \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/core/engine-strings.ts b/packages/@aws-cdk/cdk/lib/core/engine-strings.ts deleted file mode 100644 index 198b907f661c6..0000000000000 --- a/packages/@aws-cdk/cdk/lib/core/engine-strings.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { resolve } from "./tokens"; - -/** - * Interface point for provisioning engines to register themselves - */ -export class ProvisioningEngine { - /** - * Register a handler for all intrinsics for the given engine - */ - public static register(engineName: string, handler: IProvisioningEngine) { - HANDLERS[engineName] = handler; - } - - /** - * Combine resolved fragments using the appropriate engine. - * - * Resolves the result. - */ - public static combineStringFragments(fragments: StringFragment[]): any { - simplifyFragments(fragments); - - if (fragments.length === 0) { return ''; } - if (fragments.length === 1) { return fragments[0].value; } - - const engines = Array.from(new Set(fragments.filter(f => f.intrinsicEngine !== undefined).map(f => f.intrinsicEngine!))); - if (engines.length === 0) { - throw new Error('Should not happen; no intrinsics found in StringFragment list.'); - } - if (engines.length > 1) { - throw new Error(`Combining different engines in one string fragment: ${engines.join(', ')}`); - } - - const engine = engines[0]; - if (!(engine in HANDLERS)) { - throw new Error(`No Token handler registered for engine: ${engine}`); - } - - return resolve(HANDLERS[engine].combineStringFragments(fragments)); - } -} - -/** - * A (resolved) fragment of a string to be combined. - * - * The values may be string literals or intrinsics. - */ -export interface StringFragment { - /** - * Source of the fragment - */ - source: FragmentSource; - - /** - * String value - * - * Either a string literal or an intrinsic. - */ - value: any; - - /** - * The intrinsic engine - */ - intrinsicEngine?: string; -} - -/** - * Where the resolved fragment came from (a string literal or a Token) - */ -export enum FragmentSource { - Literal = 'Literal', - Intrinsic = 'Intrinsic' -} - -/** - * Interface for engine-specific Token marker handlers - */ -export interface IProvisioningEngine { - /** - * Return the language intrinsic that will combine the strings in the given engine - */ - combineStringFragments(fragments: StringFragment[]): any; -} - -/** - * Global handler map - */ -const HANDLERS: {[engine: string]: IProvisioningEngine} = {}; - -/** - * Combine adjacent string literals in the fragment list - * - * List is modified in-place. - */ -function simplifyFragments(fragments: StringFragment[]) { - let i = 0; - while (i < fragments.length - 1) { - if (fragments[i].source === FragmentSource.Literal && fragments[i + 1].source === FragmentSource.Literal) { - fragments[i].value += fragments[i + 1].value; - fragments.splice(i + 1, 1); - } else { - i++; - } - } -} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/core/engine.ts b/packages/@aws-cdk/cdk/lib/core/engine.ts new file mode 100644 index 0000000000000..22f0fb1811381 --- /dev/null +++ b/packages/@aws-cdk/cdk/lib/core/engine.ts @@ -0,0 +1,138 @@ +import { resolve } from "./tokens"; + +/** + * Interface that provisioning engines implement + */ +export interface IProvisioningEngine { + /** + * The name of the engine. + * + * Must be unique per engine. + */ + engineName: string; + + /** + * Return the language intrinsic that will combine the strings in the given engine + */ + combineStringFragments(fragments: StringFragment[]): any; +} + +/** + * A (resolved) fragment of a string to be combined. + * + * The values may be string literals or intrinsics. + */ +export interface StringFragment { + /** + * Source of the fragment + */ + source: FragmentSource; + + /** + * String value + * + * Either a string literal or an intrinsic. + */ + value: any; +} + +/** + * Where the resolved fragment came from (a string literal or a Token) + */ +export enum FragmentSource { + Literal = 'Literal', + Intrinsic = 'Intrinsic' +} + +/** + * Mark a given object as a provisioning engine-intrinsic value. + * + * Pass a reference to a singleton object that implements the engine's + * functionality. + * + * Any value that has been marked as intrinsic to a provisioning engine + * will escape all further type checks and attempts to manipulate, and be + * passed on as-is to the final provisioning engine. + * + * Note that this is separate from a Token: a Token represents a lazy value. + * The result of evaluating a Token is a value, which may or may not be an + * engine intrinsic value. If you want to combine the two, see `IntrinsicToken`. + */ +export function markAsIntrinsic(x: any, engine: IProvisioningEngine): any { + // This could have been a wrapper class, but that breaks all test.deepEqual()s. + // So instead, it's implemented as a hidden property on the object (which is + // hidden from JSON.stringify() and test.deepEqual(). + + Object.defineProperty(x, INTRINSIC_VALUE_PROPERTY, { + value: engine, + enumerable: false, + writable: false, + }); + return x; +} + +/** + * Return whether the given value is an intrinsic + */ +export function isIntrinsic(x: any): boolean { + return x[INTRINSIC_VALUE_PROPERTY] !== undefined; +} + +/** + * Return the intrinsic engine for the given intrinsic value + */ +export function intrinsicEngine(x: any): IProvisioningEngine | undefined { + return x[INTRINSIC_VALUE_PROPERTY]; +} + +/** + * The hidden marker property that marks an object as an engine-intrinsic value. + */ +const INTRINSIC_VALUE_PROPERTY = '__intrinsicValue__'; + +/** + * Combine resolved fragments using the appropriate engine. + * + * Resolves the result. + */ +export function combineStringFragments(fragments: StringFragment[]): any { + simplifyFragments(fragments); + + if (fragments.length === 0) { return ''; } + if (fragments.length === 1) { return fragments[0].value; } + + const engines: IProvisioningEngine[] = fragments.map(f => intrinsicEngine(f.value)).filter(x => x !== undefined) as any; + + // Two reasons to look at engine names here instead of object identity: + // 1) So we can display a better error message + // 2) If the library gets loaded multiple times, the same engine will be instantiated + // multiple times and so the objects will compare as different, even though they all + // do the same, and any one of them would be fine. + const engineNames = Array.from(new Set(engines.map(e => e.engineName))); + + if (engines.length === 0) { + throw new Error('Should not happen; no intrinsics found in StringFragment list.'); + } + if (engineNames.length > 1) { + throw new Error(`Combining different engines in one string fragment: ${engineNames.join(', ')}`); + } + + return resolve(engines[0].combineStringFragments(fragments)); +} + +/** + * Combine adjacent string literals in the fragment list + * + * List is modified in-place. + */ +function simplifyFragments(fragments: StringFragment[]) { + let i = 0; + while (i < fragments.length - 1) { + if (fragments[i].source === FragmentSource.Literal && fragments[i + 1].source === FragmentSource.Literal) { + fragments[i].value += fragments[i + 1].value; + fragments.splice(i + 1, 1); + } else { + i++; + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/core/token-json.ts b/packages/@aws-cdk/cdk/lib/core/token-json.ts index f9fc80037af4a..80766e6e777c1 100644 --- a/packages/@aws-cdk/cdk/lib/core/token-json.ts +++ b/packages/@aws-cdk/cdk/lib/core/token-json.ts @@ -1,5 +1,4 @@ -import { isIntrinsic } from "./engine-intrinsics"; -import { ProvisioningEngine } from "./engine-strings"; +import { combineStringFragments, isIntrinsic } from "./engine"; import { resolve, Token } from "./tokens"; import { resolveMarkerSpans, splitOnMarkers } from "./util"; @@ -24,7 +23,7 @@ export class TokenJSON { const fragments = resolveMarkerSpans(spans, (id) => { return allocatedIntrinsics[id].quotedIntrinsic(); }); - return ProvisioningEngine.combineStringFragments(fragments); + return combineStringFragments(fragments); }); /** diff --git a/packages/@aws-cdk/cdk/lib/core/tokens.ts b/packages/@aws-cdk/cdk/lib/core/tokens.ts index 9575efd6f87f0..a5e0aebd72176 100644 --- a/packages/@aws-cdk/cdk/lib/core/tokens.ts +++ b/packages/@aws-cdk/cdk/lib/core/tokens.ts @@ -1,6 +1,5 @@ import { Construct } from "./construct"; -import { isIntrinsic, markAsIntrinsic } from "./engine-intrinsics"; -import { ProvisioningEngine } from "./engine-strings"; +import { combineStringFragments, IProvisioningEngine, isIntrinsic, markAsIntrinsic } from "./engine"; import { resolveMarkerSpans, splitOnMarkers } from './util'; /** @@ -89,7 +88,7 @@ export class Token { * */ export abstract class IntrinsicToken extends Token { - protected abstract readonly engine: string; + protected abstract readonly engine: IProvisioningEngine; public resolve(): any { // Get the inner value, and deep-resolve it to resolve further Tokens. @@ -271,7 +270,7 @@ class TokenStringMap { return isIntrinsic(resolved) ? resolved : `${resolved}`; }); - return ProvisioningEngine.combineStringFragments(fragments); + return combineStringFragments(fragments); } /** @@ -293,4 +292,4 @@ const VALID_KEY_CHARS = 'a-zA-Z0-9:._-'; /** * Singleton instance of the token string map */ -const TOKEN_STRING_MAP = new TokenStringMap(); \ No newline at end of file +const TOKEN_STRING_MAP = new TokenStringMap(); diff --git a/packages/@aws-cdk/cdk/lib/core/util.ts b/packages/@aws-cdk/cdk/lib/core/util.ts index f8e52bf939798..454e5bf25a1a8 100644 --- a/packages/@aws-cdk/cdk/lib/core/util.ts +++ b/packages/@aws-cdk/cdk/lib/core/util.ts @@ -1,5 +1,4 @@ -import { intrinsicEngine, isIntrinsic } from './engine-intrinsics'; -import { FragmentSource, StringFragment } from './engine-strings'; +import { FragmentSource, intrinsicEngine, isIntrinsic, StringFragment } from './engine'; import { resolve } from './tokens'; /** diff --git a/packages/@aws-cdk/cdk/lib/index.ts b/packages/@aws-cdk/cdk/lib/index.ts index 4f708b99b0ea4..7f830bcb4ba58 100644 --- a/packages/@aws-cdk/cdk/lib/index.ts +++ b/packages/@aws-cdk/cdk/lib/index.ts @@ -1,5 +1,5 @@ export * from './core/construct'; -export * from './core/engine-intrinsics'; +export * from './core/engine'; export * from './core/token-json'; export * from './core/tokens'; export * from './core/jsx'; @@ -7,7 +7,7 @@ export * from './core/jsx'; export * from './cloudformation/condition'; export * from './cloudformation/fn'; export * from './cloudformation/include'; -export * from './cloudformation/intrinsics'; +export * from './cloudformation/engine'; export * from './cloudformation/logical-id'; export * from './cloudformation/mapping'; export * from './cloudformation/output'; From a3ec610010317fc1d030b2545f385296a349c000 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Tue, 14 Aug 2018 15:33:29 +0200 Subject: [PATCH 14/17] Move JSONification to CloudFormation library, remove any reference to engines --- .../@aws-cdk/aws-cloudwatch/lib/dashboard.ts | 4 +- .../lib/cloudformation/cloudformation-json.ts | 83 +++++++ .../cloudformation/cloudformation-token.ts | 38 ++++ .../@aws-cdk/cdk/lib/cloudformation/engine.ts | 21 -- .../@aws-cdk/cdk/lib/cloudformation/fn.ts | 4 +- .../@aws-cdk/cdk/lib/cloudformation/pseudo.ts | 4 +- .../cdk/lib/cloudformation/resource.ts | 4 +- .../@aws-cdk/cdk/lib/cloudformation/stack.ts | 4 +- packages/@aws-cdk/cdk/lib/core/engine.ts | 138 ------------ packages/@aws-cdk/cdk/lib/core/token-json.ts | 113 ---------- packages/@aws-cdk/cdk/lib/core/tokens.ts | 203 ++++++++++++++---- packages/@aws-cdk/cdk/lib/core/util.ts | 67 ------ packages/@aws-cdk/cdk/lib/index.ts | 5 +- .../{core => cloudformation}/evaluate-cfn.ts | 0 .../test.cloudformation-json.ts} | 30 +-- .../@aws-cdk/cdk/test/core/test.tokens.ts | 6 +- 16 files changed, 308 insertions(+), 416 deletions(-) create mode 100644 packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-json.ts create mode 100644 packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-token.ts delete mode 100644 packages/@aws-cdk/cdk/lib/cloudformation/engine.ts delete mode 100644 packages/@aws-cdk/cdk/lib/core/engine.ts delete mode 100644 packages/@aws-cdk/cdk/lib/core/token-json.ts rename packages/@aws-cdk/cdk/test/{core => cloudformation}/evaluate-cfn.ts (100%) rename packages/@aws-cdk/cdk/test/{core/test.token-json.ts => cloudformation/test.cloudformation-json.ts} (78%) diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts b/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts index 4fcd4a7750cac..5eebc6432e217 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts @@ -1,4 +1,4 @@ -import { Construct, Stack, Token, TokenJSON } from "@aws-cdk/cdk"; +import { CloudFormationJSON, Construct, Stack, Token } from "@aws-cdk/cdk"; import { cloudformation } from './cloudwatch.generated'; import { Column, Row } from "./layout"; import { IWidget } from "./widget"; @@ -33,7 +33,7 @@ export class Dashboard extends Construct { dashboardBody: new Token(() => { const column = new Column(...this.rows); column.position(0, 0); - return TokenJSON.stringify({ widgets: column.toJson() }); + return CloudFormationJSON.stringify({ widgets: column.toJson() }); }) }); } diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-json.ts b/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-json.ts new file mode 100644 index 0000000000000..49b51bbbdb142 --- /dev/null +++ b/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-json.ts @@ -0,0 +1,83 @@ +import { resolve, Token } from "../core/tokens"; +import { CloudFormationToken, isIntrinsic } from "./cloudformation-token"; + +/** + * Class for JSON routines that are framework-aware + */ +export class CloudFormationJSON { + /** + * Turn an arbitrary structure potentially containing Tokens into JSON. + */ + public static stringify(obj: any): any { + return new Token(() => { + // Resolve inner value so that if they evaluate to literals, we maintain the type. + // + // Then replace intrinsics with a special sublcass of Token that overrides toJSON() to + // and deep-escapes the intrinsic, so if we resolve() the strings again it evaluates + // to the right string. + const resolved = resolve(obj); + + // We can just directly return this value, since resolve() will be called + // on our return value anyway. + return JSON.stringify(deepReplaceIntrinsics(resolved)); + }); + + /** + * Recurse into a structure, replace all intrinsics with + */ + function deepReplaceIntrinsics(x: any): any { + if (isIntrinsic(x)) { + return wrapIntrinsic(x); + } + + if (Array.isArray(x)) { + return x.map(deepReplaceIntrinsics); + } + + if (typeof x === 'object') { + for (const key of Object.keys(x)) { + x[key] = deepReplaceIntrinsics(x[key]); + } + } + + return x; + } + + function wrapIntrinsic(intrinsic: any): IntrinsicToken { + return new IntrinsicToken(() => deepQuoteStringsForJSON(intrinsic)); + } + } +} + +class IntrinsicToken extends CloudFormationToken { + /** + * Special handler that gets called when JSON.stringify() is used. + */ + public toJSON() { + return this.toString(); + } +} + +/** + * Deep escape strings for use in a JSON context + */ +function deepQuoteStringsForJSON(x: any): any { + if (typeof x === 'string') { + // Whenever we escape a string we strip off the outermost quotes + // since we're already in a quoted context. + const stringified = JSON.stringify(x); + return stringified.substring(1, stringified.length - 1); + } + + if (Array.isArray(x)) { + return x.map(deepQuoteStringsForJSON); + } + + if (typeof x === 'object') { + for (const key of Object.keys(x)) { + x[key] = deepQuoteStringsForJSON(x[key]); + } + } + + return x; +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-token.ts b/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-token.ts new file mode 100644 index 0000000000000..e3fac12815d2d --- /dev/null +++ b/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-token.ts @@ -0,0 +1,38 @@ +import { Token } from "../core/tokens"; + +/** + * Base class for CloudFormation built-ins + */ +export class CloudFormationToken extends Token { + constructor(valueOrFunction: any, stringRepresentationHint?: string) { + super(valueOrFunction, { + joiner: CLOUDFORMATION_JOINER, + stringRepresentationHint + }); + } +} + +import { FnConcat } from "./fn"; + +/** + * The default intrinsics Token engine for CloudFormation + */ +export const CLOUDFORMATION_JOINER = { + joinerName: 'CloudFormation', + + joinStringFragments(fragments: any[]): any { + return new FnConcat(...fragments.map(x => isIntrinsic(x) ? x : `${x}`)); + } +}; + +/** + * Return whether the given value represents a CloudFormation intrinsic + */ +export function isIntrinsic(x: any) { + if (Array.isArray(x) || x === null || typeof x !== 'object') { return false; } + + const keys = Object.keys(x); + if (keys.length !== 1) { return false; } + + return keys[0] === 'Ref' || keys[0].startsWith('Fn::'); +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/engine.ts b/packages/@aws-cdk/cdk/lib/cloudformation/engine.ts deleted file mode 100644 index b44fc5492b47b..0000000000000 --- a/packages/@aws-cdk/cdk/lib/cloudformation/engine.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { IProvisioningEngine, StringFragment } from "../core/engine"; -import { IntrinsicToken } from "../core/tokens"; - -import { FnConcat } from "./fn"; - -/** - * The default intrinsics Token engine for CloudFormation - */ -export const CLOUDFORMATION_ENGINE = { - engineName: 'CloudFormation', - combineStringFragments(fragments: StringFragment[]) { - return new FnConcat(...fragments.map(f => f.value)); - } -}; - -/** - * Base class for CloudFormation built-ins - */ -export class CloudFormationIntrinsicToken extends IntrinsicToken { - protected readonly engine: IProvisioningEngine = CLOUDFORMATION_ENGINE; -} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/fn.ts b/packages/@aws-cdk/cdk/lib/cloudformation/fn.ts index e5d820f43b43d..2254997854ade 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/fn.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/fn.ts @@ -1,11 +1,11 @@ -import { CloudFormationIntrinsicToken } from './engine'; +import { CloudFormationToken } from './cloudformation-token'; // tslint:disable:max-line-length /** * CloudFormation intrinsic functions. * http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html */ -export class Fn extends CloudFormationIntrinsicToken { +export class Fn extends CloudFormationToken { constructor(name: string, value: any) { super(() => ({ [name]: value })); } diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts b/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts index a82dd9862a435..d52d62840667d 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts @@ -1,6 +1,6 @@ -import { CloudFormationIntrinsicToken } from './engine'; +import { CloudFormationToken } from './cloudformation-token'; -export class PseudoParameter extends CloudFormationIntrinsicToken { +export class PseudoParameter extends CloudFormationToken { constructor(name: string) { super(() => ({ Ref: name }), name); } diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts b/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts index 3444ede31d18e..1ba916ae6df11 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts @@ -1,8 +1,8 @@ import { Construct } from '../core/construct'; import { Token } from '../core/tokens'; import { capitalizePropertyNames, ignoreEmpty } from '../core/util'; +import { CloudFormationToken } from './cloudformation-token'; import { Condition } from './condition'; -import { CloudFormationIntrinsicToken } from './engine'; import { CreationPolicy, DeletionPolicy, UpdatePolicy } from './resource-policy'; import { IDependable, Referenceable, StackElement } from './stack'; @@ -83,7 +83,7 @@ export class Resource extends Referenceable { * @param attributeName The name of the attribute. */ public getAtt(attributeName: string): Token { - return new CloudFormationIntrinsicToken(() => ({ 'Fn::GetAtt': [this.logicalId, attributeName] }), `${this.logicalId}.${attributeName}`); + return new CloudFormationToken(() => ({ 'Fn::GetAtt': [this.logicalId, attributeName] }), `${this.logicalId}.${attributeName}`); } /** diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts b/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts index 89e042156b07e..02d84b159ec68 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts @@ -3,7 +3,7 @@ import { App } from '../app'; import { Construct, PATH_SEP } from '../core/construct'; import { resolve, Token } from '../core/tokens'; import { Environment } from '../environment'; -import { CloudFormationIntrinsicToken } from './engine'; +import { CloudFormationToken } from './cloudformation-token'; import { HashedAddressingScheme, IAddressingScheme, LogicalIDs } from './logical-id'; import { Resource } from './resource'; @@ -393,7 +393,7 @@ export abstract class Referenceable extends StackElement { * Returns a token to a CloudFormation { Ref } that references this entity based on it's logical ID. */ public get ref(): Token { - return new CloudFormationIntrinsicToken(() => ({ Ref: this.logicalId }), `${this.logicalId}.Ref`); + return new CloudFormationToken(() => ({ Ref: this.logicalId }), `${this.logicalId}.Ref`); } } diff --git a/packages/@aws-cdk/cdk/lib/core/engine.ts b/packages/@aws-cdk/cdk/lib/core/engine.ts deleted file mode 100644 index 22f0fb1811381..0000000000000 --- a/packages/@aws-cdk/cdk/lib/core/engine.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { resolve } from "./tokens"; - -/** - * Interface that provisioning engines implement - */ -export interface IProvisioningEngine { - /** - * The name of the engine. - * - * Must be unique per engine. - */ - engineName: string; - - /** - * Return the language intrinsic that will combine the strings in the given engine - */ - combineStringFragments(fragments: StringFragment[]): any; -} - -/** - * A (resolved) fragment of a string to be combined. - * - * The values may be string literals or intrinsics. - */ -export interface StringFragment { - /** - * Source of the fragment - */ - source: FragmentSource; - - /** - * String value - * - * Either a string literal or an intrinsic. - */ - value: any; -} - -/** - * Where the resolved fragment came from (a string literal or a Token) - */ -export enum FragmentSource { - Literal = 'Literal', - Intrinsic = 'Intrinsic' -} - -/** - * Mark a given object as a provisioning engine-intrinsic value. - * - * Pass a reference to a singleton object that implements the engine's - * functionality. - * - * Any value that has been marked as intrinsic to a provisioning engine - * will escape all further type checks and attempts to manipulate, and be - * passed on as-is to the final provisioning engine. - * - * Note that this is separate from a Token: a Token represents a lazy value. - * The result of evaluating a Token is a value, which may or may not be an - * engine intrinsic value. If you want to combine the two, see `IntrinsicToken`. - */ -export function markAsIntrinsic(x: any, engine: IProvisioningEngine): any { - // This could have been a wrapper class, but that breaks all test.deepEqual()s. - // So instead, it's implemented as a hidden property on the object (which is - // hidden from JSON.stringify() and test.deepEqual(). - - Object.defineProperty(x, INTRINSIC_VALUE_PROPERTY, { - value: engine, - enumerable: false, - writable: false, - }); - return x; -} - -/** - * Return whether the given value is an intrinsic - */ -export function isIntrinsic(x: any): boolean { - return x[INTRINSIC_VALUE_PROPERTY] !== undefined; -} - -/** - * Return the intrinsic engine for the given intrinsic value - */ -export function intrinsicEngine(x: any): IProvisioningEngine | undefined { - return x[INTRINSIC_VALUE_PROPERTY]; -} - -/** - * The hidden marker property that marks an object as an engine-intrinsic value. - */ -const INTRINSIC_VALUE_PROPERTY = '__intrinsicValue__'; - -/** - * Combine resolved fragments using the appropriate engine. - * - * Resolves the result. - */ -export function combineStringFragments(fragments: StringFragment[]): any { - simplifyFragments(fragments); - - if (fragments.length === 0) { return ''; } - if (fragments.length === 1) { return fragments[0].value; } - - const engines: IProvisioningEngine[] = fragments.map(f => intrinsicEngine(f.value)).filter(x => x !== undefined) as any; - - // Two reasons to look at engine names here instead of object identity: - // 1) So we can display a better error message - // 2) If the library gets loaded multiple times, the same engine will be instantiated - // multiple times and so the objects will compare as different, even though they all - // do the same, and any one of them would be fine. - const engineNames = Array.from(new Set(engines.map(e => e.engineName))); - - if (engines.length === 0) { - throw new Error('Should not happen; no intrinsics found in StringFragment list.'); - } - if (engineNames.length > 1) { - throw new Error(`Combining different engines in one string fragment: ${engineNames.join(', ')}`); - } - - return resolve(engines[0].combineStringFragments(fragments)); -} - -/** - * Combine adjacent string literals in the fragment list - * - * List is modified in-place. - */ -function simplifyFragments(fragments: StringFragment[]) { - let i = 0; - while (i < fragments.length - 1) { - if (fragments[i].source === FragmentSource.Literal && fragments[i + 1].source === FragmentSource.Literal) { - fragments[i].value += fragments[i + 1].value; - fragments.splice(i + 1, 1); - } else { - i++; - } - } -} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/core/token-json.ts b/packages/@aws-cdk/cdk/lib/core/token-json.ts deleted file mode 100644 index 80766e6e777c1..0000000000000 --- a/packages/@aws-cdk/cdk/lib/core/token-json.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { combineStringFragments, isIntrinsic } from "./engine"; -import { resolve, Token } from "./tokens"; -import { resolveMarkerSpans, splitOnMarkers } from "./util"; - -/** - * Class for JSON routines that are framework-aware - */ -export class TokenJSON { - /** - * Turn an arbitrary structure potentially containing Tokens into JSON. - */ - public static stringify(obj: any): any { - let counter: number = 0; - const allocatedIntrinsics: {[key: string]: JSONIntrinsic} = {}; - - return new Token(() => { - // General strategy: resolve the inner value, replace remaining intrinsics with objects - // that override toJSON() to return a special marker string, then split on the - // marker strings and restore the original intrinsics. - const resolved = resolve(obj); - const stringified = JSON.stringify(deepReplaceIntrinsics(resolved)); - const spans = splitOnMarkers(stringified, BEGIN_MARKER, "[a-zA-Z0-9.]+", END_MARKER); - const fragments = resolveMarkerSpans(spans, (id) => { - return allocatedIntrinsics[id].quotedIntrinsic(); - }); - return combineStringFragments(fragments); - }); - - /** - * Recurse into a structure, replace all intrinsics with - */ - function deepReplaceIntrinsics(x: any): any { - if (isIntrinsic(x)) { - return allocateIntrinsic(x); - } - - if (Array.isArray(x)) { - return x.map(deepReplaceIntrinsics); - } - - if (typeof x === 'object') { - for (const key of Object.keys(x)) { - x[key] = deepReplaceIntrinsics(x[key]); - } - } - - return x; - } - - function allocateIntrinsic(intrinsic: any): JSONIntrinsic { - counter++; - const key = `INTRINSIC.${counter}`; - return allocatedIntrinsics[key] = new JSONIntrinsic(key, intrinsic); - } - } -} - -/** - * Class to hold intrinsic values, returning special marker values in calling toJSON() - */ -class JSONIntrinsic { - constructor(private readonly marker: string, private readonly intrinsic: any) { - } - - /** - * Return the intrinsic value, quoted for use within JSON context - * - * Because we know we're in a JSON context, deep recurse into the strings - * inside the intrinsic, assume they're display strings, and escape them - * for use in a JSON context. - * - * This is not entirely correct without knowing more about the structure of - * the intrinsics themselves, but doesn't break as long as they don't - * contain functional strings that break when passed through escaping. - */ - public quotedIntrinsic(): any { - return deepQuoteStringsForJSON(this.intrinsic); - } - - /** - * Return a special serialized marker string for this value - */ - public toJSON(): any { - return `${BEGIN_MARKER}${this.marker}${END_MARKER}`; - } -} - -const BEGIN_MARKER = "#{Json["; -const END_MARKER = "]}"; - -/** - * Deep escape strings for use in a JSON context - */ -function deepQuoteStringsForJSON(x: any): any { - if (typeof x === 'string') { - // Whenever we escape a string we strip off the outermost quotes - // since we're already in a quoted context. - const stringified = JSON.stringify(x); - return stringified.substring(1, stringified.length - 1); - } - - if (Array.isArray(x)) { - return x.map(deepQuoteStringsForJSON); - } - - if (typeof x === 'object') { - for (const key of Object.keys(x)) { - x[key] = deepQuoteStringsForJSON(x[key]); - } - } - - return x; -} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/core/tokens.ts b/packages/@aws-cdk/cdk/lib/core/tokens.ts index a5e0aebd72176..4c8cfb97860f0 100644 --- a/packages/@aws-cdk/cdk/lib/core/tokens.ts +++ b/packages/@aws-cdk/cdk/lib/core/tokens.ts @@ -1,6 +1,4 @@ import { Construct } from "./construct"; -import { combineStringFragments, IProvisioningEngine, isIntrinsic, markAsIntrinsic } from "./engine"; -import { resolveMarkerSpans, splitOnMarkers } from './util'; /** * If objects has a function property by this name, they will be considered tokens, and this @@ -8,6 +6,30 @@ import { resolveMarkerSpans, splitOnMarkers } from './util'; */ export const RESOLVE_METHOD = 'resolve'; +/** + * Properties for Token customization + */ +export interface TokenProps { + /** + * A human-readable representation hint for this Token + * + * stringRepresentationHint is used in the placeholder string of stringified + * Tokens, so that if humans look at the string its purpose makes sense to + * them. Must contain only alphanumeric and simple separator characters + * (_.:-). + * + * @default No string representation + */ + stringRepresentationHint?: string; + + /** + * Function used to concatenate strings and Token results together + * + * @default No joining + */ + joiner?: ITokenJoiner; +} + /** * Represents a lazy-evaluated value. * @@ -15,7 +37,10 @@ export const RESOLVE_METHOD = 'resolve'; * that it requires some context or late-bound data. */ export class Token { + public readonly joiner?: ITokenJoiner; + private tokenKey?: string; + private readonly stringRepresentationHint?: string; /** * Creates a token that resolves to `value`. @@ -23,16 +48,12 @@ export class Token { * If value is a function, the function is evaluated upon resolution and * the value it returns will be used as the token's value. * - * stringRepresentation is used in the placeholder string of stringified - * Tokens, so that if humans look at the string its purpose makes sense to - * them. Must contain only alphanumeric and simple separator characters - * (_.:-). - * * @param valueOrFunction What this token will evaluate to, literal or function. - * @param stringRepresentation A human-readable string describing the token's value. * */ - constructor(private readonly valueOrFunction?: any, public readonly stringRepresentation?: string) { + constructor(private readonly valueOrFunction?: any, props: TokenProps = {}) { + this.stringRepresentationHint = props && props.stringRepresentationHint; + this.joiner = props && props.joiner; } /** @@ -67,7 +88,7 @@ export class Token { } if (this.tokenKey === undefined) { - this.tokenKey = TOKEN_STRING_MAP.register(this); + this.tokenKey = TOKEN_STRING_MAP.register(this, this.stringRepresentationHint); } return this.tokenKey; } @@ -83,22 +104,6 @@ export class Token { } } -/** - * Class that tags the Token's return value as an Intrinsic. - * - */ -export abstract class IntrinsicToken extends Token { - protected abstract readonly engine: IProvisioningEngine; - - public resolve(): any { - // Get the inner value, and deep-resolve it to resolve further Tokens. - // Necessary to do this now since an intrinsic will never be resolved - // any deeper. - const resolved = resolve(super.resolve()); - return markAsIntrinsic(resolved, this.engine); - } -} - /** * Returns true if obj is a token (i.e. has the resolve() method) * @param obj The object to test. @@ -162,14 +167,6 @@ export function resolve(obj: any, prefix?: string[]): any { return obj; } - // - // intrinsics - return intrinsic without further resolution - // - - if (isIntrinsic(obj)) { - return obj; - } - // // tokens - invoke 'resolve' and continue to resolve recursively // @@ -245,9 +242,9 @@ class TokenStringMap { * hint. This may be used to produce aesthetically pleasing and * recognizable token representations for humans. */ - public register(token: Token): string { + public register(token: Token, representationHint?: string): string { const counter = Object.keys(this.tokenMap).length; - const representation = token.stringRepresentation || `TOKEN`; + const representation = representationHint || `TOKEN`; const key = `${representation}.${counter}`; if (new RegExp(`[^${VALID_KEY_CHARS}]`).exec(key)) { @@ -262,21 +259,15 @@ class TokenStringMap { * Replace any Token markers in this string with their resolved values */ public resolveMarkers(s: string): any { - const unresolved = splitOnMarkers(s, BEGIN_TOKEN_MARKER, `[${VALID_KEY_CHARS}]+`, END_TOKEN_MARKER); - const fragments = resolveMarkerSpans(unresolved, (id) => { - const resolved = resolve(this.lookupToken(id)); - - // Convert to string unless intrinsic - return isIntrinsic(resolved) ? resolved : `${resolved}`; - }); - - return combineStringFragments(fragments); + const str = new TokenString(s, BEGIN_TOKEN_MARKER, `[${VALID_KEY_CHARS}]+`, END_TOKEN_MARKER); + const fragments = str.split(this.lookupToken.bind(this)); + return fragments.join(); } /** * Find a Token by key */ - private lookupToken(key: string): Token { + public lookupToken(key: string): Token { if (!(key in this.tokenMap)) { throw new Error(`Unrecognized token key: ${key}`); } @@ -293,3 +284,123 @@ const VALID_KEY_CHARS = 'a-zA-Z0-9:._-'; * Singleton instance of the token string map */ const TOKEN_STRING_MAP = new TokenStringMap(); + +/** + * Interface that provisioning engines implement + */ +export interface ITokenJoiner { + /** + * The name of the joiner. + * + * Must be unique per joiner, because it will be used. + */ + joinerName: string; + + /** + * Return the language intrinsic that will combine the strings in the given engine + */ + joinStringFragments(fragments: any[]): any; +} + +/** + * A string with markers in it that can be resolved to external values + */ +class TokenString { + constructor( + private readonly str: string, + private readonly beginMarker: string, + private readonly idPattern: string, + private readonly endMarker: string) { + } + + /** + * Split string on markers, substituting markers with Tokens + */ + public split(lookup: (id: string) => Token): TokenStringFragments { + const re = new RegExp(`${regexQuote(this.beginMarker)}(${this.idPattern})${regexQuote(this.endMarker)}`, 'g'); + const ret = new TokenStringFragments(); + + let rest = 0; + let m = re.exec(this.str); + while (m) { + if (m.index > rest) { + ret.addString(this.str.substring(rest, m.index)); + } + + ret.addToken(lookup(m[1])); + + rest = re.lastIndex; + m = re.exec(this.str); + } + + if (rest < this.str.length) { + ret.addString(this.str.substring(rest)); + } + + return ret; + } +} + +/** + * Result of the split of a string with Tokens + * + * Either a literal part of the string, or an unresolved Token. + */ +type Fragment = { type: 'string'; str: string } | { type: 'token'; token: Token }; + +/** + * Fragments of a string with markers + */ +class TokenStringFragments { + private readonly fragments = new Array(); + + public values(): any[] { + return this.fragments.map(f => f.type === 'token' ? resolve(f.token) : f.str); + } + + public addString(str: string) { + this.fragments.push({ type: 'string', str }); + } + + public addToken(token: Token) { + this.fragments.push({ type: 'token', token }); + } + + /** + * Combine resolved fragments using the appropriate engine. + * + * Resolves the result. + */ + public join(): any { + if (this.fragments.length === 0) { return ''; } + if (this.fragments.length === 1) { return this.values()[0]; } + + const joiners = this.fragments.map(f => f.type === 'token' ? f.token.joiner : undefined).filter(x => x !== undefined) as ITokenJoiner[]; + // Two reasons to look at joiner names here instead of object identity: + // 1) So we can display a better error message + // 2) If the library gets loaded multiple times, the same engine will be instantiated + // multiple times and so the objects will compare as different, even though they all + // do the same, and any one of them would be fine. + const joinerNames = Array.from(new Set(joiners.map(e => e.joinerName))); + + if (joiners.length === 0) { + // No joiners. This can happen if we only have non language-specific Tokens. Stay + // in literal-land, convert all to string and combine. + return this.values().map(x => `${x}`).join(''); + } + + if (joinerNames.length > 1) { + throw new Error(`Combining different joiners in one string fragment: ${joinerNames.join(', ')}`); + } + + // This might return another Token, so resolve again + return resolve(joiners[0].joinStringFragments(this.values())); + } +} + +/** + * Quote a string for use in a regex + */ +function regexQuote(s: string) { + return s.replace(/[.?*+^$[\]\\(){}|-]/g, "\\$&"); +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/core/util.ts b/packages/@aws-cdk/cdk/lib/core/util.ts index 454e5bf25a1a8..32e325c519f8a 100644 --- a/packages/@aws-cdk/cdk/lib/core/util.ts +++ b/packages/@aws-cdk/cdk/lib/core/util.ts @@ -1,4 +1,3 @@ -import { FragmentSource, intrinsicEngine, isIntrinsic, StringFragment } from './engine'; import { resolve } from './tokens'; /** @@ -49,69 +48,3 @@ export function ignoreEmpty(o: any): any { return o; } - -/** - * Result of the split of a string with Tokens - * - * Either a literal part of the string, or an unresolved Token. - */ -export type MarkerSpan = { type: 'string'; value: string } | { type: 'marker'; id: string }; - -/** - * Split a string up into String Spans and Marker Spans - */ -export function splitOnMarkers(s: string, beginMarker: string, idPattern: string, endMarker: string): MarkerSpan[] { - // tslint:disable-next-line:max-line-length - const re = new RegExp(`${regexQuote(beginMarker)}(${idPattern})${regexQuote(endMarker)}`, 'g'); - const ret = new Array(); - - let rest = 0; - let m = re.exec(s); - while (m) { - if (m.index > rest) { - ret.push({ type: 'string', value: s.substring(rest, m.index) }); - } - - ret.push({ type: 'marker', id: m[1] }); - - rest = re.lastIndex; - m = re.exec(s); - } - - if (rest < s.length) { - ret.push({ type: 'string', value: s.substring(rest) }); - } - - if (ret.length === 0) { - ret.push({ type: 'string', value: '' }); - } - - return ret; -} - -/** - * Resolves marker spans to string fragments - */ -export function resolveMarkerSpans(spans: MarkerSpan[], lookup: (id: string) => any): StringFragment[] { - return spans.map(span => { - - switch (span.type) { - case 'string': - return { source: FragmentSource.Literal, value: span.value }; - case 'marker': - const value = lookup(span.id); - if (isIntrinsic(value)) { - return { source: FragmentSource.Intrinsic, value, intrinsicEngine: intrinsicEngine(value) }; - } else { - return { source: FragmentSource.Literal, value: `${value}` }; - } - } - }); -} - -/** - * Quote a string for use in a regex - */ -function regexQuote(s: string) { - return s.replace(/[.?*+^$[\]\\(){}|-]/g, "\\$&"); -} diff --git a/packages/@aws-cdk/cdk/lib/index.ts b/packages/@aws-cdk/cdk/lib/index.ts index 7f830bcb4ba58..4a1167345295f 100644 --- a/packages/@aws-cdk/cdk/lib/index.ts +++ b/packages/@aws-cdk/cdk/lib/index.ts @@ -1,13 +1,12 @@ export * from './core/construct'; -export * from './core/engine'; -export * from './core/token-json'; export * from './core/tokens'; export * from './core/jsx'; +export * from './cloudformation/cloudformation-json'; +export * from './cloudformation/cloudformation-token'; export * from './cloudformation/condition'; export * from './cloudformation/fn'; export * from './cloudformation/include'; -export * from './cloudformation/engine'; export * from './cloudformation/logical-id'; export * from './cloudformation/mapping'; export * from './cloudformation/output'; diff --git a/packages/@aws-cdk/cdk/test/core/evaluate-cfn.ts b/packages/@aws-cdk/cdk/test/cloudformation/evaluate-cfn.ts similarity index 100% rename from packages/@aws-cdk/cdk/test/core/evaluate-cfn.ts rename to packages/@aws-cdk/cdk/test/cloudformation/evaluate-cfn.ts diff --git a/packages/@aws-cdk/cdk/test/core/test.token-json.ts b/packages/@aws-cdk/cdk/test/cloudformation/test.cloudformation-json.ts similarity index 78% rename from packages/@aws-cdk/cdk/test/core/test.token-json.ts rename to packages/@aws-cdk/cdk/test/cloudformation/test.cloudformation-json.ts index cd85673f1b13a..389dee29cca89 100644 --- a/packages/@aws-cdk/cdk/test/core/test.token-json.ts +++ b/packages/@aws-cdk/cdk/test/cloudformation/test.cloudformation-json.ts @@ -1,5 +1,5 @@ import { Test } from 'nodeunit'; -import { CloudFormationIntrinsicToken, FnConcat, resolve, Token, TokenJSON } from '../../lib'; +import { CloudFormationJSON, CloudFormationToken, FnConcat, resolve, Token } from '../../lib'; import { evaluateCFN } from './evaluate-cfn'; export = { @@ -21,7 +21,7 @@ export = { const fido = { name: 'Fido', speaks: token }; // WHEN - const resolved = resolve(TokenJSON.stringify(fido)); + const resolved = resolve(CloudFormationJSON.stringify(fido)); // THEN test.deepEqual(evaluateCFN(resolved), '{"name":"Fido","speaks":"woof woof"}'); @@ -36,7 +36,7 @@ export = { const fido = { name: 'Fido', speaks: `deep ${token}` }; // WHEN - const resolved = resolve(TokenJSON.stringify(fido)); + const resolved = resolve(CloudFormationJSON.stringify(fido)); // THEN test.deepEqual(evaluateCFN(resolved), '{"name":"Fido","speaks":"deep woof woof"}'); @@ -52,8 +52,8 @@ export = { // WHEN test.equal(evaluateCFN(resolve(embedded)), "the number is 1"); - test.equal(evaluateCFN(resolve(TokenJSON.stringify({ embedded }))), "{\"embedded\":\"the number is 1\"}"); - test.equal(evaluateCFN(resolve(TokenJSON.stringify({ num }))), "{\"num\":1}"); + test.equal(evaluateCFN(resolve(CloudFormationJSON.stringify({ embedded }))), "{\"embedded\":\"the number is 1\"}"); + test.equal(evaluateCFN(resolve(CloudFormationJSON.stringify({ num }))), "{\"num\":1}"); test.done(); }, @@ -62,7 +62,7 @@ export = { // GIVEN for (const token of tokensThatResolveTo('pong!')) { // WHEN - const stringified = TokenJSON.stringify(`ping? ${token}`); + const stringified = CloudFormationJSON.stringify(`ping? ${token}`); // THEN test.equal(evaluateCFN(resolve(stringified)), '"ping? pong!"'); @@ -73,10 +73,10 @@ export = { 'intrinsic Tokens embed correctly in JSONification'(test: Test) { // GIVEN - const bucketName = new CloudFormationIntrinsicToken({ Ref: 'MyBucket' }); + const bucketName = new CloudFormationToken({ Ref: 'MyBucket' }); // WHEN - const resolved = resolve(TokenJSON.stringify({ theBucket: bucketName })); + const resolved = resolve(CloudFormationJSON.stringify({ theBucket: bucketName })); // THEN const context = {MyBucket: 'TheName'}; @@ -90,7 +90,7 @@ export = { const token = new FnConcat('Hello', 'This\nIs', 'Very "cool"'); // WHEN - const resolved = resolve(TokenJSON.stringify({ + const resolved = resolve(CloudFormationJSON.stringify({ literal: 'I can also "contain" quotes', token })); @@ -104,11 +104,11 @@ export = { 'Tokens in Tokens are handled correctly'(test: Test) { // GIVEN - const bucketName = new CloudFormationIntrinsicToken({ Ref: 'MyBucket' }); + const bucketName = new CloudFormationToken({ Ref: 'MyBucket' }); const combinedName = new FnConcat('The bucket name is ', bucketName); // WHEN - const resolved = resolve(TokenJSON.stringify({ theBucket: combinedName })); + const resolved = resolve(CloudFormationJSON.stringify({ theBucket: combinedName })); // THEN const context = {MyBucket: 'TheName'}; @@ -122,7 +122,7 @@ export = { const fidoSays = new Token(() => 'woof'); // WHEN - const resolved = resolve(TokenJSON.stringify({ + const resolved = resolve(CloudFormationJSON.stringify({ information: `Did you know that Fido says: ${fidoSays}` })); @@ -134,10 +134,10 @@ export = { 'Doubly nested intrinsics evaluate correctly in JSON context'(test: Test) { // WHEN - const fidoSays = new CloudFormationIntrinsicToken(() => ({ Ref: 'Something' })); + const fidoSays = new CloudFormationToken(() => ({ Ref: 'Something' })); // WHEN - const resolved = resolve(TokenJSON.stringify({ + const resolved = resolve(CloudFormationJSON.stringify({ information: `Did you know that Fido says: ${fidoSays}` })); @@ -153,7 +153,7 @@ export = { const fidoSays = new Token(() => '"woof"'); // WHEN - const resolved = resolve(TokenJSON.stringify({ + const resolved = resolve(CloudFormationJSON.stringify({ information: `Did you know that Fido says: ${fidoSays}` })); diff --git a/packages/@aws-cdk/cdk/test/core/test.tokens.ts b/packages/@aws-cdk/cdk/test/core/test.tokens.ts index 86497645962c3..80512e49c9cb6 100644 --- a/packages/@aws-cdk/cdk/test/core/test.tokens.ts +++ b/packages/@aws-cdk/cdk/test/core/test.tokens.ts @@ -1,6 +1,6 @@ import { Test } from 'nodeunit'; -import { CloudFormationIntrinsicToken, isToken, resolve, Token } from '../../lib'; -import { evaluateCFN } from './evaluate-cfn'; +import { CloudFormationToken, isToken, resolve, Token } from '../../lib'; +import { evaluateCFN } from '../cloudformation/evaluate-cfn'; export = { 'resolve a plain old object should just return the object'(test: Test) { @@ -188,7 +188,7 @@ export = { 'intrinsic Tokens can be stringified and evaluate to conceptual value'(test: Test) { // GIVEN - const bucketName = new CloudFormationIntrinsicToken({ Ref: 'MyBucket' }); + const bucketName = new CloudFormationToken({ Ref: 'MyBucket' }); // WHEN const resolved = resolve(`my bucket is named ${bucketName}`); From 7361815b804e3a42d406858634ddc70089f4666b Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 15 Aug 2018 09:40:09 +0200 Subject: [PATCH 15/17] Explain better in comments, fix cross-account-destination --- .../aws-logs/lib/cross-account-destination.ts | 10 +++- .../lib/cloudformation/cloudformation-json.ts | 28 ++++++++--- .../cloudformation/cloudformation-token.ts | 8 ++-- packages/@aws-cdk/cdk/lib/core/tokens.ts | 48 +++++++++++-------- 4 files changed, 63 insertions(+), 31 deletions(-) diff --git a/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts b/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts index 7de523d836c8d..0e982c9e61cc3 100644 --- a/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts +++ b/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts @@ -64,7 +64,8 @@ export class CrossAccountDestination extends cdk.Construct implements ILogSubscr this.resource = new cloudformation.DestinationResource(this, 'Resource', { destinationName, - destinationPolicy: this.policyDocument, + // Must be stringified policy + destinationPolicy: new cdk.Token(() => this.stringifiedPolicyDocument()), roleArn: props.role.roleArn, targetArn: props.targetArn }); @@ -89,6 +90,13 @@ export class CrossAccountDestination extends cdk.Construct implements ILogSubscr const stack = cdk.Stack.find(this); return stack.name + '-' + this.resource.logicalId; } + + /** + * Return a stringified JSON version of the PolicyDocument + */ + private stringifiedPolicyDocument() { + return this.policyDocument.isEmpty ? '' : cdk.CloudFormationJSON.stringify(cdk.resolve(this.policyDocument)); + } } /** diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-json.ts b/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-json.ts index 49b51bbbdb142..b5d9b03d54e44 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-json.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-json.ts @@ -6,15 +6,26 @@ import { CloudFormationToken, isIntrinsic } from "./cloudformation-token"; */ export class CloudFormationJSON { /** - * Turn an arbitrary structure potentially containing Tokens into JSON. + * Turn an arbitrary structure potentially containing Tokens into a JSON string. + * + * Returns a Token which will evaluate to CloudFormation expression that + * will be evaluated by CloudFormation to the JSON representation of the + * input structure. + * + * All Tokens substituted in this way must return strings, or the evaluation + * in CloudFormation will fail. */ - public static stringify(obj: any): any { + public static stringify(obj: any): Token { return new Token(() => { - // Resolve inner value so that if they evaluate to literals, we maintain the type. + // Resolve inner value first so that if they evaluate to literals, we + // maintain the type (and discard 'undefined's). // - // Then replace intrinsics with a special sublcass of Token that overrides toJSON() to - // and deep-escapes the intrinsic, so if we resolve() the strings again it evaluates - // to the right string. + // Then replace intrinsics with a special subclass of Token that + // overrides toJSON() to the marker string, so if we resolve() the + // strings again it evaluates to the right string. It also + // deep-escapes any strings inside the intrinsic, so that if literal + // strings are used in {Fn::Join} or something, they will end up + // escaped in the final JSON output. const resolved = resolve(obj); // We can just directly return this value, since resolve() will be called @@ -23,7 +34,7 @@ export class CloudFormationJSON { }); /** - * Recurse into a structure, replace all intrinsics with + * Recurse into a structure, replace all intrinsics with IntrinsicTokens. */ function deepReplaceIntrinsics(x: any): any { if (isIntrinsic(x)) { @@ -49,6 +60,9 @@ export class CloudFormationJSON { } } +/** + * Token that also stringifies in the toJSON() operation. + */ class IntrinsicToken extends CloudFormationToken { /** * Special handler that gets called when JSON.stringify() is used. diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-token.ts b/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-token.ts index e3fac12815d2d..f669e4137e457 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-token.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-token.ts @@ -4,10 +4,10 @@ import { Token } from "../core/tokens"; * Base class for CloudFormation built-ins */ export class CloudFormationToken extends Token { - constructor(valueOrFunction: any, stringRepresentationHint?: string) { + constructor(valueOrFunction: any, displayName?: string) { super(valueOrFunction, { joiner: CLOUDFORMATION_JOINER, - stringRepresentationHint + displayName }); } } @@ -18,9 +18,9 @@ import { FnConcat } from "./fn"; * The default intrinsics Token engine for CloudFormation */ export const CLOUDFORMATION_JOINER = { - joinerName: 'CloudFormation', + id: 'CloudFormation', - joinStringFragments(fragments: any[]): any { + join(fragments: any[]): any { return new FnConcat(...fragments.map(x => isIntrinsic(x) ? x : `${x}`)); } }; diff --git a/packages/@aws-cdk/cdk/lib/core/tokens.ts b/packages/@aws-cdk/cdk/lib/core/tokens.ts index 4c8cfb97860f0..108f640ee798a 100644 --- a/packages/@aws-cdk/cdk/lib/core/tokens.ts +++ b/packages/@aws-cdk/cdk/lib/core/tokens.ts @@ -11,21 +11,30 @@ export const RESOLVE_METHOD = 'resolve'; */ export interface TokenProps { /** - * A human-readable representation hint for this Token + * A human-readable display hint for this Token * - * stringRepresentationHint is used in the placeholder string of stringified - * Tokens, so that if humans look at the string its purpose makes sense to - * them. Must contain only alphanumeric and simple separator characters - * (_.:-). + * This is used to represent the Token when it's embedded into a string; it + * will look something like this: * - * @default No string representation + * "embedded in a larger string is ${Token[DISPLAY_NAME.123]}" + * + * This value is used as a hint to humans what the meaning of the Token is, + * and does not have any effect on the evaluation. + * + * Must contain only alphanumeric and simple separator characters (_.:-). + * + * @default TOKEN */ - stringRepresentationHint?: string; + displayName?: string; /** - * Function used to concatenate strings and Token results together + * Function used to concatenate strings and Token results together. + * + * After resolve() is called to restore Token values that have been + * encoded into string literals, the fragments will be combined using + * this combinator. * - * @default No joining + * @default Literal string joining */ joiner?: ITokenJoiner; } @@ -40,7 +49,7 @@ export class Token { public readonly joiner?: ITokenJoiner; private tokenKey?: string; - private readonly stringRepresentationHint?: string; + private readonly displayName?: string; /** * Creates a token that resolves to `value`. @@ -52,7 +61,7 @@ export class Token { * */ constructor(private readonly valueOrFunction?: any, props: TokenProps = {}) { - this.stringRepresentationHint = props && props.stringRepresentationHint; + this.displayName = props && props.displayName; this.joiner = props && props.joiner; } @@ -88,7 +97,7 @@ export class Token { } if (this.tokenKey === undefined) { - this.tokenKey = TOKEN_STRING_MAP.register(this, this.stringRepresentationHint); + this.tokenKey = TOKEN_STRING_MAP.register(this, this.displayName); } return this.tokenKey; } @@ -100,7 +109,7 @@ export class Token { * it's not possible to do this properly, so we just throw an error here. */ public toJSON(): any { - throw new Error('JSON.stringify() cannot be applied to structure with a deferred Token in it. Use TokenJSON.stringify() instead.'); + throw new Error('JSON.stringify() cannot be applied to structure with a deferred Token in it. Use CloudFormationJSON.stringify() instead.'); } } @@ -286,20 +295,21 @@ const VALID_KEY_CHARS = 'a-zA-Z0-9:._-'; const TOKEN_STRING_MAP = new TokenStringMap(); /** - * Interface that provisioning engines implement + * Interface that Token joiners implement */ export interface ITokenJoiner { /** * The name of the joiner. * - * Must be unique per joiner, because it will be used. + * Must be unique per joiner: this value will be used to assert that there + * is exactly only type of joiner in a join operation. */ - joinerName: string; + id: string; /** * Return the language intrinsic that will combine the strings in the given engine */ - joinStringFragments(fragments: any[]): any; + join(fragments: any[]): any; } /** @@ -381,7 +391,7 @@ class TokenStringFragments { // 2) If the library gets loaded multiple times, the same engine will be instantiated // multiple times and so the objects will compare as different, even though they all // do the same, and any one of them would be fine. - const joinerNames = Array.from(new Set(joiners.map(e => e.joinerName))); + const joinerNames = Array.from(new Set(joiners.map(e => e.id))); if (joiners.length === 0) { // No joiners. This can happen if we only have non language-specific Tokens. Stay @@ -394,7 +404,7 @@ class TokenStringFragments { } // This might return another Token, so resolve again - return resolve(joiners[0].joinStringFragments(this.values())); + return resolve(joiners[0].join(this.values())); } } From a7b41e2ada82a39d1ce3599c9ec0425c5011b608 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 15 Aug 2018 11:54:09 +0200 Subject: [PATCH 16/17] Remove joiner(), make Tokens concat() themselves To avoid an undue number of {Fn::Join}s in the output, add an optimization into FnConcat() to inline nested FnConcats. --- ...g.pipeline-code-commit-build.expected.json | 38 ++---- .../test/integ.pipeline-events.expected.json | 38 ++---- .../test/integ.project-bucket.expected.json | 56 ++++----- .../test/integ.project-events.expected.json | 50 +++----- .../aws-codebuild/test/test.codebuild.ts | 66 ++--------- .../@aws-cdk/aws-events/test/test.rule.ts | 11 +- .../cloudformation/cloudformation-token.ts | 26 ++-- .../@aws-cdk/cdk/lib/cloudformation/fn.ts | 41 ++++++- packages/@aws-cdk/cdk/lib/core/tokens.ts | 112 ++++++++---------- .../cdk/test/cloudformation/test.perms.ts | 2 +- .../@aws-cdk/cdk/test/core/test.tokens.ts | 78 ++++++++++-- .../test/integ.rtv.lambda.expected.json | 35 +++--- 12 files changed, 263 insertions(+), 290 deletions(-) diff --git a/packages/@aws-cdk/aws-codebuild-codepipeline/test/integ.pipeline-code-commit-build.expected.json b/packages/@aws-cdk/aws-codebuild-codepipeline/test/integ.pipeline-code-commit-build.expected.json index 86435a6edcfc2..c2d5d6a2ceb96 100644 --- a/packages/@aws-cdk/aws-codebuild-codepipeline/test/integ.pipeline-code-commit-build.expected.json +++ b/packages/@aws-cdk/aws-codebuild-codepipeline/test/integ.pipeline-code-commit-build.expected.json @@ -240,16 +240,9 @@ ":", "log-group", ":", + "/aws/codebuild/", { - "Fn::Join": [ - "", - [ - "/aws/codebuild/", - { - "Ref": "MyBuildProject30DB9D6E" - } - ] - ] + "Ref": "MyBuildProject30DB9D6E" } ] ] @@ -280,16 +273,9 @@ ":", "log-group", ":", + "/aws/codebuild/", { - "Fn::Join": [ - "", - [ - "/aws/codebuild/", - { - "Ref": "MyBuildProject30DB9D6E" - } - ] - ] + "Ref": "MyBuildProject30DB9D6E" } ] ] @@ -348,23 +334,23 @@ "MyBuildProject30DB9D6E": { "Type": "AWS::CodeBuild::Project", "Properties": { - "Source": { - "Type": "CODEPIPELINE" - }, "Artifacts": { "Type": "CODEPIPELINE" }, + "Environment": { + "ComputeType": "BUILD_GENERAL1_SMALL", + "Image": "aws/codebuild/ubuntu-base:14.04", + "PrivilegedMode": false, + "Type": "LINUX_CONTAINER" + }, "ServiceRole": { "Fn::GetAtt": [ "MyBuildProjectRole6B7E2258", "Arn" ] }, - "Environment": { - "Type": "LINUX_CONTAINER", - "PrivilegedMode": false, - "Image": "aws/codebuild/ubuntu-base:14.04", - "ComputeType": "BUILD_GENERAL1_SMALL" + "Source": { + "Type": "CODEPIPELINE" } } } diff --git a/packages/@aws-cdk/aws-codebuild-codepipeline/test/integ.pipeline-events.expected.json b/packages/@aws-cdk/aws-codebuild-codepipeline/test/integ.pipeline-events.expected.json index bee622e2cab0a..13462c828f0a8 100644 --- a/packages/@aws-cdk/aws-codebuild-codepipeline/test/integ.pipeline-events.expected.json +++ b/packages/@aws-cdk/aws-codebuild-codepipeline/test/integ.pipeline-events.expected.json @@ -413,16 +413,9 @@ ":", "log-group", ":", + "/aws/codebuild/", { - "Fn::Join": [ - "", - [ - "/aws/codebuild/", - { - "Ref": "BuildProject097C5DB7" - } - ] - ] + "Ref": "BuildProject097C5DB7" } ] ] @@ -453,16 +446,9 @@ ":", "log-group", ":", + "/aws/codebuild/", { - "Fn::Join": [ - "", - [ - "/aws/codebuild/", - { - "Ref": "BuildProject097C5DB7" - } - ] - ] + "Ref": "BuildProject097C5DB7" } ] ] @@ -521,23 +507,23 @@ "BuildProject097C5DB7": { "Type": "AWS::CodeBuild::Project", "Properties": { - "Source": { - "Type": "CODEPIPELINE" - }, "Artifacts": { "Type": "CODEPIPELINE" }, + "Environment": { + "ComputeType": "BUILD_GENERAL1_SMALL", + "Image": "aws/codebuild/ubuntu-base:14.04", + "PrivilegedMode": false, + "Type": "LINUX_CONTAINER" + }, "ServiceRole": { "Fn::GetAtt": [ "BuildProjectRoleAA92C755", "Arn" ] }, - "Environment": { - "Type": "LINUX_CONTAINER", - "PrivilegedMode": false, - "Image": "aws/codebuild/ubuntu-base:14.04", - "ComputeType": "BUILD_GENERAL1_SMALL" + "Source": { + "Type": "CODEPIPELINE" } } }, diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.project-bucket.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.project-bucket.expected.json index b6dc07f204e9f..d2fdc48288a40 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.project-bucket.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.project-bucket.expected.json @@ -86,16 +86,9 @@ ":", "log-group", ":", + "/aws/codebuild/", { - "Fn::Join": [ - "", - [ - "/aws/codebuild/", - { - "Ref": "MyProject39F7B0AE" - } - ] - ] + "Ref": "MyProject39F7B0AE" } ] ] @@ -126,16 +119,9 @@ ":", "log-group", ":", + "/aws/codebuild/", { - "Fn::Join": [ - "", - [ - "/aws/codebuild/", - { - "Ref": "MyProject39F7B0AE" - } - ] - ] + "Ref": "MyProject39F7B0AE" } ] ] @@ -160,8 +146,22 @@ "MyProject39F7B0AE": { "Type": "AWS::CodeBuild::Project", "Properties": { + "Artifacts": { + "Type": "NO_ARTIFACTS" + }, + "Environment": { + "ComputeType": "BUILD_GENERAL1_LARGE", + "Image": "aws/codebuild/ubuntu-base:14.04", + "PrivilegedMode": false, + "Type": "LINUX_CONTAINER" + }, + "ServiceRole": { + "Fn::GetAtt": [ + "MyProjectRole9BBE5233", + "Arn" + ] + }, "Source": { - "Type": "S3", "Location": { "Fn::Join": [ "", @@ -173,22 +173,8 @@ "path/to/my/source.zip" ] ] - } - }, - "Artifacts": { - "Type": "NO_ARTIFACTS" - }, - "ServiceRole": { - "Fn::GetAtt": [ - "MyProjectRole9BBE5233", - "Arn" - ] - }, - "Environment": { - "Type": "LINUX_CONTAINER", - "PrivilegedMode": false, - "Image": "aws/codebuild/ubuntu-base:14.04", - "ComputeType": "BUILD_GENERAL1_LARGE" + }, + "Type": "S3" } } } diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.project-events.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.project-events.expected.json index d7f34cd26b3ae..2e08546fea552 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.project-events.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.project-events.expected.json @@ -129,16 +129,9 @@ ":", "log-group", ":", + "/aws/codebuild/", { - "Fn::Join": [ - "", - [ - "/aws/codebuild/", - { - "Ref": "MyProject39F7B0AE" - } - ] - ] + "Ref": "MyProject39F7B0AE" } ] ] @@ -169,16 +162,9 @@ ":", "log-group", ":", + "/aws/codebuild/", { - "Fn::Join": [ - "", - [ - "/aws/codebuild/", - { - "Ref": "MyProject39F7B0AE" - } - ] - ] + "Ref": "MyProject39F7B0AE" } ] ] @@ -203,29 +189,29 @@ "MyProject39F7B0AE": { "Type": "AWS::CodeBuild::Project", "Properties": { - "Source": { - "Type": "CODECOMMIT", - "Location": { - "Fn::GetAtt": [ - "MyRepoF4F48043", - "CloneUrlHttp" - ] - } - }, "Artifacts": { "Type": "NO_ARTIFACTS" }, + "Environment": { + "ComputeType": "BUILD_GENERAL1_SMALL", + "Image": "aws/codebuild/ubuntu-base:14.04", + "PrivilegedMode": false, + "Type": "LINUX_CONTAINER" + }, "ServiceRole": { "Fn::GetAtt": [ "MyProjectRole9BBE5233", "Arn" ] }, - "Environment": { - "Type": "LINUX_CONTAINER", - "PrivilegedMode": false, - "Image": "aws/codebuild/ubuntu-base:14.04", - "ComputeType": "BUILD_GENERAL1_SMALL" + "Source": { + "Location": { + "Fn::GetAtt": [ + "MyRepoF4F48043", + "CloneUrlHttp" + ] + }, + "Type": "CODECOMMIT" } } }, diff --git a/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts b/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts index 6a719cb7b9d8f..c96c6f7c1ee9d 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts @@ -71,16 +71,9 @@ export = { ":", "log-group", ":", + "/aws/codebuild/", { - "Fn::Join": [ - "", - [ - "/aws/codebuild/", - { - "Ref": "MyProject39F7B0AE" - } - ] - ] + "Ref": "MyProject39F7B0AE" } ] ] @@ -111,16 +104,9 @@ export = { ":", "log-group", ":", + "/aws/codebuild/", { - "Fn::Join": [ - "", - [ - "/aws/codebuild/", - { - "Ref": "MyProject39F7B0AE" - } - ] - ] + "Ref": "MyProject39F7B0AE" } ] ] @@ -252,16 +238,9 @@ export = { ":", "log-group", ":", + "/aws/codebuild/", { - "Fn::Join": [ - "", - [ - "/aws/codebuild/", - { - "Ref": "MyProject39F7B0AE" - } - ] - ] + "Ref": "MyProject39F7B0AE" } ] ] @@ -292,16 +271,9 @@ export = { ":", "log-group", ":", + "/aws/codebuild/", { - "Fn::Join": [ - "", - [ - "/aws/codebuild/", - { - "Ref": "MyProject39F7B0AE" - } - ] - ] + "Ref": "MyProject39F7B0AE" } ] ] @@ -455,16 +427,9 @@ export = { ":", "log-group", ":", + "/aws/codebuild/", { - "Fn::Join": [ - "", - [ - "/aws/codebuild/", - { - "Ref": "MyProject39F7B0AE" - } - ] - ] + "Ref": "MyProject39F7B0AE" } ] ] @@ -495,16 +460,9 @@ export = { ":", "log-group", ":", + "/aws/codebuild/", { - "Fn::Join": [ - "", - [ - "/aws/codebuild/", - { - "Ref": "MyProject39F7B0AE" - } - ] - ] + "Ref": "MyProject39F7B0AE" } ] ] diff --git a/packages/@aws-cdk/aws-events/test/test.rule.ts b/packages/@aws-cdk/aws-events/test/test.rule.ts index 3ae1f5acc8e40..2117b52b862b4 100644 --- a/packages/@aws-cdk/aws-events/test/test.rule.ts +++ b/packages/@aws-cdk/aws-events/test/test.rule.ts @@ -259,15 +259,8 @@ export = { "", [ "\"", - { - "Fn::Join": [ - "", - [ - "a", - "b" - ] - ] - }, + "a", + "b", "\"" ] ] diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-token.ts b/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-token.ts index f669e4137e457..a796c63a36c16 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-token.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-token.ts @@ -1,30 +1,24 @@ -import { Token } from "../core/tokens"; +import { resolve, Token } from "../core/tokens"; /** * Base class for CloudFormation built-ins */ export class CloudFormationToken extends Token { constructor(valueOrFunction: any, displayName?: string) { - super(valueOrFunction, { - joiner: CLOUDFORMATION_JOINER, - displayName - }); + super(valueOrFunction, displayName); + } + + public concat(left: any | undefined, right: any | undefined): Token { + const parts = new Array(); + if (left !== undefined) { parts.push(left); } + parts.push(resolve(this)); + if (right !== undefined) { parts.push(right); } + return new FnConcat(...parts); } } import { FnConcat } from "./fn"; -/** - * The default intrinsics Token engine for CloudFormation - */ -export const CLOUDFORMATION_JOINER = { - id: 'CloudFormation', - - join(fragments: any[]): any { - return new FnConcat(...fragments.map(x => isIntrinsic(x) ? x : `${x}`)); - } -}; - /** * Return whether the given value represents a CloudFormation intrinsic */ diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/fn.ts b/packages/@aws-cdk/cdk/lib/cloudformation/fn.ts index 2254997854ade..4d61e6fc169bf 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/fn.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/fn.ts @@ -1,4 +1,4 @@ -import { CloudFormationToken } from './cloudformation-token'; +import { CloudFormationToken, isIntrinsic } from './cloudformation-token'; // tslint:disable:max-line-length /** @@ -100,15 +100,54 @@ export class FnJoin extends Fn { * Alias for ``FnJoin('', listOfValues)``. */ export class FnConcat extends FnJoin { + private readonly listOfValues: any[]; + /** * Creates an ``Fn::Join`` function with an empty delimiter. * @param listOfValues The list of values to concatenate. */ constructor(...listOfValues: any[]) { + // Optimization: if any of the input arguments is also a FnConcat, + // splice their list of values into the current FnConcat. 'instanceof' + // can fail, but we do not depend depend on this for correctness. + // + // Do the same for resolved intrinsics, so we can detect this + // happening both at Token as well as at CloudFormation level. + + let i = 0; + while (i < listOfValues.length) { + const el = listOfValues[i]; + if (el instanceof FnConcat) { + listOfValues.splice(i, 1, ...el.listOfValues); + i += el.listOfValues.length; + } else if (isConcatIntrinsic(el)) { + const values = concatIntrinsicValues(el); + listOfValues.splice(i, 1, ...values); + i += values; + } else { + i++; + } + } + super('', listOfValues); + this.listOfValues = listOfValues; } } +/** + * Return whether the given object represents a CloudFormation intrinsic that is the result of a FnConcat resolution + */ +function isConcatIntrinsic(x: any) { + return isIntrinsic(x) && Object.keys(x)[0] === 'Fn::Join' && x['Fn::Join'][0] === ''; +} + +/** + * Return the concatted values of the concat intrinsic + */ +function concatIntrinsicValues(x: any) { + return x['Fn::Join'][1]; +} + /** * The intrinsic function ``Fn::Select`` returns a single object from a list of objects by index. */ diff --git a/packages/@aws-cdk/cdk/lib/core/tokens.ts b/packages/@aws-cdk/cdk/lib/core/tokens.ts index 108f640ee798a..99ffbb4a1f9a3 100644 --- a/packages/@aws-cdk/cdk/lib/core/tokens.ts +++ b/packages/@aws-cdk/cdk/lib/core/tokens.ts @@ -6,39 +6,6 @@ import { Construct } from "./construct"; */ export const RESOLVE_METHOD = 'resolve'; -/** - * Properties for Token customization - */ -export interface TokenProps { - /** - * A human-readable display hint for this Token - * - * This is used to represent the Token when it's embedded into a string; it - * will look something like this: - * - * "embedded in a larger string is ${Token[DISPLAY_NAME.123]}" - * - * This value is used as a hint to humans what the meaning of the Token is, - * and does not have any effect on the evaluation. - * - * Must contain only alphanumeric and simple separator characters (_.:-). - * - * @default TOKEN - */ - displayName?: string; - - /** - * Function used to concatenate strings and Token results together. - * - * After resolve() is called to restore Token values that have been - * encoded into string literals, the fragments will be combined using - * this combinator. - * - * @default Literal string joining - */ - joiner?: ITokenJoiner; -} - /** * Represents a lazy-evaluated value. * @@ -46,10 +13,7 @@ export interface TokenProps { * that it requires some context or late-bound data. */ export class Token { - public readonly joiner?: ITokenJoiner; - private tokenKey?: string; - private readonly displayName?: string; /** * Creates a token that resolves to `value`. @@ -57,12 +21,20 @@ export class Token { * If value is a function, the function is evaluated upon resolution and * the value it returns will be used as the token's value. * - * @param valueOrFunction What this token will evaluate to, literal or function. + * displayName is used to represent the Token when it's embedded into a string; it + * will look something like this: + * + * "embedded in a larger string is ${Token[DISPLAY_NAME.123]}" * + * This value is used as a hint to humans what the meaning of the Token is, + * and does not have any effect on the evaluation. + * + * Must contain only alphanumeric and simple separator characters (_.:-). + * + * @param valueOrFunction What this token will evaluate to, literal or function. + * @param displayName A human-readable display hint for this Token */ - constructor(private readonly valueOrFunction?: any, props: TokenProps = {}) { - this.displayName = props && props.displayName; - this.joiner = props && props.joiner; + constructor(private readonly valueOrFunction?: any, private readonly displayName?: string) { } /** @@ -111,6 +83,17 @@ export class Token { public toJSON(): any { throw new Error('JSON.stringify() cannot be applied to structure with a deferred Token in it. Use CloudFormationJSON.stringify() instead.'); } + + /** + * Return a concated version of this Token in a string context + * + * The default implementation of this combines strings, but specialized + * implements of Token can return a more appropriate value. + */ + public concat(left: any | undefined, right: any | undefined): Token { + const parts = [left, resolve(this), right].filter(x => x !== undefined); + return new Token(() => parts.map(x => `${x}`).join('')); + } } /** @@ -356,7 +339,9 @@ class TokenString { * * Either a literal part of the string, or an unresolved Token. */ -type Fragment = { type: 'string'; str: string } | { type: 'token'; token: Token }; +type StringFragment = { type: 'string'; str: string }; +type TokenFragment = { type: 'token'; token: Token }; +type Fragment = StringFragment | TokenFragment; /** * Fragments of a string with markers @@ -377,37 +362,44 @@ class TokenStringFragments { } /** - * Combine resolved fragments using the appropriate engine. + * Combine the resolved string fragments using the Tokens to join. * * Resolves the result. */ public join(): any { if (this.fragments.length === 0) { return ''; } - if (this.fragments.length === 1) { return this.values()[0]; } - - const joiners = this.fragments.map(f => f.type === 'token' ? f.token.joiner : undefined).filter(x => x !== undefined) as ITokenJoiner[]; - // Two reasons to look at joiner names here instead of object identity: - // 1) So we can display a better error message - // 2) If the library gets loaded multiple times, the same engine will be instantiated - // multiple times and so the objects will compare as different, even though they all - // do the same, and any one of them would be fine. - const joinerNames = Array.from(new Set(joiners.map(e => e.id))); - - if (joiners.length === 0) { - // No joiners. This can happen if we only have non language-specific Tokens. Stay - // in literal-land, convert all to string and combine. - return this.values().map(x => `${x}`).join(''); + if (this.fragments.length === 1) { return resolveFragment(this.fragments[0]); } + + const first = this.fragments[0]; + + let i; + let token: Token; + + if (first.type === 'token') { + token = first.token; + i = 1; + } else { + // We never have two strings in a row + token = (this.fragments[1] as TokenFragment).token.concat(first.str, undefined); + i = 2; } - if (joinerNames.length > 1) { - throw new Error(`Combining different joiners in one string fragment: ${joinerNames.join(', ')}`); + while (i < this.fragments.length) { + token = token.concat(undefined, resolveFragment(this.fragments[i])); + i++; } - // This might return another Token, so resolve again - return resolve(joiners[0].join(this.values())); + return resolve(token); } } +/** + * Resolve the value from a single fragment + */ +function resolveFragment(fragment: Fragment): any { + return fragment.type === 'string' ? fragment.str : resolve(fragment.token); +} + /** * Quote a string for use in a regex */ diff --git a/packages/@aws-cdk/cdk/test/cloudformation/test.perms.ts b/packages/@aws-cdk/cdk/test/cloudformation/test.perms.ts index cb71587da67c2..b924f0e418d93 100644 --- a/packages/@aws-cdk/cdk/test/cloudformation/test.perms.ts +++ b/packages/@aws-cdk/cdk/test/cloudformation/test.perms.ts @@ -26,7 +26,7 @@ export = { [ 'arn:', { Ref: 'AWS::Partition' }, ':iam::', - { 'Fn::Join': [ '', [ 'my', 'account', 'name' ] ] }, + 'my', 'account', 'name', ':root' ] ] } }, Condition: { StringEquals: { 'sts:ExternalId': '12221121221' } } }); diff --git a/packages/@aws-cdk/cdk/test/core/test.tokens.ts b/packages/@aws-cdk/cdk/test/core/test.tokens.ts index 80512e49c9cb6..f62623f11bc2c 100644 --- a/packages/@aws-cdk/cdk/test/core/test.tokens.ts +++ b/packages/@aws-cdk/cdk/test/core/test.tokens.ts @@ -157,6 +157,21 @@ export = { test.done(); }, + 'Tokens stringification and reversing of CloudFormation Tokens is implemented using Fn::Join'(test: Test) { + // GIVEN + const token = new CloudFormationToken(() => 'woof woof'); + + // WHEN + const stringified = `The dog says: ${token}`; + const resolved = resolve(stringified); + + // THEN + test.deepEqual(resolved, { + 'Fn::Join': ['', ['The dog says: ', 'woof woof']] + }); + test.done(); + }, + 'Doubly nested strings evaluate correctly in scalar context'(test: Test) { // GIVEN const token1 = new Token(() => "world"); @@ -175,7 +190,7 @@ export = { 'integer Tokens can be stringified and evaluate to conceptual value'(test: Test) { // GIVEN - for (const token of tokensThatResolveTo(1)) { + for (const token of literalTokensThatResolveTo(1)) { // WHEN const stringified = `the number is ${token}`; const resolved = resolve(stringified); @@ -188,14 +203,42 @@ export = { 'intrinsic Tokens can be stringified and evaluate to conceptual value'(test: Test) { // GIVEN - const bucketName = new CloudFormationToken({ Ref: 'MyBucket' }); + for (const bucketName of cloudFormationTokensThatResolveTo({ Ref: 'MyBucket' })) { + // WHEN + const resolved = resolve(`my bucket is named ${bucketName}`); - // WHEN - const resolved = resolve(`my bucket is named ${bucketName}`); + // THEN + const context = {MyBucket: 'TheName'}; + test.equal(evaluateCFN(resolved, context), 'my bucket is named TheName'); + } - // THEN - const context = {MyBucket: 'TheName'}; - test.equal(evaluateCFN(resolved, context), 'my bucket is named TheName'); + test.done(); + }, + + 'tokens resolve properly in initial position'(test: Test) { + // GIVEN + for (const token of tokensThatResolveTo('Hello')) { + // WHEN + const resolved = resolve(`${token} world`); + + // THEN + test.equal(evaluateCFN(resolved), 'Hello world'); + } + + test.done(); + }, + + 'side-by-side Tokens resolve correctly'(test: Test) { + // GIVEN + for (const token1 of tokensThatResolveTo('Hello ')) { + for (const token2 of tokensThatResolveTo('world')) { + // WHEN + const resolved = resolve(`${token1}${token2}`); + + // THEN + test.equal(evaluateCFN(resolved), 'Hello world'); + } + } test.done(); }, @@ -235,11 +278,28 @@ class DataType extends BaseDataType { } /** - * Return two Tokens, one of which evaluates to a Token directly, one which evaluates to it lazily + * Return various flavors of Tokens that resolve to the given value */ -function tokensThatResolveTo(value: any): Token[] { +function literalTokensThatResolveTo(value: any): Token[] { return [ new Token(value), new Token(() => value) ]; +} + +/** + * Return various flavors of Tokens that resolve to the given value + */ +function cloudFormationTokensThatResolveTo(value: any): Token[] { + return [ + new CloudFormationToken(value), + new CloudFormationToken(() => value) + ]; +} + +/** + * Return Tokens in both flavors that resolve to the given string + */ +function tokensThatResolveTo(value: string): Token[] { + return literalTokensThatResolveTo(value).concat(cloudFormationTokensThatResolveTo(value)); } \ No newline at end of file diff --git a/packages/@aws-cdk/runtime-values/test/integ.rtv.lambda.expected.json b/packages/@aws-cdk/runtime-values/test/integ.rtv.lambda.expected.json index a9b3a0ac18a24..6d0c2a5f1c8d1 100644 --- a/packages/@aws-cdk/runtime-values/test/integ.rtv.lambda.expected.json +++ b/packages/@aws-cdk/runtime-values/test/integ.rtv.lambda.expected.json @@ -78,21 +78,14 @@ ":", "parameter", "/", + "/rtv/", { - "Fn::Join": [ - "", - [ - "/rtv/", - { - "Ref": "AWS::StackName" - }, - "/", - "com.myorg", - "/", - "MyQueueURL" - ] - ] - } + "Ref": "AWS::StackName" + }, + "/", + "com.myorg", + "/", + "MyQueueURL" ] ] } @@ -114,13 +107,6 @@ "Code": { "ZipFile": "exports.handler = function runtimeCode(_event, _context, callback) {\n return callback();\n}" }, - "Environment": { - "Variables": { - "RTV_STACK_NAME": { - "Ref": "AWS::StackName" - } - } - }, "Handler": "index.handler", "Role": { "Fn::GetAtt": [ @@ -129,6 +115,13 @@ ] }, "Runtime": "nodejs6.10", + "Environment": { + "Variables": { + "RTV_STACK_NAME": { + "Ref": "AWS::StackName" + } + } + }, "Timeout": 30 }, "DependsOn": [ From c5b3b68075ceefd7bd076172a8c8f24e2b42ce2d Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 15 Aug 2018 12:55:45 +0200 Subject: [PATCH 17/17] Remove laziness of many existing Token use cases --- .../cdk/lib/cloudformation/cloudformation-token.ts | 4 ---- packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts | 2 +- .../@aws-cdk/cdk/lib/cloudformation/resource.ts | 2 +- packages/@aws-cdk/cdk/lib/cloudformation/stack.ts | 2 +- packages/@aws-cdk/cdk/lib/core/tokens.ts | 13 +++++++++---- packages/@aws-cdk/cdk/package-lock.json | 12 ++++++------ 6 files changed, 18 insertions(+), 17 deletions(-) diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-token.ts b/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-token.ts index a796c63a36c16..51479f86ff532 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-token.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/cloudformation-token.ts @@ -4,10 +4,6 @@ import { resolve, Token } from "../core/tokens"; * Base class for CloudFormation built-ins */ export class CloudFormationToken extends Token { - constructor(valueOrFunction: any, displayName?: string) { - super(valueOrFunction, displayName); - } - public concat(left: any | undefined, right: any | undefined): Token { const parts = new Array(); if (left !== undefined) { parts.push(left); } diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts b/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts index d52d62840667d..1e39b291418e0 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/pseudo.ts @@ -2,7 +2,7 @@ import { CloudFormationToken } from './cloudformation-token'; export class PseudoParameter extends CloudFormationToken { constructor(name: string) { - super(() => ({ Ref: name }), name); + super({ Ref: name }, name); } } diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts b/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts index 1ba916ae6df11..ad831f7f27e20 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/resource.ts @@ -83,7 +83,7 @@ export class Resource extends Referenceable { * @param attributeName The name of the attribute. */ public getAtt(attributeName: string): Token { - return new CloudFormationToken(() => ({ 'Fn::GetAtt': [this.logicalId, attributeName] }), `${this.logicalId}.${attributeName}`); + return new CloudFormationToken({ 'Fn::GetAtt': [this.logicalId, attributeName] }, `${this.logicalId}.${attributeName}`); } /** diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts b/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts index 0e64c3743cdc1..8a4211ad3cf0d 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts @@ -400,7 +400,7 @@ export abstract class Referenceable extends StackElement { * Returns a token to a CloudFormation { Ref } that references this entity based on it's logical ID. */ public get ref(): Token { - return new CloudFormationToken(() => ({ Ref: this.logicalId }), `${this.logicalId}.Ref`); + return new CloudFormationToken({ Ref: this.logicalId }, `${this.logicalId}.Ref`); } } diff --git a/packages/@aws-cdk/cdk/lib/core/tokens.ts b/packages/@aws-cdk/cdk/lib/core/tokens.ts index 99ffbb4a1f9a3..2aa6ed95538ff 100644 --- a/packages/@aws-cdk/cdk/lib/core/tokens.ts +++ b/packages/@aws-cdk/cdk/lib/core/tokens.ts @@ -7,10 +7,14 @@ import { Construct } from "./construct"; export const RESOLVE_METHOD = 'resolve'; /** - * Represents a lazy-evaluated value. + * Represents a special or lazily-evaluated value. * * Can be used to delay evaluation of a certain value in case, for example, - * that it requires some context or late-bound data. + * that it requires some context or late-bound data. Can also be used to + * mark values that need special processing at document rendering time. + * + * Tokens can be embedded into strings while retaining their original + * semantics. */ export class Token { private tokenKey?: string; @@ -81,7 +85,8 @@ export class Token { * it's not possible to do this properly, so we just throw an error here. */ public toJSON(): any { - throw new Error('JSON.stringify() cannot be applied to structure with a deferred Token in it. Use CloudFormationJSON.stringify() instead.'); + // tslint:disable-next-line:max-line-length + throw new Error('JSON.stringify() cannot be applied to structure with a Token in it. Use a document-specific stringification method instead.'); } /** @@ -92,7 +97,7 @@ export class Token { */ public concat(left: any | undefined, right: any | undefined): Token { const parts = [left, resolve(this), right].filter(x => x !== undefined); - return new Token(() => parts.map(x => `${x}`).join('')); + return new Token(parts.map(x => `${x}`).join('')); } } diff --git a/packages/@aws-cdk/cdk/package-lock.json b/packages/@aws-cdk/cdk/package-lock.json index 5d9a20916b9c9..59b67394eae31 100644 --- a/packages/@aws-cdk/cdk/package-lock.json +++ b/packages/@aws-cdk/cdk/package-lock.json @@ -12,7 +12,7 @@ "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-0.1.7.tgz", "integrity": "sha1-rcMgD6RxzCEbDaf1ZrcemLnWc0c=", "requires": { - "es5-ext": "0.8.x" + "es5-ext": "0.8.2" } }, "difflib": { @@ -20,7 +20,7 @@ "resolved": "https://registry.npmjs.org/difflib/-/difflib-0.2.4.tgz", "integrity": "sha1-teMDYabbAjF21WKJLbhZQKcY9H4=", "requires": { - "heap": ">= 0.2.0" + "heap": "0.2.6" } }, "dreamopt": { @@ -28,7 +28,7 @@ "resolved": "https://registry.npmjs.org/dreamopt/-/dreamopt-0.6.0.tgz", "integrity": "sha1-2BPM2sjTnYrVJndVFKE92mZNa0s=", "requires": { - "wordwrap": ">=0.0.2" + "wordwrap": "1.0.0" } }, "es5-ext": { @@ -51,9 +51,9 @@ "resolved": "https://registry.npmjs.org/json-diff/-/json-diff-0.3.1.tgz", "integrity": "sha1-bbw64tJeB1p/1xvNmHRFhmb7aBs=", "requires": { - "cli-color": "~0.1.6", - "difflib": "~0.2.1", - "dreamopt": "~0.6.0" + "cli-color": "0.1.7", + "difflib": "0.2.4", + "dreamopt": "0.6.0" } }, "wordwrap": {