From 750708bc75d61e7a1564f64c8573d8ea05c44149 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Thu, 25 Jul 2019 20:21:06 +0200 Subject: [PATCH] fix(toolkit): avoid EMFILE and preserve mode when zipping (#3428) To preserve file order using `archiver` files must be appended serially either using stream or buffer (appending by file path does not preserve order even when done serially). Appending using buffer seems to be the only way to solve `EMFILE` errors. Call `fs.stat` before appending to preserve mode. Closes #3145, Closes #3344, Closes #3413 --- packages/aws-cdk/lib/archive.ts | 12 ++++++++---- packages/aws-cdk/test/test-archive/executable.txt | 0 packages/aws-cdk/test/test.archive.ts | 6 ++++++ 3 files changed, 14 insertions(+), 4 deletions(-) create mode 100755 packages/aws-cdk/test/test-archive/executable.txt diff --git a/packages/aws-cdk/lib/archive.ts b/packages/aws-cdk/lib/archive.ts index a5994ea035369..e149ac287b5b2 100644 --- a/packages/aws-cdk/lib/archive.ts +++ b/packages/aws-cdk/lib/archive.ts @@ -5,7 +5,7 @@ import glob = require('glob'); import path = require('path'); export function zipDirectory(directory: string, outputFile: string): Promise { - return new Promise((ok, fail) => { + return new Promise(async (ok, fail) => { // The below options are needed to support following symlinks when building zip files: // - nodir: This will prevent symlinks themselves from being copied into the zip. // - follow: This will follow symlinks and copy the files within. @@ -24,12 +24,16 @@ export function zipDirectory(directory: string, outputFile: string): Promise { // Append files serially to ensure file order - archive.append(fs.createReadStream(path.join(directory, file)), { + // Append files serially to ensure file order + for (const file of files) { + const fullPath = path.join(directory, file); + const [data, stat] = await Promise.all([fs.readFile(fullPath), fs.stat(fullPath)]); + archive.append(data, { name: file, date: new Date('1980-01-01T00:00:00.000Z'), // reset dates to get the same hash for the same content + mode: stat.mode, }); - }); + } archive.finalize(); diff --git a/packages/aws-cdk/test/test-archive/executable.txt b/packages/aws-cdk/test/test-archive/executable.txt new file mode 100755 index 0000000000000..e69de29bb2d1d diff --git a/packages/aws-cdk/test/test.archive.ts b/packages/aws-cdk/test/test.archive.ts index c709cce749765..8eda93cd28465 100644 --- a/packages/aws-cdk/test/test.archive.ts +++ b/packages/aws-cdk/test/test.archive.ts @@ -32,6 +32,12 @@ export = { test.equal(dates[0], '1980-01-01T00:00:00.000Z', 'Dates are not reset'); test.equal(new Set(dates).size, 1, 'Dates are not equal'); + // check that mode is preserved + const stat = await fs.stat(path.join(extractDir, 'executable.txt')); + // tslint:disable-next-line:no-bitwise + const isExec = (stat.mode & fs.constants.S_IXUSR) || (stat.mode & fs.constants.S_IXGRP) || (stat.mode & fs.constants.S_IXOTH); + test.ok(isExec, 'File is not executable'); + await fs.remove(stagingDir); await fs.remove(extractDir); test.done();