Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(events): API Gateway target #13823

Merged
merged 12 commits into from Apr 23, 2021
44 changes: 44 additions & 0 deletions packages/@aws-cdk/aws-events-targets/README.md
Expand Up @@ -19,6 +19,7 @@ Currently supported are:
* [Start a CodePipeline pipeline](#start-a-codepipeline-pipeline)
* Run an ECS task
* [Invoke a Lambda function](#invoke-a-lambda-function)
* [Invoke a API Gateway REST API](#invoke-a-api-gateway-rest-api)
* Publish a message to an SNS topic
* Send a message to an SQS queue
* [Start a StepFunctions state machine](#start-a-stepfunctions-state-machine)
Expand Down Expand Up @@ -185,3 +186,46 @@ rule.addTarget(new targets.SfnStateMachine(stateMachine, {
deadLetterQueue: dlq,
}));
```

## Invoke a API Gateway REST API

Use the `ApiGateway` target to trigger a REST API.

The code snippet below creates a Api Gateway REST API that is invoked every hour.

```typescript
import * as iam from '@aws-sdk/aws-iam';
import * as sqs from '@aws-sdk/aws-sqs';
import * as api from '@aws-cdk/aws-apigateway';
import * as targets from "@aws-cdk/aws-events-targets";

const rule = new events.Rule(stack, 'Rule', {
schedule: events.Schedule.rate(cdk.Duration.minutes(1)),
});

const fn = new lambda.Function( this, 'MyFunc', {
handler: 'index.handler',
runtime: lambda.Runtime.NODEJS_12_X,
code: lambda.Code.fromInline( 'exports.handler = e => {}' ),
} );

const restApi = new api.LambdaRestApi( this, 'MyRestAPI', { handler: fn } );

const dlq = new sqs.Queue(stack, 'DeadLetterQueue');

rule.addTarget(
new targets.ApiGateway( restApi, {
path: '/*/test',
mehod: 'GET',
stage: 'prod',
pathParameterValues: ['path-value'],
headerParameters: {
Header1: 'header1',
},
queryStringParameters: {
QueryParam1: 'query-param-1',
},
deadLetterQueue: queue
} ),
)
```
123 changes: 123 additions & 0 deletions packages/@aws-cdk/aws-events-targets/lib/api-gateway.ts
@@ -0,0 +1,123 @@
import * as api from '@aws-cdk/aws-apigateway';
import * as events from '@aws-cdk/aws-events';
import * as iam from '@aws-cdk/aws-iam';
import { addToDeadLetterQueueResourcePolicy, bindBaseTargetConfig, singletonEventRole, TargetBaseProps } from './util';

/**
* Customize the API Gateway Event Target
*/
export interface ApiGatewayProps extends TargetBaseProps {

/**
* The method for api resource invoked by the rule.
*
* @default '*' that treated as ANY
*/
readonly method?: string;

/**
* The api resource invoked by the rule.
* We can use wildcards('*') to specify the path. In that case,
* an equal number of real values must be specified for pathParameterValues.
*
* @default '/'
*/
readonly path?: string;

/**
* The deploy stage of api gateway invoked by the rule.
*
* @default the value of deploymentStage.stageName of target api gateway.
*/
readonly stage?: string;

/**
* The headers to be set when requesting API
*
* @default no header parameters
*/
readonly headerParameters?: { [key: string]: (string) };

/**
* The path parameter values to be used to
* populate to wildcards("*") of requesting api path
*
* @default no path parameters
*/
readonly pathParameterValues?: string[];

/**
* The query parameters to be set when requesting API.
*
* @default no querystring parameters
*/
readonly queryStringParameters?: { [key: string]: (string) };

/**
* This will be the post request body send to the API.
*
* @default the entire EventBridge event
*/
readonly postBody?: events.RuleTargetInput;

/**
* The role to assume before invoking the target
* (i.e., the pipeline) when the given rule is triggered.
*
* @default - a new role will be created
*/
readonly eventRole?: iam.IRole;
}

/**
* Use an API Gateway REST APIs as a target for Amazon EventBridge rules.
*/
export class ApiGateway implements events.IRuleTarget {

constructor(public readonly restApi: api.RestApi, private readonly props?: ApiGatewayProps) {
}

/**
* Returns a RuleTarget that can be used to trigger this API Gateway REST APIs
* as a result from an EventBridge event.
*
* @see https://docs.aws.amazon.com/eventbridge/latest/userguide/resource-based-policies-eventbridge.html#sqs-permissions
*/
public bind(rule: events.IRule, _id?: string): events.RuleTargetConfig {
if (this.props?.deadLetterQueue) {
addToDeadLetterQueueResourcePolicy(rule, this.props.deadLetterQueue);
}

const wildcardCountsInPath = this.props?.path?.match( /\*/g )?.length ?? 0;
if (wildcardCountsInPath !== (this.props?.pathParameterValues || []).length) {
throw new Error('The number of wildcards in the path does not match the number of path pathParameterValues.');
}

const restApiArn = this.restApi.arnForExecuteApi(
this.props?.method,
this.props?.path || '/',
this.props?.stage || this.restApi.deploymentStage.stageName,
);
return {
...(this.props ? bindBaseTargetConfig(this.props) : {}),
arn: restApiArn,
role: this.props?.eventRole || singletonEventRole(this.restApi, [new iam.PolicyStatement({
resources: [restApiArn],
actions: [
'execute-api:Invoke',
'execute-api:ManageConnections',
],
})]),
deadLetterConfig: this.props?.deadLetterQueue && { arn: this.props.deadLetterQueue?.queueArn },
input: this.props?.postBody,
targetResource: this.restApi,
httpParameters: {
headerParameters: this.props?.headerParameters,
queryStringParameters: this.props?.queryStringParameters,
pathParameterValues: this.props?.pathParameterValues,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we not validate pathParameterValues.length agains the number of * in the path ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know if it would be better to validate with CDK or wait for Cloudformation to handle the errors naturally at runtime.
(If it is documented somewhere, I would appreciate it if you could let me know.)
I have implemented validation as you mentioned.

},
};
}

}

1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-events-targets/lib/index.ts
Expand Up @@ -12,4 +12,5 @@ export * from './state-machine';
export * from './kinesis-stream';
export * from './log-group';
export * from './kinesis-firehose-stream';
export * from './api-gateway';
export * from './util';
5 changes: 5 additions & 0 deletions packages/@aws-cdk/aws-events-targets/package.json
Expand Up @@ -83,6 +83,8 @@
"@aws-cdk/assert-internal": "0.0.0"
},
"dependencies": {
"@aws-cdk/aws-apigateway": "0.0.0",
"@aws-cdk/aws-batch": "0.0.0",
"@aws-cdk/aws-codebuild": "0.0.0",
"@aws-cdk/aws-codepipeline": "0.0.0",
"@aws-cdk/aws-ec2": "0.0.0",
Expand All @@ -103,6 +105,8 @@
},
"homepage": "https://github.com/aws/aws-cdk",
"peerDependencies": {
"@aws-cdk/aws-apigateway": "0.0.0",
"@aws-cdk/aws-batch": "0.0.0",
"@aws-cdk/aws-codebuild": "0.0.0",
"@aws-cdk/aws-codepipeline": "0.0.0",
"@aws-cdk/aws-ec2": "0.0.0",
Expand Down Expand Up @@ -131,6 +135,7 @@
"docs-public-apis:@aws-cdk/aws-events-targets.SfnStateMachine.machine",
"docs-public-apis:@aws-cdk/aws-events-targets.SnsTopic.topic",
"docs-public-apis:@aws-cdk/aws-events-targets.SqsQueue.queue",
"docs-public-apis:@aws-cdk/aws-events-targets.ApiGateway.restApi",
"docs-public-apis:@aws-cdk/aws-events-targets.ContainerOverride",
"props-default-doc:@aws-cdk/aws-events-targets.ContainerOverride.environment",
"props-default-doc:@aws-cdk/aws-events-targets.EcsTaskProps.containerOverrides"
Expand Down