Skip to content

Commit

Permalink
Pass Function or Alias in same property
Browse files Browse the repository at this point in the history
  • Loading branch information
biffgaut committed Feb 18, 2025
1 parent e9c4704 commit 92b501f
Show file tree
Hide file tree
Showing 23 changed files with 177 additions and 202 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Construct } from 'constructs';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as logs from 'aws-cdk-lib/aws-logs';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as defaults from '@aws-solutions-constructs/core';
import { RestApiBaseProps } from 'aws-cdk-lib/aws-apigateway';
Expand Down Expand Up @@ -116,21 +117,37 @@ export class OpenApiGatewayToLambda extends Construct {
// store a counter to be able to uniquely name lambda functions to avoid naming collisions
let lambdaCounter = 0;

this.apiLambdaFunctions = props.apiIntegrations.map(apiIntegration => {
if (apiIntegration.existingLambdaObj && apiIntegration.lambdaFunctionProps) {
throw new Error(`Error - Cannot provide both lambdaFunctionProps and existingLambdaObj in an ApiIntegrationfor the api integration with id: ${apiIntegration.id}`);
}
if (apiIntegration.existingLambdaObj || apiIntegration.lambdaFunctionProps) {
// TODO: Should this functionality be moved to openapi-helper.ts?
this.apiLambdaFunctions = props.apiIntegrations.map(rawApiIntegration => {
// let updatedIntegration: ApiIntegration;
// if (rawApiIntegration.alternateType) {
// if ((rawApiIntegration.alternateType as lambda.Alias).aliasName) {
// updatedIntegration = {
// ...rawApiIntegration,
// existingFunctionAlias: rawApiIntegration.alternateType as lambda.Alias
// };
// } else {
// updatedIntegration = {
// ...rawApiIntegration,
// existingLambdaObj: rawApiIntegration.alternateType as lambda.Function
// };
// }
// } else {
// updatedIntegration = rawApiIntegration;
// }
if (rawApiIntegration.existingLambdaObj && this.isResourceAnAlias(rawApiIntegration.existingLambdaObj)) {
return {
id: apiIntegration.id,
lambdaFunction: defaults.buildLambdaFunction(this, {
existingLambdaObj: apiIntegration.existingLambdaObj,
lambdaFunctionProps: apiIntegration.lambdaFunctionProps
}, `${apiIntegration.id}ApiFunction${lambdaCounter++}`),
functionAlias: apiIntegration.existingFunctionAlias
id: rawApiIntegration.id,
functionAlias: rawApiIntegration.existingLambdaObj as lambda.Alias
};
} else {
throw new Error(`One of existingLambdaObj or lambdaFunctionProps must be specified for the api integration with id: ${apiIntegration.id}`);
return {
id: rawApiIntegration.id,
lambdaFunction: defaults.buildLambdaFunction(this, {
existingLambdaObj: rawApiIntegration.existingLambdaObj as lambda.Function,
lambdaFunctionProps: rawApiIntegration.lambdaFunctionProps
}, `${rawApiIntegration.id}ApiFunction${lambdaCounter++}`),
};
}
});

Expand All @@ -156,8 +173,11 @@ export class OpenApiGatewayToLambda extends Construct {
// Redeploy the API any time a decoupled (non-inline) API definition changes (from asset or s3 object)
this.apiGateway.latestDeployment?.addToLogicalId(props.apiDefinitionKey ?? props.apiDefinitionAsset?.s3ObjectKey);
this.apiLambdaFunctions.forEach(apiLambdaFunction => {
// We confirm upstream that one of these two values exists, so we can cast away Typescripts doubt
const targetInterface: lambda.IFunction = (apiLambdaFunction.lambdaFunction ?? apiLambdaFunction.functionAlias) as lambda.IFunction;

// Redeploy the API any time one of the lambda functions changes
this.apiGateway.latestDeployment?.addToLogicalId(apiLambdaFunction.lambdaFunction.functionArn);
this.apiGateway.latestDeployment?.addToLogicalId(targetInterface.functionArn);
if (apiLambdaFunction.functionAlias) {
// Grant APIGW invocation rights for each lambda function
apiLambdaFunction.functionAlias.addPermission(`${id}PermitAPIGInvocation`, {
Expand All @@ -166,11 +186,15 @@ export class OpenApiGatewayToLambda extends Construct {
});
} else {
// Grant APIGW invocation rights for each lambda function
apiLambdaFunction.lambdaFunction.addPermission(`${id}PermitAPIGInvocation`, {
targetInterface.addPermission(`${id}PermitAPIGInvocation`, {
principal: new iam.ServicePrincipal('apigateway.amazonaws.com'),
sourceArn: this.apiGateway.arnForExecuteApi('*')
});
}
});
}

private isResourceAnAlias(lambdaResource: lambda.Function | lambda.Alias): boolean {
return (lambdaResource as lambda.Alias).aliasName !== undefined;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,7 @@ export interface ApiIntegration {
*
* One and only one of existingLambdaObj or lambdaFunctionProps must be specified, any other combination will cause an error.
*/
readonly existingLambdaObj?: lambda.Function;
/**
* An Alias to the provided existingLambdbaObj that should be used in the integration with the API
*
* This is only valid if and existingLambdaObj is specified
*/
readonly existingFunctionAlias?: lambda.Alias;
readonly existingLambdaObj?: lambda.Function | lambda.Alias;
/**
* Properties for the Lambda function to create and associate with the API method in the OpenAPI file matched by id.
*
Expand All @@ -71,12 +65,10 @@ export interface ApiLambdaFunction {
*/
readonly id: string;
/**
* The instantiated lambda.Function.
*/
readonly lambdaFunction: lambda.Function;
/**
* Optional Alias to the function - if provided, it will be used instead of the function whereever possible.
* The function the API method will integrate with -
* Must be defined in lambdaFunction or functionAlias (but not both)
*/
readonly lambdaFunction?: lambda.Function;
readonly functionAlias?: lambda.Alias;
}

Expand Down Expand Up @@ -114,6 +106,20 @@ export function CheckOpenApiProps(props: OpenApiProps) {
if (props.apiIntegrations === undefined || props.apiIntegrations.length < 1) {
errorMessages += 'At least one ApiIntegration must be specified in the apiIntegrations property\n';
errorFound = true;
} else {
props.apiIntegrations.forEach((apiIntegration: ApiIntegration) => {
if (!apiIntegration.id) {
errorMessages += 'Each ApiIntegration must have a non-empty id property\n';
errorFound = true;
}
let functionDefCount = 0;
if (apiIntegration.lambdaFunctionProps) { functionDefCount++; }
if (apiIntegration.existingLambdaObj) { functionDefCount++; }
if (functionDefCount !== 1) {
errorMessages += `ApiIntegration id:${apiIntegration.id} must have exactly one of lambdaFunctionProps or existingLambdaObj\n`;
errorFound = true;
}
});
}

if (errorFound) {
Expand Down Expand Up @@ -146,7 +152,8 @@ export function ObtainApiDefinition(scope: Construct, props: ObtainApiDefinition
const uriPlaceholderString = apiLambdaFunction.id;
// the endpoint URI of the backing lambda function, as defined in the API Gateway extensions for OpenAPI here:
// https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-integration.html
const targetArn = apiLambdaFunction.functionAlias ? apiLambdaFunction.functionAlias.functionArn : apiLambdaFunction.lambdaFunction.functionArn;
// We know that either functionAlias or lambdaFunction must be defined, so we can use ! to satisfy Typescript
const targetArn = apiLambdaFunction.functionAlias ? apiLambdaFunction.functionAlias.functionArn : apiLambdaFunction.lambdaFunction!.functionArn;
const uriResolvedValue = `arn:${Aws.PARTITION}:apigateway:${Aws.REGION}:lambda:path/2015-03-31/functions/${targetArn}/invocations`;

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
"validateOnSynth": false,
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}",
"cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}",
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/612d2d6d1d4300f28e6d6d9d00dbf05384b632eb7e6ab071b251e70c6086594a.json",
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/90e62a6fcf8b3c3369d639d53ad7ae48d7a7b571c06fd65356e6c844754bb19d.json",
"requiresBootstrapStackVersion": 6,
"bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version",
"additionalDependencies": [
Expand Down Expand Up @@ -205,7 +205,7 @@
"/opilam-apiFromAssetLambdaFunctionAliases/OpenApiGatewayToLambda/SpecRestApi/Deployment/Resource": [
{
"type": "aws:cdk:logicalId",
"data": "OpenApiGatewayToLambdaSpecRestApiDeploymentC8BD0775114a8d1d0c9bf9b9d904a22f099d1d64"
"data": "OpenApiGatewayToLambdaSpecRestApiDeploymentC8BD077506cc115d2977eccc01a7a14e0a95492a"
}
],
"/opilam-apiFromAssetLambdaFunctionAliases/OpenApiGatewayToLambda/SpecRestApi/DeploymentStage.prod/Resource": [
Expand Down Expand Up @@ -255,15 +255,6 @@
"type": "aws:cdk:logicalId",
"data": "CheckBootstrapVersion"
}
],
"LambdaFunctionCurrentVersion7D5C86E363f0ff02d21229f6578ddc05f60b9083": [
{
"type": "aws:cdk:logicalId",
"data": "LambdaFunctionCurrentVersion7D5C86E363f0ff02d21229f6578ddc05f60b9083",
"trace": [
"!!DESTRUCTIVE_CHANGES: WILL_DESTROY"
]
}
]
},
"displayName": "opilam-apiFromAssetLambdaFunctionAliases"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,15 @@
}
}
},
"612d2d6d1d4300f28e6d6d9d00dbf05384b632eb7e6ab071b251e70c6086594a": {
"90e62a6fcf8b3c3369d639d53ad7ae48d7a7b571c06fd65356e6c844754bb19d": {
"source": {
"path": "opilam-apiFromAssetLambdaFunctionAliases.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "612d2d6d1d4300f28e6d6d9d00dbf05384b632eb7e6ab071b251e70c6086594a.json",
"objectKey": "90e62a6fcf8b3c3369d639d53ad7ae48d7a7b571c06fd65356e6c844754bb19d.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -839,7 +839,7 @@
}
}
},
"OpenApiGatewayToLambdaSpecRestApiDeploymentC8BD0775114a8d1d0c9bf9b9d904a22f099d1d64": {
"OpenApiGatewayToLambdaSpecRestApiDeploymentC8BD077506cc115d2977eccc01a7a14e0a95492a": {
"Type": "AWS::ApiGateway::Deployment",
"Properties": {
"Description": "Automatically created by the RestApi construct",
Expand Down Expand Up @@ -871,7 +871,7 @@
"Format": "{\"requestId\":\"$context.requestId\",\"ip\":\"$context.identity.sourceIp\",\"user\":\"$context.identity.user\",\"caller\":\"$context.identity.caller\",\"requestTime\":\"$context.requestTime\",\"httpMethod\":\"$context.httpMethod\",\"resourcePath\":\"$context.resourcePath\",\"status\":\"$context.status\",\"protocol\":\"$context.protocol\",\"responseLength\":\"$context.responseLength\"}"
},
"DeploymentId": {
"Ref": "OpenApiGatewayToLambdaSpecRestApiDeploymentC8BD0775114a8d1d0c9bf9b9d904a22f099d1d64"
"Ref": "OpenApiGatewayToLambdaSpecRestApiDeploymentC8BD077506cc115d2977eccc01a7a14e0a95492a"
},
"MethodSettings": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1231,7 +1231,7 @@
"format": "{\"requestId\":\"$context.requestId\",\"ip\":\"$context.identity.sourceIp\",\"user\":\"$context.identity.user\",\"caller\":\"$context.identity.caller\",\"requestTime\":\"$context.requestTime\",\"httpMethod\":\"$context.httpMethod\",\"resourcePath\":\"$context.resourcePath\",\"status\":\"$context.status\",\"protocol\":\"$context.protocol\",\"responseLength\":\"$context.responseLength\"}"
},
"deploymentId": {
"Ref": "OpenApiGatewayToLambdaSpecRestApiDeploymentC8BD0775114a8d1d0c9bf9b9d904a22f099d1d64"
"Ref": "OpenApiGatewayToLambdaSpecRestApiDeploymentC8BD077506cc115d2977eccc01a7a14e0a95492a"
},
"methodSettings": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,7 @@ new OpenApiGatewayToLambda(stack, 'OpenApiGatewayToLambda', {
apiIntegrations: [
{
id: 'MessagesHandler',
existingLambdaObj: messagesLambda,
existingFunctionAlias: messagesAlias
existingLambdaObj: messagesAlias
},
{
id: 'PhotosHandler',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ export const handler = async (event) => {
case 'POST':
return {
statusCode: 200,
body: JSON.stringify({"message": "successfully handled POST from messages lambda"})
body: JSON.stringify({"message": "NEW - successfully handled POST from messages lambda"})
};
case 'GET':
return {
statusCode: 200,
body: JSON.stringify({"message": "successfully handled GET from messages lambda"})
body: JSON.stringify({"message": "NEW - successfully handled GET from messages lambda"})
};
default:
throw new Error(`cannot handle httpMethod: ${event.httpMethod}`);
Expand Down

This file was deleted.

This file was deleted.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 92b501f

Please sign in to comment.