Compare commits

...

16 Commits

Author SHA1 Message Date
nickfyson
bc5263b2b5 wip update 2024-03-28 12:47:39 +00:00
Koen Vlaswinkel
84ba7fb56d Merge pull request #2199 from github/koesie10/cli-sarif-merge
Use `github merge-results` command for merging SARIF files
2024-03-27 14:39:20 +01:00
Koen Vlaswinkel
016720d81f Refactor areAllRunsProducedByCodeQL to use Array.every 2024-03-26 11:09:44 +01:00
Koen Vlaswinkel
f835435c19 Rename run to uploadArtifacts 2024-03-26 11:05:21 +01:00
Koen Vlaswinkel
58194bf81a Do not require debug mode for debugging uploads 2024-03-26 11:05:04 +01:00
Koen Vlaswinkel
82b82e58c0 Only upload combined SARIF files when env var is set 2024-03-25 10:15:42 +01:00
Koen Vlaswinkel
3c4b8bc120 Change log levels 2024-03-25 10:15:37 +01:00
Koen Vlaswinkel
a0a63a2dd4 Do not try merging a single SARIF file 2024-03-25 10:15:16 +01:00
Koen Vlaswinkel
3bd271cec3 Introduce areAllRunsProducedByCodeQL function 2024-03-25 10:14:59 +01:00
Koen Vlaswinkel
70aa50b057 Use runTool for mergeResults 2024-03-25 10:13:21 +01:00
Koen Vlaswinkel
6ac57535de Use options object instead of boolean for mergeResults 2024-03-25 10:13:15 +01:00
Koen Vlaswinkel
e20c273295 Use supportsFeature check for merging SARIF files 2024-03-22 14:15:42 +01:00
Koen Vlaswinkel
2bbafcdd7f Upload debug artifacts for upload-sarif 2024-03-22 14:15:17 +01:00
Koen Vlaswinkel
7e30c622b0 Use subdirectory for combined SARIF file 2024-03-22 14:15:16 +01:00
Koen Vlaswinkel
ccc609bf1a Use github merge-results command when feature flag is enabled 2024-03-22 14:15:00 +01:00
Koen Vlaswinkel
a12b868bbc Create feature flag for merging SARIF files using the CLI 2024-03-22 14:14:44 +01:00
25 changed files with 466 additions and 11 deletions

View File

