diff --git a/packages/aws-cdk/lib/archive.ts b/packages/aws-cdk/lib/archive.ts index a5994ea035369..04de202e868f1 100644 --- a/packages/aws-cdk/lib/archive.ts +++ b/packages/aws-cdk/lib/archive.ts @@ -4,6 +4,18 @@ import fs = require('fs-extra'); import glob = require('glob'); import path = require('path'); +const appendFiles = async (directory: string, files: string[], archive: archiver.Archiver): Promise => { + for (const file of files) { + const srcPath = path.join(directory, file); + const stat = await fs.stat(srcPath); + archive.append(fs.createReadStream(srcPath), { + mode: stat.mode, + name: file, + date: new Date('1980-01-01T00:00:00.000Z'), // reset dates to get the same hash for the same content + }); + } +}; + export function zipDirectory(directory: string, outputFile: string): Promise { return new Promise((ok, fail) => { // The below options are needed to support following symlinks when building zip files: @@ -24,17 +36,12 @@ export function zipDirectory(directory: string, outputFile: string): Promise { // Append files serially to ensure file order - archive.append(fs.createReadStream(path.join(directory, file)), { - name: file, - date: new Date('1980-01-01T00:00:00.000Z'), // reset dates to get the same hash for the same content - }); - }); + appendFiles(directory, files, archive).then(() => { + archive.finalize(); - archive.finalize(); - - // archive has been finalized and the output file descriptor has closed, resolve promise - output.once('close', () => ok()); + // archive has been finalized and the output file descriptor has closed, resolve promise + output.once('close', () => ok()); + }); }); } diff --git a/packages/aws-cdk/test/test-archive-mode/executable.txt b/packages/aws-cdk/test/test-archive-mode/executable.txt new file mode 100755 index 0000000000000..ccb69856e7157 --- /dev/null +++ b/packages/aws-cdk/test/test-archive-mode/executable.txt @@ -0,0 +1,2 @@ +I am file2 +BLA! \ No newline at end of file diff --git a/packages/aws-cdk/test/test-archive-mode/readable.txt b/packages/aws-cdk/test/test-archive-mode/readable.txt new file mode 100644 index 0000000000000..7bb7edbd4b634 --- /dev/null +++ b/packages/aws-cdk/test/test-archive-mode/readable.txt @@ -0,0 +1 @@ +I am file1 \ No newline at end of file diff --git a/packages/aws-cdk/test/test.archive.ts b/packages/aws-cdk/test/test.archive.ts index c709cce749765..20fb6d23ed22d 100644 --- a/packages/aws-cdk/test/test.archive.ts +++ b/packages/aws-cdk/test/test.archive.ts @@ -70,5 +70,39 @@ export = { await fs.remove(stagingDir); await fs.remove(extractDir); test.done(); - } + }, + + async 'zipDirectory preserves mode of source files'(test: Test) { + const stagingDir = await fs.mkdtemp(path.join(os.tmpdir(), 'test.archive')); + const zipFile = path.join(stagingDir, 'output.zip'); + const originalDir = path.join(__dirname, 'test-archive-mode'); + const extractDir = await fs.mkdtemp(path.join(os.tmpdir(), 'test.archive.mode')); + await zipDirectory(originalDir, zipFile); + + // unzip and verify that the resulting tree is the same + await exec(`unzip ${zipFile}`, { cwd: extractDir }); + + // tslint:disable-next-line:no-bitwise + const beExecutable = fs.constants.S_IXGRP | fs.constants.S_IXOTH | fs.constants.S_IXUSR; + + let readable = true; + try { + await fs.access(path.join(extractDir, 'readable.txt')); + } catch { + readable = false; + } + test.ok(readable, 'extracted readable.txt cannot be accessed'); + + let executable = true; + try { + await fs.access(path.join(extractDir, 'executable.txt'), beExecutable); + } catch { + executable = false; + } + test.ok(executable, 'extracted executable.txt cannot be executable'); + + await fs.remove(stagingDir); + await fs.remove(extractDir); + test.done(); + }, };