Skip to content

Commit

Permalink
aws-cdk-events: jsonTemplate versus textTemplate (take 2) (#27)
Browse files Browse the repository at this point in the history
Input transformers are primarily designed to allow
formatting JSON documents that will be sent to the
target, in which case the template will look like
this:

    '{ "foo": <bar> }'

The tokens in angle brackets are first substituted
and then the string is parsed as JSON.

If users want to send a string for the target input,
they will need the template to look like this:

    '"This is a <bar> string"'

(note the double quotes).

To facilitate the common use case where inputs 
templates should resolve to a string, but also support
the JSON option, `TargetInputTemplate` now accepts
two mutually exclusive options: `jsonTemplate` and
`textTemplate`.

`jsonTemplate` is simply passed as-is.

If `textTemplate` is used and a value of type string
is passed in, it is `JSON.stringify`ed. If a non-string
value is passed in, we assume it includes tokens, and
we simply wrap with double quotes using `FnConcat`.
  • Loading branch information
eladb authored Jun 5, 2018
1 parent 575395b commit 35c594d
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 52 deletions.
4 changes: 2 additions & 2 deletions packages/aws-cdk-codebuild/test/integ.project-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ project.onStateChange('StateChange', topic);
// The phase will be extracted from the "completed-phase" field of the event
// details.
project.onPhaseChange('PhaseChange').addTarget(topic, {
template: `Build phase changed to <phase>`,
textTemplate: `Build phase changed to <phase>`,
pathsMap: {
phase: '$.detail.completed-phase'
}
Expand All @@ -31,7 +31,7 @@ project.onPhaseChange('PhaseChange').addTarget(topic, {
// trigger a build when a commit is pushed to the repo
const onCommitRule = repo.onCommit('OnCommit', project, 'master');
onCommitRule.addTarget(topic, {
template: 'A commit was pushed to the repository <repo> on branch <branch>',
textTemplate: 'A commit was pushed to the repository <repo> on branch <branch>',
pathsMap: {
branch: '$.detail.referenceName',
repo: '$.detail.repositoryName'
Expand Down
4 changes: 2 additions & 2 deletions packages/aws-cdk-codepipeline/test/integ.pipeline-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ const repository = new Repository(stack, 'CodeCommitRepo', { repositoryName: 'fo
const project = new BuildProject(stack, 'BuildProject', { source: new CodePipelineSource() });

const sourceAction = new CodeCommitSource(sourceStage, 'CodeCommitSource', { artifactName: 'Source', repository });
new CodeBuildAction(buildStage, 'CodeBuildAction', { source: sourceAction, project });
new CodeBuildAction(buildStage, 'CodeBuildAction', { inputArtifact: sourceAction.artifact, project });

const topic = new Topic(stack, 'MyTopic');
topic.subscribeEmail('benisrae', '[email protected]');

pipeline.onStateChange('OnPipelineStateChange').addTarget(topic, {
template: 'Pipeline <pipeline> changed state to <state>',
textTemplate: 'Pipeline <pipeline> changed state to <state>',
pathsMap: {
pipeline: '$.detail.pipeline',
state: '$.detail.state'
Expand Down
33 changes: 31 additions & 2 deletions packages/aws-cdk-events/lib/input-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,38 @@ export interface TargetInputTemplate {
* inputPathsMap to customize the data sent to the target. Enclose each
* InputPathsMaps value in brackets: <value>
*
* The value will be serialized as a JSON object (JSON.stringify).
* The value passed here will be double-quoted to indicate it's a string value.
* This option is mutually exclusive with `jsonTemplate`.
*
* @example
*
* {
* textTemplate: 'Build <buildid> started',
* pathsMap: {
* buildid: '$.detail.id'
* }
* }
*/
textTemplate?: any;

/**
* Input template where you can use the values of the keys from
* inputPathsMap to customize the data sent to the target. Enclose each
* InputPathsMaps value in brackets: <value>
*
* This option is mutually exclusive with `textTemplate`.
*
* @example
*
* {
* jsonTemplate: '{ "commands": <commandsToRun> }' ,
* pathsMap: {
* commandsToRun: '$.detail.commands'
* }
* }
*
*/
template?: any;
jsonTemplate?: any;

/**
* Map of JSON paths to be extracted from the event. These are key-value
Expand Down
38 changes: 31 additions & 7 deletions packages/aws-cdk-events/lib/rule.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Construct, resolve, Token } from 'aws-cdk';
import { Construct, FnConcat, Token } from 'aws-cdk';
import { events } from 'aws-cdk-resources';
import { EventPattern } from './event-pattern';
import { TargetInputTemplate } from './input-options';
Expand Down Expand Up @@ -107,13 +107,37 @@ export class EventRule extends EventRuleRef {

this.targets.push({
...target.eventRuleTarget,
inputTransformer: inputOptions && {
inputTemplate: typeof(inputOptions.template) === 'string'
? JSON.stringify(inputOptions.template)
: resolve(inputOptions.template),
inputPathsMap: inputOptions.pathsMap
},
inputTransformer: renderTransformer(),
});

function renderTransformer(): events.RuleResource.InputTransformerProperty | undefined {
if (!inputOptions) {
return undefined;
}

if (inputOptions.jsonTemplate && inputOptions.textTemplate) {
throw new Error('"jsonTemplate" and "textTemplate" are mutually exclusive');
}

if (!inputOptions.jsonTemplate && !inputOptions.textTemplate) {
throw new Error('One of "jsonTemplate" or "textTemplate" are required');
}

let inputTemplate: any;

if (inputOptions.jsonTemplate) {
inputTemplate = inputOptions.jsonTemplate;
} else if (typeof(inputOptions.textTemplate) === 'string') {
inputTemplate = JSON.stringify(inputOptions.textTemplate);
} else {
inputTemplate = new FnConcat('"', inputOptions.textTemplate, '"');
}

return {
inputPathsMap: inputOptions.pathsMap,
inputTemplate
};
}
}

/**
Expand Down
126 changes: 87 additions & 39 deletions packages/aws-cdk-events/test/test.rule.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { App, Arn, FnConcat, Stack } from 'aws-cdk';
import { App, Arn, FnConcat, FnJoin, Stack } from 'aws-cdk';
import { expect } from 'aws-cdk-assert';
import { iam } from 'aws-cdk-resources';
import { Test } from 'nodeunit';
Expand Down Expand Up @@ -158,7 +158,7 @@ export = {
});

rule.addTarget(t2, {
template: 'This is <bla>',
textTemplate: 'This is <bla>',
pathsMap: {
bla: '$.detail.bla'
}
Expand Down Expand Up @@ -201,67 +201,115 @@ export = {
'input template can contain tokens'(test: Test) {
const stack = new Stack();
const t1: IEventRuleTarget = {
eventRuleTarget: {
id: 'T1',
arn: new Arn('ARN1'),
kinesisParameters: { partitionKeyPath: 'partitionKeyPath' }
}
};
const t2: IEventRuleTarget = {
eventRuleTarget: {
id: 'T2',
arn: new Arn('ARN2'),
roleArn: new iam.RoleArn('IAM-ROLE-ARN')
}
eventRuleTarget: { id: 'T1', arn: new Arn('ARN1'), kinesisParameters: { partitionKeyPath: 'partitionKeyPath' } }
};

const t2: IEventRuleTarget = { eventRuleTarget: { id: 'T2', arn: new Arn('ARN2'), roleArn: new iam.RoleArn('IAM-ROLE-ARN') } };
const t3: IEventRuleTarget = { eventRuleTarget: { id: 'T3', arn: new Arn('ARN3') } };
const t4: IEventRuleTarget = { eventRuleTarget: { id: 'T4', arn: new Arn('ARN4') } };

const rule = new EventRule(stack, 'EventRule');

// a plain string should just be stringified (i.e. double quotes added and escaped)
rule.addTarget(t2, {
textTemplate: 'Hello, "world"'
});

// tokens are used here (FnConcat), but this is a text template so we
// expect it to be wrapped in double quotes automatically for us.
rule.addTarget(t1, {
template: new FnConcat('a', 'b')
textTemplate: new FnConcat('a', 'b')
});
rule.addTarget(t2, {
template: 'Hello, world'

// jsonTemplate can be used to format JSON documents with replacements
rule.addTarget(t3, {
jsonTemplate: '{ "foo": <bar> }',
pathsMap: {
bar: '$.detail.bar'
}
});

// tokens can also used for JSON templates, but that means escaping is
// the responsibility of the user.
rule.addTarget(t4, {
jsonTemplate: new FnJoin(' ', '"', 'hello', '\"world\"', '"'),
});

expect(stack).toMatch({
"Resources": {
"EventRule5A491D2C": {
"Type": "AWS::Events::Rule",
"Properties": {
"State": "ENABLED",
"Targets": [
{
"Arn": "ARN1",
"Id": "T1",
"InputTransformer": {
"InputTemplate": {
"State": "ENABLED",
"Targets": [
{
"Arn": "ARN2",
"Id": "T2",
"InputTransformer": {
"InputTemplate": "\"Hello, \\\"world\\\"\""
},
"RoleArn": "IAM-ROLE-ARN"
},
{
"Arn": "ARN1",
"Id": "T1",
"InputTransformer": {
"InputTemplate": {
"Fn::Join": [
"",
[
"a",
"b"
"\"",
{
"Fn::Join": [
"",
[
"a",
"b"
]
]
},
"\""
]
]
}
},
"KinesisParameters": {
"PartitionKeyPath": "partitionKeyPath"
}
},
"KinesisParameters": {
"PartitionKeyPath": "partitionKeyPath"
}
},
{
"Arn": "ARN2",
"Id": "T2",
{
"Arn": "ARN3",
"Id": "T3",
"InputTransformer": {
"InputTemplate": "\"Hello, world\""
},
"RoleArn": "IAM-ROLE-ARN"
}
]
"InputPathsMap": {
"bar": "$.detail.bar"
},
"InputTemplate": "{ \"foo\": <bar> }"
}
},
{
"Arn": "ARN4",
"Id": "T4",
"InputTransformer": {
"InputTemplate": {
"Fn::Join": [
" ",
[
"\"",
"hello",
"\"world\"",
"\""
]
]
}
}
}
]
}
}
}
});

test.done();
}
};
};

0 comments on commit 35c594d

Please sign in to comment.