@@ -31,10 +31,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(require("@actions/core"));
const analyzeActionPostHelper = __importStar(require("./analyze-action-post-helper"));
const debugArtifacts = __importStar(require("./debug-artifacts"));
const uploadSarifActionPostHelper = __importStar(require("./upload-sarif-action-post-helper"));
const util_1 = require("./util");
async function runWrapper() {
try {
await analyzeActionPostHelper.run(debugArtifacts.uploadSarifDebugArtifact);
// Also run the upload-sarif post action since we're potentially running
// the same steps in the analyze action.
await uploadSarifActionPostHelper.uploadArtifacts(debugArtifacts.uploadDebugArtifacts);
}
catch (error) {
core.setFailed(`analyze post-action step failed: ${(0, util_1.wrapError)(error).message}`);

View File

@@ -1 +1 @@
{"version":3,"file":"analyze-action-post.js","sourceRoot":"","sources":["../src/analyze-action-post.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;GAIG;AACH,oDAAsC;AAEtC,sFAAwE;AACxE,kEAAoD;AACpD,iCAAmC;AAEnC,KAAK,UAAU,UAAU;IACvB,IAAI,CAAC;QACH,MAAM,uBAAuB,CAAC,GAAG,CAAC,cAAc,CAAC,wBAAwB,CAAC,CAAC;IAC7E,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,SAAS,CACZ,oCAAoC,IAAA,gBAAS,EAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAC/D,CAAC;IACJ,CAAC;AACH,CAAC;AAED,KAAK,UAAU,EAAE,CAAC"}
{"version":3,"file":"analyze-action-post.js","sourceRoot":"","sources":["../src/analyze-action-post.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;GAIG;AACH,oDAAsC;AAEtC,sFAAwE;AACxE,kEAAoD;AACpD,+FAAiF;AACjF,iCAAmC;AAEnC,KAAK,UAAU,UAAU;IACvB,IAAI,CAAC;QACH,MAAM,uBAAuB,CAAC,GAAG,CAAC,cAAc,CAAC,wBAAwB,CAAC,CAAC;QAE3E,wEAAwE;QACxE,wCAAwC;QACxC,MAAM,2BAA2B,CAAC,eAAe,CAC/C,cAAc,CAAC,oBAAoB,CACpC,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,SAAS,CACZ,oCAAoC,IAAA,gBAAS,EAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAC/D,CAAC;IACJ,CAAC;AACH,CAAC;AAED,KAAK,UAAU,EAAE,CAAC"}

17
lib/codeql.js generated
View File

@@ -207,6 +207,7 @@ function setCodeQL(partialCodeql) {
databaseExportDiagnostics: resolveFunction(partialCodeql, "databaseExportDiagnostics"),
diagnosticsExport: resolveFunction(partialCodeql, "diagnosticsExport"),
resolveExtractor: resolveFunction(partialCodeql, "resolveExtractor"),
mergeResults: resolveFunction(partialCodeql, "mergeResults"),
};
return cachedCodeQL;
}
@@ -664,6 +665,22 @@ async function getCodeQLForCmd(cmd, checkVersion) {
}).exec();
return JSON.parse(extractorPath);
},
async mergeResults(sarifFiles, outputFile, { mergeRunsFromEqualCategory = false, }) {
const args = [
"github",
"merge-results",
"--output",
outputFile,
...getExtraOptionsFromEnv(["github", "merge-results"]),
];
for (const sarifFile of sarifFiles) {
args.push("--sarif", sarifFile);
}
if (mergeRunsFromEqualCategory) {
args.push("--sarif-merge-runs-from-equal-category");
}
await runTool(cmd, args);
},
};
// To ensure that status reports include the CodeQL CLI version wherever
// possible, we want to call getVersion(), which populates the version value

File diff suppressed because one or more lines are too long

7
lib/feature-flags.js generated
View File

