-
Notifications
You must be signed in to change notification settings - Fork 249
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(aws-openapigateway-lambda): update construct to allow specifying an inline api definition #1190
Merged
aws-solutions-constructs-team
merged 14 commits into
awslabs:main
from
indieisaconcept:feat/apigateway-inline-asset
Sep 11, 2024
Merged
feat(aws-openapigateway-lambda): update construct to allow specifying an inline api definition #1190
Changes from 12 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
966f511
feat(aws-openapigateway-lambda): update construct to allow specifying…
f5b8a32
Merge branch 'main' into feat/apigateway-inline-asset
indieisaconcept 2de0bfa
fix: amend typo in comment
indieisaconcept b4a1065
Merge branch 'main' into feat/apigateway-inline-asset
indieisaconcept 8d3afeb
Constructs Team Updates
biffgaut f57f5e0
Final edits (new tests, var name changes)
biffgaut abe6882
Merge branch 'main' into feat/apigateway-inline-asset
aws-solutions-constructs-team 8267121
Sync README
biffgaut 0ec5439
Change name back
biffgaut 15f7f20
Comment on the export
biffgaut a45c703
Change name to apiDefinitionJson
biffgaut b0d27f1
Address comments
biffgaut 584a215
trailing whitespace
biffgaut 52fbcad
Build tweaks
biffgaut File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
185 changes: 185 additions & 0 deletions
185
source/patterns/@aws-solutions-constructs/aws-openapigateway-lambda/lib/openapi-helper.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
/** | ||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance | ||
* with the License. A copy of the License is located at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES | ||
* OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions | ||
* and limitations under the License. | ||
*/ | ||
|
||
/* | ||
* The functions found here in the core library are for internal use and can be changed | ||
* or removed outside of a major release. We recommend against calling them directly from client code. | ||
*/ | ||
|
||
/* | ||
* This file is core openapi functionality and should ideally be in the core library. Since | ||
* that causes a circular reference with the resources library we have left it here for now | ||
* in the interest of getting these updates out faster | ||
*/ | ||
|
||
import * as apigateway from 'aws-cdk-lib/aws-apigateway'; | ||
import * as lambda from 'aws-cdk-lib/aws-lambda'; | ||
import * as s3 from 'aws-cdk-lib/aws-s3'; | ||
import { Asset } from 'aws-cdk-lib/aws-s3-assets'; | ||
import { Aws, Duration } from "aws-cdk-lib"; | ||
import { Construct } from 'constructs'; | ||
import * as resources from '@aws-solutions-constructs/resources'; | ||
|
||
/** | ||
* The ApiIntegration interface is used to correlate a user-specified id with either a existing lambda function or set of lambda props. | ||
* | ||
* See the 'Overview of how the OpenAPI file transformation works' section of the README.md for more details on its usage. | ||
*/ | ||
export interface ApiIntegration { | ||
/** | ||
* Id of the ApiIntegration, used to correlate this lambda function to the api integration in the open api definition. | ||
* | ||
* Note this is not a CDK Construct ID, and is instead a client defined string used to map the resolved lambda resource with the OpenAPI definition. | ||
*/ | ||
readonly id: string; | ||
/** | ||
* The Lambda function to associate with the API method in the OpenAPI file matched by id. | ||
* | ||
* One and only one of existingLambdaObj or lambdaFunctionProps must be specified, any other combination will cause an error. | ||
*/ | ||
readonly existingLambdaObj?: lambda.Function; | ||
/** | ||
* Properties for the Lambda function to create and associate with the API method in the OpenAPI file matched by id. | ||
* | ||
* One and only one of existingLambdaObj or lambdaFunctionProps must be specified, any other combination will cause an error. | ||
*/ | ||
readonly lambdaFunctionProps?: lambda.FunctionProps; | ||
} | ||
|
||
/** | ||
* Helper object to map an ApiIntegration id to its resolved lambda.Function. This type is exposed as a property on the instantiated construct. | ||
*/ | ||
export interface ApiLambdaFunction { | ||
/** | ||
* Id of the ApiIntegration, used to correlate this lambda function to the api integration in the open api definition. | ||
*/ | ||
readonly id: string; | ||
/** | ||
* The instantiated lambda.Function. | ||
*/ | ||
readonly lambdaFunction: lambda.Function; | ||
} | ||
|
||
export interface OpenApiProps { | ||
readonly apiDefinitionAsset?: Asset, | ||
readonly apiJsonDefinition?: any, | ||
readonly apiDefinitionBucket?: s3.IBucket, | ||
readonly apiDefinitionKey?: string, | ||
readonly apiIntegrations: ApiIntegration[] | ||
} | ||
|
||
/** | ||
* @internal This is an internal core function and should not be called directly by Solutions Constructs clients. | ||
*/ | ||
export function CheckOpenApiProps(props: OpenApiProps) { | ||
|
||
let errorMessages = ''; | ||
let errorFound = false; | ||
|
||
if ((props.apiDefinitionBucket && !props.apiDefinitionKey) || (!props.apiDefinitionBucket && props.apiDefinitionKey)) { | ||
errorMessages += 'apiDefinitionBucket and apiDefinitionKey must be specified together.\n'; | ||
errorFound = true; | ||
} | ||
|
||
const definitionCount: number = | ||
(props.apiDefinitionAsset ? 1 : 0) + | ||
(props.apiDefinitionBucket ? 1 : 0) + | ||
(props.apiJsonDefinition ? 1 : 0); | ||
|
||
if (definitionCount !== 1) { | ||
errorMessages += 'Exactly one of apiDefinitionAsset, apiInlineDefinition or (apiDefinitionBucket/apiDefinitionKey) must be provided\n'; | ||
errorFound = true; | ||
} | ||
|
||
if (props.apiIntegrations === undefined || props.apiIntegrations.length < 1) { | ||
errorMessages += 'At least one ApiIntegration must be specified in the apiIntegrations property\n'; | ||
errorFound = true; | ||
} | ||
|
||
if (errorFound) { | ||
throw new Error(errorMessages); | ||
} | ||
|
||
} | ||
|
||
export interface ObtainApiDefinitionProps { | ||
readonly tokenToFunctionMap: ApiLambdaFunction[], | ||
readonly apiDefinitionBucket?: s3.IBucket, | ||
readonly apiDefinitionKey?: string, | ||
readonly apiDefinitionAsset?: Asset, | ||
readonly apiJsonDefinition?: any, | ||
readonly internalTransformTimeout?: Duration, | ||
readonly internalTransformMemorySize?: number | ||
} | ||
|
||
/** | ||
* @internal This is an internal core function and should not be called directly by Solutions Constructs clients. | ||
*/ | ||
export function ObtainApiDefinition(scope: Construct, props: ObtainApiDefinitionProps): apigateway.ApiDefinition { | ||
const apiRawInlineSpec = props.apiJsonDefinition; | ||
const meldedDefinitionBucket = props.apiDefinitionBucket ?? props.apiDefinitionAsset?.bucket; | ||
const meldedDefinitionKey = props.apiDefinitionKey ?? props.apiDefinitionAsset?.s3ObjectKey; | ||
|
||
// Map each id and lambda function pair to the required format for the template writer custom resource | ||
const apiIntegrationUris = props.tokenToFunctionMap.map(apiLambdaFunction => { | ||
// the placeholder string that will be replaced in the OpenAPI Definition | ||
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 uriResolvedValue = `arn:${Aws.PARTITION}:apigateway:${Aws.REGION}:lambda:path/2015-03-31/functions/${apiLambdaFunction.lambdaFunction.functionArn}/invocations`; | ||
|
||
return { | ||
id: uriPlaceholderString, | ||
value: uriResolvedValue | ||
}; | ||
}); | ||
|
||
let apiDefinitionWriter: resources.CreateTemplateWriterResponse | undefined; | ||
let newApiDefinition: apigateway.ApiDefinition | undefined; | ||
|
||
if (props.apiDefinitionAsset || props.apiDefinitionBucket) { | ||
// This custom resource will overwrite the string placeholders in the openapi definition with the resolved values of the lambda URIs | ||
apiDefinitionWriter = resources.createTemplateWriterCustomResource(scope, 'Api', { | ||
// CheckOpenapiProps() has confirmed the existence of these values | ||
templateBucket: meldedDefinitionBucket!, | ||
templateKey: meldedDefinitionKey!, | ||
templateValues: apiIntegrationUris, | ||
timeout: props.internalTransformTimeout ?? Duration.minutes(1), | ||
memorySize: props.internalTransformMemorySize ?? 1024 | ||
}); | ||
|
||
newApiDefinition = apigateway.ApiDefinition.fromBucket( | ||
apiDefinitionWriter.s3Bucket, | ||
apiDefinitionWriter.s3Key | ||
); | ||
} else if (apiRawInlineSpec) { | ||
const apiInlineSpec = new apigateway.InlineApiDefinition(apiRawInlineSpec); | ||
newApiDefinition = InlineTemplateWriter(apiInlineSpec.bind(scope), apiIntegrationUris); | ||
} else { | ||
throw new Error("No definition provided (this code should be unreachable)"); | ||
} | ||
|
||
return newApiDefinition!; | ||
} | ||
|
||
function InlineTemplateWriter({ inlineDefinition }: apigateway.ApiDefinitionConfig, templateValues: resources.TemplateValue[]) { | ||
let template = JSON.stringify(inlineDefinition); | ||
|
||
// This replicates logic in the template writer custom resource (resources/lib/template-writer-custom-resource/index.ts), | ||
// any logic changes should be made to both locations every time | ||
templateValues.forEach((templateValue) => { | ||
template = template?.replace(new RegExp(templateValue.id, 'g'), templateValue.value); | ||
}); | ||
|
||
return new apigateway.InlineApiDefinition(JSON.parse(template)); | ||
} |
16 changes: 16 additions & 0 deletions
16
...snapshot/asset.654d49d4ea47a6be417d57b94dc0310933d0e971a3e48a3080c3e48487af3e50/index.mjs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
export const handler = async (event) => { | ||
switch (event.httpMethod) { | ||
case 'POST': | ||
return { | ||
statusCode: 200, | ||
body: JSON.stringify({"message": "successfully handled POST from photos lambda"}) | ||
}; | ||
case 'GET': | ||
return { | ||
statusCode: 200, | ||
body: JSON.stringify({"message": "successfully handled GET from photos lambda"}) | ||
}; | ||
default: | ||
throw new Error(`cannot handle httpMethod: ${event.httpMethod}`); | ||
} | ||
}; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps this isn't needed since
apiRawInlineSpec
is technically already expected to be a JSON object. Unless you are relying on the initial assignment to trigger validation, which would make sense in this instance. Non-and empty objects would throw.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, code is redundant to the check already done in CheckOpenapiProps. But including it makes the code explicit, these steps aren't just executing because it is "not something else", it is executing because it IS "this". Putting that in code is better than a comment - even at the cost of a couple clock ticks.