snyk-docker-plugin
Version:
Snyk CLI docker plugin
210 lines • 9.29 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.getContentAsString = exports.getContentAsBuffer = exports.isWhitedOutFile = exports.getUserInstructionLayersFromConfig = exports.getDetectedLayersInfoFromConfig = exports.getPlatformFromConfig = exports.getRootFsLayersFromConfig = exports.extractImageContent = exports.InvalidArchiveError = void 0;
const path = require("path");
const instruction_parser_1 = require("../dockerfile/instruction-parser");
const types_1 = require("../types");
const dockerExtractor = require("./docker-archive");
const kanikoExtractor = require("./kaniko-archive");
const ociExtractor = require("./oci-archive");
class InvalidArchiveError extends Error {
constructor(message) {
super();
this.name = "InvalidArchiveError";
this.message = message;
}
}
exports.InvalidArchiveError = InvalidArchiveError;
class ArchiveExtractor {
constructor(extractor, path, actions, options) {
this.fileSystemPath = path;
this.extractActions = actions;
this.extractor = extractor;
this.options = options;
}
getExtractor() {
return this.extractor;
}
async getLayersAndManifest() {
return await this.extractor.extractArchive(this.fileSystemPath, this.extractActions, this.options);
}
getImageIdFromManifest(manifest) {
return this.extractor.getImageIdFromManifest(manifest);
}
getManifestLayers(manifest) {
return this.extractor.getManifestLayers(manifest);
}
}
/**
* Given a path on the file system to a image archive, open it up to inspect the layers
* and look for specific files. File content can be transformed with a custom callback function if needed.
* @param fileSystemPath Path to an existing archive.
* @param extractActions This denotes a file pattern to look for and how to transform the file if it is found.
* By default the file is returned raw if no processing is desired.
*/
async function extractImageContent(imageType, fileSystemPath, extractActions, options) {
const extractors = new Map([
[
types_1.ImageType.DockerArchive,
new ArchiveExtractor(dockerExtractor, fileSystemPath, extractActions, options),
],
[
types_1.ImageType.OciArchive,
new ArchiveExtractor(ociExtractor, fileSystemPath, extractActions, options),
],
[
types_1.ImageType.KanikoArchive,
new ArchiveExtractor(kanikoExtractor, fileSystemPath, extractActions, options),
],
]);
let extractor;
let archiveContent;
if (!extractors.has(imageType)) {
// default to Docker extractor if image type is unknown
imageType = types_1.ImageType.DockerArchive;
}
extractor = extractors.get(imageType);
try {
archiveContent = await extractor.getLayersAndManifest();
}
catch (err) {
if (err instanceof InvalidArchiveError) {
// fallback to the other extractor if layer extraction failed
[archiveContent, extractor] = await extractArchiveContentFallback(extractors);
}
else {
throw err;
}
}
return {
imageId: extractor.getImageIdFromManifest(archiveContent.manifest),
manifestLayers: extractor.getManifestLayers(archiveContent.manifest),
imageCreationTime: archiveContent.imageConfig.created,
extractedLayers: layersWithLatestFileModifications(archiveContent.layers),
rootFsLayers: getRootFsLayersFromConfig(archiveContent.imageConfig),
autoDetectedUserInstructions: getDetectedLayersInfoFromConfig(archiveContent.imageConfig),
platform: getPlatformFromConfig(archiveContent.imageConfig),
imageLabels: archiveContent.imageConfig.config.Labels,
};
}
exports.extractImageContent = extractImageContent;
async function extractArchiveContentFallback(extractors) {
for (const extractor of extractors.values()) {
try {
return [await extractor.getLayersAndManifest(), extractor];
}
catch (error) {
continue;
}
}
throw new InvalidArchiveError(`Unsupported archive type. Please use a Docker archive, OCI image layout, or Kaniko-compatible tarball.`);
}
function getRootFsLayersFromConfig(imageConfig) {
try {
return imageConfig.rootfs.diff_ids;
}
catch (err) {
throw new Error("Failed to extract rootfs array from image config");
}
}
exports.getRootFsLayersFromConfig = getRootFsLayersFromConfig;
function getPlatformFromConfig(imageConfig) {
return (imageConfig === null || imageConfig === void 0 ? void 0 : imageConfig.os) && (imageConfig === null || imageConfig === void 0 ? void 0 : imageConfig.architecture)
? `${imageConfig.os}/${imageConfig.architecture}`
: undefined;
}
exports.getPlatformFromConfig = getPlatformFromConfig;
function getDetectedLayersInfoFromConfig(imageConfig) {
const runInstructions = getUserInstructionLayersFromConfig(imageConfig)
.filter((instruction) => !instruction.empty_layer && instruction.created_by)
.map((instruction) => instruction.created_by.replace("# buildkit", ""));
const dockerfilePackages = (0, instruction_parser_1.getPackagesFromRunInstructions)(runInstructions);
const dockerfileLayers = (0, instruction_parser_1.getLayersFromPackages)(dockerfilePackages);
return { dockerfilePackages, dockerfileLayers };
}
exports.getDetectedLayersInfoFromConfig = getDetectedLayersInfoFromConfig;
function getUserInstructionLayersFromConfig(imageConfig) {
const diffInHours = (d1, d2) => Math.abs(d1 - d2) / 1000 / (60 * 60);
const maxDiffInHours = 5;
const history = imageConfig.history;
if (!history) {
return [];
}
const lastInstructionTime = new Date(history.slice(-1)[0].created);
const userInstructionLayers = history.filter((layer) => {
return (diffInHours(new Date(layer.created), lastInstructionTime) <=
maxDiffInHours);
});
// should only happen if there are no layers created by user instructions
if (userInstructionLayers.length === history.length) {
return [];
}
return userInstructionLayers;
}
exports.getUserInstructionLayersFromConfig = getUserInstructionLayersFromConfig;
function layersWithLatestFileModifications(layers) {
const extractedLayers = {};
const removedFilesToIgnore = new Set();
// TODO: This removes the information about the layer name, maybe we would need it in the future?
for (const layer of layers) {
// go over extracted files products found in this layer
for (const filename of Object.keys(layer)) {
// if finding a deleted file - trimming to its original file name for excluding it from extractedLayers
// + not adding this file
if (isWhitedOutFile(filename)) {
removedFilesToIgnore.add(filename.replace(/.wh./, ""));
continue;
}
// not adding previously found to be whited out files to extractedLayers
if (removedFilesToIgnore.has(filename)) {
continue;
}
// not adding path that has removed path as parent
if (isFileInARemovedFolder(filename, removedFilesToIgnore)) {
continue;
}
// file not already in extractedLayers
if (!Reflect.has(extractedLayers, filename)) {
extractedLayers[filename] = layer[filename];
}
}
}
return extractedLayers;
}
function isWhitedOutFile(filename) {
return filename.match(/.wh./gm);
}
exports.isWhitedOutFile = isWhitedOutFile;
function isBufferType(type) {
return type.buffer !== undefined;
}
function isStringType(type) {
return type.substring !== undefined;
}
function getContentAsBuffer(extractedLayers, extractAction) {
const content = getContent(extractedLayers, extractAction);
return content !== undefined && isBufferType(content) ? content : undefined;
}
exports.getContentAsBuffer = getContentAsBuffer;
function getContentAsString(extractedLayers, extractAction) {
const content = getContent(extractedLayers, extractAction);
return content !== undefined && isStringType(content) ? content : undefined;
}
exports.getContentAsString = getContentAsString;
function getContent(extractedLayers, extractAction) {
const fileNames = Object.keys(extractedLayers);
const fileNamesProducedByTheExtractAction = fileNames.filter((name) => extractAction.actionName in extractedLayers[name]);
const firstFileNameMatch = fileNamesProducedByTheExtractAction.find((match) => extractAction.filePathMatches(match));
return firstFileNameMatch !== undefined
? extractedLayers[firstFileNameMatch][extractAction.actionName]
: undefined;
}
function isFileInFolder(file, folder) {
const folderPath = path.normalize(folder);
const filePath = path.normalize(file);
return filePath.startsWith(path.join(folderPath, path.sep));
}
function isFileInARemovedFolder(filename, removedFilesToIgnore) {
return Array.from(removedFilesToIgnore).some((removedFile) => isFileInFolder(filename, removedFile));
}
//# sourceMappingURL=index.js.map
;