Skip to content

Commit

Permalink
feat(apigatewayv2): HttpRouteIntegration supports AWS services integr…
Browse files Browse the repository at this point in the history
…ations (aws#18154)

Add support for integration subtype and credentials allowing to extend
`HttpRouteIntegration` to create integrations for AWS services.

See https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-aws-services.html

Extracted part of aws#16287 to make it more reviewer friendly.

BREAKING CHANGE: `HttpIntegrationType.LAMBDA_PROXY` has been renamed to `HttpIntegrationType.AWS_PROXY`


----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
jogold authored and TikiTDO committed Feb 21, 2022
1 parent 07a4cb3 commit 34ae776
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 14 deletions.
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',
}

/**
* 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,
};
}
}
}

0 comments on commit 34ae776

Please sign in to comment.