Skip to content

Commit

Permalink
feat(aws-cdk): added tools to deploy to AWS
Browse files Browse the repository at this point in the history
  • Loading branch information
kedrzu committed Jun 5, 2023
1 parent af5c4e0 commit 661dfde
Show file tree
Hide file tree
Showing 30 changed files with 367 additions and 7,650 deletions.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
"workspaces": [
"packages/*"
],
"scripts": {
"build": "tsc --build",
"watch": "tsc --build --watch"
},
"devDependencies": {
"@types/jest": "^29.5.1",
"babel-jest": "^29.5.0",
Expand Down
38 changes: 38 additions & 0 deletions packages/aws-cdk/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "@nzyme/aws-cdk",
"version": "1.0.0",
"type": "module",
"main": "./dist/index.js",
"exports": {
".": {
"source": "./src/index.ts",
"import": "./dist/index.js"
}
},
"types": "./src/index.ts",
"sideEffects": false,
"repository": "https://github.com/nzymejs/nzyme.git",
"author": "Michał Kędrzyński <m.kedrzynski@gmail.com>",
"dependencies": {
"@aws-cdk/cloudformation-diff": "^2.73.0",
"@aws-cdk/core": "1.202.0",
"@babel/preset-env": "^7.22.4",
"@nzyme/ioc": "1.0.0",
"@nzyme/utils": "1.0.0",
"aws-cdk": "2.81.0",
"cdk-assets": "^2.60.0",
"chalk": "^5.2.0",
"consola": "^3.1.0",
"promptly": "^3.2.0",
"proxy-agent": "^5.0.0"
},
"depcheck": {
"ignoreDeps": [
"aws-sdk",
"promptly",
"proxy-agent",
"cdk-assets",
"@babel/preset-env"
]
}
}
133 changes: 133 additions & 0 deletions packages/aws-cdk/src/App.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { diffTemplate, formatDifferences } from '@aws-cdk/cloudformation-diff';
import * as cdk from '@aws-cdk/core';
import { SdkProvider } from 'aws-cdk/lib/api/aws-auth/index.js';
import { Bootstrapper } from 'aws-cdk/lib/api/bootstrap/index.js';
import { AssetManifestArtifact, CloudFormationStackArtifact } from '@aws-cdk/cx-api';
import { Deployments } from 'aws-cdk/lib/api/deployments.js';
import { WorkGraphBuilder } from 'aws-cdk/lib/util/work-graph-builder.js';
import chalk from 'chalk';
import consola from 'consola';

import { arrayReverse } from '@nzyme/utils';

import { Stack } from './Stack.js';

export interface AppOptions {
readonly sdkProvider: SdkProvider;
}

export class App extends cdk.App {
private readonly sdkProvider: SdkProvider;
private readonly deployments: Deployments;

constructor(options: AppOptions) {
super();
this.sdkProvider = options.sdkProvider;
this.deployments = new Deployments({ sdkProvider: this.sdkProvider });
}

public get stacks() {
return this.node.children.filter(child => child instanceof Stack) as Stack[];
}

/**
* Bootstrapping environment is required for stacks using cross-region resources, like CloudFront.
*/
public async bootstrap() {
const bootstrapper = new Bootstrapper({
source: 'default',
});

const region = this.sdkProvider.defaultRegion;
const account = (await this.sdkProvider.defaultAccount())?.accountId;
if (!account) {
throw new Error('No AWS account detected.');
}

return await bootstrapper.bootstrapEnvironment(
{
name: '',
region,
account,
},
this.sdkProvider,
);
}

public async build() {
for (const stack of this.stacks) {
await stack.$.execute();
}
}

public async deploy() {
await this.build();

const cloudAssembly = this.synth();
const stacks = this.stacks;

for (const artifact of cloudAssembly.artifacts) {
if (artifact instanceof CloudFormationStackArtifact) {
const stack = stacks.find(stack => stack.stackName === artifact.stackName);
const stackName = artifact.stackName;

consola.info(`Deploying stack ${chalk.green(stackName)}`);
stack?.$.emit('deploy:start');

const deployment = await this.deployments.deployStack({
stack: artifact,
deployName: artifact.stackName,
});

consola.success(`Successfully deployed stack ${chalk.green(stackName)}`);
stack?.$.emit('deploy:finished', deployment);
}
}
}

public async destroy() {
const cloudAssembly = this.synth();

// Destroy stacks in reverse order.
for (const stack of arrayReverse(this.stacks)) {
const stackName = stack.stackName;
const region = stack.region ?? this.sdkProvider.defaultRegion;

consola.info(`Destroying stack ${chalk.green(stackName)} in ${chalk.green(region)}`);
stack.$.emit('destroy:start');

await this.deployments.destroyStack({
stack: cloudAssembly.getStackByName(stackName),
});

consola.success(`Successfully destroyed stack ${chalk.green(stackName)}`);
stack.$.emit('destroy:finished');
}
}

public async diff() {
await this.build();

const cloudAssembly = this.synth();
const stacks = this.stacks;

for (const artifact of cloudAssembly.artifacts) {
if (artifact instanceof CloudFormationStackArtifact) {
const currentTemplate = await this.deployments.readCurrentTemplateWithNestedStacks(
artifact,
);

const diff = diffTemplate(currentTemplate, artifact.template);

if (diff.isEmpty) {
consola.success(
`No changes detected for stack ${chalk.green(artifact.stackName)}`,
);
} else {
consola.info(`Changes for stack ${chalk.green(artifact.stackName)}:\n`);
formatDifferences(process.stdout, diff);
}
}
}
}
}
75 changes: 75 additions & 0 deletions packages/aws-cdk/src/Stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import * as cdk from '@aws-cdk/core';
import { CfnOutput } from '@aws-cdk/core';
import { DeployStackResult } from 'aws-cdk/lib/api';

