renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
126 lines (125 loc) • 5.78 kB
JavaScript
import { SYSTEM_INSUFFICIENT_MEMORY } from "../../../constants/error-messages.js";
import { newlineRegex, regEx } from "../../regex.js";
import { GlobalConfig } from "../../../config/global.js";
import { logger } from "../../../logger/index.js";
import { isCommandWithOptions } from "../utils.js";
import { rawExec } from "../common.js";
import { uniq } from "../../uniq.js";
import { isNonEmptyString, isString } from "@sindresorhus/is";
import { join } from "shlex";
//#region lib/util/exec/docker/index.ts
const prefetchedImages = /* @__PURE__ */ new Map();
const digestRegex = regEx("Digest: (.*?)\n");
const sideCarName = "sidecar";
async function prefetchDockerImage(taggedImage) {
if (prefetchedImages.has(taggedImage)) logger.debug(`Docker image is already prefetched: ${taggedImage}@${prefetchedImages.get(taggedImage)}`);
else {
logger.debug(`Fetching Docker image: ${taggedImage}`);
const res = await rawExec(`docker pull ${taggedImage}`, {});
const imageDigest = digestRegex.exec(res?.stdout)?.[1] ?? "unknown";
logger.debug(`Finished fetching Docker image ${taggedImage}@${imageDigest}`);
prefetchedImages.set(taggedImage, imageDigest);
}
}
function expandVolumeOption(x) {
if (isNonEmptyString(x)) return [x, x];
if (Array.isArray(x) && x.length === 2) {
const [from, to] = x;
// v8 ignore else -- TODO: add test #40625
if (isNonEmptyString(from) && isNonEmptyString(to)) return [from, to];
}
return null;
}
function volumesEql(x, y) {
const [xFrom, xTo] = x;
const [yFrom, yTo] = y;
return xFrom === yFrom && xTo === yTo;
}
function prepareVolumes(volumes) {
return uniq(volumes.map(expandVolumeOption).filter((vol) => vol !== null), volumesEql).map(([from, to]) => `-v "${from}":"${to}"`);
}
function getContainerName(name, prefix) {
return `${prefix ?? "renovate_"}${name}`.replace(regEx(/\//g), "_");
}
function getContainerLabel(prefix) {
return `${prefix ?? "renovate_"}child`;
}
async function removeDockerContainer(image, prefix) {
const containerName = getContainerName(sideCarName, prefix);
let cmd = `docker ps --filter name=${containerName} -aq`;
try {
const containerId = (await rawExec(cmd, {}))?.stdout?.trim() || "";
if (containerId.length) {
logger.debug(`Removing container with ID: ${containerId}`);
cmd = `docker rm -f ${containerId}`;
await rawExec(cmd, {});
} else logger.trace({
image,
containerName
}, "No running containers to remove");
} catch (err) {
logger.warn({
image,
containerName,
cmd,
err
}, "Could not remove Docker container");
}
}
async function removeDanglingContainers() {
if (GlobalConfig.get("binarySource") !== "docker") return;
try {
const containerLabel = getContainerLabel(GlobalConfig.get("dockerChildPrefix"));
logger.debug(`Removing dangling child containers with label ${containerLabel}`);
const res = await rawExec(`docker ps --filter label=${containerLabel} -aq`, {});
if (res?.stdout?.trim().length) {
const containerIds = res.stdout.trim().split(newlineRegex).map((container) => container.trim()).filter(Boolean);
logger.debug({ containerIds }, "Removing dangling child containers");
await rawExec(`docker rm -f ${containerIds.join(" ")}`, {});
} else logger.debug("No dangling containers to remove");
} catch (err) {
if (err.errno === "ENOMEM") throw new Error(SYSTEM_INSUFFICIENT_MEMORY);
if (err.stderr?.includes("Cannot connect to the Docker daemon")) logger.info("No docker daemon found");
else logger.warn({ err }, "Error removing dangling containers");
}
}
async function generateDockerCommand(commands, preCommands, options, sideCarImage) {
const { envVars, cwd } = options;
const volumes = options.volumes ?? [];
const { localDir, cacheDir, containerbaseDir, dockerUser, dockerChildPrefix, dockerCliOptions } = GlobalConfig.get();
const result = ["docker run --rm"];
const containerName = getContainerName(sideCarName, dockerChildPrefix);
const containerLabel = getContainerLabel(dockerChildPrefix);
result.push(`--name=${containerName}`);
result.push(`--label=${containerLabel}`);
if (dockerUser) result.push(`--user=${dockerUser}`);
if (dockerCliOptions) result.push(dockerCliOptions);
const volumeDirs = [localDir, cacheDir];
if (containerbaseDir) if (cacheDir && containerbaseDir.startsWith(cacheDir)) logger.debug("containerbaseDir is inside cacheDir");
else {
logger.debug("containerbaseDir is separate from cacheDir");
volumeDirs.push(containerbaseDir);
}
else logger.debug("containerbaseDir is missing");
volumeDirs.push(...volumes);
result.push(...prepareVolumes(volumeDirs));
// v8 ignore else -- TODO: add test #40625
if (envVars) result.push(...uniq(envVars).filter(isString).map((e) => `-e ${e}`));
if (cwd) result.push(`-w "${cwd}"`);
logger.debug({ image: sideCarImage }, "Resolved tag constraint");
await prefetchDockerImage(sideCarImage);
result.push(sideCarImage);
const bashCommandParts = [];
for (const preCommand of preCommands) if (isCommandWithOptions(preCommand) && isString(join(preCommand.command))) if (preCommand.ignoreFailure) bashCommandParts.push(`${join(preCommand.command)} || true`);
else bashCommandParts.push(join(preCommand.command));
else if (isString(preCommand)) bashCommandParts.push(preCommand);
for (const command of commands) if (isCommandWithOptions(command)) if (command.ignoreFailure) bashCommandParts.push(`${join(command.command)} || true`);
else bashCommandParts.push(join(command.command));
else bashCommandParts.push(command);
const bashCommand = bashCommandParts.join(" && ");
result.push(`bash -l -c "${bashCommand.replace(regEx(/"/g), "\\\"")}"`);
return result.join(" ");
}
//#endregion
export { generateDockerCommand, removeDanglingContainers, removeDockerContainer };
//# sourceMappingURL=index.js.map