@pwrdrvr/microapps-publish 0.0.17 → 0.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/run +5 -0
- package/dist/commands/nextjs-docker-auto.d.ts +38 -0
- package/dist/commands/nextjs-docker-auto.d.ts.map +1 -0
- package/dist/commands/nextjs-docker-auto.js +415 -0
- package/dist/commands/nextjs-docker-auto.js.map +1 -0
- package/dist/commands/nextjs-version-restore.d.ts +14 -0
- package/dist/commands/nextjs-version-restore.d.ts.map +1 -0
- package/dist/commands/nextjs-version-restore.js +58 -0
- package/dist/commands/nextjs-version-restore.js.map +1 -0
- package/dist/commands/nextjs-version.d.ts +16 -0
- package/dist/commands/nextjs-version.d.ts.map +1 -0
- package/dist/commands/nextjs-version.js +94 -0
- package/dist/commands/nextjs-version.js.map +1 -0
- package/dist/commands/preflight.d.ts +15 -0
- package/dist/commands/preflight.d.ts.map +1 -0
- package/dist/commands/preflight.js +111 -0
- package/dist/commands/preflight.js.map +1 -0
- package/dist/commands/publish.d.ts +23 -0
- package/dist/commands/publish.d.ts.map +1 -0
- package/dist/commands/publish.js +300 -0
- package/dist/commands/publish.js.map +1 -0
- package/dist/config/Config.d.ts +0 -2
- package/dist/config/Config.d.ts.map +1 -1
- package/dist/config/Config.js +0 -5
- package/dist/config/Config.js.map +1 -1
- package/dist/index.d.ts +1 -30
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -366
- package/dist/index.js.map +1 -1
- package/dist/lib/DeployClient.d.ts +34 -0
- package/dist/lib/DeployClient.d.ts.map +1 -0
- package/dist/{DeployClient.js → lib/DeployClient.js} +24 -5
- package/dist/lib/DeployClient.js.map +1 -0
- package/dist/lib/S3TransferUtility.d.ts +19 -0
- package/dist/lib/S3TransferUtility.d.ts.map +1 -0
- package/dist/{S3TransferUtility.js → lib/S3TransferUtility.js} +13 -0
- package/dist/lib/S3TransferUtility.js.map +1 -0
- package/dist/lib/S3Uploader.d.ts +27 -0
- package/dist/lib/S3Uploader.d.ts.map +1 -0
- package/dist/lib/S3Uploader.js +76 -0
- package/dist/lib/S3Uploader.js.map +1 -0
- package/dist/lib/Versions.d.ts +27 -0
- package/dist/lib/Versions.d.ts.map +1 -0
- package/dist/lib/Versions.js +76 -0
- package/dist/lib/Versions.js.map +1 -0
- package/package.json +11 -2
- package/src/commands/nextjs-docker-auto.ts +498 -0
- package/src/commands/nextjs-version-restore.ts +73 -0
- package/src/commands/nextjs-version.ts +113 -0
- package/src/commands/preflight.ts +121 -0
- package/src/commands/publish.ts +358 -0
- package/src/config/Config.ts +0 -4
- package/src/index.ts +1 -454
- package/src/{DeployClient.ts → lib/DeployClient.ts} +34 -11
- package/src/{S3TransferUtility.ts → lib/S3TransferUtility.ts} +15 -3
- package/src/{S3Uploader.ts → lib/S3Uploader.ts} +46 -11
- package/src/lib/Versions.ts +95 -0
- package/dist/DeployClient.d.ts +0 -15
- package/dist/DeployClient.d.ts.map +0 -1
- package/dist/DeployClient.js.map +0 -1
- package/dist/S3TransferUtility.d.ts +0 -6
- package/dist/S3TransferUtility.d.ts.map +0 -1
- package/dist/S3TransferUtility.js.map +0 -1
- package/dist/S3Uploader.d.ts +0 -8
- package/dist/S3Uploader.d.ts.map +0 -1
- package/dist/S3Uploader.js +0 -47
- package/dist/S3Uploader.js.map +0 -1
- package/dist/config/FileStore.d.ts +0 -7
- package/dist/config/FileStore.d.ts.map +0 -1
- package/dist/config/FileStore.js +0 -17
- package/dist/config/FileStore.js.map +0 -1
- package/src/config/FileStore.ts +0 -14
package/src/index.ts
CHANGED
|
@@ -1,454 +1 @@
|
|
|
1
|
-
|
|
2
|
-
/* eslint-disable no-console */
|
|
3
|
-
|
|
4
|
-
import 'source-map-support/register';
|
|
5
|
-
// Used by ts-convict
|
|
6
|
-
import 'reflect-metadata';
|
|
7
|
-
import { exec } from 'child_process';
|
|
8
|
-
import * as util from 'util';
|
|
9
|
-
import * as lambda from '@aws-sdk/client-lambda';
|
|
10
|
-
import * as sts from '@aws-sdk/client-sts';
|
|
11
|
-
import { Command, flags as flagsParser } from '@oclif/command';
|
|
12
|
-
import { IConfig as OCLIFIConfig } from '@oclif/config';
|
|
13
|
-
import { handle as errorHandler } from '@oclif/errors';
|
|
14
|
-
import chalk from 'chalk';
|
|
15
|
-
import { promises as fs, pathExists } from 'fs-extra';
|
|
16
|
-
import { Listr, ListrErrorTypes, ListrTask, ListrTaskObject } from 'listr2';
|
|
17
|
-
import { Config, IConfig } from './config/Config';
|
|
18
|
-
import DeployClient, { IDeployVersionPreflightResult } from './DeployClient';
|
|
19
|
-
import S3Uploader from './S3Uploader';
|
|
20
|
-
const asyncSetTimeout = util.promisify(setTimeout);
|
|
21
|
-
const asyncExec = util.promisify(exec);
|
|
22
|
-
|
|
23
|
-
const RUNNING_TEXT = ' RUNS ';
|
|
24
|
-
const RUNNING = chalk.reset.inverse.yellow.bold(RUNNING_TEXT) + ' ';
|
|
25
|
-
|
|
26
|
-
const lambdaClient = new lambda.LambdaClient({
|
|
27
|
-
maxAttempts: 8,
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
interface IVersions {
|
|
31
|
-
version: string;
|
|
32
|
-
alias?: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
interface IContext {
|
|
36
|
-
preflightResult: IDeployVersionPreflightResult;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
class PublishTool extends Command {
|
|
40
|
-
static flags = {
|
|
41
|
-
version: flagsParser.version({
|
|
42
|
-
char: 'v',
|
|
43
|
-
}),
|
|
44
|
-
help: flagsParser.help(),
|
|
45
|
-
deployerLambdaName: flagsParser.string({
|
|
46
|
-
char: 'd',
|
|
47
|
-
multiple: false,
|
|
48
|
-
required: true,
|
|
49
|
-
description: 'Name of the deployer lambda function',
|
|
50
|
-
}),
|
|
51
|
-
newVersion: flagsParser.string({
|
|
52
|
-
char: 'n',
|
|
53
|
-
multiple: false,
|
|
54
|
-
required: true,
|
|
55
|
-
description: 'New semantic version to apply',
|
|
56
|
-
}),
|
|
57
|
-
repoName: flagsParser.string({
|
|
58
|
-
char: 'r',
|
|
59
|
-
multiple: false,
|
|
60
|
-
required: true,
|
|
61
|
-
description: 'Name (not URI) of the Docker repo for the app',
|
|
62
|
-
}),
|
|
63
|
-
leaveCopy: flagsParser.boolean({
|
|
64
|
-
char: 'l',
|
|
65
|
-
default: false,
|
|
66
|
-
required: false,
|
|
67
|
-
description: 'Leave a copy of the modifed files as .modified',
|
|
68
|
-
}),
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
|
|
72
|
-
private static escapeRegExp(value: string): string {
|
|
73
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
private VersionAndAlias: IVersions;
|
|
77
|
-
private IMAGE_TAG = '';
|
|
78
|
-
private IMAGE_URI = '';
|
|
79
|
-
private FILES_TO_MODIFY: {
|
|
80
|
-
path: string;
|
|
81
|
-
versions: IVersions;
|
|
82
|
-
}[];
|
|
83
|
-
private _restoreFilesStarted = false;
|
|
84
|
-
|
|
85
|
-
constructor(argv: string[], config: OCLIFIConfig) {
|
|
86
|
-
super(argv, config);
|
|
87
|
-
this.restoreFiles = this.restoreFiles.bind(this);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async run(): Promise<void> {
|
|
91
|
-
const { flags: parsedFlags } = this.parse(PublishTool);
|
|
92
|
-
const version = parsedFlags.newVersion;
|
|
93
|
-
const leaveFiles = parsedFlags.leaveCopy;
|
|
94
|
-
const lambdaName = parsedFlags.deployerLambdaName;
|
|
95
|
-
const ecrRepo = parsedFlags.repoName;
|
|
96
|
-
|
|
97
|
-
// Override the config value
|
|
98
|
-
const config = Config.instance;
|
|
99
|
-
config.deployer.lambdaName = lambdaName;
|
|
100
|
-
config.app.semVer = version;
|
|
101
|
-
|
|
102
|
-
// Get the account ID and region from STS
|
|
103
|
-
// TODO: Move this to the right place
|
|
104
|
-
if (config.app.awsAccountID === 0 || config.app.awsRegion === '') {
|
|
105
|
-
const stsClient = new sts.STSClient({
|
|
106
|
-
maxAttempts: 8,
|
|
107
|
-
});
|
|
108
|
-
const stsResponse = await stsClient.send(new sts.GetCallerIdentityCommand({}));
|
|
109
|
-
if (config.app.awsAccountID === 0) {
|
|
110
|
-
config.app.awsAccountID = parseInt(stsResponse.Account, 10);
|
|
111
|
-
}
|
|
112
|
-
if (config.app.awsRegion === '') {
|
|
113
|
-
config.app.awsRegion = stsClient.config.region as string;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
if (config.app.ecrHost === '') {
|
|
117
|
-
config.app.ecrHost = `${config.app.awsAccountID}.dkr.ecr.${config.app.awsRegion}.amazonaws.com`;
|
|
118
|
-
}
|
|
119
|
-
if (ecrRepo) {
|
|
120
|
-
config.app.ecrRepoName = ecrRepo;
|
|
121
|
-
} else if (config.app.ecrRepoName === '') {
|
|
122
|
-
config.app.ecrRepoName = `microapps-app-${config.app.name}${Config.envLevel}-repo`;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
this.VersionAndAlias = this.createVersions(version);
|
|
126
|
-
const versionOnly = { version: this.VersionAndAlias.version };
|
|
127
|
-
|
|
128
|
-
this.FILES_TO_MODIFY = [
|
|
129
|
-
{ path: 'package.json', versions: versionOnly },
|
|
130
|
-
// { path: 'deploy.json', versions: this.VersionAndAlias },
|
|
131
|
-
{ path: 'next.config.js', versions: versionOnly },
|
|
132
|
-
] as { path: string; versions: IVersions }[];
|
|
133
|
-
|
|
134
|
-
// Install handler to ensure that we restore files
|
|
135
|
-
process.on('SIGINT', async () => {
|
|
136
|
-
if (this._restoreFilesStarted) {
|
|
137
|
-
return;
|
|
138
|
-
} else {
|
|
139
|
-
this._restoreFilesStarted = true;
|
|
140
|
-
}
|
|
141
|
-
console.log('Caught Ctrl-C, restoring files');
|
|
142
|
-
await S3Uploader.removeTempDirIfExists();
|
|
143
|
-
await this.restoreFiles();
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
if (config === undefined) {
|
|
147
|
-
this.error('Failed to load the config file');
|
|
148
|
-
}
|
|
149
|
-
if (config.app.staticAssetsPath === undefined) {
|
|
150
|
-
this.error('StaticAssetsPath must be specified in the config file');
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
//
|
|
154
|
-
// TODO: Setup Tasks
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
const tasks = new Listr<IContext>([
|
|
158
|
-
{
|
|
159
|
-
title: 'Logging into ECR',
|
|
160
|
-
task: async (ctx, task) => {
|
|
161
|
-
const origTitle = task.title;
|
|
162
|
-
task.title = RUNNING + origTitle;
|
|
163
|
-
|
|
164
|
-
await this.loginToECR(config);
|
|
165
|
-
|
|
166
|
-
task.title = origTitle;
|
|
167
|
-
},
|
|
168
|
-
},
|
|
169
|
-
{
|
|
170
|
-
title: 'Modifying Config Files',
|
|
171
|
-
task: async (ctx, task) => {
|
|
172
|
-
const origTitle = task.title;
|
|
173
|
-
task.title = RUNNING + origTitle;
|
|
174
|
-
|
|
175
|
-
// Modify the existing files with the new version
|
|
176
|
-
for (const fileToModify of this.FILES_TO_MODIFY) {
|
|
177
|
-
task.output = `Patching version (${this.VersionAndAlias.version}) into ${fileToModify.path}`;
|
|
178
|
-
if (
|
|
179
|
-
!(await this.writeNewVersions(fileToModify.path, fileToModify.versions, leaveFiles))
|
|
180
|
-
) {
|
|
181
|
-
task.output = `Failed modifying file: ${fileToModify.path}`;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
task.title = origTitle;
|
|
186
|
-
},
|
|
187
|
-
},
|
|
188
|
-
{
|
|
189
|
-
title: 'Preflight Version Check',
|
|
190
|
-
task: async (ctx, task) => {
|
|
191
|
-
const origTitle = task.title;
|
|
192
|
-
task.title = RUNNING + origTitle;
|
|
193
|
-
|
|
194
|
-
// Confirm the Version Does Not Exist in Published State
|
|
195
|
-
task.output = `Checking if deployed app/version already exists for ${config.app.name}/${version}`;
|
|
196
|
-
ctx.preflightResult = await DeployClient.DeployVersionPreflight(config);
|
|
197
|
-
if (ctx.preflightResult.exists) {
|
|
198
|
-
task.output = `Warning: App/Version already exists: ${config.app.name}/${config.app.semVer}`;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
task.title = origTitle;
|
|
202
|
-
},
|
|
203
|
-
},
|
|
204
|
-
{
|
|
205
|
-
title: 'Serverless Next.js Build',
|
|
206
|
-
task: async (ctx, task) => {
|
|
207
|
-
const origTitle = task.title;
|
|
208
|
-
task.title = RUNNING + origTitle;
|
|
209
|
-
|
|
210
|
-
task.output = `Invoking serverless next.js build for ${config.app.name}/${version}`;
|
|
211
|
-
|
|
212
|
-
// Run the serverless next.js build
|
|
213
|
-
await asyncExec('serverless');
|
|
214
|
-
|
|
215
|
-
if (config.app.serverlessNextRouterPath !== undefined) {
|
|
216
|
-
task.output = 'Copying Serverless Next.js router to build output directory';
|
|
217
|
-
await fs.copyFile(config.app.serverlessNextRouterPath, './.serverless_nextjs/index.js');
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
task.title = origTitle;
|
|
221
|
-
},
|
|
222
|
-
},
|
|
223
|
-
{
|
|
224
|
-
title: 'Publish to ECR',
|
|
225
|
-
task: async (ctx, task) => {
|
|
226
|
-
const origTitle = task.title;
|
|
227
|
-
task.title = RUNNING + origTitle;
|
|
228
|
-
|
|
229
|
-
// Docker, build, tag, push to ECR
|
|
230
|
-
// Note: Need to already have AWS env vars set
|
|
231
|
-
await this.publishToECR(config);
|
|
232
|
-
|
|
233
|
-
task.title = origTitle;
|
|
234
|
-
},
|
|
235
|
-
},
|
|
236
|
-
{
|
|
237
|
-
title: 'Deploy to Lambda',
|
|
238
|
-
task: async (ctx, task) => {
|
|
239
|
-
const origTitle = task.title;
|
|
240
|
-
task.title = RUNNING + origTitle;
|
|
241
|
-
|
|
242
|
-
// Update the Lambda function
|
|
243
|
-
await this.deployToLambda(config, this.VersionAndAlias);
|
|
244
|
-
|
|
245
|
-
task.title = origTitle;
|
|
246
|
-
},
|
|
247
|
-
},
|
|
248
|
-
{
|
|
249
|
-
title: 'Confirm Static Assets Folder Exists',
|
|
250
|
-
task: async (ctx, task) => {
|
|
251
|
-
const origTitle = task.title;
|
|
252
|
-
task.title = RUNNING + origTitle;
|
|
253
|
-
|
|
254
|
-
// Check that Static Assets Folder exists
|
|
255
|
-
if (!(await pathExists(config.app.staticAssetsPath))) {
|
|
256
|
-
this.error(`Static asset path does not exist: ${config.app.staticAssetsPath}`);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
task.title = origTitle;
|
|
260
|
-
},
|
|
261
|
-
},
|
|
262
|
-
{
|
|
263
|
-
title: 'Upload Files to S3 Staging AppName/Version Prefix',
|
|
264
|
-
task: async (ctx, task) => {
|
|
265
|
-
const origTitle = task.title;
|
|
266
|
-
task.title = RUNNING + origTitle;
|
|
267
|
-
|
|
268
|
-
// Upload Files to S3 Staging AppName/Version Prefix
|
|
269
|
-
await S3Uploader.Upload(
|
|
270
|
-
config,
|
|
271
|
-
ctx.preflightResult.response.s3UploadUrl,
|
|
272
|
-
ctx.preflightResult.response,
|
|
273
|
-
);
|
|
274
|
-
|
|
275
|
-
task.title = origTitle;
|
|
276
|
-
},
|
|
277
|
-
},
|
|
278
|
-
{
|
|
279
|
-
title: `Creating MicroApp Application: ${config.app.name}`,
|
|
280
|
-
task: async (ctx, task) => {
|
|
281
|
-
const origTitle = task.title;
|
|
282
|
-
task.title = RUNNING + origTitle;
|
|
283
|
-
|
|
284
|
-
// Call Deployer to Create App if Not Exists
|
|
285
|
-
await DeployClient.CreateApp(config);
|
|
286
|
-
|
|
287
|
-
task.title = origTitle;
|
|
288
|
-
},
|
|
289
|
-
},
|
|
290
|
-
{
|
|
291
|
-
title: `Creating MicroApp Version: ${config.app.semVer}`,
|
|
292
|
-
task: async (ctx, task) => {
|
|
293
|
-
const origTitle = task.title;
|
|
294
|
-
task.title = RUNNING + origTitle;
|
|
295
|
-
|
|
296
|
-
// Call Deployer to Deploy AppName/Version
|
|
297
|
-
await DeployClient.DeployVersion(config);
|
|
298
|
-
|
|
299
|
-
task.title = origTitle;
|
|
300
|
-
},
|
|
301
|
-
},
|
|
302
|
-
]);
|
|
303
|
-
|
|
304
|
-
try {
|
|
305
|
-
await tasks.run();
|
|
306
|
-
console.log(`Published: ${config.app.name}/${config.app.semVer}`);
|
|
307
|
-
} catch (error) {
|
|
308
|
-
console.log(`Caught exception: ${error.message}`);
|
|
309
|
-
} finally {
|
|
310
|
-
await this.restoreFiles();
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
public async restoreFiles(): Promise<void> {
|
|
315
|
-
// Put the old files back when succeeded or failed
|
|
316
|
-
for (const fileToModify of this.FILES_TO_MODIFY) {
|
|
317
|
-
try {
|
|
318
|
-
const stats = await fs.stat(`${fileToModify.path}.original`);
|
|
319
|
-
if (stats.isFile()) {
|
|
320
|
-
// Remove the possibly modified file
|
|
321
|
-
await fs.unlink(fileToModify.path);
|
|
322
|
-
|
|
323
|
-
// Move the original file back
|
|
324
|
-
await fs.rename(`${fileToModify.path}.original`, fileToModify.path);
|
|
325
|
-
}
|
|
326
|
-
} catch {
|
|
327
|
-
// don't care... if the file doesn't exist we can't do anything
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
private createVersions(version: string): IVersions {
|
|
333
|
-
return { version, alias: `v${version.replace(/\./g, '_')}` };
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
private async writeNewVersions(
|
|
337
|
-
path: string,
|
|
338
|
-
requiredVersions: IVersions,
|
|
339
|
-
leaveFiles: boolean,
|
|
340
|
-
): Promise<boolean> {
|
|
341
|
-
const stats = await fs.stat(path);
|
|
342
|
-
if (!stats.isFile) {
|
|
343
|
-
return false;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// Make a backup of the file
|
|
347
|
-
await fs.copyFile(path, `${path}.original`);
|
|
348
|
-
|
|
349
|
-
// File exists, check that it has the required version strings
|
|
350
|
-
let fileText = await fs.readFile(path, 'utf8');
|
|
351
|
-
|
|
352
|
-
for (const key of Object.keys(requiredVersions)) {
|
|
353
|
-
const placeHolder = key === 'version' ? '0.0.0' : 'v0_0_0';
|
|
354
|
-
if (fileText.indexOf(placeHolder) === -1) {
|
|
355
|
-
// The required placeholder is missing
|
|
356
|
-
return false;
|
|
357
|
-
} else {
|
|
358
|
-
const regExp = new RegExp(PublishTool.escapeRegExp(placeHolder), 'g');
|
|
359
|
-
fileText = fileText.replace(
|
|
360
|
-
regExp,
|
|
361
|
-
key === 'version' ? requiredVersions.version : (requiredVersions.alias as string),
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// Write the updated file contents
|
|
367
|
-
await fs.writeFile(path, fileText, 'utf8');
|
|
368
|
-
|
|
369
|
-
// Leave a copy of the modified file if requested
|
|
370
|
-
if (leaveFiles) {
|
|
371
|
-
// This copy will overwrite an existing file
|
|
372
|
-
await fs.copyFile(path, `${path}.modified`);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
return true;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
private async loginToECR(config: IConfig): Promise<boolean> {
|
|
379
|
-
this.IMAGE_TAG = `${config.app.ecrRepoName}:${this.VersionAndAlias.version}`;
|
|
380
|
-
this.IMAGE_URI = `${config.app.ecrHost}/${this.IMAGE_TAG}`;
|
|
381
|
-
|
|
382
|
-
try {
|
|
383
|
-
await asyncExec(
|
|
384
|
-
`aws ecr get-login-password --region ${config.app.awsRegion} | docker login --username AWS --password-stdin ${config.app.ecrHost}`,
|
|
385
|
-
);
|
|
386
|
-
} catch (error) {
|
|
387
|
-
throw new Error(`ECR Login Failed: ${error.message}`);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
return true;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
private async publishToECR(config: IConfig): Promise<void> {
|
|
394
|
-
console.log('Starting Docker build');
|
|
395
|
-
await asyncExec(`docker build -f Dockerfile -t ${this.IMAGE_TAG} .`);
|
|
396
|
-
await asyncExec(`docker tag ${this.IMAGE_TAG} ${config.app.ecrHost}/${this.IMAGE_TAG}`);
|
|
397
|
-
console.log('Starting Docker push to ECR');
|
|
398
|
-
await asyncExec(`docker push ${config.app.ecrHost}/${this.IMAGE_TAG}`);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
private async deployToLambda(config: IConfig, versions: IVersions): Promise<void> {
|
|
402
|
-
// Create Lambda version
|
|
403
|
-
console.log('Updating Lambda code to point to new Docker image');
|
|
404
|
-
const resultUpdate = await lambdaClient.send(
|
|
405
|
-
new lambda.UpdateFunctionCodeCommand({
|
|
406
|
-
FunctionName: config.app.lambdaName,
|
|
407
|
-
ImageUri: this.IMAGE_URI,
|
|
408
|
-
Publish: true,
|
|
409
|
-
}),
|
|
410
|
-
);
|
|
411
|
-
const lambdaVersion = resultUpdate.Version;
|
|
412
|
-
console.log('Lambda version created: ', resultUpdate.Version);
|
|
413
|
-
|
|
414
|
-
let lastUpdateStatus = resultUpdate.LastUpdateStatus;
|
|
415
|
-
for (let i = 0; i < 5; i++) {
|
|
416
|
-
// When the function is created the status will be "Pending"
|
|
417
|
-
// and we have to wait until it's done creating
|
|
418
|
-
// before we can point an alias to it
|
|
419
|
-
if (lastUpdateStatus === 'Successful') {
|
|
420
|
-
console.log(`Lambda function updated, version: ${lambdaVersion}`);
|
|
421
|
-
break;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
// If it didn't work, wait and try again
|
|
425
|
-
await asyncSetTimeout(1000 * i);
|
|
426
|
-
|
|
427
|
-
const resultGet = await lambdaClient.send(
|
|
428
|
-
new lambda.GetFunctionCommand({
|
|
429
|
-
FunctionName: config.app.lambdaName,
|
|
430
|
-
Qualifier: lambdaVersion,
|
|
431
|
-
}),
|
|
432
|
-
);
|
|
433
|
-
|
|
434
|
-
// Save the last update status so we can check on re-loop
|
|
435
|
-
lastUpdateStatus = resultGet?.Configuration?.LastUpdateStatus;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// Create Lambda alias point
|
|
439
|
-
console.log(`Creating the lambda alias for the new version: ${lambdaVersion}`);
|
|
440
|
-
const resultLambdaAlias = await lambdaClient.send(
|
|
441
|
-
new lambda.CreateAliasCommand({
|
|
442
|
-
FunctionName: config.app.lambdaName,
|
|
443
|
-
Name: versions.alias,
|
|
444
|
-
FunctionVersion: lambdaVersion,
|
|
445
|
-
}),
|
|
446
|
-
);
|
|
447
|
-
console.log(`Lambda alias created, name: ${resultLambdaAlias.Name}`);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// @ts-expect-error catch is actually defined
|
|
452
|
-
PublishTool.run().catch(errorHandler);
|
|
453
|
-
|
|
454
|
-
export default PublishTool;
|
|
1
|
+
export { run } from '@oclif/command';
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import * as lambda from '@aws-sdk/client-lambda';
|
|
2
|
-
|
|
3
|
-
// eslint-disable-next-line import/no-extraneous-dependencies,import/no-unresolved
|
|
4
2
|
import {
|
|
5
3
|
IDeployVersionPreflightRequest,
|
|
6
4
|
IDeployVersionPreflightResponse,
|
|
7
5
|
ICreateApplicationRequest,
|
|
8
6
|
IDeployerResponse,
|
|
9
7
|
IDeployVersionRequest,
|
|
10
|
-
} from '@pwrdrvr/microapps-deployer';
|
|
11
|
-
import { IConfig } from '
|
|
8
|
+
} from '@pwrdrvr/microapps-deployer-lib';
|
|
9
|
+
import { IConfig } from '../config/Config';
|
|
12
10
|
|
|
13
11
|
export interface IDeployVersionPreflightResult {
|
|
14
12
|
exists: boolean;
|
|
@@ -46,13 +44,25 @@ export default class DeployClient {
|
|
|
46
44
|
}
|
|
47
45
|
}
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Check if version exists.
|
|
49
|
+
* Optionally get S3 creds for static asset upload.
|
|
50
|
+
* @param config
|
|
51
|
+
* @param output
|
|
52
|
+
* @returns
|
|
53
|
+
*/
|
|
54
|
+
public static async DeployVersionPreflight(opts: {
|
|
55
|
+
config: IConfig;
|
|
56
|
+
needS3Creds?: boolean;
|
|
57
|
+
output: (message: string) => void;
|
|
58
|
+
}): Promise<IDeployVersionPreflightResult> {
|
|
59
|
+
const { config, needS3Creds = true, output } = opts;
|
|
60
|
+
|
|
52
61
|
const request = {
|
|
53
62
|
type: 'deployVersionPreflight',
|
|
54
63
|
appName: config.app.name,
|
|
55
64
|
semVer: config.app.semVer,
|
|
65
|
+
needS3Creds,
|
|
56
66
|
} as IDeployVersionPreflightRequest;
|
|
57
67
|
const response = await this._client.send(
|
|
58
68
|
new lambda.InvokeCommand({
|
|
@@ -66,9 +76,10 @@ export default class DeployClient {
|
|
|
66
76
|
Buffer.from(response.Payload).toString('utf-8'),
|
|
67
77
|
) as IDeployVersionPreflightResponse;
|
|
68
78
|
if (dResponse.statusCode === 404) {
|
|
69
|
-
|
|
79
|
+
output(`App/Version does not exist: ${config.app.name}/${config.app.semVer}`);
|
|
70
80
|
return { exists: false, response: dResponse };
|
|
71
81
|
} else {
|
|
82
|
+
output(`App/Version exists: ${config.app.name}/${config.app.semVer}`);
|
|
72
83
|
return { exists: true, response: dResponse };
|
|
73
84
|
}
|
|
74
85
|
} else {
|
|
@@ -76,7 +87,18 @@ export default class DeployClient {
|
|
|
76
87
|
}
|
|
77
88
|
}
|
|
78
89
|
|
|
79
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Copy S3 static assets from staging to live bucket.
|
|
92
|
+
* Create API Gateway Integration for app (if needed).
|
|
93
|
+
* Give API Gateway permission to call the Lambda.
|
|
94
|
+
* Create API Gateway routes for this specific version.
|
|
95
|
+
* @param config
|
|
96
|
+
* @param task
|
|
97
|
+
*/
|
|
98
|
+
public static async DeployVersion(
|
|
99
|
+
config: IConfig,
|
|
100
|
+
output: (message: string) => void,
|
|
101
|
+
): Promise<void> {
|
|
80
102
|
const request = {
|
|
81
103
|
type: 'deployVersion',
|
|
82
104
|
appName: config.app.name,
|
|
@@ -96,9 +118,10 @@ export default class DeployClient {
|
|
|
96
118
|
Buffer.from(response.Payload).toString('utf-8'),
|
|
97
119
|
) as IDeployerResponse;
|
|
98
120
|
if (dResponse.statusCode === 201) {
|
|
99
|
-
|
|
121
|
+
output(`Deploy succeeded: ${config.app.name}/${config.app.semVer}`);
|
|
100
122
|
} else {
|
|
101
|
-
|
|
123
|
+
output(`Deploy failed with: ${dResponse.statusCode}`);
|
|
124
|
+
throw new Error(`Lambda call to DeployVersionfailed with: ${dResponse.statusCode}`);
|
|
102
125
|
}
|
|
103
126
|
} else {
|
|
104
127
|
throw new Error(`Lambda call to DeployVersion failed: ${JSON.stringify(response)}`);
|
|
@@ -6,12 +6,19 @@ import { promises as fs, createReadStream } from 'fs';
|
|
|
6
6
|
import * as path from 'path';
|
|
7
7
|
import * as s3 from '@aws-sdk/client-s3';
|
|
8
8
|
import { Upload } from '@aws-sdk/lib-storage';
|
|
9
|
-
|
|
10
|
-
import { IDeployVersionPreflightResponse } from '@pwrdrvr/microapps-deployer';
|
|
9
|
+
import { IDeployVersionPreflightResponse } from '@pwrdrvr/microapps-deployer-lib';
|
|
11
10
|
import { contentType } from 'mime-types';
|
|
12
11
|
import pMap from 'p-map';
|
|
13
12
|
|
|
14
13
|
export default class S3TransferUtility {
|
|
14
|
+
/**
|
|
15
|
+
* @deprecated 2021-11-27
|
|
16
|
+
*
|
|
17
|
+
* @param s3Path
|
|
18
|
+
* @param destPrefixPath
|
|
19
|
+
* @param bucketName
|
|
20
|
+
* @param preflightResponse
|
|
21
|
+
*/
|
|
15
22
|
public static async UploadDir(
|
|
16
23
|
s3Path: string,
|
|
17
24
|
destPrefixPath: string,
|
|
@@ -70,7 +77,12 @@ export default class S3TransferUtility {
|
|
|
70
77
|
// Recursive getFiles from
|
|
71
78
|
// https://stackoverflow.com/a/45130990/831465
|
|
72
79
|
|
|
73
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Resursively enumerate the files to be uploaded
|
|
82
|
+
* @param dir
|
|
83
|
+
* @returns
|
|
84
|
+
*/
|
|
85
|
+
public static async GetFiles(dir: string): Promise<string | string[]> {
|
|
74
86
|
const dirents = await fs.readdir(dir, { withFileTypes: true });
|
|
75
87
|
const files = await Promise.all(
|
|
76
88
|
dirents.map((dirent) => {
|
|
@@ -1,23 +1,55 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import { IConfig } from './config/Config';
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import { IDeployVersionPreflightResponse } from '@pwrdrvr/microapps-deployer-lib';
|
|
3
|
+
import * as fs from 'fs-extra';
|
|
4
|
+
import { IConfig } from '../config/Config';
|
|
6
5
|
import S3TransferUtility from './S3TransferUtility';
|
|
7
6
|
|
|
8
7
|
export default class S3Uploader {
|
|
8
|
+
/**
|
|
9
|
+
* Copy files to local upload directory
|
|
10
|
+
* @param config
|
|
11
|
+
* @param s3UploadPath
|
|
12
|
+
* @param preflightResponse
|
|
13
|
+
*/
|
|
14
|
+
public static async CopyToUploadDir(config: IConfig, s3UploadPath: string): Promise<void> {
|
|
15
|
+
const { destinationPrefix } = S3Uploader.ParseUploadPath(s3UploadPath);
|
|
16
|
+
|
|
17
|
+
// Make a local root dir for the upload
|
|
18
|
+
const tempUploadPath = path.join(S3Uploader._tempDir, destinationPrefix);
|
|
19
|
+
await S3Uploader.removeTempDirIfExists();
|
|
20
|
+
await fs.mkdir(tempUploadPath, { recursive: true });
|
|
21
|
+
|
|
22
|
+
// Copy the files in the source dir to the root dir
|
|
23
|
+
// Note: It would be faster to move the files, then move them back
|
|
24
|
+
await fs.copy(config.app.staticAssetsPath, tempUploadPath);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public static ParseUploadPath(s3UploadPath: string): {
|
|
28
|
+
bucketName: string;
|
|
29
|
+
destinationPrefix: string;
|
|
30
|
+
} {
|
|
31
|
+
// Parse the S3 Source URI
|
|
32
|
+
const uri = new URL(s3UploadPath);
|
|
33
|
+
const bucketName = uri.host;
|
|
34
|
+
const destinationPrefix = uri.pathname.length >= 1 ? uri.pathname.slice(1) : '';
|
|
35
|
+
|
|
36
|
+
return { bucketName, destinationPrefix };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Upload files to S3
|
|
41
|
+
* @deprecated 2021-11-27
|
|
42
|
+
* @param config
|
|
43
|
+
* @param s3UploadPath
|
|
44
|
+
* @param preflightResponse
|
|
45
|
+
*/
|
|
9
46
|
public static async Upload(
|
|
10
47
|
config: IConfig,
|
|
11
48
|
s3UploadPath: string,
|
|
12
49
|
preflightResponse: IDeployVersionPreflightResponse,
|
|
13
50
|
): Promise<void> {
|
|
14
51
|
try {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
// Parse the S3 Source URI
|
|
18
|
-
const uri = new URL(s3UploadPath);
|
|
19
|
-
const bucketName = uri.host;
|
|
20
|
-
const destinationPrefix = uri.pathname.length >= 1 ? uri.pathname.slice(1) : '';
|
|
52
|
+
const { destinationPrefix, bucketName } = S3Uploader.ParseUploadPath(s3UploadPath);
|
|
21
53
|
|
|
22
54
|
// Make a local root dir for the upload
|
|
23
55
|
const tempUploadPath = path.join(S3Uploader._tempDir, destinationPrefix);
|
|
@@ -56,4 +88,7 @@ export default class S3Uploader {
|
|
|
56
88
|
}
|
|
57
89
|
|
|
58
90
|
private static readonly _tempDir = './deploytool-temp';
|
|
91
|
+
public static get TempDir(): string {
|
|
92
|
+
return S3Uploader._tempDir;
|
|
93
|
+
}
|
|
59
94
|
}
|