#!/usr/bin/env node // Runs a command as an image built by kaniko would run it, with no daemon. // // node scripts/run-image.mjs [NAME=value]... [-- command...] // // kaniko leaves the last stage's filesystem at / once a build ends. This // reads the image's config from the OCI layout the build wrote, and starts // its entrypoint with the command given, or the image's own, in that // filesystem: with the image's environment, working directory and user. It // exits with the command's status. import { spawn } from 'node:child_process'; import { readFileSync } from 'node:fs'; import { join } from 'node:path'; const [layout, ...rest] = process.argv.slice(2); if (!layout) { console.error( 'usage: run-image.mjs [NAME=value]... [-- command...]', ); process.exit(2); } const dash = rest.indexOf('--'); const vars = dash < 0 ? rest : rest.slice(0, dash); const command = dash < 0 ? [] : rest.slice(dash + 1); /** @param {string} digest */ const blob = (digest) => JSON.parse(readFileSync(join(layout, 'blobs', ...digest.split(':')), 'utf8')); const index = blob( JSON.parse(readFileSync(join(layout, 'index.json'), 'utf8')).manifests[0] .digest, ); const manifest = index.manifests ? blob(index.manifests[0].digest) : index; const { config } = blob(manifest.config.digest); const env = Object.fromEntries( [...(config.Env ?? []), ...vars].map((pair) => { const at = pair.indexOf('='); return [pair.slice(0, at), pair.slice(at + 1)]; }), ); /** @param {string} user */ function ids(user) { if (!user) return {}; const [name, group] = user.split(':'); if (/^\d+$/.test(name)) { return { uid: Number(name), gid: Number(group ?? name) }; } const entry = readFileSync('/etc/passwd', 'utf8') .split('\n') .map((line) => line.split(':')) .find((fields) => fields[0] === name); if (!entry) throw new Error(`the image's user ${name} is not in /etc/passwd`); return { uid: Number(entry[2]), gid: Number(entry[3]) }; } const argv = [ ...(config.Entrypoint ?? []), ...(command.length ? command : (config.Cmd ?? [])), ]; const child = spawn(argv[0], argv.slice(1), { cwd: config.WorkingDir || '/', env, stdio: 'inherit', ...ids(config.User ?? ''), }); child.on('exit', (code, signal) => process.exit(code ?? (signal ? 128 : 1)));