Upload failed SARIF files to Code Scanning

This commit is contained in:
Henry Mercer 2022-11-23 13:27:16 +00:00
parent 3afc2b194c
commit 5296a763b1
28 changed files with 319 additions and 63 deletions

View file

@ -17,6 +17,7 @@ import {
GITHUB_DOTCOM_URL,
isHTTPError,
isInTestMode,
parseMatrixInput,
UserError,
} from "./util";
import { getWorkflowPath } from "./workflow";
@ -192,10 +193,10 @@ export function computeAutomationID(
): string {
let automationID = `${analysis_key}/`;
// the id has to be deterministic so we sort the fields
if (environment !== undefined && environment !== "null") {
const environmentObject = JSON.parse(environment);
for (const entry of Object.entries(environmentObject).sort()) {
const matrix = parseMatrixInput(environment);
if (matrix !== undefined) {
// the id has to be deterministic so we sort the fields
for (const entry of Object.entries(matrix).sort()) {
if (typeof entry[1] === "string") {
automationID += `${entry[0]}:${entry[1]}/`;
} else {

View file

@ -262,7 +262,12 @@ async function run() {
core.setOutput("db-locations", dbLocations);
if (runStats && actionsUtil.getRequiredInput("upload") === "true") {
uploadResult = await upload_lib.uploadFromActions(outputDir, logger);
uploadResult = await upload_lib.uploadFromActions(
outputDir,
actionsUtil.getRequiredInput("checkout_path"),
actionsUtil.getOptionalInput("category"),
logger
);
core.setOutput("sarif-id", uploadResult.sarifID);
} else {
logger.info("Not uploading results");

View file

@ -3,7 +3,9 @@ import * as sinon from "sinon";
import * as configUtils from "./config-utils";
import * as initActionPostHelper from "./init-action-post-helper";
import { setupTests } from "./testing-utils";
import { getRunnerLogger } from "./logging";
import { parseRepositoryNwo } from "./repository";
import { createFeatures, setupTests } from "./testing-utils";
import * as util from "./util";
setupTests(test);
@ -29,7 +31,10 @@ test("post: init action with debug mode off", async (t) => {
await initActionPostHelper.run(
uploadDatabaseBundleSpy,
uploadLogsSpy,
printDebugLogsSpy
printDebugLogsSpy,
parseRepositoryNwo("github/codeql-action"),
createFeatures([]),
getRunnerLogger(true)
);
t.assert(uploadDatabaseBundleSpy.notCalled);
@ -59,7 +64,10 @@ test("post: init action with debug mode on", async (t) => {
await initActionPostHelper.run(
uploadDatabaseBundleSpy,
uploadLogsSpy,
printDebugLogsSpy
printDebugLogsSpy,
parseRepositoryNwo("github/codeql-action"),
createFeatures([]),
getRunnerLogger(true)
);
t.assert(uploadDatabaseBundleSpy.called);

View file

@ -1,25 +1,103 @@
import * as artifact from "@actions/artifact";
import * as core from "@actions/core";
import * as actionsUtil from "./actions-util";
import { getConfig } from "./config-utils";
import { getActionsLogger } from "./logging";
import { getCodeQL } from "./codeql";
import { Config, getConfig } from "./config-utils";
import { Feature, FeatureEnablement } from "./feature-flags";
import { Logger } from "./logging";
import { RepositoryNwo } from "./repository";
import * as uploadLib from "./upload-lib";
import { getRequiredEnvParam, isInTestMode, parseMatrixInput } from "./util";
import {
getCategoryInputOrThrow,
getCheckoutPathInputOrThrow,
getUploadInputOrThrow,
getWaitForProcessingInputOrThrow,
getWorkflow,
} from "./workflow";
async function uploadFailedSarif(
config: Config,
repositoryNwo: RepositoryNwo,
featureEnablement: FeatureEnablement,
logger: Logger
) {
if (!config.codeQLCmd) {
logger.warning(
"CodeQL command not found. Unable to upload failed SARIF file."
);
return;
}
const codeql = await getCodeQL(config.codeQLCmd);
if (
!(await featureEnablement.getValue(
Feature.UploadFailedSarifEnabled,
codeql
))
) {
logger.debug("Uploading failed SARIF is disabled.");
return;
}
const workflow = await getWorkflow();
const jobName = getRequiredEnvParam("GITHUB_JOB");
const matrix = parseMatrixInput(actionsUtil.getRequiredInput("matrix"));
if (
getUploadInputOrThrow(workflow, jobName, matrix) !== "true" ||
isInTestMode()
) {
logger.debug(
"Won't upload a failed SARIF file since SARIF upload is disabled."
);
return;
}
const category = getCategoryInputOrThrow(workflow, jobName, matrix);
const checkoutPath = getCheckoutPathInputOrThrow(workflow, jobName, matrix);
const waitForProcessing =
getWaitForProcessingInputOrThrow(workflow, jobName, matrix) === "true";
const sarifFile = "../failed-run.sarif";
await codeql.diagnosticsExport(sarifFile, category);
// Upload for debugging
await artifact.create().uploadArtifact("failed-run.sarif", [sarifFile], "..");
core.info(`Uploading failed SARIF file ${sarifFile}`);
const uploadResult = await uploadLib.uploadFromActions(
sarifFile,
checkoutPath,
category,
logger
);
if (uploadResult !== undefined && waitForProcessing) {
await uploadLib.waitForProcessing(
repositoryNwo,
uploadResult.sarifID,
logger
);
}
}
export async function run(
uploadDatabaseBundleDebugArtifact: Function,
uploadLogsDebugArtifact: Function,
printDebugLogs: Function
printDebugLogs: Function,
repositoryNwo: RepositoryNwo,
featureEnablement: FeatureEnablement,
logger: Logger
) {
const logger = getActionsLogger();
const config = await getConfig(actionsUtil.getTemporaryDirectory(), logger);
if (config === undefined) {
logger.warning(
"Debugging artifacts are unavailable since the 'init' Action failed before it could produce any."
);
return;
}
await uploadFailedSarif(config, repositoryNwo, featureEnablement, logger);
// Upload appropriate Actions artifacts for debugging
if (config?.debugMode) {
if (config.debugMode) {
core.info(
"Debug mode is on. Uploading available database bundles and logs as Actions debugging artifacts..."
);

View file

@ -7,17 +7,38 @@
import * as core from "@actions/core";
import * as actionsUtil 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 { checkGitHubVersionInRange, getRequiredEnvParam } from "./util";
async function runWrapper() {
try {
const logger = getActionsLogger();
const gitHubVersion = await getGitHubVersion();
checkGitHubVersionInRange(gitHubVersion, logger);
const repositoryNwo = parseRepositoryNwo(
getRequiredEnvParam("GITHUB_REPOSITORY")
);
const features = new Features(
gitHubVersion,
repositoryNwo,
actionsUtil.getTemporaryDirectory(),
logger
);
await initActionPostHelper.run(
debugArtifacts.uploadDatabaseBundleDebugArtifact,
debugArtifacts.uploadLogsDebugArtifact,
actionsUtil.printDebugLogs
actionsUtil.printDebugLogs,
repositoryNwo,
features,
logger
);
console.log(actionsUtil.getRequiredInput("matrix"));
} catch (error) {
core.setFailed(`init post-action step failed: ${error}`);
console.log(error);

View file

@ -158,23 +158,22 @@ export function findSarifFilesInDir(sarifPath: string): string[] {
// Uploads a single sarif file or a directory of sarif files
// depending on what the path happens to refer to.
// Returns true iff the upload occurred and succeeded
export async function uploadFromActions(
sarifPath: string,
checkoutPath: string,
category: string | undefined,
logger: Logger
): Promise<UploadResult> {
return await uploadFiles(
getSarifFilePaths(sarifPath),
parseRepositoryNwo(util.getRequiredEnvParam("GITHUB_REPOSITORY")),
await actionsUtil.getCommitOid(
actionsUtil.getRequiredInput("checkout_path")
),
await actionsUtil.getCommitOid(checkoutPath),
await actionsUtil.getRef(),
await actionsUtil.getAnalysisKey(),
actionsUtil.getOptionalInput("category"),
category,
util.getRequiredEnvParam("GITHUB_WORKFLOW"),
workflow.getWorkflowRunID(),
actionsUtil.getRequiredInput("checkout_path"),
checkoutPath,
actionsUtil.getRequiredInput("matrix"),
logger
);

View file

@ -53,6 +53,8 @@ async function run() {
try {
const uploadResult = await upload_lib.uploadFromActions(
actionsUtil.getRequiredInput("sarif_file"),
actionsUtil.getRequiredInput("checkout_path"),
actionsUtil.getOptionalInput("category"),
getActionsLogger()
);
core.setOutput("sarif-id", uploadResult.sarifID);

View file

@ -892,3 +892,12 @@ export async function shouldBypassToolcache(
}
return bypass;
}
export function parseMatrixInput(
matrixInput: string | undefined
): { [key: string]: string } | undefined {
if (matrixInput === undefined || matrixInput === "null") {
return undefined;
}
return JSON.parse(matrixInput);
}

View file

@ -319,7 +319,7 @@ function getInputOrThrow(
jobName: string,
actionName: string,
inputName: string,
matrixVars: { [key: string]: string }
matrixVars: { [key: string]: string } | undefined
) {
if (!workflow.jobs) {
throw new Error(
@ -347,11 +347,15 @@ function getInputOrThrow(
);
}
// Make a basic attempt to substitute matrix variables
// First normalize by removing whitespace
let input = inputs[0].replace(/\${{\s+/, "${{").replace(/\s+}}/, "}}");
for (const [key, value] of Object.entries(matrixVars)) {
input = input.replace(`\${{matrix.${key}}}`, value);
let input = inputs[0];
if (matrixVars !== undefined) {
// Make a basic attempt to substitute matrix variables
// First normalize by removing whitespace
input = input.replace(/\${{\s+/, "${{").replace(/\s+}}/, "}}");
for (const [key, value] of Object.entries(matrixVars)) {
input = input.replace(`\${{matrix.${key}}}`, value);
}
}
if (input.includes("${{")) {
@ -372,7 +376,7 @@ function getInputOrThrow(
export function getCategoryInputOrThrow(
workflow: Workflow,
jobName: string,
matrixVars: { [key: string]: string }
matrixVars: { [key: string]: string } | undefined
): string | undefined {
return getInputOrThrow(
workflow,
@ -393,7 +397,7 @@ export function getCategoryInputOrThrow(
export function getUploadInputOrThrow(
workflow: Workflow,
jobName: string,
matrixVars: { [key: string]: string }
matrixVars: { [key: string]: string } | undefined
): string {
return (
getInputOrThrow(
@ -405,3 +409,49 @@ export function getUploadInputOrThrow(
) || "true" // if unspecified, upload defaults to true
);
}
/**
* Makes a best effort attempt to retrieve the wait-for-processing input for the
* particular job, given a set of matrix variables.
*
* @returns the wait-for-processing input
* @throws an error if the wait-for-processing input could not be determined
*/
export function getWaitForProcessingInputOrThrow(
workflow: Workflow,
jobName: string,
matrixVars: { [key: string]: string } | undefined
): string {
return (
getInputOrThrow(
workflow,
jobName,
"github/codeql-action/analyze",
"wait-for-processing",
matrixVars
) || "true" // if unspecified, wait-for-processing defaults to true
);
}
/**
* Makes a best effort attempt to retrieve the checkout_path input for the
* particular job, given a set of matrix variables.
*
* @returns the checkout_path input
* @throws an error if the checkout_path input could not be determined
*/
export function getCheckoutPathInputOrThrow(
workflow: Workflow,
jobName: string,
matrixVars: { [key: string]: string } | undefined
): string {
return (
getInputOrThrow(
workflow,
jobName,
"github/codeql-action/analyze",
"checkout_path",
matrixVars
) || getRequiredEnvParam("GITHUB_WORKSPACE") // if unspecified, checkout_path defaults to ${{ github.workspace }}
);
}