Compare commits

...

14 Commits

Author SHA1 Message Date
Michael B. Gale
4989465202 Merge branch 'main' into mbg/fix/upload-sarif-non-sarif 2025-09-26 17:40:56 +01:00
Michael B. Gale
e080dc827a Check if file is .quality.sarif for CQ upload 2025-09-26 15:59:26 +01:00
Michael B. Gale
7b7f47d02c Check that file doesn't belong to other analysis in getSarifFilePaths 2025-09-26 15:56:20 +01:00
Michael B. Gale
b26519c6c0 Add isOtherAnalysisSarif with tests 2025-09-26 15:51:43 +01:00
Michael B. Gale
320fdb3773 Tests: ensure uploadSpecifiedFiles wasn't called if we don't expect it to be 2025-09-26 15:32:57 +01:00
Michael B. Gale
f6ff68d584 Test that uploaded files match expectations for each analysis kind 2025-09-26 15:12:55 +01:00
Michael B. Gale
0e1ad534f3 Fix sarif-ids format not matching documentation 2025-09-26 15:12:54 +01:00
Michael B. Gale
4c60b18145 Add some tests for findAndUpload and uploadSarif 2025-09-26 15:12:54 +01:00
Michael B. Gale
d7509a4aaf Use FeatureEnablement rather than Features in upload-sarif 2025-09-26 15:12:54 +01:00
Michael B. Gale
ff359630c6 Move main upload-sarif logic to new upload-sarif module 2025-09-26 15:12:54 +01:00
Michael B. Gale
c1f21e42fb Use getSarifFilePaths in upload-sarif 2025-09-26 15:12:53 +01:00
Michael B. Gale
fc8843601d Move findAndUpload to new upload-sarif module 2025-09-26 15:08:36 +01:00
Michael B. Gale
975347365f Warn if no SARIF files were uploaded 2025-09-26 15:02:03 +01:00
Michael B. Gale
022d7d5aa6 Move error checks out of getSarifFilePaths 2025-09-26 15:02:03 +01:00
12 changed files with 757 additions and 344 deletions

View File

@@ -89,7 +89,7 @@ jobs:
ref: refs/heads/main
sha: 5e235361806c361d4d3f8859e3c897658025a9a2
- name: Check output from `upload-sarif` step
if: fromJSON(steps.upload-sarif.outputs.sarif-ids)[0].analysis != 'code-quality'
if: !fromJSON(steps.upload-sarif.outputs.sarif-ids).code-quality
run: exit 1
env:
CODEQL_ACTION_TEST_MODE: true

38
lib/analyze-action.js generated
View File

