Skip to content

Commit f746a8d

Browse files
committedJul 23, 2024
initial commit
0 parents  commit f746a8d

15 files changed

+5201
-0
lines changed
 

‎.gitignore

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
*.js
2+
!jest.config.js
3+
*.d.ts
4+
node_modules
5+
6+
# CDK asset staging directory
7+
.cdk.staging
8+
cdk.out

‎.npmignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
*.ts
2+
!*.d.ts
3+
4+
# CDK asset staging directory
5+
.cdk.staging
6+
cdk.out

‎README.md

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Welcome to your CDK TypeScript project
2+
3+
This is a blank project for CDK development with TypeScript.
4+
5+
The `cdk.json` file tells the CDK Toolkit how to execute your app.
6+
7+
## Useful commands
8+
9+
* `npm run build` compile typescript to js
10+
* `npm run watch` watch for changes and compile
11+
* `npm run test` perform the jest unit tests
12+
* `npx cdk deploy` deploy this stack to your default AWS account/region
13+
* `npx cdk diff` compare deployed stack with current state
14+
* `npx cdk synth` emits the synthesized CloudFormation template
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/env node
2+
import "source-map-support/register";
3+
import { AwsSolutionsChecks } from "cdk-nag";
4+
import { App, Aspects } from "aws-cdk-lib";
5+
import { ApiStackV1 } from "../lib/apiStackV1";
6+
import { ApiStackV2 } from "../lib/apiStackV2";
7+
import { CustomDomainRouterStack } from "../lib/customDomainRouterStack";
8+
9+
const app = new App();
10+
const customDomainRouterStack = new CustomDomainRouterStack(
11+
app,
12+
"CustomDomainRouterStack"
13+
);
14+
new ApiStackV1(app, "ApiStackV1", {
15+
customDomainName: customDomainRouterStack.apiGatewayCustomDomainName,
16+
});
17+
new ApiStackV2(app, "ApiStackV2", {
18+
customDomainName: customDomainRouterStack.apiGatewayCustomDomainName,
19+
});
20+
21+
// Aspects.of(app).add(new AwsSolutionsChecks());

‎cdk.json

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
{
2+
"app": "npx ts-node --prefer-ts-exts bin/amazon-api-gateway-versioning-custom-domain.ts",
3+
"watch": {
4+
"include": [
5+
"**"
6+
],
7+
"exclude": [
8+
"README.md",
9+
"cdk*.json",
10+
"**/*.d.ts",
11+
"**/*.js",
12+
"tsconfig.json",
13+
"package*.json",
14+
"yarn.lock",
15+
"node_modules",
16+
"test"
17+
]
18+
},
19+
"context": {
20+
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
21+
"@aws-cdk/core:checkSecretUsage": true,
22+
"@aws-cdk/core:target-partitions": [
23+
"aws",
24+
"aws-cn"
25+
],
26+
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
27+
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
28+
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
29+
"@aws-cdk/aws-iam:minimizePolicies": true,
30+
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
31+
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
32+
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
33+
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
34+
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
35+
"@aws-cdk/core:enablePartitionLiterals": true,
36+
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
37+
"@aws-cdk/aws-iam:standardizedServicePrincipals": true,
38+
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
39+
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
40+
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
41+
"@aws-cdk/aws-route53-patters:useCertificate": true,
42+
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
43+
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
44+
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
45+
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
46+
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
47+
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
48+
"@aws-cdk/aws-redshift:columnId": true,
49+
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
50+
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
51+
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
52+
"@aws-cdk/aws-kms:aliasNameRef": true,
53+
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
54+
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
55+
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
56+
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
57+
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
58+
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
59+
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
60+
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
61+
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
62+
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
63+
"@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
64+
"@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
65+
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
66+
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true,
67+
"@aws-cdk/aws-eks:nodegroupNameAttribute": true,
68+
"@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true,
69+
"@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
70+
"@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false,
71+
"@aws-cdk/aws-stepfunctions-tasks:ecsReduceRunTaskPermissions": true
72+
}
73+
}

