Upload per-database diagnostic SARIFs on green and red runs (#1556)

Co-authored-by: Henry Mercer <henry.mercer@me.com>
This commit is contained in:
Angela P Wen 2023-03-20 14:09:04 -07:00 committed by GitHub
parent b4fba292aa
commit 3cbd063679
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 450 additions and 54 deletions

View file

@ -23,7 +23,10 @@ import { Features } from "./feature-flags";
import { Language } from "./languages";
import { getActionsLogger, Logger } from "./logging";
import { parseRepositoryNwo } from "./repository";
import { CODEQL_ACTION_ANALYZE_DID_COMPLETE_SUCCESSFULLY } from "./shared-environment";
import {
CODEQL_ACTION_ANALYZE_DID_COMPLETE_SUCCESSFULLY,
CODEQL_ACTION_DID_AUTOBUILD_GOLANG,
} from "./shared-environment";
import { getTotalCacheSize, uploadTrapCaches } from "./trap-caching";
import * as upload_lib from "./upload-lib";
import { UploadResult } from "./upload-lib";
@ -130,7 +133,7 @@ function doesGoExtractionOutputExist(config: Config): boolean {
* an autobuild step or manual build steps.
*
* - We detect whether an autobuild step is present by checking the
* `util.DID_AUTOBUILD_GO_ENV_VAR_NAME` environment variable, which is set
* `CODEQL_ACTION_DID_AUTOBUILD_GOLANG` environment variable, which is set
* when the autobuilder is invoked.
* - We detect whether the Go database has already been finalized in case it
* has been manually set in a prior Action step.
@ -141,7 +144,7 @@ async function runAutobuildIfLegacyGoWorkflow(config: Config, logger: Logger) {
if (!config.languages.includes(Language.go)) {
return;
}
if (process.env[util.DID_AUTOBUILD_GO_ENV_VAR_NAME] === "true") {
if (process.env[CODEQL_ACTION_DID_AUTOBUILD_GOLANG] === "true") {
logger.debug("Won't run Go autobuild since it has already been run.");
return;
}

View file

@ -14,11 +14,8 @@ import { determineAutobuildLanguages, runAutobuild } from "./autobuild";
import * as configUtils from "./config-utils";
import { Language } from "./languages";
import { getActionsLogger } from "./logging";
import {
DID_AUTOBUILD_GO_ENV_VAR_NAME,
checkGitHubVersionInRange,
initializeEnvironment,
} from "./util";
import { CODEQL_ACTION_DID_AUTOBUILD_GOLANG } from "./shared-environment";
import { checkGitHubVersionInRange, initializeEnvironment } from "./util";
interface AutobuildStatusReport extends StatusReportBase {
/** Comma-separated set of languages being auto-built. */
@ -88,7 +85,7 @@ async function run() {
currentLanguage = language;
await runAutobuild(language, config, logger);
if (language === Language.go) {
core.exportVariable(DID_AUTOBUILD_GO_ENV_VAR_NAME, "true");
core.exportVariable(CODEQL_ACTION_DID_AUTOBUILD_GOLANG, "true");
}
}
}

View file

@ -185,6 +185,17 @@ export interface CodeQL {
* Run 'codeql database print-baseline'.
*/
databasePrintBaseline(databasePath: string): Promise<string>;
/**
* Run 'codeql database export-diagnostics'
*
* Note that the "--sarif-include-diagnostics" option is always used, as the command should
* only be run if the ExportDiagnosticsEnabled feature flag is on.
*/
databaseExportDiagnostics(
databasePath: string,
sarifFile: string,
automationDetailsId: string | undefined
): Promise<void>;
/**
* Run 'codeql diagnostics export'.
*/
@ -429,6 +440,10 @@ export function setCodeQL(partialCodeql: Partial<CodeQL>): CodeQL {
partialCodeql,
"databasePrintBaseline"
),
databaseExportDiagnostics: resolveFunction(
partialCodeql,
"databaseExportDiagnostics"
),
diagnosticsExport: resolveFunction(partialCodeql, "diagnosticsExport"),
};
return cachedCodeQL;
@ -880,6 +895,9 @@ export async function getCodeQLForCmd(
) {
codeqlArgs.push("--sarif-add-baseline-file-info");
}
if (await features.getValue(Feature.ExportDiagnosticsEnabled, this)) {
codeqlArgs.push("--sarif-include-diagnostics");
}
codeqlArgs.push(databasePath);
if (querySuitePaths) {
codeqlArgs.push(...querySuitePaths);
@ -982,6 +1000,27 @@ export async function getCodeQLForCmd(
];
await new toolrunner.ToolRunner(cmd, args).exec();
},
async databaseExportDiagnostics(
databasePath: string,
sarifFile: string,
automationDetailsId: string | undefined
): Promise<void> {
const args = [
"database",
"export-diagnostics",
`${databasePath}`,
"--db-cluster", // Database is always a cluster for CodeQL versions that support diagnostics.
"--format=sarif-latest",
`--output=${sarifFile}`,
"--sarif-include-diagnostics", // ExportDiagnosticsEnabled is always true if this command is run.
"-vvv",
...getExtraOptionsFromEnv(["diagnostics", "export"]),
];
if (automationDetailsId !== undefined) {
args.push("--sarif-category", automationDetailsId);
}
await new toolrunner.ToolRunner(cmd, args).exec();
},
async diagnosticsExport(
sarifFile: string,
automationDetailsId: string | undefined,

View file

@ -37,6 +37,7 @@ export enum Feature {
CliConfigFileEnabled = "cli_config_file_enabled",
DisableKotlinAnalysisEnabled = "disable_kotlin_analysis_enabled",
ExportCodeScanningConfigEnabled = "export_code_scanning_config_enabled",
ExportDiagnosticsEnabled = "export_diagnostics_enabled",
MlPoweredQueriesEnabled = "ml_powered_queries_enabled",
UploadFailedSarifEnabled = "upload_failed_sarif_enabled",
}
@ -60,6 +61,12 @@ export const featureConfig: Record<
minimumVersion: "2.12.3",
defaultValue: false,
},
[Feature.ExportDiagnosticsEnabled]: {
envVar: "CODEQL_ACTION_EXPORT_DIAGNOSTICS",
minimumVersion: "2.12.4",
defaultValue: false,
},
[Feature.MlPoweredQueriesEnabled]: {
envVar: "CODEQL_ML_POWERED_QUERIES",
minimumVersion: "2.7.5",

View file

@ -83,7 +83,7 @@ test("post: init action with debug mode on", async (t) => {
});
});
test("uploads failed SARIF run for typical workflow", async (t) => {
test("uploads failed SARIF run with `diagnostics export` if feature flag is off", async (t) => {
const actionsWorkflow = createTestWorkflow([
{
name: "Checkout repository",
@ -107,6 +107,60 @@ test("uploads failed SARIF run for typical workflow", async (t) => {
await testFailedSarifUpload(t, actionsWorkflow, { category: "my-category" });
});
test("uploads failed SARIF run with `diagnostics export` if the database doesn't exist", async (t) => {
const actionsWorkflow = createTestWorkflow([
{
name: "Checkout repository",
uses: "actions/checkout@v3",
},
{
name: "Initialize CodeQL",
uses: "github/codeql-action/init@v2",
with: {
languages: "javascript",
},
},
{
name: "Perform CodeQL Analysis",
uses: "github/codeql-action/analyze@v2",
with: {
category: "my-category",
},
},
]);
await testFailedSarifUpload(t, actionsWorkflow, {
category: "my-category",
databaseExists: false,
});
});
test("uploads failed SARIF run with database export-diagnostics if the database exists and feature flag is on", async (t) => {
const actionsWorkflow = createTestWorkflow([
{
name: "Checkout repository",
uses: "actions/checkout@v3",
},
{
name: "Initialize CodeQL",
uses: "github/codeql-action/init@v2",
with: {
languages: "javascript",
},
},
{
name: "Perform CodeQL Analysis",
uses: "github/codeql-action/analyze@v2",
with: {
category: "my-category",
},
},
]);
await testFailedSarifUpload(t, actionsWorkflow, {
category: "my-category",
exportDiagnosticsEnabled: true,
});
});
test("doesn't upload failed SARIF for workflow with upload: false", async (t) => {
const actionsWorkflow = createTestWorkflow([
{
@ -239,10 +293,14 @@ async function testFailedSarifUpload(
actionsWorkflow: workflow.Workflow,
{
category,
databaseExists = true,
exportDiagnosticsEnabled = false,
expectUpload = true,
matrix = {},
}: {
category?: string;
databaseExists?: boolean;
exportDiagnosticsEnabled?: boolean;
expectUpload?: boolean;
matrix?: { [key: string]: string };
} = {}
@ -253,6 +311,9 @@ async function testFailedSarifUpload(
languages: [],
packs: [],
} as unknown as configUtils.Config;
if (databaseExists) {
config.dbLocation = "path/to/database";
}
process.env["GITHUB_JOB"] = "analyze";
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
process.env["GITHUB_WORKSPACE"] =
@ -264,6 +325,10 @@ async function testFailedSarifUpload(
const codeqlObject = await codeql.getCodeQLForTesting();
sinon.stub(codeql, "getCodeQL").resolves(codeqlObject);
const databaseExportDiagnosticsStub = sinon.stub(
codeqlObject,
"databaseExportDiagnostics"
);
const diagnosticsExportStub = sinon.stub(codeqlObject, "diagnosticsExport");
sinon.stub(workflow, "getWorkflow").resolves(actionsWorkflow);
@ -275,10 +340,15 @@ async function testFailedSarifUpload(
} as uploadLib.UploadResult);
const waitForProcessing = sinon.stub(uploadLib, "waitForProcessing");
const features = [Feature.UploadFailedSarifEnabled];
if (exportDiagnosticsEnabled) {
features.push(Feature.ExportDiagnosticsEnabled);
}
const result = await initActionPostHelper.tryUploadSarifIfRunFailed(
config,
parseRepositoryNwo("github/codeql-action"),
createFeatures([Feature.UploadFailedSarifEnabled]),
createFeatures(features),
getRunnerLogger(true)
);
if (expectUpload) {
@ -288,15 +358,26 @@ async function testFailedSarifUpload(
});
}
if (expectUpload) {
t.true(
diagnosticsExportStub.calledOnceWith(
sinon.match.string,
category,
sinon.match.any,
sinon.match.any
),
`Actual args were: ${diagnosticsExportStub.args}`
);
if (databaseExists && exportDiagnosticsEnabled) {
t.true(
databaseExportDiagnosticsStub.calledOnceWith(
config.dbLocation,
sinon.match.string,
category
),
`Actual args were: ${databaseExportDiagnosticsStub.args}`
);
} else {
t.true(
diagnosticsExportStub.calledOnceWith(
sinon.match.string,
category,
config,
sinon.match.any
),
`Actual args were: ${diagnosticsExportStub.args}`
);
}
t.true(
uploadFromActions.calledOnceWith(
sinon.match.string,

View file

@ -64,9 +64,20 @@ async function maybeUploadFailedSarif(
}
const category = getCategoryInputOrThrow(workflow, jobName, matrix);
const checkoutPath = getCheckoutPathInputOrThrow(workflow, jobName, matrix);
const databasePath = config.dbLocation;
const sarifFile = "../codeql-failed-run.sarif";
await codeql.diagnosticsExport(sarifFile, category, config, features);
// If there is no database or the feature flag is off, we run 'export diagnostics'
if (
databasePath === undefined ||
!(await features.getValue(Feature.ExportDiagnosticsEnabled, codeql))
) {
await codeql.diagnosticsExport(sarifFile, category, config, features);
} else {
// We call 'database export-diagnostics' to find any per-database diagnostics.
await codeql.databaseExportDiagnostics(databasePath, sarifFile, category);
}
core.info(`Uploading failed SARIF file ${sarifFile}`);
const uploadResult = await uploadLib.uploadFromActions(
@ -134,6 +145,7 @@ export async function run(
features,
logger
);
if (uploadFailedSarifResult.upload_failed_run_skipped_because) {
logger.debug(
"Won't upload a failed SARIF file for this CodeQL code scanning run because: " +

View file

@ -1,3 +1,10 @@
/**
* Environment variable that is set to true when the CodeQL Action has invoked
* the Go autobuilder.
*/
export const CODEQL_ACTION_DID_AUTOBUILD_GOLANG =
"CODEQL_ACTION_DID_AUTOBUILD_GOLANG";
/**
* This environment variable is set to true when the `analyze` Action
* completes successfully.

View file

@ -42,13 +42,6 @@ export const DEFAULT_DEBUG_ARTIFACT_NAME = "debug-artifacts";
*/
export const DEFAULT_DEBUG_DATABASE_NAME = "db";
/**
* Environment variable that is set to "true" when the CodeQL Action has invoked
* the Go autobuilder.
*/
export const DID_AUTOBUILD_GO_ENV_VAR_NAME =
"CODEQL_ACTION_DID_AUTOBUILD_GOLANG";
export interface SarifFile {
version?: string | null;
runs: SarifRun[];