@@ -90177,6 +90177,12 @@ var CodeQuality = {
sarifPredicate: (name) => name.endsWith(CodeQuality.sarifExtension),
sentinelPrefix: "CODEQL_UPLOAD_QUALITY_SARIF_"
};
var Analyses = [CodeScanning, CodeQuality];
function isOtherAnalysisSarif(current, filepath) {
return Analyses.some(
(config) => config.kind !== current && config.sarifPredicate(filepath)
);
}
// src/analyze.ts
var fs15 = __toESM(require("fs"));
@@ -95568,19 +95574,11 @@ function findSarifFilesInDir(sarifPath, isSarif) {
walkSarifFiles(sarifPath);
return sarifFiles;
}
function getSarifFilePaths(sarifPath, isSarif) {
if (!fs18.existsSync(sarifPath)) {
throw new ConfigurationError(`Path does not exist: ${sarifPath}`);
}
let sarifFiles;
if (fs18.lstatSync(sarifPath).isDirectory()) {
sarifFiles = findSarifFilesInDir(sarifPath, isSarif);
if (sarifFiles.length === 0) {
throw new ConfigurationError(
`No SARIF files found to upload in "${sarifPath}".`
);
}
} else {
function getSarifFilePaths(sarifPath, analysis, pathStats) {
let sarifFiles = [];
if (pathStats.isDirectory()) {
sarifFiles = findSarifFilesInDir(sarifPath, analysis.sarifPredicate);
} else if (!isOtherAnalysisSarif(analysis.kind, sarifPath)) {
sarifFiles = [sarifPath];
}
return sarifFiles;
@@ -95681,10 +95679,16 @@ function buildPayload(commitOid, ref, analysisKey, analysisName, zippedSarif, wo
return payloadObj;
}
async function uploadFiles(inputSarifPath, checkoutPath, category, features, logger, uploadTarget) {
const sarifPaths = getSarifFilePaths(
inputSarifPath,
uploadTarget.sarifPredicate
);
const pathStats = fs18.lstatSync(inputSarifPath, { throwIfNoEntry: false });
if (pathStats === void 0) {
throw new ConfigurationError(`Path does not exist: ${inputSarifPath}`);
}
const sarifPaths = getSarifFilePaths(inputSarifPath, uploadTarget, pathStats);
if (sarifPaths.length === 0) {
throw new ConfigurationError(
`No SARIF files found to upload in "${inputSarifPath}".`
);
}
return uploadSpecifiedFiles(
sarifPaths,
checkoutPath,

View File

@@ -128817,6 +128817,12 @@ var CodeQuality = {
sarifPredicate: (name) => name.endsWith(CodeQuality.sarifExtension),
sentinelPrefix: "CODEQL_UPLOAD_QUALITY_SARIF_"
};
var Analyses = [CodeScanning, CodeQuality];
function isOtherAnalysisSarif(current, filepath) {
return Analyses.some(
(config) => config.kind !== current && config.sarifPredicate(filepath)
);
}
// src/caching-utils.ts
var core6 = __toESM(require_core());
@@ -133002,19 +133008,11 @@ function findSarifFilesInDir(sarifPath, isSarif) {
walkSarifFiles(sarifPath);
return sarifFiles;
}
function getSarifFilePaths(sarifPath, isSarif) {
if (!fs17.existsSync(sarifPath)) {
throw new ConfigurationError(`Path does not exist: ${sarifPath}`);
}
let sarifFiles;
if (fs17.lstatSync(sarifPath).isDirectory()) {
sarifFiles = findSarifFilesInDir(sarifPath, isSarif);
if (sarifFiles.length === 0) {
throw new ConfigurationError(
`No SARIF files found to upload in "${sarifPath}".`
);
}
} else {
function getSarifFilePaths(sarifPath, analysis, pathStats) {
let sarifFiles = [];
if (pathStats.isDirectory()) {
sarifFiles = findSarifFilesInDir(sarifPath, analysis.sarifPredicate);
} else if (!isOtherAnalysisSarif(analysis.kind, sarifPath)) {
sarifFiles = [sarifPath];
}
return sarifFiles;
@@ -133115,10 +133113,16 @@ function buildPayload(commitOid, ref, analysisKey, analysisName, zippedSarif, wo
return payloadObj;
}
async function uploadFiles(inputSarifPath, checkoutPath, category, features, logger, uploadTarget) {
const sarifPaths = getSarifFilePaths(
inputSarifPath,
uploadTarget.sarifPredicate
);
const pathStats = fs17.lstatSync(inputSarifPath, { throwIfNoEntry: false });
if (pathStats === void 0) {
throw new ConfigurationError(`Path does not exist: ${inputSarifPath}`);
}
const sarifPaths = getSarifFilePaths(inputSarifPath, uploadTarget, pathStats);
if (sarifPaths.length === 0) {
throw new ConfigurationError(
`No SARIF files found to upload in "${inputSarifPath}".`
);
}
return uploadSpecifiedFiles(
sarifPaths,
checkoutPath,

70
lib/upload-lib.js generated
View File

@@ -88530,6 +88530,36 @@ async function runTool(cmd, args = [], opts = {}) {
return stdout;
}
// src/analyses.ts
var AnalysisKind = /* @__PURE__ */ ((AnalysisKind2) => {
AnalysisKind2["CodeScanning"] = "code-scanning";
AnalysisKind2["CodeQuality"] = "code-quality";
return AnalysisKind2;
})(AnalysisKind || {});
var supportedAnalysisKinds = new Set(Object.values(AnalysisKind));
var CodeScanning = {
kind: "code-scanning" /* CodeScanning */,
name: "code scanning",
target: "PUT /repos/:owner/:repo/code-scanning/analysis" /* CODE_SCANNING */,
sarifExtension: ".sarif",
sarifPredicate: (name) => name.endsWith(CodeScanning.sarifExtension) && !CodeQuality.sarifPredicate(name),
sentinelPrefix: "CODEQL_UPLOAD_SARIF_"
};
var CodeQuality = {
kind: "code-quality" /* CodeQuality */,
name: "code quality",
target: "PUT /repos/:owner/:repo/code-quality/analysis" /* CODE_QUALITY */,
sarifExtension: ".quality.sarif",
sarifPredicate: (name) => name.endsWith(CodeQuality.sarifExtension),
sentinelPrefix: "CODEQL_UPLOAD_QUALITY_SARIF_"
};
var Analyses = [CodeScanning, CodeQuality];
function isOtherAnalysisSarif(current, filepath) {
return Analyses.some(
(config) => config.kind !== current && config.sarifPredicate(filepath)
);
}
// src/api-client.ts
var core5 = __toESM(require_core());
var githubUtils = __toESM(require_utils4());
@@ -88921,14 +88951,6 @@ function wrapCliConfigurationError(cliError) {
var fs7 = __toESM(require("fs"));
var path9 = __toESM(require("path"));
// src/analyses.ts
var AnalysisKind = /* @__PURE__ */ ((AnalysisKind2) => {
AnalysisKind2["CodeScanning"] = "code-scanning";
AnalysisKind2["CodeQuality"] = "code-quality";
return AnalysisKind2;
})(AnalysisKind || {});
var supportedAnalysisKinds = new Set(Object.values(AnalysisKind));
// src/caching-utils.ts
var core6 = __toESM(require_core());
@@ -92374,19 +92396,11 @@ function findSarifFilesInDir(sarifPath, isSarif) {
walkSarifFiles(sarifPath);
return sarifFiles;
}
function getSarifFilePaths(sarifPath, isSarif) {
if (!fs13.existsSync(sarifPath)) {
throw new ConfigurationError(`Path does not exist: ${sarifPath}`);
}
let sarifFiles;
if (fs13.lstatSync(sarifPath).isDirectory()) {
sarifFiles = findSarifFilesInDir(sarifPath, isSarif);
if (sarifFiles.length === 0) {
throw new ConfigurationError(
`No SARIF files found to upload in "${sarifPath}".`
);
}
} else {
function getSarifFilePaths(sarifPath, analysis, pathStats) {
let sarifFiles = [];
if (pathStats.isDirectory()) {
sarifFiles = findSarifFilesInDir(sarifPath, analysis.sarifPredicate);
} else if (!isOtherAnalysisSarif(analysis.kind, sarifPath)) {
sarifFiles = [sarifPath];
}
return sarifFiles;
@@ -92487,10 +92501,16 @@ function buildPayload(commitOid, ref, analysisKey, analysisName, zippedSarif, wo
return payloadObj;
}
async function uploadFiles(inputSarifPath, checkoutPath, category, features, logger, uploadTarget) {
const sarifPaths = getSarifFilePaths(
inputSarifPath,
uploadTarget.sarifPredicate
);
const pathStats = fs13.lstatSync(inputSarifPath, { throwIfNoEntry: false });
if (pathStats === void 0) {
throw new ConfigurationError(`Path does not exist: ${inputSarifPath}`);
}
const sarifPaths = getSarifFilePaths(inputSarifPath, uploadTarget, pathStats);
if (sarifPaths.length === 0) {
throw new ConfigurationError(
`No SARIF files found to upload in "${inputSarifPath}".`
);
}
return uploadSpecifiedFiles(
sarifPaths,
checkoutPath,

File diff suppressed because it is too large Load Diff

View File

@@ -22,5 +22,5 @@ steps:
ref: 'refs/heads/main'
sha: '5e235361806c361d4d3f8859e3c897658025a9a2'
- name: "Check output from `upload-sarif` step"
if: fromJSON(steps.upload-sarif.outputs.sarif-ids)[0].analysis != 'code-quality'
if: !fromJSON(steps.upload-sarif.outputs.sarif-ids).code-quality
run: exit 1

View File

@@ -2,6 +2,7 @@ import test from "ava";
import {
AnalysisKind,
isOtherAnalysisSarif,
parseAnalysisKinds,
supportedAnalysisKinds,
} from "./analyses";
@@ -34,3 +35,23 @@ test("Parsing analysis kinds requires at least one analysis kind", async (t) =>
instanceOf: ConfigurationError,
});
});
test("isOtherAnalysisSarif", async (t) => {
function expectTrue(analysisKind: AnalysisKind, filepath: string) {
t.assert(
isOtherAnalysisSarif(analysisKind, filepath),
`Expected ${filepath} to be for another analysis kind, but it matched ${analysisKind}.`,
);
}
function expectFalse(analysisKind: AnalysisKind, filepath: string) {
t.assert(
!isOtherAnalysisSarif(analysisKind, filepath),
`Expected ${filepath} to be for ${analysisKind}, but it matched some other analysis kind.`,
);
}
expectTrue(AnalysisKind.CodeQuality, "test.sarif");
expectFalse(AnalysisKind.CodeQuality, "test.quality.sarif");
expectTrue(AnalysisKind.CodeScanning, "test.quality.sarif");
expectFalse(AnalysisKind.CodeScanning, "test.sarif");
expectFalse(AnalysisKind.CodeScanning, "test.json");
});

View File

@@ -86,3 +86,23 @@ export const CodeQuality: AnalysisConfig = {
sarifPredicate: (name) => name.endsWith(CodeQuality.sarifExtension),
sentinelPrefix: "CODEQL_UPLOAD_QUALITY_SARIF_",
};
// An array of all analysis configurations we know of.
export const Analyses = [CodeScanning, CodeQuality];
/**
* Determines based on the given `filepath`, whether the file is a SARIF file for
* an analysis other than the one given by `current`.
*
* @param current The current analysis kind.
* @param filepath The path of the file to check.
* @returns True if `filepath` is a SARIF file for an analysis other than `current`; False otherwise.
*/
export function isOtherAnalysisSarif(
current: AnalysisKind,
filepath: string,
): boolean {
return Analyses.some(
(config) => config.kind !== current && config.sarifPredicate(filepath),
);
}

View File

@@ -437,23 +437,13 @@ export function findSarifFilesInDir(
export function getSarifFilePaths(
sarifPath: string,
isSarif: (name: string) => boolean,
analysis: analyses.AnalysisConfig,
pathStats: fs.Stats,
) {
if (!fs.existsSync(sarifPath)) {
// This is always a configuration error, even for first-party runs.
throw new ConfigurationError(`Path does not exist: ${sarifPath}`);
}
let sarifFiles: string[];
if (fs.lstatSync(sarifPath).isDirectory()) {
sarifFiles = findSarifFilesInDir(sarifPath, isSarif);
if (sarifFiles.length === 0) {
// This is always a configuration error, even for first-party runs.
throw new ConfigurationError(
`No SARIF files found to upload in "${sarifPath}".`,
);
}
} else {
let sarifFiles: string[] = [];
if (pathStats.isDirectory()) {
sarifFiles = findSarifFilesInDir(sarifPath, analysis.sarifPredicate);
} else if (!analyses.isOtherAnalysisSarif(analysis.kind, sarifPath)) {
sarifFiles = [sarifPath];
}
return sarifFiles;
@@ -623,10 +613,21 @@ export async function uploadFiles(
logger: Logger,
uploadTarget: analyses.AnalysisConfig,
): Promise<UploadResult> {
const sarifPaths = getSarifFilePaths(
inputSarifPath,
uploadTarget.sarifPredicate,
);
const pathStats = fs.lstatSync(inputSarifPath, { throwIfNoEntry: false });
if (pathStats === undefined) {
// This is always a configuration error, even for first-party runs.
throw new ConfigurationError(`Path does not exist: ${inputSarifPath}`);
}
const sarifPaths = getSarifFilePaths(inputSarifPath, uploadTarget, pathStats);
if (sarifPaths.length === 0) {
// This is always a configuration error, even for first-party runs.
throw new ConfigurationError(
`No SARIF files found to upload in "${inputSarifPath}".`,
);
}
return uploadSpecifiedFiles(
sarifPaths,

View File

@@ -18,6 +18,7 @@ import {
isThirdPartyAnalysis,
} from "./status-report";
import * as upload_lib from "./upload-lib";
import { uploadResultsToSarifIds, uploadSarif } from "./upload-sarif";
import {
ConfigurationError,
checkActionVersion,
@@ -32,55 +33,6 @@ interface UploadSarifStatusReport
extends StatusReportBase,
upload_lib.UploadStatusReport {}
/**
* Searches for SARIF files for the given `analysis` in the given `sarifPath`.
* If any are found, then they are uploaded to the appropriate endpoint for the given `analysis`.
*
* @param logger The logger to use.
* @param features Information about FFs.
* @param sarifPath The path to a SARIF file or directory containing SARIF files.
* @param pathStats Information about `sarifPath`.
* @param checkoutPath The checkout path.
* @param analysis The configuration of the analysis we should upload SARIF files for.
* @param category The SARIF category to use for the upload.
* @returns The result of uploading the SARIF file(s) or `undefined` if there are none.
*/
async function findAndUpload(
logger: Logger,
features: Features,
sarifPath: string,
pathStats: fs.Stats,
checkoutPath: string,
analysis: analyses.AnalysisConfig,
category?: string,
): Promise<upload_lib.UploadResult | undefined> {
let sarifFiles: string[] | undefined;
if (pathStats.isDirectory()) {
sarifFiles = upload_lib.findSarifFilesInDir(
sarifPath,
analysis.sarifPredicate,
);
} else if (pathStats.isFile() && analysis.sarifPredicate(sarifPath)) {
sarifFiles = [sarifPath];
} else {
return undefined;
}
if (sarifFiles.length !== 0) {
return await upload_lib.uploadSpecifiedFiles(
sarifFiles,
checkoutPath,
category,
features,
logger,
analysis,
);
}
return undefined;
}
async function sendSuccessStatusReport(
startedAt: Date,
uploadStats: upload_lib.UploadStatusReport,
@@ -145,50 +97,32 @@ async function run() {
throw new ConfigurationError(`Path does not exist: ${sarifPath}.`);
}
const sarifIds: Array<{ analysis: string; id: string }> = [];
const uploadResult = await findAndUpload(
const uploadResults = await uploadSarif(
logger,
features,
sarifPath,
pathStats,
checkoutPath,
analyses.CodeScanning,
category,
);
if (uploadResult !== undefined) {
core.setOutput("sarif-id", uploadResult.sarifID);
sarifIds.push({
analysis: analyses.AnalysisKind.CodeScanning,
id: uploadResult.sarifID,
});
if (Object.keys(uploadResults).length === 0) {
logger.warning(`No SARIF files were uploaded.`);
}
// If there are `.quality.sarif` files in `sarifPath`, then upload those to the code quality service.
const qualityUploadResult = await findAndUpload(
logger,
features,
sarifPath,
pathStats,
checkoutPath,
analyses.CodeQuality,
actionsUtil.fixCodeQualityCategory(logger, category),
);
if (qualityUploadResult !== undefined) {
sarifIds.push({
analysis: analyses.AnalysisKind.CodeQuality,
id: qualityUploadResult.sarifID,
});
}
const sarifIds = uploadResultsToSarifIds(uploadResults);
core.setOutput("sarif-ids", JSON.stringify(sarifIds));
// We don't upload results in test mode, so don't wait for processing
if (isInTestMode()) {
core.debug("In test mode. Waiting for processing is disabled.");
} else if (actionsUtil.getRequiredInput("wait-for-processing") === "true") {
if (uploadResult !== undefined) {
const codeScanningUploadResult =
uploadResults[analyses.AnalysisKind.CodeScanning];
if (codeScanningUploadResult !== undefined) {
await upload_lib.waitForProcessing(
getRepositoryNwo(),
uploadResult.sarifID,
codeScanningUploadResult.sarifID,
logger,
);
}
@@ -197,7 +131,7 @@ async function run() {
}
await sendSuccessStatusReport(
startedAt,
uploadResult?.statusReport || {},
uploadResults[analyses.AnalysisKind.CodeScanning]?.statusReport || {},
logger,
);
} catch (unwrappedError) {

263
src/upload-sarif.test.ts Normal file
View File

@@ -0,0 +1,263 @@
import * as fs from "fs";
import * as path from "path";
import test, { ExecutionContext } from "ava";
import * as sinon from "sinon";
import {
AnalysisConfig,
AnalysisKind,
CodeQuality,
CodeScanning,
} from "./analyses";
import { getRunnerLogger } from "./logging";
import { createFeatures, setupTests } from "./testing-utils";
import { UploadResult } from "./upload-lib";
import * as uploadLib from "./upload-lib";
import { findAndUpload, uploadSarif } from "./upload-sarif";
import * as util from "./util";
setupTests(test);
const findAndUploadMacro = test.macro({
exec: async (
t: ExecutionContext<unknown>,
sarifFiles: string[],
analysis: AnalysisConfig,
sarifPath: (tempDir: string) => string = (tempDir) => tempDir,
expectedResult: UploadResult | undefined,
) => {
await util.withTmpDir(async (tempDir) => {
sinon.stub(uploadLib, "uploadSpecifiedFiles").resolves(expectedResult);
const logger = getRunnerLogger(true);
const features = createFeatures([]);
for (const sarifFile of sarifFiles) {
fs.writeFileSync(path.join(tempDir, sarifFile), "");
}
const stats = fs.statSync(sarifPath(tempDir));
const actual = await findAndUpload(
logger,
features,
sarifPath(tempDir),
stats,
"",
analysis,
);
t.deepEqual(actual, expectedResult);
});
},
title: (providedTitle = "") => `findAndUpload - ${providedTitle}`,
});
test(
"no matching files",
findAndUploadMacro,
["test.json"],
CodeScanning,
undefined,
undefined,
);
test(
"matching files for Code Scanning with directory path",
findAndUploadMacro,
["test.sarif"],
CodeScanning,
undefined,
{
statusReport: {},
sarifID: "some-id",
},
);
test(
"matching files for Code Scanning with file path",
findAndUploadMacro,
["test.sarif"],
CodeScanning,
(tempDir) => path.join(tempDir, "test.sarif"),
{
statusReport: {},
sarifID: "some-id",
},
);
interface UploadSarifExpectedResult {
uploadResult?: UploadResult;
expectedFiles?: string[];
}
const uploadSarifMacro = test.macro({
exec: async (
t: ExecutionContext<unknown>,
sarifFiles: string[],
sarifPath: (tempDir: string) => string = (tempDir) => tempDir,
expectedResult: Partial<Record<AnalysisKind, UploadSarifExpectedResult>>,
) => {
await util.withTmpDir(async (tempDir) => {
const logger = getRunnerLogger(true);
const testPath = sarifPath(tempDir);
const features = createFeatures([]);
const toFullPath = (filename: string) => path.join(tempDir, filename);
const uploadSpecifiedFiles = sinon.stub(
uploadLib,
"uploadSpecifiedFiles",
);
for (const analysisKind of Object.values(AnalysisKind)) {
uploadSpecifiedFiles
.withArgs(
sinon.match.any,
sinon.match.any,
sinon.match.any,
features,
logger,
analysisKind === AnalysisKind.CodeScanning
? CodeScanning
: CodeQuality,
)
.resolves(expectedResult[analysisKind as AnalysisKind]?.uploadResult);
}
const fullSarifPaths = sarifFiles.map(toFullPath);
for (const sarifFile of fullSarifPaths) {
fs.writeFileSync(sarifFile, "");
}
const stats = fs.statSync(testPath);
const actual = await uploadSarif(logger, features, testPath, stats, "");
for (const analysisKind of Object.values(AnalysisKind)) {
const analyisKindResult = expectedResult[analysisKind];
if (analyisKindResult) {
// We are expecting a result for this analysis kind, check that we have it.
t.deepEqual(actual[analysisKind], analyisKindResult.uploadResult);
// Additionally, check that the mocked `uploadSpecifiedFiles` was called with only the file paths
// that we expected it to be called with.
t.assert(
uploadSpecifiedFiles.calledWith(
analyisKindResult.expectedFiles?.map(toFullPath) ??
fullSarifPaths,
sinon.match.any,
sinon.match.any,
features,
logger,
analysisKind === AnalysisKind.CodeScanning
? CodeScanning
: CodeQuality,
),
);
} else {
// Otherwise, we are not expecting a result for this analysis kind. However, note that `undefined`
// is also returned by our mocked `uploadSpecifiedFiles` when there is no expected result for this
// analysis kind.
t.is(actual[analysisKind], undefined);
// Therefore, we also check that the mocked `uploadSpecifiedFiles` was not called for this analysis kind.
t.assert(
!uploadSpecifiedFiles.calledWith(
sinon.match.any,
sinon.match.any,
sinon.match.any,
features,
logger,
analysisKind === AnalysisKind.CodeScanning
? CodeScanning
: CodeQuality,
),
`uploadSpecifiedFiles was called for ${analysisKind}, but should not have been.`,
);
}
}
});
},
title: (providedTitle = "") => `uploadSarif - ${providedTitle}`,
});
test(
"SARIF file",
uploadSarifMacro,
["test.sarif"],
(tempDir) => path.join(tempDir, "test.sarif"),
{
"code-scanning": {
uploadResult: {
statusReport: {},
sarifID: "code-scanning-sarif",
},
},
},
);
test(
"JSON file",
uploadSarifMacro,
["test.json"],
(tempDir) => path.join(tempDir, "test.json"),
{
"code-scanning": {
uploadResult: {
statusReport: {},
sarifID: "code-scanning-sarif",
},
},
},
);
test(
"Code Scanning files",
uploadSarifMacro,
["test.json", "test.sarif"],
undefined,
{
"code-scanning": {
uploadResult: {
statusReport: {},
sarifID: "code-scanning-sarif",
},
expectedFiles: ["test.sarif"],
},
},
);
test(
"Code Quality file",
uploadSarifMacro,
["test.quality.sarif"],
(tempDir) => path.join(tempDir, "test.quality.sarif"),
{
"code-quality": {
uploadResult: {
statusReport: {},
sarifID: "code-quality-sarif",
},
},
},
);
test(
"Mixed files",
uploadSarifMacro,
["test.sarif", "test.quality.sarif"],
undefined,
{
"code-scanning": {
uploadResult: {
statusReport: {},
sarifID: "code-scanning-sarif",
},
expectedFiles: ["test.sarif"],
},
"code-quality": {
uploadResult: {
statusReport: {},
sarifID: "code-quality-sarif",
},
expectedFiles: ["test.quality.sarif"],
},
},
);

115
src/upload-sarif.ts Normal file
View File

@@ -0,0 +1,115 @@
import * as fs from "fs";
import * as core from "@actions/core";
import * as actionsUtil from "./actions-util";
import * as analyses from "./analyses";
import { FeatureEnablement } from "./feature-flags";
import { Logger } from "./logging";
import * as upload_lib from "./upload-lib";
/**
* Searches for SARIF files for the given `analysis` in the given `sarifPath`.
* If any are found, then they are uploaded to the appropriate endpoint for the given `analysis`.
*
* @param logger The logger to use.
* @param features Information about FFs.
* @param sarifPath The path to a SARIF file or directory containing SARIF files.
* @param pathStats Information about `sarifPath`.
* @param checkoutPath The checkout path.
* @param analysis The configuration of the analysis we should upload SARIF files for.
* @param category The SARIF category to use for the upload.
* @returns The result of uploading the SARIF file(s) or `undefined` if there are none.
*/
export async function findAndUpload(
logger: Logger,
features: FeatureEnablement,
sarifPath: string,
pathStats: fs.Stats,
checkoutPath: string,
analysis: analyses.AnalysisConfig,
category?: string,
): Promise<upload_lib.UploadResult | undefined> {
const sarifFiles: string[] | undefined = upload_lib.getSarifFilePaths(
sarifPath,
analysis,
pathStats,
);
if (sarifFiles.length !== 0) {
return await upload_lib.uploadSpecifiedFiles(
sarifFiles,
checkoutPath,
category,
features,
logger,
analysis,
);
}
return undefined;
}
export interface UploadSarifResult {
analysis: analyses.AnalysisKind;
id: string;
}
export type UploadSarifResults = Partial<
Record<analyses.AnalysisKind, upload_lib.UploadResult>
>;
export async function uploadSarif(
logger: Logger,
features: FeatureEnablement,
sarifPath: string,
pathStats: fs.Stats,
checkoutPath: string,
category?: string,
): Promise<UploadSarifResults> {
const uploadResults: UploadSarifResults = {};
const uploadResult = await findAndUpload(
logger,
features,
sarifPath,
pathStats,
checkoutPath,
analyses.CodeScanning,
category,
);
if (uploadResult !== undefined) {
core.setOutput("sarif-id", uploadResult.sarifID);
uploadResults[analyses.AnalysisKind.CodeScanning] = uploadResult;
}
// If there are `.quality.sarif` files in `sarifPath`, then upload those to the code quality service.
if (
pathStats.isDirectory() ||
(pathStats.isFile() && analyses.CodeQuality.sarifPredicate(sarifPath))
) {
const qualityUploadResult = await findAndUpload(
logger,
features,
sarifPath,
pathStats,
checkoutPath,
analyses.CodeQuality,
actionsUtil.fixCodeQualityCategory(logger, category),
);
if (qualityUploadResult !== undefined) {
uploadResults[analyses.AnalysisKind.CodeQuality] = qualityUploadResult;
}
}
return uploadResults;
}
export function uploadResultsToSarifIds(
uploadResults: UploadSarifResults,
): Partial<Record<analyses.AnalysisKind, string>> {
const result = {};
for (const uploadResult of Object.keys(uploadResults)) {
result[uploadResult] = uploadResults[uploadResult].id;
}
return result;
}