From 13ef803dc7143f516f3fb5865048fcb5a3de8d5a Mon Sep 17 00:00:00 2001 From: Matt <77928207+mattzcarey@users.noreply.github.com> Date: Sat, 26 Aug 2023 18:54:14 +0100 Subject: [PATCH] fix: arecords --- .gitignore | 1 + services/core/helpers/certificates.ts | 19 +++++++ services/core/package.json | 5 +- .../core/resources/constructs/api-gateway.ts | 55 ++++++++++++++---- services/core/resources/index.ts | 17 +++++- services/core/resources/stacks/core-stack.ts | 57 ++++++++----------- services/core/resources/stacks/demo-stack.ts | 43 ++++++++++++++ 7 files changed, 150 insertions(+), 47 deletions(-) create mode 100644 services/core/helpers/certificates.ts create mode 100644 services/core/resources/stacks/demo-stack.ts diff --git a/.gitignore b/.gitignore index 57ed1c07..b7b00ac4 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ dist # CDK asset staging directory .cdk.staging cdk.out/ +cdk.context.json diff --git a/services/core/helpers/certificates.ts b/services/core/helpers/certificates.ts new file mode 100644 index 00000000..1d4237ac --- /dev/null +++ b/services/core/helpers/certificates.ts @@ -0,0 +1,19 @@ +import { StringParameter } from "aws-cdk-lib/aws-ssm"; +import { Construct } from "constructs"; +import { getStage } from "./env-helpers"; + +export const getCertificateArn = ( + scope: Construct, + subDomain: string +): string | undefined => { + const stage = getStage(); + return ( + StringParameter.fromStringParameterAttributes( + scope, + `CertificateArnParameter-${subDomain}-${stage}`, + { + parameterName: `${subDomain.toUpperCase()}_${stage.toUpperCase()}_CERTIFICATE_ARN`, + } + ).stringValue ?? undefined + ); +}; diff --git a/services/core/package.json b/services/core/package.json index 0867dda9..98d0daf6 100644 --- a/services/core/package.json +++ b/services/core/package.json @@ -8,7 +8,10 @@ "build": "tsc", "watch": "tsc -w", "test": "jest", - "cdk": "cdk" + "cdk": "cdk", + "deploy": "cdk deploy", + "deploy-staging": "cdk deploy -c stage=staging -c region=eu-west-2 --all --require-approval never", + "deploy-prod": "cdk deploy -c stage=prod -c region=eu-west-2 --all --require-approval never" }, "devDependencies": { "@types/aws-lambda": "^8.10.119", diff --git a/services/core/resources/constructs/api-gateway.ts b/services/core/resources/constructs/api-gateway.ts index 4fa9205c..ac5ce0f5 100644 --- a/services/core/resources/constructs/api-gateway.ts +++ b/services/core/resources/constructs/api-gateway.ts @@ -1,11 +1,22 @@ -import { RestApi, RestApiProps } from "aws-cdk-lib/aws-apigateway"; +import { + EndpointType, + RestApi, + RestApiProps, +} from "aws-cdk-lib/aws-apigateway"; +import { Certificate } from "aws-cdk-lib/aws-certificatemanager"; +import { ARecord, HostedZone, RecordTarget } from "aws-cdk-lib/aws-route53"; +import { ApiGateway } from "aws-cdk-lib/aws-route53-targets"; import { Construct } from "constructs"; import { buildResourceName } from "../../helpers"; -export interface CoreApiProps extends Omit {} +export interface CoreApiProps extends Omit { + rootDomain?: string; + subDomain?: string; + certificateArn?: string; +} -export class CoreApi extends RestApi { - constructor(scope: Construct, id: string, props?: CoreApiProps) { +export class OrionApi extends RestApi { + constructor(scope: Construct, id: string, props: CoreApiProps) { super(scope, id, { ...props, restApiName: buildResourceName(id), @@ -15,15 +26,39 @@ export class CoreApi extends RestApi { }, defaultCorsPreflightOptions: { allowHeaders: [ - 'Content-Type', - 'X-Amz-Date', - 'Authorization', - 'X-Api-Key', + "Content-Type", + "X-Amz-Date", + "Authorization", + "X-Api-Key", ], - allowMethods: ['OPTIONS', 'GET', 'POST'], + allowMethods: ["OPTIONS", "GET", "POST"], allowCredentials: true, - allowOrigins: ['http://localhost:3000'], + allowOrigins: ["http://localhost:3000", "https://*.oriontools.ai"], }, }); + + const { rootDomain, subDomain, certificateArn } = props; + + if (certificateArn && rootDomain && subDomain) { + const certificate = Certificate.fromCertificateArn( + this, + "Certificate", + certificateArn + ); + + this.addDomainName("DomainName", { + domainName: `${subDomain}.${rootDomain}`, + certificate: certificate, + endpointType: EndpointType.EDGE, + }); + + new ARecord(this, "ApiSubDomainDNS", { + zone: HostedZone.fromLookup(this, "baseZone", { + domainName: rootDomain, + }), + recordName: subDomain, + target: RecordTarget.fromAlias(new ApiGateway(this)), + }); + } } } diff --git a/services/core/resources/index.ts b/services/core/resources/index.ts index acd86a74..9b4d93c8 100644 --- a/services/core/resources/index.ts +++ b/services/core/resources/index.ts @@ -4,18 +4,31 @@ import { RemovalPolicyAspect } from "../aspects/RemovalPolicyAspect"; import { buildResourceName, getRegion, getStage } from "../helpers"; import { CoreStack } from "./stacks/core-stack"; +import { DemoStack } from "./stacks/demo-stack"; const app = new App(); + +//Env const stage = getStage(); const region = getRegion(); +//Stacks const coreStack = new CoreStack(app, "crgpt-core", { stackName: buildResourceName("crgpt-core"), stage: stage, - env: { region }, + env: { region, account: process.env.CDK_DEFAULT_ACCOUNT }, +}); + +const demoStack = new DemoStack(app, "crgpt-demo", { + stackName: buildResourceName("crgpt-demo"), + stage: stage, + env: { region, account: process.env.CDK_DEFAULT_ACCOUNT }, + userTable: coreStack.userTable, }); +//Aspects Aspects.of(coreStack).add(new RemovalPolicyAspect()); +Aspects.of(demoStack).add(new RemovalPolicyAspect()); -//enable OTel traces +//OTel traces Tags.of(app).add("baselime:tracing", `true`); diff --git a/services/core/resources/stacks/core-stack.ts b/services/core/resources/stacks/core-stack.ts index 1c247b7f..f66cfe4c 100644 --- a/services/core/resources/stacks/core-stack.ts +++ b/services/core/resources/stacks/core-stack.ts @@ -1,13 +1,13 @@ import { Stack, StackProps } from "aws-cdk-lib"; import { LambdaIntegration } from "aws-cdk-lib/aws-apigateway"; +import { Table } from "aws-cdk-lib/aws-dynamodb"; import { Key } from "aws-cdk-lib/aws-kms"; import { Construct } from "constructs"; -import { DemoReviewLambda } from "../../functions/demo-review-lambda/config"; import { GetUserLambda } from "../../functions/get-user/config"; import { ReviewLambda } from "../../functions/review-lambda/config"; import { UpdateUserLambda } from "../../functions/update-user/config"; -import { CoreApi } from "../constructs/api-gateway"; -import { ReviewBucket } from "../constructs/review-bucket"; +import { getCertificateArn, getDomainName, getStage } from "../../helpers"; +import { OrionApi } from "../constructs/api-gateway"; import { UserTable } from "../constructs/user-table"; import { getStage } from '../../helpers'; @@ -16,54 +16,43 @@ interface CoreStackProps extends StackProps { } export class CoreStack extends Stack { + userTable: Table; constructor(scope: Construct, id: string, props: CoreStackProps) { super(scope, id, props); - const api = new CoreApi(this, "core-api"); - - const userTable = new UserTable(this, "user-database"); - - const reviewLambda = new ReviewLambda(this, "review-lambda"); - - const postReviewRoute = api.root.addResource("postReview"); - postReviewRoute.addMethod("POST", new LambdaIntegration(reviewLambda)); - - // We use a separate api for the demo review to enable strong throttling on it - const demoApi = new CoreApi(this, "demo-api", { - deployOptions: { - throttlingBurstLimit: 1, - throttlingRateLimit: 1, - }, + //API + const api = new OrionApi(this, "core-api", { + rootDomain: getDomainName(props.stage), + subDomain: "api", + certificateArn: getCertificateArn(this, "api"), }); - const demoReviewBucket = new ReviewBucket(this, "demo-review-bucket"); - - const demoReviewLambda = new DemoReviewLambda(this, "demo-review-lambda", { - table: userTable, - bucket: demoReviewBucket, - }); - - const demoReviewRoute = demoApi.root.addResource("demoReview"); - demoReviewRoute.addMethod("POST", new LambdaIntegration(demoReviewLambda)); - - // Resources for user management + //KMS const kmsKey = new Key(this, "encryption-key", { enableKeyRotation: true, alias: `${getStage()}/encryption-key`, }); + //DynamoDB + this.userTable = new UserTable(this, "user-database"); + + //Lambda + const reviewLambda = new ReviewLambda(this, "review-lambda"); const updateUserLambda = new UpdateUserLambda(this, "update-user-lambda", { - table: userTable, + table: this.userTable, kmsKey: kmsKey, }); + const getUserLambda = new GetUserLambda(this, "get-user-lambda", { + table: this.userTable, + }); + + //Routes + const postReviewRoute = api.root.addResource("postReview"); + postReviewRoute.addMethod("POST", new LambdaIntegration(reviewLambda)); const updateUserRoute = api.root.addResource("updateUser"); updateUserRoute.addMethod("POST", new LambdaIntegration(updateUserLambda)); - const getUserLambda = new GetUserLambda(this, "get-user-lambda", { - table: userTable, - }); - const getUserRoute = api.root.addResource("getUser"); getUserRoute.addMethod("GET", new LambdaIntegration(getUserLambda)); } diff --git a/services/core/resources/stacks/demo-stack.ts b/services/core/resources/stacks/demo-stack.ts new file mode 100644 index 00000000..07dfc7cb --- /dev/null +++ b/services/core/resources/stacks/demo-stack.ts @@ -0,0 +1,43 @@ +import { Stack, StackProps } from "aws-cdk-lib"; +import { LambdaIntegration } from "aws-cdk-lib/aws-apigateway"; +import { Table } from "aws-cdk-lib/aws-dynamodb"; +import { Construct } from "constructs"; +import { DemoReviewLambda } from "../../functions/demo-review-lambda/config"; +import { getCertificateArn, getDomainName } from "../../helpers"; +import { OrionApi } from "../constructs/api-gateway"; +import { ReviewBucket } from "../constructs/review-bucket"; + +interface DemoStackProps extends StackProps { + stage: string; + userTable: Table; +} + +export class DemoStack extends Stack { + constructor(scope: Construct, id: string, props: DemoStackProps) { + super(scope, id, props); + + // Separate API for the demo to enable strong throttling + const demoApi = new OrionApi(this, "demo-api", { + deployOptions: { + throttlingBurstLimit: 1, + throttlingRateLimit: 1, + }, + rootDomain: getDomainName(props.stage), + subDomain: "demo", + certificateArn: getCertificateArn(this, "demo"), + }); + + //Resources + const demoReviewBucket = new ReviewBucket(this, "demo-review-bucket"); + + //Lambda + const demoReviewLambda = new DemoReviewLambda(this, "demo-review-lambda", { + table: props.userTable, + bucket: demoReviewBucket, + }); + + //Routes + const demoReviewRoute = demoApi.root.addResource("demoReview"); + demoReviewRoute.addMethod("POST", new LambdaIntegration(demoReviewLambda)); + } +}