diff --git a/lib/analyze-action-post.js b/lib/analyze-action-post.js index d95b978758..775ae41f56 100644 --- a/lib/analyze-action-post.js +++ b/lib/analyze-action-post.js @@ -161755,6 +161755,11 @@ var featureConfig = { // cannot be found when interpreting results. minimumVersion: void 0 }, + ["start_proxy_use_features_release" /* StartProxyUseFeaturesRelease */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE", + minimumVersion: void 0 + }, ["upload_overlay_db_to_api" /* UploadOverlayDbToApi */]: { defaultValue: false, envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API", diff --git a/lib/analyze-action.js b/lib/analyze-action.js index c490eaad7a..4857bbb3bf 100644 --- a/lib/analyze-action.js +++ b/lib/analyze-action.js @@ -107853,6 +107853,11 @@ var featureConfig = { // cannot be found when interpreting results. minimumVersion: void 0 }, + ["start_proxy_use_features_release" /* StartProxyUseFeaturesRelease */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE", + minimumVersion: void 0 + }, ["upload_overlay_db_to_api" /* UploadOverlayDbToApi */]: { defaultValue: false, envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API", diff --git a/lib/autobuild-action.js b/lib/autobuild-action.js index 664212ec8e..500b3396dd 100644 --- a/lib/autobuild-action.js +++ b/lib/autobuild-action.js @@ -104142,6 +104142,11 @@ var featureConfig = { // cannot be found when interpreting results. minimumVersion: void 0 }, + ["start_proxy_use_features_release" /* StartProxyUseFeaturesRelease */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE", + minimumVersion: void 0 + }, ["upload_overlay_db_to_api" /* UploadOverlayDbToApi */]: { defaultValue: false, envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API", diff --git a/lib/init-action-post.js b/lib/init-action-post.js index e4d4060d48..f53b7d3677 100644 --- a/lib/init-action-post.js +++ b/lib/init-action-post.js @@ -165252,6 +165252,11 @@ var featureConfig = { // cannot be found when interpreting results. minimumVersion: void 0 }, + ["start_proxy_use_features_release" /* StartProxyUseFeaturesRelease */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE", + minimumVersion: void 0 + }, ["upload_overlay_db_to_api" /* UploadOverlayDbToApi */]: { defaultValue: false, envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API", diff --git a/lib/init-action.js b/lib/init-action.js index 74a627293c..66de01afed 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -105371,6 +105371,11 @@ var featureConfig = { // cannot be found when interpreting results. minimumVersion: void 0 }, + ["start_proxy_use_features_release" /* StartProxyUseFeaturesRelease */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE", + minimumVersion: void 0 + }, ["upload_overlay_db_to_api" /* UploadOverlayDbToApi */]: { defaultValue: false, envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API", diff --git a/lib/resolve-environment-action.js b/lib/resolve-environment-action.js index 3df530b887..6483f98fe9 100644 --- a/lib/resolve-environment-action.js +++ b/lib/resolve-environment-action.js @@ -104133,6 +104133,11 @@ var featureConfig = { // cannot be found when interpreting results. minimumVersion: void 0 }, + ["start_proxy_use_features_release" /* StartProxyUseFeaturesRelease */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE", + minimumVersion: void 0 + }, ["upload_overlay_db_to_api" /* UploadOverlayDbToApi */]: { defaultValue: false, envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API", diff --git a/lib/setup-codeql-action.js b/lib/setup-codeql-action.js index 179fbab6af..7f63d7b93e 100644 --- a/lib/setup-codeql-action.js +++ b/lib/setup-codeql-action.js @@ -104042,6 +104042,11 @@ var featureConfig = { // cannot be found when interpreting results. minimumVersion: void 0 }, + ["start_proxy_use_features_release" /* StartProxyUseFeaturesRelease */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE", + minimumVersion: void 0 + }, ["upload_overlay_db_to_api" /* UploadOverlayDbToApi */]: { defaultValue: false, envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API", diff --git a/lib/start-proxy-action-post.js b/lib/start-proxy-action-post.js index 7ddcd2426b..dffe7d2b1d 100644 --- a/lib/start-proxy-action-post.js +++ b/lib/start-proxy-action-post.js @@ -161161,6 +161161,11 @@ var featureConfig = { // cannot be found when interpreting results. minimumVersion: void 0 }, + ["start_proxy_use_features_release" /* StartProxyUseFeaturesRelease */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE", + minimumVersion: void 0 + }, ["upload_overlay_db_to_api" /* UploadOverlayDbToApi */]: { defaultValue: false, envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API", diff --git a/lib/start-proxy-action.js b/lib/start-proxy-action.js index 6409ded796..ca664e2504 100644 --- a/lib/start-proxy-action.js +++ b/lib/start-proxy-action.js @@ -120834,6 +120834,11 @@ var featureConfig = { // cannot be found when interpreting results. minimumVersion: void 0 }, + ["start_proxy_use_features_release" /* StartProxyUseFeaturesRelease */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE", + minimumVersion: void 0 + }, ["upload_overlay_db_to_api" /* UploadOverlayDbToApi */]: { defaultValue: false, envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API", @@ -121699,28 +121704,39 @@ function getProxyPackage() { function getFallbackUrl(proxyPackage) { return `${UPDATEJOB_PROXY_URL_PREFIX}${proxyPackage}`; } -async function getLinkedRelease() { +async function getReleaseByVersion(version) { return getApiClient().rest.repos.getReleaseByTag({ owner: "github", repo: "codeql-action", - tag: bundleVersion + tag: version }); } -async function getDownloadUrl(logger) { +async function getCliVersionFromFeatures(features) { + const gitHubVersion = await getGitHubVersion(); + return await features.getDefaultCliVersion(gitHubVersion.type); +} +async function getDownloadUrl(logger, features) { const proxyPackage = getProxyPackage(); try { - const cliRelease = await getLinkedRelease(); + const useFeaturesToDetermineCLI = await features.getValue( + "start_proxy_use_features_release" /* StartProxyUseFeaturesRelease */ + ); + const versionInfo = useFeaturesToDetermineCLI ? await getCliVersionFromFeatures(features) : { + cliVersion, + tagName: bundleVersion + }; + const cliRelease = await getReleaseByVersion(versionInfo.tagName); for (const asset of cliRelease.data.assets) { if (asset.name === proxyPackage) { logger.info( - `Found '${proxyPackage}' in release '${bundleVersion}' at '${asset.url}'` + `Found '${proxyPackage}' in release '${versionInfo.tagName}' at '${asset.url}'` ); return { url: asset.url, // The `update-job-proxy` doesn't have a version as such. Since we now bundle it // with CodeQL CLI bundle releases, we use the corresponding CLI version to // differentiate between (potentially) different versions of `update-job-proxy`. - version: cliVersion + version: versionInfo.cliVersion }; } } @@ -121781,9 +121797,9 @@ async function cacheProxy(logger, source, filename, version) { function getProxyFilename() { return process.platform === "win32" ? `${UPDATEJOB_PROXY}.exe` : UPDATEJOB_PROXY; } -async function getProxyBinaryPath(logger) { +async function getProxyBinaryPath(logger, features) { const proxyFileName = getProxyFilename(); - const proxyInfo = await getDownloadUrl(logger); + const proxyInfo = await getDownloadUrl(logger, features); let proxyBin = toolcache.find(proxyFileName, proxyInfo.version); if (!proxyBin) { const apiDetails = getApiDetails(); @@ -122141,7 +122157,7 @@ async function run(startedAt) { all_credentials: credentials, ca }; - const proxyBin = await getProxyBinaryPath(logger); + const proxyBin = await getProxyBinaryPath(logger, features); const proxyInfo = await startProxy( proxyBin, proxyConfig, diff --git a/lib/upload-lib.js b/lib/upload-lib.js index f7e8206dee..04d723f26f 100644 --- a/lib/upload-lib.js +++ b/lib/upload-lib.js @@ -107301,6 +107301,11 @@ var featureConfig = { // cannot be found when interpreting results. minimumVersion: void 0 }, + ["start_proxy_use_features_release" /* StartProxyUseFeaturesRelease */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE", + minimumVersion: void 0 + }, ["upload_overlay_db_to_api" /* UploadOverlayDbToApi */]: { defaultValue: false, envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API", diff --git a/lib/upload-sarif-action-post.js b/lib/upload-sarif-action-post.js index 010dfa8973..812e73e478 100644 --- a/lib/upload-sarif-action-post.js +++ b/lib/upload-sarif-action-post.js @@ -161323,6 +161323,11 @@ var featureConfig = { // cannot be found when interpreting results. minimumVersion: void 0 }, + ["start_proxy_use_features_release" /* StartProxyUseFeaturesRelease */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE", + minimumVersion: void 0 + }, ["upload_overlay_db_to_api" /* UploadOverlayDbToApi */]: { defaultValue: false, envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API", diff --git a/lib/upload-sarif-action.js b/lib/upload-sarif-action.js index 3459ded500..c14378e0d5 100644 --- a/lib/upload-sarif-action.js +++ b/lib/upload-sarif-action.js @@ -107026,6 +107026,11 @@ var featureConfig = { // cannot be found when interpreting results. minimumVersion: void 0 }, + ["start_proxy_use_features_release" /* StartProxyUseFeaturesRelease */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE", + minimumVersion: void 0 + }, ["upload_overlay_db_to_api" /* UploadOverlayDbToApi */]: { defaultValue: false, envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API", diff --git a/src/feature-flags.ts b/src/feature-flags.ts index 6155950a46..546d2e0ff8 100644 --- a/src/feature-flags.ts +++ b/src/feature-flags.ts @@ -77,6 +77,7 @@ export enum Feature { QaTelemetryEnabled = "qa_telemetry_enabled", /** Note that this currently only disables baseline file coverage information. */ SkipFileCoverageOnPrs = "skip_file_coverage_on_prs", + StartProxyUseFeaturesRelease = "start_proxy_use_features_release", UploadOverlayDbToApi = "upload_overlay_db_to_api", UseRepositoryProperties = "use_repository_properties_v2", ValidateDbConfig = "validate_db_config", @@ -327,6 +328,11 @@ export const featureConfig = { // cannot be found when interpreting results. minimumVersion: undefined, }, + [Feature.StartProxyUseFeaturesRelease]: { + defaultValue: false, + envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE", + minimumVersion: undefined, + }, [Feature.UploadOverlayDbToApi]: { defaultValue: false, envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API", diff --git a/src/start-proxy-action.ts b/src/start-proxy-action.ts index f95a169990..438d565ae8 100644 --- a/src/start-proxy-action.ts +++ b/src/start-proxy-action.ts @@ -5,7 +5,7 @@ import * as core from "@actions/core"; import * as actionsUtil from "./actions-util"; import { getGitHubVersion } from "./api-client"; -import { Feature, FeatureEnablement, initFeatures } from "./feature-flags"; +import { FeatureEnablement, initFeatures } from "./feature-flags"; import { KnownLanguage } from "./languages"; import { getActionsLogger, Logger } from "./logging"; import { getRepositoryNwo } from "./repository"; @@ -98,7 +98,7 @@ async function run(startedAt: Date) { }; // Start the Proxy - const proxyBin = await getProxyBinaryPath(logger); + const proxyBin = await getProxyBinaryPath(logger, features); const proxyInfo = await startProxy( proxyBin, proxyConfig, diff --git a/src/start-proxy.test.ts b/src/start-proxy.test.ts index 321a41a298..b1c4926f8a 100644 --- a/src/start-proxy.test.ts +++ b/src/start-proxy.test.ts @@ -7,6 +7,7 @@ import sinon from "sinon"; import * as apiClient from "./api-client"; import * as defaults from "./defaults.json"; +import { setUpFeatureFlagTests } from "./feature-flags/testing-util"; import { KnownLanguage } from "./languages"; import { getRunnerLogger, Logger } from "./logging"; import * as startProxyExports from "./start-proxy"; @@ -14,12 +15,19 @@ import { parseLanguage } from "./start-proxy"; import * as statusReport from "./status-report"; import { checkExpectedLogMessages, + createFeatures, getRecordingLogger, makeTestToken, + RecordingLogger, setupTests, withRecordingLoggerAsync, } from "./testing-utils"; -import { ConfigurationError } from "./util"; +import { + ConfigurationError, + GitHubVariant, + GitHubVersion, + withTmpDir, +} from "./util"; setupTests(test); @@ -347,8 +355,18 @@ test("parseLanguage", async (t) => { t.deepEqual(parseLanguage(""), undefined); }); -function mockGetReleaseByTag(assets?: Array<{ name: string; url?: string }>) { - const mockClient = sinon.stub(apiClient, "getApiClient"); +function mockGetApiClient(endpoints: any) { + return ( + sinon + .stub(apiClient, "getApiClient") + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + .returns({ rest: endpoints } as any) + ); +} + +type ReleaseAssets = Array<{ name: string; url?: string }>; + +function mockGetReleaseByTag(assets?: ReleaseAssets) { const getReleaseByTag = assets === undefined ? sinon.stub().rejects() @@ -359,57 +377,82 @@ function mockGetReleaseByTag(assets?: Array<{ name: string; url?: string }>) { url: "GET /repos/:owner/:repo/releases/tags/:tag", }); - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - mockClient.returns({ - rest: { - repos: { - getReleaseByTag, - }, - }, - } as any); - return mockClient; + return mockGetApiClient({ repos: { getReleaseByTag } }); } -test("getDownloadUrl returns fallback when `getLinkedRelease` rejects", async (t) => { - mockGetReleaseByTag(); - - const info = await startProxyExports.getDownloadUrl(getRunnerLogger(true)); +function mockOfflineFeatures(tempDir: string, logger: Logger) { + // Using GHES ensures that we are using `OfflineFeatures`. + const gitHubVersion = { + type: GitHubVariant.GHES, + version: "3.0.0", + }; + sinon.stub(apiClient, "getGitHubVersion").resolves(gitHubVersion); - t.is(info.version, startProxyExports.UPDATEJOB_PROXY_VERSION); - t.is( - info.url, - startProxyExports.getFallbackUrl(startProxyExports.getProxyPackage()), - ); -}); + return setUpFeatureFlagTests(tempDir, logger, gitHubVersion); +} -test("getDownloadUrl returns fallback when there's no matching release asset", async (t) => { - const testAssets = [[], [{ name: "foo" }]]; +test("getDownloadUrl returns fallback when `getReleaseByVersion` rejects", async (t) => { + const logger = new RecordingLogger(); + mockGetReleaseByTag(); - for (const assets of testAssets) { - const stub = mockGetReleaseByTag(assets); - const info = await startProxyExports.getDownloadUrl(getRunnerLogger(true)); + await withTmpDir(async (tempDir) => { + const features = mockOfflineFeatures(tempDir, logger); + const info = await startProxyExports.getDownloadUrl( + getRunnerLogger(true), + features, + ); t.is(info.version, startProxyExports.UPDATEJOB_PROXY_VERSION); t.is( info.url, startProxyExports.getFallbackUrl(startProxyExports.getProxyPackage()), ); + }); +}); - stub.restore(); - } +test("getDownloadUrl returns fallback when there's no matching release asset", async (t) => { + const logger = new RecordingLogger(); + const testAssets = [[], [{ name: "foo" }]]; + + await withTmpDir(async (tempDir) => { + const features = mockOfflineFeatures(tempDir, logger); + + for (const assets of testAssets) { + const stub = mockGetReleaseByTag(assets); + const info = await startProxyExports.getDownloadUrl( + getRunnerLogger(true), + features, + ); + + t.is(info.version, startProxyExports.UPDATEJOB_PROXY_VERSION); + t.is( + info.url, + startProxyExports.getFallbackUrl(startProxyExports.getProxyPackage()), + ); + + stub.restore(); + } + }); }); test("getDownloadUrl returns matching release asset", async (t) => { + const logger = new RecordingLogger(); const assets = [ { name: "foo", url: "other-url" }, { name: startProxyExports.getProxyPackage(), url: "url-we-want" }, ]; mockGetReleaseByTag(assets); - const info = await startProxyExports.getDownloadUrl(getRunnerLogger(true)); + await withTmpDir(async (tempDir) => { + const features = mockOfflineFeatures(tempDir, logger); + const info = await startProxyExports.getDownloadUrl( + getRunnerLogger(true), + features, + ); - t.is(info.version, defaults.cliVersion); - t.is(info.url, "url-we-want"); + t.is(info.version, defaults.cliVersion); + t.is(info.url, "url-we-want"); + }); }); test("credentialToStr - hides passwords", (t) => { @@ -560,13 +603,15 @@ test( ); test("getProxyBinaryPath - returns path from tool cache if available", async (t) => { + const logger = new RecordingLogger(); mockGetReleaseByTag(); - await withRecordingLoggerAsync(async (logger) => { + await withTmpDir(async (tempDir) => { const toolcachePath = "/path/to/proxy/dir"; sinon.stub(toolcache, "find").returns(toolcachePath); - const path = await startProxyExports.getProxyBinaryPath(logger); + const features = mockOfflineFeatures(tempDir, logger); + const path = await startProxyExports.getProxyBinaryPath(logger, features); t.assert(path); t.is( @@ -577,12 +622,80 @@ test("getProxyBinaryPath - returns path from tool cache if available", async (t) }); test("getProxyBinaryPath - downloads proxy if not in cache", async (t) => { + const logger = new RecordingLogger(); const downloadUrl = "url-we-want"; mockGetReleaseByTag([ { name: startProxyExports.getProxyPackage(), url: downloadUrl }, ]); - await withRecordingLoggerAsync(async (logger) => { + const toolcachePath = "/path/to/proxy/dir"; + const find = sinon.stub(toolcache, "find").returns(""); + const getApiDetails = sinon.stub(apiClient, "getApiDetails").returns({ + auth: "", + url: "", + apiURL: "", + }); + const getAuthorizationHeaderFor = sinon + .stub(apiClient, "getAuthorizationHeaderFor") + .returns(undefined); + const archivePath = "/path/to/archive"; + const downloadTool = sinon + .stub(toolcache, "downloadTool") + .resolves(archivePath); + const extractedPath = "/path/to/extracted"; + const extractTar = sinon + .stub(toolcache, "extractTar") + .resolves(extractedPath); + const cacheDir = sinon.stub(toolcache, "cacheDir").resolves(toolcachePath); + + const path = await startProxyExports.getProxyBinaryPath( + logger, + createFeatures([]), + ); + + t.assert(find.calledOnce); + t.assert(getApiDetails.calledOnce); + t.assert(getAuthorizationHeaderFor.calledOnce); + t.assert(downloadTool.calledOnceWith(downloadUrl)); + t.assert(extractTar.calledOnceWith(archivePath)); + t.assert(cacheDir.calledOnceWith(extractedPath)); + t.assert(path); + t.is( + path, + filepath.join(toolcachePath, startProxyExports.getProxyFilename()), + ); + + checkExpectedLogMessages(t, logger.messages, [ + `Found '${startProxyExports.getProxyPackage()}' in release '${defaults.bundleVersion}' at '${downloadUrl}'`, + ]); +}); + +test("getProxyBinaryPath - downloads proxy based on features if not in cache", async (t) => { + const logger = new RecordingLogger(); + const expectedTag = "codeql-bundle-v2.20.1"; + const expectedParams = { + owner: "github", + repo: "codeql-action", + tag: expectedTag, + }; + const downloadUrl = "url-we-want"; + const assets = [ + { + name: startProxyExports.getProxyPackage(), + url: downloadUrl, + }, + ]; + + const getReleaseByTag = sinon.stub(); + getReleaseByTag.withArgs(sinon.match(expectedParams)).resolves({ + status: 200, + data: { assets }, + headers: {}, + url: "GET /repos/:owner/:repo/releases/tags/:tag", + }); + mockGetApiClient({ repos: { getReleaseByTag } }); + + await withTmpDir(async (tempDir) => { const toolcachePath = "/path/to/proxy/dir"; const find = sinon.stub(toolcache, "find").returns(""); const getApiDetails = sinon.stub(apiClient, "getApiDetails").returns({ @@ -603,8 +716,25 @@ test("getProxyBinaryPath - downloads proxy if not in cache", async (t) => { .resolves(extractedPath); const cacheDir = sinon.stub(toolcache, "cacheDir").resolves(toolcachePath); - const path = await startProxyExports.getProxyBinaryPath(logger); + const gitHubVersion: GitHubVersion = { + type: GitHubVariant.DOTCOM, + }; + sinon.stub(apiClient, "getGitHubVersion").resolves(gitHubVersion); + const features = setUpFeatureFlagTests(tempDir, logger, gitHubVersion); + sinon.stub(features, "getValue").callsFake(async (_feature, _codeql) => { + return true; + }); + const getDefaultCliVersion = sinon + .stub(features, "getDefaultCliVersion") + .resolves({ cliVersion: "2.20.1", tagName: expectedTag }); + const path = await startProxyExports.getProxyBinaryPath(logger, features); + + t.assert(getDefaultCliVersion.calledOnce); + sinon.assert.calledOnceWithMatch( + getReleaseByTag, + sinon.match(expectedParams), + ); t.assert(find.calledOnce); t.assert(getApiDetails.calledOnce); t.assert(getAuthorizationHeaderFor.calledOnce); @@ -618,4 +748,8 @@ test("getProxyBinaryPath - downloads proxy if not in cache", async (t) => { filepath.join(toolcachePath, startProxyExports.getProxyFilename()), ); }); + + checkExpectedLogMessages(t, logger.messages, [ + `Found '${startProxyExports.getProxyPackage()}' in release '${expectedTag}' at '${downloadUrl}'`, + ]); }); diff --git a/src/start-proxy.ts b/src/start-proxy.ts index 755c0a40c8..7ed466a413 100644 --- a/src/start-proxy.ts +++ b/src/start-proxy.ts @@ -7,10 +7,16 @@ import { getApiClient, getApiDetails, getAuthorizationHeaderFor, + getGitHubVersion, } from "./api-client"; import * as artifactScanner from "./artifact-scanner"; import { Config } from "./config-utils"; import * as defaults from "./defaults.json"; +import { + CodeQLDefaultVersionInfo, + Feature, + FeatureEnablement, +} from "./feature-flags"; import { KnownLanguage } from "./languages"; import { Logger } from "./logging"; import { @@ -391,46 +397,69 @@ export function getFallbackUrl(proxyPackage: string): string { /** * Uses the GitHub API to obtain information about the CodeQL CLI bundle release - * that is pointed at by `defaults.json`. + * that is tagged by `version`. * * @returns The response from the GitHub API. */ -async function getLinkedRelease() { +async function getReleaseByVersion(version: string) { return getApiClient().rest.repos.getReleaseByTag({ owner: "github", repo: "codeql-action", - tag: defaults.bundleVersion, + tag: version, }); } +/** Uses `features` to determine the default CLI version. */ +async function getCliVersionFromFeatures( + features: FeatureEnablement, +): Promise { + const gitHubVersion = await getGitHubVersion(); + return await features.getDefaultCliVersion(gitHubVersion.type); +} + /** * Determines the URL of the proxy release asset that we should download if its not * already in the toolcache, and its version. * * @param logger The logger to use. + * @param features Information about enabled features. * @returns Returns the download URL and version of the proxy package we plan to use. */ export async function getDownloadUrl( logger: Logger, + features: FeatureEnablement, ): Promise<{ url: string; version: string }> { const proxyPackage = getProxyPackage(); try { - // Try to retrieve information about the CLI bundle release pointed at by `defaults.json`. - const cliRelease = await getLinkedRelease(); + const useFeaturesToDetermineCLI = await features.getValue( + Feature.StartProxyUseFeaturesRelease, + ); + + // Retrieve information about the CLI version we should use. This will be either the linked + // version, or the one enabled by FFs. + const versionInfo = useFeaturesToDetermineCLI + ? await getCliVersionFromFeatures(features) + : { + cliVersion: defaults.cliVersion, + tagName: defaults.bundleVersion, + }; + + // Try to retrieve information about the CLI bundle release identified by `versionInfo`. + const cliRelease = await getReleaseByVersion(versionInfo.tagName); // Search the release's assets to find the one we are looking for. for (const asset of cliRelease.data.assets) { if (asset.name === proxyPackage) { logger.info( - `Found '${proxyPackage}' in release '${defaults.bundleVersion}' at '${asset.url}'`, + `Found '${proxyPackage}' in release '${versionInfo.tagName}' at '${asset.url}'`, ); return { url: asset.url, // The `update-job-proxy` doesn't have a version as such. Since we now bundle it // with CodeQL CLI bundle releases, we use the corresponding CLI version to // differentiate between (potentially) different versions of `update-job-proxy`. - version: defaults.cliVersion, + version: versionInfo.cliVersion, }; } } @@ -548,9 +577,12 @@ export function getProxyFilename() { * @param logger The logger to use. * @returns The path to the proxy binary. */ -export async function getProxyBinaryPath(logger: Logger): Promise { +export async function getProxyBinaryPath( + logger: Logger, + features: FeatureEnablement, +): Promise { const proxyFileName = getProxyFilename(); - const proxyInfo = await getDownloadUrl(logger); + const proxyInfo = await getDownloadUrl(logger, features); let proxyBin = toolcache.find(proxyFileName, proxyInfo.version); if (!proxyBin) {