diff --git a/.github/workflows/__quality-queries.yml b/.github/workflows/__analysis-kinds.yml similarity index 83% rename from .github/workflows/__quality-queries.yml rename to .github/workflows/__analysis-kinds.yml index fdbe0e812a..b36a0bceb3 100644 --- a/.github/workflows/__quality-queries.yml +++ b/.github/workflows/__analysis-kinds.yml @@ -3,7 +3,7 @@ # pr-checks/sync.sh # to regenerate this file. -name: PR Check - Quality queries input +name: PR Check - Analysis kinds env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GO111MODULE: auto @@ -29,9 +29,9 @@ defaults: shell: bash concurrency: cancel-in-progress: ${{ github.event_name == 'pull_request' || false }} - group: quality-queries-${{github.ref}} + group: analysis-kinds-${{github.ref}} jobs: - quality-queries: + analysis-kinds: strategy: fail-fast: false matrix: @@ -45,6 +45,9 @@ jobs: - os: ubuntu-latest version: linked analysis-kinds: code-scanning,code-quality + - os: ubuntu-latest + version: linked + analysis-kinds: csra - os: ubuntu-latest version: nightly-latest analysis-kinds: code-scanning @@ -54,7 +57,10 @@ jobs: - os: ubuntu-latest version: nightly-latest analysis-kinds: code-scanning,code-quality - name: Quality queries input + - os: ubuntu-latest + version: nightly-latest + analysis-kinds: csra + name: Analysis kinds if: github.triggering_actor != 'dependabot[bot]' permissions: contents: read @@ -81,30 +87,24 @@ jobs: output: ${{ runner.temp }}/results upload-database: false post-processed-sarif-path: ${{ runner.temp }}/post-processed - - name: Upload security SARIF - if: contains(matrix.analysis-kinds, 'code-scanning') - uses: actions/upload-artifact@v6 - with: - name: | - quality-queries-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}.sarif.json - path: ${{ runner.temp }}/results/javascript.sarif - retention-days: 7 - - name: Upload quality SARIF - if: contains(matrix.analysis-kinds, 'code-quality') + + - name: Upload SARIF files uses: actions/upload-artifact@v6 with: name: | - quality-queries-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}.quality.sarif.json - path: ${{ runner.temp }}/results/javascript.quality.sarif + analysis-kinds-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }} + path: ${{ runner.temp }}/results/*.sarif retention-days: 7 + - name: Upload post-processed SARIF uses: actions/upload-artifact@v6 with: name: | - post-processed-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}.sarif.json + post-processed-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }} path: ${{ runner.temp }}/post-processed retention-days: 7 if-no-files-found: error + - name: Check quality query does not appear in security SARIF if: contains(matrix.analysis-kinds, 'code-scanning') uses: actions/github-script@v8 @@ -121,7 +121,16 @@ jobs: EXPECT_PRESENT: 'true' with: script: ${{ env.CHECK_SCRIPT }} + - name: Check quality query does not appear in CSRA SARIF + if: contains(matrix.analysis-kinds, 'csra') + uses: actions/github-script@v8 + env: + SARIF_PATH: ${{ runner.temp }}/results/javascript.csra.sarif + EXPECT_PRESENT: 'false' + with: + script: ${{ env.CHECK_SCRIPT }} env: + CODEQL_ACTION_CSRA_ASSESSMENT_ID: 1 CHECK_SCRIPT: | const fs = require('fs'); diff --git a/lib/analyze-action-post.js b/lib/analyze-action-post.js index d097d992d3..bceaf258f3 100644 --- a/lib/analyze-action-post.js +++ b/lib/analyze-action-post.js @@ -161224,6 +161224,7 @@ var path3 = __toESM(require("path")); var AnalysisKind = /* @__PURE__ */ ((AnalysisKind2) => { AnalysisKind2["CodeScanning"] = "code-scanning"; AnalysisKind2["CodeQuality"] = "code-quality"; + AnalysisKind2["CSRA"] = "csra"; return AnalysisKind2; })(AnalysisKind || {}); var supportedAnalysisKinds = new Set(Object.values(AnalysisKind)); diff --git a/lib/analyze-action.js b/lib/analyze-action.js index c0288403bf..8ff4fc810e 100644 --- a/lib/analyze-action.js +++ b/lib/analyze-action.js @@ -106485,6 +106485,7 @@ function fixCodeQualityCategory(logger, category) { var AnalysisKind = /* @__PURE__ */ ((AnalysisKind2) => { AnalysisKind2["CodeScanning"] = "code-scanning"; AnalysisKind2["CodeQuality"] = "code-quality"; + AnalysisKind2["CSRA"] = "csra"; return AnalysisKind2; })(AnalysisKind || {}); var supportedAnalysisKinds = new Set(Object.values(AnalysisKind)); @@ -106494,9 +106495,10 @@ var 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), + sarifPredicate: (name) => name.endsWith(CodeScanning.sarifExtension) && !CodeQuality.sarifPredicate(name) && !CSRA.sarifPredicate(name), fixCategory: (_, category) => category, - sentinelPrefix: "CODEQL_UPLOAD_SARIF_" + sentinelPrefix: "CODEQL_UPLOAD_SARIF_", + transformPayload: (payload) => payload }; var CodeQuality = { kind: "code-quality" /* CodeQuality */, @@ -106505,7 +106507,33 @@ var CodeQuality = { sarifExtension: ".quality.sarif", sarifPredicate: (name) => name.endsWith(CodeQuality.sarifExtension), fixCategory: fixCodeQualityCategory, - sentinelPrefix: "CODEQL_UPLOAD_QUALITY_SARIF_" + sentinelPrefix: "CODEQL_UPLOAD_QUALITY_SARIF_", + transformPayload: (payload) => payload +}; +function addAssessmentId(payload) { + const rawAssessmentId = getRequiredEnvParam("CODEQL_ACTION_CSRA_ASSESSMENT_ID" /* CSRA_ASSESSMENT_ID */); + const assessmentId = parseInt(rawAssessmentId, 10); + if (Number.isNaN(assessmentId)) { + throw new Error( + `${"CODEQL_ACTION_CSRA_ASSESSMENT_ID" /* CSRA_ASSESSMENT_ID */} must not be NaN: ${rawAssessmentId}` + ); + } + if (assessmentId < 0) { + throw new Error( + `${"CODEQL_ACTION_CSRA_ASSESSMENT_ID" /* CSRA_ASSESSMENT_ID */} must not be negative: ${rawAssessmentId}` + ); + } + return { sarif: payload.sarif, assessment_id: assessmentId }; +} +var CSRA = { + kind: "csra" /* CSRA */, + name: "csra", + target: "PUT /repos/:owner/:repo/code-scanning/risk-assessment" /* CSRA */, + sarifExtension: ".csra.sarif", + sarifPredicate: (name) => name.endsWith(CSRA.sarifExtension), + fixCategory: (_, category) => category, + sentinelPrefix: "CODEQL_UPLOAD_CSRA_SARIF_", + transformPayload: addAssessmentId }; function getAnalysisConfig(kind) { switch (kind) { @@ -106513,9 +106541,15 @@ function getAnalysisConfig(kind) { return CodeScanning; case "code-quality" /* CodeQuality */: return CodeQuality; + case "csra" /* CSRA */: + return CSRA; } } -var SarifScanOrder = [CodeQuality, CodeScanning]; +var SarifScanOrder = [ + CSRA, + CodeQuality, + CodeScanning +]; // src/analyze.ts var fs12 = __toESM(require("fs")); @@ -108416,10 +108450,13 @@ function isCodeQualityEnabled(config) { return config.analysisKinds.includes("code-quality" /* CodeQuality */); } function getPrimaryAnalysisKind(config) { + if (config.analysisKinds.length === 1) { + return config.analysisKinds[0]; + } return isCodeScanningEnabled(config) ? "code-scanning" /* CodeScanning */ : "code-quality" /* CodeQuality */; } function getPrimaryAnalysisConfig(config) { - return getPrimaryAnalysisKind(config) === "code-scanning" /* CodeScanning */ ? CodeScanning : CodeQuality; + return getAnalysisConfig(getPrimaryAnalysisKind(config)); } // src/setup-codeql.ts @@ -110530,10 +110567,7 @@ async function runQueries(sarifFolder, memoryFlag, threadsFlag, diffRangePackDir return statusReport; async function runInterpretResultsFor(analysis, language, queries, enableDebugLogging) { logger.info(`Interpreting ${analysis.name} results for ${language}`); - let category = automationDetailsId; - if (analysis.kind === "code-quality" /* CodeQuality */) { - category = analysis.fixCategory(logger, automationDetailsId); - } + const category = analysis.fixCategory(logger, automationDetailsId); const sarifFile = path12.join( sarifFolder, addSarifExtension(analysis, language) @@ -112518,18 +112552,20 @@ async function uploadPostProcessedFiles(logger, checkoutPath, uploadTarget, post logger.debug(`Compressing serialized SARIF`); const zippedSarif = import_zlib.default.gzipSync(sarifPayload).toString("base64"); const checkoutURI = url.pathToFileURL(checkoutPath).href; - const payload = buildPayload( - await getCommitOid(checkoutPath), - await getRef(), - postProcessingResults.analysisKey, - getRequiredEnvParam("GITHUB_WORKFLOW"), - zippedSarif, - getWorkflowRunID(), - getWorkflowRunAttempt(), - checkoutURI, - postProcessingResults.environment, - toolNames, - await determineBaseBranchHeadCommitOid() + const payload = uploadTarget.transformPayload( + buildPayload( + await getCommitOid(checkoutPath), + await getRef(), + postProcessingResults.analysisKey, + getRequiredEnvParam("GITHUB_WORKFLOW"), + zippedSarif, + getWorkflowRunID(), + getWorkflowRunAttempt(), + checkoutURI, + postProcessingResults.environment, + toolNames, + await determineBaseBranchHeadCommitOid() + ) ); const rawUploadSizeBytes = sarifPayload.length; logger.debug(`Raw upload size: ${rawUploadSizeBytes} bytes`); diff --git a/lib/autobuild-action.js b/lib/autobuild-action.js index 416cc22720..9aa58f4170 100644 --- a/lib/autobuild-action.js +++ b/lib/autobuild-action.js @@ -103629,6 +103629,7 @@ var path4 = __toESM(require("path")); var AnalysisKind = /* @__PURE__ */ ((AnalysisKind2) => { AnalysisKind2["CodeScanning"] = "code-scanning"; AnalysisKind2["CodeQuality"] = "code-quality"; + AnalysisKind2["CSRA"] = "csra"; return AnalysisKind2; })(AnalysisKind || {}); var supportedAnalysisKinds = new Set(Object.values(AnalysisKind)); diff --git a/lib/init-action-post.js b/lib/init-action-post.js index 44e368f8a4..8f0a6ce6f7 100644 --- a/lib/init-action-post.js +++ b/lib/init-action-post.js @@ -164545,6 +164545,7 @@ var path6 = __toESM(require("path")); var AnalysisKind = /* @__PURE__ */ ((AnalysisKind2) => { AnalysisKind2["CodeScanning"] = "code-scanning"; AnalysisKind2["CodeQuality"] = "code-quality"; + AnalysisKind2["CSRA"] = "csra"; return AnalysisKind2; })(AnalysisKind || {}); var supportedAnalysisKinds = new Set(Object.values(AnalysisKind)); @@ -164553,9 +164554,10 @@ var 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), + sarifPredicate: (name) => name.endsWith(CodeScanning.sarifExtension) && !CodeQuality.sarifPredicate(name) && !CSRA.sarifPredicate(name), fixCategory: (_2, category) => category, - sentinelPrefix: "CODEQL_UPLOAD_SARIF_" + sentinelPrefix: "CODEQL_UPLOAD_SARIF_", + transformPayload: (payload) => payload }; var CodeQuality = { kind: "code-quality" /* CodeQuality */, @@ -164564,7 +164566,33 @@ var CodeQuality = { sarifExtension: ".quality.sarif", sarifPredicate: (name) => name.endsWith(CodeQuality.sarifExtension), fixCategory: fixCodeQualityCategory, - sentinelPrefix: "CODEQL_UPLOAD_QUALITY_SARIF_" + sentinelPrefix: "CODEQL_UPLOAD_QUALITY_SARIF_", + transformPayload: (payload) => payload +}; +function addAssessmentId(payload) { + const rawAssessmentId = getRequiredEnvParam("CODEQL_ACTION_CSRA_ASSESSMENT_ID" /* CSRA_ASSESSMENT_ID */); + const assessmentId = parseInt(rawAssessmentId, 10); + if (Number.isNaN(assessmentId)) { + throw new Error( + `${"CODEQL_ACTION_CSRA_ASSESSMENT_ID" /* CSRA_ASSESSMENT_ID */} must not be NaN: ${rawAssessmentId}` + ); + } + if (assessmentId < 0) { + throw new Error( + `${"CODEQL_ACTION_CSRA_ASSESSMENT_ID" /* CSRA_ASSESSMENT_ID */} must not be negative: ${rawAssessmentId}` + ); + } + return { sarif: payload.sarif, assessment_id: assessmentId }; +} +var CSRA = { + kind: "csra" /* CSRA */, + name: "csra", + target: "PUT /repos/:owner/:repo/code-scanning/risk-assessment" /* CSRA */, + sarifExtension: ".csra.sarif", + sarifPredicate: (name) => name.endsWith(CSRA.sarifExtension), + fixCategory: (_2, category) => category, + sentinelPrefix: "CODEQL_UPLOAD_CSRA_SARIF_", + transformPayload: addAssessmentId }; // src/config/db-config.ts @@ -169131,18 +169159,20 @@ async function uploadPostProcessedFiles(logger, checkoutPath, uploadTarget, post logger.debug(`Compressing serialized SARIF`); const zippedSarif = import_zlib.default.gzipSync(sarifPayload).toString("base64"); const checkoutURI = url.pathToFileURL(checkoutPath).href; - const payload = buildPayload( - await getCommitOid(checkoutPath), - await getRef(), - postProcessingResults.analysisKey, - getRequiredEnvParam("GITHUB_WORKFLOW"), - zippedSarif, - getWorkflowRunID(), - getWorkflowRunAttempt(), - checkoutURI, - postProcessingResults.environment, - toolNames, - await determineBaseBranchHeadCommitOid() + const payload = uploadTarget.transformPayload( + buildPayload( + await getCommitOid(checkoutPath), + await getRef(), + postProcessingResults.analysisKey, + getRequiredEnvParam("GITHUB_WORKFLOW"), + zippedSarif, + getWorkflowRunID(), + getWorkflowRunAttempt(), + checkoutURI, + postProcessingResults.environment, + toolNames, + await determineBaseBranchHeadCommitOid() + ) ); const rawUploadSizeBytes = sarifPayload.length; logger.debug(`Raw upload size: ${rawUploadSizeBytes} bytes`); diff --git a/lib/init-action.js b/lib/init-action.js index 8607b23914..6bf9880a5b 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -103833,8 +103833,14 @@ function isAnalyzingPullRequest() { var AnalysisKind = /* @__PURE__ */ ((AnalysisKind3) => { AnalysisKind3["CodeScanning"] = "code-scanning"; AnalysisKind3["CodeQuality"] = "code-quality"; + AnalysisKind3["CSRA"] = "csra"; return AnalysisKind3; })(AnalysisKind || {}); +var compatibilityMatrix = { + ["code-scanning" /* CodeScanning */]: /* @__PURE__ */ new Set(["code-quality" /* CodeQuality */]), + ["code-quality" /* CodeQuality */]: /* @__PURE__ */ new Set(["code-scanning" /* CodeScanning */]), + ["csra" /* CSRA */]: /* @__PURE__ */ new Set() +}; var supportedAnalysisKinds = new Set(Object.values(AnalysisKind)); async function parseAnalysisKinds(input) { const components = input.split(","); @@ -103857,7 +103863,7 @@ async function getAnalysisKinds(logger, skipCache = false) { if (!skipCache && cachedAnalysisKinds !== void 0) { return cachedAnalysisKinds; } - cachedAnalysisKinds = await parseAnalysisKinds( + const analysisKinds = await parseAnalysisKinds( getRequiredInput("analysis-kinds") ); const qualityQueriesInput = getOptionalInput("quality-queries"); @@ -103866,9 +103872,20 @@ async function getAnalysisKinds(logger, skipCache = false) { "The `quality-queries` input is deprecated and will be removed in a future version of the CodeQL Action. Use the `analysis-kinds` input to configure different analysis kinds instead." ); } - if (!cachedAnalysisKinds.includes("code-quality" /* CodeQuality */) && qualityQueriesInput !== void 0) { - cachedAnalysisKinds.push("code-quality" /* CodeQuality */); + if (!analysisKinds.includes("code-quality" /* CodeQuality */) && qualityQueriesInput !== void 0) { + analysisKinds.push("code-quality" /* CodeQuality */); + } + for (const analysisKind of analysisKinds) { + for (const otherAnalysisKind of analysisKinds) { + if (analysisKind === otherAnalysisKind) continue; + if (!compatibilityMatrix[analysisKind].has(otherAnalysisKind)) { + throw new ConfigurationError( + `${otherAnalysisKind} cannot be enabled at the same time as ${analysisKind}` + ); + } + } } + cachedAnalysisKinds = analysisKinds; return cachedAnalysisKinds; } var codeQualityQueries = ["code-quality"]; diff --git a/lib/resolve-environment-action.js b/lib/resolve-environment-action.js index a841072511..d2c4da143b 100644 --- a/lib/resolve-environment-action.js +++ b/lib/resolve-environment-action.js @@ -103628,6 +103628,7 @@ var path3 = __toESM(require("path")); var AnalysisKind = /* @__PURE__ */ ((AnalysisKind2) => { AnalysisKind2["CodeScanning"] = "code-scanning"; AnalysisKind2["CodeQuality"] = "code-quality"; + AnalysisKind2["CSRA"] = "csra"; return AnalysisKind2; })(AnalysisKind || {}); var supportedAnalysisKinds = new Set(Object.values(AnalysisKind)); diff --git a/lib/setup-codeql-action.js b/lib/setup-codeql-action.js index c01ec64f56..ddc06f431d 100644 --- a/lib/setup-codeql-action.js +++ b/lib/setup-codeql-action.js @@ -104553,6 +104553,7 @@ function wrapCliConfigurationError(cliError) { var AnalysisKind = /* @__PURE__ */ ((AnalysisKind2) => { AnalysisKind2["CodeScanning"] = "code-scanning"; AnalysisKind2["CodeQuality"] = "code-quality"; + AnalysisKind2["CSRA"] = "csra"; return AnalysisKind2; })(AnalysisKind || {}); var supportedAnalysisKinds = new Set(Object.values(AnalysisKind)); diff --git a/lib/start-proxy-action-post.js b/lib/start-proxy-action-post.js index c29841a85b..adccec31bc 100644 --- a/lib/start-proxy-action-post.js +++ b/lib/start-proxy-action-post.js @@ -160859,6 +160859,7 @@ var path = __toESM(require("path")); var AnalysisKind = /* @__PURE__ */ ((AnalysisKind2) => { AnalysisKind2["CodeScanning"] = "code-scanning"; AnalysisKind2["CodeQuality"] = "code-quality"; + AnalysisKind2["CSRA"] = "csra"; return AnalysisKind2; })(AnalysisKind || {}); var supportedAnalysisKinds = new Set(Object.values(AnalysisKind)); diff --git a/lib/start-proxy-action.js b/lib/start-proxy-action.js index 072924405e..7baced5b4b 100644 --- a/lib/start-proxy-action.js +++ b/lib/start-proxy-action.js @@ -121161,6 +121161,7 @@ var core9 = __toESM(require_core()); var AnalysisKind = /* @__PURE__ */ ((AnalysisKind2) => { AnalysisKind2["CodeScanning"] = "code-scanning"; AnalysisKind2["CodeQuality"] = "code-quality"; + AnalysisKind2["CSRA"] = "csra"; return AnalysisKind2; })(AnalysisKind || {}); var supportedAnalysisKinds = new Set(Object.values(AnalysisKind)); diff --git a/lib/upload-lib.js b/lib/upload-lib.js index 0fa50e3967..344aa24263 100644 --- a/lib/upload-lib.js +++ b/lib/upload-lib.js @@ -106141,6 +106141,7 @@ function fixCodeQualityCategory(logger, category) { var AnalysisKind = /* @__PURE__ */ ((AnalysisKind2) => { AnalysisKind2["CodeScanning"] = "code-scanning"; AnalysisKind2["CodeQuality"] = "code-quality"; + AnalysisKind2["CSRA"] = "csra"; return AnalysisKind2; })(AnalysisKind || {}); var supportedAnalysisKinds = new Set(Object.values(AnalysisKind)); @@ -106149,9 +106150,10 @@ var 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), + sarifPredicate: (name) => name.endsWith(CodeScanning.sarifExtension) && !CodeQuality.sarifPredicate(name) && !CSRA.sarifPredicate(name), fixCategory: (_, category) => category, - sentinelPrefix: "CODEQL_UPLOAD_SARIF_" + sentinelPrefix: "CODEQL_UPLOAD_SARIF_", + transformPayload: (payload) => payload }; var CodeQuality = { kind: "code-quality" /* CodeQuality */, @@ -106160,9 +106162,39 @@ var CodeQuality = { sarifExtension: ".quality.sarif", sarifPredicate: (name) => name.endsWith(CodeQuality.sarifExtension), fixCategory: fixCodeQualityCategory, - sentinelPrefix: "CODEQL_UPLOAD_QUALITY_SARIF_" + sentinelPrefix: "CODEQL_UPLOAD_QUALITY_SARIF_", + transformPayload: (payload) => payload }; -var SarifScanOrder = [CodeQuality, CodeScanning]; +function addAssessmentId(payload) { + const rawAssessmentId = getRequiredEnvParam("CODEQL_ACTION_CSRA_ASSESSMENT_ID" /* CSRA_ASSESSMENT_ID */); + const assessmentId = parseInt(rawAssessmentId, 10); + if (Number.isNaN(assessmentId)) { + throw new Error( + `${"CODEQL_ACTION_CSRA_ASSESSMENT_ID" /* CSRA_ASSESSMENT_ID */} must not be NaN: ${rawAssessmentId}` + ); + } + if (assessmentId < 0) { + throw new Error( + `${"CODEQL_ACTION_CSRA_ASSESSMENT_ID" /* CSRA_ASSESSMENT_ID */} must not be negative: ${rawAssessmentId}` + ); + } + return { sarif: payload.sarif, assessment_id: assessmentId }; +} +var CSRA = { + kind: "csra" /* CSRA */, + name: "csra", + target: "PUT /repos/:owner/:repo/code-scanning/risk-assessment" /* CSRA */, + sarifExtension: ".csra.sarif", + sarifPredicate: (name) => name.endsWith(CSRA.sarifExtension), + fixCategory: (_, category) => category, + sentinelPrefix: "CODEQL_UPLOAD_CSRA_SARIF_", + transformPayload: addAssessmentId +}; +var SarifScanOrder = [ + CSRA, + CodeQuality, + CodeScanning +]; // src/api-client.ts var core5 = __toESM(require_core()); @@ -110447,18 +110479,20 @@ async function uploadPostProcessedFiles(logger, checkoutPath, uploadTarget, post logger.debug(`Compressing serialized SARIF`); const zippedSarif = import_zlib.default.gzipSync(sarifPayload).toString("base64"); const checkoutURI = url.pathToFileURL(checkoutPath).href; - const payload = buildPayload( - await getCommitOid(checkoutPath), - await getRef(), - postProcessingResults.analysisKey, - getRequiredEnvParam("GITHUB_WORKFLOW"), - zippedSarif, - getWorkflowRunID(), - getWorkflowRunAttempt(), - checkoutURI, - postProcessingResults.environment, - toolNames, - await determineBaseBranchHeadCommitOid() + const payload = uploadTarget.transformPayload( + buildPayload( + await getCommitOid(checkoutPath), + await getRef(), + postProcessingResults.analysisKey, + getRequiredEnvParam("GITHUB_WORKFLOW"), + zippedSarif, + getWorkflowRunID(), + getWorkflowRunAttempt(), + checkoutURI, + postProcessingResults.environment, + toolNames, + await determineBaseBranchHeadCommitOid() + ) ); const rawUploadSizeBytes = sarifPayload.length; logger.debug(`Raw upload size: ${rawUploadSizeBytes} bytes`); diff --git a/lib/upload-sarif-action-post.js b/lib/upload-sarif-action-post.js index a733e8c04b..04f5016943 100644 --- a/lib/upload-sarif-action-post.js +++ b/lib/upload-sarif-action-post.js @@ -160866,6 +160866,7 @@ var io5 = __toESM(require_io()); var AnalysisKind = /* @__PURE__ */ ((AnalysisKind2) => { AnalysisKind2["CodeScanning"] = "code-scanning"; AnalysisKind2["CodeQuality"] = "code-quality"; + AnalysisKind2["CSRA"] = "csra"; return AnalysisKind2; })(AnalysisKind || {}); var supportedAnalysisKinds = new Set(Object.values(AnalysisKind)); diff --git a/lib/upload-sarif-action.js b/lib/upload-sarif-action.js index edc7840847..e3511b2260 100644 --- a/lib/upload-sarif-action.js +++ b/lib/upload-sarif-action.js @@ -106179,6 +106179,7 @@ function fixCodeQualityCategory(logger, category) { var AnalysisKind = /* @__PURE__ */ ((AnalysisKind2) => { AnalysisKind2["CodeScanning"] = "code-scanning"; AnalysisKind2["CodeQuality"] = "code-quality"; + AnalysisKind2["CSRA"] = "csra"; return AnalysisKind2; })(AnalysisKind || {}); var supportedAnalysisKinds = new Set(Object.values(AnalysisKind)); @@ -106187,9 +106188,10 @@ var 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), + sarifPredicate: (name) => name.endsWith(CodeScanning.sarifExtension) && !CodeQuality.sarifPredicate(name) && !CSRA.sarifPredicate(name), fixCategory: (_, category) => category, - sentinelPrefix: "CODEQL_UPLOAD_SARIF_" + sentinelPrefix: "CODEQL_UPLOAD_SARIF_", + transformPayload: (payload) => payload }; var CodeQuality = { kind: "code-quality" /* CodeQuality */, @@ -106198,7 +106200,33 @@ var CodeQuality = { sarifExtension: ".quality.sarif", sarifPredicate: (name) => name.endsWith(CodeQuality.sarifExtension), fixCategory: fixCodeQualityCategory, - sentinelPrefix: "CODEQL_UPLOAD_QUALITY_SARIF_" + sentinelPrefix: "CODEQL_UPLOAD_QUALITY_SARIF_", + transformPayload: (payload) => payload +}; +function addAssessmentId(payload) { + const rawAssessmentId = getRequiredEnvParam("CODEQL_ACTION_CSRA_ASSESSMENT_ID" /* CSRA_ASSESSMENT_ID */); + const assessmentId = parseInt(rawAssessmentId, 10); + if (Number.isNaN(assessmentId)) { + throw new Error( + `${"CODEQL_ACTION_CSRA_ASSESSMENT_ID" /* CSRA_ASSESSMENT_ID */} must not be NaN: ${rawAssessmentId}` + ); + } + if (assessmentId < 0) { + throw new Error( + `${"CODEQL_ACTION_CSRA_ASSESSMENT_ID" /* CSRA_ASSESSMENT_ID */} must not be negative: ${rawAssessmentId}` + ); + } + return { sarif: payload.sarif, assessment_id: assessmentId }; +} +var CSRA = { + kind: "csra" /* CSRA */, + name: "csra", + target: "PUT /repos/:owner/:repo/code-scanning/risk-assessment" /* CSRA */, + sarifExtension: ".csra.sarif", + sarifPredicate: (name) => name.endsWith(CSRA.sarifExtension), + fixCategory: (_, category) => category, + sentinelPrefix: "CODEQL_UPLOAD_CSRA_SARIF_", + transformPayload: addAssessmentId }; function getAnalysisConfig(kind) { switch (kind) { @@ -106206,9 +106234,15 @@ function getAnalysisConfig(kind) { return CodeScanning; case "code-quality" /* CodeQuality */: return CodeQuality; + case "csra" /* CSRA */: + return CSRA; } } -var SarifScanOrder = [CodeQuality, CodeScanning]; +var SarifScanOrder = [ + CSRA, + CodeQuality, + CodeScanning +]; // src/api-client.ts var core5 = __toESM(require_core()); @@ -110955,18 +110989,20 @@ async function uploadPostProcessedFiles(logger, checkoutPath, uploadTarget, post logger.debug(`Compressing serialized SARIF`); const zippedSarif = import_zlib.default.gzipSync(sarifPayload).toString("base64"); const checkoutURI = url.pathToFileURL(checkoutPath).href; - const payload = buildPayload( - await getCommitOid(checkoutPath), - await getRef(), - postProcessingResults.analysisKey, - getRequiredEnvParam("GITHUB_WORKFLOW"), - zippedSarif, - getWorkflowRunID(), - getWorkflowRunAttempt(), - checkoutURI, - postProcessingResults.environment, - toolNames, - await determineBaseBranchHeadCommitOid() + const payload = uploadTarget.transformPayload( + buildPayload( + await getCommitOid(checkoutPath), + await getRef(), + postProcessingResults.analysisKey, + getRequiredEnvParam("GITHUB_WORKFLOW"), + zippedSarif, + getWorkflowRunID(), + getWorkflowRunAttempt(), + checkoutURI, + postProcessingResults.environment, + toolNames, + await determineBaseBranchHeadCommitOid() + ) ); const rawUploadSizeBytes = sarifPayload.length; logger.debug(`Raw upload size: ${rawUploadSizeBytes} bytes`); diff --git a/pr-checks/checks/quality-queries.yml b/pr-checks/checks/analysis-kinds.yml similarity index 75% rename from pr-checks/checks/quality-queries.yml rename to pr-checks/checks/analysis-kinds.yml index 353abbb774..18483e6e1b 100644 --- a/pr-checks/checks/quality-queries.yml +++ b/pr-checks/checks/analysis-kinds.yml @@ -1,8 +1,9 @@ -name: "Quality queries input" -description: "Tests that queries specified in the quality-queries input are used." +name: "Analysis kinds" +description: "Tests basic functionality for different `analysis-kinds` inputs." versions: ["linked", "nightly-latest"] -analysisKinds: ["code-scanning", "code-quality", "code-scanning,code-quality"] +analysisKinds: ["code-scanning", "code-quality", "code-scanning,code-quality", "csra"] env: + CODEQL_ACTION_CSRA_ASSESSMENT_ID: 1 CHECK_SCRIPT: | const fs = require('fs'); @@ -37,30 +38,24 @@ steps: output: "${{ runner.temp }}/results" upload-database: false post-processed-sarif-path: "${{ runner.temp }}/post-processed" - - name: Upload security SARIF - if: contains(matrix.analysis-kinds, 'code-scanning') - uses: actions/upload-artifact@v6 - with: - name: | - quality-queries-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}.sarif.json - path: "${{ runner.temp }}/results/javascript.sarif" - retention-days: 7 - - name: Upload quality SARIF - if: contains(matrix.analysis-kinds, 'code-quality') + + - name: Upload SARIF files uses: actions/upload-artifact@v6 with: name: | - quality-queries-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}.quality.sarif.json - path: "${{ runner.temp }}/results/javascript.quality.sarif" + analysis-kinds-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }} + path: "${{ runner.temp }}/results/*.sarif" retention-days: 7 + - name: Upload post-processed SARIF uses: actions/upload-artifact@v6 with: name: | - post-processed-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}.sarif.json + post-processed-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }} path: "${{ runner.temp }}/post-processed" retention-days: 7 if-no-files-found: error + - name: Check quality query does not appear in security SARIF if: contains(matrix.analysis-kinds, 'code-scanning') uses: actions/github-script@v8 @@ -77,3 +72,11 @@ steps: EXPECT_PRESENT: "true" with: script: ${{ env.CHECK_SCRIPT }} + - name: Check quality query does not appear in CSRA SARIF + if: contains(matrix.analysis-kinds, 'csra') + uses: actions/github-script@v8 + env: + SARIF_PATH: "${{ runner.temp }}/results/javascript.csra.sarif" + EXPECT_PRESENT: "false" + with: + script: ${{ env.CHECK_SCRIPT }} diff --git a/src/analyses.test.ts b/src/analyses.test.ts index 9178ffbd5a..89fe7b4fd3 100644 --- a/src/analyses.test.ts +++ b/src/analyses.test.ts @@ -1,15 +1,23 @@ +import path from "path"; + import test from "ava"; import * as sinon from "sinon"; import * as actionsUtil from "./actions-util"; import { AnalysisKind, + CodeScanning, + compatibilityMatrix, + CSRA, + getAnalysisConfig, getAnalysisKinds, parseAnalysisKinds, supportedAnalysisKinds, } from "./analyses"; +import { EnvVar } from "./environment"; import { getRunnerLogger } from "./logging"; import { setupTests } from "./testing-utils"; +import { AssessmentPayload } from "./upload-lib/types"; import { ConfigurationError } from "./util"; setupTests(test); @@ -67,3 +75,107 @@ test("getAnalysisKinds - throws if `analysis-kinds` input is invalid", async (t) requiredInputStub.withArgs("analysis-kinds").returns("no-such-thing"); await t.throwsAsync(getAnalysisKinds(getRunnerLogger(true), true)); }); + +// Test the compatibility matrix by looping through all analysis kinds. +const analysisKinds = Object.values(AnalysisKind); +for (let i = 0; i < analysisKinds.length; i++) { + const analysisKind = analysisKinds[i]; + + for (let j = i + 1; j < analysisKinds.length; j++) { + const otherAnalysis = analysisKinds[j]; + + if (analysisKind === otherAnalysis) continue; + if (compatibilityMatrix[analysisKind].has(otherAnalysis)) { + test(`getAnalysisKinds - allows ${analysisKind} with ${otherAnalysis}`, async (t) => { + const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput"); + requiredInputStub + .withArgs("analysis-kinds") + .returns([analysisKind, otherAnalysis].join(",")); + const result = await getAnalysisKinds(getRunnerLogger(true), true); + t.is(result.length, 2); + }); + } else { + test(`getAnalysisKinds - throws if ${analysisKind} is enabled with ${otherAnalysis}`, async (t) => { + const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput"); + requiredInputStub + .withArgs("analysis-kinds") + .returns([analysisKind, otherAnalysis].join(",")); + await t.throwsAsync(getAnalysisKinds(getRunnerLogger(true), true), { + instanceOf: ConfigurationError, + message: `${otherAnalysis} cannot be enabled at the same time as ${analysisKind}`, + }); + }); + } + } +} + +test("Code Scanning configuration does not accept other SARIF extensions", (t) => { + for (const analysisKind of supportedAnalysisKinds) { + if (analysisKind === AnalysisKind.CodeScanning) continue; + + const analysis = getAnalysisConfig(analysisKind); + const sarifPath = path.join("path", "to", `file${analysis.sarifExtension}`); + + // The Code Scanning configuration's `sarifPredicate` should not accept a path which + // ends in a different configuration's `sarifExtension`. + t.false(CodeScanning.sarifPredicate(sarifPath)); + } +}); + +test("CSRA configuration transforms SARIF upload payload", (t) => { + process.env[EnvVar.CSRA_ASSESSMENT_ID] = "1"; + const payload = CSRA.transformPayload({ + commit_oid: "abc", + sarif: "sarif", + ref: "ref", + workflow_run_attempt: 1, + workflow_run_id: 1, + checkout_uri: "uri", + tool_names: [], + }) as AssessmentPayload; + + const expected: AssessmentPayload = { sarif: "sarif", assessment_id: 1 }; + t.deepEqual(expected, payload); +}); + +test("CSRA configuration throws for negative assessment IDs", (t) => { + process.env[EnvVar.CSRA_ASSESSMENT_ID] = "-1"; + t.throws( + () => + CSRA.transformPayload({ + commit_oid: "abc", + sarif: "sarif", + ref: "ref", + workflow_run_attempt: 1, + workflow_run_id: 1, + checkout_uri: "uri", + tool_names: [], + }), + { + instanceOf: Error, + message: (msg) => + msg.startsWith(`${EnvVar.CSRA_ASSESSMENT_ID} must not be negative: `), + }, + ); +}); + +test("CSRA configuration throws for invalid IDs", (t) => { + process.env[EnvVar.CSRA_ASSESSMENT_ID] = "foo"; + t.throws( + () => + CSRA.transformPayload({ + commit_oid: "abc", + sarif: "sarif", + ref: "ref", + workflow_run_attempt: 1, + workflow_run_id: 1, + checkout_uri: "uri", + tool_names: [], + }), + { + instanceOf: Error, + message: (msg) => + msg.startsWith(`${EnvVar.CSRA_ASSESSMENT_ID} must not be NaN: `), + }, + ); +}); diff --git a/src/analyses.ts b/src/analyses.ts index 4f91ab07c0..5bf5482d2d 100644 --- a/src/analyses.ts +++ b/src/analyses.ts @@ -3,14 +3,30 @@ import { getOptionalInput, getRequiredInput, } from "./actions-util"; +import { EnvVar } from "./environment"; import { Logger } from "./logging"; -import { ConfigurationError } from "./util"; +import { + AssessmentPayload, + BasePayload, + UploadPayload, +} from "./upload-lib/types"; +import { ConfigurationError, getRequiredEnvParam } from "./util"; export enum AnalysisKind { CodeScanning = "code-scanning", CodeQuality = "code-quality", + CSRA = "csra", } +export type CompatibilityMatrix = Record>; + +/** A mapping from analysis kinds to other analysis kinds which can be enabled concurrently. */ +export const compatibilityMatrix: CompatibilityMatrix = { + [AnalysisKind.CodeScanning]: new Set([AnalysisKind.CodeQuality]), + [AnalysisKind.CodeQuality]: new Set([AnalysisKind.CodeScanning]), + [AnalysisKind.CSRA]: new Set(), +}; + // Exported for testing. A set of all known analysis kinds. export const supportedAnalysisKinds = new Set(Object.values(AnalysisKind)); @@ -67,7 +83,7 @@ export async function getAnalysisKinds( return cachedAnalysisKinds; } - cachedAnalysisKinds = await parseAnalysisKinds( + const analysisKinds = await parseAnalysisKinds( getRequiredInput("analysis-kinds"), ); @@ -85,12 +101,27 @@ export async function getAnalysisKinds( // if an input to `quality-queries` was specified. We should remove this once // `quality-queries` is no longer used. if ( - !cachedAnalysisKinds.includes(AnalysisKind.CodeQuality) && + !analysisKinds.includes(AnalysisKind.CodeQuality) && qualityQueriesInput !== undefined ) { - cachedAnalysisKinds.push(AnalysisKind.CodeQuality); + analysisKinds.push(AnalysisKind.CodeQuality); + } + + // Check that all enabled analysis kinds are compatible with each other. + for (const analysisKind of analysisKinds) { + for (const otherAnalysisKind of analysisKinds) { + if (analysisKind === otherAnalysisKind) continue; + + if (!compatibilityMatrix[analysisKind].has(otherAnalysisKind)) { + throw new ConfigurationError( + `${otherAnalysisKind} cannot be enabled at the same time as ${analysisKind}`, + ); + } + } } + // Cache the analysis kinds and return them. + cachedAnalysisKinds = analysisKinds; return cachedAnalysisKinds; } @@ -101,6 +132,7 @@ export const codeQualityQueries: string[] = ["code-quality"]; enum SARIF_UPLOAD_ENDPOINT { CODE_SCANNING = "PUT /repos/:owner/:repo/code-scanning/analysis", CODE_QUALITY = "PUT /repos/:owner/:repo/code-quality/analysis", + CSRA = "PUT /repos/:owner/:repo/code-scanning/risk-assessment", } // Represents configurations for different analysis kinds. @@ -120,6 +152,8 @@ export interface AnalysisConfig { fixCategory: (logger: Logger, category?: string) => string | undefined; /** A prefix for environment variables used to track the uniqueness of SARIF uploads. */ sentinelPrefix: string; + /** Transforms the upload payload in an analysis-specific way. */ + transformPayload: (payload: UploadPayload) => BasePayload; } // Represents the Code Scanning analysis configuration. @@ -130,9 +164,11 @@ export const CodeScanning: AnalysisConfig = { sarifExtension: ".sarif", sarifPredicate: (name) => name.endsWith(CodeScanning.sarifExtension) && - !CodeQuality.sarifPredicate(name), + !CodeQuality.sarifPredicate(name) && + !CSRA.sarifPredicate(name), fixCategory: (_, category) => category, sentinelPrefix: "CODEQL_UPLOAD_SARIF_", + transformPayload: (payload) => payload, }; // Represents the Code Quality analysis configuration. @@ -144,6 +180,38 @@ export const CodeQuality: AnalysisConfig = { sarifPredicate: (name) => name.endsWith(CodeQuality.sarifExtension), fixCategory: fixCodeQualityCategory, sentinelPrefix: "CODEQL_UPLOAD_QUALITY_SARIF_", + transformPayload: (payload) => payload, +}; + +/** + * Retrieves the CSRA assessment id from an environment variable and adds it to the payload. + * @param payload The base payload. + */ +function addAssessmentId(payload: UploadPayload): AssessmentPayload { + const rawAssessmentId = getRequiredEnvParam(EnvVar.CSRA_ASSESSMENT_ID); + const assessmentId = parseInt(rawAssessmentId, 10); + if (Number.isNaN(assessmentId)) { + throw new Error( + `${EnvVar.CSRA_ASSESSMENT_ID} must not be NaN: ${rawAssessmentId}`, + ); + } + if (assessmentId < 0) { + throw new Error( + `${EnvVar.CSRA_ASSESSMENT_ID} must not be negative: ${rawAssessmentId}`, + ); + } + return { sarif: payload.sarif, assessment_id: assessmentId }; +} + +export const CSRA: AnalysisConfig = { + kind: AnalysisKind.CSRA, + name: "csra", + target: SARIF_UPLOAD_ENDPOINT.CSRA, + sarifExtension: ".csra.sarif", + sarifPredicate: (name) => name.endsWith(CSRA.sarifExtension), + fixCategory: (_, category) => category, + sentinelPrefix: "CODEQL_UPLOAD_CSRA_SARIF_", + transformPayload: addAssessmentId, }; /** @@ -160,6 +228,8 @@ export function getAnalysisConfig(kind: AnalysisKind): AnalysisConfig { return CodeScanning; case AnalysisKind.CodeQuality: return CodeQuality; + case AnalysisKind.CSRA: + return CSRA; } } @@ -167,4 +237,8 @@ export function getAnalysisConfig(kind: AnalysisKind): AnalysisConfig { // we want to scan a folder containing SARIF files in an order that finds the more // specific extensions first. This constant defines an array in the order of analyis // configurations with more specific extensions to less specific extensions. -export const SarifScanOrder = [CodeQuality, CodeScanning]; +export const SarifScanOrder: AnalysisConfig[] = [ + CSRA, + CodeQuality, + CodeScanning, +]; diff --git a/src/analyze.test.ts b/src/analyze.test.ts index 27fe4a6f47..c5aec6e4fe 100644 --- a/src/analyze.test.ts +++ b/src/analyze.test.ts @@ -4,7 +4,7 @@ import * as path from "path"; import test from "ava"; import * as sinon from "sinon"; -import { CodeQuality, CodeScanning } from "./analyses"; +import { CodeQuality, CodeScanning, CSRA } from "./analyses"; import { runQueries, defaultSuites, @@ -155,5 +155,6 @@ test("addSarifExtension", (t) => { addSarifExtension(CodeQuality, language), `${language}.quality.sarif`, ); + t.is(addSarifExtension(CSRA, language), `${language}.csra.sarif`); } }); diff --git a/src/analyze.ts b/src/analyze.ts index bb38415047..352efd9756 100644 --- a/src/analyze.ts +++ b/src/analyze.ts @@ -549,12 +549,9 @@ export async function runQueries( ): Promise<{ summary: string; sarifFile: string }> { logger.info(`Interpreting ${analysis.name} results for ${language}`); - // If this is a Code Quality analysis, correct the category to one - // accepted by the Code Quality backend. - let category = automationDetailsId; - if (analysis.kind === analyses.AnalysisKind.CodeQuality) { - category = analysis.fixCategory(logger, automationDetailsId); - } + // Apply the analysis configuration's `fixCategory` function to adjust the category if needed. + // This is a no-op for Code Scanning. + const category = analysis.fixCategory(logger, automationDetailsId); const sarifFile = path.join( sarifFolder, diff --git a/src/config-utils.test.ts b/src/config-utils.test.ts index 276a7a344b..d0583f1674 100644 --- a/src/config-utils.test.ts +++ b/src/config-utils.test.ts @@ -7,7 +7,7 @@ import * as yaml from "js-yaml"; import * as sinon from "sinon"; import * as actionsUtil from "./actions-util"; -import { AnalysisKind } from "./analyses"; +import { AnalysisKind, supportedAnalysisKinds } from "./analyses"; import * as api from "./api-client"; import { CachingKind } from "./caching-utils"; import { createStubCodeQL } from "./codeql"; @@ -1829,3 +1829,22 @@ test("hasActionsWorkflows doesn't throw if workflows folder doesn't exist", asyn t.notThrows(() => configUtils.hasActionsWorkflows(tmpDir)); }); }); + +test("getPrimaryAnalysisConfig - single analysis kind", (t) => { + // If only one analysis kind is configured, we expect to get the matching configuration. + for (const analysisKind of supportedAnalysisKinds) { + const singleKind = createTestConfig({ analysisKinds: [analysisKind] }); + t.is(configUtils.getPrimaryAnalysisConfig(singleKind).kind, analysisKind); + } +}); + +test("getPrimaryAnalysisConfig - Code Scanning + Code Quality", (t) => { + // For CS+CQ, we expect to get the Code Scanning configuration. + const codeScanningAndCodeQuality = createTestConfig({ + analysisKinds: [AnalysisKind.CodeScanning, AnalysisKind.CodeQuality], + }); + t.is( + configUtils.getPrimaryAnalysisConfig(codeScanningAndCodeQuality).kind, + AnalysisKind.CodeScanning, + ); +}); diff --git a/src/config-utils.ts b/src/config-utils.ts index e52070b47f..c35bad33bd 100644 --- a/src/config-utils.ts +++ b/src/config-utils.ts @@ -12,9 +12,8 @@ import { import { AnalysisConfig, AnalysisKind, - CodeQuality, codeQualityQueries, - CodeScanning, + getAnalysisConfig, } from "./analyses"; import * as api from "./api-client"; import { CachingKind, getCachingKind } from "./caching-utils"; @@ -1389,28 +1388,27 @@ export function isCodeQualityEnabled(config: Config): boolean { } /** - * Returns the primary analysis kind that the Action is initialised with. This is - * always `AnalysisKind.CodeScanning` unless `AnalysisKind.CodeScanning` is not enabled. + * Returns the primary analysis kind that the Action is initialised with. If there is only + * one analysis kind, then that is returned. * - * @returns Returns `AnalysisKind.CodeScanning` if `AnalysisKind.CodeScanning` is enabled; - * otherwise `AnalysisKind.CodeQuality`. + * The special case is Code Scanning + Code Quality, which can be enabled at the same time. + * In that case, this function returns Code Scanning. */ function getPrimaryAnalysisKind(config: Config): AnalysisKind { + if (config.analysisKinds.length === 1) { + return config.analysisKinds[0]; + } + return isCodeScanningEnabled(config) ? AnalysisKind.CodeScanning : AnalysisKind.CodeQuality; } /** - * Returns the primary analysis configuration that the Action is initialised with. This is - * always `CodeScanning` unless `CodeScanning` is not enabled. - * - * @returns Returns `CodeScanning` if `AnalysisKind.CodeScanning` is enabled; otherwise `CodeQuality`. + * Returns the primary analysis configuration that the Action is initialised with. */ export function getPrimaryAnalysisConfig(config: Config): AnalysisConfig { - return getPrimaryAnalysisKind(config) === AnalysisKind.CodeScanning - ? CodeScanning - : CodeQuality; + return getAnalysisConfig(getPrimaryAnalysisKind(config)); } /** Logs the Git version as a telemetry diagnostic. */ diff --git a/src/environment.ts b/src/environment.ts index 4d7b44c808..3a71a8f2a1 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -141,4 +141,7 @@ export enum EnvVar { * `getAnalysisKey`, but can also be set manually for testing and non-standard applications. */ ANALYSIS_KEY = "CODEQL_ACTION_ANALYSIS_KEY", + + /** Used by CSRA to communicate the assessment ID to the CodeQL Action. */ + CSRA_ASSESSMENT_ID = "CODEQL_ACTION_CSRA_ASSESSMENT_ID", } diff --git a/src/testing-utils.ts b/src/testing-utils.ts index aff7780436..d8dbabab3c 100644 --- a/src/testing-utils.ts +++ b/src/testing-utils.ts @@ -147,11 +147,64 @@ export function setupActionsVars(tempDir: string, toolsDir: string) { process.env["GITHUB_WORKSPACE"] = tempDir; } +type LogLevel = "debug" | "info" | "warning" | "error"; + export interface LoggedMessage { - type: "debug" | "info" | "warning" | "error"; + type: LogLevel; message: string | Error; } +export class RecordingLogger implements Logger { + messages: LoggedMessage[] = []; + groups: string[] = []; + unfinishedGroups: Set = new Set(); + private currentGroup: string | undefined = undefined; + + constructor(private readonly logToConsole: boolean = true) {} + + private addMessage(level: LogLevel, message: string | Error): void { + this.messages.push({ type: level, message }); + + if (this.logToConsole) { + // eslint-disable-next-line no-console + console.debug(message); + } + } + + isDebug() { + return true; + } + + debug(message: string) { + this.addMessage("debug", message); + } + + info(message: string) { + this.addMessage("info", message); + } + + warning(message: string | Error) { + this.addMessage("warning", message); + } + + error(message: string | Error) { + this.addMessage("error", message); + } + + startGroup(name: string) { + this.groups.push(name); + this.currentGroup = name; + this.unfinishedGroups.add(name); + } + + endGroup() { + if (this.currentGroup !== undefined) { + this.unfinishedGroups.delete(this.currentGroup); + } + this.currentGroup = undefined; + } +} + export function getRecordingLogger( messages: LoggedMessage[], { logToConsole }: { logToConsole?: boolean } = { logToConsole: true }, @@ -196,14 +249,26 @@ export function checkExpectedLogMessages( messages: LoggedMessage[], expectedMessages: string[], ) { + const missingMessages: string[] = []; + for (const expectedMessage of expectedMessages) { - t.assert( - messages.some( + if ( + !messages.some( (msg) => typeof msg.message === "string" && msg.message.includes(expectedMessage), - ), - `Expected '${expectedMessage}' in the logger output, but didn't find it in:\n ${messages.map((m) => ` - '${m.message}'`).join("\n")}`, + ) + ) { + missingMessages.push(expectedMessage); + } + } + + if (missingMessages.length > 0) { + const listify = (lines: string[]) => + lines.map((m) => ` - '${m}'`).join("\n"); + + t.fail( + `Expected\n\n${listify(missingMessages)}\n\nin the logger output, but didn't find it in:\n\n${messages.map((m) => ` - '${m.message}'`).join("\n")}`, ); } } diff --git a/src/upload-lib.test.ts b/src/upload-lib.test.ts index 7a5be6382f..e9c6d0899f 100644 --- a/src/upload-lib.test.ts +++ b/src/upload-lib.test.ts @@ -12,6 +12,7 @@ import * as api from "./api-client"; import { getRunnerLogger, Logger } from "./logging"; import { setupTests } from "./testing-utils"; import * as uploadLib from "./upload-lib"; +import { UploadPayload } from "./upload-lib/types"; import { GitHubVariant, initializeEnvironment, withTmpDir } from "./util"; setupTests(test); @@ -128,11 +129,21 @@ test("finding SARIF files", async (t) => { "file", ); - // 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"), ""); + // add some non-Code Scanning files that should be ignored, unless we look for them specifically + for (const analysisKind of analyses.supportedAnalysisKinds) { + if (analysisKind === AnalysisKind.CodeScanning) continue; - const expectedSarifFiles = [ + const analysis = analyses.getAnalysisConfig(analysisKind); + + fs.writeFileSync(path.join(tmpDir, `a${analysis.sarifExtension}`), ""); + fs.writeFileSync( + path.join(tmpDir, "dir1", `b${analysis.sarifExtension}`), + "", + ); + } + + const expectedSarifFiles: Partial> = {}; + expectedSarifFiles[AnalysisKind.CodeScanning] = [ path.join(tmpDir, "a.sarif"), path.join(tmpDir, "b.sarif"), path.join(tmpDir, "dir1", "d.sarif"), @@ -143,18 +154,24 @@ test("finding SARIF files", async (t) => { CodeScanning.sarifPredicate, ); - t.deepEqual(sarifFiles, expectedSarifFiles); + t.deepEqual(sarifFiles, expectedSarifFiles[AnalysisKind.CodeScanning]); - const expectedQualitySarifFiles = [ - path.join(tmpDir, "a.quality.sarif"), - path.join(tmpDir, "dir1", "b.quality.sarif"), - ]; - const qualitySarifFiles = uploadLib.findSarifFilesInDir( - tmpDir, - CodeQuality.sarifPredicate, - ); + for (const analysisKind of analyses.supportedAnalysisKinds) { + if (analysisKind === AnalysisKind.CodeScanning) continue; + + const analysis = analyses.getAnalysisConfig(analysisKind); - t.deepEqual(qualitySarifFiles, expectedQualitySarifFiles); + expectedSarifFiles[analysisKind] = [ + path.join(tmpDir, `a${analysis.sarifExtension}`), + path.join(tmpDir, "dir1", `b${analysis.sarifExtension}`), + ]; + const foundSarifFiles = uploadLib.findSarifFilesInDir( + tmpDir, + analysis.sarifPredicate, + ); + + t.deepEqual(foundSarifFiles, expectedSarifFiles[analysisKind]); + } const groupedSarifFiles = await uploadLib.getGroupedSarifFilePaths( getRunnerLogger(true), @@ -162,16 +179,31 @@ test("finding SARIF files", async (t) => { ); t.not(groupedSarifFiles, undefined); - t.not(groupedSarifFiles[AnalysisKind.CodeScanning], undefined); - t.not(groupedSarifFiles[AnalysisKind.CodeQuality], undefined); - t.deepEqual( - groupedSarifFiles[AnalysisKind.CodeScanning], - expectedSarifFiles, - ); - t.deepEqual( - groupedSarifFiles[AnalysisKind.CodeQuality], - expectedQualitySarifFiles, + for (const analysisKind of analyses.supportedAnalysisKinds) { + t.not(groupedSarifFiles[analysisKind], undefined); + t.deepEqual( + groupedSarifFiles[analysisKind], + expectedSarifFiles[analysisKind], + ); + } + }); +}); + +test("getGroupedSarifFilePaths - CSRA", async (t) => { + await withTmpDir(async (tmpDir) => { + const sarifPath = path.join(tmpDir, "a.csra.sarif"); + fs.writeFileSync(sarifPath, ""); + + const groupedSarifFiles = await uploadLib.getGroupedSarifFilePaths( + getRunnerLogger(true), + sarifPath, ); + + t.not(groupedSarifFiles, undefined); + t.is(groupedSarifFiles[AnalysisKind.CodeScanning], undefined); + t.is(groupedSarifFiles[AnalysisKind.CodeQuality], undefined); + t.not(groupedSarifFiles[AnalysisKind.CSRA], undefined); + t.deepEqual(groupedSarifFiles[AnalysisKind.CSRA], [sarifPath]); }); }); @@ -188,6 +220,7 @@ test("getGroupedSarifFilePaths - Code Quality file", async (t) => { t.not(groupedSarifFiles, undefined); t.is(groupedSarifFiles[AnalysisKind.CodeScanning], undefined); t.not(groupedSarifFiles[AnalysisKind.CodeQuality], undefined); + t.is(groupedSarifFiles[AnalysisKind.CSRA], undefined); t.deepEqual(groupedSarifFiles[AnalysisKind.CodeQuality], [sarifPath]); }); }); @@ -205,6 +238,7 @@ test("getGroupedSarifFilePaths - Code Scanning file", async (t) => { t.not(groupedSarifFiles, undefined); t.not(groupedSarifFiles[AnalysisKind.CodeScanning], undefined); t.is(groupedSarifFiles[AnalysisKind.CodeQuality], undefined); + t.is(groupedSarifFiles[AnalysisKind.CSRA], undefined); t.deepEqual(groupedSarifFiles[AnalysisKind.CodeScanning], [sarifPath]); }); }); @@ -222,6 +256,7 @@ test("getGroupedSarifFilePaths - Other file", async (t) => { t.not(groupedSarifFiles, undefined); t.not(groupedSarifFiles[AnalysisKind.CodeScanning], undefined); t.is(groupedSarifFiles[AnalysisKind.CodeQuality], undefined); + t.is(groupedSarifFiles[AnalysisKind.CSRA], undefined); t.deepEqual(groupedSarifFiles[AnalysisKind.CodeScanning], [sarifPath]); }); }); @@ -875,7 +910,15 @@ function createMockSarif(id?: string, tool?: string) { function uploadPayloadFixtures(analysis: analyses.AnalysisConfig) { const mockData = { - payload: { sarif: "base64data", commit_sha: "abc123" }, + payload: { + commit_oid: "abc123", + ref: "ref", + sarif: "base64data", + workflow_run_id: 1, + workflow_run_attempt: 1, + checkout_uri: "uri", + tool_names: ["codeql"], + } satisfies UploadPayload, owner: "test-owner", repo: "test-repo", response: { @@ -907,7 +950,9 @@ function uploadPayloadFixtures(analysis: analyses.AnalysisConfig) { }; } -for (const analysis of [CodeScanning, CodeQuality]) { +for (const analysisKind of analyses.supportedAnalysisKinds) { + const analysis = analyses.getAnalysisConfig(analysisKind); + test(`uploadPayload on ${analysis.name} uploads successfully`, async (t) => { const { upload, requestStub, mockData } = uploadPayloadFixtures(analysis); requestStub diff --git a/src/upload-lib.ts b/src/upload-lib.ts index ac02745207..43039c596f 100644 --- a/src/upload-lib.ts +++ b/src/upload-lib.ts @@ -21,6 +21,7 @@ import * as gitUtils from "./git-utils"; import { initCodeQL } from "./init"; import { Logger } from "./logging"; import { getRepositoryNwo, RepositoryNwo } from "./repository"; +import { BasePayload, UploadPayload } from "./upload-lib/types"; import * as util from "./util"; import { ConfigurationError, @@ -326,7 +327,7 @@ function getAutomationID( * This is exported for testing purposes only. */ export async function uploadPayload( - payload: any, + payload: BasePayload, repositoryNwo: RepositoryNwo, logger: Logger, analysis: analyses.AnalysisConfig, @@ -618,8 +619,8 @@ export function buildPayload( environment: string | undefined, toolNames: string[], mergeBaseCommitOid: string | undefined, -) { - const payloadObj = { +): UploadPayload { + const payloadObj: UploadPayload = { commit_oid: commitOid, ref, analysis_key: analysisKey, @@ -847,18 +848,20 @@ export async function uploadPostProcessedFiles( const zippedSarif = zlib.gzipSync(sarifPayload).toString("base64"); const checkoutURI = url.pathToFileURL(checkoutPath).href; - const payload = buildPayload( - await gitUtils.getCommitOid(checkoutPath), - await gitUtils.getRef(), - postProcessingResults.analysisKey, - util.getRequiredEnvParam("GITHUB_WORKFLOW"), - zippedSarif, - actionsUtil.getWorkflowRunID(), - actionsUtil.getWorkflowRunAttempt(), - checkoutURI, - postProcessingResults.environment, - toolNames, - await gitUtils.determineBaseBranchHeadCommitOid(), + const payload = uploadTarget.transformPayload( + buildPayload( + await gitUtils.getCommitOid(checkoutPath), + await gitUtils.getRef(), + postProcessingResults.analysisKey, + util.getRequiredEnvParam("GITHUB_WORKFLOW"), + zippedSarif, + actionsUtil.getWorkflowRunID(), + actionsUtil.getWorkflowRunAttempt(), + checkoutURI, + postProcessingResults.environment, + toolNames, + await gitUtils.determineBaseBranchHeadCommitOid(), + ), ); // Log some useful debug info about the info diff --git a/src/upload-lib/types.ts b/src/upload-lib/types.ts new file mode 100644 index 0000000000..64480a4fc4 --- /dev/null +++ b/src/upload-lib/types.ts @@ -0,0 +1,22 @@ +export interface BasePayload { + sarif: string; +} + +export interface UploadPayload extends BasePayload { + commit_oid: string; + ref: string; + analysis_key?: string; + analysis_name?: string; + workflow_run_id: number; + workflow_run_attempt: number; + checkout_uri: string; + environment?: string; + started_at?: string; + tool_names: string[]; + base_ref?: string; + base_sha?: string; +} + +export interface AssessmentPayload extends BasePayload { + assessment_id: number; +}