Merge branch 'main' into update-bundle/codeql-bundle-v2.14.2
This commit is contained in:
commit
1e14fd9e7a
68 changed files with 1711 additions and 759 deletions
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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!;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
60
src/status-report.test.ts
Normal 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
341
src/status-report.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
),
|
||||
|
|
|
|||
28
src/util.ts
28
src/util.ts
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue