UNPKG

snyk-docker-plugin

Version:
202 lines 9.04 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.extractArchive = void 0; const Debug = require("debug"); const fs_1 = require("fs"); const gunzip = require("gunzip-maybe"); const path_1 = require("path"); const stream_1 = require("stream"); const tar_stream_1 = require("tar-stream"); const __1 = require(".."); const stream_utils_1 = require("../../stream-utils"); const layer_1 = require("../layer"); const debug = Debug("snyk"); const MEDIATYPE_DOCKER_MANIFEST_V2 = "application/vnd.docker.distribution.manifest.v2+json"; const MEDIATYPE_DOCKER_MANIFEST_LIST_V2 = "application/vnd.docker.distribution.manifest.list.v2+json"; const MEDIATYPE_OCI_MANIFEST_V1 = "application/vnd.oci.image.manifest.v1+json"; const MEDIATYPE_OCI_MANIFEST_LIST_V1 = "application/vnd.oci.image.index.v1+json"; /** * Retrieve the products of files content from the specified oci-archive. * @param ociArchiveFilesystemPath Path to image file saved in oci-archive format. * @param extractActions Array of pattern-callbacks pairs. * @param options PluginOptions * @returns Array of extracted files products sorted by the reverse order of the layers from last to first. */ async function extractArchive(ociArchiveFilesystemPath, extractActions, options) { return new Promise((resolve, reject) => { const tarExtractor = (0, tar_stream_1.extract)(); const layers = {}; const manifests = {}; const configs = []; let mainIndexFile; const indexFiles = {}; tarExtractor.on("entry", async (header, stream, next) => { if (header.type === "file") { const normalizedHeaderName = (0, path_1.normalize)(header.name); if (isMainIndexFile(normalizedHeaderName)) { mainIndexFile = await (0, stream_utils_1.streamToJson)(stream); } else { const jsonStream = new stream_1.PassThrough(); const layerStream = new stream_1.PassThrough(); stream.pipe(jsonStream); stream.pipe(layerStream); const promises = [ (0, stream_utils_1.streamToJson)(jsonStream).catch(() => undefined), (0, layer_1.extractImageLayer)(layerStream, extractActions).catch(() => undefined), ]; const [manifest, layer] = await Promise.all(promises); // header format is /blobs/hash_name/hash_value // we're extracting hash_name:hash_value format to match manifest digest const headerParts = normalizedHeaderName.split(path_1.sep); const hashName = headerParts[1]; const hashValue = headerParts[headerParts.length - 1]; const digest = `${hashName}:${hashValue}`; if (isArchiveManifest(manifest)) { manifests[digest] = manifest; } else if (isImageIndexFile(manifest)) { indexFiles[digest] = manifest; } else if (isImageConfigFile(manifest)) { configs.push(manifest); } if (layer !== undefined) { layers[digest] = layer; } } } stream.resume(); // auto drain the stream next(); // ready for next entry }); tarExtractor.on("finish", () => { try { resolve(getLayersContentAndArchiveManifest(mainIndexFile, manifests, indexFiles, configs, layers, options)); } catch (error) { debug(`Error getting layers and manifest content from oci archive: '${error.message}'`); reject(new __1.InvalidArchiveError("Invalid OCI archive")); } }); tarExtractor.on("error", (error) => { reject(error); }); (0, fs_1.createReadStream)(ociArchiveFilesystemPath) .pipe(gunzip()) .pipe(tarExtractor); }); } exports.extractArchive = extractArchive; function getLayersContentAndArchiveManifest(imageIndex, manifestCollection, indexFiles, configs, layers, options) { const platform = (options === null || options === void 0 ? void 0 : options.platform) || (configs.length === 1 ? (0, __1.getPlatformFromConfig)(configs[0]) : "linux/amd64"); const platformInfo = getOciPlatformInfoFromOptionString(platform); // get manifest file first const manifest = getManifest(imageIndex, manifestCollection, indexFiles, platformInfo); // filter empty layers // get the layers content without the name // reverse layers order from last to first const filteredLayers = manifest.layers .filter((layer) => layers[layer.digest]) .map((layer) => layers[layer.digest]) .reverse(); if (filteredLayers.length === 0) { throw new Error("We found no layers in the provided image"); } const imageConfig = getImageConfig(configs, platformInfo); if (imageConfig === undefined) { throw new Error("Could not find the image config in the provided image"); } return { layers: filteredLayers, manifest, imageConfig, }; } function getManifest(imageIndex, manifestCollection, indexFiles, platformInfo) { if (!imageIndex) { return manifestCollection[Object.keys(manifestCollection)[0]]; } const allManifests = getAllManifestsIndexItems(imageIndex, indexFiles); const manifestInfo = getImageManifestInfo(allManifests, platformInfo); if (manifestInfo === undefined) { throw new Error("Image does not support type of CPU architecture or operating system"); } return manifestCollection[manifestInfo.digest]; } function getAllManifestsIndexItems(imageIndex, indexFiles) { const allManifestsInfo = []; for (const manifest of imageIndex.manifests) { if (manifest.mediaType === MEDIATYPE_OCI_MANIFEST_V1 || manifest.mediaType === MEDIATYPE_DOCKER_MANIFEST_V2) { // an archive manifest file allManifestsInfo.push(manifest); } else if (manifest.mediaType === MEDIATYPE_OCI_MANIFEST_LIST_V1 || manifest.mediaType === MEDIATYPE_DOCKER_MANIFEST_LIST_V2) { // nested index const index = indexFiles[manifest.digest]; allManifestsInfo.push(...getAllManifestsIndexItems(index, indexFiles)); } } return allManifestsInfo; } function isArchiveManifest(manifest) { return (manifest !== undefined && manifest.layers && Array.isArray(manifest.layers)); } function isImageConfigFile(json) { return json !== undefined && json.architecture && json.rootfs; } function isImageIndexFile(json) { return (((json === null || json === void 0 ? void 0 : json.mediaType) === MEDIATYPE_OCI_MANIFEST_LIST_V1 || (json === null || json === void 0 ? void 0 : json.mediaType) === MEDIATYPE_DOCKER_MANIFEST_LIST_V2) && Array.isArray(json === null || json === void 0 ? void 0 : json.manifests)); } function isMainIndexFile(name) { return name === "index.json"; } function getOciPlatformInfoFromOptionString(platform) { const [os, architecture, variant] = platform.split("/"); return { os, architecture, variant, }; } function getImageManifestInfo(manifests, platformInfo) { // manifests do not always have a plaform, this is the case for OCI // images built with Docker when no platform is specified if (manifests.length === 1 && !manifests[0].platform) { return manifests[0]; } return getBestMatchForPlatform(manifests, platformInfo, (target) => { var _a, _b, _c; return { os: (_a = target.platform) === null || _a === void 0 ? void 0 : _a.os, architecture: (_b = target.platform) === null || _b === void 0 ? void 0 : _b.architecture, variant: (_c = target.platform) === null || _c === void 0 ? void 0 : _c.variant, }; }); } function getImageConfig(manifests, platformInfo) { return getBestMatchForPlatform(manifests, platformInfo, (target) => { return { os: target.os, architecture: target.architecture, }; }); } function getBestMatchForPlatform(manifests, platformInfo, extractPlatformInfoFromManifest) { const matches = manifests.filter((item) => { const { os, architecture } = extractPlatformInfoFromManifest(item); return os === platformInfo.os && architecture === platformInfo.architecture; }); if (matches.length > 1) { return matches.find((item) => { const { variant } = extractPlatformInfoFromManifest(item); return variant === platformInfo.variant; }); } return matches[0] || undefined; } //# sourceMappingURL=layer.js.map