Skip to content

Commit f5e4dcc

Browse files
authored
Merge pull request #51 from ppandit-sfdc/fix/copyfile-preserve-permissions
Fix permission-dropped executable bit in zip dependency staging
2 parents 0b50444 + 75fcca2 commit f5e4dcc

2 files changed

Lines changed: 49 additions & 6 deletions

File tree

src/utils/zipBuilder.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616
import { existsSync, mkdtempSync, rmSync, statSync, readFileSync } from 'node:fs';
17-
import { mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';
17+
import { copyFile, mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';
1818
import { tmpdir } from 'node:os';
1919
import * as path from 'node:path';
2020
import { debuglog } from 'node:util';
@@ -146,11 +146,6 @@ function assertBuildFileExists(label: string, filePath: string): void {
146146
}
147147
}
148148

149-
async function copyFile(src: string, dest: string): Promise<void> {
150-
const data = new Uint8Array(await readFile(src));
151-
await writeFile(dest, data);
152-
}
153-
154149
async function copyTree(src: string, dest: string): Promise<void> {
155150
const entries = await readdir(src, { withFileTypes: true });
156151
await mkdir(dest, { recursive: true });

test/utils/zipBuilder.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
readFileSync,
2323
symlinkSync,
2424
chmodSync,
25+
statSync,
2526
} from 'node:fs';
2627
import { tmpdir } from 'node:os';
2728
import * as path from 'node:path';
@@ -464,4 +465,51 @@ describe('zipBuilder.prepareDependencyArchive', () => {
464465
expect(caught!.message).to.match(/Dockerfile\.dependencies/);
465466
expect(missing.calls.build).to.have.length(0);
466467
});
468+
469+
it('stages build_native_dependencies.sh with executable permission preserved', async function () {
470+
if (process.platform === 'win32') {
471+
this.skip();
472+
return;
473+
}
474+
chmodSync(path.join(baseDir, 'build_native_dependencies.sh'), 0o755);
475+
476+
const { runner } = makeRunner({
477+
imageExists: true,
478+
onRun: (mountPath) => {
479+
const stagedScript = path.join(mountPath, 'build_native_dependencies.sh');
480+
const mode = statSync(stagedScript).mode & 0o777;
481+
expect(mode & 0o111, 'staged build script must have executable bits').to.not.equal(0);
482+
writeFileSync(path.join(mountPath, 'native_dependencies.tar.gz'), 'data');
483+
},
484+
});
485+
486+
await prepareDependencyArchive(baseDir, 'default', 'script', () => {}, runner);
487+
});
488+
489+
it('preserves executable permissions on py-files copied via copyTree (function packages)', async function () {
490+
if (process.platform === 'win32') {
491+
this.skip();
492+
return;
493+
}
494+
495+
const { runner } = makeRunner({
496+
imageExists: true,
497+
onRun: (mountPath) => {
498+
const pyFilesSrc = path.join(mountPath, 'py-files');
499+
mkdirSync(pyFilesSrc);
500+
const execScript = path.join(pyFilesSrc, 'run_me.sh');
501+
writeFileSync(execScript, '#!/bin/bash\necho hello\n');
502+
chmodSync(execScript, 0o755);
503+
writeFileSync(path.join(pyFilesSrc, 'lib.py'), 'pass');
504+
},
505+
});
506+
507+
await prepareDependencyArchive(baseDir, 'default', 'function', () => {}, runner);
508+
509+
const dest = path.join(baseDir, 'payload', 'py-files');
510+
const scriptMode = statSync(path.join(dest, 'run_me.sh')).mode & 0o777;
511+
const libMode = statSync(path.join(dest, 'lib.py')).mode & 0o777;
512+
expect(scriptMode & 0o111, 'executable script in py-files must retain execute bits').to.not.equal(0);
513+
expect(libMode & 0o111, 'non-executable file should not gain execute bits').to.equal(0);
514+
});
467515
});

0 commit comments

Comments
 (0)