UNPKG

snyk-docker-plugin

Version:
210 lines 9.29 kB
"use strict"; 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