Merge pull request #2957 from github/update-v3.29.2-4c57370d0

Merge main into releases/v3
This commit is contained in:
Michael B. Gale 2025-06-30 14:00:45 +01:00 committed by GitHub
commit 181d5eefc2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 601 additions and 122 deletions

View file

@ -64,37 +64,54 @@ jobs:
with:
output: ${{ runner.temp }}/results
upload-database: false
- name: Upload SARIF
- name: Upload security SARIF
uses: actions/upload-artifact@v4
with:
name: config-export-${{ matrix.os }}-${{ matrix.version }}.sarif.json
name: quality-queries-${{ matrix.os }}-${{ matrix.version }}.sarif.json
path: ${{ runner.temp }}/results/javascript.sarif
retention-days: 7
- name: Check config properties appear in SARIF
- name: Upload quality SARIF
uses: actions/upload-artifact@v4
with:
name: quality-queries-${{ matrix.os }}-${{ matrix.version }}.quality.sarif.json
path: ${{ runner.temp }}/results/javascript.quality.sarif
retention-days: 7
- name: Check quality query does not appear in security SARIF
uses: actions/github-script@v7
env:
SARIF_PATH: ${{ runner.temp }}/results/javascript.sarif
EXPECT_PRESENT: 'false'
with:
script: |
const fs = require('fs');
const sarif = JSON.parse(fs.readFileSync(process.env['SARIF_PATH'], 'utf8'));
const run = sarif.runs[0];
const configSummary = run.properties.codeqlConfigSummary;
if (configSummary === undefined) {
core.setFailed('`codeqlConfigSummary` property not found in the SARIF run property bag.');
}
if (configSummary.disableDefaultQueries !== false) {
core.setFailed('`disableDefaultQueries` property incorrect: expected false, got ' +
`${JSON.stringify(configSummary.disableDefaultQueries)}.`);
}
const expectedQueries = [{ type: 'builtinSuite', uses: 'code-quality' }];
// Use JSON.stringify to deep-equal the arrays.
if (JSON.stringify(configSummary.queries) !== JSON.stringify(expectedQueries)) {
core.setFailed(`\`queries\` property incorrect: expected ${JSON.stringify(expectedQueries)}, got ` +
`${JSON.stringify(configSummary.queries)}.`);
}
core.info('Finished config export tests.');
script: ${{ env.CHECK_SCRIPT }}
- name: Check quality query appears in quality SARIF
uses: actions/github-script@v7
env:
SARIF_PATH: ${{ runner.temp }}/results/javascript.quality.sarif
EXPECT_PRESENT: 'true'
with:
script: ${{ env.CHECK_SCRIPT }}
env:
CHECK_SCRIPT: |
const fs = require('fs');
const sarif = JSON.parse(fs.readFileSync(process.env['SARIF_PATH'], 'utf8'));
const expectPresent = JSON.parse(process.env['EXPECT_PRESENT']);
const run = sarif.runs[0];
const extensions = run.tool.extensions;
if (extensions === undefined) {
core.setFailed('`extensions` property not found in the SARIF run property bag.');
}
// ID of a query we want to check the presence for
const targetId = 'js/regex/always-matches';
const found = extensions.find(extension => extension.rules && extension.rules.find(rule => rule.id === targetId));
if (found && expectPresent) {
console.log(`Found rule with id '${targetId}'.`);
} else if (!found && !expectPresent) {
console.log(`Rule with id '${targetId}' was not found.`);
} else {
core.setFailed(`${ found ? "Found" : "Didn't find" } rule ${targetId}`);
}
CODEQL_ACTION_TEST_MODE: true

78
.github/workflows/__upload-quality-sarif.yml generated vendored Normal file
View file

@ -0,0 +1,78 @@
# Warning: This file is generated automatically, and should not be modified.
# Instead, please modify the template in the pr-checks directory and run:
# (cd pr-checks; pip install ruamel.yaml@0.17.31 && python3 sync.py)
# to regenerate this file.
name: 'PR Check - Upload-sarif: code quality endpoint'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GO111MODULE: auto
on:
push:
branches:
- main
- releases/v*
pull_request:
types:
- opened
- synchronize
- reopened
- ready_for_review
schedule:
- cron: '0 5 * * *'
workflow_dispatch: {}
jobs:
upload-quality-sarif:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
version: default
- os: macos-latest
version: default
- os: windows-latest
version: default
name: 'Upload-sarif: code quality endpoint'
permissions:
contents: read
security-events: read
timeout-minutes: 45
runs-on: ${{ matrix.os }}
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Prepare test
id: prepare-test
uses: ./.github/actions/prepare-test
with:
version: ${{ matrix.version }}
use-all-platform-bundle: 'false'
setup-kotlin: 'true'
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: '>=1.21.0'
cache: false
- uses: ./../action/init
with:
tools: ${{ steps.prepare-test.outputs.tools-url }}
languages: cpp,csharp,java,javascript,python
config-file: ${{ github.repository }}/tests/multi-language-repo/.github/codeql/custom-queries.yml@${{
github.sha }}
quality-queries: code-quality
- name: Build code
shell: bash
run: ./build.sh
# Generate some SARIF we can upload with the upload-sarif step
- uses: ./../action/analyze
with:
ref: refs/heads/main
sha: 5e235361806c361d4d3f8859e3c897658025a9a2
upload: never
- uses: ./../action/upload-sarif
with:
ref: refs/heads/main
sha: 5e235361806c361d4d3f8859e3c897658025a9a2
env:
CODEQL_ACTION_TEST_MODE: true

View file

