Skip to content

Commit a2076d0

Browse files
committed
Add cdk-helper modules
1 parent a0f0837 commit a2076d0

File tree

11 files changed

+227
-43
lines changed

11 files changed

+227
-43
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
**/coverage
1515
**/coverage-reports
1616
**/.nyc_output
17+
**/mock-*
1718

1819
# misc
1920
**/npm-debug.log

deployment/cdk-solution-helper/README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
## CDK Solution Helper
22

3-
**For aws-solutions internal purposes only**
3+
**For aws-solutions internal purposes only**
44

5-
This is a module to package templates and lambda binaries on solution internal pipelines.
6-
It achieves the following:
5+
`cdk-solution-helper` runs on solution internal pipeline and makes needed artifact modifications to support 1-click deployment using solution CloudFormation template.
6+
Additionally, it packages templates and lambda binaries and prepares them to be staged on the solution buckets.
7+
8+
It does the following:
79
+ Replace references to local cdk generated assets in the template with solution assets
810

911
For eg.
1012
> cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region} --> solutions-${AWS::Region}
1113
1214
+ Package lambda and UI assets generated by cdk and stage them on solution buckets
1315

14-
`cdk-solution-helper` runs on solution internal pipeline and makes needed artifact modifications to support 1-click deployment using solution CloudFormation template.
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import { handler } from "./index";
7+
import { CDKAssetPackager } from "./asset-packager";
8+
import { mkdir, writeFile, rm } from "node:fs/promises";
9+
import path from "path";
10+
11+
const mockAssetDirectoryPath = path.join(__dirname, "mock-dir");
12+
const mockOutputPath = path.join(__dirname, "mock-dir-output");
13+
describe("Handler", () => {
14+
beforeAll(async function Arrange() {
15+
await rm(mockAssetDirectoryPath, { recursive: true, force: true });
16+
await rm(mockOutputPath, { recursive: true, force: true });
17+
await mkdir(mockAssetDirectoryPath);
18+
await mkdir(mockOutputPath);
19+
});
20+
21+
it("should fail in absence of path inputs ", async function () {
22+
await expect(handler("", "")).rejects.toThrowError("undefined input path");
23+
await expect(handler(undefined, undefined)).rejects.toThrowError("undefined input path");
24+
});
25+
26+
it("should fail for invalid cdk asset path", async function () {
27+
await expect(handler("invalidPath", mockOutputPath)).rejects.toThrowError(); // TODO check error thrown
28+
});
29+
30+
it("should succeed if cdk assets not found", async function () {
31+
await expect(handler(mockAssetDirectoryPath, "invalidPath")).resolves;
32+
});
33+
34+
it("should fail for invalid output path", async function () {
35+
// Arrange
36+
const mockAssetPath = path.join(mockAssetDirectoryPath, "asset.cdkAsset.zip");
37+
await writeFile(mockAssetPath, "NoOp");
38+
// Act, Assert
39+
await expect(handler(mockAssetDirectoryPath, "invalidPath")).rejects.toThrowError(); // TODO check error thrown
40+
});
41+
42+
it("should succeed for valid paths", async function () {
43+
await expect(handler(mockAssetDirectoryPath, mockOutputPath)).resolves;
44+
// check for zip created
45+
});
46+
47+
afterAll(async function Cleanup() {
48+
// await rm(mockAssetDirectoryPath, { recursive: true, force: true });
49+
// await rm(mockOutputPath, { recursive: true, force: true });
50+
});
51+
});
52+
53+
describe("CDKAssetPackager", () => {
54+
beforeAll(function Arrange() {
55+
// mock fs calls
56+
});
57+
describe("getAssetPaths", function () {
58+
xit("should return empty array for invalid path", function () {});
59+
xit("should return empty array when no assets found", function () {});
60+
xit("should return array of paths when assets found", function () {});
61+
});
62+
describe("createAssetZip", function () {
63+
xit("should skip doing anything if path not a folder", function () {});
64+
xit("should zip assets in the folder for valid path", function () {});
65+
xit("should throw error if error encountered", function () {});
66+
});
67+
describe("moveZip", function () {
68+
xit("should move zips for valid paths", function () {});
69+
xit("should throw error if error encountered", function () {});
70+
});
71+
});

deployment/cdk-solution-helper/asset-packager/asset-packager.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ import { readdir, lstat, rename } from "node:fs/promises";
77
import path from "path";
88
import AdmZip from "adm-zip";
99