‎jest.config.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module.exports = {
2+
testEnvironment: 'node',
3+
roots: ['<rootDir>/test'],
4+
testMatch: ['**/*.test.ts'],
5+
transform: {
6+
'^.+\\.tsx?$': 'ts-jest'
7+
}
8+
};

‎lambdas/v1/add-numbers-v1/index.ts

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { Logger } from "@aws-lambda-powertools/logger";
2+
import { Tracer } from "@aws-lambda-powertools/tracer";
3+
import type { LambdaInterface } from "@aws-lambda-powertools/commons/types";
4+
import {
5+
APIGatewayProxyEvent,
6+
APIGatewayProxyResult,
7+
Context,
8+
} from "aws-lambda";
9+
10+
const logger = new Logger();
11+
const tracer = new Tracer();
12+
13+
class Lambda implements LambdaInterface {
14+
@logger.injectLambdaContext({ logEvent: true })
15+
@tracer.captureLambdaHandler()
16+
public async handler(
17+
event: APIGatewayProxyEvent,
18+
_context: Context
19+
): Promise<APIGatewayProxyResult> {
20+
var body = JSON.parse(event.body ?? "");
21+
22+
const num1 = body.num1;
23+
const num2 = body.num2;
24+
25+
logger.info(
26+
`--- SumRequest received --- num1: ${num1} --- num2: ${num2} ---`
27+
);
28+
29+
const sumResult = num1 + num2;
30+
31+
logger.debug(`--- Calculation result: ${sumResult} ---`);
32+
33+
const apiResponse = {
34+
result: sumResult,
35+
};
36+
37+
return {
38+
statusCode: 200,
39+
body: JSON.stringify(apiResponse),
40+
};
41+
}
42+
}
43+
44+
const addNumbersV1 = new Lambda();
45+
export const handler = addNumbersV1.handler.bind(addNumbersV1);

‎lambdas/v2/add-numbers-v2/index.ts

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { Logger } from "@aws-lambda-powertools/logger";
2+
import { parser } from "@aws-lambda-powertools/parser";
3+
import { Tracer } from "@aws-lambda-powertools/tracer";
4+
import type { LambdaInterface } from "@aws-lambda-powertools/commons/types";
5+
import { APIGatewayProxyResult, Context } from "aws-lambda";
6+
import { z } from "zod";
7+
import { ApiGatewayEnvelope } from "@aws-lambda-powertools/parser/envelopes";
8+
import { ParsedResult } from "@aws-lambda-powertools/parser/types";
9+
10+
const logger = new Logger();
11+
const tracer = new Tracer();
12+
13+
const sumRequestSchema = z.object({
14+
num1: z.number(),
15+
num2: z.number(),
16+
});
17+
18+
type SumRequest = z.infer<typeof sumRequestSchema>;
19+
20+
class Lambda implements LambdaInterface {
21+
@logger.injectLambdaContext({ logEvent: true })
22+
@tracer.captureLambdaHandler()
23+
@parser({
24+
schema: sumRequestSchema,
25+
envelope: ApiGatewayEnvelope,
26+
safeParse: true,
27+
})
28+
public async handler(
29+
event: ParsedResult<SumRequest>,
30+
_context: Context
31+
): Promise<APIGatewayProxyResult> {
32+
if (event.success) {
33+
var validatedEvent = event.data as SumRequest;
34+
35+
const num1 = validatedEvent.num1;
36+
const num2 = validatedEvent.num2;
37+
38+
logger.info(
39+
`--- SumRequest received --- num1: ${num1} --- num2: ${num2} ---`
40+
);
41+
42+
const sumResult = num1 + num2;
43+
44+
logger.debug(`--- Calculation result: ${sumResult} ---`);
45+
46+
const apiResponse = {
47+
result: sumResult,
48+
};
49+
50+
return {
51+
statusCode: 200,
52+
body: JSON.stringify(apiResponse),
53+
};
54+
} else {
55+
logger.error(`--- Validation Error --- ${event.error.message} ---`);
56+
57+
return {
58+
statusCode: 400,
59+
body: JSON.stringify({ error: event.error.message }),
60+
};
61+
}
62+
}
63+
}
64+
65+
const addNumbersV2 = new Lambda();
66+
export const handler = addNumbersV2.handler.bind(addNumbersV2);

