visual-journal / scripts /cleanup-docker-fixtures.mjs
misonL's picture
Deploy 96f3dcf to Docker Space
919485b verified
Raw
History Blame Contribute Delete
5.57 kB
#!/usr/bin/env node
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { isMainModule, pickFailureOutput, printJson, runCommand } from './command-center-utils.mjs';
const LEGACY_FIXTURE_CONTAINER = 'gipc-local-image-fixture';
const LEGACY_FIXTURE_DESTINATION = '/workspace';
const DOCKER_TIMEOUT_MS = 30_000;
const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
export function hasLegacyFixtureRepoMount(mounts, repoRoot = REPO_ROOT) {
const resolvedRepoRoot = resolve(repoRoot);
return mounts.some(
(mount) =>
mount?.Type === 'bind' &&
typeof mount.Source === 'string' &&
resolve(mount.Source) === resolvedRepoRoot &&
mount.Destination === LEGACY_FIXTURE_DESTINATION
);
}
export function parseDockerInspectContainer(output) {
const parsed = JSON.parse(output);
if (!Array.isArray(parsed) || !parsed[0] || typeof parsed[0] !== 'object') {
throw new Error('docker inspect did not return a container object');
}
return parsed[0];
}
export function summarizeDockerMounts(mounts) {
return mounts.map((mount) => ({
type: mount.Type,
source: mount.Source,
destination: mount.Destination,
mode: mount.Mode,
writable: mount.RW
}));
}
function parseArgs(argv) {
const options = {
dryRun: false,
help: false,
containerName: LEGACY_FIXTURE_CONTAINER
};
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
if (arg === '--dry-run') {
options.dryRun = true;
continue;
}
if (arg === '--help' || arg === '-h') {
options.help = true;
continue;
}
if (arg === '--container') {
const value = argv[index + 1];
if (!value) throw new Error('--container requires a value');
options.containerName = value;
index += 1;
continue;
}
throw new Error(`Unknown option: ${arg}`);
}
return options;
}
function printHelp() {
console.log(`用法:npm run docker:cleanup-fixtures -- [--dry-run] [--container <name>]
清理遗留的本地图片上游 Docker fixture 容器。该脚本只会删除同时满足以下条件的容器:
- 容器名匹配,默认 ${LEGACY_FIXTURE_CONTAINER}
- 存在 ${REPO_ROOT} -> ${LEGACY_FIXTURE_DESTINATION} 的整仓 bind mount
推荐使用 npm run smoke:image-upstream-local 启动本地 fixture。不要用 docker run -v "$PWD:${LEGACY_FIXTURE_DESTINATION}" 启动 fixture。`);
}
function isMissingContainer(result) {
const output = `${result.stdout}\n${result.stderr}`;
return /No such object|No such container/i.test(output);
}
export function buildAbsentReport(containerName) {
return {
ok: true,
command: 'docker:cleanup-fixtures',
container: containerName,
present: false,
unsafe_repo_mount: false,
removed: false
};
}
export function buildSkippedReport(containerName, mounts) {
return {
ok: true,
command: 'docker:cleanup-fixtures',
container: containerName,
present: true,
unsafe_repo_mount: false,
removed: false,
mounts: summarizeDockerMounts(mounts)
};
}
function cleanupLegacyFixtureContainer(options) {
const inspect = runCommand('docker', ['inspect', options.containerName], { timeoutMs: DOCKER_TIMEOUT_MS });
if (!inspect.ok) {
if (isMissingContainer(inspect)) return buildAbsentReport(options.containerName);
return {
ok: false,
command: 'docker:cleanup-fixtures',
phase: 'inspect',
container: options.containerName,
error: pickFailureOutput(inspect)
};
}
let container;
try {
container = parseDockerInspectContainer(inspect.stdout);
} catch (error) {
return {
ok: false,
command: 'docker:cleanup-fixtures',
phase: 'parse-inspect',
container: options.containerName,
error: error instanceof Error ? error.message : String(error)
};
}
const mounts = Array.isArray(container.Mounts) ? container.Mounts : [];
if (!hasLegacyFixtureRepoMount(mounts, REPO_ROOT)) return buildSkippedReport(options.containerName, mounts);
if (options.dryRun) {
return {
ok: true,
command: 'docker:cleanup-fixtures',
container: options.containerName,
present: true,
unsafe_repo_mount: true,
removed: false,
dry_run: true,
mounts: summarizeDockerMounts(mounts)
};
}
const remove = runCommand('docker', ['rm', '-f', options.containerName], { timeoutMs: DOCKER_TIMEOUT_MS });
return {
ok: remove.ok,
command: 'docker:cleanup-fixtures',
container: options.containerName,
present: true,
unsafe_repo_mount: true,
removed: remove.ok,
mounts: summarizeDockerMounts(mounts),
...(remove.ok ? {} : { phase: 'remove', error: pickFailureOutput(remove) })
};
}
function main() {
const options = parseArgs(process.argv.slice(2));
if (options.help) {
printHelp();
return;
}
const report = cleanupLegacyFixtureContainer(options);
printJson(report);
if (!report.ok) process.exitCode = 1;
}
if (isMainModule(import.meta.url, process.argv[1])) main();