10-
export class AssetPackager {
11-
constructor(readonly cdkAssetPath: string) {}
12-
async getCDKAssetFiles() {
10+
export class CDKAssetPackager {
11+
constructor(private readonly assetFolderPath: string) {}
12+
13+
async getAssetPaths() {
1314
try {
14-
const allFiles = await readdir(this.cdkAssetPath);
15+
const allFiles = await readdir(this.assetFolderPath);
1516
const assetFilePaths = allFiles
1617
.filter((file) => file.includes("asset"))
17-
.map((file) => path.join(this.cdkAssetPath, file));
18+
.map((file) => path.join(this.assetFolderPath, file));
1819
return assetFilePaths;
1920
} catch (err) {
2021
console.error(err);
@@ -26,22 +27,23 @@ export class AssetPackager {
2627
* @description goes down 1 level deep to create zip
2728
* @param folderPath
2829
*/
29-
async createZip(folderPath: string) {
30+
async createAssetZip(folderPath: string) {
3031
const isDir = (await lstat(folderPath)).isDirectory();
3132
if (isDir) {
3233
const zip = new AdmZip();
3334
const allFiles = await readdir(folderPath);
3435
for (const file of allFiles) zip.addLocalFile(path.join(folderPath, file));
35-
const newName = `${folderPath.split("/").pop()?.split(".").pop()}.zip`;
36-
zip.writeZip(path.join(this.cdkAssetPath, newName));
36+
const zipName = `${folderPath.split("/").pop()}.zip`;
37+
zip.writeZip(path.join(this.assetFolderPath, zipName));
3738
}
3839
}
3940

4041
async moveZips(outputPath: string) {
41-
const allFiles = await readdir(this.cdkAssetPath);
42-
const allZips = allFiles.filter((file) => path.extname(file) === ".zip");
43-
for (const zip of allZips) {
44-
await rename(path.join(this.cdkAssetPath, zip), path.join(outputPath, zip));
42+
const allFiles = await readdir(this.assetFolderPath);
43+
const allZipPaths = allFiles.filter((file) => path.extname(file) === ".zip");
44+
for (const zipPath of allZipPaths) {
45+
await rename(path.join(this.assetFolderPath, zipPath), path.join(outputPath, zipPath.split("asset.").pop()!));
46+
// remove cdk prepended string "asset.*"
4547
}
4648
}
4749
}
Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,30 @@
1-
import { AssetPackager } from "./asset-packager";
2-
import * as path from "path";
3-
async function handler() {
4-
const cdkAssetPath = process.argv[2];
5-
const outputPath = process.argv[3];
6-
const assetPackager = new AssetPackager(cdkAssetPath);
7-
const assetFilePaths = await assetPackager.getCDKAssetFiles();
8-
for (const path of assetFilePaths) {
9-
await assetPackager.createZip(path);
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import { CDKAssetPackager } from "./asset-packager";
7+
import path from "path";
8+
9+
export async function handler(cdkAssetFolderPath: string | undefined, outputPath: string | undefined) {
10+
if (!cdkAssetFolderPath || !outputPath) throw new Error("undefined input path");
11+
const assetPackager = new CDKAssetPackager(cdkAssetFolderPath);
12+
const assetPaths = await assetPackager.getAssetPaths();
13+
for (const path of assetPaths) {
14+
await assetPackager.createAssetZip(path);
1015
}
1116
await assetPackager.moveZips(outputPath);
1217
}
1318

14-
handler()
15-
.then(() => console.log("all assets packaged"))
16-
.catch((err) => {
17-
console.error(err);
18-
throw err;
19-
});
19+
if (require.main === module) {
20+
// this module was run directly from the command line, getting command line arguments
21+
// e.g. npx ts-node index.ts cdkAssetPath outputPath
22+
const cdkAssetPath = process.argv[3];
23+
const outputPath = process.argv[4];
24+
handler(cdkAssetPath, outputPath)
25+
.then(() => console.log("all assets packaged"))
26+
.catch((err) => {
27+
console.error(err);
28+
throw err;
29+
});
30+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import type { Config } from "jest";
7+
8+
const config: Config = {
9+
clearMocks: false,
10+
11+
collectCoverage: false,
12+
13+
// The directory where Jest should output its coverage files
14+
coverageDirectory: "coverage",
15+
16+
// An array of regexp pattern strings used to skip coverage collection
17+
coveragePathIgnorePatterns: ["/node_modules/"],
18+
19+
// An array of directory names to be searched recursively up from the requiring module's location
20+
moduleDirectories: ["node_modules"],
21+
22+
// An array of file extensions your modules use
23+
moduleFileExtensions: ["ts", "json", "jsx", "js", "tsx", "node"],
24+
25+
// Automatically reset mock state between every test
26+
resetMocks: false,
27+
28+
// The glob patterns Jest uses to detect test files
29+
testMatch: ["**/?(*.)+(spec|test).[t]s?(x)"],
30+
31+
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
32+
testPathIgnorePatterns: ["/node_modules/"],
33+
34+
// A map from regular expressions to paths to transformers
35+
transform: {
36+
"^.+\\.(t)sx?$": "ts-jest",
37+
},
38+
39+
// Indicates whether each individual test should be reported during the run
40+
verbose: true,
41+
42+
// An array of glob patterns indicating a set of files for which coverage information should be collected
43+
collectCoverageFrom: ["**/*.ts", "!**/*.spec.ts", "!./jest.config.ts", "!./jest.setup.ts"],
44+
45+
// A list of paths to modules that run some code to configure or set up the testing environment
46+
// setupFiles: ["./jest.setup.ts"],
47+
48+
// coverageReporters: [["lcov", { projectRoot: "../" }], "text"],
49+
50+
rootDir: "./",
51+
};
52+
53+
export default config;

deployment/cdk-solution-helper/package-lock.json

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

deployment/cdk-solution-helper/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "cdk-solution-helper",
33
"version": "1.0.0",
4-
"description": "helper to update references in cdk generated cfn template",
4+
"description": "helper to update references in cdk generated cfn template and package lambda assets",
55
"main": "index.js",
66
"scripts": {
77
"test": "jest"
@@ -10,6 +10,7 @@
1010
"license": "Apache-2.0",
1111
"devDependencies": {
1212
"@types/adm-zip": "^0.5.0",
13+
"@types/jest": "^29.4.0",
1314
"@types/node": "^18.11.18",
1415
"jest": "^29.4.1",
1516
"ts-jest": "^29.0.5",

deployment/cdk-solution-helper/template-builder/index.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@
66
import { writeFile } from "node:fs/promises";
77
import { TemplateBuilder } from "./template-builder";
88

9-
async function handler() {
10-
const CDKHelper = new TemplateBuilder(process.argv[2], process.argv[3], process.argv[4], process.argv[5]);
9+
export async function handler(
10+
templatePath: string | undefined,
11+
solutionName: string | undefined,
12+
lambdaBucket: string | undefined,
13+
version: string | undefined
14+
) {
15+
if (!templatePath || !solutionName || !lambdaBucket || !version) throw new Error("undefined arguments");
16+
const CDKHelper = new TemplateBuilder(templatePath, solutionName, lambdaBucket, version);
1117
const templatePaths = await CDKHelper.getTemplateFilePaths();
1218
for (const path of templatePaths) {
1319
const templateContents = await CDKHelper.parseJsonTemplate(path);
@@ -19,9 +25,17 @@ async function handler() {
1925
}
2026
}
2127

22-
handler()
23-
.then(() => console.log("written all cfn templates"))
24-
.catch((err) => {
25-
console.error(err);
26-
throw err;
27-
});
28+
if (require.main === module) {
29+
// this module was run directly from the command line, getting command line arguments
30+
// e.g. npx ts-node index.ts templatePath mySolution lambdaAssetBucketName myVersion
31+
const templatePath = process.argv[3];
32+
const solutionName = process.argv[4];
33+
const lambdaBucketName = process.argv[5];
34+
const version = process.argv[6];
35+
handler(templatePath, solutionName, lambdaBucketName, version)
36+
.then(() => console.log("written all cfn templates"))
37+
.catch((err) => {
38+
console.error(err);
39+
throw err;
40+
});
41+
}

deployment/cdk-solution-helper/template-builder/template-builder.test.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,11 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
// TODO add unit tests
6+
import { handler } from "./index";
7+
8+
describe("Handler Tests", () => {
9+
it("should ", async function () {
10+
// Arrange, Act
11+
// Assert
12+
});
13+
});

0 commit comments

Comments
 (0)