Merge branch 'main' into update-bundle/codeql-bundle-v2.14.2

This commit is contained in:
Henry Mercer 2023-08-11 11:15:43 +01:00 committed by GitHub
commit 1e14fd9e7a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
68 changed files with 1711 additions and 759 deletions

View file

@ -5,7 +5,7 @@ import test from "ava";
import * as sinon from "sinon";
import * as actionsUtil from "./actions-util";
import { computeAutomationID, createStatusReportBase } from "./api-client";
import { computeAutomationID } from "./api-client";
import { EnvVar } from "./environment";
import { setupActionsVars, setupTests } from "./testing-utils";
import { initializeEnvironment, withTmpDir } from "./util";
@ -267,53 +267,3 @@ test("isAnalyzingDefaultBranch()", async (t) => {
getAdditionalInputStub.restore();
});
});
test("createStatusReportBase", async (t) => {
await withTmpDir(async (tmpDir: string) => {
setupActionsVars(tmpDir, tmpDir);
process.env["GITHUB_REF"] = "refs/heads/main";
process.env["GITHUB_SHA"] = "a".repeat(40);
process.env["GITHUB_RUN_ID"] = "100";
process.env["GITHUB_RUN_ATTEMPT"] = "2";
process.env["GITHUB_REPOSITORY"] = "octocat/HelloWorld";
process.env["CODEQL_ACTION_ANALYSIS_KEY"] = "analysis-key";
process.env["RUNNER_OS"] = "macOS";
const getRequiredInput = sinon.stub(actionsUtil, "getRequiredInput");
getRequiredInput.withArgs("matrix").resolves("input/matrix");
const statusReport = await createStatusReportBase(
"init",
"failure",
new Date("May 19, 2023 05:19:00"),
"failure cause",
"exception stack trace",
);
t.assert(typeof statusReport.job_run_uuid === "string");
t.assert(statusReport.workflow_run_id === 100);
t.assert(statusReport.workflow_run_attempt === 2);
t.assert(
statusReport.workflow_name === (process.env["GITHUB_WORKFLOW"] || ""),
);
t.assert(statusReport.job_name === (process.env["GITHUB_JOB"] || ""));
t.assert(statusReport.analysis_key === "analysis-key");
t.assert(statusReport.commit_oid === process.env["GITHUB_SHA"]);
t.assert(statusReport.ref === process.env["GITHUB_REF"]);
t.assert(statusReport.action_name === "init");
t.assert(statusReport.action_oid === "unknown");
t.assert(
statusReport.started_at === process.env[EnvVar.WORKFLOW_STARTED_AT],
);
t.assert(
statusReport.action_started_at ===
new Date("May 19, 2023 05:19:00").toISOString(),
);
t.assert(statusReport.status === "failure");
t.assert(statusReport.cause === "failure cause");
t.assert(statusReport.exception === "exception stack trace");
t.assert(statusReport.runner_os === process.env["RUNNER_OS"]);
t.assert(typeof statusReport.action_version === "string");
});
});

View file

@ -239,126 +239,6 @@ function getRefFromEnv(): string {
return refEnv;
}
export type ActionName =
| "init"
| "autobuild"
| "finish"
| "upload-sarif"
| "init-post"
| "resolve-environment";
export type ActionStatus =
| "starting"
| "aborted"
| "success"
| "failure"
| "user-error";
// Any status report may include an array of EventReports associated with it.
export interface EventReport {
/** An enumerable description of the event. */
event: string;
/** Time this event started. */
started_at: string;
/** Time this event ended. */
completed_at: string;
/** eg: `success`, `failure`, `timeout`, etc. */
exit_status?: string;
/** If the event is language-specific. */
language?: string;
/**
* A generic JSON blob of data related to this event.
* Use Object.assign() to append additional fields to the object.
*/
properties?: object;
}
export interface StatusReportBase {
/**
* UUID representing the job run that this status report belongs to. We
* generate our own UUID here because Actions currently does not expose a
* unique job run identifier. This UUID will allow us to more easily match
* reports from different steps in the same workflow job.
*
* If and when Actions does expose a unique job ID, we plan to populate a
* separate int field, `job_run_id`, with the Actions-generated identifier,
* as it will allow us to more easily join our telemetry data with Actions
* telemetry tables.
*/
job_run_uuid: string;
/** ID of the workflow run containing the action run. */
workflow_run_id: number;
/** Attempt number of the run containing the action run. */
workflow_run_attempt: number;
/** Workflow name. Converted to analysis_name further down the pipeline.. */
workflow_name: string;
/** Job name from the workflow. */
job_name: string;
/** Analysis key, normally composed from the workflow path and job name. */
analysis_key: string;
/** Value of the matrix for this instantiation of the job. */
matrix_vars?: string;
/** Commit oid that the workflow was triggered on. */
commit_oid: string;
/** Ref that the workflow was triggered on. */
ref: string;
/** Name of the action being executed. */
action_name: ActionName;
/** Version of the action being executed, as a ref. */
action_ref?: string;
/** Version of the action being executed, as a commit oid. */
action_oid: string;
/** Time the first action started. Normally the init action. */
started_at: string;
/** Time this action started. */
action_started_at: string;
/** Time this action completed, or undefined if not yet completed. */
completed_at?: string;
/** State this action is currently in. */
status: ActionStatus;
/**
* Testing environment: Set if non-production environment.
* The server accepts one of the following values:
* `["", "qa-rc", "qa-rc-1", "qa-rc-2", "qa-experiment-1", "qa-experiment-2", "qa-experiment-3"]`.
*/
testing_environment: string;
/**
* Information about the enablement of the ML-powered JS query pack.
*
* @see {@link util.getMlPoweredJsQueriesStatus}
*/
ml_powered_javascript_queries?: string;
/** Cause of the failure (or undefined if status is not failure). */
cause?: string;
/** Stack trace of the failure (or undefined if status is not failure). */
exception?: string;
/** Action runner operating system (context runner.os). */
runner_os: string;
/** Action runner hardware architecture (context runner.arch). */
runner_arch?: string;
/** Action runner operating system release (x.y.z from os.release()). */
runner_os_release?: string;
/** Action version (x.y.z from package.json). */
action_version: string;
/** CodeQL CLI version (x.y.z from the CLI). */
codeql_version?: string;
}
export interface DatabaseCreationTimings {
scanned_language_extraction_duration_ms?: number;
trap_import_duration_ms?: number;
}
export function getActionsStatus(
error?: unknown,
otherFailureCause?: string,
): ActionStatus {
if (error || otherFailureCause) {
return error instanceof UserError ? "user-error" : "failure";
} else {
return "success";
}
}
export function getActionVersion(): string {
return pkg.version!;
}

