mirror of
https://github.com/github/codeql-action.git
synced 2026-10-03 09:14:58 +00:00
Accept GitHub release URLs in the tools input
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -4,7 +4,7 @@ See the [releases page](https://github.com/github/codeql-action/releases) for th
|
||||
|
||||
## [UNRELEASED]
|
||||
|
||||
No user facing changes.
|
||||
- The `tools` input to the `init` and `setup-codeql` actions now accepts GitHub release URLs. The Action selects a compatible bundle from the specified release, including a per-language bundle when eligible.
|
||||
|
||||
## 4.38.2 - 24 Sept 2026
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ inputs:
|
||||
|
||||
- A local path to a CodeQL Bundle tarball, or
|
||||
- The URL of a CodeQL Bundle tarball GitHub release asset, or
|
||||
- A release URL from GitHub.com or the current GitHub instance, such as
|
||||
https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.27.0.
|
||||
- A special value `linked` which uses the version of the CodeQL tools
|
||||
that the Action has been bundled with.
|
||||
- A special value `nightly` which uses the latest nightly version of the
|
||||
|
||||
156
lib/entry-points.js
generated
156
lib/entry-points.js
generated
@@ -151352,6 +151352,74 @@ function logMissingPerLanguageBundle({ logger }, language, location) {
|
||||
}
|
||||
|
||||
// src/codeql-release.ts
|
||||
function parseCodeQLReleaseUrl(input, apiDetails) {
|
||||
let url2;
|
||||
try {
|
||||
url2 = new URL(input);
|
||||
} catch {
|
||||
return void 0;
|
||||
}
|
||||
if (url2.protocol !== "https:" || url2.username || url2.password) {
|
||||
return void 0;
|
||||
}
|
||||
const isCurrentInstance = url2.origin === new URL(apiDetails.url).origin;
|
||||
if (!isCurrentInstance && url2.origin !== new URL(GITHUB_DOTCOM_URL).origin) {
|
||||
return void 0;
|
||||
}
|
||||
const match2 = url2.pathname.replace(/\/$/, "").match(
|
||||
/^\/([\w.-]+)\/([\w.-]+)\/releases\/(?:tag\/(.+)|(codeql-bundle-[^/]+))$/
|
||||
);
|
||||
if (match2 === null) {
|
||||
return void 0;
|
||||
}
|
||||
let tagName;
|
||||
try {
|
||||
tagName = decodeURIComponent(match2[3] ?? match2[4]);
|
||||
} catch {
|
||||
throw new ConfigurationError(
|
||||
"Invalid URL encoding in the CodeQL release tag."
|
||||
);
|
||||
}
|
||||
return {
|
||||
serverURL: url2.origin,
|
||||
isCurrentInstance,
|
||||
owner: match2[1],
|
||||
repo: match2[2],
|
||||
tagName
|
||||
};
|
||||
}
|
||||
function parseCliVersion(value) {
|
||||
const parsed = semver7.parse(value);
|
||||
if (parsed === null) {
|
||||
return void 0;
|
||||
}
|
||||
return parsed.version + (parsed.build.length ? `+${parsed.build.join(".")}` : "");
|
||||
}
|
||||
function getReleaseCliVersion(tagName, assetNames, logger) {
|
||||
const versions = /* @__PURE__ */ new Set();
|
||||
for (const name of assetNames) {
|
||||
const match2 = name.match(/^cli-version-(.+)\.txt$/);
|
||||
if (match2 === null) {
|
||||
continue;
|
||||
}
|
||||
const version = parseCliVersion(match2[1]);
|
||||
if (version !== void 0) {
|
||||
versions.add(version);
|
||||
} else {
|
||||
logger.debug(`Ignoring invalid CLI version marker ${name}.`);
|
||||
}
|
||||
}
|
||||
if (versions.size > 1) {
|
||||
logger.warning(
|
||||
`Release ${tagName} has conflicting CLI version markers. Using a combined CodeQL bundle.`
|
||||
);
|
||||
return void 0;
|
||||
}
|
||||
if (versions.size === 1) {
|
||||
return versions.values().next().value;
|
||||
}
|
||||
return parseCliVersion(tagName.replace(/^codeql-bundle-/, ""));
|
||||
}
|
||||
function encodeTag(tagName) {
|
||||
return tagName.split("/").map(encodeURIComponent).join("/");
|
||||
}
|
||||
@@ -151379,6 +151447,21 @@ function getPublicRelease(reference) {
|
||||
getAssetURL: (name) => `${serverURL}/${owner}/${repo}/releases/download/${encodeTag(tagName)}/${name}`
|
||||
};
|
||||
}
|
||||
async function getRequestedRelease(action, requested) {
|
||||
if (!requested.isCurrentInstance) {
|
||||
return getPublicRelease(requested);
|
||||
}
|
||||
try {
|
||||
return await getRelease(action, requested);
|
||||
} catch (e) {
|
||||
if (asHTTPError(e)?.status === 404) {
|
||||
throw new ConfigurationError(
|
||||
`Could not find the CodeQL release ${getReleasePageURL(requested)}. Check that it exists and that the token has access to it.`
|
||||
);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
function getCompressionMethods({
|
||||
cliVersion: cliVersion2,
|
||||
isLatestNightly,
|
||||
@@ -152348,11 +152431,14 @@ async function getCodeQLSource(toolsInput, defaultCliVersion, rawLanguages, useO
|
||||
toolsVersion: "local"
|
||||
};
|
||||
}
|
||||
const requestedRelease = toolsInput === void 0 ? void 0 : parseCodeQLReleaseUrl(toolsInput, apiDetails);
|
||||
let cliVersion2;
|
||||
let tagName;
|
||||
let url2;
|
||||
let bundle;
|
||||
const canForceNightlyWithFF = isDynamicWorkflow() || isInTestMode();
|
||||
let release2;
|
||||
let customReleaseURL;
|
||||
const canForceNightlyWithFF = requestedRelease === void 0 && (isDynamicWorkflow() || isInTestMode());
|
||||
const forceNightlyValueFF = await features.getValue("force_nightly" /* ForceNightly */);
|
||||
const forceNightly = forceNightlyValueFF && canForceNightlyWithFF;
|
||||
const nightlyRequestedByToolsInput = toolsInput !== void 0 && CODEQL_NIGHTLY_TOOLS_INPUTS.includes(toolsInput);
|
||||
@@ -152434,6 +152520,18 @@ async function getCodeQLSource(toolsInput, defaultCliVersion, rawLanguages, useO
|
||||
cliVersion2 = version.cliVersion;
|
||||
tagName = version.tagName;
|
||||
}
|
||||
} else if (requestedRelease !== void 0) {
|
||||
release2 = await getRequestedRelease(
|
||||
{ apiClient: getApiClient() },
|
||||
requestedRelease
|
||||
);
|
||||
tagName = requestedRelease.tagName;
|
||||
cliVersion2 = getReleaseCliVersion(
|
||||
tagName,
|
||||
release2.assetNames ?? [],
|
||||
logger
|
||||
);
|
||||
customReleaseURL = release2.url;
|
||||
} else if (toolsInput !== void 0) {
|
||||
tagName = tryGetTagNameFromUrl(toolsInput, logger);
|
||||
url2 = toolsInput;
|
||||
@@ -152454,18 +152552,21 @@ async function getCodeQLSource(toolsInput, defaultCliVersion, rawLanguages, useO
|
||||
cliVersion2 = version.cliVersion;
|
||||
tagName = version.tagName;
|
||||
}
|
||||
const bundleVersion2 = tagName !== void 0 ? tryGetBundleVersionFromTagName(tagName, logger) : void 0;
|
||||
const bundleVersion2 = (
|
||||
// Custom releases aren't cached, and their tags needn't contain a bundle version.
|
||||
tagName !== void 0 && customReleaseURL === void 0 ? tryGetBundleVersionFromTagName(tagName, logger) : void 0
|
||||
);
|
||||
const resolvedVersion = cliVersion2 ?? (bundleVersion2 !== void 0 ? 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"}.`
|
||||
);
|
||||
const codeqlFolder = await findCodeQLInToolcache(
|
||||
const codeqlFolder = customReleaseURL === void 0 ? await findCodeQLInToolcache(
|
||||
cliVersion2,
|
||||
tagName,
|
||||
humanReadableVersion,
|
||||
logger
|
||||
);
|
||||
) : void 0;
|
||||
if (codeqlFolder) {
|
||||
if (cliVersion2) {
|
||||
logger.info(
|
||||
@@ -152492,23 +152593,34 @@ async function getCodeQLSource(toolsInput, defaultCliVersion, rawLanguages, useO
|
||||
let compressionMethod;
|
||||
let perLanguageBundleFallback;
|
||||
if (!url2) {
|
||||
if (tagName === void 0) {
|
||||
throw new Error(
|
||||
"Could not determine a release tag for the requested CodeQL bundle."
|
||||
const selectionOptions = {
|
||||
rawLanguages,
|
||||
cliVersion: cliVersion2,
|
||||
platform: getBundlePlatform(),
|
||||
variant,
|
||||
tarSupportsZstd
|
||||
};
|
||||
let selection;
|
||||
if (release2 !== void 0) {
|
||||
selection = await selectBundle(
|
||||
{ env: getEnv(), features, logger },
|
||||
release2,
|
||||
selectionOptions
|
||||
);
|
||||
} else {
|
||||
if (tagName === void 0) {
|
||||
throw new Error(
|
||||
"Could not determine a release tag for the requested CodeQL bundle."
|
||||
);
|
||||
}
|
||||
selection = await selectDefaultBundle(
|
||||
{ env: getEnv(), features, logger },
|
||||
tagName,
|
||||
apiDetails,
|
||||
selectionOptions
|
||||
);
|
||||
}
|
||||
({ bundle, compressionMethod, perLanguageBundleFallback } = await selectDefaultBundle(
|
||||
{ env: getEnv(), features, logger },
|
||||
tagName,
|
||||
apiDetails,
|
||||
{
|
||||
rawLanguages,
|
||||
cliVersion: cliVersion2,
|
||||
platform: getBundlePlatform(),
|
||||
variant,
|
||||
tarSupportsZstd
|
||||
}
|
||||
));
|
||||
({ bundle, compressionMethod, perLanguageBundleFallback } = selection);
|
||||
url2 = bundle.url;
|
||||
} else {
|
||||
const method = inferCompressionMethod(url2);
|
||||
@@ -152535,6 +152647,7 @@ async function getCodeQLSource(toolsInput, defaultCliVersion, rawLanguages, useO
|
||||
bundleVersion: bundleVersion2,
|
||||
cliVersion: cliVersion2,
|
||||
compressionMethod,
|
||||
...customReleaseURL !== void 0 ? { customReleaseURL } : {},
|
||||
...perLanguageBundleFallback ? { perLanguageBundleFallback } : {},
|
||||
sourceType: "download",
|
||||
toolsVersion: resolvedVersion ?? "unknown"
|
||||
@@ -152658,6 +152771,11 @@ var downloadCodeQL = async function(source, apiDetails, tarVersion, tempDir, log
|
||||
};
|
||||
};
|
||||
function getToolcacheDestination({ logger }, source) {
|
||||
if (source.customReleaseURL !== void 0) {
|
||||
return new Failure(
|
||||
`Not caching the CodeQL tools from ${source.customReleaseURL}, since we don't cache releases requested by URL.`
|
||||
);
|
||||
}
|
||||
if (source.bundle.kind !== "combined") {
|
||||
return new Failure(
|
||||
"Not caching the CodeQL tools because they came from a bundle that contains only a single language."
|
||||
|
||||
@@ -10,6 +10,8 @@ inputs:
|
||||
|
||||
- A local path to a CodeQL Bundle tarball, or
|
||||
- The URL of a CodeQL Bundle tarball GitHub release asset, or
|
||||
- A release URL from GitHub.com or the current GitHub instance, such as
|
||||
https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.27.0.
|
||||
- A special value `linked` which uses the version of the CodeQL tools
|
||||
that the Action has been bundled with.
|
||||
- A special value `nightly` which uses the latest nightly version of the
|
||||
|
||||
@@ -29,6 +29,11 @@ export interface CodeQLDownloadSource {
|
||||
toolsVersion: string;
|
||||
/** The release lacks the eligible per-language bundle, so we selected the combined bundle. */
|
||||
perLanguageBundleFallback?: true;
|
||||
/**
|
||||
* The page of a requested release that may contain a different build than the bundle we cache for
|
||||
* its version, so we don't cache it.
|
||||
*/
|
||||
customReleaseURL?: string;
|
||||
}
|
||||
|
||||
/** Returns the exact bundle asset name for a platform and optional language. */
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
} from "./testing-utils";
|
||||
import * as toolsDownload from "./tools-download";
|
||||
import {
|
||||
ConfigurationError,
|
||||
getErrorMessage,
|
||||
GitHubVariant,
|
||||
HTTPError,
|
||||
@@ -1382,6 +1383,285 @@ for (const scenario of ["per-language", "fallback", "missing"] as const) {
|
||||
);
|
||||
}
|
||||
|
||||
const GHES_API_DETAILS = {
|
||||
auth: "enterprise-token",
|
||||
url: "https://github.example.test",
|
||||
apiURL: "https://github.example.test/api/v3",
|
||||
};
|
||||
|
||||
/** Models a hosted Linux runner with zstd and a release in `repository` on the instance `apiDetails` describes. */
|
||||
function stubRequestedRelease({
|
||||
apiDetails = SAMPLE_DOTCOM_API_DETAILS,
|
||||
repository = "octo/tools",
|
||||
tagName = `codeql-bundle-v${MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION}`,
|
||||
assetNames = [
|
||||
"codeql-bundle-linux64.tar.zst",
|
||||
"codeql-bundle-java-linux64.tar.zst",
|
||||
],
|
||||
markers = [] as string[],
|
||||
status = 200,
|
||||
} = {}) {
|
||||
sinon.stub(process, "platform").value("linux");
|
||||
sinon.stub(process, "arch").value("x64");
|
||||
sinon.stub(actionsUtil, "isRunningLocalAction").returns(false);
|
||||
process.env[ActionsEnvVars.RUNNER_ENVIRONMENT] = "github-hosted";
|
||||
sinon.stub(tar, "isZstdAvailable").resolves({
|
||||
available: true,
|
||||
foundZstdBinary: true,
|
||||
});
|
||||
const apiBase = `${apiDetails.apiURL}/repos/${repository}/releases`;
|
||||
const assets = [
|
||||
...assetNames,
|
||||
...markers.map((version) => `cli-version-${version}.txt`),
|
||||
].map((name, index) => ({ name, url: `${apiBase}/assets/${1000 + index}` }));
|
||||
const requests: string[] = [];
|
||||
const client = github.getOctokit("123", {
|
||||
baseUrl: apiDetails.apiURL,
|
||||
request: {
|
||||
fetch: async (input) => {
|
||||
const url = String(input);
|
||||
requests.push(url);
|
||||
if (url !== `${apiBase}/tags/${tagName}`) {
|
||||
throw new Error(`Unexpected API request: ${url}`);
|
||||
}
|
||||
return new Response(JSON.stringify({ tag_name: tagName, assets }), {
|
||||
status,
|
||||
headers: { "content-type": "application/json" },
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
sinon.stub(api, "getApiClient").value(() => client);
|
||||
return {
|
||||
assets,
|
||||
requests,
|
||||
releaseURL: `${apiDetails.url}/${repository}/releases/tag/${tagName}`,
|
||||
};
|
||||
}
|
||||
|
||||
for (const scenario of ["combined", "per-language", "fallback"] as const) {
|
||||
test.serial(
|
||||
`setupCodeQLBundle downloads a ${scenario} bundle from a custom release without using the toolcache`,
|
||||
async (t) => {
|
||||
const fixture = stubRequestedRelease();
|
||||
const extract = stubDownloadAndExtract();
|
||||
if (scenario === "fallback") {
|
||||
extract.onFirstCall().rejects(new HTTPError("Not Found", 404));
|
||||
}
|
||||
const find = sinon.spy(toolcache, "find");
|
||||
sinon.stub(actionsUtil, "isDynamicWorkflow").returns(true);
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
createToolcacheEntry(
|
||||
tmpDir,
|
||||
"CodeQL",
|
||||
MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION,
|
||||
);
|
||||
const result = await setupCodeql.setupCodeQLBundle(
|
||||
fixture.releaseURL,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
tmpDir,
|
||||
GitHubVariant.DOTCOM,
|
||||
PER_LANGUAGE_CLI_VERSION,
|
||||
scenario === "combined" ? undefined : ["java"],
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
// The requested release takes precedence over forcing the nightly.
|
||||
createFeatures([Feature.PerLanguageBundles, Feature.ForceNightly]),
|
||||
getRunnerLogger(true),
|
||||
);
|
||||
|
||||
t.is(fixture.requests.length, 1);
|
||||
t.true(find.notCalled);
|
||||
t.is(result.toolsSource, setupCodeql.ToolsSource.Download);
|
||||
t.is(result.toolsVersion, MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION);
|
||||
t.is(extract.callCount, scenario === "fallback" ? 2 : 1);
|
||||
t.is(
|
||||
extract.lastCall.args[0],
|
||||
fixture.assets[scenario === "per-language" ? 1 : 0].url,
|
||||
);
|
||||
t.is(extract.lastCall.args[3], "token token");
|
||||
t.is(path.dirname(result.codeqlFolder), tmpDir);
|
||||
t.false(fs.existsSync(`${result.codeqlFolder}.complete`));
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
test.serial(
|
||||
"setupCodeQLBundle downloads the gzip bundle from a requested release that only has gzip bundles",
|
||||
async (t) => {
|
||||
const fixture = stubRequestedRelease({
|
||||
assetNames: ["codeql-bundle-linux64.tar.gz"],
|
||||
});
|
||||
const extract = stubDownloadAndExtract();
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const result = await setupCodeql.setupCodeQLBundle(
|
||||
fixture.releaseURL,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
tmpDir,
|
||||
GitHubVariant.DOTCOM,
|
||||
PER_LANGUAGE_CLI_VERSION,
|
||||
["java"],
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
createFeatures([Feature.PerLanguageBundles]),
|
||||
getRunnerLogger(true),
|
||||
);
|
||||
|
||||
t.true(extract.calledOnce);
|
||||
t.is(extract.firstCall.args[0], fixture.assets[0].url);
|
||||
t.is(extract.firstCall.args[1], "gzip");
|
||||
t.is(result.toolsSource, setupCodeql.ToolsSource.Download);
|
||||
t.is(result.toolsDownloadStatusReport?.perLanguage, undefined);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
for (const { repository, tagName, markers } of [
|
||||
{
|
||||
repository: "octo/tools",
|
||||
tagName: "codeql-bundle-feature_branch",
|
||||
markers: [],
|
||||
},
|
||||
]) {
|
||||
test.serial(
|
||||
`setupCodeQLBundle doesn't share the toolcache with ${tagName} in ${repository}`,
|
||||
async (t) => {
|
||||
const fixture = stubRequestedRelease({ repository, tagName, markers });
|
||||
const extract = stubDownloadAndExtract();
|
||||
const find = sinon.spy(toolcache, "find");
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
createToolcacheEntry(
|
||||
tmpDir,
|
||||
"CodeQL",
|
||||
MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION,
|
||||
);
|
||||
const result = await setupCodeql.setupCodeQLBundle(
|
||||
fixture.releaseURL,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
tmpDir,
|
||||
GitHubVariant.DOTCOM,
|
||||
PER_LANGUAGE_CLI_VERSION,
|
||||
undefined, // rawLanguages
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
createFeatures([]),
|
||||
getRunnerLogger(true),
|
||||
);
|
||||
|
||||
t.true(find.notCalled);
|
||||
t.is(result.toolsSource, setupCodeql.ToolsSource.Download);
|
||||
t.is(extract.firstCall.args[0], fixture.assets[0].url);
|
||||
t.is(path.dirname(result.codeqlFolder), tmpDir);
|
||||
t.false(fs.existsSync(`${result.codeqlFolder}.complete`));
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
for (const repository of ["github/codeql-action", "octo/tools"]) {
|
||||
test.serial(
|
||||
`setupCodeQLBundle downloads a GitHub.com release in ${repository} from GHES by URL without credentials`,
|
||||
async (t) => {
|
||||
const fixture = stubRequestedRelease({ repository });
|
||||
const extract = stubDownloadAndExtract();
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const result = await setupCodeql.setupCodeQLBundle(
|
||||
fixture.releaseURL,
|
||||
GHES_API_DETAILS,
|
||||
tmpDir,
|
||||
GitHubVariant.GHES,
|
||||
PER_LANGUAGE_CLI_VERSION,
|
||||
["java"],
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
createFeatures([Feature.PerLanguageBundles]),
|
||||
getRunnerLogger(true),
|
||||
);
|
||||
|
||||
t.deepEqual(fixture.requests, []);
|
||||
t.true(extract.calledOnce);
|
||||
t.is(
|
||||
extract.firstCall.args[0],
|
||||
`https://github.com/${repository}/releases/download/codeql-bundle-v${MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION}/codeql-bundle-linux64.tar.zst`,
|
||||
);
|
||||
t.is(extract.firstCall.args[3], undefined);
|
||||
t.false(fs.existsSync(`${result.codeqlFolder}.complete`));
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
test.serial(
|
||||
"setupCodeQLBundle doesn't substitute another release for a requested release that can't be found",
|
||||
async (t) => {
|
||||
const fixture = stubRequestedRelease({ status: 404 });
|
||||
const extract = stubDownloadAndExtract();
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
await t.throwsAsync(
|
||||
setupCodeql.setupCodeQLBundle(
|
||||
fixture.releaseURL,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
tmpDir,
|
||||
GitHubVariant.DOTCOM,
|
||||
PER_LANGUAGE_CLI_VERSION,
|
||||
undefined, // rawLanguages
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
createFeatures([]),
|
||||
getRunnerLogger(true),
|
||||
),
|
||||
{
|
||||
instanceOf: ConfigurationError,
|
||||
message: `Could not find the CodeQL release ${fixture.releaseURL}. Check that it exists and that the token has access to it.`,
|
||||
},
|
||||
);
|
||||
t.is(fixture.requests.length, 1);
|
||||
t.true(extract.notCalled);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"setupCodeQLBundle uses the CLI version marker of a requested release",
|
||||
async (t) => {
|
||||
const fixture = stubRequestedRelease({
|
||||
tagName: "run-123",
|
||||
markers: [MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION],
|
||||
});
|
||||
const extract = stubDownloadAndExtract();
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const result = await setupCodeql.setupCodeQLBundle(
|
||||
fixture.releaseURL,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
tmpDir,
|
||||
GitHubVariant.DOTCOM,
|
||||
PER_LANGUAGE_CLI_VERSION,
|
||||
["java"],
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
createFeatures([Feature.PerLanguageBundles]),
|
||||
getRunnerLogger(true),
|
||||
);
|
||||
|
||||
// The tag doesn't give a version, so the job can only use the per-language bundle because of
|
||||
// the marker.
|
||||
t.is(extract.firstCall.args[0], fixture.assets[1].url);
|
||||
t.is(result.toolsVersion, MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION);
|
||||
t.is(
|
||||
result.toolsDownloadStatusReport?.perLanguage?.tools_bundle_language,
|
||||
BuiltInLanguage.java,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
for (const bundle of ["per-language", "combined", "fallback"] as const) {
|
||||
test.serial(
|
||||
`setupCodeQLBundle preserves the nightly version for a ${bundle} download`,
|
||||
|
||||
@@ -25,8 +25,12 @@ import {
|
||||
import {
|
||||
BundleSelection,
|
||||
BundleSelectionOptions,
|
||||
CodeQLRelease,
|
||||
getPublicRelease,
|
||||
getRelease,
|
||||
getReleaseCliVersion,
|
||||
getRequestedRelease,
|
||||
parseCodeQLReleaseUrl,
|
||||
selectBundle,
|
||||
} from "./codeql-release";
|
||||
import * as defaults from "./defaults.json";
|
||||
@@ -392,6 +396,10 @@ async function resolveDefaultCliVersion(
|
||||
* We handle the `tools` input in this order:
|
||||
*
|
||||
* - A local path is extracted without using the toolcache.
|
||||
* - A release URL selects a bundle from that release, and takes precedence over the `force_nightly`
|
||||
* feature flag. We don't use the toolcache, since a release may contain a different build than
|
||||
* the cached bundle for its version. Bundle URLs keep using the toolcache for the version in
|
||||
* their tag, for compatibility.
|
||||
* - `nightly` or `nightly-latest`, or the `force_nightly` feature flag in a dynamic workflow,
|
||||
* selects a bundle from the latest nightly release. We then continue with that bundle's URL.
|
||||
* - `linked`, or its old name `latest`, selects the version shipped with the Action.
|
||||
@@ -400,8 +408,9 @@ async function resolveDefaultCliVersion(
|
||||
* - Any other value is the URL of a bundle.
|
||||
* - Without a `tools` input, we use the default version.
|
||||
*
|
||||
* Apart from a local path, we look for the resolved version in the toolcache before downloading. A
|
||||
* cached version takes precedence even if the job could use a per-language bundle.
|
||||
* For other inputs apart from a local path, we look for the resolved version in the toolcache
|
||||
* before downloading. A cached version takes precedence even if the job could use a per-language
|
||||
* bundle.
|
||||
*
|
||||
* @param toolsInput The argument provided for the `tools` input, if any.
|
||||
* @param defaultCliVersion The default CLI version that's linked to the CodeQL Action.
|
||||
@@ -450,6 +459,11 @@ export async function getCodeQLSource(
|
||||
};
|
||||
}
|
||||
|
||||
const requestedRelease =
|
||||
toolsInput === undefined
|
||||
? undefined
|
||||
: parseCodeQLReleaseUrl(toolsInput, apiDetails);
|
||||
|
||||
/** Requested CLI version number, for example 2.12.6. */
|
||||
let cliVersion: string | undefined;
|
||||
/** Tag name of the CodeQL bundle, for example `codeql-bundle-20230120`. */
|
||||
@@ -461,10 +475,17 @@ export async function getCodeQLSource(
|
||||
*/
|
||||
let url: string | undefined;
|
||||
let bundle: CodeQLBundle | undefined;
|
||||
/** The release requested by URL, which we select the bundle from. */
|
||||
let release: CodeQLRelease | undefined;
|
||||
/** The page of a requested release whose bundles we don't cache. */
|
||||
let customReleaseURL: string | undefined;
|
||||
|
||||
// We allow forcing the nightly CLI via the FF for `dynamic` events (or in test mode) where the
|
||||
// `tools` input cannot be adjusted to explicitly request it.
|
||||
const canForceNightlyWithFF = isDynamicWorkflow() || util.isInTestMode();
|
||||
// `tools` input cannot be adjusted to explicitly request it. An explicitly requested release
|
||||
// takes precedence.
|
||||
const canForceNightlyWithFF =
|
||||
requestedRelease === undefined &&
|
||||
(isDynamicWorkflow() || util.isInTestMode());
|
||||
const forceNightlyValueFF = await features.getValue(Feature.ForceNightly);
|
||||
const forceNightly = forceNightlyValueFF && canForceNightlyWithFF;
|
||||
|
||||
@@ -585,6 +606,18 @@ export async function getCodeQLSource(
|
||||
cliVersion = version.cliVersion;
|
||||
tagName = version.tagName;
|
||||
}
|
||||
} else if (requestedRelease !== undefined) {
|
||||
release = await getRequestedRelease(
|
||||
{ apiClient: api.getApiClient() },
|
||||
requestedRelease,
|
||||
);
|
||||
tagName = requestedRelease.tagName;
|
||||
cliVersion = getReleaseCliVersion(
|
||||
tagName,
|
||||
release.assetNames ?? [],
|
||||
logger,
|
||||
);
|
||||
customReleaseURL = release.url;
|
||||
} else if (toolsInput !== undefined) {
|
||||
// Any other value is a bundle URL, including one we selected from the latest nightly above.
|
||||
// We use the version in its tag, if any, for the toolcache, so we assume that bundles with the
|
||||
@@ -612,7 +645,8 @@ export async function getCodeQLSource(
|
||||
}
|
||||
|
||||
const bundleVersion =
|
||||
tagName !== undefined
|
||||
// Custom releases aren't cached, and their tags needn't contain a bundle version.
|
||||
tagName !== undefined && customReleaseURL === undefined
|
||||
? tryGetBundleVersionFromTagName(tagName, logger)
|
||||
: undefined;
|
||||
const resolvedVersion =
|
||||
@@ -629,12 +663,16 @@ export async function getCodeQLSource(
|
||||
`URL: ${url ?? "unspecified"}.`,
|
||||
);
|
||||
|
||||
const codeqlFolder = await findCodeQLInToolcache(
|
||||
cliVersion,
|
||||
tagName,
|
||||
humanReadableVersion,
|
||||
logger,
|
||||
);
|
||||
// A custom release may contain a different build than the bundle with the same version.
|
||||
const codeqlFolder =
|
||||
customReleaseURL === undefined
|
||||
? await findCodeQLInToolcache(
|
||||
cliVersion,
|
||||
tagName,
|
||||
humanReadableVersion,
|
||||
logger,
|
||||
)
|
||||
: undefined;
|
||||
if (codeqlFolder) {
|
||||
if (cliVersion) {
|
||||
logger.info(
|
||||
@@ -671,24 +709,34 @@ export async function getCodeQLSource(
|
||||
let perLanguageBundleFallback: true | undefined;
|
||||
|
||||
if (!url) {
|
||||
if (tagName === undefined) {
|
||||
throw new Error(
|
||||
"Could not determine a release tag for the requested CodeQL bundle.",
|
||||
const selectionOptions: BundleSelectionOptions = {
|
||||
rawLanguages,
|
||||
cliVersion,
|
||||
platform: getBundlePlatform(),
|
||||
variant,
|
||||
tarSupportsZstd,
|
||||
};
|
||||
let selection: BundleSelection;
|
||||
if (release !== undefined) {
|
||||
selection = await selectBundle(
|
||||
{ env: getEnv(), features, logger },
|
||||
release,
|
||||
selectionOptions,
|
||||
);
|
||||
}
|
||||
({ bundle, compressionMethod, perLanguageBundleFallback } =
|
||||
await selectDefaultBundle(
|
||||
} else {
|
||||
if (tagName === undefined) {
|
||||
throw new Error(
|
||||
"Could not determine a release tag for the requested CodeQL bundle.",
|
||||
);
|
||||
}
|
||||
selection = await selectDefaultBundle(
|
||||
{ env: getEnv(), features, logger },
|
||||
tagName,
|
||||
apiDetails,
|
||||
{
|
||||
rawLanguages,
|
||||
cliVersion,
|
||||
platform: getBundlePlatform(),
|
||||
variant,
|
||||
tarSupportsZstd,
|
||||
},
|
||||
));
|
||||
selectionOptions,
|
||||
);
|
||||
}
|
||||
({ bundle, compressionMethod, perLanguageBundleFallback } = selection);
|
||||
url = bundle.url;
|
||||
} else {
|
||||
const method = tar.inferCompressionMethod(url);
|
||||
@@ -720,6 +768,7 @@ export async function getCodeQLSource(
|
||||
bundleVersion,
|
||||
cliVersion,
|
||||
compressionMethod,
|
||||
...(customReleaseURL !== undefined ? { customReleaseURL } : {}),
|
||||
...(perLanguageBundleFallback ? { perLanguageBundleFallback } : {}),
|
||||
sourceType: "download",
|
||||
toolsVersion: resolvedVersion ?? "unknown",
|
||||
@@ -905,12 +954,19 @@ export const downloadCodeQL = async function (
|
||||
* Returns the canonical toolcache directory, or the reason the bundle cannot be cached.
|
||||
*
|
||||
* The toolcache is keyed by version, so we don't cache bundles that would give a later request for
|
||||
* the same version the wrong tools, such as per-language bundles, which lack the other languages.
|
||||
* the same version the wrong tools: per-language bundles, which lack the other languages, and
|
||||
* custom releases, which may be a different build.
|
||||
*/
|
||||
function getToolcacheDestination(
|
||||
{ logger }: ActionState<["Logger"]>,
|
||||
source: CodeQLDownloadSource,
|
||||
): util.Result<string, string> {
|
||||
if (source.customReleaseURL !== undefined) {
|
||||
return new util.Failure(
|
||||
`Not caching the CodeQL tools from ${source.customReleaseURL}, since we don't cache ` +
|
||||
"releases requested by URL.",
|
||||
);
|
||||
}
|
||||
if (source.bundle.kind !== "combined") {
|
||||
return new util.Failure(
|
||||
"Not caching the CodeQL tools because they came from a bundle that contains only a " +
|
||||
|
||||
Reference in New Issue
Block a user