Pull out a dedicated status report file

This commit is contained in:
Henry Mercer 2023-08-07 13:42:43 +01:00
parent c55207f0a2
commit c6d284324b
41 changed files with 768 additions and 669 deletions

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.
*/