diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 345b7314d..9562cf6a4 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -11,9 +11,12 @@ The CodeQL Action is used in GitHub Actions workflows to run CodeQL scans using ## Review instructions -- When wording review comments, be helpful and friendly. Assume that the PR author has written the code with the best of intentions. Word your comments constructively as suggestions for improvements. Do not word suggestions as commands. +- Prioritise identifying critical issues, such as logic bugs, over comments on documentation or typos. +- When wording review comments, be helpful and friendly. Assume that the PR author has written the code with the best of intentions. Never word suggestions as commands or using the imperative. Examples of what you should never write: "Preserve that non-fatal behaviour here" or "Please preserve that non-fatal behaviour here". +- When writing feedback, assume that the PR author is competent and is able to understand whether the point you are making is valid or not, why you are raising it, and how to best address it. While you can suggest how to address a point you are making, don't phrase it as a requirement or assume that it is the only solution. +- Always word your comments constructively as suggestions for improvements, for example: "It would make sense to ..." or "Could you add ...?" or "Would it make sense to ...?" - If you want to comment on a change that you believe will fail a CI check, do not present the CI failure you expect as a fact. Instead, write that you think a change "may" lead to a failure in CI. Suggest that, if such a failure manifests, the changes you are commenting on may be the place responsible for the failure and are worth looking at. -- If a suggestion you make is suitable for a follow-up, such as a refactoring that doesn't change the behaviour or fixing a typo in a comment, mention that it can be addressed in a later PR rather than blocking this one. +- If a suggestion you make is suitable for a follow-up, such as a refactoring that doesn't change the behaviour or fixing a typo in a comment, mention that it can be addressed in a later PR rather than blocking this one or prefix your comment with "Minor: ". - If a change is a net improvement, for example because it improves on an existing limitation of existing code, do not complain about pre-existing problems that remain. You may comment on them, but you should make it clear that the thing you are commenting on is not new by writing e.g. "Not new in this PR, but [..]" followed by your description of the issue and a suggestion that it could be improved at the same time with e.g. "Consider whether this is worth addressing as part of this PR as well." ## Generated code @@ -22,6 +25,16 @@ The main codebase of the CodeQL Action is written in TypeScript and compiled to GitHub Actions workflows in the `.github/workflows` directory whose filenames start with two underscores (e.g. `__all-platform-bundle.yml`) are automatically generated using the `pr-checks/sync.sh` script from template files in the `pr-checks/checks` directory. Therefore, you do not need to review files in the `.github/workflows` directory that start with two underscores. However, you should review changes to the `pr-checks` directory as well as workflows in the `.github/workflows` directory that do not start with underscores. +## Feature flags + +Some functionality in the code may be gated behind feature flags, which are controlled server-side. If the functionality that is gated behind them is user-facing, then this should be documented in a change note at the time that the gated functionality is added. See `CHANGELOG.md` for existing notes that mention "experimental" for examples. These typically include information about when users may expect to see the feature rolled out. + +When a feature flag that gates some functionality is removed, this does not need to be documented in a change note since the feature is already rolled out and the notes for the release which introduced the feature already describe it. The only exception is functionality which will start having an effect on GHES as a result of the feature flag removal: + +- Most functionality gated behind feature flags does not affect GHES. +- We can make functionality available on GHES by setting `defaultValue` to `true` first and waiting for this to have had an effect on GHES. No change note is needed if a feature flag is removed where `defaultValue` was `true`. +- When removing a feature flag where `defaultValue` was `false` and the functionality starts to affect GHES, a change note may be appropriate. + ## Release process Changes that are merged to the `main` branch are not used in production straight away. Instead, changes from `main` must first be released using the release process, which has to be manually triggered by a staff member. Therefore, it is possible for further changes to be made to the code after some changes have been merged to `main` but before they are released. Once the release process automation is started, a series of PRs are automatically generated: diff --git a/.github/workflows/__bundle-from-toolcache.yml b/.github/workflows/__bundle-from-toolcache.yml index a1c1fade0..1a307ba2e 100644 --- a/.github/workflows/__bundle-from-toolcache.yml +++ b/.github/workflows/__bundle-from-toolcache.yml @@ -46,6 +46,15 @@ jobs: steps: - name: Check out repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Install Node.js + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + with: + node-version: 20.x + cache: npm + - name: Install newer npm + run: npm install -g npm@11.19.1 + - name: Install dependencies + run: npm ci - name: Prepare test id: prepare-test uses: ./.github/actions/prepare-test @@ -53,31 +62,20 @@ jobs: version: ${{ matrix.version }} use-all-platform-bundle: 'false' setup-kotlin: 'true' - - name: Install @actions/tool-cache - run: npm install @actions/tool-cache@3 - name: Check toolcache contains CodeQL continue-on-error: true - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - with: - script: | - const toolcache = require('@actions/tool-cache'); - const allCodeqlVersions = toolcache.findAllVersions('CodeQL'); - if (allCodeqlVersions.length === 0) { - throw new Error(`CodeQL could not be found in the toolcache`); - } + run: | + cd ./../action + npx tsx ./pr-checks/toolcache.ts installed + - id: setup-codeql uses: ./../action/setup-codeql with: tools: ${{ steps.prepare-test.outputs.tools-url }} + - name: Check CodeQL is installed within the toolcache - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - with: - script: | - const toolcache = require('@actions/tool-cache'); - const allCodeqlVersions = toolcache.findAllVersions('CodeQL'); - console.log(`Found CodeQL versions: ${allCodeqlVersions}`); - if (allCodeqlVersions.length === 0) { - throw new Error('CodeQL not found in toolcache'); - } + run: | + cd ./../action + npx tsx ./pr-checks/toolcache.ts installed env: CODEQL_ACTION_TEST_MODE: true diff --git a/.github/workflows/__bundle-toolcache.yml b/.github/workflows/__bundle-toolcache.yml index 0055f9470..96a901cf3 100644 --- a/.github/workflows/__bundle-toolcache.yml +++ b/.github/workflows/__bundle-toolcache.yml @@ -50,6 +50,15 @@ jobs: steps: - name: Check out repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Install Node.js + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + with: + node-version: 20.x + cache: npm + - name: Install newer npm + run: npm install -g npm@11.19.1 + - name: Install dependencies + run: npm ci - name: Prepare test id: prepare-test uses: ./.github/actions/prepare-test @@ -58,47 +67,30 @@ jobs: use-all-platform-bundle: 'false' setup-kotlin: 'true' - name: Remove CodeQL from toolcache - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - with: - script: | - const fs = require('fs'); - const path = require('path'); - const codeqlPath = path.join(process.env['RUNNER_TOOL_CACHE'], 'CodeQL'); - fs.rmdirSync(codeqlPath, { recursive: true }); - - name: Install @actions/tool-cache - run: npm install @actions/tool-cache@3 + run: | + cd ./../action + npx tsx ./pr-checks/toolcache.ts clear + - name: Check toolcache does not contain CodeQL - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - with: - script: | - const toolcache = require('@actions/tool-cache'); - const allCodeqlVersions = toolcache.findAllVersions('CodeQL'); - if (allCodeqlVersions.length !== 0) { - throw new Error(`CodeQL should not be found in the toolcache, but found ${allCodeqlVersions}`); - } - console.log('No versions of CodeQL found in the toolcache'); + run: | + cd ./../action + npx tsx ./pr-checks/toolcache.ts absent + - id: init uses: ./../action/init with: # Request multiple languages so this check uses the combined bundle. languages: javascript,python tools: ${{ steps.prepare-test.outputs.tools-url }} + - uses: ./../action/analyze with: output: ${{ runner.temp }}/results upload-database: false + - name: Check CodeQL is installed within the toolcache - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - with: - script: | - const toolcache = require('@actions/tool-cache'); - const allCodeqlVersions = toolcache.findAllVersions('CodeQL'); - console.log(`Found CodeQL versions: ${allCodeqlVersions}`); - if (allCodeqlVersions.length === 0) { - throw new Error('CodeQL not found in toolcache'); - } - if (allCodeqlVersions.length > 1) { - throw new Error('Multiple CodeQL versions found in toolcache'); - } + run: | + cd ./../action + npx tsx ./pr-checks/toolcache.ts installed -n 1 env: CODEQL_ACTION_TEST_MODE: true diff --git a/.github/workflows/__diagnostics-export.yml b/.github/workflows/__diagnostics-export.yml index 32361c922..930ff0eb4 100644 --- a/.github/workflows/__diagnostics-export.yml +++ b/.github/workflows/__diagnostics-export.yml @@ -48,6 +48,15 @@ jobs: steps: - name: Check out repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Install Node.js + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + with: + node-version: 20.x + cache: npm + - name: Install newer npm + run: npm install -g npm@11.19.1 + - name: Install dependencies + run: npm ci - name: Prepare test id: prepare-test uses: ./.github/actions/prepare-test @@ -81,56 +90,13 @@ jobs: name: diagnostics-export-${{ matrix.os }}-${{ matrix.version }}.sarif.json path: '${{ runner.temp }}/results/javascript.sarif' retention-days: 7 + - name: Check diagnostics appear in SARIF - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: SARIF_PATH: '${{ runner.temp }}/results/javascript.sarif' - with: - script: | - const fs = require('fs'); - - function checkStatusPageNotification(n) { - const expectedMessage = 'Plaintext message'; - if (n.message.text !== expectedMessage) { - core.setFailed(`Expected the status page diagnostic to have the message '${expectedMessage}', but found '${n.message.text}'.`); - } - if (n.locations.length !== 1) { - core.setFailed(`Expected the status page diagnostic to have exactly 1 location, but found ${n.locations.length}.`); - } - } - - const sarif = JSON.parse(fs.readFileSync(process.env['SARIF_PATH'], 'utf8')); - const run = sarif.runs[0]; - - const toolExecutionNotifications = run.invocations[0].toolExecutionNotifications; - const statusPageNotifications = toolExecutionNotifications.filter(n => - n.descriptor.id === 'lang/diagnostics/example' && n.properties?.visibility?.statusPage - ); - if (statusPageNotifications.length !== 1) { - core.setFailed( - 'Expected exactly one status page reporting descriptor for this diagnostic in the ' + - `'runs[].invocations[].toolExecutionNotifications[]' SARIF property, but found ` + - `${statusPageNotifications.length}. All notification reporting descriptors: ` + - `${JSON.stringify(toolExecutionNotifications)}.` - ); - } - checkStatusPageNotification(statusPageNotifications[0]); - - const notifications = run.tool.driver.notifications; - const diagnosticNotification = notifications.filter(n => - n.id === 'lang/diagnostics/example' && n.name === 'lang/diagnostics/example' && - n.fullDescription.text === 'Diagnostic name' - ); - if (diagnosticNotification.length !== 1) { - core.setFailed( - 'Expected exactly one notification for this diagnostic in the ' + - `'runs[].tool.driver.notifications[]' SARIF property, but found ` + - `${diagnosticNotification.length}. All notifications: ` + - `${JSON.stringify(notifications)}.` - ); - } - - core.info('Finished diagnostic export test'); + run: | + cd ../action + npx tsx ./pr-checks/check-diagnostics.ts env: CODEQL_ACTION_EXPORT_DIAGNOSTICS: true CODEQL_ACTION_TEST_MODE: true diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index ccbe4a6d2..5d2b1576f 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -2,6 +2,9 @@ name: PR Checks on: push: + branches: + - "main" + - "releases/v*" pull_request: merge_group: types: [checks_requested] @@ -145,12 +148,11 @@ jobs: - name: Fetch base commit id: fetch-base - # Forks and Dependabot PRs don't have permission to write comments, so skip the repo size - # check in those cases. + # Skip this (and subsequent steps) if the PR originates from a fork, since this workflow + # won't have the necessary permissions to post comments on the PR. if: >- github.event_name == 'pull_request' && - github.event.pull_request.head.repo.full_name == github.repository && - github.event.pull_request.user.login != 'dependabot[bot]' + github.event.pull_request.head.repo.full_name == github.repository env: BASE_SHA: ${{ github.event.pull_request.base.sha }} HEAD_SHA: ${{ github.event.pull_request.head.sha }} @@ -209,7 +211,7 @@ jobs: if: >- github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && - github.event.pull_request.user.login != 'dependabot[bot]' && + github.actor != 'dependabot[bot]' && needs.other-checks.result == 'success' permissions: contents: read diff --git a/CHANGELOG.md b/CHANGELOG.md index b63b79671..8a96e0a9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ See the [releases page](https://github.com/github/codeql-action/releases) for th No user facing changes. +## 4.39.1 - 21 Oct 2026 + +- Update default CodeQL bundle version to [2.27.3](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.27.3). [#4256](https://github.com/github/codeql-action/pull/4256) + ## 4.39.0 - 15 Oct 2026 - _Breaking change_: Bump the minimum required CodeQL bundle version to 2.20.7. [#4205](https://github.com/github/codeql-action/pull/4205) diff --git a/init/action.yml b/init/action.yml index 7787a0a07..8d26999f7 100644 --- a/init/action.yml +++ b/init/action.yml @@ -153,7 +153,10 @@ inputs: required: false trap-caching: description: >- - Explicitly enable or disable TRAP caching rather than respecting the feature flag for it. + Explicitly enable or disable TRAP caching instead of using the automatically determined default. + TRAP caching is disabled by default when improved incremental analysis is enabled, + because improved incremental analysis of a pull request only extracts changed files, + which rarely benefit from TRAP caching. required: false dependency-caching: description: >- diff --git a/lib/defaults.json b/lib/defaults.json index 71decd839..cde5136e2 100644 --- a/lib/defaults.json +++ b/lib/defaults.json @@ -1,6 +1,6 @@ { - "bundleVersion": "codeql-bundle-v2.27.2", - "cliVersion": "2.27.2", - "priorBundleVersion": "codeql-bundle-v2.27.1", - "priorCliVersion": "2.27.1" + "bundleVersion": "codeql-bundle-v2.27.3", + "cliVersion": "2.27.3", + "priorBundleVersion": "codeql-bundle-v2.27.2", + "priorCliVersion": "2.27.2" } diff --git a/lib/entry-points.js b/lib/entry-points.js index 8d9d423be..e9741ef55 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -169889,8 +169889,15 @@ async function delay(milliseconds, opts) { function isGoodVersion(versionSpec) { return !BROKEN_VERSIONS.includes(versionSpec); } -function shouldSkipSarifUpload() { - return isInTestMode() || process.env["CODEQL_ACTION_SKIP_SARIF_UPLOAD" /* SKIP_SARIF_UPLOAD */] === "true"; +function shouldSkipGenerateFailedSarif(env = getEnv()) { + const skipGenerateFailedSarif = env.getOptional( + "CODEQL_ACTION_SKIP_GENERATE_FAILED_SARIF" /* SKIP_GENERATE_FAILED_SARIF */ + ); + return skipGenerateFailedSarif !== "false" && (shouldSkipSarifUpload(env) || skipGenerateFailedSarif === "true"); +} +function shouldSkipSarifUpload(env = getEnv()) { + const skipSarifUpload = env.getOptional("CODEQL_ACTION_SKIP_SARIF_UPLOAD" /* SKIP_SARIF_UPLOAD */); + return skipSarifUpload !== "false" && (env.isSkippingUploadsInTests() || skipSarifUpload === "true"); } function getTestingEnvironment() { const testingEnvironment = process.env["CODEQL_ACTION_TESTING_ENVIRONMENT" /* TESTING_ENVIRONMENT */] || ""; @@ -170154,7 +170161,7 @@ function getDiffRangesJsonFilePath(env = getEnv()) { return path2.join(getTemporaryDirectory(env), PR_DIFF_RANGE_JSON_FILENAME); } function getActionVersion() { - return "4.39.1"; + return "4.39.2"; } function getWorkflowEventName(env = getEnv()) { return env.getRequired("GITHUB_EVENT_NAME" /* GITHUB_EVENT_NAME */); @@ -170452,12 +170459,24 @@ function fixCodeQualityCategory(logger2, category, env = getEnv()) { // src/logging.ts var core5 = __toESM(require_core()); +var joinMessageStrings = (message) => { + if (Array.isArray(message)) { + return message.join(" "); + } else { + return message; + } +}; +var autoJoinMessageStrings = (fn) => { + return (message) => { + return fn(joinMessageStrings(message)); + }; +}; function getActionsLogger() { return { - debug: core5.debug, - info: core5.info, - warning: core5.warning, - error: core5.error, + debug: autoJoinMessageStrings(core5.debug), + info: autoJoinMessageStrings(core5.info), + warning: autoJoinMessageStrings(core5.warning), + error: autoJoinMessageStrings(core5.error), isDebug: core5.isDebug, startGroup: core5.startGroup, endGroup: core5.endGroup @@ -170867,14 +170886,6 @@ async function listActionsCaches(keyPrefix, ref) { } ); } -async function deleteActionsCache(id) { - const repositoryNwo = getRepositoryNwo(); - await getApiClient().rest.actions.deleteActionsCacheById({ - owner: repositoryNwo.owner, - repo: repositoryNwo.repo, - cache_id: id - }); -} async function getRepositoryProperties(repositoryNwo) { return getApiClient().request("GET /repos/:owner/:repo/properties/values", { owner: repositoryNwo.owner, @@ -171792,8 +171803,8 @@ var path6 = __toESM(require("path")); var semver3 = __toESM(require_semver2()); // src/defaults.json -var bundleVersion = "codeql-bundle-v2.27.2"; -var cliVersion = "2.27.2"; +var bundleVersion = "codeql-bundle-v2.27.3"; +var cliVersion = "2.27.3"; // src/overlay/index.ts var fs5 = __toESM(require("fs")); @@ -171932,11 +171943,6 @@ var featureConfig = { envVar: "CODEQL_ACTION_ALLOW_MULTIPLE_ANALYSIS_KINDS", minimumVersion: void 0 }, - ["cleanup_trap_caches" /* CleanupTrapCaches */]: { - defaultValue: false, - envVar: "CODEQL_ACTION_CLEANUP_TRAP_CACHES", - minimumVersion: void 0 - }, ["config_file_repository_property" /* ConfigFileRepositoryProperty */]: { defaultValue: false, envVar: "CODEQL_ACTION_CONFIG_FILE_REPOSITORY_PROPERTY", @@ -172085,11 +172091,6 @@ var featureConfig = { minimumVersion: CODEQL_OVERLAY_MINIMUM_VERSION_RUBY }, // Other overlay-related feature flags - ["overlay_analysis_disable_trap_caching" /* OverlayAnalysisDisableTrapCaching */]: { - defaultValue: false, - envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_DISABLE_TRAP_CACHING", - minimumVersion: void 0 - }, ["overlay_analysis_match_codeql_version" /* OverlayAnalysisMatchCodeqlVersion */]: { defaultValue: false, envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_MATCH_CODEQL_VERSION", @@ -174245,83 +174246,16 @@ async function uploadTrapCaches(codeql, config, logger2) { } return true; } -async function cleanupTrapCaches(config, features, logger2) { - const env = getEnv(); - if (!await features.getValue("cleanup_trap_caches" /* CleanupTrapCaches */)) { - return { - trap_cache_cleanup_skipped_because: "feature disabled" - }; - } - logger2.warning( - "TRAP cache cleanup is deprecated and will be removed in May 2026. We recommend instead disabling TRAP caching by passing the `trap-caching: false` input to the `init` Action." - ); - if (!await isAnalyzingDefaultBranch(env, config.repositoryRoot)) { - return { - trap_cache_cleanup_skipped_because: "not analyzing default branch" - }; - } - try { - let totalBytesCleanedUp = 0; - const allCaches = await listActionsCaches( - CODEQL_TRAP_CACHE_PREFIX, - await getRef(env, config.repositoryRoot) +function warnIfTrapCacheCleanupEnvVarIsSet({ + env, + logger: logger2 +}) { + if (env.getOptional("CODEQL_ACTION_CLEANUP_TRAP_CACHES") !== void 0) { + logger2.warning( + "TRAP cache cleanup has been removed, so the CODEQL_ACTION_CLEANUP_TRAP_CACHES environment variable no longer has any effect and can be removed from your workflow. If you used TRAP cache cleanup to limit Actions cache usage, we recommend disabling TRAP caching instead by passing the `trap-caching: false` input to the `init` Action." ); - for (const language of config.languages) { - if (config.trapCaches[language]) { - const cachesToRemove = await getTrapCachesForLanguage( - allCaches, - language, - logger2 - ); - cachesToRemove.sort((a, b) => a.created_at.localeCompare(b.created_at)); - const mostRecentCache = cachesToRemove.pop(); - logger2.debug( - `Keeping most recent TRAP cache (${JSON.stringify(mostRecentCache)})` - ); - if (cachesToRemove.length === 0) { - logger2.info(`No TRAP caches to clean up for ${language}.`); - continue; - } - for (const cache of cachesToRemove) { - logger2.debug(`Cleaning up TRAP cache (${JSON.stringify(cache)})`); - await deleteActionsCache(cache.id); - } - const bytesCleanedUp = cachesToRemove.reduce( - (acc, item) => acc + item.size_in_bytes, - 0 - ); - totalBytesCleanedUp += bytesCleanedUp; - const megabytesCleanedUp = (bytesCleanedUp / (1024 * 1024)).toFixed(2); - logger2.info( - `Cleaned up ${megabytesCleanedUp} MiB of old TRAP caches for ${language}.` - ); - } - } - return { trap_cache_cleanup_size_bytes: totalBytesCleanedUp }; - } catch (e) { - if (asHTTPError(e)?.status === 403) { - logger2.warning( - `Could not cleanup TRAP caches as the token did not have the required permissions. To clean up TRAP caches, ensure the token has the "actions:write" permission. See ${"https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs" /* ASSIGNING_PERMISSIONS_TO_JOBS */} for more information.` - ); - } else { - logger2.info(`Failed to cleanup TRAP caches, continuing. Details: ${e}`); - } - return { trap_cache_cleanup_error: getErrorMessage(e) }; } } -async function getTrapCachesForLanguage(allCaches, language, logger2) { - logger2.debug(`Listing TRAP caches for ${language}`); - for (const cache of allCaches) { - if (!cache.created_at || !cache.id || !cache.key || !cache.size_in_bytes) { - throw new Error( - `An unexpected cache item was returned from the API that was missing one or more required fields: ${JSON.stringify(cache)}` - ); - } - } - return allCaches.filter((cache) => { - return cache.key?.includes(`-${language}-`); - }); -} async function getLanguagesSupportingCaching(codeql, languages, logger2) { const result = []; const resolveResult = await codeql.resolveLanguages(); @@ -174829,11 +174763,11 @@ async function validateOverlayDatabaseMode(overlayDatabaseMode, useOverlayDataba overlayModeSetExplicitly }); } -async function isTrapCachingEnabled(features, overlayDatabaseMode) { +function isTrapCachingEnabled(overlayDatabaseMode) { const trapCaching = getOptionalInput("trap-caching"); if (trapCaching !== void 0) return trapCaching === "true"; if (!looksLikeHostedRunner()) return false; - if (overlayDatabaseMode !== "none" /* None */ && await features.getValue("overlay_analysis_disable_trap_caching" /* OverlayAnalysisDisableTrapCaching */)) { + if (overlayDatabaseMode !== "none" /* None */) { return false; } return true; @@ -175036,7 +174970,7 @@ async function initConfig(actionState, inputs) { inputs.codeql, logger2 ); - if (await isTrapCachingEnabled(features, config.overlayDatabaseMode)) { + if (isTrapCachingEnabled(config.overlayDatabaseMode)) { const { trapCaches, trapCacheDownloadTime } = await downloadCacheWithTime( inputs.codeql, config.languages, @@ -178524,6 +178458,7 @@ __export(upload_lib_exports, { filterAlertsByDiffRange: () => filterAlertsByDiffRange, findSarifFilesInDir: () => findSarifFilesInDir, getGroupedSarifFilePaths: () => getGroupedSarifFilePaths, + getSavedPayloadPath: () => getSavedPayloadPath, populateRunAutomationDetails: () => populateRunAutomationDetails, postProcessSarifFiles: () => postProcessSarifFiles, readSarifFileOrThrow: () => readSarifFileOrThrow, @@ -179999,7 +179934,7 @@ async function shouldDisableCombineSarifFiles(sarifObjects, githubVersion) { } return true; } -async function combineSarifFilesUsingCLI(sarifFiles, gitHubVersion, features, logger2) { +async function combineSarifFilesUsingCLI(sarifFiles, gitHubVersion, features, logger2, config) { const env = getEnv(); logger2.info("Combining SARIF files using the CodeQL CLI"); const sarifObjects = sarifFiles.map(readSarifFile); @@ -180020,7 +179955,6 @@ async function combineSarifFilesUsingCLI(sarifFiles, gitHubVersion, features, lo } let codeQL; let tempDir = getTemporaryDirectory(); - const config = await getConfig(tempDir, logger2); if (config !== void 0) { codeQL = await getCodeQL(logger2, config.codeQLCmd); tempDir = config.tempDir; @@ -180087,13 +180021,16 @@ function getAutomationID2(category, analysis_key, environment) { } return computeAutomationID(analysis_key, environment); } +function getSavedPayloadPath(analysisKind) { + return path19.join( + getTemporaryDirectory(), + `payload-${analysisKind}.json` + ); +} async function uploadPayload(payload, repositoryNwo, logger2, analysis) { logger2.info("Uploading results"); if (shouldSkipSarifUpload()) { - const payloadSaveFile = path19.join( - getTemporaryDirectory(), - `payload-${analysis.kind}.json` - ); + const payloadSaveFile = getSavedPayloadPath(analysis.kind); logger2.info( `SARIF upload disabled by an environment variable. Saving to ${payloadSaveFile}` ); @@ -180305,7 +180242,7 @@ function buildPayload(commitOid, ref, analysisKey, analysisName, zippedSarif, wo } return payloadObj; } -async function postProcessSarifFiles(logger2, features, checkoutPath, sarifPaths, category, analysis) { +async function postProcessSarifFiles(logger2, features, config, checkoutPath, sarifPaths, category, analysis) { logger2.info(`Post-processing sarif files: ${JSON.stringify(sarifPaths)}`); const gitHubVersion = await getGitHubVersion(); let sarifLog; @@ -180319,7 +180256,8 @@ async function postProcessSarifFiles(logger2, features, checkoutPath, sarifPaths sarifPaths, gitHubVersion, features, - logger2 + logger2, + config ); } else { const sarifPath = sarifPaths[0]; @@ -180352,13 +180290,14 @@ async function writePostProcessedFiles(logger2, pathInput, uploadTarget, postPro logger2.debug(`Not writing post-processed SARIF files.`); } } -async function uploadFiles(inputSarifPath, checkoutPath, category, features, logger2, uploadTarget) { +async function uploadFiles(config, inputSarifPath, checkoutPath, category, features, logger2, uploadTarget) { const sarifPaths = getSarifFilePaths( inputSarifPath, uploadTarget.sarifPredicate ); return uploadSpecifiedFiles( sarifPaths, + config, checkoutPath, category, features, @@ -180366,10 +180305,11 @@ async function uploadFiles(inputSarifPath, checkoutPath, category, features, log uploadTarget ); } -async function uploadSpecifiedFiles(sarifPaths, checkoutPath, category, features, logger2, uploadTarget) { +async function uploadSpecifiedFiles(sarifPaths, config, checkoutPath, category, features, logger2, uploadTarget) { const processingResults = await postProcessSarifFiles( logger2, features, + config, checkoutPath, sarifPaths, category, @@ -180454,6 +180394,12 @@ async function waitForProcessing(repositoryNwo, sarifID, logger2, options = { }) { logger2.startGroup("Waiting for processing to finish"); try { + if (shouldSkipSarifUpload()) { + logger2.info( + "Nothing to wait for, because SARIF upload is disabled by an environment variable and the SARIF was saved to disk." + ); + return; + } const client = getApiClient(); let statusCheckBackoff = STATUS_CHECK_INITIAL_BACKOFF_MILLISECONDS; if (process.env["NODE_ENV"] !== "test") { @@ -180599,7 +180545,7 @@ function filterAlertsByDiffRange(logger2, sarifLog) { } // src/upload-sarif.ts -async function postProcessAndUploadSarif(logger2, features, uploadKind, checkoutPath, sarifPath, category, postProcessedOutputPath) { +async function postProcessAndUploadSarif(logger2, features, config, uploadKind, checkoutPath, sarifPath, category, postProcessedOutputPath) { const sarifGroups = await getGroupedSarifFilePaths( logger2, sarifPath @@ -180612,6 +180558,7 @@ async function postProcessAndUploadSarif(logger2, features, uploadKind, checkout const postProcessingResults = await postProcessSarifFiles( logger2, features, + config, checkoutPath, sarifFiles, category, @@ -180636,7 +180583,7 @@ async function postProcessAndUploadSarif(logger2, features, uploadKind, checkout } // src/analyze-action.ts -async function sendStatusReport2(startedAt, config, stats, error3, trapCacheUploadTime, dbCreationTimings, didUploadTrapCaches, trapCacheCleanup, dependencyCacheResults, databaseUploadResults, logger2) { +async function sendStatusReport2(startedAt, config, stats, error3, trapCacheUploadTime, dbCreationTimings, didUploadTrapCaches, dependencyCacheResults, databaseUploadResults, logger2) { const status = getActionsStatus(error3, stats?.analyze_failure_language); const statusReportBase = await createStatusReportBase( "finish" /* Analyze */, @@ -180653,7 +180600,6 @@ async function sendStatusReport2(startedAt, config, stats, error3, trapCacheUplo ...statusReportBase, ...stats || {}, ...dbCreationTimings || {}, - ...trapCacheCleanup || {}, dependency_caching_upload_results: dependencyCacheResults, database_upload_results: databaseUploadResults }; @@ -180737,7 +180683,6 @@ async function run(action) { let uploadResults = void 0; let runStats = void 0; let config = void 0; - let trapCacheCleanupTelemetry = void 0; let trapCacheUploadTime = void 0; let dbCreationTimings = void 0; let didUploadTrapCaches = false; @@ -180774,6 +180719,7 @@ async function run(action) { "The 'cleanup-level' input is ignored since the CodeQL Action now automatically manages database cleanup. This input can safely be removed from your workflow." ); } + warnIfTrapCacheCleanupEnvVarIsSet(action); const apiDetails = getApiDetails(); const outputDir = getRequiredInput("output"); action.env.export("CODEQL_ACTION_SARIF_RESULTS_OUTPUT_DIR" /* SARIF_RESULTS_OUTPUT_DIR */, outputDir); @@ -180842,6 +180788,7 @@ async function run(action) { uploadResults = await postProcessAndUploadSarif( logger2, features, + config, uploadKind, checkoutPath, outputDir, @@ -180881,11 +180828,6 @@ async function run(action) { const trapCacheUploadStartTime = import_perf_hooks6.performance.now(); didUploadTrapCaches = await uploadTrapCaches(codeql, config, logger2); trapCacheUploadTime = import_perf_hooks6.performance.now() - trapCacheUploadStartTime; - trapCacheCleanupTelemetry = await cleanupTrapCaches( - config, - features, - logger2 - ); if (shouldStoreCache(config.dependencyCachingEnabled)) { dependencyCacheResults = await uploadDependencyCaches( codeql, @@ -180894,8 +180836,10 @@ async function run(action) { logger2 ); } - if (isInTestMode()) { - logger2.debug("In test mode. Waiting for processing is disabled."); + if (shouldSkipSarifUpload()) { + logger2.debug( + "SARIF upload is disabled and nothing was uploaded, so we do not need to wait for processing to finish." + ); } else if (uploadResults?.["code-scanning" /* CodeScanning */] !== void 0 && getRequiredInput("wait-for-processing") === "true") { await waitForProcessing( getRepositoryNwo(), @@ -180922,7 +180866,6 @@ async function run(action) { trapCacheUploadTime, dbCreationTimings, didUploadTrapCaches, - trapCacheCleanupTelemetry, dependencyCacheResults, databaseUploadResults, logger2 @@ -180941,7 +180884,6 @@ async function run(action) { trapCacheUploadTime, dbCreationTimings, didUploadTrapCaches, - trapCacheCleanupTelemetry, dependencyCacheResults, databaseUploadResults, logger2 @@ -180955,7 +180897,6 @@ async function run(action) { trapCacheUploadTime, dbCreationTimings, didUploadTrapCaches, - trapCacheCleanupTelemetry, dependencyCacheResults, databaseUploadResults, logger2 @@ -180969,7 +180910,6 @@ async function run(action) { trapCacheUploadTime, dbCreationTimings, didUploadTrapCaches, - trapCacheCleanupTelemetry, dependencyCacheResults, databaseUploadResults, logger2 @@ -186888,7 +186828,7 @@ async function prepareFailedSarif(logger2, features, config) { } const jobName = getRequiredEnvParam("GITHUB_JOB"); const matrix = parseMatrixInput(getRequiredInput("matrix")); - if (shouldSkipSarifUpload()) { + if (shouldSkipGenerateFailedSarif()) { return new Failure({ upload_failed_run_skipped_because: "SARIF upload is disabled" }); @@ -186959,6 +186899,7 @@ async function maybeUploadFailedSarif(config, repositoryNwo, features, logger2) const failedSarif = failedSarifResult.value; logger2.info(`Uploading failed SARIF file ${failedSarif.sarifFile}`); const uploadResult = await uploadFiles( + config, failedSarif.sarifFile, failedSarif.checkoutPath, failedSarif.category, @@ -186972,6 +186913,9 @@ async function maybeUploadFailedSarif(config, repositoryNwo, features, logger2) logger2, { isUnsuccessfulExecution: true } ); + if (uploadResult.sarifID && shouldSkipSarifUpload()) { + await uploadSarifArtifact(logger2, "test-mode-sarif", failedSarif); + } return uploadResult ? { ...uploadResult.statusReport, sarifID: uploadResult.sarifID } : {}; } async function maybeUploadFailedSarifArtifact(config, features, logger2) { @@ -186980,19 +186924,25 @@ async function maybeUploadFailedSarifArtifact(config, features, logger2) { return failedSarifResult.value; } const failedSarif = failedSarifResult.value; - logger2.info( - `Uploading failed SARIF file ${failedSarif.sarifFile} as artifact` - ); - const gitHubVersion = await getGitHubVersion(); - const client = await getArtifactUploaderClient(logger2, gitHubVersion.type); - const suffix = getArtifactSuffix(getOptionalInput("matrix")); - const name = sanitizeArtifactName(`sarif-artifact-${suffix}`); - await client.uploadArtifact( - name, - [import_path8.default.normalize(failedSarif.sarifFile)], - import_path8.default.normalize("..") - ); - return { sarifID: name }; + return uploadSarifArtifact(logger2, "sarif-artifact", failedSarif); +} +async function uploadSarifArtifact(logger2, prefix, failedSarif) { + logger2.startGroup(`Uploading failed SARIF file as artifact`); + logger2.info(`Uploading '${failedSarif.sarifFile}' as artifact`); + try { + const gitHubVersion = await getGitHubVersion(); + const client = await getArtifactUploaderClient(logger2, gitHubVersion.type); + const suffix = getArtifactSuffix(getOptionalInput("matrix")); + const name = sanitizeArtifactName(`${prefix}-${suffix}`); + await client.uploadArtifact( + name, + [import_path8.default.normalize(failedSarif.sarifFile)], + import_path8.default.normalize(import_path8.default.dirname(failedSarif.sarifFile)) + ); + return { sarifID: name }; + } finally { + logger2.endGroup(); + } } async function tryUploadSarifIfRunFailed(config, repositoryNwo, features, logger2) { if (process.env["CODEQL_ACTION_ANALYZE_DID_COMPLETE_SUCCESSFULLY" /* ANALYZE_DID_COMPLETE_SUCCESSFULLY */] === "true") { @@ -187217,6 +187167,9 @@ async function run4(action) { const tmpDir = getTemporaryDirectory(); config = await getConfig(tmpDir, logger2); if (config === void 0) { + logger2.debug( + "Will try to load the configuration file from an aborted 'init' step, since there is no regular one." + ); config = getAbortedInitConfig(action, tmpDir); } const gitHubVersion = config?.gitHubVersion ?? await getGitHubVersion(); @@ -188435,11 +188388,12 @@ async function run8({ startedAt, logger: logger2 }) { const gitHubVersion = await getGitHubVersion(); checkActionVersion(getActionVersion(), gitHubVersion); persistInputs(); + const tempDir = getTemporaryDirectory(); const repositoryNwo = getRepositoryNwo(); const features = initFeatures( gitHubVersion, repositoryNwo, - getTemporaryDirectory(), + tempDir, logger2 ); const startingStatusReportBase = await createStatusReportBase( @@ -188456,9 +188410,11 @@ async function run8({ startedAt, logger: logger2 }) { const sarifPath = getRequiredInput("sarif_file"); const checkoutPath = getRequiredInput("checkout_path"); const category = getOptionalInput("category"); + const maybeConfig = await getConfig(tempDir, logger2); const uploadResults = await postProcessAndUploadSarif( logger2, features, + maybeConfig, "always", checkoutPath, sarifPath, diff --git a/package-lock.json b/package-lock.json index d702b0f6f..a22b5686d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "codeql", - "version": "4.39.1", + "version": "4.39.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "codeql", - "version": "4.39.1", + "version": "4.39.2", "license": "MIT", "workspaces": [ "pr-checks" @@ -10484,6 +10484,7 @@ "dependencies": { "@actions/core": "^2.0.3", "@actions/github": "^8.0.1", + "@actions/tool-cache": "^3.0.1", "@octokit/core": "^7.0.8", "@octokit/plugin-paginate-rest": ">=16.0.0", "@octokit/plugin-rest-endpoint-methods": "^18.0.0", diff --git a/package.json b/package.json index 209ded679..9bfd79222 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codeql", - "version": "4.39.1", + "version": "4.39.2", "private": true, "description": "CodeQL action", "scripts": { diff --git a/pr-checks/check-diagnostics.ts b/pr-checks/check-diagnostics.ts new file mode 100644 index 000000000..0a35ac28b --- /dev/null +++ b/pr-checks/check-diagnostics.ts @@ -0,0 +1,77 @@ +import fs from "fs"; + +import * as core from "@actions/core"; + +import * as sarif from "../src/sarif"; + +function checkStatusPageNotification(n) { + const expectedMessage = "Plaintext message"; + if (n.message.text !== expectedMessage) { + core.setFailed( + `Expected the status page diagnostic to have the message '${expectedMessage}', but found '${n.message.text}'.`, + ); + } + if (n.locations.length !== 1) { + core.setFailed( + `Expected the status page diagnostic to have exactly 1 location, but found ${n.locations.length}.`, + ); + } +} + +const sarifPath = process.env["SARIF_PATH"]; + +if (sarifPath === undefined) { + throw new Error("The path to a SARIF file must be set in 'SARIF_PATH'."); +} + +const log = JSON.parse(fs.readFileSync(sarifPath, "utf8")) as sarif.Log; +const run = log.runs[0]; + +if (run.invocations === undefined) { + throw new Error("run.invocations is undefined"); +} + +const toolExecutionNotifications = + run.invocations[0].toolExecutionNotifications; + +if (toolExecutionNotifications === undefined) { + throw new Error("toolExecutionNotifications is undefined"); +} + +const statusPageNotifications = toolExecutionNotifications.filter( + (n) => + n.descriptor?.id === "lang/diagnostics/example" && + n.properties?.visibility?.statusPage, +); +if (statusPageNotifications.length !== 1) { + core.setFailed( + "Expected exactly one status page reporting descriptor for this diagnostic in the " + + `'runs[].invocations[].toolExecutionNotifications[]' SARIF property, but found ` + + `${statusPageNotifications.length}. All notification reporting descriptors: ` + + `${JSON.stringify(toolExecutionNotifications)}.`, + ); +} +checkStatusPageNotification(statusPageNotifications[0]); + +const notifications = run.tool.driver.notifications; + +if (notifications === undefined) { + throw new Error("notifications is undefined"); +} + +const diagnosticNotification = notifications.filter( + (n) => + n.id === "lang/diagnostics/example" && + n.name === "lang/diagnostics/example" && + n.fullDescription?.text === "Diagnostic name", +); +if (diagnosticNotification.length !== 1) { + core.setFailed( + "Expected exactly one notification for this diagnostic in the " + + `'runs[].tool.driver.notifications[]' SARIF property, but found ` + + `${diagnosticNotification.length}. All notifications: ` + + `${JSON.stringify(notifications)}.`, + ); +} + +core.info("Finished diagnostic export test"); diff --git a/pr-checks/checks/bundle-from-toolcache.yml b/pr-checks/checks/bundle-from-toolcache.yml index 0e51e2e12..947b27390 100644 --- a/pr-checks/checks/bundle-from-toolcache.yml +++ b/pr-checks/checks/bundle-from-toolcache.yml @@ -2,30 +2,20 @@ name: "Bundle: From toolcache" description: "The CodeQL bundle should be cached within the toolcache" versions: - toolcache +installNode: true steps: - - name: Install @actions/tool-cache - run: npm install @actions/tool-cache@3 - name: Check toolcache contains CodeQL continue-on-error: true - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - with: - script: | - const toolcache = require('@actions/tool-cache'); - const allCodeqlVersions = toolcache.findAllVersions('CodeQL'); - if (allCodeqlVersions.length === 0) { - throw new Error(`CodeQL could not be found in the toolcache`); - } + run: | + cd ./../action + npx tsx ./pr-checks/toolcache.ts installed + - id: setup-codeql uses: ./../action/setup-codeql with: tools: ${{ steps.prepare-test.outputs.tools-url }} + - name: Check CodeQL is installed within the toolcache - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - with: - script: | - const toolcache = require('@actions/tool-cache'); - const allCodeqlVersions = toolcache.findAllVersions('CodeQL'); - console.log(`Found CodeQL versions: ${allCodeqlVersions}`); - if (allCodeqlVersions.length === 0) { - throw new Error('CodeQL not found in toolcache'); - } + run: | + cd ./../action + npx tsx ./pr-checks/toolcache.ts installed diff --git a/pr-checks/checks/bundle-toolcache.yml b/pr-checks/checks/bundle-toolcache.yml index f74c6af75..3317a45f4 100644 --- a/pr-checks/checks/bundle-toolcache.yml +++ b/pr-checks/checks/bundle-toolcache.yml @@ -6,47 +6,31 @@ operatingSystems: - ubuntu - macos - windows +installNode: true steps: - name: Remove CodeQL from toolcache - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - with: - script: | - const fs = require('fs'); - const path = require('path'); - const codeqlPath = path.join(process.env['RUNNER_TOOL_CACHE'], 'CodeQL'); - fs.rmdirSync(codeqlPath, { recursive: true }); - - name: Install @actions/tool-cache - run: npm install @actions/tool-cache@3 + run: | + cd ./../action + npx tsx ./pr-checks/toolcache.ts clear + - name: Check toolcache does not contain CodeQL - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - with: - script: | - const toolcache = require('@actions/tool-cache'); - const allCodeqlVersions = toolcache.findAllVersions('CodeQL'); - if (allCodeqlVersions.length !== 0) { - throw new Error(`CodeQL should not be found in the toolcache, but found ${allCodeqlVersions}`); - } - console.log('No versions of CodeQL found in the toolcache'); + run: | + cd ./../action + npx tsx ./pr-checks/toolcache.ts absent + - id: init uses: ./../action/init with: # Request multiple languages so this check uses the combined bundle. languages: javascript,python tools: ${{ steps.prepare-test.outputs.tools-url }} + - uses: ./../action/analyze with: output: ${{ runner.temp }}/results upload-database: false + - name: Check CodeQL is installed within the toolcache - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - with: - script: | - const toolcache = require('@actions/tool-cache'); - const allCodeqlVersions = toolcache.findAllVersions('CodeQL'); - console.log(`Found CodeQL versions: ${allCodeqlVersions}`); - if (allCodeqlVersions.length === 0) { - throw new Error('CodeQL not found in toolcache'); - } - if (allCodeqlVersions.length > 1) { - throw new Error('Multiple CodeQL versions found in toolcache'); - } + run: | + cd ./../action + npx tsx ./pr-checks/toolcache.ts installed -n 1 diff --git a/pr-checks/checks/diagnostics-export.yml b/pr-checks/checks/diagnostics-export.yml index 93b3a8f60..0548531b9 100644 --- a/pr-checks/checks/diagnostics-export.yml +++ b/pr-checks/checks/diagnostics-export.yml @@ -5,6 +5,7 @@ versions: - nightly-latest env: CODEQL_ACTION_EXPORT_DIAGNOSTICS: true +installNode: true steps: - uses: ./../action/init id: init @@ -32,53 +33,10 @@ steps: name: diagnostics-export-${{ matrix.os }}-${{ matrix.version }}.sarif.json path: "${{ runner.temp }}/results/javascript.sarif" retention-days: 7 + - name: Check diagnostics appear in SARIF - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: SARIF_PATH: "${{ runner.temp }}/results/javascript.sarif" - with: - script: | - const fs = require('fs'); - - function checkStatusPageNotification(n) { - const expectedMessage = 'Plaintext message'; - if (n.message.text !== expectedMessage) { - core.setFailed(`Expected the status page diagnostic to have the message '${expectedMessage}', but found '${n.message.text}'.`); - } - if (n.locations.length !== 1) { - core.setFailed(`Expected the status page diagnostic to have exactly 1 location, but found ${n.locations.length}.`); - } - } - - const sarif = JSON.parse(fs.readFileSync(process.env['SARIF_PATH'], 'utf8')); - const run = sarif.runs[0]; - - const toolExecutionNotifications = run.invocations[0].toolExecutionNotifications; - const statusPageNotifications = toolExecutionNotifications.filter(n => - n.descriptor.id === 'lang/diagnostics/example' && n.properties?.visibility?.statusPage - ); - if (statusPageNotifications.length !== 1) { - core.setFailed( - 'Expected exactly one status page reporting descriptor for this diagnostic in the ' + - `'runs[].invocations[].toolExecutionNotifications[]' SARIF property, but found ` + - `${statusPageNotifications.length}. All notification reporting descriptors: ` + - `${JSON.stringify(toolExecutionNotifications)}.` - ); - } - checkStatusPageNotification(statusPageNotifications[0]); - - const notifications = run.tool.driver.notifications; - const diagnosticNotification = notifications.filter(n => - n.id === 'lang/diagnostics/example' && n.name === 'lang/diagnostics/example' && - n.fullDescription.text === 'Diagnostic name' - ); - if (diagnosticNotification.length !== 1) { - core.setFailed( - 'Expected exactly one notification for this diagnostic in the ' + - `'runs[].tool.driver.notifications[]' SARIF property, but found ` + - `${diagnosticNotification.length}. All notifications: ` + - `${JSON.stringify(notifications)}.` - ); - } - - core.info('Finished diagnostic export test'); + run: | + cd ../action + npx tsx ./pr-checks/check-diagnostics.ts diff --git a/pr-checks/package.json b/pr-checks/package.json index 3f20d119d..343f94381 100644 --- a/pr-checks/package.json +++ b/pr-checks/package.json @@ -5,6 +5,7 @@ "dependencies": { "@actions/core": "^2.0.3", "@actions/github": "^8.0.1", + "@actions/tool-cache": "^3.0.1", "@octokit/core": "^7.0.8", "@octokit/plugin-paginate-rest": ">=16.0.0", "@octokit/plugin-rest-endpoint-methods": "^18.0.0", diff --git a/pr-checks/toolcache.ts b/pr-checks/toolcache.ts new file mode 100755 index 000000000..aa7d9afc1 --- /dev/null +++ b/pr-checks/toolcache.ts @@ -0,0 +1,94 @@ +#!/usr/bin/env npx tsx + +/** + * Provides utility commands for working with the Actions toolcache in PR checks. + */ + +import fs from "fs"; +import { parseArgs } from "node:util"; +import path from "path"; + +import * as core from "@actions/core"; +import * as toolcache from "@actions/tool-cache"; + +function removeCodeQLFromToolcache() { + const toolcachePath = process.env["RUNNER_TOOL_CACHE"]; + + if (toolcachePath === undefined) { + throw new Error("Expected RUNNER_TOOL_CACHE to be defined"); + } + + const codeqlPath = path.join(toolcachePath, "CodeQL"); + fs.rmSync(codeqlPath, { recursive: true }); + + core.info(`Removed '${codeqlPath}' from the toolcache.`); +} + +function findCodeQL() { + return toolcache.findAllVersions("CodeQL"); +} + +function checkNoCodeQLInToolcache() { + const allCodeqlVersions = findCodeQL(); + if (allCodeqlVersions.length !== 0) { + throw new Error( + `CodeQL should not be found in the toolcache, but found ${allCodeqlVersions}`, + ); + } + core.info("No versions of CodeQL found in the toolcache"); +} + +function hasCodeQLInToolcache(expectedNumber?: string) { + const expected = + expectedNumber !== undefined + ? Number.parseInt(expectedNumber, 10) + : undefined; + + const allCodeqlVersions = findCodeQL(); + if (allCodeqlVersions.length === 0) { + throw new Error("CodeQL not found in toolcache"); + } + + core.info(`Found CodeQL versions: ${allCodeqlVersions.join(", ")}`); + if (expected !== undefined && allCodeqlVersions.length !== expected) { + throw new Error( + `Expected ${expected} versions of CodeQL in the toolcache, but found ${allCodeqlVersions.length}`, + ); + } +} + +function main() { + const { positionals, values } = parseArgs({ + options: { + // The number of expected versions in the toolcache. + "expected-version-count": { type: "string", short: "n" }, + }, + allowPositionals: true, + }); + + if (positionals.length < 1) { + throw new Error("Command not specified."); + } + + const cmd = positionals[0]; + + switch (cmd.trim().toLowerCase()) { + case "clear": + removeCodeQLFromToolcache(); + return; + case "absent": + checkNoCodeQLInToolcache(); + return; + case "installed": + hasCodeQLInToolcache(values["expected-version-count"]); + return; + default: + throw new Error(`Unknown command '${cmd}'.`); + } +} + +try { + main(); +} catch (err) { + core.setFailed(err instanceof Error ? err.message : String(err)); +} diff --git a/src/actions-util.test.ts b/src/actions-util.test.ts index 17cf96a4a..175fdea96 100644 --- a/src/actions-util.test.ts +++ b/src/actions-util.test.ts @@ -11,7 +11,6 @@ import { } from "./actions-util"; import { computeAutomationID } from "./api-client"; import { EnvVar } from "./environment"; -import { getRunnerLogger } from "./logging"; import { RecordingLogger, setupTests } from "./testing-utils"; import { initializeEnvironment, withTmpDir } from "./util"; @@ -238,7 +237,7 @@ test.serial("fixCodeQualityCategory", (t) => { GITHUB_EVENT_NAME: "dynamic", }, () => { - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); // Categories that should get adjusted. t.is(fixCodeQualityCategory(logger, "/language:c#"), "/language:csharp"); diff --git a/src/analyses.test.ts b/src/analyses.test.ts index 9d0a9eb3e..b9d182618 100644 --- a/src/analyses.test.ts +++ b/src/analyses.test.ts @@ -15,7 +15,6 @@ import { supportedAnalysisKinds, } from "./analyses"; import { EnvVar } from "./environment"; -import { getRunnerLogger } from "./logging"; import { createFeatures, RecordingLogger, @@ -65,7 +64,7 @@ test.serial( .withArgs("analysis-kinds") .returns("code-scanning,code-quality"); const result = await getAnalysisKinds( - getRunnerLogger(true), + new RecordingLogger(), features, true, ); @@ -139,7 +138,7 @@ test.serial( const optionalInputStub = sinon.stub(actionsUtil, "getOptionalInput"); optionalInputStub.withArgs("quality-queries").returns("code-quality"); const result = await getAnalysisKinds( - getRunnerLogger(true), + new RecordingLogger(), features, true, ); @@ -155,7 +154,7 @@ test.serial( const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput"); requiredInputStub.withArgs("analysis-kinds").returns("no-such-thing"); await t.throwsAsync( - getAnalysisKinds(getRunnerLogger(true), features, true), + getAnalysisKinds(new RecordingLogger(), features, true), ); }, ); @@ -181,7 +180,7 @@ for (let i = 0; i < analysisKinds.length; i++) { .withArgs("analysis-kinds") .returns([analysisKind, otherAnalysis].join(",")); const result = await getAnalysisKinds( - getRunnerLogger(true), + new RecordingLogger(), features, true, ); @@ -199,7 +198,7 @@ for (let i = 0; i < analysisKinds.length; i++) { .withArgs("analysis-kinds") .returns([analysisKind, otherAnalysis].join(",")); await t.throwsAsync( - getAnalysisKinds(getRunnerLogger(true), features, true), + getAnalysisKinds(new RecordingLogger(), features, true), { instanceOf: ConfigurationError, message: `${analysisKind} and ${otherAnalysis} cannot be enabled at the same time`, diff --git a/src/analyze-action.ts b/src/analyze-action.ts index 9efbb529b..23ce54d30 100644 --- a/src/analyze-action.ts +++ b/src/analyze-action.ts @@ -50,9 +50,8 @@ import { StatusReportBase, } from "./status-report"; import { - cleanupTrapCaches, - TrapCacheCleanupStatusReport, uploadTrapCaches, + warnIfTrapCacheCleanupEnvVarIsSet, } from "./trap-caching"; import * as uploadLib from "./upload-lib"; import { UploadResult } from "./upload-lib"; @@ -86,7 +85,6 @@ async function sendStatusReport( trapCacheUploadTime: number | undefined, dbCreationTimings: DatabaseCreationTimings | undefined, didUploadTrapCaches: boolean, - trapCacheCleanup: TrapCacheCleanupStatusReport | undefined, dependencyCacheResults: DependencyCacheUploadStatusReport | undefined, databaseUploadResults: DatabaseUploadResult[], logger: Logger, @@ -107,7 +105,6 @@ async function sendStatusReport( ...statusReportBase, ...(stats || {}), ...(dbCreationTimings || {}), - ...(trapCacheCleanup || {}), dependency_caching_upload_results: dependencyCacheResults, database_upload_results: databaseUploadResults, }; @@ -229,8 +226,6 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) { | undefined = undefined; let runStats: QueriesStatusReport | undefined = undefined; let config: Config | undefined = undefined; - let trapCacheCleanupTelemetry: TrapCacheCleanupStatusReport | undefined = - undefined; let trapCacheUploadTime: number | undefined = undefined; let dbCreationTimings: DatabaseCreationTimings | undefined = undefined; let didUploadTrapCaches = false; @@ -278,6 +273,8 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) { ); } + warnIfTrapCacheCleanupEnvVarIsSet(action); + const apiDetails = getApiDetails(); const outputDir = actionsUtil.getRequiredInput("output"); action.env.export(EnvVar.SARIF_RESULTS_OUTPUT_DIR, outputDir); @@ -361,6 +358,7 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) { uploadResults = await postProcessAndUploadSarif( logger, features, + config, uploadKind, checkoutPath, outputDir, @@ -415,13 +413,6 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) { didUploadTrapCaches = await uploadTrapCaches(codeql, config, logger); trapCacheUploadTime = performance.now() - trapCacheUploadStartTime; - // Clean up TRAP caches - trapCacheCleanupTelemetry = await cleanupTrapCaches( - config, - features, - logger, - ); - // Store dependency cache(s) if dependency caching is enabled. if (shouldStoreCache(config.dependencyCachingEnabled)) { dependencyCacheResults = await uploadDependencyCaches( @@ -433,8 +424,10 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) { } // We don't upload results in test mode, so don't wait for processing - if (util.isInTestMode()) { - logger.debug("In test mode. Waiting for processing is disabled."); + if (util.shouldSkipSarifUpload()) { + logger.debug( + "SARIF upload is disabled and nothing was uploaded, so we do not need to wait for processing to finish.", + ); } else if ( uploadResults?.[analyses.AnalysisKind.CodeScanning] !== undefined && actionsUtil.getRequiredInput("wait-for-processing") === "true" @@ -471,7 +464,6 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) { trapCacheUploadTime, dbCreationTimings, didUploadTrapCaches, - trapCacheCleanupTelemetry, dependencyCacheResults, databaseUploadResults, logger, @@ -494,7 +486,6 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) { trapCacheUploadTime, dbCreationTimings, didUploadTrapCaches, - trapCacheCleanupTelemetry, dependencyCacheResults, databaseUploadResults, logger, @@ -508,7 +499,6 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) { trapCacheUploadTime, dbCreationTimings, didUploadTrapCaches, - trapCacheCleanupTelemetry, dependencyCacheResults, databaseUploadResults, logger, @@ -522,7 +512,6 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) { trapCacheUploadTime, dbCreationTimings, didUploadTrapCaches, - trapCacheCleanupTelemetry, dependencyCacheResults, databaseUploadResults, logger, diff --git a/src/analyze.test.ts b/src/analyze.test.ts index e14d87d10..c9f060881 100644 --- a/src/analyze.test.ts +++ b/src/analyze.test.ts @@ -17,12 +17,12 @@ import { defaultSuites } from "./config/db-config"; import { Feature } from "./feature-flags"; import * as gitUtils from "./git-utils"; import { BuiltInLanguage } from "./languages"; -import { getRunnerLogger } from "./logging"; import { setupTests, setupActionsVars, createFeatures, createTestConfig, + RecordingLogger, callee, } from "./testing-utils"; import * as uploadLib from "./upload-lib"; @@ -195,7 +195,7 @@ test.serial("status report fields", async (t) => { undefined, codeql, config, - getRunnerLogger(true), + new RecordingLogger(), createFeatures([Feature.QaTelemetryEnabled]), ); t.deepEqual(Object.keys(statusReport).sort(), [ diff --git a/src/api-client.ts b/src/api-client.ts index 7aa4d61d7..8a456c169 100644 --- a/src/api-client.ts +++ b/src/api-client.ts @@ -401,21 +401,6 @@ export async function listActionsCaches( ); } -/** - * Delete an Actions cache item by its ID. - * - * See https://docs.github.com/en/rest/actions/cache#delete-a-github-actions-cache-for-a-repository-using-a-cache-id. - */ -export async function deleteActionsCache(id: number) { - const repositoryNwo = getRepositoryNwo(); - - await getApiClient().rest.actions.deleteActionsCacheById({ - owner: repositoryNwo.owner, - repo: repositoryNwo.repo, - cache_id: id, - }); -} - /** * Retrieve all custom repository properties. * diff --git a/src/artifact-scanner.test.ts b/src/artifact-scanner.test.ts index 56f99e113..50c0d3fd2 100644 --- a/src/artifact-scanner.test.ts +++ b/src/artifact-scanner.test.ts @@ -10,12 +10,12 @@ import { scanArtifactsForTokens, TokenType, } from "./artifact-scanner"; -import { getRunnerLogger } from "./logging"; import { checkExpectedLogMessages, getRecordingLogger, LoggedMessage, makeTestToken, + RecordingLogger, } from "./testing-utils"; test("makeTestToken", (t) => { @@ -121,7 +121,7 @@ for (const { type, value, checkPattern } of testTokens) { } test("scanArtifactsForTokens handles files without tokens", async (t) => { - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "scanner-test-")); try { diff --git a/src/cli/output-cache.test.ts b/src/cli/output-cache.test.ts index 656cfd820..ed49dab09 100644 --- a/src/cli/output-cache.test.ts +++ b/src/cli/output-cache.test.ts @@ -3,15 +3,14 @@ import path from "path"; import test from "ava"; -import { getRunnerLogger } from "../logging"; -import { setupTests } from "../testing-utils"; +import { RecordingLogger, setupTests } from "../testing-utils"; import * as util from "../util"; import { getCachedCodeQlVersion } from "./output-cache"; setupTests(test); -const logger = getRunnerLogger(true); +const logger = new RecordingLogger(); test.serial( "getCachedCodeQlVersion reuses a version persisted by an earlier step", diff --git a/src/codeql.test.ts b/src/codeql.test.ts index e70dcd99f..d702fe438 100644 --- a/src/codeql.test.ts +++ b/src/codeql.test.ts @@ -22,7 +22,6 @@ import type { Config } from "./config-utils"; import * as defaults from "./defaults.json"; import { DocUrl } from "./doc-url"; import { BuiltInLanguage } from "./languages"; -import { getRunnerLogger } from "./logging"; import { ToolsSource } from "./setup-codeql"; import { setupTests, @@ -34,6 +33,8 @@ import { makeVersionInfo, createTestConfig, makeMacro, + getCodeQLForTesting, + RecordingLogger, } from "./testing-utils"; import { ToolsDownloadStatusReport } from "./tools-download"; import * as util from "./util"; @@ -102,7 +103,7 @@ async function installIntoToolcache({ undefined, // otherLanguagePacksReason false, // useOverlayAwareDefaultCliVersion createFeatures([]), - getRunnerLogger(true), + new RecordingLogger(), false, ); } @@ -140,7 +141,7 @@ function mockApiDetails(apiDetails: Required) { } async function stubCodeql(): Promise { - const codeqlObject = await codeql.getCodeQLForTesting(); + const codeqlObject = await getCodeQLForTesting(); sinon.stub(codeqlObject, "getVersion").resolves(makeVersionInfo("2.17.6")); sinon .stub(codeqlObject, "isTracedLanguage") @@ -176,7 +177,7 @@ test.serial( undefined, // otherLanguagePacksReason false, // useOverlayAwareDefaultCliVersion features, - getRunnerLogger(true), + new RecordingLogger(), false, ); @@ -212,7 +213,7 @@ test.serial( undefined, // otherLanguagePacksReason false, // useOverlayAwareDefaultCliVersion features, - getRunnerLogger(true), + new RecordingLogger(), false, ); @@ -252,7 +253,7 @@ test.serial( undefined, // otherLanguagePacksReason false, // useOverlayAwareDefaultCliVersion features, - getRunnerLogger(true), + new RecordingLogger(), false, ); t.assert(toolcache.find("CodeQL", "0.0.0-20200610")); @@ -362,7 +363,7 @@ for (const { undefined, // otherLanguagePacksReason false, // useOverlayAwareDefaultCliVersion features, - getRunnerLogger(true), + new RecordingLogger(), false, ); t.assert(toolcache.find("CodeQL", expectedToolcacheVersion)); @@ -405,7 +406,7 @@ for (const toolcacheVersion of [ undefined, // otherLanguagePacksReason false, // useOverlayAwareDefaultCliVersion features, - getRunnerLogger(true), + new RecordingLogger(), false, ); t.is( @@ -450,7 +451,7 @@ test.serial( undefined, // otherLanguagePacksReason false, // useOverlayAwareDefaultCliVersion features, - getRunnerLogger(true), + new RecordingLogger(), false, ); t.deepEqual(result.toolsVersion, "0.0.0-20200601"); @@ -497,7 +498,7 @@ test.serial( undefined, // otherLanguagePacksReason false, // useOverlayAwareDefaultCliVersion features, - getRunnerLogger(true), + new RecordingLogger(), false, ); t.deepEqual(result.toolsVersion, defaults.cliVersion); @@ -537,7 +538,7 @@ test.serial( undefined, // otherLanguagePacksReason false, // useOverlayAwareDefaultCliVersion features, - getRunnerLogger(true), + new RecordingLogger(), false, ); t.deepEqual(result.toolsVersion, defaults.cliVersion); @@ -579,7 +580,7 @@ test.serial( undefined, // otherLanguagePacksReason false, // useOverlayAwareDefaultCliVersion features, - getRunnerLogger(true), + new RecordingLogger(), false, ); @@ -664,7 +665,7 @@ const injectedConfigMacro = makeMacro({ tempDir, }; thisStubConfig.computedConfig = generateCodeScanningConfig( - getRunnerLogger(true), + new RecordingLogger(), thisStubConfig.originalUserInput, augmentationProperties, ); @@ -1039,7 +1040,7 @@ test.serial("runTool summarizes autobuilder errors", async (t) => { [2019-09-18 12:00:01] [autobuild] A non-error message `; stubToolRunnerConstructor(1, stderr); - const codeqlObject = await codeql.getCodeQLForTesting(); + const codeqlObject = await getCodeQLForTesting(); sinon.stub(codeqlObject, "getVersion").resolves(makeVersionInfo("2.17.6")); sinon.stub(codeqlObject, "resolveExtractor").resolves("/path/to/extractor"); // io throws because of the test CodeQL object. @@ -1095,7 +1096,7 @@ test.serial("runTool recognizes fatal internal errors", async (t) => { com.semmle.util.exception.CatastrophicError: An error occurred while evaluating ControlFlowGraph::ControlFlow::Root.isRootOf/1#dispred#f610e6ed/2@86282cc8 Severe disk cache trouble (corruption or out of space) at /home/runner/work/_temp/codeql_databases/go/db-go/default/cache/pages/28/33.pack: Failed to write item to disk`; stubToolRunnerConstructor(1, stderr); - const codeqlObject = await codeql.getCodeQLForTesting(); + const codeqlObject = await getCodeQLForTesting(); sinon.stub(codeqlObject, "getVersion").resolves(makeVersionInfo("2.17.6")); sinon.stub(codeqlObject, "resolveExtractor").resolves("/path/to/extractor"); // io throws because of the test CodeQL object. diff --git a/src/codeql.ts b/src/codeql.ts index a76e29727..4f39fc0cc 100644 --- a/src/codeql.ts +++ b/src/codeql.ts @@ -26,7 +26,7 @@ import { } from "./feature-flags"; import { isAnalyzingDefaultBranch } from "./git-utils"; import { Language } from "./languages"; -import { getRunnerLogger, Logger } from "./logging"; +import type { Logger } from "./logging"; import { writeBaseDatabaseOidsFile, writeOverlayChangesFile } from "./overlay"; import { OverlayDatabaseMode } from "./overlay/overlay-database-mode"; import * as setupCodeql from "./setup-codeql"; @@ -486,18 +486,6 @@ export function createStubCodeQL(partialCodeql: Partial): CodeQL { }; } -/** - * Get a real, newly created CodeQL instance for testing. The instance refers to - * a non-existent placeholder codeql command, so tests that use this function - * should also stub the toolrunner.ToolRunner constructor. - */ -export async function getCodeQLForTesting( - cmd = "codeql-for-testing", - logger: Logger = getRunnerLogger(true), -): Promise { - return getCodeQLForCmd(logger, cmd, false); -} - /** * Return a CodeQL object for CodeQL CLI access. * @@ -506,7 +494,7 @@ export async function getCodeQLForTesting( * version requirement. Must be set to true outside tests. * @returns A new CodeQL object */ -async function getCodeQLForCmd( +export async function getCodeQLForCmd( logger: Logger, cmd: string, checkVersion: boolean, diff --git a/src/config-utils.test.ts b/src/config-utils.test.ts index 9ed0187e6..b66d4a797 100644 --- a/src/config-utils.test.ts +++ b/src/config-utils.test.ts @@ -21,7 +21,6 @@ import { RepositoryProperties } from "./feature-flags/properties"; import * as gitUtils from "./git-utils"; import { GitVersionInfo } from "./git-utils"; import { BuiltInLanguage, Language } from "./languages"; -import { getRunnerLogger } from "./logging"; import { CODEQL_OVERLAY_MINIMUM_VERSION } from "./overlay"; import * as overlayDiagnostics from "./overlay/diagnostics"; import { OverlayDisabledReason } from "./overlay/diagnostics"; @@ -42,6 +41,7 @@ import { callee, SAMPLE_DOTCOM_API_DETAILS, AssertableTarget, + RecordingLogger, } from "./testing-utils"; import { GitHubVariant, @@ -102,7 +102,7 @@ function createTestInitConfigInputs( features: createFeatures([]), repositoryProperties: {}, enableFileCoverageInformation: true, - logger: getRunnerLogger(true), + logger: new RecordingLogger(), } satisfies configUtils.InitConfigInputs, overrides, ); @@ -150,7 +150,7 @@ function mockListLanguages(languages: string[]) { test.serial("load empty config", async (t) => { return await withTmpDir(async (tempDir) => { - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const languages = "javascript,python"; setupActionsVars(tempDir, tempDir); @@ -196,7 +196,7 @@ test.serial("load empty config", async (t) => { test.serial("load code quality config", async (t) => { return await withTmpDir(async (tempDir) => { - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const languages = "actions"; setupActionsVars(tempDir, tempDir); @@ -252,7 +252,7 @@ test.serial( "initActionState doesn't throw if there are queries configured in the repository properties", async (t) => { return await withTmpDir(async (tempDir) => { - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const languages = "javascript"; setupActionsVars(tempDir, tempDir); @@ -315,7 +315,7 @@ test.serial( test.serial("loading a saved config produces the same config", async (t) => { return await withTmpDir(async (tempDir) => { - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const codeql = createStubCodeQL({ async resolveLanguages() { @@ -363,7 +363,7 @@ test.serial("loading a saved config produces the same config", async (t) => { test.serial("loading config with version mismatch throws", async (t) => { return await withTmpDir(async (tempDir) => { - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const codeql = createStubCodeQL({ async resolveLanguages() { @@ -788,12 +788,12 @@ test.serial("Unknown languages", async (t) => { }); }); -const mockLogger = getRunnerLogger(true); +const mockLogger = new RecordingLogger(); test.serial("no generateRegistries when registries is undefined", async (t) => { return await withTmpDir(async (tmpDir) => { const registriesInput = undefined; - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const { registriesAuthTokens, qlconfigFile } = await configUtils.generateRegistries(registriesInput, tmpDir, logger); @@ -814,7 +814,7 @@ test.serial( token: "not-a-token", }, ]); - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const { registriesAuthTokens, qlconfigFile } = await configUtils.generateRegistries(registriesInput, tmpDir, logger); @@ -2126,7 +2126,7 @@ test.serial("getPrimaryAnalysisConfig - Code Scanning + Code Quality", (t) => { }); test.serial( - "isTrapCachingEnabled: explicit input true is respected", + "isTrapCachingEnabled: explicit true input overrides self-hosted default", async (t) => { return await withTmpDir(async (tmpDir) => { setupActionsVars(tmpDir, tmpDir); @@ -2134,12 +2134,40 @@ test.serial( .stub(actionsUtil, "getOptionalInput") .withArgs("trap-caching") .returns("true"); - t.true( - await configUtils.isTrapCachingEnabled( - createFeatures([]), - OverlayDatabaseMode.None, - ), - ); + t.true(configUtils.isTrapCachingEnabled(OverlayDatabaseMode.None)); + }); + }, +); + +test.serial( + "isTrapCachingEnabled: explicit true input overrides overlay defaults", + async (t) => { + return await withTmpDir(async (tmpDir) => { + setupActionsVars(tmpDir, path.join(tmpDir, "hostedtoolcache")); + sinon + .stub(actionsUtil, "getOptionalInput") + .withArgs("trap-caching") + .returns("true"); + for (const overlayDatabaseMode of [ + OverlayDatabaseMode.Overlay, + OverlayDatabaseMode.OverlayBase, + ]) { + t.true(configUtils.isTrapCachingEnabled(overlayDatabaseMode)); + } + }); + }, +); + +test.serial( + "isTrapCachingEnabled: explicit false input overrides hosted default", + async (t) => { + return await withTmpDir(async (tmpDir) => { + setupActionsVars(tmpDir, path.join(tmpDir, "hostedtoolcache")); + sinon + .stub(actionsUtil, "getOptionalInput") + .withArgs("trap-caching") + .returns("false"); + t.false(configUtils.isTrapCachingEnabled(OverlayDatabaseMode.None)); }); }, ); @@ -2153,18 +2181,13 @@ test.serial( .stub(actionsUtil, "getOptionalInput") .withArgs("trap-caching") .returns(undefined); - t.false( - await configUtils.isTrapCachingEnabled( - createFeatures([]), - OverlayDatabaseMode.None, - ), - ); + t.false(configUtils.isTrapCachingEnabled(OverlayDatabaseMode.None)); }); }, ); test.serial( - "isTrapCachingEnabled: enabled on hosted runner by default", + "isTrapCachingEnabled: enabled on hosted runner without overlay analysis", async (t) => { return await withTmpDir(async (tmpDir) => { const hostedToolCache = path.join(tmpDir, "hostedtoolcache"); @@ -2173,18 +2196,13 @@ test.serial( .stub(actionsUtil, "getOptionalInput") .withArgs("trap-caching") .returns(undefined); - t.true( - await configUtils.isTrapCachingEnabled( - createFeatures([]), - OverlayDatabaseMode.None, - ), - ); + t.true(configUtils.isTrapCachingEnabled(OverlayDatabaseMode.None)); }); }, ); test.serial( - "isTrapCachingEnabled: enabled on hosted runner when overlay enabled but feature flag off", + "isTrapCachingEnabled: disabled on hosted runner with overlay analysis", async (t) => { return await withTmpDir(async (tmpDir) => { const hostedToolCache = path.join(tmpDir, "hostedtoolcache"); @@ -2193,52 +2211,12 @@ test.serial( .stub(actionsUtil, "getOptionalInput") .withArgs("trap-caching") .returns(undefined); - t.true( - await configUtils.isTrapCachingEnabled( - createFeatures([]), - OverlayDatabaseMode.Overlay, - ), - ); - }); - }, -); - -test.serial( - "isTrapCachingEnabled: disabled on hosted runner when overlay enabled and feature flag on", - async (t) => { - return await withTmpDir(async (tmpDir) => { - const hostedToolCache = path.join(tmpDir, "hostedtoolcache"); - setupActionsVars(tmpDir, hostedToolCache); - sinon - .stub(actionsUtil, "getOptionalInput") - .withArgs("trap-caching") - .returns(undefined); - t.false( - await configUtils.isTrapCachingEnabled( - createFeatures([Feature.OverlayAnalysisDisableTrapCaching]), - OverlayDatabaseMode.Overlay, - ), - ); - }); - }, -); - -test.serial( - "isTrapCachingEnabled: enabled on hosted runner when overlay is None even with feature flag on", - async (t) => { - return await withTmpDir(async (tmpDir) => { - const hostedToolCache = path.join(tmpDir, "hostedtoolcache"); - setupActionsVars(tmpDir, hostedToolCache); - sinon - .stub(actionsUtil, "getOptionalInput") - .withArgs("trap-caching") - .returns(undefined); - t.true( - await configUtils.isTrapCachingEnabled( - createFeatures([Feature.OverlayAnalysisDisableTrapCaching]), - OverlayDatabaseMode.None, - ), - ); + for (const overlayDatabaseMode of [ + OverlayDatabaseMode.Overlay, + OverlayDatabaseMode.OverlayBase, + ]) { + t.false(configUtils.isTrapCachingEnabled(overlayDatabaseMode)); + } }); }, ); @@ -2247,7 +2225,7 @@ test("applyIncrementalAnalysisSettings: no-op when mode is not Overlay and diff const config = createTestConfig({}); config.overlayDatabaseMode = OverlayDatabaseMode.None; const codeql = createStubCodeQL({}); - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); await configUtils.applyIncrementalAnalysisSettings( config, @@ -2265,7 +2243,7 @@ test("applyIncrementalAnalysisSettings: keeps overlay mode and adds exclusions w overlayDatabaseMode: OverlayDatabaseMode.Overlay, }); const codeql = createStubCodeQL({}); - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); await configUtils.applyIncrementalAnalysisSettings( config, @@ -2288,7 +2266,7 @@ test.serial( }); config.useOverlayDatabaseCaching = true; const codeql = createStubCodeQL({}); - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const addDiagnosticsStub = sinon .stub(overlayDiagnostics, "addOverlayDisablementDiagnostics") .resolves(); @@ -2320,7 +2298,7 @@ test.serial( config.useOverlayDatabaseCaching = false; config.overlayModeSetExplicitly = true; const codeql = createStubCodeQL({}); - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const addDiagnosticsStub = sinon .stub(overlayDiagnostics, "addOverlayDisablementDiagnostics") .resolves(); @@ -2343,7 +2321,7 @@ test("applyIncrementalAnalysisSettings: adds exclusions for diff-informed-only r const config = createTestConfig({}); config.overlayDatabaseMode = OverlayDatabaseMode.None; const codeql = createStubCodeQL({}); - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); await configUtils.applyIncrementalAnalysisSettings( config, @@ -2557,7 +2535,7 @@ test("determineUserConfig - merges configs if FF is enabled in Default Setup", a // also check whether loading the configuration from disk that was written // by `determineUserConfig` matches our expectations. const loadedFromDisk = configUtils.getLocalConfig( - getRunnerLogger(true), + new RecordingLogger(), expectedConfigPath, false, ); diff --git a/src/config-utils.ts b/src/config-utils.ts index de506aac0..7bb7a37c0 100644 --- a/src/config-utils.ts +++ b/src/config-utils.ts @@ -944,10 +944,14 @@ async function validateOverlayDatabaseMode( }); } -export async function isTrapCachingEnabled( - features: FeatureEnablement, +/** + * Determines whether TRAP caching is enabled for the current run. + * + * @returns Whether TRAP caches should be downloaded and uploaded. + */ +export function isTrapCachingEnabled( overlayDatabaseMode: OverlayDatabaseMode, -): Promise { +): boolean { // If the workflow specified something, always respect that. const trapCaching = getOptionalInput("trap-caching"); if (trapCaching !== undefined) return trapCaching === "true"; @@ -955,12 +959,8 @@ export async function isTrapCachingEnabled( // On self-hosted runners which may have slow network access, disable TRAP caching by default. if (!looksLikeHostedRunner()) return false; - // If overlay analysis is enabled, then disable TRAP caching since overlay analysis supersedes it. - // This change is gated behind a feature flag. - if ( - overlayDatabaseMode !== OverlayDatabaseMode.None && - (await features.getValue(Feature.OverlayAnalysisDisableTrapCaching)) - ) { + // Overlay analysis supersedes TRAP caching, so disable TRAP caching by default when it is active. + if (overlayDatabaseMode !== OverlayDatabaseMode.None) { return false; } @@ -1302,7 +1302,7 @@ export async function initConfig( logger, ); - if (await isTrapCachingEnabled(features, config.overlayDatabaseMode)) { + if (isTrapCachingEnabled(config.overlayDatabaseMode)) { const { trapCaches, trapCacheDownloadTime } = await downloadCacheWithTime( inputs.codeql, config.languages, diff --git a/src/config/db-config.test.ts b/src/config/db-config.test.ts index 76fa91801..4a10a9701 100644 --- a/src/config/db-config.test.ts +++ b/src/config/db-config.test.ts @@ -2,7 +2,6 @@ import test, { ExecutionContext } from "ava"; import { RepositoryProperties } from "../feature-flags/properties"; import { BuiltInLanguage, Language } from "../languages"; -import { getRunnerLogger } from "../logging"; import { checkExpectedLogMessages, getRecordingLogger, @@ -384,7 +383,7 @@ calculateAugmentationErrorMacro( test("parseUserConfig - successfully parses valid YAML", (t) => { const result = dbConfig.parseUserConfig( - getRunnerLogger(true), + new RecordingLogger(), "test", ` paths-ignore: @@ -410,7 +409,7 @@ test("parseUserConfig - throws a ConfigurationError if the file is not valid YAM t.throws( () => dbConfig.parseUserConfig( - getRunnerLogger(true), + new RecordingLogger(), "test", ` paths-ignore: @@ -629,7 +628,7 @@ test("mergeDefaultSetupAndUserConfigs - warns about invalid keys from Default Se /** Parses `contents` as a configuration without validating it. */ function parseUnvalidatedConfig(contents: string): dbConfig.UserConfig { return dbConfig.parseUserConfig( - getRunnerLogger(true), + new RecordingLogger(), "test", contents, false, diff --git a/src/defaults.json b/src/defaults.json index 71decd839..cde5136e2 100644 --- a/src/defaults.json +++ b/src/defaults.json @@ -1,6 +1,6 @@ { - "bundleVersion": "codeql-bundle-v2.27.2", - "cliVersion": "2.27.2", - "priorBundleVersion": "codeql-bundle-v2.27.1", - "priorCliVersion": "2.27.1" + "bundleVersion": "codeql-bundle-v2.27.3", + "cliVersion": "2.27.3", + "priorBundleVersion": "codeql-bundle-v2.27.2", + "priorCliVersion": "2.27.2" } diff --git a/src/diff-informed-analysis-utils.test.ts b/src/diff-informed-analysis-utils.test.ts index e07240e32..76e5f33b4 100644 --- a/src/diff-informed-analysis-utils.test.ts +++ b/src/diff-informed-analysis-utils.test.ts @@ -10,7 +10,6 @@ import { exportedForTesting, } from "./diff-informed-analysis-utils"; import { Feature, FeatureEnablement, initFeatures } from "./feature-flags"; -import { getRunnerLogger } from "./logging"; import { parseRepositoryNwo } from "./repository"; import { setupTests, @@ -19,6 +18,7 @@ import { mockFeatureFlagApiEndpoint, setupActionsVars, makeMacro, + RecordingLogger, } from "./testing-utils"; import { GitHubVariant, withTmpDir } from "./util"; import type { GitHubVersion } from "./util"; @@ -55,7 +55,7 @@ const testShouldPerformDiffInformedAnalysis = makeMacro({ setupActionsVars(tmpDir, tmpDir); const testCase = { ...defaultTestCase, ...partialTestCase }; - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const codeql = mockCodeQLVersion(testCase.codeQLVersion); if (testCase.diffInformedQueriesEnvVar !== undefined) { @@ -182,7 +182,7 @@ test.serial( async (t) => { await withTmpDir(async (tmpDir) => { setupActionsVars(tmpDir, tmpDir); - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const codeql = mockCodeQLVersion("2.21.0"); const features = createFeatures([Feature.DiffInformedQueries]); @@ -207,7 +207,7 @@ test.serial( async (t) => { await withTmpDir(async (tmpDir) => { setupActionsVars(tmpDir, tmpDir); - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const codeql = mockCodeQLVersion("2.21.0"); // A features implementation whose getValue rejects, simulating an // unexpected failure when determining whether diff-informed analysis @@ -237,7 +237,7 @@ test.serial( async (t) => { await withTmpDir(async (tmpDir) => { setupActionsVars(tmpDir, tmpDir); - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const codeql = mockCodeQLVersion("2.21.0"); const features = createFeatures([Feature.DiffInformedQueries]); @@ -274,7 +274,7 @@ test.serial( async (t) => { await withTmpDir(async (tmpDir) => { setupActionsVars(tmpDir, tmpDir); - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const codeql = mockCodeQLVersion("2.21.0"); const features = createFeatures([Feature.DiffInformedQueries]); @@ -313,7 +313,7 @@ function runGetDiffRanges(changes: number, patch: string[] | undefined): any { changes, patch: patch?.join("\n"), }, - getRunnerLogger(true), + new RecordingLogger(), ); } diff --git a/src/doc-url.ts b/src/doc-url.ts index c624817e9..97f5451d8 100644 --- a/src/doc-url.ts +++ b/src/doc-url.ts @@ -3,7 +3,6 @@ */ export enum DocUrl { - ASSIGNING_PERMISSIONS_TO_JOBS = "https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs", AUTOMATIC_BUILD_FAILED = "https://docs.github.com/en/code-security/code-scanning/troubleshooting-code-scanning/automatic-build-failed", CODEQL_BUILD_MODES = "https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages#codeql-build-modes", DEFINE_ENV_VARIABLES = "https://docs.github.com/en/actions/learn-github-actions/variables#defining-environment-variables-for-a-single-workflow", diff --git a/src/environment.ts b/src/environment.ts index 9f6107025..ffc3f305a 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -165,9 +165,21 @@ export enum EnvVar { */ SARIF_DUMP_DIR = "CODEQL_ACTION_SARIF_DUMP_DIR", + /** + * Whether to skip generating a failed SARIF file. Intended for testing purposes. + * This setting is more specific than `CODEQL_ACTION_TEST_MODE` and + * `CODEQL_ACTION_SKIP_SARIF_UPLOAD`, which imply this option. + * + * Setting this to `false` takes precedence over `CODEQL_ACTION_TEST_MODE: true` or + * `CODEQL_ACTION_SKIP_SARIF_UPLOAD: true`. + */ + SKIP_GENERATE_FAILED_SARIF = "CODEQL_ACTION_SKIP_GENERATE_FAILED_SARIF", + /** * Whether to skip uploading SARIF results to GitHub. Intended for testing purposes. * This setting is more specific than `CODEQL_ACTION_TEST_MODE`, which implies this option. + * + * Setting this to `false` takes precedence over `CODEQL_ACTION_TEST_MODE: true`. */ SKIP_SARIF_UPLOAD = "CODEQL_ACTION_SKIP_SARIF_UPLOAD", diff --git a/src/feature-flags.ts b/src/feature-flags.ts index b43f23c5d..1e8e75533 100644 --- a/src/feature-flags.ts +++ b/src/feature-flags.ts @@ -74,7 +74,6 @@ export enum Feature { AllowMergeConfigFiles = "allow_merge_config_files", /** Controls whether we allow multiple values for the `analysis-kinds` input. */ AllowMultipleAnalysisKinds = "allow_multiple_analysis_kinds", - CleanupTrapCaches = "cleanup_trap_caches", /** Whether to allow the `config-file` input to be specified via a repository property. */ ConfigFileRepositoryProperty = "config_file_repository_property", CppDependencyInstallation = "cpp_dependency_installation_enabled", @@ -103,8 +102,6 @@ export enum Feature { OverlayAnalysisCodeScanningRuby = "overlay_analysis_code_scanning_ruby", OverlayAnalysisCpp = "overlay_analysis_cpp", OverlayAnalysisCsharp = "overlay_analysis_csharp", - /** Disable TRAP caching when overlay analysis is enabled. */ - OverlayAnalysisDisableTrapCaching = "overlay_analysis_disable_trap_caching", OverlayAnalysisGo = "overlay_analysis_go", OverlayAnalysisJava = "overlay_analysis_java", OverlayAnalysisJavascript = "overlay_analysis_javascript", @@ -211,11 +208,6 @@ export const featureConfig = { envVar: "CODEQL_ACTION_ALLOW_MULTIPLE_ANALYSIS_KINDS", minimumVersion: undefined, }, - [Feature.CleanupTrapCaches]: { - defaultValue: false, - envVar: "CODEQL_ACTION_CLEANUP_TRAP_CACHES", - minimumVersion: undefined, - }, [Feature.ConfigFileRepositoryProperty]: { defaultValue: false, envVar: "CODEQL_ACTION_CONFIG_FILE_REPOSITORY_PROPERTY", @@ -364,11 +356,6 @@ export const featureConfig = { minimumVersion: CODEQL_OVERLAY_MINIMUM_VERSION_RUBY, }, // Other overlay-related feature flags - [Feature.OverlayAnalysisDisableTrapCaching]: { - defaultValue: false, - envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_DISABLE_TRAP_CACHING", - minimumVersion: undefined, - }, [Feature.OverlayAnalysisMatchCodeqlVersion]: { defaultValue: false, envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_MATCH_CODEQL_VERSION", diff --git a/src/feature-flags/properties.test.ts b/src/feature-flags/properties.test.ts index d3094a8d1..6544d15d3 100644 --- a/src/feature-flags/properties.test.ts +++ b/src/feature-flags/properties.test.ts @@ -2,7 +2,6 @@ import test from "ava"; import * as sinon from "sinon"; import * as api from "../api-client"; -import { getRunnerLogger } from "../logging"; import { parseRepositoryNwo } from "../repository"; import { RecordingLogger, setupTests } from "../testing-utils"; @@ -19,7 +18,7 @@ test.serial( url: "", data: {}, }); - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const mockRepositoryNwo = parseRepositoryNwo("owner/repo"); await t.throwsAsync( properties.loadPropertiesFromApi(logger, mockRepositoryNwo), @@ -39,7 +38,7 @@ test.serial( url: "", data: [{}], }); - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const mockRepositoryNwo = parseRepositoryNwo("owner/repo"); await t.throwsAsync( properties.loadPropertiesFromApi(logger, mockRepositoryNwo), @@ -63,7 +62,7 @@ test.serial( { property_name: "also-not-used-by-us", value: ["A", "B", "C"] }, ], }); - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const mockRepositoryNwo = parseRepositoryNwo("owner/repo"); await t.notThrowsAsync( properties.loadPropertiesFromApi(logger, mockRepositoryNwo), @@ -86,7 +85,7 @@ test.serial("loadPropertiesFromApi loads known properties", async (t) => { { property_name: "unknown-property", value: "something" }, ] satisfies properties.GitHubPropertiesResponse, }); - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const mockRepositoryNwo = parseRepositoryNwo("owner/repo"); const response = await properties.loadPropertiesFromApi( logger, @@ -113,7 +112,7 @@ test.serial("loadPropertiesFromApi parses true boolean property", async (t) => { { property_name: "github-codeql-extra-queries", value: "+queries" }, ] satisfies properties.GitHubPropertiesResponse, }); - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const warningSpy = sinon.spy(logger, "warning"); const mockRepositoryNwo = parseRepositoryNwo("owner/repo"); const response = await properties.loadPropertiesFromApi( @@ -141,7 +140,7 @@ test.serial( }, ] satisfies properties.GitHubPropertiesResponse, }); - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const warningSpy = sinon.spy(logger, "warning"); const mockRepositoryNwo = parseRepositoryNwo("owner/repo"); const response = await properties.loadPropertiesFromApi( @@ -164,7 +163,7 @@ test.serial( url: "", data: [{ property_name: "github-codeql-extra-queries", value: 123 }], }); - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const mockRepositoryNwo = parseRepositoryNwo("owner/repo"); await t.throwsAsync( properties.loadPropertiesFromApi(logger, mockRepositoryNwo), @@ -190,7 +189,7 @@ test.serial( }, ] satisfies properties.GitHubPropertiesResponse, }); - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const warningSpy = sinon.spy(logger, "warning"); const mockRepositoryNwo = parseRepositoryNwo("owner/repo"); const response = await properties.loadPropertiesFromApi( diff --git a/src/feature-flags/testing-util.ts b/src/feature-flags/testing-util.ts index 202f2c97b..46795bdf0 100644 --- a/src/feature-flags/testing-util.ts +++ b/src/feature-flags/testing-util.ts @@ -8,11 +8,12 @@ import { FeatureWithoutCLI, initFeatures, } from "../feature-flags"; -import { getRunnerLogger } from "../logging"; +import type { Logger } from "../logging"; import { parseRepositoryNwo } from "../repository"; import { LoggedMessage, mockCodeQLVersion, + RecordingLogger, setupActionsVars, } from "../testing-utils"; import { ToolsFeature } from "../tools-features"; @@ -51,7 +52,7 @@ export function assertAllFeaturesUndefinedInApi( export function setUpFeatureFlagTests( tmpDir: string, - logger = getRunnerLogger(true), + logger: Logger = new RecordingLogger(), gitHubVersion = { type: GitHubVariant.DOTCOM } as util.GitHubVersion, ): FeatureEnablement { setupActionsVars(tmpDir, tmpDir); diff --git a/src/fingerprints.test.ts b/src/fingerprints.test.ts index 6cb9b6861..6b480a777 100644 --- a/src/fingerprints.test.ts +++ b/src/fingerprints.test.ts @@ -5,9 +5,8 @@ import * as ava from "ava"; import test from "ava"; import * as fingerprints from "./fingerprints"; -import { getRunnerLogger } from "./logging"; import * as sarif from "./sarif"; -import { setupTests } from "./testing-utils"; +import { RecordingLogger, setupTests } from "./testing-utils"; import * as util from "./util"; setupTests(test); @@ -131,7 +130,7 @@ function testResolveUriToFile(uri: any, index: any, artifactsURIs: any[]) { location, artifacts, process.cwd(), - getRunnerLogger(true), + new RecordingLogger(), ); } @@ -218,7 +217,7 @@ test("addFingerprints", async (t) => { await fingerprints.addFingerprints( input, sourceRoot, - getRunnerLogger(true), + new RecordingLogger(), ), expected, ); @@ -246,7 +245,7 @@ test("missingRegions", async (t) => { await fingerprints.addFingerprints( input, sourceRoot, - getRunnerLogger(true), + new RecordingLogger(), ), expected, ); diff --git a/src/init-action-post-helper.test.ts b/src/init-action-post-helper.test.ts index fd1f489f7..d64d303fa 100644 --- a/src/init-action-post-helper.test.ts +++ b/src/init-action-post-helper.test.ts @@ -11,7 +11,6 @@ import * as debugArtifacts from "./debug-artifacts"; import { EnvVar } from "./environment"; import { Feature } from "./feature-flags"; import * as initActionPostHelper from "./init-action-post-helper"; -import { getRunnerLogger } from "./logging"; import { OverlayDatabaseMode } from "./overlay/overlay-database-mode"; import * as overlayStatus from "./overlay/status"; import { parseRepositoryNwo } from "./repository"; @@ -20,6 +19,7 @@ import { createFeatures, createTestConfig, DEFAULT_ACTIONS_VARS, + getCodeQLForTesting, getTestEnv, makeMacro, makeVersionInfo, @@ -62,7 +62,7 @@ test.serial("init-post action with debug mode off", async (t) => { createFeatures([]), "success", getTestEnv(), - getRunnerLogger(true), + new RecordingLogger(), ); t.assert(uploadAllAvailableDebugArtifactsSpy.notCalled); @@ -86,7 +86,7 @@ test.serial("init-post action with debug mode on", async (t) => { createFeatures([]), "success", getTestEnv(), - getRunnerLogger(true), + new RecordingLogger(), ); t.assert(uploadAllAvailableDebugArtifactsSpy.called); @@ -383,7 +383,7 @@ test.serial( createFeatures([Feature.OverlayAnalysisStatusSave]), "success", getTestEnv(), - getRunnerLogger(true), + new RecordingLogger(), ); t.true( @@ -453,7 +453,7 @@ test.serial( createFeatures([]), "success", getTestEnv(), - getRunnerLogger(true), + new RecordingLogger(), ); t.true( @@ -497,7 +497,7 @@ test.serial("does not save overlay status when build successful", async (t) => { createFeatures([Feature.OverlayAnalysisStatusSave]), "success", env, - getRunnerLogger(true), + new RecordingLogger(), ); t.true( @@ -536,7 +536,7 @@ test.serial( createFeatures([]), "success", getTestEnv(), - getRunnerLogger(true), + new RecordingLogger(), ); t.true( @@ -589,7 +589,7 @@ async function runOverlayPostStep({ createFeatures([Feature.OverlayAnalysisStatusSave]), jobStatus, env, - getRunnerLogger(true), + new RecordingLogger(), ); return { saveOverlayStatusStub }; @@ -737,7 +737,7 @@ async function testFailedSarifUpload( .withArgs("matrix") .returns(JSON.stringify(matrix)); - const codeqlObject = await codeql.getCodeQLForTesting(); + const codeqlObject = await getCodeQLForTesting(); sinon.stub(codeql, "getCodeQL").resolves(codeqlObject); sinon.stub(codeqlObject, "getVersion").resolves(makeVersionInfo("2.17.6")); const databaseExportDiagnosticsStub = sinon.stub( @@ -764,7 +764,7 @@ async function testFailedSarifUpload( config, parseRepositoryNwo("github/codeql-action"), createFeatures(features), - getRunnerLogger(true), + new RecordingLogger(), ); if (expectUpload) { t.deepEqual(result, { @@ -793,6 +793,7 @@ async function testFailedSarifUpload( } t.true( uploadFiles.calledOnceWith( + config, sinon.match.string, sinon.match.string, category, @@ -832,7 +833,7 @@ async function mockRiskAssessmentEnv(matrix: string) { .stub(apiClient, "getGitHubVersion") .resolves({ type: util.GitHubVariant.GHES, version: "3.0.0" }); - const codeqlObject = await codeql.getCodeQLForTesting(); + const codeqlObject = await getCodeQLForTesting(); const databaseExportDiagnostics = sinon .stub(codeqlObject, "databaseExportDiagnostics") .resolves(); diff --git a/src/init-action-post-helper.ts b/src/init-action-post-helper.ts index f9021b789..cfaab312f 100644 --- a/src/init-action-post-helper.ts +++ b/src/init-action-post-helper.ts @@ -44,6 +44,7 @@ import { getRequiredEnvParam, parseMatrixInput, Result, + shouldSkipGenerateFailedSarif, shouldSkipSarifUpload, Success, wrapError, @@ -115,7 +116,7 @@ async function prepareFailedSarif( const jobName = getRequiredEnvParam("GITHUB_JOB"); const matrix = parseMatrixInput(actionsUtil.getRequiredInput("matrix")); - if (shouldSkipSarifUpload()) { + if (shouldSkipGenerateFailedSarif()) { return new Failure({ upload_failed_run_skipped_because: "SARIF upload is disabled", }); @@ -226,6 +227,7 @@ async function maybeUploadFailedSarif( logger.info(`Uploading failed SARIF file ${failedSarif.sarifFile}`); const uploadResult = await uploadLib.uploadFiles( + config, failedSarif.sarifFile, failedSarif.checkoutPath, failedSarif.category, @@ -239,6 +241,14 @@ async function maybeUploadFailedSarif( logger, { isUnsuccessfulExecution: true }, ); + + // If SARIF upload is disabled for testing, `uploadFiles` will not have uploaded the failed + // SARIF file and instead will have written it to disk. Upload it as a workflow artifact so that + // we can inspect it in validation jobs. + if (uploadResult.sarifID && shouldSkipSarifUpload()) { + await uploadSarifArtifact(logger, "test-mode-sarif", failedSarif); + } + return uploadResult ? { ...uploadResult.statusReport, sarifID: uploadResult.sarifID } : {}; @@ -257,23 +267,34 @@ async function maybeUploadFailedSarifArtifact( } const failedSarif = failedSarifResult.value; + return uploadSarifArtifact(logger, "sarif-artifact", failedSarif); +} - logger.info( - `Uploading failed SARIF file ${failedSarif.sarifFile} as artifact`, - ); +/** Uploads a failed SARIF file as workflow artifact. */ +async function uploadSarifArtifact( + logger: Logger, + prefix: string, + failedSarif: FailedSarifInfo, +): Promise { + logger.startGroup(`Uploading failed SARIF file as artifact`); + logger.info(`Uploading '${failedSarif.sarifFile}' as artifact`); - const gitHubVersion = await getGitHubVersion(); - const client = await getArtifactUploaderClient(logger, gitHubVersion.type); + try { + const gitHubVersion = await getGitHubVersion(); + const client = await getArtifactUploaderClient(logger, gitHubVersion.type); - const suffix = getArtifactSuffix(actionsUtil.getOptionalInput("matrix")); - const name = sanitizeArtifactName(`sarif-artifact-${suffix}`); - await client.uploadArtifact( - name, - [path.normalize(failedSarif.sarifFile)], - path.normalize(".."), - ); + const suffix = getArtifactSuffix(actionsUtil.getOptionalInput("matrix")); + const name = sanitizeArtifactName(`${prefix}-${suffix}`); + await client.uploadArtifact( + name, + [path.normalize(failedSarif.sarifFile)], + path.normalize(path.dirname(failedSarif.sarifFile)), + ); - return { sarifID: name }; + return { sarifID: name }; + } finally { + logger.endGroup(); + } } /** diff --git a/src/init-action-post.ts b/src/init-action-post.ts index 88aac4622..071137f54 100644 --- a/src/init-action-post.ts +++ b/src/init-action-post.ts @@ -76,6 +76,9 @@ async function run(action: ActionState<["Base", "Logger", "Env"]>) { // Try to load the "failed" configuration file instead which may have been written by the `init` step // with a minimal configuration for this post-action. if (config === undefined) { + logger.debug( + "Will try to load the configuration file from an aborted 'init' step, since there is no regular one.", + ); config = getAbortedInitConfig(action, tmpDir); } diff --git a/src/logging.test.ts b/src/logging.test.ts new file mode 100644 index 000000000..33f6f8c16 --- /dev/null +++ b/src/logging.test.ts @@ -0,0 +1,17 @@ +import test from "ava"; + +import { joinMessageStrings } from "./logging"; +import { setupTests } from "./testing-utils"; + +setupTests(test); + +test("joinMessageStrings", async (t) => { + // For strings and errors, it is the identity function. + t.deepEqual(joinMessageStrings("Hello"), "Hello"); + + const error = new Error("Some error"); + t.deepEqual(joinMessageStrings(error), error); + + // For arrays of strings, we get a join-ed string. + t.deepEqual(joinMessageStrings(["foo", "bar"]), "foo bar"); +}); diff --git a/src/logging.ts b/src/logging.ts index 2c34cb54d..da6716872 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -1,45 +1,92 @@ import * as core from "@actions/core"; -export interface Logger { - debug: (message: string) => void; - info: (message: string) => void; - warning: (message: string | Error) => void; - error: (message: string | Error) => void; +/** Types that all of our logging functions accept. */ +export type Loggable = string | string[]; +/** Some logging functions also accept errors directly. */ +export type LoggableError = Loggable | Error; + +export interface Logger { + /** + * Logs {@link message} at debug-level. + * In Actions, this will only be output if workflow debugging is enabled. + * + * @param message + * A string or an array of strings. If an array of strings is provided, they are joined using + * space as the delimiter and printed as a single line. + */ + debug: (message: Loggable) => void; + /** + * Logs {@link message} at info-level. + * In Actions, this will always be output. + * + * @param message + * A string or an array of strings. If an array of strings is provided, they are joined using + * space as the delimiter and printed as a single line. + */ + info: (message: Loggable) => void; + /** + * Logs {@link message} at warning-level. + * In Actions, this will always be output and highlighted as a warning. It will additionally + * result in a warning-level workflow annotation. + * + * @param message + * A string or an array of strings. If an array of strings is provided, they are joined using + * space as the delimiter and printed as a single line. + */ + warning: (message: LoggableError) => void; + /** + * Logs {@link message} at error-level. + * In Actions, this will always be output and highlighted as an error. It will additionally + * result in an error-level workflow annotation. + * + * @param message + * A string or an array of strings. If an array of strings is provided, they are joined using + * space as the delimiter and printed as a single line. + */ + error: (message: LoggableError) => void; + /** + * Gets a value indicating whether debugging is enabled. + */ isDebug: () => boolean; startGroup: (name: string) => void; endGroup: () => void; } +/** If `message` is an array of strings, the array is `join`-ed into one string separated by spaces. */ +export const joinMessageStrings = ( + message: string[] | string | T, +): string | T => { + if (Array.isArray(message)) { + return message.join(" "); + } else { + return message; + } +}; + +/** + * Wraps a logging function so that `joinMessageStrings` is applied to the input before + * calling the wrapped logging function. + */ +const autoJoinMessageStrings = (fn: (msg: string | T) => void) => { + return (message: string[] | string | T) => { + return fn(joinMessageStrings(message)); + }; +}; + export function getActionsLogger(): Logger { return { - debug: core.debug, - info: core.info, - warning: core.warning, - error: core.error, + debug: autoJoinMessageStrings(core.debug), + info: autoJoinMessageStrings(core.info), + warning: autoJoinMessageStrings(core.warning), + error: autoJoinMessageStrings(core.error), isDebug: core.isDebug, startGroup: core.startGroup, endGroup: core.endGroup, }; } -export function getRunnerLogger(debugMode: boolean): Logger { - return { - // eslint-disable-next-line no-console - debug: debugMode ? console.debug : () => undefined, - // eslint-disable-next-line no-console - info: console.info, - // eslint-disable-next-line no-console - warning: console.warn, - // eslint-disable-next-line no-console - error: console.error, - isDebug: () => debugMode, - startGroup: () => undefined, - endGroup: () => undefined, - }; -} - export function withGroup(groupName: string, f: () => T): T { core.startGroup(groupName); try { diff --git a/src/overlay/caching.test.ts b/src/overlay/caching.test.ts index 364eeff9c..e965b06a7 100644 --- a/src/overlay/caching.test.ts +++ b/src/overlay/caching.test.ts @@ -11,11 +11,11 @@ import type { ResolveDatabaseOutput } from "../codeql"; import * as environment from "../environment"; import * as gitUtils from "../git-utils"; import { BuiltInLanguage } from "../languages"; -import { getRunnerLogger } from "../logging"; import { createTestConfig, makeMacro, mockCodeQLVersion, + RecordingLogger, setupTests, } from "../testing-utils"; import * as utils from "../util"; @@ -63,7 +63,7 @@ const testDownloadOverlayBaseDatabaseFromCache = makeMacro({ const dbLocation = path.join(tmpDir, "db"); await fs.promises.mkdir(dbLocation, { recursive: true }); - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const testCase = { ...defaultDownloadTestCase, ...partialTestCase }; const config = createTestConfig({ dbLocation, @@ -214,7 +214,7 @@ testDownloadOverlayBaseDatabaseFromCache.serial( ); test.serial("overlay-base database cache keys remain stable", async (t) => { - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const config = createTestConfig({ languages: ["python", "javascript"] }); const codeQlVersion = "2.23.0"; const commitOid = "abc123def456"; @@ -261,7 +261,7 @@ test.serial("overlay-base database cache keys remain stable", async (t) => { test.serial( "getCodeQlVersionsForOverlayBaseDatabases returns unique versions sorted latest first", async (t) => { - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); sinon.stub(apiClient, "getAutomationID").resolves("test-automation-id/"); sinon.stub(apiClient, "listActionsCaches").resolves([ @@ -287,7 +287,7 @@ test.serial( test.serial( "getCodeQlVersionsForOverlayBaseDatabases returns empty list when no caches exist", async (t) => { - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); sinon.stub(apiClient, "getAutomationID").resolves("test-automation-id/"); sinon.stub(apiClient, "listActionsCaches").resolves([]); @@ -303,7 +303,7 @@ test.serial( test.serial( "getCodeQlVersionsForOverlayBaseDatabases returns empty list when cache keys are unparseable", async (t) => { - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); sinon.stub(apiClient, "getAutomationID").resolves("test-automation-id/"); sinon.stub(apiClient, "listActionsCaches").resolves([ @@ -324,7 +324,7 @@ test.serial( test.serial( "getCodeQlVersionsForOverlayBaseDatabases returns the single version when only one cache exists", async (t) => { - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); sinon.stub(apiClient, "getAutomationID").resolves("test-automation-id/"); sinon.stub(apiClient, "listActionsCaches").resolves([ @@ -344,7 +344,7 @@ test.serial( test.serial( "getCodeQlVersionsForOverlayBaseDatabases resolves language aliases", async (t) => { - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); // The alias `c++` should be resolved to "cpp" and match cache entries keyed with "cpp" sinon.stub(apiClient, "getAutomationID").resolves("test-automation-id/"); @@ -365,7 +365,7 @@ test.serial( test.serial( "getCodeQlVersionsForOverlayBaseDatabases de-duplicates resolved language aliases", async (t) => { - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); sinon.stub(apiClient, "getAutomationID").resolves("test-automation-id/"); const listActionsCachesStub = sinon @@ -391,7 +391,7 @@ test.serial( test.serial( "getCodeQlVersionsForOverlayBaseDatabases ignores nightly versions with build metadata", async (t) => { - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); sinon.stub(apiClient, "getAutomationID").resolves("test-automation-id/"); sinon.stub(apiClient, "listActionsCaches").resolves([ diff --git a/src/overlay/index.test.ts b/src/overlay/index.test.ts index 967ccf3a7..4534006f8 100644 --- a/src/overlay/index.test.ts +++ b/src/overlay/index.test.ts @@ -6,8 +6,11 @@ import * as sinon from "sinon"; import * as actionsUtil from "../actions-util"; import * as gitUtils from "../git-utils"; -import { getRunnerLogger } from "../logging"; -import { createTestConfig, setupTests } from "../testing-utils"; +import { + createTestConfig, + RecordingLogger, + setupTests, +} from "../testing-utils"; import { withTmpDir } from "../util"; import { writeBaseDatabaseOidsFile, writeOverlayChangesFile } from "."; @@ -27,7 +30,7 @@ test.serial( ), ); - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const config = createTestConfig({ dbLocation }); // Mock the getFileOidsUnderPath function to return base OIDs @@ -91,7 +94,7 @@ test.serial( ), ); - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const config = createTestConfig({ dbLocation }); // Mock the getFileOidsUnderPath function to return base OIDs @@ -169,7 +172,7 @@ test.serial( ), ); - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const config = createTestConfig({ dbLocation }); // Mock the getFileOidsUnderPath function to return base OIDs @@ -232,7 +235,7 @@ test.serial( ), ); - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const config = createTestConfig({ dbLocation }); // Base OIDs (sourceRoot-relative paths) diff --git a/src/setup-codeql.test.ts b/src/setup-codeql.test.ts index 780586ddf..d1d1ba8da 100644 --- a/src/setup-codeql.test.ts +++ b/src/setup-codeql.test.ts @@ -14,7 +14,6 @@ import * as diagnostics from "./diagnostics"; import { ActionsEnvVars, Env, EnvVar, ReadOnlyEnv } from "./environment"; import { Feature } from "./feature-flags"; import { BuiltInLanguage } from "./languages"; -import { getRunnerLogger } from "./logging"; import { getCacheRestoreKeyPrefix } from "./overlay/caching"; import { MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION } from "./per-language-bundles"; import * as setupCodeql from "./setup-codeql"; @@ -22,6 +21,7 @@ import * as tar from "./tar"; import { LINKED_CLI_VERSION, LoggedMessage, + RecordingLogger, SAMPLE_DEFAULT_CLI_VERSION, SAMPLE_DOTCOM_API_DETAILS, checkExpectedLogMessages, @@ -117,7 +117,7 @@ test.serial("convert to semver", (t) => { try { const parsedVersion = setupCodeql.convertToSemVer( version, - getRunnerLogger(true), + new RecordingLogger(), ); t.deepEqual(parsedVersion, expectedVersion); } catch (e) { @@ -145,7 +145,7 @@ test.serial( GitHubVariant.DOTCOM, false, features, - getRunnerLogger(true), + new RecordingLogger(), ); t.deepEqual(source, { @@ -231,7 +231,7 @@ for (const { GitHubVariant.DOTCOM, tarSupportsZstd, features, - getRunnerLogger(true), + new RecordingLogger(), ); t.is(source.toolsVersion, LINKED_CLI_VERSION.cliVersion); @@ -597,7 +597,7 @@ test.serial( undefined, // otherLanguagePacksReason false, // useOverlayAwareDefaultCliVersion createFeatures([]), - getRunnerLogger(true), + new RecordingLogger(), ); t.is(result.toolsVersion, "local"); @@ -634,7 +634,7 @@ for (const toolsInput of ["nightly", "nightly-latest"]) { GitHubVariant.DOTCOM, true, // tarSupportsZstd createFeatures([]), - getRunnerLogger(true), + new RecordingLogger(), ); t.deepEqual(source, { @@ -686,7 +686,7 @@ test.serial( GitHubVariant.DOTCOM, true, // tarSupportsZstd createFeatures([]), - getRunnerLogger(true), + new RecordingLogger(), ); t.is(source.sourceType, "download"); @@ -720,7 +720,7 @@ test.serial( GitHubVariant.DOTCOM, true, // tarSupportsZstd createFeatures([Feature.ForceNightly]), - getRunnerLogger(true), + new RecordingLogger(), ); t.is(source.sourceType, "download"); @@ -832,7 +832,7 @@ test.serial( GitHubVariant.DOTCOM, true, createFeatures([]), - getRunnerLogger(true), + new RecordingLogger(), ), { message: @@ -1004,7 +1004,7 @@ test.serial( t.is( setupCodeql.tryGetTagNameFromUrl( "https://github.com/org/codeql-bundle-testing/releases/download/codeql-bundle-v2.19.0/codeql-bundle-linux64.tar.zst", - getRunnerLogger(true), + new RecordingLogger(), ), "codeql-bundle-v2.19.0", ); @@ -1016,7 +1016,7 @@ test.serial( (t) => { sinon.stub(toolcache, "findAllVersions").returns([]); t.is( - setupCodeql.getLatestToolcacheVersion(getRunnerLogger(true)), + setupCodeql.getLatestToolcacheVersion(new RecordingLogger()), undefined, ); }, @@ -1028,7 +1028,7 @@ test.serial( const testVersions = ["2.3.1", "3.2.1", "1.2.3"]; sinon.stub(toolcache, "findAllVersions").returns(testVersions); - t.is(setupCodeql.getLatestToolcacheVersion(getRunnerLogger(true)), "3.2.1"); + t.is(setupCodeql.getLatestToolcacheVersion(new RecordingLogger()), "3.2.1"); }, ); @@ -1082,7 +1082,7 @@ test.serial( GitHubVariant.DOTCOM, false, createFeatures([Feature.OverlayAnalysisMatchCodeqlVersion]), - getRunnerLogger(true), + new RecordingLogger(), ); t.assert(listStub.calledOnce); @@ -1121,7 +1121,7 @@ test.serial( GitHubVariant.DOTCOM, false, createFeatures([Feature.OverlayAnalysisMatchCodeqlVersion]), - getRunnerLogger(true), + new RecordingLogger(), ); t.assert(listStub.notCalled); @@ -1159,7 +1159,7 @@ test.serial( GitHubVariant.DOTCOM, true, // tarSupportsZstd createFeatures([]), - getRunnerLogger(true), + new RecordingLogger(), ); t.is(source.sourceType, "download"); @@ -1213,7 +1213,7 @@ test.serial( variant, true, // tarSupportsZstd createFeatures([]), - getRunnerLogger(true), + new RecordingLogger(), ); t.is(source.sourceType, "download"); @@ -1283,7 +1283,7 @@ for (const fallback of [false, true]) { undefined, // otherLanguagePacksReason false, // useOverlayAwareDefaultCliVersion createFeatures([]), - getRunnerLogger(true), + new RecordingLogger(), ); t.true(fetchRelease.calledTwice); @@ -1548,7 +1548,7 @@ for (const error of [ undefined, // otherLanguagePacksReason false, // useOverlayAwareDefaultCliVersion createFeatures([]), - getRunnerLogger(true), + new RecordingLogger(), ), { is: error }, ); @@ -1584,7 +1584,7 @@ test.serial( undefined, // otherLanguagePacksReason false, // useOverlayAwareDefaultCliVersion createFeatures([]), - getRunnerLogger(true), + new RecordingLogger(), ), { is: error }, ); @@ -1617,7 +1617,7 @@ test.serial( overlayMatchEnabledVersions, ["javascript"], createFeatures([Feature.OverlayAnalysisMatchCodeqlVersion]), - getRunnerLogger(true), + new RecordingLogger(), ); t.deepEqual(result, [ { cliVersion: "2.20.1", tagName: "codeql-bundle-v2.20.1" }, @@ -1640,7 +1640,7 @@ test.serial( overlayMatchEnabledVersions, ["javascript"], createFeatures([Feature.OverlayAnalysisMatchCodeqlVersion]), - getRunnerLogger(true), + new RecordingLogger(), ); t.deepEqual(result, []); }, @@ -1657,7 +1657,7 @@ const noLanguagesMacro = makeMacro({ overlayMatchEnabledVersions, rawLanguages, createFeatures([Feature.OverlayAnalysisMatchCodeqlVersion]), - getRunnerLogger(true), + new RecordingLogger(), ); t.deepEqual(result, []); t.assert( @@ -1682,7 +1682,7 @@ test.serial( overlayMatchEnabledVersions, ["javascript"], createFeatures([Feature.OverlayAnalysisMatchCodeqlVersion]), - getRunnerLogger(true), + new RecordingLogger(), ); t.deepEqual(result, []); }, @@ -1702,7 +1702,7 @@ test.serial( overlayMatchEnabledVersions, ["javascript"], createFeatures([Feature.OverlayAnalysisMatchCodeqlVersion]), - getRunnerLogger(true), + new RecordingLogger(), ); t.deepEqual(result, [ { cliVersion: "2.20.2", tagName: "codeql-bundle-v2.20.2" }, @@ -1719,7 +1719,7 @@ test.serial( overlayMatchEnabledVersions, ["javascript"], createFeatures([]), - getRunnerLogger(true), + new RecordingLogger(), ); t.deepEqual(result, []); t.assert( @@ -1743,7 +1743,7 @@ test.serial( overlayMatchEnabledVersions, ["javascript"], createFeatures([Feature.OverlayAnalysisMatchCodeqlVersionDryRun]), - getRunnerLogger(true), + new RecordingLogger(), ); t.deepEqual( result, @@ -1774,7 +1774,7 @@ test.serial( Feature.OverlayAnalysisMatchCodeqlVersion, Feature.OverlayAnalysisMatchCodeqlVersionDryRun, ]), - getRunnerLogger(true), + new RecordingLogger(), ); t.deepEqual(result, [ { cliVersion: "2.20.1", tagName: "codeql-bundle-v2.20.1" }, @@ -2080,7 +2080,7 @@ test.serial( env: new ReadOnlyEnv({ [ActionsEnvVars.RUNNER_TOOL_CACHE]: injectedRoot, }), - logger: getRunnerLogger(true), + logger: new RecordingLogger(), }); t.deepEqual(result, { @@ -2235,7 +2235,7 @@ test.serial( undefined, // otherLanguagePacksReason false, // useOverlayAwareDefaultCliVersion createFeatures([]), - getRunnerLogger(true), + new RecordingLogger(), ); t.is( @@ -2275,7 +2275,7 @@ test.serial( undefined, // otherLanguagePacksReason false, // useOverlayAwareDefaultCliVersion createFeatures([]), - getRunnerLogger(true), + new RecordingLogger(), ), { is: error }, ); @@ -2390,7 +2390,7 @@ test.serial( "isToolcacheOnWorkspaceFilesystem assumes a different filesystem when it cannot tell", async (t) => { await withTmpDir(async (tmpDir) => { - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); setupActionsVars(tmpDir, tmpDir); t.true(toolsDownload.isToolcacheOnWorkspaceFilesystem(logger)); diff --git a/src/start-proxy.test.ts b/src/start-proxy.test.ts index 78704a38a..2446c994c 100644 --- a/src/start-proxy.test.ts +++ b/src/start-proxy.test.ts @@ -13,7 +13,7 @@ import { setUpFeatureFlagTests } from "./feature-flags/testing-util"; import { UnvalidatedObject, validateSchema } from "./json"; import { makeFromSchema } from "./json/testing-util"; import { BuiltInLanguage } from "./languages"; -import { getRunnerLogger, Logger } from "./logging"; +import { Logger } from "./logging"; import { BundlePlatform, getBundlePlatform } from "./platform"; import * as repository from "./setup/repository"; import * as startProxyExports from "./start-proxy"; @@ -67,7 +67,7 @@ test("getCredentials prefers registriesCredentials over registrySecrets", async ]); const credentials = startProxyExports.getCredentials( - getRunnerLogger(true), + new RecordingLogger(), registrySecrets, registryCredentials, undefined, @@ -84,7 +84,7 @@ test("getCredentials throws an error when configurations are not an array", asyn t.throws( () => startProxyExports.getCredentials( - getRunnerLogger(true), + new RecordingLogger(), undefined, registryCredentials, undefined, @@ -103,7 +103,7 @@ test("getCredentials throws error when credential is not an object", async (t) = t.throws( () => startProxyExports.getCredentials( - getRunnerLogger(true), + new RecordingLogger(), undefined, testCredential, undefined, @@ -124,7 +124,7 @@ test("getCredentials throws error when credential is missing type", async (t) => t.throws( () => startProxyExports.getCredentials( - getRunnerLogger(true), + new RecordingLogger(), undefined, testCredential, undefined, @@ -147,7 +147,7 @@ test("getCredentials throws error when credential missing host and url", async ( t.throws( () => startProxyExports.getCredentials( - getRunnerLogger(true), + new RecordingLogger(), undefined, testCredential, undefined, @@ -161,7 +161,7 @@ test("getCredentials throws error when credential missing host and url", async ( test("getCredentials filters by language when specified", async (t) => { const credentials = startProxyExports.getCredentials( - getRunnerLogger(true), + new RecordingLogger(), undefined, toEncodedJSON(mixedCredentials), BuiltInLanguage.java, @@ -172,7 +172,7 @@ test("getCredentials filters by language when specified", async (t) => { test("getCredentials returns all for a language when specified", async (t) => { const credentials = startProxyExports.getCredentials( - getRunnerLogger(true), + new RecordingLogger(), undefined, toEncodedJSON([...mixedCredentials, gitSourceCredential]), BuiltInLanguage.go, @@ -192,7 +192,7 @@ test("getCredentials returns all goproxy_servers for Go when specified", async ( ]; const credentials = startProxyExports.getCredentials( - getRunnerLogger(true), + new RecordingLogger(), undefined, toEncodedJSON(multipleGoproxyServers), BuiltInLanguage.go, @@ -221,7 +221,7 @@ test("getCredentials returns all maven_repositories for Java when specified", as ]; const credentials = startProxyExports.getCredentials( - getRunnerLogger(true), + new RecordingLogger(), undefined, toEncodedJSON(multipleMavenRepositories), BuiltInLanguage.java, @@ -239,7 +239,7 @@ test("getCredentials returns all credentials when no language specified", async const credentialsInput = toEncodedJSON(mixedCredentials); const credentials = startProxyExports.getCredentials( - getRunnerLogger(true), + new RecordingLogger(), undefined, credentialsInput, undefined, @@ -269,7 +269,7 @@ test("getCredentials throws an error when non-printable characters are used", as t.throws( () => startProxyExports.getCredentials( - getRunnerLogger(true), + new RecordingLogger(), undefined, credentialsInput, undefined, @@ -300,7 +300,7 @@ for (const oidcSchemaInfo of startProxyExports.oidcSchemas) { t.throws( () => startProxyExports.getCredentials( - getRunnerLogger(true), + new RecordingLogger(), undefined, credentialsInput, undefined, @@ -324,7 +324,7 @@ test("getCredentials accepts OIDC configurations", (t) => { ); const credentials = startProxyExports.getCredentials( - getRunnerLogger(true), + new RecordingLogger(), undefined, toEncodedJSON(oidcConfigurations), BuiltInLanguage.csharp, @@ -514,7 +514,7 @@ test("getCredentials validates 'replaces-base' correctly", async (t) => { ]); const credentials = startProxyExports.getCredentials( - getRunnerLogger(true), + new RecordingLogger(), undefined, credentialsInput, BuiltInLanguage.java, @@ -533,7 +533,7 @@ test("getCredentials validates 'replaces-base' correctly", async (t) => { }; t.throws(() => startProxyExports.getCredentials( - getRunnerLogger(true), + new RecordingLogger(), undefined, toEncodedJSON([{ ...baseInvalid, "replaces-base": null }]), BuiltInLanguage.java, @@ -541,7 +541,7 @@ test("getCredentials validates 'replaces-base' correctly", async (t) => { ); t.throws(() => startProxyExports.getCredentials( - getRunnerLogger(true), + new RecordingLogger(), undefined, toEncodedJSON([{ ...baseInvalid, "replaces-base": 123 }]), BuiltInLanguage.java, @@ -549,7 +549,7 @@ test("getCredentials validates 'replaces-base' correctly", async (t) => { ); t.throws(() => startProxyExports.getCredentials( - getRunnerLogger(true), + new RecordingLogger(), undefined, toEncodedJSON([{ ...baseInvalid, "replaces-base": "true" }]), BuiltInLanguage.java, @@ -565,7 +565,7 @@ test("getCredentials returns only ALWAYS_ENABLED_REGISTRY_TYPE credentials for A ]); const credentials = startProxyExports.getCredentials( - getRunnerLogger(true), + new RecordingLogger(), undefined, credentialsInput, BuiltInLanguage.actions, @@ -597,7 +597,7 @@ test("getCredentials always returns ALWAYS_ENABLED_REGISTRY_TYPE credentials for // Test all languages. for (const language of Object.values(BuiltInLanguage)) { const credentials = startProxyExports.getCredentials( - getRunnerLogger(true), + new RecordingLogger(), undefined, credentialsInput, language, diff --git a/src/status-report.test.ts b/src/status-report.test.ts index 2b763da70..004161efa 100644 --- a/src/status-report.test.ts +++ b/src/status-report.test.ts @@ -6,7 +6,6 @@ import * as actionsUtil from "./actions-util"; import { Config } from "./config-utils"; import { EnvVar, RegistryProxyVars } from "./environment"; import { BuiltInLanguage } from "./languages"; -import { getRunnerLogger } from "./logging"; import { ToolsSource } from "./setup-codeql"; import type { Registry } from "./start-proxy"; import { @@ -154,7 +153,7 @@ test.serial("createStatusReportBase", async (t) => { languages: [BuiltInLanguage.java, BuiltInLanguage.swift], }), { numAvailableBytes: 100, numTotalBytes: 500 }, - getRunnerLogger(false), + new RecordingLogger(), "failure cause", "exception stack trace", ); @@ -203,7 +202,7 @@ test.serial("createStatusReportBase - empty configuration", async (t) => { new Date("May 19, 2023 05:19:00"), {}, { numAvailableBytes: 100, numTotalBytes: 500 }, - getRunnerLogger(false), + new RecordingLogger(), ); if (t.truthy(statusReport)) { @@ -225,7 +224,7 @@ test.serial("createStatusReportBase - partial configuration", async (t) => { languages: ["go"], }, { numAvailableBytes: 100, numTotalBytes: 500 }, - getRunnerLogger(false), + new RecordingLogger(), ); if (t.truthy(statusReport)) { @@ -248,7 +247,7 @@ test.serial("createStatusReportBase_firstParty", async (t) => { new Date("May 19, 2023 05:19:00"), createTestConfig({}), { numAvailableBytes: 100, numTotalBytes: 500 }, - getRunnerLogger(false), + new RecordingLogger(), "failure cause", "exception stack trace", ) @@ -264,7 +263,7 @@ test.serial("createStatusReportBase_firstParty", async (t) => { new Date("May 19, 2023 05:19:00"), createTestConfig({}), { numAvailableBytes: 100, numTotalBytes: 500 }, - getRunnerLogger(false), + new RecordingLogger(), "failure cause", "exception stack trace", ) @@ -281,7 +280,7 @@ test.serial("createStatusReportBase_firstParty", async (t) => { new Date("May 19, 2023 05:19:00"), createTestConfig({}), { numAvailableBytes: 100, numTotalBytes: 500 }, - getRunnerLogger(false), + new RecordingLogger(), "failure cause", "exception stack trace", ) @@ -297,7 +296,7 @@ test.serial("createStatusReportBase_firstParty", async (t) => { new Date("May 19, 2023 05:19:00"), createTestConfig({}), { numAvailableBytes: 100, numTotalBytes: 500 }, - getRunnerLogger(false), + new RecordingLogger(), "failure cause", "exception stack trace", ) @@ -314,7 +313,7 @@ test.serial("createStatusReportBase_firstParty", async (t) => { new Date("May 19, 2023 05:19:00"), createTestConfig({}), { numAvailableBytes: 100, numTotalBytes: 500 }, - getRunnerLogger(false), + new RecordingLogger(), "failure cause", "exception stack trace", ) @@ -330,7 +329,7 @@ test.serial("createStatusReportBase_firstParty", async (t) => { new Date("May 19, 2023 05:19:00"), createTestConfig({}), { numAvailableBytes: 100, numTotalBytes: 500 }, - getRunnerLogger(false), + new RecordingLogger(), "failure cause", "exception stack trace", ) @@ -411,7 +410,7 @@ const testCreateInitWithConfigStatusReport = makeMacro({ new Date("May 19, 2023 05:19:00"), config, { numAvailableBytes: 100, numTotalBytes: 500 }, - getRunnerLogger(false), + new RecordingLogger(), "failure cause", "exception stack trace", ); diff --git a/src/tar.test.ts b/src/tar.test.ts index 48f4e866d..99d68b531 100644 --- a/src/tar.test.ts +++ b/src/tar.test.ts @@ -3,9 +3,8 @@ import * as stream from "stream"; import test from "ava"; -import { getRunnerLogger } from "./logging"; import { extractTarZst } from "./tar"; -import { setupTests } from "./testing-utils"; +import { RecordingLogger, setupTests } from "./testing-utils"; import { withTmpDir } from "./util"; setupTests(test); @@ -17,7 +16,7 @@ test("extractTarZst rejects if the input stream errors", async (t) => { archive, path.join(tmpDir, "dest"), { type: "gnu", version: "1.34" }, - getRunnerLogger(true), + new RecordingLogger(), ); archive.destroy( diff --git a/src/testing-utils.ts b/src/testing-utils.ts index 8c38187c4..5cb896787 100644 --- a/src/testing-utils.ts +++ b/src/testing-utils.ts @@ -30,7 +30,7 @@ import { featureConfig, FeatureEnablement, } from "./feature-flags"; -import { Logger } from "./logging"; +import { joinMessageStrings, Loggable, LoggableError, Logger } from "./logging"; import { OverlayDatabaseMode } from "./overlay/overlay-database-mode"; import { getBundlePlatform } from "./platform"; import { ActionName } from "./status-report"; @@ -658,12 +658,13 @@ export class RecordingLogger implements Logger { constructor(private readonly logToConsole: boolean = true) {} - private addMessage(level: LogLevel, message: string | Error): void { - this.messages.push({ type: level, message }); + private addMessage(level: LogLevel, message: LoggableError): void { + const joinedMessage = joinMessageStrings(message); + this.messages.push({ type: level, message: joinedMessage }); if (this.logToConsole) { // eslint-disable-next-line no-console - console.debug(message); + console.debug(joinedMessage); } } @@ -684,19 +685,19 @@ export class RecordingLogger implements Logger { return true; } - debug(message: string) { + debug(message: Loggable) { this.addMessage("debug", message); } - info(message: string) { + info(message: Loggable) { this.addMessage("info", message); } - warning(message: string | Error) { + warning(message: LoggableError) { this.addMessage("warning", message); } - error(message: string | Error) { + error(message: LoggableError) { this.addMessage("error", message); } @@ -1041,3 +1042,15 @@ export function makeTestToken(length: number = 36) { "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; return chars.repeat(Math.ceil(length / chars.length)).slice(0, length); } + +/** + * Get a real, newly created CodeQL instance for testing. The instance refers to + * a non-existent placeholder codeql command, so tests that use this function + * should also stub the toolrunner.ToolRunner constructor. + */ +export async function getCodeQLForTesting( + cmd = "codeql-for-testing", + logger: Logger = new RecordingLogger(), +): Promise { + return codeql.getCodeQLForCmd(logger, cmd, false); +} diff --git a/src/tools-download.test.ts b/src/tools-download.test.ts index 407e2ea48..1ba980bbe 100644 --- a/src/tools-download.test.ts +++ b/src/tools-download.test.ts @@ -10,9 +10,8 @@ import { https } from "follow-redirects"; import nock from "nock"; import * as sinon from "sinon"; -import { getRunnerLogger } from "./logging"; import * as tar from "./tar"; -import { setupTests } from "./testing-utils"; +import { RecordingLogger, setupTests } from "./testing-utils"; import { downloadAndExtract } from "./tools-download"; import * as util from "./util"; @@ -34,7 +33,7 @@ test.serial( undefined, {}, undefined, - getRunnerLogger(true), + new RecordingLogger(), ); t.assert(Number.isInteger(statusReport.downloadDurationMs)); @@ -72,7 +71,7 @@ test.serial( undefined, {}, { type: "gnu", version: "1.34" }, - getRunnerLogger(true), + new RecordingLogger(), ); t.assert(Number.isInteger(statusReport.downloadDurationMs)); @@ -105,7 +104,7 @@ test.serial( undefined, {}, { type: "gnu", version: "1.34" }, - getRunnerLogger(true), + new RecordingLogger(), ), { instanceOf: util.HTTPError, @@ -149,7 +148,7 @@ test.serial( undefined, {}, { type: "gnu", version: "1.34" }, - getRunnerLogger(true), + new RecordingLogger(), ); t.assert(Number.isInteger(statusReport.downloadDurationMs)); @@ -189,7 +188,7 @@ test.serial( undefined, {}, { type: "gnu", version: "1.34" }, - getRunnerLogger(true), + new RecordingLogger(), ); t.is( @@ -237,7 +236,7 @@ test.serial( undefined, {}, { type: "gnu", version: "1.34" }, - getRunnerLogger(true), + new RecordingLogger(), ); t.assert(Number.isInteger(statusReport.totalDurationMs)); diff --git a/src/tracer-config.test.ts b/src/tracer-config.test.ts index a3c42d668..f410d47c1 100644 --- a/src/tracer-config.test.ts +++ b/src/tracer-config.test.ts @@ -4,11 +4,12 @@ import * as path from "path"; import test from "ava"; import * as sinon from "sinon"; -import { CodeQL, getCodeQLForTesting } from "./codeql"; +import type { CodeQL } from "./codeql"; import * as configUtils from "./config-utils"; import { BuiltInLanguage } from "./languages"; import { createTestConfig, + getCodeQLForTesting, makeVersionInfo, RecordingLogger, setupTests, diff --git a/src/trap-caching.test.ts b/src/trap-caching.test.ts index 15ab49481..99dc59706 100644 --- a/src/trap-caching.test.ts +++ b/src/trap-caching.test.ts @@ -6,29 +6,26 @@ import test from "ava"; import * as sinon from "sinon"; import * as actionsUtil from "./actions-util"; -import * as apiClient from "./api-client"; import { getTrapCachingExtractorConfigArgs, getTrapCachingExtractorConfigArgsForLang, createStubCodeQL, } from "./codeql"; import * as configUtils from "./config-utils"; -import { Feature } from "./feature-flags"; import * as gitUtils from "./git-utils"; import { BuiltInLanguage } from "./languages"; -import { getRunnerLogger } from "./logging"; import { - createFeatures, + callee, createTestConfig, getRecordingLogger, makeVersionInfo, setupTests, } from "./testing-utils"; import { - cleanupTrapCaches, downloadTrapCaches, getLanguagesSupportingCaching, uploadTrapCaches, + warnIfTrapCacheCleanupEnvVarIsSet, } from "./trap-caching"; import * as util from "./util"; @@ -200,83 +197,27 @@ test.serial( }, ); -test.serial("cleanup removes only old CodeQL TRAP caches", async (t) => { - await util.withTmpDir(async (tmpDir) => { - // This config specifies that we are analyzing JavaScript and Ruby, but not Swift. - const config = getTestConfigWithTempDir(tmpDir); - - sinon.stub(gitUtils, "getRef").resolves("refs/heads/main"); - sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(true); - const listStub = sinon.stub(apiClient, "listActionsCaches").resolves([ - // Should be kept, since it's not relevant to CodeQL. In reality, the API shouldn't return - // this in the first place, but this is a defensive check. - { - id: 1, - key: "some-other-key", - created_at: "2024-05-23T14:25:00Z", - size_in_bytes: 100 * 1024 * 1024, - }, - // Should be kept, since it's the newest TRAP cache for JavaScript - { - id: 2, - key: "codeql-trap-1-2.0.0-javascript-newest", - created_at: "2024-04-23T14:25:00Z", - size_in_bytes: 50 * 1024 * 1024, - }, - // Should be cleaned up - { - id: 3, - key: "codeql-trap-1-2.0.0-javascript-older", - created_at: "2024-03-22T14:25:00Z", - size_in_bytes: 200 * 1024 * 1024, - }, - // Should be cleaned up - { - id: 4, - key: "codeql-trap-1-2.0.0-javascript-oldest", - created_at: "2024-02-21T14:25:00Z", - size_in_bytes: 300 * 1024 * 1024, - }, - // Should be kept, since it's the newest TRAP cache for Ruby - { - id: 5, - key: "codeql-trap-1-2.0.0-ruby-newest", - created_at: "2024-02-20T14:25:00Z", - size_in_bytes: 300 * 1024 * 1024, - }, - // Should be kept, since we aren't analyzing Swift - { - id: 6, - key: "codeql-trap-1-2.0.0-swift-newest", - created_at: "2024-02-22T14:25:00Z", - size_in_bytes: 300 * 1024 * 1024, - }, - // Should be kept, since we aren't analyzing Swift - { - id: 7, - key: "codeql-trap-1-2.0.0-swift-older", - created_at: "2024-02-21T14:25:00Z", - size_in_bytes: 300 * 1024 * 1024, - }, - ]); - - const deleteStub = sinon.stub(apiClient, "deleteActionsCache").resolves(); - - const statusReport = await cleanupTrapCaches( - config, - createFeatures([Feature.CleanupTrapCaches]), - getRunnerLogger(true), +// `CODEQL_ACTION_CLEANUP_TRAP_CACHES` has no effect with either value, so we warn even when it's +// set to `false`. +for (const value of ["true", "false"]) { + test(`warnIfTrapCacheCleanupEnvVarIsSet warns when CODEQL_ACTION_CLEANUP_TRAP_CACHES is '${value}'`, async (t) => { + const target = callee(warnIfTrapCacheCleanupEnvVarIsSet) + .withEnv((env) => env.set("CODEQL_ACTION_CLEANUP_TRAP_CACHES", value)) + .withArgs() + .logs( + t, + "CODEQL_ACTION_CLEANUP_TRAP_CACHES environment variable no longer has any effect", + ); + await target.passes(t.is, undefined); + t.deepEqual( + target.getLogger().messages.map((m) => m.type), + ["warning"], ); - - t.is(listStub.callCount, 1); - t.assert(listStub.calledWithExactly("codeql-trap", "refs/heads/main")); - - t.deepEqual(statusReport, { - trap_cache_cleanup_size_bytes: 500 * 1024 * 1024, - }); - - t.is(deleteStub.callCount, 2); - t.assert(deleteStub.calledWithExactly(3)); - t.assert(deleteStub.calledWithExactly(4)); }); +} + +test("warnIfTrapCacheCleanupEnvVarIsSet doesn't warn when CODEQL_ACTION_CLEANUP_TRAP_CACHES is unset", async (t) => { + const target = callee(warnIfTrapCacheCleanupEnvVarIsSet).withArgs(); + await target.passes(t.is, undefined); + t.deepEqual(target.getLogger().messages, []); }); diff --git a/src/trap-caching.ts b/src/trap-caching.ts index d93936832..58d96890f 100644 --- a/src/trap-caching.ts +++ b/src/trap-caching.ts @@ -3,22 +3,14 @@ import * as path from "path"; import * as actionsCache from "@actions/cache"; +import { type ActionState } from "./action-common"; import * as actionsUtil from "./actions-util"; -import * as apiClient from "./api-client"; import { type CodeQL } from "./codeql"; import { type Config } from "./config-utils"; -import { DocUrl } from "./doc-url"; -import { Feature, FeatureEnablement } from "./feature-flags"; import * as gitUtils from "./git-utils"; import { Language } from "./languages"; import { Logger } from "./logging"; -import { - asHTTPError, - getEnv, - getErrorMessage, - tryGetFolderBytes, - waitForResultWithTimeLimit, -} from "./util"; +import { getEnv, tryGetFolderBytes, waitForResultWithTimeLimit } from "./util"; // This constant should be bumped if we make a breaking change // to how the CodeQL Action stores or retrieves the TRAP cache, @@ -178,113 +170,25 @@ export async function uploadTrapCaches( return true; } -export interface TrapCacheCleanupStatusReport { - trap_cache_cleanup_error?: string; - trap_cache_cleanup_size_bytes?: number; - trap_cache_cleanup_skipped_because?: string; -} - -export async function cleanupTrapCaches( - config: Config, - features: FeatureEnablement, - logger: Logger, -): Promise { - const env = getEnv(); - - if (!(await features.getValue(Feature.CleanupTrapCaches))) { - return { - trap_cache_cleanup_skipped_because: "feature disabled", - }; - } - logger.warning( - "TRAP cache cleanup is deprecated and will be removed in May 2026. " + - "We recommend instead disabling TRAP caching by passing the `trap-caching: false` input to the `init` Action.", - ); - if (!(await gitUtils.isAnalyzingDefaultBranch(env, config.repositoryRoot))) { - return { - trap_cache_cleanup_skipped_because: "not analyzing default branch", - }; - } - - try { - let totalBytesCleanedUp = 0; - - const allCaches = await apiClient.listActionsCaches( - CODEQL_TRAP_CACHE_PREFIX, - await gitUtils.getRef(env, config.repositoryRoot), +/** + * Warns if the `CODEQL_ACTION_CLEANUP_TRAP_CACHES` environment variable is set to a non-empty + * value. That variable controlled the removed TRAP cache cleanup feature, so setting it has no + * effect. + */ +export function warnIfTrapCacheCleanupEnvVarIsSet({ + env, + logger, +}: ActionState<["Logger", "ReadOnlyEnv"]>): void { + if (env.getOptional("CODEQL_ACTION_CLEANUP_TRAP_CACHES") !== undefined) { + logger.warning( + "TRAP cache cleanup has been removed, so the CODEQL_ACTION_CLEANUP_TRAP_CACHES environment " + + "variable no longer has any effect and can be removed from your workflow. If you used TRAP " + + "cache cleanup to limit Actions cache usage, we recommend disabling TRAP caching instead by " + + "passing the `trap-caching: false` input to the `init` Action.", ); - - for (const language of config.languages) { - if (config.trapCaches[language]) { - const cachesToRemove = await getTrapCachesForLanguage( - allCaches, - language, - logger, - ); - // Dates returned by the API are in ISO 8601 format, so we can sort them lexicographically - cachesToRemove.sort((a, b) => a.created_at.localeCompare(b.created_at)); - // Keep the most recent cache - const mostRecentCache = cachesToRemove.pop(); - logger.debug( - `Keeping most recent TRAP cache (${JSON.stringify(mostRecentCache)})`, - ); - - if (cachesToRemove.length === 0) { - logger.info(`No TRAP caches to clean up for ${language}.`); - continue; - } - - for (const cache of cachesToRemove) { - logger.debug(`Cleaning up TRAP cache (${JSON.stringify(cache)})`); - await apiClient.deleteActionsCache(cache.id); - } - const bytesCleanedUp = cachesToRemove.reduce( - (acc, item) => acc + item.size_in_bytes, - 0, - ); - totalBytesCleanedUp += bytesCleanedUp; - const megabytesCleanedUp = (bytesCleanedUp / (1024 * 1024)).toFixed(2); - logger.info( - `Cleaned up ${megabytesCleanedUp} MiB of old TRAP caches for ${language}.`, - ); - } - } - return { trap_cache_cleanup_size_bytes: totalBytesCleanedUp }; - } catch (e) { - if (asHTTPError(e)?.status === 403) { - logger.warning( - "Could not cleanup TRAP caches as the token did not have the required permissions. " + - 'To clean up TRAP caches, ensure the token has the "actions:write" permission. ' + - `See ${DocUrl.ASSIGNING_PERMISSIONS_TO_JOBS} for more information.`, - ); - } else { - logger.info(`Failed to cleanup TRAP caches, continuing. Details: ${e}`); - } - return { trap_cache_cleanup_error: getErrorMessage(e) }; } } -async function getTrapCachesForLanguage( - allCaches: apiClient.ActionsCacheItem[], - language: Language, - logger: Logger, -): Promise>> { - logger.debug(`Listing TRAP caches for ${language}`); - - for (const cache of allCaches) { - if (!cache.created_at || !cache.id || !cache.key || !cache.size_in_bytes) { - throw new Error( - "An unexpected cache item was returned from the API that was missing one or " + - `more required fields: ${JSON.stringify(cache)}`, - ); - } - } - - return allCaches.filter((cache) => { - return cache.key?.includes(`-${language}-`); - }) as Array>; -} - export async function getLanguagesSupportingCaching( codeql: CodeQL, languages: Language[], diff --git a/src/upload-lib.test.ts b/src/upload-lib.test.ts index 2c27d56c1..ebfc5cd40 100644 --- a/src/upload-lib.test.ts +++ b/src/upload-lib.test.ts @@ -10,9 +10,9 @@ import * as analyses from "./analyses"; import { AnalysisKind, CodeQuality, CodeScanning } from "./analyses"; import * as api from "./api-client"; import * as diffUtils from "./diff-informed-analysis-utils"; -import { getRunnerLogger, Logger } from "./logging"; +import { Logger } from "./logging"; import * as sarif from "./sarif"; -import { setupTests } from "./testing-utils"; +import { RecordingLogger, setupTests } from "./testing-utils"; import * as uploadLib from "./upload-lib"; import { UploadPayload } from "./upload-lib/types"; import { GitHubVariant, initializeEnvironment, withTmpDir } from "./util"; @@ -29,7 +29,7 @@ test.serial("validateSarifFileSchema - valid", (t) => { uploadLib.validateSarifFileSchema( uploadLib.readSarifFileOrThrow(inputFile), inputFile, - getRunnerLogger(true), + new RecordingLogger(), ), ); }); @@ -40,7 +40,7 @@ test.serial("validateSarifFileSchema - invalid", (t) => { uploadLib.validateSarifFileSchema( uploadLib.readSarifFileOrThrow(inputFile), inputFile, - getRunnerLogger(true), + new RecordingLogger(), ), ); }); @@ -179,7 +179,7 @@ test.serial("finding SARIF files", async (t) => { } const groupedSarifFiles = await uploadLib.getGroupedSarifFilePaths( - getRunnerLogger(true), + new RecordingLogger(), tmpDir, ); @@ -200,7 +200,7 @@ test.serial("getGroupedSarifFilePaths - Risk Assessment files", async (t) => { fs.writeFileSync(sarifPath, ""); const groupedSarifFiles = await uploadLib.getGroupedSarifFilePaths( - getRunnerLogger(true), + new RecordingLogger(), sarifPath, ); @@ -218,7 +218,7 @@ test.serial("getGroupedSarifFilePaths - Code Quality file", async (t) => { fs.writeFileSync(sarifPath, ""); const groupedSarifFiles = await uploadLib.getGroupedSarifFilePaths( - getRunnerLogger(true), + new RecordingLogger(), sarifPath, ); @@ -236,7 +236,7 @@ test.serial("getGroupedSarifFilePaths - Code Scanning file", async (t) => { fs.writeFileSync(sarifPath, ""); const groupedSarifFiles = await uploadLib.getGroupedSarifFilePaths( - getRunnerLogger(true), + new RecordingLogger(), sarifPath, ); @@ -254,7 +254,7 @@ test.serial("getGroupedSarifFilePaths - Other file", async (t) => { fs.writeFileSync(sarifPath, ""); const groupedSarifFiles = await uploadLib.getGroupedSarifFilePaths( - getRunnerLogger(true), + new RecordingLogger(), sarifPath, ); @@ -932,7 +932,7 @@ function uploadPayloadFixtures(analysis: analyses.AnalysisConfig) { owner: mockData.owner, repo: mockData.repo, }, - getRunnerLogger(true), + new RecordingLogger(), analysis, ); @@ -1019,7 +1019,7 @@ function runFilterAlertsByDiffRange( diffRanges: diffUtils.DiffThunkRange[], ): Partial { sinon.stub(diffUtils, "readDiffRangesJsonFile").returns(diffRanges); - return uploadLib.filterAlertsByDiffRange(getRunnerLogger(true), input); + return uploadLib.filterAlertsByDiffRange(new RecordingLogger(), input); } test.serial( diff --git a/src/upload-lib.ts b/src/upload-lib.ts index 3c46c1519..4e97c1d1d 100644 --- a/src/upload-lib.ts +++ b/src/upload-lib.ts @@ -12,7 +12,7 @@ import * as analyses from "./analyses"; import * as api from "./api-client"; import { getGitHubVersion, wrapApiConfigurationError } from "./api-client"; import { CodeQL, getCodeQL } from "./codeql"; -import { getConfig } from "./config-utils"; +import type { ActionConfig } from "./config/action-config"; import { readDiffRangesJsonFile } from "./diff-informed-analysis-utils"; import { ActionsEnvVars, EnvVar, exportEnvVar } from "./environment"; import { FeatureEnablement } from "./feature-flags"; @@ -94,15 +94,19 @@ async function shouldDisableCombineSarifFiles( return true; } -// Takes a list of paths to sarif files and combines them together using the -// CLI `github merge-results` command when all SARIF files are produced by -// CodeQL. Otherwise, it will fall back to combining the files in the action. -// Returns the contents of the combined sarif file. +/** + * Takes a list of paths to sarif files and combines them together using the + * CLI `github merge-results` command when all SARIF files are produced by + * CodeQL. Otherwise, it will fall back to combining the files in the action. + * + * @returns The contents of the combined sarif file. + */ async function combineSarifFilesUsingCLI( sarifFiles: string[], gitHubVersion: GitHubVersion, features: FeatureEnablement, logger: Logger, + config: ActionConfig<"codeQLCmd" | "tempDir"> | undefined, ): Promise> { const env = util.getEnv(); @@ -140,7 +144,6 @@ async function combineSarifFilesUsingCLI( let codeQL: CodeQL; let tempDir: string = actionsUtil.getTemporaryDirectory(); - const config = await getConfig(tempDir, logger); if (config !== undefined) { codeQL = await getCodeQL(logger, config.codeQLCmd); tempDir = config.tempDir; @@ -228,6 +231,14 @@ function getAutomationID( return api.computeAutomationID(analysis_key, environment); } +/** Gets the path to a saved payload file for {@link analysisKind}. */ +export function getSavedPayloadPath(analysisKind: analyses.AnalysisKind) { + return path.join( + actionsUtil.getTemporaryDirectory(), + `payload-${analysisKind}.json`, + ); +} + /** * Upload the given payload. * If the request fails then this will retry a small number of times. @@ -242,10 +253,7 @@ export async function uploadPayload( logger.info("Uploading results"); if (util.shouldSkipSarifUpload()) { - const payloadSaveFile = path.join( - actionsUtil.getTemporaryDirectory(), - `payload-${analysis.kind}.json`, - ); + const payloadSaveFile = getSavedPayloadPath(analysis.kind); logger.info( `SARIF upload disabled by an environment variable. Saving to ${payloadSaveFile}`, ); @@ -599,6 +607,7 @@ export interface PostProcessingResults { export async function postProcessSarifFiles( logger: Logger, features: FeatureEnablement, + config: ActionConfig<"codeQLCmd" | "tempDir"> | undefined, checkoutPath: string, sarifPaths: string[], category: string | undefined, @@ -623,6 +632,7 @@ export async function postProcessSarifFiles( gitHubVersion, features, logger, + config, ); } else { const sarifPath = sarifPaths[0]; @@ -683,6 +693,7 @@ export async function writePostProcessedFiles( * to. */ export async function uploadFiles( + config: ActionConfig<"codeQLCmd" | "tempDir"> | undefined, inputSarifPath: string, checkoutPath: string, category: string | undefined, @@ -697,6 +708,7 @@ export async function uploadFiles( return uploadSpecifiedFiles( sarifPaths, + config, checkoutPath, category, features, @@ -710,6 +722,7 @@ export async function uploadFiles( */ async function uploadSpecifiedFiles( sarifPaths: string[], + config: ActionConfig<"codeQLCmd" | "tempDir"> | undefined, checkoutPath: string, category: string | undefined, features: FeatureEnablement, @@ -719,6 +732,7 @@ async function uploadSpecifiedFiles( const processingResults: PostProcessingResults = await postProcessSarifFiles( logger, features, + config, checkoutPath, sarifPaths, category, @@ -857,6 +871,16 @@ export async function waitForProcessing( ): Promise { logger.startGroup("Waiting for processing to finish"); try { + // If SARIF upload is disabled and we reach this point, then the file was written to disk and + // there is nothing to wait for. We still allow ourselves to get to this point so that we can + // see in the log that we would have waited for processing to finish if the upload had happened. + if (util.shouldSkipSarifUpload()) { + logger.info( + "Nothing to wait for, because SARIF upload is disabled by an environment variable and the SARIF was saved to disk.", + ); + return; + } + const client = api.getApiClient(); // Do an initial wait because processing will always take a minimum of 2-3 seconds diff --git a/src/upload-sarif-action.ts b/src/upload-sarif-action.ts index d3437510c..447314fb1 100644 --- a/src/upload-sarif-action.ts +++ b/src/upload-sarif-action.ts @@ -5,6 +5,7 @@ import * as actionsUtil from "./actions-util"; import { getActionVersion, getTemporaryDirectory } from "./actions-util"; import * as analyses from "./analyses"; import { getGitHubVersion } from "./api-client"; +import { getConfig } from "./config-utils"; import { initFeatures } from "./feature-flags"; import { Logger } from "./logging"; import { getRepositoryNwo } from "./repository"; @@ -66,11 +67,12 @@ async function run({ startedAt, logger }: ActionState<["Base", "Logger"]>) { // Make inputs accessible in the `post` step. actionsUtil.persistInputs(); + const tempDir = getTemporaryDirectory(); const repositoryNwo = getRepositoryNwo(); const features = initFeatures( gitHubVersion, repositoryNwo, - getTemporaryDirectory(), + tempDir, logger, ); @@ -91,9 +93,13 @@ async function run({ startedAt, logger }: ActionState<["Base", "Logger"]>) { const checkoutPath = actionsUtil.getRequiredInput("checkout_path"); const category = actionsUtil.getOptionalInput("category"); + // Load the configuration from a previous step if there is one. This allows us to reuse the CLI that + // may have been installed by a previous step. + const maybeConfig = await getConfig(tempDir, logger); const uploadResults = await postProcessAndUploadSarif( logger, features, + maybeConfig, "always", checkoutPath, sarifPath, diff --git a/src/upload-sarif.test.ts b/src/upload-sarif.test.ts index d310bce92..a1ea5dbee 100644 --- a/src/upload-sarif.test.ts +++ b/src/upload-sarif.test.ts @@ -5,8 +5,12 @@ import test, { ExecutionContext } from "ava"; import * as sinon from "sinon"; import { AnalysisKind, getAnalysisConfig } from "./analyses"; -import { getRunnerLogger } from "./logging"; -import { createFeatures, makeMacro, setupTests } from "./testing-utils"; +import { + createFeatures, + makeMacro, + RecordingLogger, + setupTests, +} from "./testing-utils"; import { UploadResult } from "./upload-lib"; import * as uploadLib from "./upload-lib"; import { postProcessAndUploadSarif } from "./upload-sarif"; @@ -26,6 +30,7 @@ function mockPostProcessSarifFiles() { const analysisConfig = getAnalysisConfig(analysisKind); postProcessSarifFiles .withArgs( + sinon.match.object, sinon.match.any, sinon.match.any, sinon.match.any, @@ -51,7 +56,7 @@ const postProcessAndUploadSarifMacro = makeMacro({ expectedResult: Partial>, ) => { await util.withTmpDir(async (tempDir) => { - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const testPath = sarifPath(tempDir); const features = createFeatures([]); @@ -75,9 +80,11 @@ const postProcessAndUploadSarifMacro = makeMacro({ fs.writeFileSync(sarifFile, ""); } + const config = undefined; const actual = await postProcessAndUploadSarif( logger, features, + config, "always", "", testPath, @@ -94,6 +101,7 @@ const postProcessAndUploadSarifMacro = makeMacro({ postProcessSarifFiles.calledWith( logger, features, + config, sinon.match.any, analysisKindResult.expectedFiles?.map(toFullPath) ?? fullSarifPaths, @@ -206,7 +214,7 @@ test.serial( "postProcessAndUploadSarif doesn't upload if upload is disabled", async (t) => { await util.withTmpDir(async (tempDir) => { - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const features = createFeatures([]); const toFullPath = (filename: string) => path.join(tempDir, filename); @@ -223,6 +231,7 @@ test.serial( const actual = await postProcessAndUploadSarif( logger, features, + undefined, "never", "", tempDir, @@ -239,7 +248,7 @@ test.serial( "postProcessAndUploadSarif writes post-processed SARIF files if output directory is provided", async (t) => { await util.withTmpDir(async (tempDir) => { - const logger = getRunnerLogger(true); + const logger = new RecordingLogger(); const features = createFeatures([]); const toFullPath = (filename: string) => path.join(tempDir, filename); @@ -253,6 +262,7 @@ test.serial( const actual = await postProcessAndUploadSarif( logger, features, + undefined, "never", "", tempDir, diff --git a/src/upload-sarif.ts b/src/upload-sarif.ts index bc2c88698..0a8d8a14a 100644 --- a/src/upload-sarif.ts +++ b/src/upload-sarif.ts @@ -1,5 +1,6 @@ import { UploadKind } from "./actions-util"; import * as analyses from "./analyses"; +import type { ActionConfig } from "./config/action-config"; import { FeatureEnablement } from "./feature-flags"; import { Logger } from "./logging"; import * as upload_lib from "./upload-lib"; @@ -26,6 +27,7 @@ export type UploadSarifResults = Partial< export async function postProcessAndUploadSarif( logger: Logger, features: FeatureEnablement, + config: ActionConfig<"codeQLCmd" | "tempDir"> | undefined, uploadKind: UploadKind, checkoutPath: string, sarifPath: string, @@ -45,6 +47,7 @@ export async function postProcessAndUploadSarif( const postProcessingResults = await upload_lib.postProcessSarifFiles( logger, features, + config, checkoutPath, sarifFiles, category, diff --git a/src/util.test.ts b/src/util.test.ts index bca2b452e..af69171f3 100644 --- a/src/util.test.ts +++ b/src/util.test.ts @@ -10,8 +10,7 @@ import * as sinon from "sinon"; import * as api from "./api-client"; import { EnvVar } from "./environment"; -import { getRunnerLogger } from "./logging"; -import { setupTests } from "./testing-utils"; +import { RecordingLogger, setupTests, getTestEnv } from "./testing-utils"; import * as util from "./util"; setupTests(test); @@ -92,7 +91,7 @@ for (const { test("getMemoryFlag() throws if the ram input is < 0 or NaN", async (t) => { for (const input of ["-1", "hello!"]) { - t.throws(() => util.getMemoryFlag(input, getRunnerLogger(true))); + t.throws(() => util.getMemoryFlag(input, new RecordingLogger())); } }); @@ -109,13 +108,13 @@ test("getThreadsFlag() should return the correct --threads flag", (t) => { ]; for (const [input, expectedFlag] of tests) { - const flag = util.getThreadsFlag(input, getRunnerLogger(true)); + const flag = util.getThreadsFlag(input, new RecordingLogger()); t.deepEqual(flag, expectedFlag); } }); test("getThreadsFlag() throws if the threads input is not an integer", (t) => { - t.throws(() => util.getThreadsFlag("hello!", getRunnerLogger(true))); + t.throws(() => util.getThreadsFlag("hello!", new RecordingLogger())); }); test.serial( @@ -385,7 +384,7 @@ test("getCgroupCpuCountFromCpus calculates the number of CPUs correctly", async const testCpuFile = `${tmpDir}/cpus-file`; fs.writeFileSync(testCpuFile, "1, 9-10\n", "utf-8"); t.deepEqual( - util.getCgroupCpuCountFromCpus(testCpuFile, getRunnerLogger(true)), + util.getCgroupCpuCountFromCpus(testCpuFile, new RecordingLogger()), 3, ); }); @@ -396,7 +395,7 @@ test("getCgroupCpuCountFromCpus returns undefined if the CPU file doesn't exist" const testCpuFile = `${tmpDir}/cpus-file`; t.false(fs.existsSync(testCpuFile)); t.deepEqual( - util.getCgroupCpuCountFromCpus(testCpuFile, getRunnerLogger(true)), + util.getCgroupCpuCountFromCpus(testCpuFile, new RecordingLogger()), undefined, ); }); @@ -407,7 +406,7 @@ test("getCgroupCpuCountFromCpus returns undefined if the CPU file exists but is const testCpuFile = `${tmpDir}/cpus-file`; fs.writeFileSync(testCpuFile, "\n", "utf-8"); t.deepEqual( - util.getCgroupCpuCountFromCpus(testCpuFile, getRunnerLogger(true)), + util.getCgroupCpuCountFromCpus(testCpuFile, new RecordingLogger()), undefined, ); }); @@ -417,7 +416,7 @@ test.serial( "checkDiskUsage succeeds and produces positive numbers", async (t) => { process.env["GITHUB_WORKSPACE"] = os.tmpdir(); - const diskUsage = await util.checkDiskUsage(getRunnerLogger(true)); + const diskUsage = await util.checkDiskUsage(new RecordingLogger()); if (t.truthy(diskUsage)) { t.true(diskUsage.numAvailableBytes > 0); t.true(diskUsage.numTotalBytes > 0); @@ -493,3 +492,70 @@ test("Failure.orElse returns the default value for a failure result", (t) => { const result = new util.Failure(new Error("test error")); t.is(result.orElse("default value"), "default value"); }); + +test("shouldSkipGenerateFailedSarif", (t) => { + const env = getTestEnv(); + + // Don't skip by default + t.is(util.shouldSkipGenerateFailedSarif(env), false); + + // Skip in test mode + env.setAll({ + [EnvVar.TEST_MODE]: "true", + [EnvVar.SKIP_SARIF_UPLOAD]: undefined, + [EnvVar.SKIP_GENERATE_FAILED_SARIF]: undefined, + }); + t.is(util.shouldSkipGenerateFailedSarif(env), true); + + // Skip if explicitly requested + env.setAll({ + [EnvVar.TEST_MODE]: undefined, + [EnvVar.SKIP_SARIF_UPLOAD]: undefined, + [EnvVar.SKIP_GENERATE_FAILED_SARIF]: "true", + }); + t.is(util.shouldSkipGenerateFailedSarif(env), true); + + // Skip if not uploading SARIF + env.setAll({ + [EnvVar.TEST_MODE]: undefined, + [EnvVar.SKIP_SARIF_UPLOAD]: "true", + [EnvVar.SKIP_GENERATE_FAILED_SARIF]: undefined, + }); + t.is(util.shouldSkipGenerateFailedSarif(env), true); + + // Don't skip if explicitly requested + env.setAll({ + [EnvVar.TEST_MODE]: "true", + [EnvVar.SKIP_SARIF_UPLOAD]: "true", + [EnvVar.SKIP_GENERATE_FAILED_SARIF]: "false", + }); + t.is(util.shouldSkipGenerateFailedSarif(env), false); +}); + +test("shouldSkipSarifUpload", (t) => { + const env = getTestEnv(); + + // Don't skip by default + t.is(util.shouldSkipSarifUpload(env), false); + + // Skip in test mode + env.setAll({ + [EnvVar.TEST_MODE]: "true", + [EnvVar.SKIP_SARIF_UPLOAD]: undefined, + }); + t.is(util.shouldSkipSarifUpload(env), true); + + // Skip if explicitly requested + env.setAll({ + [EnvVar.TEST_MODE]: undefined, + [EnvVar.SKIP_SARIF_UPLOAD]: "true", + }); + t.is(util.shouldSkipSarifUpload(env), true); + + // Don't skip if explicitly requested + env.setAll({ + [EnvVar.TEST_MODE]: "true", + [EnvVar.SKIP_SARIF_UPLOAD]: "false", + }); + t.is(util.shouldSkipSarifUpload(env), false); +}); diff --git a/src/util.ts b/src/util.ts index 02e105d57..ba0c3c7d4 100644 --- a/src/util.ts +++ b/src/util.ts @@ -18,10 +18,10 @@ import type { Config } from "./config-utils"; import { EnvVar, getRequiredEnvParam, - isInTestMode, exportEnvVar, getEnv, type Env, + type ReadOnlyEnv, } from "./environment"; import * as json from "./json"; import { Language } from "./languages"; @@ -684,11 +684,30 @@ export function isGoodVersion(versionSpec: string) { return !BROKEN_VERSIONS.includes(versionSpec); } +/** + * Returns whether we specifically want to skip generating a failed SARIF. + */ +export function shouldSkipGenerateFailedSarif( + env: ReadOnlyEnv = getEnv(), +): boolean { + const skipGenerateFailedSarif = env.getOptional( + EnvVar.SKIP_GENERATE_FAILED_SARIF, + ); + return ( + skipGenerateFailedSarif !== "false" && + (shouldSkipSarifUpload(env) || skipGenerateFailedSarif === "true") + ); +} + /** * Returns whether we specifically want to skip uploading SARIF files. */ -export function shouldSkipSarifUpload(): boolean { - return isInTestMode() || process.env[EnvVar.SKIP_SARIF_UPLOAD] === "true"; +export function shouldSkipSarifUpload(env: ReadOnlyEnv = getEnv()): boolean { + const skipSarifUpload = env.getOptional(EnvVar.SKIP_SARIF_UPLOAD); + return ( + skipSarifUpload !== "false" && + (env.isSkippingUploadsInTests() || skipSarifUpload === "true") + ); } /** diff --git a/src/workflow.test.ts b/src/workflow.test.ts index 2373e8bd1..c7b7aebc0 100644 --- a/src/workflow.test.ts +++ b/src/workflow.test.ts @@ -3,10 +3,11 @@ import * as yaml from "js-yaml"; import * as sinon from "sinon"; import * as actionsUtil from "./actions-util"; -import { createStubCodeQL, getCodeQLForTesting } from "./codeql"; +import { createStubCodeQL } from "./codeql"; import { ActionsEnvVars, EnvVar } from "./environment"; import { checkExpectedLogMessages, + getCodeQLForTesting, createTestConfig, getRecordingLogger, getTestEnv, diff --git a/unreleased-change-notes/README.md b/unreleased-change-notes/README.md index 60778c795..3030210a7 100644 --- a/unreleased-change-notes/README.md +++ b/unreleased-change-notes/README.md @@ -2,6 +2,16 @@ Change-notes are Markdown files used to document user-facing changes. When making a change that affects users, create a Markdown file here that describes the change. During the next release, the change-note files in `unreleased-change-notes/` will automatically be added to `CHANGELOG.md`. +### What do document + +Some guidelines to help you decide whether a change needs a change note: + +- New functionality that is gated behind a feature flag should be documented when it is added, highlighting that the feature will only be rolled out later. Ideally, this should also include an estimate for when the feature is expected to become available. +- When a feature flag is removed and the gated functionality becomes the default behaviour, this does not need to be documented. +- Changes that may require users to make changes to their workflows must be documented. + +If in doubt, see `CHANGELOG.md` for what we have included previously. + ### Change-note file format Change-note files must follow a certain format so that they can be automatically validated and processed. Failure to follow the format will result in a failed PR check. diff --git a/unreleased-change-notes/remove-trap-cache-cleanup.md b/unreleased-change-notes/remove-trap-cache-cleanup.md new file mode 100644 index 000000000..b3cb822b8 --- /dev/null +++ b/unreleased-change-notes/remove-trap-cache-cleanup.md @@ -0,0 +1 @@ +- The undocumented TRAP cache cleanup feature that could be enabled using the `CODEQL_ACTION_CLEANUP_TRAP_CACHES` environment variable has been removed, as announced in the release notes for CodeQL Action 4.35.2. The `analyze` Action now warns if this environment variable is set. If you used TRAP cache cleanup to limit Actions cache usage, we recommend disabling TRAP caching by passing the `trap-caching: false` input to the `init` Action. [#4243](https://github.com/github/codeql-action/pull/4243)