Use getCodeQLAssetDownloadURL for start-proxy

This commit is contained in:
Michael B. Gale
2026-10-06 16:13:32 +01:00
parent de99f0ee4b
commit 133ffebe62
4 changed files with 167 additions and 159 deletions

62
lib/entry-points.js generated
View File

@@ -187489,8 +187489,6 @@ async function sendFailedStatusReport(logger2, startedAt, language, unwrappedErr
}
}
var UPDATEJOB_PROXY = "update-job-proxy";
var UPDATEJOB_PROXY_VERSION = "v2.0.20250624110901";
var UPDATEJOB_PROXY_URL_PREFIX = "https://github.com/github/codeql-action/releases/download/codeql-bundle-v2.22.0/";
function isPAT(value) {
return isAuthToken(value, [
GITHUB_PAT_CLASSIC_PATTERN,
@@ -187608,21 +187606,11 @@ function getCredentials(logger2, registrySecrets, registriesCredentials, languag
function getProxyPackage(platform2) {
return `${UPDATEJOB_PROXY}-${platform2}.tar.gz`;
}
function getFallbackUrl(proxyPackage) {
return `${UPDATEJOB_PROXY_URL_PREFIX}${proxyPackage}`;
}
async function getReleaseByVersion(version) {
return getApiClient().rest.repos.getReleaseByTag({
owner: "github",
repo: "codeql-action",
tag: version
});
}
async function getCliVersionFromFeatures(features) {
const gitHubVersion = await getGitHubVersion();
return await features.getEnabledDefaultCliVersions(gitHubVersion.type);
}
async function getDownloadUrl(action) {
async function getDownloadUrl(action, apiDetails) {
let platform2 = getBundlePlatform(action.platform, action.arch);
if (platform2 === void 0) {
action.logger.warning(
@@ -187639,33 +187627,25 @@ async function getDownloadUrl(action) {
cliVersion,
tagName: bundleVersion
};
const cliRelease = await getReleaseByVersion(versionInfo.tagName);
for (const asset of cliRelease.data.assets) {
if (asset.name === proxyPackage) {
action.logger.info(
`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: versionInfo.cliVersion
};
}
}
const proxyAssetUrl = await getCodeQLAssetDownloadURL(
action,
apiDetails,
versionInfo.tagName,
proxyPackage,
"private registry proxy"
);
return {
url: proxyAssetUrl,
// 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: versionInfo.cliVersion
};
} catch (ex) {
action.logger.warning(
throw new Error(
`Failed to retrieve information about the linked release: ${getErrorMessage(ex)}`
);
}
action.logger.info(
`Did not find '${proxyPackage}' in the linked release, falling back to hard-coded version.`
);
return {
url: getFallbackUrl(proxyPackage),
version: UPDATEJOB_PROXY_VERSION
};
}
async function downloadProxy(logger2, url2, authorization) {
try {
@@ -187708,13 +187688,12 @@ async function cacheProxy(logger2, source, filename, version) {
function getProxyFilename() {
return process.platform === "win32" ? `${UPDATEJOB_PROXY}.exe` : UPDATEJOB_PROXY;
}
async function getProxyBinaryPath(action) {
async function getProxyBinaryPath(action, apiDetails) {
const logger2 = action.logger;
const proxyFileName = getProxyFilename();
const proxyInfo = await getDownloadUrl(action);
const proxyInfo = await getDownloadUrl(action, apiDetails);
let proxyBin = toolcache4.find(proxyFileName, proxyInfo.version);
if (!proxyBin) {
const apiDetails = getApiDetails();
const authorization = getAuthorizationHeaderFor(
logger2,
apiDetails,
@@ -188091,7 +188070,10 @@ async function run7(action) {
all_credentials: credentials,
ca
};
const proxyBin = await getProxyBinaryPath({ ...action, features });
const proxyBin = await getProxyBinaryPath(
{ ...action, features },
getApiDetails(action.env)
);
const proxyInfo = await startProxy(
proxyBin,
proxyConfig,

View File

@@ -5,7 +5,7 @@ import * as core from "@actions/core";
import { Action, ActionState, runInActions } from "./action-common";
import * as actionsUtil from "./actions-util";
import { getGitHubVersion } from "./api-client";
import { getApiDetails, getGitHubVersion } from "./api-client";
import { FeatureEnablement, initFeatures } from "./feature-flags";
import { BuiltInLanguage, parseBuiltInLanguage } from "./languages";
import { Logger } from "./logging";
@@ -98,7 +98,10 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
};
// Start the Proxy
const proxyBin = await getProxyBinaryPath({ ...action, features });
const proxyBin = await getProxyBinaryPath(
{ ...action, features },
getApiDetails(action.env),
);
const proxyInfo = await startProxy(
proxyBin,
proxyConfig,

View File

@@ -6,17 +6,21 @@ import test, { ExecutionContext } from "ava";
import sinon from "sinon";
import { ActionState } from "./action-common";
import * as actionsUtil from "./actions-util";
import * as apiClient from "./api-client";
import * as defaults from "./defaults.json";
import { ActionsEnvVars } from "./environment";
import { setUpFeatureFlagTests } from "./feature-flags/testing-util";
import { UnvalidatedObject, validateSchema } from "./json";
import { makeFromSchema } from "./json/testing-util";
import { BuiltInLanguage } from "./languages";
import { getRunnerLogger, Logger } from "./logging";
import { BundlePlatform, getBundlePlatform } from "./platform";
import * as repository from "./setup/repository";
import * as startProxyExports from "./start-proxy";
import * as statusReport from "./status-report";
import {
addSinonAssertions,
assertNotLogged,
checkExpectedLogMessages,
createFeatures,
@@ -24,6 +28,7 @@ import {
makeMacro,
makeTestToken,
RecordingLogger,
SAMPLE_DOTCOM_API_DETAILS,
setupTests,
withRecordingLoggerAsync,
} from "./testing-utils";
@@ -705,15 +710,23 @@ function mockGetApiClient(endpoints: any) {
type ReleaseAssets = Array<{ name: string; url?: string }>;
function mockGetReleaseByTag(assets?: ReleaseAssets) {
function mockGetReleaseByTag(expectedTag?: string, assets?: ReleaseAssets) {
const getReleaseByTag =
assets === undefined
? sinon.stub().rejects()
: sinon.stub().resolves({
status: 200,
data: { assets },
headers: {},
url: "GET /repos/:owner/:repo/releases/tags/:tag",
: sinon.stub().callsFake(({ tag }) => {
if (tag === expectedTag) {
return {
status: 200,
data: { assets },
headers: {},
url: "GET /repos/:owner/:repo/releases/tags/:tag",
};
} else {
return {
status: 404,
};
}
});
return mockGetApiClient({ repos: { getReleaseByTag } });
@@ -751,25 +764,33 @@ test.serial(
logger,
features,
});
const info = await startProxyExports.getDownloadUrl(state);
t.is(info.version, startProxyExports.UPDATEJOB_PROXY_VERSION);
t.is(
info.url,
startProxyExports.getFallbackUrl(
startProxyExports.getProxyPackage(BundlePlatform.Linux64),
),
// Stub this to always return `true` so we get the default repo for the tests.
sinon.stub(actionsUtil, "isRunningLocalAction").returns(true);
const info = await startProxyExports.getDownloadUrl(
state,
SAMPLE_DOTCOM_API_DETAILS,
);
t.true(
logger.hasMessage(`Unsupported platform android on architecture ppc`),
);
t.is(info.version, defaults.cliVersion);
t.is(
info.url,
repository.getDefaultDotComDownloadURL(
defaults.bundleVersion,
startProxyExports.getProxyPackage(BundlePlatform.Linux64),
),
);
});
},
);
test.serial(
"getDownloadUrl returns fallback when `getReleaseByVersion` rejects",
"getDownloadUrl returns default bundle URL for linked version",
async (t) => {
const logger = new RecordingLogger();
mockGetReleaseByTag();
@@ -777,12 +798,20 @@ test.serial(
await withTmpDir(async (tempDir) => {
const features = mockOfflineFeatures(tempDir, logger);
const state = initAllState({ logger, features });
const info = await startProxyExports.getDownloadUrl(state);
t.is(info.version, startProxyExports.UPDATEJOB_PROXY_VERSION);
// Stub this to always return `true` so we get the default repo for the tests.
sinon.stub(actionsUtil, "isRunningLocalAction").returns(true);
const info = await startProxyExports.getDownloadUrl(
state,
SAMPLE_DOTCOM_API_DETAILS,
);
t.is(info.version, defaults.cliVersion);
t.is(
info.url,
startProxyExports.getFallbackUrl(
repository.getDefaultDotComDownloadURL(
defaults.bundleVersion,
startProxyExports.getProxyPackage(getTestPlatform(state)),
),
);
@@ -791,7 +820,7 @@ test.serial(
);
test.serial(
"getDownloadUrl returns fallback when there's no matching release asset",
"getDownloadUrl doesn't check that the default asset exists",
async (t) => {
const logger = new RecordingLogger();
const testAssets = [[], [{ name: "foo" }]];
@@ -800,14 +829,21 @@ test.serial(
const features = mockOfflineFeatures(tempDir, logger);
const state = initAllState({ logger, features });
for (const assets of testAssets) {
const stub = mockGetReleaseByTag(assets);
const info = await startProxyExports.getDownloadUrl(state);
// Stub this to always return `true` so we get the default repo for the tests.
sinon.stub(actionsUtil, "isRunningLocalAction").returns(true);
t.is(info.version, startProxyExports.UPDATEJOB_PROXY_VERSION);
for (const assets of testAssets) {
const stub = mockGetReleaseByTag(defaults.bundleVersion, assets);
const info = await startProxyExports.getDownloadUrl(
state,
SAMPLE_DOTCOM_API_DETAILS,
);
t.is(info.version, defaults.cliVersion);
t.is(
info.url,
startProxyExports.getFallbackUrl(
repository.getDefaultDotComDownloadURL(
defaults.bundleVersion,
startProxyExports.getProxyPackage(getTestPlatform(state)),
),
);
@@ -828,11 +864,22 @@ test.serial("getDownloadUrl returns matching release asset", async (t) => {
url: "url-we-want",
},
];
mockGetReleaseByTag(assets);
const getReleaseByTag = mockGetReleaseByTag(defaults.bundleVersion, assets);
await withTmpDir(async (tempDir) => {
state.env.set(ActionsEnvVars.RUNNER_TEMP, tempDir);
const features = mockOfflineFeatures(tempDir, logger);
const info = await startProxyExports.getDownloadUrl({ ...state, features });
// Stub this to always return `false` so we force `getCodeQLAssetDownloadURL` to use the (mocked) API.
sinon.stub(actionsUtil, "isRunningLocalAction").returns(false);
state.env.set(ActionsEnvVars.GITHUB_ACTION_REPOSITORY, "test/repo");
const info = await startProxyExports.getDownloadUrl(
{ ...state, features },
SAMPLE_DOTCOM_API_DETAILS,
);
t.is(getReleaseByTag.callCount, 1);
t.is(info.version, defaults.cliVersion);
t.is(info.url, "url-we-want");
@@ -965,9 +1012,13 @@ test.serial(
const toolcachePath = "/path/to/proxy/dir";
sinon.stub(toolcache, "find").returns(toolcachePath);
// Stub this to always return `true` so we get the default repo for the tests.
sinon.stub(actionsUtil, "isRunningLocalAction").returns(true);
const features = mockOfflineFeatures(tempDir, logger);
const path = await startProxyExports.getProxyBinaryPath(
initAllState({ logger, features }),
SAMPLE_DOTCOM_API_DETAILS,
);
t.assert(path);
@@ -985,7 +1036,7 @@ test.serial(
const logger = new RecordingLogger();
const state = initAllState({ logger });
const downloadUrl = "url-we-want";
mockGetReleaseByTag([
const getApiClient = mockGetReleaseByTag(defaults.bundleVersion, [
{
name: startProxyExports.getProxyPackage(getTestPlatform(state)),
url: downloadUrl,
@@ -994,11 +1045,6 @@ test.serial(
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);
@@ -1012,15 +1058,27 @@ test.serial(
.resolves(extractedPath);
const cacheDir = sinon.stub(toolcache, "cacheDir").resolves(toolcachePath);
const path = await startProxyExports.getProxyBinaryPath({
...state,
features: createFeatures([]),
});
// Stub this to always return `false` so we force `getCodeQLAssetDownloadURL` to use the (mocked) API.
sinon.stub(actionsUtil, "isRunningLocalAction").returns(false);
state.env.set(ActionsEnvVars.GITHUB_ACTION_REPOSITORY, "test/repo");
const path = await startProxyExports.getProxyBinaryPath(
{
...state,
features: createFeatures([]),
},
SAMPLE_DOTCOM_API_DETAILS,
);
t.is(getApiClient.callCount, 1);
t.assert(find.calledOnce);
t.assert(getApiDetails.calledOnce);
t.assert(getAuthorizationHeaderFor.calledOnce);
t.assert(downloadTool.calledOnceWith(downloadUrl));
const st = addSinonAssertions(t);
st.onceWith(downloadTool, downloadUrl);
st.onceWith(extractTar, archivePath);
st.onceWith(cacheDir, extractedPath);
t.assert(extractTar.calledOnceWith(archivePath));
t.assert(cacheDir.calledOnceWith(extractedPath));
t.assert(path);
@@ -1030,7 +1088,7 @@ test.serial(
);
checkExpectedLogMessages(t, logger.messages, [
`Found '${startProxyExports.getProxyPackage(getTestPlatform(state))}' in release '${defaults.bundleVersion}' at '${downloadUrl}'`,
`Found private registry proxy ${startProxyExports.getProxyPackage(getTestPlatform(state))} in test/repo on https://github.com with URL ${downloadUrl}`,
]);
},
);
@@ -1042,8 +1100,8 @@ test.serial(
const state = initAllState({ logger });
const expectedTag = "codeql-bundle-v2.20.1";
const expectedParams = {
owner: "github",
repo: "codeql-action",
owner: "test",
repo: "repo",
tag: expectedTag,
};
const downloadUrl = "url-we-want";
@@ -1066,11 +1124,6 @@ test.serial(
await withTmpDir(async (tempDir) => {
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);
@@ -1100,22 +1153,28 @@ test.serial(
.resolves({
enabledVersions: [{ cliVersion: "2.20.1", tagName: expectedTag }],
});
const path = await startProxyExports.getProxyBinaryPath({
...state,
features,
});
// Stub this to always return `false` so we force `getCodeQLAssetDownloadURL` to use the (mocked) API.
sinon.stub(actionsUtil, "isRunningLocalAction").returns(false);
state.env.set(ActionsEnvVars.GITHUB_ACTION_REPOSITORY, "test/repo");
const path = await startProxyExports.getProxyBinaryPath(
{
...state,
features,
},
SAMPLE_DOTCOM_API_DETAILS,
);
t.assert(getDefaultCliVersion.calledOnce);
sinon.assert.calledOnceWithMatch(
getReleaseByTag,
sinon.match(expectedParams),
);
const st = addSinonAssertions(t);
st.onceWith(getReleaseByTag, expectedParams);
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));
st.onceWith(downloadTool, downloadUrl);
st.onceWith(extractTar, archivePath);
st.onceWith(cacheDir, extractedPath);
t.assert(path);
t.is(
@@ -1125,7 +1184,7 @@ test.serial(
});
checkExpectedLogMessages(t, logger.messages, [
`Found '${startProxyExports.getProxyPackage(getTestPlatform(state))}' in release '${expectedTag}' at '${downloadUrl}'`,
`Found private registry proxy ${startProxyExports.getProxyPackage(getTestPlatform(state))} in test/repo on https://github.com with URL url-we-want.`,
]);
},
);

View File

@@ -5,10 +5,9 @@ import * as toolcache from "@actions/tool-cache";
import { ActionState } from "./action-common";
import {
getApiClient,
getApiDetails,
getAuthorizationHeaderFor,
getGitHubVersion,
GitHubApiDetails,
} from "./api-client";
import * as artifactScanner from "./artifact-scanner";
import { Config } from "./config-utils";
@@ -22,6 +21,7 @@ import * as json from "./json";
import { BuiltInLanguage } from "./languages";
import { Logger } from "./logging";
import { BundlePlatform, getBundlePlatform } from "./platform";
import { getCodeQLAssetDownloadURL } from "./setup/repository";
import {
Address,
Registry,
@@ -171,10 +171,8 @@ export async function sendFailedStatusReport(
}
}
/** The basename of the proxy artifacts. */
export const UPDATEJOB_PROXY = "update-job-proxy";
export const UPDATEJOB_PROXY_VERSION = "v2.0.20250624110901";
const UPDATEJOB_PROXY_URL_PREFIX =
"https://github.com/github/codeql-action/releases/download/codeql-bundle-v2.22.0/";
function isPAT(value: string) {
return artifactScanner.isAuthToken(value, [
@@ -379,30 +377,6 @@ export function getProxyPackage(platform: BundlePlatform): string {
return `${UPDATEJOB_PROXY}-${platform}.tar.gz`;
}
/**
* Gets the fallback URL for downloading the proxy release asset.
*
* @param proxyPackage The asset name.
* @returns The full URL to download the specified asset from the fallback release.
*/
export function getFallbackUrl(proxyPackage: string): string {
return `${UPDATEJOB_PROXY_URL_PREFIX}${proxyPackage}`;
}
/**
* Uses the GitHub API to obtain information about the CodeQL CLI bundle release
* that is tagged by `version`.
*
* @returns The response from the GitHub API.
*/
async function getReleaseByVersion(version: string) {
return getApiClient().rest.repos.getReleaseByTag({
owner: "github",
repo: "codeql-action",
tag: version,
});
}
/** Uses `features` to determine the default CLI version. */
async function getCliVersionFromFeatures(
features: FeatureEnablement,
@@ -419,7 +393,8 @@ async function getCliVersionFromFeatures(
* @returns Returns the download URL and version of the proxy package we plan to use.
*/
export async function getDownloadUrl(
action: ActionState<["Base", "Logger", "FeatureFlags"]>,
action: ActionState<["Base", "ReadOnlyEnv", "Logger", "FeatureFlags"]>,
apiDetails: GitHubApiDetails,
): Promise<{ url: string; version: string }> {
// Default to linux64 if we don't recognise the platform+arch pair.
// This maintains the behaviour we had before switching to `getBundlePlatform` here.
@@ -448,37 +423,26 @@ export async function getDownloadUrl(
};
// Try to retrieve information about the CLI bundle release identified by `versionInfo`.
const cliRelease = await getReleaseByVersion(versionInfo.tagName);
const proxyAssetUrl = await getCodeQLAssetDownloadURL(
action,
apiDetails,
versionInfo.tagName,
proxyPackage,
"private registry proxy",
);
// Search the release's assets to find the one we are looking for.
for (const asset of cliRelease.data.assets) {
if (asset.name === proxyPackage) {
action.logger.info(
`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: versionInfo.cliVersion,
};
}
}
return {
url: proxyAssetUrl,
// 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: versionInfo.cliVersion,
};
} catch (ex) {
action.logger.warning(
throw new Error(
`Failed to retrieve information about the linked release: ${getErrorMessage(ex)}`,
);
}
// Fallback to the hard-coded URL.
action.logger.info(
`Did not find '${proxyPackage}' in the linked release, falling back to hard-coded version.`,
);
return {
url: getFallbackUrl(proxyPackage),
version: UPDATEJOB_PROXY_VERSION,
};
}
/**
@@ -569,15 +533,15 @@ export function getProxyFilename() {
* @returns The path to the proxy binary.
*/
export async function getProxyBinaryPath(
action: ActionState<["Base", "Logger", "FeatureFlags"]>,
action: ActionState<["Base", "ReadOnlyEnv", "Logger", "FeatureFlags"]>,
apiDetails: GitHubApiDetails,
): Promise<string> {
const logger = action.logger;
const proxyFileName = getProxyFilename();
const proxyInfo = await getDownloadUrl(action);
const proxyInfo = await getDownloadUrl(action, apiDetails);
let proxyBin = toolcache.find(proxyFileName, proxyInfo.version);
if (!proxyBin) {
const apiDetails = getApiDetails();
const authorization = getAuthorizationHeaderFor(
logger,
apiDetails,