Skip to content

Commit

Permalink
feat(events): group CW Event Targets in module (#2576)
Browse files Browse the repository at this point in the history
Move new event targets into `@aws-cdk/aws-events-targets`

- CodePipeline
- EC2 task
- StepFunctions StateMachine

Invocation payloads are now under the control of the target classes, so
that targets for which the payload maps onto API calls (such as ECS,
CodeBuild, etc) can specify the API parameters directly as props.

`EventTargetInput` is a union class with three variants, allowing
serialization of strings, multiline strings and objects. Target inputs
support a special type of Token (`EventField`) which can be used to
refer to fields from the event, instead of value literals. A number of
predefined events and the fields that they have available have been
defined, so that accessing them becomes even more convenient
(`StageChangeEvent`, `PhaseChangeEvent`, `ReferenceEvent`).

To be able to use Tokens to implement EventInput substitution, add a
refactoring and more principaled separation of concerns in the Token
code. Resolution can now be more easily hooked, CloudFormation-aware
string concatenation is implemented using a plugin, and Token callbacks
receive an `IResolveContext` which they can use to resolve deeper (and
will reuse the same settings that the resolver was started with). We
should as good as be able to get rid of `stack.node.resolve()` in the
near future.

ALSO:

- Simplified `LatestDeploymentResource` to use existing logical ID
  overriding features.
- Add an `onEvent()` method to `CloudTrail`.
- Fix API misuse in the rendering of a CodePipeline, where Token
  rendering would lead to stateful side effects. Make Actions render
  out their region directly, if set, without requiring overrides.
- In ECS task `assignPublicIp` now defaults to `undefined`, instead
  of `DISABLED`, to align with service API.
- Fix a bug in Token resolution where Tokens returning fresh
  `CfnReference` objects upon resolution would fail to be detected
  during cross-stack reference analysis; `CfnReference` now has 
  static methods that return singleton objects.
- Get rid of an extra "resolve" call in
  `CfnResource.toCloudFormation()`. We can now use `PostProcessToken`
  to apply the property renames and output validation we were originally
  doing the resolve for.

Fixes #2403, fixes #2404, fixes #2581.

BREAKING CHANGES

* `@aws-cdk/aws-codepipeline.Pipeline` is no longer an event target
  itself, use `@aws-cdk/aws-events-targets.CodePipeline` instead.
* `@aws-cdk/aws-stepfunctions.StateMachine` is no longer an event target
  itself, use `@aws-cdk/aws-events-targets.SfnStateMachine` instead.
* `@aws-cdk/aws-ecs.Ec2RunTask` has been renamed to
  `@aws-cdk/aws-events-targets.EcsEc2Task`.
* `CloudFormationJSON.stringify()` is now renamed to
  `CloudFormationLang.toJSON()`.
  • Loading branch information
rix0rrr committed May 20, 2019
1 parent 1f004f6 commit 7cb8e5e
Show file tree
Hide file tree
Showing 98 changed files with 2,308 additions and 1,394 deletions.
48 changes: 4 additions & 44 deletions packages/@aws-cdk/aws-apigateway/lib/deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,51 +91,13 @@ export class Deployment extends Resource {
}

class LatestDeploymentResource extends CfnDeployment {
private originalLogicalId?: string;
private lazyLogicalIdRequired: boolean;
private lazyLogicalId?: string;
private logicalIdToken: Token;
private hashComponents = new Array<any>();
private originalLogicalId: string;

constructor(scope: Construct, id: string, props: CfnDeploymentProps) {
super(scope, id, props);

// from this point, don't allow accessing logical ID before synthesis
this.lazyLogicalIdRequired = true;

this.logicalIdToken = new Token(() => this.lazyLogicalId);
}

/**
* Returns either the original or the custom logical ID of this resource.
*/
public get logicalId() {
if (!this.lazyLogicalIdRequired) {
return this.originalLogicalId!;
}

return this.logicalIdToken.toString();
}

/**
* Sets the logical ID of this resource.
*/
public set logicalId(v: string) {
this.originalLogicalId = v;
}

/**
* Returns a lazy reference to this resource (evaluated only upon synthesis).
*/
public get ref() {
return new Token(() => ({ Ref: this.lazyLogicalId })).toString();
}

/**
* Does nothing.
*/
public set ref(_v: string) {
return;
this.originalLogicalId = this.node.stack.logicalIds.getLogicalId(this);
}

/**
Expand All @@ -159,15 +121,13 @@ class LatestDeploymentResource extends CfnDeployment {
protected prepare() {
// if hash components were added to the deployment, we use them to calculate
// a logical ID for the deployment resource.
if (this.hashComponents.length === 0) {
this.lazyLogicalId = this.originalLogicalId;
} else {
if (this.hashComponents.length > 0) {
const md5 = crypto.createHash('md5');
this.hashComponents
.map(c => this.node.resolve(c))
.forEach(c => md5.update(JSON.stringify(c)));

this.lazyLogicalId = this.originalLogicalId + md5.digest("hex");
this.overrideLogicalId(this.originalLogicalId + md5.digest("hex"));
}

super.prepare();
Expand Down
16 changes: 16 additions & 0 deletions packages/@aws-cdk/aws-cloudtrail/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import events = require('@aws-cdk/aws-events');
import iam = require('@aws-cdk/aws-iam');
import kms = require('@aws-cdk/aws-kms');
import logs = require('@aws-cdk/aws-logs');
Expand Down Expand Up @@ -209,6 +210,21 @@ export class Trail extends Resource {
}]
});
}

/**
* Create an event rule for when an event is recorded by any trail.
*
* Note that the event doesn't necessarily have to come from this
* trail. Be sure to filter the event properly using an event pattern.
*/
public onEvent(name: string, target?: events.IRuleTarget, options?: events.RuleProps) {
const rule = new events.Rule(this, name, options);
rule.addTarget(target);
rule.addEventPattern({
detailType: ['AWS API Call via CloudTrail']
});
return rule;
}
}

/**
Expand Down
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-cloudtrail/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"pkglint": "^0.31.0"
},
"dependencies": {
"@aws-cdk/aws-events": "^0.31.0",
"@aws-cdk/aws-iam": "^0.31.0",
"@aws-cdk/aws-kms": "^0.31.0",
"@aws-cdk/aws-logs": "^0.31.0",
Expand All @@ -75,6 +76,7 @@
},
"homepage": "https://github.com/awslabs/aws-cdk",
"peerDependencies": {
"@aws-cdk/aws-events": "^0.31.0",
"@aws-cdk/aws-iam": "^0.31.0",
"@aws-cdk/aws-kms": "^0.31.0",
"@aws-cdk/aws-logs": "^0.31.0",
Expand Down
34 changes: 33 additions & 1 deletion packages/@aws-cdk/aws-cloudtrail/test/test.cloudtrail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,39 @@ export = {
test.done();
},
}
}
},

'add an event rule'(test: Test) {
// GIVEN
const stack = getTestStack();
const trail = new Trail(stack, 'MyAmazingCloudTrail', { managementEvents: ReadWriteType.WriteOnly });

// WHEN
trail.onEvent('DoEvents', {
bind: () => ({
arn: 'arn',
id: 'myid'
})
});

// THEN
expect(stack).to(haveResource('AWS::Events::Rule', {
EventPattern: {
"detail-type": [
"AWS API Call via CloudTrail"
]
},
State: "ENABLED",
Targets: [
{
Arn: "arn",
Id: "myid"
}
]
}));

test.done();
},
};

function getTestStack(): Stack {
Expand Down
88 changes: 88 additions & 0 deletions packages/@aws-cdk/aws-codebuild/lib/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import events = require('@aws-cdk/aws-events');

/**
* Event fields for the CodeBuild "state change" event
*
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/sample-build-notifications.html#sample-build-notifications-ref
*/
export class StateChangeEvent {
/**
* The triggering build's status
*/
public static get buildStatus() {
return events.EventField.fromPath('$.detail.build-status');
}

/**
* The triggering build's project name
*/
public static get projectName() {
return events.EventField.fromPath('$.detail.project-name');
}

/**
* Return the build id
*/
public static get buildId() {
return events.EventField.fromPath('$.detail.build-id');
}

public static get currentPhase() {
return events.EventField.fromPath('$.detail.current-phase');
}

private constructor() {
}
}

/**
* Event fields for the CodeBuild "phase change" event
*
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/sample-build-notifications.html#sample-build-notifications-ref
*/
export class PhaseChangeEvent {
/**
* The triggering build's project name
*/
public static get projectName() {
return events.EventField.fromPath('$.detail.project-name');
}

/**
* The triggering build's id
*/
public static get buildId() {
return events.EventField.fromPath('$.detail.build-id');
}

/**
* The phase that was just completed
*/
public static get completedPhase() {
return events.EventField.fromPath('$.detail.completed-phase');
}

/**
* The status of the completed phase
*/
public static get completedPhaseStatus() {
return events.EventField.fromPath('$.detail.completed-phase-status');
}

/**
* The duration of the completed phase
*/
public static get completedPhaseDurationSeconds() {
return events.EventField.fromPath('$.detail.completed-phase-duration-seconds');
}

/**
* Whether the build is complete
*/
public static get buildComplete() {
return events.EventField.fromPath('$.detail.build-complete');
}

private constructor() {
}
}
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-codebuild/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './events';
export * from './pipeline-project';
export * from './project';
export * from './source';
Expand Down
39 changes: 27 additions & 12 deletions packages/@aws-cdk/aws-codebuild/lib/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,32 +52,35 @@ export interface IProject extends IResource, iam.IGrantable {
* You can also use the methods `onBuildFailed` and `onBuildSucceeded` to define rules for
* these specific state changes.
*
* To access fields from the event in the event target input,
* use the static fields on the `StateChangeEvent` class.
*
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/sample-build-notifications.html
*/
onStateChange(name: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps): events.EventRule;
onStateChange(name: string, target?: events.IRuleTarget, options?: events.RuleProps): events.Rule;

/**
* Defines a CloudWatch event rule that triggers upon phase change of this
* build project.
*
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/sample-build-notifications.html
*/
onPhaseChange(name: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps): events.EventRule;
onPhaseChange(name: string, target?: events.IRuleTarget, options?: events.RuleProps): events.Rule;

/**
* Defines an event rule which triggers when a build starts.
*/
onBuildStarted(name: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps): events.EventRule;
onBuildStarted(name: string, target?: events.IRuleTarget, options?: events.RuleProps): events.Rule;

/**
* Defines an event rule which triggers when a build fails.
*/
onBuildFailed(name: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps): events.EventRule;
onBuildFailed(name: string, target?: events.IRuleTarget, options?: events.RuleProps): events.Rule;

/**
* Defines an event rule which triggers when a build completes successfully.
*/
onBuildSucceeded(name: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps): events.EventRule;
onBuildSucceeded(name: string, target?: events.IRuleTarget, options?: events.RuleProps): events.Rule;

/**
* @returns a CloudWatch metric associated with this build project.
Expand Down Expand Up @@ -174,10 +177,13 @@ abstract class ProjectBase extends Resource implements IProject {
* You can also use the methods `onBuildFailed` and `onBuildSucceeded` to define rules for
* these specific state changes.
*
* To access fields from the event in the event target input,
* use the static fields on the `StateChangeEvent` class.
*
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/sample-build-notifications.html
*/
public onStateChange(name: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps) {
const rule = new events.EventRule(this, name, options);
public onStateChange(name: string, target?: events.IRuleTarget, options?: events.RuleProps) {
const rule = new events.Rule(this, name, options);
rule.addTarget(target);
rule.addEventPattern({
source: ['aws.codebuild'],
Expand All @@ -197,8 +203,8 @@ abstract class ProjectBase extends Resource implements IProject {
*
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/sample-build-notifications.html
*/
public onPhaseChange(name: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps) {
const rule = new events.EventRule(this, name, options);
public onPhaseChange(name: string, target?: events.IRuleTarget, options?: events.RuleProps) {
const rule = new events.Rule(this, name, options);
rule.addTarget(target);
rule.addEventPattern({
source: ['aws.codebuild'],
Expand All @@ -214,8 +220,11 @@ abstract class ProjectBase extends Resource implements IProject {

/**
* Defines an event rule which triggers when a build starts.
*
* To access fields from the event in the event target input,
* use the static fields on the `StateChangeEvent` class.
*/
public onBuildStarted(name: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps) {
public onBuildStarted(name: string, target?: events.IRuleTarget, options?: events.RuleProps) {
const rule = this.onStateChange(name, target, options);
rule.addEventPattern({
detail: {
Expand All @@ -227,8 +236,11 @@ abstract class ProjectBase extends Resource implements IProject {

/**
* Defines an event rule which triggers when a build fails.
*
* To access fields from the event in the event target input,
* use the static fields on the `StateChangeEvent` class.
*/
public onBuildFailed(name: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps) {
public onBuildFailed(name: string, target?: events.IRuleTarget, options?: events.RuleProps) {
const rule = this.onStateChange(name, target, options);
rule.addEventPattern({
detail: {
Expand All @@ -240,8 +252,11 @@ abstract class ProjectBase extends Resource implements IProject {

/**
* Defines an event rule which triggers when a build completes successfully.
*
* To access fields from the event in the event target input,
* use the static fields on the `StateChangeEvent` class.
*/
public onBuildSucceeded(name: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps) {
public onBuildSucceeded(name: string, target?: events.IRuleTarget, options?: events.RuleProps) {
const rule = this.onStateChange(name, target, options);
rule.addEventPattern({
detail: {
Expand Down
Loading

0 comments on commit 7cb8e5e

Please sign in to comment.