Skip to content

Commit

Permalink
feat(aws-apigatewayv2-integrations): Introduce L2 construct for Event…
Browse files Browse the repository at this point in the history
…Bridge-PutEvents AWS service integration

This is in reference to GitHub Issue aws#11947

BREAKING CHANGE: In order to propagate necessary configuration parameters to the AWS service integration type, the following breaking changes were introduced:
* **aws-apigatewayv2:** Added AWS_PROXY enum property to HttpIntegrationType
* **aws-apigatewayv2:** Added nullable integrationSubtype and nullable requestParameters types to HttpIntegrationProps
* **aws-apigatewayv2:** Changed integrationUri in HttpIntegrationProps to a nullable type as AWS service integrations do not require this field per the CloudFormation spec
* **aws-apigatewayv2:** Bubbled up nullable credentialsArn in HttpIntegrationProps
* **aws-apigatewayv2:** Modified constructor of HttpIntegration to throw an Error if AWS_PROXY parameters are misconfigured
* **aws-apigatewayv2:** Sorted properties in call to new CfnIntegration in constructor of HttpIntegration
* **aws-apigatewayv2:** Introduced private method renderRequestParameters to convert L2 requestParameters to CloudFormation compantible property names
* **aws-apigatewayv2:** Modified uri in HttpRouteIntegrationConfig to a nullable type as AWS service integrations do not require this field per the CloudFormation spec
* **aws-apigatewayv2:** Added nullable credentialsArn, nullable integrationSubtype, and nullable requestParameters in HttpRouteIntegrationConfig
  • Loading branch information
shankben committed Feb 12, 2021
1 parent 01a05db commit cc206d7
Show file tree
Hide file tree
Showing 13 changed files with 12,639 additions and 9,475 deletions.
26 changes: 26 additions & 0 deletions packages/@aws-cdk/aws-apigatewayv2-integrations/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,32 @@

Integrations connect a route to backend resources. HTTP APIs support Lambda proxy, AWS service, and HTTP proxy integrations. HTTP proxy integrations are also known as private integrations.

### AWS Service

You can integrate your HTTP API with AWS services by using first-class integrations. A first-class integration connects an HTTP API route to an AWS service API. When a client invokes a route that's backed by a first-class integration, API Gateway invokes an AWS service API for you.

At this time only the EventBridge PutEvents integration is supported. Each AWS service integration subtype expects a set of `requestParamters` as part of the configuration. Detailed information with respect to the AWS service integration subtypes and request parameter formats can be found at [Integration Subtype Reference](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-aws-services.html).

The following code configures a default HTTP API integration with the EventBridge service's PutEvents API call.

```ts
const eventBus = new EventBus(stack, 'EventBus');

const endpoint = new HttpApi(stack, 'EventBridgeProxyApi', {
defaultIntegration: new EventBridgeIntegration({
eventBus,
requestParameters: {
eventBusName: eventBus.eventBusName,
source: 'test',
detail: '$request.body.result',
detailType: '$request.body.description',
time: '$context.requestTimeEpoch',
},
}),
});
```


### Lambda

Lambda integrations enable integrating an HTTP API route with a Lambda function. When a client invokes the route, the
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {
AwsServiceIntegrationSubtype,
HttpConnectionType,
HttpIntegrationType,
HttpRouteIntegrationBindOptions,
HttpRouteIntegrationConfig,
IHttpRouteIntegration,
PayloadFormatVersion,
} from '@aws-cdk/aws-apigatewayv2';

/**
* The HTTP Private integration resource for HTTP API
*
* @internal
*/
export abstract class AwsServiceIntegration implements IHttpRouteIntegration {
protected connectionType = HttpConnectionType.INTERNET
protected integrationSubtype?: AwsServiceIntegrationSubtype;
protected integrationType = HttpIntegrationType.AWS_PROXY;
protected payloadFormatVersion = PayloadFormatVersion.VERSION_1_0; // 1.0 is required and is the only supported format

public abstract bind(options: HttpRouteIntegrationBindOptions): HttpRouteIntegrationConfig;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,11 @@ export interface HttpPrivateIntegrationOptions {
* @default HttpMethod.ANY
*/
readonly method?: HttpMethod;
}
}


