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(apigatewayv2): HttpRouteIntegration supports AWS services integrations #18154

Merged
merged 6 commits into from
Jan 12, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ export class HttpLambdaIntegration extends HttpRouteIntegration {
});

return {
type: HttpIntegrationType.LAMBDA_PROXY,
type: HttpIntegrationType.AWS_PROXY,
uri: this.handler.functionArn,
payloadFormatVersion: this.props.payloadFormatVersion ?? PayloadFormatVersion.VERSION_2_0,
parameterMapping: this.props.parameterMapping,
};
}
}
}
143 changes: 134 additions & 9 deletions packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { IRole } from '@aws-cdk/aws-iam';
import { Resource } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { CfnIntegration } from '../apigatewayv2.generated';
Expand All @@ -23,15 +24,96 @@ export interface IHttpIntegration extends IIntegration {
*/
export enum HttpIntegrationType {
/**
* Integration type is a Lambda proxy
* @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
* Integration type is an HTTP proxy.
*
* For integrating the route or method request with an HTTP endpoint, with the
* client request passed through as-is. This is also referred to as HTTP proxy
* integration. For HTTP API private integrations, use an HTTP_PROXY integration.
*
* @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-http.html
*/
LAMBDA_PROXY = 'AWS_PROXY',
HTTP_PROXY = 'HTTP_PROXY',

/**
* Integration type is an HTTP proxy
* Integration type is an AWS proxy.
*
* For integrating the route or method request with a Lambda function or other
* AWS service action. This integration is also referred to as a Lambda proxy
* integration.
*
* @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-aws-services.html
* @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
*/
HTTP_PROXY = 'HTTP_PROXY',
AWS_PROXY = 'AWS_PROXY',
jogold marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Supported integration subtypes
* @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-aws-services-reference.html
*/
export enum HttpIntegrationSubtype {
/**
* EventBridge PutEvents integration
*/
EVENTBRIDGE_PUT_EVENTS = 'EventBridge-PutEvents',
/**
* SQS SendMessage integration
*/
SQS_SEND_MESSAGE = 'SQS-SendMessage',
/**
* SQS ReceiveMessage integration,
*/
SQS_RECEIVE_MESSAGE = 'SQS-ReceiveMessage',
/**
* SQS DeleteMessage integration,
*/
SQS_DELETE_MESSAGE = 'SQS-DeleteMessage',
/**
* SQS PurgeQueue integration
*/
SQS_PURGE_QUEUE = 'SQS-PurgeQueue',
/**
* AppConfig GetConfiguration integration
*/
APPCONFIG_GET_CONFIGURATION = 'AppConfig-GetConfiguration',
/**
* Kinesis PutRecord integration
*/
KINESIS_PUT_RECORD = 'Kinesis-PutRecord',
/**
* Step Functions StartExecution integration
*/
STEPFUNCTIONS_START_EXECUTION = 'StepFunctions-StartExecution',
/**
* Step Functions StartSyncExecution integration
*/
STEPFUNCTIONS_START_SYNC_EXECUTION = 'StepFunctions-StartSyncExecution',
/**
* Step Functions StopExecution integration
*/
STEPFUNCTIONS_STOP_EXECUTION = 'StepFunctions-StopExecution',
}

/**
* Credentials used for AWS Service integrations.
*/
export abstract class IntegrationCredentials {
/**
* Use the specified role for integration requests
*/
public static fromRole(role: IRole): IntegrationCredentials {
return { credentialsArn: role.roleArn };
}

/** Use the calling user's identity to call the integration */
public static useCallerIdentity(): IntegrationCredentials {
return { credentialsArn: 'arn:aws:iam::*:user/*' };
}

/**
* The ARN of the credentials
*/
public abstract readonly credentialsArn: string;
}

/**
Expand Down Expand Up @@ -88,12 +170,23 @@ export interface HttpIntegrationProps {
*/
readonly integrationType: HttpIntegrationType;

/**
* Integration subtype.
*
* Used for AWS Service integrations, specifies the target of the integration.
*
* @default - none, required if no `integrationUri` is defined.
*/
readonly integrationSubtype?: HttpIntegrationSubtype;

/**
* Integration URI.
* This will be the function ARN in the case of `HttpIntegrationType.LAMBDA_PROXY`,
* This will be the function ARN in the case of `HttpIntegrationType.AWS_PROXY`,
* or HTTP URL in the case of `HttpIntegrationType.HTTP_PROXY`.
*
* @default - none, required if no `integrationSubtype` is defined.
*/
readonly integrationUri: string;
readonly integrationUri?: string;

/**
* The HTTP method to use when calling the underlying HTTP proxy
Expand All @@ -118,7 +211,7 @@ export interface HttpIntegrationProps {
/**
* The version of the payload format
* @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
* @default - defaults to latest in the case of HttpIntegrationType.LAMBDA_PROXY`, irrelevant otherwise.
* @default - defaults to latest in the case of HttpIntegrationType.AWS_PROXY`, irrelevant otherwise.
*/
readonly payloadFormatVersion?: PayloadFormatVersion;

Expand All @@ -135,6 +228,13 @@ export interface HttpIntegrationProps {
* @default undefined requests are sent to the backend unmodified
*/
readonly parameterMapping?: ParameterMapping;

/**
* The credentials with which to invoke the integration.
*
* @default - no credentials, use resource-based permissions on supported AWS services
*/
readonly credentials?: IntegrationCredentials;
}

/**
Expand All @@ -148,15 +248,22 @@ export class HttpIntegration extends Resource implements IHttpIntegration {

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

if (!props.integrationSubtype && !props.integrationUri) {
throw new Error('Either `integrationSubtype` or `integrationUri` must be specified.');
}

const integ = new CfnIntegration(this, 'Resource', {
apiId: props.httpApi.apiId,
integrationType: props.integrationType,
integrationSubtype: props.integrationSubtype,
integrationUri: props.integrationUri,
integrationMethod: props.method,
connectionId: props.connectionId,
connectionType: props.connectionType,
payloadFormatVersion: props.payloadFormatVersion?.version,
requestParameters: props.parameterMapping?.mappings,
credentialsArn: props.credentials?.credentialsArn,
});

if (props.secureServerName) {
Expand Down Expand Up @@ -214,13 +321,15 @@ export abstract class HttpRouteIntegration {
this.integration = new HttpIntegration(options.scope, this.id, {
httpApi: options.route.httpApi,
integrationType: config.type,
integrationSubtype: config.subtype,
integrationUri: config.uri,
method: config.method,
connectionId: config.connectionId,
connectionType: config.connectionType,
payloadFormatVersion: config.payloadFormatVersion,
secureServerName: config.secureServerName,
parameterMapping: config.parameterMapping,
credentials: config.credentials,
});
}
return { integrationId: this.integration.integrationId };
Expand All @@ -241,10 +350,19 @@ export interface HttpRouteIntegrationConfig {
*/
readonly type: HttpIntegrationType;

/**
* Integration subtype.
*
* @default - none, required if no `integrationUri` is defined.
*/
readonly subtype?: HttpIntegrationSubtype;

/**
* Integration URI
*
* @default - none, required if no `integrationSubtype` is defined.
*/
readonly uri: string;
readonly uri?: string;

/**
* The HTTP method that must be used to invoke the underlying proxy.
Expand Down Expand Up @@ -287,4 +405,11 @@ export interface HttpRouteIntegrationConfig {
* @default undefined requests are sent to the backend unmodified
*/
readonly parameterMapping?: ParameterMapping;

/**
* The credentials with which to invoke the integration.
*
* @default - no credentials, use resource-based permissions on supported AWS services
*/
readonly credentials?: IntegrationCredentials;
}
14 changes: 13 additions & 1 deletion packages/@aws-cdk/aws-apigatewayv2/lib/parameter-mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,18 @@ export class MappingValue implements IMappingValue {
* Represents a Parameter Mapping.
*/
export class ParameterMapping {

/**
* Creates a mapping from an object.
*/
public static fromObject(obj: { [key: string]: MappingValue }): ParameterMapping {
const mapping = new ParameterMapping();
for (const [k, m] of Object.entries(obj)) {
mapping.custom(k, m.value);
}
return mapping;
}

/**
* Represents all created parameter mappings.
*/
Expand Down Expand Up @@ -142,4 +154,4 @@ export class ParameterMapping {
this.mappings[key] = value;
return this;
}
}
}
56 changes: 54 additions & 2 deletions packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Template } from '@aws-cdk/assertions';
import { AccountPrincipal, Role } from '@aws-cdk/aws-iam';
import { AccountPrincipal, Role, ServicePrincipal } from '@aws-cdk/aws-iam';
import { Stack, App } from '@aws-cdk/core';
import {
HttpApi, HttpAuthorizer, HttpAuthorizerType, HttpConnectionType, HttpIntegrationType, HttpMethod, HttpRoute,
HttpRouteAuthorizerBindOptions, HttpRouteAuthorizerConfig, HttpRouteIntegrationConfig, HttpRouteKey, IHttpRouteAuthorizer, HttpRouteIntegration,
MappingValue,
ParameterMapping,
PayloadFormatVersion,
HttpIntegrationSubtype,
IntegrationCredentials,
} from '../../lib';

