mirror of
https://github.com/github/codeql-action.git
synced 2026-04-02 01:32:17 +00:00
Check in compiled files
This commit is contained in:
430
lib/actions-util.js
generated
430
lib/actions-util.js
generated
@@ -1,430 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.restoreInputs = exports.persistInputs = exports.CommandInvocationError = exports.getFileType = exports.FileCmdNotFoundError = exports.getOptionalInput = exports.getRequiredInput = void 0;
|
||||
exports.getTemporaryDirectory = getTemporaryDirectory;
|
||||
exports.getActionVersion = getActionVersion;
|
||||
exports.getWorkflowEventName = getWorkflowEventName;
|
||||
exports.isRunningLocalAction = isRunningLocalAction;
|
||||
exports.getRelativeScriptPath = getRelativeScriptPath;
|
||||
exports.getWorkflowEvent = getWorkflowEvent;
|
||||
exports.printDebugLogs = printDebugLogs;
|
||||
exports.getUploadValue = getUploadValue;
|
||||
exports.getWorkflowRunID = getWorkflowRunID;
|
||||
exports.getWorkflowRunAttempt = getWorkflowRunAttempt;
|
||||
exports.isSelfHostedRunner = isSelfHostedRunner;
|
||||
exports.isDefaultSetup = isDefaultSetup;
|
||||
exports.prettyPrintInvocation = prettyPrintInvocation;
|
||||
exports.ensureEndsInPeriod = ensureEndsInPeriod;
|
||||
exports.runTool = runTool;
|
||||
exports.getPullRequestBranches = getPullRequestBranches;
|
||||
exports.isAnalyzingPullRequest = isAnalyzingPullRequest;
|
||||
exports.fixCodeQualityCategory = fixCodeQualityCategory;
|
||||
const fs = __importStar(require("fs"));
|
||||
const path = __importStar(require("path"));
|
||||
const core = __importStar(require("@actions/core"));
|
||||
const toolrunner = __importStar(require("@actions/exec/lib/toolrunner"));
|
||||
const github = __importStar(require("@actions/github"));
|
||||
const io = __importStar(require("@actions/io"));
|
||||
const util_1 = require("./util");
|
||||
// eslint-disable-next-line import/no-commonjs, @typescript-eslint/no-require-imports
|
||||
const pkg = require("../package.json");
|
||||
/**
|
||||
* Wrapper around core.getInput for inputs that always have a value.
|
||||
* Also see getOptionalInput.
|
||||
*
|
||||
* This allows us to get stronger type checking of required/optional inputs.
|
||||
*/
|
||||
const getRequiredInput = function (name) {
|
||||
const value = core.getInput(name);
|
||||
if (!value) {
|
||||
throw new util_1.ConfigurationError(`Input required and not supplied: ${name}`);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
exports.getRequiredInput = getRequiredInput;
|
||||
/**
|
||||
* Wrapper around core.getInput that converts empty inputs to undefined.
|
||||
* Also see getRequiredInput.
|
||||
*
|
||||
* This allows us to get stronger type checking of required/optional inputs.
|
||||
*/
|
||||
const getOptionalInput = function (name) {
|
||||
const value = core.getInput(name);
|
||||
return value.length > 0 ? value : undefined;
|
||||
};
|
||||
exports.getOptionalInput = getOptionalInput;
|
||||
function getTemporaryDirectory() {
|
||||
const value = process.env["CODEQL_ACTION_TEMP"];
|
||||
return value !== undefined && value !== ""
|
||||
? value
|
||||
: (0, util_1.getRequiredEnvParam)("RUNNER_TEMP");
|
||||
}
|
||||
function getActionVersion() {
|
||||
return pkg.version;
|
||||
}
|
||||
/**
|
||||
* Returns the name of the event that triggered this workflow.
|
||||
*
|
||||
* This will be "dynamic" for default setup workflow runs.
|
||||
*/
|
||||
function getWorkflowEventName() {
|
||||
return (0, util_1.getRequiredEnvParam)("GITHUB_EVENT_NAME");
|
||||
}
|
||||
/**
|
||||
* Returns whether the current workflow is executing a local copy of the Action, e.g. we're running
|
||||
* a workflow on the codeql-action repo itself.
|
||||
*/
|
||||
function isRunningLocalAction() {
|
||||
const relativeScriptPath = getRelativeScriptPath();
|
||||
return (relativeScriptPath.startsWith("..") || path.isAbsolute(relativeScriptPath));
|
||||
}
|
||||
/**
|
||||
* Get the location where the Action is running from.
|
||||
*
|
||||
* This can be used to get the Action's name or tell if we're running a local Action.
|
||||
*/
|
||||
function getRelativeScriptPath() {
|
||||
const runnerTemp = (0, util_1.getRequiredEnvParam)("RUNNER_TEMP");
|
||||
const actionsDirectory = path.join(path.dirname(runnerTemp), "_actions");
|
||||
return path.relative(actionsDirectory, __filename);
|
||||
}
|
||||
/** Returns the contents of `GITHUB_EVENT_PATH` as a JSON object. */
|
||||
function getWorkflowEvent() {
|
||||
const eventJsonFile = (0, util_1.getRequiredEnvParam)("GITHUB_EVENT_PATH");
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(eventJsonFile, "utf-8"));
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error(`Unable to read workflow event JSON from ${eventJsonFile}: ${e}`);
|
||||
}
|
||||
}
|
||||
async function printDebugLogs(config) {
|
||||
for (const language of config.languages) {
|
||||
const databaseDirectory = (0, util_1.getCodeQLDatabasePath)(config, language);
|
||||
const logsDirectory = path.join(databaseDirectory, "log");
|
||||
if (!(0, util_1.doesDirectoryExist)(logsDirectory)) {
|
||||
core.info(`Directory ${logsDirectory} does not exist.`);
|
||||
continue; // Skip this language database.
|
||||
}
|
||||
const walkLogFiles = (dir) => {
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
if (entries.length === 0) {
|
||||
core.info(`No debug logs found at directory ${logsDirectory}.`);
|
||||
}
|
||||
for (const entry of entries) {
|
||||
if (entry.isFile()) {
|
||||
const absolutePath = path.resolve(dir, entry.name);
|
||||
core.startGroup(`CodeQL Debug Logs - ${language} - ${entry.name} from file at path ${absolutePath}`);
|
||||
process.stdout.write(fs.readFileSync(absolutePath));
|
||||
core.endGroup();
|
||||
}
|
||||
else if (entry.isDirectory()) {
|
||||
walkLogFiles(path.resolve(dir, entry.name));
|
||||
}
|
||||
}
|
||||
};
|
||||
walkLogFiles(logsDirectory);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Parses the `upload` input into an `UploadKind`, converting unspecified and deprecated upload
|
||||
* inputs appropriately.
|
||||
*/
|
||||
function getUploadValue(input) {
|
||||
switch (input) {
|
||||
case undefined:
|
||||
case "true":
|
||||
case "always":
|
||||
return "always";
|
||||
case "false":
|
||||
case "failure-only":
|
||||
return "failure-only";
|
||||
case "never":
|
||||
return "never";
|
||||
default:
|
||||
core.warning(`Unrecognized 'upload' input to 'analyze' Action: ${input}. Defaulting to 'always'.`);
|
||||
return "always";
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get the workflow run ID.
|
||||
*/
|
||||
function getWorkflowRunID() {
|
||||
const workflowRunIdString = (0, util_1.getRequiredEnvParam)("GITHUB_RUN_ID");
|
||||
const workflowRunID = parseInt(workflowRunIdString, 10);
|
||||
if (Number.isNaN(workflowRunID)) {
|
||||
throw new Error(`GITHUB_RUN_ID must define a non NaN workflow run ID. Current value is ${workflowRunIdString}`);
|
||||
}
|
||||
if (workflowRunID < 0) {
|
||||
throw new Error(`GITHUB_RUN_ID must be a non-negative integer. Current value is ${workflowRunIdString}`);
|
||||
}
|
||||
return workflowRunID;
|
||||
}
|
||||
/**
|
||||
* Get the workflow run attempt number.
|
||||
*/
|
||||
function getWorkflowRunAttempt() {
|
||||
const workflowRunAttemptString = (0, util_1.getRequiredEnvParam)("GITHUB_RUN_ATTEMPT");
|
||||
const workflowRunAttempt = parseInt(workflowRunAttemptString, 10);
|
||||
if (Number.isNaN(workflowRunAttempt)) {
|
||||
throw new Error(`GITHUB_RUN_ATTEMPT must define a non NaN workflow run attempt. Current value is ${workflowRunAttemptString}`);
|
||||
}
|
||||
if (workflowRunAttempt <= 0) {
|
||||
throw new Error(`GITHUB_RUN_ATTEMPT must be a positive integer. Current value is ${workflowRunAttemptString}`);
|
||||
}
|
||||
return workflowRunAttempt;
|
||||
}
|
||||
class FileCmdNotFoundError extends Error {
|
||||
constructor(msg) {
|
||||
super(msg);
|
||||
this.name = "FileCmdNotFoundError";
|
||||
}
|
||||
}
|
||||
exports.FileCmdNotFoundError = FileCmdNotFoundError;
|
||||
/**
|
||||
* Tries to obtain the output of the `file` command for the file at the specified path.
|
||||
* The output will vary depending on the type of `file`, which operating system we are running on, etc.
|
||||
*/
|
||||
const getFileType = async (filePath) => {
|
||||
let stderr = "";
|
||||
let stdout = "";
|
||||
let fileCmdPath;
|
||||
try {
|
||||
fileCmdPath = await io.which("file", true);
|
||||
}
|
||||
catch (e) {
|
||||
throw new FileCmdNotFoundError(`The \`file\` program is required, but does not appear to be installed. Please install it: ${e}`);
|
||||
}
|
||||
try {
|
||||
// The `file` command will output information about the type of file pointed at by `filePath`.
|
||||
// For binary files, this may include e.g. whether they are static of dynamic binaries.
|
||||
// The `-L` switch instructs the command to follow symbolic links.
|
||||
await new toolrunner.ToolRunner(fileCmdPath, ["-L", filePath], {
|
||||
silent: true,
|
||||
listeners: {
|
||||
stdout: (data) => {
|
||||
stdout += data.toString();
|
||||
},
|
||||
stderr: (data) => {
|
||||
stderr += data.toString();
|
||||
},
|
||||
},
|
||||
}).exec();
|
||||
return stdout.trim();
|
||||
}
|
||||
catch (e) {
|
||||
core.info(`Could not determine type of ${filePath} from ${stdout}. ${stderr}`);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
exports.getFileType = getFileType;
|
||||
function isSelfHostedRunner() {
|
||||
return process.env.RUNNER_ENVIRONMENT === "self-hosted";
|
||||
}
|
||||
/** Determines whether we are running in default setup. */
|
||||
function isDefaultSetup() {
|
||||
return getWorkflowEventName() === "dynamic";
|
||||
}
|
||||
function prettyPrintInvocation(cmd, args) {
|
||||
return [cmd, ...args].map((x) => (x.includes(" ") ? `'${x}'` : x)).join(" ");
|
||||
}
|
||||
/**
|
||||
* An error from a tool invocation, with associated exit code, stderr, etc.
|
||||
*/
|
||||
class CommandInvocationError extends Error {
|
||||
constructor(cmd, args, exitCode, stderr, stdout = "") {
|
||||
const prettyCommand = prettyPrintInvocation(cmd, args);
|
||||
const lastLine = ensureEndsInPeriod(stderr.trim().split("\n").pop()?.trim() || "n/a");
|
||||
super(`Failed to run "${prettyCommand}". ` +
|
||||
`Exit code was ${exitCode} and last log line was: ${lastLine} See the logs for more details.`);
|
||||
this.cmd = cmd;
|
||||
this.args = args;
|
||||
this.exitCode = exitCode;
|
||||
this.stderr = stderr;
|
||||
this.stdout = stdout;
|
||||
}
|
||||
}
|
||||
exports.CommandInvocationError = CommandInvocationError;
|
||||
function ensureEndsInPeriod(text) {
|
||||
return text[text.length - 1] === "." ? text : `${text}.`;
|
||||
}
|
||||
/**
|
||||
* A constant defining the maximum number of characters we will keep from
|
||||
* the programs stderr for logging.
|
||||
*
|
||||
* This serves two purposes:
|
||||
* 1. It avoids an OOM if a program fails in a way that results it
|
||||
* printing many log lines.
|
||||
* 2. It avoids us hitting the limit of how much data we can send in our
|
||||
* status reports on GitHub.com.
|
||||
*/
|
||||
const MAX_STDERR_BUFFER_SIZE = 20000;
|
||||
/**
|
||||
* Runs a CLI tool.
|
||||
*
|
||||
* @returns Standard output produced by the tool.
|
||||
* @throws A `CommandInvocationError` if the tool exits with a non-zero status code.
|
||||
*/
|
||||
async function runTool(cmd, args = [], opts = {}) {
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
if (!opts.noStreamStdout) {
|
||||
process.stdout.write(`[command]${cmd} ${args.join(" ")}\n`);
|
||||
}
|
||||
const exitCode = await new toolrunner.ToolRunner(cmd, args, {
|
||||
ignoreReturnCode: true,
|
||||
listeners: {
|
||||
stdout: (data) => {
|
||||
stdout += data.toString("utf8");
|
||||
if (!opts.noStreamStdout) {
|
||||
process.stdout.write(data);
|
||||
}
|
||||
},
|
||||
stderr: (data) => {
|
||||
let readStartIndex = 0;
|
||||
// If the error is too large, then we only take the last MAX_STDERR_BUFFER_SIZE characters
|
||||
if (data.length - MAX_STDERR_BUFFER_SIZE > 0) {
|
||||
// Eg: if we have MAX_STDERR_BUFFER_SIZE the start index should be 2.
|
||||
readStartIndex = data.length - MAX_STDERR_BUFFER_SIZE + 1;
|
||||
}
|
||||
stderr += data.toString("utf8", readStartIndex);
|
||||
// Mimic the standard behavior of the toolrunner by writing stderr to stdout
|
||||
process.stdout.write(data);
|
||||
},
|
||||
},
|
||||
silent: true,
|
||||
...(opts.stdin ? { input: Buffer.from(opts.stdin || "") } : {}),
|
||||
}).exec();
|
||||
if (exitCode !== 0) {
|
||||
throw new CommandInvocationError(cmd, args, exitCode, stderr, stdout);
|
||||
}
|
||||
return stdout;
|
||||
}
|
||||
const persistedInputsKey = "persisted_inputs";
|
||||
/**
|
||||
* Persists all inputs to the action as state that can be retrieved later in the post-action.
|
||||
* This would be simplified if actions/runner#3514 is addressed.
|
||||
* https://github.com/actions/runner/issues/3514
|
||||
*/
|
||||
const persistInputs = function () {
|
||||
const inputEnvironmentVariables = Object.entries(process.env).filter(([name]) => name.startsWith("INPUT_"));
|
||||
core.saveState(persistedInputsKey, JSON.stringify(inputEnvironmentVariables));
|
||||
};
|
||||
exports.persistInputs = persistInputs;
|
||||
/**
|
||||
* Restores all inputs to the action from the persisted state.
|
||||
*/
|
||||
const restoreInputs = function () {
|
||||
const persistedInputs = core.getState(persistedInputsKey);
|
||||
if (persistedInputs) {
|
||||
for (const [name, value] of JSON.parse(persistedInputs)) {
|
||||
process.env[name] = value;
|
||||
}
|
||||
}
|
||||
};
|
||||
exports.restoreInputs = restoreInputs;
|
||||
/**
|
||||
* Returns the base and head branches of the pull request being analyzed.
|
||||
*
|
||||
* @returns the base and head branches of the pull request, or undefined if
|
||||
* we are not analyzing a pull request.
|
||||
*/
|
||||
function getPullRequestBranches() {
|
||||
const pullRequest = github.context.payload.pull_request;
|
||||
if (pullRequest) {
|
||||
return {
|
||||
base: pullRequest.base.ref,
|
||||
// We use the head label instead of the head ref here, because the head
|
||||
// ref lacks owner information and by itself does not uniquely identify
|
||||
// the head branch (which may be in a forked repository).
|
||||
head: pullRequest.head.label,
|
||||
};
|
||||
}
|
||||
// PR analysis under Default Setup does not have the pull_request context,
|
||||
// but it should set CODE_SCANNING_REF and CODE_SCANNING_BASE_BRANCH.
|
||||
const codeScanningRef = process.env.CODE_SCANNING_REF;
|
||||
const codeScanningBaseBranch = process.env.CODE_SCANNING_BASE_BRANCH;
|
||||
if (codeScanningRef && codeScanningBaseBranch) {
|
||||
return {
|
||||
base: codeScanningBaseBranch,
|
||||
// PR analysis under Default Setup analyzes the PR head commit instead of
|
||||
// the merge commit, so we can use the provided ref directly.
|
||||
head: codeScanningRef,
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Returns whether we are analyzing a pull request.
|
||||
*/
|
||||
function isAnalyzingPullRequest() {
|
||||
return getPullRequestBranches() !== undefined;
|
||||
}
|
||||
/**
|
||||
* A workaround for code quality to map category names from old default setup workflows
|
||||
* to ones that the code quality service expects.
|
||||
*/
|
||||
const qualityCategoryMapping = {
|
||||
"c#": "csharp",
|
||||
cpp: "c-cpp",
|
||||
c: "c-cpp",
|
||||
"c++": "c-cpp",
|
||||
java: "java-kotlin",
|
||||
javascript: "javascript-typescript",
|
||||
typescript: "javascript-typescript",
|
||||
kotlin: "java-kotlin",
|
||||
};
|
||||
/** Adjusts the category string for a Code Quality SARIF file if an "old"
|
||||
* category identifier is used by Default Setup.
|
||||
*/
|
||||
function fixCodeQualityCategory(logger, category) {
|
||||
// The `category` should always be set by Default Setup. We perform this check
|
||||
// to avoid potential issues if Code Quality supports Advanced Setup in the future
|
||||
// and before this workaround is removed.
|
||||
if (category !== undefined &&
|
||||
isDefaultSetup() &&
|
||||
category.startsWith("/language:")) {
|
||||
const language = category.substring("/language:".length);
|
||||
const mappedLanguage = qualityCategoryMapping[language];
|
||||
if (mappedLanguage) {
|
||||
const newCategory = `/language:${mappedLanguage}`;
|
||||
logger.info(`Adjusted category for Code Quality from '${category}' to '${newCategory}'.`);
|
||||
return newCategory;
|
||||
}
|
||||
}
|
||||
return category;
|
||||
}
|
||||
//# sourceMappingURL=actions-util.js.map
|
||||
File diff suppressed because one or more lines are too long
192
lib/actions-util.test.js
generated
192
lib/actions-util.test.js
generated
@@ -1,192 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const github = __importStar(require("@actions/github"));
|
||||
const ava_1 = __importDefault(require("ava"));
|
||||
const actions_util_1 = require("./actions-util");
|
||||
const api_client_1 = require("./api-client");
|
||||
const environment_1 = require("./environment");
|
||||
const logging_1 = require("./logging");
|
||||
const testing_utils_1 = require("./testing-utils");
|
||||
const util_1 = require("./util");
|
||||
(0, testing_utils_1.setupTests)(ava_1.default);
|
||||
function withMockedContext(mockPayload, testFn) {
|
||||
const originalPayload = github.context.payload;
|
||||
github.context.payload = mockPayload;
|
||||
try {
|
||||
return testFn();
|
||||
}
|
||||
finally {
|
||||
github.context.payload = originalPayload;
|
||||
}
|
||||
}
|
||||
function withMockedEnv(envVars, testFn) {
|
||||
const originalEnv = { ...process.env };
|
||||
// Apply environment changes
|
||||
for (const [key, value] of Object.entries(envVars)) {
|
||||
if (value === undefined) {
|
||||
delete process.env[key];
|
||||
}
|
||||
else {
|
||||
process.env[key] = value;
|
||||
}
|
||||
}
|
||||
try {
|
||||
return testFn();
|
||||
}
|
||||
finally {
|
||||
// Restore original environment
|
||||
process.env = originalEnv;
|
||||
}
|
||||
}
|
||||
(0, ava_1.default)("computeAutomationID()", async (t) => {
|
||||
let actualAutomationID = (0, api_client_1.computeAutomationID)(".github/workflows/codeql-analysis.yml:analyze", '{"language": "javascript", "os": "linux"}');
|
||||
t.deepEqual(actualAutomationID, ".github/workflows/codeql-analysis.yml:analyze/language:javascript/os:linux/");
|
||||
// check the environment sorting
|
||||
actualAutomationID = (0, api_client_1.computeAutomationID)(".github/workflows/codeql-analysis.yml:analyze", '{"os": "linux", "language": "javascript"}');
|
||||
t.deepEqual(actualAutomationID, ".github/workflows/codeql-analysis.yml:analyze/language:javascript/os:linux/");
|
||||
// check that an empty environment produces the right results
|
||||
actualAutomationID = (0, api_client_1.computeAutomationID)(".github/workflows/codeql-analysis.yml:analyze", "{}");
|
||||
t.deepEqual(actualAutomationID, ".github/workflows/codeql-analysis.yml:analyze/");
|
||||
// check non string environment values
|
||||
actualAutomationID = (0, api_client_1.computeAutomationID)(".github/workflows/codeql-analysis.yml:analyze", '{"number": 1, "object": {"language": "javascript"}}');
|
||||
t.deepEqual(actualAutomationID, ".github/workflows/codeql-analysis.yml:analyze/number:/object:/");
|
||||
// check undefined environment
|
||||
actualAutomationID = (0, api_client_1.computeAutomationID)(".github/workflows/codeql-analysis.yml:analyze", undefined);
|
||||
t.deepEqual(actualAutomationID, ".github/workflows/codeql-analysis.yml:analyze/");
|
||||
});
|
||||
(0, ava_1.default)("getPullRequestBranches() with pull request context", (t) => {
|
||||
withMockedContext({
|
||||
pull_request: {
|
||||
number: 123,
|
||||
base: { ref: "main" },
|
||||
head: { label: "user:feature-branch" },
|
||||
},
|
||||
}, () => {
|
||||
t.deepEqual((0, actions_util_1.getPullRequestBranches)(), {
|
||||
base: "main",
|
||||
head: "user:feature-branch",
|
||||
});
|
||||
t.is((0, actions_util_1.isAnalyzingPullRequest)(), true);
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("getPullRequestBranches() returns undefined with push context", (t) => {
|
||||
withMockedContext({
|
||||
push: {
|
||||
ref: "refs/heads/main",
|
||||
},
|
||||
}, () => {
|
||||
t.is((0, actions_util_1.getPullRequestBranches)(), undefined);
|
||||
t.is((0, actions_util_1.isAnalyzingPullRequest)(), false);
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("getPullRequestBranches() with Default Setup environment variables", (t) => {
|
||||
withMockedContext({}, () => {
|
||||
withMockedEnv({
|
||||
CODE_SCANNING_REF: "refs/heads/feature-branch",
|
||||
CODE_SCANNING_BASE_BRANCH: "main",
|
||||
}, () => {
|
||||
t.deepEqual((0, actions_util_1.getPullRequestBranches)(), {
|
||||
base: "main",
|
||||
head: "refs/heads/feature-branch",
|
||||
});
|
||||
t.is((0, actions_util_1.isAnalyzingPullRequest)(), true);
|
||||
});
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("getPullRequestBranches() returns undefined when only CODE_SCANNING_REF is set", (t) => {
|
||||
withMockedContext({}, () => {
|
||||
withMockedEnv({
|
||||
CODE_SCANNING_REF: "refs/heads/feature-branch",
|
||||
CODE_SCANNING_BASE_BRANCH: undefined,
|
||||
}, () => {
|
||||
t.is((0, actions_util_1.getPullRequestBranches)(), undefined);
|
||||
t.is((0, actions_util_1.isAnalyzingPullRequest)(), false);
|
||||
});
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("getPullRequestBranches() returns undefined when only CODE_SCANNING_BASE_BRANCH is set", (t) => {
|
||||
withMockedContext({}, () => {
|
||||
withMockedEnv({
|
||||
CODE_SCANNING_REF: undefined,
|
||||
CODE_SCANNING_BASE_BRANCH: "main",
|
||||
}, () => {
|
||||
t.is((0, actions_util_1.getPullRequestBranches)(), undefined);
|
||||
t.is((0, actions_util_1.isAnalyzingPullRequest)(), false);
|
||||
});
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("getPullRequestBranches() returns undefined when no PR context", (t) => {
|
||||
withMockedContext({}, () => {
|
||||
withMockedEnv({
|
||||
CODE_SCANNING_REF: undefined,
|
||||
CODE_SCANNING_BASE_BRANCH: undefined,
|
||||
}, () => {
|
||||
t.is((0, actions_util_1.getPullRequestBranches)(), undefined);
|
||||
t.is((0, actions_util_1.isAnalyzingPullRequest)(), false);
|
||||
});
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("initializeEnvironment", (t) => {
|
||||
(0, util_1.initializeEnvironment)("1.2.3");
|
||||
t.deepEqual(process.env[environment_1.EnvVar.VERSION], "1.2.3");
|
||||
});
|
||||
(0, ava_1.default)("fixCodeQualityCategory", (t) => {
|
||||
withMockedEnv({
|
||||
GITHUB_EVENT_NAME: "dynamic",
|
||||
}, () => {
|
||||
const logger = (0, logging_1.getRunnerLogger)(true);
|
||||
// Categories that should get adjusted.
|
||||
t.is((0, actions_util_1.fixCodeQualityCategory)(logger, "/language:c#"), "/language:csharp");
|
||||
t.is((0, actions_util_1.fixCodeQualityCategory)(logger, "/language:cpp"), "/language:c-cpp");
|
||||
t.is((0, actions_util_1.fixCodeQualityCategory)(logger, "/language:c"), "/language:c-cpp");
|
||||
t.is((0, actions_util_1.fixCodeQualityCategory)(logger, "/language:java"), "/language:java-kotlin");
|
||||
t.is((0, actions_util_1.fixCodeQualityCategory)(logger, "/language:javascript"), "/language:javascript-typescript");
|
||||
t.is((0, actions_util_1.fixCodeQualityCategory)(logger, "/language:typescript"), "/language:javascript-typescript");
|
||||
t.is((0, actions_util_1.fixCodeQualityCategory)(logger, "/language:kotlin"), "/language:java-kotlin");
|
||||
// Categories that should not get adjusted.
|
||||
t.is((0, actions_util_1.fixCodeQualityCategory)(logger, "/language:csharp"), "/language:csharp");
|
||||
t.is((0, actions_util_1.fixCodeQualityCategory)(logger, "/language:go"), "/language:go");
|
||||
t.is((0, actions_util_1.fixCodeQualityCategory)(logger, "/language:actions"), "/language:actions");
|
||||
// Other cases.
|
||||
t.is((0, actions_util_1.fixCodeQualityCategory)(logger, undefined), undefined);
|
||||
t.is((0, actions_util_1.fixCodeQualityCategory)(logger, "random string"), "random string");
|
||||
t.is((0, actions_util_1.fixCodeQualityCategory)(logger, "kotlin"), "kotlin");
|
||||
});
|
||||
});
|
||||
//# sourceMappingURL=actions-util.test.js.map
|
||||
File diff suppressed because one or more lines are too long
107
lib/analyze-action-env.test.js
generated
107
lib/analyze-action-env.test.js
generated
@@ -1,107 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const ava_1 = __importDefault(require("ava"));
|
||||
const sinon = __importStar(require("sinon"));
|
||||
const actionsUtil = __importStar(require("./actions-util"));
|
||||
const analyze = __importStar(require("./analyze"));
|
||||
const api = __importStar(require("./api-client"));
|
||||
const configUtils = __importStar(require("./config-utils"));
|
||||
const gitUtils = __importStar(require("./git-utils"));
|
||||
const statusReport = __importStar(require("./status-report"));
|
||||
const testing_utils_1 = require("./testing-utils");
|
||||
const util = __importStar(require("./util"));
|
||||
(0, testing_utils_1.setupTests)(ava_1.default);
|
||||
// This test needs to be in its own file so that ava would run it in its own
|
||||
// nodejs process. The code being tested is in analyze-action.ts, which runs
|
||||
// immediately on load. So the file needs to be loaded during part of the test,
|
||||
// and that can happen only once per nodejs process. If multiple such tests are
|
||||
// in the same test file, ava would run them in the same nodejs process, and all
|
||||
// but the first test would fail.
|
||||
(0, ava_1.default)("analyze action with RAM & threads from environment variables", async (t) => {
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
process.env["GITHUB_SERVER_URL"] = util.GITHUB_DOTCOM_URL;
|
||||
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
|
||||
process.env["GITHUB_API_URL"] = "https://api.github.com";
|
||||
sinon
|
||||
.stub(statusReport, "createStatusReportBase")
|
||||
.resolves({});
|
||||
sinon.stub(statusReport, "sendStatusReport").resolves();
|
||||
sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(true);
|
||||
const gitHubVersion = {
|
||||
type: util.GitHubVariant.DOTCOM,
|
||||
};
|
||||
sinon.stub(configUtils, "getConfig").resolves({
|
||||
gitHubVersion,
|
||||
augmentationProperties: {},
|
||||
languages: [],
|
||||
packs: [],
|
||||
trapCaches: {},
|
||||
});
|
||||
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
|
||||
requiredInputStub.withArgs("token").returns("fake-token");
|
||||
requiredInputStub.withArgs("upload-database").returns("false");
|
||||
requiredInputStub.withArgs("output").returns("out");
|
||||
const optionalInputStub = sinon.stub(actionsUtil, "getOptionalInput");
|
||||
optionalInputStub.withArgs("expect-error").returns("false");
|
||||
sinon.stub(api, "getGitHubVersion").resolves(gitHubVersion);
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, {});
|
||||
// When there are no action inputs for RAM and threads, the action uses
|
||||
// environment variables (passed down from the init action) to set RAM and
|
||||
// threads usage.
|
||||
process.env["CODEQL_THREADS"] = "-1";
|
||||
process.env["CODEQL_RAM"] = "4992";
|
||||
const runFinalizeStub = sinon.stub(analyze, "runFinalize");
|
||||
const runQueriesStub = sinon.stub(analyze, "runQueries");
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const analyzeAction = require("./analyze-action");
|
||||
// When analyze-action.ts loads, it runs an async function from the top
|
||||
// level but does not wait for it to finish. To ensure that calls to
|
||||
// runFinalize and runQueries are correctly captured by spies, we explicitly
|
||||
// wait for the action promise to complete before starting verification.
|
||||
await analyzeAction.runPromise;
|
||||
t.assert(runFinalizeStub.calledOnce);
|
||||
t.deepEqual(runFinalizeStub.firstCall.args[1], "--threads=-1");
|
||||
t.deepEqual(runFinalizeStub.firstCall.args[2], "--ram=4992");
|
||||
t.assert(runQueriesStub.calledOnce);
|
||||
t.deepEqual(runQueriesStub.firstCall.args[3], "--threads=-1");
|
||||
t.deepEqual(runQueriesStub.firstCall.args[1], "--ram=4992");
|
||||
});
|
||||
});
|
||||
//# sourceMappingURL=analyze-action-env.test.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"analyze-action-env.test.js","sourceRoot":"","sources":["../src/analyze-action-env.test.ts"],"names":[],"mappings":"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"}
|
||||
107
lib/analyze-action-input.test.js
generated
107
lib/analyze-action-input.test.js
generated
@@ -1,107 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const ava_1 = __importDefault(require("ava"));
|
||||
const sinon = __importStar(require("sinon"));
|
||||
const actionsUtil = __importStar(require("./actions-util"));
|
||||
const analyze = __importStar(require("./analyze"));
|
||||
const api = __importStar(require("./api-client"));
|
||||
const configUtils = __importStar(require("./config-utils"));
|
||||
const gitUtils = __importStar(require("./git-utils"));
|
||||
const statusReport = __importStar(require("./status-report"));
|
||||
const testing_utils_1 = require("./testing-utils");
|
||||
const util = __importStar(require("./util"));
|
||||
(0, testing_utils_1.setupTests)(ava_1.default);
|
||||
// This test needs to be in its own file so that ava would run it in its own
|
||||
// nodejs process. The code being tested is in analyze-action.ts, which runs
|
||||
// immediately on load. So the file needs to be loaded during part of the test,
|
||||
// and that can happen only once per nodejs process. If multiple such tests are
|
||||
// in the same test file, ava would run them in the same nodejs process, and all
|
||||
// but the first test would fail.
|
||||
(0, ava_1.default)("analyze action with RAM & threads from action inputs", async (t) => {
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
process.env["GITHUB_SERVER_URL"] = util.GITHUB_DOTCOM_URL;
|
||||
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
|
||||
process.env["GITHUB_API_URL"] = "https://api.github.com";
|
||||
sinon
|
||||
.stub(statusReport, "createStatusReportBase")
|
||||
.resolves({});
|
||||
sinon.stub(statusReport, "sendStatusReport").resolves();
|
||||
const gitHubVersion = {
|
||||
type: util.GitHubVariant.DOTCOM,
|
||||
};
|
||||
sinon.stub(configUtils, "getConfig").resolves({
|
||||
gitHubVersion,
|
||||
augmentationProperties: {},
|
||||
languages: [],
|
||||
packs: [],
|
||||
trapCaches: {},
|
||||
});
|
||||
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
|
||||
requiredInputStub.withArgs("token").returns("fake-token");
|
||||
requiredInputStub.withArgs("upload-database").returns("false");
|
||||
requiredInputStub.withArgs("output").returns("out");
|
||||
const optionalInputStub = sinon.stub(actionsUtil, "getOptionalInput");
|
||||
optionalInputStub.withArgs("expect-error").returns("false");
|
||||
sinon.stub(api, "getGitHubVersion").resolves(gitHubVersion);
|
||||
sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(true);
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, {});
|
||||
process.env["CODEQL_THREADS"] = "1";
|
||||
process.env["CODEQL_RAM"] = "4992";
|
||||
// Action inputs have precedence over environment variables.
|
||||
optionalInputStub.withArgs("threads").returns("-1");
|
||||
optionalInputStub.withArgs("ram").returns("3012");
|
||||
const runFinalizeStub = sinon.stub(analyze, "runFinalize");
|
||||
const runQueriesStub = sinon.stub(analyze, "runQueries");
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const analyzeAction = require("./analyze-action");
|
||||
// When analyze-action.ts loads, it runs an async function from the top
|
||||
// level but does not wait for it to finish. To ensure that calls to
|
||||
// runFinalize and runQueries are correctly captured by spies, we explicitly
|
||||
// wait for the action promise to complete before starting verification.
|
||||
await analyzeAction.runPromise;
|
||||
t.assert(runFinalizeStub.calledOnce);
|
||||
t.deepEqual(runFinalizeStub.firstCall.args[1], "--threads=-1");
|
||||
t.deepEqual(runFinalizeStub.firstCall.args[2], "--ram=3012");
|
||||
t.assert(runQueriesStub.calledOnce);
|
||||
t.deepEqual(runQueriesStub.firstCall.args[3], "--threads=-1");
|
||||
t.deepEqual(runQueriesStub.firstCall.args[1], "--ram=3012");
|
||||
});
|
||||
});
|
||||
//# sourceMappingURL=analyze-action-input.test.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"analyze-action-input.test.js","sourceRoot":"","sources":["../src/analyze-action-input.test.ts"],"names":[],"mappings":"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"}
|
||||
127110
lib/analyze-action-post.js
generated
127110
lib/analyze-action-post.js
generated
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"analyze-action-post.js","sourceRoot":"","sources":["../src/analyze-action-post.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;GAIG;AACH,uCAAyB;AAEzB,oDAAsC;AAEtC,4DAA8C;AAC9C,6CAAgD;AAChD,qCAAqC;AACrC,iDAA2C;AAC3C,kEAAoD;AACpD,6DAAgE;AAChE,+CAAuC;AACvC,uCAA6C;AAC7C,iCAAoE;AAEpE,KAAK,UAAU,UAAU;IACvB,IAAI,CAAC;QACH,WAAW,CAAC,aAAa,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,IAAA,0BAAgB,GAAE,CAAC;QAClC,MAAM,aAAa,GAAG,MAAM,IAAA,6BAAgB,GAAE,CAAC;QAC/C,IAAA,gCAAyB,EAAC,aAAa,EAAE,MAAM,CAAC,CAAC;QAEjD,kFAAkF;QAClF,wFAAwF;QACxF,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAM,CAAC,mBAAmB,CAAC,KAAK,MAAM,EAAE,CAAC;YACvD,MAAM,MAAM,GAAG,MAAM,IAAA,wBAAS,EAC5B,WAAW,CAAC,qBAAqB,EAAE,EACnC,MAAM,CACP,CAAC;YACF,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,MAAM,MAAM,GAAG,MAAM,IAAA,kBAAS,EAAC,MAAM,CAAC,SAAS,CAAC,CAAC;gBACjD,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,UAAU,EAAE,CAAC;gBAC1C,MAAM,cAAc,CAAC,4BAA4B,CAC/C,MAAM,EACN,MAAM,CAAC,aAAa,CAAC,IAAI,EACzB,OAAO,CAAC,OAAO,CAChB,CAAC;YACJ,CAAC;QACH,CAAC;QAED,+EAA+E;QAC/E,4EAA4E;QAC5E,+BAA+B;QAC/B,MAAM,qBAAqB,GAAG,IAAA,6CAAwB,GAAE,CAAC;QACzD,IAAI,EAAE,CAAC,UAAU,CAAC,qBAAqB,CAAC,EAAE,CAAC;YACzC,IAAI,CAAC;gBACH,EAAE,CAAC,MAAM,CAAC,qBAAqB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACxD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,IAAI,CACT,2DAA2D,IAAA,sBAAe,EAAC,KAAK,CAAC,EAAE,CACpF,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,SAAS,CACZ,oCAAoC,IAAA,sBAAe,EAAC,KAAK,CAAC,EAAE,CAC7D,CAAC;IACJ,CAAC;AACH,CAAC;AAED,KAAK,UAAU,EAAE,CAAC"}
|
||||
98851
lib/analyze-action.js
generated
98851
lib/analyze-action.js
generated
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
575
lib/analyze.js
generated
575
lib/analyze.js
generated
@@ -1,575 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.exportedForTesting = exports.defaultSuites = exports.CodeQLAnalysisError = void 0;
|
||||
exports.runExtraction = runExtraction;
|
||||
exports.dbIsFinalized = dbIsFinalized;
|
||||
exports.setupDiffInformedQueryRun = setupDiffInformedQueryRun;
|
||||
exports.resolveQuerySuiteAlias = resolveQuerySuiteAlias;
|
||||
exports.runQueries = runQueries;
|
||||
exports.runFinalize = runFinalize;
|
||||
exports.warnIfGoInstalledAfterInit = warnIfGoInstalledAfterInit;
|
||||
const fs = __importStar(require("fs"));
|
||||
const path = __importStar(require("path"));
|
||||
const perf_hooks_1 = require("perf_hooks");
|
||||
const io = __importStar(require("@actions/io"));
|
||||
const del_1 = __importDefault(require("del"));
|
||||
const yaml = __importStar(require("js-yaml"));
|
||||
const actions_util_1 = require("./actions-util");
|
||||
const api_client_1 = require("./api-client");
|
||||
const autobuild_1 = require("./autobuild");
|
||||
const dependency_caching_1 = require("./dependency-caching");
|
||||
const diagnostics_1 = require("./diagnostics");
|
||||
const diff_informed_analysis_utils_1 = require("./diff-informed-analysis-utils");
|
||||
const environment_1 = require("./environment");
|
||||
const feature_flags_1 = require("./feature-flags");
|
||||
const languages_1 = require("./languages");
|
||||
const logging_1 = require("./logging");
|
||||
const overlay_database_utils_1 = require("./overlay-database-utils");
|
||||
const repository_1 = require("./repository");
|
||||
const tracer_config_1 = require("./tracer-config");
|
||||
const util = __importStar(require("./util"));
|
||||
const util_1 = require("./util");
|
||||
class CodeQLAnalysisError extends Error {
|
||||
constructor(queriesStatusReport, message, error) {
|
||||
super(message);
|
||||
this.queriesStatusReport = queriesStatusReport;
|
||||
this.message = message;
|
||||
this.error = error;
|
||||
this.name = "CodeQLAnalysisError";
|
||||
}
|
||||
}
|
||||
exports.CodeQLAnalysisError = CodeQLAnalysisError;
|
||||
async function setupPythonExtractor(logger) {
|
||||
const codeqlPython = process.env["CODEQL_PYTHON"];
|
||||
if (codeqlPython === undefined || codeqlPython.length === 0) {
|
||||
// If CODEQL_PYTHON is not set, no dependencies were installed, so we don't need to do anything
|
||||
return;
|
||||
}
|
||||
logger.warning("The CODEQL_PYTHON environment variable is no longer supported. Please remove it from your workflow. This environment variable was originally used to specify a Python executable that included the dependencies of your Python code, however Python analysis no longer uses these dependencies." +
|
||||
"\nIf you used CODEQL_PYTHON to force the version of Python to analyze as, please use CODEQL_EXTRACTOR_PYTHON_ANALYSIS_VERSION instead, such as 'CODEQL_EXTRACTOR_PYTHON_ANALYSIS_VERSION=2.7' or 'CODEQL_EXTRACTOR_PYTHON_ANALYSIS_VERSION=3.11'.");
|
||||
return;
|
||||
}
|
||||
async function runExtraction(codeql, config, logger) {
|
||||
for (const language of config.languages) {
|
||||
if (dbIsFinalized(config, language, logger)) {
|
||||
logger.debug(`Database for ${language} has already been finalized, skipping extraction.`);
|
||||
continue;
|
||||
}
|
||||
if (await shouldExtractLanguage(codeql, config, language)) {
|
||||
logger.startGroup(`Extracting ${language}`);
|
||||
if (language === languages_1.KnownLanguage.python) {
|
||||
await setupPythonExtractor(logger);
|
||||
}
|
||||
if (config.buildMode) {
|
||||
if (language === languages_1.KnownLanguage.cpp &&
|
||||
config.buildMode === util_1.BuildMode.Autobuild) {
|
||||
await (0, autobuild_1.setupCppAutobuild)(codeql, logger);
|
||||
}
|
||||
// The Java `build-mode: none` extractor places dependencies (.jar files) in the
|
||||
// database scratch directory by default. For dependency caching purposes, we want
|
||||
// a stable path that caches can be restored into and that we can cache at the
|
||||
// end of the workflow (i.e. that does not get removed when the scratch directory is).
|
||||
if (language === languages_1.KnownLanguage.java &&
|
||||
config.buildMode === util_1.BuildMode.None) {
|
||||
process.env["CODEQL_EXTRACTOR_JAVA_OPTION_BUILDLESS_DEPENDENCY_DIR"] =
|
||||
(0, dependency_caching_1.getJavaTempDependencyDir)();
|
||||
}
|
||||
await codeql.extractUsingBuildMode(config, language);
|
||||
}
|
||||
else {
|
||||
await codeql.extractScannedLanguage(config, language);
|
||||
}
|
||||
logger.endGroup();
|
||||
}
|
||||
}
|
||||
}
|
||||
async function shouldExtractLanguage(codeql, config, language) {
|
||||
return (config.buildMode === util_1.BuildMode.None ||
|
||||
(config.buildMode === util_1.BuildMode.Autobuild &&
|
||||
process.env[environment_1.EnvVar.AUTOBUILD_DID_COMPLETE_SUCCESSFULLY] !== "true") ||
|
||||
(!config.buildMode && (await codeql.isScannedLanguage(language))));
|
||||
}
|
||||
function dbIsFinalized(config, language, logger) {
|
||||
const dbPath = util.getCodeQLDatabasePath(config, language);
|
||||
try {
|
||||
const dbInfo = yaml.load(fs.readFileSync(path.resolve(dbPath, "codeql-database.yml"), "utf8"));
|
||||
return !("inProgress" in dbInfo);
|
||||
}
|
||||
catch {
|
||||
logger.warning(`Could not check whether database for ${language} was finalized. Assuming it is not.`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
async function finalizeDatabaseCreation(codeql, config, threadsFlag, memoryFlag, logger) {
|
||||
const extractionStart = perf_hooks_1.performance.now();
|
||||
await runExtraction(codeql, config, logger);
|
||||
const extractionTime = perf_hooks_1.performance.now() - extractionStart;
|
||||
const trapImportStart = perf_hooks_1.performance.now();
|
||||
for (const language of config.languages) {
|
||||
if (dbIsFinalized(config, language, logger)) {
|
||||
logger.info(`There is already a finalized database for ${language} at the location where the CodeQL Action places databases, so we did not create one.`);
|
||||
}
|
||||
else {
|
||||
logger.startGroup(`Finalizing ${language}`);
|
||||
await codeql.finalizeDatabase(util.getCodeQLDatabasePath(config, language), threadsFlag, memoryFlag, config.debugMode);
|
||||
logger.endGroup();
|
||||
}
|
||||
}
|
||||
const trapImportTime = perf_hooks_1.performance.now() - trapImportStart;
|
||||
return {
|
||||
scanned_language_extraction_duration_ms: Math.round(extractionTime),
|
||||
trap_import_duration_ms: Math.round(trapImportTime),
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Set up the diff-informed analysis feature.
|
||||
*
|
||||
* @returns Absolute path to the directory containing the extension pack for
|
||||
* the diff range information, or `undefined` if the feature is disabled.
|
||||
*/
|
||||
async function setupDiffInformedQueryRun(branches, logger) {
|
||||
return await (0, logging_1.withGroupAsync)("Generating diff range extension pack", async () => {
|
||||
logger.info(`Calculating diff ranges for ${branches.base}...${branches.head}`);
|
||||
const diffRanges = await getPullRequestEditedDiffRanges(branches, logger);
|
||||
const packDir = writeDiffRangeDataExtensionPack(logger, diffRanges);
|
||||
if (packDir === undefined) {
|
||||
logger.warning("Cannot create diff range extension pack for diff-informed queries; " +
|
||||
"reverting to performing full analysis.");
|
||||
}
|
||||
else {
|
||||
logger.info(`Successfully created diff range extension pack at ${packDir}.`);
|
||||
}
|
||||
return packDir;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Return the file line ranges that were added or modified in the pull request.
|
||||
*
|
||||
* @param branches The base and head branches of the pull request.
|
||||
* @param logger
|
||||
* @returns An array of tuples, where each tuple contains the absolute path of a
|
||||
* file, the start line and the end line (both 1-based and inclusive) of an
|
||||
* added or modified range in that file. Returns `undefined` if the action was
|
||||
* not triggered by a pull request or if there was an error.
|
||||
*/
|
||||
async function getPullRequestEditedDiffRanges(branches, logger) {
|
||||
const fileDiffs = await getFileDiffsWithBasehead(branches, logger);
|
||||
if (fileDiffs === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (fileDiffs.length >= 300) {
|
||||
// The "compare two commits" API returns a maximum of 300 changed files. If
|
||||
// we see that many changed files, it is possible that there could be more,
|
||||
// with the rest being truncated. In this case, we should not attempt to
|
||||
// compute the diff ranges, as the result would be incomplete.
|
||||
logger.warning(`Cannot retrieve the full diff because there are too many ` +
|
||||
`(${fileDiffs.length}) changed files in the pull request.`);
|
||||
return undefined;
|
||||
}
|
||||
const results = [];
|
||||
for (const filediff of fileDiffs) {
|
||||
const diffRanges = getDiffRanges(filediff, logger);
|
||||
if (diffRanges === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
results.push(...diffRanges);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
async function getFileDiffsWithBasehead(branches, logger) {
|
||||
// Check CODE_SCANNING_REPOSITORY first. If it is empty or not set, fall back
|
||||
// to GITHUB_REPOSITORY.
|
||||
const repositoryNwo = (0, repository_1.getRepositoryNwoFromEnv)("CODE_SCANNING_REPOSITORY", "GITHUB_REPOSITORY");
|
||||
const basehead = `${branches.base}...${branches.head}`;
|
||||
try {
|
||||
const response = await (0, api_client_1.getApiClient)().rest.repos.compareCommitsWithBasehead({
|
||||
owner: repositoryNwo.owner,
|
||||
repo: repositoryNwo.repo,
|
||||
basehead,
|
||||
per_page: 1,
|
||||
});
|
||||
logger.debug(`Response from compareCommitsWithBasehead(${basehead}):` +
|
||||
`\n${JSON.stringify(response, null, 2)}`);
|
||||
return response.data.files;
|
||||
}
|
||||
catch (error) {
|
||||
if (error.status) {
|
||||
logger.warning(`Error retrieving diff ${basehead}: ${error.message}`);
|
||||
logger.debug(`Error running compareCommitsWithBasehead(${basehead}):` +
|
||||
`\nRequest: ${JSON.stringify(error.request, null, 2)}` +
|
||||
`\nError Response: ${JSON.stringify(error.response, null, 2)}`);
|
||||
return undefined;
|
||||
}
|
||||
else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
function getDiffRanges(fileDiff, logger) {
|
||||
// Diff-informed queries expect the file path to be absolute. CodeQL always
|
||||
// uses forward slashes as the path separator, so on Windows we need to
|
||||
// replace any backslashes with forward slashes.
|
||||
const filename = path
|
||||
.join((0, actions_util_1.getRequiredInput)("checkout_path"), fileDiff.filename)
|
||||
.replaceAll(path.sep, "/");
|
||||
if (fileDiff.patch === undefined) {
|
||||
if (fileDiff.changes === 0) {
|
||||
// There are situations where a changed file legitimately has no diff.
|
||||
// For example, the file may be a binary file, or that the file may have
|
||||
// been renamed with no changes to its contents. In these cases, the
|
||||
// file would be reported as having 0 changes, and we can return an empty
|
||||
// array to indicate no diff range in this file.
|
||||
return [];
|
||||
}
|
||||
// If a file is reported to have nonzero changes but no patch, that may be
|
||||
// due to the file diff being too large. In this case, we should fall back
|
||||
// to a special diff range that covers the entire file.
|
||||
return [
|
||||
{
|
||||
path: filename,
|
||||
startLine: 0,
|
||||
endLine: 0,
|
||||
},
|
||||
];
|
||||
}
|
||||
// The 1-based file line number of the current line
|
||||
let currentLine = 0;
|
||||
// The 1-based file line number that starts the current range of added lines
|
||||
let additionRangeStartLine = undefined;
|
||||
const diffRanges = [];
|
||||
const diffLines = fileDiff.patch.split("\n");
|
||||
// Adding a fake context line at the end ensures that the following loop will
|
||||
// always terminate the last range of added lines.
|
||||
diffLines.push(" ");
|
||||
for (const diffLine of diffLines) {
|
||||
if (diffLine.startsWith("-")) {
|
||||
// Ignore deletions completely -- we do not even want to consider them when
|
||||
// calculating consecutive ranges of added lines.
|
||||
continue;
|
||||
}
|
||||
if (diffLine.startsWith("+")) {
|
||||
if (additionRangeStartLine === undefined) {
|
||||
additionRangeStartLine = currentLine;
|
||||
}
|
||||
currentLine++;
|
||||
continue;
|
||||
}
|
||||
if (additionRangeStartLine !== undefined) {
|
||||
// Any line that does not start with a "+" or "-" terminates the current
|
||||
// range of added lines.
|
||||
diffRanges.push({
|
||||
path: filename,
|
||||
startLine: additionRangeStartLine,
|
||||
endLine: currentLine - 1,
|
||||
});
|
||||
additionRangeStartLine = undefined;
|
||||
}
|
||||
if (diffLine.startsWith("@@ ")) {
|
||||
// A new hunk header line resets the current line number.
|
||||
const match = diffLine.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
|
||||
if (match === null) {
|
||||
logger.warning(`Cannot parse diff hunk header for ${fileDiff.filename}: ${diffLine}`);
|
||||
return undefined;
|
||||
}
|
||||
currentLine = parseInt(match[1], 10);
|
||||
continue;
|
||||
}
|
||||
if (diffLine.startsWith(" ")) {
|
||||
// An unchanged context line advances the current line number.
|
||||
currentLine++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return diffRanges;
|
||||
}
|
||||
/**
|
||||
* Create an extension pack in the temporary directory that contains the file
|
||||
* line ranges that were added or modified in the pull request.
|
||||
*
|
||||
* @param logger
|
||||
* @param ranges The file line ranges, as returned by
|
||||
* `getPullRequestEditedDiffRanges`.
|
||||
* @returns The absolute path of the directory containing the extension pack, or
|
||||
* `undefined` if no extension pack was created.
|
||||
*/
|
||||
function writeDiffRangeDataExtensionPack(logger, ranges) {
|
||||
if (ranges === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (ranges.length === 0) {
|
||||
// An empty diff range means that there are no added or modified lines in
|
||||
// the pull request. But the `restrictAlertsTo` extensible predicate
|
||||
// interprets an empty data extension differently, as an indication that
|
||||
// all alerts should be included. So we need to specifically set the diff
|
||||
// range to a non-empty list that cannot match any alert location.
|
||||
ranges = [{ path: "", startLine: 0, endLine: 0 }];
|
||||
}
|
||||
const diffRangeDir = path.join((0, actions_util_1.getTemporaryDirectory)(), "pr-diff-range");
|
||||
// We expect the Actions temporary directory to already exist, so are mainly
|
||||
// using `recursive: true` to avoid errors if the directory already exists,
|
||||
// for example if the analyze Action is run multiple times in the same job.
|
||||
// This is not really something that is supported, but we make use of it in
|
||||
// tests.
|
||||
fs.mkdirSync(diffRangeDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(diffRangeDir, "qlpack.yml"), `
|
||||
name: codeql-action/pr-diff-range
|
||||
version: 0.0.0
|
||||
library: true
|
||||
extensionTargets:
|
||||
codeql/util: '*'
|
||||
dataExtensions:
|
||||
- pr-diff-range.yml
|
||||
`);
|
||||
const header = `
|
||||
extensions:
|
||||
- addsTo:
|
||||
pack: codeql/util
|
||||
extensible: restrictAlertsTo
|
||||
checkPresence: false
|
||||
data:
|
||||
`;
|
||||
let data = ranges
|
||||
.map((range) =>
|
||||
// Using yaml.dump() with `forceQuotes: true` ensures that all special
|
||||
// characters are escaped, and that the path is always rendered as a
|
||||
// quoted string on a single line.
|
||||
` - [${yaml.dump(range.path, { forceQuotes: true }).trim()}, ` +
|
||||
`${range.startLine}, ${range.endLine}]\n`)
|
||||
.join("");
|
||||
if (!data) {
|
||||
// Ensure that the data extension is not empty, so that a pull request with
|
||||
// no edited lines would exclude (instead of accepting) all alerts.
|
||||
data = ' - ["", 0, 0]\n';
|
||||
}
|
||||
const extensionContents = header + data;
|
||||
const extensionFilePath = path.join(diffRangeDir, "pr-diff-range.yml");
|
||||
fs.writeFileSync(extensionFilePath, extensionContents);
|
||||
logger.debug(`Wrote pr-diff-range extension pack to ${extensionFilePath}:\n${extensionContents}`);
|
||||
// Write the diff ranges to a JSON file, for action-side alert filtering by the
|
||||
// upload-lib module.
|
||||
(0, diff_informed_analysis_utils_1.writeDiffRangesJsonFile)(logger, ranges);
|
||||
return diffRangeDir;
|
||||
}
|
||||
// A set of default query suite names that are understood by the CLI.
|
||||
exports.defaultSuites = new Set([
|
||||
"security-experimental",
|
||||
"security-extended",
|
||||
"security-and-quality",
|
||||
"code-quality",
|
||||
"code-scanning",
|
||||
]);
|
||||
/**
|
||||
* If `maybeSuite` is the name of a default query suite, it is resolved into the corresponding
|
||||
* query suite name for the given `language`. Otherwise, `maybeSuite` is returned as is.
|
||||
*
|
||||
* @param language The language for which to resolve the default query suite name.
|
||||
* @param maybeSuite The string that potentially contains the name of a default query suite.
|
||||
* @returns Returns the resolved query suite name, or the unmodified input.
|
||||
*/
|
||||
function resolveQuerySuiteAlias(language, maybeSuite) {
|
||||
if (exports.defaultSuites.has(maybeSuite)) {
|
||||
return `${language}-${maybeSuite}.qls`;
|
||||
}
|
||||
return maybeSuite;
|
||||
}
|
||||
// Runs queries and creates sarif files in the given folder
|
||||
async function runQueries(sarifFolder, memoryFlag, addSnippetsFlag, threadsFlag, diffRangePackDir, automationDetailsId, codeql, config, logger, features) {
|
||||
const statusReport = {};
|
||||
const queryFlags = [memoryFlag, threadsFlag];
|
||||
const incrementalMode = [];
|
||||
// Preserve cached intermediate results for overlay-base databases.
|
||||
if (config.augmentationProperties.overlayDatabaseMode !==
|
||||
overlay_database_utils_1.OverlayDatabaseMode.OverlayBase) {
|
||||
queryFlags.push("--expect-discarded-cache");
|
||||
}
|
||||
statusReport.analysis_is_diff_informed = diffRangePackDir !== undefined;
|
||||
if (diffRangePackDir) {
|
||||
queryFlags.push(`--additional-packs=${diffRangePackDir}`);
|
||||
queryFlags.push("--extension-packs=codeql-action/pr-diff-range");
|
||||
incrementalMode.push("diff-informed");
|
||||
}
|
||||
statusReport.analysis_is_overlay =
|
||||
config.augmentationProperties.overlayDatabaseMode ===
|
||||
overlay_database_utils_1.OverlayDatabaseMode.Overlay;
|
||||
statusReport.analysis_builds_overlay_base_database =
|
||||
config.augmentationProperties.overlayDatabaseMode ===
|
||||
overlay_database_utils_1.OverlayDatabaseMode.OverlayBase;
|
||||
if (config.augmentationProperties.overlayDatabaseMode ===
|
||||
overlay_database_utils_1.OverlayDatabaseMode.Overlay) {
|
||||
incrementalMode.push("overlay");
|
||||
}
|
||||
const sarifRunPropertyFlag = incrementalMode.length > 0
|
||||
? `--sarif-run-property=incrementalMode=${incrementalMode.join(",")}`
|
||||
: undefined;
|
||||
for (const language of config.languages) {
|
||||
try {
|
||||
const sarifFile = path.join(sarifFolder, `${language}.sarif`);
|
||||
const queries = [];
|
||||
if (config.augmentationProperties.qualityQueriesInput !== undefined) {
|
||||
queries.push(util.getGeneratedSuitePath(config, language));
|
||||
for (const qualityQuery of config.augmentationProperties
|
||||
.qualityQueriesInput) {
|
||||
queries.push(resolveQuerySuiteAlias(language, qualityQuery.uses));
|
||||
}
|
||||
}
|
||||
// The work needed to generate the query suites
|
||||
// is done in the CLI. We just need to make a single
|
||||
// call to run all the queries for each language and
|
||||
// another to interpret the results.
|
||||
logger.startGroup(`Running queries for ${language}`);
|
||||
const startTimeRunQueries = new Date().getTime();
|
||||
const databasePath = util.getCodeQLDatabasePath(config, language);
|
||||
await codeql.databaseRunQueries(databasePath, queryFlags, queries);
|
||||
logger.debug(`Finished running queries for ${language}.`);
|
||||
// TODO should not be using `builtin` here. We should be using `all` instead.
|
||||
// The status report does not support `all` yet.
|
||||
statusReport[`analyze_builtin_queries_${language}_duration_ms`] =
|
||||
new Date().getTime() - startTimeRunQueries;
|
||||
logger.startGroup(`Interpreting results for ${language}`);
|
||||
const startTimeInterpretResults = new Date();
|
||||
const analysisSummary = await runInterpretResults(language, undefined, sarifFile, config.debugMode, automationDetailsId);
|
||||
let qualityAnalysisSummary;
|
||||
if (config.augmentationProperties.qualityQueriesInput !== undefined) {
|
||||
logger.info(`Interpreting quality results for ${language}`);
|
||||
const qualityCategory = (0, actions_util_1.fixCodeQualityCategory)(logger, automationDetailsId);
|
||||
const qualitySarifFile = path.join(sarifFolder, `${language}.quality.sarif`);
|
||||
qualityAnalysisSummary = await runInterpretResults(language, config.augmentationProperties.qualityQueriesInput.map((i) => resolveQuerySuiteAlias(language, i.uses)), qualitySarifFile, config.debugMode, qualityCategory);
|
||||
}
|
||||
const endTimeInterpretResults = new Date();
|
||||
statusReport[`interpret_results_${language}_duration_ms`] =
|
||||
endTimeInterpretResults.getTime() - startTimeInterpretResults.getTime();
|
||||
logger.endGroup();
|
||||
logger.info(analysisSummary);
|
||||
if (qualityAnalysisSummary) {
|
||||
logger.info(qualityAnalysisSummary);
|
||||
}
|
||||
if (await features.getValue(feature_flags_1.Feature.QaTelemetryEnabled)) {
|
||||
const perQueryAlertCounts = getPerQueryAlertCounts(sarifFile);
|
||||
const perQueryAlertCountEventReport = {
|
||||
event: "codeql database interpret-results",
|
||||
started_at: startTimeInterpretResults.toISOString(),
|
||||
completed_at: endTimeInterpretResults.toISOString(),
|
||||
exit_status: "success",
|
||||
language,
|
||||
properties: {
|
||||
alertCounts: perQueryAlertCounts,
|
||||
},
|
||||
};
|
||||
if (statusReport["event_reports"] === undefined) {
|
||||
statusReport["event_reports"] = [];
|
||||
}
|
||||
statusReport["event_reports"].push(perQueryAlertCountEventReport);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
statusReport.analyze_failure_language = language;
|
||||
throw new CodeQLAnalysisError(statusReport, `Error running analysis for ${language}: ${util.getErrorMessage(e)}`, util.wrapError(e));
|
||||
}
|
||||
}
|
||||
return statusReport;
|
||||
async function runInterpretResults(language, queries, sarifFile, enableDebugLogging, category) {
|
||||
const databasePath = util.getCodeQLDatabasePath(config, language);
|
||||
return await codeql.databaseInterpretResults(databasePath, queries, sarifFile, addSnippetsFlag, threadsFlag, enableDebugLogging ? "-vv" : "-v", sarifRunPropertyFlag, category, config, features);
|
||||
}
|
||||
/** Get an object with all queries and their counts parsed from a SARIF file path. */
|
||||
function getPerQueryAlertCounts(sarifPath) {
|
||||
const sarifObject = JSON.parse(fs.readFileSync(sarifPath, "utf8"));
|
||||
// We do not need to compute fingerprints because we are not sending data based off of locations.
|
||||
// Generate the query: alert count object
|
||||
const perQueryAlertCounts = {};
|
||||
// All rules (queries), from all results, from all runs
|
||||
for (const sarifRun of sarifObject.runs) {
|
||||
if (sarifRun.results) {
|
||||
for (const result of sarifRun.results) {
|
||||
const query = result.rule?.id || result.ruleId;
|
||||
if (query) {
|
||||
perQueryAlertCounts[query] = (perQueryAlertCounts[query] || 0) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return perQueryAlertCounts;
|
||||
}
|
||||
}
|
||||
async function runFinalize(outputDir, threadsFlag, memoryFlag, codeql, config, logger) {
|
||||
try {
|
||||
await (0, del_1.default)(outputDir, { force: true });
|
||||
}
|
||||
catch (error) {
|
||||
if (error?.code !== "ENOENT") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
await fs.promises.mkdir(outputDir, { recursive: true });
|
||||
const timings = await finalizeDatabaseCreation(codeql, config, threadsFlag, memoryFlag, logger);
|
||||
// If we didn't already end tracing in the autobuild Action, end it now.
|
||||
if (process.env[environment_1.EnvVar.AUTOBUILD_DID_COMPLETE_SUCCESSFULLY] !== "true") {
|
||||
await (0, tracer_config_1.endTracingForCluster)(codeql, config, logger);
|
||||
}
|
||||
return timings;
|
||||
}
|
||||
async function warnIfGoInstalledAfterInit(config, logger) {
|
||||
// Check that `which go` still points at the same path it did when the `init` Action ran to ensure that no steps
|
||||
// in-between performed any setup. We encourage users to perform all setup tasks before initializing CodeQL so that
|
||||
// the setup tasks do not interfere with our analysis.
|
||||
// Furthermore, if we installed a wrapper script in the `init` Action, we need to ensure that there isn't a step
|
||||
// in the workflow after the `init` step which installs a different version of Go and takes precedence in the PATH,
|
||||
// thus potentially circumventing our workaround that allows tracing to work.
|
||||
const goInitPath = process.env[environment_1.EnvVar.GO_BINARY_LOCATION];
|
||||
if (process.env[environment_1.EnvVar.DID_AUTOBUILD_GOLANG] !== "true" &&
|
||||
goInitPath !== undefined) {
|
||||
const goBinaryPath = await io.which("go", true);
|
||||
if (goInitPath !== goBinaryPath) {
|
||||
logger.warning(`Expected \`which go\` to return ${goInitPath}, but got ${goBinaryPath}: please ensure that the correct version of Go is installed before the \`codeql-action/init\` Action is used.`);
|
||||
(0, diagnostics_1.addDiagnostic)(config, languages_1.KnownLanguage.go, (0, diagnostics_1.makeDiagnostic)("go/workflow/go-installed-after-codeql-init", "Go was installed after the `codeql-action/init` Action was run", {
|
||||
markdownMessage: "To avoid interfering with the CodeQL analysis, perform all installation steps before calling the `github/codeql-action/init` Action.",
|
||||
visibility: {
|
||||
statusPage: true,
|
||||
telemetry: true,
|
||||
cliSummaryTable: true,
|
||||
},
|
||||
severity: "warning",
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.exportedForTesting = {
|
||||
getDiffRanges,
|
||||
};
|
||||
//# sourceMappingURL=analyze.js.map
|
||||
File diff suppressed because one or more lines are too long
330
lib/analyze.test.js
generated
330
lib/analyze.test.js
generated
@@ -1,330 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const fs = __importStar(require("fs"));
|
||||
const path = __importStar(require("path"));
|
||||
const ava_1 = __importDefault(require("ava"));
|
||||
const sinon = __importStar(require("sinon"));
|
||||
const actionsUtil = __importStar(require("./actions-util"));
|
||||
const analyze_1 = require("./analyze");
|
||||
const codeql_1 = require("./codeql");
|
||||
const feature_flags_1 = require("./feature-flags");
|
||||
const languages_1 = require("./languages");
|
||||
const logging_1 = require("./logging");
|
||||
const testing_utils_1 = require("./testing-utils");
|
||||
const uploadLib = __importStar(require("./upload-lib"));
|
||||
const util = __importStar(require("./util"));
|
||||
(0, testing_utils_1.setupTests)(ava_1.default);
|
||||
/**
|
||||
* Checks the status report produced by the analyze Action.
|
||||
*
|
||||
* - Checks that the duration fields are populated for the correct language.
|
||||
* - Checks that the QA telemetry status report fields are populated when the QA feature flag is enabled.
|
||||
*/
|
||||
(0, ava_1.default)("status report fields", async (t) => {
|
||||
return await util.withTmpDir(async (tmpDir) => {
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
const memoryFlag = "";
|
||||
const addSnippetsFlag = "";
|
||||
const threadsFlag = "";
|
||||
sinon.stub(uploadLib, "validateSarifFileSchema");
|
||||
for (const language of Object.values(languages_1.KnownLanguage)) {
|
||||
const codeql = (0, codeql_1.createStubCodeQL)({
|
||||
databaseRunQueries: async () => { },
|
||||
databaseInterpretResults: async (_db, _queriesRun, sarifFile) => {
|
||||
fs.writeFileSync(sarifFile, JSON.stringify({
|
||||
runs: [
|
||||
// references a rule with the lines-of-code tag, so baseline should be injected
|
||||
{
|
||||
tool: {
|
||||
extensions: [
|
||||
{
|
||||
rules: [
|
||||
{
|
||||
properties: {
|
||||
tags: ["lines-of-code"],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
properties: {
|
||||
metricResults: [
|
||||
{
|
||||
rule: {
|
||||
index: 0,
|
||||
toolComponent: {
|
||||
index: 0,
|
||||
},
|
||||
},
|
||||
value: 123,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{},
|
||||
],
|
||||
}));
|
||||
return "";
|
||||
},
|
||||
databasePrintBaseline: async () => "",
|
||||
});
|
||||
const config = (0, testing_utils_1.createTestConfig)({
|
||||
languages: [language],
|
||||
tempDir: tmpDir,
|
||||
dbLocation: path.resolve(tmpDir, "codeql_databases"),
|
||||
});
|
||||
fs.mkdirSync(util.getCodeQLDatabasePath(config, language), {
|
||||
recursive: true,
|
||||
});
|
||||
const statusReport = await (0, analyze_1.runQueries)(tmpDir, memoryFlag, addSnippetsFlag, threadsFlag, undefined, undefined, codeql, config, (0, logging_1.getRunnerLogger)(true), (0, testing_utils_1.createFeatures)([feature_flags_1.Feature.QaTelemetryEnabled]));
|
||||
t.deepEqual(Object.keys(statusReport).sort(), [
|
||||
"analysis_builds_overlay_base_database",
|
||||
"analysis_is_diff_informed",
|
||||
"analysis_is_overlay",
|
||||
`analyze_builtin_queries_${language}_duration_ms`,
|
||||
"event_reports",
|
||||
`interpret_results_${language}_duration_ms`,
|
||||
]);
|
||||
for (const eventReport of statusReport.event_reports) {
|
||||
t.deepEqual(eventReport.event, "codeql database interpret-results");
|
||||
t.true("properties" in eventReport);
|
||||
t.true("alertCounts" in eventReport.properties);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
function runGetDiffRanges(changes, patch) {
|
||||
sinon
|
||||
.stub(actionsUtil, "getRequiredInput")
|
||||
.withArgs("checkout_path")
|
||||
.returns("/checkout/path");
|
||||
return analyze_1.exportedForTesting.getDiffRanges({
|
||||
filename: "test.txt",
|
||||
changes,
|
||||
patch: patch?.join("\n"),
|
||||
}, (0, logging_1.getRunnerLogger)(true));
|
||||
}
|
||||
(0, ava_1.default)("getDiffRanges: file unchanged", async (t) => {
|
||||
const diffRanges = runGetDiffRanges(0, undefined);
|
||||
t.deepEqual(diffRanges, []);
|
||||
});
|
||||
(0, ava_1.default)("getDiffRanges: file diff too large", async (t) => {
|
||||
const diffRanges = runGetDiffRanges(1000000, undefined);
|
||||
t.deepEqual(diffRanges, [
|
||||
{
|
||||
path: "/checkout/path/test.txt",
|
||||
startLine: 0,
|
||||
endLine: 0,
|
||||
},
|
||||
]);
|
||||
});
|
||||
(0, ava_1.default)("getDiffRanges: diff thunk with single addition range", async (t) => {
|
||||
const diffRanges = runGetDiffRanges(2, [
|
||||
"@@ -30,6 +50,8 @@",
|
||||
" a",
|
||||
" b",
|
||||
" c",
|
||||
"+1",
|
||||
"+2",
|
||||
" d",
|
||||
" e",
|
||||
" f",
|
||||
]);
|
||||
t.deepEqual(diffRanges, [
|
||||
{
|
||||
path: "/checkout/path/test.txt",
|
||||
startLine: 53,
|
||||
endLine: 54,
|
||||
},
|
||||
]);
|
||||
});
|
||||
(0, ava_1.default)("getDiffRanges: diff thunk with single deletion range", async (t) => {
|
||||
const diffRanges = runGetDiffRanges(2, [
|
||||
"@@ -30,8 +50,6 @@",
|
||||
" a",
|
||||
" b",
|
||||
" c",
|
||||
"-1",
|
||||
"-2",
|
||||
" d",
|
||||
" e",
|
||||
" f",
|
||||
]);
|
||||
t.deepEqual(diffRanges, []);
|
||||
});
|
||||
(0, ava_1.default)("getDiffRanges: diff thunk with single update range", async (t) => {
|
||||
const diffRanges = runGetDiffRanges(2, [
|
||||
"@@ -30,7 +50,7 @@",
|
||||
" a",
|
||||
" b",
|
||||
" c",
|
||||
"-1",
|
||||
"+2",
|
||||
" d",
|
||||
" e",
|
||||
" f",
|
||||
]);
|
||||
t.deepEqual(diffRanges, [
|
||||
{
|
||||
path: "/checkout/path/test.txt",
|
||||
startLine: 53,
|
||||
endLine: 53,
|
||||
},
|
||||
]);
|
||||
});
|
||||
(0, ava_1.default)("getDiffRanges: diff thunk with addition ranges", async (t) => {
|
||||
const diffRanges = runGetDiffRanges(2, [
|
||||
"@@ -30,7 +50,9 @@",
|
||||
" a",
|
||||
" b",
|
||||
" c",
|
||||
"+1",
|
||||
" c",
|
||||
"+2",
|
||||
" d",
|
||||
" e",
|
||||
" f",
|
||||
]);
|
||||
t.deepEqual(diffRanges, [
|
||||
{
|
||||
path: "/checkout/path/test.txt",
|
||||
startLine: 53,
|
||||
endLine: 53,
|
||||
},
|
||||
{
|
||||
path: "/checkout/path/test.txt",
|
||||
startLine: 55,
|
||||
endLine: 55,
|
||||
},
|
||||
]);
|
||||
});
|
||||
(0, ava_1.default)("getDiffRanges: diff thunk with mixed ranges", async (t) => {
|
||||
const diffRanges = runGetDiffRanges(2, [
|
||||
"@@ -30,7 +50,7 @@",
|
||||
" a",
|
||||
" b",
|
||||
" c",
|
||||
"-1",
|
||||
" d",
|
||||
"-2",
|
||||
"+3",
|
||||
" e",
|
||||
" f",
|
||||
"+4",
|
||||
"+5",
|
||||
" g",
|
||||
" h",
|
||||
" i",
|
||||
]);
|
||||
t.deepEqual(diffRanges, [
|
||||
{
|
||||
path: "/checkout/path/test.txt",
|
||||
startLine: 54,
|
||||
endLine: 54,
|
||||
},
|
||||
{
|
||||
path: "/checkout/path/test.txt",
|
||||
startLine: 57,
|
||||
endLine: 58,
|
||||
},
|
||||
]);
|
||||
});
|
||||
(0, ava_1.default)("getDiffRanges: multiple diff thunks", async (t) => {
|
||||
const diffRanges = runGetDiffRanges(2, [
|
||||
"@@ -30,6 +50,8 @@",
|
||||
" a",
|
||||
" b",
|
||||
" c",
|
||||
"+1",
|
||||
"+2",
|
||||
" d",
|
||||
" e",
|
||||
" f",
|
||||
"@@ -130,6 +150,8 @@",
|
||||
" a",
|
||||
" b",
|
||||
" c",
|
||||
"+1",
|
||||
"+2",
|
||||
" d",
|
||||
" e",
|
||||
" f",
|
||||
]);
|
||||
t.deepEqual(diffRanges, [
|
||||
{
|
||||
path: "/checkout/path/test.txt",
|
||||
startLine: 53,
|
||||
endLine: 54,
|
||||
},
|
||||
{
|
||||
path: "/checkout/path/test.txt",
|
||||
startLine: 153,
|
||||
endLine: 154,
|
||||
},
|
||||
]);
|
||||
});
|
||||
(0, ava_1.default)("getDiffRanges: no diff context lines", async (t) => {
|
||||
const diffRanges = runGetDiffRanges(2, ["@@ -30 +50,2 @@", "+1", "+2"]);
|
||||
t.deepEqual(diffRanges, [
|
||||
{
|
||||
path: "/checkout/path/test.txt",
|
||||
startLine: 50,
|
||||
endLine: 51,
|
||||
},
|
||||
]);
|
||||
});
|
||||
(0, ava_1.default)("getDiffRanges: malformed thunk header", async (t) => {
|
||||
const diffRanges = runGetDiffRanges(2, ["@@ 30 +50,2 @@", "+1", "+2"]);
|
||||
t.deepEqual(diffRanges, undefined);
|
||||
});
|
||||
(0, ava_1.default)("resolveQuerySuiteAlias", (t) => {
|
||||
// default query suite names should resolve to something language-specific ending in `.qls`.
|
||||
for (const suite of analyze_1.defaultSuites) {
|
||||
const resolved = (0, analyze_1.resolveQuerySuiteAlias)(languages_1.KnownLanguage.go, suite);
|
||||
t.assert(resolved.endsWith(".qls"), "Resolved default suite doesn't end in .qls");
|
||||
t.assert(resolved.indexOf(languages_1.KnownLanguage.go) >= 0, "Resolved default suite doesn't contain language name");
|
||||
}
|
||||
// other inputs should be returned unchanged
|
||||
const names = ["foo", "bar", "codeql/go-queries@1.0"];
|
||||
for (const name of names) {
|
||||
t.deepEqual((0, analyze_1.resolveQuerySuiteAlias)(languages_1.KnownLanguage.go, name), name);
|
||||
}
|
||||
});
|
||||
//# sourceMappingURL=analyze.test.js.map
|
||||
File diff suppressed because one or more lines are too long
220
lib/api-client.js
generated
220
lib/api-client.js
generated
@@ -1,220 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.DisallowedAPIVersionReason = void 0;
|
||||
exports.getApiDetails = getApiDetails;
|
||||
exports.getApiClient = getApiClient;
|
||||
exports.getApiClientWithExternalAuth = getApiClientWithExternalAuth;
|
||||
exports.getGitHubVersionFromApi = getGitHubVersionFromApi;
|
||||
exports.getGitHubVersion = getGitHubVersion;
|
||||
exports.getWorkflowRelativePath = getWorkflowRelativePath;
|
||||
exports.getAnalysisKey = getAnalysisKey;
|
||||
exports.getAutomationID = getAutomationID;
|
||||
exports.computeAutomationID = computeAutomationID;
|
||||
exports.listActionsCaches = listActionsCaches;
|
||||
exports.deleteActionsCache = deleteActionsCache;
|
||||
exports.wrapApiConfigurationError = wrapApiConfigurationError;
|
||||
const core = __importStar(require("@actions/core"));
|
||||
const githubUtils = __importStar(require("@actions/github/lib/utils"));
|
||||
const retry = __importStar(require("@octokit/plugin-retry"));
|
||||
const console_log_level_1 = __importDefault(require("console-log-level"));
|
||||
const actions_util_1 = require("./actions-util");
|
||||
const repository_1 = require("./repository");
|
||||
const util_1 = require("./util");
|
||||
const GITHUB_ENTERPRISE_VERSION_HEADER = "x-github-enterprise-version";
|
||||
var DisallowedAPIVersionReason;
|
||||
(function (DisallowedAPIVersionReason) {
|
||||
DisallowedAPIVersionReason[DisallowedAPIVersionReason["ACTION_TOO_OLD"] = 0] = "ACTION_TOO_OLD";
|
||||
DisallowedAPIVersionReason[DisallowedAPIVersionReason["ACTION_TOO_NEW"] = 1] = "ACTION_TOO_NEW";
|
||||
})(DisallowedAPIVersionReason || (exports.DisallowedAPIVersionReason = DisallowedAPIVersionReason = {}));
|
||||
function createApiClientWithDetails(apiDetails, { allowExternal = false } = {}) {
|
||||
const auth = (allowExternal && apiDetails.externalRepoAuth) || apiDetails.auth;
|
||||
const retryingOctokit = githubUtils.GitHub.plugin(retry.retry);
|
||||
return new retryingOctokit(githubUtils.getOctokitOptions(auth, {
|
||||
baseUrl: apiDetails.apiURL,
|
||||
userAgent: `CodeQL-Action/${(0, actions_util_1.getActionVersion)()}`,
|
||||
log: (0, console_log_level_1.default)({ level: "debug" }),
|
||||
}));
|
||||
}
|
||||
function getApiDetails() {
|
||||
return {
|
||||
auth: (0, actions_util_1.getRequiredInput)("token"),
|
||||
url: (0, util_1.getRequiredEnvParam)("GITHUB_SERVER_URL"),
|
||||
apiURL: (0, util_1.getRequiredEnvParam)("GITHUB_API_URL"),
|
||||
};
|
||||
}
|
||||
function getApiClient() {
|
||||
return createApiClientWithDetails(getApiDetails());
|
||||
}
|
||||
function getApiClientWithExternalAuth(apiDetails) {
|
||||
return createApiClientWithDetails(apiDetails, { allowExternal: true });
|
||||
}
|
||||
let cachedGitHubVersion = undefined;
|
||||
async function getGitHubVersionFromApi(apiClient, apiDetails) {
|
||||
// We can avoid making an API request in the standard dotcom case
|
||||
if ((0, util_1.parseGitHubUrl)(apiDetails.url) === util_1.GITHUB_DOTCOM_URL) {
|
||||
return { type: util_1.GitHubVariant.DOTCOM };
|
||||
}
|
||||
// Doesn't strictly have to be the meta endpoint as we're only
|
||||
// using the response headers which are available on every request.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
const response = await apiClient.rest.meta.get();
|
||||
// This happens on dotcom, although we expect to have already returned in that
|
||||
// case. This can also serve as a fallback in cases we haven't foreseen.
|
||||
if (response.headers[GITHUB_ENTERPRISE_VERSION_HEADER] === undefined) {
|
||||
return { type: util_1.GitHubVariant.DOTCOM };
|
||||
}
|
||||
if (response.headers[GITHUB_ENTERPRISE_VERSION_HEADER] === "ghe.com") {
|
||||
return { type: util_1.GitHubVariant.GHE_DOTCOM };
|
||||
}
|
||||
const version = response.headers[GITHUB_ENTERPRISE_VERSION_HEADER];
|
||||
return { type: util_1.GitHubVariant.GHES, version };
|
||||
}
|
||||
/**
|
||||
* Report the GitHub server version. This is a wrapper around
|
||||
* util.getGitHubVersion() that automatically supplies GitHub API details using
|
||||
* GitHub Action inputs.
|
||||
*
|
||||
* @returns GitHub version
|
||||
*/
|
||||
async function getGitHubVersion() {
|
||||
if (cachedGitHubVersion === undefined) {
|
||||
cachedGitHubVersion = await getGitHubVersionFromApi(getApiClient(), getApiDetails());
|
||||
}
|
||||
return cachedGitHubVersion;
|
||||
}
|
||||
/**
|
||||
* Get the path of the currently executing workflow relative to the repository root.
|
||||
*/
|
||||
async function getWorkflowRelativePath() {
|
||||
const repo_nwo = (0, repository_1.getRepositoryNwo)();
|
||||
const run_id = Number((0, util_1.getRequiredEnvParam)("GITHUB_RUN_ID"));
|
||||
const apiClient = getApiClient();
|
||||
const runsResponse = await apiClient.request("GET /repos/:owner/:repo/actions/runs/:run_id?exclude_pull_requests=true", {
|
||||
owner: repo_nwo.owner,
|
||||
repo: repo_nwo.repo,
|
||||
run_id,
|
||||
});
|
||||
const workflowUrl = runsResponse.data.workflow_url;
|
||||
const requiredWorkflowRegex = /\/repos\/[^/]+\/[^/]+\/actions\/required_workflows\/[^/]+/;
|
||||
if (!workflowUrl || requiredWorkflowRegex.test(workflowUrl)) {
|
||||
// For required workflows, the workflowUrl is invalid so we cannot fetch more informations
|
||||
// about the workflow.
|
||||
// However, the path is available in the original response.
|
||||
return runsResponse.data.path;
|
||||
}
|
||||
const workflowResponse = await apiClient.request(`GET ${workflowUrl}`);
|
||||
return workflowResponse.data.path;
|
||||
}
|
||||
/**
|
||||
* Get the analysis key parameter for the current job.
|
||||
*
|
||||
* This will combine the workflow path and current job name.
|
||||
* Computing this the first time requires making requests to
|
||||
* the GitHub API, but after that the result will be cached.
|
||||
*/
|
||||
async function getAnalysisKey() {
|
||||
const analysisKeyEnvVar = "CODEQL_ACTION_ANALYSIS_KEY";
|
||||
let analysisKey = process.env[analysisKeyEnvVar];
|
||||
if (analysisKey !== undefined) {
|
||||
return analysisKey;
|
||||
}
|
||||
const workflowPath = await getWorkflowRelativePath();
|
||||
const jobName = (0, util_1.getRequiredEnvParam)("GITHUB_JOB");
|
||||
analysisKey = `${workflowPath}:${jobName}`;
|
||||
core.exportVariable(analysisKeyEnvVar, analysisKey);
|
||||
return analysisKey;
|
||||
}
|
||||
async function getAutomationID() {
|
||||
const analysis_key = await getAnalysisKey();
|
||||
const environment = (0, actions_util_1.getRequiredInput)("matrix");
|
||||
return computeAutomationID(analysis_key, environment);
|
||||
}
|
||||
function computeAutomationID(analysis_key, environment) {
|
||||
let automationID = `${analysis_key}/`;
|
||||
const matrix = (0, util_1.parseMatrixInput)(environment);
|
||||
if (matrix !== undefined) {
|
||||
// the id has to be deterministic so we sort the fields
|
||||
for (const entry of Object.entries(matrix).sort()) {
|
||||
if (typeof entry[1] === "string") {
|
||||
automationID += `${entry[0]}:${entry[1]}/`;
|
||||
}
|
||||
else {
|
||||
// In code scanning we just handle the string values,
|
||||
// the rest get converted to the empty string
|
||||
automationID += `${entry[0]}:/`;
|
||||
}
|
||||
}
|
||||
}
|
||||
return automationID;
|
||||
}
|
||||
/** List all Actions cache entries matching the provided key and ref. */
|
||||
async function listActionsCaches(key, ref) {
|
||||
const repositoryNwo = (0, repository_1.getRepositoryNwo)();
|
||||
return await getApiClient().paginate("GET /repos/{owner}/{repo}/actions/caches", {
|
||||
owner: repositoryNwo.owner,
|
||||
repo: repositoryNwo.repo,
|
||||
key,
|
||||
ref,
|
||||
});
|
||||
}
|
||||
/** Delete an Actions cache item by its ID. */
|
||||
async function deleteActionsCache(id) {
|
||||
const repositoryNwo = (0, repository_1.getRepositoryNwo)();
|
||||
await getApiClient().rest.actions.deleteActionsCacheById({
|
||||
owner: repositoryNwo.owner,
|
||||
repo: repositoryNwo.repo,
|
||||
cache_id: id,
|
||||
});
|
||||
}
|
||||
function wrapApiConfigurationError(e) {
|
||||
if ((0, util_1.isHTTPError)(e)) {
|
||||
if (e.message.includes("API rate limit exceeded for installation") ||
|
||||
e.message.includes("commit not found") ||
|
||||
e.message.includes("Resource not accessible by integration") ||
|
||||
/ref .* not found in this repository/.test(e.message)) {
|
||||
return new util_1.ConfigurationError(e.message);
|
||||
}
|
||||
else if (e.message.includes("Bad credentials") ||
|
||||
e.message.includes("Not Found")) {
|
||||
return new util_1.ConfigurationError("Please check that your token is valid and has the required permissions: contents: read, security-events: write");
|
||||
}
|
||||
}
|
||||
return e;
|
||||
}
|
||||
//# sourceMappingURL=api-client.js.map
|
||||
File diff suppressed because one or more lines are too long
159
lib/api-client.test.js
generated
159
lib/api-client.test.js
generated
@@ -1,159 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const github = __importStar(require("@actions/github"));
|
||||
const githubUtils = __importStar(require("@actions/github/lib/utils"));
|
||||
const ava_1 = __importDefault(require("ava"));
|
||||
const sinon = __importStar(require("sinon"));
|
||||
const actionsUtil = __importStar(require("./actions-util"));
|
||||
const api = __importStar(require("./api-client"));
|
||||
const testing_utils_1 = require("./testing-utils");
|
||||
const util = __importStar(require("./util"));
|
||||
(0, testing_utils_1.setupTests)(ava_1.default);
|
||||
ava_1.default.beforeEach(() => {
|
||||
util.initializeEnvironment(actionsUtil.getActionVersion());
|
||||
});
|
||||
(0, ava_1.default)("getApiClient", async (t) => {
|
||||
const pluginStub = sinon.stub(githubUtils.GitHub, "plugin");
|
||||
const githubStub = sinon.stub();
|
||||
pluginStub.returns(githubStub);
|
||||
sinon.stub(actionsUtil, "getRequiredInput").withArgs("token").returns("xyz");
|
||||
const requiredEnvParamStub = sinon.stub(util, "getRequiredEnvParam");
|
||||
requiredEnvParamStub
|
||||
.withArgs("GITHUB_SERVER_URL")
|
||||
.returns("http://github.localhost");
|
||||
requiredEnvParamStub
|
||||
.withArgs("GITHUB_API_URL")
|
||||
.returns("http://api.github.localhost");
|
||||
api.getApiClient();
|
||||
t.assert(githubStub.calledOnceWithExactly({
|
||||
auth: "token xyz",
|
||||
baseUrl: "http://api.github.localhost",
|
||||
log: sinon.match.any,
|
||||
userAgent: `CodeQL-Action/${actionsUtil.getActionVersion()}`,
|
||||
}));
|
||||
});
|
||||
function mockGetMetaVersionHeader(versionHeader) {
|
||||
// Passing an auth token is required, so we just use a dummy value
|
||||
const client = github.getOctokit("123");
|
||||
const response = {
|
||||
headers: {
|
||||
"x-github-enterprise-version": versionHeader,
|
||||
},
|
||||
};
|
||||
const spyGetContents = sinon
|
||||
.stub(client.rest.meta, "get")
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
.resolves(response);
|
||||
sinon.stub(api, "getApiClient").value(() => client);
|
||||
return spyGetContents;
|
||||
}
|
||||
(0, ava_1.default)("getGitHubVersion for Dotcom", async (t) => {
|
||||
const apiDetails = {
|
||||
auth: "",
|
||||
url: "https://github.com",
|
||||
apiURL: "",
|
||||
};
|
||||
sinon.stub(api, "getApiDetails").returns(apiDetails);
|
||||
const v = await api.getGitHubVersionFromApi(github.getOctokit("123"), apiDetails);
|
||||
t.deepEqual(util.GitHubVariant.DOTCOM, v.type);
|
||||
});
|
||||
(0, ava_1.default)("getGitHubVersion for GHES", async (t) => {
|
||||
mockGetMetaVersionHeader("2.0");
|
||||
const v2 = await api.getGitHubVersionFromApi(api.getApiClient(), {
|
||||
auth: "",
|
||||
url: "https://ghe.example.com",
|
||||
apiURL: undefined,
|
||||
});
|
||||
t.deepEqual({ type: util.GitHubVariant.GHES, version: "2.0" }, v2);
|
||||
});
|
||||
(0, ava_1.default)("getGitHubVersion for different domain", async (t) => {
|
||||
mockGetMetaVersionHeader(undefined);
|
||||
const v3 = await api.getGitHubVersionFromApi(api.getApiClient(), {
|
||||
auth: "",
|
||||
url: "https://ghe.example.com",
|
||||
apiURL: undefined,
|
||||
});
|
||||
t.deepEqual({ type: util.GitHubVariant.DOTCOM }, v3);
|
||||
});
|
||||
(0, ava_1.default)("getGitHubVersion for GHE_DOTCOM", async (t) => {
|
||||
mockGetMetaVersionHeader("ghe.com");
|
||||
const gheDotcom = await api.getGitHubVersionFromApi(api.getApiClient(), {
|
||||
auth: "",
|
||||
url: "https://foo.ghe.com",
|
||||
apiURL: undefined,
|
||||
});
|
||||
t.deepEqual({ type: util.GitHubVariant.GHE_DOTCOM }, gheDotcom);
|
||||
});
|
||||
(0, ava_1.default)("wrapApiConfigurationError correctly wraps specific configuration errors", (t) => {
|
||||
// We don't reclassify arbitrary errors
|
||||
const arbitraryError = new Error("arbitrary error");
|
||||
let res = api.wrapApiConfigurationError(arbitraryError);
|
||||
t.is(res, arbitraryError);
|
||||
// Same goes for arbitrary errors
|
||||
const configError = new util.ConfigurationError("arbitrary error");
|
||||
res = api.wrapApiConfigurationError(configError);
|
||||
t.is(res, configError);
|
||||
// If an HTTP error doesn't contain a specific error message, we don't
|
||||
// wrap is an an API error.
|
||||
const httpError = new util.HTTPError("arbitrary HTTP error", 456);
|
||||
res = api.wrapApiConfigurationError(httpError);
|
||||
t.is(res, httpError);
|
||||
// For other HTTP errors, we wrap them as Configuration errors if they contain
|
||||
// specific error messages.
|
||||
const httpNotFoundError = new util.HTTPError("commit not found", 404);
|
||||
res = api.wrapApiConfigurationError(httpNotFoundError);
|
||||
t.deepEqual(res, new util.ConfigurationError("commit not found"));
|
||||
const refNotFoundError = new util.HTTPError("ref 'refs/heads/jitsi' not found in this repository - https://docs.github.com/rest", 404);
|
||||
res = api.wrapApiConfigurationError(refNotFoundError);
|
||||
t.deepEqual(res, new util.ConfigurationError("ref 'refs/heads/jitsi' not found in this repository - https://docs.github.com/rest"));
|
||||
const apiRateLimitError = new util.HTTPError("API rate limit exceeded for installation", 403);
|
||||
res = api.wrapApiConfigurationError(apiRateLimitError);
|
||||
t.deepEqual(res, new util.ConfigurationError("API rate limit exceeded for installation"));
|
||||
const tokenSuggestionMessage = "Please check that your token is valid and has the required permissions: contents: read, security-events: write";
|
||||
const badCredentialsError = new util.HTTPError("Bad credentials", 401);
|
||||
res = api.wrapApiConfigurationError(badCredentialsError);
|
||||
t.deepEqual(res, new util.ConfigurationError(tokenSuggestionMessage));
|
||||
const notFoundError = new util.HTTPError("Not Found", 404);
|
||||
res = api.wrapApiConfigurationError(notFoundError);
|
||||
t.deepEqual(res, new util.ConfigurationError(tokenSuggestionMessage));
|
||||
const resourceNotAccessibleError = new util.HTTPError("Resource not accessible by integration", 403);
|
||||
res = api.wrapApiConfigurationError(resourceNotAccessibleError);
|
||||
t.deepEqual(res, new util.ConfigurationError("Resource not accessible by integration"));
|
||||
});
|
||||
//# sourceMappingURL=api-client.test.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{ "maximumVersion": "3.18", "minimumVersion": "3.14" }
|
||||
89107
lib/autobuild-action.js
generated
89107
lib/autobuild-action.js
generated
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"autobuild-action.js","sourceRoot":"","sources":["../src/autobuild-action.ts"],"names":[],"mappings":"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"}
|
||||
162
lib/autobuild.js
generated
162
lib/autobuild.js
generated
@@ -1,162 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.determineAutobuildLanguages = determineAutobuildLanguages;
|
||||
exports.setupCppAutobuild = setupCppAutobuild;
|
||||
exports.runAutobuild = runAutobuild;
|
||||
const core = __importStar(require("@actions/core"));
|
||||
const actions_util_1 = require("./actions-util");
|
||||
const api_client_1 = require("./api-client");
|
||||
const codeql_1 = require("./codeql");
|
||||
const doc_url_1 = require("./doc-url");
|
||||
const environment_1 = require("./environment");
|
||||
const feature_flags_1 = require("./feature-flags");
|
||||
const languages_1 = require("./languages");
|
||||
const repository_1 = require("./repository");
|
||||
const util_1 = require("./util");
|
||||
async function determineAutobuildLanguages(codeql, config, logger) {
|
||||
if (config.buildMode === util_1.BuildMode.None ||
|
||||
config.buildMode === util_1.BuildMode.Manual) {
|
||||
logger.info(`Using build mode "${config.buildMode}", nothing to autobuild. ` +
|
||||
`See ${doc_url_1.DocUrl.CODEQL_BUILD_MODES} for more information.`);
|
||||
return undefined;
|
||||
}
|
||||
// Attempt to find a language to autobuild
|
||||
// We want pick the dominant language in the repo from the ones we're able to build
|
||||
// The languages are sorted in order specified by user or by lines of code if we got
|
||||
// them from the GitHub API, so try to build the first language on the list.
|
||||
const autobuildLanguages = await (0, util_1.asyncFilter)(config.languages, async (language) => await codeql.isTracedLanguage(language));
|
||||
if (autobuildLanguages.length === 0) {
|
||||
logger.info("None of the languages in this project require extra build steps");
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Additionally autobuild Go in the autobuild Action to ensure backwards
|
||||
* compatibility for users performing a multi-language build within a single
|
||||
* job.
|
||||
*
|
||||
* For example, consider a user with the following workflow file:
|
||||
*
|
||||
* ```yml
|
||||
* - uses: github/codeql-action/init@v3
|
||||
* with:
|
||||
* languages: go, java
|
||||
* - uses: github/codeql-action/autobuild@v3
|
||||
* - uses: github/codeql-action/analyze@v3
|
||||
* ```
|
||||
*
|
||||
* - With Go extraction disabled, we will run the Java autobuilder in the
|
||||
* autobuild Action, ensuring we extract both Java and Go code.
|
||||
* - With Go extraction enabled, taking the previous behavior we'd run the Go
|
||||
* autobuilder, since Go is first on the list of languages. We wouldn't run
|
||||
* the Java autobuilder at all and so we'd only extract Go code.
|
||||
*
|
||||
* We therefore introduce a special case here such that we'll autobuild Go
|
||||
* in addition to the primary non-Go traced language in the autobuild Action.
|
||||
*
|
||||
* This special case behavior should be removed as part of the next major
|
||||
* version of the CodeQL Action.
|
||||
*/
|
||||
const autobuildLanguagesWithoutGo = autobuildLanguages.filter((l) => l !== languages_1.KnownLanguage.go);
|
||||
const languages = [];
|
||||
// First run the autobuilder for the first non-Go traced language, if one
|
||||
// exists.
|
||||
if (autobuildLanguagesWithoutGo[0] !== undefined) {
|
||||
languages.push(autobuildLanguagesWithoutGo[0]);
|
||||
}
|
||||
// If Go is requested, run the Go autobuilder last to ensure it doesn't
|
||||
// interfere with the other autobuilder.
|
||||
if (autobuildLanguages.length !== autobuildLanguagesWithoutGo.length) {
|
||||
languages.push(languages_1.KnownLanguage.go);
|
||||
}
|
||||
logger.debug(`Will autobuild ${languages.join(" and ")}.`);
|
||||
// In general the autobuilders for other traced languages may conflict with
|
||||
// each other. Therefore if a user has requested more than one non-Go traced
|
||||
// language, we ask for manual build steps.
|
||||
// Matrixing the build would also work, but that would change the SARIF
|
||||
// categories, potentially leading to a "stale tips" situation where alerts
|
||||
// that should be fixed remain on a repo since they are linked to SARIF
|
||||
// categories that are no longer updated.
|
||||
if (autobuildLanguagesWithoutGo.length > 1) {
|
||||
logger.warning(`We will only automatically build ${languages.join(" and ")} code. If you wish to scan ${autobuildLanguagesWithoutGo
|
||||
.slice(1)
|
||||
.join(" and ")}, you must replace the autobuild step of your workflow with custom build steps. ` +
|
||||
`See ${doc_url_1.DocUrl.SPECIFY_BUILD_STEPS_MANUALLY} for more information.`);
|
||||
}
|
||||
return languages;
|
||||
}
|
||||
async function setupCppAutobuild(codeql, logger) {
|
||||
const envVar = feature_flags_1.featureConfig[feature_flags_1.Feature.CppDependencyInstallation].envVar;
|
||||
const featureName = "C++ automatic installation of dependencies";
|
||||
const gitHubVersion = await (0, api_client_1.getGitHubVersion)();
|
||||
const repositoryNwo = (0, repository_1.getRepositoryNwo)();
|
||||
const features = new feature_flags_1.Features(gitHubVersion, repositoryNwo, (0, actions_util_1.getTemporaryDirectory)(), logger);
|
||||
if (await features.getValue(feature_flags_1.Feature.CppDependencyInstallation, codeql)) {
|
||||
// disable autoinstall on self-hosted runners unless explicitly requested
|
||||
if (process.env["RUNNER_ENVIRONMENT"] === "self-hosted" &&
|
||||
process.env[envVar] !== "true") {
|
||||
logger.info(`Disabling ${featureName} as we are on a self-hosted runner.${(0, actions_util_1.getWorkflowEventName)() !== "dynamic"
|
||||
? ` To override this, set the ${envVar} environment variable to 'true' in your workflow. See ${doc_url_1.DocUrl.DEFINE_ENV_VARIABLES} for more information.`
|
||||
: ""}`);
|
||||
core.exportVariable(envVar, "false");
|
||||
}
|
||||
else {
|
||||
logger.info(`Enabling ${featureName}. This can be disabled by setting the ${envVar} environment variable to 'false'. See ${doc_url_1.DocUrl.DEFINE_ENV_VARIABLES} for more information.`);
|
||||
core.exportVariable(envVar, "true");
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.info(`Disabling ${featureName}.`);
|
||||
core.exportVariable(envVar, "false");
|
||||
}
|
||||
}
|
||||
async function runAutobuild(config, language, logger) {
|
||||
logger.startGroup(`Attempting to automatically build ${language} code`);
|
||||
const codeQL = await (0, codeql_1.getCodeQL)(config.codeQLCmd);
|
||||
if (language === languages_1.KnownLanguage.cpp) {
|
||||
await setupCppAutobuild(codeQL, logger);
|
||||
}
|
||||
if (config.buildMode) {
|
||||
await codeQL.extractUsingBuildMode(config, language);
|
||||
}
|
||||
else {
|
||||
await codeQL.runAutobuild(config, language);
|
||||
}
|
||||
if (language === languages_1.KnownLanguage.go) {
|
||||
core.exportVariable(environment_1.EnvVar.DID_AUTOBUILD_GOLANG, "true");
|
||||
}
|
||||
logger.endGroup();
|
||||
}
|
||||
//# sourceMappingURL=autobuild.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"autobuild.js","sourceRoot":"","sources":["../src/autobuild.ts"],"names":[],"mappings":"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"}
|
||||
117
lib/caching-utils.js
generated
117
lib/caching-utils.js
generated
@@ -1,117 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.CachingKind = void 0;
|
||||
exports.getTotalCacheSize = getTotalCacheSize;
|
||||
exports.shouldStoreCache = shouldStoreCache;
|
||||
exports.shouldRestoreCache = shouldRestoreCache;
|
||||
exports.getCachingKind = getCachingKind;
|
||||
exports.getDependencyCachingEnabled = getDependencyCachingEnabled;
|
||||
const core = __importStar(require("@actions/core"));
|
||||
const actions_util_1 = require("./actions-util");
|
||||
const environment_1 = require("./environment");
|
||||
const util_1 = require("./util");
|
||||
/**
|
||||
* Returns the total size of all the specified paths.
|
||||
* @param paths The paths for which to calculate the total size.
|
||||
* @param logger A logger to record some informational messages to.
|
||||
* @param quiet A value indicating whether to suppress logging warnings (default: false).
|
||||
* @returns The total size of all specified paths.
|
||||
*/
|
||||
async function getTotalCacheSize(paths, logger, quiet = false) {
|
||||
const sizes = await Promise.all(paths.map((cacheDir) => (0, util_1.tryGetFolderBytes)(cacheDir, logger, quiet)));
|
||||
return sizes.map((a) => a || 0).reduce((a, b) => a + b, 0);
|
||||
}
|
||||
/* Enumerates caching modes. */
|
||||
var CachingKind;
|
||||
(function (CachingKind) {
|
||||
/** Do not restore or store any caches. */
|
||||
CachingKind["None"] = "none";
|
||||
/** Store caches, but do not restore any existing ones. */
|
||||
CachingKind["Store"] = "store";
|
||||
/** Restore existing caches, but do not store any new ones. */
|
||||
CachingKind["Restore"] = "restore";
|
||||
/** Restore existing caches, and store new ones. */
|
||||
CachingKind["Full"] = "full";
|
||||
})(CachingKind || (exports.CachingKind = CachingKind = {}));
|
||||
/** Returns a value indicating whether new caches should be stored, based on `kind`. */
|
||||
function shouldStoreCache(kind) {
|
||||
return kind === CachingKind.Full || kind === CachingKind.Store;
|
||||
}
|
||||
/** Returns a value indicating whether existing caches should be restored, based on `kind`. */
|
||||
function shouldRestoreCache(kind) {
|
||||
return kind === CachingKind.Full || kind === CachingKind.Restore;
|
||||
}
|
||||
/**
|
||||
* Parses the `upload` input into an `UploadKind`.
|
||||
*/
|
||||
function getCachingKind(input) {
|
||||
switch (input) {
|
||||
case undefined:
|
||||
case "none":
|
||||
case "off":
|
||||
case "false":
|
||||
return CachingKind.None;
|
||||
case "full":
|
||||
case "on":
|
||||
case "true":
|
||||
return CachingKind.Full;
|
||||
case "store":
|
||||
return CachingKind.Store;
|
||||
case "restore":
|
||||
return CachingKind.Restore;
|
||||
default:
|
||||
core.warning(`Unrecognized 'dependency-caching' input: ${input}. Defaulting to 'none'.`);
|
||||
return CachingKind.None;
|
||||
}
|
||||
}
|
||||
/** Determines whether dependency caching is enabled. */
|
||||
function getDependencyCachingEnabled() {
|
||||
// If the workflow specified something always respect that
|
||||
const dependencyCaching = (0, actions_util_1.getOptionalInput)("dependency-caching") ||
|
||||
process.env[environment_1.EnvVar.DEPENDENCY_CACHING];
|
||||
if (dependencyCaching !== undefined)
|
||||
return getCachingKind(dependencyCaching);
|
||||
// On self-hosted runners which may have dependencies installed centrally, disable caching by default
|
||||
if (!(0, util_1.isHostedRunner)())
|
||||
return CachingKind.None;
|
||||
// Disable in advanced workflows by default.
|
||||
if (!(0, actions_util_1.isDefaultSetup)())
|
||||
return CachingKind.None;
|
||||
// On hosted runners, disable dependency caching by default.
|
||||
// TODO: Review later whether we can enable this by default.
|
||||
return CachingKind.None;
|
||||
}
|
||||
//# sourceMappingURL=caching-utils.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"caching-utils.js","sourceRoot":"","sources":["../src/caching-utils.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAcA,8CASC;AAeD,4CAEC;AAGD,gDAEC;AAKD,wCAqBC;AAGD,kEAgBC;AA1FD,oDAAsC;AAEtC,iDAAkE;AAClE,+CAAuC;AAEvC,iCAA2D;AAE3D;;;;;;GAMG;AACI,KAAK,UAAU,iBAAiB,CACrC,KAAe,EACf,MAAc,EACd,QAAiB,KAAK;IAEtB,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,GAAG,CAC7B,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAA,wBAAiB,EAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CACpE,CAAC;IACF,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED,+BAA+B;AAC/B,IAAY,WASX;AATD,WAAY,WAAW;IACrB,0CAA0C;IAC1C,4BAAa,CAAA;IACb,0DAA0D;IAC1D,8BAAe,CAAA;IACf,8DAA8D;IAC9D,kCAAmB,CAAA;IACnB,mDAAmD;IACnD,4BAAa,CAAA;AACf,CAAC,EATW,WAAW,2BAAX,WAAW,QAStB;AAED,uFAAuF;AACvF,SAAgB,gBAAgB,CAAC,IAAiB;IAChD,OAAO,IAAI,KAAK,WAAW,CAAC,IAAI,IAAI,IAAI,KAAK,WAAW,CAAC,KAAK,CAAC;AACjE,CAAC;AAED,8FAA8F;AAC9F,SAAgB,kBAAkB,CAAC,IAAiB;IAClD,OAAO,IAAI,KAAK,WAAW,CAAC,IAAI,IAAI,IAAI,KAAK,WAAW,CAAC,OAAO,CAAC;AACnE,CAAC;AAED;;GAEG;AACH,SAAgB,cAAc,CAAC,KAAyB;IACtD,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,SAAS,CAAC;QACf,KAAK,MAAM,CAAC;QACZ,KAAK,KAAK,CAAC;QACX,KAAK,OAAO;YACV,OAAO,WAAW,CAAC,IAAI,CAAC;QAC1B,KAAK,MAAM,CAAC;QACZ,KAAK,IAAI,CAAC;QACV,KAAK,MAAM;YACT,OAAO,WAAW,CAAC,IAAI,CAAC;QAC1B,KAAK,OAAO;YACV,OAAO,WAAW,CAAC,KAAK,CAAC;QAC3B,KAAK,SAAS;YACZ,OAAO,WAAW,CAAC,OAAO,CAAC;QAC7B;YACE,IAAI,CAAC,OAAO,CACV,4CAA4C,KAAK,yBAAyB,CAC3E,CAAC;YACF,OAAO,WAAW,CAAC,IAAI,CAAC;IAC5B,CAAC;AACH,CAAC;AAED,wDAAwD;AACxD,SAAgB,2BAA2B;IACzC,0DAA0D;IAC1D,MAAM,iBAAiB,GACrB,IAAA,+BAAgB,EAAC,oBAAoB,CAAC;QACtC,OAAO,CAAC,GAAG,CAAC,oBAAM,CAAC,kBAAkB,CAAC,CAAC;IACzC,IAAI,iBAAiB,KAAK,SAAS;QAAE,OAAO,cAAc,CAAC,iBAAiB,CAAC,CAAC;IAE9E,qGAAqG;IACrG,IAAI,CAAC,IAAA,qBAAc,GAAE;QAAE,OAAO,WAAW,CAAC,IAAI,CAAC;IAE/C,4CAA4C;IAC5C,IAAI,CAAC,IAAA,6BAAc,GAAE;QAAE,OAAO,WAAW,CAAC,IAAI,CAAC;IAE/C,4DAA4D;IAC5D,4DAA4D;IAC5D,OAAO,WAAW,CAAC,IAAI,CAAC;AAC1B,CAAC"}
|
||||
314
lib/cli-errors.js
generated
314
lib/cli-errors.js
generated
@@ -1,314 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.cliErrorsConfig = exports.CliConfigErrorCategory = exports.CliError = void 0;
|
||||
exports.wrapCliConfigurationError = wrapCliConfigurationError;
|
||||
const actions_util_1 = require("./actions-util");
|
||||
const doc_url_1 = require("./doc-url");
|
||||
const util_1 = require("./util");
|
||||
const SUPPORTED_PLATFORMS = [
|
||||
["linux", "x64"],
|
||||
["win32", "x64"],
|
||||
["darwin", "x64"],
|
||||
["darwin", "arm64"],
|
||||
];
|
||||
/**
|
||||
* An error from a CodeQL CLI invocation, with associated exit code, stderr, etc.
|
||||
*/
|
||||
class CliError extends Error {
|
||||
constructor({ cmd, args, exitCode, stderr }) {
|
||||
const prettyCommand = (0, actions_util_1.prettyPrintInvocation)(cmd, args);
|
||||
const fatalErrors = extractFatalErrors(stderr);
|
||||
const autobuildErrors = extractAutobuildErrors(stderr);
|
||||
let message;
|
||||
if (fatalErrors) {
|
||||
message =
|
||||
`Encountered a fatal error while running "${prettyCommand}". ` +
|
||||
`Exit code was ${exitCode} and error was: ${(0, actions_util_1.ensureEndsInPeriod)(fatalErrors.trim())} See the logs for more details.`;
|
||||
}
|
||||
else if (autobuildErrors) {
|
||||
message =
|
||||
"We were unable to automatically build your code. Please provide manual build steps. " +
|
||||
`See ${doc_url_1.DocUrl.AUTOMATIC_BUILD_FAILED} for more information. ` +
|
||||
`Encountered the following error: ${autobuildErrors}`;
|
||||
}
|
||||
else {
|
||||
const lastLine = (0, actions_util_1.ensureEndsInPeriod)(stderr.trim().split("\n").pop()?.trim() || "n/a");
|
||||
message =
|
||||
`Encountered a fatal error while running "${prettyCommand}". ` +
|
||||
`Exit code was ${exitCode} and last log line was: ${lastLine} See the logs for more details.`;
|
||||
}
|
||||
super(message);
|
||||
this.exitCode = exitCode;
|
||||
this.stderr = stderr;
|
||||
}
|
||||
}
|
||||
exports.CliError = CliError;
|
||||
/**
|
||||
* Provide a better error message from the stderr of a CLI invocation that failed with a fatal
|
||||
* error.
|
||||
*
|
||||
* - If the CLI invocation failed with a fatal error, this returns that fatal error, followed by
|
||||
* any fatal errors that occurred in plumbing commands.
|
||||
* - If the CLI invocation did not fail with a fatal error, this returns `undefined`.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```
|
||||
* Running TRAP import for CodeQL database at /home/runner/work/_temp/codeql_databases/javascript...
|
||||
* A fatal error occurred: Evaluator heap must be at least 384.00 MiB
|
||||
* A fatal error occurred: Dataset import for
|
||||
* /home/runner/work/_temp/codeql_databases/javascript/db-javascript failed with code 2
|
||||
* ```
|
||||
*
|
||||
* becomes
|
||||
*
|
||||
* ```
|
||||
* Encountered a fatal error while running "codeql-for-testing database finalize --finalize-dataset
|
||||
* --threads=2 --ram=2048 db". Exit code was 32 and error was: A fatal error occurred: Dataset
|
||||
* import for /home/runner/work/_temp/codeql_databases/javascript/db-javascript failed with code 2.
|
||||
* Context: A fatal error occurred: Evaluator heap must be at least 384.00 MiB.
|
||||
* ```
|
||||
*
|
||||
* Where possible, this tries to summarize the error into a single line, as this displays better in
|
||||
* the Actions UI.
|
||||
*/
|
||||
function extractFatalErrors(error) {
|
||||
const fatalErrorRegex = /.*fatal (internal )?error occurr?ed(. Details)?:/gi;
|
||||
let fatalErrors = [];
|
||||
let lastFatalErrorIndex;
|
||||
let match;
|
||||
while ((match = fatalErrorRegex.exec(error)) !== null) {
|
||||
if (lastFatalErrorIndex !== undefined) {
|
||||
fatalErrors.push(error.slice(lastFatalErrorIndex, match.index).trim());
|
||||
}
|
||||
lastFatalErrorIndex = match.index;
|
||||
}
|
||||
if (lastFatalErrorIndex !== undefined) {
|
||||
const lastError = error.slice(lastFatalErrorIndex).trim();
|
||||
if (fatalErrors.length === 0) {
|
||||
// No other errors
|
||||
return lastError;
|
||||
}
|
||||
const isOneLiner = !fatalErrors.some((e) => e.includes("\n"));
|
||||
if (isOneLiner) {
|
||||
fatalErrors = fatalErrors.map(actions_util_1.ensureEndsInPeriod);
|
||||
}
|
||||
return [
|
||||
(0, actions_util_1.ensureEndsInPeriod)(lastError),
|
||||
"Context:",
|
||||
...fatalErrors.reverse(),
|
||||
].join(isOneLiner ? " " : "\n");
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
function extractAutobuildErrors(error) {
|
||||
const pattern = /.*\[autobuild\] \[ERROR\] (.*)/gi;
|
||||
let errorLines = [...error.matchAll(pattern)].map((match) => match[1]);
|
||||
// Truncate if there are more than 10 matching lines.
|
||||
if (errorLines.length > 10) {
|
||||
errorLines = errorLines.slice(0, 10);
|
||||
errorLines.push("(truncated)");
|
||||
}
|
||||
return errorLines.join("\n") || undefined;
|
||||
}
|
||||
/** Error messages from the CLI that we consider configuration errors and handle specially. */
|
||||
var CliConfigErrorCategory;
|
||||
(function (CliConfigErrorCategory) {
|
||||
CliConfigErrorCategory["AutobuildError"] = "AutobuildError";
|
||||
CliConfigErrorCategory["CouldNotCreateTempDir"] = "CouldNotCreateTempDir";
|
||||
CliConfigErrorCategory["ExternalRepositoryCloneFailed"] = "ExternalRepositoryCloneFailed";
|
||||
CliConfigErrorCategory["GradleBuildFailed"] = "GradleBuildFailed";
|
||||
CliConfigErrorCategory["IncompatibleWithActionVersion"] = "IncompatibleWithActionVersion";
|
||||
CliConfigErrorCategory["InitCalledTwice"] = "InitCalledTwice";
|
||||
CliConfigErrorCategory["InvalidConfigFile"] = "InvalidConfigFile";
|
||||
CliConfigErrorCategory["InvalidExternalRepoSpecifier"] = "InvalidExternalRepoSpecifier";
|
||||
CliConfigErrorCategory["InvalidSourceRoot"] = "InvalidSourceRoot";
|
||||
CliConfigErrorCategory["MavenBuildFailed"] = "MavenBuildFailed";
|
||||
CliConfigErrorCategory["NoBuildCommandAutodetected"] = "NoBuildCommandAutodetected";
|
||||
CliConfigErrorCategory["NoBuildMethodAutodetected"] = "NoBuildMethodAutodetected";
|
||||
CliConfigErrorCategory["NoSourceCodeSeen"] = "NoSourceCodeSeen";
|
||||
CliConfigErrorCategory["NoSupportedBuildCommandSucceeded"] = "NoSupportedBuildCommandSucceeded";
|
||||
CliConfigErrorCategory["NoSupportedBuildSystemDetected"] = "NoSupportedBuildSystemDetected";
|
||||
CliConfigErrorCategory["NotFoundInRegistry"] = "NotFoundInRegistry";
|
||||
CliConfigErrorCategory["OutOfMemoryOrDisk"] = "OutOfMemoryOrDisk";
|
||||
CliConfigErrorCategory["PackCannotBeFound"] = "PackCannotBeFound";
|
||||
CliConfigErrorCategory["PackMissingAuth"] = "PackMissingAuth";
|
||||
CliConfigErrorCategory["SwiftBuildFailed"] = "SwiftBuildFailed";
|
||||
CliConfigErrorCategory["UnsupportedBuildMode"] = "UnsupportedBuildMode";
|
||||
})(CliConfigErrorCategory || (exports.CliConfigErrorCategory = CliConfigErrorCategory = {}));
|
||||
/**
|
||||
* All of our caught CLI error messages that we handle specially: ie. if we
|
||||
* would like to categorize an error as a configuration error or not.
|
||||
*/
|
||||
exports.cliErrorsConfig = {
|
||||
[CliConfigErrorCategory.AutobuildError]: {
|
||||
cliErrorMessageCandidates: [
|
||||
new RegExp("We were unable to automatically build your code"),
|
||||
],
|
||||
},
|
||||
[CliConfigErrorCategory.CouldNotCreateTempDir]: {
|
||||
cliErrorMessageCandidates: [new RegExp("Could not create temp directory")],
|
||||
},
|
||||
[CliConfigErrorCategory.ExternalRepositoryCloneFailed]: {
|
||||
cliErrorMessageCandidates: [
|
||||
new RegExp("Failed to clone external Git repository"),
|
||||
],
|
||||
},
|
||||
[CliConfigErrorCategory.GradleBuildFailed]: {
|
||||
cliErrorMessageCandidates: [
|
||||
new RegExp("\\[autobuild\\] FAILURE: Build failed with an exception."),
|
||||
],
|
||||
},
|
||||
// Version of CodeQL CLI is incompatible with this version of the CodeQL Action
|
||||
[CliConfigErrorCategory.IncompatibleWithActionVersion]: {
|
||||
cliErrorMessageCandidates: [
|
||||
new RegExp("is not compatible with this CodeQL CLI"),
|
||||
],
|
||||
},
|
||||
[CliConfigErrorCategory.InitCalledTwice]: {
|
||||
cliErrorMessageCandidates: [
|
||||
new RegExp("Refusing to create databases .* but could not process any of it"),
|
||||
],
|
||||
additionalErrorMessageToAppend: `Is the "init" action called twice in the same job?`,
|
||||
},
|
||||
[CliConfigErrorCategory.InvalidConfigFile]: {
|
||||
cliErrorMessageCandidates: [
|
||||
new RegExp("Config file .* is not valid"),
|
||||
new RegExp("The supplied config file is empty"),
|
||||
],
|
||||
},
|
||||
[CliConfigErrorCategory.InvalidExternalRepoSpecifier]: {
|
||||
cliErrorMessageCandidates: [
|
||||
new RegExp("Specifier for external repository is invalid"),
|
||||
],
|
||||
},
|
||||
// Expected source location for database creation does not exist
|
||||
[CliConfigErrorCategory.InvalidSourceRoot]: {
|
||||
cliErrorMessageCandidates: [new RegExp("Invalid source root")],
|
||||
},
|
||||
[CliConfigErrorCategory.MavenBuildFailed]: {
|
||||
cliErrorMessageCandidates: [
|
||||
new RegExp("\\[autobuild\\] \\[ERROR\\] Failed to execute goal"),
|
||||
],
|
||||
},
|
||||
[CliConfigErrorCategory.NoBuildCommandAutodetected]: {
|
||||
cliErrorMessageCandidates: [
|
||||
new RegExp("Could not auto-detect a suitable build method"),
|
||||
],
|
||||
},
|
||||
[CliConfigErrorCategory.NoBuildMethodAutodetected]: {
|
||||
cliErrorMessageCandidates: [
|
||||
new RegExp("Could not detect a suitable build command for the source checkout"),
|
||||
],
|
||||
},
|
||||
// Usually when a manual build script has failed, or if an autodetected language
|
||||
// was unintended to have CodeQL analysis run on it.
|
||||
[CliConfigErrorCategory.NoSourceCodeSeen]: {
|
||||
exitCode: 32,
|
||||
cliErrorMessageCandidates: [
|
||||
new RegExp("CodeQL detected code written in .* but could not process any of it"),
|
||||
new RegExp("CodeQL did not detect any code written in languages supported by CodeQL"),
|
||||
],
|
||||
},
|
||||
[CliConfigErrorCategory.NoSupportedBuildCommandSucceeded]: {
|
||||
cliErrorMessageCandidates: [
|
||||
new RegExp("No supported build command succeeded"),
|
||||
],
|
||||
},
|
||||
[CliConfigErrorCategory.NoSupportedBuildSystemDetected]: {
|
||||
cliErrorMessageCandidates: [
|
||||
new RegExp("No supported build system detected"),
|
||||
],
|
||||
},
|
||||
[CliConfigErrorCategory.OutOfMemoryOrDisk]: {
|
||||
cliErrorMessageCandidates: [
|
||||
new RegExp("CodeQL is out of memory."),
|
||||
new RegExp("out of disk"),
|
||||
new RegExp("No space left on device"),
|
||||
],
|
||||
additionalErrorMessageToAppend: "For more information, see https://gh.io/troubleshooting-code-scanning/out-of-disk-or-memory",
|
||||
},
|
||||
[CliConfigErrorCategory.PackCannotBeFound]: {
|
||||
cliErrorMessageCandidates: [
|
||||
new RegExp("Query pack .* cannot be found\\. Check the spelling of the pack\\."),
|
||||
],
|
||||
},
|
||||
[CliConfigErrorCategory.PackMissingAuth]: {
|
||||
cliErrorMessageCandidates: [
|
||||
new RegExp("GitHub Container registry .* 403 Forbidden"),
|
||||
new RegExp("Do you need to specify a token to authenticate to the registry?"),
|
||||
],
|
||||
},
|
||||
[CliConfigErrorCategory.SwiftBuildFailed]: {
|
||||
cliErrorMessageCandidates: [
|
||||
new RegExp("\\[autobuilder/build\\] \\[build-command-failed\\] `autobuild` failed to run the build command"),
|
||||
],
|
||||
},
|
||||
[CliConfigErrorCategory.UnsupportedBuildMode]: {
|
||||
cliErrorMessageCandidates: [
|
||||
new RegExp("does not support the .* build mode. Please try using one of the following build modes instead"),
|
||||
],
|
||||
},
|
||||
[CliConfigErrorCategory.NotFoundInRegistry]: {
|
||||
cliErrorMessageCandidates: [
|
||||
new RegExp("'.*' not found in the registry '.*'"),
|
||||
],
|
||||
},
|
||||
};
|
||||
/**
|
||||
* Check if the given CLI error or exit code, if applicable, apply to any known
|
||||
* CLI errors in the configuration record. If either the CLI error message matches one of
|
||||
* the error messages in the config record, or the exit codes match, return the error category;
|
||||
* if not, return undefined.
|
||||
*/
|
||||
function getCliConfigCategoryIfExists(cliError) {
|
||||
for (const [category, configuration] of Object.entries(exports.cliErrorsConfig)) {
|
||||
if (cliError.exitCode !== undefined &&
|
||||
configuration.exitCode !== undefined &&
|
||||
cliError.exitCode === configuration.exitCode) {
|
||||
return category;
|
||||
}
|
||||
for (const e of configuration.cliErrorMessageCandidates) {
|
||||
if (cliError.message.match(e) || cliError.stderr.match(e)) {
|
||||
return category;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Check if we are running on an unsupported platform/architecture combination.
|
||||
*/
|
||||
function isUnsupportedPlatform() {
|
||||
return !SUPPORTED_PLATFORMS.some(([platform, arch]) => platform === process.platform && arch === process.arch);
|
||||
}
|
||||
/**
|
||||
* Transform a CLI error into a ConfigurationError for an unsupported platform.
|
||||
*/
|
||||
function getUnsupportedPlatformError(cliError) {
|
||||
return new util_1.ConfigurationError("The CodeQL CLI does not support the platform/architecture combination of " +
|
||||
`${process.platform}/${process.arch} ` +
|
||||
`(see ${doc_url_1.DocUrl.SYSTEM_REQUIREMENTS}). ` +
|
||||
`The underlying error was: ${cliError.message}`);
|
||||
}
|
||||
/**
|
||||
* Changes an error received from the CLI to a ConfigurationError with the message
|
||||
* optionally being transformed, if it is a known configuration error. Otherwise,
|
||||
* simply returns the original error.
|
||||
*/
|
||||
function wrapCliConfigurationError(cliError) {
|
||||
if (isUnsupportedPlatform()) {
|
||||
return getUnsupportedPlatformError(cliError);
|
||||
}
|
||||
const cliConfigErrorCategory = getCliConfigCategoryIfExists(cliError);
|
||||
if (cliConfigErrorCategory === undefined) {
|
||||
return cliError;
|
||||
}
|
||||
let errorMessageBuilder = cliError.message;
|
||||
const additionalErrorMessageToAppend = exports.cliErrorsConfig[cliConfigErrorCategory].additionalErrorMessageToAppend;
|
||||
if (additionalErrorMessageToAppend !== undefined) {
|
||||
errorMessageBuilder = `${errorMessageBuilder} ${additionalErrorMessageToAppend}`;
|
||||
}
|
||||
return new util_1.ConfigurationError(errorMessageBuilder);
|
||||
}
|
||||
//# sourceMappingURL=cli-errors.js.map
|
||||
File diff suppressed because one or more lines are too long
220
lib/cli-errors.test.js
generated
220
lib/cli-errors.test.js
generated
@@ -1,220 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const ava_1 = __importDefault(require("ava"));
|
||||
const sinon = __importStar(require("sinon"));
|
||||
const actions_util_1 = require("./actions-util");
|
||||
const cli_errors_1 = require("./cli-errors");
|
||||
const testing_utils_1 = require("./testing-utils");
|
||||
const util_1 = require("./util");
|
||||
(0, testing_utils_1.setupTests)(ava_1.default);
|
||||
(0, ava_1.default)("CliError constructor with fatal errors", (t) => {
|
||||
const commandError = new actions_util_1.CommandInvocationError("codeql", ["database", "finalize"], 32, "Running TRAP import for CodeQL database...\nA fatal error occurred: Evaluator heap must be at least 384.00 MiB\nA fatal error occurred: Dataset import failed with code 2");
|
||||
const cliError = new cli_errors_1.CliError(commandError);
|
||||
t.is(cliError.exitCode, 32);
|
||||
t.is(cliError.stderr, "Running TRAP import for CodeQL database...\nA fatal error occurred: Evaluator heap must be at least 384.00 MiB\nA fatal error occurred: Dataset import failed with code 2");
|
||||
t.true(cliError.message.includes("A fatal error occurred: Dataset import failed with code 2."));
|
||||
t.true(cliError.message.includes("Context: A fatal error occurred: Evaluator heap must be at least 384.00 MiB."));
|
||||
});
|
||||
(0, ava_1.default)("CliError constructor with single fatal error", (t) => {
|
||||
const commandError = new actions_util_1.CommandInvocationError("codeql", ["database", "create"], 1, "A fatal error occurred: Out of memory");
|
||||
const cliError = new cli_errors_1.CliError(commandError);
|
||||
t.is(cliError.exitCode, 1);
|
||||
t.true(cliError.message.includes("A fatal error occurred: Out of memory"));
|
||||
t.false(cliError.message.includes("Context:"));
|
||||
});
|
||||
(0, ava_1.default)("CliError constructor with autobuild errors", (t) => {
|
||||
const commandError = new actions_util_1.CommandInvocationError("codeql", ["database", "create"], 1, "[autobuild] [ERROR] Build failed\n[autobuild] [ERROR] Compilation error");
|
||||
const cliError = new cli_errors_1.CliError(commandError);
|
||||
t.is(cliError.exitCode, 1);
|
||||
t.true(cliError.message.includes("We were unable to automatically build your code"));
|
||||
t.true(cliError.message.includes("Build failed\nCompilation error"));
|
||||
});
|
||||
(0, ava_1.default)("CliError constructor with truncated autobuild errors", (t) => {
|
||||
const stderr = Array.from({ length: 12 }, (_, i) => `[autobuild] [ERROR] Error ${i + 1}`).join("\n");
|
||||
const commandError = new actions_util_1.CommandInvocationError("codeql", ["database", "create"], 1, stderr);
|
||||
const cliError = new cli_errors_1.CliError(commandError);
|
||||
t.true(cliError.message.includes("(truncated)"));
|
||||
// Should only include first 10 errors plus truncation message
|
||||
const errorLines = cliError.message
|
||||
.split("Encountered the following error: ")[1]
|
||||
.split("\n");
|
||||
t.is(errorLines.length, 11); // 10 errors + "(truncated)"
|
||||
});
|
||||
(0, ava_1.default)("CliError constructor with generic error", (t) => {
|
||||
const commandError = new actions_util_1.CommandInvocationError("codeql", ["version"], 1, "Some generic error message\nLast line of error");
|
||||
const cliError = new cli_errors_1.CliError(commandError);
|
||||
t.is(cliError.exitCode, 1);
|
||||
t.true(cliError.message.includes('Encountered a fatal error while running "codeql version"'));
|
||||
t.true(cliError.message.includes("Exit code was 1 and last log line was: Last line of error."));
|
||||
});
|
||||
(0, ava_1.default)("CliError constructor with empty stderr", (t) => {
|
||||
const commandError = new actions_util_1.CommandInvocationError("codeql", ["version"], 1, "");
|
||||
const cliError = new cli_errors_1.CliError(commandError);
|
||||
t.true(cliError.message.includes("last log line was: n/a"));
|
||||
});
|
||||
for (const [platform, arch] of [
|
||||
["weird_plat", "x64"],
|
||||
["linux", "arm64"],
|
||||
["win32", "arm64"],
|
||||
]) {
|
||||
(0, ava_1.default)(`wrapCliConfigurationError - ${platform}/${arch} unsupported`, (t) => {
|
||||
sinon.stub(process, "platform").value(platform);
|
||||
sinon.stub(process, "arch").value(arch);
|
||||
const commandError = new actions_util_1.CommandInvocationError("codeql", ["version"], 1, "Some error");
|
||||
const cliError = new cli_errors_1.CliError(commandError);
|
||||
const wrappedError = (0, cli_errors_1.wrapCliConfigurationError)(cliError);
|
||||
t.true(wrappedError instanceof util_1.ConfigurationError);
|
||||
t.true(wrappedError.message.includes("CodeQL CLI does not support the platform/architecture combination"));
|
||||
t.true(wrappedError.message.includes(`${platform}/${arch}`));
|
||||
});
|
||||
}
|
||||
(0, ava_1.default)("wrapCliConfigurationError - supported platform", (t) => {
|
||||
const commandError = new actions_util_1.CommandInvocationError("codeql", ["version"], 1, "Some error");
|
||||
const cliError = new cli_errors_1.CliError(commandError);
|
||||
const wrappedError = (0, cli_errors_1.wrapCliConfigurationError)(cliError);
|
||||
// Should return the original error since platform is supported
|
||||
t.is(wrappedError, cliError);
|
||||
});
|
||||
(0, ava_1.default)("wrapCliConfigurationError - autobuild error", (t) => {
|
||||
const commandError = new actions_util_1.CommandInvocationError("codeql", ["database", "create"], 1, "We were unable to automatically build your code");
|
||||
const cliError = new cli_errors_1.CliError(commandError);
|
||||
const wrappedError = (0, cli_errors_1.wrapCliConfigurationError)(cliError);
|
||||
t.true(wrappedError instanceof util_1.ConfigurationError);
|
||||
t.true(wrappedError.message.includes("We were unable to automatically build your code"));
|
||||
});
|
||||
(0, ava_1.default)("wrapCliConfigurationError - init called twice", (t) => {
|
||||
const commandError = new actions_util_1.CommandInvocationError("codeql", ["database", "create"], 1, "Refusing to create databases /some/path but could not process any of it");
|
||||
const cliError = new cli_errors_1.CliError(commandError);
|
||||
const wrappedError = (0, cli_errors_1.wrapCliConfigurationError)(cliError);
|
||||
t.true(wrappedError instanceof util_1.ConfigurationError);
|
||||
t.true(wrappedError.message.includes('Is the "init" action called twice in the same job?'));
|
||||
});
|
||||
(0, ava_1.default)("wrapCliConfigurationError - no source code seen by exit code", (t) => {
|
||||
const commandError = new actions_util_1.CommandInvocationError("codeql", ["database", "finalize"], 32, "Some other error message");
|
||||
const cliError = new cli_errors_1.CliError(commandError);
|
||||
const wrappedError = (0, cli_errors_1.wrapCliConfigurationError)(cliError);
|
||||
t.true(wrappedError instanceof util_1.ConfigurationError);
|
||||
});
|
||||
(0, ava_1.default)("wrapCliConfigurationError - no source code seen by message", (t) => {
|
||||
const commandError = new actions_util_1.CommandInvocationError("codeql", ["database", "finalize"], 1, "CodeQL detected code written in JavaScript but could not process any of it");
|
||||
const cliError = new cli_errors_1.CliError(commandError);
|
||||
const wrappedError = (0, cli_errors_1.wrapCliConfigurationError)(cliError);
|
||||
t.true(wrappedError instanceof util_1.ConfigurationError);
|
||||
});
|
||||
(0, ava_1.default)("wrapCliConfigurationError - out of memory error with additional message", (t) => {
|
||||
const commandError = new actions_util_1.CommandInvocationError("codeql", ["database", "analyze"], 1, "CodeQL is out of memory.");
|
||||
const cliError = new cli_errors_1.CliError(commandError);
|
||||
const wrappedError = (0, cli_errors_1.wrapCliConfigurationError)(cliError);
|
||||
t.true(wrappedError instanceof util_1.ConfigurationError);
|
||||
t.true(wrappedError.message.includes("For more information, see https://gh.io/troubleshooting-code-scanning/out-of-disk-or-memory"));
|
||||
});
|
||||
(0, ava_1.default)("wrapCliConfigurationError - gradle build failed", (t) => {
|
||||
const commandError = new actions_util_1.CommandInvocationError("codeql", ["database", "create"], 1, "[autobuild] FAILURE: Build failed with an exception.");
|
||||
const cliError = new cli_errors_1.CliError(commandError);
|
||||
const wrappedError = (0, cli_errors_1.wrapCliConfigurationError)(cliError);
|
||||
t.true(wrappedError instanceof util_1.ConfigurationError);
|
||||
});
|
||||
(0, ava_1.default)("wrapCliConfigurationError - maven build failed", (t) => {
|
||||
const commandError = new actions_util_1.CommandInvocationError("codeql", ["database", "create"], 1, "[autobuild] [ERROR] Failed to execute goal");
|
||||
const cliError = new cli_errors_1.CliError(commandError);
|
||||
const wrappedError = (0, cli_errors_1.wrapCliConfigurationError)(cliError);
|
||||
t.true(wrappedError instanceof util_1.ConfigurationError);
|
||||
});
|
||||
(0, ava_1.default)("wrapCliConfigurationError - swift build failed", (t) => {
|
||||
const commandError = new actions_util_1.CommandInvocationError("codeql", ["database", "create"], 1, "[autobuilder/build] [build-command-failed] `autobuild` failed to run the build command");
|
||||
const cliError = new cli_errors_1.CliError(commandError);
|
||||
const wrappedError = (0, cli_errors_1.wrapCliConfigurationError)(cliError);
|
||||
t.true(wrappedError instanceof util_1.ConfigurationError);
|
||||
});
|
||||
(0, ava_1.default)("wrapCliConfigurationError - pack cannot be found", (t) => {
|
||||
const commandError = new actions_util_1.CommandInvocationError("codeql", ["pack", "install"], 1, "Query pack my-pack cannot be found. Check the spelling of the pack.");
|
||||
const cliError = new cli_errors_1.CliError(commandError);
|
||||
const wrappedError = (0, cli_errors_1.wrapCliConfigurationError)(cliError);
|
||||
t.true(wrappedError instanceof util_1.ConfigurationError);
|
||||
});
|
||||
(0, ava_1.default)("wrapCliConfigurationError - pack missing auth", (t) => {
|
||||
const commandError = new actions_util_1.CommandInvocationError("codeql", ["pack", "download"], 1, "GitHub Container registry returned 403 Forbidden");
|
||||
const cliError = new cli_errors_1.CliError(commandError);
|
||||
const wrappedError = (0, cli_errors_1.wrapCliConfigurationError)(cliError);
|
||||
t.true(wrappedError instanceof util_1.ConfigurationError);
|
||||
});
|
||||
(0, ava_1.default)("wrapCliConfigurationError - invalid config file", (t) => {
|
||||
const commandError = new actions_util_1.CommandInvocationError("codeql", ["database", "create"], 1, "Config file .codeql/config.yml is not valid");
|
||||
const cliError = new cli_errors_1.CliError(commandError);
|
||||
const wrappedError = (0, cli_errors_1.wrapCliConfigurationError)(cliError);
|
||||
t.true(wrappedError instanceof util_1.ConfigurationError);
|
||||
});
|
||||
(0, ava_1.default)("wrapCliConfigurationError - incompatible CLI version", (t) => {
|
||||
const commandError = new actions_util_1.CommandInvocationError("codeql", ["version"], 1, "is not compatible with this CodeQL CLI");
|
||||
const cliError = new cli_errors_1.CliError(commandError);
|
||||
const wrappedError = (0, cli_errors_1.wrapCliConfigurationError)(cliError);
|
||||
t.true(wrappedError instanceof util_1.ConfigurationError);
|
||||
});
|
||||
(0, ava_1.default)("wrapCliConfigurationError - unknown error remains unchanged", (t) => {
|
||||
const commandError = new actions_util_1.CommandInvocationError("codeql", ["version"], 1, "Some unknown error that doesn't match any patterns");
|
||||
const cliError = new cli_errors_1.CliError(commandError);
|
||||
const wrappedError = (0, cli_errors_1.wrapCliConfigurationError)(cliError);
|
||||
// Should return the original CliError since it doesn't match any known patterns
|
||||
t.is(wrappedError, cliError);
|
||||
t.true(wrappedError instanceof cli_errors_1.CliError);
|
||||
t.false(wrappedError instanceof util_1.ConfigurationError);
|
||||
});
|
||||
// Test all error categories to ensure they're properly configured
|
||||
(0, ava_1.default)("all CLI config error categories have valid configurations", (t) => {
|
||||
const allCategories = Object.values(cli_errors_1.CliConfigErrorCategory);
|
||||
for (const category of allCategories) {
|
||||
// Each category should be a string
|
||||
t.is(typeof category, "string");
|
||||
// Create a test error that matches this category
|
||||
let testError;
|
||||
switch (category) {
|
||||
case cli_errors_1.CliConfigErrorCategory.NoSourceCodeSeen:
|
||||
// This category matches by exit code
|
||||
testError = new cli_errors_1.CliError(new actions_util_1.CommandInvocationError("codeql", [], 32, "some error"));
|
||||
break;
|
||||
default:
|
||||
// For other categories, we'll test with a generic message that should not match
|
||||
testError = new cli_errors_1.CliError(new actions_util_1.CommandInvocationError("codeql", [], 1, "generic error"));
|
||||
break;
|
||||
}
|
||||
// The test should not throw an error when processing
|
||||
t.notThrows(() => (0, cli_errors_1.wrapCliConfigurationError)(testError));
|
||||
}
|
||||
});
|
||||
//# sourceMappingURL=cli-errors.test.js.map
|
||||
File diff suppressed because one or more lines are too long
784
lib/codeql.js
generated
784
lib/codeql.js
generated
@@ -1,784 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.setupCodeQL = setupCodeQL;
|
||||
exports.getCodeQL = getCodeQL;
|
||||
exports.setCodeQL = setCodeQL;
|
||||
exports.createStubCodeQL = createStubCodeQL;
|
||||
exports.getCodeQLForTesting = getCodeQLForTesting;
|
||||
exports.getCodeQLForCmd = getCodeQLForCmd;
|
||||
exports.getExtraOptions = getExtraOptions;
|
||||
exports.getTrapCachingExtractorConfigArgs = getTrapCachingExtractorConfigArgs;
|
||||
exports.getTrapCachingExtractorConfigArgsForLang = getTrapCachingExtractorConfigArgsForLang;
|
||||
exports.getGeneratedCodeScanningConfigPath = getGeneratedCodeScanningConfigPath;
|
||||
const fs = __importStar(require("fs"));
|
||||
const path = __importStar(require("path"));
|
||||
const core = __importStar(require("@actions/core"));
|
||||
const toolrunner = __importStar(require("@actions/exec/lib/toolrunner"));
|
||||
const yaml = __importStar(require("js-yaml"));
|
||||
const actions_util_1 = require("./actions-util");
|
||||
const cli_errors_1 = require("./cli-errors");
|
||||
const config_utils_1 = require("./config-utils");
|
||||
const doc_url_1 = require("./doc-url");
|
||||
const environment_1 = require("./environment");
|
||||
const feature_flags_1 = require("./feature-flags");
|
||||
const git_utils_1 = require("./git-utils");
|
||||
const overlay_database_utils_1 = require("./overlay-database-utils");
|
||||
const setupCodeql = __importStar(require("./setup-codeql"));
|
||||
const tools_features_1 = require("./tools-features");
|
||||
const tracer_config_1 = require("./tracer-config");
|
||||
const util = __importStar(require("./util"));
|
||||
const util_1 = require("./util");
|
||||
/**
|
||||
* Stores the CodeQL object, and is populated by `setupCodeQL` or `getCodeQL`.
|
||||
*/
|
||||
let cachedCodeQL = undefined;
|
||||
/**
|
||||
* The oldest version of CodeQL that the Action will run with. This should be
|
||||
* at least three minor versions behind the current version and must include the
|
||||
* CLI versions shipped with each supported version of GHES.
|
||||
*
|
||||
* The version flags below can be used to conditionally enable certain features
|
||||
* on versions newer than this.
|
||||
*/
|
||||
const CODEQL_MINIMUM_VERSION = "2.16.6";
|
||||
/**
|
||||
* This version will shortly become the oldest version of CodeQL that the Action will run with.
|
||||
*/
|
||||
const CODEQL_NEXT_MINIMUM_VERSION = "2.17.6";
|
||||
/**
|
||||
* This is the version of GHES that was most recently deprecated.
|
||||
*/
|
||||
const GHES_VERSION_MOST_RECENTLY_DEPRECATED = "3.13";
|
||||
/**
|
||||
* This is the deprecation date for the version of GHES that was most recently deprecated.
|
||||
*/
|
||||
const GHES_MOST_RECENT_DEPRECATION_DATE = "2025-06-19";
|
||||
/** The CLI verbosity level to use for extraction in debug mode. */
|
||||
const EXTRACTION_DEBUG_MODE_VERBOSITY = "progress++";
|
||||
/*
|
||||
* Deprecated in favor of ToolsFeature.
|
||||
*
|
||||
* Versions of CodeQL that version-flag certain functionality in the Action.
|
||||
* For convenience, please keep these in descending order. Once a version
|
||||
* flag is older than the oldest supported version above, it may be removed.
|
||||
*/
|
||||
/**
|
||||
* Versions 2.17.1+ of the CodeQL CLI support the `--cache-cleanup` option.
|
||||
*/
|
||||
const CODEQL_VERSION_CACHE_CLEANUP = "2.17.1";
|
||||
/**
|
||||
* Set up CodeQL CLI access.
|
||||
*
|
||||
* @param toolsInput
|
||||
* @param apiDetails
|
||||
* @param tempDir
|
||||
* @param variant
|
||||
* @param defaultCliVersion
|
||||
* @param logger
|
||||
* @param checkVersion Whether to check that CodeQL CLI meets the minimum
|
||||
* version requirement. Must be set to true outside tests.
|
||||
* @returns a { CodeQL, toolsVersion } object.
|
||||
*/
|
||||
async function setupCodeQL(toolsInput, apiDetails, tempDir, variant, defaultCliVersion, logger, checkVersion) {
|
||||
try {
|
||||
const { codeqlFolder, toolsDownloadStatusReport, toolsSource, toolsVersion, zstdAvailability, } = await setupCodeql.setupCodeQLBundle(toolsInput, apiDetails, tempDir, variant, defaultCliVersion, logger);
|
||||
logger.debug(`Bundle download status report: ${JSON.stringify(toolsDownloadStatusReport)}`);
|
||||
let codeqlCmd = path.join(codeqlFolder, "codeql", "codeql");
|
||||
if (process.platform === "win32") {
|
||||
codeqlCmd += ".exe";
|
||||
}
|
||||
else if (process.platform !== "linux" && process.platform !== "darwin") {
|
||||
throw new util.ConfigurationError(`Unsupported platform: ${process.platform}`);
|
||||
}
|
||||
cachedCodeQL = await getCodeQLForCmd(codeqlCmd, checkVersion);
|
||||
return {
|
||||
codeql: cachedCodeQL,
|
||||
toolsDownloadStatusReport,
|
||||
toolsSource,
|
||||
toolsVersion,
|
||||
zstdAvailability,
|
||||
};
|
||||
}
|
||||
catch (e) {
|
||||
const ErrorClass = e instanceof util.ConfigurationError ||
|
||||
(e instanceof Error && e.message.includes("ENOSPC")) // out of disk space
|
||||
? util.ConfigurationError
|
||||
: Error;
|
||||
throw new ErrorClass(`Unable to download and extract CodeQL CLI: ${(0, util_1.getErrorMessage)(e)}${e instanceof Error && e.stack ? `\n\nDetails: ${e.stack}` : ""}`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Use the CodeQL executable located at the given path.
|
||||
*/
|
||||
async function getCodeQL(cmd) {
|
||||
if (cachedCodeQL === undefined) {
|
||||
cachedCodeQL = await getCodeQLForCmd(cmd, true);
|
||||
}
|
||||
return cachedCodeQL;
|
||||
}
|
||||
/**
|
||||
* Overrides the CodeQL object. Only for use in tests that cannot override
|
||||
* CodeQL via dependency injection.
|
||||
*
|
||||
* Accepts a partial object. Any undefined methods will be implemented
|
||||
* to immediately throw an exception indicating which method is missing.
|
||||
*/
|
||||
function setCodeQL(codeql) {
|
||||
cachedCodeQL = createStubCodeQL(codeql);
|
||||
}
|
||||
function resolveFunction(partialCodeql, methodName, defaultImplementation) {
|
||||
if (typeof partialCodeql[methodName] !== "function") {
|
||||
if (defaultImplementation !== undefined) {
|
||||
return defaultImplementation;
|
||||
}
|
||||
const dummyMethod = () => {
|
||||
throw new Error(`CodeQL ${methodName} method not correctly defined`);
|
||||
};
|
||||
return dummyMethod;
|
||||
}
|
||||
return partialCodeql[methodName];
|
||||
}
|
||||
/**
|
||||
* Creates a stub CodeQL object. Only for use in tests.
|
||||
*
|
||||
* Accepts a partial object. Any undefined methods will be implemented
|
||||
* to immediately throw an exception indicating which method is missing.
|
||||
*/
|
||||
function createStubCodeQL(partialCodeql) {
|
||||
return {
|
||||
getPath: resolveFunction(partialCodeql, "getPath", () => "/tmp/dummy-path"),
|
||||
getVersion: resolveFunction(partialCodeql, "getVersion", async () => ({
|
||||
version: "1.0.0",
|
||||
})),
|
||||
printVersion: resolveFunction(partialCodeql, "printVersion"),
|
||||
supportsFeature: resolveFunction(partialCodeql, "supportsFeature", async (feature) => !!partialCodeql.getVersion &&
|
||||
(0, tools_features_1.isSupportedToolsFeature)(await partialCodeql.getVersion(), feature)),
|
||||
isTracedLanguage: resolveFunction(partialCodeql, "isTracedLanguage"),
|
||||
isScannedLanguage: resolveFunction(partialCodeql, "isScannedLanguage"),
|
||||
databaseInitCluster: resolveFunction(partialCodeql, "databaseInitCluster"),
|
||||
runAutobuild: resolveFunction(partialCodeql, "runAutobuild"),
|
||||
extractScannedLanguage: resolveFunction(partialCodeql, "extractScannedLanguage"),
|
||||
extractUsingBuildMode: resolveFunction(partialCodeql, "extractUsingBuildMode"),
|
||||
finalizeDatabase: resolveFunction(partialCodeql, "finalizeDatabase"),
|
||||
resolveLanguages: resolveFunction(partialCodeql, "resolveLanguages"),
|
||||
betterResolveLanguages: resolveFunction(partialCodeql, "betterResolveLanguages", async () => ({ aliases: {}, extractors: {} })),
|
||||
resolveBuildEnvironment: resolveFunction(partialCodeql, "resolveBuildEnvironment"),
|
||||
databaseCleanupCluster: resolveFunction(partialCodeql, "databaseCleanupCluster"),
|
||||
databaseBundle: resolveFunction(partialCodeql, "databaseBundle"),
|
||||
databaseRunQueries: resolveFunction(partialCodeql, "databaseRunQueries"),
|
||||
databaseInterpretResults: resolveFunction(partialCodeql, "databaseInterpretResults"),
|
||||
databasePrintBaseline: resolveFunction(partialCodeql, "databasePrintBaseline"),
|
||||
databaseExportDiagnostics: resolveFunction(partialCodeql, "databaseExportDiagnostics"),
|
||||
diagnosticsExport: resolveFunction(partialCodeql, "diagnosticsExport"),
|
||||
resolveExtractor: resolveFunction(partialCodeql, "resolveExtractor"),
|
||||
resolveQueriesStartingPacks: resolveFunction(partialCodeql, "resolveQueriesStartingPacks"),
|
||||
mergeResults: resolveFunction(partialCodeql, "mergeResults"),
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Get a real, newly created CodeQL instance for testing. The instance refers to
|
||||
* a non-existent placeholder codeql command, so tests that use this function
|
||||
* should also stub the toolrunner.ToolRunner constructor.
|
||||
*/
|
||||
async function getCodeQLForTesting(cmd = "codeql-for-testing") {
|
||||
return getCodeQLForCmd(cmd, false);
|
||||
}
|
||||
/**
|
||||
* Return a CodeQL object for CodeQL CLI access.
|
||||
*
|
||||
* @param cmd Path to CodeQL CLI
|
||||
* @param checkVersion Whether to check that CodeQL CLI meets the minimum
|
||||
* version requirement. Must be set to true outside tests.
|
||||
* @returns A new CodeQL object
|
||||
*/
|
||||
async function getCodeQLForCmd(cmd, checkVersion) {
|
||||
const codeql = {
|
||||
getPath() {
|
||||
return cmd;
|
||||
},
|
||||
async getVersion() {
|
||||
let result = util.getCachedCodeQlVersion();
|
||||
if (result === undefined) {
|
||||
const output = await runCli(cmd, ["version", "--format=json"], {
|
||||
noStreamStdout: true,
|
||||
});
|
||||
try {
|
||||
result = JSON.parse(output);
|
||||
}
|
||||
catch {
|
||||
throw Error(`Invalid JSON output from \`version --format=json\`: ${output}`);
|
||||
}
|
||||
util.cacheCodeQlVersion(result);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
async printVersion() {
|
||||
await runCli(cmd, ["version", "--format=json"]);
|
||||
},
|
||||
async supportsFeature(feature) {
|
||||
return (0, tools_features_1.isSupportedToolsFeature)(await this.getVersion(), feature);
|
||||
},
|
||||
async isTracedLanguage(language) {
|
||||
const extractorPath = await this.resolveExtractor(language);
|
||||
const tracingConfigPath = path.join(extractorPath, "tools", "tracing-config.lua");
|
||||
return fs.existsSync(tracingConfigPath);
|
||||
},
|
||||
async isScannedLanguage(language) {
|
||||
return !(await this.isTracedLanguage(language));
|
||||
},
|
||||
async databaseInitCluster(config, sourceRoot, processName, qlconfigFile, logger) {
|
||||
const extraArgs = config.languages.map((language) => `--language=${language}`);
|
||||
if (await (0, tracer_config_1.shouldEnableIndirectTracing)(codeql, config)) {
|
||||
extraArgs.push("--begin-tracing");
|
||||
extraArgs.push(...(await getTrapCachingExtractorConfigArgs(config)));
|
||||
extraArgs.push(`--trace-process-name=${processName}`);
|
||||
}
|
||||
const codeScanningConfigFile = await writeCodeScanningConfigFile(config, logger);
|
||||
const externalRepositoryToken = (0, actions_util_1.getOptionalInput)("external-repository-token");
|
||||
extraArgs.push(`--codescanning-config=${codeScanningConfigFile}`);
|
||||
if (externalRepositoryToken) {
|
||||
extraArgs.push("--external-repository-token-stdin");
|
||||
}
|
||||
if (config.buildMode !== undefined) {
|
||||
extraArgs.push(`--build-mode=${config.buildMode}`);
|
||||
}
|
||||
if (qlconfigFile !== undefined) {
|
||||
extraArgs.push(`--qlconfig-file=${qlconfigFile}`);
|
||||
}
|
||||
const overwriteFlag = (0, tools_features_1.isSupportedToolsFeature)(await this.getVersion(), tools_features_1.ToolsFeature.ForceOverwrite)
|
||||
? "--force-overwrite"
|
||||
: "--overwrite";
|
||||
const overlayDatabaseMode = config.augmentationProperties.overlayDatabaseMode;
|
||||
if (overlayDatabaseMode === overlay_database_utils_1.OverlayDatabaseMode.Overlay) {
|
||||
const overlayChangesFile = await (0, overlay_database_utils_1.writeOverlayChangesFile)(config, sourceRoot, logger);
|
||||
extraArgs.push(`--overlay-changes=${overlayChangesFile}`);
|
||||
}
|
||||
else if (overlayDatabaseMode === overlay_database_utils_1.OverlayDatabaseMode.OverlayBase) {
|
||||
extraArgs.push("--overlay-base");
|
||||
}
|
||||
await runCli(cmd, [
|
||||
"database",
|
||||
"init",
|
||||
...(overlayDatabaseMode === overlay_database_utils_1.OverlayDatabaseMode.Overlay
|
||||
? []
|
||||
: [overwriteFlag]),
|
||||
"--db-cluster",
|
||||
config.dbLocation,
|
||||
`--source-root=${sourceRoot}`,
|
||||
"--calculate-language-specific-baseline",
|
||||
"--extractor-include-aliases",
|
||||
"--sublanguage-file-coverage",
|
||||
...extraArgs,
|
||||
...getExtraOptionsFromEnv(["database", "init"], {
|
||||
ignoringOptions: ["--overwrite"],
|
||||
}),
|
||||
], { stdin: externalRepositoryToken });
|
||||
if (overlayDatabaseMode === overlay_database_utils_1.OverlayDatabaseMode.OverlayBase) {
|
||||
await (0, overlay_database_utils_1.writeBaseDatabaseOidsFile)(config, sourceRoot);
|
||||
}
|
||||
},
|
||||
async runAutobuild(config, language) {
|
||||
applyAutobuildAzurePipelinesTimeoutFix();
|
||||
const autobuildCmd = path.join(await this.resolveExtractor(language), "tools", process.platform === "win32" ? "autobuild.cmd" : "autobuild.sh");
|
||||
// Bump the verbosity of the autobuild command if we're in debug mode
|
||||
if (config.debugMode) {
|
||||
process.env[environment_1.EnvVar.CLI_VERBOSITY] =
|
||||
process.env[environment_1.EnvVar.CLI_VERBOSITY] || EXTRACTION_DEBUG_MODE_VERBOSITY;
|
||||
}
|
||||
// On macOS, System Integrity Protection (SIP) typically interferes with
|
||||
// CodeQL build tracing of protected binaries.
|
||||
// The usual workaround is to prefix `$CODEQL_RUNNER` to build commands:
|
||||
// `$CODEQL_RUNNER` (not to be confused with the deprecated CodeQL Runner tool)
|
||||
// points to a simple wrapper binary included with the CLI, and the extra layer of
|
||||
// process indirection helps the tracer bypass SIP.
|
||||
// The above SIP workaround is *not* needed here.
|
||||
// At the `autobuild` step in the Actions workflow, we assume the `init` step
|
||||
// has successfully run, and will have exported `DYLD_INSERT_LIBRARIES`
|
||||
// into the environment of subsequent steps, to activate the tracer.
|
||||
// When `DYLD_INSERT_LIBRARIES` is set in the environment for a step,
|
||||
// the Actions runtime introduces its own workaround for SIP
|
||||
// (https://github.com/actions/runner/pull/416).
|
||||
await runCli(autobuildCmd);
|
||||
},
|
||||
async extractScannedLanguage(config, language) {
|
||||
await runCli(cmd, [
|
||||
"database",
|
||||
"trace-command",
|
||||
"--index-traceless-dbs",
|
||||
...(await getTrapCachingExtractorConfigArgsForLang(config, language)),
|
||||
...getExtractionVerbosityArguments(config.debugMode),
|
||||
...getExtraOptionsFromEnv(["database", "trace-command"]),
|
||||
util.getCodeQLDatabasePath(config, language),
|
||||
]);
|
||||
},
|
||||
async extractUsingBuildMode(config, language) {
|
||||
if (config.buildMode === util_1.BuildMode.Autobuild) {
|
||||
applyAutobuildAzurePipelinesTimeoutFix();
|
||||
}
|
||||
try {
|
||||
await runCli(cmd, [
|
||||
"database",
|
||||
"trace-command",
|
||||
"--use-build-mode",
|
||||
"--working-dir",
|
||||
process.cwd(),
|
||||
...(await getTrapCachingExtractorConfigArgsForLang(config, language)),
|
||||
...getExtractionVerbosityArguments(config.debugMode),
|
||||
...getExtraOptionsFromEnv(["database", "trace-command"]),
|
||||
util.getCodeQLDatabasePath(config, language),
|
||||
]);
|
||||
}
|
||||
catch (e) {
|
||||
if (config.buildMode === util_1.BuildMode.Autobuild) {
|
||||
const prefix = "We were unable to automatically build your code. " +
|
||||
"Please change the build mode for this language to manual and specify build steps " +
|
||||
`for your project. See ${doc_url_1.DocUrl.AUTOMATIC_BUILD_FAILED} for more information.`;
|
||||
throw new util.ConfigurationError(`${prefix} ${(0, util_1.getErrorMessage)(e)}`);
|
||||
}
|
||||
else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
},
|
||||
async finalizeDatabase(databasePath, threadsFlag, memoryFlag, enableDebugLogging) {
|
||||
const args = [
|
||||
"database",
|
||||
"finalize",
|
||||
"--finalize-dataset",
|
||||
threadsFlag,
|
||||
memoryFlag,
|
||||
...getExtractionVerbosityArguments(enableDebugLogging),
|
||||
...getExtraOptionsFromEnv(["database", "finalize"]),
|
||||
databasePath,
|
||||
];
|
||||
await runCli(cmd, args);
|
||||
},
|
||||
async resolveLanguages() {
|
||||
const codeqlArgs = [
|
||||
"resolve",
|
||||
"languages",
|
||||
"--format=json",
|
||||
...getExtraOptionsFromEnv(["resolve", "languages"]),
|
||||
];
|
||||
const output = await runCli(cmd, codeqlArgs);
|
||||
try {
|
||||
return JSON.parse(output);
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error(`Unexpected output from codeql resolve languages: ${e}`);
|
||||
}
|
||||
},
|
||||
async betterResolveLanguages() {
|
||||
const codeqlArgs = [
|
||||
"resolve",
|
||||
"languages",
|
||||
"--format=betterjson",
|
||||
"--extractor-options-verbosity=4",
|
||||
"--extractor-include-aliases",
|
||||
...getExtraOptionsFromEnv(["resolve", "languages"]),
|
||||
];
|
||||
const output = await runCli(cmd, codeqlArgs);
|
||||
try {
|
||||
return JSON.parse(output);
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error(`Unexpected output from codeql resolve languages with --format=betterjson: ${e}`);
|
||||
}
|
||||
},
|
||||
async resolveBuildEnvironment(workingDir, language) {
|
||||
const codeqlArgs = [
|
||||
"resolve",
|
||||
"build-environment",
|
||||
`--language=${language}`,
|
||||
"--extractor-include-aliases",
|
||||
...getExtraOptionsFromEnv(["resolve", "build-environment"]),
|
||||
];
|
||||
if (workingDir !== undefined) {
|
||||
codeqlArgs.push("--working-dir", workingDir);
|
||||
}
|
||||
const output = await runCli(cmd, codeqlArgs);
|
||||
try {
|
||||
return JSON.parse(output);
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error(`Unexpected output from codeql resolve build-environment: ${e} in\n${output}`);
|
||||
}
|
||||
},
|
||||
async databaseRunQueries(databasePath, flags, queries = []) {
|
||||
const codeqlArgs = [
|
||||
"database",
|
||||
"run-queries",
|
||||
...flags,
|
||||
databasePath,
|
||||
"--intra-layer-parallelism",
|
||||
"--min-disk-free=1024", // Try to leave at least 1GB free
|
||||
"-v",
|
||||
...queries,
|
||||
...getExtraOptionsFromEnv(["database", "run-queries"], {
|
||||
ignoringOptions: ["--expect-discarded-cache"],
|
||||
}),
|
||||
];
|
||||
await runCli(cmd, codeqlArgs);
|
||||
},
|
||||
async databaseInterpretResults(databasePath, querySuitePaths, sarifFile, addSnippetsFlag, threadsFlag, verbosityFlag, sarifRunPropertyFlag, automationDetailsId, config, features) {
|
||||
const shouldExportDiagnostics = await features.getValue(feature_flags_1.Feature.ExportDiagnosticsEnabled, this);
|
||||
const codeqlArgs = [
|
||||
"database",
|
||||
"interpret-results",
|
||||
threadsFlag,
|
||||
"--format=sarif-latest",
|
||||
verbosityFlag,
|
||||
`--output=${sarifFile}`,
|
||||
addSnippetsFlag,
|
||||
"--print-diagnostics-summary",
|
||||
"--print-metrics-summary",
|
||||
"--sarif-add-baseline-file-info",
|
||||
`--sarif-codescanning-config=${getGeneratedCodeScanningConfigPath(config)}`,
|
||||
"--sarif-group-rules-by-pack",
|
||||
"--sarif-include-query-help=always",
|
||||
"--sublanguage-file-coverage",
|
||||
...(await getJobRunUuidSarifOptions(this)),
|
||||
...getExtraOptionsFromEnv(["database", "interpret-results"]),
|
||||
];
|
||||
if (sarifRunPropertyFlag !== undefined) {
|
||||
codeqlArgs.push(sarifRunPropertyFlag);
|
||||
}
|
||||
if (automationDetailsId !== undefined) {
|
||||
codeqlArgs.push("--sarif-category", automationDetailsId);
|
||||
}
|
||||
if (shouldExportDiagnostics) {
|
||||
codeqlArgs.push("--sarif-include-diagnostics");
|
||||
}
|
||||
else {
|
||||
codeqlArgs.push("--no-sarif-include-diagnostics");
|
||||
}
|
||||
if (!(0, tools_features_1.isSupportedToolsFeature)(await this.getVersion(), tools_features_1.ToolsFeature.AnalysisSummaryV2IsDefault)) {
|
||||
codeqlArgs.push("--new-analysis-summary");
|
||||
}
|
||||
codeqlArgs.push(databasePath);
|
||||
if (querySuitePaths) {
|
||||
codeqlArgs.push(...querySuitePaths);
|
||||
}
|
||||
// Capture the stdout, which contains the analysis summary. Don't stream it to the Actions
|
||||
// logs to avoid printing it twice.
|
||||
return await runCli(cmd, codeqlArgs, {
|
||||
noStreamStdout: true,
|
||||
});
|
||||
},
|
||||
async databasePrintBaseline(databasePath) {
|
||||
const codeqlArgs = [
|
||||
"database",
|
||||
"print-baseline",
|
||||
...getExtraOptionsFromEnv(["database", "print-baseline"]),
|
||||
databasePath,
|
||||
];
|
||||
return await runCli(cmd, codeqlArgs);
|
||||
},
|
||||
async databaseCleanupCluster(config, cleanupLevel) {
|
||||
const cacheCleanupFlag = (await util.codeQlVersionAtLeast(this, CODEQL_VERSION_CACHE_CLEANUP))
|
||||
? "--cache-cleanup"
|
||||
: "--mode";
|
||||
for (const language of config.languages) {
|
||||
const databasePath = util.getCodeQLDatabasePath(config, language);
|
||||
const codeqlArgs = [
|
||||
"database",
|
||||
"cleanup",
|
||||
databasePath,
|
||||
`${cacheCleanupFlag}=${cleanupLevel}`,
|
||||
...getExtraOptionsFromEnv(["database", "cleanup"]),
|
||||
];
|
||||
await runCli(cmd, codeqlArgs);
|
||||
}
|
||||
},
|
||||
async databaseBundle(databasePath, outputFilePath, databaseName) {
|
||||
const args = [
|
||||
"database",
|
||||
"bundle",
|
||||
databasePath,
|
||||
`--output=${outputFilePath}`,
|
||||
`--name=${databaseName}`,
|
||||
...getExtraOptionsFromEnv(["database", "bundle"]),
|
||||
];
|
||||
await new toolrunner.ToolRunner(cmd, args).exec();
|
||||
},
|
||||
async databaseExportDiagnostics(databasePath, sarifFile, automationDetailsId) {
|
||||
const args = [
|
||||
"database",
|
||||
"export-diagnostics",
|
||||
`${databasePath}`,
|
||||
"--db-cluster", // Database is always a cluster for CodeQL versions that support diagnostics.
|
||||
"--format=sarif-latest",
|
||||
`--output=${sarifFile}`,
|
||||
"--sarif-include-diagnostics", // ExportDiagnosticsEnabled is always true if this command is run.
|
||||
"-vvv",
|
||||
...getExtraOptionsFromEnv(["diagnostics", "export"]),
|
||||
];
|
||||
if (automationDetailsId !== undefined) {
|
||||
args.push("--sarif-category", automationDetailsId);
|
||||
}
|
||||
await new toolrunner.ToolRunner(cmd, args).exec();
|
||||
},
|
||||
async diagnosticsExport(sarifFile, automationDetailsId, config) {
|
||||
const args = [
|
||||
"diagnostics",
|
||||
"export",
|
||||
"--format=sarif-latest",
|
||||
`--output=${sarifFile}`,
|
||||
`--sarif-codescanning-config=${getGeneratedCodeScanningConfigPath(config)}`,
|
||||
...getExtraOptionsFromEnv(["diagnostics", "export"]),
|
||||
];
|
||||
if (automationDetailsId !== undefined) {
|
||||
args.push("--sarif-category", automationDetailsId);
|
||||
}
|
||||
await new toolrunner.ToolRunner(cmd, args).exec();
|
||||
},
|
||||
async resolveExtractor(language) {
|
||||
// Request it using `format=json` so we don't need to strip the trailing new line generated by
|
||||
// the CLI.
|
||||
let extractorPath = "";
|
||||
await new toolrunner.ToolRunner(cmd, [
|
||||
"resolve",
|
||||
"extractor",
|
||||
"--format=json",
|
||||
`--language=${language}`,
|
||||
"--extractor-include-aliases",
|
||||
...getExtraOptionsFromEnv(["resolve", "extractor"]),
|
||||
], {
|
||||
silent: true,
|
||||
listeners: {
|
||||
stdout: (data) => {
|
||||
extractorPath += data.toString();
|
||||
},
|
||||
stderr: (data) => {
|
||||
process.stderr.write(data);
|
||||
},
|
||||
},
|
||||
}).exec();
|
||||
return JSON.parse(extractorPath);
|
||||
},
|
||||
async resolveQueriesStartingPacks(queries) {
|
||||
const codeqlArgs = [
|
||||
"resolve",
|
||||
"queries",
|
||||
"--format=startingpacks",
|
||||
...getExtraOptionsFromEnv(["resolve", "queries"]),
|
||||
...queries,
|
||||
];
|
||||
const output = await runCli(cmd, codeqlArgs, { noStreamStdout: true });
|
||||
try {
|
||||
return JSON.parse(output);
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error(`Unexpected output from codeql resolve queries --format=startingpacks: ${e}`);
|
||||
}
|
||||
},
|
||||
async mergeResults(sarifFiles, outputFile, { mergeRunsFromEqualCategory = false, }) {
|
||||
const args = [
|
||||
"github",
|
||||
"merge-results",
|
||||
"--output",
|
||||
outputFile,
|
||||
...getExtraOptionsFromEnv(["github", "merge-results"]),
|
||||
];
|
||||
for (const sarifFile of sarifFiles) {
|
||||
args.push("--sarif", sarifFile);
|
||||
}
|
||||
if (mergeRunsFromEqualCategory) {
|
||||
args.push("--sarif-merge-runs-from-equal-category");
|
||||
}
|
||||
await runCli(cmd, args);
|
||||
},
|
||||
};
|
||||
// To ensure that status reports include the CodeQL CLI version wherever
|
||||
// possible, we want to call getVersion(), which populates the version value
|
||||
// used by status reporting, at the earliest opportunity. But invoking
|
||||
// getVersion() directly here breaks tests that only pretend to create a
|
||||
// CodeQL object. So instead we rely on the assumption that all non-test
|
||||
// callers would set checkVersion to true, and util.codeQlVersionAbove()
|
||||
// would call getVersion(), so the CLI version would be cached as soon as the
|
||||
// CodeQL object is created.
|
||||
if (checkVersion &&
|
||||
!(await util.codeQlVersionAtLeast(codeql, CODEQL_MINIMUM_VERSION))) {
|
||||
throw new util.ConfigurationError(`Expected a CodeQL CLI with version at least ${CODEQL_MINIMUM_VERSION} but got version ${(await codeql.getVersion()).version}`);
|
||||
}
|
||||
else if (checkVersion &&
|
||||
process.env[environment_1.EnvVar.SUPPRESS_DEPRECATED_SOON_WARNING] !== "true" &&
|
||||
!(await util.codeQlVersionAtLeast(codeql, CODEQL_NEXT_MINIMUM_VERSION))) {
|
||||
const result = await codeql.getVersion();
|
||||
core.warning(`CodeQL CLI version ${result.version} was discontinued on ` +
|
||||
`${GHES_MOST_RECENT_DEPRECATION_DATE} alongside GitHub Enterprise Server ` +
|
||||
`${GHES_VERSION_MOST_RECENTLY_DEPRECATED} and will not be supported by the next minor ` +
|
||||
`release of the CodeQL Action. Please update to CodeQL CLI version ` +
|
||||
`${CODEQL_NEXT_MINIMUM_VERSION} or later. For instance, if you have specified a custom ` +
|
||||
"version of the CLI using the 'tools' input to the 'init' Action, you can remove this " +
|
||||
"input to use the default version.\n\n" +
|
||||
"Alternatively, if you want to continue using CodeQL CLI version " +
|
||||
`${result.version}, you can replace 'github/codeql-action/*@v${(0, actions_util_1.getActionVersion)().split(".")[0]}' by 'github/codeql-action/*@v${(0, actions_util_1.getActionVersion)()}' in your code scanning workflow to ` +
|
||||
"continue using this version of the CodeQL Action.");
|
||||
core.exportVariable(environment_1.EnvVar.SUPPRESS_DEPRECATED_SOON_WARNING, "true");
|
||||
}
|
||||
return codeql;
|
||||
}
|
||||
/**
|
||||
* Gets the options for `path` of `options` as an array of extra option strings.
|
||||
*
|
||||
* @param ignoringOptions Options that should be ignored, for example because they have already
|
||||
* been passed and it is an error to pass them more than once.
|
||||
*/
|
||||
function getExtraOptionsFromEnv(paths, { ignoringOptions } = {}) {
|
||||
const options = util.getExtraOptionsEnvParam();
|
||||
return getExtraOptions(options, paths, []).filter((option) => !ignoringOptions?.includes(option));
|
||||
}
|
||||
/**
|
||||
* Gets `options` as an array of extra option strings.
|
||||
*
|
||||
* - throws an exception mentioning `pathInfo` if this conversion is impossible.
|
||||
*/
|
||||
function asExtraOptions(options, pathInfo) {
|
||||
if (options === undefined) {
|
||||
return [];
|
||||
}
|
||||
if (!Array.isArray(options)) {
|
||||
const msg = `The extra options for '${pathInfo.join(".")}' ('${JSON.stringify(options)}') are not in an array.`;
|
||||
throw new Error(msg);
|
||||
}
|
||||
return options.map((o) => {
|
||||
const t = typeof o;
|
||||
if (t !== "string" && t !== "number" && t !== "boolean") {
|
||||
const msg = `The extra option for '${pathInfo.join(".")}' ('${JSON.stringify(o)}') is not a primitive value.`;
|
||||
throw new Error(msg);
|
||||
}
|
||||
return `${o}`;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Gets the options for `path` of `options` as an array of extra option strings.
|
||||
*
|
||||
* - the special terminal step name '*' in `options` matches all path steps
|
||||
* - throws an exception if this conversion is impossible.
|
||||
*
|
||||
* Exported for testing.
|
||||
*/
|
||||
function getExtraOptions(options, paths, pathInfo) {
|
||||
const all = asExtraOptions(options?.["*"], pathInfo.concat("*"));
|
||||
const specific = paths.length === 0
|
||||
? asExtraOptions(options, pathInfo)
|
||||
: getExtraOptions(options?.[paths[0]], paths?.slice(1), pathInfo.concat(paths[0]));
|
||||
return all.concat(specific);
|
||||
}
|
||||
async function runCli(cmd, args = [], opts = {}) {
|
||||
try {
|
||||
return await (0, actions_util_1.runTool)(cmd, args, opts);
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof actions_util_1.CommandInvocationError) {
|
||||
throw (0, cli_errors_1.wrapCliConfigurationError)(new cli_errors_1.CliError(e));
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Generates a code scanning configuration that is to be used for a scan.
|
||||
*
|
||||
* @param codeql The CodeQL object to use.
|
||||
* @param config The configuration to use.
|
||||
* @returns the path to the generated user configuration file.
|
||||
*/
|
||||
async function writeCodeScanningConfigFile(config, logger) {
|
||||
const codeScanningConfigFile = getGeneratedCodeScanningConfigPath(config);
|
||||
const augmentedConfig = (0, config_utils_1.generateCodeScanningConfig)(config.originalUserInput, config.augmentationProperties);
|
||||
logger.info(`Writing augmented user configuration file to ${codeScanningConfigFile}`);
|
||||
logger.startGroup("Augmented user configuration file contents");
|
||||
logger.info(yaml.dump(augmentedConfig));
|
||||
logger.endGroup();
|
||||
fs.writeFileSync(codeScanningConfigFile, yaml.dump(augmentedConfig));
|
||||
return codeScanningConfigFile;
|
||||
}
|
||||
// This constant sets the size of each TRAP cache in megabytes.
|
||||
const TRAP_CACHE_SIZE_MB = 1024;
|
||||
async function getTrapCachingExtractorConfigArgs(config) {
|
||||
const result = [];
|
||||
for (const language of config.languages)
|
||||
result.push(await getTrapCachingExtractorConfigArgsForLang(config, language));
|
||||
return result.flat();
|
||||
}
|
||||
async function getTrapCachingExtractorConfigArgsForLang(config, language) {
|
||||
const cacheDir = config.trapCaches[language];
|
||||
if (cacheDir === undefined)
|
||||
return [];
|
||||
const write = await (0, git_utils_1.isAnalyzingDefaultBranch)();
|
||||
return [
|
||||
`-O=${language}.trap.cache.dir=${cacheDir}`,
|
||||
`-O=${language}.trap.cache.bound=${TRAP_CACHE_SIZE_MB}`,
|
||||
`-O=${language}.trap.cache.write=${write}`,
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Get the path to the code scanning configuration generated by the CLI.
|
||||
*
|
||||
* This will not exist if the configuration is being parsed in the Action.
|
||||
*/
|
||||
function getGeneratedCodeScanningConfigPath(config) {
|
||||
return path.resolve(config.tempDir, "user-config.yaml");
|
||||
}
|
||||
function getExtractionVerbosityArguments(enableDebugLogging) {
|
||||
return enableDebugLogging
|
||||
? [`--verbosity=${EXTRACTION_DEBUG_MODE_VERBOSITY}`]
|
||||
: [];
|
||||
}
|
||||
/**
|
||||
* Updates the `JAVA_TOOL_OPTIONS` environment variable to resolve an issue with Azure Pipelines
|
||||
* timing out connections after 4 minutes and Maven not properly handling closed connections.
|
||||
*
|
||||
* Without the fix, long build processes will timeout when pulling down Java packages
|
||||
* https://developercommunity.visualstudio.com/content/problem/292284/maven-hosted-agent-connection-timeout.html
|
||||
*/
|
||||
function applyAutobuildAzurePipelinesTimeoutFix() {
|
||||
const javaToolOptions = process.env["JAVA_TOOL_OPTIONS"] || "";
|
||||
process.env["JAVA_TOOL_OPTIONS"] = [
|
||||
...javaToolOptions.split(/\s+/),
|
||||
"-Dhttp.keepAlive=false",
|
||||
"-Dmaven.wagon.http.pool=false",
|
||||
].join(" ");
|
||||
}
|
||||
async function getJobRunUuidSarifOptions(codeql) {
|
||||
const jobRunUuid = process.env[environment_1.EnvVar.JOB_RUN_UUID];
|
||||
return jobRunUuid &&
|
||||
(await codeql.supportsFeature(tools_features_1.ToolsFeature.DatabaseInterpretResultsSupportsSarifRunProperty))
|
||||
? [`--sarif-run-property=jobRunUuid=${jobRunUuid}`]
|
||||
: [];
|
||||
}
|
||||
//# sourceMappingURL=codeql.js.map
|
||||
File diff suppressed because one or more lines are too long
666
lib/codeql.test.js
generated
666
lib/codeql.test.js
generated
@@ -1,666 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.stubToolRunnerConstructor = stubToolRunnerConstructor;
|
||||
const fs = __importStar(require("fs"));
|
||||
const toolrunner = __importStar(require("@actions/exec/lib/toolrunner"));
|
||||
const io = __importStar(require("@actions/io"));
|
||||
const toolcache = __importStar(require("@actions/tool-cache"));
|
||||
const ava_1 = __importDefault(require("ava"));
|
||||
const del_1 = __importDefault(require("del"));
|
||||
const yaml = __importStar(require("js-yaml"));
|
||||
const nock_1 = __importDefault(require("nock"));
|
||||
const sinon = __importStar(require("sinon"));
|
||||
const actionsUtil = __importStar(require("./actions-util"));
|
||||
const cli_errors_1 = require("./cli-errors");
|
||||
const codeql = __importStar(require("./codeql"));
|
||||
const config_utils_1 = require("./config-utils");
|
||||
const defaults = __importStar(require("./defaults.json"));
|
||||
const doc_url_1 = require("./doc-url");
|
||||
const languages_1 = require("./languages");
|
||||
const logging_1 = require("./logging");
|
||||
const setup_codeql_1 = require("./setup-codeql");
|
||||
const testing_utils_1 = require("./testing-utils");
|
||||
const tools_features_1 = require("./tools-features");
|
||||
const util = __importStar(require("./util"));
|
||||
const util_1 = require("./util");
|
||||
(0, testing_utils_1.setupTests)(ava_1.default);
|
||||
let stubConfig;
|
||||
ava_1.default.beforeEach(() => {
|
||||
(0, util_1.initializeEnvironment)("1.2.3");
|
||||
stubConfig = (0, testing_utils_1.createTestConfig)({
|
||||
languages: [languages_1.KnownLanguage.cpp],
|
||||
});
|
||||
});
|
||||
async function installIntoToolcache({ apiDetails = testing_utils_1.SAMPLE_DOTCOM_API_DETAILS, cliVersion, isPinned, tagName, tmpDir, }) {
|
||||
const url = (0, testing_utils_1.mockBundleDownloadApi)({ apiDetails, isPinned, tagName });
|
||||
await codeql.setupCodeQL(cliVersion !== undefined ? undefined : url, apiDetails, tmpDir, util.GitHubVariant.GHES, cliVersion !== undefined
|
||||
? { cliVersion, tagName }
|
||||
: testing_utils_1.SAMPLE_DEFAULT_CLI_VERSION, (0, logging_1.getRunnerLogger)(true), false);
|
||||
}
|
||||
function mockReleaseApi({ apiDetails = testing_utils_1.SAMPLE_DOTCOM_API_DETAILS, assetNames, tagName, }) {
|
||||
return (0, nock_1.default)(apiDetails.apiURL)
|
||||
.get(`/repos/github/codeql-action/releases/tags/${tagName}`)
|
||||
.reply(200, {
|
||||
assets: assetNames.map((name) => ({
|
||||
name,
|
||||
})),
|
||||
tag_name: tagName,
|
||||
});
|
||||
}
|
||||
function mockApiDetails(apiDetails) {
|
||||
// This is a workaround to mock `api.getApiDetails()` since it doesn't seem to be possible to
|
||||
// mock this directly. The difficulty is that `getApiDetails()` is called locally in
|
||||
// `api-client.ts`, but `sinon.stub(api, "getApiDetails")` only affects calls to
|
||||
// `getApiDetails()` via an imported `api` module.
|
||||
sinon
|
||||
.stub(actionsUtil, "getRequiredInput")
|
||||
.withArgs("token")
|
||||
.returns(apiDetails.auth);
|
||||
process.env["GITHUB_SERVER_URL"] = apiDetails.url;
|
||||
process.env["GITHUB_API_URL"] = apiDetails.apiURL || "";
|
||||
}
|
||||
async function stubCodeql() {
|
||||
const codeqlObject = await codeql.getCodeQLForTesting();
|
||||
sinon.stub(codeqlObject, "getVersion").resolves((0, testing_utils_1.makeVersionInfo)("2.17.6"));
|
||||
sinon
|
||||
.stub(codeqlObject, "isTracedLanguage")
|
||||
.withArgs(languages_1.KnownLanguage.cpp)
|
||||
.resolves(true);
|
||||
return codeqlObject;
|
||||
}
|
||||
(0, ava_1.default)("downloads and caches explicitly requested bundles that aren't in the toolcache", async (t) => {
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
const versions = ["20200601", "20200610"];
|
||||
for (let i = 0; i < versions.length; i++) {
|
||||
const version = versions[i];
|
||||
const url = (0, testing_utils_1.mockBundleDownloadApi)({
|
||||
tagName: `codeql-bundle-${version}`,
|
||||
isPinned: false,
|
||||
});
|
||||
const result = await codeql.setupCodeQL(url, testing_utils_1.SAMPLE_DOTCOM_API_DETAILS, tmpDir, util.GitHubVariant.DOTCOM, testing_utils_1.SAMPLE_DEFAULT_CLI_VERSION, (0, logging_1.getRunnerLogger)(true), false);
|
||||
t.assert(toolcache.find("CodeQL", `0.0.0-${version}`));
|
||||
t.is(result.toolsVersion, `0.0.0-${version}`);
|
||||
t.is(result.toolsSource, setup_codeql_1.ToolsSource.Download);
|
||||
}
|
||||
t.is(toolcache.findAllVersions("CodeQL").length, 2);
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("caches semantically versioned bundles using their semantic version number", async (t) => {
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
const url = (0, testing_utils_1.mockBundleDownloadApi)({
|
||||
tagName: `codeql-bundle-v2.15.0`,
|
||||
isPinned: false,
|
||||
});
|
||||
const result = await codeql.setupCodeQL(url, testing_utils_1.SAMPLE_DOTCOM_API_DETAILS, tmpDir, util.GitHubVariant.DOTCOM, testing_utils_1.SAMPLE_DEFAULT_CLI_VERSION, (0, logging_1.getRunnerLogger)(true), false);
|
||||
t.is(toolcache.findAllVersions("CodeQL").length, 1);
|
||||
t.assert(toolcache.find("CodeQL", `2.15.0`));
|
||||
t.is(result.toolsVersion, `2.15.0`);
|
||||
t.is(result.toolsSource, setup_codeql_1.ToolsSource.Download);
|
||||
if (result.toolsDownloadStatusReport) {
|
||||
assertDurationsInteger(t, result.toolsDownloadStatusReport);
|
||||
}
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("downloads an explicitly requested bundle even if a different version is cached", async (t) => {
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
await installIntoToolcache({
|
||||
tagName: "codeql-bundle-20200601",
|
||||
isPinned: true,
|
||||
tmpDir,
|
||||
});
|
||||
const url = (0, testing_utils_1.mockBundleDownloadApi)({
|
||||
tagName: "codeql-bundle-20200610",
|
||||
});
|
||||
const result = await codeql.setupCodeQL(url, testing_utils_1.SAMPLE_DOTCOM_API_DETAILS, tmpDir, util.GitHubVariant.DOTCOM, testing_utils_1.SAMPLE_DEFAULT_CLI_VERSION, (0, logging_1.getRunnerLogger)(true), false);
|
||||
t.assert(toolcache.find("CodeQL", "0.0.0-20200610"));
|
||||
t.deepEqual(result.toolsVersion, "0.0.0-20200610");
|
||||
t.is(result.toolsSource, setup_codeql_1.ToolsSource.Download);
|
||||
if (result.toolsDownloadStatusReport) {
|
||||
assertDurationsInteger(t, result.toolsDownloadStatusReport);
|
||||
}
|
||||
});
|
||||
});
|
||||
const EXPLICITLY_REQUESTED_BUNDLE_TEST_CASES = [
|
||||
{
|
||||
tagName: "codeql-bundle-2.17.6",
|
||||
expectedToolcacheVersion: "2.17.6",
|
||||
},
|
||||
{
|
||||
tagName: "codeql-bundle-20240805",
|
||||
expectedToolcacheVersion: "0.0.0-20240805",
|
||||
},
|
||||
];
|
||||
for (const { tagName, expectedToolcacheVersion, } of EXPLICITLY_REQUESTED_BUNDLE_TEST_CASES) {
|
||||
(0, ava_1.default)(`caches explicitly requested bundle ${tagName} as ${expectedToolcacheVersion}`, async (t) => {
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
mockApiDetails(testing_utils_1.SAMPLE_DOTCOM_API_DETAILS);
|
||||
sinon.stub(actionsUtil, "isRunningLocalAction").returns(true);
|
||||
const url = (0, testing_utils_1.mockBundleDownloadApi)({
|
||||
tagName,
|
||||
});
|
||||
const result = await codeql.setupCodeQL(url, testing_utils_1.SAMPLE_DOTCOM_API_DETAILS, tmpDir, util.GitHubVariant.DOTCOM, testing_utils_1.SAMPLE_DEFAULT_CLI_VERSION, (0, logging_1.getRunnerLogger)(true), false);
|
||||
t.assert(toolcache.find("CodeQL", expectedToolcacheVersion));
|
||||
t.deepEqual(result.toolsVersion, expectedToolcacheVersion);
|
||||
t.is(result.toolsSource, setup_codeql_1.ToolsSource.Download);
|
||||
t.assert(Number.isInteger(result.toolsDownloadStatusReport?.downloadDurationMs));
|
||||
});
|
||||
});
|
||||
}
|
||||
for (const toolcacheVersion of [
|
||||
// Test that we use the tools from the toolcache when `SAMPLE_DEFAULT_CLI_VERSION` is requested
|
||||
// and `SAMPLE_DEFAULT_CLI_VERSION-` is in the toolcache.
|
||||
testing_utils_1.SAMPLE_DEFAULT_CLI_VERSION.cliVersion,
|
||||
`${testing_utils_1.SAMPLE_DEFAULT_CLI_VERSION.cliVersion}-20230101`,
|
||||
]) {
|
||||
(0, ava_1.default)(`uses tools from toolcache when ${testing_utils_1.SAMPLE_DEFAULT_CLI_VERSION.cliVersion} is requested and ` +
|
||||
`${toolcacheVersion} is installed`, async (t) => {
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
sinon
|
||||
.stub(toolcache, "find")
|
||||
.withArgs("CodeQL", toolcacheVersion)
|
||||
.returns("path/to/cached/codeql");
|
||||
sinon.stub(toolcache, "findAllVersions").returns([toolcacheVersion]);
|
||||
const result = await codeql.setupCodeQL(undefined, testing_utils_1.SAMPLE_DOTCOM_API_DETAILS, tmpDir, util.GitHubVariant.DOTCOM, testing_utils_1.SAMPLE_DEFAULT_CLI_VERSION, (0, logging_1.getRunnerLogger)(true), false);
|
||||
t.is(result.toolsVersion, testing_utils_1.SAMPLE_DEFAULT_CLI_VERSION.cliVersion);
|
||||
t.is(result.toolsSource, setup_codeql_1.ToolsSource.Toolcache);
|
||||
t.is(result.toolsDownloadStatusReport?.combinedDurationMs, undefined);
|
||||
t.is(result.toolsDownloadStatusReport?.downloadDurationMs, undefined);
|
||||
t.is(result.toolsDownloadStatusReport?.extractionDurationMs, undefined);
|
||||
});
|
||||
});
|
||||
}
|
||||
(0, ava_1.default)(`uses a cached bundle when no tools input is given on GHES`, async (t) => {
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
await installIntoToolcache({
|
||||
tagName: "codeql-bundle-20200601",
|
||||
isPinned: true,
|
||||
tmpDir,
|
||||
});
|
||||
const result = await codeql.setupCodeQL(undefined, testing_utils_1.SAMPLE_DOTCOM_API_DETAILS, tmpDir, util.GitHubVariant.GHES, {
|
||||
cliVersion: defaults.cliVersion,
|
||||
tagName: defaults.bundleVersion,
|
||||
}, (0, logging_1.getRunnerLogger)(true), false);
|
||||
t.deepEqual(result.toolsVersion, "0.0.0-20200601");
|
||||
t.is(result.toolsSource, setup_codeql_1.ToolsSource.Toolcache);
|
||||
t.is(result.toolsDownloadStatusReport?.combinedDurationMs, undefined);
|
||||
t.is(result.toolsDownloadStatusReport?.downloadDurationMs, undefined);
|
||||
t.is(result.toolsDownloadStatusReport?.extractionDurationMs, undefined);
|
||||
const cachedVersions = toolcache.findAllVersions("CodeQL");
|
||||
t.is(cachedVersions.length, 1);
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)(`downloads bundle if only an unpinned version is cached on GHES`, async (t) => {
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
await installIntoToolcache({
|
||||
tagName: "codeql-bundle-20200601",
|
||||
isPinned: false,
|
||||
tmpDir,
|
||||
});
|
||||
(0, testing_utils_1.mockBundleDownloadApi)({
|
||||
tagName: defaults.bundleVersion,
|
||||
});
|
||||
const result = await codeql.setupCodeQL(undefined, testing_utils_1.SAMPLE_DOTCOM_API_DETAILS, tmpDir, util.GitHubVariant.GHES, {
|
||||
cliVersion: defaults.cliVersion,
|
||||
tagName: defaults.bundleVersion,
|
||||
}, (0, logging_1.getRunnerLogger)(true), false);
|
||||
t.deepEqual(result.toolsVersion, defaults.cliVersion);
|
||||
t.is(result.toolsSource, setup_codeql_1.ToolsSource.Download);
|
||||
if (result.toolsDownloadStatusReport) {
|
||||
assertDurationsInteger(t, result.toolsDownloadStatusReport);
|
||||
}
|
||||
const cachedVersions = toolcache.findAllVersions("CodeQL");
|
||||
t.is(cachedVersions.length, 2);
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)('downloads bundle if "latest" tools specified but not cached', async (t) => {
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
await installIntoToolcache({
|
||||
tagName: "codeql-bundle-20200601",
|
||||
isPinned: true,
|
||||
tmpDir,
|
||||
});
|
||||
(0, testing_utils_1.mockBundleDownloadApi)({
|
||||
tagName: defaults.bundleVersion,
|
||||
});
|
||||
const result = await codeql.setupCodeQL("latest", testing_utils_1.SAMPLE_DOTCOM_API_DETAILS, tmpDir, util.GitHubVariant.DOTCOM, testing_utils_1.SAMPLE_DEFAULT_CLI_VERSION, (0, logging_1.getRunnerLogger)(true), false);
|
||||
t.deepEqual(result.toolsVersion, defaults.cliVersion);
|
||||
t.is(result.toolsSource, setup_codeql_1.ToolsSource.Download);
|
||||
if (result.toolsDownloadStatusReport) {
|
||||
assertDurationsInteger(t, result.toolsDownloadStatusReport);
|
||||
}
|
||||
const cachedVersions = toolcache.findAllVersions("CodeQL");
|
||||
t.is(cachedVersions.length, 2);
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("bundle URL from another repo is cached as 0.0.0-bundleVersion", async (t) => {
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
mockApiDetails(testing_utils_1.SAMPLE_DOTCOM_API_DETAILS);
|
||||
sinon.stub(actionsUtil, "isRunningLocalAction").returns(true);
|
||||
const releasesApiMock = mockReleaseApi({
|
||||
assetNames: ["cli-version-2.14.6.txt"],
|
||||
tagName: "codeql-bundle-20230203",
|
||||
});
|
||||
(0, testing_utils_1.mockBundleDownloadApi)({
|
||||
repo: "codeql-testing/codeql-cli-nightlies",
|
||||
platformSpecific: false,
|
||||
tagName: "codeql-bundle-20230203",
|
||||
});
|
||||
const result = await codeql.setupCodeQL("https://github.com/codeql-testing/codeql-cli-nightlies/releases/download/codeql-bundle-20230203/codeql-bundle.tar.gz", testing_utils_1.SAMPLE_DOTCOM_API_DETAILS, tmpDir, util.GitHubVariant.DOTCOM, testing_utils_1.SAMPLE_DEFAULT_CLI_VERSION, (0, logging_1.getRunnerLogger)(true), false);
|
||||
t.is(result.toolsVersion, "0.0.0-20230203");
|
||||
t.is(result.toolsSource, setup_codeql_1.ToolsSource.Download);
|
||||
if (result.toolsDownloadStatusReport) {
|
||||
assertDurationsInteger(t, result.toolsDownloadStatusReport);
|
||||
}
|
||||
const cachedVersions = toolcache.findAllVersions("CodeQL");
|
||||
t.is(cachedVersions.length, 1);
|
||||
t.is(cachedVersions[0], "0.0.0-20230203");
|
||||
t.false(releasesApiMock.isDone());
|
||||
});
|
||||
});
|
||||
function assertDurationsInteger(t, statusReport) {
|
||||
t.assert(Number.isInteger(statusReport?.combinedDurationMs));
|
||||
if (statusReport.downloadDurationMs !== undefined) {
|
||||
t.assert(Number.isInteger(statusReport?.downloadDurationMs));
|
||||
t.assert(Number.isInteger(statusReport?.extractionDurationMs));
|
||||
}
|
||||
}
|
||||
(0, ava_1.default)("getExtraOptions works for explicit paths", (t) => {
|
||||
t.deepEqual(codeql.getExtraOptions({}, ["foo"], []), []);
|
||||
t.deepEqual(codeql.getExtraOptions({ foo: [42] }, ["foo"], []), ["42"]);
|
||||
t.deepEqual(codeql.getExtraOptions({ foo: { bar: [42] } }, ["foo", "bar"], []), ["42"]);
|
||||
});
|
||||
(0, ava_1.default)("getExtraOptions works for wildcards", (t) => {
|
||||
t.deepEqual(codeql.getExtraOptions({ "*": [42] }, ["foo"], []), ["42"]);
|
||||
});
|
||||
(0, ava_1.default)("getExtraOptions works for wildcards and explicit paths", (t) => {
|
||||
const o1 = { "*": [42], foo: [87] };
|
||||
t.deepEqual(codeql.getExtraOptions(o1, ["foo"], []), ["42", "87"]);
|
||||
const o2 = { "*": [42], foo: [87] };
|
||||
t.deepEqual(codeql.getExtraOptions(o2, ["foo", "bar"], []), ["42"]);
|
||||
const o3 = { "*": [42], foo: { "*": [87], bar: [99] } };
|
||||
const p = ["foo", "bar"];
|
||||
t.deepEqual(codeql.getExtraOptions(o3, p, []), ["42", "87", "99"]);
|
||||
});
|
||||
(0, ava_1.default)("getExtraOptions throws for bad content", (t) => {
|
||||
t.throws(() => codeql.getExtraOptions({ "*": 42 }, ["foo"], []));
|
||||
t.throws(() => codeql.getExtraOptions({ foo: 87 }, ["foo"], []));
|
||||
t.throws(() => codeql.getExtraOptions({ "*": [42], foo: { "*": 87, bar: [99] } }, ["foo", "bar"], []));
|
||||
});
|
||||
// Test macro for ensuring different variants of injected augmented configurations
|
||||
const injectedConfigMacro = ava_1.default.macro({
|
||||
exec: async (t, augmentationProperties, configOverride, expectedConfig) => {
|
||||
await util.withTmpDir(async (tempDir) => {
|
||||
const runnerConstructorStub = stubToolRunnerConstructor();
|
||||
const codeqlObject = await stubCodeql();
|
||||
const thisStubConfig = {
|
||||
...stubConfig,
|
||||
...configOverride,
|
||||
tempDir,
|
||||
augmentationProperties,
|
||||
};
|
||||
await codeqlObject.databaseInitCluster(thisStubConfig, "", undefined, undefined, (0, logging_1.getRunnerLogger)(true));
|
||||
const args = runnerConstructorStub.firstCall.args[1];
|
||||
// should have used an config file
|
||||
const configArg = args.find((arg) => arg.startsWith("--codescanning-config="));
|
||||
t.truthy(configArg, "Should have injected a codescanning config");
|
||||
const configFile = configArg.split("=")[1];
|
||||
const augmentedConfig = yaml.load(fs.readFileSync(configFile, "utf8"));
|
||||
t.deepEqual(augmentedConfig, expectedConfig);
|
||||
await (0, del_1.default)(configFile, { force: true });
|
||||
});
|
||||
},
|
||||
title: (providedTitle = "") => `databaseInitCluster() injected config: ${providedTitle}`,
|
||||
});
|
||||
(0, ava_1.default)("basic", injectedConfigMacro, {
|
||||
...config_utils_1.defaultAugmentationProperties,
|
||||
}, {}, {});
|
||||
(0, ava_1.default)("injected packs from input", injectedConfigMacro, {
|
||||
...config_utils_1.defaultAugmentationProperties,
|
||||
packsInput: ["xxx", "yyy"],
|
||||
}, {}, {
|
||||
packs: ["xxx", "yyy"],
|
||||
});
|
||||
(0, ava_1.default)("injected packs from input with existing packs combines", injectedConfigMacro, {
|
||||
...config_utils_1.defaultAugmentationProperties,
|
||||
packsInputCombines: true,
|
||||
packsInput: ["xxx", "yyy"],
|
||||
}, {
|
||||
originalUserInput: {
|
||||
packs: {
|
||||
cpp: ["codeql/something-else"],
|
||||
},
|
||||
},
|
||||
}, {
|
||||
packs: {
|
||||
cpp: ["codeql/something-else", "xxx", "yyy"],
|
||||
},
|
||||
});
|
||||
(0, ava_1.default)("injected packs from input with existing packs overrides", injectedConfigMacro, {
|
||||
...config_utils_1.defaultAugmentationProperties,
|
||||
packsInput: ["xxx", "yyy"],
|
||||
}, {
|
||||
originalUserInput: {
|
||||
packs: {
|
||||
cpp: ["codeql/something-else"],
|
||||
},
|
||||
},
|
||||
}, {
|
||||
packs: ["xxx", "yyy"],
|
||||
});
|
||||
// similar, but with queries
|
||||
(0, ava_1.default)("injected queries from input", injectedConfigMacro, {
|
||||
...config_utils_1.defaultAugmentationProperties,
|
||||
queriesInput: [{ uses: "xxx" }, { uses: "yyy" }],
|
||||
}, {}, {
|
||||
queries: [
|
||||
{
|
||||
uses: "xxx",
|
||||
},
|
||||
{
|
||||
uses: "yyy",
|
||||
},
|
||||
],
|
||||
});
|
||||
(0, ava_1.default)("injected queries from input overrides", injectedConfigMacro, {
|
||||
...config_utils_1.defaultAugmentationProperties,
|
||||
queriesInput: [{ uses: "xxx" }, { uses: "yyy" }],
|
||||
}, {
|
||||
originalUserInput: {
|
||||
queries: [{ uses: "zzz" }],
|
||||
},
|
||||
}, {
|
||||
queries: [
|
||||
{
|
||||
uses: "xxx",
|
||||
},
|
||||
{
|
||||
uses: "yyy",
|
||||
},
|
||||
],
|
||||
});
|
||||
(0, ava_1.default)("injected queries from input combines", injectedConfigMacro, {
|
||||
...config_utils_1.defaultAugmentationProperties,
|
||||
queriesInputCombines: true,
|
||||
queriesInput: [{ uses: "xxx" }, { uses: "yyy" }],
|
||||
}, {
|
||||
originalUserInput: {
|
||||
queries: [{ uses: "zzz" }],
|
||||
},
|
||||
}, {
|
||||
queries: [
|
||||
{
|
||||
uses: "zzz",
|
||||
},
|
||||
{
|
||||
uses: "xxx",
|
||||
},
|
||||
{
|
||||
uses: "yyy",
|
||||
},
|
||||
],
|
||||
});
|
||||
(0, ava_1.default)("injected queries from input combines 2", injectedConfigMacro, {
|
||||
...config_utils_1.defaultAugmentationProperties,
|
||||
queriesInputCombines: true,
|
||||
packsInputCombines: true,
|
||||
queriesInput: [{ uses: "xxx" }, { uses: "yyy" }],
|
||||
}, {}, {
|
||||
queries: [
|
||||
{
|
||||
uses: "xxx",
|
||||
},
|
||||
{
|
||||
uses: "yyy",
|
||||
},
|
||||
],
|
||||
});
|
||||
(0, ava_1.default)("injected queries and packs, but empty", injectedConfigMacro, {
|
||||
...config_utils_1.defaultAugmentationProperties,
|
||||
queriesInputCombines: true,
|
||||
packsInputCombines: true,
|
||||
queriesInput: [],
|
||||
packsInput: [],
|
||||
}, {
|
||||
originalUserInput: {
|
||||
packs: [],
|
||||
queries: [],
|
||||
},
|
||||
}, {});
|
||||
(0, ava_1.default)("passes a code scanning config AND qlconfig to the CLI", async (t) => {
|
||||
await util.withTmpDir(async (tempDir) => {
|
||||
const runnerConstructorStub = stubToolRunnerConstructor();
|
||||
const codeqlObject = await stubCodeql();
|
||||
await codeqlObject.databaseInitCluster({ ...stubConfig, tempDir }, "", undefined, "/path/to/qlconfig.yml", (0, logging_1.getRunnerLogger)(true));
|
||||
const args = runnerConstructorStub.firstCall.args[1];
|
||||
// should have used a config file
|
||||
const hasCodeScanningConfigArg = args.some((arg) => arg.startsWith("--codescanning-config="));
|
||||
t.true(hasCodeScanningConfigArg, "Should have injected a qlconfig");
|
||||
// should have passed a qlconfig file
|
||||
const hasQlconfigArg = args.some((arg) => arg.startsWith("--qlconfig-file="));
|
||||
t.truthy(hasQlconfigArg, "Should have injected a codescanning config");
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("does not pass a qlconfig to the CLI when it is undefined", async (t) => {
|
||||
await util.withTmpDir(async (tempDir) => {
|
||||
const runnerConstructorStub = stubToolRunnerConstructor();
|
||||
const codeqlObject = await stubCodeql();
|
||||
await codeqlObject.databaseInitCluster({ ...stubConfig, tempDir }, "", undefined, undefined, // undefined qlconfigFile
|
||||
(0, logging_1.getRunnerLogger)(true));
|
||||
const args = runnerConstructorStub.firstCall.args[1];
|
||||
const hasQlconfigArg = args.some((arg) => arg.startsWith("--qlconfig-file="));
|
||||
t.false(hasQlconfigArg, "should NOT have injected a qlconfig");
|
||||
});
|
||||
});
|
||||
const NEW_ANALYSIS_SUMMARY_TEST_CASES = [
|
||||
{
|
||||
codeqlVersion: (0, testing_utils_1.makeVersionInfo)("2.15.0", {
|
||||
[tools_features_1.ToolsFeature.AnalysisSummaryV2IsDefault]: true,
|
||||
}),
|
||||
githubVersion: {
|
||||
type: util.GitHubVariant.DOTCOM,
|
||||
},
|
||||
flagPassed: false,
|
||||
negativeFlagPassed: false,
|
||||
},
|
||||
{
|
||||
codeqlVersion: (0, testing_utils_1.makeVersionInfo)("2.15.0"),
|
||||
githubVersion: {
|
||||
type: util.GitHubVariant.DOTCOM,
|
||||
},
|
||||
flagPassed: true,
|
||||
negativeFlagPassed: false,
|
||||
},
|
||||
{
|
||||
codeqlVersion: (0, testing_utils_1.makeVersionInfo)("2.15.0"),
|
||||
githubVersion: {
|
||||
type: util.GitHubVariant.GHES,
|
||||
version: "3.10.0",
|
||||
},
|
||||
flagPassed: true,
|
||||
negativeFlagPassed: false,
|
||||
},
|
||||
];
|
||||
for (const { codeqlVersion, flagPassed, githubVersion, negativeFlagPassed, } of NEW_ANALYSIS_SUMMARY_TEST_CASES) {
|
||||
(0, ava_1.default)(`database interpret-results passes ${flagPassed
|
||||
? "--new-analysis-summary"
|
||||
: negativeFlagPassed
|
||||
? "--no-new-analysis-summary"
|
||||
: "nothing"} for CodeQL version ${JSON.stringify(codeqlVersion)} and ${util.GitHubVariant[githubVersion.type]} ${githubVersion.version ? ` ${githubVersion.version}` : ""}`, async (t) => {
|
||||
const runnerConstructorStub = stubToolRunnerConstructor();
|
||||
const codeqlObject = await codeql.getCodeQLForTesting();
|
||||
sinon.stub(codeqlObject, "getVersion").resolves(codeqlVersion);
|
||||
// io throws because of the test CodeQL object.
|
||||
sinon.stub(io, "which").resolves("");
|
||||
await codeqlObject.databaseInterpretResults("", [], "", "", "", "-v", undefined, "", Object.assign({}, stubConfig, { gitHubVersion: githubVersion }), (0, testing_utils_1.createFeatures)([]));
|
||||
const actualArgs = runnerConstructorStub.firstCall.args[1];
|
||||
t.is(actualArgs.includes("--new-analysis-summary"), flagPassed, `--new-analysis-summary should${flagPassed ? "" : "n't"} be passed`);
|
||||
t.is(actualArgs.includes("--no-new-analysis-summary"), negativeFlagPassed, `--no-new-analysis-summary should${negativeFlagPassed ? "" : "n't"} be passed`);
|
||||
});
|
||||
}
|
||||
(0, ava_1.default)("runTool summarizes several fatal errors", async (t) => {
|
||||
const heapError = "A fatal error occurred: Evaluator heap must be at least 384.00 MiB";
|
||||
const datasetImportError = "A fatal error occurred: Dataset import for /home/runner/work/_temp/codeql_databases/javascript/db-javascript failed with code 2";
|
||||
const cliStderr = `Running TRAP import for CodeQL database at /home/runner/work/_temp/codeql_databases/javascript...\n` +
|
||||
`${heapError}\n${datasetImportError}.`;
|
||||
stubToolRunnerConstructor(32, cliStderr);
|
||||
const codeqlObject = await stubCodeql();
|
||||
// io throws because of the test CodeQL object.
|
||||
sinon.stub(io, "which").resolves("");
|
||||
await t.throwsAsync(async () => await codeqlObject.finalizeDatabase("db", "--threads=2", "--ram=2048", false), {
|
||||
instanceOf: util.ConfigurationError,
|
||||
message: new RegExp('Encountered a fatal error while running \\"codeql-for-testing database finalize --finalize-dataset --threads=2 --ram=2048 db\\"\\. ' +
|
||||
`Exit code was 32 and error was: ${datasetImportError.replaceAll(".", "\\.")}\\. Context: ${heapError.replaceAll(".", "\\.")}\\. See the logs for more details\\.`),
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("runTool summarizes autobuilder errors", async (t) => {
|
||||
const stderr = `
|
||||
[2019-09-18 12:00:00] [autobuild] A non-error message
|
||||
[2019-09-18 12:00:00] Untagged message
|
||||
[2019-09-18 12:00:00] [autobuild] [ERROR] Start of the error message
|
||||
[2019-09-18 12:00:00] [autobuild] An interspersed non-error message
|
||||
[2019-09-18 12:00:01] [autobuild] [ERROR] Some more context about the error message
|
||||
[2019-09-18 12:00:01] [autobuild] [ERROR] continued
|
||||
[2019-09-18 12:00:01] [autobuild] [ERROR] and finished here.
|
||||
[2019-09-18 12:00:01] [autobuild] A non-error message
|
||||
`;
|
||||
stubToolRunnerConstructor(1, stderr);
|
||||
const codeqlObject = await codeql.getCodeQLForTesting();
|
||||
sinon.stub(codeqlObject, "getVersion").resolves((0, testing_utils_1.makeVersionInfo)("2.17.6"));
|
||||
sinon.stub(codeqlObject, "resolveExtractor").resolves("/path/to/extractor");
|
||||
// io throws because of the test CodeQL object.
|
||||
sinon.stub(io, "which").resolves("");
|
||||
await t.throwsAsync(async () => await codeqlObject.runAutobuild(stubConfig, languages_1.KnownLanguage.java), {
|
||||
instanceOf: util.ConfigurationError,
|
||||
message: "We were unable to automatically build your code. Please provide manual build steps. " +
|
||||
`See ${doc_url_1.DocUrl.AUTOMATIC_BUILD_FAILED} for more information. ` +
|
||||
"Encountered the following error: Start of the error message\n" +
|
||||
" Some more context about the error message\n" +
|
||||
" continued\n" +
|
||||
" and finished here.",
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("runTool truncates long autobuilder errors", async (t) => {
|
||||
const stderr = Array.from({ length: 20 }, (_, i) => `[2019-09-18 12:00:00] [autobuild] [ERROR] line${i + 1}`).join("\n");
|
||||
stubToolRunnerConstructor(1, stderr);
|
||||
const codeqlObject = await stubCodeql();
|
||||
sinon.stub(codeqlObject, "resolveExtractor").resolves("/path/to/extractor");
|
||||
// io throws because of the test CodeQL object.
|
||||
sinon.stub(io, "which").resolves("");
|
||||
await t.throwsAsync(async () => await codeqlObject.runAutobuild(stubConfig, languages_1.KnownLanguage.java), {
|
||||
instanceOf: util.ConfigurationError,
|
||||
message: "We were unable to automatically build your code. Please provide manual build steps. " +
|
||||
`See ${doc_url_1.DocUrl.AUTOMATIC_BUILD_FAILED} for more information. ` +
|
||||
"Encountered the following error: " +
|
||||
`${Array.from({ length: 10 }, (_, i) => `line${i + 1}`).join("\n")}\n(truncated)`,
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("runTool recognizes fatal internal errors", async (t) => {
|
||||
const stderr = `
|
||||
[11/31 eval 8m19s] Evaluation done; writing results to codeql/go-queries/Security/CWE-020/MissingRegexpAnchor.bqrs.
|
||||
Oops! A fatal internal error occurred. Details:
|
||||
com.semmle.util.exception.CatastrophicError: An error occurred while evaluating ControlFlowGraph::ControlFlow::Root.isRootOf/1#dispred#f610e6ed/2@86282cc8
|
||||
Severe disk cache trouble (corruption or out of space) at /home/runner/work/_temp/codeql_databases/go/db-go/default/cache/pages/28/33.pack: Failed to write item to disk`;
|
||||
stubToolRunnerConstructor(1, stderr);
|
||||
const codeqlObject = await codeql.getCodeQLForTesting();
|
||||
sinon.stub(codeqlObject, "getVersion").resolves((0, testing_utils_1.makeVersionInfo)("2.17.6"));
|
||||
sinon.stub(codeqlObject, "resolveExtractor").resolves("/path/to/extractor");
|
||||
// io throws because of the test CodeQL object.
|
||||
sinon.stub(io, "which").resolves("");
|
||||
await t.throwsAsync(async () => await codeqlObject.databaseRunQueries(stubConfig.dbLocation, []), {
|
||||
instanceOf: cli_errors_1.CliError,
|
||||
message: `Encountered a fatal error while running "codeql-for-testing database run-queries --intra-layer-parallelism --min-disk-free=1024 -v". Exit code was 1 and error was: Oops! A fatal internal error occurred. Details:
|
||||
com.semmle.util.exception.CatastrophicError: An error occurred while evaluating ControlFlowGraph::ControlFlow::Root.isRootOf/1#dispred#f610e6ed/2@86282cc8
|
||||
Severe disk cache trouble (corruption or out of space) at /home/runner/work/_temp/codeql_databases/go/db-go/default/cache/pages/28/33.pack: Failed to write item to disk. See the logs for more details.`,
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("runTool outputs last line of stderr if fatal error could not be found", async (t) => {
|
||||
const cliStderr = "line1\nline2\nline3\nline4\nline5";
|
||||
stubToolRunnerConstructor(32, cliStderr);
|
||||
const codeqlObject = await stubCodeql();
|
||||
// io throws because of the test CodeQL object.
|
||||
sinon.stub(io, "which").resolves("");
|
||||
await t.throwsAsync(async () => await codeqlObject.finalizeDatabase("db", "--threads=2", "--ram=2048", false), {
|
||||
instanceOf: util.ConfigurationError,
|
||||
message: new RegExp('Encountered a fatal error while running \\"codeql-for-testing database finalize --finalize-dataset --threads=2 --ram=2048 db\\"\\. ' +
|
||||
"Exit code was 32 and last log line was: line5\\. See the logs for more details\\."),
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("Avoids duplicating --overwrite flag if specified in CODEQL_ACTION_EXTRA_OPTIONS", async (t) => {
|
||||
const runnerConstructorStub = stubToolRunnerConstructor();
|
||||
const codeqlObject = await stubCodeql();
|
||||
// io throws because of the test CodeQL object.
|
||||
sinon.stub(io, "which").resolves("");
|
||||
process.env["CODEQL_ACTION_EXTRA_OPTIONS"] =
|
||||
'{ "database": { "init": ["--overwrite"] } }';
|
||||
await codeqlObject.databaseInitCluster(stubConfig, "sourceRoot", undefined, undefined, (0, logging_1.getRunnerLogger)(false));
|
||||
t.true(runnerConstructorStub.calledOnce);
|
||||
const args = runnerConstructorStub.firstCall.args[1];
|
||||
t.is(args.filter((option) => option === "--overwrite").length, 1, "--overwrite should only be passed once");
|
||||
// Clean up
|
||||
const configArg = args.find((arg) => arg.startsWith("--codescanning-config="));
|
||||
t.truthy(configArg, "Should have injected a codescanning config");
|
||||
const configFile = configArg.split("=")[1];
|
||||
await (0, del_1.default)(configFile, { force: true });
|
||||
});
|
||||
function stubToolRunnerConstructor(exitCode = 0, stderr) {
|
||||
const runnerObjectStub = sinon.createStubInstance(toolrunner.ToolRunner);
|
||||
const runnerConstructorStub = sinon.stub(toolrunner, "ToolRunner");
|
||||
let stderrListener = undefined;
|
||||
runnerConstructorStub.callsFake((_cmd, _args, options) => {
|
||||
stderrListener = options.listeners?.stderr;
|
||||
return runnerObjectStub;
|
||||
});
|
||||
runnerObjectStub.exec.callsFake(async () => {
|
||||
if (stderrListener !== undefined && stderr !== undefined) {
|
||||
stderrListener(Buffer.from(stderr));
|
||||
}
|
||||
return exitCode;
|
||||
});
|
||||
return runnerConstructorStub;
|
||||
}
|
||||
//# sourceMappingURL=codeql.test.js.map
|
||||
File diff suppressed because one or more lines are too long
923
lib/config-utils.js
generated
923
lib/config-utils.js
generated
@@ -1,923 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.defaultAugmentationProperties = void 0;
|
||||
exports.getPacksStrInvalid = getPacksStrInvalid;
|
||||
exports.getConfigFileOutsideWorkspaceErrorMessage = getConfigFileOutsideWorkspaceErrorMessage;
|
||||
exports.getConfigFileDoesNotExistErrorMessage = getConfigFileDoesNotExistErrorMessage;
|
||||
exports.getConfigFileRepoFormatInvalidMessage = getConfigFileRepoFormatInvalidMessage;
|
||||
exports.getConfigFileFormatInvalidMessage = getConfigFileFormatInvalidMessage;
|
||||
exports.getConfigFileDirectoryGivenMessage = getConfigFileDirectoryGivenMessage;
|
||||
exports.getNoLanguagesError = getNoLanguagesError;
|
||||
exports.getUnknownLanguagesError = getUnknownLanguagesError;
|
||||
exports.getSupportedLanguageMap = getSupportedLanguageMap;
|
||||
exports.hasActionsWorkflows = hasActionsWorkflows;
|
||||
exports.getRawLanguagesInRepo = getRawLanguagesInRepo;
|
||||
exports.getLanguages = getLanguages;
|
||||
exports.getRawLanguagesNoAutodetect = getRawLanguagesNoAutodetect;
|
||||
exports.getRawLanguages = getRawLanguages;
|
||||
exports.getDefaultConfig = getDefaultConfig;
|
||||
exports.calculateAugmentation = calculateAugmentation;
|
||||
exports.getOverlayDatabaseMode = getOverlayDatabaseMode;
|
||||
exports.parsePacksFromInput = parsePacksFromInput;
|
||||
exports.parsePacksSpecification = parsePacksSpecification;
|
||||
exports.validatePackSpecification = validatePackSpecification;
|
||||
exports.initConfig = initConfig;
|
||||
exports.parseRegistriesWithoutCredentials = parseRegistriesWithoutCredentials;
|
||||
exports.getPathToParsedConfigFile = getPathToParsedConfigFile;
|
||||
exports.getConfig = getConfig;
|
||||
exports.generateRegistries = generateRegistries;
|
||||
exports.wrapEnvironment = wrapEnvironment;
|
||||
exports.parseBuildModeInput = parseBuildModeInput;
|
||||
exports.generateCodeScanningConfig = generateCodeScanningConfig;
|
||||
const fs = __importStar(require("fs"));
|
||||
const path = __importStar(require("path"));
|
||||
const perf_hooks_1 = require("perf_hooks");
|
||||
const yaml = __importStar(require("js-yaml"));
|
||||
const semver = __importStar(require("semver"));
|
||||
const actions_util_1 = require("./actions-util");
|
||||
const api = __importStar(require("./api-client"));
|
||||
const caching_utils_1 = require("./caching-utils");
|
||||
const diff_informed_analysis_utils_1 = require("./diff-informed-analysis-utils");
|
||||
const feature_flags_1 = require("./feature-flags");
|
||||
const git_utils_1 = require("./git-utils");
|
||||
const languages_1 = require("./languages");
|
||||
const overlay_database_utils_1 = require("./overlay-database-utils");
|
||||
const trap_caching_1 = require("./trap-caching");
|
||||
const util_1 = require("./util");
|
||||
// Property names from the user-supplied config file.
|
||||
const PACKS_PROPERTY = "packs";
|
||||
/**
|
||||
* The default, empty augmentation properties. This is most useful
|
||||
* for tests.
|
||||
*/
|
||||
exports.defaultAugmentationProperties = {
|
||||
queriesInputCombines: false,
|
||||
packsInputCombines: false,
|
||||
packsInput: undefined,
|
||||
queriesInput: undefined,
|
||||
qualityQueriesInput: undefined,
|
||||
extraQueryExclusions: [],
|
||||
overlayDatabaseMode: overlay_database_utils_1.OverlayDatabaseMode.None,
|
||||
useOverlayDatabaseCaching: false,
|
||||
};
|
||||
function getPacksStrInvalid(packStr, configFile) {
|
||||
return configFile
|
||||
? getConfigFilePropertyError(configFile, PACKS_PROPERTY, `"${packStr}" is not a valid pack`)
|
||||
: `"${packStr}" is not a valid pack`;
|
||||
}
|
||||
function getConfigFileOutsideWorkspaceErrorMessage(configFile) {
|
||||
return `The configuration file "${configFile}" is outside of the workspace`;
|
||||
}
|
||||
function getConfigFileDoesNotExistErrorMessage(configFile) {
|
||||
return `The configuration file "${configFile}" does not exist`;
|
||||
}
|
||||
function getConfigFileRepoFormatInvalidMessage(configFile) {
|
||||
let error = `The configuration file "${configFile}" is not a supported remote file reference.`;
|
||||
error += " Expected format <owner>/<repository>/<file-path>@<ref>";
|
||||
return error;
|
||||
}
|
||||
function getConfigFileFormatInvalidMessage(configFile) {
|
||||
return `The configuration file "${configFile}" could not be read`;
|
||||
}
|
||||
function getConfigFileDirectoryGivenMessage(configFile) {
|
||||
return `The configuration file "${configFile}" looks like a directory, not a file`;
|
||||
}
|
||||
function getConfigFilePropertyError(configFile, property, error) {
|
||||
if (configFile === undefined) {
|
||||
return `The workflow property "${property}" is invalid: ${error}`;
|
||||
}
|
||||
else {
|
||||
return `The configuration file "${configFile}" is invalid: property "${property}" ${error}`;
|
||||
}
|
||||
}
|
||||
function getNoLanguagesError() {
|
||||
return ("Did not detect any languages to analyze. " +
|
||||
"Please update input in workflow or check that GitHub detects the correct languages in your repository.");
|
||||
}
|
||||
function getUnknownLanguagesError(languages) {
|
||||
return `Did not recognize the following languages: ${languages.join(", ")}`;
|
||||
}
|
||||
async function getSupportedLanguageMap(codeql) {
|
||||
const resolveResult = await codeql.betterResolveLanguages();
|
||||
const supportedLanguages = {};
|
||||
// Populate canonical language names
|
||||
for (const extractor of Object.keys(resolveResult.extractors)) {
|
||||
// Require the language to be a known language.
|
||||
// This is a temporary workaround since we have extractors that are not
|
||||
// supported languages, such as `csv`, `html`, `properties`, `xml`, and
|
||||
// `yaml`. We should replace this with a more robust solution in the future.
|
||||
if (languages_1.KnownLanguage[extractor] !== undefined) {
|
||||
supportedLanguages[extractor] = extractor;
|
||||
}
|
||||
}
|
||||
// Populate language aliases
|
||||
if (resolveResult.aliases) {
|
||||
for (const [alias, extractor] of Object.entries(resolveResult.aliases)) {
|
||||
supportedLanguages[alias] = extractor;
|
||||
}
|
||||
}
|
||||
return supportedLanguages;
|
||||
}
|
||||
const baseWorkflowsPath = ".github/workflows";
|
||||
/**
|
||||
* Determines if there exists a `.github/workflows` directory with at least
|
||||
* one file in it, which we use as an indicator that there are Actions
|
||||
* workflows in the workspace. This doesn't perfectly detect whether there
|
||||
* are actually workflows, but should be a good approximation.
|
||||
*
|
||||
* Alternatively, we could check specifically for yaml files, or call the
|
||||
* API to check if it knows about workflows.
|
||||
*
|
||||
* @returns True if the non-empty directory exists, false if not.
|
||||
*/
|
||||
function hasActionsWorkflows(sourceRoot) {
|
||||
const workflowsPath = path.resolve(sourceRoot, baseWorkflowsPath);
|
||||
const stats = fs.lstatSync(workflowsPath);
|
||||
return (stats !== undefined &&
|
||||
stats.isDirectory() &&
|
||||
fs.readdirSync(workflowsPath).length > 0);
|
||||
}
|
||||
/**
|
||||
* Gets the set of languages in the current repository.
|
||||
*/
|
||||
async function getRawLanguagesInRepo(repository, sourceRoot, logger) {
|
||||
logger.debug(`Automatically detecting languages (${repository.owner}/${repository.repo})`);
|
||||
const response = await api.getApiClient().rest.repos.listLanguages({
|
||||
owner: repository.owner,
|
||||
repo: repository.repo,
|
||||
});
|
||||
logger.debug(`Languages API response: ${JSON.stringify(response)}`);
|
||||
const result = Object.keys(response.data).map((language) => language.trim().toLowerCase());
|
||||
if (hasActionsWorkflows(sourceRoot)) {
|
||||
logger.debug(`Found a .github/workflows directory`);
|
||||
result.push("actions");
|
||||
}
|
||||
logger.debug(`Raw languages in repository: ${result.join(", ")}`);
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Get the languages to analyse.
|
||||
*
|
||||
* The result is obtained from the action input parameter 'languages' if that
|
||||
* has been set, otherwise it is deduced as all languages in the repo that
|
||||
* can be analysed.
|
||||
*
|
||||
* If no languages could be detected from either the workflow or the repository
|
||||
* then throw an error.
|
||||
*/
|
||||
async function getLanguages(codeql, languagesInput, repository, sourceRoot, logger) {
|
||||
// Obtain languages without filtering them.
|
||||
const { rawLanguages, autodetected } = await getRawLanguages(languagesInput, repository, sourceRoot, logger);
|
||||
const languageMap = await getSupportedLanguageMap(codeql);
|
||||
const languagesSet = new Set();
|
||||
const unknownLanguages = [];
|
||||
// Make sure they are supported
|
||||
for (const language of rawLanguages) {
|
||||
const extractorName = languageMap[language];
|
||||
if (extractorName === undefined) {
|
||||
unknownLanguages.push(language);
|
||||
}
|
||||
else {
|
||||
languagesSet.add(extractorName);
|
||||
}
|
||||
}
|
||||
const languages = Array.from(languagesSet);
|
||||
if (!autodetected && unknownLanguages.length > 0) {
|
||||
throw new util_1.ConfigurationError(getUnknownLanguagesError(unknownLanguages));
|
||||
}
|
||||
// If the languages parameter was not given and no languages were
|
||||
// detected then fail here as this is a workflow configuration error.
|
||||
if (languages.length === 0) {
|
||||
throw new util_1.ConfigurationError(getNoLanguagesError());
|
||||
}
|
||||
if (autodetected) {
|
||||
logger.info(`Autodetected languages: ${languages.join(", ")}`);
|
||||
}
|
||||
else {
|
||||
logger.info(`Languages from configuration: ${languages.join(", ")}`);
|
||||
}
|
||||
return languages;
|
||||
}
|
||||
function getRawLanguagesNoAutodetect(languagesInput) {
|
||||
return (languagesInput || "")
|
||||
.split(",")
|
||||
.map((x) => x.trim().toLowerCase())
|
||||
.filter((x) => x.length > 0);
|
||||
}
|
||||
/**
|
||||
* Gets the set of languages in the current repository without checking to
|
||||
* see if these languages are actually supported by CodeQL.
|
||||
*
|
||||
* @param languagesInput The languages from the workflow input
|
||||
* @param repository the owner/name of the repository
|
||||
* @param logger a logger
|
||||
* @returns A tuple containing a list of languages in this repository that might be
|
||||
* analyzable and whether or not this list was determined automatically.
|
||||
*/
|
||||
async function getRawLanguages(languagesInput, repository, sourceRoot, logger) {
|
||||
// If the user has specified languages, use those.
|
||||
const languagesFromInput = getRawLanguagesNoAutodetect(languagesInput);
|
||||
if (languagesFromInput.length > 0) {
|
||||
return { rawLanguages: languagesFromInput, autodetected: false };
|
||||
}
|
||||
// Otherwise, autodetect languages in the repository.
|
||||
return {
|
||||
rawLanguages: await getRawLanguagesInRepo(repository, sourceRoot, logger),
|
||||
autodetected: true,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Get the default config, populated without user configuration file.
|
||||
*/
|
||||
async function getDefaultConfig({ languagesInput, queriesInput, qualityQueriesInput, packsInput, buildModeInput, dbLocation, trapCachingEnabled, dependencyCachingEnabled, debugMode, debugArtifactName, debugDatabaseName, repository, tempDir, codeql, sourceRoot, githubVersion, features, logger, }) {
|
||||
const languages = await getLanguages(codeql, languagesInput, repository, sourceRoot, logger);
|
||||
const buildMode = await parseBuildModeInput(buildModeInput, languages, features, logger);
|
||||
const augmentationProperties = await calculateAugmentation(packsInput, queriesInput, qualityQueriesInput, languages);
|
||||
const { trapCaches, trapCacheDownloadTime } = await downloadCacheWithTime(trapCachingEnabled, codeql, languages, logger);
|
||||
return {
|
||||
languages,
|
||||
buildMode,
|
||||
originalUserInput: {},
|
||||
tempDir,
|
||||
codeQLCmd: codeql.getPath(),
|
||||
gitHubVersion: githubVersion,
|
||||
dbLocation: dbLocationOrDefault(dbLocation, tempDir),
|
||||
debugMode,
|
||||
debugArtifactName,
|
||||
debugDatabaseName,
|
||||
augmentationProperties,
|
||||
trapCaches,
|
||||
trapCacheDownloadTime,
|
||||
dependencyCachingEnabled: (0, caching_utils_1.getCachingKind)(dependencyCachingEnabled),
|
||||
};
|
||||
}
|
||||
async function downloadCacheWithTime(trapCachingEnabled, codeQL, languages, logger) {
|
||||
let trapCaches = {};
|
||||
let trapCacheDownloadTime = 0;
|
||||
if (trapCachingEnabled) {
|
||||
const start = perf_hooks_1.performance.now();
|
||||
trapCaches = await (0, trap_caching_1.downloadTrapCaches)(codeQL, languages, logger);
|
||||
trapCacheDownloadTime = perf_hooks_1.performance.now() - start;
|
||||
}
|
||||
return { trapCaches, trapCacheDownloadTime };
|
||||
}
|
||||
async function loadUserConfig(configFile, workspacePath, apiDetails, tempDir) {
|
||||
if (isLocal(configFile)) {
|
||||
if (configFile !== userConfigFromActionPath(tempDir)) {
|
||||
// If the config file is not generated by the Action, it should be relative to the workspace.
|
||||
configFile = path.resolve(workspacePath, configFile);
|
||||
// Error if the config file is now outside of the workspace
|
||||
if (!(configFile + path.sep).startsWith(workspacePath + path.sep)) {
|
||||
throw new util_1.ConfigurationError(getConfigFileOutsideWorkspaceErrorMessage(configFile));
|
||||
}
|
||||
}
|
||||
return getLocalConfig(configFile);
|
||||
}
|
||||
else {
|
||||
return await getRemoteConfig(configFile, apiDetails);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Calculates how the codeql config file needs to be augmented before passing
|
||||
* it to the CLI. The reason this is necessary is the codeql-action can be called
|
||||
* with extra inputs from the workflow. These inputs are not part of the config
|
||||
* and the CLI does not know about these inputs so we need to inject them into
|
||||
* the config file sent to the CLI.
|
||||
*
|
||||
* @param rawPacksInput The packs input from the action configuration.
|
||||
* @param rawQueriesInput The queries input from the action configuration.
|
||||
* @param languages The languages that the config file is for. If the packs input
|
||||
* is non-empty, then there must be exactly one language. Otherwise, an
|
||||
* error is thrown.
|
||||
*
|
||||
* @returns The properties that need to be augmented in the config file.
|
||||
*
|
||||
* @throws An error if the packs input is non-empty and the languages input does
|
||||
* not have exactly one language.
|
||||
*/
|
||||
// exported for testing.
|
||||
async function calculateAugmentation(rawPacksInput, rawQueriesInput, rawQualityQueriesInput, languages) {
|
||||
const packsInputCombines = shouldCombine(rawPacksInput);
|
||||
const packsInput = parsePacksFromInput(rawPacksInput, languages, packsInputCombines);
|
||||
const queriesInputCombines = shouldCombine(rawQueriesInput);
|
||||
const queriesInput = parseQueriesFromInput(rawQueriesInput, queriesInputCombines);
|
||||
const qualityQueriesInput = parseQueriesFromInput(rawQualityQueriesInput, false);
|
||||
return {
|
||||
packsInputCombines,
|
||||
packsInput: packsInput?.[languages[0]],
|
||||
queriesInput,
|
||||
queriesInputCombines,
|
||||
qualityQueriesInput,
|
||||
extraQueryExclusions: [],
|
||||
overlayDatabaseMode: overlay_database_utils_1.OverlayDatabaseMode.None,
|
||||
useOverlayDatabaseCaching: false,
|
||||
};
|
||||
}
|
||||
function parseQueriesFromInput(rawQueriesInput, queriesInputCombines) {
|
||||
if (!rawQueriesInput) {
|
||||
return undefined;
|
||||
}
|
||||
const trimmedInput = queriesInputCombines
|
||||
? rawQueriesInput.trim().slice(1).trim()
|
||||
: (rawQueriesInput?.trim() ?? "");
|
||||
if (queriesInputCombines && trimmedInput.length === 0) {
|
||||
throw new util_1.ConfigurationError(getConfigFilePropertyError(undefined, "queries", "A '+' was used in the 'queries' input to specify that you wished to add some packs to your CodeQL analysis. However, no packs were specified. Please either remove the '+' or specify some packs."));
|
||||
}
|
||||
return trimmedInput.split(",").map((query) => ({ uses: query.trim() }));
|
||||
}
|
||||
const OVERLAY_ANALYSIS_FEATURES = {
|
||||
actions: feature_flags_1.Feature.OverlayAnalysisActions,
|
||||
cpp: feature_flags_1.Feature.OverlayAnalysisCpp,
|
||||
csharp: feature_flags_1.Feature.OverlayAnalysisCsharp,
|
||||
go: feature_flags_1.Feature.OverlayAnalysisGo,
|
||||
java: feature_flags_1.Feature.OverlayAnalysisJava,
|
||||
javascript: feature_flags_1.Feature.OverlayAnalysisJavascript,
|
||||
python: feature_flags_1.Feature.OverlayAnalysisPython,
|
||||
ruby: feature_flags_1.Feature.OverlayAnalysisRuby,
|
||||
rust: feature_flags_1.Feature.OverlayAnalysisRust,
|
||||
swift: feature_flags_1.Feature.OverlayAnalysisSwift,
|
||||
};
|
||||
const OVERLAY_ANALYSIS_CODE_SCANNING_FEATURES = {
|
||||
actions: feature_flags_1.Feature.OverlayAnalysisCodeScanningActions,
|
||||
cpp: feature_flags_1.Feature.OverlayAnalysisCodeScanningCpp,
|
||||
csharp: feature_flags_1.Feature.OverlayAnalysisCodeScanningCsharp,
|
||||
go: feature_flags_1.Feature.OverlayAnalysisCodeScanningGo,
|
||||
java: feature_flags_1.Feature.OverlayAnalysisCodeScanningJava,
|
||||
javascript: feature_flags_1.Feature.OverlayAnalysisCodeScanningJavascript,
|
||||
python: feature_flags_1.Feature.OverlayAnalysisCodeScanningPython,
|
||||
ruby: feature_flags_1.Feature.OverlayAnalysisCodeScanningRuby,
|
||||
rust: feature_flags_1.Feature.OverlayAnalysisCodeScanningRust,
|
||||
swift: feature_flags_1.Feature.OverlayAnalysisCodeScanningSwift,
|
||||
};
|
||||
async function isOverlayAnalysisFeatureEnabled(repository, features, codeql, languages, codeScanningConfig) {
|
||||
// TODO: Remove the repository owner check once support for overlay analysis
|
||||
// stabilizes, and no more backward-incompatible changes are expected.
|
||||
if (!["github", "dsp-testing"].includes(repository.owner)) {
|
||||
return false;
|
||||
}
|
||||
if (!(await features.getValue(feature_flags_1.Feature.OverlayAnalysis, codeql))) {
|
||||
return false;
|
||||
}
|
||||
let enableForCodeScanningOnly = false;
|
||||
for (const language of languages) {
|
||||
const feature = OVERLAY_ANALYSIS_FEATURES[language];
|
||||
if (feature && (await features.getValue(feature, codeql))) {
|
||||
continue;
|
||||
}
|
||||
const codeScanningFeature = OVERLAY_ANALYSIS_CODE_SCANNING_FEATURES[language];
|
||||
if (codeScanningFeature &&
|
||||
(await features.getValue(codeScanningFeature, codeql))) {
|
||||
enableForCodeScanningOnly = true;
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (enableForCodeScanningOnly) {
|
||||
// A code-scanning configuration runs only the (default) code-scanning suite
|
||||
// if the default queries are not disabled, and no packs, queries, or
|
||||
// query-filters are specified.
|
||||
return (codeScanningConfig["disable-default-queries"] !== true &&
|
||||
codeScanningConfig.packs === undefined &&
|
||||
codeScanningConfig.queries === undefined &&
|
||||
codeScanningConfig["query-filters"] === undefined);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Calculate and validate the overlay database mode and caching to use.
|
||||
*
|
||||
* - If the environment variable `CODEQL_OVERLAY_DATABASE_MODE` is set, use it.
|
||||
* In this case, the workflow is responsible for managing database storage and
|
||||
* retrieval, and the action will not perform overlay database caching. Think
|
||||
* of it as a "manual control" mode where the calling workflow is responsible
|
||||
* for making sure that everything is set up correctly.
|
||||
* - Otherwise, if `Feature.OverlayAnalysis` is enabled, calculate the mode
|
||||
* based on what we are analyzing. Think of it as a "automatic control" mode
|
||||
* where the action will do the right thing by itself.
|
||||
* - If we are analyzing a pull request, use `Overlay` with caching.
|
||||
* - If we are analyzing the default branch, use `OverlayBase` with caching.
|
||||
* - Otherwise, use `None`.
|
||||
*
|
||||
* For `Overlay` and `OverlayBase`, the function performs further checks and
|
||||
* reverts to `None` if any check should fail.
|
||||
*
|
||||
* @returns An object containing the overlay database mode and whether the
|
||||
* action should perform overlay-base database caching.
|
||||
*/
|
||||
async function getOverlayDatabaseMode(codeql, repository, features, languages, sourceRoot, buildMode, codeScanningConfig, logger) {
|
||||
let overlayDatabaseMode = overlay_database_utils_1.OverlayDatabaseMode.None;
|
||||
let useOverlayDatabaseCaching = false;
|
||||
const modeEnv = process.env.CODEQL_OVERLAY_DATABASE_MODE;
|
||||
// Any unrecognized CODEQL_OVERLAY_DATABASE_MODE value will be ignored and
|
||||
// treated as if the environment variable was not set.
|
||||
if (modeEnv === overlay_database_utils_1.OverlayDatabaseMode.Overlay ||
|
||||
modeEnv === overlay_database_utils_1.OverlayDatabaseMode.OverlayBase ||
|
||||
modeEnv === overlay_database_utils_1.OverlayDatabaseMode.None) {
|
||||
overlayDatabaseMode = modeEnv;
|
||||
logger.info(`Setting overlay database mode to ${overlayDatabaseMode} ` +
|
||||
"from the CODEQL_OVERLAY_DATABASE_MODE environment variable.");
|
||||
}
|
||||
else if (await isOverlayAnalysisFeatureEnabled(repository, features, codeql, languages, codeScanningConfig)) {
|
||||
if ((0, actions_util_1.isAnalyzingPullRequest)()) {
|
||||
overlayDatabaseMode = overlay_database_utils_1.OverlayDatabaseMode.Overlay;
|
||||
useOverlayDatabaseCaching = true;
|
||||
logger.info(`Setting overlay database mode to ${overlayDatabaseMode} ` +
|
||||
"with caching because we are analyzing a pull request.");
|
||||
}
|
||||
else if (await (0, git_utils_1.isAnalyzingDefaultBranch)()) {
|
||||
overlayDatabaseMode = overlay_database_utils_1.OverlayDatabaseMode.OverlayBase;
|
||||
useOverlayDatabaseCaching = true;
|
||||
logger.info(`Setting overlay database mode to ${overlayDatabaseMode} ` +
|
||||
"with caching because we are analyzing the default branch.");
|
||||
}
|
||||
}
|
||||
const nonOverlayAnalysis = {
|
||||
overlayDatabaseMode: overlay_database_utils_1.OverlayDatabaseMode.None,
|
||||
useOverlayDatabaseCaching: false,
|
||||
};
|
||||
if (overlayDatabaseMode === overlay_database_utils_1.OverlayDatabaseMode.None) {
|
||||
return nonOverlayAnalysis;
|
||||
}
|
||||
if (buildMode !== util_1.BuildMode.None &&
|
||||
(await Promise.all(languages.map(async (l) => await codeql.isTracedLanguage(l)))).some(Boolean)) {
|
||||
logger.warning(`Cannot build an ${overlayDatabaseMode} database because ` +
|
||||
`build-mode is set to "${buildMode}" instead of "none". ` +
|
||||
"Falling back to creating a normal full database instead.");
|
||||
return nonOverlayAnalysis;
|
||||
}
|
||||
if (!(await (0, util_1.codeQlVersionAtLeast)(codeql, overlay_database_utils_1.CODEQL_OVERLAY_MINIMUM_VERSION))) {
|
||||
logger.warning(`Cannot build an ${overlayDatabaseMode} database because ` +
|
||||
`the CodeQL CLI is older than ${overlay_database_utils_1.CODEQL_OVERLAY_MINIMUM_VERSION}. ` +
|
||||
"Falling back to creating a normal full database instead.");
|
||||
return nonOverlayAnalysis;
|
||||
}
|
||||
if ((await (0, git_utils_1.getGitRoot)(sourceRoot)) === undefined) {
|
||||
logger.warning(`Cannot build an ${overlayDatabaseMode} database because ` +
|
||||
`the source root "${sourceRoot}" is not inside a git repository. ` +
|
||||
"Falling back to creating a normal full database instead.");
|
||||
return nonOverlayAnalysis;
|
||||
}
|
||||
return {
|
||||
overlayDatabaseMode,
|
||||
useOverlayDatabaseCaching,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Pack names must be in the form of `scope/name`, with only alpha-numeric characters,
|
||||
* and `-` allowed as long as not the first or last char.
|
||||
**/
|
||||
const PACK_IDENTIFIER_PATTERN = (function () {
|
||||
const alphaNumeric = "[a-z0-9]";
|
||||
const alphaNumericDash = "[a-z0-9-]";
|
||||
const component = `${alphaNumeric}(${alphaNumericDash}*${alphaNumeric})?`;
|
||||
return new RegExp(`^${component}/${component}$`);
|
||||
})();
|
||||
// Exported for testing
|
||||
function parsePacksFromInput(rawPacksInput, languages, packsInputCombines) {
|
||||
if (!rawPacksInput?.trim()) {
|
||||
return undefined;
|
||||
}
|
||||
if (languages.length > 1) {
|
||||
throw new util_1.ConfigurationError("Cannot specify a 'packs' input in a multi-language analysis. Use a codeql-config.yml file instead and specify packs by language.");
|
||||
}
|
||||
else if (languages.length === 0) {
|
||||
throw new util_1.ConfigurationError("No languages specified. Cannot process the packs input.");
|
||||
}
|
||||
rawPacksInput = rawPacksInput.trim();
|
||||
if (packsInputCombines) {
|
||||
rawPacksInput = rawPacksInput.trim().substring(1).trim();
|
||||
if (!rawPacksInput) {
|
||||
throw new util_1.ConfigurationError(getConfigFilePropertyError(undefined, "packs", "A '+' was used in the 'packs' input to specify that you wished to add some packs to your CodeQL analysis. However, no packs were specified. Please either remove the '+' or specify some packs."));
|
||||
}
|
||||
}
|
||||
return {
|
||||
[languages[0]]: rawPacksInput.split(",").reduce((packs, pack) => {
|
||||
packs.push(validatePackSpecification(pack));
|
||||
return packs;
|
||||
}, []),
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Validates that this package specification is syntactically correct.
|
||||
* It may not point to any real package, but after this function returns
|
||||
* without throwing, we are guaranteed that the package specification
|
||||
* is roughly correct.
|
||||
*
|
||||
* The CLI itself will do a more thorough validation of the package
|
||||
* specification.
|
||||
*
|
||||
* A package specification looks like this:
|
||||
*
|
||||
* `scope/name@version:path`
|
||||
*
|
||||
* Version and path are optional.
|
||||
*
|
||||
* @param packStr the package specification to verify.
|
||||
* @param configFile Config file to use for error reporting
|
||||
*/
|
||||
function parsePacksSpecification(packStr) {
|
||||
if (typeof packStr !== "string") {
|
||||
throw new util_1.ConfigurationError(getPacksStrInvalid(packStr));
|
||||
}
|
||||
packStr = packStr.trim();
|
||||
const atIndex = packStr.indexOf("@");
|
||||
const colonIndex = packStr.indexOf(":", atIndex);
|
||||
const packStart = 0;
|
||||
const versionStart = atIndex + 1 || undefined;
|
||||
const pathStart = colonIndex + 1 || undefined;
|
||||
const packEnd = Math.min(atIndex > 0 ? atIndex : Infinity, colonIndex > 0 ? colonIndex : Infinity, packStr.length);
|
||||
const versionEnd = versionStart
|
||||
? Math.min(colonIndex > 0 ? colonIndex : Infinity, packStr.length)
|
||||
: undefined;
|
||||
const pathEnd = pathStart ? packStr.length : undefined;
|
||||
const packName = packStr.slice(packStart, packEnd).trim();
|
||||
const version = versionStart
|
||||
? packStr.slice(versionStart, versionEnd).trim()
|
||||
: undefined;
|
||||
const packPath = pathStart
|
||||
? packStr.slice(pathStart, pathEnd).trim()
|
||||
: undefined;
|
||||
if (!PACK_IDENTIFIER_PATTERN.test(packName)) {
|
||||
throw new util_1.ConfigurationError(getPacksStrInvalid(packStr));
|
||||
}
|
||||
if (version) {
|
||||
try {
|
||||
new semver.Range(version);
|
||||
}
|
||||
catch {
|
||||
// The range string is invalid. OK to ignore the caught error
|
||||
throw new util_1.ConfigurationError(getPacksStrInvalid(packStr));
|
||||
}
|
||||
}
|
||||
if (packPath &&
|
||||
(path.isAbsolute(packPath) ||
|
||||
// Permit using "/" instead of "\" on Windows
|
||||
// Use `x.split(y).join(z)` as a polyfill for `x.replaceAll(y, z)` since
|
||||
// if we used a regex we'd need to escape the path separator on Windows
|
||||
// which seems more awkward.
|
||||
path.normalize(packPath).split(path.sep).join("/") !==
|
||||
packPath.split(path.sep).join("/"))) {
|
||||
throw new util_1.ConfigurationError(getPacksStrInvalid(packStr));
|
||||
}
|
||||
if (!packPath && pathStart) {
|
||||
// 0 length path
|
||||
throw new util_1.ConfigurationError(getPacksStrInvalid(packStr));
|
||||
}
|
||||
return {
|
||||
name: packName,
|
||||
version,
|
||||
path: packPath,
|
||||
};
|
||||
}
|
||||
function validatePackSpecification(pack) {
|
||||
return (0, util_1.prettyPrintPack)(parsePacksSpecification(pack));
|
||||
}
|
||||
/**
|
||||
* The convention in this action is that an input value that is prefixed with a '+' will
|
||||
* be combined with the corresponding value in the config file.
|
||||
*
|
||||
* Without a '+', an input value will override the corresponding value in the config file.
|
||||
*
|
||||
* @param inputValue The input value to process.
|
||||
* @returns true if the input value should replace the corresponding value in the config file,
|
||||
* false if it should be appended.
|
||||
*/
|
||||
function shouldCombine(inputValue) {
|
||||
return !!inputValue?.trim().startsWith("+");
|
||||
}
|
||||
function dbLocationOrDefault(dbLocation, tempDir) {
|
||||
return dbLocation || path.resolve(tempDir, "codeql_databases");
|
||||
}
|
||||
function userConfigFromActionPath(tempDir) {
|
||||
return path.resolve(tempDir, "user-config-from-action.yml");
|
||||
}
|
||||
/**
|
||||
* Load and return the config.
|
||||
*
|
||||
* This will parse the config from the user input if present, or generate
|
||||
* a default config. The parsed config is then stored to a known location.
|
||||
*/
|
||||
async function initConfig(inputs) {
|
||||
const { logger, tempDir } = inputs;
|
||||
// if configInput is set, it takes precedence over configFile
|
||||
if (inputs.configInput) {
|
||||
if (inputs.configFile) {
|
||||
logger.warning(`Both a config file and config input were provided. Ignoring config file.`);
|
||||
}
|
||||
inputs.configFile = userConfigFromActionPath(tempDir);
|
||||
fs.writeFileSync(inputs.configFile, inputs.configInput);
|
||||
logger.debug(`Using config from action input: ${inputs.configFile}`);
|
||||
}
|
||||
let userConfig = {};
|
||||
if (!inputs.configFile) {
|
||||
logger.debug("No configuration file was provided");
|
||||
}
|
||||
else {
|
||||
logger.debug(`Using configuration file: ${inputs.configFile}`);
|
||||
userConfig = await loadUserConfig(inputs.configFile, inputs.workspacePath, inputs.apiDetails, tempDir);
|
||||
}
|
||||
const config = await getDefaultConfig(inputs);
|
||||
const augmentationProperties = config.augmentationProperties;
|
||||
config.originalUserInput = userConfig;
|
||||
// The choice of overlay database mode depends on the selection of languages
|
||||
// and queries, which in turn depends on the user config and the augmentation
|
||||
// properties. So we need to calculate the overlay database mode after the
|
||||
// rest of the config has been populated.
|
||||
const { overlayDatabaseMode, useOverlayDatabaseCaching } = await getOverlayDatabaseMode(inputs.codeql, inputs.repository, inputs.features, config.languages, inputs.sourceRoot, config.buildMode, generateCodeScanningConfig(userConfig, augmentationProperties), logger);
|
||||
logger.info(`Using overlay database mode: ${overlayDatabaseMode} ` +
|
||||
`${useOverlayDatabaseCaching ? "with" : "without"} caching.`);
|
||||
augmentationProperties.overlayDatabaseMode = overlayDatabaseMode;
|
||||
augmentationProperties.useOverlayDatabaseCaching = useOverlayDatabaseCaching;
|
||||
if (overlayDatabaseMode === overlay_database_utils_1.OverlayDatabaseMode.Overlay ||
|
||||
(await (0, diff_informed_analysis_utils_1.shouldPerformDiffInformedAnalysis)(inputs.codeql, inputs.features, logger))) {
|
||||
augmentationProperties.extraQueryExclusions.push({
|
||||
exclude: { tags: "exclude-from-incremental" },
|
||||
});
|
||||
}
|
||||
// Save the config so we can easily access it again in the future
|
||||
await saveConfig(config, logger);
|
||||
return config;
|
||||
}
|
||||
function parseRegistries(registriesInput) {
|
||||
try {
|
||||
return registriesInput
|
||||
? yaml.load(registriesInput)
|
||||
: undefined;
|
||||
}
|
||||
catch {
|
||||
throw new util_1.ConfigurationError("Invalid registries input. Must be a YAML string.");
|
||||
}
|
||||
}
|
||||
function parseRegistriesWithoutCredentials(registriesInput) {
|
||||
return parseRegistries(registriesInput)?.map((r) => {
|
||||
const { url, packages, kind } = r;
|
||||
return { url, packages, kind };
|
||||
});
|
||||
}
|
||||
function isLocal(configPath) {
|
||||
// If the path starts with ./, look locally
|
||||
if (configPath.indexOf("./") === 0) {
|
||||
return true;
|
||||
}
|
||||
return configPath.indexOf("@") === -1;
|
||||
}
|
||||
function getLocalConfig(configFile) {
|
||||
// Error if the file does not exist
|
||||
if (!fs.existsSync(configFile)) {
|
||||
throw new util_1.ConfigurationError(getConfigFileDoesNotExistErrorMessage(configFile));
|
||||
}
|
||||
return yaml.load(fs.readFileSync(configFile, "utf8"));
|
||||
}
|
||||
async function getRemoteConfig(configFile, apiDetails) {
|
||||
// retrieve the various parts of the config location, and ensure they're present
|
||||
const format = new RegExp("(?<owner>[^/]+)/(?<repo>[^/]+)/(?<path>[^@]+)@(?<ref>.*)");
|
||||
const pieces = format.exec(configFile);
|
||||
// 5 = 4 groups + the whole expression
|
||||
if (pieces === null || pieces.groups === undefined || pieces.length < 5) {
|
||||
throw new util_1.ConfigurationError(getConfigFileRepoFormatInvalidMessage(configFile));
|
||||
}
|
||||
const response = await api
|
||||
.getApiClientWithExternalAuth(apiDetails)
|
||||
.rest.repos.getContent({
|
||||
owner: pieces.groups.owner,
|
||||
repo: pieces.groups.repo,
|
||||
path: pieces.groups.path,
|
||||
ref: pieces.groups.ref,
|
||||
});
|
||||
let fileContents;
|
||||
if ("content" in response.data && response.data.content !== undefined) {
|
||||
fileContents = response.data.content;
|
||||
}
|
||||
else if (Array.isArray(response.data)) {
|
||||
throw new util_1.ConfigurationError(getConfigFileDirectoryGivenMessage(configFile));
|
||||
}
|
||||
else {
|
||||
throw new util_1.ConfigurationError(getConfigFileFormatInvalidMessage(configFile));
|
||||
}
|
||||
return yaml.load(Buffer.from(fileContents, "base64").toString("binary"));
|
||||
}
|
||||
/**
|
||||
* Get the file path where the parsed config will be stored.
|
||||
*/
|
||||
function getPathToParsedConfigFile(tempDir) {
|
||||
return path.join(tempDir, "config");
|
||||
}
|
||||
/**
|
||||
* Store the given config to the path returned from getPathToParsedConfigFile.
|
||||
*/
|
||||
async function saveConfig(config, logger) {
|
||||
const configString = JSON.stringify(config);
|
||||
const configFile = getPathToParsedConfigFile(config.tempDir);
|
||||
fs.mkdirSync(path.dirname(configFile), { recursive: true });
|
||||
fs.writeFileSync(configFile, configString, "utf8");
|
||||
logger.debug("Saved config:");
|
||||
logger.debug(configString);
|
||||
}
|
||||
/**
|
||||
* Get the config that has been saved to the given temp dir.
|
||||
* If the config could not be found then returns undefined.
|
||||
*/
|
||||
async function getConfig(tempDir, logger) {
|
||||
const configFile = getPathToParsedConfigFile(tempDir);
|
||||
if (!fs.existsSync(configFile)) {
|
||||
return undefined;
|
||||
}
|
||||
const configString = fs.readFileSync(configFile, "utf8");
|
||||
logger.debug("Loaded config:");
|
||||
logger.debug(configString);
|
||||
return JSON.parse(configString);
|
||||
}
|
||||
/**
|
||||
* Generate a `qlconfig.yml` file from the `registries` input.
|
||||
* This file is used by the CodeQL CLI to list the registries to use for each
|
||||
* pack.
|
||||
*
|
||||
* @param registriesInput The value of the `registries` input.
|
||||
* @param codeQL a codeQL object, used only for checking the version of CodeQL.
|
||||
* @param tempDir a temporary directory to store the generated qlconfig.yml file.
|
||||
* @param logger a logger object.
|
||||
* @returns The path to the generated `qlconfig.yml` file and the auth tokens to
|
||||
* use for each registry.
|
||||
*/
|
||||
async function generateRegistries(registriesInput, tempDir, logger) {
|
||||
const registries = parseRegistries(registriesInput);
|
||||
let registriesAuthTokens;
|
||||
let qlconfigFile;
|
||||
if (registries) {
|
||||
// generate a qlconfig.yml file to hold the registry configs.
|
||||
const qlconfig = createRegistriesBlock(registries);
|
||||
qlconfigFile = path.join(tempDir, "qlconfig.yml");
|
||||
const qlconfigContents = yaml.dump(qlconfig);
|
||||
fs.writeFileSync(qlconfigFile, qlconfigContents, "utf8");
|
||||
logger.debug("Generated qlconfig.yml:");
|
||||
logger.debug(qlconfigContents);
|
||||
registriesAuthTokens = registries
|
||||
.map((registry) => `${registry.url}=${registry.token}`)
|
||||
.join(",");
|
||||
}
|
||||
if (typeof process.env.CODEQL_REGISTRIES_AUTH === "string") {
|
||||
logger.debug("Using CODEQL_REGISTRIES_AUTH environment variable to authenticate with registries.");
|
||||
}
|
||||
return {
|
||||
registriesAuthTokens:
|
||||
// if the user has explicitly set the CODEQL_REGISTRIES_AUTH env var then use that
|
||||
process.env.CODEQL_REGISTRIES_AUTH ?? registriesAuthTokens,
|
||||
qlconfigFile,
|
||||
};
|
||||
}
|
||||
function createRegistriesBlock(registries) {
|
||||
if (!Array.isArray(registries) ||
|
||||
registries.some((r) => !r.url || !r.packages)) {
|
||||
throw new util_1.ConfigurationError("Invalid 'registries' input. Must be an array of objects with 'url' and 'packages' properties.");
|
||||
}
|
||||
// be sure to remove the `token` field from the registry before writing it to disk.
|
||||
const safeRegistries = registries.map((registry) => ({
|
||||
// ensure the url ends with a slash to avoid a bug in the CLI 2.10.4
|
||||
url: !registry?.url.endsWith("/") ? `${registry.url}/` : registry.url,
|
||||
packages: registry.packages,
|
||||
kind: registry.kind,
|
||||
}));
|
||||
const qlconfig = {
|
||||
registries: safeRegistries,
|
||||
};
|
||||
return qlconfig;
|
||||
}
|
||||
/**
|
||||
* Create a temporary environment based on the existing environment and overridden
|
||||
* by the given environment variables that are passed in as arguments.
|
||||
*
|
||||
* Use this new environment in the context of the given operation. After completing
|
||||
* the operation, restore the original environment.
|
||||
*
|
||||
* This function does not support un-setting environment variables.
|
||||
*
|
||||
* @param env
|
||||
* @param operation
|
||||
*/
|
||||
async function wrapEnvironment(env, operation) {
|
||||
// Remember the original env
|
||||
const oldEnv = { ...process.env };
|
||||
// Set the new env
|
||||
for (const [key, value] of Object.entries(env)) {
|
||||
// Ignore undefined keys
|
||||
if (value !== undefined) {
|
||||
process.env[key] = value;
|
||||
}
|
||||
}
|
||||
try {
|
||||
// Run the operation
|
||||
await operation();
|
||||
}
|
||||
finally {
|
||||
// Restore the old env
|
||||
for (const [key, value] of Object.entries(oldEnv)) {
|
||||
process.env[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Exported for testing
|
||||
async function parseBuildModeInput(input, languages, features, logger) {
|
||||
if (input === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (!Object.values(util_1.BuildMode).includes(input)) {
|
||||
throw new util_1.ConfigurationError(`Invalid build mode: '${input}'. Supported build modes are: ${Object.values(util_1.BuildMode).join(", ")}.`);
|
||||
}
|
||||
if (languages.includes(languages_1.KnownLanguage.csharp) &&
|
||||
(await features.getValue(feature_flags_1.Feature.DisableCsharpBuildless))) {
|
||||
logger.warning("Scanning C# code without a build is temporarily unavailable. Falling back to 'autobuild' build mode.");
|
||||
return util_1.BuildMode.Autobuild;
|
||||
}
|
||||
if (languages.includes(languages_1.KnownLanguage.java) &&
|
||||
(await features.getValue(feature_flags_1.Feature.DisableJavaBuildlessEnabled))) {
|
||||
logger.warning("Scanning Java code without a build is temporarily unavailable. Falling back to 'autobuild' build mode.");
|
||||
return util_1.BuildMode.Autobuild;
|
||||
}
|
||||
return input;
|
||||
}
|
||||
function generateCodeScanningConfig(originalUserInput, augmentationProperties) {
|
||||
// make a copy so we can modify it
|
||||
const augmentedConfig = (0, util_1.cloneObject)(originalUserInput);
|
||||
// Inject the queries from the input
|
||||
if (augmentationProperties.queriesInput) {
|
||||
if (augmentationProperties.queriesInputCombines) {
|
||||
augmentedConfig.queries = (augmentedConfig.queries || []).concat(augmentationProperties.queriesInput);
|
||||
}
|
||||
else {
|
||||
augmentedConfig.queries = augmentationProperties.queriesInput;
|
||||
}
|
||||
}
|
||||
if (augmentedConfig.queries?.length === 0) {
|
||||
delete augmentedConfig.queries;
|
||||
}
|
||||
// Inject the packs from the input
|
||||
if (augmentationProperties.packsInput) {
|
||||
if (augmentationProperties.packsInputCombines) {
|
||||
// At this point, we already know that this is a single-language analysis
|
||||
if (Array.isArray(augmentedConfig.packs)) {
|
||||
augmentedConfig.packs = (augmentedConfig.packs || []).concat(augmentationProperties.packsInput);
|
||||
}
|
||||
else if (!augmentedConfig.packs) {
|
||||
augmentedConfig.packs = augmentationProperties.packsInput;
|
||||
}
|
||||
else {
|
||||
// At this point, we know there is only one language.
|
||||
// If there were more than one language, an error would already have been thrown.
|
||||
const language = Object.keys(augmentedConfig.packs)[0];
|
||||
augmentedConfig.packs[language] = augmentedConfig.packs[language].concat(augmentationProperties.packsInput);
|
||||
}
|
||||
}
|
||||
else {
|
||||
augmentedConfig.packs = augmentationProperties.packsInput;
|
||||
}
|
||||
}
|
||||
if (Array.isArray(augmentedConfig.packs) && !augmentedConfig.packs.length) {
|
||||
delete augmentedConfig.packs;
|
||||
}
|
||||
augmentedConfig["query-filters"] = [
|
||||
// Ordering matters. If the first filter is an inclusion, it implicitly
|
||||
// excludes all queries that are not included. If it is an exclusion,
|
||||
// it implicitly includes all queries that are not excluded. So user
|
||||
// filters (if any) should always be first to preserve intent.
|
||||
...(augmentedConfig["query-filters"] || []),
|
||||
...augmentationProperties.extraQueryExclusions,
|
||||
];
|
||||
if (augmentedConfig["query-filters"]?.length === 0) {
|
||||
delete augmentedConfig["query-filters"];
|
||||
}
|
||||
return augmentedConfig;
|
||||
}
|
||||
//# sourceMappingURL=config-utils.js.map
|
||||
File diff suppressed because one or more lines are too long
1201
lib/config-utils.test.js
generated
1201
lib/config-utils.test.js
generated
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
115
lib/database-upload.js
generated
115
lib/database-upload.js
generated
@@ -1,115 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.uploadDatabases = uploadDatabases;
|
||||
const fs = __importStar(require("fs"));
|
||||
const actionsUtil = __importStar(require("./actions-util"));
|
||||
const api_client_1 = require("./api-client");
|
||||
const gitUtils = __importStar(require("./git-utils"));
|
||||
const logging_1 = require("./logging");
|
||||
const util = __importStar(require("./util"));
|
||||
const util_1 = require("./util");
|
||||
async function uploadDatabases(repositoryNwo, codeql, config, apiDetails, logger) {
|
||||
if (actionsUtil.getRequiredInput("upload-database") !== "true") {
|
||||
logger.debug("Database upload disabled in workflow. Skipping upload.");
|
||||
return;
|
||||
}
|
||||
if (util.isInTestMode()) {
|
||||
logger.debug("In test mode. Skipping database upload.");
|
||||
return;
|
||||
}
|
||||
// Do nothing when not running against github.com
|
||||
if (config.gitHubVersion.type !== util.GitHubVariant.DOTCOM &&
|
||||
config.gitHubVersion.type !== util.GitHubVariant.GHE_DOTCOM) {
|
||||
logger.debug("Not running against github.com or GHEC-DR. Skipping upload.");
|
||||
return;
|
||||
}
|
||||
if (!(await gitUtils.isAnalyzingDefaultBranch())) {
|
||||
// We only want to upload a database if we are analyzing the default branch.
|
||||
logger.debug("Not analyzing default branch. Skipping upload.");
|
||||
return;
|
||||
}
|
||||
// Clean up the database, since intermediate results may still be written to the
|
||||
// database if there is high RAM pressure.
|
||||
await (0, logging_1.withGroupAsync)("Cleaning up databases", async () => {
|
||||
await codeql.databaseCleanupCluster(config, "clear");
|
||||
});
|
||||
const client = (0, api_client_1.getApiClient)();
|
||||
const uploadsUrl = new URL((0, util_1.parseGitHubUrl)(apiDetails.url));
|
||||
uploadsUrl.hostname = `uploads.${uploadsUrl.hostname}`;
|
||||
// Octokit expects the baseUrl to not have a trailing slash,
|
||||
// but it is included by default in a URL.
|
||||
let uploadsBaseUrl = uploadsUrl.toString();
|
||||
if (uploadsBaseUrl.endsWith("/")) {
|
||||
uploadsBaseUrl = uploadsBaseUrl.slice(0, -1);
|
||||
}
|
||||
for (const language of config.languages) {
|
||||
try {
|
||||
// Upload the database bundle.
|
||||
// Although we are uploading arbitrary file contents to the API, it's worth
|
||||
// noting that it's the API's job to validate that the contents is acceptable.
|
||||
// This API method is available to anyone with write access to the repo.
|
||||
const bundledDb = await (0, util_1.bundleDb)(config, language, codeql, language);
|
||||
const bundledDbSize = fs.statSync(bundledDb).size;
|
||||
const bundledDbReadStream = fs.createReadStream(bundledDb);
|
||||
const commitOid = await gitUtils.getCommitOid(actionsUtil.getRequiredInput("checkout_path"));
|
||||
try {
|
||||
await client.request(`POST /repos/:owner/:repo/code-scanning/codeql/databases/:language?name=:name&commit_oid=:commit_oid`, {
|
||||
baseUrl: uploadsBaseUrl,
|
||||
owner: repositoryNwo.owner,
|
||||
repo: repositoryNwo.repo,
|
||||
language,
|
||||
name: `${language}-database`,
|
||||
commit_oid: commitOid,
|
||||
data: bundledDbReadStream,
|
||||
headers: {
|
||||
authorization: `token ${apiDetails.auth}`,
|
||||
"Content-Type": "application/zip",
|
||||
"Content-Length": bundledDbSize,
|
||||
},
|
||||
});
|
||||
logger.debug(`Successfully uploaded database for ${language}`);
|
||||
}
|
||||
finally {
|
||||
bundledDbReadStream.close();
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
// Log a warning but don't fail the workflow
|
||||
logger.warning(`Failed to upload database for ${language}: ${e}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=database-upload.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"database-upload.js","sourceRoot":"","sources":["../src/database-upload.ts"],"names":[],"mappings":"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"}
|
||||
189
lib/database-upload.test.js
generated
189
lib/database-upload.test.js
generated
@@ -1,189 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const fs = __importStar(require("fs"));
|
||||
const github = __importStar(require("@actions/github"));
|
||||
const ava_1 = __importDefault(require("ava"));
|
||||
const sinon = __importStar(require("sinon"));
|
||||
const actionsUtil = __importStar(require("./actions-util"));
|
||||
const apiClient = __importStar(require("./api-client"));
|
||||
const codeql_1 = require("./codeql");
|
||||
const database_upload_1 = require("./database-upload");
|
||||
const gitUtils = __importStar(require("./git-utils"));
|
||||
const languages_1 = require("./languages");
|
||||
const testing_utils_1 = require("./testing-utils");
|
||||
const util_1 = require("./util");
|
||||
(0, testing_utils_1.setupTests)(ava_1.default);
|
||||
ava_1.default.beforeEach(() => {
|
||||
(0, util_1.initializeEnvironment)("1.2.3");
|
||||
});
|
||||
const testRepoName = { owner: "github", repo: "example" };
|
||||
const testApiDetails = {
|
||||
auth: "1234",
|
||||
url: "https://github.com",
|
||||
apiURL: undefined,
|
||||
};
|
||||
function getTestConfig(tmpDir) {
|
||||
return (0, testing_utils_1.createTestConfig)({
|
||||
languages: [languages_1.KnownLanguage.javascript],
|
||||
dbLocation: tmpDir,
|
||||
});
|
||||
}
|
||||
async function mockHttpRequests(databaseUploadStatusCode) {
|
||||
// Passing an auth token is required, so we just use a dummy value
|
||||
const client = github.getOctokit("123");
|
||||
const requestSpy = sinon.stub(client, "request");
|
||||
const url = "POST /repos/:owner/:repo/code-scanning/codeql/databases/:language?name=:name&commit_oid=:commit_oid";
|
||||
const databaseUploadSpy = requestSpy.withArgs(url);
|
||||
if (databaseUploadStatusCode < 300) {
|
||||
databaseUploadSpy.resolves(undefined);
|
||||
}
|
||||
else {
|
||||
databaseUploadSpy.throws(new util_1.HTTPError("some error message", databaseUploadStatusCode));
|
||||
}
|
||||
sinon.stub(apiClient, "getApiClient").value(() => client);
|
||||
return databaseUploadSpy;
|
||||
}
|
||||
function getCodeQL() {
|
||||
return (0, codeql_1.createStubCodeQL)({
|
||||
async databaseBundle(_, outputFilePath) {
|
||||
fs.writeFileSync(outputFilePath, "");
|
||||
},
|
||||
async databaseCleanupCluster() {
|
||||
// Do nothing, as we are not testing cleanup here.
|
||||
},
|
||||
});
|
||||
}
|
||||
(0, ava_1.default)("Abort database upload if 'upload-database' input set to false", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
sinon
|
||||
.stub(actionsUtil, "getRequiredInput")
|
||||
.withArgs("upload-database")
|
||||
.returns("false");
|
||||
sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(true);
|
||||
const loggedMessages = [];
|
||||
await (0, database_upload_1.uploadDatabases)(testRepoName, getCodeQL(), getTestConfig(tmpDir), testApiDetails, (0, testing_utils_1.getRecordingLogger)(loggedMessages));
|
||||
t.assert(loggedMessages.find((v) => v.type === "debug" &&
|
||||
v.message ===
|
||||
"Database upload disabled in workflow. Skipping upload.") !== undefined);
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("Abort database upload if running against GHES", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
sinon
|
||||
.stub(actionsUtil, "getRequiredInput")
|
||||
.withArgs("upload-database")
|
||||
.returns("true");
|
||||
sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(true);
|
||||
const config = getTestConfig(tmpDir);
|
||||
config.gitHubVersion = { type: util_1.GitHubVariant.GHES, version: "3.0" };
|
||||
const loggedMessages = [];
|
||||
await (0, database_upload_1.uploadDatabases)(testRepoName, getCodeQL(), config, testApiDetails, (0, testing_utils_1.getRecordingLogger)(loggedMessages));
|
||||
t.assert(loggedMessages.find((v) => v.type === "debug" &&
|
||||
v.message ===
|
||||
"Not running against github.com or GHEC-DR. Skipping upload.") !== undefined);
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("Abort database upload if not analyzing default branch", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
sinon
|
||||
.stub(actionsUtil, "getRequiredInput")
|
||||
.withArgs("upload-database")
|
||||
.returns("true");
|
||||
sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(false);
|
||||
const loggedMessages = [];
|
||||
await (0, database_upload_1.uploadDatabases)(testRepoName, getCodeQL(), getTestConfig(tmpDir), testApiDetails, (0, testing_utils_1.getRecordingLogger)(loggedMessages));
|
||||
t.assert(loggedMessages.find((v) => v.type === "debug" &&
|
||||
v.message === "Not analyzing default branch. Skipping upload.") !== undefined);
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("Don't crash if uploading a database fails", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
sinon
|
||||
.stub(actionsUtil, "getRequiredInput")
|
||||
.withArgs("upload-database")
|
||||
.returns("true");
|
||||
sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(true);
|
||||
await mockHttpRequests(500);
|
||||
const loggedMessages = [];
|
||||
await (0, database_upload_1.uploadDatabases)(testRepoName, getCodeQL(), getTestConfig(tmpDir), testApiDetails, (0, testing_utils_1.getRecordingLogger)(loggedMessages));
|
||||
t.assert(loggedMessages.find((v) => v.type === "warning" &&
|
||||
v.message ===
|
||||
"Failed to upload database for javascript: Error: some error message") !== undefined);
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("Successfully uploading a database to github.com", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
sinon
|
||||
.stub(actionsUtil, "getRequiredInput")
|
||||
.withArgs("upload-database")
|
||||
.returns("true");
|
||||
sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(true);
|
||||
await mockHttpRequests(201);
|
||||
const loggedMessages = [];
|
||||
await (0, database_upload_1.uploadDatabases)(testRepoName, getCodeQL(), getTestConfig(tmpDir), testApiDetails, (0, testing_utils_1.getRecordingLogger)(loggedMessages));
|
||||
t.assert(loggedMessages.find((v) => v.type === "debug" &&
|
||||
v.message === "Successfully uploaded database for javascript") !== undefined);
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("Successfully uploading a database to GHEC-DR", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
sinon
|
||||
.stub(actionsUtil, "getRequiredInput")
|
||||
.withArgs("upload-database")
|
||||
.returns("true");
|
||||
sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(true);
|
||||
const databaseUploadSpy = await mockHttpRequests(201);
|
||||
const loggedMessages = [];
|
||||
await (0, database_upload_1.uploadDatabases)(testRepoName, getCodeQL(), getTestConfig(tmpDir), {
|
||||
auth: "1234",
|
||||
url: "https://tenant.ghe.com",
|
||||
apiURL: undefined,
|
||||
}, (0, testing_utils_1.getRecordingLogger)(loggedMessages));
|
||||
t.assert(loggedMessages.find((v) => v.type === "debug" &&
|
||||
v.message === "Successfully uploaded database for javascript") !== undefined);
|
||||
t.assert(databaseUploadSpy.calledOnceWith(sinon.match.string, sinon.match.has("baseUrl", "https://uploads.tenant.ghe.com")));
|
||||
});
|
||||
});
|
||||
//# sourceMappingURL=database-upload.test.js.map
|
||||
File diff suppressed because one or more lines are too long
275
lib/debug-artifacts.js
generated
275
lib/debug-artifacts.js
generated
@@ -1,275 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.sanitizeArtifactName = sanitizeArtifactName;
|
||||
exports.uploadCombinedSarifArtifacts = uploadCombinedSarifArtifacts;
|
||||
exports.tryUploadAllAvailableDebugArtifacts = tryUploadAllAvailableDebugArtifacts;
|
||||
exports.uploadDebugArtifacts = uploadDebugArtifacts;
|
||||
exports.getArtifactUploaderClient = getArtifactUploaderClient;
|
||||
const fs = __importStar(require("fs"));
|
||||
const path = __importStar(require("path"));
|
||||
const artifact = __importStar(require("@actions/artifact"));
|
||||
const artifactLegacy = __importStar(require("@actions/artifact-legacy"));
|
||||
const core = __importStar(require("@actions/core"));
|
||||
const archiver_1 = __importDefault(require("archiver"));
|
||||
const del_1 = __importDefault(require("del"));
|
||||
const actions_util_1 = require("./actions-util");
|
||||
const analyze_1 = require("./analyze");
|
||||
const environment_1 = require("./environment");
|
||||
const logging_1 = require("./logging");
|
||||
const tools_features_1 = require("./tools-features");
|
||||
const util_1 = require("./util");
|
||||
function sanitizeArtifactName(name) {
|
||||
return name.replace(/[^a-zA-Z0-9_-]+/g, "");
|
||||
}
|
||||
/**
|
||||
* Upload Actions SARIF artifacts for debugging when CODEQL_ACTION_DEBUG_COMBINED_SARIF
|
||||
* environment variable is set
|
||||
*/
|
||||
async function uploadCombinedSarifArtifacts(logger, gitHubVariant, codeQlVersion) {
|
||||
const tempDir = (0, actions_util_1.getTemporaryDirectory)();
|
||||
// Upload Actions SARIF artifacts for debugging when environment variable is set
|
||||
if (process.env["CODEQL_ACTION_DEBUG_COMBINED_SARIF"] === "true") {
|
||||
await (0, logging_1.withGroup)("Uploading combined SARIF debug artifact", async () => {
|
||||
logger.info("Uploading available combined SARIF files as Actions debugging artifact...");
|
||||
const baseTempDir = path.resolve(tempDir, "combined-sarif");
|
||||
const toUpload = [];
|
||||
if (fs.existsSync(baseTempDir)) {
|
||||
const outputDirs = fs.readdirSync(baseTempDir);
|
||||
for (const outputDir of outputDirs) {
|
||||
const sarifFiles = fs
|
||||
.readdirSync(path.resolve(baseTempDir, outputDir))
|
||||
.filter((f) => f.endsWith(".sarif"));
|
||||
for (const sarifFile of sarifFiles) {
|
||||
toUpload.push(path.resolve(baseTempDir, outputDir, sarifFile));
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
await uploadDebugArtifacts(logger, toUpload, baseTempDir, "combined-sarif-artifacts", gitHubVariant, codeQlVersion);
|
||||
}
|
||||
catch (e) {
|
||||
logger.warning(`Failed to upload combined SARIF files as Actions debugging artifact. Reason: ${(0, util_1.getErrorMessage)(e)}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Try to prepare a SARIF result debug artifact for the given language.
|
||||
*
|
||||
* @return The path to that debug artifact, or undefined if an error occurs.
|
||||
*/
|
||||
function tryPrepareSarifDebugArtifact(config, language, logger) {
|
||||
try {
|
||||
const analyzeActionOutputDir = process.env[environment_1.EnvVar.SARIF_RESULTS_OUTPUT_DIR];
|
||||
if (analyzeActionOutputDir !== undefined &&
|
||||
fs.existsSync(analyzeActionOutputDir) &&
|
||||
fs.lstatSync(analyzeActionOutputDir).isDirectory()) {
|
||||
const sarifFile = path.resolve(analyzeActionOutputDir, `${language}.sarif`);
|
||||
// Move SARIF to DB location so that they can be uploaded with the same root directory as the other artifacts.
|
||||
if (fs.existsSync(sarifFile)) {
|
||||
const sarifInDbLocation = path.resolve(config.dbLocation, `${language}.sarif`);
|
||||
fs.copyFileSync(sarifFile, sarifInDbLocation);
|
||||
return sarifInDbLocation;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
logger.warning(`Failed to find SARIF results path for ${language}. Reason: ${(0, util_1.getErrorMessage)(e)}`);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Try to bundle the database for the given language.
|
||||
*
|
||||
* @return The path to the database bundle, or undefined if an error occurs.
|
||||
*/
|
||||
async function tryBundleDatabase(codeql, config, language, logger) {
|
||||
try {
|
||||
if ((0, analyze_1.dbIsFinalized)(config, language, logger)) {
|
||||
try {
|
||||
return await createDatabaseBundleCli(codeql, config, language);
|
||||
}
|
||||
catch (e) {
|
||||
logger.warning(`Failed to bundle database for ${language} using the CLI. ` +
|
||||
`Falling back to a partial bundle. Reason: ${(0, util_1.getErrorMessage)(e)}`);
|
||||
}
|
||||
}
|
||||
return await createPartialDatabaseBundle(config, language);
|
||||
}
|
||||
catch (e) {
|
||||
logger.warning(`Failed to bundle database for ${language}. Reason: ${(0, util_1.getErrorMessage)(e)}`);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Attempt to upload all available debug artifacts.
|
||||
*
|
||||
* Logs and suppresses any errors that occur.
|
||||
*/
|
||||
async function tryUploadAllAvailableDebugArtifacts(codeql, config, logger, codeQlVersion) {
|
||||
const filesToUpload = [];
|
||||
try {
|
||||
for (const language of config.languages) {
|
||||
await (0, logging_1.withGroup)(`Uploading debug artifacts for ${language}`, async () => {
|
||||
logger.info("Preparing SARIF result debug artifact...");
|
||||
const sarifResultDebugArtifact = tryPrepareSarifDebugArtifact(config, language, logger);
|
||||
if (sarifResultDebugArtifact) {
|
||||
filesToUpload.push(sarifResultDebugArtifact);
|
||||
logger.info("SARIF result debug artifact ready for upload.");
|
||||
}
|
||||
logger.info("Preparing database logs debug artifact...");
|
||||
const databaseDirectory = (0, util_1.getCodeQLDatabasePath)(config, language);
|
||||
const logsDirectory = path.resolve(databaseDirectory, "log");
|
||||
if ((0, util_1.doesDirectoryExist)(logsDirectory)) {
|
||||
filesToUpload.push(...(0, util_1.listFolder)(logsDirectory));
|
||||
logger.info("Database logs debug artifact ready for upload.");
|
||||
}
|
||||
// Multilanguage tracing: there are additional logs in the root of the cluster
|
||||
logger.info("Preparing database cluster logs debug artifact...");
|
||||
const multiLanguageTracingLogsDirectory = path.resolve(config.dbLocation, "log");
|
||||
if ((0, util_1.doesDirectoryExist)(multiLanguageTracingLogsDirectory)) {
|
||||
filesToUpload.push(...(0, util_1.listFolder)(multiLanguageTracingLogsDirectory));
|
||||
logger.info("Database cluster logs debug artifact ready for upload.");
|
||||
}
|
||||
// Add database bundle
|
||||
logger.info("Preparing database bundle debug artifact...");
|
||||
const databaseBundle = await tryBundleDatabase(codeql, config, language, logger);
|
||||
if (databaseBundle) {
|
||||
filesToUpload.push(databaseBundle);
|
||||
logger.info("Database bundle debug artifact ready for upload.");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
logger.warning(`Failed to prepare debug artifacts. Reason: ${(0, util_1.getErrorMessage)(e)}`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await (0, logging_1.withGroup)("Uploading debug artifacts", async () => uploadDebugArtifacts(logger, filesToUpload, config.dbLocation, config.debugArtifactName, config.gitHubVersion.type, codeQlVersion));
|
||||
}
|
||||
catch (e) {
|
||||
logger.warning(`Failed to upload debug artifacts. Reason: ${(0, util_1.getErrorMessage)(e)}`);
|
||||
}
|
||||
}
|
||||
async function uploadDebugArtifacts(logger, toUpload, rootDir, artifactName, ghVariant, codeQlVersion) {
|
||||
if (toUpload.length === 0) {
|
||||
return "no-artifacts-to-upload";
|
||||
}
|
||||
const uploadSupported = (0, tools_features_1.isSafeArtifactUpload)(codeQlVersion);
|
||||
if (!uploadSupported) {
|
||||
core.info(`Skipping debug artifact upload because the current CLI does not support safe upload. Please upgrade to CLI v${tools_features_1.SafeArtifactUploadVersion} or later.`);
|
||||
return "upload-not-supported";
|
||||
}
|
||||
let suffix = "";
|
||||
const matrix = (0, actions_util_1.getOptionalInput)("matrix");
|
||||
if (matrix) {
|
||||
try {
|
||||
for (const [, matrixVal] of Object.entries(JSON.parse(matrix)).sort())
|
||||
suffix += `-${matrixVal}`;
|
||||
}
|
||||
catch {
|
||||
core.info("Could not parse user-specified `matrix` input into JSON. The debug artifact will not be named with the user's `matrix` input.");
|
||||
}
|
||||
}
|
||||
const artifactUploader = await getArtifactUploaderClient(logger, ghVariant);
|
||||
try {
|
||||
await artifactUploader.uploadArtifact(sanitizeArtifactName(`${artifactName}${suffix}`), toUpload.map((file) => path.normalize(file)), path.normalize(rootDir), {
|
||||
// ensure we don't keep the debug artifacts around for too long since they can be large.
|
||||
retentionDays: 7,
|
||||
});
|
||||
return "upload-successful";
|
||||
}
|
||||
catch (e) {
|
||||
// A failure to upload debug artifacts should not fail the entire action.
|
||||
core.warning(`Failed to upload debug artifacts: ${e}`);
|
||||
return "upload-failed";
|
||||
}
|
||||
}
|
||||
// `@actions/artifact@v2` is not yet supported on GHES so the legacy version of the client will be used on GHES
|
||||
// until it is supported. We also use the legacy version of the client if the feature flag is disabled.
|
||||
// The feature flag is named `ArtifactV4Upgrade` to reduce customer confusion; customers are primarily affected by
|
||||
// `actions/download-artifact`, whose upgrade to v4 must be accompanied by the `@actions/artifact@v2` upgrade.
|
||||
async function getArtifactUploaderClient(logger, ghVariant) {
|
||||
if (ghVariant === util_1.GitHubVariant.GHES) {
|
||||
logger.info("Debug artifacts can be consumed with `actions/download-artifact@v3` because the `v4` version is not yet compatible on GHES.");
|
||||
return artifactLegacy.create();
|
||||
}
|
||||
else {
|
||||
logger.info("Debug artifacts can be consumed with `actions/download-artifact@v4`.");
|
||||
return new artifact.DefaultArtifactClient();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* If a database has not been finalized, we cannot run the `codeql database bundle`
|
||||
* command in the CLI because it will return an error. Instead we directly zip
|
||||
* all files in the database folder and return the path.
|
||||
*/
|
||||
async function createPartialDatabaseBundle(config, language) {
|
||||
const databasePath = (0, util_1.getCodeQLDatabasePath)(config, language);
|
||||
const databaseBundlePath = path.resolve(config.dbLocation, `${config.debugDatabaseName}-${language}-partial.zip`);
|
||||
core.info(`${config.debugDatabaseName}-${language} is not finalized. Uploading partial database bundle at ${databaseBundlePath}...`);
|
||||
// See `bundleDb` for explanation behind deleting existing db bundle.
|
||||
if (fs.existsSync(databaseBundlePath)) {
|
||||
await (0, del_1.default)(databaseBundlePath, { force: true });
|
||||
}
|
||||
const output = fs.createWriteStream(databaseBundlePath);
|
||||
const zip = (0, archiver_1.default)("zip");
|
||||
zip.on("error", (err) => {
|
||||
throw err;
|
||||
});
|
||||
zip.on("warning", (err) => {
|
||||
// Ignore ENOENT warnings. There's nothing anyone can do about it.
|
||||
if (err.code !== "ENOENT") {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
zip.pipe(output);
|
||||
zip.directory(databasePath, false);
|
||||
await zip.finalize();
|
||||
return databaseBundlePath;
|
||||
}
|
||||
/**
|
||||
* Runs `codeql database bundle` command and returns the path.
|
||||
*/
|
||||
async function createDatabaseBundleCli(codeql, config, language) {
|
||||
const databaseBundlePath = await (0, util_1.bundleDb)(config, language, codeql, `${config.debugDatabaseName}-${language}`);
|
||||
return databaseBundlePath;
|
||||
}
|
||||
//# sourceMappingURL=debug-artifacts.js.map
|
||||
File diff suppressed because one or more lines are too long
93
lib/debug-artifacts.test.js
generated
93
lib/debug-artifacts.test.js
generated
@@ -1,93 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const ava_1 = __importDefault(require("ava"));
|
||||
const debugArtifacts = __importStar(require("./debug-artifacts"));
|
||||
const logging_1 = require("./logging");
|
||||
const util_1 = require("./util");
|
||||
(0, ava_1.default)("sanitizeArtifactName", (t) => {
|
||||
t.deepEqual(debugArtifacts.sanitizeArtifactName("hello-world_"), "hello-world_");
|
||||
t.deepEqual(debugArtifacts.sanitizeArtifactName("hello`world`"), "helloworld");
|
||||
t.deepEqual(debugArtifacts.sanitizeArtifactName("hello===123"), "hello123");
|
||||
t.deepEqual(debugArtifacts.sanitizeArtifactName("*m)a&n^y%i££n+v!a:l[i]d"), "manyinvalid");
|
||||
t.deepEqual(debugArtifacts.sanitizeArtifactName("\\foo\\bar//baz"), "foobarbaz");
|
||||
});
|
||||
// These next tests check the correctness of the logic to determine whether or not
|
||||
// artifacts are uploaded in debug mode. Since it's not easy to mock the actual
|
||||
// call to upload an artifact, we just check that we get an "upload-failed" result,
|
||||
// instead of actually uploading the artifact.
|
||||
//
|
||||
// For tests where we expect artifact upload to be blocked, we check for a different
|
||||
// response from the function.
|
||||
(0, ava_1.default)("uploadDebugArtifacts when artifacts empty should emit 'no-artifacts-to-upload'", async (t) => {
|
||||
// Test that no error is thrown if artifacts list is empty.
|
||||
const logger = (0, logging_1.getActionsLogger)();
|
||||
await t.notThrowsAsync(async () => {
|
||||
const uploaded = await debugArtifacts.uploadDebugArtifacts(logger, [], "i-dont-exist", "artifactName", util_1.GitHubVariant.DOTCOM, undefined);
|
||||
t.is(uploaded, "no-artifacts-to-upload", "Should not have uploaded any artifacts");
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("uploadDebugArtifacts when no codeql version is used should invoke artifact upload", async (t) => {
|
||||
// Test that the artifact is uploaded.
|
||||
const logger = (0, logging_1.getActionsLogger)();
|
||||
await t.notThrowsAsync(async () => {
|
||||
const uploaded = await debugArtifacts.uploadDebugArtifacts(logger, ["hucairz"], "i-dont-exist", "artifactName", util_1.GitHubVariant.DOTCOM, undefined);
|
||||
t.is(uploaded,
|
||||
// The failure is expected since we don't want to actually upload any artifacts in unit tests.
|
||||
"upload-failed", "Expect failure to upload artifacts since root dir does not exist");
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("uploadDebugArtifacts when new codeql version is used should invoke artifact upload", async (t) => {
|
||||
// Test that the artifact is uploaded.
|
||||
const logger = (0, logging_1.getActionsLogger)();
|
||||
await t.notThrowsAsync(async () => {
|
||||
const uploaded = await debugArtifacts.uploadDebugArtifacts(logger, ["hucairz"], "i-dont-exist", "artifactName", util_1.GitHubVariant.DOTCOM, "2.20.3");
|
||||
t.is(uploaded,
|
||||
// The failure is expected since we don't want to actually upload any artifacts in unit tests.
|
||||
"upload-failed", "Expect failure to upload artifacts since root dir does not exist");
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("uploadDebugArtifacts when old codeql is used should avoid trying to upload artifacts", async (t) => {
|
||||
// Test that the artifact is not uploaded.
|
||||
const logger = (0, logging_1.getActionsLogger)();
|
||||
await t.notThrowsAsync(async () => {
|
||||
const uploaded = await debugArtifacts.uploadDebugArtifacts(logger, ["hucairz"], "i-dont-exist", "artifactName", util_1.GitHubVariant.DOTCOM, "2.20.2");
|
||||
t.is(uploaded, "upload-not-supported", "Expected artifact upload to be blocked because of old CodeQL version");
|
||||
});
|
||||
});
|
||||
//# sourceMappingURL=debug-artifacts.test.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"debug-artifacts.test.js","sourceRoot":"","sources":["../src/debug-artifacts.test.ts"],"names":[],"mappings":"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"}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"bundleVersion": "codeql-bundle-v2.22.3",
|
||||
"cliVersion": "2.22.3",
|
||||
"priorBundleVersion": "codeql-bundle-v2.22.2",
|
||||
"priorCliVersion": "2.22.2"
|
||||
}
|
||||
220
lib/dependency-caching.js
generated
220
lib/dependency-caching.js
generated
@@ -1,220 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getJavaTempDependencyDir = getJavaTempDependencyDir;
|
||||
exports.downloadDependencyCaches = downloadDependencyCaches;
|
||||
exports.uploadDependencyCaches = uploadDependencyCaches;
|
||||
const os = __importStar(require("os"));
|
||||
const path_1 = require("path");
|
||||
const actionsCache = __importStar(require("@actions/cache"));
|
||||
const glob = __importStar(require("@actions/glob"));
|
||||
const actions_util_1 = require("./actions-util");
|
||||
const caching_utils_1 = require("./caching-utils");
|
||||
const environment_1 = require("./environment");
|
||||
const util_1 = require("./util");
|
||||
const CODEQL_DEPENDENCY_CACHE_PREFIX = "codeql-dependencies";
|
||||
const CODEQL_DEPENDENCY_CACHE_VERSION = 1;
|
||||
/**
|
||||
* Returns a path to a directory intended to be used to store .jar files
|
||||
* for the Java `build-mode: none` extractor.
|
||||
* @returns The path to the directory that should be used by the `build-mode: none` extractor.
|
||||
*/
|
||||
function getJavaTempDependencyDir() {
|
||||
return (0, path_1.join)((0, actions_util_1.getTemporaryDirectory)(), "codeql_java", "repository");
|
||||
}
|
||||
/**
|
||||
* Default caching configurations per language.
|
||||
*/
|
||||
function getDefaultCacheConfig() {
|
||||
return {
|
||||
java: {
|
||||
paths: [
|
||||
// Maven
|
||||
(0, path_1.join)(os.homedir(), ".m2", "repository"),
|
||||
// Gradle
|
||||
(0, path_1.join)(os.homedir(), ".gradle", "caches"),
|
||||
// CodeQL Java build-mode: none
|
||||
getJavaTempDependencyDir(),
|
||||
],
|
||||
hash: [
|
||||
// Maven
|
||||
"**/pom.xml",
|
||||
// Gradle
|
||||
"**/*.gradle*",
|
||||
"**/gradle-wrapper.properties",
|
||||
"buildSrc/**/Versions.kt",
|
||||
"buildSrc/**/Dependencies.kt",
|
||||
"gradle/*.versions.toml",
|
||||
"**/versions.properties",
|
||||
],
|
||||
},
|
||||
csharp: {
|
||||
paths: [(0, path_1.join)(os.homedir(), ".nuget", "packages")],
|
||||
hash: [
|
||||
// NuGet
|
||||
"**/packages.lock.json",
|
||||
// Paket
|
||||
"**/paket.lock",
|
||||
],
|
||||
},
|
||||
go: {
|
||||
paths: [(0, path_1.join)(os.homedir(), "go", "pkg", "mod")],
|
||||
hash: ["**/go.sum"],
|
||||
},
|
||||
};
|
||||
}
|
||||
async function makeGlobber(patterns) {
|
||||
return glob.create(patterns.join("\n"));
|
||||
}
|
||||
/**
|
||||
* Attempts to restore dependency caches for the languages being analyzed.
|
||||
*
|
||||
* @param languages The languages being analyzed.
|
||||
* @param logger A logger to record some informational messages to.
|
||||
* @returns A list of languages for which dependency caches were restored.
|
||||
*/
|
||||
async function downloadDependencyCaches(languages, logger) {
|
||||
const restoredCaches = [];
|
||||
for (const language of languages) {
|
||||
const cacheConfig = getDefaultCacheConfig()[language];
|
||||
if (cacheConfig === undefined) {
|
||||
logger.info(`Skipping download of dependency cache for ${language} as we have no caching configuration for it.`);
|
||||
continue;
|
||||
}
|
||||
// Check that we can find files to calculate the hash for the cache key from, so we don't end up
|
||||
// with an empty string.
|
||||
const globber = await makeGlobber(cacheConfig.hash);
|
||||
if ((await globber.glob()).length === 0) {
|
||||
logger.info(`Skipping download of dependency cache for ${language} as we cannot calculate a hash for the cache key.`);
|
||||
continue;
|
||||
}
|
||||
const primaryKey = await cacheKey(language, cacheConfig);
|
||||
const restoreKeys = [await cachePrefix(language)];
|
||||
logger.info(`Downloading cache for ${language} with key ${primaryKey} and restore keys ${restoreKeys.join(", ")}`);
|
||||
const hitKey = await actionsCache.restoreCache(cacheConfig.paths, primaryKey, restoreKeys);
|
||||
if (hitKey !== undefined) {
|
||||
logger.info(`Cache hit on key ${hitKey} for ${language}.`);
|
||||
restoredCaches.push(language);
|
||||
}
|
||||
else {
|
||||
logger.info(`No suitable cache found for ${language}.`);
|
||||
}
|
||||
}
|
||||
return restoredCaches;
|
||||
}
|
||||
/**
|
||||
* Attempts to store caches for the languages that were analyzed.
|
||||
*
|
||||
* @param config The configuration for this workflow.
|
||||
* @param logger A logger to record some informational messages to.
|
||||
*/
|
||||
async function uploadDependencyCaches(config, logger) {
|
||||
for (const language of config.languages) {
|
||||
const cacheConfig = getDefaultCacheConfig()[language];
|
||||
if (cacheConfig === undefined) {
|
||||
logger.info(`Skipping upload of dependency cache for ${language} as we have no caching configuration for it.`);
|
||||
continue;
|
||||
}
|
||||
// Check that we can find files to calculate the hash for the cache key from, so we don't end up
|
||||
// with an empty string.
|
||||
const globber = await makeGlobber(cacheConfig.hash);
|
||||
if ((await globber.glob()).length === 0) {
|
||||
logger.info(`Skipping upload of dependency cache for ${language} as we cannot calculate a hash for the cache key.`);
|
||||
continue;
|
||||
}
|
||||
// Calculate the size of the files that we would store in the cache. We use this to determine whether the
|
||||
// cache should be saved or not. For example, if there are no files to store, then we skip creating the
|
||||
// cache. In the future, we could also:
|
||||
// - Skip uploading caches with a size below some threshold: this makes sense for avoiding the overhead
|
||||
// of storing and restoring small caches, but does not help with alert wobble if a package repository
|
||||
// cannot be reached in a given run.
|
||||
// - Skip uploading caches with a size above some threshold: this could be a concern if other workflows
|
||||
// use the cache quota that we compete with. In that case, we do not wish to use up all of the quota
|
||||
// with the dependency caches. For this, we could use the Cache API to check whether other workflows
|
||||
// are using the quota and how full it is.
|
||||
const size = await (0, caching_utils_1.getTotalCacheSize)(cacheConfig.paths, logger, true);
|
||||
// Skip uploading an empty cache.
|
||||
if (size === 0) {
|
||||
logger.info(`Skipping upload of dependency cache for ${language} since it is empty.`);
|
||||
continue;
|
||||
}
|
||||
const key = await cacheKey(language, cacheConfig);
|
||||
logger.info(`Uploading cache of size ${size} for ${language} with key ${key}...`);
|
||||
try {
|
||||
await actionsCache.saveCache(cacheConfig.paths, key);
|
||||
}
|
||||
catch (error) {
|
||||
// `ReserveCacheError` indicates that the cache key is already in use, which means that a
|
||||
// cache with that key already exists or is in the process of being uploaded by another
|
||||
// workflow. We can ignore this.
|
||||
if (error instanceof actionsCache.ReserveCacheError) {
|
||||
logger.info(`Not uploading cache for ${language}, because ${key} is already in use.`);
|
||||
logger.debug(error.message);
|
||||
}
|
||||
else {
|
||||
// Propagate other errors upwards.
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Computes a cache key for the specified language.
|
||||
*
|
||||
* @param language The language being analyzed.
|
||||
* @param cacheConfig The cache configuration for the language.
|
||||
* @returns A cache key capturing information about the project(s) being analyzed in the specified language.
|
||||
*/
|
||||
async function cacheKey(language, cacheConfig) {
|
||||
const hash = await glob.hashFiles(cacheConfig.hash.join("\n"));
|
||||
return `${await cachePrefix(language)}${hash}`;
|
||||
}
|
||||
/**
|
||||
* Constructs a prefix for the cache key, comprised of a CodeQL-specific prefix, a version number that
|
||||
* can be changed to invalidate old caches, the runner's operating system, and the specified language name.
|
||||
*
|
||||
* @param language The language being analyzed.
|
||||
* @returns The prefix that identifies what a cache is for.
|
||||
*/
|
||||
async function cachePrefix(language) {
|
||||
const runnerOs = (0, util_1.getRequiredEnvParam)("RUNNER_OS");
|
||||
const customPrefix = process.env[environment_1.EnvVar.DEPENDENCY_CACHING_PREFIX];
|
||||
let prefix = CODEQL_DEPENDENCY_CACHE_PREFIX;
|
||||
if (customPrefix !== undefined && customPrefix.length > 0) {
|
||||
prefix = `${prefix}-${customPrefix}`;
|
||||
}
|
||||
return `${prefix}-${CODEQL_DEPENDENCY_CACHE_VERSION}-${runnerOs}-${language}-`;
|
||||
}
|
||||
//# sourceMappingURL=dependency-caching.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"dependency-caching.js","sourceRoot":"","sources":["../src/dependency-caching.ts"],"names":[],"mappings":"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"}
|
||||
101
lib/diagnostics.js
generated
101
lib/diagnostics.js
generated
@@ -1,101 +0,0 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.makeDiagnostic = makeDiagnostic;
|
||||
exports.addDiagnostic = addDiagnostic;
|
||||
exports.logUnwrittenDiagnostics = logUnwrittenDiagnostics;
|
||||
exports.flushDiagnostics = flushDiagnostics;
|
||||
const fs_1 = require("fs");
|
||||
const path_1 = __importDefault(require("path"));
|
||||
const logging_1 = require("./logging");
|
||||
const util_1 = require("./util");
|
||||
/** A list of diagnostics which have not yet been written to disk. */
|
||||
let unwrittenDiagnostics = [];
|
||||
/**
|
||||
* Constructs a new diagnostic message with the specified id and name, as well as optional additional data.
|
||||
*
|
||||
* @param id An identifier under which it makes sense to group this diagnostic message.
|
||||
* @param name Display name for the ID.
|
||||
* @param data Optional additional data to initialize the diagnostic with.
|
||||
* @returns Returns the new diagnostic message.
|
||||
*/
|
||||
function makeDiagnostic(id, name, data = undefined) {
|
||||
return {
|
||||
...data,
|
||||
timestamp: data?.timestamp ?? new Date().toISOString(),
|
||||
source: { ...data?.source, id, name },
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Adds the given diagnostic to the database. If the database does not yet exist,
|
||||
* the diagnostic will be written to it once it has been created.
|
||||
*
|
||||
* @param config The configuration that tells us where to store the diagnostic.
|
||||
* @param language The language which the diagnostic is for.
|
||||
* @param diagnostic The diagnostic message to add to the database.
|
||||
*/
|
||||
function addDiagnostic(config, language, diagnostic) {
|
||||
const logger = (0, logging_1.getActionsLogger)();
|
||||
const databasePath = language
|
||||
? (0, util_1.getCodeQLDatabasePath)(config, language)
|
||||
: config.dbLocation;
|
||||
// Check that the database exists before writing to it. If the database does not yet exist,
|
||||
// store the diagnostic in memory and write it later.
|
||||
if ((0, fs_1.existsSync)(databasePath)) {
|
||||
writeDiagnostic(config, language, diagnostic);
|
||||
}
|
||||
else {
|
||||
logger.debug(`Writing a diagnostic for ${language}, but the database at ${databasePath} does not exist yet.`);
|
||||
unwrittenDiagnostics.push({ diagnostic, language });
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Writes the given diagnostic to the database.
|
||||
*
|
||||
* @param config The configuration that tells us where to store the diagnostic.
|
||||
* @param language The language which the diagnostic is for.
|
||||
* @param diagnostic The diagnostic message to add to the database.
|
||||
*/
|
||||
function writeDiagnostic(config, language, diagnostic) {
|
||||
const logger = (0, logging_1.getActionsLogger)();
|
||||
const databasePath = language
|
||||
? (0, util_1.getCodeQLDatabasePath)(config, language)
|
||||
: config.dbLocation;
|
||||
const diagnosticsPath = path_1.default.resolve(databasePath, "diagnostic", "codeql-action");
|
||||
try {
|
||||
// Create the directory if it doesn't exist yet.
|
||||
(0, fs_1.mkdirSync)(diagnosticsPath, { recursive: true });
|
||||
const jsonPath = path_1.default.resolve(diagnosticsPath,
|
||||
// Remove colons from the timestamp as these are not allowed in Windows filenames.
|
||||
`codeql-action-${diagnostic.timestamp.replaceAll(":", "")}.json`);
|
||||
(0, fs_1.writeFileSync)(jsonPath, JSON.stringify(diagnostic));
|
||||
}
|
||||
catch (err) {
|
||||
logger.warning(`Unable to write diagnostic message to database: ${err}`);
|
||||
logger.debug(JSON.stringify(diagnostic));
|
||||
}
|
||||
}
|
||||
/** Report if there are unwritten diagnostics and write them to the log. */
|
||||
function logUnwrittenDiagnostics() {
|
||||
const logger = (0, logging_1.getActionsLogger)();
|
||||
const num = unwrittenDiagnostics.length;
|
||||
if (num > 0) {
|
||||
logger.warning(`${num} diagnostic(s) could not be written to the database and will not appear on the Tool Status Page.`);
|
||||
for (const unwritten of unwrittenDiagnostics) {
|
||||
logger.debug(JSON.stringify(unwritten.diagnostic));
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Writes all unwritten diagnostics to disk. */
|
||||
function flushDiagnostics(config) {
|
||||
const logger = (0, logging_1.getActionsLogger)();
|
||||
logger.debug(`Writing ${unwrittenDiagnostics.length} diagnostic(s) to database.`);
|
||||
for (const unwritten of unwrittenDiagnostics) {
|
||||
writeDiagnostic(config, unwritten.language, unwritten.diagnostic);
|
||||
}
|
||||
// Reset the unwritten diagnostics array.
|
||||
unwrittenDiagnostics = [];
|
||||
}
|
||||
//# sourceMappingURL=diagnostics.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"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"}
|
||||
95
lib/diff-informed-analysis-utils.js
generated
95
lib/diff-informed-analysis-utils.js
generated
@@ -1,95 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.shouldPerformDiffInformedAnalysis = shouldPerformDiffInformedAnalysis;
|
||||
exports.getDiffInformedAnalysisBranches = getDiffInformedAnalysisBranches;
|
||||
exports.writeDiffRangesJsonFile = writeDiffRangesJsonFile;
|
||||
exports.readDiffRangesJsonFile = readDiffRangesJsonFile;
|
||||
const fs = __importStar(require("fs"));
|
||||
const path = __importStar(require("path"));
|
||||
const actionsUtil = __importStar(require("./actions-util"));
|
||||
const api_client_1 = require("./api-client");
|
||||
const feature_flags_1 = require("./feature-flags");
|
||||
const util_1 = require("./util");
|
||||
/**
|
||||
* Check if the action should perform diff-informed analysis.
|
||||
*/
|
||||
async function shouldPerformDiffInformedAnalysis(codeql, features, logger) {
|
||||
return ((await getDiffInformedAnalysisBranches(codeql, features, logger)) !==
|
||||
undefined);
|
||||
}
|
||||
/**
|
||||
* Get the branches to use for diff-informed analysis.
|
||||
*
|
||||
* @returns If the action should perform diff-informed analysis, return
|
||||
* the base and head branches that should be used to compute the diff ranges.
|
||||
* Otherwise return `undefined`.
|
||||
*/
|
||||
async function getDiffInformedAnalysisBranches(codeql, features, logger) {
|
||||
if (!(await features.getValue(feature_flags_1.Feature.DiffInformedQueries, codeql))) {
|
||||
return undefined;
|
||||
}
|
||||
const gitHubVersion = await (0, api_client_1.getGitHubVersion)();
|
||||
if (gitHubVersion.type === util_1.GitHubVariant.GHES &&
|
||||
(0, util_1.satisfiesGHESVersion)(gitHubVersion.version, "<3.19", true)) {
|
||||
return undefined;
|
||||
}
|
||||
const branches = actionsUtil.getPullRequestBranches();
|
||||
if (!branches) {
|
||||
logger.info("Not performing diff-informed analysis " +
|
||||
"because we are not analyzing a pull request.");
|
||||
}
|
||||
return branches;
|
||||
}
|
||||
function getDiffRangesJsonFilePath() {
|
||||
return path.join(actionsUtil.getTemporaryDirectory(), "pr-diff-range.json");
|
||||
}
|
||||
function writeDiffRangesJsonFile(logger, ranges) {
|
||||
const jsonContents = JSON.stringify(ranges, null, 2);
|
||||
const jsonFilePath = getDiffRangesJsonFilePath();
|
||||
fs.writeFileSync(jsonFilePath, jsonContents);
|
||||
logger.debug(`Wrote pr-diff-range JSON file to ${jsonFilePath}:\n${jsonContents}`);
|
||||
}
|
||||
function readDiffRangesJsonFile(logger) {
|
||||
const jsonFilePath = getDiffRangesJsonFilePath();
|
||||
if (!fs.existsSync(jsonFilePath)) {
|
||||
logger.debug(`Diff ranges JSON file does not exist at ${jsonFilePath}`);
|
||||
return undefined;
|
||||
}
|
||||
const jsonContents = fs.readFileSync(jsonFilePath, "utf8");
|
||||
logger.debug(`Read pr-diff-range JSON file from ${jsonFilePath}:\n${jsonContents}`);
|
||||
return JSON.parse(jsonContents);
|
||||
}
|
||||
//# sourceMappingURL=diff-informed-analysis-utils.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"diff-informed-analysis-utils.js","sourceRoot":"","sources":["../src/diff-informed-analysis-utils.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAcA,8EASC;AASD,0EAyBC;AAYD,0DAUC;AAED,wDAaC;AA9FD,uCAAyB;AACzB,2CAA6B;AAE7B,4DAA8C;AAE9C,6CAAgD;AAEhD,mDAA6D;AAE7D,iCAA6D;AAE7D;;GAEG;AACI,KAAK,UAAU,iCAAiC,CACrD,MAAc,EACd,QAA2B,EAC3B,MAAc;IAEd,OAAO,CACL,CAAC,MAAM,+BAA+B,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;QACjE,SAAS,CACV,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACI,KAAK,UAAU,+BAA+B,CACnD,MAAc,EACd,QAA2B,EAC3B,MAAc;IAEd,IAAI,CAAC,CAAC,MAAM,QAAQ,CAAC,QAAQ,CAAC,uBAAO,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;QACpE,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,aAAa,GAAG,MAAM,IAAA,6BAAgB,GAAE,CAAC;IAC/C,IACE,aAAa,CAAC,IAAI,KAAK,oBAAa,CAAC,IAAI;QACzC,IAAA,2BAAoB,EAAC,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAC1D,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,QAAQ,GAAG,WAAW,CAAC,sBAAsB,EAAE,CAAC;IACtD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,CAAC,IAAI,CACT,wCAAwC;YACtC,8CAA8C,CACjD,CAAC;IACJ,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAQD,SAAS,yBAAyB;IAChC,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,qBAAqB,EAAE,EAAE,oBAAoB,CAAC,CAAC;AAC9E,CAAC;AAED,SAAgB,uBAAuB,CACrC,MAAc,EACd,MAAwB;IAExB,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACrD,MAAM,YAAY,GAAG,yBAAyB,EAAE,CAAC;IACjD,EAAE,CAAC,aAAa,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;IAC7C,MAAM,CAAC,KAAK,CACV,oCAAoC,YAAY,MAAM,YAAY,EAAE,CACrE,CAAC;AACJ,CAAC;AAED,SAAgB,sBAAsB,CACpC,MAAc;IAEd,MAAM,YAAY,GAAG,yBAAyB,EAAE,CAAC;IACjD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QACjC,MAAM,CAAC,KAAK,CAAC,2CAA2C,YAAY,EAAE,CAAC,CAAC;QACxE,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,YAAY,GAAG,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAC3D,MAAM,CAAC,KAAK,CACV,qCAAqC,YAAY,MAAM,YAAY,EAAE,CACtE,CAAC;IACF,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAqB,CAAC;AACtD,CAAC"}
|
||||
130
lib/diff-informed-analysis-utils.test.js
generated
130
lib/diff-informed-analysis-utils.test.js
generated
@@ -1,130 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const ava_1 = __importDefault(require("ava"));
|
||||
const sinon = __importStar(require("sinon"));
|
||||
const actionsUtil = __importStar(require("./actions-util"));
|
||||
const apiClient = __importStar(require("./api-client"));
|
||||
const diff_informed_analysis_utils_1 = require("./diff-informed-analysis-utils");
|
||||
const feature_flags_1 = require("./feature-flags");
|
||||
const logging_1 = require("./logging");
|
||||
const repository_1 = require("./repository");
|
||||
const testing_utils_1 = require("./testing-utils");
|
||||
const util_1 = require("./util");
|
||||
(0, testing_utils_1.setupTests)(ava_1.default);
|
||||
const defaultTestCase = {
|
||||
featureEnabled: true,
|
||||
gitHubVersion: {
|
||||
type: util_1.GitHubVariant.DOTCOM,
|
||||
},
|
||||
pullRequestBranches: {
|
||||
base: "main",
|
||||
head: "feature-branch",
|
||||
},
|
||||
codeQLVersion: "2.21.0",
|
||||
};
|
||||
const testShouldPerformDiffInformedAnalysis = ava_1.default.macro({
|
||||
exec: async (t, _title, partialTestCase, expectedResult) => {
|
||||
return await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
const testCase = { ...defaultTestCase, ...partialTestCase };
|
||||
const logger = (0, logging_1.getRunnerLogger)(true);
|
||||
const codeql = (0, testing_utils_1.mockCodeQLVersion)(testCase.codeQLVersion);
|
||||
if (testCase.diffInformedQueriesEnvVar !== undefined) {
|
||||
process.env.CODEQL_ACTION_DIFF_INFORMED_QUERIES =
|
||||
testCase.diffInformedQueriesEnvVar.toString();
|
||||
}
|
||||
else {
|
||||
delete process.env.CODEQL_ACTION_DIFF_INFORMED_QUERIES;
|
||||
}
|
||||
const features = new feature_flags_1.Features(testCase.gitHubVersion, (0, repository_1.parseRepositoryNwo)("github/example"), tmpDir, logger);
|
||||
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, {
|
||||
[feature_flags_1.Feature.DiffInformedQueries]: testCase.featureEnabled,
|
||||
});
|
||||
const getGitHubVersionStub = sinon
|
||||
.stub(apiClient, "getGitHubVersion")
|
||||
.resolves(testCase.gitHubVersion);
|
||||
const getPullRequestBranchesStub = sinon
|
||||
.stub(actionsUtil, "getPullRequestBranches")
|
||||
.returns(testCase.pullRequestBranches);
|
||||
const result = await (0, diff_informed_analysis_utils_1.shouldPerformDiffInformedAnalysis)(codeql, features, logger);
|
||||
t.is(result, expectedResult);
|
||||
delete process.env.CODEQL_ACTION_DIFF_INFORMED_QUERIES;
|
||||
getGitHubVersionStub.restore();
|
||||
getPullRequestBranchesStub.restore();
|
||||
});
|
||||
},
|
||||
title: (_, title) => `shouldPerformDiffInformedAnalysis: ${title}`,
|
||||
});
|
||||
(0, ava_1.default)(testShouldPerformDiffInformedAnalysis, "returns true in the default test case", {}, true);
|
||||
(0, ava_1.default)(testShouldPerformDiffInformedAnalysis, "returns false when feature flag is disabled from the API", {
|
||||
featureEnabled: false,
|
||||
}, false);
|
||||
(0, ava_1.default)(testShouldPerformDiffInformedAnalysis, "returns false when CODEQL_ACTION_DIFF_INFORMED_QUERIES is set to false", {
|
||||
featureEnabled: true,
|
||||
diffInformedQueriesEnvVar: false,
|
||||
}, false);
|
||||
(0, ava_1.default)(testShouldPerformDiffInformedAnalysis, "returns true when CODEQL_ACTION_DIFF_INFORMED_QUERIES is set to true", {
|
||||
featureEnabled: false,
|
||||
diffInformedQueriesEnvVar: true,
|
||||
}, true);
|
||||
(0, ava_1.default)(testShouldPerformDiffInformedAnalysis, "returns false for CodeQL version 2.20.0", {
|
||||
codeQLVersion: "2.20.0",
|
||||
}, false);
|
||||
(0, ava_1.default)(testShouldPerformDiffInformedAnalysis, "returns false for invalid GHES version", {
|
||||
gitHubVersion: {
|
||||
type: util_1.GitHubVariant.GHES,
|
||||
version: "invalid-version",
|
||||
},
|
||||
}, false);
|
||||
(0, ava_1.default)(testShouldPerformDiffInformedAnalysis, "returns false for GHES version 3.18.5", {
|
||||
gitHubVersion: {
|
||||
type: util_1.GitHubVariant.GHES,
|
||||
version: "3.18.5",
|
||||
},
|
||||
}, false);
|
||||
(0, ava_1.default)(testShouldPerformDiffInformedAnalysis, "returns true for GHES version 3.19.0", {
|
||||
gitHubVersion: {
|
||||
type: util_1.GitHubVariant.GHES,
|
||||
version: "3.19.0",
|
||||
},
|
||||
}, true);
|
||||
(0, ava_1.default)(testShouldPerformDiffInformedAnalysis, "returns false when not a pull request", {
|
||||
pullRequestBranches: undefined,
|
||||
}, false);
|
||||
//# sourceMappingURL=diff-informed-analysis-utils.test.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"diff-informed-analysis-utils.test.js","sourceRoot":"","sources":["../src/diff-informed-analysis-utils.test.ts"],"names":[],"mappings":"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"}
|
||||
18
lib/doc-url.js
generated
18
lib/doc-url.js
generated
@@ -1,18 +0,0 @@
|
||||
"use strict";
|
||||
/**
|
||||
* URLs to code scanning docs linked to from CodeQL Action logs.
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.DocUrl = void 0;
|
||||
var DocUrl;
|
||||
(function (DocUrl) {
|
||||
DocUrl["ASSIGNING_PERMISSIONS_TO_JOBS"] = "https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs";
|
||||
DocUrl["AUTOMATIC_BUILD_FAILED"] = "https://docs.github.com/en/code-security/code-scanning/troubleshooting-code-scanning/automatic-build-failed";
|
||||
DocUrl["DEFINE_ENV_VARIABLES"] = "https://docs.github.com/en/actions/learn-github-actions/variables#defining-environment-variables-for-a-single-workflow";
|
||||
DocUrl["SCANNING_ON_PUSH"] = "https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning#scanning-on-push";
|
||||
DocUrl["SPECIFY_BUILD_STEPS_MANUALLY"] = "https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages#about-specifying-build-steps-manually";
|
||||
DocUrl["TRACK_CODE_SCANNING_ALERTS_ACROSS_RUNS"] = "https://docs.github.com/en/enterprise-cloud@latest/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning#providing-data-to-track-code-scanning-alerts-across-runs";
|
||||
DocUrl["CODEQL_BUILD_MODES"] = "https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages#codeql-build-modes";
|
||||
DocUrl["SYSTEM_REQUIREMENTS"] = "https://codeql.github.com/docs/codeql-overview/system-requirements/";
|
||||
})(DocUrl || (exports.DocUrl = DocUrl = {}));
|
||||
//# sourceMappingURL=doc-url.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"doc-url.js","sourceRoot":"","sources":["../src/doc-url.ts"],"names":[],"mappings":";AAAA;;GAEG;;;AAEH,IAAY,MASX;AATD,WAAY,MAAM;IAChB,uHAA6G,CAAA;IAC7G,gJAAsI,CAAA;IACtI,yJAA+I,CAAA;IAC/I,qMAA2L,CAAA;IAC3L,gOAAsN,CAAA;IACtN,2PAAiP,CAAA;IACjP,mMAAyL,CAAA;IACzL,qGAA2F,CAAA;AAC7F,CAAC,EATW,MAAM,sBAAN,MAAM,QASjB"}
|
||||
101
lib/environment.js
generated
101
lib/environment.js
generated
@@ -1,101 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.EnvVar = void 0;
|
||||
/**
|
||||
* Environment variables used by the CodeQL Action.
|
||||
*
|
||||
* We recommend prefixing environment variables with `CODEQL_ACTION_`
|
||||
* to reduce the risk that they are overwritten by other steps.
|
||||
*/
|
||||
var EnvVar;
|
||||
(function (EnvVar) {
|
||||
/** Whether the `analyze` Action completes successfully. */
|
||||
EnvVar["ANALYZE_DID_COMPLETE_SUCCESSFULLY"] = "CODEQL_ACTION_ANALYZE_DID_COMPLETE_SUCCESSFULLY";
|
||||
/** Whether the `autobuild` Action completes successfully. */
|
||||
EnvVar["AUTOBUILD_DID_COMPLETE_SUCCESSFULLY"] = "CODEQL_ACTION_AUTOBUILD_DID_COMPLETE_SUCCESSFULLY";
|
||||
/**
|
||||
* The verbosity level of the CLI. One of the following: `errors`, `warnings`, `progress`,
|
||||
* `progress+`, `progress++`, `progress+++`.
|
||||
*/
|
||||
EnvVar["CLI_VERBOSITY"] = "CODEQL_VERBOSITY";
|
||||
/** Whether the CodeQL Action has invoked the Go autobuilder. */
|
||||
EnvVar["DID_AUTOBUILD_GOLANG"] = "CODEQL_ACTION_DID_AUTOBUILD_GOLANG";
|
||||
/**
|
||||
* Whether to disable the SARIF post-processing in the Action that removes duplicate locations from
|
||||
* notifications in the `run[].invocations[].toolExecutionNotifications` SARIF property.
|
||||
*/
|
||||
EnvVar["DISABLE_DUPLICATE_LOCATION_FIX"] = "CODEQL_ACTION_DISABLE_DUPLICATE_LOCATION_FIX";
|
||||
/**
|
||||
* Whether the CodeQL Action is using its own deprecated and non-standard way of scanning for
|
||||
* multiple languages.
|
||||
*/
|
||||
EnvVar["FEATURE_MULTI_LANGUAGE"] = "CODEQL_ACTION_FEATURE_MULTI_LANGUAGE";
|
||||
/** Whether the CodeQL Action is using its own sandwiched workflow mechanism. */
|
||||
EnvVar["FEATURE_SANDWICH"] = "CODEQL_ACTION_FEATURE_SANDWICH";
|
||||
/**
|
||||
* Whether the CodeQL Action might combine SARIF output from several `interpret-results` runs for
|
||||
* the same language.
|
||||
*/
|
||||
EnvVar["FEATURE_SARIF_COMBINE"] = "CODEQL_ACTION_FEATURE_SARIF_COMBINE";
|
||||
/** Whether the CodeQL Action will upload SARIF, not the CLI. */
|
||||
EnvVar["FEATURE_WILL_UPLOAD"] = "CODEQL_ACTION_FEATURE_WILL_UPLOAD";
|
||||
/** Whether the CodeQL Action has already warned the user about low disk space. */
|
||||
EnvVar["HAS_WARNED_ABOUT_DISK_SPACE"] = "CODEQL_ACTION_HAS_WARNED_ABOUT_DISK_SPACE";
|
||||
/** Whether the init action has been run. */
|
||||
EnvVar["INIT_ACTION_HAS_RUN"] = "CODEQL_ACTION_INIT_HAS_RUN";
|
||||
/** Whether the error for a deprecated version of the CodeQL Action was logged. */
|
||||
EnvVar["LOG_VERSION_DEPRECATION"] = "CODEQL_ACTION_DID_LOG_VERSION_DEPRECATION";
|
||||
/**
|
||||
* For macOS. Result of `csrutil status` to determine whether System Integrity
|
||||
* Protection is enabled.
|
||||
*/
|
||||
EnvVar["IS_SIP_ENABLED"] = "CODEQL_ACTION_IS_SIP_ENABLED";
|
||||
/** UUID representing the current job run. */
|
||||
EnvVar["JOB_RUN_UUID"] = "JOB_RUN_UUID";
|
||||
/** Status for the entire job, submitted to the status report in `init-post` */
|
||||
EnvVar["JOB_STATUS"] = "CODEQL_ACTION_JOB_STATUS";
|
||||
EnvVar["ODASA_TRACER_CONFIGURATION"] = "ODASA_TRACER_CONFIGURATION";
|
||||
/** The value of the `output` input for the analyze action. */
|
||||
EnvVar["SARIF_RESULTS_OUTPUT_DIR"] = "CODEQL_ACTION_SARIF_RESULTS_OUTPUT_DIR";
|
||||
/**
|
||||
* What percentage of the total amount of RAM over 8 GB that the Action should reserve for the
|
||||
* system.
|
||||
*/
|
||||
EnvVar["SCALING_RESERVED_RAM_PERCENTAGE"] = "CODEQL_ACTION_SCALING_RESERVED_RAM_PERCENTAGE";
|
||||
/** Whether to suppress the warning if the current CLI will soon be unsupported. */
|
||||
EnvVar["SUPPRESS_DEPRECATED_SOON_WARNING"] = "CODEQL_ACTION_SUPPRESS_DEPRECATED_SOON_WARNING";
|
||||
/** Whether to disable uploading SARIF results or status reports to the GitHub API */
|
||||
EnvVar["TEST_MODE"] = "CODEQL_ACTION_TEST_MODE";
|
||||
EnvVar["TESTING_ENVIRONMENT"] = "CODEQL_ACTION_TESTING_ENVIRONMENT";
|
||||
/** Semver of the CodeQL Action as specified in `package.json`. */
|
||||
EnvVar["VERSION"] = "CODEQL_ACTION_VERSION";
|
||||
/**
|
||||
* The time at which the first action (normally init) started executing.
|
||||
* If a workflow invokes a different action without first invoking the init
|
||||
* action (i.e. the upload action is being used by a third-party integrator)
|
||||
* then this variable will be assigned the start time of the action invoked
|
||||
* rather that the init action.
|
||||
*/
|
||||
EnvVar["WORKFLOW_STARTED_AT"] = "CODEQL_WORKFLOW_STARTED_AT";
|
||||
/**
|
||||
* The path where we initially discovered the Go binary in the system path.
|
||||
* We check this later to ensure that it hasn't been tampered with by a late e.g. `setup-go` step.
|
||||
*/
|
||||
EnvVar["GO_BINARY_LOCATION"] = "CODEQL_ACTION_GO_BINARY";
|
||||
/**
|
||||
* Used as an alternative to the `dependency-caching` input for the `init` Action.
|
||||
* Useful for experiments where it is easier to set an environment variable than
|
||||
* change the inputs to the Action.
|
||||
*/
|
||||
EnvVar["DEPENDENCY_CACHING"] = "CODEQL_ACTION_DEPENDENCY_CACHING";
|
||||
/**
|
||||
* An optional string to add into the cache key used by dependency caching.
|
||||
* Useful for testing purposes where multiple caches may be stored in the same repository.
|
||||
*/
|
||||
EnvVar["DEPENDENCY_CACHING_PREFIX"] = "CODEQL_ACTION_DEPENDENCY_CACHE_PREFIX";
|
||||
/**
|
||||
* Whether to enable experimental extractors for CodeQL.
|
||||
*/
|
||||
EnvVar["EXPERIMENTAL_FEATURES"] = "CODEQL_ENABLE_EXPERIMENTAL_FEATURES";
|
||||
})(EnvVar || (exports.EnvVar = EnvVar = {}));
|
||||
//# sourceMappingURL=environment.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"environment.js","sourceRoot":"","sources":["../src/environment.ts"],"names":[],"mappings":";;;AAAA;;;;;GAKG;AACH,IAAY,MAmHX;AAnHD,WAAY,MAAM;IAChB,2DAA2D;IAC3D,+FAAqF,CAAA;IAErF,6DAA6D;IAC7D,mGAAyF,CAAA;IAEzF;;;OAGG;IACH,4CAAkC,CAAA;IAElC,gEAAgE;IAChE,qEAA2D,CAAA;IAE3D;;;OAGG;IACH,yFAA+E,CAAA;IAE/E;;;OAGG;IACH,yEAA+D,CAAA;IAE/D,gFAAgF;IAChF,6DAAmD,CAAA;IAEnD;;;OAGG;IACH,uEAA6D,CAAA;IAE7D,gEAAgE;IAChE,mEAAyD,CAAA;IAEzD,kFAAkF;IAClF,mFAAyE,CAAA;IAEzE,4CAA4C;IAC5C,4DAAkD,CAAA;IAElD,kFAAkF;IAClF,+EAAqE,CAAA;IAErE;;;OAGG;IACH,yDAA+C,CAAA;IAE/C,6CAA6C;IAC7C,uCAA6B,CAAA;IAE7B,+EAA+E;IAC/E,iDAAuC,CAAA;IAEvC,mEAAyD,CAAA;IAEzD,8DAA8D;IAC9D,6EAAmE,CAAA;IAEnE;;;OAGG;IACH,2FAAiF,CAAA;IAEjF,mFAAmF;IACnF,6FAAmF,CAAA;IAEnF,qFAAqF;IACrF,+CAAqC,CAAA;IAErC,mEAAyD,CAAA;IAEzD,kEAAkE;IAClE,2CAAiC,CAAA;IAEjC;;;;;;OAMG;IACH,4DAAkD,CAAA;IAElD;;;OAGG;IACH,wDAA8C,CAAA;IAE9C;;;;OAIG;IACH,iEAAuD,CAAA;IAEvD;;;OAGG;IACH,6EAAmE,CAAA;IAEnE;;OAEG;IACH,uEAA6D,CAAA;AAC/D,CAAC,EAnHW,MAAM,sBAAN,MAAM,QAmHjB"}
|
||||
505
lib/feature-flags.js
generated
505
lib/feature-flags.js
generated
@@ -1,505 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Features = exports.FEATURE_FLAGS_FILE_NAME = exports.featureConfig = exports.Feature = exports.CODEQL_VERSION_ZSTD_BUNDLE = void 0;
|
||||
const fs = __importStar(require("fs"));
|
||||
const path = __importStar(require("path"));
|
||||
const semver = __importStar(require("semver"));
|
||||
const api_client_1 = require("./api-client");
|
||||
const defaults = __importStar(require("./defaults.json"));
|
||||
const overlay_database_utils_1 = require("./overlay-database-utils");
|
||||
const tools_features_1 = require("./tools-features");
|
||||
const util = __importStar(require("./util"));
|
||||
const DEFAULT_VERSION_FEATURE_FLAG_PREFIX = "default_codeql_version_";
|
||||
const DEFAULT_VERSION_FEATURE_FLAG_SUFFIX = "_enabled";
|
||||
/**
|
||||
* The first version of the CodeQL Bundle that shipped with zstd-compressed bundles.
|
||||
*/
|
||||
exports.CODEQL_VERSION_ZSTD_BUNDLE = "2.19.0";
|
||||
/**
|
||||
* Feature enablement as returned by the GitHub API endpoint.
|
||||
*
|
||||
* Do not include the `codeql_action_` prefix as this is stripped by the API
|
||||
* endpoint.
|
||||
*
|
||||
* Legacy features should end with `_enabled`.
|
||||
*/
|
||||
var Feature;
|
||||
(function (Feature) {
|
||||
Feature["CleanupTrapCaches"] = "cleanup_trap_caches";
|
||||
Feature["CppDependencyInstallation"] = "cpp_dependency_installation_enabled";
|
||||
Feature["DiffInformedQueries"] = "diff_informed_queries";
|
||||
Feature["DisableCsharpBuildless"] = "disable_csharp_buildless";
|
||||
Feature["DisableJavaBuildlessEnabled"] = "disable_java_buildless_enabled";
|
||||
Feature["DisableKotlinAnalysisEnabled"] = "disable_kotlin_analysis_enabled";
|
||||
Feature["ExportDiagnosticsEnabled"] = "export_diagnostics_enabled";
|
||||
Feature["OverlayAnalysis"] = "overlay_analysis";
|
||||
Feature["OverlayAnalysisActions"] = "overlay_analysis_actions";
|
||||
Feature["OverlayAnalysisCodeScanningActions"] = "overlay_analysis_code_scanning_actions";
|
||||
Feature["OverlayAnalysisCodeScanningCpp"] = "overlay_analysis_code_scanning_cpp";
|
||||
Feature["OverlayAnalysisCodeScanningCsharp"] = "overlay_analysis_code_scanning_csharp";
|
||||
Feature["OverlayAnalysisCodeScanningGo"] = "overlay_analysis_code_scanning_go";
|
||||
Feature["OverlayAnalysisCodeScanningJava"] = "overlay_analysis_code_scanning_java";
|
||||
Feature["OverlayAnalysisCodeScanningJavascript"] = "overlay_analysis_code_scanning_javascript";
|
||||
Feature["OverlayAnalysisCodeScanningPython"] = "overlay_analysis_code_scanning_python";
|
||||
Feature["OverlayAnalysisCodeScanningRuby"] = "overlay_analysis_code_scanning_ruby";
|
||||
Feature["OverlayAnalysisCodeScanningRust"] = "overlay_analysis_code_scanning_rust";
|
||||
Feature["OverlayAnalysisCodeScanningSwift"] = "overlay_analysis_code_scanning_swift";
|
||||
Feature["OverlayAnalysisCpp"] = "overlay_analysis_cpp";
|
||||
Feature["OverlayAnalysisCsharp"] = "overlay_analysis_csharp";
|
||||
Feature["OverlayAnalysisGo"] = "overlay_analysis_go";
|
||||
Feature["OverlayAnalysisJava"] = "overlay_analysis_java";
|
||||
Feature["OverlayAnalysisJavascript"] = "overlay_analysis_javascript";
|
||||
Feature["OverlayAnalysisPython"] = "overlay_analysis_python";
|
||||
Feature["OverlayAnalysisRuby"] = "overlay_analysis_ruby";
|
||||
Feature["OverlayAnalysisRust"] = "overlay_analysis_rust";
|
||||
Feature["OverlayAnalysisSwift"] = "overlay_analysis_swift";
|
||||
Feature["PythonDefaultIsToNotExtractStdlib"] = "python_default_is_to_not_extract_stdlib";
|
||||
Feature["QaTelemetryEnabled"] = "qa_telemetry_enabled";
|
||||
})(Feature || (exports.Feature = Feature = {}));
|
||||
exports.featureConfig = {
|
||||
[Feature.CleanupTrapCaches]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_CLEANUP_TRAP_CACHES",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.CppDependencyInstallation]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_EXTRACTOR_CPP_AUTOINSTALL_DEPENDENCIES",
|
||||
legacyApi: true,
|
||||
minimumVersion: "2.15.0",
|
||||
},
|
||||
[Feature.DiffInformedQueries]: {
|
||||
defaultValue: true,
|
||||
envVar: "CODEQL_ACTION_DIFF_INFORMED_QUERIES",
|
||||
minimumVersion: "2.21.0",
|
||||
},
|
||||
[Feature.DisableCsharpBuildless]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_DISABLE_CSHARP_BUILDLESS",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.DisableJavaBuildlessEnabled]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_DISABLE_JAVA_BUILDLESS",
|
||||
legacyApi: true,
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.DisableKotlinAnalysisEnabled]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_DISABLE_KOTLIN_ANALYSIS",
|
||||
legacyApi: true,
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.ExportDiagnosticsEnabled]: {
|
||||
defaultValue: true,
|
||||
envVar: "CODEQL_ACTION_EXPORT_DIAGNOSTICS",
|
||||
legacyApi: true,
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.OverlayAnalysis]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS",
|
||||
minimumVersion: overlay_database_utils_1.CODEQL_OVERLAY_MINIMUM_VERSION,
|
||||
},
|
||||
[Feature.OverlayAnalysisActions]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_ACTIONS",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.OverlayAnalysisCodeScanningActions]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_CODE_SCANNING_ACTIONS",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.OverlayAnalysisCodeScanningCpp]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_CODE_SCANNING_CPP",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.OverlayAnalysisCodeScanningCsharp]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_CODE_SCANNING_CSHARP",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.OverlayAnalysisCodeScanningGo]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_CODE_SCANNING_GO",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.OverlayAnalysisCodeScanningJava]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_CODE_SCANNING_JAVA",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.OverlayAnalysisCodeScanningJavascript]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_CODE_SCANNING_JAVASCRIPT",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.OverlayAnalysisCodeScanningPython]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_CODE_SCANNING_PYTHON",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.OverlayAnalysisCodeScanningRuby]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_CODE_SCANNING_RUBY",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.OverlayAnalysisCodeScanningRust]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_CODE_SCANNING_RUST",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.OverlayAnalysisCodeScanningSwift]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_CODE_SCANNING_SWIFT",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.OverlayAnalysisCpp]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_CPP",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.OverlayAnalysisCsharp]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_CSHARP",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.OverlayAnalysisGo]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_GO",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.OverlayAnalysisJava]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_JAVA",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.OverlayAnalysisJavascript]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_JAVASCRIPT",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.OverlayAnalysisPython]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_PYTHON",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.OverlayAnalysisRuby]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_RUBY",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.OverlayAnalysisRust]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_RUST",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.OverlayAnalysisSwift]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_SWIFT",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.PythonDefaultIsToNotExtractStdlib]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_DISABLE_PYTHON_STANDARD_LIBRARY_EXTRACTION",
|
||||
minimumVersion: undefined,
|
||||
toolsFeature: tools_features_1.ToolsFeature.PythonDefaultIsToNotExtractStdlib,
|
||||
},
|
||||
[Feature.QaTelemetryEnabled]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_QA_TELEMETRY",
|
||||
legacyApi: true,
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
};
|
||||
exports.FEATURE_FLAGS_FILE_NAME = "cached-feature-flags.json";
|
||||
/**
|
||||
* Determines the enablement status of a number of features.
|
||||
* If feature enablement is not able to be determined locally, a request to the
|
||||
* GitHub API is made to determine the enablement status.
|
||||
*/
|
||||
class Features {
|
||||
constructor(gitHubVersion, repositoryNwo, tempDir, logger) {
|
||||
this.logger = logger;
|
||||
this.gitHubFeatureFlags = new GitHubFeatureFlags(gitHubVersion, repositoryNwo, path.join(tempDir, exports.FEATURE_FLAGS_FILE_NAME), logger);
|
||||
}
|
||||
async getDefaultCliVersion(variant) {
|
||||
return await this.gitHubFeatureFlags.getDefaultCliVersion(variant);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param feature The feature to check.
|
||||
* @param codeql An optional CodeQL object. If provided, and a `minimumVersion` is specified for the
|
||||
* feature, the version of the CodeQL CLI will be checked against the minimum version.
|
||||
* If the version is less than the minimum version, the feature will be considered
|
||||
* disabled. If not provided, and a `minimumVersion` is specified for the feature, the
|
||||
* this function will throw.
|
||||
* @returns true if the feature is enabled, false otherwise.
|
||||
*
|
||||
* @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided.
|
||||
*/
|
||||
async getValue(feature, codeql) {
|
||||
if (!codeql && exports.featureConfig[feature].minimumVersion) {
|
||||
throw new Error(`Internal error: A minimum version is specified for feature ${feature}, but no instance of CodeQL was provided.`);
|
||||
}
|
||||
if (!codeql && exports.featureConfig[feature].toolsFeature) {
|
||||
throw new Error(`Internal error: A required tools feature is specified for feature ${feature}, but no instance of CodeQL was provided.`);
|
||||
}
|
||||
const envVar = (process.env[exports.featureConfig[feature].envVar] || "").toLocaleLowerCase();
|
||||
// Do not use this feature if user explicitly disables it via an environment variable.
|
||||
if (envVar === "false") {
|
||||
this.logger.debug(`Feature ${feature} is disabled via the environment variable ${exports.featureConfig[feature].envVar}.`);
|
||||
return false;
|
||||
}
|
||||
// Never use this feature if the CLI version explicitly can't support it.
|
||||
const minimumVersion = exports.featureConfig[feature].minimumVersion;
|
||||
if (codeql && minimumVersion) {
|
||||
if (!(await util.codeQlVersionAtLeast(codeql, minimumVersion))) {
|
||||
this.logger.debug(`Feature ${feature} is disabled because the CodeQL CLI version is older than the minimum ` +
|
||||
`version ${minimumVersion}.`);
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
this.logger.debug(`CodeQL CLI version ${(await codeql.getVersion()).version} is newer than the minimum ` +
|
||||
`version ${minimumVersion} for feature ${feature}.`);
|
||||
}
|
||||
}
|
||||
const toolsFeature = exports.featureConfig[feature].toolsFeature;
|
||||
if (codeql && toolsFeature) {
|
||||
if (!(await codeql.supportsFeature(toolsFeature))) {
|
||||
this.logger.debug(`Feature ${feature} is disabled because the CodeQL CLI version does not support the ` +
|
||||
`required tools feature ${toolsFeature}.`);
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
this.logger.debug(`CodeQL CLI version ${(await codeql.getVersion()).version} supports the required tools feature ${toolsFeature} for feature ${feature}.`);
|
||||
}
|
||||
}
|
||||
// Use this feature if user explicitly enables it via an environment variable.
|
||||
if (envVar === "true") {
|
||||
this.logger.debug(`Feature ${feature} is enabled via the environment variable ${exports.featureConfig[feature].envVar}.`);
|
||||
return true;
|
||||
}
|
||||
// Ask the GitHub API if the feature is enabled.
|
||||
const apiValue = await this.gitHubFeatureFlags.getValue(feature);
|
||||
if (apiValue !== undefined) {
|
||||
this.logger.debug(`Feature ${feature} is ${apiValue ? "enabled" : "disabled"} via the GitHub API.`);
|
||||
return apiValue;
|
||||
}
|
||||
const defaultValue = exports.featureConfig[feature].defaultValue;
|
||||
this.logger.debug(`Feature ${feature} is ${defaultValue ? "enabled" : "disabled"} due to its default value.`);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
exports.Features = Features;
|
||||
class GitHubFeatureFlags {
|
||||
constructor(gitHubVersion, repositoryNwo, featureFlagsFile, logger) {
|
||||
this.gitHubVersion = gitHubVersion;
|
||||
this.repositoryNwo = repositoryNwo;
|
||||
this.featureFlagsFile = featureFlagsFile;
|
||||
this.logger = logger;
|
||||
this.hasAccessedRemoteFeatureFlags = false; // Not accessed by default.
|
||||
}
|
||||
getCliVersionFromFeatureFlag(f) {
|
||||
if (!f.startsWith(DEFAULT_VERSION_FEATURE_FLAG_PREFIX) ||
|
||||
!f.endsWith(DEFAULT_VERSION_FEATURE_FLAG_SUFFIX)) {
|
||||
return undefined;
|
||||
}
|
||||
const version = f
|
||||
.substring(DEFAULT_VERSION_FEATURE_FLAG_PREFIX.length, f.length - DEFAULT_VERSION_FEATURE_FLAG_SUFFIX.length)
|
||||
.replace(/_/g, ".");
|
||||
if (!semver.valid(version)) {
|
||||
this.logger.warning(`Ignoring feature flag ${f} as it does not specify a valid CodeQL version.`);
|
||||
return undefined;
|
||||
}
|
||||
return version;
|
||||
}
|
||||
async getDefaultCliVersion(variant) {
|
||||
if (variant === util.GitHubVariant.DOTCOM) {
|
||||
return await this.getDefaultDotcomCliVersion();
|
||||
}
|
||||
return {
|
||||
cliVersion: defaults.cliVersion,
|
||||
tagName: defaults.bundleVersion,
|
||||
};
|
||||
}
|
||||
async getDefaultDotcomCliVersion() {
|
||||
const response = await this.getAllFeatures();
|
||||
const enabledFeatureFlagCliVersions = Object.entries(response)
|
||||
.map(([f, isEnabled]) => isEnabled ? this.getCliVersionFromFeatureFlag(f) : undefined)
|
||||
.filter((f) => f !== undefined);
|
||||
if (enabledFeatureFlagCliVersions.length === 0) {
|
||||
// We expect at least one default CLI version to be enabled on Dotcom at any time. However if
|
||||
// the feature flags are misconfigured, rather than crashing, we fall back to the CLI version
|
||||
// shipped with the Action in defaults.json. This has the effect of immediately rolling out
|
||||
// new CLI versions to all users running the latest Action.
|
||||
//
|
||||
// A drawback of this approach relates to the small number of users that run old versions of
|
||||
// the Action on Dotcom. As a result of this approach, if we misconfigure the feature flags
|
||||
// then these users will experience some alert churn. This is because the CLI version in the
|
||||
// defaults.json shipped with an old version of the Action is likely older than the CLI
|
||||
// version that would have been specified by the feature flags before they were misconfigured.
|
||||
this.logger.warning("Feature flags do not specify a default CLI version. Falling back to the CLI version " +
|
||||
`shipped with the Action. This is ${defaults.cliVersion}.`);
|
||||
const result = {
|
||||
cliVersion: defaults.cliVersion,
|
||||
tagName: defaults.bundleVersion,
|
||||
};
|
||||
if (this.hasAccessedRemoteFeatureFlags) {
|
||||
result.toolsFeatureFlagsValid = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
const maxCliVersion = enabledFeatureFlagCliVersions.reduce((maxVersion, currentVersion) => currentVersion > maxVersion ? currentVersion : maxVersion, enabledFeatureFlagCliVersions[0]);
|
||||
this.logger.debug(`Derived default CLI version of ${maxCliVersion} from feature flags.`);
|
||||
return {
|
||||
cliVersion: maxCliVersion,
|
||||
tagName: `codeql-bundle-v${maxCliVersion}`,
|
||||
toolsFeatureFlagsValid: true,
|
||||
};
|
||||
}
|
||||
async getValue(feature) {
|
||||
const response = await this.getAllFeatures();
|
||||
if (response === undefined) {
|
||||
this.logger.debug(`No feature flags API response for ${feature}.`);
|
||||
return undefined;
|
||||
}
|
||||
const features = response[feature];
|
||||
if (features === undefined) {
|
||||
this.logger.debug(`Feature '${feature}' undefined in API response.`);
|
||||
return undefined;
|
||||
}
|
||||
return !!features;
|
||||
}
|
||||
async getAllFeatures() {
|
||||
// if we have an in memory cache, use that
|
||||
if (this.cachedApiResponse !== undefined) {
|
||||
return this.cachedApiResponse;
|
||||
}
|
||||
// if a previous step has written a feature flags file to disk, use that
|
||||
const fileFlags = await this.readLocalFlags();
|
||||
if (fileFlags !== undefined) {
|
||||
this.cachedApiResponse = fileFlags;
|
||||
return fileFlags;
|
||||
}
|
||||
// if not, request flags from the server
|
||||
let remoteFlags = await this.loadApiResponse();
|
||||
if (remoteFlags === undefined) {
|
||||
remoteFlags = {};
|
||||
}
|
||||
// cache the response in memory
|
||||
this.cachedApiResponse = remoteFlags;
|
||||
// and cache them to disk so future workflow steps can use them
|
||||
await this.writeLocalFlags(remoteFlags);
|
||||
return remoteFlags;
|
||||
}
|
||||
async readLocalFlags() {
|
||||
try {
|
||||
if (fs.existsSync(this.featureFlagsFile)) {
|
||||
this.logger.debug(`Loading feature flags from ${this.featureFlagsFile}`);
|
||||
return JSON.parse(fs.readFileSync(this.featureFlagsFile, "utf8"));
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
this.logger.warning(`Error reading cached feature flags file ${this.featureFlagsFile}: ${e}. Requesting from GitHub instead.`);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
async writeLocalFlags(flags) {
|
||||
try {
|
||||
this.logger.debug(`Writing feature flags to ${this.featureFlagsFile}`);
|
||||
fs.writeFileSync(this.featureFlagsFile, JSON.stringify(flags));
|
||||
}
|
||||
catch (e) {
|
||||
this.logger.warning(`Error writing cached feature flags file ${this.featureFlagsFile}: ${e}.`);
|
||||
}
|
||||
}
|
||||
async loadApiResponse() {
|
||||
// Do nothing when not running against github.com
|
||||
if (this.gitHubVersion.type !== util.GitHubVariant.DOTCOM &&
|
||||
this.gitHubVersion.type !== util.GitHubVariant.GHE_DOTCOM) {
|
||||
this.logger.debug("Not running against github.com. Disabling all toggleable features.");
|
||||
this.hasAccessedRemoteFeatureFlags = false;
|
||||
return {};
|
||||
}
|
||||
try {
|
||||
const featuresToRequest = Object.entries(exports.featureConfig)
|
||||
.filter(([, config]) => !config.legacyApi)
|
||||
.map(([f]) => f);
|
||||
const FEATURES_PER_REQUEST = 25;
|
||||
const featureChunks = [];
|
||||
while (featuresToRequest.length > 0) {
|
||||
featureChunks.push(featuresToRequest.splice(0, FEATURES_PER_REQUEST));
|
||||
}
|
||||
let remoteFlags = {};
|
||||
for (const chunk of featureChunks) {
|
||||
const response = await (0, api_client_1.getApiClient)().request("GET /repos/:owner/:repo/code-scanning/codeql-action/features", {
|
||||
owner: this.repositoryNwo.owner,
|
||||
repo: this.repositoryNwo.repo,
|
||||
features: chunk.join(","),
|
||||
});
|
||||
const chunkFlags = response.data;
|
||||
remoteFlags = { ...remoteFlags, ...chunkFlags };
|
||||
}
|
||||
this.logger.debug("Loaded the following default values for the feature flags from the Code Scanning API:");
|
||||
for (const [feature, value] of Object.entries(remoteFlags).sort(([nameA], [nameB]) => nameA.localeCompare(nameB))) {
|
||||
this.logger.debug(` ${feature}: ${value}`);
|
||||
}
|
||||
this.hasAccessedRemoteFeatureFlags = true;
|
||||
return remoteFlags;
|
||||
}
|
||||
catch (e) {
|
||||
if (util.isHTTPError(e) && e.status === 403) {
|
||||
this.logger.warning("This run of the CodeQL Action does not have permission to access Code Scanning API endpoints. " +
|
||||
"As a result, it will not be opted into any experimental features. " +
|
||||
"This could be because the Action is running on a pull request from a fork. If not, " +
|
||||
`please ensure the Action has the 'security-events: write' permission. Details: ${e.message}`);
|
||||
this.hasAccessedRemoteFeatureFlags = false;
|
||||
return {};
|
||||
}
|
||||
else {
|
||||
// Some features, such as `ml_powered_queries_enabled` affect the produced alerts.
|
||||
// Considering these features disabled in the event of a transient error could
|
||||
// therefore lead to alert churn. As a result, we crash if we cannot determine the value of
|
||||
// the feature.
|
||||
throw new Error(`Encountered an error while trying to determine feature enablement: ${e}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=feature-flags.js.map
|
||||
File diff suppressed because one or more lines are too long
392
lib/feature-flags.test.js
generated
392
lib/feature-flags.test.js
generated
@@ -1,392 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const fs = __importStar(require("fs"));
|
||||
const path = __importStar(require("path"));
|
||||
const ava_1 = __importDefault(require("ava"));
|
||||
const defaults = __importStar(require("./defaults.json"));
|
||||
const feature_flags_1 = require("./feature-flags");
|
||||
const logging_1 = require("./logging");
|
||||
const repository_1 = require("./repository");
|
||||
const testing_utils_1 = require("./testing-utils");
|
||||
const tools_features_1 = require("./tools-features");
|
||||
const util_1 = require("./util");
|
||||
(0, testing_utils_1.setupTests)(ava_1.default);
|
||||
ava_1.default.beforeEach(() => {
|
||||
(0, util_1.initializeEnvironment)("1.2.3");
|
||||
});
|
||||
const testRepositoryNwo = (0, repository_1.parseRepositoryNwo)("github/example");
|
||||
(0, ava_1.default)(`All features are disabled if running against GHES`, async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const loggedMessages = [];
|
||||
const features = setUpFeatureFlagTests(tmpDir, (0, testing_utils_1.getRecordingLogger)(loggedMessages), { type: util_1.GitHubVariant.GHES, version: "3.0.0" });
|
||||
for (const feature of Object.values(feature_flags_1.Feature)) {
|
||||
t.deepEqual(await features.getValue(feature, includeCodeQlIfRequired(feature)), feature_flags_1.featureConfig[feature].defaultValue);
|
||||
}
|
||||
t.assert(loggedMessages.find((v) => v.type === "debug" &&
|
||||
v.message ===
|
||||
"Not running against github.com. Disabling all toggleable features.") !== undefined);
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)(`Feature flags are requested in Proxima`, async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const loggedMessages = [];
|
||||
const features = setUpFeatureFlagTests(tmpDir, (0, testing_utils_1.getRecordingLogger)(loggedMessages), { type: util_1.GitHubVariant.GHE_DOTCOM });
|
||||
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, (0, testing_utils_1.initializeFeatures)(true));
|
||||
for (const feature of Object.values(feature_flags_1.Feature)) {
|
||||
// Ensure we have gotten a response value back from the Mock API
|
||||
t.assert(await features.getValue(feature, includeCodeQlIfRequired(feature)));
|
||||
}
|
||||
// And that we haven't bailed preemptively.
|
||||
t.assert(loggedMessages.find((v) => v.type === "debug" &&
|
||||
v.message ===
|
||||
"Not running against github.com. Disabling all toggleable features.") === undefined);
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("API response missing and features use default value", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const loggedMessages = [];
|
||||
const features = setUpFeatureFlagTests(tmpDir, (0, testing_utils_1.getRecordingLogger)(loggedMessages));
|
||||
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(403, {});
|
||||
for (const feature of Object.values(feature_flags_1.Feature)) {
|
||||
t.assert((await features.getValue(feature, includeCodeQlIfRequired(feature))) ===
|
||||
feature_flags_1.featureConfig[feature].defaultValue);
|
||||
}
|
||||
assertAllFeaturesUndefinedInApi(t, loggedMessages);
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("Features use default value if they're not returned in API response", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const loggedMessages = [];
|
||||
const features = setUpFeatureFlagTests(tmpDir, (0, testing_utils_1.getRecordingLogger)(loggedMessages));
|
||||
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, {});
|
||||
for (const feature of Object.values(feature_flags_1.Feature)) {
|
||||
t.assert((await features.getValue(feature, includeCodeQlIfRequired(feature))) ===
|
||||
feature_flags_1.featureConfig[feature].defaultValue);
|
||||
}
|
||||
assertAllFeaturesUndefinedInApi(t, loggedMessages);
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("Include no more than 25 features in each API request", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
(0, testing_utils_1.stubFeatureFlagApiEndpoint)((request) => {
|
||||
const requestedFeatures = request.features.split(",");
|
||||
return {
|
||||
status: requestedFeatures.length <= 25 ? 200 : 400,
|
||||
messageIfError: "Can request a maximum of 25 features.",
|
||||
data: {},
|
||||
};
|
||||
});
|
||||
// We only need to call getValue once, and it does not matter which feature
|
||||
// we ask for. Under the hood, the features library will request all features
|
||||
// from the API.
|
||||
const feature = Object.values(feature_flags_1.Feature)[0];
|
||||
await t.notThrowsAsync(async () => features.getValue(feature, includeCodeQlIfRequired(feature)));
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("Feature flags exception is propagated if the API request errors", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(500, {});
|
||||
const someFeature = Object.values(feature_flags_1.Feature)[0];
|
||||
await t.throwsAsync(async () => features.getValue(someFeature, includeCodeQlIfRequired(someFeature)), {
|
||||
message: "Encountered an error while trying to determine feature enablement: Error: some error message",
|
||||
});
|
||||
});
|
||||
});
|
||||
for (const feature of Object.keys(feature_flags_1.featureConfig)) {
|
||||
(0, ava_1.default)(`Only feature '${feature}' is enabled if enabled in the API response. Other features disabled`, async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
// set all features to false except the one we're testing
|
||||
const expectedFeatureEnablement = {};
|
||||
for (const f of Object.keys(feature_flags_1.featureConfig)) {
|
||||
expectedFeatureEnablement[f] = f === feature;
|
||||
}
|
||||
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, expectedFeatureEnablement);
|
||||
// retrieve the values of the actual features
|
||||
const actualFeatureEnablement = {};
|
||||
for (const f of Object.keys(feature_flags_1.featureConfig)) {
|
||||
actualFeatureEnablement[f] = await features.getValue(f, includeCodeQlIfRequired(f));
|
||||
}
|
||||
// All features should be false except the one we're testing
|
||||
t.deepEqual(actualFeatureEnablement, expectedFeatureEnablement);
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)(`Only feature '${feature}' is enabled if the associated environment variable is true. Others disabled.`, async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = (0, testing_utils_1.initializeFeatures)(false);
|
||||
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, expectedFeatureEnablement);
|
||||
// feature should be disabled initially
|
||||
t.assert(!(await features.getValue(feature, includeCodeQlIfRequired(feature))));
|
||||
// set env var to true and check that the feature is now enabled
|
||||
process.env[feature_flags_1.featureConfig[feature].envVar] = "true";
|
||||
t.assert(await features.getValue(feature, includeCodeQlIfRequired(feature)));
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)(`Feature '${feature}' is disabled if the associated environment variable is false, even if enabled in API`, async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = (0, testing_utils_1.initializeFeatures)(true);
|
||||
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, expectedFeatureEnablement);
|
||||
// feature should be enabled initially
|
||||
t.assert(await features.getValue(feature, includeCodeQlIfRequired(feature)));
|
||||
// set env var to false and check that the feature is now disabled
|
||||
process.env[feature_flags_1.featureConfig[feature].envVar] = "false";
|
||||
t.assert(!(await features.getValue(feature, includeCodeQlIfRequired(feature))));
|
||||
});
|
||||
});
|
||||
if (feature_flags_1.featureConfig[feature].minimumVersion !== undefined ||
|
||||
feature_flags_1.featureConfig[feature].toolsFeature !== undefined) {
|
||||
(0, ava_1.default)(`Getting feature '${feature} should throw if no codeql is provided`, async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = (0, testing_utils_1.initializeFeatures)(true);
|
||||
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, expectedFeatureEnablement);
|
||||
await t.throwsAsync(async () => features.getValue(feature), {
|
||||
message: `Internal error: A ${feature_flags_1.featureConfig[feature].minimumVersion !== undefined
|
||||
? "minimum version"
|
||||
: "required tools feature"} is specified for feature ${feature}, but no instance of CodeQL was provided.`,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
if (feature_flags_1.featureConfig[feature].minimumVersion !== undefined) {
|
||||
(0, ava_1.default)(`Feature '${feature}' is disabled if the minimum CLI version is below ${feature_flags_1.featureConfig[feature].minimumVersion}`, async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = (0, testing_utils_1.initializeFeatures)(true);
|
||||
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, expectedFeatureEnablement);
|
||||
// feature should be disabled when an old CLI version is set
|
||||
let codeql = (0, testing_utils_1.mockCodeQLVersion)("2.0.0");
|
||||
t.assert(!(await features.getValue(feature, codeql)));
|
||||
// even setting the env var to true should not enable the feature if
|
||||
// the minimum CLI version is not met
|
||||
process.env[feature_flags_1.featureConfig[feature].envVar] = "true";
|
||||
t.assert(!(await features.getValue(feature, codeql)));
|
||||
// feature should be enabled when a new CLI version is set
|
||||
// and env var is not set
|
||||
process.env[feature_flags_1.featureConfig[feature].envVar] = "";
|
||||
codeql = (0, testing_utils_1.mockCodeQLVersion)(feature_flags_1.featureConfig[feature].minimumVersion);
|
||||
t.assert(await features.getValue(feature, codeql));
|
||||
// set env var to false and check that the feature is now disabled
|
||||
process.env[feature_flags_1.featureConfig[feature].envVar] = "false";
|
||||
t.assert(!(await features.getValue(feature, codeql)));
|
||||
});
|
||||
});
|
||||
}
|
||||
if (feature_flags_1.featureConfig[feature].toolsFeature !== undefined) {
|
||||
(0, ava_1.default)(`Feature '${feature}' is disabled if the required tools feature is not enabled`, async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = (0, testing_utils_1.initializeFeatures)(true);
|
||||
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, expectedFeatureEnablement);
|
||||
// feature should be disabled when the required tools feature is not enabled
|
||||
let codeql = (0, testing_utils_1.mockCodeQLVersion)("2.0.0");
|
||||
t.assert(!(await features.getValue(feature, codeql)));
|
||||
// even setting the env var to true should not enable the feature if
|
||||
// the required tools feature is not enabled
|
||||
process.env[feature_flags_1.featureConfig[feature].envVar] = "true";
|
||||
t.assert(!(await features.getValue(feature, codeql)));
|
||||
// feature should be enabled when the required tools feature is enabled
|
||||
// and env var is not set
|
||||
process.env[feature_flags_1.featureConfig[feature].envVar] = "";
|
||||
codeql = (0, testing_utils_1.mockCodeQLVersion)("2.0.0", {
|
||||
[feature_flags_1.featureConfig[feature].toolsFeature]: true,
|
||||
});
|
||||
t.assert(await features.getValue(feature, codeql));
|
||||
// set env var to false and check that the feature is now disabled
|
||||
process.env[feature_flags_1.featureConfig[feature].envVar] = "false";
|
||||
t.assert(!(await features.getValue(feature, codeql)));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
(0, ava_1.default)("Feature flags are saved to disk", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = (0, testing_utils_1.initializeFeatures)(true);
|
||||
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, expectedFeatureEnablement);
|
||||
const cachedFeatureFlags = path.join(tmpDir, feature_flags_1.FEATURE_FLAGS_FILE_NAME);
|
||||
t.false(fs.existsSync(cachedFeatureFlags), "Feature flag cached file should not exist before getting feature flags");
|
||||
t.true(await features.getValue(feature_flags_1.Feature.QaTelemetryEnabled, includeCodeQlIfRequired(feature_flags_1.Feature.QaTelemetryEnabled)), "Feature flag should be enabled initially");
|
||||
t.true(fs.existsSync(cachedFeatureFlags), "Feature flag cached file should exist after getting feature flags");
|
||||
const actualFeatureEnablement = JSON.parse(fs.readFileSync(cachedFeatureFlags, "utf8"));
|
||||
t.deepEqual(actualFeatureEnablement, expectedFeatureEnablement);
|
||||
// now test that we actually use the feature flag cache instead of the server
|
||||
actualFeatureEnablement[feature_flags_1.Feature.QaTelemetryEnabled] = false;
|
||||
fs.writeFileSync(cachedFeatureFlags, JSON.stringify(actualFeatureEnablement));
|
||||
// delete the in memory cache so that we are forced to use the cached file
|
||||
features.gitHubFeatureFlags.cachedApiResponse = undefined;
|
||||
t.false(await features.getValue(feature_flags_1.Feature.QaTelemetryEnabled, includeCodeQlIfRequired(feature_flags_1.Feature.QaTelemetryEnabled)), "Feature flag should be enabled after reading from cached file");
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("Environment variable can override feature flag cache", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = (0, testing_utils_1.initializeFeatures)(true);
|
||||
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, expectedFeatureEnablement);
|
||||
const cachedFeatureFlags = path.join(tmpDir, feature_flags_1.FEATURE_FLAGS_FILE_NAME);
|
||||
t.true(await features.getValue(feature_flags_1.Feature.QaTelemetryEnabled, includeCodeQlIfRequired(feature_flags_1.Feature.QaTelemetryEnabled)), "Feature flag should be enabled initially");
|
||||
t.true(fs.existsSync(cachedFeatureFlags), "Feature flag cached file should exist after getting feature flags");
|
||||
process.env.CODEQL_ACTION_QA_TELEMETRY = "false";
|
||||
t.false(await features.getValue(feature_flags_1.Feature.QaTelemetryEnabled, includeCodeQlIfRequired(feature_flags_1.Feature.QaTelemetryEnabled)), "Feature flag should be disabled after setting env var");
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)(`selects CLI from defaults.json on GHES`, async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
const defaultCliVersion = await features.getDefaultCliVersion(util_1.GitHubVariant.GHES);
|
||||
t.deepEqual(defaultCliVersion, {
|
||||
cliVersion: defaults.cliVersion,
|
||||
tagName: defaults.bundleVersion,
|
||||
});
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("selects CLI v2.20.1 on Dotcom when feature flags enable v2.20.0 and v2.20.1", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = (0, testing_utils_1.initializeFeatures)(true);
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_0_enabled"] = true;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_1_enabled"] = true;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_2_enabled"] = false;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_3_enabled"] = false;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_4_enabled"] = false;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_5_enabled"] = false;
|
||||
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, expectedFeatureEnablement);
|
||||
const defaultCliVersion = await features.getDefaultCliVersion(util_1.GitHubVariant.DOTCOM);
|
||||
t.deepEqual(defaultCliVersion, {
|
||||
cliVersion: "2.20.1",
|
||||
tagName: "codeql-bundle-v2.20.1",
|
||||
toolsFeatureFlagsValid: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("includes tag name", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = (0, testing_utils_1.initializeFeatures)(true);
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_0_enabled"] = true;
|
||||
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, expectedFeatureEnablement);
|
||||
const defaultCliVersion = await features.getDefaultCliVersion(util_1.GitHubVariant.DOTCOM);
|
||||
t.deepEqual(defaultCliVersion, {
|
||||
cliVersion: "2.20.0",
|
||||
tagName: "codeql-bundle-v2.20.0",
|
||||
toolsFeatureFlagsValid: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)(`selects CLI from defaults.json on Dotcom when no default version feature flags are enabled`, async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = (0, testing_utils_1.initializeFeatures)(true);
|
||||
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, expectedFeatureEnablement);
|
||||
const defaultCliVersion = await features.getDefaultCliVersion(util_1.GitHubVariant.DOTCOM);
|
||||
t.deepEqual(defaultCliVersion, {
|
||||
cliVersion: defaults.cliVersion,
|
||||
tagName: defaults.bundleVersion,
|
||||
toolsFeatureFlagsValid: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("ignores invalid version numbers in default version feature flags", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const loggedMessages = [];
|
||||
const features = setUpFeatureFlagTests(tmpDir, (0, testing_utils_1.getRecordingLogger)(loggedMessages));
|
||||
const expectedFeatureEnablement = (0, testing_utils_1.initializeFeatures)(true);
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_0_enabled"] = true;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_1_enabled"] = true;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_invalid_enabled"] =
|
||||
true;
|
||||
(0, testing_utils_1.mockFeatureFlagApiEndpoint)(200, expectedFeatureEnablement);
|
||||
const defaultCliVersion = await features.getDefaultCliVersion(util_1.GitHubVariant.DOTCOM);
|
||||
t.deepEqual(defaultCliVersion, {
|
||||
cliVersion: "2.20.1",
|
||||
tagName: "codeql-bundle-v2.20.1",
|
||||
toolsFeatureFlagsValid: true,
|
||||
});
|
||||
t.assert(loggedMessages.find((v) => v.type === "warning" &&
|
||||
v.message ===
|
||||
"Ignoring feature flag default_codeql_version_2_20_invalid_enabled as it does not specify a valid CodeQL version.") !== undefined);
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("legacy feature flags should end with _enabled", async (t) => {
|
||||
for (const [feature, config] of Object.entries(feature_flags_1.featureConfig)) {
|
||||
if (config.legacyApi) {
|
||||
t.assert(feature.endsWith("_enabled"), `legacy feature ${feature} should end with '_enabled'`);
|
||||
}
|
||||
}
|
||||
});
|
||||
(0, ava_1.default)("non-legacy feature flags should not end with _enabled", async (t) => {
|
||||
for (const [feature, config] of Object.entries(feature_flags_1.featureConfig)) {
|
||||
if (!config.legacyApi) {
|
||||
t.false(feature.endsWith("_enabled"), `non-legacy feature ${feature} should not end with '_enabled'`);
|
||||
}
|
||||
}
|
||||
});
|
||||
(0, ava_1.default)("non-legacy feature flags should not start with codeql_action_", async (t) => {
|
||||
for (const [feature, config] of Object.entries(feature_flags_1.featureConfig)) {
|
||||
if (!config.legacyApi) {
|
||||
t.false(feature.startsWith("codeql_action_"), `non-legacy feature ${feature} should not start with 'codeql_action_'`);
|
||||
}
|
||||
}
|
||||
});
|
||||
function assertAllFeaturesUndefinedInApi(t, loggedMessages) {
|
||||
for (const feature of Object.keys(feature_flags_1.featureConfig)) {
|
||||
t.assert(loggedMessages.find((v) => v.type === "debug" &&
|
||||
v.message.includes(feature) &&
|
||||
v.message.includes("undefined in API response")) !== undefined);
|
||||
}
|
||||
}
|
||||
function setUpFeatureFlagTests(tmpDir, logger = (0, logging_1.getRunnerLogger)(true), gitHubVersion = { type: util_1.GitHubVariant.DOTCOM }) {
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
return new feature_flags_1.Features(gitHubVersion, testRepositoryNwo, tmpDir, logger);
|
||||
}
|
||||
/**
|
||||
* Returns an argument to pass to `getValue` that if required includes a CodeQL object meeting the
|
||||
* minimum version or tool feature requirements specified by the feature.
|
||||
*/
|
||||
function includeCodeQlIfRequired(feature) {
|
||||
return feature_flags_1.featureConfig[feature].minimumVersion !== undefined ||
|
||||
feature_flags_1.featureConfig[feature].toolsFeature !== undefined
|
||||
? (0, testing_utils_1.mockCodeQLVersion)("9.9.9", Object.fromEntries(Object.values(tools_features_1.ToolsFeature).map((v) => [v, true])))
|
||||
: undefined;
|
||||
}
|
||||
//# sourceMappingURL=feature-flags.test.js.map
|
||||
File diff suppressed because one or more lines are too long
292
lib/fingerprints.js
generated
292
lib/fingerprints.js
generated
@@ -1,292 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.hash = hash;
|
||||
exports.resolveUriToFile = resolveUriToFile;
|
||||
exports.addFingerprints = addFingerprints;
|
||||
const fs = __importStar(require("fs"));
|
||||
const path_1 = __importDefault(require("path"));
|
||||
const long_1 = __importDefault(require("long"));
|
||||
const doc_url_1 = require("./doc-url");
|
||||
const tab = "\t".charCodeAt(0);
|
||||
const space = " ".charCodeAt(0);
|
||||
const lf = "\n".charCodeAt(0);
|
||||
const cr = "\r".charCodeAt(0);
|
||||
const EOF = 65535;
|
||||
const BLOCK_SIZE = 100;
|
||||
const MOD = long_1.default.fromInt(37); // L
|
||||
// Compute the starting point for the hash mod
|
||||
function computeFirstMod() {
|
||||
let firstMod = long_1.default.ONE; // L
|
||||
for (let i = 0; i < BLOCK_SIZE; i++) {
|
||||
firstMod = firstMod.multiply(MOD);
|
||||
}
|
||||
return firstMod;
|
||||
}
|
||||
/**
|
||||
* Hash the contents of a file
|
||||
*
|
||||
* The hash method computes a rolling hash for every line in the input. The hash is computed using the first
|
||||
* BLOCK_SIZE non-space/tab characters counted from the start of the line. For the computation of the hash all
|
||||
* line endings (i.e. \r, \n, and \r\n) are normalized to '\n'. A special value (-1) is added at the end of the
|
||||
* file followed by enough '\0' characters to ensure that there are BLOCK_SIZE characters available for computing
|
||||
* the hashes of the lines near the end of the file.
|
||||
*
|
||||
* @param callback function that is called with the line number (1-based) and hash for every line
|
||||
* @param filepath The path to the file to hash
|
||||
*/
|
||||
async function hash(callback, filepath) {
|
||||
// A rolling view in to the input
|
||||
const window = Array(BLOCK_SIZE).fill(0);
|
||||
// If the character in the window is the start of a new line
|
||||
// then records the line number, otherwise will be -1.
|
||||
// Indexes match up with those from the window variable.
|
||||
const lineNumbers = Array(BLOCK_SIZE).fill(-1);
|
||||
// The current hash value, updated as we read each character
|
||||
let hashRaw = long_1.default.ZERO;
|
||||
const firstMod = computeFirstMod();
|
||||
// The current index in the window, will wrap around to zero when we reach BLOCK_SIZE
|
||||
let index = 0;
|
||||
// The line number of the character we are currently processing from the input
|
||||
let lineNumber = 0;
|
||||
// Is the next character to be read the start of a new line
|
||||
let lineStart = true;
|
||||
// Was the previous character a CR (carriage return)
|
||||
let prevCR = false;
|
||||
// A map of hashes we've seen before and how many times,
|
||||
// so we can disambiguate identical hashes
|
||||
const hashCounts = {};
|
||||
// Output the current hash and line number to the callback function
|
||||
const outputHash = function () {
|
||||
const hashValue = hashRaw.toUnsigned().toString(16);
|
||||
if (!hashCounts[hashValue]) {
|
||||
hashCounts[hashValue] = 0;
|
||||
}
|
||||
hashCounts[hashValue]++;
|
||||
callback(lineNumbers[index], `${hashValue}:${hashCounts[hashValue]}`);
|
||||
lineNumbers[index] = -1;
|
||||
};
|
||||
// Update the current hash value and increment the index in the window
|
||||
const updateHash = function (current) {
|
||||
const begin = window[index];
|
||||
window[index] = current;
|
||||
hashRaw = MOD.multiply(hashRaw)
|
||||
.add(long_1.default.fromInt(current))
|
||||
.subtract(firstMod.multiply(long_1.default.fromInt(begin)));
|
||||
index = (index + 1) % BLOCK_SIZE;
|
||||
};
|
||||
// First process every character in the input, updating the hash and lineNumbers
|
||||
// as we go. Once we reach a point in the window again then we've processed
|
||||
// BLOCK_SIZE characters and if the last character at this point in the window
|
||||
// was the start of a line then we should output the hash for that line.
|
||||
const processCharacter = function (current) {
|
||||
// skip tabs, spaces, and line feeds that come directly after a carriage return
|
||||
if (current === space || current === tab || (prevCR && current === lf)) {
|
||||
prevCR = false;
|
||||
return;
|
||||
}
|
||||
// replace CR with LF
|
||||
if (current === cr) {
|
||||
current = lf;
|
||||
prevCR = true;
|
||||
}
|
||||
else {
|
||||
prevCR = false;
|
||||
}
|
||||
if (lineNumbers[index] !== -1) {
|
||||
outputHash();
|
||||
}
|
||||
if (lineStart) {
|
||||
lineStart = false;
|
||||
lineNumber++;
|
||||
lineNumbers[index] = lineNumber;
|
||||
}
|
||||
if (current === lf) {
|
||||
lineStart = true;
|
||||
}
|
||||
updateHash(current);
|
||||
};
|
||||
const readStream = fs.createReadStream(filepath, "utf8");
|
||||
for await (const data of readStream) {
|
||||
for (let i = 0; i < data.length; ++i) {
|
||||
processCharacter(data.charCodeAt(i));
|
||||
}
|
||||
}
|
||||
processCharacter(EOF);
|
||||
// Flush the remaining lines
|
||||
for (let i = 0; i < BLOCK_SIZE; i++) {
|
||||
if (lineNumbers[index] !== -1) {
|
||||
outputHash();
|
||||
}
|
||||
updateHash(0);
|
||||
}
|
||||
}
|
||||
// Generate a hash callback function that updates the given result in-place
|
||||
// when it receives a hash for the correct line number. Ignores hashes for other lines.
|
||||
function locationUpdateCallback(result, location, logger) {
|
||||
let locationStartLine = location.physicalLocation?.region?.startLine;
|
||||
if (locationStartLine === undefined) {
|
||||
// We expect the region section to be present, but it can be absent if the
|
||||
// alert pertains to the entire file. In this case, we compute the fingerprint
|
||||
// using the hash of the first line of the file.
|
||||
locationStartLine = 1;
|
||||
}
|
||||
return function (lineNumber, hashValue) {
|
||||
// Ignore hashes for lines that don't concern us
|
||||
if (locationStartLine !== lineNumber) {
|
||||
return;
|
||||
}
|
||||
if (!result.partialFingerprints) {
|
||||
result.partialFingerprints = {};
|
||||
}
|
||||
const existingFingerprint = result.partialFingerprints.primaryLocationLineHash;
|
||||
// If the hash doesn't match the existing fingerprint then
|
||||
// output a warning and don't overwrite it.
|
||||
if (!existingFingerprint) {
|
||||
result.partialFingerprints.primaryLocationLineHash = hashValue;
|
||||
}
|
||||
else if (existingFingerprint !== hashValue) {
|
||||
logger.warning(`Calculated fingerprint of ${hashValue} for file ${location.physicalLocation.artifactLocation.uri} line ${lineNumber}, but found existing inconsistent fingerprint value ${existingFingerprint}`);
|
||||
}
|
||||
};
|
||||
}
|
||||
// Can we fingerprint the given location. This requires access to
|
||||
// the source file so we can hash it.
|
||||
// If possible returns a absolute file path for the source file,
|
||||
// or if not possible then returns undefined.
|
||||
function resolveUriToFile(location, artifacts, sourceRoot, logger) {
|
||||
// This may be referencing an artifact
|
||||
if (!location.uri && location.index !== undefined) {
|
||||
if (typeof location.index !== "number" ||
|
||||
location.index < 0 ||
|
||||
location.index >= artifacts.length ||
|
||||
typeof artifacts[location.index].location !== "object") {
|
||||
logger.debug(`Ignoring location as index "${location.index}" is invalid`);
|
||||
return undefined;
|
||||
}
|
||||
location = artifacts[location.index].location;
|
||||
}
|
||||
// Get the URI and decode
|
||||
if (typeof location.uri !== "string") {
|
||||
logger.debug(`Ignoring location as URI "${location.uri}" is invalid`);
|
||||
return undefined;
|
||||
}
|
||||
let uri;
|
||||
try {
|
||||
uri = decodeURIComponent(location.uri);
|
||||
}
|
||||
catch {
|
||||
logger.debug(`Ignoring location as URI "${location.uri}" is invalid`);
|
||||
return undefined;
|
||||
}
|
||||
// Remove a file scheme, and abort if the scheme is anything else
|
||||
const fileUriPrefix = "file://";
|
||||
if (uri.startsWith(fileUriPrefix)) {
|
||||
uri = uri.substring(fileUriPrefix.length);
|
||||
}
|
||||
if (uri.indexOf("://") !== -1) {
|
||||
logger.debug(`Ignoring location URI "${uri}" as the scheme is not recognised`);
|
||||
return undefined;
|
||||
}
|
||||
// Discard any absolute paths that aren't in the src root
|
||||
const srcRootPrefix = `${sourceRoot}/`;
|
||||
if (uri.startsWith("/") && !uri.startsWith(srcRootPrefix)) {
|
||||
logger.debug(`Ignoring location URI "${uri}" as it is outside of the src root`);
|
||||
return undefined;
|
||||
}
|
||||
// Just assume a relative path is relative to the src root.
|
||||
// This is not necessarily true but should be a good approximation
|
||||
// and here we likely want to err on the side of handling more cases.
|
||||
if (!path_1.default.isAbsolute(uri)) {
|
||||
uri = srcRootPrefix + uri;
|
||||
}
|
||||
// Check the file exists
|
||||
if (!fs.existsSync(uri)) {
|
||||
logger.debug(`Unable to compute fingerprint for non-existent file: ${uri}`);
|
||||
return undefined;
|
||||
}
|
||||
if (fs.statSync(uri).isDirectory()) {
|
||||
logger.debug(`Unable to compute fingerprint for directory: ${uri}`);
|
||||
return undefined;
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
// Compute fingerprints for results in the given sarif file
|
||||
// and return an updated sarif file contents.
|
||||
async function addFingerprints(sarif, sourceRoot, logger) {
|
||||
logger.info(`Adding fingerprints to SARIF file. See ${doc_url_1.DocUrl.TRACK_CODE_SCANNING_ALERTS_ACROSS_RUNS} for more information.`);
|
||||
// Gather together results for the same file and construct
|
||||
// callbacks to accept hashes for that file and update the location
|
||||
const callbacksByFile = {};
|
||||
for (const run of sarif.runs || []) {
|
||||
// We may need the list of artifacts to resolve against
|
||||
const artifacts = run.artifacts || [];
|
||||
for (const result of run.results || []) {
|
||||
// Check the primary location is defined correctly and is in the src root
|
||||
const primaryLocation = (result.locations || [])[0];
|
||||
if (!primaryLocation?.physicalLocation?.artifactLocation) {
|
||||
logger.debug(`Unable to compute fingerprint for invalid location: ${JSON.stringify(primaryLocation)}`);
|
||||
continue;
|
||||
}
|
||||
if (primaryLocation?.physicalLocation?.region?.startLine === undefined) {
|
||||
// Locations without a line number are unlikely to be source files
|
||||
continue;
|
||||
}
|
||||
const filepath = resolveUriToFile(primaryLocation.physicalLocation.artifactLocation, artifacts, sourceRoot, logger);
|
||||
if (!filepath) {
|
||||
continue;
|
||||
}
|
||||
if (!callbacksByFile[filepath]) {
|
||||
callbacksByFile[filepath] = [];
|
||||
}
|
||||
callbacksByFile[filepath].push(locationUpdateCallback(result, primaryLocation, logger));
|
||||
}
|
||||
}
|
||||
// Now hash each file that was found
|
||||
for (const [filepath, callbacks] of Object.entries(callbacksByFile)) {
|
||||
// A callback that forwards the hash to all other callbacks for that file
|
||||
const teeCallback = function (lineNumber, hashValue) {
|
||||
for (const c of Object.values(callbacks)) {
|
||||
c(lineNumber, hashValue);
|
||||
}
|
||||
};
|
||||
await hash(teeCallback, filepath);
|
||||
}
|
||||
return sarif;
|
||||
}
|
||||
//# sourceMappingURL=fingerprints.js.map
|
||||
File diff suppressed because one or more lines are too long
213
lib/fingerprints.test.js
generated
213
lib/fingerprints.test.js
generated
@@ -1,213 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const fs = __importStar(require("fs"));
|
||||
const path = __importStar(require("path"));
|
||||
const ava_1 = __importDefault(require("ava"));
|
||||
const fingerprints = __importStar(require("./fingerprints"));
|
||||
const logging_1 = require("./logging");
|
||||
const testing_utils_1 = require("./testing-utils");
|
||||
const util = __importStar(require("./util"));
|
||||
(0, testing_utils_1.setupTests)(ava_1.default);
|
||||
async function testHash(t, input, expectedHashes) {
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
const tmpFile = path.resolve(tmpDir, "testfile");
|
||||
fs.writeFileSync(tmpFile, input);
|
||||
let index = 0;
|
||||
const callback = function (lineNumber, hash) {
|
||||
t.is(lineNumber, index + 1);
|
||||
t.is(hash, expectedHashes[index]);
|
||||
index++;
|
||||
};
|
||||
await fingerprints.hash(callback, tmpFile);
|
||||
t.is(index, input.split(/\r\n|\r|\n/).length);
|
||||
});
|
||||
}
|
||||
(0, ava_1.default)("hash", async (t) => {
|
||||
// Try empty file
|
||||
await testHash(t, "", ["c129715d7a2bc9a3:1"]);
|
||||
// Try various combinations of newline characters
|
||||
await testHash(t, " a\nb\n \t\tc\n d", [
|
||||
"271789c17abda88f:1",
|
||||
"54703d4cd895b18:1",
|
||||
"180aee12dab6264:1",
|
||||
"a23a3dc5e078b07b:1",
|
||||
]);
|
||||
await testHash(t, " hello; \t\nworld!!!\n\n\n \t\tGreetings\n End", [
|
||||
"8b7cf3e952e7aeb2:1",
|
||||
"b1ae1287ec4718d9:1",
|
||||
"bff680108adb0fcc:1",
|
||||
"c6805c5e1288b612:1",
|
||||
"b86d3392aea1be30:1",
|
||||
"e6ceba753e1a442:1",
|
||||
]);
|
||||
await testHash(t, " hello; \t\nworld!!!\n\n\n \t\tGreetings\n End\n", [
|
||||
"e9496ae3ebfced30:1",
|
||||
"fb7c023a8b9ccb3f:1",
|
||||
"ce8ba1a563dcdaca:1",
|
||||
"e20e36e16fcb0cc8:1",
|
||||
"b3edc88f2938467e:1",
|
||||
"c8e28b0b4002a3a0:1",
|
||||
"c129715d7a2bc9a3:1",
|
||||
]);
|
||||
await testHash(t, " hello; \t\nworld!!!\r\r\r \t\tGreetings\r End\r", [
|
||||
"e9496ae3ebfced30:1",
|
||||
"fb7c023a8b9ccb3f:1",
|
||||
"ce8ba1a563dcdaca:1",
|
||||
"e20e36e16fcb0cc8:1",
|
||||
"b3edc88f2938467e:1",
|
||||
"c8e28b0b4002a3a0:1",
|
||||
"c129715d7a2bc9a3:1",
|
||||
]);
|
||||
await testHash(t, " hello; \t\r\nworld!!!\r\n\r\n\r\n \t\tGreetings\r\n End\r\n", [
|
||||
"e9496ae3ebfced30:1",
|
||||
"fb7c023a8b9ccb3f:1",
|
||||
"ce8ba1a563dcdaca:1",
|
||||
"e20e36e16fcb0cc8:1",
|
||||
"b3edc88f2938467e:1",
|
||||
"c8e28b0b4002a3a0:1",
|
||||
"c129715d7a2bc9a3:1",
|
||||
]);
|
||||
await testHash(t, " hello; \t\nworld!!!\r\n\n\r \t\tGreetings\r End\r\n", [
|
||||
"e9496ae3ebfced30:1",
|
||||
"fb7c023a8b9ccb3f:1",
|
||||
"ce8ba1a563dcdaca:1",
|
||||
"e20e36e16fcb0cc8:1",
|
||||
"b3edc88f2938467e:1",
|
||||
"c8e28b0b4002a3a0:1",
|
||||
"c129715d7a2bc9a3:1",
|
||||
]);
|
||||
// Try repeating line that will generate identical hashes
|
||||
await testHash(t, "Lorem ipsum dolor sit amet.\n".repeat(10), [
|
||||
"a7f2ff13bc495cf2:1",
|
||||
"a7f2ff13bc495cf2:2",
|
||||
"a7f2ff13bc495cf2:3",
|
||||
"a7f2ff13bc495cf2:4",
|
||||
"a7f2ff13bc495cf2:5",
|
||||
"a7f2ff13bc495cf2:6",
|
||||
"a7f2ff1481e87703:1",
|
||||
"a9cf91f7bbf1862b:1",
|
||||
"55ec222b86bcae53:1",
|
||||
"cc97dc7b1d7d8f7b:1",
|
||||
"c129715d7a2bc9a3:1",
|
||||
]);
|
||||
await testHash(t, "x = 2\nx = 1\nprint(x)\nx = 3\nprint(x)\nx = 4\nprint(x)\n", [
|
||||
"e54938cc54b302f1:1",
|
||||
"bb609acbe9138d60:1",
|
||||
"1131fd5871777f34:1",
|
||||
"5c482a0f8b35ea28:1",
|
||||
"54517377da7028d2:1",
|
||||
"2c644846cb18d53e:1",
|
||||
"f1b89f20de0d133:1",
|
||||
"c129715d7a2bc9a3:1",
|
||||
]);
|
||||
});
|
||||
function testResolveUriToFile(uri, index, artifactsURIs) {
|
||||
const location = { uri, index };
|
||||
const artifacts = artifactsURIs.map((artifactURI) => ({
|
||||
location: { uri: artifactURI },
|
||||
}));
|
||||
return fingerprints.resolveUriToFile(location, artifacts, process.cwd(), (0, logging_1.getRunnerLogger)(true));
|
||||
}
|
||||
(0, ava_1.default)("resolveUriToFile", (t) => {
|
||||
// The resolveUriToFile method checks that the file exists and is in the right directory
|
||||
// so we need to give it real files to look at. We will use this file as an example.
|
||||
// For this to work we require the current working directory to be a parent, but this
|
||||
// should generally always be the case so this is fine.
|
||||
const filepath = __filename.split(path.sep).join("/");
|
||||
const relativeFilepath = path
|
||||
.relative(process.cwd(), __filename)
|
||||
.split(path.sep)
|
||||
.join("/");
|
||||
// Absolute paths are unmodified
|
||||
t.is(testResolveUriToFile(filepath, undefined, []), filepath);
|
||||
t.is(testResolveUriToFile(`file://${filepath}`, undefined, []), filepath);
|
||||
// Relative paths are made absolute
|
||||
t.is(testResolveUriToFile(relativeFilepath, undefined, [])
|
||||
?.split(path.sep)
|
||||
.join("/"), filepath);
|
||||
t.is(testResolveUriToFile(`file://${relativeFilepath}`, undefined, [])
|
||||
?.split(path.sep)
|
||||
.join("/"), filepath);
|
||||
// Absolute paths outside the src root are discarded
|
||||
t.is(testResolveUriToFile("/src/foo/bar.js", undefined, []), undefined);
|
||||
t.is(testResolveUriToFile("file:///src/foo/bar.js", undefined, []), undefined);
|
||||
// Other schemes are discarded
|
||||
t.is(testResolveUriToFile(`https://${filepath}`, undefined, []), undefined);
|
||||
t.is(testResolveUriToFile(`ftp://${filepath}`, undefined, []), undefined);
|
||||
// Invalid URIs are discarded
|
||||
t.is(testResolveUriToFile(1, undefined, []), undefined);
|
||||
t.is(testResolveUriToFile(undefined, undefined, []), undefined);
|
||||
// Non-existent files are discarded
|
||||
t.is(testResolveUriToFile(`${filepath}2`, undefined, []), undefined);
|
||||
// Index is resolved
|
||||
t.is(testResolveUriToFile(undefined, 0, [filepath]), filepath);
|
||||
t.is(testResolveUriToFile(undefined, 1, ["foo", filepath]), filepath);
|
||||
// Invalid indexes are discarded
|
||||
t.is(testResolveUriToFile(undefined, 1, [filepath]), undefined);
|
||||
t.is(testResolveUriToFile(undefined, "0", [filepath]), undefined);
|
||||
// Directories are discarded
|
||||
const dirpath = __dirname;
|
||||
t.is(testResolveUriToFile(dirpath, undefined, []), undefined);
|
||||
t.is(testResolveUriToFile(`file://${dirpath}`, undefined, []), undefined);
|
||||
});
|
||||
(0, ava_1.default)("addFingerprints", async (t) => {
|
||||
// Run an end-to-end test on a test file
|
||||
const input = JSON.parse(fs
|
||||
.readFileSync(`${__dirname}/../src/testdata/fingerprinting.input.sarif`)
|
||||
.toString());
|
||||
const expected = JSON.parse(fs
|
||||
.readFileSync(`${__dirname}/../src/testdata/fingerprinting.expected.sarif`)
|
||||
.toString());
|
||||
// The URIs in the SARIF files resolve to files in the testdata directory
|
||||
const sourceRoot = path.normalize(`${__dirname}/../src/testdata`);
|
||||
t.deepEqual(await fingerprints.addFingerprints(input, sourceRoot, (0, logging_1.getRunnerLogger)(true)), expected);
|
||||
});
|
||||
(0, ava_1.default)("missingRegions", async (t) => {
|
||||
// Run an end-to-end test on a test file
|
||||
const input = JSON.parse(fs
|
||||
.readFileSync(`${__dirname}/../src/testdata/fingerprinting2.input.sarif`)
|
||||
.toString());
|
||||
const expected = JSON.parse(fs
|
||||
.readFileSync(`${__dirname}/../src/testdata/fingerprinting2.expected.sarif`)
|
||||
.toString());
|
||||
// The URIs in the SARIF files resolve to files in the testdata directory
|
||||
const sourceRoot = path.normalize(`${__dirname}/../src/testdata`);
|
||||
t.deepEqual(await fingerprints.addFingerprints(input, sourceRoot, (0, logging_1.getRunnerLogger)(true)), expected);
|
||||
});
|
||||
//# sourceMappingURL=fingerprints.test.js.map
|
||||
File diff suppressed because one or more lines are too long
379
lib/git-utils.js
generated
379
lib/git-utils.js
generated
@@ -1,379 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getFileOidsUnderPath = exports.getGitRoot = exports.decodeGitFilePath = exports.gitRepack = exports.gitFetch = exports.deepenGitHistory = exports.determineBaseBranchHeadCommitOid = exports.getCommitOid = exports.runGitCommand = void 0;
|
||||
exports.getRef = getRef;
|
||||
exports.isAnalyzingDefaultBranch = isAnalyzingDefaultBranch;
|
||||
const core = __importStar(require("@actions/core"));
|
||||
const toolrunner = __importStar(require("@actions/exec/lib/toolrunner"));
|
||||
const io = __importStar(require("@actions/io"));
|
||||
const actions_util_1 = require("./actions-util");
|
||||
const util_1 = require("./util");
|
||||
const runGitCommand = async function (workingDirectory, args, customErrorMessage) {
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
core.debug(`Running git command: git ${args.join(" ")}`);
|
||||
try {
|
||||
await new toolrunner.ToolRunner(await io.which("git", true), args, {
|
||||
silent: true,
|
||||
listeners: {
|
||||
stdout: (data) => {
|
||||
stdout += data.toString();
|
||||
},
|
||||
stderr: (data) => {
|
||||
stderr += data.toString();
|
||||
},
|
||||
},
|
||||
cwd: workingDirectory,
|
||||
}).exec();
|
||||
return stdout;
|
||||
}
|
||||
catch (error) {
|
||||
let reason = stderr;
|
||||
if (stderr.includes("not a git repository")) {
|
||||
reason =
|
||||
"The checkout path provided to the action does not appear to be a git repository.";
|
||||
}
|
||||
core.info(`git call failed. ${customErrorMessage} Error: ${reason}`);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
exports.runGitCommand = runGitCommand;
|
||||
/**
|
||||
* Gets the SHA of the commit that is currently checked out.
|
||||
*/
|
||||
const getCommitOid = async function (checkoutPath, ref = "HEAD") {
|
||||
// Try to use git to get the current commit SHA. If that fails then
|
||||
// log but otherwise silently fall back to using the SHA from the environment.
|
||||
// The only time these two values will differ is during analysis of a PR when
|
||||
// the workflow has changed the current commit to the head commit instead of
|
||||
// the merge commit, which must mean that git is available.
|
||||
// Even if this does go wrong, it's not a huge problem for the alerts to
|
||||
// reported on the merge commit.
|
||||
try {
|
||||
const stdout = await (0, exports.runGitCommand)(checkoutPath, ["rev-parse", ref], "Continuing with commit SHA from user input or environment.");
|
||||
return stdout.trim();
|
||||
}
|
||||
catch {
|
||||
return (0, actions_util_1.getOptionalInput)("sha") || (0, util_1.getRequiredEnvParam)("GITHUB_SHA");
|
||||
}
|
||||
};
|
||||
exports.getCommitOid = getCommitOid;
|
||||
/**
|
||||
* If the action was triggered by a pull request, determine the commit sha at
|
||||
* the head of the base branch, using the merge commit that this workflow analyzes.
|
||||
* Returns undefined if run by other triggers or the base branch commit cannot be
|
||||
* determined.
|
||||
*/
|
||||
const determineBaseBranchHeadCommitOid = async function (checkoutPathOverride) {
|
||||
if ((0, actions_util_1.getWorkflowEventName)() !== "pull_request") {
|
||||
return undefined;
|
||||
}
|
||||
const mergeSha = (0, util_1.getRequiredEnvParam)("GITHUB_SHA");
|
||||
const checkoutPath = checkoutPathOverride ?? (0, actions_util_1.getOptionalInput)("checkout_path");
|
||||
try {
|
||||
let commitOid = "";
|
||||
let baseOid = "";
|
||||
let headOid = "";
|
||||
const stdout = await (0, exports.runGitCommand)(checkoutPath, ["show", "-s", "--format=raw", mergeSha], "Will calculate the base branch SHA on the server.");
|
||||
for (const data of stdout.split("\n")) {
|
||||
if (data.startsWith("commit ") && commitOid === "") {
|
||||
commitOid = data.substring(7);
|
||||
}
|
||||
else if (data.startsWith("parent ")) {
|
||||
if (baseOid === "") {
|
||||
baseOid = data.substring(7);
|
||||
}
|
||||
else if (headOid === "") {
|
||||
headOid = data.substring(7);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Let's confirm our assumptions: We had a merge commit and the parsed parent data looks correct
|
||||
if (commitOid === mergeSha &&
|
||||
headOid.length === 40 &&
|
||||
baseOid.length === 40) {
|
||||
return baseOid;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
catch {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
exports.determineBaseBranchHeadCommitOid = determineBaseBranchHeadCommitOid;
|
||||
/**
|
||||
* Deepen the git history of HEAD by one level. Errors are logged.
|
||||
*
|
||||
* This function uses the `checkout_path` to determine the repository path and
|
||||
* works only when called from `analyze` or `upload-sarif`.
|
||||
*/
|
||||
const deepenGitHistory = async function () {
|
||||
try {
|
||||
await (0, exports.runGitCommand)((0, actions_util_1.getOptionalInput)("checkout_path"), [
|
||||
"fetch",
|
||||
"origin",
|
||||
"HEAD",
|
||||
"--no-tags",
|
||||
"--no-recurse-submodules",
|
||||
"--deepen=1",
|
||||
], "Cannot deepen the shallow repository.");
|
||||
}
|
||||
catch {
|
||||
// Errors are already logged by runGitCommand()
|
||||
}
|
||||
};
|
||||
exports.deepenGitHistory = deepenGitHistory;
|
||||
/**
|
||||
* Fetch the given remote branch. Errors are logged.
|
||||
*
|
||||
* This function uses the `checkout_path` to determine the repository path and
|
||||
* works only when called from `analyze` or `upload-sarif`.
|
||||
*/
|
||||
const gitFetch = async function (branch, extraFlags) {
|
||||
try {
|
||||
await (0, exports.runGitCommand)((0, actions_util_1.getOptionalInput)("checkout_path"), ["fetch", "--no-tags", ...extraFlags, "origin", `${branch}:${branch}`], `Cannot fetch ${branch}.`);
|
||||
}
|
||||
catch {
|
||||
// Errors are already logged by runGitCommand()
|
||||
}
|
||||
};
|
||||
exports.gitFetch = gitFetch;
|
||||
/**
|
||||
* Repack the git repository, using with the given flags. Errors are logged.
|
||||
*
|
||||
* This function uses the `checkout_path` to determine the repository path and
|
||||
* works only when called from `analyze` or `upload-sarif`.
|
||||
*/
|
||||
const gitRepack = async function (flags) {
|
||||
try {
|
||||
await (0, exports.runGitCommand)((0, actions_util_1.getOptionalInput)("checkout_path"), ["repack", ...flags], "Cannot repack the repository.");
|
||||
}
|
||||
catch {
|
||||
// Errors are already logged by runGitCommand()
|
||||
}
|
||||
};
|
||||
exports.gitRepack = gitRepack;
|
||||
/**
|
||||
* Decode, if necessary, a file path produced by Git. See
|
||||
* https://git-scm.com/docs/git-config#Documentation/git-config.txt-corequotePath
|
||||
* for details on how Git encodes file paths with special characters.
|
||||
*
|
||||
* This function works only for Git output with `core.quotePath=false`.
|
||||
*/
|
||||
const decodeGitFilePath = function (filePath) {
|
||||
if (filePath.startsWith('"') && filePath.endsWith('"')) {
|
||||
filePath = filePath.substring(1, filePath.length - 1);
|
||||
return filePath.replace(/\\([abfnrtv\\"]|[0-7]{1,3})/g, (_match, seq) => {
|
||||
switch (seq[0]) {
|
||||
case "a":
|
||||
return "\x07";
|
||||
case "b":
|
||||
return "\b";
|
||||
case "f":
|
||||
return "\f";
|
||||
case "n":
|
||||
return "\n";
|
||||
case "r":
|
||||
return "\r";
|
||||
case "t":
|
||||
return "\t";
|
||||
case "v":
|
||||
return "\v";
|
||||
case "\\":
|
||||
return "\\";
|
||||
case '"':
|
||||
return '"';
|
||||
default:
|
||||
// Both String.fromCharCode() and String.fromCodePoint() works only
|
||||
// for constructing an entire character at once. If a Unicode
|
||||
// character is encoded as a sequence of escaped bytes, calling these
|
||||
// methods sequentially on the individual byte values would *not*
|
||||
// produce the original multi-byte Unicode character. As a result,
|
||||
// this implementation works only with the Git option core.quotePath
|
||||
// set to false.
|
||||
return String.fromCharCode(parseInt(seq, 8));
|
||||
}
|
||||
});
|
||||
}
|
||||
return filePath;
|
||||
};
|
||||
exports.decodeGitFilePath = decodeGitFilePath;
|
||||
/**
|
||||
* Get the root of the Git repository.
|
||||
*
|
||||
* @param sourceRoot The source root of the code being analyzed.
|
||||
* @returns The root of the Git repository.
|
||||
*/
|
||||
const getGitRoot = async function (sourceRoot) {
|
||||
try {
|
||||
const stdout = await (0, exports.runGitCommand)(sourceRoot, ["rev-parse", "--show-toplevel"], `Cannot find Git repository root from the source root ${sourceRoot}.`);
|
||||
return stdout.trim();
|
||||
}
|
||||
catch {
|
||||
// Errors are already logged by runGitCommand()
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
exports.getGitRoot = getGitRoot;
|
||||
/**
|
||||
* Returns the Git OIDs of all tracked files (in the index and in the working
|
||||
* tree) that are under the given base path, including files in active
|
||||
* submodules. Untracked files and files not under the given base path are
|
||||
* ignored.
|
||||
*
|
||||
* @param basePath A path into the Git repository.
|
||||
* @returns a map from file paths (relative to `basePath`) to Git OIDs.
|
||||
* @throws {Error} if "git ls-tree" produces unexpected output.
|
||||
*/
|
||||
const getFileOidsUnderPath = async function (basePath) {
|
||||
// Without the --full-name flag, the path is relative to the current working
|
||||
// directory of the git command, which is basePath.
|
||||
const stdout = await (0, exports.runGitCommand)(basePath, ["ls-files", "--recurse-submodules", "--format=%(objectname)_%(path)"], "Cannot list Git OIDs of tracked files.");
|
||||
const fileOidMap = {};
|
||||
// With --format=%(objectname)_%(path), the output is a list of lines like:
|
||||
// 30d998ded095371488be3a729eb61d86ed721a18_lib/git-utils.js
|
||||
// d89514599a9a99f22b4085766d40af7b99974827_lib/git-utils.js.map
|
||||
const regex = /^([0-9a-f]{40})_(.+)$/;
|
||||
for (const line of stdout.split("\n")) {
|
||||
if (line) {
|
||||
const match = line.match(regex);
|
||||
if (match) {
|
||||
const oid = match[1];
|
||||
const path = (0, exports.decodeGitFilePath)(match[2]);
|
||||
fileOidMap[path] = oid;
|
||||
}
|
||||
else {
|
||||
throw new Error(`Unexpected "git ls-files" output: ${line}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return fileOidMap;
|
||||
};
|
||||
exports.getFileOidsUnderPath = getFileOidsUnderPath;
|
||||
function getRefFromEnv() {
|
||||
// To workaround a limitation of Actions dynamic workflows not setting
|
||||
// the GITHUB_REF in some cases, we accept also the ref within the
|
||||
// CODE_SCANNING_REF variable. When possible, however, we prefer to use
|
||||
// the GITHUB_REF as that is a protected variable and cannot be overwritten.
|
||||
let refEnv;
|
||||
try {
|
||||
refEnv = (0, util_1.getRequiredEnvParam)("GITHUB_REF");
|
||||
}
|
||||
catch (e) {
|
||||
// If the GITHUB_REF is not set, we try to rescue by getting the
|
||||
// CODE_SCANNING_REF.
|
||||
const maybeRef = process.env["CODE_SCANNING_REF"];
|
||||
if (maybeRef === undefined || maybeRef.length === 0) {
|
||||
throw e;
|
||||
}
|
||||
refEnv = maybeRef;
|
||||
}
|
||||
return refEnv;
|
||||
}
|
||||
/**
|
||||
* Get the ref currently being analyzed.
|
||||
*/
|
||||
async function getRef() {
|
||||
// Will be in the form "refs/heads/master" on a push event
|
||||
// or in the form "refs/pull/N/merge" on a pull_request event
|
||||
const refInput = (0, actions_util_1.getOptionalInput)("ref");
|
||||
const shaInput = (0, actions_util_1.getOptionalInput)("sha");
|
||||
const checkoutPath = (0, actions_util_1.getOptionalInput)("checkout_path") ||
|
||||
(0, actions_util_1.getOptionalInput)("source-root") ||
|
||||
(0, util_1.getRequiredEnvParam)("GITHUB_WORKSPACE");
|
||||
const hasRefInput = !!refInput;
|
||||
const hasShaInput = !!shaInput;
|
||||
// If one of 'ref' or 'sha' are provided, both are required
|
||||
if ((hasRefInput || hasShaInput) && !(hasRefInput && hasShaInput)) {
|
||||
throw new util_1.ConfigurationError("Both 'ref' and 'sha' are required if one of them is provided.");
|
||||
}
|
||||
const ref = refInput || getRefFromEnv();
|
||||
const sha = shaInput || (0, util_1.getRequiredEnvParam)("GITHUB_SHA");
|
||||
// If the ref is a user-provided input, we have to skip logic
|
||||
// and assume that it is really where they want to upload the results.
|
||||
if (refInput) {
|
||||
return refInput;
|
||||
}
|
||||
// For pull request refs we want to detect whether the workflow
|
||||
// has run `git checkout HEAD^2` to analyze the 'head' ref rather
|
||||
// than the 'merge' ref. If so, we want to convert the ref that
|
||||
// we report back.
|
||||
const pull_ref_regex = /refs\/pull\/(\d+)\/merge/;
|
||||
if (!pull_ref_regex.test(ref)) {
|
||||
return ref;
|
||||
}
|
||||
const head = await (0, exports.getCommitOid)(checkoutPath, "HEAD");
|
||||
// in actions/checkout@v2+ we can check if git rev-parse HEAD == GITHUB_SHA
|
||||
// in actions/checkout@v1 this may not be true as it checks out the repository
|
||||
// using GITHUB_REF. There is a subtle race condition where
|
||||
// git rev-parse GITHUB_REF != GITHUB_SHA, so we must check
|
||||
// git rev-parse GITHUB_REF == git rev-parse HEAD instead.
|
||||
const hasChangedRef = sha !== head &&
|
||||
(await (0, exports.getCommitOid)(checkoutPath, ref.replace(/^refs\/pull\//, "refs/remotes/pull/"))) !== head;
|
||||
if (hasChangedRef) {
|
||||
const newRef = ref.replace(pull_ref_regex, "refs/pull/$1/head");
|
||||
core.debug(`No longer on merge commit, rewriting ref from ${ref} to ${newRef}.`);
|
||||
return newRef;
|
||||
}
|
||||
else {
|
||||
return ref;
|
||||
}
|
||||
}
|
||||
function removeRefsHeadsPrefix(ref) {
|
||||
return ref.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : ref;
|
||||
}
|
||||
/**
|
||||
* Returns whether we are analyzing the default branch for the repository.
|
||||
*
|
||||
* This first checks the environment variable `CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH`. This
|
||||
* environment variable can be set in cases where repository information might not be available, for
|
||||
* example dynamic workflows.
|
||||
*/
|
||||
async function isAnalyzingDefaultBranch() {
|
||||
if (process.env.CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH === "true") {
|
||||
return true;
|
||||
}
|
||||
// Get the current ref and trim and refs/heads/ prefix
|
||||
let currentRef = await getRef();
|
||||
currentRef = removeRefsHeadsPrefix(currentRef);
|
||||
const event = (0, actions_util_1.getWorkflowEvent)();
|
||||
let defaultBranch = event?.repository?.default_branch;
|
||||
if ((0, actions_util_1.getWorkflowEventName)() === "schedule") {
|
||||
defaultBranch = removeRefsHeadsPrefix(getRefFromEnv());
|
||||
}
|
||||
return currentRef === defaultBranch;
|
||||
}
|
||||
//# sourceMappingURL=git-utils.js.map
|
||||
File diff suppressed because one or more lines are too long
339
lib/git-utils.test.js
generated
339
lib/git-utils.test.js
generated
@@ -1,339 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const fs = __importStar(require("fs"));
|
||||
const path = __importStar(require("path"));
|
||||
const core = __importStar(require("@actions/core"));
|
||||
const ava_1 = __importDefault(require("ava"));
|
||||
const sinon = __importStar(require("sinon"));
|
||||
const actionsUtil = __importStar(require("./actions-util"));
|
||||
const gitUtils = __importStar(require("./git-utils"));
|
||||
const testing_utils_1 = require("./testing-utils");
|
||||
const util_1 = require("./util");
|
||||
(0, testing_utils_1.setupTests)(ava_1.default);
|
||||
(0, ava_1.default)("getRef() throws on the empty string", async (t) => {
|
||||
process.env["GITHUB_REF"] = "";
|
||||
await t.throwsAsync(gitUtils.getRef);
|
||||
});
|
||||
(0, ava_1.default)("getRef() returns merge PR ref if GITHUB_SHA still checked out", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
const expectedRef = "refs/pull/1/merge";
|
||||
const currentSha = "a".repeat(40);
|
||||
process.env["GITHUB_REF"] = expectedRef;
|
||||
process.env["GITHUB_SHA"] = currentSha;
|
||||
const callback = sinon.stub(gitUtils, "getCommitOid");
|
||||
callback.withArgs("HEAD").resolves(currentSha);
|
||||
const actualRef = await gitUtils.getRef();
|
||||
t.deepEqual(actualRef, expectedRef);
|
||||
callback.restore();
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("getRef() returns merge PR ref if GITHUB_REF still checked out but sha has changed (actions checkout@v1)", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
const expectedRef = "refs/pull/1/merge";
|
||||
process.env["GITHUB_REF"] = expectedRef;
|
||||
process.env["GITHUB_SHA"] = "b".repeat(40);
|
||||
const sha = "a".repeat(40);
|
||||
const callback = sinon.stub(gitUtils, "getCommitOid");
|
||||
callback.withArgs("refs/remotes/pull/1/merge").resolves(sha);
|
||||
callback.withArgs("HEAD").resolves(sha);
|
||||
const actualRef = await gitUtils.getRef();
|
||||
t.deepEqual(actualRef, expectedRef);
|
||||
callback.restore();
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("getRef() returns head PR ref if GITHUB_REF no longer checked out", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
process.env["GITHUB_REF"] = "refs/pull/1/merge";
|
||||
process.env["GITHUB_SHA"] = "a".repeat(40);
|
||||
const callback = sinon.stub(gitUtils, "getCommitOid");
|
||||
callback.withArgs(tmpDir, "refs/pull/1/merge").resolves("a".repeat(40));
|
||||
callback.withArgs(tmpDir, "HEAD").resolves("b".repeat(40));
|
||||
const actualRef = await gitUtils.getRef();
|
||||
t.deepEqual(actualRef, "refs/pull/1/head");
|
||||
callback.restore();
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("getRef() returns ref provided as an input and ignores current HEAD", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
const getAdditionalInputStub = sinon.stub(actionsUtil, "getOptionalInput");
|
||||
getAdditionalInputStub.withArgs("ref").resolves("refs/pull/2/merge");
|
||||
getAdditionalInputStub.withArgs("sha").resolves("b".repeat(40));
|
||||
// These values are be ignored
|
||||
process.env["GITHUB_REF"] = "refs/pull/1/merge";
|
||||
process.env["GITHUB_SHA"] = "a".repeat(40);
|
||||
const callback = sinon.stub(gitUtils, "getCommitOid");
|
||||
callback.withArgs("refs/pull/1/merge").resolves("b".repeat(40));
|
||||
callback.withArgs("HEAD").resolves("b".repeat(40));
|
||||
const actualRef = await gitUtils.getRef();
|
||||
t.deepEqual(actualRef, "refs/pull/2/merge");
|
||||
callback.restore();
|
||||
getAdditionalInputStub.restore();
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("getRef() returns CODE_SCANNING_REF as a fallback for GITHUB_REF", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
const expectedRef = "refs/pull/1/HEAD";
|
||||
const currentSha = "a".repeat(40);
|
||||
process.env["CODE_SCANNING_REF"] = expectedRef;
|
||||
process.env["GITHUB_REF"] = "";
|
||||
process.env["GITHUB_SHA"] = currentSha;
|
||||
const actualRef = await gitUtils.getRef();
|
||||
t.deepEqual(actualRef, expectedRef);
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("getRef() returns GITHUB_REF over CODE_SCANNING_REF if both are provided", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
const expectedRef = "refs/pull/1/merge";
|
||||
const currentSha = "a".repeat(40);
|
||||
process.env["CODE_SCANNING_REF"] = "refs/pull/1/HEAD";
|
||||
process.env["GITHUB_REF"] = expectedRef;
|
||||
process.env["GITHUB_SHA"] = currentSha;
|
||||
const actualRef = await gitUtils.getRef();
|
||||
t.deepEqual(actualRef, expectedRef);
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("getRef() throws an error if only `ref` is provided as an input", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
const getAdditionalInputStub = sinon.stub(actionsUtil, "getOptionalInput");
|
||||
getAdditionalInputStub.withArgs("ref").resolves("refs/pull/1/merge");
|
||||
await t.throwsAsync(async () => {
|
||||
await gitUtils.getRef();
|
||||
}, {
|
||||
instanceOf: Error,
|
||||
message: "Both 'ref' and 'sha' are required if one of them is provided.",
|
||||
});
|
||||
getAdditionalInputStub.restore();
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("getRef() throws an error if only `sha` is provided as an input", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
process.env["GITHUB_WORKSPACE"] = "/tmp";
|
||||
const getAdditionalInputStub = sinon.stub(actionsUtil, "getOptionalInput");
|
||||
getAdditionalInputStub.withArgs("sha").resolves("a".repeat(40));
|
||||
await t.throwsAsync(async () => {
|
||||
await gitUtils.getRef();
|
||||
}, {
|
||||
instanceOf: Error,
|
||||
message: "Both 'ref' and 'sha' are required if one of them is provided.",
|
||||
});
|
||||
getAdditionalInputStub.restore();
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("isAnalyzingDefaultBranch()", async (t) => {
|
||||
process.env["GITHUB_EVENT_NAME"] = "push";
|
||||
process.env["CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH"] = "true";
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), true);
|
||||
process.env["CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH"] = "false";
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
(0, testing_utils_1.setupActionsVars)(tmpDir, tmpDir);
|
||||
const envFile = path.join(tmpDir, "event.json");
|
||||
fs.writeFileSync(envFile, JSON.stringify({
|
||||
repository: {
|
||||
default_branch: "main",
|
||||
},
|
||||
}));
|
||||
process.env["GITHUB_EVENT_PATH"] = envFile;
|
||||
process.env["GITHUB_REF"] = "main";
|
||||
process.env["GITHUB_SHA"] = "1234";
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), true);
|
||||
process.env["GITHUB_REF"] = "refs/heads/main";
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), true);
|
||||
process.env["GITHUB_REF"] = "feature";
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), false);
|
||||
fs.writeFileSync(envFile, JSON.stringify({
|
||||
schedule: "0 0 * * *",
|
||||
}));
|
||||
process.env["GITHUB_EVENT_NAME"] = "schedule";
|
||||
process.env["GITHUB_REF"] = "refs/heads/main";
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), true);
|
||||
const getAdditionalInputStub = sinon.stub(actionsUtil, "getOptionalInput");
|
||||
getAdditionalInputStub
|
||||
.withArgs("ref")
|
||||
.resolves("refs/heads/something-else");
|
||||
getAdditionalInputStub
|
||||
.withArgs("sha")
|
||||
.resolves("0000000000000000000000000000000000000000");
|
||||
process.env["GITHUB_EVENT_NAME"] = "schedule";
|
||||
process.env["GITHUB_REF"] = "refs/heads/main";
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), false);
|
||||
getAdditionalInputStub.restore();
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("determineBaseBranchHeadCommitOid non-pullrequest", async (t) => {
|
||||
const infoStub = sinon.stub(core, "info");
|
||||
process.env["GITHUB_EVENT_NAME"] = "hucairz";
|
||||
process.env["GITHUB_SHA"] = "100912429fab4cb230e66ffb11e738ac5194e73a";
|
||||
const result = await gitUtils.determineBaseBranchHeadCommitOid(__dirname);
|
||||
t.deepEqual(result, undefined);
|
||||
t.deepEqual(0, infoStub.callCount);
|
||||
infoStub.restore();
|
||||
});
|
||||
(0, ava_1.default)("determineBaseBranchHeadCommitOid not git repository", async (t) => {
|
||||
const infoStub = sinon.stub(core, "info");
|
||||
process.env["GITHUB_EVENT_NAME"] = "pull_request";
|
||||
process.env["GITHUB_SHA"] = "100912429fab4cb230e66ffb11e738ac5194e73a";
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
await gitUtils.determineBaseBranchHeadCommitOid(tmpDir);
|
||||
});
|
||||
t.deepEqual(1, infoStub.callCount);
|
||||
t.deepEqual(infoStub.firstCall.args[0], "git call failed. Will calculate the base branch SHA on the server. Error: " +
|
||||
"The checkout path provided to the action does not appear to be a git repository.");
|
||||
infoStub.restore();
|
||||
});
|
||||
(0, ava_1.default)("determineBaseBranchHeadCommitOid other error", async (t) => {
|
||||
const infoStub = sinon.stub(core, "info");
|
||||
process.env["GITHUB_EVENT_NAME"] = "pull_request";
|
||||
process.env["GITHUB_SHA"] = "100912429fab4cb230e66ffb11e738ac5194e73a";
|
||||
const result = await gitUtils.determineBaseBranchHeadCommitOid(path.join(__dirname, "../../i-dont-exist"));
|
||||
t.deepEqual(result, undefined);
|
||||
t.deepEqual(1, infoStub.callCount);
|
||||
t.assert(infoStub.firstCall.args[0].startsWith("git call failed. Will calculate the base branch SHA on the server. Error: "));
|
||||
t.assert(!infoStub.firstCall.args[0].endsWith("The checkout path provided to the action does not appear to be a git repository."));
|
||||
infoStub.restore();
|
||||
});
|
||||
(0, ava_1.default)("decodeGitFilePath unquoted strings", async (t) => {
|
||||
t.deepEqual(gitUtils.decodeGitFilePath("foo"), "foo");
|
||||
t.deepEqual(gitUtils.decodeGitFilePath("foo bar"), "foo bar");
|
||||
t.deepEqual(gitUtils.decodeGitFilePath("foo\\\\bar"), "foo\\\\bar");
|
||||
t.deepEqual(gitUtils.decodeGitFilePath('foo\\"bar'), 'foo\\"bar');
|
||||
t.deepEqual(gitUtils.decodeGitFilePath("foo\\001bar"), "foo\\001bar");
|
||||
t.deepEqual(gitUtils.decodeGitFilePath("foo\\abar"), "foo\\abar");
|
||||
t.deepEqual(gitUtils.decodeGitFilePath("foo\\bbar"), "foo\\bbar");
|
||||
t.deepEqual(gitUtils.decodeGitFilePath("foo\\fbar"), "foo\\fbar");
|
||||
t.deepEqual(gitUtils.decodeGitFilePath("foo\\nbar"), "foo\\nbar");
|
||||
t.deepEqual(gitUtils.decodeGitFilePath("foo\\rbar"), "foo\\rbar");
|
||||
t.deepEqual(gitUtils.decodeGitFilePath("foo\\tbar"), "foo\\tbar");
|
||||
t.deepEqual(gitUtils.decodeGitFilePath("foo\\vbar"), "foo\\vbar");
|
||||
t.deepEqual(gitUtils.decodeGitFilePath("\\a\\b\\f\\n\\r\\t\\v"), "\\a\\b\\f\\n\\r\\t\\v");
|
||||
});
|
||||
(0, ava_1.default)("decodeGitFilePath quoted strings", async (t) => {
|
||||
t.deepEqual(gitUtils.decodeGitFilePath('"foo"'), "foo");
|
||||
t.deepEqual(gitUtils.decodeGitFilePath('"foo bar"'), "foo bar");
|
||||
t.deepEqual(gitUtils.decodeGitFilePath('"foo\\\\bar"'), "foo\\bar");
|
||||
t.deepEqual(gitUtils.decodeGitFilePath('"foo\\"bar"'), 'foo"bar');
|
||||
t.deepEqual(gitUtils.decodeGitFilePath('"foo\\001bar"'), "foo\x01bar");
|
||||
t.deepEqual(gitUtils.decodeGitFilePath('"foo\\abar"'), "foo\x07bar");
|
||||
t.deepEqual(gitUtils.decodeGitFilePath('"foo\\bbar"'), "foo\bbar");
|
||||
t.deepEqual(gitUtils.decodeGitFilePath('"foo\\fbar"'), "foo\fbar");
|
||||
t.deepEqual(gitUtils.decodeGitFilePath('"foo\\nbar"'), "foo\nbar");
|
||||
t.deepEqual(gitUtils.decodeGitFilePath('"foo\\rbar"'), "foo\rbar");
|
||||
t.deepEqual(gitUtils.decodeGitFilePath('"foo\\tbar"'), "foo\tbar");
|
||||
t.deepEqual(gitUtils.decodeGitFilePath('"foo\\vbar"'), "foo\vbar");
|
||||
t.deepEqual(gitUtils.decodeGitFilePath('"\\a\\b\\f\\n\\r\\t\\v"'), "\x07\b\f\n\r\t\v");
|
||||
});
|
||||
(0, ava_1.default)("getFileOidsUnderPath returns correct file mapping", async (t) => {
|
||||
const runGitCommandStub = sinon
|
||||
.stub(gitUtils, "runGitCommand")
|
||||
.resolves("30d998ded095371488be3a729eb61d86ed721a18_lib/git-utils.js\n" +
|
||||
"d89514599a9a99f22b4085766d40af7b99974827_lib/git-utils.js.map\n" +
|
||||
"a47c11f5bfdca7661942d2c8f1b7209fb0dfdf96_src/git-utils.ts");
|
||||
try {
|
||||
const result = await gitUtils.getFileOidsUnderPath("/fake/path");
|
||||
t.deepEqual(result, {
|
||||
"lib/git-utils.js": "30d998ded095371488be3a729eb61d86ed721a18",
|
||||
"lib/git-utils.js.map": "d89514599a9a99f22b4085766d40af7b99974827",
|
||||
"src/git-utils.ts": "a47c11f5bfdca7661942d2c8f1b7209fb0dfdf96",
|
||||
});
|
||||
t.deepEqual(runGitCommandStub.firstCall.args, [
|
||||
"/fake/path",
|
||||
["ls-files", "--recurse-submodules", "--format=%(objectname)_%(path)"],
|
||||
"Cannot list Git OIDs of tracked files.",
|
||||
]);
|
||||
}
|
||||
finally {
|
||||
runGitCommandStub.restore();
|
||||
}
|
||||
});
|
||||
(0, ava_1.default)("getFileOidsUnderPath handles quoted paths", async (t) => {
|
||||
const runGitCommandStub = sinon
|
||||
.stub(gitUtils, "runGitCommand")
|
||||
.resolves("30d998ded095371488be3a729eb61d86ed721a18_lib/normal-file.js\n" +
|
||||
'd89514599a9a99f22b4085766d40af7b99974827_"lib/file with spaces.js"\n' +
|
||||
'a47c11f5bfdca7661942d2c8f1b7209fb0dfdf96_"lib/file\\twith\\ttabs.js"');
|
||||
try {
|
||||
const result = await gitUtils.getFileOidsUnderPath("/fake/path");
|
||||
t.deepEqual(result, {
|
||||
"lib/normal-file.js": "30d998ded095371488be3a729eb61d86ed721a18",
|
||||
"lib/file with spaces.js": "d89514599a9a99f22b4085766d40af7b99974827",
|
||||
"lib/file\twith\ttabs.js": "a47c11f5bfdca7661942d2c8f1b7209fb0dfdf96",
|
||||
});
|
||||
}
|
||||
finally {
|
||||
runGitCommandStub.restore();
|
||||
}
|
||||
});
|
||||
(0, ava_1.default)("getFileOidsUnderPath handles empty output", async (t) => {
|
||||
const runGitCommandStub = sinon
|
||||
.stub(gitUtils, "runGitCommand")
|
||||
.resolves("");
|
||||
try {
|
||||
const result = await gitUtils.getFileOidsUnderPath("/fake/path");
|
||||
t.deepEqual(result, {});
|
||||
}
|
||||
finally {
|
||||
runGitCommandStub.restore();
|
||||
}
|
||||
});
|
||||
(0, ava_1.default)("getFileOidsUnderPath throws on unexpected output format", async (t) => {
|
||||
const runGitCommandStub = sinon
|
||||
.stub(gitUtils, "runGitCommand")
|
||||
.resolves("30d998ded095371488be3a729eb61d86ed721a18_lib/git-utils.js\n" +
|
||||
"invalid-line-format\n" +
|
||||
"a47c11f5bfdca7661942d2c8f1b7209fb0dfdf96_src/git-utils.ts");
|
||||
try {
|
||||
await t.throwsAsync(async () => {
|
||||
await gitUtils.getFileOidsUnderPath("/fake/path");
|
||||
}, {
|
||||
instanceOf: Error,
|
||||
message: 'Unexpected "git ls-files" output: invalid-line-format',
|
||||
});
|
||||
}
|
||||
finally {
|
||||
runGitCommandStub.restore();
|
||||
}
|
||||
});
|
||||
//# sourceMappingURL=git-utils.test.js.map
|
||||
File diff suppressed because one or more lines are too long
233
lib/init-action-post-helper.js
generated
233
lib/init-action-post-helper.js
generated
@@ -1,233 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.tryUploadSarifIfRunFailed = tryUploadSarifIfRunFailed;
|
||||
exports.run = run;
|
||||
exports.getFinalJobStatus = getFinalJobStatus;
|
||||
const fs = __importStar(require("fs"));
|
||||
const core = __importStar(require("@actions/core"));
|
||||
const github = __importStar(require("@actions/github"));
|
||||
const actionsUtil = __importStar(require("./actions-util"));
|
||||
const api_client_1 = require("./api-client");
|
||||
const codeql_1 = require("./codeql");
|
||||
const environment_1 = require("./environment");
|
||||
const feature_flags_1 = require("./feature-flags");
|
||||
const repository_1 = require("./repository");
|
||||
const status_report_1 = require("./status-report");
|
||||
const uploadLib = __importStar(require("./upload-lib"));
|
||||
const util_1 = require("./util");
|
||||
const workflow_1 = require("./workflow");
|
||||
function createFailedUploadFailedSarifResult(error) {
|
||||
const wrappedError = (0, util_1.wrapError)(error);
|
||||
return {
|
||||
upload_failed_run_error: wrappedError.message,
|
||||
upload_failed_run_stack_trace: wrappedError.stack,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Upload a failed SARIF file if we can verify that SARIF upload is enabled and determine the SARIF
|
||||
* category for the workflow.
|
||||
*/
|
||||
async function maybeUploadFailedSarif(config, repositoryNwo, features, logger) {
|
||||
if (!config.codeQLCmd) {
|
||||
return { upload_failed_run_skipped_because: "CodeQL command not found" };
|
||||
}
|
||||
const workflow = await (0, workflow_1.getWorkflow)(logger);
|
||||
const jobName = (0, util_1.getRequiredEnvParam)("GITHUB_JOB");
|
||||
const matrix = (0, util_1.parseMatrixInput)(actionsUtil.getRequiredInput("matrix"));
|
||||
const shouldUpload = (0, workflow_1.getUploadInputOrThrow)(workflow, jobName, matrix);
|
||||
if (!["always", "failure-only"].includes(actionsUtil.getUploadValue(shouldUpload)) ||
|
||||
(0, util_1.isInTestMode)()) {
|
||||
return { upload_failed_run_skipped_because: "SARIF upload is disabled" };
|
||||
}
|
||||
const category = (0, workflow_1.getCategoryInputOrThrow)(workflow, jobName, matrix);
|
||||
const checkoutPath = (0, workflow_1.getCheckoutPathInputOrThrow)(workflow, jobName, matrix);
|
||||
const databasePath = config.dbLocation;
|
||||
const codeql = await (0, codeql_1.getCodeQL)(config.codeQLCmd);
|
||||
const sarifFile = "../codeql-failed-run.sarif";
|
||||
// If there is no database or the feature flag is off, we run 'export diagnostics'
|
||||
if (databasePath === undefined ||
|
||||
!(await features.getValue(feature_flags_1.Feature.ExportDiagnosticsEnabled, codeql))) {
|
||||
await codeql.diagnosticsExport(sarifFile, category, config);
|
||||
}
|
||||
else {
|
||||
// We call 'database export-diagnostics' to find any per-database diagnostics.
|
||||
await codeql.databaseExportDiagnostics(databasePath, sarifFile, category);
|
||||
}
|
||||
logger.info(`Uploading failed SARIF file ${sarifFile}`);
|
||||
const uploadResult = await uploadLib.uploadFiles(sarifFile, checkoutPath, category, features, logger, uploadLib.CodeScanningTarget);
|
||||
await uploadLib.waitForProcessing(repositoryNwo, uploadResult.sarifID, logger, { isUnsuccessfulExecution: true });
|
||||
return uploadResult
|
||||
? { ...uploadResult.statusReport, sarifID: uploadResult.sarifID }
|
||||
: {};
|
||||
}
|
||||
async function tryUploadSarifIfRunFailed(config, repositoryNwo, features, logger) {
|
||||
if (process.env[environment_1.EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY] !== "true") {
|
||||
// If analyze didn't complete successfully and the job status hasn't
|
||||
// already been set to Failure/ConfigurationError previously, this
|
||||
// means that something along the way failed in a step that is not
|
||||
// owned by the Action, for example a manual build step. We
|
||||
// consider this a configuration error.
|
||||
core.exportVariable(environment_1.EnvVar.JOB_STATUS, process.env[environment_1.EnvVar.JOB_STATUS] ?? status_report_1.JobStatus.ConfigErrorStatus);
|
||||
try {
|
||||
return await maybeUploadFailedSarif(config, repositoryNwo, features, logger);
|
||||
}
|
||||
catch (e) {
|
||||
logger.debug(`Failed to upload a SARIF file for this failed CodeQL code scanning run. ${e}`);
|
||||
return createFailedUploadFailedSarifResult(e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
core.exportVariable(environment_1.EnvVar.JOB_STATUS, process.env[environment_1.EnvVar.JOB_STATUS] ?? status_report_1.JobStatus.SuccessStatus);
|
||||
return {
|
||||
upload_failed_run_skipped_because: "Analyze Action completed successfully",
|
||||
};
|
||||
}
|
||||
}
|
||||
async function run(uploadAllAvailableDebugArtifacts, printDebugLogs, codeql, config, repositoryNwo, features, logger) {
|
||||
const uploadFailedSarifResult = await tryUploadSarifIfRunFailed(config, repositoryNwo, features, logger);
|
||||
if (uploadFailedSarifResult.upload_failed_run_skipped_because) {
|
||||
logger.debug("Won't upload a failed SARIF file for this CodeQL code scanning run because: " +
|
||||
`${uploadFailedSarifResult.upload_failed_run_skipped_because}.`);
|
||||
}
|
||||
// Throw an error if in integration tests, we expected to upload a SARIF file for a failed run
|
||||
// but we didn't upload anything.
|
||||
if (process.env["CODEQL_ACTION_EXPECT_UPLOAD_FAILED_SARIF"] === "true" &&
|
||||
!uploadFailedSarifResult.raw_upload_size_bytes) {
|
||||
const error = JSON.stringify(uploadFailedSarifResult);
|
||||
throw new Error("Expected to upload a failed SARIF file for this CodeQL code scanning run, " +
|
||||
`but the result was instead ${error}.`);
|
||||
}
|
||||
if (process.env["CODEQL_ACTION_EXPECT_UPLOAD_FAILED_SARIF"] === "true") {
|
||||
if (!github.context.payload.pull_request?.head.repo.fork) {
|
||||
await removeUploadedSarif(uploadFailedSarifResult, logger);
|
||||
}
|
||||
else {
|
||||
logger.info("Skipping deletion of failed SARIF because the workflow was triggered from a fork of " +
|
||||
"codeql-action and doesn't have the appropriate permissions for deletion.");
|
||||
}
|
||||
}
|
||||
// Upload appropriate Actions artifacts for debugging
|
||||
if (config.debugMode) {
|
||||
logger.info("Debug mode is on. Uploading available database bundles and logs as Actions debugging artifacts...");
|
||||
const version = await codeql.getVersion();
|
||||
await uploadAllAvailableDebugArtifacts(codeql, config, logger, version.version);
|
||||
await printDebugLogs(config);
|
||||
}
|
||||
if (actionsUtil.isSelfHostedRunner()) {
|
||||
try {
|
||||
fs.rmSync(config.dbLocation, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
maxRetries: 3,
|
||||
});
|
||||
logger.info(`Cleaned up database cluster directory ${config.dbLocation}.`);
|
||||
}
|
||||
catch (e) {
|
||||
logger.warning(`Failed to clean up database cluster directory ${config.dbLocation}. Details: ${e}`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.debug("Skipping cleanup of database cluster directory since we are running on a GitHub-hosted " +
|
||||
"runner which will be automatically cleaned up.");
|
||||
}
|
||||
return uploadFailedSarifResult;
|
||||
}
|
||||
async function removeUploadedSarif(uploadFailedSarifResult, logger) {
|
||||
const sarifID = uploadFailedSarifResult.sarifID;
|
||||
if (sarifID) {
|
||||
logger.startGroup("Deleting failed SARIF upload");
|
||||
logger.info(`In test mode, therefore deleting the failed analysis to avoid impacting tool status for the Action repository. SARIF ID to delete: ${sarifID}.`);
|
||||
const client = (0, api_client_1.getApiClient)();
|
||||
try {
|
||||
const repositoryNwo = (0, repository_1.getRepositoryNwo)();
|
||||
// Wait to make sure the analysis is ready for download before requesting it.
|
||||
await (0, util_1.delay)(5000);
|
||||
// Get the analysis associated with the uploaded sarif
|
||||
const analysisInfo = await client.request("GET /repos/:owner/:repo/code-scanning/analyses?sarif_id=:sarif_id", {
|
||||
owner: repositoryNwo.owner,
|
||||
repo: repositoryNwo.repo,
|
||||
sarif_id: sarifID,
|
||||
});
|
||||
// Delete the analysis.
|
||||
if (analysisInfo.data.length === 1) {
|
||||
const analysis = analysisInfo.data[0];
|
||||
logger.info(`Analysis ID to delete: ${analysis.id}.`);
|
||||
try {
|
||||
await client.request("DELETE /repos/:owner/:repo/code-scanning/analyses/:analysis_id?confirm_delete", {
|
||||
owner: repositoryNwo.owner,
|
||||
repo: repositoryNwo.repo,
|
||||
analysis_id: analysis.id,
|
||||
});
|
||||
logger.info(`Analysis deleted.`);
|
||||
}
|
||||
catch (e) {
|
||||
const origMessage = (0, util_1.getErrorMessage)(e);
|
||||
const newMessage = origMessage.includes("No analysis found for analysis ID")
|
||||
? `Analysis ${analysis.id} does not exist. It was likely already deleted.`
|
||||
: origMessage;
|
||||
throw new Error(newMessage);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Error(`Expected to find exactly one analysis with sarif_id ${sarifID}. Found ${analysisInfo.data.length}.`);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error(`Failed to delete uploaded SARIF analysis. Reason: ${(0, util_1.getErrorMessage)(e)}`);
|
||||
}
|
||||
finally {
|
||||
logger.endGroup();
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.warning("Could not delete the uploaded SARIF analysis because a SARIF ID wasn't provided by the API when uploading the SARIF file.");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns the final job status sent in the `init-post` Action, based on the
|
||||
* current value of the JOB_STATUS environment variable. If the variable is
|
||||
* unset, or if its value is not one of the JobStatus enum values, returns
|
||||
* Unknown. Otherwise it returns the status set in the environment variable.
|
||||
*/
|
||||
function getFinalJobStatus() {
|
||||
const jobStatusFromEnvironment = process.env[environment_1.EnvVar.JOB_STATUS];
|
||||
if (!jobStatusFromEnvironment ||
|
||||
!Object.values(status_report_1.JobStatus).includes(jobStatusFromEnvironment)) {
|
||||
return status_report_1.JobStatus.UnknownStatus;
|
||||
}
|
||||
return jobStatusFromEnvironment;
|
||||
}
|
||||
//# sourceMappingURL=init-action-post-helper.js.map
|
||||
File diff suppressed because one or more lines are too long
363
lib/init-action-post-helper.test.js
generated
363
lib/init-action-post-helper.test.js
generated
@@ -1,363 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const ava_1 = __importDefault(require("ava"));
|
||||
const sinon = __importStar(require("sinon"));
|
||||
const actionsUtil = __importStar(require("./actions-util"));
|
||||
const codeql = __importStar(require("./codeql"));
|
||||
const configUtils = __importStar(require("./config-utils"));
|
||||
const feature_flags_1 = require("./feature-flags");
|
||||
const initActionPostHelper = __importStar(require("./init-action-post-helper"));
|
||||
const logging_1 = require("./logging");
|
||||
const repository_1 = require("./repository");
|
||||
const testing_utils_1 = require("./testing-utils");
|
||||
const uploadLib = __importStar(require("./upload-lib"));
|
||||
const util = __importStar(require("./util"));
|
||||
const workflow = __importStar(require("./workflow"));
|
||||
(0, testing_utils_1.setupTests)(ava_1.default);
|
||||
(0, ava_1.default)("post: init action with debug mode off", async (t) => {
|
||||
return await util.withTmpDir(async (tmpDir) => {
|
||||
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
|
||||
process.env["RUNNER_TEMP"] = tmpDir;
|
||||
const gitHubVersion = {
|
||||
type: util.GitHubVariant.DOTCOM,
|
||||
};
|
||||
sinon.stub(configUtils, "getConfig").resolves({
|
||||
debugMode: false,
|
||||
gitHubVersion,
|
||||
languages: [],
|
||||
packs: [],
|
||||
});
|
||||
const uploadAllAvailableDebugArtifactsSpy = sinon.spy();
|
||||
const printDebugLogsSpy = sinon.spy();
|
||||
await initActionPostHelper.run(uploadAllAvailableDebugArtifactsSpy, printDebugLogsSpy, codeql.createStubCodeQL({}), (0, testing_utils_1.createTestConfig)({ debugMode: false }), (0, repository_1.parseRepositoryNwo)("github/codeql-action"), (0, testing_utils_1.createFeatures)([]), (0, logging_1.getRunnerLogger)(true));
|
||||
t.assert(uploadAllAvailableDebugArtifactsSpy.notCalled);
|
||||
t.assert(printDebugLogsSpy.notCalled);
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("post: init action with debug mode on", async (t) => {
|
||||
return await util.withTmpDir(async (tmpDir) => {
|
||||
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
|
||||
process.env["RUNNER_TEMP"] = tmpDir;
|
||||
const uploadAllAvailableDebugArtifactsSpy = sinon.spy();
|
||||
const printDebugLogsSpy = sinon.spy();
|
||||
await initActionPostHelper.run(uploadAllAvailableDebugArtifactsSpy, printDebugLogsSpy, codeql.createStubCodeQL({}), (0, testing_utils_1.createTestConfig)({ debugMode: true }), (0, repository_1.parseRepositoryNwo)("github/codeql-action"), (0, testing_utils_1.createFeatures)([]), (0, logging_1.getRunnerLogger)(true));
|
||||
t.assert(uploadAllAvailableDebugArtifactsSpy.called);
|
||||
t.assert(printDebugLogsSpy.called);
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("uploads failed SARIF run with `diagnostics export` if feature flag is off", async (t) => {
|
||||
const actionsWorkflow = createTestWorkflow([
|
||||
{
|
||||
name: "Checkout repository",
|
||||
uses: "actions/checkout@v5",
|
||||
},
|
||||
{
|
||||
name: "Initialize CodeQL",
|
||||
uses: "github/codeql-action/init@v3",
|
||||
with: {
|
||||
languages: "javascript",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Perform CodeQL Analysis",
|
||||
uses: "github/codeql-action/analyze@v3",
|
||||
with: {
|
||||
category: "my-category",
|
||||
},
|
||||
},
|
||||
]);
|
||||
await testFailedSarifUpload(t, actionsWorkflow, { category: "my-category" });
|
||||
});
|
||||
(0, ava_1.default)("uploads failed SARIF run with `diagnostics export` if the database doesn't exist", async (t) => {
|
||||
const actionsWorkflow = createTestWorkflow([
|
||||
{
|
||||
name: "Checkout repository",
|
||||
uses: "actions/checkout@v5",
|
||||
},
|
||||
{
|
||||
name: "Initialize CodeQL",
|
||||
uses: "github/codeql-action/init@v3",
|
||||
with: {
|
||||
languages: "javascript",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Perform CodeQL Analysis",
|
||||
uses: "github/codeql-action/analyze@v3",
|
||||
with: {
|
||||
category: "my-category",
|
||||
},
|
||||
},
|
||||
]);
|
||||
await testFailedSarifUpload(t, actionsWorkflow, {
|
||||
category: "my-category",
|
||||
databaseExists: false,
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("uploads failed SARIF run with database export-diagnostics if the database exists and feature flag is on", async (t) => {
|
||||
const actionsWorkflow = createTestWorkflow([
|
||||
{
|
||||
name: "Checkout repository",
|
||||
uses: "actions/checkout@v5",
|
||||
},
|
||||
{
|
||||
name: "Initialize CodeQL",
|
||||
uses: "github/codeql-action/init@v3",
|
||||
with: {
|
||||
languages: "javascript",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Perform CodeQL Analysis",
|
||||
uses: "github/codeql-action/analyze@v3",
|
||||
with: {
|
||||
category: "my-category",
|
||||
},
|
||||
},
|
||||
]);
|
||||
await testFailedSarifUpload(t, actionsWorkflow, {
|
||||
category: "my-category",
|
||||
exportDiagnosticsEnabled: true,
|
||||
});
|
||||
});
|
||||
const UPLOAD_INPUT_TEST_CASES = [
|
||||
{
|
||||
uploadInput: "true",
|
||||
shouldUpload: true,
|
||||
},
|
||||
{
|
||||
uploadInput: "false",
|
||||
shouldUpload: true,
|
||||
},
|
||||
{
|
||||
uploadInput: "always",
|
||||
shouldUpload: true,
|
||||
},
|
||||
{
|
||||
uploadInput: "failure-only",
|
||||
shouldUpload: true,
|
||||
},
|
||||
{
|
||||
uploadInput: "never",
|
||||
shouldUpload: false,
|
||||
},
|
||||
{
|
||||
uploadInput: "unrecognized-value",
|
||||
shouldUpload: true,
|
||||
},
|
||||
];
|
||||
for (const { uploadInput, shouldUpload } of UPLOAD_INPUT_TEST_CASES) {
|
||||
(0, ava_1.default)(`does ${shouldUpload ? "" : "not "}upload failed SARIF run for workflow with upload: ${uploadInput}`, async (t) => {
|
||||
const actionsWorkflow = createTestWorkflow([
|
||||
{
|
||||
name: "Checkout repository",
|
||||
uses: "actions/checkout@v5",
|
||||
},
|
||||
{
|
||||
name: "Initialize CodeQL",
|
||||
uses: "github/codeql-action/init@v3",
|
||||
with: {
|
||||
languages: "javascript",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Perform CodeQL Analysis",
|
||||
uses: "github/codeql-action/analyze@v3",
|
||||
with: {
|
||||
category: "my-category",
|
||||
upload: uploadInput,
|
||||
},
|
||||
},
|
||||
]);
|
||||
const result = await testFailedSarifUpload(t, actionsWorkflow, {
|
||||
category: "my-category",
|
||||
expectUpload: shouldUpload,
|
||||
});
|
||||
if (!shouldUpload) {
|
||||
t.is(result.upload_failed_run_skipped_because, "SARIF upload is disabled");
|
||||
}
|
||||
});
|
||||
}
|
||||
(0, ava_1.default)("uploading failed SARIF run succeeds when workflow uses an input with a matrix var", async (t) => {
|
||||
const actionsWorkflow = createTestWorkflow([
|
||||
{
|
||||
name: "Checkout repository",
|
||||
uses: "actions/checkout@v5",
|
||||
},
|
||||
{
|
||||
name: "Initialize CodeQL",
|
||||
uses: "github/codeql-action/init@v3",
|
||||
with: {
|
||||
languages: "javascript",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Perform CodeQL Analysis",
|
||||
uses: "github/codeql-action/analyze@v3",
|
||||
with: {
|
||||
category: "/language:${{ matrix.language }}",
|
||||
},
|
||||
},
|
||||
]);
|
||||
await testFailedSarifUpload(t, actionsWorkflow, {
|
||||
category: "/language:csharp",
|
||||
matrix: { language: "csharp" },
|
||||
});
|
||||
});
|
||||
(0, ava_1.default)("uploading failed SARIF run fails when workflow uses a complex upload input", async (t) => {
|
||||
const actionsWorkflow = createTestWorkflow([
|
||||
{
|
||||
name: "Checkout repository",
|
||||
uses: "actions/checkout@v5",
|
||||
},
|
||||
{
|
||||
name: "Initialize CodeQL",
|
||||
uses: "github/codeql-action/init@v3",
|
||||
with: {
|
||||
languages: "javascript",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Perform CodeQL Analysis",
|
||||
uses: "github/codeql-action/analyze@v3",
|
||||
with: {
|
||||
upload: "${{ matrix.language != 'csharp' }}",
|
||||
},
|
||||
},
|
||||
]);
|
||||
const result = await testFailedSarifUpload(t, actionsWorkflow, {
|
||||
expectUpload: false,
|
||||
});
|
||||
t.is(result.upload_failed_run_error, "Could not get upload input to github/codeql-action/analyze since it contained an " +
|
||||
"unrecognized dynamic value.");
|
||||
});
|
||||
(0, ava_1.default)("uploading failed SARIF run fails when workflow does not reference github/codeql-action", async (t) => {
|
||||
const actionsWorkflow = createTestWorkflow([
|
||||
{
|
||||
name: "Checkout repository",
|
||||
uses: "actions/checkout@v5",
|
||||
},
|
||||
]);
|
||||
const result = await testFailedSarifUpload(t, actionsWorkflow, {
|
||||
expectUpload: false,
|
||||
});
|
||||
t.is(result.upload_failed_run_error, "Could not get upload input to github/codeql-action/analyze since the analyze job does not " +
|
||||
"call github/codeql-action/analyze.");
|
||||
t.truthy(result.upload_failed_run_stack_trace);
|
||||
});
|
||||
function createTestWorkflow(steps) {
|
||||
return {
|
||||
name: "CodeQL",
|
||||
on: {
|
||||
push: {
|
||||
branches: ["main"],
|
||||
},
|
||||
pull_request: {
|
||||
branches: ["main"],
|
||||
},
|
||||
},
|
||||
jobs: {
|
||||
analyze: {
|
||||
name: "CodeQL Analysis",
|
||||
"runs-on": "ubuntu-latest",
|
||||
steps,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
async function testFailedSarifUpload(t, actionsWorkflow, { category, databaseExists = true, expectUpload = true, exportDiagnosticsEnabled = false, matrix = {}, } = {}) {
|
||||
const config = {
|
||||
codeQLCmd: "codeql",
|
||||
debugMode: true,
|
||||
languages: [],
|
||||
packs: [],
|
||||
};
|
||||
if (databaseExists) {
|
||||
config.dbLocation = "path/to/database";
|
||||
}
|
||||
process.env["GITHUB_JOB"] = "analyze";
|
||||
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
|
||||
process.env["GITHUB_WORKSPACE"] =
|
||||
"/home/runner/work/codeql-action/codeql-action";
|
||||
sinon
|
||||
.stub(actionsUtil, "getRequiredInput")
|
||||
.withArgs("matrix")
|
||||
.returns(JSON.stringify(matrix));
|
||||
const codeqlObject = await codeql.getCodeQLForTesting();
|
||||
sinon.stub(codeql, "getCodeQL").resolves(codeqlObject);
|
||||
sinon.stub(codeqlObject, "getVersion").resolves((0, testing_utils_1.makeVersionInfo)("2.17.6"));
|
||||
const databaseExportDiagnosticsStub = sinon.stub(codeqlObject, "databaseExportDiagnostics");
|
||||
const diagnosticsExportStub = sinon.stub(codeqlObject, "diagnosticsExport");
|
||||
sinon.stub(workflow, "getWorkflow").resolves(actionsWorkflow);
|
||||
const uploadFiles = sinon.stub(uploadLib, "uploadFiles");
|
||||
uploadFiles.resolves({
|
||||
sarifID: "42",
|
||||
statusReport: { raw_upload_size_bytes: 20, zipped_upload_size_bytes: 10 },
|
||||
});
|
||||
const waitForProcessing = sinon.stub(uploadLib, "waitForProcessing");
|
||||
const features = [];
|
||||
if (exportDiagnosticsEnabled) {
|
||||
features.push(feature_flags_1.Feature.ExportDiagnosticsEnabled);
|
||||
}
|
||||
const result = await initActionPostHelper.tryUploadSarifIfRunFailed(config, (0, repository_1.parseRepositoryNwo)("github/codeql-action"), (0, testing_utils_1.createFeatures)(features), (0, logging_1.getRunnerLogger)(true));
|
||||
if (expectUpload) {
|
||||
t.deepEqual(result, {
|
||||
sarifID: "42",
|
||||
raw_upload_size_bytes: 20,
|
||||
zipped_upload_size_bytes: 10,
|
||||
});
|
||||
if (databaseExists && exportDiagnosticsEnabled) {
|
||||
t.true(databaseExportDiagnosticsStub.calledOnceWith(config.dbLocation, sinon.match.string, category), `Actual args were: ${JSON.stringify(databaseExportDiagnosticsStub.args)}`);
|
||||
}
|
||||
else {
|
||||
t.true(diagnosticsExportStub.calledOnceWith(sinon.match.string, category, config), `Actual args were: ${JSON.stringify(diagnosticsExportStub.args)}`);
|
||||
}
|
||||
t.true(uploadFiles.calledOnceWith(sinon.match.string, sinon.match.string, category, sinon.match.any, sinon.match.any), `Actual args were: ${JSON.stringify(uploadFiles.args)}`);
|
||||
t.true(waitForProcessing.calledOnceWith(sinon.match.any, "42", sinon.match.any, {
|
||||
isUnsuccessfulExecution: true,
|
||||
}));
|
||||
}
|
||||
else {
|
||||
t.true(diagnosticsExportStub.notCalled);
|
||||
t.true(uploadFiles.notCalled);
|
||||
t.true(waitForProcessing.notCalled);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
//# sourceMappingURL=init-action-post-helper.test.js.map
|
||||
File diff suppressed because one or more lines are too long
135522
lib/init-action-post.js
generated
135522
lib/init-action-post.js
generated
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"init-action-post.js","sourceRoot":"","sources":["../src/init-action-post.ts"],"names":[],"mappings":"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"}
|
||||
93544
lib/init-action.js
generated
93544
lib/init-action.js
generated
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
202
lib/init.js
generated
202
lib/init.js
generated
@@ -1,202 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.initCodeQL = initCodeQL;
|
||||
exports.initConfig = initConfig;
|
||||
exports.runDatabaseInitCluster = runDatabaseInitCluster;
|
||||
exports.checkPacksForOverlayCompatibility = checkPacksForOverlayCompatibility;
|
||||
exports.checkInstallPython311 = checkInstallPython311;
|
||||
exports.cleanupDatabaseClusterDirectory = cleanupDatabaseClusterDirectory;
|
||||
const fs = __importStar(require("fs"));
|
||||
const path = __importStar(require("path"));
|
||||
const toolrunner = __importStar(require("@actions/exec/lib/toolrunner"));
|
||||
const io = __importStar(require("@actions/io"));
|
||||
const yaml = __importStar(require("js-yaml"));
|
||||
const actions_util_1 = require("./actions-util");
|
||||
const codeql_1 = require("./codeql");
|
||||
const configUtils = __importStar(require("./config-utils"));
|
||||
const languages_1 = require("./languages");
|
||||
const logging_1 = require("./logging");
|
||||
const util = __importStar(require("./util"));
|
||||
async function initCodeQL(toolsInput, apiDetails, tempDir, variant, defaultCliVersion, logger) {
|
||||
logger.startGroup("Setup CodeQL tools");
|
||||
const { codeql, toolsDownloadStatusReport, toolsSource, toolsVersion, zstdAvailability, } = await (0, codeql_1.setupCodeQL)(toolsInput, apiDetails, tempDir, variant, defaultCliVersion, logger, true);
|
||||
await codeql.printVersion();
|
||||
logger.endGroup();
|
||||
return {
|
||||
codeql,
|
||||
toolsDownloadStatusReport,
|
||||
toolsSource,
|
||||
toolsVersion,
|
||||
zstdAvailability,
|
||||
};
|
||||
}
|
||||
async function initConfig(inputs) {
|
||||
return await (0, logging_1.withGroupAsync)("Load language configuration", async () => {
|
||||
return await configUtils.initConfig(inputs);
|
||||
});
|
||||
}
|
||||
async function runDatabaseInitCluster(databaseInitEnvironment, codeql, config, sourceRoot, processName, qlconfigFile, logger) {
|
||||
fs.mkdirSync(config.dbLocation, { recursive: true });
|
||||
await configUtils.wrapEnvironment(databaseInitEnvironment, async () => await codeql.databaseInitCluster(config, sourceRoot, processName, qlconfigFile, logger));
|
||||
}
|
||||
/**
|
||||
* Check whether all query packs are compatible with the overlay analysis
|
||||
* support in the CodeQL CLI. If the check fails, this function will log a
|
||||
* warning and returns false.
|
||||
*
|
||||
* @param codeql A CodeQL instance.
|
||||
* @param logger A logger.
|
||||
* @returns `true` if all query packs are compatible with overlay analysis,
|
||||
* `false` otherwise.
|
||||
*/
|
||||
async function checkPacksForOverlayCompatibility(codeql, config, logger) {
|
||||
const codeQlOverlayVersion = (await codeql.getVersion()).overlayVersion;
|
||||
if (codeQlOverlayVersion === undefined) {
|
||||
logger.warning("The CodeQL CLI does not support overlay analysis.");
|
||||
return false;
|
||||
}
|
||||
for (const language of config.languages) {
|
||||
const suitePath = util.getGeneratedSuitePath(config, language);
|
||||
const packDirs = await codeql.resolveQueriesStartingPacks([suitePath]);
|
||||
if (packDirs.some((packDir) => !checkPackForOverlayCompatibility(packDir, codeQlOverlayVersion, logger))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Check a single pack for its overlay compatibility. If the check fails, this
|
||||
* function will log a warning and returns false.
|
||||
*
|
||||
* @param packDir Path to the directory containing the pack.
|
||||
* @param codeQlOverlayVersion The overlay version of the CodeQL CLI.
|
||||
* @param logger A logger.
|
||||
* @returns `true` if the pack is compatible with overlay analysis, `false`
|
||||
* otherwise.
|
||||
*/
|
||||
function checkPackForOverlayCompatibility(packDir, codeQlOverlayVersion, logger) {
|
||||
try {
|
||||
let qlpackPath = path.join(packDir, "qlpack.yml");
|
||||
if (!fs.existsSync(qlpackPath)) {
|
||||
qlpackPath = path.join(packDir, "codeql-pack.yml");
|
||||
}
|
||||
const qlpackContents = yaml.load(fs.readFileSync(qlpackPath, "utf8"));
|
||||
if (!qlpackContents.buildMetadata) {
|
||||
// This is a source-only pack, and overlay compatibility checks apply only
|
||||
// to precompiled packs.
|
||||
return true;
|
||||
}
|
||||
const packInfoPath = path.join(packDir, ".packinfo");
|
||||
if (!fs.existsSync(packInfoPath)) {
|
||||
logger.warning(`The query pack at ${packDir} does not have a .packinfo file, ` +
|
||||
"so it cannot support overlay analysis. Recompiling the query pack " +
|
||||
"with the latest CodeQL CLI should solve this problem.");
|
||||
return false;
|
||||
}
|
||||
const packInfoFileContents = JSON.parse(fs.readFileSync(packInfoPath, "utf8"));
|
||||
const packOverlayVersion = packInfoFileContents.overlayVersion;
|
||||
if (typeof packOverlayVersion !== "number") {
|
||||
logger.warning(`The .packinfo file for the query pack at ${packDir} ` +
|
||||
"does not have the overlayVersion field, which indicates that " +
|
||||
"the pack is not compatible with overlay analysis.");
|
||||
return false;
|
||||
}
|
||||
if (packOverlayVersion !== codeQlOverlayVersion) {
|
||||
logger.warning(`The query pack at ${packDir} was compiled with ` +
|
||||
`overlay version ${packOverlayVersion}, but the CodeQL CLI ` +
|
||||
`supports overlay version ${codeQlOverlayVersion}. The ` +
|
||||
"query pack needs to be recompiled to support overlay analysis.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
logger.warning(`Error while checking pack at ${packDir} ` +
|
||||
`for overlay compatibility: ${util.getErrorMessage(e)}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* If we are running python 3.12+ on windows, we need to switch to python 3.11.
|
||||
* This check happens in a powershell script.
|
||||
*/
|
||||
async function checkInstallPython311(languages, codeql) {
|
||||
if (languages.includes(languages_1.KnownLanguage.python) &&
|
||||
process.platform === "win32" &&
|
||||
!(await codeql.getVersion()).features?.supportsPython312) {
|
||||
const script = path.resolve(__dirname, "../python-setup", "check_python12.ps1");
|
||||
await new toolrunner.ToolRunner(await io.which("powershell", true), [
|
||||
script,
|
||||
]).exec();
|
||||
}
|
||||
}
|
||||
function cleanupDatabaseClusterDirectory(config, logger, options = {},
|
||||
// We can't stub the fs module in tests, so we allow the caller to override the rmSync function
|
||||
// for testing.
|
||||
rmSync = fs.rmSync) {
|
||||
if (fs.existsSync(config.dbLocation) &&
|
||||
(fs.statSync(config.dbLocation).isFile() ||
|
||||
fs.readdirSync(config.dbLocation).length > 0)) {
|
||||
if (!options.disableExistingDirectoryWarning) {
|
||||
logger.warning(`The database cluster directory ${config.dbLocation} must be empty. Attempting to clean it up.`);
|
||||
}
|
||||
try {
|
||||
rmSync(config.dbLocation, {
|
||||
force: true,
|
||||
maxRetries: 3,
|
||||
recursive: true,
|
||||
});
|
||||
logger.info(`Cleaned up database cluster directory ${config.dbLocation}.`);
|
||||
}
|
||||
catch (e) {
|
||||
const blurb = `The CodeQL Action requires an empty database cluster directory. ${(0, actions_util_1.getOptionalInput)("db-location")
|
||||
? `This is currently configured to be ${config.dbLocation}. `
|
||||
: `By default, this is located at ${config.dbLocation}. ` +
|
||||
"You can customize it using the 'db-location' input to the init Action. "}An attempt was made to clean up the directory, but this failed.`;
|
||||
// Hosted runners are automatically cleaned up, so this error should not occur for hosted runners.
|
||||
if ((0, actions_util_1.isSelfHostedRunner)()) {
|
||||
throw new util.ConfigurationError(`${blurb} This can happen if another process is using the directory or the directory is owned by a different user. ` +
|
||||
`Please clean up the directory manually and rerun the job. Details: ${util.getErrorMessage(e)}`);
|
||||
}
|
||||
else {
|
||||
throw new Error(`${blurb} This shouldn't typically happen on hosted runners. ` +
|
||||
"If you are using an advanced setup, please check your workflow, otherwise we " +
|
||||
`recommend rerunning the job. Details: ${util.getErrorMessage(e)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=init.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"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"}
|
||||
319
lib/init.test.js
generated
319
lib/init.test.js
generated
@@ -1,319 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const fs = __importStar(require("fs"));
|
||||
const path_1 = __importDefault(require("path"));
|
||||
const ava_1 = __importDefault(require("ava"));
|
||||
const codeql_1 = require("./codeql");
|
||||
const init_1 = require("./init");
|
||||
const languages_1 = require("./languages");
|
||||
const testing_utils_1 = require("./testing-utils");
|
||||
const util_1 = require("./util");
|
||||
(0, testing_utils_1.setupTests)(ava_1.default);
|
||||
(0, ava_1.default)("cleanupDatabaseClusterDirectory cleans up where possible", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const dbLocation = path_1.default.resolve(tmpDir, "dbs");
|
||||
fs.mkdirSync(dbLocation, { recursive: true });
|
||||
const fileToCleanUp = path_1.default.resolve(dbLocation, "something-to-cleanup.txt");
|
||||
fs.writeFileSync(fileToCleanUp, "");
|
||||
const messages = [];
|
||||
(0, init_1.cleanupDatabaseClusterDirectory)((0, testing_utils_1.createTestConfig)({ dbLocation }), (0, testing_utils_1.getRecordingLogger)(messages));
|
||||
t.is(messages.length, 2);
|
||||
t.is(messages[0].type, "warning");
|
||||
t.is(messages[0].message, `The database cluster directory ${dbLocation} must be empty. Attempting to clean it up.`);
|
||||
t.is(messages[1].type, "info");
|
||||
t.is(messages[1].message, `Cleaned up database cluster directory ${dbLocation}.`);
|
||||
t.false(fs.existsSync(fileToCleanUp));
|
||||
});
|
||||
});
|
||||
for (const { runnerEnv, ErrorConstructor, message } of [
|
||||
{
|
||||
runnerEnv: "self-hosted",
|
||||
ErrorConstructor: util_1.ConfigurationError,
|
||||
message: (dbLocation) => "The CodeQL Action requires an empty database cluster directory. By default, this is located " +
|
||||
`at ${dbLocation}. You can customize it using the 'db-location' input to the init Action. An ` +
|
||||
"attempt was made to clean up the directory, but this failed. This can happen if another " +
|
||||
"process is using the directory or the directory is owned by a different user. Please clean " +
|
||||
"up the directory manually and rerun the job.",
|
||||
},
|
||||
{
|
||||
runnerEnv: "github-hosted",
|
||||
ErrorConstructor: Error,
|
||||
message: (dbLocation) => "The CodeQL Action requires an empty database cluster directory. By default, this is located " +
|
||||
`at ${dbLocation}. You can customize it using the 'db-location' input to the init Action. An ` +
|
||||
"attempt was made to clean up the directory, but this failed. This shouldn't typically " +
|
||||
"happen on hosted runners. If you are using an advanced setup, please check your workflow, " +
|
||||
"otherwise we recommend rerunning the job.",
|
||||
},
|
||||
]) {
|
||||
(0, ava_1.default)(`cleanupDatabaseClusterDirectory throws a ${ErrorConstructor.name} when cleanup fails on ${runnerEnv} runner`, async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
process.env["RUNNER_ENVIRONMENT"] = runnerEnv;
|
||||
const dbLocation = path_1.default.resolve(tmpDir, "dbs");
|
||||
fs.mkdirSync(dbLocation, { recursive: true });
|
||||
const fileToCleanUp = path_1.default.resolve(dbLocation, "something-to-cleanup.txt");
|
||||
fs.writeFileSync(fileToCleanUp, "");
|
||||
const rmSyncError = `Failed to clean up file ${fileToCleanUp}`;
|
||||
const messages = [];
|
||||
t.throws(() => (0, init_1.cleanupDatabaseClusterDirectory)((0, testing_utils_1.createTestConfig)({ dbLocation }), (0, testing_utils_1.getRecordingLogger)(messages), {}, () => {
|
||||
throw new Error(rmSyncError);
|
||||
}), {
|
||||
instanceOf: ErrorConstructor,
|
||||
message: `${message(dbLocation)} Details: ${rmSyncError}`,
|
||||
});
|
||||
t.is(messages.length, 1);
|
||||
t.is(messages[0].type, "warning");
|
||||
t.is(messages[0].message, `The database cluster directory ${dbLocation} must be empty. Attempting to clean it up.`);
|
||||
});
|
||||
});
|
||||
}
|
||||
(0, ava_1.default)("cleanupDatabaseClusterDirectory can disable warning with options", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const dbLocation = path_1.default.resolve(tmpDir, "dbs");
|
||||
fs.mkdirSync(dbLocation, { recursive: true });
|
||||
const fileToCleanUp = path_1.default.resolve(dbLocation, "something-to-cleanup.txt");
|
||||
fs.writeFileSync(fileToCleanUp, "");
|
||||
const messages = [];
|
||||
(0, init_1.cleanupDatabaseClusterDirectory)((0, testing_utils_1.createTestConfig)({ dbLocation }), (0, testing_utils_1.getRecordingLogger)(messages), { disableExistingDirectoryWarning: true });
|
||||
// Should only have the info message, not the warning
|
||||
t.is(messages.length, 1);
|
||||
t.is(messages[0].type, "info");
|
||||
t.is(messages[0].message, `Cleaned up database cluster directory ${dbLocation}.`);
|
||||
t.false(fs.existsSync(fileToCleanUp));
|
||||
});
|
||||
});
|
||||
const testCheckPacksForOverlayCompatibility = ava_1.default.macro({
|
||||
exec: async (t, _title, { cliOverlayVersion, languages, packs, expectedResult, }) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const packDirsByLanguage = new Map();
|
||||
for (const [packName, packInfo] of Object.entries(packs)) {
|
||||
const packPath = path_1.default.join(tmpDir, packName);
|
||||
fs.mkdirSync(packPath, { recursive: true });
|
||||
if (packInfo.packinfoContents) {
|
||||
fs.writeFileSync(path_1.default.join(packPath, ".packinfo"), packInfo.packinfoContents);
|
||||
}
|
||||
const qlpackFileName = packInfo.qlpackFileName || "qlpack.yml";
|
||||
fs.writeFileSync(path_1.default.join(packPath, qlpackFileName), packInfo.sourceOnlyPack
|
||||
? `name: ${packName}\nversion: 1.0.0\n`
|
||||
: `name: ${packName}\nversion: 1.0.0\nbuildMetadata:\n sha: 123abc\n`);
|
||||
if (!packDirsByLanguage.has(packInfo.language)) {
|
||||
packDirsByLanguage.set(packInfo.language, []);
|
||||
}
|
||||
packDirsByLanguage.get(packInfo.language).push(packPath);
|
||||
}
|
||||
const codeql = (0, codeql_1.createStubCodeQL)({
|
||||
getVersion: async () => ({
|
||||
version: "2.22.2",
|
||||
overlayVersion: cliOverlayVersion,
|
||||
}),
|
||||
resolveQueriesStartingPacks: async (suitePaths) => {
|
||||
for (const language of packDirsByLanguage.keys()) {
|
||||
const suiteForLanguage = path_1.default.join(language, "temp", "config-queries.qls");
|
||||
if (suitePaths[0].endsWith(suiteForLanguage)) {
|
||||
return packDirsByLanguage.get(language) || [];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
},
|
||||
});
|
||||
const messages = [];
|
||||
const result = await (0, init_1.checkPacksForOverlayCompatibility)(codeql, (0, testing_utils_1.createTestConfig)({ dbLocation: tmpDir, languages }), (0, testing_utils_1.getRecordingLogger)(messages));
|
||||
t.is(result, expectedResult);
|
||||
t.deepEqual(messages.length, expectedResult ? 0 : 1, "Expected log messages");
|
||||
});
|
||||
},
|
||||
title: (_, title) => `checkPacksForOverlayCompatibility: ${title}`,
|
||||
});
|
||||
(0, ava_1.default)(testCheckPacksForOverlayCompatibility, "returns false when CLI does not support overlay", {
|
||||
cliOverlayVersion: undefined,
|
||||
languages: [languages_1.KnownLanguage.java],
|
||||
packs: {
|
||||
"codeql/java-queries": {
|
||||
language: languages_1.KnownLanguage.java,
|
||||
packinfoContents: '{"overlayVersion":2}',
|
||||
},
|
||||
},
|
||||
expectedResult: false,
|
||||
});
|
||||
(0, ava_1.default)(testCheckPacksForOverlayCompatibility, "returns true when there are no query packs", {
|
||||
cliOverlayVersion: 2,
|
||||
languages: [languages_1.KnownLanguage.java],
|
||||
packs: {},
|
||||
expectedResult: true,
|
||||
});
|
||||
(0, ava_1.default)(testCheckPacksForOverlayCompatibility, "returns true when query pack has not been compiled", {
|
||||
cliOverlayVersion: 2,
|
||||
languages: [languages_1.KnownLanguage.java],
|
||||
packs: {
|
||||
"codeql/java-queries": {
|
||||
language: languages_1.KnownLanguage.java,
|
||||
packinfoContents: undefined,
|
||||
sourceOnlyPack: true,
|
||||
},
|
||||
},
|
||||
expectedResult: true,
|
||||
});
|
||||
(0, ava_1.default)(testCheckPacksForOverlayCompatibility, "returns true when query pack has expected overlay version", {
|
||||
cliOverlayVersion: 2,
|
||||
languages: [languages_1.KnownLanguage.java],
|
||||
packs: {
|
||||
"codeql/java-queries": {
|
||||
language: languages_1.KnownLanguage.java,
|
||||
packinfoContents: '{"overlayVersion":2}',
|
||||
},
|
||||
},
|
||||
expectedResult: true,
|
||||
});
|
||||
(0, ava_1.default)(testCheckPacksForOverlayCompatibility, "returns true when query packs for all languages to analyze are compatible", {
|
||||
cliOverlayVersion: 2,
|
||||
languages: [languages_1.KnownLanguage.cpp, languages_1.KnownLanguage.java],
|
||||
packs: {
|
||||
"codeql/cpp-queries": {
|
||||
language: languages_1.KnownLanguage.cpp,
|
||||
packinfoContents: '{"overlayVersion":2}',
|
||||
},
|
||||
"codeql/java-queries": {
|
||||
language: languages_1.KnownLanguage.java,
|
||||
packinfoContents: '{"overlayVersion":2}',
|
||||
},
|
||||
},
|
||||
expectedResult: true,
|
||||
});
|
||||
(0, ava_1.default)(testCheckPacksForOverlayCompatibility, "returns true when query pack for a language not analyzed is incompatible", {
|
||||
cliOverlayVersion: 2,
|
||||
languages: [languages_1.KnownLanguage.java],
|
||||
packs: {
|
||||
"codeql/cpp-queries": {
|
||||
language: languages_1.KnownLanguage.cpp,
|
||||
packinfoContents: undefined,
|
||||
},
|
||||
"codeql/java-queries": {
|
||||
language: languages_1.KnownLanguage.java,
|
||||
packinfoContents: '{"overlayVersion":2}',
|
||||
},
|
||||
},
|
||||
expectedResult: true,
|
||||
});
|
||||
(0, ava_1.default)(testCheckPacksForOverlayCompatibility, "returns false when query pack for a language to analyze is incompatible", {
|
||||
cliOverlayVersion: 2,
|
||||
languages: [languages_1.KnownLanguage.cpp, languages_1.KnownLanguage.java],
|
||||
packs: {
|
||||
"codeql/cpp-queries": {
|
||||
language: languages_1.KnownLanguage.cpp,
|
||||
packinfoContents: '{"overlayVersion":1}',
|
||||
},
|
||||
"codeql/java-queries": {
|
||||
language: languages_1.KnownLanguage.java,
|
||||
packinfoContents: '{"overlayVersion":2}',
|
||||
},
|
||||
},
|
||||
expectedResult: false,
|
||||
});
|
||||
(0, ava_1.default)(testCheckPacksForOverlayCompatibility, "returns false when query pack is missing .packinfo", {
|
||||
cliOverlayVersion: 2,
|
||||
languages: [languages_1.KnownLanguage.java],
|
||||
packs: {
|
||||
"codeql/java-queries": {
|
||||
language: languages_1.KnownLanguage.java,
|
||||
packinfoContents: '{"overlayVersion":2}',
|
||||
},
|
||||
"custom/queries": {
|
||||
language: languages_1.KnownLanguage.java,
|
||||
packinfoContents: undefined,
|
||||
},
|
||||
},
|
||||
expectedResult: false,
|
||||
});
|
||||
(0, ava_1.default)(testCheckPacksForOverlayCompatibility, "returns false when query pack has different overlay version", {
|
||||
cliOverlayVersion: 2,
|
||||
languages: [languages_1.KnownLanguage.java],
|
||||
packs: {
|
||||
"codeql/java-queries": {
|
||||
language: languages_1.KnownLanguage.java,
|
||||
packinfoContents: '{"overlayVersion":2}',
|
||||
},
|
||||
"custom/queries": {
|
||||
language: languages_1.KnownLanguage.java,
|
||||
packinfoContents: '{"overlayVersion":1}',
|
||||
},
|
||||
},
|
||||
expectedResult: false,
|
||||
});
|
||||
(0, ava_1.default)(testCheckPacksForOverlayCompatibility, "returns false when query pack is missing overlayVersion in .packinfo", {
|
||||
cliOverlayVersion: 2,
|
||||
languages: [languages_1.KnownLanguage.java],
|
||||
packs: {
|
||||
"codeql/java-queries": {
|
||||
language: languages_1.KnownLanguage.java,
|
||||
packinfoContents: '{"overlayVersion":2}',
|
||||
},
|
||||
"custom/queries": {
|
||||
language: languages_1.KnownLanguage.java,
|
||||
packinfoContents: "{}",
|
||||
},
|
||||
},
|
||||
expectedResult: false,
|
||||
});
|
||||
(0, ava_1.default)(testCheckPacksForOverlayCompatibility, "returns false when .packinfo is not valid JSON", {
|
||||
cliOverlayVersion: 2,
|
||||
languages: [languages_1.KnownLanguage.java],
|
||||
packs: {
|
||||
"codeql/java-queries": {
|
||||
language: languages_1.KnownLanguage.java,
|
||||
packinfoContents: '{"overlayVersion":2}',
|
||||
},
|
||||
"custom/queries": {
|
||||
language: languages_1.KnownLanguage.java,
|
||||
packinfoContents: "this_is_not_valid_json",
|
||||
},
|
||||
},
|
||||
expectedResult: false,
|
||||
});
|
||||
(0, ava_1.default)(testCheckPacksForOverlayCompatibility, "returns true when query pack uses codeql-pack.yml filename", {
|
||||
cliOverlayVersion: 2,
|
||||
languages: [languages_1.KnownLanguage.java],
|
||||
packs: {
|
||||
"codeql/java-queries": {
|
||||
language: languages_1.KnownLanguage.java,
|
||||
packinfoContents: '{"overlayVersion":2}',
|
||||
qlpackFileName: "codeql-pack.yml",
|
||||
},
|
||||
},
|
||||
expectedResult: true,
|
||||
});
|
||||
//# sourceMappingURL=init.test.js.map
|
||||
File diff suppressed because one or more lines are too long
23
lib/languages.js
generated
23
lib/languages.js
generated
@@ -1,23 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.KnownLanguage = void 0;
|
||||
/**
|
||||
* A language supported by CodeQL that is treated specially by the Action.
|
||||
*
|
||||
* This is not an exhaustive list of languages supported by CodeQL and new
|
||||
* languages do not need to be added here.
|
||||
*/
|
||||
var KnownLanguage;
|
||||
(function (KnownLanguage) {
|
||||
KnownLanguage["actions"] = "actions";
|
||||
KnownLanguage["cpp"] = "cpp";
|
||||
KnownLanguage["csharp"] = "csharp";
|
||||
KnownLanguage["go"] = "go";
|
||||
KnownLanguage["java"] = "java";
|
||||
KnownLanguage["javascript"] = "javascript";
|
||||
KnownLanguage["python"] = "python";
|
||||
KnownLanguage["ruby"] = "ruby";
|
||||
KnownLanguage["rust"] = "rust";
|
||||
KnownLanguage["swift"] = "swift";
|
||||
})(KnownLanguage || (exports.KnownLanguage = KnownLanguage = {}));
|
||||
//# sourceMappingURL=languages.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"languages.js","sourceRoot":"","sources":["../src/languages.ts"],"names":[],"mappings":";;;AAGA;;;;;GAKG;AACH,IAAY,aAWX;AAXD,WAAY,aAAa;IACvB,oCAAmB,CAAA;IACnB,4BAAW,CAAA;IACX,kCAAiB,CAAA;IACjB,0BAAS,CAAA;IACT,8BAAa,CAAA;IACb,0CAAyB,CAAA;IACzB,kCAAiB,CAAA;IACjB,8BAAa,CAAA;IACb,8BAAa,CAAA;IACb,gCAAe,CAAA;AACjB,CAAC,EAXW,aAAa,6BAAb,aAAa,QAWxB"}
|
||||
90
lib/logging.js
generated
90
lib/logging.js
generated
@@ -1,90 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getActionsLogger = getActionsLogger;
|
||||
exports.getRunnerLogger = getRunnerLogger;
|
||||
exports.withGroup = withGroup;
|
||||
exports.withGroupAsync = withGroupAsync;
|
||||
exports.formatDuration = formatDuration;
|
||||
const core = __importStar(require("@actions/core"));
|
||||
function getActionsLogger() {
|
||||
return core;
|
||||
}
|
||||
function getRunnerLogger(debugMode) {
|
||||
return {
|
||||
// eslint-disable-next-line no-console
|
||||
debug: debugMode ? console.debug : () => undefined,
|
||||
// eslint-disable-next-line no-console
|
||||
info: console.info,
|
||||
// eslint-disable-next-line no-console
|
||||
warning: console.warn,
|
||||
// eslint-disable-next-line no-console
|
||||
error: console.error,
|
||||
isDebug: () => debugMode,
|
||||
startGroup: () => undefined,
|
||||
endGroup: () => undefined,
|
||||
};
|
||||
}
|
||||
function withGroup(groupName, f) {
|
||||
core.startGroup(groupName);
|
||||
try {
|
||||
return f();
|
||||
}
|
||||
finally {
|
||||
core.endGroup();
|
||||
}
|
||||
}
|
||||
async function withGroupAsync(groupName, f) {
|
||||
core.startGroup(groupName);
|
||||
try {
|
||||
return await f();
|
||||
}
|
||||
finally {
|
||||
core.endGroup();
|
||||
}
|
||||
}
|
||||
/** Format a duration for use in logs. */
|
||||
function formatDuration(durationMs) {
|
||||
if (durationMs < 1000) {
|
||||
return `${durationMs}ms`;
|
||||
}
|
||||
if (durationMs < 60 * 1000) {
|
||||
return `${(durationMs / 1000).toFixed(1)}s`;
|
||||
}
|
||||
const minutes = Math.floor(durationMs / (60 * 1000));
|
||||
const seconds = Math.floor((durationMs % (60 * 1000)) / 1000);
|
||||
return `${minutes}m${seconds}s`;
|
||||
}
|
||||
//# sourceMappingURL=logging.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"logging.js","sourceRoot":"","sources":["../src/logging.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAcA,4CAEC;AAED,0CAcC;AAED,8BAOC;AAED,wCAUC;AAGD,wCAWC;AAnED,oDAAsC;AActC,SAAgB,gBAAgB;IAC9B,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAgB,eAAe,CAAC,SAAkB;IAChD,OAAO;QACL,sCAAsC;QACtC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,SAAS;QAClD,sCAAsC;QACtC,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,sCAAsC;QACtC,OAAO,EAAE,OAAO,CAAC,IAAI;QACrB,sCAAsC;QACtC,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS;QACxB,UAAU,EAAE,GAAG,EAAE,CAAC,SAAS;QAC3B,QAAQ,EAAE,GAAG,EAAE,CAAC,SAAS;KAC1B,CAAC;AACJ,CAAC;AAED,SAAgB,SAAS,CAAI,SAAiB,EAAE,CAAU;IACxD,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IAC3B,IAAI,CAAC;QACH,OAAO,CAAC,EAAE,CAAC;IACb,CAAC;YAAS,CAAC;QACT,IAAI,CAAC,QAAQ,EAAE,CAAC;IAClB,CAAC;AACH,CAAC;AAEM,KAAK,UAAU,cAAc,CAClC,SAAiB,EACjB,CAAmB;IAEnB,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IAC3B,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,EAAE,CAAC;IACnB,CAAC;YAAS,CAAC;QACT,IAAI,CAAC,QAAQ,EAAE,CAAC;IAClB,CAAC;AACH,CAAC;AAED,yCAAyC;AACzC,SAAgB,cAAc,CAAC,UAAkB;IAC/C,IAAI,UAAU,GAAG,IAAI,EAAE,CAAC;QACtB,OAAO,GAAG,UAAU,IAAI,CAAC;IAC3B,CAAC;IAED,IAAI,UAAU,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;QAC3B,OAAO,GAAG,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IAC9C,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;IACrD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,UAAU,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC9D,OAAO,GAAG,OAAO,IAAI,OAAO,GAAG,CAAC;AAClC,CAAC"}
|
||||
328
lib/overlay-database-utils.js
generated
328
lib/overlay-database-utils.js
generated
@@ -1,328 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.CODEQL_OVERLAY_MINIMUM_VERSION = exports.OverlayDatabaseMode = void 0;
|
||||
exports.writeBaseDatabaseOidsFile = writeBaseDatabaseOidsFile;
|
||||
exports.writeOverlayChangesFile = writeOverlayChangesFile;
|
||||
exports.checkOverlayBaseDatabase = checkOverlayBaseDatabase;
|
||||
exports.uploadOverlayBaseDatabaseToCache = uploadOverlayBaseDatabaseToCache;
|
||||
exports.downloadOverlayBaseDatabaseFromCache = downloadOverlayBaseDatabaseFromCache;
|
||||
const fs = __importStar(require("fs"));
|
||||
const path = __importStar(require("path"));
|
||||
const actionsCache = __importStar(require("@actions/cache"));
|
||||
const actions_util_1 = require("./actions-util");
|
||||
const git_utils_1 = require("./git-utils");
|
||||
const logging_1 = require("./logging");
|
||||
const util_1 = require("./util");
|
||||
var OverlayDatabaseMode;
|
||||
(function (OverlayDatabaseMode) {
|
||||
OverlayDatabaseMode["Overlay"] = "overlay";
|
||||
OverlayDatabaseMode["OverlayBase"] = "overlay-base";
|
||||
OverlayDatabaseMode["None"] = "none";
|
||||
})(OverlayDatabaseMode || (exports.OverlayDatabaseMode = OverlayDatabaseMode = {}));
|
||||
exports.CODEQL_OVERLAY_MINIMUM_VERSION = "2.22.3";
|
||||
/**
|
||||
* The maximum (uncompressed) size of the overlay base database that we will
|
||||
* upload. Actions Cache has an overall capacity of 10 GB, and the Actions Cache
|
||||
* client library uses zstd compression.
|
||||
*
|
||||
* Ideally we would apply a size limit to the compressed overlay-base database,
|
||||
* but we cannot do so because compression is handled transparently by the
|
||||
* Actions Cache client library. Instead we place a limit on the uncompressed
|
||||
* size of the overlay-base database.
|
||||
*
|
||||
* Assuming 2.5:1 compression ratio, the 6 GB limit on uncompressed data would
|
||||
* translate to a limit of around 2.4 GB after compression.
|
||||
*/
|
||||
const OVERLAY_BASE_DATABASE_MAX_UPLOAD_SIZE_MB = 6000;
|
||||
const OVERLAY_BASE_DATABASE_MAX_UPLOAD_SIZE_BYTES = OVERLAY_BASE_DATABASE_MAX_UPLOAD_SIZE_MB * 1_000_000;
|
||||
/**
|
||||
* Writes a JSON file containing Git OIDs for all tracked files (represented
|
||||
* by path relative to the source root) under the source root. The file is
|
||||
* written into the database location specified in the config.
|
||||
*
|
||||
* @param config The configuration object containing the database location
|
||||
* @param sourceRoot The root directory containing the source files to process
|
||||
* @throws {Error} If the Git repository root cannot be determined
|
||||
*/
|
||||
async function writeBaseDatabaseOidsFile(config, sourceRoot) {
|
||||
const gitFileOids = await (0, git_utils_1.getFileOidsUnderPath)(sourceRoot);
|
||||
const gitFileOidsJson = JSON.stringify(gitFileOids);
|
||||
const baseDatabaseOidsFilePath = getBaseDatabaseOidsFilePath(config);
|
||||
await fs.promises.writeFile(baseDatabaseOidsFilePath, gitFileOidsJson);
|
||||
}
|
||||
/**
|
||||
* Reads and parses the JSON file containing the base database Git OIDs.
|
||||
* This file contains the mapping of file paths to their corresponding Git OIDs
|
||||
* that was previously written by writeBaseDatabaseOidsFile().
|
||||
*
|
||||
* @param config The configuration object containing the database location
|
||||
* @param logger The logger instance to use for error reporting
|
||||
* @returns An object mapping file paths (relative to source root) to their Git OIDs
|
||||
* @throws {Error} If the file cannot be read or parsed
|
||||
*/
|
||||
async function readBaseDatabaseOidsFile(config, logger) {
|
||||
const baseDatabaseOidsFilePath = getBaseDatabaseOidsFilePath(config);
|
||||
try {
|
||||
const contents = await fs.promises.readFile(baseDatabaseOidsFilePath, "utf-8");
|
||||
return JSON.parse(contents);
|
||||
}
|
||||
catch (e) {
|
||||
logger.error("Failed to read overlay-base file OIDs from " +
|
||||
`${baseDatabaseOidsFilePath}: ${e.message || e}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
function getBaseDatabaseOidsFilePath(config) {
|
||||
return path.join(config.dbLocation, "base-database-oids.json");
|
||||
}
|
||||
/**
|
||||
* Writes a JSON file containing the source-root-relative paths of files under
|
||||
* `sourceRoot` that have changed (added, removed, or modified) from the overlay
|
||||
* base database.
|
||||
*
|
||||
* This function uses the Git index to determine which files have changed, so it
|
||||
* requires the following preconditions, both when this function is called and
|
||||
* when the overlay-base database was initialized:
|
||||
*
|
||||
* - It requires that `sourceRoot` is inside a Git repository.
|
||||
* - It assumes that all changes in the working tree are staged in the index.
|
||||
* - It assumes that all files of interest are tracked by Git, e.g. not covered
|
||||
* by `.gitignore`.
|
||||
*/
|
||||
async function writeOverlayChangesFile(config, sourceRoot, logger) {
|
||||
const baseFileOids = await readBaseDatabaseOidsFile(config, logger);
|
||||
const overlayFileOids = await (0, git_utils_1.getFileOidsUnderPath)(sourceRoot);
|
||||
const changedFiles = computeChangedFiles(baseFileOids, overlayFileOids);
|
||||
logger.info(`Found ${changedFiles.length} changed file(s) under ${sourceRoot}.`);
|
||||
const changedFilesJson = JSON.stringify({ changes: changedFiles });
|
||||
const overlayChangesFile = path.join((0, actions_util_1.getTemporaryDirectory)(), "overlay-changes.json");
|
||||
logger.debug(`Writing overlay changed files to ${overlayChangesFile}: ${changedFilesJson}`);
|
||||
await fs.promises.writeFile(overlayChangesFile, changedFilesJson);
|
||||
return overlayChangesFile;
|
||||
}
|
||||
function computeChangedFiles(baseFileOids, overlayFileOids) {
|
||||
const changes = [];
|
||||
for (const [file, oid] of Object.entries(overlayFileOids)) {
|
||||
if (!(file in baseFileOids) || baseFileOids[file] !== oid) {
|
||||
changes.push(file);
|
||||
}
|
||||
}
|
||||
for (const file of Object.keys(baseFileOids)) {
|
||||
if (!(file in overlayFileOids)) {
|
||||
changes.push(file);
|
||||
}
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
// Constants for database caching
|
||||
const CACHE_VERSION = 1;
|
||||
const CACHE_PREFIX = "codeql-overlay-base-database";
|
||||
const MAX_CACHE_OPERATION_MS = 120_000; // Two minutes
|
||||
/**
|
||||
* Checks that the overlay-base database is valid by checking for the
|
||||
* existence of the base database OIDs file.
|
||||
*
|
||||
* @param config The configuration object
|
||||
* @param logger The logger instance
|
||||
* @param warningPrefix Prefix for the check failure warning message
|
||||
* @returns True if the verification succeeded, false otherwise
|
||||
*/
|
||||
function checkOverlayBaseDatabase(config, logger, warningPrefix) {
|
||||
// An overlay-base database should contain the base database OIDs file.
|
||||
const baseDatabaseOidsFilePath = getBaseDatabaseOidsFilePath(config);
|
||||
if (!fs.existsSync(baseDatabaseOidsFilePath)) {
|
||||
logger.warning(`${warningPrefix}: ${baseDatabaseOidsFilePath} does not exist`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Uploads the overlay-base database to the GitHub Actions cache. If conditions
|
||||
* for uploading are not met, the function does nothing and returns false.
|
||||
*
|
||||
* This function uses the `checkout_path` input to determine the repository path
|
||||
* and works only when called from `analyze` or `upload-sarif`.
|
||||
*
|
||||
* @param codeql The CodeQL instance
|
||||
* @param config The configuration object
|
||||
* @param logger The logger instance
|
||||
* @returns A promise that resolves to true if the upload was performed and
|
||||
* successfully completed, or false otherwise
|
||||
*/
|
||||
async function uploadOverlayBaseDatabaseToCache(codeql, config, logger) {
|
||||
const overlayDatabaseMode = config.augmentationProperties.overlayDatabaseMode;
|
||||
if (overlayDatabaseMode !== OverlayDatabaseMode.OverlayBase) {
|
||||
logger.debug(`Overlay database mode is ${overlayDatabaseMode}. ` +
|
||||
"Skip uploading overlay-base database to cache.");
|
||||
return false;
|
||||
}
|
||||
if (!config.augmentationProperties.useOverlayDatabaseCaching) {
|
||||
logger.debug("Overlay database caching is disabled. " +
|
||||
"Skip uploading overlay-base database to cache.");
|
||||
return false;
|
||||
}
|
||||
if ((0, util_1.isInTestMode)()) {
|
||||
logger.debug("In test mode. Skip uploading overlay-base database to cache.");
|
||||
return false;
|
||||
}
|
||||
const databaseIsValid = checkOverlayBaseDatabase(config, logger, "Abort uploading overlay-base database to cache");
|
||||
if (!databaseIsValid) {
|
||||
return false;
|
||||
}
|
||||
// Clean up the database using the overlay cleanup level.
|
||||
await (0, logging_1.withGroupAsync)("Cleaning up databases", async () => {
|
||||
await codeql.databaseCleanupCluster(config, "overlay");
|
||||
});
|
||||
const dbLocation = config.dbLocation;
|
||||
const databaseSizeBytes = await (0, util_1.tryGetFolderBytes)(dbLocation, logger);
|
||||
if (databaseSizeBytes === undefined) {
|
||||
logger.warning("Failed to determine database size. " +
|
||||
"Skip uploading overlay-base database to cache.");
|
||||
return false;
|
||||
}
|
||||
if (databaseSizeBytes > OVERLAY_BASE_DATABASE_MAX_UPLOAD_SIZE_BYTES) {
|
||||
const databaseSizeMB = Math.round(databaseSizeBytes / 1_000_000);
|
||||
logger.warning(`Database size (${databaseSizeMB} MB) ` +
|
||||
`exceeds maximum upload size (${OVERLAY_BASE_DATABASE_MAX_UPLOAD_SIZE_MB} MB). ` +
|
||||
"Skip uploading overlay-base database to cache.");
|
||||
return false;
|
||||
}
|
||||
const codeQlVersion = (await codeql.getVersion()).version;
|
||||
const checkoutPath = (0, actions_util_1.getRequiredInput)("checkout_path");
|
||||
const cacheKey = await generateCacheKey(config, codeQlVersion, checkoutPath);
|
||||
logger.info(`Uploading overlay-base database to Actions cache with key ${cacheKey}`);
|
||||
try {
|
||||
const cacheId = await (0, util_1.withTimeout)(MAX_CACHE_OPERATION_MS, actionsCache.saveCache([dbLocation], cacheKey), () => { });
|
||||
if (cacheId === undefined) {
|
||||
logger.warning("Timed out while uploading overlay-base database");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
logger.warning("Failed to upload overlay-base database to cache: " +
|
||||
`${error instanceof Error ? error.message : String(error)}`);
|
||||
return false;
|
||||
}
|
||||
logger.info(`Successfully uploaded overlay-base database from ${dbLocation}`);
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Downloads the overlay-base database from the GitHub Actions cache. If conditions
|
||||
* for downloading are not met, the function does nothing and returns false.
|
||||
*
|
||||
* @param codeql The CodeQL instance
|
||||
* @param config The configuration object
|
||||
* @param logger The logger instance
|
||||
* @returns A promise that resolves to download statistics if an overlay-base
|
||||
* database was successfully downloaded, or undefined if the download was
|
||||
* either not performed or failed.
|
||||
*/
|
||||
async function downloadOverlayBaseDatabaseFromCache(codeql, config, logger) {
|
||||
const overlayDatabaseMode = config.augmentationProperties.overlayDatabaseMode;
|
||||
if (overlayDatabaseMode !== OverlayDatabaseMode.Overlay) {
|
||||
logger.debug(`Overlay database mode is ${overlayDatabaseMode}. ` +
|
||||
"Skip downloading overlay-base database from cache.");
|
||||
return undefined;
|
||||
}
|
||||
if (!config.augmentationProperties.useOverlayDatabaseCaching) {
|
||||
logger.debug("Overlay database caching is disabled. " +
|
||||
"Skip downloading overlay-base database from cache.");
|
||||
return undefined;
|
||||
}
|
||||
if ((0, util_1.isInTestMode)()) {
|
||||
logger.debug("In test mode. Skip downloading overlay-base database from cache.");
|
||||
return undefined;
|
||||
}
|
||||
const dbLocation = config.dbLocation;
|
||||
const codeQlVersion = (await codeql.getVersion()).version;
|
||||
const restoreKey = getCacheRestoreKey(config, codeQlVersion);
|
||||
logger.info(`Looking in Actions cache for overlay-base database with restore key ${restoreKey}`);
|
||||
let databaseDownloadDurationMs = 0;
|
||||
try {
|
||||
const databaseDownloadStart = performance.now();
|
||||
const foundKey = await (0, util_1.withTimeout)(MAX_CACHE_OPERATION_MS, actionsCache.restoreCache([dbLocation], restoreKey), () => {
|
||||
logger.info("Timed out downloading overlay-base database from cache");
|
||||
});
|
||||
databaseDownloadDurationMs = Math.round(performance.now() - databaseDownloadStart);
|
||||
if (foundKey === undefined) {
|
||||
logger.info("No overlay-base database found in Actions cache");
|
||||
return undefined;
|
||||
}
|
||||
logger.info(`Downloaded overlay-base database in cache with key ${foundKey}`);
|
||||
}
|
||||
catch (error) {
|
||||
logger.warning("Failed to download overlay-base database from cache: " +
|
||||
`${error instanceof Error ? error.message : String(error)}`);
|
||||
return undefined;
|
||||
}
|
||||
const databaseIsValid = checkOverlayBaseDatabase(config, logger, "Downloaded overlay-base database is invalid");
|
||||
if (!databaseIsValid) {
|
||||
logger.warning("Downloaded overlay-base database failed validation");
|
||||
return undefined;
|
||||
}
|
||||
const databaseSizeBytes = await (0, util_1.tryGetFolderBytes)(dbLocation, logger);
|
||||
if (databaseSizeBytes === undefined) {
|
||||
logger.info("Filesystem error while accessing downloaded overlay-base database");
|
||||
// The problem that warrants reporting download failure is not that we are
|
||||
// unable to determine the size of the database. Rather, it is that we
|
||||
// encountered a filesystem error while accessing the database, which
|
||||
// indicates that an overlay analysis will likely fail.
|
||||
return undefined;
|
||||
}
|
||||
logger.info(`Successfully downloaded overlay-base database to ${dbLocation}`);
|
||||
return {
|
||||
databaseSizeBytes: Math.round(databaseSizeBytes),
|
||||
databaseDownloadDurationMs,
|
||||
};
|
||||
}
|
||||
async function generateCacheKey(config, codeQlVersion, checkoutPath) {
|
||||
const sha = await (0, git_utils_1.getCommitOid)(checkoutPath);
|
||||
return `${getCacheRestoreKey(config, codeQlVersion)}${sha}`;
|
||||
}
|
||||
function getCacheRestoreKey(config, codeQlVersion) {
|
||||
// The restore key (prefix) specifies which cached overlay-base databases are
|
||||
// compatible with the current analysis: the cached database must have the
|
||||
// same cache version and the same CodeQL bundle version.
|
||||
//
|
||||
// Actions cache supports using multiple restore keys to indicate preference.
|
||||
// Technically we prefer a cached overlay-base database with the same SHA as
|
||||
// we are analyzing. However, since overlay-base databases are built from the
|
||||
// default branch and used in PR analysis, it is exceedingly unlikely that
|
||||
// the commit SHA will ever be the same, so we can just leave it out.
|
||||
const languages = [...config.languages].sort().join("_");
|
||||
return `${CACHE_PREFIX}-${CACHE_VERSION}-${languages}-${codeQlVersion}-`;
|
||||
}
|
||||
//# sourceMappingURL=overlay-database-utils.js.map
|
||||
File diff suppressed because one or more lines are too long
185
lib/overlay-database-utils.test.js
generated
185
lib/overlay-database-utils.test.js
generated
@@ -1,185 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const fs = __importStar(require("fs"));
|
||||
const path = __importStar(require("path"));
|
||||
const actionsCache = __importStar(require("@actions/cache"));
|
||||
const ava_1 = __importDefault(require("ava"));
|
||||
const sinon = __importStar(require("sinon"));
|
||||
const actionsUtil = __importStar(require("./actions-util"));
|
||||
const gitUtils = __importStar(require("./git-utils"));
|
||||
const logging_1 = require("./logging");
|
||||
const overlay_database_utils_1 = require("./overlay-database-utils");
|
||||
const testing_utils_1 = require("./testing-utils");
|
||||
const utils = __importStar(require("./util"));
|
||||
const util_1 = require("./util");
|
||||
(0, testing_utils_1.setupTests)(ava_1.default);
|
||||
(0, ava_1.default)("writeOverlayChangesFile generates correct changes file", async (t) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const dbLocation = path.join(tmpDir, "db");
|
||||
await fs.promises.mkdir(dbLocation, { recursive: true });
|
||||
const sourceRoot = path.join(tmpDir, "src");
|
||||
await fs.promises.mkdir(sourceRoot, { recursive: true });
|
||||
const tempDir = path.join(tmpDir, "temp");
|
||||
await fs.promises.mkdir(tempDir, { recursive: true });
|
||||
const logger = (0, logging_1.getRunnerLogger)(true);
|
||||
const config = (0, testing_utils_1.createTestConfig)({ dbLocation });
|
||||
// Mock the getFileOidsUnderPath function to return base OIDs
|
||||
const baseOids = {
|
||||
"unchanged.js": "aaa111",
|
||||
"modified.js": "bbb222",
|
||||
"deleted.js": "ccc333",
|
||||
};
|
||||
const getFileOidsStubForBase = sinon
|
||||
.stub(gitUtils, "getFileOidsUnderPath")
|
||||
.resolves(baseOids);
|
||||
// Write the base database OIDs file
|
||||
await (0, overlay_database_utils_1.writeBaseDatabaseOidsFile)(config, sourceRoot);
|
||||
getFileOidsStubForBase.restore();
|
||||
// Mock the getFileOidsUnderPath function to return overlay OIDs
|
||||
const currentOids = {
|
||||
"unchanged.js": "aaa111",
|
||||
"modified.js": "ddd444", // Changed OID
|
||||
"added.js": "eee555", // New file
|
||||
};
|
||||
const getFileOidsStubForOverlay = sinon
|
||||
.stub(gitUtils, "getFileOidsUnderPath")
|
||||
.resolves(currentOids);
|
||||
// Write the overlay changes file, which uses the mocked overlay OIDs
|
||||
// and the base database OIDs file
|
||||
const getTempDirStub = sinon
|
||||
.stub(actionsUtil, "getTemporaryDirectory")
|
||||
.returns(tempDir);
|
||||
const changesFilePath = await (0, overlay_database_utils_1.writeOverlayChangesFile)(config, sourceRoot, logger);
|
||||
getFileOidsStubForOverlay.restore();
|
||||
getTempDirStub.restore();
|
||||
const fileContent = await fs.promises.readFile(changesFilePath, "utf-8");
|
||||
const parsedContent = JSON.parse(fileContent);
|
||||
t.deepEqual(parsedContent.changes.sort(), ["added.js", "deleted.js", "modified.js"], "Should identify added, deleted, and modified files");
|
||||
});
|
||||
});
|
||||
const defaultDownloadTestCase = {
|
||||
overlayDatabaseMode: overlay_database_utils_1.OverlayDatabaseMode.Overlay,
|
||||
useOverlayDatabaseCaching: true,
|
||||
isInTestMode: false,
|
||||
restoreCacheResult: "cache-key",
|
||||
hasBaseDatabaseOidsFile: true,
|
||||
tryGetFolderBytesSucceeds: true,
|
||||
codeQLVersion: "2.20.5",
|
||||
};
|
||||
const testDownloadOverlayBaseDatabaseFromCache = ava_1.default.macro({
|
||||
exec: async (t, _title, partialTestCase, expectDownloadSuccess) => {
|
||||
await (0, util_1.withTmpDir)(async (tmpDir) => {
|
||||
const dbLocation = path.join(tmpDir, "db");
|
||||
await fs.promises.mkdir(dbLocation, { recursive: true });
|
||||
const logger = (0, logging_1.getRunnerLogger)(true);
|
||||
const config = (0, testing_utils_1.createTestConfig)({ dbLocation });
|
||||
const testCase = { ...defaultDownloadTestCase, ...partialTestCase };
|
||||
config.augmentationProperties.overlayDatabaseMode =
|
||||
testCase.overlayDatabaseMode;
|
||||
config.augmentationProperties.useOverlayDatabaseCaching =
|
||||
testCase.useOverlayDatabaseCaching;
|
||||
if (testCase.hasBaseDatabaseOidsFile) {
|
||||
const baseDatabaseOidsFile = path.join(dbLocation, "base-database-oids.json");
|
||||
await fs.promises.writeFile(baseDatabaseOidsFile, JSON.stringify({}));
|
||||
}
|
||||
const stubs = [];
|
||||
const isInTestModeStub = sinon
|
||||
.stub(utils, "isInTestMode")
|
||||
.returns(testCase.isInTestMode);
|
||||
stubs.push(isInTestModeStub);
|
||||
if (testCase.restoreCacheResult instanceof Error) {
|
||||
const restoreCacheStub = sinon
|
||||
.stub(actionsCache, "restoreCache")
|
||||
.rejects(testCase.restoreCacheResult);
|
||||
stubs.push(restoreCacheStub);
|
||||
}
|
||||
else {
|
||||
const restoreCacheStub = sinon
|
||||
.stub(actionsCache, "restoreCache")
|
||||
.resolves(testCase.restoreCacheResult);
|
||||
stubs.push(restoreCacheStub);
|
||||
}
|
||||
const tryGetFolderBytesStub = sinon
|
||||
.stub(utils, "tryGetFolderBytes")
|
||||
.resolves(testCase.tryGetFolderBytesSucceeds ? 1024 * 1024 : undefined);
|
||||
stubs.push(tryGetFolderBytesStub);
|
||||
try {
|
||||
const result = await (0, overlay_database_utils_1.downloadOverlayBaseDatabaseFromCache)((0, testing_utils_1.mockCodeQLVersion)(testCase.codeQLVersion), config, logger);
|
||||
if (expectDownloadSuccess) {
|
||||
t.truthy(result);
|
||||
}
|
||||
else {
|
||||
t.is(result, undefined);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
for (const stub of stubs) {
|
||||
stub.restore();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
title: (_, title) => `downloadOverlayBaseDatabaseFromCache: ${title}`,
|
||||
});
|
||||
(0, ava_1.default)(testDownloadOverlayBaseDatabaseFromCache, "returns stats when successful", {}, true);
|
||||
(0, ava_1.default)(testDownloadOverlayBaseDatabaseFromCache, "returns undefined when mode is OverlayDatabaseMode.OverlayBase", {
|
||||
overlayDatabaseMode: overlay_database_utils_1.OverlayDatabaseMode.OverlayBase,
|
||||
}, false);
|
||||
(0, ava_1.default)(testDownloadOverlayBaseDatabaseFromCache, "returns undefined when mode is OverlayDatabaseMode.None", {
|
||||
overlayDatabaseMode: overlay_database_utils_1.OverlayDatabaseMode.None,
|
||||
}, false);
|
||||
(0, ava_1.default)(testDownloadOverlayBaseDatabaseFromCache, "returns undefined when caching is disabled", {
|
||||
useOverlayDatabaseCaching: false,
|
||||
}, false);
|
||||
(0, ava_1.default)(testDownloadOverlayBaseDatabaseFromCache, "returns undefined in test mode", {
|
||||
isInTestMode: true,
|
||||
}, false);
|
||||
(0, ava_1.default)(testDownloadOverlayBaseDatabaseFromCache, "returns undefined when cache miss", {
|
||||
restoreCacheResult: undefined,
|
||||
}, false);
|
||||
(0, ava_1.default)(testDownloadOverlayBaseDatabaseFromCache, "returns undefined when download fails", {
|
||||
restoreCacheResult: new Error("Download failed"),
|
||||
}, false);
|
||||
(0, ava_1.default)(testDownloadOverlayBaseDatabaseFromCache, "returns undefined when downloaded database is invalid", {
|
||||
hasBaseDatabaseOidsFile: false,
|
||||
}, false);
|
||||
(0, ava_1.default)(testDownloadOverlayBaseDatabaseFromCache, "returns undefined when filesystem error occurs", {
|
||||
tryGetFolderBytesSucceeds: false,
|
||||
}, false);
|
||||
//# sourceMappingURL=overlay-database-utils.test.js.map
|
||||
File diff suppressed because one or more lines are too long
41
lib/repository.js
generated
41
lib/repository.js
generated
@@ -1,41 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getRepositoryNwo = getRepositoryNwo;
|
||||
exports.getRepositoryNwoFromEnv = getRepositoryNwoFromEnv;
|
||||
exports.parseRepositoryNwo = parseRepositoryNwo;
|
||||
const util_1 = require("./util");
|
||||
/**
|
||||
* Get the repository name with owner from the environment variable
|
||||
* `GITHUB_REPOSITORY`.
|
||||
*
|
||||
* @returns The repository name with owner.
|
||||
*/
|
||||
function getRepositoryNwo() {
|
||||
return getRepositoryNwoFromEnv("GITHUB_REPOSITORY");
|
||||
}
|
||||
/**
|
||||
* Get the repository name with owner from the first environment variable that
|
||||
* is set and non-empty.
|
||||
*
|
||||
* @param envVarNames The names of the environment variables to check.
|
||||
* @returns The repository name with owner.
|
||||
* @throws ConfigurationError if none of the environment variables are set.
|
||||
*/
|
||||
function getRepositoryNwoFromEnv(...envVarNames) {
|
||||
const envVarName = envVarNames.find((name) => process.env[name]);
|
||||
if (!envVarName) {
|
||||
throw new util_1.ConfigurationError(`None of the env vars ${envVarNames.join(", ")} are set`);
|
||||
}
|
||||
return parseRepositoryNwo((0, util_1.getRequiredEnvParam)(envVarName));
|
||||
}
|
||||
function parseRepositoryNwo(input) {
|
||||
const parts = input.split("/");
|
||||
if (parts.length !== 2) {
|
||||
throw new util_1.ConfigurationError(`"${input}" is not a valid repository name`);
|
||||
}
|
||||
return {
|
||||
owner: parts[0],
|
||||
repo: parts[1],
|
||||
};
|
||||
}
|
||||
//# sourceMappingURL=repository.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"repository.js","sourceRoot":"","sources":["../src/repository.ts"],"names":[],"mappings":";;AAcA,4CAEC;AAUD,0DAUC;AAED,gDASC;AA/CD,iCAAiE;AAQjE;;;;;GAKG;AACH,SAAgB,gBAAgB;IAC9B,OAAO,uBAAuB,CAAC,mBAAmB,CAAC,CAAC;AACtD,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,uBAAuB,CACrC,GAAG,WAAqB;IAExB,MAAM,UAAU,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IACjE,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,yBAAkB,CAC1B,wBAAwB,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CACzD,CAAC;IACJ,CAAC;IACD,OAAO,kBAAkB,CAAC,IAAA,0BAAmB,EAAC,UAAU,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED,SAAgB,kBAAkB,CAAC,KAAa;IAC9C,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,yBAAkB,CAAC,IAAI,KAAK,kCAAkC,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;QACf,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;KACf,CAAC;AACJ,CAAC"}
|
||||
88711
lib/resolve-environment-action.js
generated
88711
lib/resolve-environment-action.js
generated
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"resolve-environment-action.js","sourceRoot":"","sources":["../src/resolve-environment-action.ts"],"names":[],"mappings":"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"}
|
||||
15
lib/resolve-environment.js
generated
15
lib/resolve-environment.js
generated
@@ -1,15 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.runResolveBuildEnvironment = runResolveBuildEnvironment;
|
||||
const codeql_1 = require("./codeql");
|
||||
async function runResolveBuildEnvironment(cmd, logger, workingDir, language) {
|
||||
logger.startGroup(`Attempting to resolve build environment for ${language}`);
|
||||
const codeql = await (0, codeql_1.getCodeQL)(cmd);
|
||||
if (workingDir !== undefined) {
|
||||
logger.info(`Using ${workingDir} as the working directory.`);
|
||||
}
|
||||
const result = await codeql.resolveBuildEnvironment(workingDir, language);
|
||||
logger.endGroup();
|
||||
return result;
|
||||
}
|
||||
//# sourceMappingURL=resolve-environment.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"resolve-environment.js","sourceRoot":"","sources":["../src/resolve-environment.ts"],"names":[],"mappings":";;AAGA,gEAkBC;AArBD,qCAAqC;AAG9B,KAAK,UAAU,0BAA0B,CAC9C,GAAW,EACX,MAAc,EACd,UAA8B,EAC9B,QAAgB;IAEhB,MAAM,CAAC,UAAU,CAAC,+CAA+C,QAAQ,EAAE,CAAC,CAAC;IAE7E,MAAM,MAAM,GAAG,MAAM,IAAA,kBAAS,EAAC,GAAG,CAAC,CAAC;IAEpC,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,CAAC,IAAI,CAAC,SAAS,UAAU,4BAA4B,CAAC,CAAC;IAC/D,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,uBAAuB,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAE1E,MAAM,CAAC,QAAQ,EAAE,CAAC;IAClB,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
||||
554
lib/setup-codeql.js
generated
554
lib/setup-codeql.js
generated
@@ -1,554 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.downloadCodeQL = exports.CODEQL_DEFAULT_ACTION_REPOSITORY = exports.ToolsSource = void 0;
|
||||
exports.getCodeQLActionRepository = getCodeQLActionRepository;
|
||||
exports.tryGetTagNameFromUrl = tryGetTagNameFromUrl;
|
||||
exports.tryGetBundleVersionFromUrl = tryGetBundleVersionFromUrl;
|
||||
exports.convertToSemVer = convertToSemVer;
|
||||
exports.getCodeQLSource = getCodeQLSource;
|
||||
exports.tryGetFallbackToolcacheVersion = tryGetFallbackToolcacheVersion;
|
||||
exports.getCodeQLURLVersion = getCodeQLURLVersion;
|
||||
exports.setupCodeQLBundle = setupCodeQLBundle;
|
||||
const fs = __importStar(require("fs"));
|
||||
const path = __importStar(require("path"));
|
||||
const toolcache = __importStar(require("@actions/tool-cache"));
|
||||
const fast_deep_equal_1 = __importDefault(require("fast-deep-equal"));
|
||||
const semver = __importStar(require("semver"));
|
||||
const uuid_1 = require("uuid");
|
||||
const actions_util_1 = require("./actions-util");
|
||||
const api = __importStar(require("./api-client"));
|
||||
const defaults = __importStar(require("./defaults.json"));
|
||||
const feature_flags_1 = require("./feature-flags");
|
||||
const tar = __importStar(require("./tar"));
|
||||
const tools_download_1 = require("./tools-download");
|
||||
const util = __importStar(require("./util"));
|
||||
const util_1 = require("./util");
|
||||
var ToolsSource;
|
||||
(function (ToolsSource) {
|
||||
ToolsSource["Unknown"] = "UNKNOWN";
|
||||
ToolsSource["Local"] = "LOCAL";
|
||||
ToolsSource["Toolcache"] = "TOOLCACHE";
|
||||
ToolsSource["Download"] = "DOWNLOAD";
|
||||
})(ToolsSource || (exports.ToolsSource = ToolsSource = {}));
|
||||
exports.CODEQL_DEFAULT_ACTION_REPOSITORY = "github/codeql-action";
|
||||
const CODEQL_BUNDLE_VERSION_ALIAS = ["linked", "latest"];
|
||||
function getCodeQLBundleExtension(compressionMethod) {
|
||||
switch (compressionMethod) {
|
||||
case "gzip":
|
||||
return ".tar.gz";
|
||||
case "zstd":
|
||||
return ".tar.zst";
|
||||
default:
|
||||
util.assertNever(compressionMethod);
|
||||
}
|
||||
}
|
||||
function getCodeQLBundleName(compressionMethod) {
|
||||
const extension = getCodeQLBundleExtension(compressionMethod);
|
||||
let platform;
|
||||
if (process.platform === "win32") {
|
||||
platform = "win64";
|
||||
}
|
||||
else if (process.platform === "linux") {
|
||||
platform = "linux64";
|
||||
}
|
||||
else if (process.platform === "darwin") {
|
||||
platform = "osx64";
|
||||
}
|
||||
else {
|
||||
return `codeql-bundle${extension}`;
|
||||
}
|
||||
return `codeql-bundle-${platform}${extension}`;
|
||||
}
|
||||
function getCodeQLActionRepository(logger) {
|
||||
if ((0, actions_util_1.isRunningLocalAction)()) {
|
||||
// This handles the case where the Action does not come from an Action repository,
|
||||
// e.g. our integration tests which use the Action code from the current checkout.
|
||||
// In these cases, the GITHUB_ACTION_REPOSITORY environment variable is not set.
|
||||
logger.info("The CodeQL Action is checked out locally. Using the default CodeQL Action repository.");
|
||||
return exports.CODEQL_DEFAULT_ACTION_REPOSITORY;
|
||||
}
|
||||
return util.getRequiredEnvParam("GITHUB_ACTION_REPOSITORY");
|
||||
}
|
||||
async function getCodeQLBundleDownloadURL(tagName, apiDetails, compressionMethod, logger) {
|
||||
const codeQLActionRepository = getCodeQLActionRepository(logger);
|
||||
const potentialDownloadSources = [
|
||||
// This GitHub instance, and this Action.
|
||||
[apiDetails.url, codeQLActionRepository],
|
||||
// This GitHub instance, and the canonical Action.
|
||||
[apiDetails.url, exports.CODEQL_DEFAULT_ACTION_REPOSITORY],
|
||||
// GitHub.com, and the canonical Action.
|
||||
[util.GITHUB_DOTCOM_URL, exports.CODEQL_DEFAULT_ACTION_REPOSITORY],
|
||||
];
|
||||
// We now filter out any duplicates.
|
||||
// Duplicates will happen either because the GitHub instance is GitHub.com, or because the Action is not a fork.
|
||||
const uniqueDownloadSources = potentialDownloadSources.filter((source, index, self) => {
|
||||
return !self.slice(0, index).some((other) => (0, fast_deep_equal_1.default)(source, other));
|
||||
});
|
||||
const codeQLBundleName = getCodeQLBundleName(compressionMethod);
|
||||
for (const downloadSource of uniqueDownloadSources) {
|
||||
const [apiURL, repository] = downloadSource;
|
||||
// If we've reached the final case, short-circuit the API check since we know the bundle exists and is public.
|
||||
if (apiURL === util.GITHUB_DOTCOM_URL &&
|
||||
repository === exports.CODEQL_DEFAULT_ACTION_REPOSITORY) {
|
||||
break;
|
||||
}
|
||||
const [repositoryOwner, repositoryName] = repository.split("/");
|
||||
try {
|
||||
const release = await api.getApiClient().rest.repos.getReleaseByTag({
|
||||
owner: repositoryOwner,
|
||||
repo: repositoryName,
|
||||
tag: tagName,
|
||||
});
|
||||
for (const asset of release.data.assets) {
|
||||
if (asset.name === codeQLBundleName) {
|
||||
logger.info(`Found CodeQL bundle ${codeQLBundleName} in ${repository} on ${apiURL} with URL ${asset.url}.`);
|
||||
return asset.url;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
logger.info(`Looked for CodeQL bundle ${codeQLBundleName} in ${repository} on ${apiURL} but got error ${e}.`);
|
||||
}
|
||||
}
|
||||
return `https://github.com/${exports.CODEQL_DEFAULT_ACTION_REPOSITORY}/releases/download/${tagName}/${codeQLBundleName}`;
|
||||
}
|
||||
function tryGetBundleVersionFromTagName(tagName, logger) {
|
||||
const match = tagName.match(/^codeql-bundle-(.*)$/);
|
||||
if (match === null || match.length < 2) {
|
||||
logger.debug(`Could not determine bundle version from tag ${tagName}.`);
|
||||
return undefined;
|
||||
}
|
||||
return match[1];
|
||||
}
|
||||
function tryGetTagNameFromUrl(url, logger) {
|
||||
const matches = [...url.matchAll(/\/(codeql-bundle-[^/]*)\//g)];
|
||||
if (matches.length === 0) {
|
||||
logger.debug(`Could not determine tag name for URL ${url}.`);
|
||||
return undefined;
|
||||
}
|
||||
// Example: https://github.com/org/codeql-bundle-testing/releases/download/codeql-bundle-v2.19.0/codeql-bundle-linux64.tar.zst
|
||||
// We require a trailing forward slash to be part of the match, so the last match gives us the tag
|
||||
// name. An alternative approach would be to also match against `/releases/`, but this approach
|
||||
// assumes less about the structure of the URL.
|
||||
const match = matches[matches.length - 1];
|
||||
if (match === null || match.length !== 2) {
|
||||
logger.debug(`Could not determine tag name for URL ${url}. Matched ${JSON.stringify(match)}.`);
|
||||
return undefined;
|
||||
}
|
||||
return match[1];
|
||||
}
|
||||
function tryGetBundleVersionFromUrl(url, logger) {
|
||||
const tagName = tryGetTagNameFromUrl(url, logger);
|
||||
if (tagName === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return tryGetBundleVersionFromTagName(tagName, logger);
|
||||
}
|
||||
function convertToSemVer(version, logger) {
|
||||
if (!semver.valid(version)) {
|
||||
logger.debug(`Bundle version ${version} is not in SemVer format. Will treat it as pre-release 0.0.0-${version}.`);
|
||||
version = `0.0.0-${version}`;
|
||||
}
|
||||
const s = semver.clean(version);
|
||||
if (!s) {
|
||||
throw new Error(`Bundle version ${version} is not in SemVer format.`);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
/**
|
||||
* Look for a version of the CodeQL tools in the cache which could override the requested CLI version.
|
||||
*/
|
||||
async function findOverridingToolsInCache(humanReadableVersion, logger) {
|
||||
const candidates = toolcache
|
||||
.findAllVersions("CodeQL")
|
||||
.filter(util_1.isGoodVersion)
|
||||
.map((version) => ({
|
||||
folder: toolcache.find("CodeQL", version),
|
||||
version,
|
||||
}))
|
||||
.filter(({ folder }) => fs.existsSync(path.join(folder, "pinned-version")));
|
||||
if (candidates.length === 1) {
|
||||
const candidate = candidates[0];
|
||||
logger.debug(`CodeQL tools version ${candidate.version} in toolcache overriding version ${humanReadableVersion}.`);
|
||||
return {
|
||||
codeqlFolder: candidate.folder,
|
||||
sourceType: "toolcache",
|
||||
toolsVersion: candidate.version,
|
||||
};
|
||||
}
|
||||
else if (candidates.length === 0) {
|
||||
logger.debug("Did not find any candidate pinned versions of the CodeQL tools in the toolcache.");
|
||||
}
|
||||
else {
|
||||
logger.debug("Could not use CodeQL tools from the toolcache since more than one candidate pinned " +
|
||||
"version was found in the toolcache.");
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
async function getCodeQLSource(toolsInput, defaultCliVersion, apiDetails, variant, tarSupportsZstd, logger) {
|
||||
if (toolsInput &&
|
||||
!CODEQL_BUNDLE_VERSION_ALIAS.includes(toolsInput) &&
|
||||
!toolsInput.startsWith("http")) {
|
||||
logger.info(`Using CodeQL CLI from local path ${toolsInput}`);
|
||||
const compressionMethod = tar.inferCompressionMethod(toolsInput);
|
||||
if (compressionMethod === undefined) {
|
||||
throw new util.ConfigurationError(`Could not infer compression method from path ${toolsInput}. Please specify a path ` +
|
||||
"ending in '.tar.gz' or '.tar.zst'.");
|
||||
}
|
||||
return {
|
||||
codeqlTarPath: toolsInput,
|
||||
compressionMethod,
|
||||
sourceType: "local",
|
||||
toolsVersion: "local",
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Whether the tools shipped with the Action, i.e. those in `defaults.json`, have been forced.
|
||||
*
|
||||
* We use the special value of 'linked' to prioritize the version in `defaults.json` over the
|
||||
* version specified by the feature flags on Dotcom and over any pinned cached version on
|
||||
* Enterprise Server.
|
||||
*
|
||||
* Previously we have been using 'latest' to force the shipped tools, but this was not clear
|
||||
* enough for the users, so it has been changed to `linked`. We're keeping around `latest` for
|
||||
* backwards compatibility.
|
||||
*/
|
||||
const forceShippedTools = toolsInput && CODEQL_BUNDLE_VERSION_ALIAS.includes(toolsInput);
|
||||
if (forceShippedTools) {
|
||||
logger.info(`'tools: ${toolsInput}' was requested, so using CodeQL version ${defaultCliVersion.cliVersion}, the version shipped with the Action.`);
|
||||
if (toolsInput === "latest") {
|
||||
logger.warning("`tools: latest` has been renamed to `tools: linked`, but the old name is still supported. No action is required.");
|
||||
}
|
||||
}
|
||||
/** CLI version number, for example 2.12.6. */
|
||||
let cliVersion;
|
||||
/** Tag name of the CodeQL bundle, for example `codeql-bundle-20230120`. */
|
||||
let tagName;
|
||||
/**
|
||||
* URL of the CodeQL bundle.
|
||||
*
|
||||
* This does not always include a tag name.
|
||||
*/
|
||||
let url;
|
||||
if (forceShippedTools) {
|
||||
cliVersion = defaults.cliVersion;
|
||||
tagName = defaults.bundleVersion;
|
||||
}
|
||||
else if (toolsInput !== undefined) {
|
||||
// If a tools URL was provided, then use that.
|
||||
tagName = tryGetTagNameFromUrl(toolsInput, logger);
|
||||
url = toolsInput;
|
||||
if (tagName) {
|
||||
const bundleVersion = tryGetBundleVersionFromTagName(tagName, logger);
|
||||
// If the bundle version is a semantic version, it is a CLI version number.
|
||||
if (bundleVersion && semver.valid(bundleVersion)) {
|
||||
cliVersion = convertToSemVer(bundleVersion, logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Otherwise, use the default CLI version passed in.
|
||||
cliVersion = defaultCliVersion.cliVersion;
|
||||
tagName = defaultCliVersion.tagName;
|
||||
}
|
||||
const bundleVersion = tagName && tryGetBundleVersionFromTagName(tagName, logger);
|
||||
const humanReadableVersion = cliVersion ??
|
||||
(bundleVersion && convertToSemVer(bundleVersion, logger)) ??
|
||||
tagName ??
|
||||
url ??
|
||||
"unknown";
|
||||
logger.debug("Attempting to obtain CodeQL tools. " +
|
||||
`CLI version: ${cliVersion ?? "unknown"}, ` +
|
||||
`bundle tag name: ${tagName ?? "unknown"}, ` +
|
||||
`URL: ${url ?? "unspecified"}.`);
|
||||
let codeqlFolder;
|
||||
if (cliVersion) {
|
||||
// If we find the specified CLI version, we always use that.
|
||||
codeqlFolder = toolcache.find("CodeQL", cliVersion);
|
||||
// Fall back to matching `x.y.z-<tagName>`.
|
||||
if (!codeqlFolder) {
|
||||
logger.debug("Didn't find a version of the CodeQL tools in the toolcache with a version number " +
|
||||
`exactly matching ${cliVersion}.`);
|
||||
const allVersions = toolcache.findAllVersions("CodeQL");
|
||||
logger.debug(`Found the following versions of the CodeQL tools in the toolcache: ${JSON.stringify(allVersions)}.`);
|
||||
// If there is exactly one version of the CodeQL tools in the toolcache, and that version is
|
||||
// the form `x.y.z-<tagName>`, then use it.
|
||||
const candidateVersions = allVersions.filter((version) => version.startsWith(`${cliVersion}-`));
|
||||
if (candidateVersions.length === 1) {
|
||||
logger.debug(`Exactly one version of the CodeQL tools starting with ${cliVersion} found in the ` +
|
||||
"toolcache, using that.");
|
||||
codeqlFolder = toolcache.find("CodeQL", candidateVersions[0]);
|
||||
}
|
||||
else if (candidateVersions.length === 0) {
|
||||
logger.debug(`Didn't find any versions of the CodeQL tools starting with ${cliVersion} ` +
|
||||
`in the toolcache. Trying next fallback method.`);
|
||||
}
|
||||
else {
|
||||
logger.warning(`Found ${candidateVersions.length} versions of the CodeQL tools starting with ` +
|
||||
`${cliVersion} in the toolcache, but at most one was expected.`);
|
||||
logger.debug("Trying next fallback method.");
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fall back to matching `0.0.0-<bundleVersion>`.
|
||||
if (!codeqlFolder && tagName) {
|
||||
const fallbackVersion = await tryGetFallbackToolcacheVersion(cliVersion, tagName, logger);
|
||||
if (fallbackVersion) {
|
||||
codeqlFolder = toolcache.find("CodeQL", fallbackVersion);
|
||||
}
|
||||
else {
|
||||
logger.debug("Could not determine a fallback toolcache version number for CodeQL tools version " +
|
||||
`${humanReadableVersion}.`);
|
||||
}
|
||||
}
|
||||
if (codeqlFolder) {
|
||||
logger.info(`Found CodeQL tools version ${humanReadableVersion} in the toolcache.`);
|
||||
}
|
||||
else {
|
||||
logger.info(`Did not find CodeQL tools version ${humanReadableVersion} in the toolcache.`);
|
||||
}
|
||||
if (codeqlFolder) {
|
||||
if (cliVersion) {
|
||||
logger.info(`Using CodeQL CLI version ${cliVersion} from toolcache at ${codeqlFolder}`);
|
||||
}
|
||||
else {
|
||||
logger.info(`Using CodeQL CLI from toolcache at ${codeqlFolder}`);
|
||||
}
|
||||
return {
|
||||
codeqlFolder,
|
||||
sourceType: "toolcache",
|
||||
toolsVersion: cliVersion ?? humanReadableVersion,
|
||||
};
|
||||
}
|
||||
// If we don't find the requested version on Enterprise, we may allow a
|
||||
// different version to save download time if the version hasn't been
|
||||
// specified explicitly (in which case we always honor it).
|
||||
if (variant !== util.GitHubVariant.DOTCOM &&
|
||||
!forceShippedTools &&
|
||||
!toolsInput) {
|
||||
const result = await findOverridingToolsInCache(humanReadableVersion, logger);
|
||||
if (result !== undefined) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
let compressionMethod;
|
||||
if (!url) {
|
||||
compressionMethod =
|
||||
cliVersion !== undefined &&
|
||||
(await useZstdBundle(cliVersion, tarSupportsZstd))
|
||||
? "zstd"
|
||||
: "gzip";
|
||||
url = await getCodeQLBundleDownloadURL(tagName, apiDetails, compressionMethod, logger);
|
||||
}
|
||||
else {
|
||||
const method = tar.inferCompressionMethod(url);
|
||||
if (method === undefined) {
|
||||
throw new util.ConfigurationError(`Could not infer compression method from URL ${url}. Please specify a URL ` +
|
||||
"ending in '.tar.gz' or '.tar.zst'.");
|
||||
}
|
||||
compressionMethod = method;
|
||||
}
|
||||
if (cliVersion) {
|
||||
logger.info(`Using CodeQL CLI version ${cliVersion} sourced from ${url} .`);
|
||||
}
|
||||
else {
|
||||
logger.info(`Using CodeQL CLI sourced from ${url} .`);
|
||||
}
|
||||
return {
|
||||
bundleVersion: tagName && tryGetBundleVersionFromTagName(tagName, logger),
|
||||
cliVersion,
|
||||
codeqlURL: url,
|
||||
compressionMethod,
|
||||
sourceType: "download",
|
||||
toolsVersion: cliVersion ?? humanReadableVersion,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Gets a fallback version number to use when looking for CodeQL in the toolcache if we didn't find
|
||||
* the `x.y.z` version. This is to support old versions of the toolcache.
|
||||
*/
|
||||
async function tryGetFallbackToolcacheVersion(cliVersion, tagName, logger) {
|
||||
const bundleVersion = tryGetBundleVersionFromTagName(tagName, logger);
|
||||
if (!bundleVersion) {
|
||||
return undefined;
|
||||
}
|
||||
const fallbackVersion = convertToSemVer(bundleVersion, logger);
|
||||
logger.debug(`Computed a fallback toolcache version number of ${fallbackVersion} for CodeQL version ` +
|
||||
`${cliVersion ?? tagName}.`);
|
||||
return fallbackVersion;
|
||||
}
|
||||
// Exported using `export const` for testing purposes. Specifically, we want to
|
||||
// be able to stub this function and have other functions in this file use that stub.
|
||||
const downloadCodeQL = async function (codeqlURL, compressionMethod, maybeBundleVersion, maybeCliVersion, apiDetails, tarVersion, tempDir, logger) {
|
||||
const parsedCodeQLURL = new URL(codeqlURL);
|
||||
const searchParams = new URLSearchParams(parsedCodeQLURL.search);
|
||||
const headers = {
|
||||
accept: "application/octet-stream",
|
||||
};
|
||||
// We only want to provide an authorization header if we are downloading
|
||||
// from the same GitHub instance the Action is running on.
|
||||
// This avoids leaking Enterprise tokens to dotcom.
|
||||
// We also don't want to send an authorization header if there's already a token provided in the URL.
|
||||
let authorization = undefined;
|
||||
if (searchParams.has("token")) {
|
||||
logger.debug("CodeQL tools URL contains an authorization token.");
|
||||
}
|
||||
else if (codeqlURL.startsWith(`${apiDetails.url}/`) ||
|
||||
(apiDetails.apiURL && codeqlURL.startsWith(`${apiDetails.apiURL}/`))) {
|
||||
logger.debug("Providing an authorization token to download CodeQL tools.");
|
||||
authorization = `token ${apiDetails.auth}`;
|
||||
}
|
||||
else {
|
||||
logger.debug("Downloading CodeQL tools without an authorization token.");
|
||||
}
|
||||
const toolcacheInfo = getToolcacheDestinationInfo(maybeBundleVersion, maybeCliVersion, logger);
|
||||
const extractedBundlePath = toolcacheInfo?.path ?? getTempExtractionDir(tempDir);
|
||||
const statusReport = await (0, tools_download_1.downloadAndExtract)(codeqlURL, compressionMethod, extractedBundlePath, authorization, { "User-Agent": "CodeQL Action", ...headers }, tarVersion, logger);
|
||||
if (!toolcacheInfo) {
|
||||
logger.debug("Could not cache CodeQL tools because we could not determine the bundle version from the " +
|
||||
`URL ${codeqlURL}.`);
|
||||
return {
|
||||
codeqlFolder: extractedBundlePath,
|
||||
statusReport,
|
||||
toolsVersion: maybeCliVersion ?? "unknown",
|
||||
};
|
||||
}
|
||||
(0, tools_download_1.writeToolcacheMarkerFile)(toolcacheInfo.path, logger);
|
||||
return {
|
||||
codeqlFolder: extractedBundlePath,
|
||||
statusReport,
|
||||
toolsVersion: maybeCliVersion ?? toolcacheInfo.version,
|
||||
};
|
||||
};
|
||||
exports.downloadCodeQL = downloadCodeQL;
|
||||
function getToolcacheDestinationInfo(maybeBundleVersion, maybeCliVersion, logger) {
|
||||
if (maybeBundleVersion) {
|
||||
const version = getCanonicalToolcacheVersion(maybeCliVersion, maybeBundleVersion, logger);
|
||||
return {
|
||||
path: (0, tools_download_1.getToolcacheDirectory)(version),
|
||||
version,
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
function getCodeQLURLVersion(url) {
|
||||
const match = url.match(/\/codeql-bundle-(.*)\//);
|
||||
if (match === null || match.length < 2) {
|
||||
throw new util.ConfigurationError(`Malformed tools url: ${url}. Version could not be inferred`);
|
||||
}
|
||||
return match[1];
|
||||
}
|
||||
/**
|
||||
* Returns the toolcache version number to use to store the bundle with the associated CLI version
|
||||
* and bundle version.
|
||||
*
|
||||
* This is the canonical version number, since toolcaches populated by different versions of the
|
||||
* CodeQL Action or different runner image creation scripts may store the bundle using a different
|
||||
* version number. Functions like `getCodeQLSource` that fetch the bundle from rather than save the
|
||||
* bundle to the toolcache should handle these different version numbers.
|
||||
*/
|
||||
function getCanonicalToolcacheVersion(cliVersion, bundleVersion, logger) {
|
||||
// If the CLI version is a pre-release or contains build metadata, then cache the
|
||||
// bundle as `0.0.0-<bundleVersion>` to avoid the bundle being interpreted as containing a stable
|
||||
// CLI release. In principle, it should be enough to just check that the CLI version isn't a
|
||||
// pre-release, but the version numbers of CodeQL nightlies have the format `x.y.z+<timestamp>`,
|
||||
// and we don't want these nightlies to override stable CLI versions in the toolcache.
|
||||
if (!cliVersion?.match(/^[0-9]+\.[0-9]+\.[0-9]+$/)) {
|
||||
return convertToSemVer(bundleVersion, logger);
|
||||
}
|
||||
// Bundles are now semantically versioned and can be looked up based on just the CLI version
|
||||
// number, so we can version them in the toolcache using just the CLI version number.
|
||||
return cliVersion;
|
||||
}
|
||||
/**
|
||||
* Obtains the CodeQL bundle, installs it in the toolcache if appropriate, and extracts it.
|
||||
*
|
||||
* @returns the path to the extracted bundle, and the version of the tools
|
||||
*/
|
||||
async function setupCodeQLBundle(toolsInput, apiDetails, tempDir, variant, defaultCliVersion, logger) {
|
||||
if (!(await util.isBinaryAccessible("tar", logger))) {
|
||||
throw new util.ConfigurationError("Could not find tar in PATH, so unable to extract CodeQL bundle.");
|
||||
}
|
||||
const zstdAvailability = await tar.isZstdAvailable(logger);
|
||||
const source = await getCodeQLSource(toolsInput, defaultCliVersion, apiDetails, variant, zstdAvailability.available, logger);
|
||||
let codeqlFolder;
|
||||
let toolsVersion = source.toolsVersion;
|
||||
let toolsDownloadStatusReport;
|
||||
let toolsSource;
|
||||
switch (source.sourceType) {
|
||||
case "local": {
|
||||
codeqlFolder = await tar.extract(source.codeqlTarPath, getTempExtractionDir(tempDir), source.compressionMethod, zstdAvailability.version, logger);
|
||||
toolsSource = ToolsSource.Local;
|
||||
break;
|
||||
}
|
||||
case "toolcache":
|
||||
codeqlFolder = source.codeqlFolder;
|
||||
logger.debug(`CodeQL found in cache ${codeqlFolder}`);
|
||||
toolsSource = ToolsSource.Toolcache;
|
||||
break;
|
||||
case "download": {
|
||||
const result = await (0, exports.downloadCodeQL)(source.codeqlURL, source.compressionMethod, source.bundleVersion, source.cliVersion, apiDetails, zstdAvailability.version, tempDir, logger);
|
||||
toolsVersion = result.toolsVersion;
|
||||
codeqlFolder = result.codeqlFolder;
|
||||
toolsDownloadStatusReport = result.statusReport;
|
||||
toolsSource = ToolsSource.Download;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
util.assertNever(source);
|
||||
}
|
||||
return {
|
||||
codeqlFolder,
|
||||
toolsDownloadStatusReport,
|
||||
toolsSource,
|
||||
toolsVersion,
|
||||
zstdAvailability,
|
||||
};
|
||||
}
|
||||
async function useZstdBundle(cliVersion, tarSupportsZstd) {
|
||||
return (
|
||||
// In testing, gzip performs better than zstd on Windows.
|
||||
process.platform !== "win32" &&
|
||||
tarSupportsZstd &&
|
||||
semver.gte(cliVersion, feature_flags_1.CODEQL_VERSION_ZSTD_BUNDLE));
|
||||
}
|
||||
function getTempExtractionDir(tempDir) {
|
||||
return path.join(tempDir, (0, uuid_1.v4)());
|
||||
}
|
||||
//# sourceMappingURL=setup-codeql.js.map
|
||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user