Skip to content

Commit

Permalink
Move CDK logic to Aws provider and implement factory logic
Browse files Browse the repository at this point in the history
  • Loading branch information
fredericbarthelet committed Jun 16, 2021
1 parent a571f1b commit 694e8f7
Show file tree
Hide file tree
Showing 14 changed files with 208 additions and 117 deletions.
2 changes: 1 addition & 1 deletion src/CloudFormation.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DescribeStacksInput, DescribeStacksOutput } from "aws-sdk/clients/cloudformation";
import { CfnOutput, Stack } from "@aws-cdk/core";
import { debug } from "./utils/logger";
import AwsProvider from "./classes/AwsProvider";
import { AwsProvider } from "./classes/AwsProvider";

export async function getStackOutput(aws: AwsProvider, output: CfnOutput): Promise<string | undefined> {
const outputId = Stack.of(output.stack).resolve(output.logicalId) as string;
Expand Down
25 changes: 25 additions & 0 deletions src/classes/AwsConstruct.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Construct as CdkConstruct } from "@aws-cdk/core";
import { ConstructInterface } from ".";
import { AwsProvider } from "./AwsProvider";

export abstract class AwsConstruct<T extends Record<string, unknown>>
extends CdkConstruct
implements ConstructInterface {
constructor(
protected readonly scope: CdkConstruct,
protected readonly id: string,
protected readonly configuration: T,
protected readonly provider: AwsProvider
) {
super(scope, id);
}

abstract outputs(): Record<string, () => Promise<string | undefined>>;

abstract commands(): Record<string, () => void | Promise<void>>;

/**
* CloudFormation references
*/
abstract references(): Record<string, Record<string, unknown>>;
}
32 changes: 28 additions & 4 deletions src/classes/AwsProvider.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,40 @@
import { CfnOutput, Stack } from "@aws-cdk/core";
import { App, CfnOutput, Stack } from "@aws-cdk/core";
import { get, merge } from "lodash";
import { getStackOutput } from "../CloudFormation";
import { Provider as LegacyAwsProvider, Serverless } from "../types/serverless";
import { Constructs } from "../constructs";
import { CloudformationTemplate, Provider as LegacyAwsProvider, Serverless } from "../types/serverless";
import { awsRequest } from "./aws";
import { AwsConstruct } from ".";

