From 02778762a29cad34d22a27346a081f4b96f9c7f5 Mon Sep 17 00:00:00 2001 From: Angela P Wen Date: Thu, 2 Mar 2023 17:56:25 -0800 Subject: [PATCH] Upload per-database failed SARIFs, if applicable --- src/init-action-post-helper.test.ts | 20 ++--- src/init-action-post-helper.ts | 116 +++++++++++++++++++--------- src/init-action-post.ts | 18 +++-- src/util.ts | 9 +++ 4 files changed, 111 insertions(+), 52 deletions(-) diff --git a/src/init-action-post-helper.test.ts b/src/init-action-post-helper.test.ts index 685d54775..04c559351 100644 --- a/src/init-action-post-helper.test.ts +++ b/src/init-action-post-helper.test.ts @@ -132,7 +132,7 @@ test("doesn't upload failed SARIF for workflow with upload: false", async (t) => const result = await testFailedSarifUpload(t, actionsWorkflow, { expectUpload: false, }); - t.is(result.upload_failed_run_skipped_because, "SARIF upload is disabled"); + t.is(result[0].upload_failed_run_skipped_because, "SARIF upload is disabled"); }); test("uploading failed SARIF run succeeds when workflow uses an input with a matrix var", async (t) => { @@ -187,7 +187,7 @@ test("uploading failed SARIF run fails when workflow uses a complex upload input expectUpload: false, }); t.is( - result.upload_failed_run_error, + result[0].upload_failed_run_error, "Could not get upload input to github/codeql-action/analyze since it contained an " + "unrecognized dynamic value." ); @@ -204,11 +204,11 @@ test("uploading failed SARIF run fails when workflow does not reference github/c expectUpload: false, }); t.is( - result.upload_failed_run_error, + result[0].upload_failed_run_error, "Could not get upload input to github/codeql-action/analyze since the analyze job does not " + "call github/codeql-action/analyze." ); - t.truthy(result.upload_failed_run_stack_trace); + t.truthy(result[0].upload_failed_run_stack_trace); }); function createTestWorkflow( @@ -246,7 +246,7 @@ async function testFailedSarifUpload( expectUpload?: boolean; matrix?: { [key: string]: string }; } = {} -): Promise { +): Promise { const config = { codeQLCmd: "codeql", debugMode: true, @@ -282,10 +282,12 @@ async function testFailedSarifUpload( getRunnerLogger(true) ); if (expectUpload) { - t.deepEqual(result, { - raw_upload_size_bytes: 20, - zipped_upload_size_bytes: 10, - }); + t.deepEqual(result, [ + { + raw_upload_size_bytes: 20, + zipped_upload_size_bytes: 10, + }, + ]); } if (expectUpload) { t.true( diff --git a/src/init-action-post-helper.ts b/src/init-action-post-helper.ts index da3dabe64..017c4912f 100644 --- a/src/init-action-post-helper.ts +++ b/src/init-action-post-helper.ts @@ -6,9 +6,17 @@ import { Config, getConfig } from "./config-utils"; import { Feature, FeatureEnablement } from "./feature-flags"; import { Logger } from "./logging"; import { RepositoryNwo } from "./repository"; -import { CODEQL_ACTION_ANALYZE_DID_COMPLETE_SUCCESSFULLY } from "./shared-environment"; +import { + CODEQL_ACTION_ANALYZE_DID_COMPLETE_SUCCESSFULLY, + CODEQL_ACTION_IS_DATABASE_CLUSTER, +} from "./shared-environment"; import * as uploadLib from "./upload-lib"; -import { getRequiredEnvParam, isInTestMode, parseMatrixInput } from "./util"; +import { + getExistingDatabasePaths, + getRequiredEnvParam, + isInTestMode, + parseMatrixInput, +} from "./util"; import { getCategoryInputOrThrow, getCheckoutPathInputOrThrow, @@ -45,9 +53,9 @@ async function maybeUploadFailedSarif( repositoryNwo: RepositoryNwo, featureEnablement: FeatureEnablement, logger: Logger -): Promise { +): Promise { if (!config.codeQLCmd) { - return { upload_failed_run_skipped_because: "CodeQL command not found" }; + return [{ upload_failed_run_skipped_because: "CodeQL command not found" }]; } const codeql = await getCodeQL(config.codeQLCmd); if ( @@ -56,7 +64,7 @@ async function maybeUploadFailedSarif( codeql )) ) { - return { upload_failed_run_skipped_because: "Feature disabled" }; + return [{ upload_failed_run_skipped_because: "Feature disabled" }]; } const workflow = await getWorkflow(); const jobName = getRequiredEnvParam("GITHUB_JOB"); @@ -65,28 +73,62 @@ async function maybeUploadFailedSarif( getUploadInputOrThrow(workflow, jobName, matrix) !== "true" || isInTestMode() ) { - return { upload_failed_run_skipped_because: "SARIF upload is disabled" }; + return [{ upload_failed_run_skipped_because: "SARIF upload is disabled" }]; } const category = getCategoryInputOrThrow(workflow, jobName, matrix); const checkoutPath = getCheckoutPathInputOrThrow(workflow, jobName, matrix); - const sarifFile = "../codeql-failed-run.sarif"; - await codeql.diagnosticsExport(sarifFile, category); - - core.info(`Uploading failed SARIF file ${sarifFile}`); - const uploadResult = await uploadLib.uploadFromActions( - sarifFile, - checkoutPath, - category, - logger - ); - await uploadLib.waitForProcessing( - repositoryNwo, - uploadResult.sarifID, - logger, - { isUnsuccessfulExecution: true } - ); - return uploadResult?.statusReport ?? {}; + const existingDatabasePaths = getExistingDatabasePaths(config); + if (existingDatabasePaths.length === 0) { + const sarifFile = "../codeql-failed-run.sarif"; + await codeql.diagnosticsExport(sarifFile, category); + core.info(`Uploading failed SARIF file ${sarifFile}`); + const uploadResult = await uploadLib.uploadFromActions( + sarifFile, + checkoutPath, + category, + logger + ); + await uploadLib.waitForProcessing( + repositoryNwo, + uploadResult.sarifID, + logger, + { isUnsuccessfulExecution: true } + ); + return [uploadResult?.statusReport ?? {}]; + } else { + const uploadStatusReports: uploadLib.UploadStatusReport[] = []; + const maybeIsDatabaseCluster = + process.env[CODEQL_ACTION_IS_DATABASE_CLUSTER]; + // If we have already created database(s), then we call database diagnostic export. + existingDatabasePaths.map(async (databasePath) => { + const databaseSarifFile = `../codeql-failed-run-${existingDatabasePaths}.sarif`; + await codeql.databaseExportDiagnostics( + databasePath, + maybeIsDatabaseCluster === undefined || + maybeIsDatabaseCluster.length === 0 + ? false + : true, + databaseSarifFile, + category + ); + core.info(`Uploading failed SARIF file ${databaseSarifFile}`); + const uploadResult = await uploadLib.uploadFromActions( + databaseSarifFile, + checkoutPath, + category, + logger + ); + await uploadLib.waitForProcessing( + repositoryNwo, + uploadResult.sarifID, + logger, + { isUnsuccessfulExecution: true } + ); + uploadStatusReports.push(uploadResult.statusReport); + }); + return uploadStatusReports ?? []; + } } export async function tryUploadSarifIfRunFailed( @@ -94,7 +136,7 @@ export async function tryUploadSarifIfRunFailed( repositoryNwo: RepositoryNwo, featureEnablement: FeatureEnablement, logger: Logger -): Promise { +): Promise { if (process.env[CODEQL_ACTION_ANALYZE_DID_COMPLETE_SUCCESSFULLY] !== "true") { try { return await maybeUploadFailedSarif( @@ -107,13 +149,15 @@ export async function tryUploadSarifIfRunFailed( logger.debug( `Failed to upload a SARIF file for this failed CodeQL code scanning run. ${e}` ); - return createFailedUploadFailedSarifResult(e); + return [createFailedUploadFailedSarifResult(e)]; } } else { - return { - upload_failed_run_skipped_because: - "Analyze Action completed successfully", - }; + return [ + { + upload_failed_run_skipped_because: + "Analyze Action completed successfully", + }, + ]; } } @@ -133,27 +177,29 @@ export async function run( return; } - const uploadFailedSarifResult = await tryUploadSarifIfRunFailed( + const uploadFailedSarifResults = await tryUploadSarifIfRunFailed( config, repositoryNwo, featureEnablement, logger ); - if (uploadFailedSarifResult.upload_failed_run_skipped_because) { + + // If the upload was skipped, it is the only item in the results array. + if (uploadFailedSarifResults[0].upload_failed_run_skipped_because) { logger.debug( "Won't upload a failed SARIF file for this CodeQL code scanning run because: " + - `${uploadFailedSarifResult.upload_failed_run_skipped_because}.` + `${uploadFailedSarifResults[0].upload_failed_run_skipped_because}.` ); } // Throw an error if in integration tests, we expected to upload a SARIF file for a failed run // but we didn't upload anything. if ( process.env["CODEQL_ACTION_EXPECT_UPLOAD_FAILED_SARIF"] === "true" && - !uploadFailedSarifResult.raw_upload_size_bytes + !uploadFailedSarifResults[0].raw_upload_size_bytes ) { throw new Error( "Expected to upload a failed SARIF file for this CodeQL code scanning run, " + - `but the result was instead ${uploadFailedSarifResult}.` + `but the result was instead ${uploadFailedSarifResults}.` ); } @@ -168,5 +214,5 @@ export async function run( await printDebugLogs(config); } - return uploadFailedSarifResult; + return uploadFailedSarifResults; } diff --git a/src/init-action-post.ts b/src/init-action-post.ts index 00481a42d..f8b933e70 100644 --- a/src/init-action-post.ts +++ b/src/init-action-post.ts @@ -28,8 +28,8 @@ interface InitPostStatusReport async function runWrapper() { const startedAt = new Date(); - let uploadFailedSarifResult: - | initActionPostHelper.UploadFailedSarifResult + let uploadFailedSarifResults: + | initActionPostHelper.UploadFailedSarifResult[] | undefined; try { const logger = getActionsLogger(); @@ -46,7 +46,7 @@ async function runWrapper() { logger ); - uploadFailedSarifResult = await initActionPostHelper.run( + uploadFailedSarifResults = await initActionPostHelper.run( debugArtifacts.uploadDatabaseBundleDebugArtifact, debugArtifacts.uploadLogsDebugArtifact, printDebugLogs, @@ -74,11 +74,13 @@ async function runWrapper() { "success", startedAt ); - const statusReport: InitPostStatusReport = { - ...statusReportBase, - ...uploadFailedSarifResult, - }; - await sendStatusReport(statusReport); + for (const result of uploadFailedSarifResults ?? []) { + const statusReport: InitPostStatusReport = { + ...statusReportBase, + ...result, + }; + await sendStatusReport(statusReport); + } } void runWrapper(); diff --git a/src/util.ts b/src/util.ts index 56b1d6e17..9724e5205 100644 --- a/src/util.ts +++ b/src/util.ts @@ -252,6 +252,15 @@ export function getCodeQLDatabasePath(config: Config, language: Language) { return path.resolve(config.dbLocation, language); } +/** + * Gets the paths of all CodeQL databases that currently exist. + */ +export function getExistingDatabasePaths(config: Config): string[] { + return config.languages + .map((language) => getCodeQLDatabasePath(config, language)) + .filter((databasePath) => fs.existsSync(databasePath)); +} + /** * Parses user input of a github.com or GHES URL to a canonical form. * Removes any API prefix or suffix if one is present.