@@ -48,6 +48,7 @@ exports.CODEQL_VERSION_FINE_GRAINED_PARALLELISM = "2.15.1";
*/
var Feature;
(function (Feature) {
Feature["CliSarifMerge"] = "cli_sarif_merge_enabled";
Feature["CppDependencyInstallation"] = "cpp_dependency_installation_enabled";
Feature["CppTrapCachingEnabled"] = "cpp_trap_caching_enabled";
Feature["DisableJavaBuildlessEnabled"] = "disable_java_buildless_enabled";
@@ -58,6 +59,12 @@ var Feature;
Feature["QaTelemetryEnabled"] = "qa_telemetry_enabled";
})(Feature || (exports.Feature = Feature = {}));
exports.featureConfig = {
[Feature.CliSarifMerge]: {
envVar: "CODEQL_ACTION_CLI_SARIF_MERGE",
// This is guarded by a `supportsFeature` check rather than by a version check.
minimumVersion: undefined,
defaultValue: false,
},
[Feature.CppDependencyInstallation]: {
envVar: "CODEQL_EXTRACTOR_CPP_AUTOINSTALL_DEPENDENCIES",
minimumVersion: "2.15.0",

File diff suppressed because one or more lines are too long

1
lib/tools-features.js generated
View File

@@ -8,6 +8,7 @@ var ToolsFeature;
ToolsFeature["InformsAboutUnsupportedPathFilters"] = "informsAboutUnsupportedPathFilters";
ToolsFeature["SetsCodeqlRunnerEnvVar"] = "setsCodeqlRunnerEnvVar";
ToolsFeature["TraceCommandUseBuildMode"] = "traceCommandUseBuildMode";
ToolsFeature["SarifMergeRunsFromEqualCategory"] = "sarifMergeRunsFromEqualCategory";
})(ToolsFeature || (exports.ToolsFeature = ToolsFeature = {}));
/**
* Determines if the given feature is supported by the CLI.

View File

@@ -1 +1 @@
{"version":3,"file":"tools-features.js","sourceRoot":"","sources":["../src/tools-features.ts"],"names":[],"mappings":";;;AAEA,IAAY,YAMX;AAND,WAAY,YAAY;IACtB,mDAAmC,CAAA;IACnC,+FAA+E,CAAA;IAC/E,yFAAyE,CAAA;IACzE,iEAAiD,CAAA;IACjD,qEAAqD,CAAA;AACvD,CAAC,EANW,YAAY,4BAAZ,YAAY,QAMvB;AAED;;;;;;GAMG;AACH,SAAgB,uBAAuB,CACrC,WAAwB,EACxB,OAAqB;IAErB,OAAO,CAAC,CAAC,WAAW,CAAC,QAAQ,IAAI,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;AACjE,CAAC;AALD,0DAKC"}
{"version":3,"file":"tools-features.js","sourceRoot":"","sources":["../src/tools-features.ts"],"names":[],"mappings":";;;AAEA,IAAY,YAOX;AAPD,WAAY,YAAY;IACtB,mDAAmC,CAAA;IACnC,+FAA+E,CAAA;IAC/E,yFAAyE,CAAA;IACzE,iEAAiD,CAAA;IACjD,qEAAqD,CAAA;IACrD,mFAAmE,CAAA;AACrE,CAAC,EAPW,YAAY,4BAAZ,YAAY,QAOvB;AAED;;;;;;GAMG;AACH,SAAgB,uBAAuB,CACrC,WAAwB,EACxB,OAAqB;IAErB,OAAO,CAAC,CAAC,WAAW,CAAC,QAAQ,IAAI,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;AACjE,CAAC;AALD,0DAKC"}

71
lib/upload-lib.js generated
View File

@@ -34,10 +34,17 @@ const core = __importStar(require("@actions/core"));
const file_url_1 = __importDefault(require("file-url"));
const jsonschema = __importStar(require("jsonschema"));
const actionsUtil = __importStar(require("./actions-util"));
const actions_util_1 = require("./actions-util");
const api = __importStar(require("./api-client"));
const api_client_1 = require("./api-client");
const codeql_1 = require("./codeql");
const config_utils_1 = require("./config-utils");
const environment_1 = require("./environment");
const feature_flags_1 = require("./feature-flags");
const fingerprints = __importStar(require("./fingerprints"));
const init_1 = require("./init");
const repository_1 = require("./repository");
const tools_features_1 = require("./tools-features");
const util = __importStar(require("./util"));
const util_1 = require("./util");
const GENERIC_403_MSG = "The repo on which this action is running has not opted-in to CodeQL code scanning.";
@@ -62,6 +69,64 @@ function combineSarifFiles(sarifFiles) {
}
return combinedSarif;
}
/**
* Checks whether all the runs in the given SARIF files were produced by CodeQL.
* @param sarifFiles The list of SARIF files to check.
*/
function areAllRunsProducedByCodeQL(sarifFiles) {
return sarifFiles.every((sarifFile) => {
const sarifObject = JSON.parse(fs.readFileSync(sarifFile, "utf8"));
return sarifObject.runs?.every((run) => run.tool?.driver?.name === "CodeQL");
});
}
// Takes a list of paths to sarif files and combines them together using the
// CLI `github merge-results` command when all SARIF files are produced by
// CodeQL. Otherwise, it will fall back to combining the files in the action.
// Returns the contents of the combined sarif file.
async function combineSarifFilesUsingCLI(sarifFiles, gitHubVersion, features, logger) {
if (sarifFiles.length === 1) {
return JSON.parse(fs.readFileSync(sarifFiles[0], "utf8"));
}
if (!areAllRunsProducedByCodeQL(sarifFiles)) {
logger.debug("Not all SARIF files were produced by CodeQL. Merging files in the action.");
// If not, use the naive method of combining the files.
return combineSarifFiles(sarifFiles);
}
// Initialize CodeQL, either by using the config file from the 'init' step,
// or by initializing it here.
let codeQL;
let tempDir = actionsUtil.getTemporaryDirectory();
const config = await (0, config_utils_1.getConfig)(tempDir, logger);
if (config !== undefined) {
codeQL = await (0, codeql_1.getCodeQL)(config.codeQLCmd);
tempDir = config.tempDir;
}
else {
logger.info("Initializing CodeQL since the 'init' Action was not called before this step.");
const apiDetails = {
auth: (0, actions_util_1.getRequiredInput)("token"),
externalRepoAuth: (0, actions_util_1.getOptionalInput)("external-repository-token"),
url: (0, util_1.getRequiredEnvParam)("GITHUB_SERVER_URL"),
apiURL: (0, util_1.getRequiredEnvParam)("GITHUB_API_URL"),
};
const codeQLDefaultVersionInfo = await features.getDefaultCliVersion(gitHubVersion.type);
const initCodeQLResult = await (0, init_1.initCodeQL)(undefined, // There is no tools input on the upload action
apiDetails, tempDir, gitHubVersion.type, codeQLDefaultVersionInfo, logger);
codeQL = initCodeQLResult.codeql;
}
if (!(await codeQL.supportsFeature(tools_features_1.ToolsFeature.SarifMergeRunsFromEqualCategory))) {
logger.warning("The CodeQL CLI does not support merging SARIF files. Merging files in the action.");
return combineSarifFiles(sarifFiles);
}
const baseTempDir = path.resolve(tempDir, "combined-sarif");
fs.mkdirSync(baseTempDir, { recursive: true });
const outputDirectory = fs.mkdtempSync(path.resolve(baseTempDir, "output-"));
const outputFile = path.resolve(outputDirectory, "combined-sarif.sarif");
await codeQL.mergeResults(sarifFiles, outputFile, {
mergeRunsFromEqualCategory: true,
});
return JSON.parse(fs.readFileSync(outputFile, "utf8"));
}
// Populates the run.automationDetails.id field using the analysis_key and environment
// and return an updated sarif file contents.
function populateRunAutomationDetails(sarif, category, analysis_key, environment) {
@@ -264,11 +329,15 @@ exports.buildPayload = buildPayload;
async function uploadFiles(sarifFiles, repositoryNwo, commitOid, ref, analysisKey, category, analysisName, workflowRunID, workflowRunAttempt, sourceRoot, environment, logger) {
logger.startGroup("Uploading results");
logger.info(`Processing sarif files: ${JSON.stringify(sarifFiles)}`);
const gitHubVersion = await (0, api_client_1.getGitHubVersion)();
const features = new feature_flags_1.Features(gitHubVersion, repositoryNwo, actionsUtil.getTemporaryDirectory(), logger);
// Validate that the files we were asked to upload are all valid SARIF files
for (const file of sarifFiles) {
validateSarifFileSchema(file, logger);
}
let sarif = combineSarifFiles(sarifFiles);
let sarif = (await features.getValue(feature_flags_1.Feature.CliSarifMerge))
? await combineSarifFilesUsingCLI(sarifFiles, gitHubVersion, features, logger)
: combineSarifFiles(sarifFiles);
sarif = await fingerprints.addFingerprints(sarif, sourceRoot, logger);
sarif = populateRunAutomationDetails(sarif, category, analysisKey, environment);
const toolNames = util.getToolNames(sarif);

File diff suppressed because one or more lines are too long

View File

@@ -193,7 +193,7 @@ ava_1.default.beforeEach(() => {
const sarifFile = `${__dirname}/../src/testdata/with-invalid-uri.sarif`;
uploadLib.validateSarifFileSchema(sarifFile, mockLogger);
t.deepEqual(loggedMessages.length, 1);
t.deepEqual(loggedMessages[0], "Warning: 'not a valid URI' is not a valid URI in 'instance.runs[0].results[0].locations[0].physicalLocation.artifactLocation.uri'.");
t.deepEqual(loggedMessages[0], "Warning: 'not a valid URI' is not a valid UR in 'instance.runs[0].results[0].locations[0].physicalLocation.artifactLocation.uri'.");
});
function createMockSarif(id, tool) {
return {

File diff suppressed because one or more lines are too long

55
lib/upload-sarif-action-post-helper.js generated Normal file
View File

@@ -0,0 +1,55 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.uploadArtifacts = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const core = __importStar(require("@actions/core"));
const actionsUtil = __importStar(require("./actions-util"));
async function uploadArtifacts(uploadDebugArtifacts) {
const tempDir = actionsUtil.getTemporaryDirectory();
// Upload Actions SARIF artifacts for debugging when environment variable is set
if (process.env["CODEQL_ACTION_DEBUG_COMBINED_SARIF"] === "true") {
core.info("Uploading available combined SARIF files as Actions debugging artifact...");
const baseTempDir = path.resolve(tempDir, "combined-sarif");
const toUpload = [];
if (fs.existsSync(baseTempDir)) {
const outputDirs = fs.readdirSync(baseTempDir);
for (const outputDir of outputDirs) {
const sarifFiles = fs
.readdirSync(path.resolve(baseTempDir, outputDir))
.filter((f) => f.endsWith(".sarif"));
for (const sarifFile of sarifFiles) {
toUpload.push(path.resolve(baseTempDir, outputDir, sarifFile));
}
}
}
if (toUpload.length > 0) {
await uploadDebugArtifacts(toUpload, baseTempDir, "upload-debug-artifacts");
}
}
}
exports.uploadArtifacts = uploadArtifacts;
//# sourceMappingURL=upload-sarif-action-post-helper.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"upload-sarif-action-post-helper.js","sourceRoot":"","sources":["../src/upload-sarif-action-post-helper.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uCAAyB;AACzB,2CAA6B;AAE7B,oDAAsC;AAEtC,4DAA8C;AAEvC,KAAK,UAAU,eAAe,CACnC,oBAIkB;IAElB,MAAM,OAAO,GAAG,WAAW,CAAC,qBAAqB,EAAE,CAAC;IAEpD,gFAAgF;IAChF,IAAI,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,KAAK,MAAM,EAAE,CAAC;QACjE,IAAI,CAAC,IAAI,CACP,2EAA2E,CAC5E,CAAC;QAEF,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;QAE5D,MAAM,QAAQ,GAAa,EAAE,CAAC;QAE9B,IAAI,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YAC/B,MAAM,UAAU,GAAG,EAAE,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;YAE/C,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;gBACnC,MAAM,UAAU,GAAG,EAAE;qBAClB,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;qBACjD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAEvC,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;oBACnC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC;gBACjE,CAAC;YACH,CAAC;QACH,CAAC;QAED,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,oBAAoB,CACxB,QAAQ,EACR,WAAW,EACX,wBAAwB,CACzB,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAzCD,0CAyCC"}

44
lib/upload-sarif-action-post.js generated Normal file
View File

@@ -0,0 +1,44 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
/**
* This file is the entry point for the `post:` hook of `upload-sarif-action.yml`.
* It will run after the all steps in this job, in reverse order in relation to
* other `post:` hooks.
*/
const core = __importStar(require("@actions/core"));
const debugArtifacts = __importStar(require("./debug-artifacts"));
const uploadSarifActionPostHelper = __importStar(require("./upload-sarif-action-post-helper"));
const util_1 = require("./util");
async function runWrapper() {
try {
await uploadSarifActionPostHelper.uploadArtifacts(debugArtifacts.uploadDebugArtifacts);
}
catch (error) {
core.setFailed(`upload-sarif post-action step failed: ${(0, util_1.wrapError)(error).message}`);
}
}
void runWrapper();
//# sourceMappingURL=upload-sarif-action-post.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"upload-sarif-action-post.js","sourceRoot":"","sources":["../src/upload-sarif-action-post.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;GAIG;AACH,oDAAsC;AAEtC,kEAAoD;AACpD,+FAAiF;AACjF,iCAAmC;AAEnC,KAAK,UAAU,UAAU;IACvB,IAAI,CAAC;QACH,MAAM,2BAA2B,CAAC,eAAe,CAC/C,cAAc,CAAC,oBAAoB,CACpC,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,SAAS,CACZ,yCAAyC,IAAA,gBAAS,EAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CACpE,CAAC;IACJ,CAAC;AACH,CAAC;AAED,KAAK,UAAU,EAAE,CAAC"}

View File

@@ -7,11 +7,18 @@ import * as core from "@actions/core";
import * as analyzeActionPostHelper from "./analyze-action-post-helper";
import * as debugArtifacts from "./debug-artifacts";
import * as uploadSarifActionPostHelper from "./upload-sarif-action-post-helper";
import { wrapError } from "./util";
async function runWrapper() {
try {
await analyzeActionPostHelper.run(debugArtifacts.uploadSarifDebugArtifact);
// Also run the upload-sarif post action since we're potentially running
// the same steps in the analyze action.
await uploadSarifActionPostHelper.uploadArtifacts(
debugArtifacts.uploadDebugArtifacts,
);
} catch (error) {
core.setFailed(
`analyze post-action step failed: ${wrapError(error).message}`,

View File

@@ -50,6 +50,10 @@ interface ExtraOptions {
extractor?: Options;
queries?: Options;
};
github?: {
"*"?: Options;
"merge-results"?: Options;
};
}
export interface CodeQL {
@@ -191,6 +195,14 @@ export interface CodeQL {
): Promise<void>;
/** Get the location of an extractor for the specified language. */
resolveExtractor(language: Language): Promise<string>;
/**
* Run 'codeql github merge-results'.
*/
mergeResults(
sarifFiles: string[],
outputFile: string,
options: { mergeRunsFromEqualCategory?: boolean },
): Promise<void>;
}
export interface VersionInfo {
@@ -489,6 +501,7 @@ export function setCodeQL(partialCodeql: Partial<CodeQL>): CodeQL {
),
diagnosticsExport: resolveFunction(partialCodeql, "diagnosticsExport"),
resolveExtractor: resolveFunction(partialCodeql, "resolveExtractor"),
mergeResults: resolveFunction(partialCodeql, "mergeResults"),
};
return cachedCodeQL;
}
@@ -1077,6 +1090,31 @@ export async function getCodeQLForCmd(
).exec();
return JSON.parse(extractorPath);
},
async mergeResults(
sarifFiles: string[],
outputFile: string,
{
mergeRunsFromEqualCategory = false,
}: { mergeRunsFromEqualCategory?: boolean },
): Promise<void> {
const args = [
"github",
"merge-results",
"--output",
outputFile,
...getExtraOptionsFromEnv(["github", "merge-results"]),
];
for (const sarifFile of sarifFiles) {
args.push("--sarif", sarifFile);
}
if (mergeRunsFromEqualCategory) {
args.push("--sarif-merge-runs-from-equal-category");
}
await runTool(cmd, args);
},
};
// To ensure that status reports include the CodeQL CLI version wherever
// possible, we want to call getVersion(), which populates the version value

View File

@@ -44,6 +44,7 @@ export interface FeatureEnablement {
* Each value of this enum should end with `_enabled`.
*/
export enum Feature {
CliSarifMerge = "cli_sarif_merge_enabled",
CppDependencyInstallation = "cpp_dependency_installation_enabled",
CppTrapCachingEnabled = "cpp_trap_caching_enabled",
DisableJavaBuildlessEnabled = "disable_java_buildless_enabled",
@@ -58,6 +59,12 @@ export const featureConfig: Record<
Feature,
{ envVar: string; minimumVersion: string | undefined; defaultValue: boolean }
> = {
[Feature.CliSarifMerge]: {
envVar: "CODEQL_ACTION_CLI_SARIF_MERGE",
// This is guarded by a `supportsFeature` check rather than by a version check.
minimumVersion: undefined,
defaultValue: false,
},
[Feature.CppDependencyInstallation]: {
envVar: "CODEQL_EXTRACTOR_CPP_AUTOINSTALL_DEPENDENCIES",
minimumVersion: "2.15.0",

View File

@@ -6,6 +6,7 @@ export enum ToolsFeature {
InformsAboutUnsupportedPathFilters = "informsAboutUnsupportedPathFilters",
SetsCodeqlRunnerEnvVar = "setsCodeqlRunnerEnvVar",
TraceCommandUseBuildMode = "traceCommandUseBuildMode",
SarifMergeRunsFromEqualCategory = "sarifMergeRunsFromEqualCategory",
}
/**

View File

@@ -320,7 +320,7 @@ test("accept results with invalid artifactLocation.uri value", (t) => {
t.deepEqual(loggedMessages.length, 1);
t.deepEqual(
loggedMessages[0],
"Warning: 'not a valid URI' is not a valid URI in 'instance.runs[0].results[0].locations[0].physicalLocation.artifactLocation.uri'.",
"Warning: 'not a valid URI' is not a valid UR in 'instance.runs[0].results[0].locations[0].physicalLocation.artifactLocation.uri'.",
);
});

View File

@@ -8,13 +8,26 @@ import fileUrl from "file-url";
import * as jsonschema from "jsonschema";
import * as actionsUtil from "./actions-util";
import { getOptionalInput, getRequiredInput } from "./actions-util";
import * as api from "./api-client";
import { getGitHubVersion } from "./api-client";
import { CodeQL, getCodeQL } from "./codeql";
import { getConfig } from "./config-utils";
import { EnvVar } from "./environment";
import { Feature, Features } from "./feature-flags";
import * as fingerprints from "./fingerprints";
import { initCodeQL } from "./init";
import { Logger } from "./logging";
import { parseRepositoryNwo, RepositoryNwo } from "./repository";
import { ToolsFeature } from "./tools-features";
import * as util from "./util";
import { SarifFile, ConfigurationError, wrapError } from "./util";
import {
ConfigurationError,
getRequiredEnvParam,
GitHubVersion,
SarifFile,
wrapError,
} from "./util";
const GENERIC_403_MSG =
"The repo on which this action is running has not opted-in to CodeQL code scanning.";
@@ -48,6 +61,107 @@ function combineSarifFiles(sarifFiles: string[]): SarifFile {
return combinedSarif;
}
/**
* Checks whether all the runs in the given SARIF files were produced by CodeQL.
* @param sarifFiles The list of SARIF files to check.
*/
function areAllRunsProducedByCodeQL(sarifFiles: string[]): boolean {
return sarifFiles.every((sarifFile) => {
const sarifObject = JSON.parse(
fs.readFileSync(sarifFile, "utf8"),
) as SarifFile;
return sarifObject.runs?.every(
(run) => run.tool?.driver?.name === "CodeQL",
);
});
}
// Takes a list of paths to sarif files and combines them together using the
// CLI `github merge-results` command when all SARIF files are produced by
// CodeQL. Otherwise, it will fall back to combining the files in the action.
// Returns the contents of the combined sarif file.
async function combineSarifFilesUsingCLI(
sarifFiles: string[],
gitHubVersion: GitHubVersion,
features: Features,
logger: Logger,
): Promise<SarifFile> {
if (sarifFiles.length === 1) {
return JSON.parse(fs.readFileSync(sarifFiles[0], "utf8")) as SarifFile;
}
if (!areAllRunsProducedByCodeQL(sarifFiles)) {
logger.debug(
"Not all SARIF files were produced by CodeQL. Merging files in the action.",
);
// If not, use the naive method of combining the files.
return combineSarifFiles(sarifFiles);
}
// Initialize CodeQL, either by using the config file from the 'init' step,
// or by initializing it here.
let codeQL: CodeQL;
let tempDir: string = actionsUtil.getTemporaryDirectory();
const config = await getConfig(tempDir, logger);
if (config !== undefined) {
codeQL = await getCodeQL(config.codeQLCmd);
tempDir = config.tempDir;
} else {
logger.info(
"Initializing CodeQL since the 'init' Action was not called before this step.",
);
const apiDetails = {
auth: getRequiredInput("token"),
externalRepoAuth: getOptionalInput("external-repository-token"),
url: getRequiredEnvParam("GITHUB_SERVER_URL"),
apiURL: getRequiredEnvParam("GITHUB_API_URL"),
};
const codeQLDefaultVersionInfo = await features.getDefaultCliVersion(
gitHubVersion.type,
);
const initCodeQLResult = await initCodeQL(
undefined, // There is no tools input on the upload action
apiDetails,
tempDir,
gitHubVersion.type,
codeQLDefaultVersionInfo,
logger,
);
codeQL = initCodeQLResult.codeql;
}
if (
!(await codeQL.supportsFeature(
ToolsFeature.SarifMergeRunsFromEqualCategory,
))
) {
logger.warning(
"The CodeQL CLI does not support merging SARIF files. Merging files in the action.",
);
return combineSarifFiles(sarifFiles);
}
const baseTempDir = path.resolve(tempDir, "combined-sarif");
fs.mkdirSync(baseTempDir, { recursive: true });
const outputDirectory = fs.mkdtempSync(path.resolve(baseTempDir, "output-"));
const outputFile = path.resolve(outputDirectory, "combined-sarif.sarif");
await codeQL.mergeResults(sarifFiles, outputFile, {
mergeRunsFromEqualCategory: true,
});
return JSON.parse(fs.readFileSync(outputFile, "utf8")) as SarifFile;
}
// Populates the run.automationDetails.id field using the analysis_key and environment
// and return an updated sarif file contents.
export function populateRunAutomationDetails(
@@ -363,12 +477,27 @@ async function uploadFiles(
logger.startGroup("Uploading results");
logger.info(`Processing sarif files: ${JSON.stringify(sarifFiles)}`);
const gitHubVersion = await getGitHubVersion();
const features = new Features(
gitHubVersion,
repositoryNwo,
actionsUtil.getTemporaryDirectory(),
logger,
);
// Validate that the files we were asked to upload are all valid SARIF files
for (const file of sarifFiles) {
validateSarifFileSchema(file, logger);
}
let sarif = combineSarifFiles(sarifFiles);
let sarif = (await features.getValue(Feature.CliSarifMerge))
? await combineSarifFilesUsingCLI(
sarifFiles,
gitHubVersion,
features,
logger,
)
: combineSarifFiles(sarifFiles);
sarif = await fingerprints.addFingerprints(sarif, sourceRoot, logger);
sarif = populateRunAutomationDetails(

View File

@@ -0,0 +1,49 @@
import * as fs from "fs";
import * as path from "path";
import * as core from "@actions/core";
import * as actionsUtil from "./actions-util";
export async function uploadArtifacts(
uploadDebugArtifacts: (
toUpload: string[],
rootDir: string,
artifactName: string,
) => Promise<void>,
) {
const tempDir = actionsUtil.getTemporaryDirectory();
// Upload Actions SARIF artifacts for debugging when environment variable is set
if (process.env["CODEQL_ACTION_DEBUG_COMBINED_SARIF"] === "true") {
core.info(
"Uploading available combined SARIF files as Actions debugging artifact...",
);
const baseTempDir = path.resolve(tempDir, "combined-sarif");
const toUpload: string[] = [];
if (fs.existsSync(baseTempDir)) {
const outputDirs = fs.readdirSync(baseTempDir);
for (const outputDir of outputDirs) {
const sarifFiles = fs
.readdirSync(path.resolve(baseTempDir, outputDir))
.filter((f) => f.endsWith(".sarif"));
for (const sarifFile of sarifFiles) {
toUpload.push(path.resolve(baseTempDir, outputDir, sarifFile));
}
}
}
if (toUpload.length > 0) {
await uploadDebugArtifacts(
toUpload,
baseTempDir,
"upload-debug-artifacts",
);
}
}
}

View File

@@ -0,0 +1,24 @@
/**
* This file is the entry point for the `post:` hook of `upload-sarif-action.yml`.
* It will run after the all steps in this job, in reverse order in relation to
* other `post:` hooks.
*/
import * as core from "@actions/core";
import * as debugArtifacts from "./debug-artifacts";
import * as uploadSarifActionPostHelper from "./upload-sarif-action-post-helper";
import { wrapError } from "./util";
async function runWrapper() {
try {
await uploadSarifActionPostHelper.uploadArtifacts(
debugArtifacts.uploadDebugArtifacts,
);
} catch (error) {
core.setFailed(
`upload-sarif post-action step failed: ${wrapError(error).message}`,
);
}
}
void runWrapper();

View File

@@ -38,3 +38,4 @@ outputs:
runs:
using: node20
main: '../lib/upload-sarif-action.js'
post: '../lib/upload-sarif-action-post.js'