Move CodeQL setup to its own file
This commit is contained in:
parent
a76fe4f9bd
commit
bd2f52fcef
12 changed files with 871 additions and 769 deletions
249
lib/codeql.js
generated
249
lib/codeql.js
generated
|
|
@ -18,24 +18,17 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
||||||
__setModuleDefault(result, mod);
|
__setModuleDefault(result, mod);
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
||||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
||||||
};
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.getExtraOptions = exports.getCodeQLForTesting = exports.getCachedCodeQL = exports.setCodeQL = exports.getCodeQL = exports.convertToSemVer = exports.getCodeQLURLVersion = exports.setupCodeQL = exports.findCodeQLBundleTagDotcomOnly = exports.getCodeQLActionRepository = exports.CODEQL_VERSION_BETTER_RESOLVE_LANGUAGES = exports.CODEQL_VERSION_ML_POWERED_QUERIES_WINDOWS = exports.CODEQL_VERSION_TRACING_GLIBC_2_34 = exports.CODEQL_VERSION_NEW_TRACING = exports.CODEQL_VERSION_GHES_PACK_DOWNLOAD = exports.CODEQL_DEFAULT_ACTION_REPOSITORY = exports.CommandInvocationError = void 0;
|
exports.getExtraOptions = exports.getCodeQLForCmd = exports.getCodeQLForTesting = exports.getCachedCodeQL = exports.setCodeQL = exports.getCodeQL = exports.setupCodeQL = exports.CODEQL_VERSION_BETTER_RESOLVE_LANGUAGES = exports.CODEQL_VERSION_ML_POWERED_QUERIES_WINDOWS = exports.CODEQL_VERSION_TRACING_GLIBC_2_34 = exports.CODEQL_VERSION_NEW_TRACING = exports.CODEQL_VERSION_GHES_PACK_DOWNLOAD = exports.CommandInvocationError = void 0;
|
||||||
const fs = __importStar(require("fs"));
|
const fs = __importStar(require("fs"));
|
||||||
const path = __importStar(require("path"));
|
const path = __importStar(require("path"));
|
||||||
const toolrunner = __importStar(require("@actions/exec/lib/toolrunner"));
|
const toolrunner = __importStar(require("@actions/exec/lib/toolrunner"));
|
||||||
const toolcache = __importStar(require("@actions/tool-cache"));
|
const toolcache = __importStar(require("@actions/tool-cache"));
|
||||||
const fast_deep_equal_1 = __importDefault(require("fast-deep-equal"));
|
|
||||||
const yaml = __importStar(require("js-yaml"));
|
const yaml = __importStar(require("js-yaml"));
|
||||||
const semver = __importStar(require("semver"));
|
|
||||||
const uuid_1 = require("uuid");
|
|
||||||
const actions_util_1 = require("./actions-util");
|
const actions_util_1 = require("./actions-util");
|
||||||
const api = __importStar(require("./api-client"));
|
|
||||||
const defaults = __importStar(require("./defaults.json")); // Referenced from codeql-action-sync-tool!
|
|
||||||
const error_matcher_1 = require("./error-matcher");
|
const error_matcher_1 = require("./error-matcher");
|
||||||
const languages_1 = require("./languages");
|
const languages_1 = require("./languages");
|
||||||
|
const setup_codeql_1 = require("./setup-codeql");
|
||||||
const toolrunner_error_catcher_1 = require("./toolrunner-error-catcher");
|
const toolrunner_error_catcher_1 = require("./toolrunner-error-catcher");
|
||||||
const trap_caching_1 = require("./trap-caching");
|
const trap_caching_1 = require("./trap-caching");
|
||||||
const util = __importStar(require("./util"));
|
const util = __importStar(require("./util"));
|
||||||
|
|
@ -54,8 +47,6 @@ exports.CommandInvocationError = CommandInvocationError;
|
||||||
* Can be overridden in tests using `setCodeQL`.
|
* Can be overridden in tests using `setCodeQL`.
|
||||||
*/
|
*/
|
||||||
let cachedCodeQL = undefined;
|
let cachedCodeQL = undefined;
|
||||||
const CODEQL_BUNDLE_VERSION = defaults.bundleVersion;
|
|
||||||
exports.CODEQL_DEFAULT_ACTION_REPOSITORY = "github/codeql-action";
|
|
||||||
/**
|
/**
|
||||||
* The oldest version of CodeQL that the Action will run with. This should be
|
* 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
|
* at least three minor versions behind the current version and must include the
|
||||||
|
|
@ -101,217 +92,6 @@ exports.CODEQL_VERSION_ML_POWERED_QUERIES_WINDOWS = "2.9.0";
|
||||||
* --extractor-options-verbosity that we need.
|
* --extractor-options-verbosity that we need.
|
||||||
*/
|
*/
|
||||||
exports.CODEQL_VERSION_BETTER_RESOLVE_LANGUAGES = "2.10.3";
|
exports.CODEQL_VERSION_BETTER_RESOLVE_LANGUAGES = "2.10.3";
|
||||||
function getCodeQLBundleName() {
|
|
||||||
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.tar.gz";
|
|
||||||
}
|
|
||||||
return `codeql-bundle-${platform}.tar.gz`;
|
|
||||||
}
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
exports.getCodeQLActionRepository = getCodeQLActionRepository;
|
|
||||||
async function findCodeQLBundleTagDotcomOnly(cliVersion, logger) {
|
|
||||||
const apiClient = api.getApiClient();
|
|
||||||
const codeQLActionRepository = getCodeQLActionRepository(logger);
|
|
||||||
const releases = await apiClient.paginate(apiClient.repos.listReleases, {
|
|
||||||
owner: codeQLActionRepository.split("/")[0],
|
|
||||||
repo: codeQLActionRepository.split("/")[1],
|
|
||||||
});
|
|
||||||
logger.debug(`Found ${releases.length} releases.`);
|
|
||||||
for (const release of releases) {
|
|
||||||
const cliVersionFileVersions = release.assets
|
|
||||||
.map((asset) => { var _a; return (_a = asset.name.match(/cli-version-(.*)\.txt/)) === null || _a === void 0 ? void 0 : _a[1]; })
|
|
||||||
.filter((v) => v)
|
|
||||||
.map((v) => v);
|
|
||||||
if (cliVersionFileVersions.length === 0) {
|
|
||||||
logger.debug(`Ignoring release ${release.tag_name} with no CLI version marker file.`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cliVersionFileVersions.length > 1) {
|
|
||||||
logger.warning(`Ignoring release ${release.tag_name} with multiple CLI version marker files.`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cliVersionFileVersions[0] === cliVersion) {
|
|
||||||
return release.tag_name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new Error(`Failed to find a CodeQL bundle release for CLI version ${cliVersion}.`);
|
|
||||||
}
|
|
||||||
exports.findCodeQLBundleTagDotcomOnly = findCodeQLBundleTagDotcomOnly;
|
|
||||||
async function getCodeQLBundleDownloadURL(apiDetails, variant, 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();
|
|
||||||
if (variant === util.GitHubVariant.GHAE) {
|
|
||||||
try {
|
|
||||||
const release = await api
|
|
||||||
.getApiClient()
|
|
||||||
.request("GET /enterprise/code-scanning/codeql-bundle/find/{tag}", {
|
|
||||||
tag: CODEQL_BUNDLE_VERSION,
|
|
||||||
});
|
|
||||||
const assetID = release.data.assets[codeQLBundleName];
|
|
||||||
if (assetID !== undefined) {
|
|
||||||
const download = await api
|
|
||||||
.getApiClient()
|
|
||||||
.request("GET /enterprise/code-scanning/codeql-bundle/download/{asset_id}", { asset_id: assetID });
|
|
||||||
const downloadURL = download.data.url;
|
|
||||||
logger.info(`Found CodeQL bundle at GitHub AE endpoint with URL ${downloadURL}.`);
|
|
||||||
return downloadURL;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
logger.info(`Attempted to fetch bundle from GitHub AE endpoint but the bundle ${codeQLBundleName} was not found in the assets ${JSON.stringify(release.data.assets)}.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
logger.info(`Attempted to fetch bundle from GitHub AE endpoint but got error ${e}.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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().repos.getReleaseByTag({
|
|
||||||
owner: repositoryOwner,
|
|
||||||
repo: repositoryName,
|
|
||||||
tag: CODEQL_BUNDLE_VERSION,
|
|
||||||
});
|
|
||||||
for (const asset of release.data.assets) {
|
|
||||||
if (asset.name === codeQLBundleName) {
|
|
||||||
logger.info(`Found CodeQL bundle in ${downloadSource[1]} on ${downloadSource[0]} with URL ${asset.url}.`);
|
|
||||||
return asset.url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
logger.info(`Looked for CodeQL bundle in ${downloadSource[1]} on ${downloadSource[0]} but got error ${e}.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return `https://github.com/${exports.CODEQL_DEFAULT_ACTION_REPOSITORY}/releases/download/${CODEQL_BUNDLE_VERSION}/${codeQLBundleName}`;
|
|
||||||
}
|
|
||||||
async function getCodeQLSource(toolsInput, bypassToolcache, apiDetails, variant, logger) {
|
|
||||||
var _a;
|
|
||||||
if (toolsInput && toolsInput !== "latest" && !toolsInput.startsWith("http")) {
|
|
||||||
return {
|
|
||||||
codeqlTarPath: toolsInput,
|
|
||||||
sourceType: "local",
|
|
||||||
toolsVersion: "local",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const forceLatestReason =
|
|
||||||
// We use the special value of 'latest' to prioritize the version in the
|
|
||||||
// defaults over any pinned cached version.
|
|
||||||
toolsInput === "latest"
|
|
||||||
? '"tools: latest" was requested'
|
|
||||||
: // If the user hasn't requested a particular CodeQL version, then bypass
|
|
||||||
// the toolcache when the appropriate feature is enabled. This
|
|
||||||
// allows us to quickly rollback a broken bundle that has made its way
|
|
||||||
// into the toolcache.
|
|
||||||
toolsInput === undefined && bypassToolcache
|
|
||||||
? "a specific version of CodeQL was not requested and the bypass toolcache feature is enabled"
|
|
||||||
: undefined;
|
|
||||||
const forceLatest = forceLatestReason !== undefined;
|
|
||||||
if (forceLatest) {
|
|
||||||
logger.debug(`Forcing the latest version of the CodeQL tools since ${forceLatestReason}.`);
|
|
||||||
}
|
|
||||||
const codeqlURL = forceLatest ? undefined : toolsInput;
|
|
||||||
const requestedSemVer = convertToSemVer(getCodeQLURLVersion(codeqlURL || `/${CODEQL_BUNDLE_VERSION}/`), logger);
|
|
||||||
// If we find the specified version, we always use that.
|
|
||||||
const codeqlFolder = toolcache.find("CodeQL", requestedSemVer);
|
|
||||||
if (codeqlFolder) {
|
|
||||||
return {
|
|
||||||
codeqlFolder,
|
|
||||||
sourceType: "toolcache",
|
|
||||||
toolsVersion: requestedSemVer,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// If we don't find the requested version, in some cases 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 (!codeqlURL && !forceLatest) {
|
|
||||||
const codeqlVersions = toolcache.findAllVersions("CodeQL");
|
|
||||||
if (codeqlVersions.length === 1 && (0, util_1.isGoodVersion)(codeqlVersions[0])) {
|
|
||||||
const tmpCodeqlFolder = toolcache.find("CodeQL", codeqlVersions[0]);
|
|
||||||
if (fs.existsSync(path.join(tmpCodeqlFolder, "pinned-version"))) {
|
|
||||||
logger.debug(`CodeQL in cache overriding the default ${CODEQL_BUNDLE_VERSION}`);
|
|
||||||
return {
|
|
||||||
codeqlFolder: tmpCodeqlFolder,
|
|
||||||
sourceType: "toolcache",
|
|
||||||
toolsVersion: codeqlVersions[0],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
codeqlURL: codeqlURL ||
|
|
||||||
(await getCodeQLBundleDownloadURL(apiDetails, variant, logger)),
|
|
||||||
semanticVersion: requestedSemVer,
|
|
||||||
sourceType: "download",
|
|
||||||
toolsVersion: ((_a = semver.prerelease(requestedSemVer)) === null || _a === void 0 ? void 0 : _a.join(".")) || requestedSemVer,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
async function downloadCodeQL(codeqlURL, semanticVersion, apiDetails, 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.
|
|
||||||
if (searchParams.has("token")) {
|
|
||||||
logger.debug("CodeQL tools URL contains an authorization token.");
|
|
||||||
}
|
|
||||||
else if (codeqlURL.startsWith(`${apiDetails.url}/`)) {
|
|
||||||
logger.debug("Providing an authorization token to download CodeQL tools.");
|
|
||||||
headers.authorization = `token ${apiDetails.auth}`;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
logger.debug("Downloading CodeQL tools without an authorization token.");
|
|
||||||
}
|
|
||||||
logger.info(`Downloading CodeQL tools from ${codeqlURL}. This may take a while.`);
|
|
||||||
const dest = path.join(tempDir, (0, uuid_1.v4)());
|
|
||||||
const finalHeaders = Object.assign({ "User-Agent": "CodeQL Action" }, headers);
|
|
||||||
const codeqlPath = await toolcache.downloadTool(codeqlURL, dest, undefined, finalHeaders);
|
|
||||||
logger.debug(`CodeQL bundle download to ${codeqlPath} complete.`);
|
|
||||||
const codeqlExtracted = await toolcache.extractTar(codeqlPath);
|
|
||||||
return await toolcache.cacheDir(codeqlExtracted, "CodeQL", semanticVersion);
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* Set up CodeQL CLI access.
|
* Set up CodeQL CLI access.
|
||||||
*
|
*
|
||||||
|
|
@ -327,7 +107,7 @@ async function downloadCodeQL(codeqlURL, semanticVersion, apiDetails, tempDir, l
|
||||||
*/
|
*/
|
||||||
async function setupCodeQL(toolsInput, apiDetails, tempDir, variant, bypassToolcache, logger, checkVersion) {
|
async function setupCodeQL(toolsInput, apiDetails, tempDir, variant, bypassToolcache, logger, checkVersion) {
|
||||||
try {
|
try {
|
||||||
const source = await getCodeQLSource(toolsInput, bypassToolcache, apiDetails, variant, logger);
|
const source = await (0, setup_codeql_1.getCodeQLSource)(toolsInput, bypassToolcache, apiDetails, variant, logger);
|
||||||
let codeqlFolder;
|
let codeqlFolder;
|
||||||
switch (source.sourceType) {
|
switch (source.sourceType) {
|
||||||
case "local":
|
case "local":
|
||||||
|
|
@ -338,7 +118,7 @@ async function setupCodeQL(toolsInput, apiDetails, tempDir, variant, bypassToolc
|
||||||
logger.debug(`CodeQL found in cache ${codeqlFolder}`);
|
logger.debug(`CodeQL found in cache ${codeqlFolder}`);
|
||||||
break;
|
break;
|
||||||
case "download":
|
case "download":
|
||||||
codeqlFolder = await downloadCodeQL(source.codeqlURL, source.semanticVersion, apiDetails, tempDir, logger);
|
codeqlFolder = await (0, setup_codeql_1.downloadCodeQL)(source.codeqlURL, source.semanticVersion, apiDetails, tempDir, logger);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
(0, util_1.assertNever)(source);
|
(0, util_1.assertNever)(source);
|
||||||
|
|
@ -359,26 +139,6 @@ async function setupCodeQL(toolsInput, apiDetails, tempDir, variant, bypassToolc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.setupCodeQL = setupCodeQL;
|
exports.setupCodeQL = setupCodeQL;
|
||||||
function getCodeQLURLVersion(url) {
|
|
||||||
const match = url.match(/\/codeql-bundle-(.*)\//);
|
|
||||||
if (match === null || match.length < 2) {
|
|
||||||
throw new Error(`Malformed tools url: ${url}. Version could not be inferred`);
|
|
||||||
}
|
|
||||||
return match[1];
|
|
||||||
}
|
|
||||||
exports.getCodeQLURLVersion = getCodeQLURLVersion;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
exports.convertToSemVer = convertToSemVer;
|
|
||||||
/**
|
/**
|
||||||
* Use the CodeQL executable located at the given path.
|
* Use the CodeQL executable located at the given path.
|
||||||
*/
|
*/
|
||||||
|
|
@ -864,6 +624,7 @@ async function getCodeQLForCmd(cmd, checkVersion) {
|
||||||
}
|
}
|
||||||
return codeql;
|
return codeql;
|
||||||
}
|
}
|
||||||
|
exports.getCodeQLForCmd = getCodeQLForCmd;
|
||||||
/**
|
/**
|
||||||
* Gets the options for `path` of `options` as an array of extra option strings.
|
* Gets the options for `path` of `options` as an array of extra option strings.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
87
lib/codeql.test.js
generated
87
lib/codeql.test.js
generated
|
|
@ -24,7 +24,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.stubToolRunnerConstructor = void 0;
|
exports.stubToolRunnerConstructor = void 0;
|
||||||
const fs = __importStar(require("fs"));
|
const fs = __importStar(require("fs"));
|
||||||
const path = __importStar(require("path"));
|
const path_1 = __importDefault(require("path"));
|
||||||
const toolrunner = __importStar(require("@actions/exec/lib/toolrunner"));
|
const toolrunner = __importStar(require("@actions/exec/lib/toolrunner"));
|
||||||
const toolcache = __importStar(require("@actions/tool-cache"));
|
const toolcache = __importStar(require("@actions/tool-cache"));
|
||||||
const safeWhich = __importStar(require("@chrisgavin/safe-which"));
|
const safeWhich = __importStar(require("@chrisgavin/safe-which"));
|
||||||
|
|
@ -34,9 +34,8 @@ const yaml = __importStar(require("js-yaml"));
|
||||||
const nock_1 = __importDefault(require("nock"));
|
const nock_1 = __importDefault(require("nock"));
|
||||||
const sinon = __importStar(require("sinon"));
|
const sinon = __importStar(require("sinon"));
|
||||||
const actionsUtil = __importStar(require("./actions-util"));
|
const actionsUtil = __importStar(require("./actions-util"));
|
||||||
const api = __importStar(require("./api-client"));
|
|
||||||
const codeql = __importStar(require("./codeql"));
|
const codeql = __importStar(require("./codeql"));
|
||||||
const defaults = __importStar(require("./defaults.json"));
|
const defaults = __importStar(require("./defaults.json")); // Referenced from codeql-action-sync-tool!
|
||||||
const feature_flags_1 = require("./feature-flags");
|
const feature_flags_1 = require("./feature-flags");
|
||||||
const languages_1 = require("./languages");
|
const languages_1 = require("./languages");
|
||||||
const logging_1 = require("./logging");
|
const logging_1 = require("./logging");
|
||||||
|
|
@ -101,7 +100,7 @@ async function mockDownloadApi({ apiDetails = sampleApiDetails, isPinned, tagNam
|
||||||
: `/download/${tagName}/codeql-bundle.tar.gz`;
|
: `/download/${tagName}/codeql-bundle.tar.gz`;
|
||||||
(0, nock_1.default)(baseUrl)
|
(0, nock_1.default)(baseUrl)
|
||||||
.get(relativeUrl)
|
.get(relativeUrl)
|
||||||
.replyWithFile(200, path.join(__dirname, `/../src/testdata/codeql-bundle${isPinned ? "-pinned" : ""}.tar.gz`));
|
.replyWithFile(200, path_1.default.join(__dirname, `/../src/testdata/codeql-bundle${isPinned ? "-pinned" : ""}.tar.gz`));
|
||||||
return `${baseUrl}${relativeUrl}`;
|
return `${baseUrl}${relativeUrl}`;
|
||||||
}
|
}
|
||||||
async function installIntoToolcache({ apiDetails = sampleApiDetails, isPinned, tagName, tmpDir, }) {
|
async function installIntoToolcache({ apiDetails = sampleApiDetails, isPinned, tagName, tmpDir, }) {
|
||||||
|
|
@ -238,7 +237,7 @@ for (const [isFeatureEnabled, toolsInput, shouldToolcacheBeBypassed,] of TOOLCAC
|
||||||
});
|
});
|
||||||
(0, nock_1.default)("https://example.githubenterprise.com")
|
(0, nock_1.default)("https://example.githubenterprise.com")
|
||||||
.get(`/github/codeql-action/releases/download/${defaults.bundleVersion}/${codeQLBundleName}`)
|
.get(`/github/codeql-action/releases/download/${defaults.bundleVersion}/${codeQLBundleName}`)
|
||||||
.replyWithFile(200, path.join(__dirname, `/../src/testdata/codeql-bundle-pinned.tar.gz`));
|
.replyWithFile(200, path_1.default.join(__dirname, `/../src/testdata/codeql-bundle-pinned.tar.gz`));
|
||||||
// This is a workaround to mock `api.getApiDetails()` since it doesn't seem to be possible to
|
// 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
|
// mock this directly. The difficulty is that `getApiDetails()` is called locally in
|
||||||
// `api-client.ts`, but `sinon.stub(api, "getApiDetails")` only affects calls to
|
// `api-client.ts`, but `sinon.stub(api, "getApiDetails")` only affects calls to
|
||||||
|
|
@ -261,28 +260,6 @@ for (const [isFeatureEnabled, toolsInput, shouldToolcacheBeBypassed,] of TOOLCAC
|
||||||
t.is(cachedVersions.length, 1);
|
t.is(cachedVersions.length, 1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
(0, ava_1.default)("parse codeql bundle url version", (t) => {
|
|
||||||
t.deepEqual(codeql.getCodeQLURLVersion("https://github.com/.../codeql-bundle-20200601/..."), "20200601");
|
|
||||||
});
|
|
||||||
(0, ava_1.default)("convert to semver", (t) => {
|
|
||||||
const tests = {
|
|
||||||
"20200601": "0.0.0-20200601",
|
|
||||||
"20200601.0": "0.0.0-20200601.0",
|
|
||||||
"20200601.0.0": "20200601.0.0",
|
|
||||||
"1.2.3": "1.2.3",
|
|
||||||
"1.2.3-alpha": "1.2.3-alpha",
|
|
||||||
"1.2.3-beta.1": "1.2.3-beta.1",
|
|
||||||
};
|
|
||||||
for (const [version, expectedVersion] of Object.entries(tests)) {
|
|
||||||
try {
|
|
||||||
const parsedVersion = codeql.convertToSemVer(version, (0, logging_1.getRunnerLogger)(true));
|
|
||||||
t.deepEqual(parsedVersion, expectedVersion);
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
t.fail(e instanceof Error ? e.message : String(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
(0, ava_1.default)("getExtraOptions works for explicit paths", (t) => {
|
(0, ava_1.default)("getExtraOptions works for explicit paths", (t) => {
|
||||||
t.deepEqual(codeql.getExtraOptions({}, ["foo"], []), []);
|
t.deepEqual(codeql.getExtraOptions({}, ["foo"], []), []);
|
||||||
t.deepEqual(codeql.getExtraOptions({ foo: [42] }, ["foo"], []), ["42"]);
|
t.deepEqual(codeql.getExtraOptions({ foo: [42] }, ["foo"], []), ["42"]);
|
||||||
|
|
@ -305,20 +282,6 @@ for (const [isFeatureEnabled, toolsInput, shouldToolcacheBeBypassed,] of TOOLCAC
|
||||||
t.throws(() => codeql.getExtraOptions({ foo: 87 }, ["foo"], []));
|
t.throws(() => codeql.getExtraOptions({ foo: 87 }, ["foo"], []));
|
||||||
t.throws(() => codeql.getExtraOptions({ "*": [42], foo: { "*": 87, bar: [99] } }, ["foo", "bar"], []));
|
t.throws(() => codeql.getExtraOptions({ "*": [42], foo: { "*": 87, bar: [99] } }, ["foo", "bar"], []));
|
||||||
});
|
});
|
||||||
(0, ava_1.default)("getCodeQLActionRepository", (t) => {
|
|
||||||
const logger = (0, logging_1.getRunnerLogger)(true);
|
|
||||||
(0, util_1.initializeEnvironment)("1.2.3");
|
|
||||||
// isRunningLocalAction() === true
|
|
||||||
delete process.env["GITHUB_ACTION_REPOSITORY"];
|
|
||||||
process.env["RUNNER_TEMP"] = path.dirname(__dirname);
|
|
||||||
const repoLocalRunner = codeql.getCodeQLActionRepository(logger);
|
|
||||||
t.deepEqual(repoLocalRunner, "github/codeql-action");
|
|
||||||
// isRunningLocalAction() === false
|
|
||||||
sinon.stub(actionsUtil, "isRunningLocalAction").returns(false);
|
|
||||||
process.env["GITHUB_ACTION_REPOSITORY"] = "xxx/yyy";
|
|
||||||
const repoEnv = codeql.getCodeQLActionRepository(logger);
|
|
||||||
t.deepEqual(repoEnv, "xxx/yyy");
|
|
||||||
});
|
|
||||||
(0, ava_1.default)("databaseInterpretResults() does not set --sarif-add-query-help for 2.7.0", async (t) => {
|
(0, ava_1.default)("databaseInterpretResults() does not set --sarif-add-query-help for 2.7.0", async (t) => {
|
||||||
const runnerConstructorStub = stubToolRunnerConstructor();
|
const runnerConstructorStub = stubToolRunnerConstructor();
|
||||||
const codeqlObject = await codeql.getCodeQLForTesting();
|
const codeqlObject = await codeql.getCodeQLForTesting();
|
||||||
|
|
@ -603,48 +566,6 @@ const injectedConfigMacro = ava_1.default.macro({
|
||||||
await codeqlObject.databaseInterpretResults("", [], "", "", "", "-v", "");
|
await codeqlObject.databaseInterpretResults("", [], "", "", "", "-v", "");
|
||||||
t.false(runnerConstructorStub.firstCall.args[1].includes("--sarif-add-baseline-file-info"), "--sarif-add-baseline-file-info must be absent, but it is present");
|
t.false(runnerConstructorStub.firstCall.args[1].includes("--sarif-add-baseline-file-info"), "--sarif-add-baseline-file-info must be absent, but it is present");
|
||||||
});
|
});
|
||||||
(0, ava_1.default)("findCodeQLBundleTagDotcomOnly() matches GitHub Release with marker file", async (t) => {
|
|
||||||
// Look for GitHub Releases in github/codeql-action
|
|
||||||
sinon.stub(actionsUtil, "isRunningLocalAction").resolves(true);
|
|
||||||
sinon.stub(api, "getApiClient").value(() => ({
|
|
||||||
repos: {
|
|
||||||
listReleases: sinon.stub().resolves(undefined),
|
|
||||||
},
|
|
||||||
paginate: sinon.stub().resolves([
|
|
||||||
{
|
|
||||||
assets: [
|
|
||||||
{
|
|
||||||
name: "cli-version-2.12.0.txt",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
tag_name: "codeql-bundle-20230106",
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
}));
|
|
||||||
t.is(await codeql.findCodeQLBundleTagDotcomOnly("2.12.0", (0, logging_1.getRunnerLogger)(true)), "codeql-bundle-20230106");
|
|
||||||
});
|
|
||||||
(0, ava_1.default)("findCodeQLBundleTagDotcomOnly() errors if no GitHub Release matches marker file", async (t) => {
|
|
||||||
// Look for GitHub Releases in github/codeql-action
|
|
||||||
sinon.stub(actionsUtil, "isRunningLocalAction").resolves(true);
|
|
||||||
sinon.stub(api, "getApiClient").value(() => ({
|
|
||||||
repos: {
|
|
||||||
listReleases: sinon.stub().resolves(undefined),
|
|
||||||
},
|
|
||||||
paginate: sinon.stub().resolves([
|
|
||||||
{
|
|
||||||
assets: [
|
|
||||||
{
|
|
||||||
name: "cli-version-2.12.0.txt",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
tag_name: "codeql-bundle-20230106",
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
}));
|
|
||||||
await t.throwsAsync(async () => await codeql.findCodeQLBundleTagDotcomOnly("2.12.1", (0, logging_1.getRunnerLogger)(true)), {
|
|
||||||
message: "Failed to find a CodeQL bundle release for CLI version 2.12.1.",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
function stubToolRunnerConstructor() {
|
function stubToolRunnerConstructor() {
|
||||||
const runnerObjectStub = sinon.createStubInstance(toolrunner.ToolRunner);
|
const runnerObjectStub = sinon.createStubInstance(toolrunner.ToolRunner);
|
||||||
runnerObjectStub.exec.resolves(0);
|
runnerObjectStub.exec.resolves(0);
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
272
lib/setup-codeql.js
generated
Normal file
272
lib/setup-codeql.js
generated
Normal file
|
|
@ -0,0 +1,272 @@
|
||||||
|
"use strict";
|
||||||
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
||||||
|
}) : (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 (mod) {
|
||||||
|
if (mod && mod.__esModule) return mod;
|
||||||
|
var result = {};
|
||||||
|
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||||
|
__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.convertToSemVer = exports.getCodeQLURLVersion = exports.downloadCodeQL = exports.getCodeQLSource = exports.findCodeQLBundleTagDotcomOnly = exports.getCodeQLActionRepository = exports.CODEQL_DEFAULT_ACTION_REPOSITORY = void 0;
|
||||||
|
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")); // Referenced from codeql-action-sync-tool!
|
||||||
|
const util = __importStar(require("./util"));
|
||||||
|
const util_1 = require("./util");
|
||||||
|
const CODEQL_BUNDLE_VERSION = defaults.bundleVersion;
|
||||||
|
exports.CODEQL_DEFAULT_ACTION_REPOSITORY = "github/codeql-action";
|
||||||
|
function getCodeQLBundleName() {
|
||||||
|
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.tar.gz";
|
||||||
|
}
|
||||||
|
return `codeql-bundle-${platform}.tar.gz`;
|
||||||
|
}
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
exports.getCodeQLActionRepository = getCodeQLActionRepository;
|
||||||
|
async function findCodeQLBundleTagDotcomOnly(cliVersion, logger) {
|
||||||
|
const apiClient = api.getApiClient();
|
||||||
|
const codeQLActionRepository = getCodeQLActionRepository(logger);
|
||||||
|
const releases = await apiClient.paginate(apiClient.repos.listReleases, {
|
||||||
|
owner: codeQLActionRepository.split("/")[0],
|
||||||
|
repo: codeQLActionRepository.split("/")[1],
|
||||||
|
});
|
||||||
|
logger.debug(`Found ${releases.length} releases.`);
|
||||||
|
for (const release of releases) {
|
||||||
|
const cliVersionFileVersions = release.assets
|
||||||
|
.map((asset) => { var _a; return (_a = asset.name.match(/cli-version-(.*)\.txt/)) === null || _a === void 0 ? void 0 : _a[1]; })
|
||||||
|
.filter((v) => v)
|
||||||
|
.map((v) => v);
|
||||||
|
if (cliVersionFileVersions.length === 0) {
|
||||||
|
logger.debug(`Ignoring release ${release.tag_name} with no CLI version marker file.`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (cliVersionFileVersions.length > 1) {
|
||||||
|
logger.warning(`Ignoring release ${release.tag_name} with multiple CLI version marker files.`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (cliVersionFileVersions[0] === cliVersion) {
|
||||||
|
return release.tag_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to find a CodeQL bundle release for CLI version ${cliVersion}.`);
|
||||||
|
}
|
||||||
|
exports.findCodeQLBundleTagDotcomOnly = findCodeQLBundleTagDotcomOnly;
|
||||||
|
async function getCodeQLBundleDownloadURL(apiDetails, variant, 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();
|
||||||
|
if (variant === util.GitHubVariant.GHAE) {
|
||||||
|
try {
|
||||||
|
const release = await api
|
||||||
|
.getApiClient()
|
||||||
|
.request("GET /enterprise/code-scanning/codeql-bundle/find/{tag}", {
|
||||||
|
tag: CODEQL_BUNDLE_VERSION,
|
||||||
|
});
|
||||||
|
const assetID = release.data.assets[codeQLBundleName];
|
||||||
|
if (assetID !== undefined) {
|
||||||
|
const download = await api
|
||||||
|
.getApiClient()
|
||||||
|
.request("GET /enterprise/code-scanning/codeql-bundle/download/{asset_id}", { asset_id: assetID });
|
||||||
|
const downloadURL = download.data.url;
|
||||||
|
logger.info(`Found CodeQL bundle at GitHub AE endpoint with URL ${downloadURL}.`);
|
||||||
|
return downloadURL;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logger.info(`Attempted to fetch bundle from GitHub AE endpoint but the bundle ${codeQLBundleName} was not found in the assets ${JSON.stringify(release.data.assets)}.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
logger.info(`Attempted to fetch bundle from GitHub AE endpoint but got error ${e}.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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().repos.getReleaseByTag({
|
||||||
|
owner: repositoryOwner,
|
||||||
|
repo: repositoryName,
|
||||||
|
tag: CODEQL_BUNDLE_VERSION,
|
||||||
|
});
|
||||||
|
for (const asset of release.data.assets) {
|
||||||
|
if (asset.name === codeQLBundleName) {
|
||||||
|
logger.info(`Found CodeQL bundle in ${downloadSource[1]} on ${downloadSource[0]} with URL ${asset.url}.`);
|
||||||
|
return asset.url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
logger.info(`Looked for CodeQL bundle in ${downloadSource[1]} on ${downloadSource[0]} but got error ${e}.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return `https://github.com/${exports.CODEQL_DEFAULT_ACTION_REPOSITORY}/releases/download/${CODEQL_BUNDLE_VERSION}/${codeQLBundleName}`;
|
||||||
|
}
|
||||||
|
async function getCodeQLSource(toolsInput, bypassToolcache, apiDetails, variant, logger) {
|
||||||
|
var _a;
|
||||||
|
if (toolsInput && toolsInput !== "latest" && !toolsInput.startsWith("http")) {
|
||||||
|
return {
|
||||||
|
codeqlTarPath: toolsInput,
|
||||||
|
sourceType: "local",
|
||||||
|
toolsVersion: "local",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const forceLatestReason =
|
||||||
|
// We use the special value of 'latest' to prioritize the version in the
|
||||||
|
// defaults over any pinned cached version.
|
||||||
|
toolsInput === "latest"
|
||||||
|
? '"tools: latest" was requested'
|
||||||
|
: // If the user hasn't requested a particular CodeQL version, then bypass
|
||||||
|
// the toolcache when the appropriate feature is enabled. This
|
||||||
|
// allows us to quickly rollback a broken bundle that has made its way
|
||||||
|
// into the toolcache.
|
||||||
|
toolsInput === undefined && bypassToolcache
|
||||||
|
? "a specific version of CodeQL was not requested and the bypass toolcache feature is enabled"
|
||||||
|
: undefined;
|
||||||
|
const forceLatest = forceLatestReason !== undefined;
|
||||||
|
if (forceLatest) {
|
||||||
|
logger.debug(`Forcing the latest version of the CodeQL tools since ${forceLatestReason}.`);
|
||||||
|
}
|
||||||
|
const codeqlURL = forceLatest ? undefined : toolsInput;
|
||||||
|
const requestedSemVer = convertToSemVer(getCodeQLURLVersion(codeqlURL || `/${CODEQL_BUNDLE_VERSION}/`), logger);
|
||||||
|
// If we find the specified version, we always use that.
|
||||||
|
const codeqlFolder = toolcache.find("CodeQL", requestedSemVer);
|
||||||
|
if (codeqlFolder) {
|
||||||
|
return {
|
||||||
|
codeqlFolder,
|
||||||
|
sourceType: "toolcache",
|
||||||
|
toolsVersion: requestedSemVer,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// If we don't find the requested version, in some cases 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 (!codeqlURL && !forceLatest) {
|
||||||
|
const codeqlVersions = toolcache.findAllVersions("CodeQL");
|
||||||
|
if (codeqlVersions.length === 1 && (0, util_1.isGoodVersion)(codeqlVersions[0])) {
|
||||||
|
const tmpCodeqlFolder = toolcache.find("CodeQL", codeqlVersions[0]);
|
||||||
|
if (fs.existsSync(path.join(tmpCodeqlFolder, "pinned-version"))) {
|
||||||
|
logger.debug(`CodeQL in cache overriding the default ${CODEQL_BUNDLE_VERSION}`);
|
||||||
|
return {
|
||||||
|
codeqlFolder: tmpCodeqlFolder,
|
||||||
|
sourceType: "toolcache",
|
||||||
|
toolsVersion: codeqlVersions[0],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
codeqlURL: codeqlURL ||
|
||||||
|
(await getCodeQLBundleDownloadURL(apiDetails, variant, logger)),
|
||||||
|
semanticVersion: requestedSemVer,
|
||||||
|
sourceType: "download",
|
||||||
|
toolsVersion: ((_a = semver.prerelease(requestedSemVer)) === null || _a === void 0 ? void 0 : _a.join(".")) || requestedSemVer,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
exports.getCodeQLSource = getCodeQLSource;
|
||||||
|
async function downloadCodeQL(codeqlURL, semanticVersion, apiDetails, 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.
|
||||||
|
if (searchParams.has("token")) {
|
||||||
|
logger.debug("CodeQL tools URL contains an authorization token.");
|
||||||
|
}
|
||||||
|
else if (codeqlURL.startsWith(`${apiDetails.url}/`)) {
|
||||||
|
logger.debug("Providing an authorization token to download CodeQL tools.");
|
||||||
|
headers.authorization = `token ${apiDetails.auth}`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logger.debug("Downloading CodeQL tools without an authorization token.");
|
||||||
|
}
|
||||||
|
logger.info(`Downloading CodeQL tools from ${codeqlURL}. This may take a while.`);
|
||||||
|
const dest = path.join(tempDir, (0, uuid_1.v4)());
|
||||||
|
const finalHeaders = Object.assign({ "User-Agent": "CodeQL Action" }, headers);
|
||||||
|
const codeqlPath = await toolcache.downloadTool(codeqlURL, dest, undefined, finalHeaders);
|
||||||
|
logger.debug(`CodeQL bundle download to ${codeqlPath} complete.`);
|
||||||
|
const codeqlExtracted = await toolcache.extractTar(codeqlPath);
|
||||||
|
return await toolcache.cacheDir(codeqlExtracted, "CodeQL", semanticVersion);
|
||||||
|
}
|
||||||
|
exports.downloadCodeQL = downloadCodeQL;
|
||||||
|
function getCodeQLURLVersion(url) {
|
||||||
|
const match = url.match(/\/codeql-bundle-(.*)\//);
|
||||||
|
if (match === null || match.length < 2) {
|
||||||
|
throw new Error(`Malformed tools url: ${url}. Version could not be inferred`);
|
||||||
|
}
|
||||||
|
return match[1];
|
||||||
|
}
|
||||||
|
exports.getCodeQLURLVersion = getCodeQLURLVersion;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
exports.convertToSemVer = convertToSemVer;
|
||||||
|
//# sourceMappingURL=setup-codeql.js.map
|
||||||
1
lib/setup-codeql.js.map
Normal file
1
lib/setup-codeql.js.map
Normal file
File diff suppressed because one or more lines are too long
116
lib/setup-codeql.test.js
generated
Normal file
116
lib/setup-codeql.test.js
generated
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
"use strict";
|
||||||
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
||||||
|
}) : (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 (mod) {
|
||||||
|
if (mod && mod.__esModule) return mod;
|
||||||
|
var result = {};
|
||||||
|
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||||
|
__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 path = __importStar(require("path"));
|
||||||
|
const ava_1 = __importDefault(require("ava"));
|
||||||
|
const sinon = __importStar(require("sinon"));
|
||||||
|
const actionsUtil = __importStar(require("./actions-util"));
|
||||||
|
const api = __importStar(require("./api-client"));
|
||||||
|
const logging_1 = require("./logging");
|
||||||
|
const setupCodeql = __importStar(require("./setup-codeql"));
|
||||||
|
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");
|
||||||
|
});
|
||||||
|
(0, ava_1.default)("parse codeql bundle url version", (t) => {
|
||||||
|
t.deepEqual(setupCodeql.getCodeQLURLVersion("https://github.com/.../codeql-bundle-20200601/..."), "20200601");
|
||||||
|
});
|
||||||
|
(0, ava_1.default)("convert to semver", (t) => {
|
||||||
|
const tests = {
|
||||||
|
"20200601": "0.0.0-20200601",
|
||||||
|
"20200601.0": "0.0.0-20200601.0",
|
||||||
|
"20200601.0.0": "20200601.0.0",
|
||||||
|
"1.2.3": "1.2.3",
|
||||||
|
"1.2.3-alpha": "1.2.3-alpha",
|
||||||
|
"1.2.3-beta.1": "1.2.3-beta.1",
|
||||||
|
};
|
||||||
|
for (const [version, expectedVersion] of Object.entries(tests)) {
|
||||||
|
try {
|
||||||
|
const parsedVersion = setupCodeql.convertToSemVer(version, (0, logging_1.getRunnerLogger)(true));
|
||||||
|
t.deepEqual(parsedVersion, expectedVersion);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
t.fail(e instanceof Error ? e.message : String(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
(0, ava_1.default)("getCodeQLActionRepository", (t) => {
|
||||||
|
const logger = (0, logging_1.getRunnerLogger)(true);
|
||||||
|
(0, util_1.initializeEnvironment)("1.2.3");
|
||||||
|
// isRunningLocalAction() === true
|
||||||
|
delete process.env["GITHUB_ACTION_REPOSITORY"];
|
||||||
|
process.env["RUNNER_TEMP"] = path.dirname(__dirname);
|
||||||
|
const repoLocalRunner = setupCodeql.getCodeQLActionRepository(logger);
|
||||||
|
t.deepEqual(repoLocalRunner, "github/codeql-action");
|
||||||
|
// isRunningLocalAction() === false
|
||||||
|
sinon.stub(actionsUtil, "isRunningLocalAction").returns(false);
|
||||||
|
process.env["GITHUB_ACTION_REPOSITORY"] = "xxx/yyy";
|
||||||
|
const repoEnv = setupCodeql.getCodeQLActionRepository(logger);
|
||||||
|
t.deepEqual(repoEnv, "xxx/yyy");
|
||||||
|
});
|
||||||
|
(0, ava_1.default)("findCodeQLBundleTagDotcomOnly() matches GitHub Release with marker file", async (t) => {
|
||||||
|
// Look for GitHub Releases in github/codeql-action
|
||||||
|
sinon.stub(actionsUtil, "isRunningLocalAction").resolves(true);
|
||||||
|
sinon.stub(api, "getApiClient").value(() => ({
|
||||||
|
repos: {
|
||||||
|
listReleases: sinon.stub().resolves(undefined),
|
||||||
|
},
|
||||||
|
paginate: sinon.stub().resolves([
|
||||||
|
{
|
||||||
|
assets: [
|
||||||
|
{
|
||||||
|
name: "cli-version-2.12.0.txt",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tag_name: "codeql-bundle-20230106",
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
}));
|
||||||
|
t.is(await setupCodeql.findCodeQLBundleTagDotcomOnly("2.12.0", (0, logging_1.getRunnerLogger)(true)), "codeql-bundle-20230106");
|
||||||
|
});
|
||||||
|
(0, ava_1.default)("findCodeQLBundleTagDotcomOnly() errors if no GitHub Release matches marker file", async (t) => {
|
||||||
|
// Look for GitHub Releases in github/codeql-action
|
||||||
|
sinon.stub(actionsUtil, "isRunningLocalAction").resolves(true);
|
||||||
|
sinon.stub(api, "getApiClient").value(() => ({
|
||||||
|
repos: {
|
||||||
|
listReleases: sinon.stub().resolves(undefined),
|
||||||
|
},
|
||||||
|
paginate: sinon.stub().resolves([
|
||||||
|
{
|
||||||
|
assets: [
|
||||||
|
{
|
||||||
|
name: "cli-version-2.12.0.txt",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tag_name: "codeql-bundle-20230106",
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
}));
|
||||||
|
await t.throwsAsync(async () => await setupCodeql.findCodeQLBundleTagDotcomOnly("2.12.1", (0, logging_1.getRunnerLogger)(true)), {
|
||||||
|
message: "Failed to find a CodeQL bundle release for CLI version 2.12.1.",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
//# sourceMappingURL=setup-codeql.test.js.map
|
||||||
1
lib/setup-codeql.test.js.map
Normal file
1
lib/setup-codeql.test.js.map
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"file":"setup-codeql.test.js","sourceRoot":"","sources":["../src/setup-codeql.test.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAA6B;AAE7B,8CAAuB;AACvB,6CAA+B;AAE/B,4DAA8C;AAC9C,kDAAoC;AACpC,uCAA4C;AAC5C,4DAA8C;AAC9C,mDAA6C;AAC7C,iCAA+C;AAE/C,IAAA,0BAAU,EAAC,aAAI,CAAC,CAAC;AAEjB,aAAI,CAAC,UAAU,CAAC,GAAG,EAAE;IACnB,IAAA,4BAAqB,EAAC,OAAO,CAAC,CAAC;AACjC,CAAC,CAAC,CAAC;AAEH,IAAA,aAAI,EAAC,iCAAiC,EAAE,CAAC,CAAC,EAAE,EAAE;IAC5C,CAAC,CAAC,SAAS,CACT,WAAW,CAAC,mBAAmB,CAC7B,mDAAmD,CACpD,EACD,UAAU,CACX,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,IAAA,aAAI,EAAC,mBAAmB,EAAE,CAAC,CAAC,EAAE,EAAE;IAC9B,MAAM,KAAK,GAAG;QACZ,UAAU,EAAE,gBAAgB;QAC5B,YAAY,EAAE,kBAAkB;QAChC,cAAc,EAAE,cAAc;QAC9B,OAAO,EAAE,OAAO;QAChB,aAAa,EAAE,aAAa;QAC5B,cAAc,EAAE,cAAc;KAC/B,CAAC;IAEF,KAAK,MAAM,CAAC,OAAO,EAAE,eAAe,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;QAC9D,IAAI;YACF,MAAM,aAAa,GAAG,WAAW,CAAC,eAAe,CAC/C,OAAO,EACP,IAAA,yBAAe,EAAC,IAAI,CAAC,CACtB,CAAC;YACF,CAAC,CAAC,SAAS,CAAC,aAAa,EAAE,eAAe,CAAC,CAAC;SAC7C;QAAC,OAAO,CAAC,EAAE;YACV,CAAC,CAAC,IAAI,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;SACpD;KACF;AACH,CAAC,CAAC,CAAC;AAEH,IAAA,aAAI,EAAC,2BAA2B,EAAE,CAAC,CAAC,EAAE,EAAE;IACtC,MAAM,MAAM,GAAG,IAAA,yBAAe,EAAC,IAAI,CAAC,CAAC;IAErC,IAAA,4BAAqB,EAAC,OAAO,CAAC,CAAC;IAE/B,kCAAkC;IAClC,OAAO,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;IAC/C,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACrD,MAAM,eAAe,GAAG,WAAW,CAAC,yBAAyB,CAAC,MAAM,CAAC,CAAC;IACtE,CAAC,CAAC,SAAS,CAAC,eAAe,EAAE,sBAAsB,CAAC,CAAC;IAErD,mCAAmC;IACnC,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,sBAAsB,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC/D,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,GAAG,SAAS,CAAC;IACpD,MAAM,OAAO,GAAG,WAAW,CAAC,yBAAyB,CAAC,MAAM,CAAC,CAAC;IAC9D,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;AAClC,CAAC,CAAC,CAAC;AAEH,IAAA,aAAI,EAAC,yEAAyE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IAC1F,mDAAmD;IACnD,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,sBAAsB,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC/D,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;QAC3C,KAAK,EAAE;YACL,YAAY,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC;SAC/C;QACD,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC;YAC9B;gBACE,MAAM,EAAE;oBACN;wBACE,IAAI,EAAE,wBAAwB;qBAC/B;iBACF;gBACD,QAAQ,EAAE,wBAAwB;aACnC;SACF,CAAC;KACH,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC,EAAE,CACF,MAAM,WAAW,CAAC,6BAA6B,CAC7C,QAAQ,EACR,IAAA,yBAAe,EAAC,IAAI,CAAC,CACtB,EACD,wBAAwB,CACzB,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,IAAA,aAAI,EAAC,iFAAiF,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IAClG,mDAAmD;IACnD,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,sBAAsB,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC/D,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;QAC3C,KAAK,EAAE;YACL,YAAY,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC;SAC/C;QACD,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC;YAC9B;gBACE,MAAM,EAAE;oBACN;wBACE,IAAI,EAAE,wBAAwB;qBAC/B;iBACF;gBACD,QAAQ,EAAE,wBAAwB;aACnC;SACF,CAAC;KACH,CAAC,CAAC,CAAC;IACJ,MAAM,CAAC,CAAC,WAAW,CACjB,KAAK,IAAI,EAAE,CACT,MAAM,WAAW,CAAC,6BAA6B,CAC7C,QAAQ,EACR,IAAA,yBAAe,EAAC,IAAI,CAAC,CACtB,EACH;QACE,OAAO,EAAE,gEAAgE;KAC1E,CACF,CAAC;AACJ,CAAC,CAAC,CAAC"}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as path from "path";
|
import path from "path";
|
||||||
|
|
||||||
import * as toolrunner from "@actions/exec/lib/toolrunner";
|
import * as toolrunner from "@actions/exec/lib/toolrunner";
|
||||||
import * as toolcache from "@actions/tool-cache";
|
import * as toolcache from "@actions/tool-cache";
|
||||||
|
|
@ -11,15 +11,14 @@ import nock from "nock";
|
||||||
import * as sinon from "sinon";
|
import * as sinon from "sinon";
|
||||||
|
|
||||||
import * as actionsUtil from "./actions-util";
|
import * as actionsUtil from "./actions-util";
|
||||||
import * as api from "./api-client";
|
|
||||||
import { GitHubApiDetails } from "./api-client";
|
import { GitHubApiDetails } from "./api-client";
|
||||||
import * as codeql from "./codeql";
|
import * as codeql from "./codeql";
|
||||||
import { AugmentationProperties, Config } from "./config-utils";
|
import { AugmentationProperties, Config } from "./config-utils";
|
||||||
import * as defaults from "./defaults.json";
|
import * as defaults from "./defaults.json"; // Referenced from codeql-action-sync-tool!
|
||||||
import { Feature, featureConfig } from "./feature-flags";
|
import { Feature, featureConfig } from "./feature-flags";
|
||||||
import { Language } from "./languages";
|
import { Language } from "./languages";
|
||||||
import { getRunnerLogger } from "./logging";
|
import { getRunnerLogger } from "./logging";
|
||||||
import { setupTests, setupActionsVars, createFeatures } from "./testing-utils";
|
import { setupTests, createFeatures, setupActionsVars } from "./testing-utils";
|
||||||
import * as util from "./util";
|
import * as util from "./util";
|
||||||
import { initializeEnvironment } from "./util";
|
import { initializeEnvironment } from "./util";
|
||||||
|
|
||||||
|
|
@ -401,38 +400,6 @@ test("download codeql bundle from github ae endpoint", async (t) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("parse codeql bundle url version", (t) => {
|
|
||||||
t.deepEqual(
|
|
||||||
codeql.getCodeQLURLVersion(
|
|
||||||
"https://github.com/.../codeql-bundle-20200601/..."
|
|
||||||
),
|
|
||||||
"20200601"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("convert to semver", (t) => {
|
|
||||||
const tests = {
|
|
||||||
"20200601": "0.0.0-20200601",
|
|
||||||
"20200601.0": "0.0.0-20200601.0",
|
|
||||||
"20200601.0.0": "20200601.0.0",
|
|
||||||
"1.2.3": "1.2.3",
|
|
||||||
"1.2.3-alpha": "1.2.3-alpha",
|
|
||||||
"1.2.3-beta.1": "1.2.3-beta.1",
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const [version, expectedVersion] of Object.entries(tests)) {
|
|
||||||
try {
|
|
||||||
const parsedVersion = codeql.convertToSemVer(
|
|
||||||
version,
|
|
||||||
getRunnerLogger(true)
|
|
||||||
);
|
|
||||||
t.deepEqual(parsedVersion, expectedVersion);
|
|
||||||
} catch (e) {
|
|
||||||
t.fail(e instanceof Error ? e.message : String(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test("getExtraOptions works for explicit paths", (t) => {
|
test("getExtraOptions works for explicit paths", (t) => {
|
||||||
t.deepEqual(codeql.getExtraOptions({}, ["foo"], []), []);
|
t.deepEqual(codeql.getExtraOptions({}, ["foo"], []), []);
|
||||||
|
|
||||||
|
|
@ -474,24 +441,6 @@ test("getExtraOptions throws for bad content", (t) => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("getCodeQLActionRepository", (t) => {
|
|
||||||
const logger = getRunnerLogger(true);
|
|
||||||
|
|
||||||
initializeEnvironment("1.2.3");
|
|
||||||
|
|
||||||
// isRunningLocalAction() === true
|
|
||||||
delete process.env["GITHUB_ACTION_REPOSITORY"];
|
|
||||||
process.env["RUNNER_TEMP"] = path.dirname(__dirname);
|
|
||||||
const repoLocalRunner = codeql.getCodeQLActionRepository(logger);
|
|
||||||
t.deepEqual(repoLocalRunner, "github/codeql-action");
|
|
||||||
|
|
||||||
// isRunningLocalAction() === false
|
|
||||||
sinon.stub(actionsUtil, "isRunningLocalAction").returns(false);
|
|
||||||
process.env["GITHUB_ACTION_REPOSITORY"] = "xxx/yyy";
|
|
||||||
const repoEnv = codeql.getCodeQLActionRepository(logger);
|
|
||||||
t.deepEqual(repoEnv, "xxx/yyy");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("databaseInterpretResults() does not set --sarif-add-query-help for 2.7.0", async (t) => {
|
test("databaseInterpretResults() does not set --sarif-add-query-help for 2.7.0", async (t) => {
|
||||||
const runnerConstructorStub = stubToolRunnerConstructor();
|
const runnerConstructorStub = stubToolRunnerConstructor();
|
||||||
const codeqlObject = await codeql.getCodeQLForTesting();
|
const codeqlObject = await codeql.getCodeQLForTesting();
|
||||||
|
|
@ -930,60 +879,6 @@ test("databaseInterpretResults() does not set --sarif-add-baseline-file-info for
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("findCodeQLBundleTagDotcomOnly() matches GitHub Release with marker file", async (t) => {
|
|
||||||
// Look for GitHub Releases in github/codeql-action
|
|
||||||
sinon.stub(actionsUtil, "isRunningLocalAction").resolves(true);
|
|
||||||
sinon.stub(api, "getApiClient").value(() => ({
|
|
||||||
repos: {
|
|
||||||
listReleases: sinon.stub().resolves(undefined),
|
|
||||||
},
|
|
||||||
paginate: sinon.stub().resolves([
|
|
||||||
{
|
|
||||||
assets: [
|
|
||||||
{
|
|
||||||
name: "cli-version-2.12.0.txt",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
tag_name: "codeql-bundle-20230106",
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
}));
|
|
||||||
t.is(
|
|
||||||
await codeql.findCodeQLBundleTagDotcomOnly("2.12.0", getRunnerLogger(true)),
|
|
||||||
"codeql-bundle-20230106"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("findCodeQLBundleTagDotcomOnly() errors if no GitHub Release matches marker file", async (t) => {
|
|
||||||
// Look for GitHub Releases in github/codeql-action
|
|
||||||
sinon.stub(actionsUtil, "isRunningLocalAction").resolves(true);
|
|
||||||
sinon.stub(api, "getApiClient").value(() => ({
|
|
||||||
repos: {
|
|
||||||
listReleases: sinon.stub().resolves(undefined),
|
|
||||||
},
|
|
||||||
paginate: sinon.stub().resolves([
|
|
||||||
{
|
|
||||||
assets: [
|
|
||||||
{
|
|
||||||
name: "cli-version-2.12.0.txt",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
tag_name: "codeql-bundle-20230106",
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
}));
|
|
||||||
await t.throwsAsync(
|
|
||||||
async () =>
|
|
||||||
await codeql.findCodeQLBundleTagDotcomOnly(
|
|
||||||
"2.12.1",
|
|
||||||
getRunnerLogger(true)
|
|
||||||
),
|
|
||||||
{
|
|
||||||
message: "Failed to find a CodeQL bundle release for CLI version 2.12.1.",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export function stubToolRunnerConstructor(): sinon.SinonStub<
|
export function stubToolRunnerConstructor(): sinon.SinonStub<
|
||||||
any[],
|
any[],
|
||||||
toolrunner.ToolRunner
|
toolrunner.ToolRunner
|
||||||
|
|
|
||||||
336
src/codeql.ts
336
src/codeql.ts
|
|
@ -1,29 +1,25 @@
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { OutgoingHttpHeaders } from "http";
|
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
|
|
||||||
import * as toolrunner from "@actions/exec/lib/toolrunner";
|
import * as toolrunner from "@actions/exec/lib/toolrunner";
|
||||||
import * as toolcache from "@actions/tool-cache";
|
import * as toolcache from "@actions/tool-cache";
|
||||||
import { default as deepEqual } from "fast-deep-equal";
|
|
||||||
import * as yaml from "js-yaml";
|
import * as yaml from "js-yaml";
|
||||||
import * as semver from "semver";
|
|
||||||
import { v4 as uuidV4 } from "uuid";
|
|
||||||
|
|
||||||
import { getOptionalInput, isRunningLocalAction } from "./actions-util";
|
import { getOptionalInput } from "./actions-util";
|
||||||
import * as api from "./api-client";
|
import * as api from "./api-client";
|
||||||
import { Config } from "./config-utils";
|
import { Config } from "./config-utils";
|
||||||
import * as defaults from "./defaults.json"; // Referenced from codeql-action-sync-tool!
|
|
||||||
import { errorMatchers } from "./error-matcher";
|
import { errorMatchers } from "./error-matcher";
|
||||||
import { FeatureEnablement } from "./feature-flags";
|
import { FeatureEnablement } from "./feature-flags";
|
||||||
import { isTracedLanguage, Language } from "./languages";
|
import { isTracedLanguage, Language } from "./languages";
|
||||||
import { Logger } from "./logging";
|
import { Logger } from "./logging";
|
||||||
|
import { downloadCodeQL, getCodeQLSource } from "./setup-codeql";
|
||||||
import { toolrunnerErrorCatcher } from "./toolrunner-error-catcher";
|
import { toolrunnerErrorCatcher } from "./toolrunner-error-catcher";
|
||||||
import {
|
import {
|
||||||
getTrapCachingExtractorConfigArgs,
|
getTrapCachingExtractorConfigArgs,
|
||||||
getTrapCachingExtractorConfigArgsForLang,
|
getTrapCachingExtractorConfigArgsForLang,
|
||||||
} from "./trap-caching";
|
} from "./trap-caching";
|
||||||
import * as util from "./util";
|
import * as util from "./util";
|
||||||
import { assertNever, isGoodVersion } from "./util";
|
import { assertNever } from "./util";
|
||||||
|
|
||||||
type Options = Array<string | number | boolean>;
|
type Options = Array<string | number | boolean>;
|
||||||
|
|
||||||
|
|
@ -232,9 +228,6 @@ interface PackDownloadItem {
|
||||||
*/
|
*/
|
||||||
let cachedCodeQL: CodeQL | undefined = undefined;
|
let cachedCodeQL: CodeQL | undefined = undefined;
|
||||||
|
|
||||||
const CODEQL_BUNDLE_VERSION = defaults.bundleVersion;
|
|
||||||
export const CODEQL_DEFAULT_ACTION_REPOSITORY = "github/codeql-action";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The oldest version of CodeQL that the Action will run with. This should be
|
* 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
|
* at least three minor versions behind the current version and must include the
|
||||||
|
|
@ -286,301 +279,6 @@ export const CODEQL_VERSION_ML_POWERED_QUERIES_WINDOWS = "2.9.0";
|
||||||
*/
|
*/
|
||||||
export const CODEQL_VERSION_BETTER_RESOLVE_LANGUAGES = "2.10.3";
|
export const CODEQL_VERSION_BETTER_RESOLVE_LANGUAGES = "2.10.3";
|
||||||
|
|
||||||
function getCodeQLBundleName(): string {
|
|
||||||
let platform: string;
|
|
||||||
if (process.platform === "win32") {
|
|
||||||
platform = "win64";
|
|
||||||
} else if (process.platform === "linux") {
|
|
||||||
platform = "linux64";
|
|
||||||
} else if (process.platform === "darwin") {
|
|
||||||
platform = "osx64";
|
|
||||||
} else {
|
|
||||||
return "codeql-bundle.tar.gz";
|
|
||||||
}
|
|
||||||
return `codeql-bundle-${platform}.tar.gz`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCodeQLActionRepository(logger: Logger): string {
|
|
||||||
if (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 CODEQL_DEFAULT_ACTION_REPOSITORY;
|
|
||||||
}
|
|
||||||
|
|
||||||
return util.getRequiredEnvParam("GITHUB_ACTION_REPOSITORY");
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function findCodeQLBundleTagDotcomOnly(
|
|
||||||
cliVersion: string,
|
|
||||||
logger: Logger
|
|
||||||
): Promise<string> {
|
|
||||||
const apiClient = api.getApiClient();
|
|
||||||
const codeQLActionRepository = getCodeQLActionRepository(logger);
|
|
||||||
const releases = await apiClient.paginate(apiClient.repos.listReleases, {
|
|
||||||
owner: codeQLActionRepository.split("/")[0],
|
|
||||||
repo: codeQLActionRepository.split("/")[1],
|
|
||||||
});
|
|
||||||
logger.debug(`Found ${releases.length} releases.`);
|
|
||||||
|
|
||||||
for (const release of releases) {
|
|
||||||
const cliVersionFileVersions = release.assets
|
|
||||||
.map((asset) => asset.name.match(/cli-version-(.*)\.txt/)?.[1])
|
|
||||||
.filter((v) => v)
|
|
||||||
.map((v) => v as string);
|
|
||||||
|
|
||||||
if (cliVersionFileVersions.length === 0) {
|
|
||||||
logger.debug(
|
|
||||||
`Ignoring release ${release.tag_name} with no CLI version marker file.`
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cliVersionFileVersions.length > 1) {
|
|
||||||
logger.warning(
|
|
||||||
`Ignoring release ${release.tag_name} with multiple CLI version marker files.`
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cliVersionFileVersions[0] === cliVersion) {
|
|
||||||
return release.tag_name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new Error(
|
|
||||||
`Failed to find a CodeQL bundle release for CLI version ${cliVersion}.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getCodeQLBundleDownloadURL(
|
|
||||||
apiDetails: api.GitHubApiDetails,
|
|
||||||
variant: util.GitHubVariant,
|
|
||||||
logger: Logger
|
|
||||||
): Promise<string> {
|
|
||||||
const codeQLActionRepository = getCodeQLActionRepository(logger);
|
|
||||||
const potentialDownloadSources = [
|
|
||||||
// This GitHub instance, and this Action.
|
|
||||||
[apiDetails.url, codeQLActionRepository],
|
|
||||||
// This GitHub instance, and the canonical Action.
|
|
||||||
[apiDetails.url, CODEQL_DEFAULT_ACTION_REPOSITORY],
|
|
||||||
// GitHub.com, and the canonical Action.
|
|
||||||
[util.GITHUB_DOTCOM_URL, 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) => deepEqual(source, other));
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const codeQLBundleName = getCodeQLBundleName();
|
|
||||||
if (variant === util.GitHubVariant.GHAE) {
|
|
||||||
try {
|
|
||||||
const release = await api
|
|
||||||
.getApiClient()
|
|
||||||
.request("GET /enterprise/code-scanning/codeql-bundle/find/{tag}", {
|
|
||||||
tag: CODEQL_BUNDLE_VERSION,
|
|
||||||
});
|
|
||||||
const assetID = release.data.assets[codeQLBundleName];
|
|
||||||
if (assetID !== undefined) {
|
|
||||||
const download = await api
|
|
||||||
.getApiClient()
|
|
||||||
.request(
|
|
||||||
"GET /enterprise/code-scanning/codeql-bundle/download/{asset_id}",
|
|
||||||
{ asset_id: assetID }
|
|
||||||
);
|
|
||||||
const downloadURL = download.data.url;
|
|
||||||
logger.info(
|
|
||||||
`Found CodeQL bundle at GitHub AE endpoint with URL ${downloadURL}.`
|
|
||||||
);
|
|
||||||
return downloadURL;
|
|
||||||
} else {
|
|
||||||
logger.info(
|
|
||||||
`Attempted to fetch bundle from GitHub AE endpoint but the bundle ${codeQLBundleName} was not found in the assets ${JSON.stringify(
|
|
||||||
release.data.assets
|
|
||||||
)}.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
logger.info(
|
|
||||||
`Attempted to fetch bundle from GitHub AE endpoint but got error ${e}.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 === CODEQL_DEFAULT_ACTION_REPOSITORY
|
|
||||||
) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const [repositoryOwner, repositoryName] = repository.split("/");
|
|
||||||
try {
|
|
||||||
const release = await api.getApiClient().repos.getReleaseByTag({
|
|
||||||
owner: repositoryOwner,
|
|
||||||
repo: repositoryName,
|
|
||||||
tag: CODEQL_BUNDLE_VERSION,
|
|
||||||
});
|
|
||||||
for (const asset of release.data.assets) {
|
|
||||||
if (asset.name === codeQLBundleName) {
|
|
||||||
logger.info(
|
|
||||||
`Found CodeQL bundle in ${downloadSource[1]} on ${downloadSource[0]} with URL ${asset.url}.`
|
|
||||||
);
|
|
||||||
return asset.url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
logger.info(
|
|
||||||
`Looked for CodeQL bundle in ${downloadSource[1]} on ${downloadSource[0]} but got error ${e}.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return `https://github.com/${CODEQL_DEFAULT_ACTION_REPOSITORY}/releases/download/${CODEQL_BUNDLE_VERSION}/${codeQLBundleName}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
type CodeQLToolsSource =
|
|
||||||
| { codeqlTarPath: string; sourceType: "local"; toolsVersion: "local" }
|
|
||||||
| {
|
|
||||||
codeqlFolder: string;
|
|
||||||
sourceType: "toolcache";
|
|
||||||
toolsVersion: string;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
codeqlURL: string;
|
|
||||||
semanticVersion: string;
|
|
||||||
sourceType: "download";
|
|
||||||
toolsVersion: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
async function getCodeQLSource(
|
|
||||||
toolsInput: string | undefined,
|
|
||||||
bypassToolcache: boolean,
|
|
||||||
apiDetails: api.GitHubApiDetails,
|
|
||||||
variant: util.GitHubVariant,
|
|
||||||
logger: Logger
|
|
||||||
): Promise<CodeQLToolsSource> {
|
|
||||||
if (toolsInput && toolsInput !== "latest" && !toolsInput.startsWith("http")) {
|
|
||||||
return {
|
|
||||||
codeqlTarPath: toolsInput,
|
|
||||||
sourceType: "local",
|
|
||||||
toolsVersion: "local",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const forceLatestReason =
|
|
||||||
// We use the special value of 'latest' to prioritize the version in the
|
|
||||||
// defaults over any pinned cached version.
|
|
||||||
toolsInput === "latest"
|
|
||||||
? '"tools: latest" was requested'
|
|
||||||
: // If the user hasn't requested a particular CodeQL version, then bypass
|
|
||||||
// the toolcache when the appropriate feature is enabled. This
|
|
||||||
// allows us to quickly rollback a broken bundle that has made its way
|
|
||||||
// into the toolcache.
|
|
||||||
toolsInput === undefined && bypassToolcache
|
|
||||||
? "a specific version of CodeQL was not requested and the bypass toolcache feature is enabled"
|
|
||||||
: undefined;
|
|
||||||
const forceLatest = forceLatestReason !== undefined;
|
|
||||||
if (forceLatest) {
|
|
||||||
logger.debug(
|
|
||||||
`Forcing the latest version of the CodeQL tools since ${forceLatestReason}.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const codeqlURL = forceLatest ? undefined : toolsInput;
|
|
||||||
const requestedSemVer = convertToSemVer(
|
|
||||||
getCodeQLURLVersion(codeqlURL || `/${CODEQL_BUNDLE_VERSION}/`),
|
|
||||||
logger
|
|
||||||
);
|
|
||||||
|
|
||||||
// If we find the specified version, we always use that.
|
|
||||||
const codeqlFolder = toolcache.find("CodeQL", requestedSemVer);
|
|
||||||
if (codeqlFolder) {
|
|
||||||
return {
|
|
||||||
codeqlFolder,
|
|
||||||
sourceType: "toolcache",
|
|
||||||
toolsVersion: requestedSemVer,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we don't find the requested version, in some cases 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 (!codeqlURL && !forceLatest) {
|
|
||||||
const codeqlVersions = toolcache.findAllVersions("CodeQL");
|
|
||||||
if (codeqlVersions.length === 1 && isGoodVersion(codeqlVersions[0])) {
|
|
||||||
const tmpCodeqlFolder = toolcache.find("CodeQL", codeqlVersions[0]);
|
|
||||||
if (fs.existsSync(path.join(tmpCodeqlFolder, "pinned-version"))) {
|
|
||||||
logger.debug(
|
|
||||||
`CodeQL in cache overriding the default ${CODEQL_BUNDLE_VERSION}`
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
codeqlFolder: tmpCodeqlFolder,
|
|
||||||
sourceType: "toolcache",
|
|
||||||
toolsVersion: codeqlVersions[0],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
codeqlURL:
|
|
||||||
codeqlURL ||
|
|
||||||
(await getCodeQLBundleDownloadURL(apiDetails, variant, logger)),
|
|
||||||
semanticVersion: requestedSemVer,
|
|
||||||
sourceType: "download",
|
|
||||||
toolsVersion:
|
|
||||||
semver.prerelease(requestedSemVer)?.join(".") || requestedSemVer,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function downloadCodeQL(
|
|
||||||
codeqlURL: string,
|
|
||||||
semanticVersion: string,
|
|
||||||
apiDetails: api.GitHubApiDetails,
|
|
||||||
tempDir: string,
|
|
||||||
logger: Logger
|
|
||||||
): Promise<string> {
|
|
||||||
const parsedCodeQLURL = new URL(codeqlURL);
|
|
||||||
const searchParams = new URLSearchParams(parsedCodeQLURL.search);
|
|
||||||
const headers: OutgoingHttpHeaders = {
|
|
||||||
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.
|
|
||||||
if (searchParams.has("token")) {
|
|
||||||
logger.debug("CodeQL tools URL contains an authorization token.");
|
|
||||||
} else if (codeqlURL.startsWith(`${apiDetails.url}/`)) {
|
|
||||||
logger.debug("Providing an authorization token to download CodeQL tools.");
|
|
||||||
headers.authorization = `token ${apiDetails.auth}`;
|
|
||||||
} else {
|
|
||||||
logger.debug("Downloading CodeQL tools without an authorization token.");
|
|
||||||
}
|
|
||||||
logger.info(
|
|
||||||
`Downloading CodeQL tools from ${codeqlURL}. This may take a while.`
|
|
||||||
);
|
|
||||||
|
|
||||||
const dest = path.join(tempDir, uuidV4());
|
|
||||||
const finalHeaders = Object.assign(
|
|
||||||
{ "User-Agent": "CodeQL Action" },
|
|
||||||
headers
|
|
||||||
);
|
|
||||||
const codeqlPath = await toolcache.downloadTool(
|
|
||||||
codeqlURL,
|
|
||||||
dest,
|
|
||||||
undefined,
|
|
||||||
finalHeaders
|
|
||||||
);
|
|
||||||
logger.debug(`CodeQL bundle download to ${codeqlPath} complete.`);
|
|
||||||
|
|
||||||
const codeqlExtracted = await toolcache.extractTar(codeqlPath);
|
|
||||||
return await toolcache.cacheDir(codeqlExtracted, "CodeQL", semanticVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up CodeQL CLI access.
|
* Set up CodeQL CLI access.
|
||||||
*
|
*
|
||||||
|
|
@ -650,32 +348,6 @@ export async function setupCodeQL(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCodeQLURLVersion(url: string): string {
|
|
||||||
const match = url.match(/\/codeql-bundle-(.*)\//);
|
|
||||||
if (match === null || match.length < 2) {
|
|
||||||
throw new Error(
|
|
||||||
`Malformed tools url: ${url}. Version could not be inferred`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return match[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function convertToSemVer(version: string, logger: Logger): string {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use the CodeQL executable located at the given path.
|
* Use the CodeQL executable located at the given path.
|
||||||
*/
|
*/
|
||||||
|
|
@ -783,7 +455,7 @@ export async function getCodeQLForTesting(
|
||||||
* version requirement. Must be set to true outside tests.
|
* version requirement. Must be set to true outside tests.
|
||||||
* @returns A new CodeQL object
|
* @returns A new CodeQL object
|
||||||
*/
|
*/
|
||||||
async function getCodeQLForCmd(
|
export async function getCodeQLForCmd(
|
||||||
cmd: string,
|
cmd: string,
|
||||||
checkVersion: boolean
|
checkVersion: boolean
|
||||||
): Promise<CodeQL> {
|
): Promise<CodeQL> {
|
||||||
|
|
|
||||||
124
src/setup-codeql.test.ts
Normal file
124
src/setup-codeql.test.ts
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
import * as path from "path";
|
||||||
|
|
||||||
|
import test from "ava";
|
||||||
|
import * as sinon from "sinon";
|
||||||
|
|
||||||
|
import * as actionsUtil from "./actions-util";
|
||||||
|
import * as api from "./api-client";
|
||||||
|
import { getRunnerLogger } from "./logging";
|
||||||
|
import * as setupCodeql from "./setup-codeql";
|
||||||
|
import { setupTests } from "./testing-utils";
|
||||||
|
import { initializeEnvironment } from "./util";
|
||||||
|
|
||||||
|
setupTests(test);
|
||||||
|
|
||||||
|
test.beforeEach(() => {
|
||||||
|
initializeEnvironment("1.2.3");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("parse codeql bundle url version", (t) => {
|
||||||
|
t.deepEqual(
|
||||||
|
setupCodeql.getCodeQLURLVersion(
|
||||||
|
"https://github.com/.../codeql-bundle-20200601/..."
|
||||||
|
),
|
||||||
|
"20200601"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("convert to semver", (t) => {
|
||||||
|
const tests = {
|
||||||
|
"20200601": "0.0.0-20200601",
|
||||||
|
"20200601.0": "0.0.0-20200601.0",
|
||||||
|
"20200601.0.0": "20200601.0.0",
|
||||||
|
"1.2.3": "1.2.3",
|
||||||
|
"1.2.3-alpha": "1.2.3-alpha",
|
||||||
|
"1.2.3-beta.1": "1.2.3-beta.1",
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [version, expectedVersion] of Object.entries(tests)) {
|
||||||
|
try {
|
||||||
|
const parsedVersion = setupCodeql.convertToSemVer(
|
||||||
|
version,
|
||||||
|
getRunnerLogger(true)
|
||||||
|
);
|
||||||
|
t.deepEqual(parsedVersion, expectedVersion);
|
||||||
|
} catch (e) {
|
||||||
|
t.fail(e instanceof Error ? e.message : String(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getCodeQLActionRepository", (t) => {
|
||||||
|
const logger = getRunnerLogger(true);
|
||||||
|
|
||||||
|
initializeEnvironment("1.2.3");
|
||||||
|
|
||||||
|
// isRunningLocalAction() === true
|
||||||
|
delete process.env["GITHUB_ACTION_REPOSITORY"];
|
||||||
|
process.env["RUNNER_TEMP"] = path.dirname(__dirname);
|
||||||
|
const repoLocalRunner = setupCodeql.getCodeQLActionRepository(logger);
|
||||||
|
t.deepEqual(repoLocalRunner, "github/codeql-action");
|
||||||
|
|
||||||
|
// isRunningLocalAction() === false
|
||||||
|
sinon.stub(actionsUtil, "isRunningLocalAction").returns(false);
|
||||||
|
process.env["GITHUB_ACTION_REPOSITORY"] = "xxx/yyy";
|
||||||
|
const repoEnv = setupCodeql.getCodeQLActionRepository(logger);
|
||||||
|
t.deepEqual(repoEnv, "xxx/yyy");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("findCodeQLBundleTagDotcomOnly() matches GitHub Release with marker file", async (t) => {
|
||||||
|
// Look for GitHub Releases in github/codeql-action
|
||||||
|
sinon.stub(actionsUtil, "isRunningLocalAction").resolves(true);
|
||||||
|
sinon.stub(api, "getApiClient").value(() => ({
|
||||||
|
repos: {
|
||||||
|
listReleases: sinon.stub().resolves(undefined),
|
||||||
|
},
|
||||||
|
paginate: sinon.stub().resolves([
|
||||||
|
{
|
||||||
|
assets: [
|
||||||
|
{
|
||||||
|
name: "cli-version-2.12.0.txt",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tag_name: "codeql-bundle-20230106",
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
}));
|
||||||
|
t.is(
|
||||||
|
await setupCodeql.findCodeQLBundleTagDotcomOnly(
|
||||||
|
"2.12.0",
|
||||||
|
getRunnerLogger(true)
|
||||||
|
),
|
||||||
|
"codeql-bundle-20230106"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("findCodeQLBundleTagDotcomOnly() errors if no GitHub Release matches marker file", async (t) => {
|
||||||
|
// Look for GitHub Releases in github/codeql-action
|
||||||
|
sinon.stub(actionsUtil, "isRunningLocalAction").resolves(true);
|
||||||
|
sinon.stub(api, "getApiClient").value(() => ({
|
||||||
|
repos: {
|
||||||
|
listReleases: sinon.stub().resolves(undefined),
|
||||||
|
},
|
||||||
|
paginate: sinon.stub().resolves([
|
||||||
|
{
|
||||||
|
assets: [
|
||||||
|
{
|
||||||
|
name: "cli-version-2.12.0.txt",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tag_name: "codeql-bundle-20230106",
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
}));
|
||||||
|
await t.throwsAsync(
|
||||||
|
async () =>
|
||||||
|
await setupCodeql.findCodeQLBundleTagDotcomOnly(
|
||||||
|
"2.12.1",
|
||||||
|
getRunnerLogger(true)
|
||||||
|
),
|
||||||
|
{
|
||||||
|
message: "Failed to find a CodeQL bundle release for CLI version 2.12.1.",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
339
src/setup-codeql.ts
Normal file
339
src/setup-codeql.ts
Normal file
|
|
@ -0,0 +1,339 @@
|
||||||
|
import * as fs from "fs";
|
||||||
|
import { OutgoingHttpHeaders } from "http";
|
||||||
|
import * as path from "path";
|
||||||
|
|
||||||
|
import * as toolcache from "@actions/tool-cache";
|
||||||
|
import { default as deepEqual } from "fast-deep-equal";
|
||||||
|
import * as semver from "semver";
|
||||||
|
import { v4 as uuidV4 } from "uuid";
|
||||||
|
|
||||||
|
import { isRunningLocalAction } from "./actions-util";
|
||||||
|
import * as api from "./api-client";
|
||||||
|
import * as defaults from "./defaults.json"; // Referenced from codeql-action-sync-tool!
|
||||||
|
import { Logger } from "./logging";
|
||||||
|
import * as util from "./util";
|
||||||
|
import { isGoodVersion } from "./util";
|
||||||
|
|
||||||
|
const CODEQL_BUNDLE_VERSION = defaults.bundleVersion;
|
||||||
|
export const CODEQL_DEFAULT_ACTION_REPOSITORY = "github/codeql-action";
|
||||||
|
|
||||||
|
function getCodeQLBundleName(): string {
|
||||||
|
let platform: string;
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
platform = "win64";
|
||||||
|
} else if (process.platform === "linux") {
|
||||||
|
platform = "linux64";
|
||||||
|
} else if (process.platform === "darwin") {
|
||||||
|
platform = "osx64";
|
||||||
|
} else {
|
||||||
|
return "codeql-bundle.tar.gz";
|
||||||
|
}
|
||||||
|
return `codeql-bundle-${platform}.tar.gz`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCodeQLActionRepository(logger: Logger): string {
|
||||||
|
if (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 CODEQL_DEFAULT_ACTION_REPOSITORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.getRequiredEnvParam("GITHUB_ACTION_REPOSITORY");
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function findCodeQLBundleTagDotcomOnly(
|
||||||
|
cliVersion: string,
|
||||||
|
logger: Logger
|
||||||
|
): Promise<string> {
|
||||||
|
const apiClient = api.getApiClient();
|
||||||
|
const codeQLActionRepository = getCodeQLActionRepository(logger);
|
||||||
|
const releases = await apiClient.paginate(apiClient.repos.listReleases, {
|
||||||
|
owner: codeQLActionRepository.split("/")[0],
|
||||||
|
repo: codeQLActionRepository.split("/")[1],
|
||||||
|
});
|
||||||
|
logger.debug(`Found ${releases.length} releases.`);
|
||||||
|
|
||||||
|
for (const release of releases) {
|
||||||
|
const cliVersionFileVersions = release.assets
|
||||||
|
.map((asset) => asset.name.match(/cli-version-(.*)\.txt/)?.[1])
|
||||||
|
.filter((v) => v)
|
||||||
|
.map((v) => v as string);
|
||||||
|
|
||||||
|
if (cliVersionFileVersions.length === 0) {
|
||||||
|
logger.debug(
|
||||||
|
`Ignoring release ${release.tag_name} with no CLI version marker file.`
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (cliVersionFileVersions.length > 1) {
|
||||||
|
logger.warning(
|
||||||
|
`Ignoring release ${release.tag_name} with multiple CLI version marker files.`
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (cliVersionFileVersions[0] === cliVersion) {
|
||||||
|
return release.tag_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
`Failed to find a CodeQL bundle release for CLI version ${cliVersion}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getCodeQLBundleDownloadURL(
|
||||||
|
apiDetails: api.GitHubApiDetails,
|
||||||
|
variant: util.GitHubVariant,
|
||||||
|
logger: Logger
|
||||||
|
): Promise<string> {
|
||||||
|
const codeQLActionRepository = getCodeQLActionRepository(logger);
|
||||||
|
const potentialDownloadSources = [
|
||||||
|
// This GitHub instance, and this Action.
|
||||||
|
[apiDetails.url, codeQLActionRepository],
|
||||||
|
// This GitHub instance, and the canonical Action.
|
||||||
|
[apiDetails.url, CODEQL_DEFAULT_ACTION_REPOSITORY],
|
||||||
|
// GitHub.com, and the canonical Action.
|
||||||
|
[util.GITHUB_DOTCOM_URL, 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) => deepEqual(source, other));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const codeQLBundleName = getCodeQLBundleName();
|
||||||
|
if (variant === util.GitHubVariant.GHAE) {
|
||||||
|
try {
|
||||||
|
const release = await api
|
||||||
|
.getApiClient()
|
||||||
|
.request("GET /enterprise/code-scanning/codeql-bundle/find/{tag}", {
|
||||||
|
tag: CODEQL_BUNDLE_VERSION,
|
||||||
|
});
|
||||||
|
const assetID = release.data.assets[codeQLBundleName];
|
||||||
|
if (assetID !== undefined) {
|
||||||
|
const download = await api
|
||||||
|
.getApiClient()
|
||||||
|
.request(
|
||||||
|
"GET /enterprise/code-scanning/codeql-bundle/download/{asset_id}",
|
||||||
|
{ asset_id: assetID }
|
||||||
|
);
|
||||||
|
const downloadURL = download.data.url;
|
||||||
|
logger.info(
|
||||||
|
`Found CodeQL bundle at GitHub AE endpoint with URL ${downloadURL}.`
|
||||||
|
);
|
||||||
|
return downloadURL;
|
||||||
|
} else {
|
||||||
|
logger.info(
|
||||||
|
`Attempted to fetch bundle from GitHub AE endpoint but the bundle ${codeQLBundleName} was not found in the assets ${JSON.stringify(
|
||||||
|
release.data.assets
|
||||||
|
)}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.info(
|
||||||
|
`Attempted to fetch bundle from GitHub AE endpoint but got error ${e}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 === CODEQL_DEFAULT_ACTION_REPOSITORY
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const [repositoryOwner, repositoryName] = repository.split("/");
|
||||||
|
try {
|
||||||
|
const release = await api.getApiClient().repos.getReleaseByTag({
|
||||||
|
owner: repositoryOwner,
|
||||||
|
repo: repositoryName,
|
||||||
|
tag: CODEQL_BUNDLE_VERSION,
|
||||||
|
});
|
||||||
|
for (const asset of release.data.assets) {
|
||||||
|
if (asset.name === codeQLBundleName) {
|
||||||
|
logger.info(
|
||||||
|
`Found CodeQL bundle in ${downloadSource[1]} on ${downloadSource[0]} with URL ${asset.url}.`
|
||||||
|
);
|
||||||
|
return asset.url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.info(
|
||||||
|
`Looked for CodeQL bundle in ${downloadSource[1]} on ${downloadSource[0]} but got error ${e}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return `https://github.com/${CODEQL_DEFAULT_ACTION_REPOSITORY}/releases/download/${CODEQL_BUNDLE_VERSION}/${codeQLBundleName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
type CodeQLToolsSource =
|
||||||
|
| { codeqlTarPath: string; sourceType: "local"; toolsVersion: "local" }
|
||||||
|
| {
|
||||||
|
codeqlFolder: string;
|
||||||
|
sourceType: "toolcache";
|
||||||
|
toolsVersion: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
codeqlURL: string;
|
||||||
|
semanticVersion: string;
|
||||||
|
sourceType: "download";
|
||||||
|
toolsVersion: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function getCodeQLSource(
|
||||||
|
toolsInput: string | undefined,
|
||||||
|
bypassToolcache: boolean,
|
||||||
|
apiDetails: api.GitHubApiDetails,
|
||||||
|
variant: util.GitHubVariant,
|
||||||
|
logger: Logger
|
||||||
|
): Promise<CodeQLToolsSource> {
|
||||||
|
if (toolsInput && toolsInput !== "latest" && !toolsInput.startsWith("http")) {
|
||||||
|
return {
|
||||||
|
codeqlTarPath: toolsInput,
|
||||||
|
sourceType: "local",
|
||||||
|
toolsVersion: "local",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const forceLatestReason =
|
||||||
|
// We use the special value of 'latest' to prioritize the version in the
|
||||||
|
// defaults over any pinned cached version.
|
||||||
|
toolsInput === "latest"
|
||||||
|
? '"tools: latest" was requested'
|
||||||
|
: // If the user hasn't requested a particular CodeQL version, then bypass
|
||||||
|
// the toolcache when the appropriate feature is enabled. This
|
||||||
|
// allows us to quickly rollback a broken bundle that has made its way
|
||||||
|
// into the toolcache.
|
||||||
|
toolsInput === undefined && bypassToolcache
|
||||||
|
? "a specific version of CodeQL was not requested and the bypass toolcache feature is enabled"
|
||||||
|
: undefined;
|
||||||
|
const forceLatest = forceLatestReason !== undefined;
|
||||||
|
if (forceLatest) {
|
||||||
|
logger.debug(
|
||||||
|
`Forcing the latest version of the CodeQL tools since ${forceLatestReason}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const codeqlURL = forceLatest ? undefined : toolsInput;
|
||||||
|
const requestedSemVer = convertToSemVer(
|
||||||
|
getCodeQLURLVersion(codeqlURL || `/${CODEQL_BUNDLE_VERSION}/`),
|
||||||
|
logger
|
||||||
|
);
|
||||||
|
|
||||||
|
// If we find the specified version, we always use that.
|
||||||
|
const codeqlFolder = toolcache.find("CodeQL", requestedSemVer);
|
||||||
|
if (codeqlFolder) {
|
||||||
|
return {
|
||||||
|
codeqlFolder,
|
||||||
|
sourceType: "toolcache",
|
||||||
|
toolsVersion: requestedSemVer,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't find the requested version, in some cases 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 (!codeqlURL && !forceLatest) {
|
||||||
|
const codeqlVersions = toolcache.findAllVersions("CodeQL");
|
||||||
|
if (codeqlVersions.length === 1 && isGoodVersion(codeqlVersions[0])) {
|
||||||
|
const tmpCodeqlFolder = toolcache.find("CodeQL", codeqlVersions[0]);
|
||||||
|
if (fs.existsSync(path.join(tmpCodeqlFolder, "pinned-version"))) {
|
||||||
|
logger.debug(
|
||||||
|
`CodeQL in cache overriding the default ${CODEQL_BUNDLE_VERSION}`
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
codeqlFolder: tmpCodeqlFolder,
|
||||||
|
sourceType: "toolcache",
|
||||||
|
toolsVersion: codeqlVersions[0],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
codeqlURL:
|
||||||
|
codeqlURL ||
|
||||||
|
(await getCodeQLBundleDownloadURL(apiDetails, variant, logger)),
|
||||||
|
semanticVersion: requestedSemVer,
|
||||||
|
sourceType: "download",
|
||||||
|
toolsVersion:
|
||||||
|
semver.prerelease(requestedSemVer)?.join(".") || requestedSemVer,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function downloadCodeQL(
|
||||||
|
codeqlURL: string,
|
||||||
|
semanticVersion: string,
|
||||||
|
apiDetails: api.GitHubApiDetails,
|
||||||
|
tempDir: string,
|
||||||
|
logger: Logger
|
||||||
|
): Promise<string> {
|
||||||
|
const parsedCodeQLURL = new URL(codeqlURL);
|
||||||
|
const searchParams = new URLSearchParams(parsedCodeQLURL.search);
|
||||||
|
const headers: OutgoingHttpHeaders = {
|
||||||
|
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.
|
||||||
|
if (searchParams.has("token")) {
|
||||||
|
logger.debug("CodeQL tools URL contains an authorization token.");
|
||||||
|
} else if (codeqlURL.startsWith(`${apiDetails.url}/`)) {
|
||||||
|
logger.debug("Providing an authorization token to download CodeQL tools.");
|
||||||
|
headers.authorization = `token ${apiDetails.auth}`;
|
||||||
|
} else {
|
||||||
|
logger.debug("Downloading CodeQL tools without an authorization token.");
|
||||||
|
}
|
||||||
|
logger.info(
|
||||||
|
`Downloading CodeQL tools from ${codeqlURL}. This may take a while.`
|
||||||
|
);
|
||||||
|
|
||||||
|
const dest = path.join(tempDir, uuidV4());
|
||||||
|
const finalHeaders = Object.assign(
|
||||||
|
{ "User-Agent": "CodeQL Action" },
|
||||||
|
headers
|
||||||
|
);
|
||||||
|
const codeqlPath = await toolcache.downloadTool(
|
||||||
|
codeqlURL,
|
||||||
|
dest,
|
||||||
|
undefined,
|
||||||
|
finalHeaders
|
||||||
|
);
|
||||||
|
logger.debug(`CodeQL bundle download to ${codeqlPath} complete.`);
|
||||||
|
|
||||||
|
const codeqlExtracted = await toolcache.extractTar(codeqlPath);
|
||||||
|
return await toolcache.cacheDir(codeqlExtracted, "CodeQL", semanticVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCodeQLURLVersion(url: string): string {
|
||||||
|
const match = url.match(/\/codeql-bundle-(.*)\//);
|
||||||
|
if (match === null || match.length < 2) {
|
||||||
|
throw new Error(
|
||||||
|
`Malformed tools url: ${url}. Version could not be inferred`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return match[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertToSemVer(version: string, logger: Logger): string {
|
||||||
|
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;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue