UNPKG

snyk-docker-plugin

Version:
168 lines 7.69 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getPackagesFromRunInstructions = exports.instructionDigest = exports.getPackagesFromDockerfile = exports.getLayersFromPackages = exports.getDockerfileBaseImageName = void 0; const types_1 = require("../types"); const types_2 = require("./types"); // Naive regex; see tests for cases // tslint:disable-next-line:max-line-length const installRegex = /(rpm\s+-i|rpm\s+--install|apk\s+((--update|-u|--no-cache)\s+)*add(\s+(--update|-u|--no-cache))*|apt-get\s+((--assume-yes|--yes|-y)\s+)*install(\s+(--assume-yes|--yes|-y))*|apt\s+((--assume-yes|--yes|-y)\s+)*install|yum\s+install|aptitude\s+install)\s+/; function getPackagesFromDockerfile(dockerfile) { const runInstructions = getRunInstructionsFromDockerfile(dockerfile); return getPackagesFromRunInstructions(runInstructions); } exports.getPackagesFromDockerfile = getPackagesFromDockerfile; function getRunInstructionsFromDockerfile(dockerfile) { return dockerfile .getInstructions() .filter((instruction) => instruction.getInstruction().toUpperCase() === "RUN") .map((instruction) => getInstructionExpandVariables(instruction, dockerfile, types_1.UnresolvedDockerfileVariableHandling.Continue)); } /* * This is fairly ugly because a single RUN could contain multiple install * commands, which in turn may install multiple packages, so we've got a * 3-level nested array (RUN instruction[] -> install[] -> package[]) * * We also need to account for the multiple ways to split commands, and * arbitrary whitespace */ function getPackagesFromRunInstructions(runInstructions) { return runInstructions.reduce((dockerfilePackages, instruction) => { const cleanedInstruction = cleanInstruction(instruction); const commands = cleanedInstruction.split(/\s?(;|&&)\s?/); const installCommands = commands.filter((command) => installRegex.test(command)); if (installCommands.length) { // Get the packages per install command and flatten them for (const command of installCommands) { const packages = command .replace(installRegex, "") .split(/\s+/) .filter((arg) => arg && !arg.startsWith("-")); packages.forEach((pkg) => { var _a; // Use package name without version as the key let name = pkg.split("=")[0]; if (name.startsWith("$")) { name = name.slice(1); } const installCommand = ((_a = installCommands .find((cmd) => cmd.indexOf(name) !== -1)) === null || _a === void 0 ? void 0 : _a.replace(/\s+/g, " ").trim()) || "Unknown"; dockerfilePackages[name] = { instruction, installCommand, }; }); } } return dockerfilePackages; }, {}); } exports.getPackagesFromRunInstructions = getPackagesFromRunInstructions; /** * Return the instruction text without any of the image prefixes * @param instruction the full RUN instruction extracted from image */ function cleanInstruction(instruction) { let cleanedInstruction = instruction; const runDefs = ["RUN ", "/bin/sh ", "-c "]; const argsPrefixRegex = /^\|\d .*?=/; for (const runDef of runDefs) { if (cleanedInstruction.startsWith(runDef)) { cleanedInstruction = cleanedInstruction.slice(runDef.length); if (runDef === runDefs[0] && argsPrefixRegex.test(cleanedInstruction)) { const match = installRegex.exec(cleanedInstruction); if (match) { cleanedInstruction = cleanedInstruction.slice(match.index); } } } } return cleanedInstruction; } /** * Return the specified text with variables expanded * @param instruction the instruction associated with this string * @param dockerfile Dockerfile to use for expanding the variables * @param unresolvedVariableHandling Strategy for reacting to unresolved vars * @param text a string with variables to expand, if not specified * the instruction text is used */ function getInstructionExpandVariables(instruction, dockerfile, unresolvedVariableHandling, text) { let str = text || instruction.toString(); const resolvedVariables = {}; for (const variable of instruction.getVariables()) { const line = variable.getRange().start.line; const name = variable.getName(); resolvedVariables[name] = dockerfile.resolveVariable(name, line); } for (const variable of Object.keys(resolvedVariables)) { if (unresolvedVariableHandling === types_1.UnresolvedDockerfileVariableHandling.Abort && !resolvedVariables[variable]) { str = ""; break; } // The $ is a special regexp character that should be escaped with a backslash // Support both notations either with $variable_name or ${variable_name} // The global search "g" flag is used to match and replace all occurrences str = str.replace(RegExp(`\\$\{${variable}\}|\\$${variable}`, "g"), resolvedVariables[variable] || ""); } return str; } /** * Return the image name of the last from stage, after resolving all aliases * @param dockerfile Dockerfile to use for retrieving the last stage image name */ function getDockerfileBaseImageName(dockerfile) { const froms = dockerfile.getFROMs(); // collect stages names const stagesNames = froms.reduce((stagesNames, fromInstruction) => { const fromName = fromInstruction.getImage(); const args = fromInstruction.getArguments(); // the FROM expanded base name const expandedName = getInstructionExpandVariables(fromInstruction, dockerfile, types_1.UnresolvedDockerfileVariableHandling.Abort, fromName); const hasUnresolvedVariables = expandedName.split(":").some((name) => !name) || expandedName.split("@").some((name) => !name); // store the resolved stage name if (!hasUnresolvedVariables) { stagesNames.last = stagesNames.aliases[expandedName] || expandedName; } if (args.length > 2 && args[1].getValue().toUpperCase() === "AS") { // the AS alias name const aliasName = args[2].getValue(); // support nested referral stagesNames.aliases[aliasName] = stagesNames.last; } return stagesNames; }, { last: undefined, aliases: {} }); if (stagesNames.last) { return { baseImage: stagesNames.last, }; } if (!froms.length) { return { error: { code: types_2.DockerFileAnalysisErrorCode.BASE_IMAGE_NAME_NOT_FOUND, }, }; } return { error: { code: types_2.DockerFileAnalysisErrorCode.BASE_IMAGE_NON_RESOLVABLE, }, }; } exports.getDockerfileBaseImageName = getDockerfileBaseImageName; function instructionDigest(instruction) { return Buffer.from(instruction).toString("base64"); } exports.instructionDigest = instructionDigest; function getLayersFromPackages(dockerfilePkgs) { return Object.keys(dockerfilePkgs).reduce((res, pkg) => { const { instruction } = dockerfilePkgs[pkg]; res[instructionDigest(instruction)] = { instruction }; return res; }, {}); } exports.getLayersFromPackages = getLayersFromPackages; //# sourceMappingURL=instruction-parser.js.map