diff --git a/.eslintrc b/.eslintrc index adbc0b40..45e4a98c 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,7 +4,7 @@ "node": true }, "root": true, - "plugins": ["@typescript-eslint", "prettier"], + "plugins": ["@typescript-eslint", "import", "prettier"], "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaVersion": 2018, diff --git a/package-lock.json b/package-lock.json index 667fda4c..c9057ce9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "workspaces": [ "./packages/microapps-datalib/", "./packages/microapps-cdk/", + "./packages/microapps-deployer-lib/", "./packages/microapps-deployer/", "./packages/microapps-publish/", "./packages/microapps-router/", @@ -4376,6 +4377,10 @@ "resolved": "packages/microapps-deployer", "link": true }, + "node_modules/@pwrdrvr/microapps-deployer-lib": { + "resolved": "packages/microapps-deployer-lib", + "link": true + }, "node_modules/@pwrdrvr/microapps-publish": { "resolved": "packages/microapps-publish", "link": true @@ -19780,6 +19785,7 @@ "dev": true }, "packages/microapps-datalib": { + "name": "@pwrdrvr/microapps-datalib", "version": "0.0.0", "license": "MIT", "dependencies": { @@ -19832,6 +19838,20 @@ "sinon": "^10.0.0" } }, + "packages/microapps-deployer-lib": { + "name": "@pwrdrvr/microapps-deployer-lib", + "version": "0.0.0", + "license": "MIT", + "devDependencies": { + "@types/node": "^14.17.34" + } + }, + "packages/microapps-deployer-lib/node_modules/@types/node": { + "version": "14.17.34", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.34.tgz", + "integrity": "sha512-USUftMYpmuMzeWobskoPfzDi+vkpe0dvcOBRNOscFrGxVp4jomnRxWuVohgqBow2xyIPC0S3gjxV/5079jhmDg==", + "dev": true + }, "packages/microapps-deployer/node_modules/@types/node": { "version": "14.17.34", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.34.tgz", @@ -23482,6 +23502,20 @@ } } }, + "@pwrdrvr/microapps-deployer-lib": { + "version": "file:packages/microapps-deployer-lib", + "requires": { + "@types/node": "^14.17.34" + }, + "dependencies": { + "@types/node": { + "version": "14.17.34", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.34.tgz", + "integrity": "sha512-USUftMYpmuMzeWobskoPfzDi+vkpe0dvcOBRNOscFrGxVp4jomnRxWuVohgqBow2xyIPC0S3gjxV/5079jhmDg==", + "dev": true + } + } + }, "@pwrdrvr/microapps-publish": { "version": "file:packages/microapps-publish", "requires": { diff --git a/package.json b/package.json index 9c4dec9e..0b66a196 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "workspaces": [ "./packages/microapps-datalib/", "./packages/microapps-cdk/", + "./packages/microapps-deployer-lib/", "./packages/microapps-deployer/", "./packages/microapps-publish/", "./packages/microapps-router/", diff --git a/packages/microapps-deployer-lib/LICENSE b/packages/microapps-deployer-lib/LICENSE new file mode 120000 index 00000000..30cff740 --- /dev/null +++ b/packages/microapps-deployer-lib/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file diff --git a/packages/microapps-deployer-lib/package.json b/packages/microapps-deployer-lib/package.json new file mode 100644 index 00000000..631b513e --- /dev/null +++ b/packages/microapps-deployer-lib/package.json @@ -0,0 +1,25 @@ +{ + "name": "@pwrdrvr/microapps-deployer-lib", + "version": "0.0.0", + "private": true, + "description": "Deployer svc interface for the microapps framework", + "source": "src/index.ts", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/pwrdrvr/microapps-core.git" + }, + "author": "PwrDrvr LLC", + "license": "MIT", + "bugs": { + "url": "https://github.com/pwrdrvr/microapps-core/issues" + }, + "homepage": "https://github.com/pwrdrvr/microapps-core#readme", + "devDependencies": { + "@types/node": "^14.17.34" + } +} diff --git a/packages/microapps-deployer-lib/src/index.ts b/packages/microapps-deployer-lib/src/index.ts new file mode 100644 index 00000000..c9881746 --- /dev/null +++ b/packages/microapps-deployer-lib/src/index.ts @@ -0,0 +1,37 @@ +export interface IRequestBase { + type: 'createApp' | 'deployVersion' | 'deployVersionPreflight'; +} + +export interface ICreateApplicationRequest extends IRequestBase { + type: 'createApp'; + appName: string; + displayName: string; +} + +export interface IDeployVersionRequestBase extends IRequestBase { + appName: string; + semVer: string; +} + +export interface IDeployVersionPreflightRequest extends IDeployVersionRequestBase { + type: 'deployVersionPreflight'; +} + +export interface IDeployVersionRequest extends IDeployVersionRequestBase { + type: 'deployVersion'; + lambdaARN: string; + defaultFile: string; +} + +export interface IDeployerResponse { + statusCode: number; +} + +export interface IDeployVersionPreflightResponse extends IDeployerResponse { + s3UploadUrl?: string; + awsCredentials?: { + accessKeyId: string; + secretAccessKey: string; + sessionToken: string; + }; +} diff --git a/packages/microapps-deployer-lib/tsconfig.json b/packages/microapps-deployer-lib/tsconfig.json new file mode 100644 index 00000000..c57192ad --- /dev/null +++ b/packages/microapps-deployer-lib/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.packages.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "resolveJsonModule": true + }, + "references": [ + { + "path": "../microapps-datalib/" + } + ], + "exclude": ["**/*.spec.ts", "dist"] +} diff --git a/packages/microapps-deployer/src/controllers/AppController.spec.ts b/packages/microapps-deployer/src/controllers/AppController.spec.ts index 43f67397..e4e14287 100644 --- a/packages/microapps-deployer/src/controllers/AppController.spec.ts +++ b/packages/microapps-deployer/src/controllers/AppController.spec.ts @@ -2,7 +2,8 @@ import 'jest-dynalite/withDb'; import { DBManager, Application } from '@pwrdrvr/microapps-datalib'; import type * as lambda from 'aws-lambda'; import * as dynamodb from '@aws-sdk/client-dynamodb'; -import { handler, ICreateApplicationRequest, overrideDBManager } from '../index'; +import { ICreateApplicationRequest } from '@pwrdrvr/microapps-deployer-lib'; +import { handler, overrideDBManager } from '../index'; let dynamoClient: dynamodb.DynamoDBClient; let dbManager: DBManager; diff --git a/packages/microapps-deployer/src/controllers/AppController.ts b/packages/microapps-deployer/src/controllers/AppController.ts index d6da1dd1..cf65b8ba 100644 --- a/packages/microapps-deployer/src/controllers/AppController.ts +++ b/packages/microapps-deployer/src/controllers/AppController.ts @@ -1,5 +1,5 @@ import { Application, DBManager } from '@pwrdrvr/microapps-datalib'; -import { ICreateApplicationRequest, IDeployerResponse } from '../index'; +import { ICreateApplicationRequest, IDeployerResponse } from '@pwrdrvr/microapps-deployer-lib'; export default class AppController { public static async CreateApp(opts: { diff --git a/packages/microapps-deployer/src/controllers/VersionController.spec.ts b/packages/microapps-deployer/src/controllers/VersionController.spec.ts index ace78788..f6c99620 100644 --- a/packages/microapps-deployer/src/controllers/VersionController.spec.ts +++ b/packages/microapps-deployer/src/controllers/VersionController.spec.ts @@ -7,18 +7,17 @@ import * as lambda from '@aws-sdk/client-lambda'; import * as s3 from '@aws-sdk/client-s3'; import * as sts from '@aws-sdk/client-sts'; +import { + IDeployVersionPreflightRequest, + ICreateApplicationRequest, + IDeployVersionRequest, +} from '@pwrdrvr/microapps-deployer-lib'; import { DBManager, Version } from '@pwrdrvr/microapps-datalib'; import type * as lambdaTypes from 'aws-lambda'; import { mockClient, AwsClientStub } from 'aws-sdk-client-mock'; import sinon from 'sinon'; import { Config } from '../config/Config'; -import { - handler, - IDeployVersionPreflightRequest, - ICreateApplicationRequest, - IDeployVersionRequest, - overrideDBManager, -} from '../index'; +import { handler, overrideDBManager } from '../index'; let s3Client: AwsClientStub; let stsClient: AwsClientStub; diff --git a/packages/microapps-deployer/src/controllers/VersionController.ts b/packages/microapps-deployer/src/controllers/VersionController.ts index a5ac3dab..f0cd1a0f 100644 --- a/packages/microapps-deployer/src/controllers/VersionController.ts +++ b/packages/microapps-deployer/src/controllers/VersionController.ts @@ -12,7 +12,7 @@ import { IDeployerResponse, IDeployVersionPreflightResponse, IDeployVersionRequestBase, -} from '../index'; +} from '@pwrdrvr/microapps-deployer-lib'; import GatewayInfo from '../lib/GatewayInfo'; import Log from '../lib/Log'; diff --git a/packages/microapps-deployer/src/index.ts b/packages/microapps-deployer/src/index.ts index 521203ea..42681a90 100644 --- a/packages/microapps-deployer/src/index.ts +++ b/packages/microapps-deployer/src/index.ts @@ -1,6 +1,13 @@ import 'source-map-support/register'; // Used by ts-convict import 'reflect-metadata'; +import { + IRequestBase, + IDeployVersionPreflightRequest, + ICreateApplicationRequest, + IDeployerResponse, + IDeployVersionRequest, +} from '@pwrdrvr/microapps-deployer-lib'; import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; import { DBManager } from '@pwrdrvr/microapps-datalib'; import type * as lambda from 'aws-lambda'; @@ -28,44 +35,6 @@ dbManager = new DBManager({ dynamoClient, tableName: Config.instance.db.tableNam const config = Config.instance; -interface IRequestBase { - type: 'createApp' | 'deployVersion' | 'deployVersionPreflight'; -} - -export interface ICreateApplicationRequest extends IRequestBase { - type: 'createApp'; - appName: string; - displayName: string; -} - -export interface IDeployVersionRequestBase extends IRequestBase { - appName: string; - semVer: string; -} - -export interface IDeployVersionPreflightRequest extends IDeployVersionRequestBase { - type: 'deployVersionPreflight'; -} - -export interface IDeployVersionRequest extends IDeployVersionRequestBase { - type: 'deployVersion'; - lambdaARN: string; - defaultFile: string; -} - -export interface IDeployerResponse { - statusCode: number; -} - -export interface IDeployVersionPreflightResponse extends IDeployerResponse { - s3UploadUrl?: string; - awsCredentials?: { - accessKeyId: string; - secretAccessKey: string; - sessionToken: string; - }; -} - export async function handler( event: IRequestBase, context: lambda.Context, diff --git a/packages/microapps-publish/src/DeployClient.ts b/packages/microapps-publish/src/DeployClient.ts index 617897f0..509aa2fc 100644 --- a/packages/microapps-publish/src/DeployClient.ts +++ b/packages/microapps-publish/src/DeployClient.ts @@ -1,13 +1,15 @@ import * as lambda from '@aws-sdk/client-lambda'; - +import { TaskWrapper } from 'listr2/dist/lib/task-wrapper'; +import { DefaultRenderer } from 'listr2/dist/renderer/default.renderer'; import { IDeployVersionPreflightRequest, IDeployVersionPreflightResponse, ICreateApplicationRequest, IDeployerResponse, IDeployVersionRequest, -} from '@pwrdrvr/microapps-deployer'; +} from '@pwrdrvr/microapps-deployer-lib'; import { IConfig } from './config/Config'; +import { IContext } from './index'; export interface IDeployVersionPreflightResult { exists: boolean; @@ -47,6 +49,7 @@ export default class DeployClient { public static async DeployVersionPreflight( config: IConfig, + task: TaskWrapper, ): Promise { const request = { type: 'deployVersionPreflight', @@ -65,7 +68,7 @@ export default class DeployClient { Buffer.from(response.Payload).toString('utf-8'), ) as IDeployVersionPreflightResponse; if (dResponse.statusCode === 404) { - console.log(`App/Version do not exist: ${config.app.name}/${config.app.semVer}`); + task.output = `App/Version do not exist: ${config.app.name}/${config.app.semVer}`; return { exists: false, response: dResponse }; } else { return { exists: true, response: dResponse }; @@ -75,7 +78,10 @@ export default class DeployClient { } } - public static async DeployVersion(config: IConfig): Promise { + public static async DeployVersion( + config: IConfig, + task: TaskWrapper, + ): Promise { const request = { type: 'deployVersion', appName: config.app.name, @@ -95,9 +101,9 @@ export default class DeployClient { Buffer.from(response.Payload).toString('utf-8'), ) as IDeployerResponse; if (dResponse.statusCode === 201) { - console.log(`Deploy succeeded: ${config.app.name}/${config.app.semVer}`); + task.output = `Deploy succeeded: ${config.app.name}/${config.app.semVer}`; } else { - console.log(`Deploy failed with: ${dResponse.statusCode}`); + task.output = `Deploy failed with: ${dResponse.statusCode}`; } } else { throw new Error(`Lambda call to DeployVersion failed: ${JSON.stringify(response)}`); diff --git a/packages/microapps-publish/src/S3TransferUtility.ts b/packages/microapps-publish/src/S3TransferUtility.ts index 339e0def..84409d50 100644 --- a/packages/microapps-publish/src/S3TransferUtility.ts +++ b/packages/microapps-publish/src/S3TransferUtility.ts @@ -6,11 +6,19 @@ import { promises as fs, createReadStream } from 'fs'; import * as path from 'path'; import * as s3 from '@aws-sdk/client-s3'; import { Upload } from '@aws-sdk/lib-storage'; -import { IDeployVersionPreflightResponse } from '@pwrdrvr/microapps-deployer'; +import { IDeployVersionPreflightResponse } from '@pwrdrvr/microapps-deployer-lib'; import { contentType } from 'mime-types'; import pMap from 'p-map'; export default class S3TransferUtility { + /** + * @deprecated 2021-11-27 + * + * @param s3Path + * @param destPrefixPath + * @param bucketName + * @param preflightResponse + */ public static async UploadDir( s3Path: string, destPrefixPath: string, diff --git a/packages/microapps-publish/src/S3Uploader.ts b/packages/microapps-publish/src/S3Uploader.ts index 387f75bb..33a8aa18 100644 --- a/packages/microapps-publish/src/S3Uploader.ts +++ b/packages/microapps-publish/src/S3Uploader.ts @@ -1,5 +1,5 @@ import path from 'path'; -import { IDeployVersionPreflightResponse } from '@pwrdrvr/microapps-deployer'; +import { IDeployVersionPreflightResponse } from '@pwrdrvr/microapps-deployer-lib'; import fs from 'fs-extra'; import { IConfig } from './config/Config'; import S3TransferUtility from './S3TransferUtility'; @@ -38,6 +38,7 @@ export default class S3Uploader { /** * Upload files to S3 + * @deprecated 2021-11-27 * @param config * @param s3UploadPath * @param preflightResponse diff --git a/packages/microapps-publish/src/index.ts b/packages/microapps-publish/src/index.ts index 30a29a86..6a3ce05a 100755 --- a/packages/microapps-publish/src/index.ts +++ b/packages/microapps-publish/src/index.ts @@ -15,13 +15,15 @@ import { handle as errorHandler } from '@oclif/errors'; import chalk from 'chalk'; import path from 'path'; import { promises as fs, pathExists, createReadStream } from 'fs-extra'; -import { Listr, ListrErrorTypes, ListrTask, ListrTaskObject } from 'listr2'; +import { Listr, ListrTask } from 'listr2'; import { Config, IConfig } from './config/Config'; import DeployClient, { IDeployVersionPreflightResult } from './DeployClient'; import S3Uploader from './S3Uploader'; import S3TransferUtility from './S3TransferUtility'; import { Upload } from '@aws-sdk/lib-storage'; import { contentType } from 'mime-types'; +import { TaskWrapper } from 'listr2/dist/lib/task-wrapper'; +import { DefaultRenderer } from 'listr2/dist/renderer/default.renderer'; const asyncSetTimeout = util.promisify(setTimeout); const asyncExec = util.promisify(exec); @@ -37,7 +39,7 @@ interface IVersions { alias?: string; } -interface IContext { +export interface IContext { preflightResult: IDeployVersionPreflightResult; files: string[]; } @@ -201,7 +203,7 @@ class PublishTool extends Command { // Confirm the Version Does Not Exist in Published State task.output = `Checking if deployed app/version already exists for ${config.app.name}/${version}`; - ctx.preflightResult = await DeployClient.DeployVersionPreflight(config); + ctx.preflightResult = await DeployClient.DeployVersionPreflight(config, task); if (ctx.preflightResult.exists) { task.output = `Warning: App/Version already exists: ${config.app.name}/${config.app.semVer}`; } @@ -233,13 +235,13 @@ class PublishTool extends Command { }, { title: 'Publish to ECR', - task: async (ctx, task) => { + task: async (ctx: IContext, task: TaskWrapper) => { const origTitle = task.title; task.title = RUNNING + origTitle; // Docker, build, tag, push to ECR // Note: Need to already have AWS env vars set - await this.publishToECR(config); + await this.publishToECR(config, task); task.title = origTitle; }, @@ -251,7 +253,7 @@ class PublishTool extends Command { task.title = RUNNING + origTitle; // Update the Lambda function - await this.deployToLambda(config, this.VersionAndAlias); + await this.deployToLambda(config, this.VersionAndAlias, task); task.title = origTitle; }, @@ -320,7 +322,7 @@ class PublishTool extends Command { const origTitle = task.title; task.title = RUNNING + origTitle; - const { bucketName } = S3Uploader.ParseUploadPath( + const { bucketName, destinationPrefix } = S3Uploader.ParseUploadPath( ctx.preflightResult.response.s3UploadUrl, ); @@ -334,9 +336,13 @@ class PublishTool extends Command { }, }); + const pathWithoutAppAndVer = path.join(S3Uploader.TempDir, destinationPrefix); + const tasks: ListrTask[] = ctx.files.map((filePath) => ({ task: async (ctx: IContext, subtask) => { - const origTitle = subtask.title; + const relFilePath = path.relative(pathWithoutAppAndVer, filePath); + + const origTitle = relFilePath; subtask.title = RUNNING + origTitle; const upload = new Upload({ @@ -387,7 +393,7 @@ class PublishTool extends Command { task.title = RUNNING + origTitle; // Call Deployer to Deploy AppName/Version - await DeployClient.DeployVersion(config); + await DeployClient.DeployVersion(config, task); task.title = origTitle; }, @@ -402,7 +408,7 @@ class PublishTool extends Command { try { await tasks.run(); - this.log(`Published: ${config.app.name}/${config.app.semVer}`); + // this.log(`Published: ${config.app.name}/${config.app.semVer}`); } catch (error) { this.log(`Caught exception: ${error.message}`); } finally { @@ -411,7 +417,10 @@ class PublishTool extends Command { } } - public async restoreFiles(): Promise { + /** + * Restore files that the version was patched into + */ + private async restoreFiles(): Promise { // Put the old files back when succeeded or failed for (const fileToModify of this.FILES_TO_MODIFY) { try { @@ -429,10 +438,22 @@ class PublishTool extends Command { } } + /** + * Setup version and alias strings + * @param version + * @returns + */ private createVersions(version: string): IVersions { return { version, alias: `v${version.replace(/\./g, '_')}` }; } + /** + * Write new versions into specified config files + * @param path + * @param requiredVersions + * @param leaveFiles + * @returns + */ private async writeNewVersions( path: string, requiredVersions: IVersions, @@ -475,6 +496,11 @@ class PublishTool extends Command { return true; } + /** + * Login to ECR for Lambda Docker functions + * @param config + * @returns + */ private async loginToECR(config: IConfig): Promise { this.IMAGE_TAG = `${config.app.ecrRepoName}:${this.VersionAndAlias.version}`; this.IMAGE_URI = `${config.app.ecrHost}/${this.IMAGE_TAG}`; @@ -490,17 +516,33 @@ class PublishTool extends Command { return true; } - private async publishToECR(config: IConfig): Promise { - // console.log('Starting Docker build'); + /** + * Publish to ECR for Lambda Docker function + * @param config + */ + private async publishToECR( + config: IConfig, + task: TaskWrapper, + ): Promise { + task.output = 'Starting Docker build'; await asyncExec(`docker build -f Dockerfile -t ${this.IMAGE_TAG} .`); await asyncExec(`docker tag ${this.IMAGE_TAG} ${config.app.ecrHost}/${this.IMAGE_TAG}`); - // console.log('Starting Docker push to ECR'); + task.output = 'Starting Docker push to ECR'; await asyncExec(`docker push ${config.app.ecrHost}/${this.IMAGE_TAG}`); } - private async deployToLambda(config: IConfig, versions: IVersions): Promise { + /** + * Publish an app version to Lambda + * @param config + * @param versions + */ + private async deployToLambda( + config: IConfig, + versions: IVersions, + task: TaskWrapper, + ): Promise { // Create Lambda version - // console.log('Updating Lambda code to point to new Docker image'); + task.output = 'Updating Lambda code to point to new Docker image'; const resultUpdate = await lambdaClient.send( new lambda.UpdateFunctionCodeCommand({ FunctionName: config.app.lambdaName, @@ -509,7 +551,7 @@ class PublishTool extends Command { }), ); const lambdaVersion = resultUpdate.Version; - // console.log('Lambda version created: ', resultUpdate.Version); + task.output = `Lambda version created: ${resultUpdate.Version}`; let lastUpdateStatus = resultUpdate.LastUpdateStatus; for (let i = 0; i < 5; i++) { @@ -517,7 +559,7 @@ class PublishTool extends Command { // and we have to wait until it's done creating // before we can point an alias to it if (lastUpdateStatus === 'Successful') { - // console.log(`Lambda function updated, version: ${lambdaVersion}`); + task.output = `Lambda function updated, version: ${lambdaVersion}`; break; } @@ -536,7 +578,7 @@ class PublishTool extends Command { } // Create Lambda alias point - // console.log(`Creating the lambda alias for the new version: ${lambdaVersion}`); + task.output = `Creating the lambda alias for the new version: ${lambdaVersion}`; const resultLambdaAlias = await lambdaClient.send( new lambda.CreateAliasCommand({ FunctionName: config.app.lambdaName, @@ -544,7 +586,7 @@ class PublishTool extends Command { FunctionVersion: lambdaVersion, }), ); - // console.log(`Lambda alias created, name: ${resultLambdaAlias.Name}`); + task.output = `Lambda alias created, name: ${resultLambdaAlias.Name}`; } } diff --git a/tsconfig.json b/tsconfig.json index 1dc5a00b..6877db21 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,9 @@ { "path": "packages/microapps-datalib/" }, + { + "path": "packages/microapps-deployer-lib/" + }, { "path": "packages/microapps-deployer/" },