diff --git a/init/action.yml b/init/action.yml index ba5d6efcc..5b2baaeca 100644 --- a/init/action.yml +++ b/init/action.yml @@ -15,6 +15,8 @@ inputs: - A special value `nightly` which uses the latest nightly version of the CodeQL tools. Note that this is unstable and not recommended for production use. + - A special value `toolcache` which uses the latest version available in the + toolcache on the runner. If not specified, the Action will check in several places until it finds the CodeQL tools. diff --git a/lib/analyze-action.js b/lib/analyze-action.js index 6951aaef0..d0486873f 100644 --- a/lib/analyze-action.js +++ b/lib/analyze-action.js @@ -92111,6 +92111,7 @@ var CODEQL_NIGHTLIES_REPOSITORY_OWNER = "dsp-testing"; var CODEQL_NIGHTLIES_REPOSITORY_NAME = "codeql-cli-nightlies"; var CODEQL_BUNDLE_VERSION_ALIAS = ["linked", "latest"]; var CODEQL_NIGHTLY_TOOLS_INPUTS = ["nightly", "nightly-latest"]; +var CODEQL_TOOLCACHE_INPUT = "toolcache"; function getCodeQLBundleExtension(compressionMethod) { switch (compressionMethod) { case "gzip": @@ -92289,6 +92290,20 @@ async function getCodeQLSource(toolsInput, defaultCliVersion, apiDetails, varian "`tools: latest` has been renamed to `tools: linked`, but the old name is still supported. No action is required." ); } + } else if (toolsInput !== void 0 && toolsInput === CODEQL_TOOLCACHE_INPUT) { + logger.info( + `Attempting to use the latest CodeQL CLI version in the toolcache, as requested by 'tools: ${toolsInput}'.` + ); + const latestToolcacheVersion = getLatestToolcacheVersion(logger); + if (latestToolcacheVersion) { + cliVersion2 = latestToolcacheVersion; + } else { + logger.info( + `Found no CodeQL CLI in the toolcache, ignoring 'tools: ${toolsInput}'...` + ); + cliVersion2 = defaultCliVersion.cliVersion; + tagName = defaultCliVersion.tagName; + } } else if (toolsInput !== void 0) { tagName = tryGetTagNameFromUrl(toolsInput, logger); url2 = toolsInput; @@ -92595,8 +92610,24 @@ async function getNightlyToolsUrl(logger) { ); } } +function getLatestToolcacheVersion(logger) { + const allVersions = toolcache3.findAllVersions("CodeQL").sort((a, b) => semver7.lt(a, b) ? 1 : -1); + logger.debug( + `Found the following versions of the CodeQL tools in the toolcache: ${JSON.stringify( + allVersions + )}.` + ); + if (allVersions.length > 0) { + const latestToolcacheVersion = allVersions[0]; + logger.info( + `CLI version ${latestToolcacheVersion} is the latest version in the toolcache.` + ); + return latestToolcacheVersion; + } + return void 0; +} function isReservedToolsValue(tools) { - return CODEQL_BUNDLE_VERSION_ALIAS.includes(tools) || CODEQL_NIGHTLY_TOOLS_INPUTS.includes(tools); + return CODEQL_BUNDLE_VERSION_ALIAS.includes(tools) || CODEQL_NIGHTLY_TOOLS_INPUTS.includes(tools) || tools === CODEQL_TOOLCACHE_INPUT; } // src/tracer-config.ts diff --git a/lib/init-action-post.js b/lib/init-action-post.js index def7116e4..c905b89bc 100644 --- a/lib/init-action-post.js +++ b/lib/init-action-post.js @@ -130093,6 +130093,7 @@ var CODEQL_NIGHTLIES_REPOSITORY_OWNER = "dsp-testing"; var CODEQL_NIGHTLIES_REPOSITORY_NAME = "codeql-cli-nightlies"; var CODEQL_BUNDLE_VERSION_ALIAS = ["linked", "latest"]; var CODEQL_NIGHTLY_TOOLS_INPUTS = ["nightly", "nightly-latest"]; +var CODEQL_TOOLCACHE_INPUT = "toolcache"; function getCodeQLBundleExtension(compressionMethod) { switch (compressionMethod) { case "gzip": @@ -130271,6 +130272,20 @@ async function getCodeQLSource(toolsInput, defaultCliVersion, apiDetails, varian "`tools: latest` has been renamed to `tools: linked`, but the old name is still supported. No action is required." ); } + } else if (toolsInput !== void 0 && toolsInput === CODEQL_TOOLCACHE_INPUT) { + logger.info( + `Attempting to use the latest CodeQL CLI version in the toolcache, as requested by 'tools: ${toolsInput}'.` + ); + const latestToolcacheVersion = getLatestToolcacheVersion(logger); + if (latestToolcacheVersion) { + cliVersion2 = latestToolcacheVersion; + } else { + logger.info( + `Found no CodeQL CLI in the toolcache, ignoring 'tools: ${toolsInput}'...` + ); + cliVersion2 = defaultCliVersion.cliVersion; + tagName = defaultCliVersion.tagName; + } } else if (toolsInput !== void 0) { tagName = tryGetTagNameFromUrl(toolsInput, logger); url2 = toolsInput; @@ -130577,8 +130592,24 @@ async function getNightlyToolsUrl(logger) { ); } } +function getLatestToolcacheVersion(logger) { + const allVersions = toolcache3.findAllVersions("CodeQL").sort((a, b) => semver7.lt(a, b) ? 1 : -1); + logger.debug( + `Found the following versions of the CodeQL tools in the toolcache: ${JSON.stringify( + allVersions + )}.` + ); + if (allVersions.length > 0) { + const latestToolcacheVersion = allVersions[0]; + logger.info( + `CLI version ${latestToolcacheVersion} is the latest version in the toolcache.` + ); + return latestToolcacheVersion; + } + return void 0; +} function isReservedToolsValue(tools) { - return CODEQL_BUNDLE_VERSION_ALIAS.includes(tools) || CODEQL_NIGHTLY_TOOLS_INPUTS.includes(tools); + return CODEQL_BUNDLE_VERSION_ALIAS.includes(tools) || CODEQL_NIGHTLY_TOOLS_INPUTS.includes(tools) || tools === CODEQL_TOOLCACHE_INPUT; } // src/tracer-config.ts diff --git a/lib/init-action.js b/lib/init-action.js index 42e063aad..32512e9df 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -88886,6 +88886,7 @@ var CODEQL_NIGHTLIES_REPOSITORY_OWNER = "dsp-testing"; var CODEQL_NIGHTLIES_REPOSITORY_NAME = "codeql-cli-nightlies"; var CODEQL_BUNDLE_VERSION_ALIAS = ["linked", "latest"]; var CODEQL_NIGHTLY_TOOLS_INPUTS = ["nightly", "nightly-latest"]; +var CODEQL_TOOLCACHE_INPUT = "toolcache"; function getCodeQLBundleExtension(compressionMethod) { switch (compressionMethod) { case "gzip": @@ -89064,6 +89065,20 @@ async function getCodeQLSource(toolsInput, defaultCliVersion, apiDetails, varian "`tools: latest` has been renamed to `tools: linked`, but the old name is still supported. No action is required." ); } + } else if (toolsInput !== void 0 && toolsInput === CODEQL_TOOLCACHE_INPUT) { + logger.info( + `Attempting to use the latest CodeQL CLI version in the toolcache, as requested by 'tools: ${toolsInput}'.` + ); + const latestToolcacheVersion = getLatestToolcacheVersion(logger); + if (latestToolcacheVersion) { + cliVersion2 = latestToolcacheVersion; + } else { + logger.info( + `Found no CodeQL CLI in the toolcache, ignoring 'tools: ${toolsInput}'...` + ); + cliVersion2 = defaultCliVersion.cliVersion; + tagName = defaultCliVersion.tagName; + } } else if (toolsInput !== void 0) { tagName = tryGetTagNameFromUrl(toolsInput, logger); url = toolsInput; @@ -89370,8 +89385,24 @@ async function getNightlyToolsUrl(logger) { ); } } +function getLatestToolcacheVersion(logger) { + const allVersions = toolcache3.findAllVersions("CodeQL").sort((a, b) => semver7.lt(a, b) ? 1 : -1); + logger.debug( + `Found the following versions of the CodeQL tools in the toolcache: ${JSON.stringify( + allVersions + )}.` + ); + if (allVersions.length > 0) { + const latestToolcacheVersion = allVersions[0]; + logger.info( + `CLI version ${latestToolcacheVersion} is the latest version in the toolcache.` + ); + return latestToolcacheVersion; + } + return void 0; +} function isReservedToolsValue(tools) { - return CODEQL_BUNDLE_VERSION_ALIAS.includes(tools) || CODEQL_NIGHTLY_TOOLS_INPUTS.includes(tools); + return CODEQL_BUNDLE_VERSION_ALIAS.includes(tools) || CODEQL_NIGHTLY_TOOLS_INPUTS.includes(tools) || tools === CODEQL_TOOLCACHE_INPUT; } // src/tracer-config.ts diff --git a/lib/upload-lib.js b/lib/upload-lib.js index 63d887a2b..e0cbdcbb5 100644 --- a/lib/upload-lib.js +++ b/lib/upload-lib.js @@ -89927,6 +89927,7 @@ var CODEQL_NIGHTLIES_REPOSITORY_OWNER = "dsp-testing"; var CODEQL_NIGHTLIES_REPOSITORY_NAME = "codeql-cli-nightlies"; var CODEQL_BUNDLE_VERSION_ALIAS = ["linked", "latest"]; var CODEQL_NIGHTLY_TOOLS_INPUTS = ["nightly", "nightly-latest"]; +var CODEQL_TOOLCACHE_INPUT = "toolcache"; function getCodeQLBundleExtension(compressionMethod) { switch (compressionMethod) { case "gzip": @@ -90105,6 +90106,20 @@ async function getCodeQLSource(toolsInput, defaultCliVersion, apiDetails, varian "`tools: latest` has been renamed to `tools: linked`, but the old name is still supported. No action is required." ); } + } else if (toolsInput !== void 0 && toolsInput === CODEQL_TOOLCACHE_INPUT) { + logger.info( + `Attempting to use the latest CodeQL CLI version in the toolcache, as requested by 'tools: ${toolsInput}'.` + ); + const latestToolcacheVersion = getLatestToolcacheVersion(logger); + if (latestToolcacheVersion) { + cliVersion2 = latestToolcacheVersion; + } else { + logger.info( + `Found no CodeQL CLI in the toolcache, ignoring 'tools: ${toolsInput}'...` + ); + cliVersion2 = defaultCliVersion.cliVersion; + tagName = defaultCliVersion.tagName; + } } else if (toolsInput !== void 0) { tagName = tryGetTagNameFromUrl(toolsInput, logger); url2 = toolsInput; @@ -90411,8 +90426,24 @@ async function getNightlyToolsUrl(logger) { ); } } +function getLatestToolcacheVersion(logger) { + const allVersions = toolcache3.findAllVersions("CodeQL").sort((a, b) => semver7.lt(a, b) ? 1 : -1); + logger.debug( + `Found the following versions of the CodeQL tools in the toolcache: ${JSON.stringify( + allVersions + )}.` + ); + if (allVersions.length > 0) { + const latestToolcacheVersion = allVersions[0]; + logger.info( + `CLI version ${latestToolcacheVersion} is the latest version in the toolcache.` + ); + return latestToolcacheVersion; + } + return void 0; +} function isReservedToolsValue(tools) { - return CODEQL_BUNDLE_VERSION_ALIAS.includes(tools) || CODEQL_NIGHTLY_TOOLS_INPUTS.includes(tools); + return CODEQL_BUNDLE_VERSION_ALIAS.includes(tools) || CODEQL_NIGHTLY_TOOLS_INPUTS.includes(tools) || tools === CODEQL_TOOLCACHE_INPUT; } // src/tracer-config.ts diff --git a/lib/upload-sarif-action.js b/lib/upload-sarif-action.js index 30ed1a1bd..d8ec6eabe 100644 --- a/lib/upload-sarif-action.js +++ b/lib/upload-sarif-action.js @@ -90599,6 +90599,7 @@ var CODEQL_NIGHTLIES_REPOSITORY_OWNER = "dsp-testing"; var CODEQL_NIGHTLIES_REPOSITORY_NAME = "codeql-cli-nightlies"; var CODEQL_BUNDLE_VERSION_ALIAS = ["linked", "latest"]; var CODEQL_NIGHTLY_TOOLS_INPUTS = ["nightly", "nightly-latest"]; +var CODEQL_TOOLCACHE_INPUT = "toolcache"; function getCodeQLBundleExtension(compressionMethod) { switch (compressionMethod) { case "gzip": @@ -90777,6 +90778,20 @@ async function getCodeQLSource(toolsInput, defaultCliVersion, apiDetails, varian "`tools: latest` has been renamed to `tools: linked`, but the old name is still supported. No action is required." ); } + } else if (toolsInput !== void 0 && toolsInput === CODEQL_TOOLCACHE_INPUT) { + logger.info( + `Attempting to use the latest CodeQL CLI version in the toolcache, as requested by 'tools: ${toolsInput}'.` + ); + const latestToolcacheVersion = getLatestToolcacheVersion(logger); + if (latestToolcacheVersion) { + cliVersion2 = latestToolcacheVersion; + } else { + logger.info( + `Found no CodeQL CLI in the toolcache, ignoring 'tools: ${toolsInput}'...` + ); + cliVersion2 = defaultCliVersion.cliVersion; + tagName = defaultCliVersion.tagName; + } } else if (toolsInput !== void 0) { tagName = tryGetTagNameFromUrl(toolsInput, logger); url2 = toolsInput; @@ -91083,8 +91098,24 @@ async function getNightlyToolsUrl(logger) { ); } } +function getLatestToolcacheVersion(logger) { + const allVersions = toolcache3.findAllVersions("CodeQL").sort((a, b) => semver7.lt(a, b) ? 1 : -1); + logger.debug( + `Found the following versions of the CodeQL tools in the toolcache: ${JSON.stringify( + allVersions + )}.` + ); + if (allVersions.length > 0) { + const latestToolcacheVersion = allVersions[0]; + logger.info( + `CLI version ${latestToolcacheVersion} is the latest version in the toolcache.` + ); + return latestToolcacheVersion; + } + return void 0; +} function isReservedToolsValue(tools) { - return CODEQL_BUNDLE_VERSION_ALIAS.includes(tools) || CODEQL_NIGHTLY_TOOLS_INPUTS.includes(tools); + return CODEQL_BUNDLE_VERSION_ALIAS.includes(tools) || CODEQL_NIGHTLY_TOOLS_INPUTS.includes(tools) || tools === CODEQL_TOOLCACHE_INPUT; } // src/tracer-config.ts diff --git a/src/setup-codeql.test.ts b/src/setup-codeql.test.ts index f2f199b7d..f2bb7efb4 100644 --- a/src/setup-codeql.test.ts +++ b/src/setup-codeql.test.ts @@ -255,6 +255,113 @@ test("setupCodeQLBundle logs the CodeQL CLI version being used when asked to dow }); }); +test("getCodeQLSource correctly returns latest version from toolcache when tools == toolcache", async (t) => { + const loggedMessages: LoggedMessage[] = []; + const logger = getRecordingLogger(loggedMessages); + + const latestToolcacheVersion = "3.2.1"; + const latestVersionPath = "/path/to/latest"; + const testVersions = ["2.3.1", latestToolcacheVersion, "1.2.3"]; + const findAllVersionsStub = sinon + .stub(toolcache, "findAllVersions") + .returns(testVersions); + const findStub = sinon.stub(toolcache, "find"); + findStub + .withArgs("CodeQL", latestToolcacheVersion) + .returns(latestVersionPath); + + await withTmpDir(async (tmpDir) => { + setupActionsVars(tmpDir, tmpDir); + const source = await setupCodeql.getCodeQLSource( + "toolcache", + SAMPLE_DEFAULT_CLI_VERSION, + SAMPLE_DOTCOM_API_DETAILS, + GitHubVariant.DOTCOM, + false, + logger, + ); + + // Check that the toolcache functions were called with the expected arguments + t.assert( + findAllVersionsStub.calledOnceWith("CodeQL"), + `toolcache.findAllVersions("CodeQL") wasn't called`, + ); + t.assert( + findStub.calledOnceWith("CodeQL", latestToolcacheVersion), + `toolcache.find("CodeQL", ${latestToolcacheVersion}) wasn't called`, + ); + + // Check that `sourceType` and `toolsVersion` match expectations. + t.is(source.sourceType, "toolcache"); + t.is(source.toolsVersion, latestToolcacheVersion); + + // Check that key messages we would expect to find in the log are present. + const expectedMessages: string[] = [ + `Attempting to use the latest CodeQL CLI version in the toolcache, as requested by 'tools: toolcache'.`, + `CLI version ${latestToolcacheVersion} is the latest version in the toolcache.`, + `Using CodeQL CLI version ${latestToolcacheVersion} from toolcache at ${latestVersionPath}`, + ]; + for (const expectedMessage of expectedMessages) { + t.assert( + loggedMessages.some( + (msg) => + typeof msg.message === "string" && + msg.message.includes(expectedMessage), + ), + `Expected '${expectedMessage}' in the logger output, but didn't find it.`, + ); + } + }); +}); + +test("getCodeQLSource falls back to downloading the CLI if the toolcache doesn't have a CodeQL CLI when tools == toolcache", async (t) => { + const loggedMessages: LoggedMessage[] = []; + const logger = getRecordingLogger(loggedMessages); + + const testVersions = []; + const findAllVersionsStub = sinon + .stub(toolcache, "findAllVersions") + .returns(testVersions); + + await withTmpDir(async (tmpDir) => { + setupActionsVars(tmpDir, tmpDir); + const source = await setupCodeql.getCodeQLSource( + "toolcache", + SAMPLE_DEFAULT_CLI_VERSION, + SAMPLE_DOTCOM_API_DETAILS, + GitHubVariant.DOTCOM, + false, + logger, + ); + + // Check that the toolcache functions were called with the expected arguments + t.assert( + findAllVersionsStub.calledWith("CodeQL"), + `toolcache.findAllVersions("CodeQL") wasn't called`, + ); + + // Check that `sourceType` and `toolsVersion` match expectations. + t.is(source.sourceType, "download"); + t.is(source.toolsVersion, SAMPLE_DEFAULT_CLI_VERSION.cliVersion); + + // Check that key messages we would expect to find in the log are present. + const expectedMessages: string[] = [ + `Attempting to use the latest CodeQL CLI version in the toolcache, as requested by 'tools: toolcache'.`, + `Found no CodeQL CLI in the toolcache, ignoring 'tools: toolcache'...`, + ]; + for (const expectedMessage of expectedMessages) { + t.assert( + loggedMessages.some( + (msg) => + typeof msg.message === "string" && + msg.message.includes(expectedMessage), + ), + `Expected '${expectedMessage}' in the logger output, but didn't find it.`, + ); + } + }); +}); + test('tryGetTagNameFromUrl extracts the right tag name for a repo name containing "codeql-bundle"', (t) => { t.is( setupCodeql.tryGetTagNameFromUrl( diff --git a/src/setup-codeql.ts b/src/setup-codeql.ts index 4f5db88f3..5ae0c5fa3 100644 --- a/src/setup-codeql.ts +++ b/src/setup-codeql.ts @@ -38,6 +38,7 @@ const CODEQL_NIGHTLIES_REPOSITORY_NAME = "codeql-cli-nightlies"; const CODEQL_BUNDLE_VERSION_ALIAS: string[] = ["linked", "latest"]; const CODEQL_NIGHTLY_TOOLS_INPUTS = ["nightly", "nightly-latest"]; +const CODEQL_TOOLCACHE_INPUT = "toolcache"; function getCodeQLBundleExtension( compressionMethod: tar.CompressionMethod, @@ -346,6 +347,27 @@ export async function getCodeQLSource( "`tools: latest` has been renamed to `tools: linked`, but the old name is still supported. No action is required.", ); } + } else if ( + toolsInput !== undefined && + toolsInput === CODEQL_TOOLCACHE_INPUT + ) { + // If `toolsInput === "toolcache"`, try to find the latest version of the CLI that's available in the toolcache + // and use that. We perform this check here since we can set `cliVersion` directly and don't want to default to + // the linked version. + logger.info( + `Attempting to use the latest CodeQL CLI version in the toolcache, as requested by 'tools: ${toolsInput}'.`, + ); + + const latestToolcacheVersion = getLatestToolcacheVersion(logger); + if (latestToolcacheVersion) { + cliVersion = latestToolcacheVersion; + } else { + logger.info( + `Found no CodeQL CLI in the toolcache, ignoring 'tools: ${toolsInput}'...`, + ); + cliVersion = defaultCliVersion.cliVersion; + tagName = defaultCliVersion.tagName; + } } else if (toolsInput !== undefined) { // If a tools URL was provided, then use that. tagName = tryGetTagNameFromUrl(toolsInput, logger); @@ -847,6 +869,7 @@ export function getLatestToolcacheVersion(logger: Logger): string | undefined { function isReservedToolsValue(tools: string): boolean { return ( CODEQL_BUNDLE_VERSION_ALIAS.includes(tools) || - CODEQL_NIGHTLY_TOOLS_INPUTS.includes(tools) + CODEQL_NIGHTLY_TOOLS_INPUTS.includes(tools) || + tools === CODEQL_TOOLCACHE_INPUT ); }