Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 28 additions & 8 deletions bolt/src/PackageBuilder.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@
* limitations under the License.
*/

const { readFileSync } = require('node:fs');
const { exec, removeUnder } = require('./utils.cjs');
const { lstatSync, readFileSync } = require('node:fs');
const { join } = require('node:path');
const { exec, execv, removeUnder, resolveUnder, touchUnder } = require('./utils.cjs');

const SYNC_FLAGS = ['-rlpgoDX'];

class PackageBuilder {
constructor(workDir) {
Expand All @@ -28,19 +31,23 @@ class PackageBuilder {
this.initialized = false;
}

merge(rootfs, resetPaths = [], excludePaths = []) {
for (const path of excludePaths) {
merge(rootfs, override = [], exclude = []) {
for (const path of exclude) {
if (!removeUnder(rootfs, path)) {
console.warn(`Excluded path not found, skipping: ${path}`);
}
Comment thread
astolcenburg marked this conversation as resolved.
}
if (this.initialized) {
for (const path of resetPaths) {
if (!removeUnder(`${this.workDir}/bundle/rootfs`, path)) {
console.warn(`Reset path not found, skipping: ${path}`);
const touched = [];
for (const path of override) {
if (touchUnder(rootfs, path)) {
touched.push(path);
} else {
console.warn(`Override path not found, skipping: ${path}`);
}
}
exec(`rsync -crlpgoDX ${rootfs}/ ${this.workDir}/bundle/rootfs`);
execv('rsync', [...SYNC_FLAGS, '-c', '--', `${rootfs}/`, join(this.workDir, 'bundle', 'rootfs')]);
this.resync(rootfs, touched, [...SYNC_FLAGS, '-t']);
exec(`umoci repack --refresh-bundle --image ${this.workDir}/oci ${this.workDir}/bundle`);
} else {
exec(`umoci insert --rootless --image ${this.workDir}/oci ${rootfs} /`);
Expand All @@ -49,6 +56,19 @@ class PackageBuilder {
}
}

resync(rootfs, paths, flags) {
const bundleRoot = join(this.workDir, 'bundle', 'rootfs');
for (const path of paths) {
const source = resolveUnder(rootfs, path);
const dest = resolveUnder(bundleRoot, path);
if (!source || !dest || !lstatSync(source, { throwIfNoEntry: false })?.isDirectory()) {
console.warn(`Resync path not found, skipping: ${path}`);
continue;
}
execv('rsync', [...flags, '--', `${source}/`, dest]);
}
}

finish(layer) {
exec(`umoci gc --layout=${this.workDir}/oci`);
const index = JSON.parse(readFileSync(`${this.workDir}/oci/index.json`));
Expand Down
42 changes: 33 additions & 9 deletions bolt/src/utils.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* limitations under the License.
*/

const { renameSync, copyFileSync, linkSync, unlinkSync, mkdtempSync, statSync, lstatSync, rmSync, realpathSync } = require('node:fs');
const { renameSync, copyFileSync, linkSync, unlinkSync, mkdtempSync, statSync, lstatSync, rmSync, realpathSync, lutimesSync, readdirSync } = require('node:fs');
const { join, dirname, basename, isAbsolute, resolve, relative, sep } = require('node:path');
const { execSync, execFileSync } = require('node:child_process');
const config = require('./config.cjs');
Expand Down Expand Up @@ -117,29 +117,51 @@ function assertFile(path) {
}
}

function removeUnder(baseDir, relPath) {
const base = realpathSync(resolve(baseDir));
const target = resolve(base, relPath);

let realTarget;
function resolveUnder(baseDir, relPath) {
let base, realTarget;
try {
base = realpathSync(resolve(baseDir));
const target = resolve(base, relPath);
realTarget = join(realpathSync(dirname(target)), basename(target));
} catch (err) {
if (err.code === 'ENOENT') return false;
if (err.code === 'ENOENT') return null;
throw err;
}

const rel = relative(base, realTarget);
if (rel === '' || rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
throw new Error(`Refusing to remove ${JSON.stringify(relPath)}: resolves to or outside ${baseDir}`);
throw new Error(`Path ${JSON.stringify(relPath)} resolves to or outside ${baseDir}`);
}
if (lstatSync(realTarget, { throwIfNoEntry: false })) {
return realTarget;
}

function removeUnder(baseDir, relPath) {
const realTarget = resolveUnder(baseDir, relPath);
if (realTarget && lstatSync(realTarget, { throwIfNoEntry: false })) {
rmSync(realTarget, { recursive: true, force: true });
return true;
}
return false;
}

function touchTree(target, now = new Date()) {
lutimesSync(target, now, now);
if (lstatSync(target).isDirectory()) {
for (const entry of readdirSync(target)) {
touchTree(join(target, entry), now);
}
}
}

function touchUnder(baseDir, relPath) {
const realTarget = resolveUnder(baseDir, relPath);
if (realTarget && lstatSync(realTarget, { throwIfNoEntry: false })) {
touchTree(realTarget);
return true;
}
return false;
}

exports.exec = exec;
exports.execv = execv;
exports.execNoOutput = execNoOutput;
Expand All @@ -150,3 +172,5 @@ exports.makeWorkDir = makeWorkDir;
exports.resolvePath = resolvePath;
exports.assertFile = assertFile;
exports.removeUnder = removeUnder;
exports.resolveUnder = resolveUnder;
exports.touchUnder = touchUnder;