renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
218 lines • 9.15 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.sideCarImage = void 0;
exports.prefetchDockerImage = prefetchDockerImage;
exports.resetPrefetchedImages = resetPrefetchedImages;
exports.getDockerTag = getDockerTag;
exports.removeDockerContainer = removeDockerContainer;
exports.removeDanglingContainers = removeDanglingContainers;
exports.generateDockerCommand = generateDockerCommand;
const tslib_1 = require("tslib");
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
const global_1 = require("../../../config/global");
const error_messages_1 = require("../../../constants/error-messages");
const logger_1 = require("../../../logger");
const datasource_1 = require("../../../modules/datasource");
const allVersioning = tslib_1.__importStar(require("../../../modules/versioning"));
const regex_1 = require("../../regex");
const uniq_1 = require("../../uniq");
const common_1 = require("../common");
const prefetchedImages = new Map();
const digestRegex = (0, regex_1.regEx)('Digest: (.*?)\n');
exports.sideCarImage = 'sidecar';
async function prefetchDockerImage(taggedImage) {
if (prefetchedImages.has(taggedImage)) {
logger_1.logger.debug(`Docker image is already prefetched: ${taggedImage}@${prefetchedImages.get(taggedImage)}`);
}
else {
logger_1.logger.debug(`Fetching Docker image: ${taggedImage}`);
const res = await (0, common_1.rawExec)(`docker pull ${taggedImage}`, {
encoding: 'utf-8',
});
const imageDigest = digestRegex.exec(res?.stdout)?.[1] ?? 'unknown';
logger_1.logger.debug(`Finished fetching Docker image ${taggedImage}@${imageDigest}`);
prefetchedImages.set(taggedImage, imageDigest);
}
}
function resetPrefetchedImages() {
prefetchedImages.clear();
}
function expandVolumeOption(x) {
if (is_1.default.nonEmptyString(x)) {
return [x, x];
}
if (Array.isArray(x) && x.length === 2) {
const [from, to] = x;
if (is_1.default.nonEmptyString(from) && is_1.default.nonEmptyString(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) {
const expanded = volumes.map(expandVolumeOption);
const filtered = expanded.filter((vol) => vol !== null);
const unique = (0, uniq_1.uniq)(filtered, volumesEql);
return unique.map(([from, to]) => `-v "${from}":"${to}"`);
}
function prepareCommands(commands) {
return commands.filter((command) => is_1.default.string(command));
}
async function getDockerTag(packageName, constraint, versioning) {
const versioningApi = allVersioning.get(versioning);
if (!versioningApi.isValid(constraint)) {
logger_1.logger.warn({ versioning, constraint }, `Invalid Docker image version constraint`);
return 'latest';
}
logger_1.logger.debug({ packageName, versioning, constraint }, `Found version constraint - checking for a compatible image to use`);
const imageReleases = await (0, datasource_1.getPkgReleases)({
datasource: 'docker',
packageName,
versioning,
});
if (imageReleases?.releases) {
let versions = imageReleases.releases.map((release) => release.version);
versions = versions.filter((version) => versioningApi.isVersion(version) &&
versioningApi.matches(version, constraint));
// Prefer stable versions over unstable, even if the range satisfies both types
if (!versions.every((version) => versioningApi.isStable(version))) {
logger_1.logger.debug('Filtering out unstable versions');
versions = versions.filter((version) => versioningApi.isStable(version));
}
const version = versions
.sort(versioningApi.sortVersions.bind(versioningApi))
.pop();
if (version) {
logger_1.logger.debug({ packageName, versioning, constraint, version }, `Found compatible image version`);
return version;
}
}
else {
logger_1.logger.error({ packageName }, `Docker exec: no releases found`);
return 'latest';
}
logger_1.logger.warn({ packageName, constraint, versioning }, 'Failed to find a tag satisfying constraint, using "latest" tag instead');
return 'latest';
}
function getContainerName(image, prefix) {
return `${prefix ?? 'renovate_'}${image}`.replace((0, regex_1.regEx)(/\//g), '_');
}
function getContainerLabel(prefix) {
return `${prefix ?? 'renovate_'}child`;
}
async function removeDockerContainer(image, prefix) {
const containerName = getContainerName(image, prefix);
let cmd = `docker ps --filter name=${containerName} -aq`;
try {
const res = await (0, common_1.rawExec)(cmd, {
encoding: 'utf-8',
});
const containerId = res?.stdout?.trim() || '';
if (containerId.length) {
logger_1.logger.debug(`Removing container with ID: ${containerId}`);
cmd = `docker rm -f ${containerId}`;
await (0, common_1.rawExec)(cmd, {
encoding: 'utf-8',
});
}
else {
logger_1.logger.trace({ image, containerName }, 'No running containers to remove');
}
}
catch (err) {
logger_1.logger.warn({ image, containerName, cmd, err }, 'Could not remove Docker container');
}
}
async function removeDanglingContainers() {
if (global_1.GlobalConfig.get('binarySource') !== 'docker') {
return;
}
try {
const containerLabel = getContainerLabel(global_1.GlobalConfig.get('dockerChildPrefix'));
logger_1.logger.debug(`Removing dangling child containers with label ${containerLabel}`);
const res = await (0, common_1.rawExec)(`docker ps --filter label=${containerLabel} -aq`, {
encoding: 'utf-8',
});
if (res?.stdout?.trim().length) {
const containerIds = res.stdout
.trim()
.split(regex_1.newlineRegex)
.map((container) => container.trim())
.filter(Boolean);
logger_1.logger.debug({ containerIds }, 'Removing dangling child containers');
await (0, common_1.rawExec)(`docker rm -f ${containerIds.join(' ')}`, {
encoding: 'utf-8',
});
}
else {
logger_1.logger.debug('No dangling containers to remove');
}
}
catch (err) {
if (err.errno === 'ENOMEM') {
throw new Error(error_messages_1.SYSTEM_INSUFFICIENT_MEMORY);
}
if (err.stderr?.includes('Cannot connect to the Docker daemon')) {
logger_1.logger.info('No docker daemon found');
}
else {
logger_1.logger.warn({ err }, 'Error removing dangling containers');
}
}
}
async function generateDockerCommand(commands, preCommands, options) {
const { envVars, cwd } = options;
let image = exports.sideCarImage;
const volumes = options.volumes ?? [];
const { localDir, cacheDir, containerbaseDir, dockerUser, dockerChildPrefix, dockerCliOptions, dockerSidecarImage, } = global_1.GlobalConfig.get();
const result = ['docker run --rm'];
const containerName = getContainerName(image, 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_1.logger.debug('containerbaseDir is inside cacheDir');
}
else {
logger_1.logger.debug('containerbaseDir is separate from cacheDir');
volumeDirs.push(containerbaseDir);
}
}
else {
logger_1.logger.debug('containerbaseDir is missing');
}
volumeDirs.push(...volumes);
result.push(...prepareVolumes(volumeDirs));
if (envVars) {
result.push(...(0, uniq_1.uniq)(envVars)
.filter(is_1.default.string)
.map((e) => `-e ${e}`));
}
if (cwd) {
result.push(`-w "${cwd}"`);
}
// TODO: #22198
image = dockerSidecarImage;
// TODO: add constraint: const tag = getDockerTag(image, sideCarImageVersion, 'semver');
logger_1.logger.debug({ image /*, tagConstraint: sideCarImageVersion, tag */ }, 'Resolved tag constraint');
const taggedImage = image; // TODO: tag ? `${image}:${tag}` : `${image}`;
await prefetchDockerImage(taggedImage);
result.push(taggedImage);
const bashCommand = [...prepareCommands(preCommands), ...commands].join(' && ');
result.push(`bash -l -c "${bashCommand.replace((0, regex_1.regEx)(/"/g), '\\"')}"`); // lgtm [js/incomplete-sanitization]
return result.join(' ');
}
//# sourceMappingURL=index.js.map