‎lib/apiStackV1.ts

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { CfnOutput, Stack, StackProps } from "aws-cdk-lib";
2+
import {
3+
IDomainName,
4+
LambdaRestApi,
5+
MethodLoggingLevel,
6+
} from "aws-cdk-lib/aws-apigateway";
7+
import { Construct } from "constructs";
8+
import { NodejsFunctionWithRole } from "./constructs/nodejsFunctionWithRole";
9+
import path = require("path");
10+
import { CfnApiMapping } from "aws-cdk-lib/aws-apigatewayv2";
11+
12+
export interface ApiStackV1StackProps extends StackProps {
13+
customDomainName: IDomainName;
14+
}
15+
16+
export class ApiStackV1 extends Stack {
17+
lambdaRestApi: LambdaRestApi;
18+
19+
constructor(scope: Construct, id: string, props: ApiStackV1StackProps) {
20+
super(scope, id);
21+
22+
const addNumbersV1 = new NodejsFunctionWithRole(this, "AddNumbersV1", {
23+
entry: `${path.resolve(__dirname)}/../lambdas/v1/add-numbers-v1/index.ts`,
24+
environment: {},
25+
});
26+
27+
this.lambdaRestApi = new LambdaRestApi(this, "LambdaRestApiV1", {
28+
handler: addNumbersV1.function,
29+
deployOptions: {
30+
stageName: "api",
31+
tracingEnabled: true,
32+
loggingLevel: MethodLoggingLevel.INFO,
33+
},
34+
proxy: true,
35+
});
36+
37+
var apiMapping = new CfnApiMapping(this, "ApiMapping", {
38+
apiId: this.lambdaRestApi.restApiId,
39+
domainName: props.customDomainName.domainName,
40+
stage: "api",
41+
apiMappingKey: "api/v1",
42+
});
43+
44+
apiMapping.node.addDependency(this.lambdaRestApi);
45+
46+
new CfnOutput(this, "ApiV1URL", {
47+
value: `https://${this.lambdaRestApi.url}`,
48+
});
49+
}
50+
}