View file

@ -5,6 +5,7 @@ import * as actionsUtil from "./actions-util";
import * as analyze from "./analyze";
import * as api from "./api-client";
import * as configUtils from "./config-utils";
import * as statusReport from "./status-report";
import {
setupTests,
setupActionsVars,
@ -27,9 +28,9 @@ test("analyze action with RAM & threads from environment variables", async (t) =
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
process.env["GITHUB_API_URL"] = "https://api.github.com";
sinon
.stub(api, "createStatusReportBase")
.resolves({} as actionsUtil.StatusReportBase);
sinon.stub(api, "sendStatusReport").resolves(true);
.stub(statusReport, "createStatusReportBase")
.resolves({} as statusReport.StatusReportBase);
sinon.stub(statusReport, "sendStatusReport").resolves(true);
sinon.stub(actionsUtil, "isAnalyzingDefaultBranch").resolves(true);
const gitHubVersion: util.GitHubVersion = {

View file

@ -5,6 +5,7 @@ import * as actionsUtil from "./actions-util";
import * as analyze from "./analyze";
import * as api from "./api-client";
import * as configUtils from "./config-utils";
import * as statusReport from "./status-report";
import {
setupTests,
setupActionsVars,
@ -27,9 +28,9 @@ test("analyze action with RAM & threads from action inputs", async (t) => {
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
process.env["GITHUB_API_URL"] = "https://api.github.com";
sinon
.stub(api, "createStatusReportBase")
.resolves({} as actionsUtil.StatusReportBase);
sinon.stub(api, "sendStatusReport").resolves(true);
.stub(statusReport, "createStatusReportBase")
.resolves({} as statusReport.StatusReportBase);
sinon.stub(statusReport, "sendStatusReport").resolves(true);
const gitHubVersion: util.GitHubVersion = {
type: util.GitHubVariant.DOTCOM,
};

View file

@ -5,7 +5,6 @@ import { performance } from "perf_hooks";
import * as core from "@actions/core";
import * as actionsUtil from "./actions-util";
import { DatabaseCreationTimings } from "./actions-util";
import {
CodeQLAnalysisError,
dbIsFinalized,
@ -15,7 +14,6 @@ import {
runQueries,
} from "./analyze";
import { getApiDetails, getGitHubVersion } from "./api-client";
import * as api from "./api-client";
import { runAutobuild } from "./autobuild";
import { getCodeQL } from "./codeql";
import { Config, getConfig, getMlPoweredJsQueriesStatus } from "./config-utils";
@ -25,6 +23,13 @@ import { Feature, Features } from "./feature-flags";
import { Language } from "./languages";
import { getActionsLogger, Logger } from "./logging";
import { parseRepositoryNwo } from "./repository";
import * as statusReport from "./status-report";
import {
createStatusReportBase,
DatabaseCreationTimings,
getActionsStatus,
StatusReportBase,
} from "./status-report";
import { getTotalCacheSize, uploadTrapCaches } from "./trap-caching";
import * as uploadLib from "./upload-lib";
import { UploadResult } from "./upload-lib";
@ -36,8 +41,8 @@ interface AnalysisStatusReport
QueriesStatusReport {}
interface FinishStatusReport
extends actionsUtil.StatusReportBase,
actionsUtil.DatabaseCreationTimings,
extends StatusReportBase,
DatabaseCreationTimings,
AnalysisStatusReport {}
interface FinishWithTrapUploadStatusReport extends FinishStatusReport {
@ -47,7 +52,7 @@ interface FinishWithTrapUploadStatusReport extends FinishStatusReport {
trap_cache_upload_duration_ms: number;
}
export async function sendStatusReport(
async function sendStatusReport(
startedAt: Date,
config: Config | undefined,
stats: AnalysisStatusReport | undefined,
@ -57,18 +62,16 @@ export async function sendStatusReport(
didUploadTrapCaches: boolean,
logger: Logger,
) {
const status = actionsUtil.getActionsStatus(
error,
stats?.analyze_failure_language,
);
const statusReportBase = await api.createStatusReportBase(
const status = getActionsStatus(error, stats?.analyze_failure_language);
const statusReportBase = await createStatusReportBase(
"finish",
status,
startedAt,
await util.checkDiskUsage(),
error?.message,
error?.stack,
);
const statusReport: FinishStatusReport = {
const report: FinishStatusReport = {
...statusReportBase,
...(config
? {
@ -80,15 +83,15 @@ export async function sendStatusReport(
};
if (config && didUploadTrapCaches) {
const trapCacheUploadStatusReport: FinishWithTrapUploadStatusReport = {
...statusReport,
...report,
trap_cache_upload_duration_ms: Math.round(trapCacheUploadTime || 0),
trap_cache_upload_size_bytes: Math.round(
await getTotalCacheSize(config.trapCaches, logger),
),
};
await api.sendStatusReport(trapCacheUploadStatusReport);
await statusReport.sendStatusReport(trapCacheUploadStatusReport);
} else {
await api.sendStatusReport(statusReport);
await statusReport.sendStatusReport(report);
}
}
@ -181,8 +184,13 @@ async function run() {
const logger = getActionsLogger();
try {
if (
!(await api.sendStatusReport(
await api.createStatusReportBase("finish", "starting", startedAt),
!(await statusReport.sendStatusReport(
await createStatusReportBase(
"finish",
"starting",
startedAt,
await util.checkDiskUsage(logger),
),
))
) {
return;

View file

@ -6,7 +6,6 @@ import * as toolrunner from "@actions/exec/lib/toolrunner";
import del from "del";
import * as yaml from "js-yaml";
import { DatabaseCreationTimings, EventReport } from "./actions-util";
import * as analysisPaths from "./analysis-paths";
import { CodeQL, getCodeQL } from "./codeql";
import * as configUtils from "./config-utils";
@ -18,6 +17,7 @@ import {
} from "./feature-flags";
import { isScannedLanguage, Language } from "./languages";
import { Logger } from "./logging";
import { DatabaseCreationTimings, EventReport } from "./status-report";
import { endTracingForCluster } from "./tracer-config";
import { validateSarifFileSchema } from "./upload-lib";
import * as util from "./util";

View file

@ -1,31 +1,14 @@
import * as os from "os";
import * as core from "@actions/core";
import * as githubUtils from "@actions/github/lib/utils";
import * as retry from "@octokit/plugin-retry";
import consoleLogLevel from "console-log-level";
import { getActionVersion, getRequiredInput } from "./actions-util";
import {
ActionName,
ActionStatus,
StatusReportBase,
getActionVersion,
getOptionalInput,
getRef,
getRequiredInput,
getWorkflowEventName,
getWorkflowRunAttempt,
getWorkflowRunID,
} from "./actions-util";
import { EnvVar } from "./environment";
import {
getCachedCodeQlVersion,
getRequiredEnvParam,
GITHUB_DOTCOM_URL,
GitHubVariant,
GitHubVersion,
isHTTPError,
isInTestMode,
parseGitHubUrl,
parseMatrixInput,
} from "./util";
@ -136,187 +119,6 @@ export async function getGitHubVersion(): Promise<GitHubVersion> {
return cachedGitHubVersion;
}
/**
* Compose a StatusReport.
*
* @param actionName The name of the action, e.g. 'init', 'finish', 'upload-sarif'
* @param status The status. Must be 'success', 'failure', or 'starting'
* @param startedAt The time this action started executing.
* @param cause Cause of failure (only supply if status is 'failure')
* @param exception Exception (only supply if status is 'failure')
*/
export async function createStatusReportBase(
actionName: ActionName,
status: ActionStatus,
actionStartedAt: Date,
cause?: string,
exception?: string,
): Promise<StatusReportBase> {
const commitOid = getOptionalInput("sha") || process.env["GITHUB_SHA"] || "";
const ref = await getRef();
const jobRunUUID = process.env[EnvVar.JOB_RUN_UUID] || "";
const workflowRunID = getWorkflowRunID();
const workflowRunAttempt = getWorkflowRunAttempt();
const workflowName = process.env["GITHUB_WORKFLOW"] || "";
const jobName = process.env["GITHUB_JOB"] || "";
const analysis_key = await getAnalysisKey();
let workflowStartedAt = process.env[EnvVar.WORKFLOW_STARTED_AT];
if (workflowStartedAt === undefined) {
workflowStartedAt = actionStartedAt.toISOString();
core.exportVariable(EnvVar.WORKFLOW_STARTED_AT, workflowStartedAt);
}
const runnerOs = getRequiredEnvParam("RUNNER_OS");
const codeQlCliVersion = getCachedCodeQlVersion();
const actionRef = process.env["GITHUB_ACTION_REF"];
const testingEnvironment = process.env[EnvVar.TESTING_ENVIRONMENT] || "";
// re-export the testing environment variable so that it is available to subsequent steps,
// even if it was only set for this step
if (testingEnvironment !== "") {
core.exportVariable(EnvVar.TESTING_ENVIRONMENT, testingEnvironment);
}
const statusReport: StatusReportBase = {
job_run_uuid: jobRunUUID,
workflow_run_id: workflowRunID,
workflow_run_attempt: workflowRunAttempt,
workflow_name: workflowName,
job_name: jobName,
analysis_key,
commit_oid: commitOid,
ref,
action_name: actionName,
action_ref: actionRef,
action_oid: "unknown", // TODO decide if it's possible to fill this in
started_at: workflowStartedAt,
action_started_at: actionStartedAt.toISOString(),
status,
testing_environment: testingEnvironment,
runner_os: runnerOs,
action_version: getActionVersion(),
};
// Add optional parameters
if (cause) {
statusReport.cause = cause;
}
if (exception) {
statusReport.exception = exception;
}
if (
status === "success" ||
status === "failure" ||
status === "aborted" ||
status === "user-error"
) {
statusReport.completed_at = new Date().toISOString();
}
const matrix = getRequiredInput("matrix");
if (matrix) {
statusReport.matrix_vars = matrix;
}
if ("RUNNER_ARCH" in process.env) {
// RUNNER_ARCH is available only in GHES 3.4 and later
// Values other than X86, X64, ARM, or ARM64 are discarded server side
statusReport.runner_arch = process.env["RUNNER_ARCH"];
}
if (runnerOs === "Windows" || runnerOs === "macOS") {
statusReport.runner_os_release = os.release();
}
if (codeQlCliVersion !== undefined) {
statusReport.codeql_version = codeQlCliVersion;
}
return statusReport;
}
const GENERIC_403_MSG =
"The repo on which this action is running is not opted-in to CodeQL code scanning.";
const GENERIC_404_MSG =
"Not authorized to use the CodeQL code scanning feature on this repo.";
const OUT_OF_DATE_MSG =
"CodeQL Action is out-of-date. Please upgrade to the latest version of codeql-action.";
const INCOMPATIBLE_MSG =
"CodeQL Action version is incompatible with the code scanning endpoint. Please update to a compatible version of codeql-action.";
/**
* Send a status report to the code_scanning/analysis/status endpoint.
*
* Optionally checks the response from the API endpoint and sets the action
* as failed if the status report failed. This is only expected to be used
* when sending a 'starting' report.
*
* Returns whether sending the status report was successful of not.
*/
export async function sendStatusReport<S extends StatusReportBase>(
statusReport: S,
): Promise<boolean> {
const statusReportJSON = JSON.stringify(statusReport);
core.debug(`Sending status report: ${statusReportJSON}`);
// If in test mode we don't want to upload the results
if (isInTestMode()) {
core.debug("In test mode. Status reports are not uploaded.");
return true;
}
const nwo = getRequiredEnvParam("GITHUB_REPOSITORY");
const [owner, repo] = nwo.split("/");
const client = getApiClient();
try {
await client.request(
"PUT /repos/:owner/:repo/code-scanning/analysis/status",
{
owner,
repo,
data: statusReportJSON,
},
);
return true;
} catch (e) {
console.log(e);
if (isHTTPError(e)) {
switch (e.status) {
case 403:
if (
getWorkflowEventName() === "push" &&
process.env["GITHUB_ACTOR"] === "dependabot[bot]"
) {
core.setFailed(
'Workflows triggered by Dependabot on the "push" event run with read-only access. ' +
"Uploading Code Scanning results requires write access. " +
'To use Code Scanning with Dependabot, please ensure you are using the "pull_request" event for this workflow and avoid triggering on the "push" event for Dependabot branches. ' +
"See https://docs.github.com/en/code-security/secure-coding/configuring-code-scanning#scanning-on-push for more information on how to configure these events.",
);
} else {
core.setFailed(e.message || GENERIC_403_MSG);
}
return false;
case 404:
core.setFailed(GENERIC_404_MSG);
return false;
case 422:
// schema incompatibility when reporting status
// this means that this action version is no longer compatible with the API
// we still want to continue as it is likely the analysis endpoint will work
if (getRequiredEnvParam("GITHUB_SERVER_URL") !== GITHUB_DOTCOM_URL) {
core.debug(INCOMPATIBLE_MSG);
} else {
core.debug(OUT_OF_DATE_MSG);
}
return true;
}
}
// something else has gone wrong and the request/response will be logged by octokit
// it's possible this is a transient error and we should continue scanning
core.error(
"An unexpected error occurred when sending code scanning status report.",
);
return true;
}
}
/**
* Get the path of the currently executing workflow relative to the repository root.
*/

View file

@ -1,23 +1,24 @@
import * as core from "@actions/core";
import {
getActionsStatus,
getActionVersion,
getOptionalInput,
getTemporaryDirectory,
StatusReportBase,
} from "./actions-util";
import {
createStatusReportBase,
getGitHubVersion,
sendStatusReport,
} from "./api-client";
import { getGitHubVersion } from "./api-client";
import { determineAutobuildLanguages, runAutobuild } from "./autobuild";
import * as configUtils from "./config-utils";
import { EnvVar } from "./environment";
import { Language } from "./languages";
import { getActionsLogger } from "./logging";
import { Logger, getActionsLogger } from "./logging";
import {
StatusReportBase,
getActionsStatus,
createStatusReportBase,
sendStatusReport,
} from "./status-report";
import {
checkDiskUsage,
checkGitHubVersionInRange,
initializeEnvironment,
wrapError,
@ -31,6 +32,7 @@ interface AutobuildStatusReport extends StatusReportBase {
}
async function sendCompletedStatusReport(
logger: Logger,
startedAt: Date,
allLanguages: string[],
failingLanguage?: string,
@ -43,6 +45,7 @@ async function sendCompletedStatusReport(
"autobuild",
status,
startedAt,
await checkDiskUsage(logger),
cause?.message,
cause?.stack,
);
@ -62,7 +65,12 @@ async function run() {
try {
if (
!(await sendStatusReport(
await createStatusReportBase("autobuild", "starting", startedAt),
await createStatusReportBase(
"autobuild",
"starting",
startedAt,
await checkDiskUsage(logger),
),
))
) {
return;
@ -101,6 +109,7 @@ async function run() {
`We were unable to automatically build your code. Please replace the call to the autobuild action with your custom build steps. ${error.message}`,
);
await sendCompletedStatusReport(
logger,
startedAt,
languages ?? [],
currentLanguage,
@ -109,7 +118,7 @@ async function run() {
return;
}
await sendCompletedStatusReport(startedAt, languages ?? []);
await sendCompletedStatusReport(logger, startedAt, languages ?? []);
}
async function runWrapper() {

View file

@ -165,6 +165,31 @@ test("downloads and caches explicitly requested bundles that aren't in the toolc
});
});
test("caches semantically versioned bundles using their semantic version number", async (t) => {
await util.withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
const url = mockBundleDownloadApi({
tagName: `codeql-bundle-v2.14.0`,
isPinned: false,
});
const result = await codeql.setupCodeQL(
url,
SAMPLE_DOTCOM_API_DETAILS,
tmpDir,
util.GitHubVariant.DOTCOM,
SAMPLE_DEFAULT_CLI_VERSION,
getRunnerLogger(true),
false,
);
t.is(toolcache.findAllVersions("CodeQL").length, 1);
t.assert(toolcache.find("CodeQL", `2.14.0`));
t.is(result.toolsVersion, `2.14.0`);
t.is(result.toolsSource, ToolsSource.Download);
t.assert(Number.isInteger(result.toolsDownloadDurationMs));
});
});
test("downloads an explicitly requested bundle even if a different version is cached", async (t) => {
await util.withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);

View file

@ -1162,6 +1162,7 @@ async function runTool(
) {
let output = "";
let error = "";
process.stdout.write(`[command]${cmd} ${args.join(" ")}\n`);
const exitCode = await new toolrunner.ToolRunner(cmd, args, {
ignoreReturnCode: true,
listeners: {

View file

@ -1,41 +1,37 @@
export enum EnvVar {
/** Set to true when the `analyze` Action completes successfully. */
/** Whether the `analyze` Action completes successfully. */
ANALYZE_DID_COMPLETE_SUCCESSFULLY = "CODEQL_ACTION_ANALYZE_DID_COMPLETE_SUCCESSFULLY",
/** Set to "true" when the CodeQL Action has invoked the Go autobuilder. */
/** Whether the CodeQL Action has invoked the Go autobuilder. */
DID_AUTOBUILD_GOLANG = "CODEQL_ACTION_DID_AUTOBUILD_GOLANG",
/**
* Used to disable the SARIF post-processing in the Action that removes duplicate locations from
* Whether to disable the SARIF post-processing in the Action that removes duplicate locations from
* notifications in the `run[].invocations[].toolExecutionNotifications` SARIF property.
*/
DISABLE_DUPLICATE_LOCATION_FIX = "CODEQL_ACTION_DISABLE_DUPLICATE_LOCATION_FIX",
/**
* If set to the "true" string, then the CodeQL Action is using its
* own deprecated and non-standard way of scanning for multiple
* languages.
* Whether the CodeQL Action is using its own deprecated and non-standard way of scanning for
* multiple languages.
*/
FEATURE_MULTI_LANGUAGE = "CODEQL_ACTION_FEATURE_MULTI_LANGUAGE",
/**
* If set to the "true" string, then the CodeQL Action is using its
* own sandwiched workflow mechanism.
*/
/** Whether the CodeQL Action is using its own sandwiched workflow mechanism. */
FEATURE_SANDWICH = "CODEQL_ACTION_FEATURE_SANDWICH",
/**
* If set to a truthy value, then the CodeQL Action might combine SARIF
* output from several `interpret-results` runs for the same language.
* Whether the CodeQL Action might combine SARIF output from several `interpret-results` runs for
* the same language.
*/
FEATURE_SARIF_COMBINE = "CODEQL_ACTION_FEATURE_SARIF_COMBINE",
/**
* If set to the "true" string, then the CodeQL Action will upload SARIF,
* not the CLI.
*/
/** Whether the CodeQL Action will upload SARIF, not the CLI. */
FEATURE_WILL_UPLOAD = "CODEQL_ACTION_FEATURE_WILL_UPLOAD",
/** Whether the CodeQL Action has already warned the user about low disk space. */
HAS_WARNED_ABOUT_DISK_SPACE = "CODEQL_ACTION_HAS_WARNED_ABOUT_DISK_SPACE",
/** UUID representing the current job run. */
JOB_RUN_UUID = "JOB_RUN_UUID",
@ -44,7 +40,7 @@ export enum EnvVar {
/** Whether to suppress the warning if the current CLI will soon be unsupported. */
SUPPRESS_DEPRECATED_SOON_WARNING = "CODEQL_ACTION_SUPPRESS_DEPRECATED_SOON_WARNING",
/** Used to disable uploading SARIF results or status reports to the GitHub API */
/** Whether to disable uploading SARIF results or status reports to the GitHub API */
TEST_MODE = "CODEQL_ACTION_TEST_MODE",
TESTING_ENVIRONMENT = "CODEQL_ACTION_TESTING_ENVIRONMENT",

View file

@ -6,23 +6,21 @@
import * as core from "@actions/core";
import {
getActionsStatus,
getTemporaryDirectory,
printDebugLogs,
StatusReportBase,
} from "./actions-util";
import {
createStatusReportBase,
getGitHubVersion,
sendStatusReport,
} from "./api-client";
import { getTemporaryDirectory, printDebugLogs } from "./actions-util";
import { getGitHubVersion } from "./api-client";
import * as debugArtifacts from "./debug-artifacts";
import { Features } from "./feature-flags";
import * as initActionPostHelper from "./init-action-post-helper";
import { getActionsLogger } from "./logging";
import { parseRepositoryNwo } from "./repository";
import {
StatusReportBase,
sendStatusReport,
createStatusReportBase,
getActionsStatus,
} from "./status-report";
import {
checkDiskUsage,
checkGitHubVersionInRange,
getRequiredEnvParam,
wrapError,
@ -69,6 +67,7 @@ async function runWrapper() {
"init-post",
getActionsStatus(error),
startedAt,
await checkDiskUsage(),
error.message,
error.stack,
),
@ -79,6 +78,7 @@ async function runWrapper() {
"init-post",
"success",
startedAt,
await checkDiskUsage(),
);
const statusReport: InitPostStatusReport = {
...statusReportBase,

View file

@ -4,18 +4,12 @@ import * as core from "@actions/core";
import { v4 as uuidV4 } from "uuid";
import {
getActionsStatus,
getActionVersion,
getOptionalInput,
getRequiredInput,
getTemporaryDirectory,
StatusReportBase,
} from "./actions-util";
import {
createStatusReportBase,
getGitHubVersion,
sendStatusReport,
} from "./api-client";
import { getGitHubVersion } from "./api-client";
import { CodeQL } from "./codeql";
import * as configUtils from "./config-utils";
import { getMlPoweredJsQueriesStatus } from "./config-utils";
@ -26,8 +20,15 @@ import { Language } from "./languages";
import { getActionsLogger, Logger } from "./logging";
import { parseRepositoryNwo } from "./repository";
import { ToolsSource } from "./setup-codeql";
import {
StatusReportBase,
createStatusReportBase,
getActionsStatus,
sendStatusReport,
} from "./status-report";
import { getTotalCacheSize } from "./trap-caching";
import {
checkDiskUsage,
checkForTimeout,
checkGitHubVersionInRange,
DEFAULT_DEBUG_ARTIFACT_NAME,
@ -102,6 +103,7 @@ async function sendCompletedStatusReport(
"init",
getActionsStatus(error),
startedAt,
await checkDiskUsage(logger),
error?.message,
error?.stack,
);
@ -222,6 +224,7 @@ async function run() {
"init",
"starting",
startedAt,
await checkDiskUsage(logger),
workflowErrors,
),
))
@ -302,6 +305,7 @@ async function run() {
"init",
error instanceof UserError ? "user-error" : "aborted",
startedAt,
await checkDiskUsage(),
error.message,
error.stack,
),

View file

@ -1,22 +1,27 @@
import * as core from "@actions/core";
import {
getActionsStatus,
getOptionalInput,
getRequiredInput,
getTemporaryDirectory,
} from "./actions-util";
import {
createStatusReportBase,
getGitHubVersion,
sendStatusReport,
} from "./api-client";
import { getGitHubVersion } from "./api-client";
import { CommandInvocationError } from "./codeql";
import * as configUtils from "./config-utils";
import { Language, resolveAlias } from "./languages";
import { getActionsLogger } from "./logging";
import { runResolveBuildEnvironment } from "./resolve-environment";
import { checkForTimeout, checkGitHubVersionInRange, wrapError } from "./util";
import {
sendStatusReport,
createStatusReportBase,
getActionsStatus,
} from "./status-report";
import {
checkDiskUsage,
checkForTimeout,
checkGitHubVersionInRange,
wrapError,
} from "./util";
const ACTION_NAME = "resolve-environment";
const ENVIRONMENT_OUTPUT_NAME = "environment";
@ -29,7 +34,12 @@ async function run() {
try {
if (
!(await sendStatusReport(
await createStatusReportBase(ACTION_NAME, "starting", startedAt),
await createStatusReportBase(
ACTION_NAME,
"starting",
startedAt,
await checkDiskUsage(logger),
),
))
) {
return;
@ -74,6 +84,7 @@ async function run() {
ACTION_NAME,
getActionsStatus(error),
startedAt,
await checkDiskUsage(),
error.message,
error.stack,
),
@ -84,7 +95,12 @@ async function run() {
}
await sendStatusReport(
await createStatusReportBase(ACTION_NAME, "success", startedAt),
await createStatusReportBase(
ACTION_NAME,
"success",
startedAt,
await checkDiskUsage(),
),
);
}

View file

@ -15,7 +15,10 @@ import * as api from "./api-client";
// creation scripts. Ensure that any changes to the format of this file are compatible with both of
// these dependents.
import * as defaults from "./defaults.json";
import { CodeQLDefaultVersionInfo } from "./feature-flags";
import {
CODEQL_VERSION_BUNDLE_SEMANTICALLY_VERSIONED,
CodeQLDefaultVersionInfo,
} from "./feature-flags";
import { Logger } from "./logging";
import * as util from "./util";
import { isGoodVersion, wrapError } from "./util";
@ -610,20 +613,12 @@ export async function downloadCodeQL(
);
}
// Include both the CLI version and the bundle version in the toolcache version number. That way
// if the user requests the same URL again, we can get it from the cache without having to call
// any of the Releases API.
//
// Special case: If the CLI version is a pre-release or contains build metadata, then cache the
// bundle as `0.0.0-<bundleVersion>` to avoid the bundle being interpreted as containing a stable
// CLI release. In principle, it should be enough to just check that the CLI version isn't a
// pre-release, but the version numbers of CodeQL nightlies have the format `x.y.z+<timestamp>`,
// and we don't want these nightlies to override stable CLI versions in the toolcache.
const toolcacheVersion = maybeCliVersion?.match(/^[0-9]+\.[0-9]+\.[0-9]+$/)
? `${maybeCliVersion}-${bundleVersion}`
: convertToSemVer(bundleVersion, logger);
logger.debug("Caching CodeQL bundle.");
const toolcacheVersion = getCanonicalToolcacheVersion(
maybeCliVersion,
bundleVersion,
logger,
);
const toolcachedBundlePath = await toolcache.cacheDir(
extractedBundlePath,
"CodeQL",
@ -656,6 +651,38 @@ export function getCodeQLURLVersion(url: string): string {
return match[1];
}
/**
* Returns the toolcache version number to use to store the bundle with the associated CLI version
* and bundle version.
*
* This is the canonical version number, since toolcaches populated by different versions of the
* CodeQL Action or different runner image creation scripts may store the bundle using a different
* version number. Functions like `getCodeQLSource` that fetch the bundle from rather than save the
* bundle to the toolcache should handle these different version numbers.
*/
function getCanonicalToolcacheVersion(
cliVersion: string | undefined,
bundleVersion: string,
logger: Logger,
) {
// If the CLI version is a pre-release or contains build metadata, then cache the
// bundle as `0.0.0-<bundleVersion>` to avoid the bundle being interpreted as containing a stable
// CLI release. In principle, it should be enough to just check that the CLI version isn't a
// pre-release, but the version numbers of CodeQL nightlies have the format `x.y.z+<timestamp>`,
// and we don't want these nightlies to override stable CLI versions in the toolcache.
if (!cliVersion?.match(/^[0-9]+\.[0-9]+\.[0-9]+$/)) {
return convertToSemVer(bundleVersion, logger);
}
// If the bundle is semantically versioned, it can be looked up based on just the CLI version
// number, so version it in the toolcache using just the CLI version number.
if (semver.gte(cliVersion, CODEQL_VERSION_BUNDLE_SEMANTICALLY_VERSIONED)) {
return cliVersion;
}
// Include both the CLI version and the bundle version in the toolcache version number. That way
// we can find the bundle in the toolcache based on either the CLI version or the bundle version.
return `${cliVersion}-${bundleVersion}`;
}
/**
* Obtains the CodeQL bundle, installs it in the toolcache if appropriate, and extracts it.
*

60
src/status-report.test.ts Normal file
View file

@ -0,0 +1,60 @@
import test from "ava";
import * as sinon from "sinon";
import * as actionsUtil from "./actions-util";
import { EnvVar } from "./environment";
import { createStatusReportBase } from "./status-report";
import { setupTests, setupActionsVars } from "./testing-utils";
import { withTmpDir } from "./util";
setupTests(test);
test("createStatusReportBase", async (t) => {
await withTmpDir(async (tmpDir: string) => {
setupActionsVars(tmpDir, tmpDir);
process.env["CODEQL_ACTION_ANALYSIS_KEY"] = "analysis-key";
process.env["GITHUB_REF"] = "refs/heads/main";
process.env["GITHUB_REPOSITORY"] = "octocat/HelloWorld";
process.env["GITHUB_RUN_ATTEMPT"] = "2";
process.env["GITHUB_RUN_ID"] = "100";
process.env["GITHUB_SHA"] = "a".repeat(40);
process.env["ImageVersion"] = "2023.05.19.1";
process.env["RUNNER_OS"] = "macOS";
const getRequiredInput = sinon.stub(actionsUtil, "getRequiredInput");
getRequiredInput.withArgs("matrix").resolves("input/matrix");
const statusReport = await createStatusReportBase(
"init",
"failure",
new Date("May 19, 2023 05:19:00"),
{ numAvailableBytes: 100, numTotalBytes: 500 },
"failure cause",
"exception stack trace",
);
t.is(statusReport.action_name, "init");
t.is(statusReport.action_oid, "unknown");
t.is(typeof statusReport.action_version, "string");
t.is(
statusReport.action_started_at,
new Date("May 19, 2023 05:19:00").toISOString(),
);
t.is(statusReport.analysis_key, "analysis-key");
t.is(statusReport.cause, "failure cause");
t.is(statusReport.commit_oid, process.env["GITHUB_SHA"]);
t.is(statusReport.exception, "exception stack trace");
t.is(statusReport.job_name, process.env["GITHUB_JOB"] || "");
t.is(typeof statusReport.job_run_uuid, "string");
t.is(statusReport.ref, process.env["GITHUB_REF"]);
t.is(statusReport.runner_available_disk_space_bytes, 100);
t.is(statusReport.runner_image_version, process.env["ImageVersion"]);
t.is(statusReport.runner_os, process.env["RUNNER_OS"]);
t.is(statusReport.started_at, process.env[EnvVar.WORKFLOW_STARTED_AT]!);
t.is(statusReport.status, "failure");
t.is(statusReport.workflow_name, process.env["GITHUB_WORKFLOW"] || "");
t.is(statusReport.workflow_run_attempt, 2);
t.is(statusReport.workflow_run_id, 100);
});
});

341
src/status-report.ts Normal file
View file

@ -0,0 +1,341 @@
import * as os from "os";
import * as core from "@actions/core";
import {
getWorkflowEventName,
getOptionalInput,
getRef,
getWorkflowRunID,
getWorkflowRunAttempt,
getActionVersion,
getRequiredInput,
} from "./actions-util";
import { getAnalysisKey, getApiClient } from "./api-client";
import { EnvVar } from "./environment";
import {
UserError,
isHTTPError,
getRequiredEnvParam,
getCachedCodeQlVersion,
isInTestMode,
GITHUB_DOTCOM_URL,
DiskUsage,
} from "./util";
export type ActionName =
| "autobuild"
| "finish"
| "init"
| "init-post"
| "resolve-environment"
| "upload-sarif";
export type ActionStatus =
| "aborted"
| "failure"
| "starting"
| "success"
| "user-error";
export interface StatusReportBase {
/** Name of the action being executed. */
action_name: ActionName;
/** Version of the action being executed, as a commit oid. */
action_oid: string;
/** Version of the action being executed, as a ref. */
action_ref?: string;
/** Time this action started. */
action_started_at: string;
/** Action version (x.y.z from package.json). */
action_version: string;
/** Analysis key, normally composed from the workflow path and job name. */
analysis_key: string;
/** Cause of the failure (or undefined if status is not failure). */
cause?: string;
/** CodeQL CLI version (x.y.z from the CLI). */
codeql_version?: string;
/** Commit oid that the workflow was triggered on. */
commit_oid: string;
/** Time this action completed, or undefined if not yet completed. */
completed_at?: string;
/** Stack trace of the failure (or undefined if status is not failure). */
exception?: string;
/** Job name from the workflow. */
job_name: string;
/**
* UUID representing the job run that this status report belongs to. We
* generate our own UUID here because Actions currently does not expose a
* unique job run identifier. This UUID will allow us to more easily match
* reports from different steps in the same workflow job.
*
* If and when Actions does expose a unique job ID, we plan to populate a
* separate int field, `job_run_id`, with the Actions-generated identifier,
* as it will allow us to more easily join our telemetry data with Actions
* telemetry tables.
*/
job_run_uuid: string;
/** Value of the matrix for this instantiation of the job. */
matrix_vars?: string;
/**
* Information about the enablement of the ML-powered JS query pack.
*
* @see {@link util.getMlPoweredJsQueriesStatus}
*/
ml_powered_javascript_queries?: string;
/** Ref that the workflow was triggered on. */
ref: string;
/** Action runner hardware architecture (context runner.arch). */
runner_arch?: string;
/** Available disk space on the runner, in bytes. */
runner_available_disk_space_bytes: number;
/**
* Version of the runner image, for workflows running on GitHub-hosted runners. Absent otherwise.
*/
runner_image_version?: string;
/** Action runner operating system (context runner.os). */
runner_os: string;
/** Action runner operating system release (x.y.z from os.release()). */
runner_os_release?: string;
/** Total disk space on the runner, in bytes. */
runner_total_disk_space_bytes: number;
/** Time the first action started. Normally the init action. */
started_at: string;
/** State this action is currently in. */
status: ActionStatus;
/**
* Testing environment: Set if non-production environment.
* The server accepts one of the following values:
* `["", "qa-rc", "qa-rc-1", "qa-rc-2", "qa-experiment-1", "qa-experiment-2", "qa-experiment-3"]`.
*/
testing_environment: string;
/** Workflow name. Converted to analysis_name further down the pipeline.. */
workflow_name: string;
/** Attempt number of the run containing the action run. */
workflow_run_attempt: number;
/** ID of the workflow run containing the action run. */
workflow_run_id: number;
}
export interface DatabaseCreationTimings {
scanned_language_extraction_duration_ms?: number;
trap_import_duration_ms?: number;
}
export function getActionsStatus(
error?: unknown,
otherFailureCause?: string,
): ActionStatus {
if (error || otherFailureCause) {
return error instanceof UserError ? "user-error" : "failure";
} else {
return "success";
}
}
// Any status report may include an array of EventReports associated with it.
export interface EventReport {
/** Time this event ended. */
completed_at: string;
/** An enumerable description of the event. */
event: string;
/** eg: `success`, `failure`, `timeout`, etc. */
exit_status?: string;
/** If the event is language-specific. */
language?: string;
/**
* A generic JSON blob of data related to this event.
* Use Object.assign() to append additional fields to the object.
*/
properties?: object;
/** Time this event started. */
started_at: string;
}
/**
* Compose a StatusReport.
*
* @param actionName The name of the action, e.g. 'init', 'finish', 'upload-sarif'
* @param status The status. Must be 'success', 'failure', or 'starting'
* @param startedAt The time this action started executing.
* @param cause Cause of failure (only supply if status is 'failure')
* @param exception Exception (only supply if status is 'failure')
*/
export async function createStatusReportBase(
actionName: ActionName,
status: ActionStatus,
actionStartedAt: Date,
diskInfo: DiskUsage,
cause?: string,
exception?: string,
): Promise<StatusReportBase> {
const commitOid = getOptionalInput("sha") || process.env["GITHUB_SHA"] || "";
const ref = await getRef();
const jobRunUUID = process.env[EnvVar.JOB_RUN_UUID] || "";
const workflowRunID = getWorkflowRunID();
const workflowRunAttempt = getWorkflowRunAttempt();
const workflowName = process.env["GITHUB_WORKFLOW"] || "";
const jobName = process.env["GITHUB_JOB"] || "";
const analysis_key = await getAnalysisKey();
let workflowStartedAt = process.env[EnvVar.WORKFLOW_STARTED_AT];
if (workflowStartedAt === undefined) {
workflowStartedAt = actionStartedAt.toISOString();
core.exportVariable(EnvVar.WORKFLOW_STARTED_AT, workflowStartedAt);
}
const runnerOs = getRequiredEnvParam("RUNNER_OS");
const codeQlCliVersion = getCachedCodeQlVersion();
const actionRef = process.env["GITHUB_ACTION_REF"];
const testingEnvironment = process.env[EnvVar.TESTING_ENVIRONMENT] || "";
// re-export the testing environment variable so that it is available to subsequent steps,
// even if it was only set for this step
if (testingEnvironment !== "") {
core.exportVariable(EnvVar.TESTING_ENVIRONMENT, testingEnvironment);
}
const statusReport: StatusReportBase = {
action_name: actionName,
action_oid: "unknown", // TODO decide if it's possible to fill this in
action_ref: actionRef,
action_started_at: actionStartedAt.toISOString(),
action_version: getActionVersion(),
analysis_key,
commit_oid: commitOid,
job_name: jobName,
job_run_uuid: jobRunUUID,
ref,
runner_available_disk_space_bytes: diskInfo.numAvailableBytes,
runner_os: runnerOs,
runner_total_disk_space_bytes: diskInfo.numTotalBytes,
started_at: workflowStartedAt,
status,
testing_environment: testingEnvironment,
workflow_name: workflowName,
workflow_run_attempt: workflowRunAttempt,
workflow_run_id: workflowRunID,
};
// Add optional parameters
if (cause) {
statusReport.cause = cause;
}
if (exception) {
statusReport.exception = exception;
}
if (
status === "success" ||
status === "failure" ||
status === "aborted" ||
status === "user-error"
) {
statusReport.completed_at = new Date().toISOString();
}
const matrix = getRequiredInput("matrix");
if (matrix) {
statusReport.matrix_vars = matrix;
}
if ("RUNNER_ARCH" in process.env) {
// RUNNER_ARCH is available only in GHES 3.4 and later
// Values other than X86, X64, ARM, or ARM64 are discarded server side
statusReport.runner_arch = process.env["RUNNER_ARCH"];
}
if (runnerOs === "Windows" || runnerOs === "macOS") {
statusReport.runner_os_release = os.release();
}
if (codeQlCliVersion !== undefined) {
statusReport.codeql_version = codeQlCliVersion;
}
const imageVersion = process.env["ImageVersion"];
if (imageVersion) {
statusReport.runner_image_version = imageVersion;
}
return statusReport;
}
const GENERIC_403_MSG =
"The repo on which this action is running is not opted-in to CodeQL code scanning.";
const GENERIC_404_MSG =
"Not authorized to use the CodeQL code scanning feature on this repo.";
const OUT_OF_DATE_MSG =
"CodeQL Action is out-of-date. Please upgrade to the latest version of codeql-action.";
const INCOMPATIBLE_MSG =
"CodeQL Action version is incompatible with the code scanning endpoint. Please update to a compatible version of codeql-action.";
/**
* Send a status report to the code_scanning/analysis/status endpoint.
*
* Optionally checks the response from the API endpoint and sets the action
* as failed if the status report failed. This is only expected to be used
* when sending a 'starting' report.
*
* Returns whether sending the status report was successful of not.
*/
export async function sendStatusReport<S extends StatusReportBase>(
statusReport: S,
): Promise<boolean> {
const statusReportJSON = JSON.stringify(statusReport);
core.debug(`Sending status report: ${statusReportJSON}`);
// If in test mode we don't want to upload the results
if (isInTestMode()) {
core.debug("In test mode. Status reports are not uploaded.");
return true;
}
const nwo = getRequiredEnvParam("GITHUB_REPOSITORY");
const [owner, repo] = nwo.split("/");
const client = getApiClient();
try {
await client.request(
"PUT /repos/:owner/:repo/code-scanning/analysis/status",
{
owner,
repo,
data: statusReportJSON,
},
);
return true;
} catch (e) {
console.log(e);
if (isHTTPError(e)) {
switch (e.status) {
case 403:
if (
getWorkflowEventName() === "push" &&
process.env["GITHUB_ACTOR"] === "dependabot[bot]"
) {
core.setFailed(
'Workflows triggered by Dependabot on the "push" event run with read-only access. ' +
"Uploading Code Scanning results requires write access. " +
'To use Code Scanning with Dependabot, please ensure you are using the "pull_request" event for this workflow and avoid triggering on the "push" event for Dependabot branches. ' +
"See https://docs.github.com/en/code-security/secure-coding/configuring-code-scanning#scanning-on-push for more information on how to configure these events.",
);
} else {
core.setFailed(e.message || GENERIC_403_MSG);
}
return false;
case 404:
core.setFailed(GENERIC_404_MSG);
return false;
case 422:
// schema incompatibility when reporting status
// this means that this action version is no longer compatible with the API
// we still want to continue as it is likely the analysis endpoint will work
if (getRequiredEnvParam("GITHUB_SERVER_URL") !== GITHUB_DOTCOM_URL) {
core.debug(INCOMPATIBLE_MSG);
} else {
core.debug(OUT_OF_DATE_MSG);
}
return true;
}
}
// something else has gone wrong and the request/response will be logged by octokit
// it's possible this is a transient error and we should continue scanning
core.error(
"An unexpected error occurred when sending code scanning status report.",
);
return true;
}
}

View file

@ -2,11 +2,17 @@ import * as core from "@actions/core";
import * as actionsUtil from "./actions-util";
import { getActionVersion } from "./actions-util";
import { createStatusReportBase, sendStatusReport } from "./api-client";
import { getActionsLogger } from "./logging";
import { parseRepositoryNwo } from "./repository";
import {
createStatusReportBase,
sendStatusReport,
StatusReportBase,
getActionsStatus,
} from "./status-report";
import * as upload_lib from "./upload-lib";
import {
checkDiskUsage,
getRequiredEnvParam,
initializeEnvironment,
isInTestMode,
@ -14,7 +20,7 @@ import {
} from "./util";
interface UploadSarifStatusReport
extends actionsUtil.StatusReportBase,
extends StatusReportBase,
upload_lib.UploadStatusReport {}
async function sendSuccessStatusReport(
@ -25,6 +31,7 @@ async function sendSuccessStatusReport(
"upload-sarif",
"success",
startedAt,
await checkDiskUsage(),
);
const statusReport: UploadSarifStatusReport = {
...statusReportBase,
@ -35,10 +42,16 @@ async function sendSuccessStatusReport(
async function run() {
const startedAt = new Date();
const logger = getActionsLogger();
initializeEnvironment(getActionVersion());
if (
!(await sendStatusReport(
await createStatusReportBase("upload-sarif", "starting", startedAt),
await createStatusReportBase(
"upload-sarif",
"starting",
startedAt,
await checkDiskUsage(),
),
))
) {
return;
@ -49,7 +62,7 @@ async function run() {
actionsUtil.getRequiredInput("sarif_file"),
actionsUtil.getRequiredInput("checkout_path"),
actionsUtil.getOptionalInput("category"),
getActionsLogger(),
logger,
);
core.setOutput("sarif-id", uploadResult.sarifID);
@ -60,7 +73,7 @@ async function run() {
await upload_lib.waitForProcessing(
parseRepositoryNwo(getRequiredEnvParam("GITHUB_REPOSITORY")),
uploadResult.sarifID,
getActionsLogger(),
logger,
);
}
await sendSuccessStatusReport(startedAt, uploadResult.statusReport);
@ -72,8 +85,9 @@ async function run() {
await sendStatusReport(
await createStatusReportBase(
"upload-sarif",
actionsUtil.getActionsStatus(error),
getActionsStatus(error),
startedAt,
await checkDiskUsage(),
message,
error.stack,
),

View file

@ -4,6 +4,7 @@ import * as path from "path";
import { promisify } from "util";
import * as core from "@actions/core";
import checkDiskSpace from "check-disk-space";
import del from "del";
import getFolderSize from "get-folder-size";
import * as semver from "semver";
@ -844,3 +845,30 @@ export function prettyPrintPack(pack: Pack) {
pack.path ? `:${pack.path}` : ""
}`;
}
export interface DiskUsage {
numAvailableBytes: number;
numTotalBytes: number;
}
export async function checkDiskUsage(logger?: Logger): Promise<DiskUsage> {
const diskUsage = await checkDiskSpace(
getRequiredEnvParam("GITHUB_WORKSPACE"),
);
const gbInBytes = 1024 * 1024 * 1024;
if (logger && diskUsage.free < 2 * gbInBytes) {
const message =
"The Actions runner is running low on disk space " +
`(${(diskUsage.free / gbInBytes).toPrecision(4)} GB available).`;
if (process.env[EnvVar.HAS_WARNED_ABOUT_DISK_SPACE] !== "true") {
logger.warning(message);
} else {
logger.debug(message);
}
core.exportVariable(EnvVar.HAS_WARNED_ABOUT_DISK_SPACE, "true");
}
return {
numAvailableBytes: diskUsage.free,
numTotalBytes: diskUsage.size,
};
}