@ -2,6 +2,10 @@
See the [releases page](https://github.com/github/codeql-action/releases) for the relevant changes to the CodeQL CLI and language packs.
## 3.29.2 - 30 Jun 2025
- Experimental: When the `quality-queries` input for the `init` action is provided with an argument, separate `.quality.sarif` files are produced and uploaded for each language with the results of the specified queries. Do not use this in production as it is part of an internal experiment and subject to change at any time. [#2935](https://github.com/github/codeql-action/pull/2935)
## 3.29.1 - 27 Jun 2025
- Fix bug in PR analysis where user-provided `include` query filter fails to exclude non-included queries. [#2938](https://github.com/github/codeql-action/pull/2938)

6
lib/analyze-action.js generated
View file

@ -216,8 +216,12 @@ async function run() {
core.setOutput("sarif-output", path_1.default.resolve(outputDir));
const uploadInput = actionsUtil.getOptionalInput("upload");
if (runStats && actionsUtil.getUploadValue(uploadInput) === "always") {
uploadResult = await uploadLib.uploadFiles(outputDir, actionsUtil.getRequiredInput("checkout_path"), actionsUtil.getOptionalInput("category"), features, logger);
uploadResult = await uploadLib.uploadFiles(outputDir, actionsUtil.getRequiredInput("checkout_path"), actionsUtil.getOptionalInput("category"), features, logger, uploadLib.CodeScanningTarget);
core.setOutput("sarif-id", uploadResult.sarifID);
if (config.augmentationProperties.qualityQueriesInput !== undefined) {
const qualityUploadResult = await uploadLib.uploadFiles(outputDir, actionsUtil.getRequiredInput("checkout_path"), actionsUtil.getOptionalInput("category"), features, logger, uploadLib.CodeQualityTarget);
core.setOutput("quality-sarif-id", qualityUploadResult.sarifID);
}
}
else {
logger.info("Not uploading results");

File diff suppressed because one or more lines are too long

42
lib/analyze.js generated
View file

@ -36,10 +36,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.exportedForTesting = exports.CodeQLAnalysisError = void 0;
exports.exportedForTesting = exports.defaultSuites = exports.CodeQLAnalysisError = void 0;
exports.runExtraction = runExtraction;
exports.dbIsFinalized = dbIsFinalized;
exports.setupDiffInformedQueryRun = setupDiffInformedQueryRun;
exports.resolveQuerySuiteAlias = resolveQuerySuiteAlias;
exports.runQueries = runQueries;
exports.runFinalize = runFinalize;
exports.warnIfGoInstalledAfterInit = warnIfGoInstalledAfterInit;
@ -385,6 +386,28 @@ extensions:
(0, diff_informed_analysis_utils_1.writeDiffRangesJsonFile)(logger, ranges);
return diffRangeDir;
}
// A set of default query suite names that are understood by the CLI.
exports.defaultSuites = new Set([
"security-experimental",
"security-extended",
"security-and-quality",
"code-quality",
"code-scanning",
]);
/**
* If `maybeSuite` is the name of a default query suite, it is resolved into the corresponding
* query suite name for the given `language`. Otherwise, `maybeSuite` is returned as is.
*
* @param language The language for which to resolve the default query suite name.
* @param maybeSuite The string that potentially contains the name of a default query suite.
* @returns Returns the resolved query suite name, or the unmodified input.
*/
function resolveQuerySuiteAlias(language, maybeSuite) {
if (exports.defaultSuites.has(maybeSuite)) {
return `${language}-${maybeSuite}.qls`;
}
return maybeSuite;
}
// Runs queries and creates sarif files in the given folder
async function runQueries(sarifFolder, memoryFlag, addSnippetsFlag, threadsFlag, cleanupLevel, diffRangePackDir, automationDetailsId, config, logger, features) {
const statusReport = {};
@ -404,6 +427,14 @@ async function runQueries(sarifFolder, memoryFlag, addSnippetsFlag, threadsFlag,
for (const language of config.languages) {
try {
const sarifFile = path.join(sarifFolder, `${language}.sarif`);
const queries = [];
if (config.augmentationProperties.qualityQueriesInput !== undefined) {
queries.push(path.join(util.getCodeQLDatabasePath(config, language), "temp", "config-queries.qls"));
for (const qualityQuery of config.augmentationProperties
.qualityQueriesInput) {
queries.push(resolveQuerySuiteAlias(language, qualityQuery.uses));
}
}
// The work needed to generate the query suites
// is done in the CLI. We just need to make a single
// call to run all the queries for each language and
@ -411,7 +442,7 @@ async function runQueries(sarifFolder, memoryFlag, addSnippetsFlag, threadsFlag,
logger.startGroup(`Running queries for ${language}`);
const startTimeRunQueries = new Date().getTime();
const databasePath = util.getCodeQLDatabasePath(config, language);
await codeql.databaseRunQueries(databasePath, queryFlags);
await codeql.databaseRunQueries(databasePath, queryFlags, queries);
logger.debug(`Finished running queries for ${language}.`);
// TODO should not be using `builtin` here. We should be using `all` instead.
// The status report does not support `all` yet.
@ -420,6 +451,13 @@ async function runQueries(sarifFolder, memoryFlag, addSnippetsFlag, threadsFlag,
logger.startGroup(`Interpreting results for ${language}`);
const startTimeInterpretResults = new Date();
const analysisSummary = await runInterpretResults(language, undefined, sarifFile, config.debugMode);
if (config.augmentationProperties.qualityQueriesInput !== undefined) {
logger.info(`Interpreting quality results for ${language}`);
const qualitySarifFile = path.join(sarifFolder, `${language}.quality.sarif`);
const qualityAnalysisSummary = await runInterpretResults(language, config.augmentationProperties.qualityQueriesInput.map((i) => resolveQuerySuiteAlias(language, i.uses)), qualitySarifFile, config.debugMode);
// TODO: move
logger.info(qualityAnalysisSummary);
}
const endTimeInterpretResults = new Date();
statusReport[`interpret_results_${language}_duration_ms`] =
endTimeInterpretResults.getTime() - startTimeInterpretResults.getTime();

File diff suppressed because one or more lines are too long

13
lib/analyze.test.js generated
View file

@ -313,4 +313,17 @@ function runGetDiffRanges(changes, patch) {
const diffRanges = runGetDiffRanges(2, ["@@ 30 +50,2 @@", "+1", "+2"]);
t.deepEqual(diffRanges, undefined);
});
(0, ava_1.default)("resolveQuerySuiteAlias", (t) => {
// default query suite names should resolve to something language-specific ending in `.qls`.
for (const suite of analyze_1.defaultSuites) {
const resolved = (0, analyze_1.resolveQuerySuiteAlias)(languages_1.Language.go, suite);
t.assert(resolved.endsWith(".qls"), "Resolved default suite doesn't end in .qls");
t.assert(resolved.indexOf(languages_1.Language.go) >= 0, "Resolved default suite doesn't contain language name");
}
// other inputs should be returned unchanged
const names = ["foo", "bar", "codeql/go-queries@1.0"];
for (const name of names) {
t.deepEqual((0, analyze_1.resolveQuerySuiteAlias)(languages_1.Language.go, name), name);
}
});
//# sourceMappingURL=analyze.test.js.map

File diff suppressed because one or more lines are too long

11
lib/codeql.js generated
View file

@ -450,7 +450,7 @@ async function getCodeQLForCmd(cmd, checkVersion) {
throw new Error(`Unexpected output from codeql resolve build-environment: ${e} in\n${output}`);
}
},
async databaseRunQueries(databasePath, flags) {
async databaseRunQueries(databasePath, flags, queries = []) {
const codeqlArgs = [
"database",
"run-queries",
@ -459,6 +459,7 @@ async function getCodeQLForCmd(cmd, checkVersion) {
"--intra-layer-parallelism",
"--min-disk-free=1024", // Try to leave at least 1GB free
"-v",
...queries,
...getExtraOptionsFromEnv(["database", "run-queries"], {
ignoringOptions: ["--expect-discarded-cache"],
}),
@ -759,14 +760,12 @@ async function generateCodeScanningConfig(config, logger) {
// make a copy so we can modify it
const augmentedConfig = (0, util_1.cloneObject)(config.originalUserInput);
// Inject the queries from the input
if (config.augmentationProperties.queriesInput ||
config.augmentationProperties.qualityQueriesInput) {
const queryInputs = (config.augmentationProperties.queriesInput || []).concat(config.augmentationProperties.qualityQueriesInput || []);
if (config.augmentationProperties.queriesInput) {
if (config.augmentationProperties.queriesInputCombines) {
augmentedConfig.queries = (augmentedConfig.queries || []).concat(queryInputs);
augmentedConfig.queries = (augmentedConfig.queries || []).concat(config.augmentationProperties.queriesInput);
}
else {
augmentedConfig.queries = queryInputs;
augmentedConfig.queries = config.augmentationProperties.queriesInput;
}
}
if (augmentedConfig.queries?.length === 0) {

File diff suppressed because one or more lines are too long

View file

@ -87,7 +87,7 @@ async function maybeUploadFailedSarif(config, repositoryNwo, features, logger) {
await codeql.databaseExportDiagnostics(databasePath, sarifFile, category);
}
logger.info(`Uploading failed SARIF file ${sarifFile}`);
const uploadResult = await uploadLib.uploadFiles(sarifFile, checkoutPath, category, features, logger);
const uploadResult = await uploadLib.uploadFiles(sarifFile, checkoutPath, category, features, logger, uploadLib.CodeScanningTarget);
await uploadLib.waitForProcessing(repositoryNwo, uploadResult.sarifID, logger, { isUnsuccessfulExecution: true });
return uploadResult
? { ...uploadResult.statusReport, sarifID: uploadResult.sarifID }

File diff suppressed because one or more lines are too long

56
lib/upload-lib.js generated
View file

@ -36,14 +36,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.InvalidSarifUploadError = void 0;
exports.InvalidSarifUploadError = exports.CodeQualityTarget = exports.CodeScanningTarget = exports.SARIF_UPLOAD_ENDPOINT = void 0;
exports.shouldShowCombineSarifFilesDeprecationWarning = shouldShowCombineSarifFilesDeprecationWarning;
exports.populateRunAutomationDetails = populateRunAutomationDetails;
exports.findSarifFilesInDir = findSarifFilesInDir;
exports.getSarifFilePaths = getSarifFilePaths;
exports.readSarifFile = readSarifFile;
exports.validateSarifFileSchema = validateSarifFileSchema;
exports.buildPayload = buildPayload;
exports.uploadFiles = uploadFiles;
exports.uploadSpecifiedFiles = uploadSpecifiedFiles;
exports.waitForProcessing = waitForProcessing;
exports.shouldConsiderConfigurationError = shouldConsiderConfigurationError;
exports.shouldConsiderInvalidRequest = shouldConsiderInvalidRequest;
@ -234,9 +236,15 @@ function getAutomationID(category, analysis_key, environment) {
}
return api.computeAutomationID(analysis_key, environment);
}
// Enumerates API endpoints that accept SARIF files.
var SARIF_UPLOAD_ENDPOINT;
(function (SARIF_UPLOAD_ENDPOINT) {
SARIF_UPLOAD_ENDPOINT["CODE_SCANNING"] = "PUT /repos/:owner/:repo/code-scanning/analysis";
SARIF_UPLOAD_ENDPOINT["CODE_QUALITY"] = "PUT /repos/:owner/:repo/code-quality/analysis";
})(SARIF_UPLOAD_ENDPOINT || (exports.SARIF_UPLOAD_ENDPOINT = SARIF_UPLOAD_ENDPOINT = {}));
// Upload the given payload.
// If the request fails then this will retry a small number of times.
async function uploadPayload(payload, repositoryNwo, logger) {
async function uploadPayload(payload, repositoryNwo, logger, target) {
logger.info("Uploading results");
// If in test mode we don't want to upload the results
if (util.isInTestMode()) {
@ -248,7 +256,7 @@ async function uploadPayload(payload, repositoryNwo, logger) {
}
const client = api.getApiClient();
try {
const response = await client.request("PUT /repos/:owner/:repo/code-scanning/analysis", {
const response = await client.request(target, {
owner: repositoryNwo.owner,
repo: repositoryNwo.repo,
data: payload,
@ -276,12 +284,12 @@ async function uploadPayload(payload, repositoryNwo, logger) {
}
// Recursively walks a directory and returns all SARIF files it finds.
// Does not follow symlinks.
function findSarifFilesInDir(sarifPath) {
function findSarifFilesInDir(sarifPath, isSarif) {
const sarifFiles = [];
const walkSarifFiles = (dir) => {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isFile() && entry.name.endsWith(".sarif")) {
if (entry.isFile() && isSarif(entry.name)) {
sarifFiles.push(path.resolve(dir, entry.name));
}
else if (entry.isDirectory()) {
@ -292,14 +300,14 @@ function findSarifFilesInDir(sarifPath) {
walkSarifFiles(sarifPath);
return sarifFiles;
}
function getSarifFilePaths(sarifPath) {
function getSarifFilePaths(sarifPath, isSarif) {
if (!fs.existsSync(sarifPath)) {
// This is always a configuration error, even for first-party runs.
throw new util_1.ConfigurationError(`Path does not exist: ${sarifPath}`);
}
let sarifFiles;
if (fs.lstatSync(sarifPath).isDirectory()) {
sarifFiles = findSarifFilesInDir(sarifPath);
sarifFiles = findSarifFilesInDir(sarifPath, isSarif);
if (sarifFiles.length === 0) {
// This is always a configuration error, even for first-party runs.
throw new util_1.ConfigurationError(`No SARIF files found to upload in "${sarifPath}".`);
@ -409,13 +417,33 @@ function buildPayload(commitOid, ref, analysisKey, analysisName, zippedSarif, wo
}
return payloadObj;
}
// Represents the Code Scanning upload target.
exports.CodeScanningTarget = {
name: "code scanning",
target: SARIF_UPLOAD_ENDPOINT.CODE_SCANNING,
sarifPredicate: (name) => name.endsWith(".sarif") && !exports.CodeQualityTarget.sarifPredicate(name),
sentinelPrefix: "CODEQL_UPLOAD_SARIF_",
};
// Represents the Code Quality upload target.
exports.CodeQualityTarget = {
name: "code quality",
target: SARIF_UPLOAD_ENDPOINT.CODE_QUALITY,
sarifPredicate: (name) => name.endsWith(".quality.sarif"),
sentinelPrefix: "CODEQL_UPLOAD_QUALITY_SARIF_",
};
/**
* Uploads a single SARIF file or a directory of SARIF files depending on what `inputSarifPath` refers
* to.
*/
async function uploadFiles(inputSarifPath, checkoutPath, category, features, logger) {
const sarifPaths = getSarifFilePaths(inputSarifPath);
logger.startGroup("Uploading results");
async function uploadFiles(inputSarifPath, checkoutPath, category, features, logger, uploadTarget) {
const sarifPaths = getSarifFilePaths(inputSarifPath, uploadTarget.sarifPredicate);
return uploadSpecifiedFiles(sarifPaths, checkoutPath, category, features, logger, uploadTarget);
}
/**
* Uploads the given array of SARIF files.
*/
async function uploadSpecifiedFiles(sarifPaths, checkoutPath, category, features, logger, uploadTarget = exports.CodeScanningTarget) {
logger.startGroup(`Uploading ${uploadTarget.name} results`);
logger.info(`Processing sarif files: ${JSON.stringify(sarifPaths)}`);
const gitHubVersion = await (0, api_client_1.getGitHubVersion)();
let sarif;
@ -439,7 +467,7 @@ async function uploadFiles(inputSarifPath, checkoutPath, category, features, log
sarif = populateRunAutomationDetails(sarif, category, analysisKey, environment);
const toolNames = util.getToolNames(sarif);
logger.debug(`Validating that each SARIF run has a unique category`);
validateUniqueCategory(sarif);
validateUniqueCategory(sarif, uploadTarget.sentinelPrefix);
logger.debug(`Serializing SARIF for upload`);
const sarifPayload = JSON.stringify(sarif);
logger.debug(`Compressing serialized SARIF`);
@ -454,7 +482,7 @@ async function uploadFiles(inputSarifPath, checkoutPath, category, features, log
const numResultInSarif = countResultsInSarif(sarifPayload);
logger.debug(`Number of results in upload: ${numResultInSarif}`);
// Make the upload
const sarifID = await uploadPayload(payload, (0, repository_1.getRepositoryNwo)(), logger);
const sarifID = await uploadPayload(payload, (0, repository_1.getRepositoryNwo)(), logger, uploadTarget.target);
logger.endGroup();
return {
statusReport: {
@ -588,7 +616,7 @@ function handleProcessingResultForUnsuccessfulExecution(response, status, logger
util.assertNever(status);
}
}
function validateUniqueCategory(sarif) {
function validateUniqueCategory(sarif, sentinelPrefix = exports.CodeScanningTarget.sentinelPrefix) {
// duplicate categories are allowed in the same sarif file
// but not across multiple sarif files
const categories = {};
@ -599,7 +627,7 @@ function validateUniqueCategory(sarif) {
categories[category] = { id, tool };
}
for (const [category, { id, tool }] of Object.entries(categories)) {
const sentinelEnvVar = `CODEQL_UPLOAD_SARIF_${category}`;
const sentinelEnvVar = `${sentinelPrefix}${category}`;
if (process.env[sentinelEnvVar]) {
// This is always a configuration error, even for first-party runs.
throw new util_1.ConfigurationError("Aborting upload: only one run of the codeql/analyze or codeql/upload-sarif actions is allowed per job per tool/category. " +

File diff suppressed because one or more lines are too long

14
lib/upload-lib.test.js generated
View file

@ -91,13 +91,21 @@ ava_1.default.beforeEach(() => {
fs.mkdirSync(path.join(tmpDir, "dir3"));
fs.symlinkSync(tmpDir, path.join(tmpDir, "dir3", "symlink1"), "dir");
fs.symlinkSync(path.join(tmpDir, "a.sarif"), path.join(tmpDir, "dir3", "symlink2.sarif"), "file");
const sarifFiles = uploadLib.findSarifFilesInDir(tmpDir);
// add some `.quality.sarif` files that should be ignored, unless we look for them specifically
fs.writeFileSync(path.join(tmpDir, "a.quality.sarif"), "");
fs.writeFileSync(path.join(tmpDir, "dir1", "b.quality.sarif"), "");
const sarifFiles = uploadLib.findSarifFilesInDir(tmpDir, uploadLib.CodeScanningTarget.sarifPredicate);
t.deepEqual(sarifFiles, [
path.join(tmpDir, "a.sarif"),
path.join(tmpDir, "b.sarif"),
path.join(tmpDir, "dir1", "d.sarif"),
path.join(tmpDir, "dir1", "dir2", "e.sarif"),
]);
const qualitySarifFiles = uploadLib.findSarifFilesInDir(tmpDir, uploadLib.CodeQualityTarget.sarifPredicate);
t.deepEqual(qualitySarifFiles, [
path.join(tmpDir, "a.quality.sarif"),
path.join(tmpDir, "dir1", "b.quality.sarif"),
]);
});
});
(0, ava_1.default)("populateRunAutomationDetails", (t) => {
@ -194,6 +202,10 @@ ava_1.default.beforeEach(() => {
t.throws(() => uploadLib.validateUniqueCategory(sarif1));
t.throws(() => uploadLib.validateUniqueCategory(sarif2));
});
(0, ava_1.default)("validateUniqueCategory with different prefixes", (t) => {
t.notThrows(() => uploadLib.validateUniqueCategory(createMockSarif()));
t.notThrows(() => uploadLib.validateUniqueCategory(createMockSarif(), uploadLib.CodeQualityTarget.sentinelPrefix));
});
(0, ava_1.default)("accept results with invalid artifactLocation.uri value", (t) => {
const loggedMessages = [];
const mockLogger = {

File diff suppressed because one or more lines are too long

View file

@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const fs = __importStar(require("fs"));
const core = __importStar(require("@actions/core"));
const actionsUtil = __importStar(require("./actions-util"));
const actions_util_1 = require("./actions-util");
@ -68,14 +69,28 @@ async function run() {
await (0, status_report_1.sendStatusReport)(startingStatusReportBase);
}
try {
const uploadResult = await upload_lib.uploadFiles(actionsUtil.getRequiredInput("sarif_file"), actionsUtil.getRequiredInput("checkout_path"), actionsUtil.getOptionalInput("category"), features, logger);
const sarifPath = actionsUtil.getRequiredInput("sarif_file");
const checkoutPath = actionsUtil.getRequiredInput("checkout_path");
const category = actionsUtil.getOptionalInput("category");
const uploadResult = await upload_lib.uploadFiles(sarifPath, checkoutPath, category, features, logger, upload_lib.CodeScanningTarget);
core.setOutput("sarif-id", uploadResult.sarifID);
// If there are `.quality.sarif` files in `sarifPath`, then upload those to the code quality service.
// Code quality can currently only be enabled on top of security, so we'd currently always expect to
// have a directory for the results here.
if (fs.lstatSync(sarifPath).isDirectory()) {
const qualitySarifFiles = upload_lib.findSarifFilesInDir(sarifPath, upload_lib.CodeQualityTarget.sarifPredicate);
if (qualitySarifFiles.length !== 0) {
await upload_lib.uploadSpecifiedFiles(qualitySarifFiles, checkoutPath, category, features, logger, upload_lib.CodeQualityTarget);
}
}
// We don't upload results in test mode, so don't wait for processing
if ((0, util_1.isInTestMode)()) {
core.debug("In test mode. Waiting for processing is disabled.");
}
else if (actionsUtil.getRequiredInput("wait-for-processing") === "true") {
await upload_lib.waitForProcessing((0, repository_1.getRepositoryNwo)(), uploadResult.sarifID, logger);
// The code quality service does not currently have an endpoint to wait for SARIF processing,
// so we can't wait for that here.
}
await sendSuccessStatusReport(startedAt, uploadResult.statusReport, logger);
}

View file

@ -1 +1 @@
{"version":3,"file":"upload-sarif-action.js","sourceRoot":"","sources":["../src/upload-sarif-action.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"upload-sarif-action.js","sourceRoot":"","sources":["../src/upload-sarif-action.ts"],"names":[],"mappings":"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"}

2
node_modules/.package-lock.json generated vendored
View file

@ -1,6 +1,6 @@
{
"name": "codeql",
"version": "3.29.1",
"version": "3.29.2",
"lockfileVersion": 3,
"requires": true,
"packages": {

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "codeql",
"version": "3.29.1",
"version": "3.29.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "codeql",
"version": "3.29.1",
"version": "3.29.2",
"license": "MIT",
"dependencies": {
"@actions/artifact": "^2.3.1",

View file

@ -1,6 +1,6 @@
{
"name": "codeql",
"version": "3.29.1",
"version": "3.29.2",
"private": true,
"description": "CodeQL action",
"scripts": {

View file

@ -1,6 +1,30 @@
name: "Quality queries input"
description: "Tests that queries specified in the quality-queries input are used."
versions: ["linked", "nightly-latest"]
env:
CHECK_SCRIPT: |
const fs = require('fs');
const sarif = JSON.parse(fs.readFileSync(process.env['SARIF_PATH'], 'utf8'));
const expectPresent = JSON.parse(process.env['EXPECT_PRESENT']);
const run = sarif.runs[0];
const extensions = run.tool.extensions;
if (extensions === undefined) {
core.setFailed('`extensions` property not found in the SARIF run property bag.');
}
// ID of a query we want to check the presence for
const targetId = 'js/regex/always-matches';
const found = extensions.find(extension => extension.rules && extension.rules.find(rule => rule.id === targetId));
if (found && expectPresent) {
console.log(`Found rule with id '${targetId}'.`);
} else if (!found && !expectPresent) {
console.log(`Rule with id '${targetId}' was not found.`);
} else {
core.setFailed(`${ found ? "Found" : "Didn't find" } rule ${targetId}`);
}
steps:
- uses: ./../action/init
with:
@ -11,35 +35,29 @@ steps:
with:
output: "${{ runner.temp }}/results"
upload-database: false
- name: Upload SARIF
- name: Upload security SARIF
uses: actions/upload-artifact@v4
with:
name: config-export-${{ matrix.os }}-${{ matrix.version }}.sarif.json
name: quality-queries-${{ matrix.os }}-${{ matrix.version }}.sarif.json
path: "${{ runner.temp }}/results/javascript.sarif"
retention-days: 7
- name: Check config properties appear in SARIF
- name: Upload quality SARIF
uses: actions/upload-artifact@v4
with:
name: quality-queries-${{ matrix.os }}-${{ matrix.version }}.quality.sarif.json
path: "${{ runner.temp }}/results/javascript.quality.sarif"
retention-days: 7
- name: Check quality query does not appear in security SARIF
uses: actions/github-script@v7
env:
SARIF_PATH: "${{ runner.temp }}/results/javascript.sarif"
EXPECT_PRESENT: "false"
with:
script: |
const fs = require('fs');
const sarif = JSON.parse(fs.readFileSync(process.env['SARIF_PATH'], 'utf8'));
const run = sarif.runs[0];
const configSummary = run.properties.codeqlConfigSummary;
if (configSummary === undefined) {
core.setFailed('`codeqlConfigSummary` property not found in the SARIF run property bag.');
}
if (configSummary.disableDefaultQueries !== false) {
core.setFailed('`disableDefaultQueries` property incorrect: expected false, got ' +
`${JSON.stringify(configSummary.disableDefaultQueries)}.`);
}
const expectedQueries = [{ type: 'builtinSuite', uses: 'code-quality' }];
// Use JSON.stringify to deep-equal the arrays.
if (JSON.stringify(configSummary.queries) !== JSON.stringify(expectedQueries)) {
core.setFailed(`\`queries\` property incorrect: expected ${JSON.stringify(expectedQueries)}, got ` +
`${JSON.stringify(configSummary.queries)}.`);
}
core.info('Finished config export tests.');
script: ${{ env.CHECK_SCRIPT }}
- name: Check quality query appears in quality SARIF
uses: actions/github-script@v7
env:
SARIF_PATH: "${{ runner.temp }}/results/javascript.quality.sarif"
EXPECT_PRESENT: "true"
with:
script: ${{ env.CHECK_SCRIPT }}

View file

@ -0,0 +1,24 @@
name: "Upload-sarif: code quality endpoint"
description: "Checks that uploading SARIFs to the code quality endpoint works"
versions: ["default"]
installGo: "true"
steps:
- uses: ./../action/init
with:
tools: ${{ steps.prepare-test.outputs.tools-url }}
languages: cpp,csharp,java,javascript,python
config-file: ${{ github.repository }}/tests/multi-language-repo/.github/codeql/custom-queries.yml@${{ github.sha }}
quality-queries: code-quality
- name: Build code
shell: bash
run: ./build.sh
# Generate some SARIF we can upload with the upload-sarif step
- uses: ./../action/analyze
with:
ref: 'refs/heads/main'
sha: '5e235361806c361d4d3f8859e3c897658025a9a2'
upload: never
- uses: ./../action/upload-sarif
with:
ref: 'refs/heads/main'
sha: '5e235361806c361d4d3f8859e3c897658025a9a2'

View file

@ -327,8 +327,21 @@ async function run() {
actionsUtil.getOptionalInput("category"),
features,
logger,
uploadLib.CodeScanningTarget,
);
core.setOutput("sarif-id", uploadResult.sarifID);
if (config.augmentationProperties.qualityQueriesInput !== undefined) {
const qualityUploadResult = await uploadLib.uploadFiles(
outputDir,
actionsUtil.getRequiredInput("checkout_path"),
actionsUtil.getOptionalInput("category"),
features,
logger,
uploadLib.CodeQualityTarget,
);
core.setOutput("quality-sarif-id", qualityUploadResult.sarifID);
}
} else {
logger.info("Not uploading results");
}

View file

@ -5,7 +5,12 @@ import test from "ava";
import * as sinon from "sinon";
import * as actionsUtil from "./actions-util";
import { exportedForTesting, runQueries } from "./analyze";
import {
exportedForTesting,
runQueries,
defaultSuites,
resolveQuerySuiteAlias,
} from "./analyze";
import { setCodeQL } from "./codeql";
import { Feature } from "./feature-flags";
import { Language } from "./languages";
@ -320,3 +325,25 @@ test("getDiffRanges: malformed thunk header", async (t) => {
const diffRanges = runGetDiffRanges(2, ["@@ 30 +50,2 @@", "+1", "+2"]);
t.deepEqual(diffRanges, undefined);
});
test("resolveQuerySuiteAlias", (t) => {
// default query suite names should resolve to something language-specific ending in `.qls`.
for (const suite of defaultSuites) {
const resolved = resolveQuerySuiteAlias(Language.go, suite);
t.assert(
resolved.endsWith(".qls"),
"Resolved default suite doesn't end in .qls",
);
t.assert(
resolved.indexOf(Language.go) >= 0,
"Resolved default suite doesn't contain language name",
);
}
// other inputs should be returned unchanged
const names = ["foo", "bar", "codeql/go-queries@1.0"];
for (const name of names) {
t.deepEqual(resolveQuerySuiteAlias(Language.go, name), name);
}
});

View file

@ -561,6 +561,34 @@ extensions:
return diffRangeDir;
}
// A set of default query suite names that are understood by the CLI.
export const defaultSuites: Set<string> = new Set([
"security-experimental",
"security-extended",
"security-and-quality",
"code-quality",
"code-scanning",
]);
/**
* If `maybeSuite` is the name of a default query suite, it is resolved into the corresponding
* query suite name for the given `language`. Otherwise, `maybeSuite` is returned as is.
*
* @param language The language for which to resolve the default query suite name.
* @param maybeSuite The string that potentially contains the name of a default query suite.
* @returns Returns the resolved query suite name, or the unmodified input.
*/
export function resolveQuerySuiteAlias(
language: Language,
maybeSuite: string,
): string {
if (defaultSuites.has(maybeSuite)) {
return `${language}-${maybeSuite}.qls`;
}
return maybeSuite;
}
// Runs queries and creates sarif files in the given folder
export async function runQueries(
sarifFolder: string,
@ -596,6 +624,22 @@ export async function runQueries(
try {
const sarifFile = path.join(sarifFolder, `${language}.sarif`);
const queries: string[] = [];
if (config.augmentationProperties.qualityQueriesInput !== undefined) {
queries.push(
path.join(
util.getCodeQLDatabasePath(config, language),
"temp",
"config-queries.qls",
),
);
for (const qualityQuery of config.augmentationProperties
.qualityQueriesInput) {
queries.push(resolveQuerySuiteAlias(language, qualityQuery.uses));
}
}
// The work needed to generate the query suites
// is done in the CLI. We just need to make a single
// call to run all the queries for each language and
@ -603,7 +647,7 @@ export async function runQueries(
logger.startGroup(`Running queries for ${language}`);
const startTimeRunQueries = new Date().getTime();
const databasePath = util.getCodeQLDatabasePath(config, language);
await codeql.databaseRunQueries(databasePath, queryFlags);
await codeql.databaseRunQueries(databasePath, queryFlags, queries);
logger.debug(`Finished running queries for ${language}.`);
// TODO should not be using `builtin` here. We should be using `all` instead.
// The status report does not support `all` yet.
@ -618,6 +662,24 @@ export async function runQueries(
sarifFile,
config.debugMode,
);
if (config.augmentationProperties.qualityQueriesInput !== undefined) {
logger.info(`Interpreting quality results for ${language}`);
const qualitySarifFile = path.join(
sarifFolder,
`${language}.quality.sarif`,
);
const qualityAnalysisSummary = await runInterpretResults(
language,
config.augmentationProperties.qualityQueriesInput.map((i) =>
resolveQuerySuiteAlias(language, i.uses),
),
qualitySarifFile,
config.debugMode,
);
// TODO: move
logger.info(qualityAnalysisSummary);
}
const endTimeInterpretResults = new Date();
statusReport[`interpret_results_${language}_duration_ms`] =
endTimeInterpretResults.getTime() - startTimeInterpretResults.getTime();

View file

@ -157,9 +157,15 @@ export interface CodeQL {
dbName: string,
): Promise<void>;
/**
* Run 'codeql database run-queries'.
* Run 'codeql database run-queries'. If no `queries` are specified, then the CLI
* will automatically use the `config-queries.qls` (if it exists) or default queries
* for the language.
*/
databaseRunQueries(databasePath: string, flags: string[]): Promise<void>;
databaseRunQueries(
databasePath: string,
flags: string[],
queries?: string[],
): Promise<void>;
/**
* Run 'codeql database interpret-results'.
*/
@ -806,6 +812,7 @@ export async function getCodeQLForCmd(
async databaseRunQueries(
databasePath: string,
flags: string[],
queries: string[] = [],
): Promise<void> {
const codeqlArgs = [
"database",
@ -815,6 +822,7 @@ export async function getCodeQLForCmd(
"--intra-layer-parallelism",
"--min-disk-free=1024", // Try to leave at least 1GB free
"-v",
...queries,
...getExtraOptionsFromEnv(["database", "run-queries"], {
ignoringOptions: ["--expect-discarded-cache"],
}),
@ -1219,20 +1227,13 @@ async function generateCodeScanningConfig(
const augmentedConfig = cloneObject(config.originalUserInput);
// Inject the queries from the input
if (
config.augmentationProperties.queriesInput ||
config.augmentationProperties.qualityQueriesInput
) {
const queryInputs = (
config.augmentationProperties.queriesInput || []
).concat(config.augmentationProperties.qualityQueriesInput || []);
if (config.augmentationProperties.queriesInput) {
if (config.augmentationProperties.queriesInputCombines) {
augmentedConfig.queries = (augmentedConfig.queries || []).concat(
queryInputs,
config.augmentationProperties.queriesInput,
);
} else {
augmentedConfig.queries = queryInputs;
augmentedConfig.queries = config.augmentationProperties.queriesInput;
}
}
if (augmentedConfig.queries?.length === 0) {

View file

@ -104,6 +104,7 @@ async function maybeUploadFailedSarif(
category,
features,
logger,
uploadLib.CodeScanningTarget,
);
await uploadLib.waitForProcessing(
repositoryNwo,

View file

@ -122,7 +122,14 @@ test("finding SARIF files", async (t) => {
"file",
);
const sarifFiles = uploadLib.findSarifFilesInDir(tmpDir);
// add some `.quality.sarif` files that should be ignored, unless we look for them specifically
fs.writeFileSync(path.join(tmpDir, "a.quality.sarif"), "");
fs.writeFileSync(path.join(tmpDir, "dir1", "b.quality.sarif"), "");
const sarifFiles = uploadLib.findSarifFilesInDir(
tmpDir,
uploadLib.CodeScanningTarget.sarifPredicate,
);
t.deepEqual(sarifFiles, [
path.join(tmpDir, "a.sarif"),
@ -130,6 +137,16 @@ test("finding SARIF files", async (t) => {
path.join(tmpDir, "dir1", "d.sarif"),
path.join(tmpDir, "dir1", "dir2", "e.sarif"),
]);
const qualitySarifFiles = uploadLib.findSarifFilesInDir(
tmpDir,
uploadLib.CodeQualityTarget.sarifPredicate,
);
t.deepEqual(qualitySarifFiles, [
path.join(tmpDir, "a.quality.sarif"),
path.join(tmpDir, "dir1", "b.quality.sarif"),
]);
});
});
@ -313,6 +330,16 @@ test("validateUniqueCategory for multiple runs", (t) => {
t.throws(() => uploadLib.validateUniqueCategory(sarif2));
});
test("validateUniqueCategory with different prefixes", (t) => {
t.notThrows(() => uploadLib.validateUniqueCategory(createMockSarif()));
t.notThrows(() =>
uploadLib.validateUniqueCategory(
createMockSarif(),
uploadLib.CodeQualityTarget.sentinelPrefix,
),
);
});
test("accept results with invalid artifactLocation.uri value", (t) => {
const loggedMessages: string[] = [];
const mockLogger = {

View file

@ -306,12 +306,19 @@ function getAutomationID(
return api.computeAutomationID(analysis_key, environment);
}
// Enumerates API endpoints that accept SARIF files.
export enum SARIF_UPLOAD_ENDPOINT {
CODE_SCANNING = "PUT /repos/:owner/:repo/code-scanning/analysis",
CODE_QUALITY = "PUT /repos/:owner/:repo/code-quality/analysis",
}
// Upload the given payload.
// If the request fails then this will retry a small number of times.
async function uploadPayload(
payload: any,
repositoryNwo: RepositoryNwo,
logger: Logger,
target: SARIF_UPLOAD_ENDPOINT,
): Promise<string> {
logger.info("Uploading results");
@ -332,14 +339,11 @@ async function uploadPayload(
const client = api.getApiClient();
try {
const response = await client.request(
"PUT /repos/:owner/:repo/code-scanning/analysis",
{
owner: repositoryNwo.owner,
repo: repositoryNwo.repo,
data: payload,
},
);
const response = await client.request(target, {
owner: repositoryNwo.owner,
repo: repositoryNwo.repo,
data: payload,
});
logger.debug(`response status: ${response.status}`);
logger.info("Successfully uploaded results");
@ -378,12 +382,15 @@ export interface UploadResult {
// Recursively walks a directory and returns all SARIF files it finds.
// Does not follow symlinks.
export function findSarifFilesInDir(sarifPath: string): string[] {
export function findSarifFilesInDir(
sarifPath: string,
isSarif: (name: string) => boolean,
): string[] {
const sarifFiles: string[] = [];
const walkSarifFiles = (dir: string) => {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isFile() && entry.name.endsWith(".sarif")) {
if (entry.isFile() && isSarif(entry.name)) {
sarifFiles.push(path.resolve(dir, entry.name));
} else if (entry.isDirectory()) {
walkSarifFiles(path.resolve(dir, entry.name));
@ -394,7 +401,10 @@ export function findSarifFilesInDir(sarifPath: string): string[] {
return sarifFiles;
}
function getSarifFilePaths(sarifPath: string) {
export function getSarifFilePaths(
sarifPath: string,
isSarif: (name: string) => boolean,
) {
if (!fs.existsSync(sarifPath)) {
// This is always a configuration error, even for first-party runs.
throw new ConfigurationError(`Path does not exist: ${sarifPath}`);
@ -402,7 +412,7 @@ function getSarifFilePaths(sarifPath: string) {
let sarifFiles: string[];
if (fs.lstatSync(sarifPath).isDirectory()) {
sarifFiles = findSarifFilesInDir(sarifPath);
sarifFiles = findSarifFilesInDir(sarifPath, isSarif);
if (sarifFiles.length === 0) {
// This is always a configuration error, even for first-party runs.
throw new ConfigurationError(
@ -567,6 +577,31 @@ export function buildPayload(
return payloadObj;
}
// Represents configurations for different services that we can upload SARIF to.
export interface UploadTarget {
name: string;
target: SARIF_UPLOAD_ENDPOINT;
sarifPredicate: (name: string) => boolean;
sentinelPrefix: string;
}
// Represents the Code Scanning upload target.
export const CodeScanningTarget: UploadTarget = {
name: "code scanning",
target: SARIF_UPLOAD_ENDPOINT.CODE_SCANNING,
sarifPredicate: (name) =>
name.endsWith(".sarif") && !CodeQualityTarget.sarifPredicate(name),
sentinelPrefix: "CODEQL_UPLOAD_SARIF_",
};
// Represents the Code Quality upload target.
export const CodeQualityTarget: UploadTarget = {
name: "code quality",
target: SARIF_UPLOAD_ENDPOINT.CODE_QUALITY,
sarifPredicate: (name) => name.endsWith(".quality.sarif"),
sentinelPrefix: "CODEQL_UPLOAD_QUALITY_SARIF_",
};
/**
* Uploads a single SARIF file or a directory of SARIF files depending on what `inputSarifPath` refers
* to.
@ -577,10 +612,35 @@ export async function uploadFiles(
category: string | undefined,
features: FeatureEnablement,
logger: Logger,
uploadTarget: UploadTarget,
): Promise<UploadResult> {
const sarifPaths = getSarifFilePaths(inputSarifPath);
const sarifPaths = getSarifFilePaths(
inputSarifPath,
uploadTarget.sarifPredicate,
);
logger.startGroup("Uploading results");
return uploadSpecifiedFiles(
sarifPaths,
checkoutPath,
category,
features,
logger,
uploadTarget,
);
}
/**
* Uploads the given array of SARIF files.
*/
export async function uploadSpecifiedFiles(
sarifPaths: string[],
checkoutPath: string,
category: string | undefined,
features: FeatureEnablement,
logger: Logger,
uploadTarget: UploadTarget = CodeScanningTarget,
): Promise<UploadResult> {
logger.startGroup(`Uploading ${uploadTarget.name} results`);
logger.info(`Processing sarif files: ${JSON.stringify(sarifPaths)}`);
const gitHubVersion = await getGitHubVersion();
@ -621,7 +681,7 @@ export async function uploadFiles(
const toolNames = util.getToolNames(sarif);
logger.debug(`Validating that each SARIF run has a unique category`);
validateUniqueCategory(sarif);
validateUniqueCategory(sarif, uploadTarget.sentinelPrefix);
logger.debug(`Serializing SARIF for upload`);
const sarifPayload = JSON.stringify(sarif);
logger.debug(`Compressing serialized SARIF`);
@ -651,7 +711,12 @@ export async function uploadFiles(
logger.debug(`Number of results in upload: ${numResultInSarif}`);
// Make the upload
const sarifID = await uploadPayload(payload, getRepositoryNwo(), logger);
const sarifID = await uploadPayload(
payload,
getRepositoryNwo(),
logger,
uploadTarget.target,
);
logger.endGroup();
@ -833,7 +898,10 @@ function handleProcessingResultForUnsuccessfulExecution(
}
}
export function validateUniqueCategory(sarif: SarifFile): void {
export function validateUniqueCategory(
sarif: SarifFile,
sentinelPrefix: string = CodeScanningTarget.sentinelPrefix,
): void {
// duplicate categories are allowed in the same sarif file
// but not across multiple sarif files
const categories = {} as Record<string, { id?: string; tool?: string }>;
@ -846,7 +914,7 @@ export function validateUniqueCategory(sarif: SarifFile): void {
}
for (const [category, { id, tool }] of Object.entries(categories)) {
const sentinelEnvVar = `CODEQL_UPLOAD_SARIF_${category}`;
const sentinelEnvVar = `${sentinelPrefix}${category}`;
if (process.env[sentinelEnvVar]) {
// This is always a configuration error, even for first-party runs.
throw new ConfigurationError(

View file

@ -1,3 +1,5 @@
import * as fs from "fs";
import * as core from "@actions/core";
import * as actionsUtil from "./actions-util";
@ -83,15 +85,41 @@ async function run() {
}
try {
const sarifPath = actionsUtil.getRequiredInput("sarif_file");
const checkoutPath = actionsUtil.getRequiredInput("checkout_path");
const category = actionsUtil.getOptionalInput("category");
const uploadResult = await upload_lib.uploadFiles(
actionsUtil.getRequiredInput("sarif_file"),
actionsUtil.getRequiredInput("checkout_path"),
actionsUtil.getOptionalInput("category"),
sarifPath,
checkoutPath,
category,
features,
logger,
upload_lib.CodeScanningTarget,
);
core.setOutput("sarif-id", uploadResult.sarifID);
// If there are `.quality.sarif` files in `sarifPath`, then upload those to the code quality service.
// Code quality can currently only be enabled on top of security, so we'd currently always expect to
// have a directory for the results here.
if (fs.lstatSync(sarifPath).isDirectory()) {
const qualitySarifFiles = upload_lib.findSarifFilesInDir(
sarifPath,
upload_lib.CodeQualityTarget.sarifPredicate,
);
if (qualitySarifFiles.length !== 0) {
await upload_lib.uploadSpecifiedFiles(
qualitySarifFiles,
checkoutPath,
category,
features,
logger,
upload_lib.CodeQualityTarget,
);
}
}
// 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.");
@ -101,6 +129,8 @@ async function run() {
uploadResult.sarifID,
logger,
);
// The code quality service does not currently have an endpoint to wait for SARIF processing,
// so we can't wait for that here.
}
await sendSuccessStatusReport(startedAt, uploadResult.statusReport, logger);
} catch (unwrappedError) {