mirror of
https://github.com/github/codeql-action.git
synced 2026-10-15 15:22:05 +00:00
Compare commits
18 Commits
default-se
...
v4.39.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f2ac81f5c | ||
|
|
eb1c428e6e | ||
|
|
444333376e | ||
|
|
d63383a731 | ||
|
|
8dec0b9f88 | ||
|
|
4eabea6b5e | ||
|
|
57938b8568 | ||
|
|
15800587e3 | ||
|
|
6aee1f8ef9 | ||
|
|
a7700b5a50 | ||
|
|
43db2c064c | ||
|
|
6308e366c9 | ||
|
|
aa80ca0686 | ||
|
|
aae22cc693 | ||
|
|
0f6901c9a8 | ||
|
|
badda692f4 | ||
|
|
fb4048e3e7 | ||
|
|
9a9f454be5 |
@@ -2,9 +2,12 @@
|
||||
|
||||
See the [releases page](https://github.com/github/codeql-action/releases) for the relevant changes to the CodeQL CLI and language packs.
|
||||
|
||||
## [UNRELEASED]
|
||||
## 4.39.0 - 15 Oct 2026
|
||||
|
||||
- _Breaking change_: Bump the minimum required CodeQL bundle version to 2.20.7. [#4205](https://github.com/github/codeql-action/pull/4205)
|
||||
- The `codeql-action/analyze` step now validates that a correct argument for the `checkout_path` input has been provided. The `checkout_path` input must be set to the path where the repository that is being analyzed is checked out at on the runner, if that path is something other than the workspace root. If the path does not point to a repository checkout or is different from the path that was used by `codeql-action/init`, then the CodeQL Action now produces a warning. [#4148](https://github.com/github/codeql-action/pull/4148)
|
||||
- Resolved an issue with third-party caching implementations that prevented caches from being restored correctly. [#4206](https://github.com/github/codeql-action/pull/4206)
|
||||
- The `codeql-action/start-proxy` step that is used in Code Scanning Default Setup now finds CodeQL releases using the same logic as `codeql-action/init`. This primarily benefits customers who are using GitHub instances other than `github.com` where the `codeql-action/start-proxy` step may previously have been unable to locate recent releases and ended up using outdated versions of the private registry authentication proxy as a result. [#4198](https://github.com/github/codeql-action/pull/4198)
|
||||
|
||||
## 4.38.3 - 08 Oct 2026
|
||||
|
||||
|
||||
137
lib/entry-points.js
generated
137
lib/entry-points.js
generated
@@ -187602,11 +187602,73 @@ var core25 = __toESM(require_core());
|
||||
|
||||
// src/start-proxy.ts
|
||||
var path27 = __toESM(require("path"));
|
||||
var core24 = __toESM(require_core());
|
||||
var toolcache4 = __toESM(require_tool_cache());
|
||||
|
||||
// src/start-proxy/validation.ts
|
||||
// src/start-proxy/status-report.ts
|
||||
var core23 = __toESM(require_core());
|
||||
async function sendSuccessStatusReport(startedAt, config, registry_types, status, logger2) {
|
||||
const statusReportBase = await createStatusReportBase(
|
||||
"start-proxy" /* StartProxy */,
|
||||
"success",
|
||||
startedAt,
|
||||
config,
|
||||
await checkDiskUsage(logger2),
|
||||
logger2
|
||||
);
|
||||
if (statusReportBase !== void 0) {
|
||||
const statusReport = {
|
||||
...statusReportBase,
|
||||
registry_types: registry_types.sort().join(","),
|
||||
start_proxy: status
|
||||
};
|
||||
await sendStatusReport(statusReport);
|
||||
}
|
||||
}
|
||||
function getStartProxyErrorMessage(errorType) {
|
||||
switch (errorType) {
|
||||
case 0 /* DownloadFailed */:
|
||||
return "Failed to download proxy archive.";
|
||||
case 1 /* ExtractionFailed */:
|
||||
return "Failed to extract proxy archive.";
|
||||
case 2 /* CacheFailed */:
|
||||
return "Failed to add proxy to toolcache";
|
||||
}
|
||||
}
|
||||
var StartProxyError = class extends Error {
|
||||
errorType;
|
||||
constructor(errorType) {
|
||||
super();
|
||||
this.errorType = errorType;
|
||||
}
|
||||
};
|
||||
function getSafeErrorMessage(error3) {
|
||||
if (error3 instanceof StartProxyError) {
|
||||
return getStartProxyErrorMessage(error3.errorType);
|
||||
}
|
||||
return `Error from start-proxy Action omitted (${error3.constructor.name}).`;
|
||||
}
|
||||
async function sendFailedStatusReport(logger2, startedAt, language, unwrappedError) {
|
||||
const error3 = wrapError(unwrappedError);
|
||||
core23.setFailed(`start-proxy action failed: ${error3.message}`);
|
||||
const statusReportMessage = getSafeErrorMessage(error3);
|
||||
const errorStatusReportBase = await createStatusReportBase(
|
||||
"start-proxy" /* StartProxy */,
|
||||
getActionsStatus(error3),
|
||||
startedAt,
|
||||
{
|
||||
languages: language === void 0 ? void 0 : [language]
|
||||
},
|
||||
await checkDiskUsage(logger2),
|
||||
logger2,
|
||||
statusReportMessage
|
||||
);
|
||||
if (errorStatusReportBase !== void 0) {
|
||||
await sendStatusReport(errorStatusReportBase);
|
||||
}
|
||||
}
|
||||
|
||||
// src/start-proxy/validation.ts
|
||||
var core24 = __toESM(require_core());
|
||||
function cloneCredential(schema, obj) {
|
||||
const result = {};
|
||||
for (const key of Object.keys(schema)) {
|
||||
@@ -187625,14 +187687,14 @@ function getAuthConfig(config) {
|
||||
}
|
||||
if (isToken(config)) {
|
||||
if (isDefined2(config.token)) {
|
||||
core23.setSecret(config.token);
|
||||
core24.setSecret(config.token);
|
||||
}
|
||||
return cloneCredential(tokenSchema, config);
|
||||
} else {
|
||||
let username = void 0;
|
||||
let password = void 0;
|
||||
if ("password" in config && isString(config.password)) {
|
||||
core23.setSecret(config.password);
|
||||
core24.setSecret(config.password);
|
||||
password = config.password;
|
||||
}
|
||||
if ("username" in config && isString(config.username)) {
|
||||
@@ -187646,65 +187708,6 @@ function getAuthConfig(config) {
|
||||
}
|
||||
|
||||
// src/start-proxy.ts
|
||||
function getStartProxyErrorMessage(errorType) {
|
||||
switch (errorType) {
|
||||
case 0 /* DownloadFailed */:
|
||||
return "Failed to download proxy archive.";
|
||||
case 1 /* ExtractionFailed */:
|
||||
return "Failed to extract proxy archive.";
|
||||
case 2 /* CacheFailed */:
|
||||
return "Failed to add proxy to toolcache";
|
||||
}
|
||||
}
|
||||
var StartProxyError = class extends Error {
|
||||
errorType;
|
||||
constructor(errorType) {
|
||||
super();
|
||||
this.errorType = errorType;
|
||||
}
|
||||
};
|
||||
async function sendSuccessStatusReport(startedAt, config, registry_types, logger2) {
|
||||
const statusReportBase = await createStatusReportBase(
|
||||
"start-proxy" /* StartProxy */,
|
||||
"success",
|
||||
startedAt,
|
||||
config,
|
||||
await checkDiskUsage(logger2),
|
||||
logger2
|
||||
);
|
||||
if (statusReportBase !== void 0) {
|
||||
const statusReport = {
|
||||
...statusReportBase,
|
||||
registry_types: registry_types.join(",")
|
||||
};
|
||||
await sendStatusReport(statusReport);
|
||||
}
|
||||
}
|
||||
function getSafeErrorMessage(error3) {
|
||||
if (error3 instanceof StartProxyError) {
|
||||
return getStartProxyErrorMessage(error3.errorType);
|
||||
}
|
||||
return `Error from start-proxy Action omitted (${error3.constructor.name}).`;
|
||||
}
|
||||
async function sendFailedStatusReport(logger2, startedAt, language, unwrappedError) {
|
||||
const error3 = wrapError(unwrappedError);
|
||||
core24.setFailed(`start-proxy action failed: ${error3.message}`);
|
||||
const statusReportMessage = getSafeErrorMessage(error3);
|
||||
const errorStatusReportBase = await createStatusReportBase(
|
||||
"start-proxy" /* StartProxy */,
|
||||
getActionsStatus(error3),
|
||||
startedAt,
|
||||
{
|
||||
languages: language === void 0 ? void 0 : [language]
|
||||
},
|
||||
await checkDiskUsage(logger2),
|
||||
logger2,
|
||||
statusReportMessage
|
||||
);
|
||||
if (errorStatusReportBase !== void 0) {
|
||||
await sendStatusReport(errorStatusReportBase);
|
||||
}
|
||||
}
|
||||
var UPDATEJOB_PROXY = "update-job-proxy";
|
||||
function isPAT(value) {
|
||||
return isAuthToken(value, [
|
||||
@@ -187935,7 +187938,10 @@ async function getProxyBinaryPath(action, apiDetails) {
|
||||
);
|
||||
logger2.info(`Extracted the authentication proxy to '${proxyDir}'.`);
|
||||
}
|
||||
return path27.join(proxyDir, proxyFileName);
|
||||
return {
|
||||
path: path27.join(proxyDir, proxyFileName),
|
||||
version: proxyInfo.version
|
||||
};
|
||||
} finally {
|
||||
logger2.endGroup();
|
||||
}
|
||||
@@ -188306,7 +188312,7 @@ async function run7(action) {
|
||||
getApiDetails(action.env)
|
||||
);
|
||||
const proxyInfo = await startProxy(
|
||||
proxyBin,
|
||||
proxyBin.path,
|
||||
proxyConfig,
|
||||
proxyLogFilePath,
|
||||
logger2
|
||||
@@ -188318,6 +188324,7 @@ async function run7(action) {
|
||||
languages: language === void 0 ? void 0 : [language]
|
||||
},
|
||||
proxyConfig.all_credentials.map((c) => c.type),
|
||||
{ proxy_version: proxyBin.version },
|
||||
logger2
|
||||
);
|
||||
} catch (unwrappedError) {
|
||||
|
||||
@@ -50,6 +50,28 @@ function getChangenotes(): ChangenoteFile[] {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the '[UNRELEASED]' section of CHANGELOG.md with the contents
|
||||
* of the changenotes in {@link CHANGENOTES_DIR} and then deletes those changenotes.
|
||||
* @param contents The contents of CHANGELOG.md
|
||||
* @returns The updated contents of CHANGELOG.md
|
||||
*/
|
||||
export function transferChangenotesToChangelog(contents: string): string {
|
||||
const changenotes = getChangenotes();
|
||||
const changenoteBodies = changenotes.map((c) => c.content.trim());
|
||||
const changenotePaths = changenotes.map((c) => c.absolutePath);
|
||||
|
||||
const changelog = parseChangelog(contents);
|
||||
addBodyLinesToUnreleasedSection(changelog, changenoteBodies);
|
||||
const updatedChangelog = renderChangelog(changelog);
|
||||
|
||||
for (const p of changenotePaths) {
|
||||
fs.unlinkSync(p);
|
||||
}
|
||||
|
||||
return updatedChangelog;
|
||||
}
|
||||
|
||||
const entryPoint = process.argv[1];
|
||||
if (entryPoint && import.meta.url === pathToFileURL(entryPoint).href) {
|
||||
try {
|
||||
@@ -91,26 +113,13 @@ function usage(): ExitCode {
|
||||
|
||||
function assemble(): ExitCode {
|
||||
try {
|
||||
const changenotes = getChangenotes();
|
||||
const changenoteBodies = changenotes.map((c) => c.content);
|
||||
const changenotePaths = changenotes.map((c) => c.absolutePath);
|
||||
|
||||
withChangelog((contents) => {
|
||||
const changelog = parseChangelog(contents);
|
||||
addBodyLinesToUnreleasedSection(changelog, changenoteBodies);
|
||||
return renderChangelog(changelog);
|
||||
return transferChangenotesToChangelog(contents);
|
||||
}, {});
|
||||
|
||||
// Delete changenotes only after successful processing.
|
||||
for (const p of changenotePaths) {
|
||||
fs.unlinkSync(p);
|
||||
}
|
||||
|
||||
return ExitCode.Success;
|
||||
} catch (e) {
|
||||
console.error("Failed to assemble changenotes to 'CHANGELOG.md'", e);
|
||||
}
|
||||
|
||||
return ExitCode.Failure;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,13 @@ import { parseArgs } from "node:util";
|
||||
|
||||
import { type ApiClient, getApiClient } from "./api-client";
|
||||
import * as changelog from "./changelog";
|
||||
import { DryRunOption, REPO_ROOT } from "./config";
|
||||
import { transferChangenotesToChangelog } from "./changenotes";
|
||||
import {
|
||||
CHANGELOG_FILE,
|
||||
CHANGENOTES_DIR,
|
||||
DryRunOption,
|
||||
REPO_ROOT,
|
||||
} from "./config";
|
||||
import {
|
||||
getCurrentVersion,
|
||||
replaceVersionInPackageJson,
|
||||
@@ -693,11 +699,14 @@ export async function prepareNewBranch(
|
||||
|
||||
console.log("Updating changelog");
|
||||
changelog.withChangelog(
|
||||
(contents) => changelog.setVersionAndDate(version, contents),
|
||||
(contents) => {
|
||||
const populatedContents = transferChangenotesToChangelog(contents);
|
||||
return changelog.setVersionAndDate(version, populatedContents);
|
||||
},
|
||||
{ ...options, initChangelog: true },
|
||||
);
|
||||
|
||||
runGit(["add", "CHANGELOG.md"], {
|
||||
runGit(["add", CHANGELOG_FILE, CHANGENOTES_DIR], {
|
||||
dryRun: options.dryRun,
|
||||
});
|
||||
runGit(["commit", "-m", `Update changelog for v${version}`], {
|
||||
|
||||
@@ -109,7 +109,7 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
|
||||
getApiDetails(action.env),
|
||||
);
|
||||
const proxyInfo = await startProxy(
|
||||
proxyBin,
|
||||
proxyBin.path,
|
||||
proxyConfig,
|
||||
proxyLogFilePath,
|
||||
logger,
|
||||
@@ -125,6 +125,7 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
|
||||
languages: language === undefined ? undefined : [language],
|
||||
},
|
||||
proxyConfig.all_credentials.map((c) => c.type),
|
||||
{ proxy_version: proxyBin.version },
|
||||
logger,
|
||||
);
|
||||
} catch (unwrappedError) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as filepath from "path";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
import * as toolcache from "@actions/tool-cache";
|
||||
import test, { ExecutionContext } from "ava";
|
||||
import sinon from "sinon";
|
||||
@@ -18,7 +17,6 @@ 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,
|
||||
@@ -32,94 +30,10 @@ import {
|
||||
setupTests,
|
||||
withRecordingLoggerAsync,
|
||||
} from "./testing-utils";
|
||||
import {
|
||||
ConfigurationError,
|
||||
GitHubVariant,
|
||||
GitHubVersion,
|
||||
withTmpDir,
|
||||
} from "./util";
|
||||
import { GitHubVariant, GitHubVersion, withTmpDir } from "./util";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
const sendFailedStatusReportTest = makeMacro({
|
||||
exec: async (
|
||||
t: ExecutionContext<unknown>,
|
||||
err: Error,
|
||||
expectedMessage: string,
|
||||
expectedStatus: statusReport.ActionStatus = "failure",
|
||||
) => {
|
||||
const now = new Date();
|
||||
|
||||
// Override core.setFailed to avoid it setting the program's exit code
|
||||
sinon.stub(core, "setFailed").returns();
|
||||
|
||||
const createStatusReportBase = sinon.stub(
|
||||
statusReport,
|
||||
"createStatusReportBase",
|
||||
);
|
||||
createStatusReportBase.resolves(undefined);
|
||||
|
||||
await withRecordingLoggerAsync(async (logger) => {
|
||||
await startProxyExports.sendFailedStatusReport(
|
||||
logger,
|
||||
now,
|
||||
undefined,
|
||||
err,
|
||||
);
|
||||
|
||||
// Check that the stub has been called exactly once, with the expected arguments,
|
||||
// but not with the message from the error.
|
||||
sinon.assert.calledOnceWithExactly(
|
||||
createStatusReportBase,
|
||||
statusReport.ActionName.StartProxy,
|
||||
expectedStatus,
|
||||
now,
|
||||
sinon.match.any,
|
||||
sinon.match.any,
|
||||
sinon.match.any,
|
||||
expectedMessage,
|
||||
);
|
||||
t.false(
|
||||
createStatusReportBase.calledWith(
|
||||
statusReport.ActionName.StartProxy,
|
||||
expectedStatus,
|
||||
now,
|
||||
sinon.match.any,
|
||||
sinon.match.any,
|
||||
sinon.match.any,
|
||||
sinon.match((msg: string) => msg.includes(err.message)),
|
||||
),
|
||||
"createStatusReportBase was called with the error message",
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
title: (providedTitle = "") => `sendFailedStatusReport - ${providedTitle}`,
|
||||
});
|
||||
|
||||
sendFailedStatusReportTest.serial(
|
||||
"reports generic error message for non-StartProxyError error",
|
||||
new Error("Something went wrong today"),
|
||||
"Error from start-proxy Action omitted (Error).",
|
||||
);
|
||||
|
||||
sendFailedStatusReportTest.serial(
|
||||
"reports generic error message for non-StartProxyError error with safe error message",
|
||||
new Error(
|
||||
startProxyExports.getStartProxyErrorMessage(
|
||||
startProxyExports.StartProxyErrorType.DownloadFailed,
|
||||
),
|
||||
),
|
||||
"Error from start-proxy Action omitted (Error).",
|
||||
);
|
||||
|
||||
sendFailedStatusReportTest.serial(
|
||||
"reports generic error message for ConfigurationError error",
|
||||
new ConfigurationError("Something went wrong today"),
|
||||
"Error from start-proxy Action omitted (ConfigurationError).",
|
||||
"user-error",
|
||||
);
|
||||
|
||||
const toEncodedJSON = (data: any) =>
|
||||
Buffer.from(JSON.stringify(data)).toString("base64");
|
||||
|
||||
@@ -1021,16 +935,17 @@ test.serial(
|
||||
sinon.stub(actionsUtil, "isRunningLocalAction").returns(true);
|
||||
|
||||
const features = mockOfflineFeatures(tempDir, logger);
|
||||
const path = await startProxyExports.getProxyBinaryPath(
|
||||
const result = await startProxyExports.getProxyBinaryPath(
|
||||
initAllState({ logger, features }),
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
);
|
||||
|
||||
t.assert(path);
|
||||
t.assert(result);
|
||||
t.is(
|
||||
path,
|
||||
result.path,
|
||||
filepath.join(toolcachePath, startProxyExports.getProxyFilename()),
|
||||
);
|
||||
t.is(result.version, defaults.cliVersion);
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -1067,7 +982,7 @@ test.serial(
|
||||
sinon.stub(actionsUtil, "isRunningLocalAction").returns(false);
|
||||
state.env.set(ActionsEnvVars.GITHUB_ACTION_REPOSITORY, "test/repo");
|
||||
|
||||
const path = await startProxyExports.getProxyBinaryPath(
|
||||
const result = await startProxyExports.getProxyBinaryPath(
|
||||
{
|
||||
...state,
|
||||
features: createFeatures([]),
|
||||
@@ -1084,11 +999,12 @@ test.serial(
|
||||
st.onceWith(extractTar, archivePath);
|
||||
st.onceWith(cacheDir, extractedPath);
|
||||
|
||||
t.assert(path);
|
||||
t.assert(result);
|
||||
t.is(
|
||||
path,
|
||||
result.path,
|
||||
filepath.join(toolcachePath, startProxyExports.getProxyFilename()),
|
||||
);
|
||||
t.is(result.version, defaults.cliVersion);
|
||||
|
||||
checkExpectedLogMessages(t, logger.messages, [
|
||||
`Found private registry proxy ${startProxyExports.getProxyPackage(getTestPlatform(state))} in test/repo on https://github.com with URL ${downloadUrl}`,
|
||||
@@ -1101,7 +1017,8 @@ test.serial(
|
||||
async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
const state = initAllState({ logger });
|
||||
const expectedTag = "codeql-bundle-v2.20.1";
|
||||
const expectedVersion = "2.20.1";
|
||||
const expectedTag = `codeql-bundle-v${expectedVersion}`;
|
||||
const expectedParams = {
|
||||
owner: "test",
|
||||
repo: "repo",
|
||||
@@ -1154,14 +1071,16 @@ test.serial(
|
||||
const getDefaultCliVersion = sinon
|
||||
.stub(features, "getEnabledDefaultCliVersions")
|
||||
.resolves({
|
||||
enabledVersions: [{ cliVersion: "2.20.1", tagName: expectedTag }],
|
||||
enabledVersions: [
|
||||
{ cliVersion: expectedVersion, tagName: expectedTag },
|
||||
],
|
||||
});
|
||||
|
||||
// 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(
|
||||
const result = await startProxyExports.getProxyBinaryPath(
|
||||
{
|
||||
...state,
|
||||
features,
|
||||
@@ -1179,11 +1098,12 @@ test.serial(
|
||||
st.onceWith(extractTar, archivePath);
|
||||
st.onceWith(cacheDir, extractedPath);
|
||||
|
||||
t.assert(path);
|
||||
t.assert(result);
|
||||
t.is(
|
||||
path,
|
||||
result.path,
|
||||
filepath.join(toolcachePath, startProxyExports.getProxyFilename()),
|
||||
);
|
||||
t.is(result.version, expectedVersion);
|
||||
});
|
||||
|
||||
checkExpectedLogMessages(t, logger.messages, [
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as path from "path";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
import * as toolcache from "@actions/tool-cache";
|
||||
|
||||
import { ActionState } from "./action-common";
|
||||
@@ -10,7 +9,6 @@ import {
|
||||
GitHubApiDetails,
|
||||
} from "./api-client";
|
||||
import * as artifactScanner from "./artifact-scanner";
|
||||
import { Config } from "./config-utils";
|
||||
import * as defaults from "./defaults.json";
|
||||
import {
|
||||
CodeQLDefaultVersionInfo,
|
||||
@@ -22,6 +20,10 @@ import { BuiltInLanguage } from "./languages";
|
||||
import { Logger } from "./logging";
|
||||
import { BundlePlatform, getBundlePlatform } from "./platform";
|
||||
import { getCodeQLAssetDownloadURL } from "./setup/repository";
|
||||
import {
|
||||
StartProxyError,
|
||||
StartProxyErrorType,
|
||||
} from "./start-proxy/status-report";
|
||||
import {
|
||||
Address,
|
||||
Registry,
|
||||
@@ -32,144 +34,10 @@ import {
|
||||
RawCredential,
|
||||
} from "./start-proxy/types";
|
||||
import { getAuthConfig } from "./start-proxy/validation";
|
||||
import {
|
||||
ActionName,
|
||||
createStatusReportBase,
|
||||
getActionsStatus,
|
||||
sendStatusReport,
|
||||
StatusReportBase,
|
||||
} from "./status-report";
|
||||
import * as util from "./util";
|
||||
import { ConfigurationError, getErrorMessage, isDefined } from "./util";
|
||||
|
||||
export * from "./start-proxy/types";
|
||||
|
||||
/**
|
||||
* Enumerates specific error types for which we have corresponding error messages that
|
||||
* are safe to include in status reports.
|
||||
*/
|
||||
export enum StartProxyErrorType {
|
||||
DownloadFailed,
|
||||
ExtractionFailed,
|
||||
CacheFailed,
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns The error message corresponding to the error type.
|
||||
*/
|
||||
export function getStartProxyErrorMessage(
|
||||
errorType: StartProxyErrorType,
|
||||
): string {
|
||||
switch (errorType) {
|
||||
case StartProxyErrorType.DownloadFailed:
|
||||
return "Failed to download proxy archive.";
|
||||
case StartProxyErrorType.ExtractionFailed:
|
||||
return "Failed to extract proxy archive.";
|
||||
case StartProxyErrorType.CacheFailed:
|
||||
return "Failed to add proxy to toolcache";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We want to avoid accidentally leaking secrets that may be contained in exception
|
||||
* messages in the `start-proxy` action. Consequently, we don't report the messages
|
||||
* of arbitrary exceptions. This type of error ensures that the message is one from
|
||||
* `StartProxyErrorType` and therefore safe to include in a status report.
|
||||
*/
|
||||
export class StartProxyError extends Error {
|
||||
public readonly errorType: StartProxyErrorType;
|
||||
|
||||
constructor(errorType: StartProxyErrorType) {
|
||||
super();
|
||||
this.errorType = errorType;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a status report for the `start-proxy` action indicating a successful outcome.
|
||||
*
|
||||
* @param startedAt When the action was started.
|
||||
* @param config The configuration used.
|
||||
* @param registry_types The types of registries that are configured.
|
||||
* @param logger The logger to use.
|
||||
*/
|
||||
export async function sendSuccessStatusReport(
|
||||
startedAt: Date,
|
||||
config: Partial<Config>,
|
||||
registry_types: string[],
|
||||
logger: Logger,
|
||||
) {
|
||||
const statusReportBase = await createStatusReportBase(
|
||||
ActionName.StartProxy,
|
||||
"success",
|
||||
startedAt,
|
||||
config,
|
||||
await util.checkDiskUsage(logger),
|
||||
logger,
|
||||
);
|
||||
if (statusReportBase !== undefined) {
|
||||
const statusReport: StatusReportBase = {
|
||||
...statusReportBase,
|
||||
registry_types: registry_types.join(","),
|
||||
};
|
||||
await sendStatusReport(statusReport);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an error message for `error` that can safely be reported in a status report,
|
||||
* i.e. that does not contain sensitive information.
|
||||
*
|
||||
* @param error The error for which to get an error message.
|
||||
*/
|
||||
export function getSafeErrorMessage(error: Error): string {
|
||||
// If the error is a `StartProxyError`, resolve the error type to the corresponding
|
||||
// error message.
|
||||
if (error instanceof StartProxyError) {
|
||||
return getStartProxyErrorMessage(error.errorType);
|
||||
}
|
||||
|
||||
// Otherwise, omit the actual error message.
|
||||
return `Error from start-proxy Action omitted (${error.constructor.name}).`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a status report for the `start-proxy` action indicating a failure.
|
||||
*
|
||||
* @param logger The logger to use.
|
||||
* @param startedAt When the action was started.
|
||||
* @param language The language provided as input, if any.
|
||||
* @param unwrappedError The exception that was thrown.
|
||||
*/
|
||||
export async function sendFailedStatusReport(
|
||||
logger: Logger,
|
||||
startedAt: Date,
|
||||
language: BuiltInLanguage | undefined,
|
||||
unwrappedError: unknown,
|
||||
) {
|
||||
const error = util.wrapError(unwrappedError);
|
||||
core.setFailed(`start-proxy action failed: ${error.message}`);
|
||||
|
||||
// To avoid the possibility of leaking sensitive information into the telemetry,
|
||||
// we don't include arbitrary error messages. Instead, `getSafeErrorMessage` will
|
||||
// return a generic message that includes the type of the error, unless it can decide
|
||||
// that the message is safe to include.
|
||||
const statusReportMessage = getSafeErrorMessage(error);
|
||||
const errorStatusReportBase = await createStatusReportBase(
|
||||
ActionName.StartProxy,
|
||||
getActionsStatus(error),
|
||||
startedAt,
|
||||
{
|
||||
languages: language === undefined ? undefined : [language],
|
||||
},
|
||||
await util.checkDiskUsage(logger),
|
||||
logger,
|
||||
statusReportMessage,
|
||||
);
|
||||
if (errorStatusReportBase !== undefined) {
|
||||
await sendStatusReport(errorStatusReportBase);
|
||||
}
|
||||
}
|
||||
export * from "./start-proxy/status-report";
|
||||
|
||||
/** The basename of the proxy artifacts. */
|
||||
export const UPDATEJOB_PROXY = "update-job-proxy";
|
||||
@@ -529,18 +397,28 @@ export function getProxyFilename() {
|
||||
: UPDATEJOB_PROXY;
|
||||
}
|
||||
|
||||
export type ProxyBinary = {
|
||||
/** The path to the proxy binary to run. */
|
||||
path: string;
|
||||
/**
|
||||
* The version associated with the proxy binary.
|
||||
* This usually corresponds to the CodeQL CLI release that the binary was obtained from.
|
||||
*/
|
||||
version: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a path to the proxy binary. If possible, this function will find the proxy in the
|
||||
* runner's tool cache. Otherwise, it downloads and extracts the proxy binary,
|
||||
* and stores it in the tool cache.
|
||||
*
|
||||
* @param action The action state.
|
||||
* @returns The path to the proxy binary.
|
||||
* @returns The path to the proxy binary and its version.
|
||||
*/
|
||||
export async function getProxyBinaryPath(
|
||||
action: ActionState<["Base", "ReadOnlyEnv", "Logger", "FeatureFlags"]>,
|
||||
apiDetails: GitHubApiDetails,
|
||||
): Promise<string> {
|
||||
): Promise<ProxyBinary> {
|
||||
const logger = action.logger;
|
||||
logger.startGroup("Setting up authentication proxy");
|
||||
|
||||
@@ -578,7 +456,10 @@ export async function getProxyBinaryPath(
|
||||
|
||||
logger.info(`Extracted the authentication proxy to '${proxyDir}'.`);
|
||||
}
|
||||
return path.join(proxyDir, proxyFileName);
|
||||
return {
|
||||
path: path.join(proxyDir, proxyFileName),
|
||||
version: proxyInfo.version,
|
||||
};
|
||||
} finally {
|
||||
logger.endGroup();
|
||||
}
|
||||
|
||||
136
src/start-proxy/status-report.test.ts
Normal file
136
src/start-proxy/status-report.test.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import * as core from "@actions/core";
|
||||
import test, { ExecutionContext } from "ava";
|
||||
import sinon from "sinon";
|
||||
|
||||
import * as statusReport from "../status-report";
|
||||
import {
|
||||
createTestConfig,
|
||||
makeMacro,
|
||||
RecordingLogger,
|
||||
setupTests,
|
||||
withRecordingLoggerAsync,
|
||||
} from "../testing-utils";
|
||||
import { ConfigurationError } from "../util";
|
||||
|
||||
import * as startProxyExports from "./status-report";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
test.serial("sendSuccessStatusReport", async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
const now = new Date();
|
||||
const config = createTestConfig({});
|
||||
const proxy_version = "1.2.3";
|
||||
const registry_types = ["git_source", "docker_registry"];
|
||||
|
||||
const createStatusReportBase = sinon.stub(
|
||||
statusReport,
|
||||
"createStatusReportBase",
|
||||
);
|
||||
createStatusReportBase.resolves({
|
||||
action_name: statusReport.ActionName.StartProxy,
|
||||
} as statusReport.StatusReportBase);
|
||||
|
||||
const sendStatusReport = sinon
|
||||
.stub(statusReport, "sendStatusReport")
|
||||
.resolves();
|
||||
|
||||
await startProxyExports.sendSuccessStatusReport(
|
||||
now,
|
||||
config,
|
||||
registry_types,
|
||||
{ proxy_version },
|
||||
logger,
|
||||
);
|
||||
|
||||
t.is(createStatusReportBase.callCount, 1);
|
||||
|
||||
t.is(sendStatusReport.callCount, 1);
|
||||
t.is(sendStatusReport.args[0].length, 1);
|
||||
|
||||
const arg = sendStatusReport.args[0][0];
|
||||
t.like(arg, {
|
||||
action_name: statusReport.ActionName.StartProxy,
|
||||
registry_types: "docker_registry,git_source",
|
||||
start_proxy: { proxy_version },
|
||||
});
|
||||
});
|
||||
|
||||
const sendFailedStatusReportTest = makeMacro({
|
||||
exec: async (
|
||||
t: ExecutionContext<unknown>,
|
||||
err: Error,
|
||||
expectedMessage: string,
|
||||
expectedStatus: statusReport.ActionStatus = "failure",
|
||||
) => {
|
||||
const now = new Date();
|
||||
|
||||
// Override core.setFailed to avoid it setting the program's exit code
|
||||
sinon.stub(core, "setFailed").returns();
|
||||
|
||||
const createStatusReportBase = sinon.stub(
|
||||
statusReport,
|
||||
"createStatusReportBase",
|
||||
);
|
||||
createStatusReportBase.resolves(undefined);
|
||||
|
||||
await withRecordingLoggerAsync(async (logger) => {
|
||||
await startProxyExports.sendFailedStatusReport(
|
||||
logger,
|
||||
now,
|
||||
undefined,
|
||||
err,
|
||||
);
|
||||
|
||||
// Check that the stub has been called exactly once, with the expected arguments,
|
||||
// but not with the message from the error.
|
||||
sinon.assert.calledOnceWithExactly(
|
||||
createStatusReportBase,
|
||||
statusReport.ActionName.StartProxy,
|
||||
expectedStatus,
|
||||
now,
|
||||
sinon.match.any,
|
||||
sinon.match.any,
|
||||
sinon.match.any,
|
||||
expectedMessage,
|
||||
);
|
||||
t.false(
|
||||
createStatusReportBase.calledWith(
|
||||
statusReport.ActionName.StartProxy,
|
||||
expectedStatus,
|
||||
now,
|
||||
sinon.match.any,
|
||||
sinon.match.any,
|
||||
sinon.match.any,
|
||||
sinon.match((msg: string) => msg.includes(err.message)),
|
||||
),
|
||||
"createStatusReportBase was called with the error message",
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
title: (providedTitle = "") => `sendFailedStatusReport - ${providedTitle}`,
|
||||
});
|
||||
|
||||
sendFailedStatusReportTest.serial(
|
||||
"reports generic error message for non-StartProxyError error",
|
||||
new Error("Something went wrong today"),
|
||||
"Error from start-proxy Action omitted (Error).",
|
||||
);
|
||||
|
||||
sendFailedStatusReportTest.serial(
|
||||
"reports generic error message for non-StartProxyError error with safe error message",
|
||||
new Error(
|
||||
startProxyExports.getStartProxyErrorMessage(
|
||||
startProxyExports.StartProxyErrorType.DownloadFailed,
|
||||
),
|
||||
),
|
||||
"Error from start-proxy Action omitted (Error).",
|
||||
);
|
||||
|
||||
sendFailedStatusReportTest.serial(
|
||||
"reports generic error message for ConfigurationError error",
|
||||
new ConfigurationError("Something went wrong today"),
|
||||
"Error from start-proxy Action omitted (ConfigurationError).",
|
||||
"user-error",
|
||||
);
|
||||
153
src/start-proxy/status-report.ts
Normal file
153
src/start-proxy/status-report.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import type { Config } from "../config-utils";
|
||||
import type { BuiltInLanguage } from "../languages";
|
||||
import type { Logger } from "../logging";
|
||||
import {
|
||||
ActionName,
|
||||
createStatusReportBase,
|
||||
getActionsStatus,
|
||||
sendStatusReport,
|
||||
type StatusReportBase,
|
||||
} from "../status-report";
|
||||
import * as util from "../util";
|
||||
|
||||
/** Additional status report data from `start-proxy`. */
|
||||
export interface StartProxyStatus {
|
||||
proxy_version: string;
|
||||
}
|
||||
|
||||
/** The type of successful status reports from `start-proxy`. */
|
||||
type StartProxySuccessReport = StatusReportBase & {
|
||||
start_proxy: StartProxyStatus;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a status report for the `start-proxy` action indicating a successful outcome.
|
||||
*
|
||||
* @param startedAt When the action was started.
|
||||
* @param config The configuration used.
|
||||
* @param registry_types The types of registries that are configured.
|
||||
* @param status Status information.
|
||||
* @param logger The logger to use.
|
||||
*/
|
||||
export async function sendSuccessStatusReport(
|
||||
startedAt: Date,
|
||||
config: Partial<Config>,
|
||||
registry_types: string[],
|
||||
status: StartProxyStatus,
|
||||
logger: Logger,
|
||||
) {
|
||||
const statusReportBase = await createStatusReportBase(
|
||||
ActionName.StartProxy,
|
||||
"success",
|
||||
startedAt,
|
||||
config,
|
||||
await util.checkDiskUsage(logger),
|
||||
logger,
|
||||
);
|
||||
if (statusReportBase !== undefined) {
|
||||
const statusReport: StartProxySuccessReport = {
|
||||
...statusReportBase,
|
||||
registry_types: registry_types.sort().join(","),
|
||||
start_proxy: status,
|
||||
};
|
||||
await sendStatusReport(statusReport);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerates specific error types for which we have corresponding error messages that
|
||||
* are safe to include in status reports.
|
||||
*/
|
||||
export enum StartProxyErrorType {
|
||||
DownloadFailed,
|
||||
ExtractionFailed,
|
||||
CacheFailed,
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns The error message corresponding to the error type.
|
||||
*/
|
||||
export function getStartProxyErrorMessage(
|
||||
errorType: StartProxyErrorType,
|
||||
): string {
|
||||
switch (errorType) {
|
||||
case StartProxyErrorType.DownloadFailed:
|
||||
return "Failed to download proxy archive.";
|
||||
case StartProxyErrorType.ExtractionFailed:
|
||||
return "Failed to extract proxy archive.";
|
||||
case StartProxyErrorType.CacheFailed:
|
||||
return "Failed to add proxy to toolcache";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We want to avoid accidentally leaking secrets that may be contained in exception
|
||||
* messages in the `start-proxy` action. Consequently, we don't report the messages
|
||||
* of arbitrary exceptions. This type of error ensures that the message is one from
|
||||
* `StartProxyErrorType` and therefore safe to include in a status report.
|
||||
*/
|
||||
export class StartProxyError extends Error {
|
||||
public readonly errorType: StartProxyErrorType;
|
||||
|
||||
constructor(errorType: StartProxyErrorType) {
|
||||
super();
|
||||
this.errorType = errorType;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an error message for `error` that can safely be reported in a status report,
|
||||
* i.e. that does not contain sensitive information.
|
||||
*
|
||||
* @param error The error for which to get an error message.
|
||||
*/
|
||||
export function getSafeErrorMessage(error: Error): string {
|
||||
// If the error is a `StartProxyError`, resolve the error type to the corresponding
|
||||
// error message.
|
||||
if (error instanceof StartProxyError) {
|
||||
return getStartProxyErrorMessage(error.errorType);
|
||||
}
|
||||
|
||||
// Otherwise, omit the actual error message.
|
||||
return `Error from start-proxy Action omitted (${error.constructor.name}).`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a status report for the `start-proxy` action indicating a failure.
|
||||
*
|
||||
* @param logger The logger to use.
|
||||
* @param startedAt When the action was started.
|
||||
* @param language The language provided as input, if any.
|
||||
* @param unwrappedError The exception that was thrown.
|
||||
*/
|
||||
export async function sendFailedStatusReport(
|
||||
logger: Logger,
|
||||
startedAt: Date,
|
||||
language: BuiltInLanguage | undefined,
|
||||
unwrappedError: unknown,
|
||||
) {
|
||||
const error = util.wrapError(unwrappedError);
|
||||
core.setFailed(`start-proxy action failed: ${error.message}`);
|
||||
|
||||
// To avoid the possibility of leaking sensitive information into the telemetry,
|
||||
// we don't include arbitrary error messages. Instead, `getSafeErrorMessage` will
|
||||
// return a generic message that includes the type of the error, unless it can decide
|
||||
// that the message is safe to include.
|
||||
const statusReportMessage = getSafeErrorMessage(error);
|
||||
const errorStatusReportBase = await createStatusReportBase(
|
||||
ActionName.StartProxy,
|
||||
getActionsStatus(error),
|
||||
startedAt,
|
||||
{
|
||||
languages: language === undefined ? undefined : [language],
|
||||
},
|
||||
await util.checkDiskUsage(logger),
|
||||
logger,
|
||||
statusReportMessage,
|
||||
);
|
||||
if (errorStatusReportBase !== undefined) {
|
||||
await sendStatusReport(errorStatusReportBase);
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
- The `codeql-action/analyze` step now validates that a correct argument for the `checkout_path` input has been provided. The `checkout_path` input must be set to the path where the repository that is being analyzed is checked out at on the runner, if that path is something other than the workspace root. If the path does not point to a repository checkout or is different from the path that was used by `codeql-action/init`, then the CodeQL Action now produces a warning. [#4148](https://github.com/github/codeql-action/pull/4148)
|
||||
@@ -1 +0,0 @@
|
||||
- Resolved an issue with third-party caching implementations that prevented caches from being restored correctly. [#4206](https://github.com/github/codeql-action/pull/4206)
|
||||
@@ -1 +0,0 @@
|
||||
- The `codeql-action/start-proxy` step that is used in Code Scanning Default Setup now finds CodeQL releases using the same logic as `codeql-action/init`. This primarily benefits customers who are using GitHub instances other than `github.com` where the `codeql-action/start-proxy` step may previously have been unable to locate recent releases and ended up using outdated versions of the private registry authentication proxy as a result. [#4198](https://github.com/github/codeql-action/pull/4198)
|
||||
Reference in New Issue
Block a user