/**
* Base options for AWS service integration
*/
export interface AwsServiceIntegrationOptions {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {
HttpRouteIntegrationBindOptions,
HttpRouteIntegrationConfig,
EventBridgeIntegrationRequestParameters,
AwsServiceIntegrationSubtype,
} from '@aws-cdk/aws-apigatewayv2';
import { PolicyStatement, Role, ServicePrincipal } from '@aws-cdk/aws-iam';
import { IEventBus } from '../../../aws-events';
import { AwsServiceIntegration } from './aws/integration';
import { AwsServiceIntegrationOptions } from './base-types';

/**
* Properties to initialize `EventBridgeIntegration`.
*/
export interface EventBridgeIntegrationProps extends AwsServiceIntegrationOptions {
/**
* The EventBridge API call to proxy to.
* @default AwsServiceIntegrationSubtype.EVENT_BRIDGE_PUT_EVENTS
*/
readonly integrationSubtype?: AwsServiceIntegrationSubtype;

/**
* The event bus to bind proxy to.
*/
readonly eventBus: IEventBus;

/**
* The event source.
*/
readonly eventSource?: string;

/**
* The EventBridge PutEvents request parameters.
* https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_PutEvents.html
*/
readonly requestParameters: EventBridgeIntegrationRequestParameters;
}

/**
* The EventBridge integration resource for HTTP API
*/
export class EventBridgeIntegration extends AwsServiceIntegration {
constructor(private readonly props: EventBridgeIntegrationProps) {
super();
}

public bind(options: HttpRouteIntegrationBindOptions): HttpRouteIntegrationConfig {
this.integrationSubtype = this.props.integrationSubtype ?? AwsServiceIntegrationSubtype.EVENT_BRIDGE_PUT_EVENTS;

const { scope } = options;

const role = new Role(scope, 'EventBridgeIntegrationRole', {
description: 'Role for API Gateway to publish Events',
assumedBy: new ServicePrincipal('apigateway.amazonaws.com'),
});

role.addToPolicy(new PolicyStatement({
actions: ['events:PutEvents'],
resources: [this.props.eventBus.eventBusArn],
conditions: (() => !('eventSource' in this.props) ? undefined : {
StringEquals: {
'events:source': this.props.eventSource,
},
})(),
}));

return {
payloadFormatVersion: this.payloadFormatVersion,
type: this.integrationType,
connectionType: this.connectionType,
credentialsArn: role.roleArn,
integrationSubtype: this.integrationSubtype,
requestParameters: this.props.requestParameters,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ export class HttpProxyIntegration implements IHttpRouteIntegration {
uri: this.props.url,
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './nlb';
export * from './service-discovery';
export * from './http-proxy';
export * from './lambda';
export * from './eventbridge';
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,4 @@ export class LambdaProxyIntegration implements IHttpRouteIntegration {
payloadFormatVersion: this.props.payloadFormatVersion ?? PayloadFormatVersion.VERSION_2_0,
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
{
"Resources": {
"EventBus7B8748AA": {
"Type": "AWS::Events::EventBus",
"Properties": {
"Name": "integeventbridgeproxyEventBus554075B3"
}
},
"EventBridgeProxyApi73E23498": {
"Type": "AWS::ApiGatewayV2::Api",
"Properties": {
"Name": "EventBridgeProxyApi",
"ProtocolType": "HTTP"
}
},
"EventBridgeProxyApiDefaultRouteEventBridgeIntegrationRole3A0AD809": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "apigateway.amazonaws.com"
}
}
],
"Version": "2012-10-17"
},
"Description": "Role for API Gateway to publish Events"
}
},
"EventBridgeProxyApiDefaultRouteEventBridgeIntegrationRoleDefaultPolicy3589E340": {
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyDocument": {
"Statement": [
{
"Action": "events:PutEvents",
"Effect": "Allow",
"Resource": {
"Fn::GetAtt": [
"EventBus7B8748AA",
"Arn"
]
}
}
],
"Version": "2012-10-17"
},
"PolicyName": "EventBridgeProxyApiDefaultRouteEventBridgeIntegrationRoleDefaultPolicy3589E340",
"Roles": [
{
"Ref": "EventBridgeProxyApiDefaultRouteEventBridgeIntegrationRole3A0AD809"
}
]
}
},
"EventBridgeProxyApiDefaultRoute7A237CAD": {
"Type": "AWS::ApiGatewayV2::Route",
"Properties": {
"ApiId": {
"Ref": "EventBridgeProxyApi73E23498"
},
"RouteKey": "$default",
"AuthorizationScopes": [],
"Target": {
"Fn::Join": [
"",
[
"integrations/",
{
"Ref": "EventBridgeProxyApiHttpIntegrationbdce00b62e57d880c36340c0f0df5a1d0AD4F597"
}
]
]
}
}
},
"EventBridgeProxyApiHttpIntegrationbdce00b62e57d880c36340c0f0df5a1d0AD4F597": {
"Type": "AWS::ApiGatewayV2::Integration",
"Properties": {
"ApiId": {
"Ref": "EventBridgeProxyApi73E23498"
},
"IntegrationType": "AWS_PROXY",
"ConnectionType": "INTERNET",
"CredentialsArn": {
"Fn::GetAtt": [
"EventBridgeProxyApiDefaultRouteEventBridgeIntegrationRole3A0AD809",
"Arn"
]
},
"IntegrationSubtype": "EventBridge-PutEvents",
"PayloadFormatVersion": "1.0",
"RequestParameters": {
"Detail": "$request.body.result",
"DetailType": "$request.body.description",
"EventBusName": {
"Ref": "EventBus7B8748AA"
},
"Source": "test",
"Time": "$context.requestTimeEpoch"
}
}
},
"EventBridgeProxyApiDefaultStage642846E1": {
"Type": "AWS::ApiGatewayV2::Stage",
"Properties": {
"ApiId": {
"Ref": "EventBridgeProxyApi73E23498"
},
"StageName": "$default",
"AutoDeploy": true
}
}
},
"Outputs": {
"Endpoint": {
"Value": {
"Fn::Join": [
"",
[
"https://",
{
"Ref": "EventBridgeProxyApi73E23498"
},
".execute-api.",
{
"Ref": "AWS::Region"
},
".",
{
"Ref": "AWS::URLSuffix"
},
"/"
]
]
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { HttpApi } from '@aws-cdk/aws-apigatewayv2';
import { App, CfnOutput, Stack } from '@aws-cdk/core';
import { EventBus } from '../../../aws-events';
import { EventBridgeIntegration } from '../../lib';

const app = new App();

const stack = new Stack(app, 'integ-eventbridge-proxy');

const eventBus = new EventBus(stack, 'EventBus');

const endpoint = new HttpApi(stack, 'EventBridgeProxyApi', {
defaultIntegration: new EventBridgeIntegration({
eventBus,
requestParameters: {
eventBusName: eventBus.eventBusName,
source: 'test',
detail: '$request.body.result',
detailType: '$request.body.description',
time: '$context.requestTimeEpoch',
},
}),
});

new CfnOutput(stack, 'Endpoint', {
value: endpoint.url!,
});
34 changes: 33 additions & 1 deletion packages/@aws-cdk/aws-apigatewayv2/lib/common/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,36 @@ export interface IIntegration extends IResource {
* @attribute
*/
readonly integrationId: string;
}
}

/**
* AWS service integration sub types
* https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-aws-services-reference.html
*/
export enum AwsServiceIntegrationSubtype {
EVENT_BRIDGE_PUT_EVENTS = 'EventBridge-PutEvents'
// AppConfig-GetConfiguration
// Kinesis-PutRecord
// SQS-DeleteMessage
// SQS-PurgeQueue
// SQS-ReceiveMessage
// SQS-SendMessage
// StepFunctions-StartExecution
// StepFunctions-StartSyncExecution
// StepFunctions-StopExecution
}

/**
* Integration request parameters for EventBridge
*/
export interface EventBridgeIntegrationRequestParameters {
readonly detail?: string;
readonly detailType?: string;
readonly eventBusName?: string;
readonly region?: string;
readonly resources?: string[];
readonly source?: string;
readonly time?: string;
}

export type AwsServiceIntegrationRequestParameters = EventBridgeIntegrationRequestParameters;
7 changes: 5 additions & 2 deletions packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,13 +283,16 @@ abstract class HttpApiBase extends Resource implements IHttpApi { // note that t
}

const integration = new HttpIntegration(this, `HttpIntegration-${configHash}`, {
connectionId: config.connectionId,
connectionType: config.connectionType,
credentialsArn: config.credentialsArn,
httpApi: this,
integrationSubtype: config.integrationSubtype,
integrationType: config.type,
integrationUri: config.uri,
method: config.method,
connectionId: config.connectionId,
connectionType: config.connectionType,
payloadFormatVersion: config.payloadFormatVersion,
requestParameters: config.requestParameters,
});
this.httpIntegrations[configHash] = integration;

Expand Down
Loading

0 comments on commit cc206d7

Please sign in to comment.