Merge pull request #4181 from github/mbg/start-proxy/linux-arm64

Support `linux-arm64` assets in `start-proxy`
This commit is contained in:
Michael B. Gale
2026-09-29 17:31:06 +00:00
committed by GitHub
6 changed files with 140 additions and 59 deletions

33
lib/entry-points.js generated
View File

@@ -147730,6 +147730,8 @@ async function runInActions(action) {
const actionState = { const actionState = {
name: action.name, name: action.name,
startedAt, startedAt,
platform: process.platform,
arch: process.arch,
logger, logger,
env, env,
actions: actionsEnv actions: actionsEnv
@@ -163635,8 +163637,7 @@ function getCredentials(logger, registrySecrets, registriesCredentials, language
} }
return out; return out;
} }
function getProxyPackage() { function getProxyPackage(platform2) {
const platform2 = process.platform === "win32" ? "win64" : process.platform === "darwin" ? "osx64" : "linux64";
return `${UPDATEJOB_PROXY}-${platform2}.tar.gz`; return `${UPDATEJOB_PROXY}-${platform2}.tar.gz`;
} }
function getFallbackUrl(proxyPackage) { function getFallbackUrl(proxyPackage) {
@@ -163653,20 +163654,27 @@ async function getCliVersionFromFeatures(features) {
const gitHubVersion = await getGitHubVersion(); const gitHubVersion = await getGitHubVersion();
return await features.getEnabledDefaultCliVersions(gitHubVersion.type); return await features.getEnabledDefaultCliVersions(gitHubVersion.type);
} }
async function getDownloadUrl(logger, features) { async function getDownloadUrl(action) {
const proxyPackage = getProxyPackage(); let platform2 = getBundlePlatform(action.platform, action.arch);
if (platform2 === void 0) {
action.logger.warning(
`Unsupported platform ${action.platform} on architecture ${action.arch}, defaulting to ${"linux64" /* Linux64 */}`
);
platform2 = "linux64" /* Linux64 */;
}
const proxyPackage = getProxyPackage(platform2);
try { try {
const useFeaturesToDetermineCLI = await features.getValue( const useFeaturesToDetermineCLI = await action.features.getValue(
"start_proxy_use_features_release" /* StartProxyUseFeaturesRelease */ "start_proxy_use_features_release" /* StartProxyUseFeaturesRelease */
); );
const versionInfo = useFeaturesToDetermineCLI ? (await getCliVersionFromFeatures(features)).enabledVersions[0] : { const versionInfo = useFeaturesToDetermineCLI ? (await getCliVersionFromFeatures(action.features)).enabledVersions[0] : {
cliVersion, cliVersion,
tagName: bundleVersion tagName: bundleVersion
}; };
const cliRelease = await getReleaseByVersion(versionInfo.tagName); const cliRelease = await getReleaseByVersion(versionInfo.tagName);
for (const asset of cliRelease.data.assets) { for (const asset of cliRelease.data.assets) {
if (asset.name === proxyPackage) { if (asset.name === proxyPackage) {
logger.info( action.logger.info(
`Found '${proxyPackage}' in release '${versionInfo.tagName}' at '${asset.url}'` `Found '${proxyPackage}' in release '${versionInfo.tagName}' at '${asset.url}'`
); );
return { return {
@@ -163679,11 +163687,11 @@ async function getDownloadUrl(logger, features) {
} }
} }
} catch (ex) { } catch (ex) {
logger.warning( action.logger.warning(
`Failed to retrieve information about the linked release: ${getErrorMessage(ex)}` `Failed to retrieve information about the linked release: ${getErrorMessage(ex)}`
); );
} }
logger.info( action.logger.info(
`Did not find '${proxyPackage}' in the linked release, falling back to hard-coded version.` `Did not find '${proxyPackage}' in the linked release, falling back to hard-coded version.`
); );
return { return {
@@ -163732,9 +163740,10 @@ async function cacheProxy(logger, source, filename, version) {
function getProxyFilename() { function getProxyFilename() {
return process.platform === "win32" ? `${UPDATEJOB_PROXY}.exe` : UPDATEJOB_PROXY; return process.platform === "win32" ? `${UPDATEJOB_PROXY}.exe` : UPDATEJOB_PROXY;
} }
async function getProxyBinaryPath(logger, features) { async function getProxyBinaryPath(action) {
const logger = action.logger;
const proxyFileName = getProxyFilename(); const proxyFileName = getProxyFilename();
const proxyInfo = await getDownloadUrl(logger, features); const proxyInfo = await getDownloadUrl(action);
let proxyBin = toolcache4.find(proxyFileName, proxyInfo.version); let proxyBin = toolcache4.find(proxyFileName, proxyInfo.version);
if (!proxyBin) { if (!proxyBin) {
const apiDetails = getApiDetails(); const apiDetails = getApiDetails();
@@ -164114,7 +164123,7 @@ async function run7(action) {
all_credentials: credentials, all_credentials: credentials,
ca ca
}; };
const proxyBin = await getProxyBinaryPath(logger, features); const proxyBin = await getProxyBinaryPath({ ...action, features });
const proxyInfo = await startProxy( const proxyInfo = await startProxy(
proxyBin, proxyBin,
proxyConfig, proxyConfig,

View File

@@ -19,6 +19,10 @@ export interface BaseState {
name: ActionName; name: ActionName;
/** When the Action was started. */ /** When the Action was started. */
startedAt: Date; startedAt: Date;
/** The platform the Action is running on. */
platform: NodeJS.Platform;
/** The architecture of the host. */
arch: NodeJS.Architecture;
} }
/** Describes different state features that an Action may have. */ /** Describes different state features that an Action may have. */
@@ -98,6 +102,8 @@ export async function runInActions(action: Action) {
const actionState = { const actionState = {
name: action.name, name: action.name,
startedAt, startedAt,
platform: process.platform,
arch: process.arch,
logger, logger,
env, env,
actions: actionsEnv, actions: actionsEnv,

View File

@@ -98,7 +98,7 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
}; };
// Start the Proxy // Start the Proxy
const proxyBin = await getProxyBinaryPath(logger, features); const proxyBin = await getProxyBinaryPath({ ...action, features });
const proxyInfo = await startProxy( const proxyInfo = await startProxy(
proxyBin, proxyBin,
proxyConfig, proxyConfig,

View File

@@ -5,6 +5,7 @@ import * as toolcache from "@actions/tool-cache";
import test, { ExecutionContext } from "ava"; import test, { ExecutionContext } from "ava";
import sinon from "sinon"; import sinon from "sinon";
import { ActionState } from "./action-common";
import * as apiClient from "./api-client"; import * as apiClient from "./api-client";
import * as defaults from "./defaults.json"; import * as defaults from "./defaults.json";
import { setUpFeatureFlagTests } from "./feature-flags/testing-util"; import { setUpFeatureFlagTests } from "./feature-flags/testing-util";
@@ -12,12 +13,14 @@ import { UnvalidatedObject, validateSchema } from "./json";
import { makeFromSchema } from "./json/testing-util"; import { makeFromSchema } from "./json/testing-util";
import { BuiltInLanguage } from "./languages"; import { BuiltInLanguage } from "./languages";
import { getRunnerLogger, Logger } from "./logging"; import { getRunnerLogger, Logger } from "./logging";
import { BundlePlatform, getBundlePlatform } from "./platform";
import * as startProxyExports from "./start-proxy"; import * as startProxyExports from "./start-proxy";
import * as statusReport from "./status-report"; import * as statusReport from "./status-report";
import { import {
assertNotLogged, assertNotLogged,
checkExpectedLogMessages, checkExpectedLogMessages,
createFeatures, createFeatures,
initAllState,
makeMacro, makeMacro,
makeTestToken, makeTestToken,
RecordingLogger, RecordingLogger,
@@ -685,6 +688,12 @@ test("getCredentials always returns ALWAYS_ENABLED_REGISTRY_TYPE credentials for
} }
}); });
test("getProxyPackage - includes platform in name", (t) => {
for (const platform of Object.values(BundlePlatform)) {
t.true(startProxyExports.getProxyPackage(platform).includes(platform));
}
});
function mockGetApiClient(endpoints: any) { function mockGetApiClient(endpoints: any) {
return ( return (
sinon sinon
@@ -721,6 +730,44 @@ function mockOfflineFeatures(tempDir: string, logger: Logger) {
return setUpFeatureFlagTests(tempDir, logger, gitHubVersion); return setUpFeatureFlagTests(tempDir, logger, gitHubVersion);
} }
/** Gets the `BundlePlatform` based on the `action` state, but defaults to `Linux64` if undefined. */
function getTestPlatform(action: ActionState<["Base"]>) {
return (
getBundlePlatform(action.platform, action.arch) ?? BundlePlatform.Linux64
);
}
test.serial(
"getDownloadUrl logs unknown platforms/arch and defaults to linux64",
async (t) => {
const logger = new RecordingLogger();
mockGetReleaseByTag();
await withTmpDir(async (tempDir) => {
const features = mockOfflineFeatures(tempDir, logger);
const state = initAllState({
platform: "android",
arch: "ppc",
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),
),
);
t.true(
logger.hasMessage(`Unsupported platform android on architecture ppc`),
);
});
},
);
test.serial( test.serial(
"getDownloadUrl returns fallback when `getReleaseByVersion` rejects", "getDownloadUrl returns fallback when `getReleaseByVersion` rejects",
async (t) => { async (t) => {
@@ -729,15 +776,15 @@ test.serial(
await withTmpDir(async (tempDir) => { await withTmpDir(async (tempDir) => {
const features = mockOfflineFeatures(tempDir, logger); const features = mockOfflineFeatures(tempDir, logger);
const info = await startProxyExports.getDownloadUrl( const state = initAllState({ logger, features });
getRunnerLogger(true), const info = await startProxyExports.getDownloadUrl(state);
features,
);
t.is(info.version, startProxyExports.UPDATEJOB_PROXY_VERSION); t.is(info.version, startProxyExports.UPDATEJOB_PROXY_VERSION);
t.is( t.is(
info.url, info.url,
startProxyExports.getFallbackUrl(startProxyExports.getProxyPackage()), startProxyExports.getFallbackUrl(
startProxyExports.getProxyPackage(getTestPlatform(state)),
),
); );
}); });
}, },
@@ -751,18 +798,18 @@ test.serial(
await withTmpDir(async (tempDir) => { await withTmpDir(async (tempDir) => {
const features = mockOfflineFeatures(tempDir, logger); const features = mockOfflineFeatures(tempDir, logger);
const state = initAllState({ logger, features });
for (const assets of testAssets) { for (const assets of testAssets) {
const stub = mockGetReleaseByTag(assets); const stub = mockGetReleaseByTag(assets);
const info = await startProxyExports.getDownloadUrl( const info = await startProxyExports.getDownloadUrl(state);
getRunnerLogger(true),
features,
);
t.is(info.version, startProxyExports.UPDATEJOB_PROXY_VERSION); t.is(info.version, startProxyExports.UPDATEJOB_PROXY_VERSION);
t.is( t.is(
info.url, info.url,
startProxyExports.getFallbackUrl(startProxyExports.getProxyPackage()), startProxyExports.getFallbackUrl(
startProxyExports.getProxyPackage(getTestPlatform(state)),
),
); );
stub.restore(); stub.restore();
@@ -773,18 +820,19 @@ test.serial(
test.serial("getDownloadUrl returns matching release asset", async (t) => { test.serial("getDownloadUrl returns matching release asset", async (t) => {
const logger = new RecordingLogger(); const logger = new RecordingLogger();
const state = initAllState({ logger });
const assets = [ const assets = [
{ name: "foo", url: "other-url" }, { name: "foo", url: "other-url" },
{ name: startProxyExports.getProxyPackage(), url: "url-we-want" }, {
name: startProxyExports.getProxyPackage(getTestPlatform(state)),
url: "url-we-want",
},
]; ];
mockGetReleaseByTag(assets); mockGetReleaseByTag(assets);
await withTmpDir(async (tempDir) => { await withTmpDir(async (tempDir) => {
const features = mockOfflineFeatures(tempDir, logger); const features = mockOfflineFeatures(tempDir, logger);
const info = await startProxyExports.getDownloadUrl( const info = await startProxyExports.getDownloadUrl({ ...state, features });
getRunnerLogger(true),
features,
);
t.is(info.version, defaults.cliVersion); t.is(info.version, defaults.cliVersion);
t.is(info.url, "url-we-want"); t.is(info.url, "url-we-want");
@@ -918,7 +966,9 @@ test.serial(
sinon.stub(toolcache, "find").returns(toolcachePath); sinon.stub(toolcache, "find").returns(toolcachePath);
const features = mockOfflineFeatures(tempDir, logger); const features = mockOfflineFeatures(tempDir, logger);
const path = await startProxyExports.getProxyBinaryPath(logger, features); const path = await startProxyExports.getProxyBinaryPath(
initAllState({ logger, features }),
);
t.assert(path); t.assert(path);
t.is( t.is(
@@ -933,9 +983,13 @@ test.serial(
"getProxyBinaryPath - downloads proxy if not in cache", "getProxyBinaryPath - downloads proxy if not in cache",
async (t) => { async (t) => {
const logger = new RecordingLogger(); const logger = new RecordingLogger();
const state = initAllState({ logger });
const downloadUrl = "url-we-want"; const downloadUrl = "url-we-want";
mockGetReleaseByTag([ mockGetReleaseByTag([
{ name: startProxyExports.getProxyPackage(), url: downloadUrl }, {
name: startProxyExports.getProxyPackage(getTestPlatform(state)),
url: downloadUrl,
},
]); ]);
const toolcachePath = "/path/to/proxy/dir"; const toolcachePath = "/path/to/proxy/dir";
@@ -958,10 +1012,10 @@ test.serial(
.resolves(extractedPath); .resolves(extractedPath);
const cacheDir = sinon.stub(toolcache, "cacheDir").resolves(toolcachePath); const cacheDir = sinon.stub(toolcache, "cacheDir").resolves(toolcachePath);
const path = await startProxyExports.getProxyBinaryPath( const path = await startProxyExports.getProxyBinaryPath({
logger, ...state,
createFeatures([]), features: createFeatures([]),
); });
t.assert(find.calledOnce); t.assert(find.calledOnce);
t.assert(getApiDetails.calledOnce); t.assert(getApiDetails.calledOnce);
@@ -976,7 +1030,7 @@ test.serial(
); );
checkExpectedLogMessages(t, logger.messages, [ checkExpectedLogMessages(t, logger.messages, [
`Found '${startProxyExports.getProxyPackage()}' in release '${defaults.bundleVersion}' at '${downloadUrl}'`, `Found '${startProxyExports.getProxyPackage(getTestPlatform(state))}' in release '${defaults.bundleVersion}' at '${downloadUrl}'`,
]); ]);
}, },
); );
@@ -985,6 +1039,7 @@ test.serial(
"getProxyBinaryPath - downloads proxy based on features if not in cache", "getProxyBinaryPath - downloads proxy based on features if not in cache",
async (t) => { async (t) => {
const logger = new RecordingLogger(); const logger = new RecordingLogger();
const state = initAllState({ logger });
const expectedTag = "codeql-bundle-v2.20.1"; const expectedTag = "codeql-bundle-v2.20.1";
const expectedParams = { const expectedParams = {
owner: "github", owner: "github",
@@ -994,7 +1049,7 @@ test.serial(
const downloadUrl = "url-we-want"; const downloadUrl = "url-we-want";
const assets = [ const assets = [
{ {
name: startProxyExports.getProxyPackage(), name: startProxyExports.getProxyPackage(getTestPlatform(state)),
url: downloadUrl, url: downloadUrl,
}, },
]; ];
@@ -1045,7 +1100,10 @@ test.serial(
.resolves({ .resolves({
enabledVersions: [{ cliVersion: "2.20.1", tagName: expectedTag }], enabledVersions: [{ cliVersion: "2.20.1", tagName: expectedTag }],
}); });
const path = await startProxyExports.getProxyBinaryPath(logger, features); const path = await startProxyExports.getProxyBinaryPath({
...state,
features,
});
t.assert(getDefaultCliVersion.calledOnce); t.assert(getDefaultCliVersion.calledOnce);
sinon.assert.calledOnceWithMatch( sinon.assert.calledOnceWithMatch(
@@ -1067,7 +1125,7 @@ test.serial(
}); });
checkExpectedLogMessages(t, logger.messages, [ checkExpectedLogMessages(t, logger.messages, [
`Found '${startProxyExports.getProxyPackage()}' in release '${expectedTag}' at '${downloadUrl}'`, `Found '${startProxyExports.getProxyPackage(getTestPlatform(state))}' in release '${expectedTag}' at '${downloadUrl}'`,
]); ]);
}, },
); );

View File

@@ -3,6 +3,7 @@ import * as path from "path";
import * as core from "@actions/core"; import * as core from "@actions/core";
import * as toolcache from "@actions/tool-cache"; import * as toolcache from "@actions/tool-cache";
import { ActionState } from "./action-common";
import { import {
getApiClient, getApiClient,
getApiDetails, getApiDetails,
@@ -20,6 +21,7 @@ import {
import * as json from "./json"; import * as json from "./json";
import { BuiltInLanguage } from "./languages"; import { BuiltInLanguage } from "./languages";
import { Logger } from "./logging"; import { Logger } from "./logging";
import { BundlePlatform, getBundlePlatform } from "./platform";
import { import {
Address, Address,
Registry, Registry,
@@ -370,14 +372,10 @@ export function getCredentials(
/** /**
* Gets the name of the proxy release asset for the current platform. * Gets the name of the proxy release asset for the current platform.
*
* @param platform The platform to get the asset name for.
*/ */
export function getProxyPackage(): string { export function getProxyPackage(platform: BundlePlatform): string {
const platform =
process.platform === "win32"
? "win64"
: process.platform === "darwin"
? "osx64"
: "linux64";
return `${UPDATEJOB_PROXY}-${platform}.tar.gz`; return `${UPDATEJOB_PROXY}-${platform}.tar.gz`;
} }
@@ -417,25 +415,33 @@ async function getCliVersionFromFeatures(
* Determines the URL of the proxy release asset that we should download if its not * Determines the URL of the proxy release asset that we should download if its not
* already in the toolcache, and its version. * already in the toolcache, and its version.
* *
* @param logger The logger to use. * @param action The action state.
* @param features Information about enabled features.
* @returns Returns the download URL and version of the proxy package we plan to use. * @returns Returns the download URL and version of the proxy package we plan to use.
*/ */
export async function getDownloadUrl( export async function getDownloadUrl(
logger: Logger, action: ActionState<["Base", "Logger", "FeatureFlags"]>,
features: FeatureEnablement,
): Promise<{ url: string; version: string }> { ): Promise<{ url: string; version: string }> {
const proxyPackage = getProxyPackage(); // Default to linux64 if we don't recognise the platform+arch pair.
// This maintains the behaviour we had before switching to `getBundlePlatform` here.
let platform = getBundlePlatform(action.platform, action.arch);
if (platform === undefined) {
action.logger.warning(
`Unsupported platform ${action.platform} on architecture ${action.arch}, defaulting to ${BundlePlatform.Linux64}`,
);
platform = BundlePlatform.Linux64;
}
const proxyPackage = getProxyPackage(platform);
try { try {
const useFeaturesToDetermineCLI = await features.getValue( const useFeaturesToDetermineCLI = await action.features.getValue(
Feature.StartProxyUseFeaturesRelease, Feature.StartProxyUseFeaturesRelease,
); );
// Retrieve information about the CLI version we should use. This will be either the linked // Retrieve information about the CLI version we should use. This will be either the linked
// version, or the one enabled by FFs. // version, or the one enabled by FFs.
const versionInfo = useFeaturesToDetermineCLI const versionInfo = useFeaturesToDetermineCLI
? (await getCliVersionFromFeatures(features)).enabledVersions[0] ? (await getCliVersionFromFeatures(action.features)).enabledVersions[0]
: { : {
cliVersion: defaults.cliVersion, cliVersion: defaults.cliVersion,
tagName: defaults.bundleVersion, tagName: defaults.bundleVersion,
@@ -447,7 +453,7 @@ export async function getDownloadUrl(
// Search the release's assets to find the one we are looking for. // Search the release's assets to find the one we are looking for.
for (const asset of cliRelease.data.assets) { for (const asset of cliRelease.data.assets) {
if (asset.name === proxyPackage) { if (asset.name === proxyPackage) {
logger.info( action.logger.info(
`Found '${proxyPackage}' in release '${versionInfo.tagName}' at '${asset.url}'`, `Found '${proxyPackage}' in release '${versionInfo.tagName}' at '${asset.url}'`,
); );
return { return {
@@ -460,13 +466,13 @@ export async function getDownloadUrl(
} }
} }
} catch (ex) { } catch (ex) {
logger.warning( action.logger.warning(
`Failed to retrieve information about the linked release: ${getErrorMessage(ex)}`, `Failed to retrieve information about the linked release: ${getErrorMessage(ex)}`,
); );
} }
// Fallback to the hard-coded URL. // Fallback to the hard-coded URL.
logger.info( action.logger.info(
`Did not find '${proxyPackage}' in the linked release, falling back to hard-coded version.`, `Did not find '${proxyPackage}' in the linked release, falling back to hard-coded version.`,
); );
return { return {
@@ -559,15 +565,15 @@ export function getProxyFilename() {
* runner's tool cache. Otherwise, it downloads and extracts the proxy binary, * runner's tool cache. Otherwise, it downloads and extracts the proxy binary,
* and stores it in the tool cache. * and stores it in the tool cache.
* *
* @param logger The logger to use. * @param action The action state.
* @returns The path to the proxy binary. * @returns The path to the proxy binary.
*/ */
export async function getProxyBinaryPath( export async function getProxyBinaryPath(
logger: Logger, action: ActionState<["Base", "Logger", "FeatureFlags"]>,
features: FeatureEnablement,
): Promise<string> { ): Promise<string> {
const logger = action.logger;
const proxyFileName = getProxyFilename(); const proxyFileName = getProxyFilename();
const proxyInfo = await getDownloadUrl(logger, features); const proxyInfo = await getDownloadUrl(action);
let proxyBin = toolcache.find(proxyFileName, proxyInfo.version); let proxyBin = toolcache.find(proxyFileName, proxyInfo.version);
if (!proxyBin) { if (!proxyBin) {

View File

@@ -234,6 +234,8 @@ export function initAllState(
return { return {
name: ActionName.Init, name: ActionName.Init,
startedAt: new Date(), startedAt: new Date(),
platform: process.platform,
arch: process.arch,
logger: new RecordingLogger(), logger: new RecordingLogger(),
env, env,
actions: getTestActionsEnv(env), actions: getTestActionsEnv(env),