mirror of
https://github.com/github/codeql-action.git
synced 2026-10-03 09:14:58 +00:00
Separate CodeQL bundle resolution from installation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
72
lib/entry-points.js
generated
72
lib/entry-points.js
generated
@@ -152223,7 +152223,8 @@ async function getCodeQLSource(toolsInput, defaultCliVersion, rawLanguages, useO
|
||||
tagName = version.tagName;
|
||||
}
|
||||
const bundleVersion2 = tagName && tryGetBundleVersionFromTagName(tagName, logger);
|
||||
const humanReadableVersion = cliVersion2 ?? (bundleVersion2 && convertToSemVer(bundleVersion2, logger)) ?? tagName ?? url2 ?? "unknown";
|
||||
const resolvedVersion = cliVersion2 ?? (bundleVersion2 ? convertToSemVer(bundleVersion2, logger) : void 0);
|
||||
const humanReadableVersion = resolvedVersion ?? tagName ?? url2 ?? "unknown";
|
||||
logger.debug(
|
||||
`Attempting to obtain CodeQL tools. CLI version: ${cliVersion2 ?? "unknown"}, bundle tag name: ${tagName ?? "unknown"}, URL: ${url2 ?? "unspecified"}.`
|
||||
);
|
||||
@@ -152330,12 +152331,12 @@ async function getCodeQLSource(toolsInput, defaultCliVersion, rawLanguages, useO
|
||||
logger.info(`Using CodeQL CLI sourced from ${url2} .`);
|
||||
}
|
||||
return {
|
||||
bundleVersion: tagName && tryGetBundleVersionFromTagName(tagName, logger),
|
||||
bundle: { kind: "combined", url: url2 },
|
||||
bundleVersion: bundleVersion2,
|
||||
cliVersion: cliVersion2,
|
||||
codeqlURL: url2,
|
||||
compressionMethod,
|
||||
sourceType: "download",
|
||||
toolsVersion: cliVersion2 ?? humanReadableVersion
|
||||
toolsVersion: resolvedVersion ?? "unknown"
|
||||
};
|
||||
}
|
||||
async function tryGetFallbackToolcacheVersion(cliVersion2, tagName, logger) {
|
||||
@@ -152349,7 +152350,9 @@ async function tryGetFallbackToolcacheVersion(cliVersion2, tagName, logger) {
|
||||
);
|
||||
return fallbackVersion;
|
||||
}
|
||||
var downloadCodeQL = async function(codeqlURL, compressionMethod, maybeBundleVersion, maybeCliVersion, apiDetails, tarVersion, tempDir, features, logger) {
|
||||
var downloadCodeQL = async function(source, apiDetails, tarVersion, tempDir, logger) {
|
||||
const { bundle, compressionMethod } = source;
|
||||
const codeqlURL = bundle.url;
|
||||
const parsedCodeQLURL = new URL(codeqlURL);
|
||||
const searchParams = new URLSearchParams(parsedCodeQLURL.search);
|
||||
const headers = {
|
||||
@@ -152365,13 +152368,8 @@ var downloadCodeQL = async function(codeqlURL, compressionMethod, maybeBundleVer
|
||||
codeqlURL
|
||||
);
|
||||
}
|
||||
const toolcacheInfo = getToolcacheDestinationInfo(
|
||||
maybeBundleVersion,
|
||||
maybeCliVersion,
|
||||
logger
|
||||
);
|
||||
const extractedBundlePath = toolcacheInfo?.path ?? getTempExtractionDir(tempDir);
|
||||
await tryDeleteToolcacheBundles({ env: getEnv(), features, logger });
|
||||
const toolcacheDestination = getToolcacheDestination(source, logger);
|
||||
const extractedBundlePath = toolcacheDestination ?? getTempExtractionDir(tempDir);
|
||||
const statusReport = await downloadAndExtract(
|
||||
codeqlURL,
|
||||
compressionMethod,
|
||||
@@ -152381,36 +152379,29 @@ var downloadCodeQL = async function(codeqlURL, compressionMethod, maybeBundleVer
|
||||
tarVersion,
|
||||
logger
|
||||
);
|
||||
if (!toolcacheInfo) {
|
||||
if (toolcacheDestination) {
|
||||
writeToolcacheMarkerFile(toolcacheDestination, logger);
|
||||
} else {
|
||||
logger.debug(
|
||||
`Could not cache CodeQL tools because we could not determine the bundle version from the URL ${codeqlURL}.`
|
||||
);
|
||||
return {
|
||||
codeqlFolder: extractedBundlePath,
|
||||
statusReport,
|
||||
toolsVersion: maybeCliVersion ?? "unknown"
|
||||
};
|
||||
}
|
||||
writeToolcacheMarkerFile(toolcacheInfo.path, logger);
|
||||
return {
|
||||
codeqlFolder: extractedBundlePath,
|
||||
statusReport,
|
||||
toolsVersion: maybeCliVersion ?? toolcacheInfo.version
|
||||
statusReport
|
||||
};
|
||||
};
|
||||
function getToolcacheDestinationInfo(maybeBundleVersion, maybeCliVersion, logger) {
|
||||
if (maybeBundleVersion) {
|
||||
const version = getCanonicalToolcacheVersion(
|
||||
maybeCliVersion,
|
||||
maybeBundleVersion,
|
||||
logger
|
||||
);
|
||||
return {
|
||||
path: getToolcacheDirectory(version),
|
||||
version
|
||||
};
|
||||
function getToolcacheDestination(source, logger) {
|
||||
if (!source.bundleVersion) {
|
||||
return void 0;
|
||||
}
|
||||
return void 0;
|
||||
return getToolcacheDirectory(
|
||||
getCanonicalToolcacheVersion(
|
||||
source.cliVersion,
|
||||
source.bundleVersion,
|
||||
logger
|
||||
)
|
||||
);
|
||||
}
|
||||
async function tryDeleteToolcacheBundles({
|
||||
env,
|
||||
@@ -152461,7 +152452,6 @@ async function setupCodeQLBundle(toolsInput, apiDetails, tempDir, variant, defau
|
||||
logger
|
||||
);
|
||||
let codeqlFolder;
|
||||
let toolsVersion = source.toolsVersion;
|
||||
let toolsDownloadStatusReport;
|
||||
let toolsSource;
|
||||
switch (source.sourceType) {
|
||||
@@ -152482,18 +152472,14 @@ async function setupCodeQLBundle(toolsInput, apiDetails, tempDir, variant, defau
|
||||
toolsSource = "TOOLCACHE" /* Toolcache */;
|
||||
break;
|
||||
case "download": {
|
||||
const result = await downloadCodeQL(
|
||||
source.codeqlURL,
|
||||
source.compressionMethod,
|
||||
source.bundleVersion,
|
||||
source.cliVersion,
|
||||
const result = await downloadCodeQLBundle(
|
||||
source,
|
||||
apiDetails,
|
||||
zstdAvailability.version,
|
||||
tempDir,
|
||||
features,
|
||||
logger
|
||||
);
|
||||
toolsVersion = result.toolsVersion;
|
||||
codeqlFolder = result.codeqlFolder;
|
||||
toolsDownloadStatusReport = result.statusReport;
|
||||
toolsSource = "DOWNLOAD" /* Download */;
|
||||
@@ -152507,9 +152493,13 @@ async function setupCodeQLBundle(toolsInput, apiDetails, tempDir, variant, defau
|
||||
codeqlFolder,
|
||||
toolsDownloadStatusReport,
|
||||
toolsSource,
|
||||
toolsVersion
|
||||
toolsVersion: source.toolsVersion
|
||||
};
|
||||
}
|
||||
async function downloadCodeQLBundle(source, apiDetails, tarVersion, tempDir, features, logger) {
|
||||
await tryDeleteToolcacheBundles({ env: getEnv(), features, logger });
|
||||
return await downloadCodeQL(source, apiDetails, tarVersion, tempDir, logger);
|
||||
}
|
||||
async function useZstdBundle(cliVersion2, tarSupportsZstd) {
|
||||
return (
|
||||
// In testing, gzip performs better than zstd on Windows.
|
||||
|
||||
@@ -90,7 +90,7 @@ async function installIntoToolcache({
|
||||
tmpDir: string;
|
||||
}) {
|
||||
const url = mockBundleDownloadApi({ apiDetails, isPinned, tagName });
|
||||
await codeql.setupCodeQL(
|
||||
return await codeql.setupCodeQL(
|
||||
cliVersion !== undefined ? undefined : url,
|
||||
apiDetails,
|
||||
tmpDir,
|
||||
@@ -259,6 +259,65 @@ test.serial(
|
||||
},
|
||||
);
|
||||
|
||||
for (const { cliVersion, tagName, expectedToolcacheVersion } of [
|
||||
{
|
||||
cliVersion: "2.21.0",
|
||||
tagName: "codeql-bundle-20240101",
|
||||
expectedToolcacheVersion: "2.21.0",
|
||||
},
|
||||
{
|
||||
cliVersion: "2.21.0-rc.1",
|
||||
tagName: "codeql-bundle-20240101",
|
||||
expectedToolcacheVersion: "0.0.0-20240101",
|
||||
},
|
||||
{
|
||||
cliVersion: "2.21.0+20240101",
|
||||
tagName: "codeql-bundle-20240101",
|
||||
expectedToolcacheVersion: "0.0.0-20240101",
|
||||
},
|
||||
{
|
||||
cliVersion: "2.21.0",
|
||||
tagName: "custom-release",
|
||||
expectedToolcacheVersion: undefined,
|
||||
},
|
||||
]) {
|
||||
test.serial(
|
||||
`preserves CLI version ${cliVersion} when installing ${tagName}`,
|
||||
async (t) => {
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
|
||||
const result = await installIntoToolcache({
|
||||
cliVersion,
|
||||
isPinned: false,
|
||||
tagName,
|
||||
tmpDir,
|
||||
});
|
||||
|
||||
t.is(result.toolsVersion, cliVersion);
|
||||
t.is(result.toolsSource, ToolsSource.Download);
|
||||
t.true(
|
||||
Number.isInteger(result.toolsDownloadStatusReport?.totalDurationMs),
|
||||
);
|
||||
t.deepEqual(
|
||||
toolcache.findAllVersions("CodeQL"),
|
||||
expectedToolcacheVersion === undefined
|
||||
? []
|
||||
: [expectedToolcacheVersion],
|
||||
);
|
||||
if (expectedToolcacheVersion !== undefined) {
|
||||
const cachedFolder = toolcache.find(
|
||||
"CodeQL",
|
||||
expectedToolcacheVersion,
|
||||
);
|
||||
t.truthy(cachedFolder);
|
||||
t.true(fs.existsSync(`${cachedFolder}.complete`));
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const EXPLICITLY_REQUESTED_BUNDLE_TEST_CASES = [
|
||||
{
|
||||
tagName: "codeql-bundle-2.17.6",
|
||||
|
||||
@@ -34,6 +34,7 @@ import * as toolsDownload from "./tools-download";
|
||||
import {
|
||||
getErrorMessage,
|
||||
GitHubVariant,
|
||||
HTTPError,
|
||||
initializeEnvironment,
|
||||
withTmpDir,
|
||||
} from "./util";
|
||||
@@ -44,6 +45,15 @@ test.beforeEach(() => {
|
||||
initializeEnvironment("1.2.3");
|
||||
});
|
||||
|
||||
function stubDownloadAndExtract() {
|
||||
return sinon
|
||||
.stub(toolsDownload, "downloadAndExtract")
|
||||
.callsFake(async (_url, _compressionMethod, dest) => {
|
||||
fs.mkdirSync(dest, { recursive: true });
|
||||
return { downloadDurationMs: 200, totalDurationMs: 300 };
|
||||
});
|
||||
}
|
||||
|
||||
test.serial("parse codeql bundle url version", (t) => {
|
||||
t.deepEqual(
|
||||
setupCodeql.getCodeQLURLVersion(
|
||||
@@ -102,9 +112,9 @@ test.serial(
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const tagName = "codeql-bundle-v1.2.3";
|
||||
mockBundleDownloadApi({ tagName });
|
||||
const url = mockBundleDownloadApi({ tagName });
|
||||
const source = await setupCodeql.getCodeQLSource(
|
||||
`https://github.com/github/codeql-action/releases/download/${tagName}/codeql-bundle-linux64.tar.gz`,
|
||||
url,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
undefined, // rawLanguages
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
@@ -115,8 +125,14 @@ test.serial(
|
||||
getRunnerLogger(true),
|
||||
);
|
||||
|
||||
t.is(source.sourceType, "download");
|
||||
t.is(source["cliVersion"], "1.2.3");
|
||||
t.deepEqual(source, {
|
||||
bundle: { kind: "combined", url },
|
||||
bundleVersion: "v1.2.3",
|
||||
cliVersion: "1.2.3",
|
||||
compressionMethod: "gzip",
|
||||
sourceType: "download",
|
||||
toolsVersion: "1.2.3",
|
||||
} satisfies setupCodeql.CodeQLDownloadSource);
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -198,7 +214,8 @@ for (const {
|
||||
t.is(source.sourceType, "download");
|
||||
if (source.sourceType === "download") {
|
||||
t.is(source.compressionMethod, expectedCompressionMethod);
|
||||
t.true(source.codeqlURL.endsWith(`/${expectedBundleName}`));
|
||||
t.is(source.bundle.kind, "combined");
|
||||
t.true(source.bundle.url.endsWith(`/${expectedBundleName}`));
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -252,23 +269,14 @@ test.serial(
|
||||
const logger = getRecordingLogger(loggedMessages);
|
||||
const features = createFeatures([]);
|
||||
|
||||
// Stub the downloadCodeQL function to prevent downloading artefacts
|
||||
// during testing from being called.
|
||||
sinon.stub(setupCodeql, "downloadCodeQL").resolves({
|
||||
codeqlFolder: "codeql",
|
||||
statusReport: {
|
||||
downloadDurationMs: 200,
|
||||
totalDurationMs: 300,
|
||||
},
|
||||
toolsVersion: LINKED_CLI_VERSION.cliVersion,
|
||||
});
|
||||
const extractStub = stubDownloadAndExtract();
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const result = await setupCodeql.setupCodeQLBundle(
|
||||
"linked",
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
"tmp/codeql_action_test/",
|
||||
tmpDir,
|
||||
GitHubVariant.DOTCOM,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
undefined, // rawLanguages
|
||||
@@ -280,6 +288,15 @@ test.serial(
|
||||
// Basic sanity check that the version we got back is indeed
|
||||
// the linked (default) CLI version.
|
||||
t.is(result.toolsVersion, LINKED_CLI_VERSION.cliVersion);
|
||||
t.true(extractStub.calledOnce);
|
||||
t.is(
|
||||
result.codeqlFolder,
|
||||
toolcache.find("CodeQL", LINKED_CLI_VERSION.cliVersion),
|
||||
);
|
||||
t.deepEqual(result.toolsDownloadStatusReport, {
|
||||
downloadDurationMs: 200,
|
||||
totalDurationMs: 300,
|
||||
});
|
||||
|
||||
// Ensure message logging CodeQL CLI version was present in user logs.
|
||||
const expected_message: string = `Using CodeQL CLI version ${LINKED_CLI_VERSION.cliVersion}`;
|
||||
@@ -305,23 +322,14 @@ test.serial(
|
||||
"https://github.com/github/codeql-action/releases/download/codeql-bundle-v2.16.0/codeql-bundle-linux64.tar.gz";
|
||||
const expectedVersion = "2.16.0";
|
||||
|
||||
// Stub the downloadCodeQL function to prevent downloading artefacts
|
||||
// during testing from being called.
|
||||
sinon.stub(setupCodeql, "downloadCodeQL").resolves({
|
||||
codeqlFolder: "codeql",
|
||||
statusReport: {
|
||||
downloadDurationMs: 200,
|
||||
totalDurationMs: 300,
|
||||
},
|
||||
toolsVersion: expectedVersion,
|
||||
});
|
||||
const extractStub = stubDownloadAndExtract();
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const result = await setupCodeql.setupCodeQLBundle(
|
||||
bundleUrl,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
"tmp/codeql_action_test/",
|
||||
tmpDir,
|
||||
GitHubVariant.DOTCOM,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
undefined, // rawLanguages
|
||||
@@ -333,6 +341,13 @@ test.serial(
|
||||
// Basic sanity check that the version we got back is indeed the version that the
|
||||
// bundle contains..
|
||||
t.is(result.toolsVersion, expectedVersion);
|
||||
t.true(extractStub.calledOnce);
|
||||
t.is(extractStub.firstCall.args[0], bundleUrl);
|
||||
t.is(result.codeqlFolder, toolcache.find("CodeQL", expectedVersion));
|
||||
t.deepEqual(result.toolsDownloadStatusReport, {
|
||||
downloadDurationMs: 200,
|
||||
totalDurationMs: 300,
|
||||
});
|
||||
|
||||
// Ensure message logging CodeQL CLI version was present in user logs.
|
||||
const expected_message: string = `Using CodeQL CLI version 2.16.0 sourced from ${bundleUrl} .`;
|
||||
@@ -348,11 +363,12 @@ test.serial(
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"getCodeQLSource correctly returns nightly CLI version when tools == nightly",
|
||||
"getCodeQLSource and setupCodeQLBundle preserve the nightly version when tools == nightly",
|
||||
async (t) => {
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(loggedMessages);
|
||||
const features = createFeatures([]);
|
||||
const extractStub = stubDownloadAndExtract();
|
||||
|
||||
const expectedDate = "30260213";
|
||||
const expectedTag = `codeql-bundle-${expectedDate}`;
|
||||
@@ -390,14 +406,34 @@ test.serial(
|
||||
const expectedVersion = `0.0.0-${expectedDate}`;
|
||||
const expectedURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}/${setupCodeql.getCodeQLBundleName("zstd")}`;
|
||||
t.deepEqual(source, {
|
||||
bundle: { kind: "combined", url: expectedURL },
|
||||
bundleVersion: expectedDate,
|
||||
cliVersion: undefined,
|
||||
codeqlURL: expectedURL,
|
||||
compressionMethod: "zstd",
|
||||
sourceType: "download",
|
||||
toolsVersion: expectedVersion,
|
||||
} satisfies setupCodeql.CodeQLToolsSource);
|
||||
|
||||
const result = await setupCodeql.setupCodeQLBundle(
|
||||
"nightly",
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
tmpDir,
|
||||
GitHubVariant.DOTCOM,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
["javascript"],
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
features,
|
||||
logger,
|
||||
);
|
||||
|
||||
t.true(extractStub.calledOnce);
|
||||
t.is(extractStub.firstCall.args[0], expectedURL);
|
||||
t.is(result.toolsVersion, source.toolsVersion);
|
||||
t.is(result.toolsSource, setupCodeql.ToolsSource.Download);
|
||||
t.is(result.codeqlFolder, toolcache.find("CodeQL", expectedVersion));
|
||||
t.true(fs.existsSync(`${result.codeqlFolder}.complete`));
|
||||
t.deepEqual(toolcache.findAllVersions("CodeQL"), [expectedVersion]);
|
||||
|
||||
// Afterwards, ensure that we see the expected messages in the log.
|
||||
checkExpectedLogMessages(t, loggedMessages, [
|
||||
"Using the latest CodeQL CLI nightly, as requested by 'tools: nightly'.",
|
||||
@@ -453,9 +489,9 @@ test.serial(
|
||||
const expectedVersion = `0.0.0-${expectedDate}`;
|
||||
const expectedURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}/${setupCodeql.getCodeQLBundleName("zstd")}`;
|
||||
t.deepEqual(source, {
|
||||
bundle: { kind: "combined", url: expectedURL },
|
||||
bundleVersion: expectedDate,
|
||||
cliVersion: undefined,
|
||||
codeqlURL: expectedURL,
|
||||
compressionMethod: "zstd",
|
||||
sourceType: "download",
|
||||
toolsVersion: expectedVersion,
|
||||
@@ -472,6 +508,90 @@ test.serial(
|
||||
},
|
||||
);
|
||||
|
||||
for (const bundlePath of [
|
||||
"codeql-bundle.tar.gz",
|
||||
"codeql-bundle.tar.zst",
|
||||
"codeql-bundle-/codeql-bundle.tar.gz",
|
||||
]) {
|
||||
test.serial(
|
||||
`setupCodeQLBundle reports an unknown version for ${bundlePath}`,
|
||||
async (t) => {
|
||||
const extractStub = stubDownloadAndExtract();
|
||||
const downloadSpy = sinon.spy(setupCodeql, "downloadCodeQL");
|
||||
const url = `https://example.com/${bundlePath}`;
|
||||
const messages: LoggedMessage[] = [];
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const result = await setupCodeql.setupCodeQLBundle(
|
||||
url,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
tmpDir,
|
||||
GitHubVariant.DOTCOM,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
undefined, // rawLanguages
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
createFeatures([]),
|
||||
getRecordingLogger(messages),
|
||||
);
|
||||
|
||||
t.true(extractStub.calledOnce);
|
||||
t.is(extractStub.firstCall.args[0], url);
|
||||
t.is(downloadSpy.firstCall.args[0].toolsVersion, "unknown");
|
||||
t.is(result.toolsVersion, "unknown");
|
||||
t.is(result.toolsSource, setupCodeql.ToolsSource.Download);
|
||||
t.is(path.dirname(result.codeqlFolder), tmpDir);
|
||||
t.true(fs.existsSync(result.codeqlFolder));
|
||||
t.false(fs.existsSync(`${result.codeqlFolder}.complete`));
|
||||
t.deepEqual(toolcache.findAllVersions("CodeQL"), []);
|
||||
checkExpectedLogMessages(t, messages, [
|
||||
`Using CodeQL CLI sourced from ${url}`,
|
||||
]);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
test.serial(
|
||||
"setupCodeQLBundle preserves local installation without cleaning the toolcache",
|
||||
async (t) => {
|
||||
const cleanupSpy = sinon.spy(toolsDownload, "deleteToolcacheBundles");
|
||||
const downloadSpy = sinon.spy(setupCodeql, "downloadCodeQL");
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
process.env[ActionsEnvVars.RUNNER_ENVIRONMENT] = "github-hosted";
|
||||
const cachedDirectory = createToolcacheEntry(
|
||||
tmpDir,
|
||||
"CodeQL",
|
||||
CLEANUP_STALE_VERSION,
|
||||
);
|
||||
const result = await setupCodeql.setupCodeQLBundle(
|
||||
path.join(__dirname, "../src/testdata/codeql-bundle.tar.gz"),
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
tmpDir,
|
||||
GitHubVariant.DOTCOM,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
undefined, // rawLanguages
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
createFeatures([Feature.CleanupToolcacheBundles]),
|
||||
getRunnerLogger(true),
|
||||
);
|
||||
|
||||
t.is(result.toolsVersion, "local");
|
||||
t.is(result.toolsSource, setupCodeql.ToolsSource.Local);
|
||||
t.is(result.toolsDownloadStatusReport, undefined);
|
||||
t.is(path.dirname(result.codeqlFolder), tmpDir);
|
||||
t.true(fs.existsSync(result.codeqlFolder));
|
||||
t.false(fs.existsSync(`${result.codeqlFolder}.complete`));
|
||||
t.true(fs.existsSync(cachedDirectory));
|
||||
t.true(cleanupSpy.notCalled);
|
||||
t.true(downloadSpy.notCalled);
|
||||
t.is(process.env[EnvVar.HAS_SET_UP_CODEQL], "true");
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"getCodeQLSource correctly returns latest version from toolcache when tools == toolcache",
|
||||
async (t) => {
|
||||
@@ -978,21 +1098,21 @@ async function runDownloadCodeQL(
|
||||
codeqlFolder: string;
|
||||
cleanupDiagnostic: toolsDownload.ToolcacheCleanupResult | undefined;
|
||||
}> {
|
||||
sinon
|
||||
.stub(toolsDownload, "downloadAndExtract")
|
||||
.callsFake(async (_url, _compressionMethod, dest) => {
|
||||
// The real implementation creates the destination directory, which matters here because the
|
||||
// cleanup deletes it first and `writeToolcacheMarkerFile` writes into its parent afterwards.
|
||||
fs.mkdirSync(dest, { recursive: true });
|
||||
return { totalDurationMs: 1 };
|
||||
});
|
||||
stubDownloadAndExtract();
|
||||
const addDiagnostic = sinon.stub(diagnostics, "addNoLanguageDiagnostic");
|
||||
|
||||
const { codeqlFolder } = await setupCodeql.downloadCodeQL(
|
||||
"https://example.com/codeql-bundle.tar.gz",
|
||||
"gzip",
|
||||
bundleVersion,
|
||||
CLEANUP_CLI_VERSION,
|
||||
const { codeqlFolder } = await setupCodeql.downloadCodeQLBundle(
|
||||
{
|
||||
bundle: {
|
||||
kind: "combined",
|
||||
url: "https://example.com/codeql-bundle.tar.gz",
|
||||
},
|
||||
compressionMethod: "gzip",
|
||||
bundleVersion,
|
||||
cliVersion: CLEANUP_CLI_VERSION,
|
||||
sourceType: "download",
|
||||
toolsVersion: CLEANUP_CLI_VERSION,
|
||||
},
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
undefined, // tarVersion
|
||||
toolcacheRoot, // tempDir
|
||||
@@ -1072,7 +1192,7 @@ async function testToolcacheCleanup(
|
||||
}
|
||||
|
||||
test.serial(
|
||||
"downloadCodeQL does not clean up the toolcache when the feature flag is disabled",
|
||||
"downloadCodeQLBundle does not clean up the toolcache when the feature flag is disabled",
|
||||
async (t) => {
|
||||
await testToolcacheCleanup(
|
||||
t,
|
||||
@@ -1087,7 +1207,7 @@ test.serial(
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"downloadCodeQL does not clean up the toolcache when the runner is not GitHub-hosted",
|
||||
"downloadCodeQLBundle does not clean up the toolcache when the runner is not GitHub-hosted",
|
||||
async (t) => {
|
||||
await testToolcacheCleanup(
|
||||
t,
|
||||
@@ -1105,7 +1225,7 @@ test.serial(
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"downloadCodeQL does not clean up the toolcache when the runner environment is unknown",
|
||||
"downloadCodeQLBundle does not clean up the toolcache when the runner environment is unknown",
|
||||
async (t) => {
|
||||
// A runner that doesn't report its environment must be treated as not GitHub-hosted, since its
|
||||
// toolcache may well outlive the job.
|
||||
@@ -1125,7 +1245,7 @@ test.serial(
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"downloadCodeQL deletes other CodeQL bundles from the toolcache when enabled on a GitHub-hosted runner",
|
||||
"downloadCodeQLBundle deletes other CodeQL bundles from the toolcache when enabled on a GitHub-hosted runner",
|
||||
async (t) => {
|
||||
await testToolcacheCleanup(
|
||||
t,
|
||||
@@ -1153,7 +1273,7 @@ test.serial(
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"downloadCodeQL reports no deleted versions when the toolcache has no CodeQL bundles",
|
||||
"downloadCodeQLBundle reports no deleted versions when the toolcache has no CodeQL bundles",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
@@ -1175,7 +1295,7 @@ test.serial(
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"downloadCodeQL continues when deleting a CodeQL bundle from the toolcache fails",
|
||||
"downloadCodeQLBundle continues when deleting a CodeQL bundle from the toolcache fails",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
@@ -1306,7 +1426,7 @@ test.serial(
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"downloadCodeQL does not follow a symlinked CodeQL toolcache directory",
|
||||
"downloadCodeQLBundle does not follow a symlinked CodeQL toolcache directory",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const toolcacheRoot = path.join(tmpDir, "toolcache");
|
||||
@@ -1342,7 +1462,7 @@ test.serial(
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"downloadCodeQL does not clean up the toolcache once a step has already set up CodeQL",
|
||||
"downloadCodeQLBundle does not clean up the toolcache once a step has already set up CodeQL",
|
||||
async (t) => {
|
||||
// `.github/workflows/codeql.yml` sets up CodeQL twice and then runs both returned paths. If the
|
||||
// second setup downloads, it must not delete the bundle the first one handed out.
|
||||
@@ -1371,11 +1491,7 @@ test.serial(
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
delete process.env[EnvVar.HAS_SET_UP_CODEQL];
|
||||
|
||||
sinon.stub(setupCodeql, "downloadCodeQL").resolves({
|
||||
codeqlFolder: "codeql",
|
||||
statusReport: { totalDurationMs: 1 },
|
||||
toolsVersion: LINKED_CLI_VERSION.cliVersion,
|
||||
});
|
||||
stubDownloadAndExtract();
|
||||
|
||||
await setupCodeql.setupCodeQLBundle(
|
||||
"linked",
|
||||
@@ -1399,7 +1515,50 @@ test.serial(
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"downloadCodeQL cleans up the toolcache even when the download will not be cached",
|
||||
"setupCodeQLBundle cleans up once and propagates a failed combined download",
|
||||
async (t) => {
|
||||
const error = new HTTPError("Not Found", 404);
|
||||
const extractStub = stubDownloadAndExtract().rejects(error);
|
||||
const cleanupSpy = sinon.spy(toolsDownload, "deleteToolcacheBundles");
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
process.env[ActionsEnvVars.RUNNER_ENVIRONMENT] = "github-hosted";
|
||||
delete process.env[EnvVar.HAS_SET_UP_CODEQL];
|
||||
const staleDirectory = createToolcacheEntry(
|
||||
tmpDir,
|
||||
"CodeQL",
|
||||
CLEANUP_STALE_VERSION,
|
||||
);
|
||||
|
||||
await t.throwsAsync(
|
||||
setupCodeql.setupCodeQLBundle(
|
||||
"linked",
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
tmpDir,
|
||||
GitHubVariant.DOTCOM,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
undefined, // rawLanguages
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
createFeatures([Feature.CleanupToolcacheBundles]),
|
||||
getRunnerLogger(true),
|
||||
),
|
||||
{ is: error },
|
||||
);
|
||||
|
||||
t.true(cleanupSpy.calledOnce);
|
||||
t.true(cleanupSpy.calledBefore(extractStub));
|
||||
t.true(extractStub.calledOnce);
|
||||
t.false(fs.existsSync(staleDirectory));
|
||||
t.false(fs.existsSync(`${extractStub.firstCall.args[2]}.complete`));
|
||||
t.deepEqual(toolcache.findAllVersions("CodeQL"), []);
|
||||
t.is(process.env[EnvVar.HAS_SET_UP_CODEQL], undefined);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"downloadCodeQLBundle cleans up the toolcache even when the download will not be cached",
|
||||
async (t) => {
|
||||
// A `tools` URL we can't derive a bundle version from is extracted to a temporary directory
|
||||
// rather than the toolcache, but the toolcache is on the same filesystem, so emptying it still
|
||||
@@ -1434,7 +1593,7 @@ test.serial(
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"downloadCodeQL reports a failure when the toolcache cannot be inspected",
|
||||
"downloadCodeQLBundle reports a failure when the toolcache cannot be inspected",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
@@ -1466,7 +1625,7 @@ test.serial(
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"downloadCodeQL does not clean up a toolcache on a different filesystem to the workspace",
|
||||
"downloadCodeQLBundle does not clean up a toolcache on a different filesystem to the workspace",
|
||||
async (t) => {
|
||||
// Some runner images keep the toolcache on a different volume to the workspace, in which case
|
||||
// deleting the tools frees up disk space that the analysis cannot use.
|
||||
@@ -1516,7 +1675,7 @@ test.serial(
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"downloadCodeQL does not delete through a symlinked version directory",
|
||||
"downloadCodeQLBundle does not delete through a symlinked version directory",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const toolcacheRoot = path.join(tmpDir, "toolcache");
|
||||
|
||||
@@ -215,6 +215,21 @@ export function convertToSemVer(version: string, logger: Logger): string {
|
||||
return s;
|
||||
}
|
||||
|
||||
type CodeQLBundle = { kind: "combined"; url: string };
|
||||
|
||||
/** A resolved download, including its bundle identity and version. */
|
||||
export interface CodeQLDownloadSource {
|
||||
sourceType: "download";
|
||||
bundle: CodeQLBundle;
|
||||
compressionMethod: tar.CompressionMethod;
|
||||
/** Bundle version of the tools, if known. */
|
||||
bundleVersion?: string;
|
||||
/** CLI version of the tools, if known. */
|
||||
cliVersion?: string;
|
||||
/** Resolved version for telemetry, independent of whether the bundle can be cached. */
|
||||
toolsVersion: string;
|
||||
}
|
||||
|
||||
export type CodeQLToolsSource =
|
||||
| {
|
||||
codeqlTarPath: string;
|
||||
@@ -229,17 +244,7 @@ export type CodeQLToolsSource =
|
||||
/** Human-readable description of the source of the tools for telemetry purposes. */
|
||||
toolsVersion: string;
|
||||
}
|
||||
| {
|
||||
/** Bundle version of the tools, if known. */
|
||||
bundleVersion?: string;
|
||||
/** CLI version of the tools, if known. */
|
||||
cliVersion?: string;
|
||||
compressionMethod: tar.CompressionMethod;
|
||||
codeqlURL: string;
|
||||
sourceType: "download";
|
||||
/** Human-readable description of the source of the tools for telemetry purposes. */
|
||||
toolsVersion: string;
|
||||
};
|
||||
| CodeQLDownloadSource;
|
||||
|
||||
/**
|
||||
* Look for a version of the CodeQL tools in the cache which could override the requested CLI version.
|
||||
@@ -601,12 +606,10 @@ export async function getCodeQLSource(
|
||||
|
||||
const bundleVersion =
|
||||
tagName && tryGetBundleVersionFromTagName(tagName, logger);
|
||||
const humanReadableVersion =
|
||||
const resolvedVersion =
|
||||
cliVersion ??
|
||||
(bundleVersion && convertToSemVer(bundleVersion, logger)) ??
|
||||
tagName ??
|
||||
url ??
|
||||
"unknown";
|
||||
(bundleVersion ? convertToSemVer(bundleVersion, logger) : undefined);
|
||||
const humanReadableVersion = resolvedVersion ?? tagName ?? url ?? "unknown";
|
||||
|
||||
logger.debug(
|
||||
"Attempting to obtain CodeQL tools. " +
|
||||
@@ -750,12 +753,12 @@ export async function getCodeQLSource(
|
||||
logger.info(`Using CodeQL CLI sourced from ${url} .`);
|
||||
}
|
||||
return {
|
||||
bundleVersion: tagName && tryGetBundleVersionFromTagName(tagName, logger),
|
||||
bundle: { kind: "combined", url },
|
||||
bundleVersion,
|
||||
cliVersion,
|
||||
codeqlURL: url,
|
||||
compressionMethod,
|
||||
sourceType: "download",
|
||||
toolsVersion: cliVersion ?? humanReadableVersion,
|
||||
toolsVersion: resolvedVersion ?? "unknown",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -783,20 +786,17 @@ async function tryGetFallbackToolcacheVersion(
|
||||
// Exported using `export const` for testing purposes. Specifically, we want to
|
||||
// be able to stub this function and have other functions in this file use that stub.
|
||||
export const downloadCodeQL = async function (
|
||||
codeqlURL: string,
|
||||
compressionMethod: tar.CompressionMethod,
|
||||
maybeBundleVersion: string | undefined,
|
||||
maybeCliVersion: string | undefined,
|
||||
source: CodeQLDownloadSource,
|
||||
apiDetails: api.GitHubApiDetails,
|
||||
tarVersion: tar.TarVersion | undefined,
|
||||
tempDir: string,
|
||||
features: FeatureEnablement,
|
||||
logger: Logger,
|
||||
): Promise<{
|
||||
codeqlFolder: string;
|
||||
statusReport: ToolsDownloadStatusReport;
|
||||
toolsVersion: string;
|
||||
}> {
|
||||
const { bundle, compressionMethod } = source;
|
||||
const codeqlURL = bundle.url;
|
||||
const parsedCodeQLURL = new URL(codeqlURL);
|
||||
const searchParams = new URLSearchParams(parsedCodeQLURL.search);
|
||||
const headers: OutgoingHttpHeaders = {
|
||||
@@ -815,16 +815,9 @@ export const downloadCodeQL = async function (
|
||||
);
|
||||
}
|
||||
|
||||
const toolcacheInfo = getToolcacheDestinationInfo(
|
||||
maybeBundleVersion,
|
||||
maybeCliVersion,
|
||||
logger,
|
||||
);
|
||||
|
||||
const toolcacheDestination = getToolcacheDestination(source, logger);
|
||||
const extractedBundlePath =
|
||||
toolcacheInfo?.path ?? getTempExtractionDir(tempDir);
|
||||
|
||||
await tryDeleteToolcacheBundles({ env: getEnv(), features, logger });
|
||||
toolcacheDestination ?? getTempExtractionDir(tempDir);
|
||||
|
||||
const statusReport = await downloadAndExtract(
|
||||
codeqlURL,
|
||||
@@ -836,46 +829,36 @@ export const downloadCodeQL = async function (
|
||||
logger,
|
||||
);
|
||||
|
||||
if (!toolcacheInfo) {
|
||||
if (toolcacheDestination) {
|
||||
writeToolcacheMarkerFile(toolcacheDestination, logger);
|
||||
} else {
|
||||
logger.debug(
|
||||
"Could not cache CodeQL tools because we could not determine the bundle version from the " +
|
||||
`URL ${codeqlURL}.`,
|
||||
);
|
||||
return {
|
||||
codeqlFolder: extractedBundlePath,
|
||||
statusReport,
|
||||
toolsVersion: maybeCliVersion ?? "unknown",
|
||||
};
|
||||
}
|
||||
|
||||
writeToolcacheMarkerFile(toolcacheInfo.path, logger);
|
||||
|
||||
return {
|
||||
codeqlFolder: extractedBundlePath,
|
||||
statusReport,
|
||||
toolsVersion: maybeCliVersion ?? toolcacheInfo.version,
|
||||
};
|
||||
};
|
||||
|
||||
function getToolcacheDestinationInfo(
|
||||
maybeBundleVersion: string | undefined,
|
||||
maybeCliVersion: string | undefined,
|
||||
function getToolcacheDestination(
|
||||
source: CodeQLDownloadSource,
|
||||
logger: Logger,
|
||||
): { path: string; version: string } | undefined {
|
||||
if (maybeBundleVersion) {
|
||||
const version = getCanonicalToolcacheVersion(
|
||||
maybeCliVersion,
|
||||
maybeBundleVersion,
|
||||
logger,
|
||||
);
|
||||
|
||||
return {
|
||||
path: getToolcacheDirectory(version),
|
||||
version,
|
||||
};
|
||||
): string | undefined {
|
||||
if (!source.bundleVersion) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return getToolcacheDirectory(
|
||||
getCanonicalToolcacheVersion(
|
||||
source.cliVersion,
|
||||
source.bundleVersion,
|
||||
logger,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1000,7 +983,6 @@ export async function setupCodeQLBundle(
|
||||
);
|
||||
|
||||
let codeqlFolder: string;
|
||||
let toolsVersion = source.toolsVersion;
|
||||
let toolsDownloadStatusReport: ToolsDownloadStatusReport | undefined;
|
||||
let toolsSource: ToolsSource;
|
||||
switch (source.sourceType) {
|
||||
@@ -1021,18 +1003,14 @@ export async function setupCodeQLBundle(
|
||||
toolsSource = ToolsSource.Toolcache;
|
||||
break;
|
||||
case "download": {
|
||||
const result = await downloadCodeQL(
|
||||
source.codeqlURL,
|
||||
source.compressionMethod,
|
||||
source.bundleVersion,
|
||||
source.cliVersion,
|
||||
const result = await downloadCodeQLBundle(
|
||||
source,
|
||||
apiDetails,
|
||||
zstdAvailability.version,
|
||||
tempDir,
|
||||
features,
|
||||
logger,
|
||||
);
|
||||
toolsVersion = result.toolsVersion;
|
||||
codeqlFolder = result.codeqlFolder;
|
||||
toolsDownloadStatusReport = result.statusReport;
|
||||
toolsSource = ToolsSource.Download;
|
||||
@@ -1050,10 +1028,25 @@ export async function setupCodeQLBundle(
|
||||
codeqlFolder,
|
||||
toolsDownloadStatusReport,
|
||||
toolsSource,
|
||||
toolsVersion,
|
||||
toolsVersion: source.toolsVersion,
|
||||
};
|
||||
}
|
||||
|
||||
export async function downloadCodeQLBundle(
|
||||
source: CodeQLDownloadSource,
|
||||
apiDetails: api.GitHubApiDetails,
|
||||
tarVersion: tar.TarVersion | undefined,
|
||||
tempDir: string,
|
||||
features: FeatureEnablement,
|
||||
logger: Logger,
|
||||
): Promise<{
|
||||
codeqlFolder: string;
|
||||
statusReport: ToolsDownloadStatusReport;
|
||||
}> {
|
||||
await tryDeleteToolcacheBundles({ env: getEnv(), features, logger });
|
||||
return await downloadCodeQL(source, apiDetails, tarVersion, tempDir, logger);
|
||||
}
|
||||
|
||||
async function useZstdBundle(
|
||||
cliVersion: string,
|
||||
tarSupportsZstd: boolean,
|
||||
|
||||
Reference in New Issue
Block a user