import { eventEmitter } from '@nzyme/utils';

import { App } from './App.js';

export interface StackOptions {
readonly name: string;
readonly env?: cdk.Environment;
}

export interface StackEvents {
'deploy:start': void;
'deploy:finished': DeployStackResult;
'destroy:start': void;
'destroy:finished': void;
}

export type StackHandler = () => Promise<void>;

export class Stack extends cdk.Stack {
private readonly eventEmitter = eventEmitter<StackEvents>();
private readonly tasks: StackHandler[] = [];

private deployResult: DeployStackResult | undefined;

constructor(app: App, public readonly options: StackOptions) {
super(app, options.name, { env: options.env });

this.on('deploy:start', () => {
this.deployResult = undefined;
});

this.on('deploy:finished', result => {
this.deployResult = result;
});
}

public readonly on = this.eventEmitter.on;

/** Internal */
public readonly $ = {
emit: this.eventEmitter.emit,
execute: async () => {
for (const task of this.tasks) {
await task();
}
},
};

public enqueue(handler: StackHandler) {
this.tasks.push(handler);
}

public createExport(name: string, value: string) {
if (!/\w+/.test(name)) {
throw new Error('Export name can only contain letters and numbers.');
}

const output = new CfnOutput(this, name, {
value,
exportName: name,
});

return () => {
if (this.deployResult) {
return this.deployResult.outputs[name];
}

return cdk.Fn.importValue(output.importValue);
};
}
}
5 changes: 5 additions & 0 deletions packages/aws-cdk/src/getSdkProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { SdkProvider } from 'aws-cdk/lib/api/aws-auth/index.js';

export function getSdkProvider() {
return SdkProvider.withAwsCliCompatibleDefaults({});
}
3 changes: 3 additions & 0 deletions packages/aws-cdk/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './App.js';
export * from './Stack.js';
export * from './getSdkProvider.js';
4 changes: 4 additions & 0 deletions packages/aws-cdk/tsconfig.eslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"include": ["./src/**/*.ts", "./tests/**/*.ts"]
}
17 changes: 17 additions & 0 deletions packages/aws-cdk/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"baseUrl": ".",
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src"],
"references": [
{
"path": "../ioc/tsconfig.json"
},
{
"path": "../utils/tsconfig.json"
}
]
}
3 changes: 1 addition & 2 deletions packages/dom/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
"repository": "https://github.com/nzymejs/nzyme.git",
"author": "Michał Kędrzyński <m.kedrzynski@gmail.com>",
"dependencies": {
"@nzyme/types": "1.0.0",
"tslib": "^2.5.3"
"@nzyme/utils": "1.0.0"
}
}
2 changes: 1 addition & 1 deletion packages/dom/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"include": ["src"],
"references": [
{
"path": "../types/tsconfig.json"
"path": "../utils/tsconfig.json"
}
]
}
3 changes: 1 addition & 2 deletions packages/eslint/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@
"eslint-plugin-vue": "^9.14.1"
},
"dependencies": {
"@types/eslint": "^8.37.0",
"tslib": "^2.5.3"
"@types/eslint": "^8.37.0"
},
"depcheck": {
"ignoreDeps": [
Expand Down
3 changes: 1 addition & 2 deletions packages/ioc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@
"repository": "https://github.com/nzymejs/nzyme.git",
"author": "Michał Kędrzyński <m.kedrzynski@gmail.com>",
"dependencies": {
"@nzyme/types": "1.0.0",
"tslib": "^2.5.3"
"@nzyme/types": "1.0.0"
},
"depcheck": {
"ignoreFiles": [
Expand Down
5 changes: 3 additions & 2 deletions packages/ioc/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './Container.js';
export * from './Injectable.js';
export * from './Service.js';
export * from './Executable.js';
export * from './Factory.js';
export * from './Injectable.js';
export * from './Resolvable.js';
export * from './Service.js';
3 changes: 1 addition & 2 deletions packages/utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@
"dependencies": {
"@nzyme/types": "1.0.0",
"@types/lodash.isequal": "^4.5.6",
"lodash.isequal": "^4.5.0",
"tslib": "^2.5.3"
"lodash.isequal": "^4.5.0"
},
"depcheck": {
"ignoreFiles": [
Expand Down
Loading

0 comments on commit 661dfde

Please sign in to comment.