describe('HttpRoute', () => {
Expand Down Expand Up @@ -249,6 +251,56 @@ describe('HttpRoute', () => {
Template.fromStack(stack).resourceCountIs('AWS::ApiGatewayV2::VpcLink', 0);
});

test('configures AWS service integration correctly', () => {
// GIVEN
const stack = new Stack();
const httpApi = new HttpApi(stack, 'HttpApi');
const role = new Role(stack, 'Role', {
assumedBy: new ServicePrincipal('apigateway.amazonaws.com'),
});

class SqsSendMessageIntegration extends HttpRouteIntegration {
public bind(): HttpRouteIntegrationConfig {
return {
method: HttpMethod.ANY,
payloadFormatVersion: PayloadFormatVersion.VERSION_1_0,
type: HttpIntegrationType.AWS_PROXY,
subtype: HttpIntegrationSubtype.SQS_SEND_MESSAGE,
credentials: IntegrationCredentials.fromRole(role),
parameterMapping: ParameterMapping.fromObject({
QueueUrl: MappingValue.requestHeader('queueUrl'),
MessageBody: MappingValue.requestBody('message'),
}),
};
}
}

// WHEN
new HttpRoute(stack, 'HttpRoute', {
httpApi,
integration: new SqsSendMessageIntegration('SqsSendMessageIntegration'),
routeKey: HttpRouteKey.with('/sqs', HttpMethod.POST),
});

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', {
IntegrationType: 'AWS_PROXY',
IntegrationSubtype: 'SQS-SendMessage',
IntegrationMethod: 'ANY',
PayloadFormatVersion: '1.0',
CredentialsArn: {
'Fn::GetAtt': [
'Role1ABCC5F0',
'Arn',
],
},
RequestParameters: {
QueueUrl: '$request.header.queueUrl',
MessageBody: '$request.body.message',
},
});
});

test('can create route with an authorizer attached', () => {
const stack = new Stack();
const httpApi = new HttpApi(stack, 'HttpApi');
Expand Down Expand Up @@ -632,4 +684,4 @@ class SomeAuthorizerType implements IHttpRouteAuthorizer {
authorizationType: this.authorizationType,
};
}
}
}