export default class AwsProvider {
export class AwsProvider {
private readonly app: App;
private readonly stack: Stack;
public readonly region: string;
public readonly stackName: string;
private readonly legacyProvider: LegacyAwsProvider;
public naming: { getStackName: () => string; getLambdaLogicalId: (functionName: string) => string };

constructor(private readonly serverless: Serverless, public readonly stack: Stack) {
constructor(private readonly serverless: Serverless) {
this.app = new App();
this.stack = new Stack(this.app);
serverless.stack = this.stack;
this.stackName = serverless.getProvider("aws").naming.getStackName();

this.legacyProvider = serverless.getProvider("aws");
this.naming = this.legacyProvider.naming;
this.region = serverless.getProvider("aws").getRegion();
}

create(type: string, id: string): AwsConstruct<Record<string, unknown>> {
const configuration = get(this.serverless.configurationInput.constructs, id, {});
for (const Construct of Object.values(Constructs)) {
if (Construct.type === type) {
return Construct.create(this.stack, id, configuration, this);
}
}
throw new Error(`Construct ${id} has unsupported construct type: ${type}`);
}

addFunction(functionName: string, functionConfig: unknown): void {
Object.assign(this.serverless.service.functions, {
[functionName]: functionConfig,
Expand All @@ -43,4 +61,10 @@ export default class AwsProvider {
request<Input, Output>(service: string, method: string, params: Input): Promise<Output> {
return awsRequest<Input, Output>(params, service, method, this.legacyProvider);
}

appendCloudformationResources(): void {
merge(this.serverless.service, {
resources: this.app.synth().getStackByName(this.stack.stackName).template as CloudformationTemplate,
});
}
}
2 changes: 1 addition & 1 deletion src/classes/Construct.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { PolicyStatement } from "../CloudFormation";

export default interface Construct {
export interface ConstructInterface {
outputs(): Record<string, () => Promise<string | undefined>>;

commands(): Record<string, () => void | Promise<void>>;
Expand Down
3 changes: 3 additions & 0 deletions src/classes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { AwsConstruct } from "./AwsConstruct";
export { AwsProvider } from "./AwsProvider";
export type { ConstructInterface } from "./Construct";
35 changes: 25 additions & 10 deletions src/constructs/Queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ import { Construct as CdkConstruct, CfnOutput, Duration } from "@aws-cdk/core";
import chalk from "chalk";
import { PurgeQueueRequest } from "aws-sdk/clients/sqs";
import ora from "ora";
import Construct from "../classes/Construct";
import AwsProvider from "../classes/AwsProvider";
import { AwsConstruct, AwsProvider } from "../classes";
import { pollMessages, retryMessages } from "./queue/sqs";
import { sleep } from "../utils/sleep";
import { PolicyStatement } from "../CloudFormation";
Expand Down Expand Up @@ -39,19 +38,35 @@ export const QUEUE_DEFINITION = {
} as const;
type Configuration = FromSchema<typeof QUEUE_DEFINITION>;

export class Queue extends CdkConstruct implements Construct {
const isValidQueueConfiguration = (
configuration: Record<string, unknown>
): configuration is FromSchema<typeof QUEUE_DEFINITION> => {
return true;
};

export class Queue extends AwsConstruct<Configuration> {
public static type = "queue";
public static schema = QUEUE_DEFINITION;
public static create(
scope: CdkConstruct,
id: string,
configuration: Record<string, unknown>,
provider: AwsProvider
): Queue {
if (!isValidQueueConfiguration(configuration)) {
throw new Error("Wrong configuration");
}

return new Queue(scope, id, configuration, provider);
}

private readonly queue: CdkQueue;
private readonly queueArnOutput: CfnOutput;
private readonly queueUrlOutput: CfnOutput;
private readonly dlqUrlOutput: CfnOutput;

constructor(
scope: CdkConstruct,
private readonly id: string,
private readonly configuration: Configuration,
private readonly provider: AwsProvider
) {
super(scope, id);
constructor(scope: CdkConstruct, id: string, configuration: Configuration, provider: AwsProvider) {
super(scope, id, configuration, provider);

// The default function timeout is 6 seconds in the Serverless Framework
const functionTimeout = configuration.worker.timeout ?? 6;
Expand Down
35 changes: 25 additions & 10 deletions src/constructs/StaticWebsite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ import { S3Origin } from "@aws-cdk/aws-cloudfront-origins";
import * as acm from "@aws-cdk/aws-certificatemanager";
import { log } from "../utils/logger";
import { s3Sync } from "../utils/s3-sync";
import AwsProvider from "../classes/AwsProvider";
import Construct from "../classes/Construct";
import { AwsConstruct, AwsProvider } from "../classes";

export const STATIC_WEBSITE_DEFINITION = {
type: "object",
Expand Down Expand Up @@ -55,19 +54,35 @@ export const STATIC_WEBSITE_DEFINITION = {

type Configuration = FromSchema<typeof STATIC_WEBSITE_DEFINITION>;

export class StaticWebsite extends CdkConstruct implements Construct {
const isValidStaticWebsiteConfiguration = (
configuration: Record<string, unknown>
): configuration is FromSchema<typeof STATIC_WEBSITE_DEFINITION> => {
return true;
};

export class StaticWebsite extends AwsConstruct<Configuration> {
public static type = "static-website";
public static schema = STATIC_WEBSITE_DEFINITION;
public static create(
scope: CdkConstruct,
id: string,
configuration: Record<string, unknown>,
provider: AwsProvider
): StaticWebsite {
if (!isValidStaticWebsiteConfiguration(configuration)) {
throw new Error("Wrong configuration");
}

return new StaticWebsite(scope, id, configuration, provider);
}

private readonly bucketNameOutput: CfnOutput;
private readonly domainOutput: CfnOutput;
private readonly cnameOutput: CfnOutput;
private readonly distributionIdOutput: CfnOutput;

constructor(
scope: CdkConstruct,
private readonly id: string,
readonly configuration: Configuration,
private readonly provider: AwsProvider
) {
super(scope, id);
constructor(scope: CdkConstruct, id: string, configuration: Configuration, provider: AwsProvider) {
super(scope, id, configuration, provider);

if (configuration.domain !== undefined && configuration.certificate === undefined) {
throw new Error(
Expand Down
37 changes: 27 additions & 10 deletions src/constructs/Storage.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { BlockPublicAccess, Bucket, BucketEncryption, StorageClass } from "@aws-cdk/aws-s3";
import { Construct as CdkConstruct, CfnOutput, Duration, Fn, Stack } from "@aws-cdk/core";
import { FromSchema } from "json-schema-to-ts";
import Construct from "../classes/Construct";
import AwsProvider from "../classes/AwsProvider";
import { AwsConstruct, AwsProvider } from "../classes";
import { PolicyStatement } from "../CloudFormation";

export const STORAGE_DEFINITION = {
Expand All @@ -22,17 +21,35 @@ const STORAGE_DEFAULTS: Required<FromSchema<typeof STORAGE_DEFINITION>> = {
encryption: "s3",
};

export class Storage extends CdkConstruct implements Construct {
private readonly bucket: Bucket;
private readonly bucketNameOutput: CfnOutput;
type Configuration = FromSchema<typeof STORAGE_DEFINITION>;

constructor(
const isValidStorageConfiguration = (
configuration: Record<string, unknown>
): configuration is FromSchema<typeof STORAGE_DEFINITION> => {
return true;
};

export class Storage extends AwsConstruct<Configuration> {
public static type = "storage";
public static schema = STORAGE_DEFINITION;
public static create(
scope: CdkConstruct,
id: string,
configuration: FromSchema<typeof STORAGE_DEFINITION>,
private readonly provider: AwsProvider
) {
super(scope, id);
configuration: Record<string, unknown>,
provider: AwsProvider
): Storage {
if (!isValidStorageConfiguration(configuration)) {
throw new Error("Wrong configuration");
}

return new Storage(scope, id, configuration, provider);
}

private readonly bucket: Bucket;
private readonly bucketNameOutput: CfnOutput;

constructor(scope: CdkConstruct, id: string, configuration: Configuration, provider: AwsProvider) {
super(scope, id, configuration, provider);

const resolvedConfiguration = Object.assign({}, STORAGE_DEFAULTS, configuration);

Expand Down
37 changes: 27 additions & 10 deletions src/constructs/Webhook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { Function } from "@aws-cdk/aws-lambda";
import { EventBus } from "@aws-cdk/aws-events";
import { FromSchema } from "json-schema-to-ts";
import { PolicyDocument, PolicyStatement, Role, ServicePrincipal } from "@aws-cdk/aws-iam";
import AwsProvider from "../classes/AwsProvider";
import Construct from "../classes/Construct";
import { AwsConstruct, AwsProvider } from "../classes";

export const WEBHOOK_DEFINITION = {
type: "object",
Expand All @@ -30,18 +29,36 @@ const WEBHOOK_DEFAULTS = {
insecure: false,
};

export class Webhook extends CdkConstruct implements Construct {
type Configuration = FromSchema<typeof WEBHOOK_DEFINITION>;

const isValidWebhookConfiguration = (
configuration: Record<string, unknown>
): configuration is FromSchema<typeof WEBHOOK_DEFINITION> => {
return true;
};

export class Webhook extends AwsConstruct<Configuration> {
public static type = "webhook";
public static schema = WEBHOOK_DEFINITION;
public static create(
scope: CdkConstruct,
id: string,
configuration: Record<string, unknown>,
provider: AwsProvider
): Webhook {
if (!isValidWebhookConfiguration(configuration)) {
throw new Error("Wrong configuration");
}

return new Webhook(scope, id, configuration, provider);
}

private readonly bus: EventBus;
private readonly apiEndpointOutput: CfnOutput;
private readonly endpointPathOutput: CfnOutput;

constructor(
scope: CdkConstruct,
private readonly id: string,
private readonly configuration: FromSchema<typeof WEBHOOK_DEFINITION>,
private readonly provider: AwsProvider
) {
super(scope, id);
constructor(scope: CdkConstruct, id: string, configuration: Configuration, provider: AwsProvider) {
super(scope, id, configuration, provider);

const api = new HttpApi(this, "HttpApi");
this.apiEndpointOutput = new CfnOutput(this, "HttpApiEndpoint", {
Expand Down
27 changes: 5 additions & 22 deletions src/constructs/index.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,6 @@
import { Storage, STORAGE_DEFINITION } from "./Storage";
import { Queue, QUEUE_DEFINITION } from "./Queue";
import { STATIC_WEBSITE_DEFINITION, StaticWebsite } from "./StaticWebsite";
import { Webhook, WEBHOOK_DEFINITION } from "./Webhook";
import { Storage } from "./Storage";
import { Queue } from "./Queue";
import { StaticWebsite } from "./StaticWebsite";
import { Webhook } from "./Webhook";

export const constructs = {
storage: {
class: Storage,
schema: STORAGE_DEFINITION,
},
queue: {
class: Queue,
schema: QUEUE_DEFINITION,
},
"static-website": {
class: StaticWebsite,
schema: STATIC_WEBSITE_DEFINITION,
},
webhook: {
class: Webhook,
schema: WEBHOOK_DEFINITION,
},
};
export const Constructs = { Storage, Queue, StaticWebsite, Webhook };
2 changes: 1 addition & 1 deletion src/constructs/queue/sqs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
SendMessageBatchRequest,
SendMessageBatchResult,
} from "aws-sdk/clients/sqs";
import AwsProvider from "../../classes/AwsProvider";
import { AwsProvider } from "../../classes/AwsProvider";
import { log } from "../../utils/logger";
import { sleep } from "../../utils/sleep";

Expand Down
Loading

0 comments on commit 694e8f7

Please sign in to comment.