diff --git a/.github/workflows/__upload-quality-sarif.yml b/.github/workflows/__upload-quality-sarif.yml index ca3ffb9881..e2a2701489 100644 --- a/.github/workflows/__upload-quality-sarif.yml +++ b/.github/workflows/__upload-quality-sarif.yml @@ -73,10 +73,8 @@ jobs: - 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 }} - analysis-kinds: code-scanning,code-quality + languages: csharp,java,javascript,python + analysis-kinds: code-quality - name: Build code run: ./build.sh # Generate some SARIF we can upload with the upload-sarif step @@ -86,8 +84,12 @@ jobs: sha: 5e235361806c361d4d3f8859e3c897658025a9a2 upload: never - uses: ./../action/upload-sarif + id: upload-sarif with: 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' + run: exit 1 env: CODEQL_ACTION_TEST_MODE: true diff --git a/lib/upload-sarif-action.js b/lib/upload-sarif-action.js index f603d0aa17..b482d9d3d5 100644 --- a/lib/upload-sarif-action.js +++ b/lib/upload-sarif-action.js @@ -92985,23 +92985,6 @@ function findSarifFilesInDir(sarifPath, isSarif) { walkSarifFiles(sarifPath); return sarifFiles; } -function getSarifFilePaths(sarifPath, isSarif) { - if (!fs14.existsSync(sarifPath)) { - throw new ConfigurationError(`Path does not exist: ${sarifPath}`); - } - let sarifFiles; - if (fs14.lstatSync(sarifPath).isDirectory()) { - sarifFiles = findSarifFilesInDir(sarifPath, isSarif); - if (sarifFiles.length === 0) { - throw new ConfigurationError( - `No SARIF files found to upload in "${sarifPath}".` - ); - } - } else { - sarifFiles = [sarifPath]; - } - return sarifFiles; -} function countResultsInSarif(sarif) { let numResults = 0; const parsedSarif = JSON.parse(sarif); @@ -93097,20 +93080,6 @@ 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 - ); - return uploadSpecifiedFiles( - sarifPaths, - checkoutPath, - category, - features, - logger, - uploadTarget - ); -} async function uploadSpecifiedFiles(sarifPaths, checkoutPath, category, features, logger, uploadTarget) { logger.startGroup(`Uploading ${uploadTarget.name} results`); logger.info(`Processing sarif files: ${JSON.stringify(sarifPaths)}`); @@ -93358,6 +93327,30 @@ function filterAlertsByDiffRange(logger, sarif) { } // src/upload-sarif-action.ts +async function findAndUpload(logger, features, sarifPath, pathStats, checkoutPath, analysis, category) { + let sarifFiles; + if (pathStats.isDirectory()) { + sarifFiles = findSarifFilesInDir( + sarifPath, + analysis.sarifPredicate + ); + } else if (pathStats.isFile() && analysis.sarifPredicate(sarifPath)) { + sarifFiles = [sarifPath]; + } else { + return void 0; + } + if (sarifFiles.length !== 0) { + return await uploadSpecifiedFiles( + sarifFiles, + checkoutPath, + category, + features, + logger, + analysis + ); + } + return void 0; +} async function sendSuccessStatusReport(startedAt, uploadStats, logger) { const statusReportBase = await createStatusReportBase( "upload-sarif" /* UploadSarif */, @@ -93404,41 +93397,59 @@ async function run() { const sarifPath = getRequiredInput("sarif_file"); const checkoutPath = getRequiredInput("checkout_path"); const category = getOptionalInput("category"); - const uploadResult = await uploadFiles( + const pathStats = fs15.lstatSync(sarifPath, { throwIfNoEntry: false }); + if (pathStats === void 0) { + throw new ConfigurationError(`Path does not exist: ${sarifPath}.`); + } + const sarifIds = []; + const uploadResult = await findAndUpload( + logger, + features, sarifPath, + pathStats, checkoutPath, - category, - features, + CodeScanning, + category + ); + if (uploadResult !== void 0) { + core13.setOutput("sarif-id", uploadResult.sarifID); + sarifIds.push({ + analysis: "code-scanning" /* CodeScanning */, + id: uploadResult.sarifID + }); + } + const qualityUploadResult = await findAndUpload( logger, - CodeScanning + features, + sarifPath, + pathStats, + checkoutPath, + CodeQuality, + fixCodeQualityCategory(logger, category) ); - core13.setOutput("sarif-id", uploadResult.sarifID); - if (fs15.lstatSync(sarifPath).isDirectory()) { - const qualitySarifFiles = findSarifFilesInDir( - sarifPath, - CodeQuality.sarifPredicate - ); - if (qualitySarifFiles.length !== 0) { - await uploadSpecifiedFiles( - qualitySarifFiles, - checkoutPath, - fixCodeQualityCategory(logger, category), - features, - logger, - CodeQuality - ); - } + if (qualityUploadResult !== void 0) { + sarifIds.push({ + analysis: "code-quality" /* CodeQuality */, + id: qualityUploadResult.sarifID + }); } + core13.setOutput("sarif-ids", JSON.stringify(sarifIds)); if (isInTestMode()) { core13.debug("In test mode. Waiting for processing is disabled."); } else if (getRequiredInput("wait-for-processing") === "true") { - await waitForProcessing( - getRepositoryNwo(), - uploadResult.sarifID, - logger - ); + if (uploadResult !== void 0) { + await waitForProcessing( + getRepositoryNwo(), + uploadResult.sarifID, + logger + ); + } } - await sendSuccessStatusReport(startedAt, uploadResult.statusReport, logger); + await sendSuccessStatusReport( + startedAt, + uploadResult?.statusReport || {}, + logger + ); } catch (unwrappedError) { const error2 = isThirdPartyAnalysis("upload-sarif" /* UploadSarif */) && unwrappedError instanceof InvalidSarifUploadError ? new ConfigurationError(unwrappedError.message) : wrapError(unwrappedError); const message = error2.message; diff --git a/pr-checks/checks/upload-quality-sarif.yml b/pr-checks/checks/upload-quality-sarif.yml index 9538505af2..cc4786735b 100644 --- a/pr-checks/checks/upload-quality-sarif.yml +++ b/pr-checks/checks/upload-quality-sarif.yml @@ -6,9 +6,8 @@ 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 }} - analysis-kinds: code-scanning,code-quality + languages: csharp,java,javascript,python + analysis-kinds: code-quality - name: Build code run: ./build.sh # Generate some SARIF we can upload with the upload-sarif step @@ -18,6 +17,10 @@ steps: sha: '5e235361806c361d4d3f8859e3c897658025a9a2' upload: never - uses: ./../action/upload-sarif + id: upload-sarif with: 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' + run: exit 1 diff --git a/src/upload-sarif-action.ts b/src/upload-sarif-action.ts index a193e242a6..aa1a5a4443 100644 --- a/src/upload-sarif-action.ts +++ b/src/upload-sarif-action.ts @@ -32,6 +32,55 @@ 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 { + 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, @@ -86,54 +135,71 @@ async function run() { } try { + // `sarifPath` can either be a path to a single file, or a path to a directory. const sarifPath = actionsUtil.getRequiredInput("sarif_file"); const checkoutPath = actionsUtil.getRequiredInput("checkout_path"); const category = actionsUtil.getOptionalInput("category"); + const pathStats = fs.lstatSync(sarifPath, { throwIfNoEntry: false }); - const uploadResult = await upload_lib.uploadFiles( + if (pathStats === undefined) { + throw new ConfigurationError(`Path does not exist: ${sarifPath}.`); + } + + const sarifIds: Array<{ analysis: string; id: string }> = []; + const uploadResult = await findAndUpload( + logger, + features, sarifPath, + pathStats, checkoutPath, - category, - features, - logger, analyses.CodeScanning, + category, ); - core.setOutput("sarif-id", uploadResult.sarifID); + if (uploadResult !== undefined) { + core.setOutput("sarif-id", uploadResult.sarifID); + sarifIds.push({ + analysis: analyses.AnalysisKind.CodeScanning, + 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, - analyses.CodeQuality.sarifPredicate, - ); - - if (qualitySarifFiles.length !== 0) { - await upload_lib.uploadSpecifiedFiles( - qualitySarifFiles, - checkoutPath, - actionsUtil.fixCodeQualityCategory(logger, category), - features, - logger, - analyses.CodeQuality, - ); - } + 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, + }); } + 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") { - await upload_lib.waitForProcessing( - getRepositoryNwo(), - uploadResult.sarifID, - logger, - ); + if (uploadResult !== undefined) { + await upload_lib.waitForProcessing( + 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); + await sendSuccessStatusReport( + startedAt, + uploadResult?.statusReport || {}, + logger, + ); } catch (unwrappedError) { const error = isThirdPartyAnalysis(ActionName.UploadSarif) && diff --git a/upload-sarif/action.yml b/upload-sarif/action.yml index 15ff9eeff3..cd61886c69 100644 --- a/upload-sarif/action.yml +++ b/upload-sarif/action.yml @@ -34,7 +34,12 @@ inputs: default: "true" outputs: sarif-id: - description: The ID of the uploaded SARIF file. + description: The ID of the uploaded Code Scanning SARIF file, if any. + sarif-ids: + description: | + A stringified JSON object containing the SARIF ID for each kind of analysis. For example: + + { "code-scanning": "some-id", "code-quality": "some-other-id" } runs: using: node20 main: '../lib/upload-sarif-action.js'