‎lib/apiStackV2.ts

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { CfnOutput, Stack, StackProps } from "aws-cdk-lib";
2+
import {
3+
IDomainName,
4+
LambdaRestApi,
5+
MethodLoggingLevel,
6+
} from "aws-cdk-lib/aws-apigateway";
7+
import { Construct } from "constructs";
8+
import { NodejsFunctionWithRole } from "./constructs/nodejsFunctionWithRole";
9+
import path = require("path");
10+
import { CfnApiMapping } from "aws-cdk-lib/aws-apigatewayv2";
11+
12+
export interface ApiStackV2StackProps extends StackProps {
13+
customDomainName: IDomainName;
14+
}
15+
16+
export class ApiStackV2 extends Stack {
17+
lambdaRestApi: LambdaRestApi;
18+
19+
constructor(scope: Construct, id: string, props: ApiStackV2StackProps) {
20+
super(scope, id);
21+
22+
const addNumbersV2 = new NodejsFunctionWithRole(this, "AddNumbersV2", {
23+
entry: `${path.resolve(__dirname)}/../lambdas/v2/add-numbers-v2/index.ts`,
24+
environment: {},
25+
});
26+
27+
this.lambdaRestApi = new LambdaRestApi(this, "LambdaRestApiV2", {
28+
handler: addNumbersV2.function,
29+
deployOptions: {
30+
stageName: "api",
31+
tracingEnabled: true,
32+
loggingLevel: MethodLoggingLevel.INFO,
33+
},
34+
proxy: true,
35+
});
36+
37+
var apiMapping = new CfnApiMapping(this, "ApiMapping", {
38+
apiId: this.lambdaRestApi.restApiId,
39+
domainName: props.customDomainName.domainName,
40+
stage: "api",
41+
apiMappingKey: "api/v2",
42+
});
43+
44+
apiMapping.node.addDependency(this.lambdaRestApi);
45+
46+
new CfnOutput(this, "ApiV2URL", {
47+
value: `https://${this.lambdaRestApi.url}`,
48+
});
49+
}
50+
}
+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { Aws, Duration } from "aws-cdk-lib";
2+
import {
3+
Effect,
4+
IRole,
5+
ManagedPolicy,
6+
PolicyStatement,
7+
Role,
8+
ServicePrincipal,
9+
} from "aws-cdk-lib/aws-iam";
10+
import {
11+
IFunction,
12+
Runtime,
13+
Tracing,
14+
Architecture,
15+
LayerVersion,
16+
} from "aws-cdk-lib/aws-lambda";
17+
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";
18+
import { Construct } from "constructs";
19+
20+
export interface NodejsFunctionWithRoleProps {
21+
entry: string;
22+
environment?: { [key: string]: string };
23+
timeout?: Duration;
24+
memorySize?: number;
25+
retryAttempts?: number;
26+
}
27+
28+
export class NodejsFunctionWithRole extends Construct {
29+
role: IRole;
30+
function: IFunction;
31+
32+
constructor(
33+
scope: Construct,
34+
id: string,
35+
props: NodejsFunctionWithRoleProps
36+
) {
37+
super(scope, id);
38+
39+
this.role = new Role(this, "Role", {
40+
assumedBy: new ServicePrincipal("lambda.amazonaws.com"),
41+
});
42+
43+
// Add Cloudwatch Logging Permissions
44+
this.role.addManagedPolicy(
45+
new ManagedPolicy(this, "FunctionBasePolicy", {
46+
statements: [
47+
new PolicyStatement({
48+
sid: "AllowCloudWatchLogs",
49+
effect: Effect.ALLOW,
50+
resources: ["*"],
51+
actions: [
52+
"logs:CreateLogGroup",
53+
"logs:CreateLogStream",
54+
"logs:PutLogEvents",
55+
],
56+
}),
57+
new PolicyStatement({
58+
sid: "AllowXRayAccess",
59+
effect: Effect.ALLOW,
60+
actions: [
61+
"xray:PutTraceSegments",
62+
"xray:PutTelemetryRecords",
63+
"xray:GetSamplingRules",
64+
"xray:GetSamplingTargets",
65+
"xray:GetSamplingStatisticSummaries",
66+
],
67+
resources: ["*"],
68+
}),
69+
],
70+
})
71+
);
72+
73+
this.function = new NodejsFunction(this, "Function", {
74+
entry: props.entry,
75+
runtime: Runtime.NODEJS_20_X,
76+
architecture: Architecture.X86_64,
77+
role: this.role,
78+
tracing: Tracing.ACTIVE,
79+
timeout: props.timeout ?? Duration.seconds(4),
80+
memorySize: props.memorySize ?? 128,
81+
environment: {
82+
POWERTOOLS_SERVICE_NAME: id,
83+
POWERTOOLS_TRACER_CAPTURE_HTTPS_REQUESTS: "true",
84+
POWERTOOLS_LOGGER_LOG_EVENT: "true",
85+
LOG_LEVEL: "INFO",
86+
...props.environment,
87+
},
88+
layers: [
89+
LayerVersion.fromLayerVersionArn(
90+
this,
91+
"PowerToolsLayer",
92+
`arn:aws:lambda:${Aws.REGION}:094274105915:layer:AWSLambdaPowertoolsTypeScriptV2:10`
93+
),
94+
],
95+
bundling: {
96+
minify: true,
97+
// Exclude @aws-sdk v3 since it's included in the NODEJS_20 runtime
98+
// Powertools will be provided via a layer
99+
externalModules: [
100+
"@aws-sdk/*",
101+
"@aws-lambda-powertools/logger",
102+
"@aws-lambda-powertools/tracer",
103+
],
104+
},
105+
});
106+
}
107+
}

‎lib/customDomainRouterStack.ts

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { CfnOutput, CfnParameter, Stack, StackProps } from "aws-cdk-lib";
2+
import {
3+
DomainName,
4+
EndpointType,
5+
IDomainName,
6+
} from "aws-cdk-lib/aws-apigateway";
7+
import {
8+
Certificate,
9+
CertificateValidation,
10+
} from "aws-cdk-lib/aws-certificatemanager";
11+
import { Construct } from "constructs";
12+
13+
export class CustomDomainRouterStack extends Stack {
14+
apiGatewayCustomDomainName: IDomainName;
15+
16+
constructor(scope: Construct, id: string, props?: StackProps) {
17+
super(scope, id, props);
18+
19+
const prerequisiteDomainName = new CfnParameter(
20+
this,
21+
"PrerequisiteDomainName",
22+
{
23+
type: "String",
24+
description: "A domain name that you own.",
25+
}
26+
);
27+
28+
const domainNameValue = `demo.api-gateway-custom-domain-versioning.${prerequisiteDomainName.valueAsString}`;
29+
30+
const certificate = new Certificate(this, "Certificate", {
31+
domainName: domainNameValue,
32+
validation: CertificateValidation.fromDns(),
33+
});
34+
35+
this.apiGatewayCustomDomainName = new DomainName(this, "DomainName", {
36+
certificate: certificate,
37+
endpointType: EndpointType.REGIONAL,
38+
domainName: domainNameValue,
39+
basePath: "api",
40+
});
41+
42+
new CfnOutput(this, "ApiGatewayCustomDomainName", {
43+
value: `https://${domainNameValue}/api`,
44+
});
45+
}
46+
}

‎package-lock.json

+4,639
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"name": "amazon-api-gateway-versioning-custom-domain",
3+
"version": "0.1.0",
4+
"bin": {
5+
"amazon-api-gateway-versioning-custom-domain": "bin/amazon-api-gateway-versioning-custom-domain.js"
6+
},
7+
"scripts": {
8+
"build": "tsc",
9+
"watch": "tsc -w",
10+
"test": "jest",
11+
"cdk": "cdk",
12+
"deploy-routing": "npx cdk deploy CustomDomainRouterStack --parameters PrerequisiteDomainName=coreyschnedl.com",
13+
"deploy-v1": "npx cdk -a \"npx ts-node --prefer-ts-exts bin/amazon-api-gateway-versioning-custom-domain.ts\" deploy ApiStackV1",
14+
"deploy-v2": "npx cdk -a \"npx ts-node --prefer-ts-exts bin/amazon-api-gateway-versioning-custom-domain.ts\" deploy ApiStackV2"
15+
},
16+
"devDependencies": {
17+
"@types/aws-lambda": "^8.10.141",
18+
"@types/jest": "^29.5.12",
19+
"@types/node": "20.14.9",
20+
"aws-cdk": "2.149.0",
21+
"jest": "^29.7.0",
22+
"ts-jest": "^29.1.5",
23+
"ts-node": "^10.9.2",
24+
"typescript": "~5.5.3"
25+
},
26+
"dependencies": {
27+
"@aws-lambda-powertools/logger": "^2.5.0",
28+
"@aws-lambda-powertools/parser": "^2.5.0-beta",
29+
"@aws-lambda-powertools/tracer": "^2.5.0",
30+
"@middy/core": "^5.4.5",
31+
"aws-cdk-lib": "2.149.0",
32+
"cdk-nag": "^2.28.163",
33+
"constructs": "^10.0.0",
34+
"source-map-support": "^0.5.21",
35+
"zod": "^3.23.8"
36+
}
37+
}

‎tsconfig.json

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2020",
4+
"module": "commonjs",
5+
"lib": [
6+
"es2020",
7+
"dom"
8+
],
9+
"declaration": true,
10+
"strict": true,
11+
"noImplicitAny": true,
12+
"strictNullChecks": true,
13+
"noImplicitThis": true,
14+
"alwaysStrict": true,
15+
"noUnusedLocals": false,
16+
"noUnusedParameters": false,
17+
"noImplicitReturns": true,
18+
"noFallthroughCasesInSwitch": false,
19+
"inlineSourceMap": true,
20+
"inlineSources": true,
21+
"experimentalDecorators": true,
22+
"strictPropertyInitialization": false,
23+
"typeRoots": [
24+
"./node_modules/@types"
25+
]
26+
},
27+
"exclude": [
28+
"node_modules",
29+
"cdk.out"
30+
]
31+
}

0 commit comments

Comments
 (0)
Please sign in to comment.