Compare commits

..
88 changed files with 2157 additions and 2691 deletions

No files matched your search

@@ -63,7 +63,7 @@ jobs:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Java
uses: actions/setup-java@b843ebf9b913146b38da2eaa7aeddf76074ae1ad # v6.1.0
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
with:
java-version: ${{ inputs.java-version || '17' }}
distribution: temurin
+1 -1
View File
@@ -63,7 +63,7 @@ jobs:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Java
uses: actions/setup-java@b843ebf9b913146b38da2eaa7aeddf76074ae1ad # v6.1.0
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
with:
java-version: ${{ inputs.java-version || '17' }}
distribution: temurin
+19 -17
View File
@@ -46,15 +46,6 @@ 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
@@ -62,20 +53,31 @@ 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
run: |
cd ./../action
npx tsx ./pr-checks/toolcache.ts installed
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`);
}
- id: setup-codeql
uses: ./../action/setup-codeql
with:
tools: ${{ steps.prepare-test.outputs.tools-url }}
- name: Check CodeQL is installed within the toolcache
run: |
cd ./../action
npx tsx ./pr-checks/toolcache.ts installed
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');
}
env:
CODEQL_ACTION_TEST_MODE: true
+30 -22
View File
@@ -50,15 +50,6 @@ 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
@@ -67,30 +58,47 @@ jobs:
use-all-platform-bundle: 'false'
setup-kotlin: 'true'
- name: Remove CodeQL from toolcache
run: |
cd ./../action
npx tsx ./pr-checks/toolcache.ts clear
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
- name: Check toolcache does not contain CodeQL
run: |
cd ./../action
npx tsx ./pr-checks/toolcache.ts absent
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');
- 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
run: |
cd ./../action
npx tsx ./pr-checks/toolcache.ts installed -n 1
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');
}
env:
CODEQL_ACTION_TEST_MODE: true
-143
View File
@@ -1,143 +0,0 @@
# Warning: This file is generated automatically, and should not be modified.
# Instead, please modify the template in the pr-checks directory and run:
# pr-checks/sync.sh
# to regenerate this file.
name: PR Check - Diagnostic export without database
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GO111MODULE: auto
on:
push:
branches:
- main
- releases/v*
pull_request: {}
merge_group:
types:
- checks_requested
schedule:
- cron: '0 5 * * *'
workflow_dispatch:
inputs: {}
workflow_call:
inputs: {}
defaults:
run:
shell: bash
concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' || false }}
group: diagnostics-without-db-${{github.ref}}
jobs:
diagnostics-without-db:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
version: linked
name: Diagnostic export without database
if: github.triggering_actor != 'dependabot[bot]'
permissions:
contents: read
security-events: read
timeout-minutes: 45
runs-on: ${{ matrix.os }}
steps:
# We deliberately need to make this step fail before the database is initialised, but after
# some diagnostic has been produced. The simplest way to do that right now is to:
#
# - Produce a diagnostic before the db is initialised by forcing the use of a nightly release
# with the `CODEQL_ACTION_FORCE_NIGHTLY` feature flag.
# - Cause `init` to abort because we are attempting to analyse Swift on a non-macOS runner.
#
- 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
with:
version: ${{ matrix.version }}
use-all-platform-bundle: 'false'
setup-kotlin: 'true'
- name: Initialise failing analysis
id: init
uses: ./../action/init
continue-on-error: true
env:
CODEQL_ACTION_FORCE_NIGHTLY: true
with:
languages: swift
tools: ${{ steps.prepare-test.outputs.tools-url }}
- name: Analyze
uses: ./analyze
# In a real workflow, this step wouldn't run. Since we used `continue-on-error`
# above, we manually disable it with an `if` condition. Having this step is required
# for the init post-action because it checks the workflow definition for e.g. the
# `category` input. Without this step, that part of the init post-action would fail.
if: false
with:
category: '/test-codeql-version:${{ matrix.version }}'
- name: Check diagnostics appear on disk
env:
DIAGNOSTICS_DIR: '${{ runner.temp }}/codeql-action-temp-diagnostics/'
run: |
cd ../action
npx tsx ./pr-checks/diagnostics-without-db.ts
env:
CODEQL_ACTION_SKIP_GENERATE_FAILED_SARIF: false
CODEQL_ACTION_SKIP_SARIF_UPLOAD: true
CODEQL_ACTION_TESTING_ENVIRONMENT: codeql-action-pr-checks
CODEQL_ACTION_TEST_MODE: true
diagnostics-without-db-validation:
if: github.triggering_actor != 'dependabot[bot]'
needs:
- diagnostics-without-db
permissions:
contents: read
security-events: read
timeout-minutes: 5
runs-on: ubuntu-latest
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: Download workflow artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: test-mode-sarif-*
path: sarif
- name: List artifact contents
run: ls -laR ./sarif
- name: Check diagnostics appear in SARIF
env:
SARIF_PATH: "./sarif/codeql-failed-run.sarif"
DIAGNOSTIC_ID: "codeql-action/forced-nightly-cli"
DIAGNOSTIC_NAME: "A nightly release of CodeQL was used"
EXPECTED_LOCATIONS: "0"
EXPECTED_MESSAGE: |
GitHub configured this analysis to use a nightly release of CodeQL to allow you to preview changes from an upcoming release.
run: npx tsx ./pr-checks/check-diagnostics.ts
env:
CODEQL_ACTION_TEST_MODE: true
+1 -5
View File
@@ -91,11 +91,6 @@ jobs:
with:
languages: javascript
tools: ${{ steps.prepare-test.outputs.tools-url }}
# To balance speed and coverage, analyze only JavaScript but use the combined bundle so
# baseline information is reported for every language in the multi-language repository. A
# custom configuration prevents per-language bundles from being used.
config: |
paths-ignore: []
- name: Build code
run: ./build.sh
- uses: ./../action/analyze
@@ -129,4 +124,5 @@ jobs:
env:
CODEQL_ACTION_SKIP_FILE_COVERAGE_ON_PRS: false
CODEQL_ACTION_SUBLANGUAGE_FILE_COVERAGE: true
CODEQL_ACTION_PER_LANGUAGE_BUNDLES: false
CODEQL_ACTION_TEST_MODE: true
+14 -25
View File
@@ -36,40 +36,40 @@ jobs:
include:
- language: actions
os: ubuntu-latest
version: linked
version: nightly-latest
expected-extractors: actions javascript
- language: cpp
os: ubuntu-latest
version: linked
version: nightly-latest
build-mode: manual
build-command: gcc -o main main.c
- language: csharp
os: ubuntu-latest
version: linked
version: nightly-latest
build-mode: none
- language: go
os: ubuntu-latest
version: linked
version: nightly-latest
build-mode: autobuild
- language: java
os: ubuntu-latest
version: linked
version: nightly-latest
build-mode: none
- language: javascript
os: ubuntu-latest
version: linked
version: nightly-latest
- language: python
os: ubuntu-latest
version: linked
version: nightly-latest
- language: ruby
os: ubuntu-latest
version: linked
version: nightly-latest
- language: rust
os: ubuntu-latest
version: linked
version: nightly-latest
- language: swift
os: macos-latest-xlarge
version: linked
version: nightly-latest
build-mode: autobuild
name: Per-language bundles
if: github.triggering_actor != 'dependabot[bot]'
@@ -81,15 +81,6 @@ 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
@@ -97,10 +88,6 @@ jobs:
version: ${{ matrix.version }}
use-all-platform-bundle: 'false'
setup-kotlin: 'true'
- name: Remove CodeQL from toolcache
run: |
cd ./../action
npx tsx ./pr-checks/toolcache.ts clear
- uses: ./../action/init
id: init
with:
@@ -141,8 +128,9 @@ jobs:
CODEQL_PATH: ${{ steps.init.outputs.codeql-path }}
run: |
# A bundle that is missing most of its extractors must never be left in the toolcache,
# where a later job analyzing a different language could pick it up. Per-language bundles
# must instead be extracted under the runner's temporary directory.
# where a later job analyzing a different language could pick it up. The runner image
# ships with its own CodeQL in the toolcache, so check where this bundle was extracted to
# rather than whether the toolcache contains CodeQL at all.
echo "CodeQL is at $CODEQL_PATH"
if [[ "$CODEQL_PATH" == "$RUNNER_TOOL_CACHE"/* ]]; then
echo "::error::The per-language bundle was added to the toolcache at $CODEQL_PATH."
@@ -172,4 +160,5 @@ jobs:
fi
echo "Created a ${LANGUAGE} database at ${database}."
env:
CODEQL_ACTION_PER_LANGUAGE_BUNDLES: true
CODEQL_ACTION_TEST_MODE: true
+1 -1
View File
@@ -54,7 +54,7 @@ jobs:
use-all-platform-bundle: 'false'
setup-kotlin: 'true'
- name: Set up Ruby
uses: ruby/setup-ruby@651ff0d62c5c79bed6d53ec518387aae20e72c1a # v1.329.0
uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
with:
ruby-version: 2.6
- name: Install Code Scanning integration
+1 -3
View File
@@ -71,9 +71,7 @@ jobs:
run: exit 1
- uses: ./analyze
# In a real workflow, this step wouldn't run. Since we used `continue-on-error`
# above, we manually disable it with an `if` condition. Having this step is required
# for the init post-action because it checks the workflow definition for e.g. the
# `category` input. Without this step, that part of the init post-action would fail.
# above, we manually disable it with an `if` condition.
if: false
with:
category: '/test-codeql-version:${{ matrix.version }}'
+1 -1
View File
@@ -134,7 +134,7 @@ jobs:
echo "::endgroup::"
- name: Generate token
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
id: app-token
with:
app-id: ${{ vars.AUTOMATION_APP_ID }}
-28
View File
@@ -1,28 +0,0 @@
name: PR dequeued
on:
pull_request:
types:
- dequeued
jobs:
notify:
name: Notify
runs-on: ubuntu-slim
if: github.event.pull_request.merged != true
permissions:
pull-requests: write
steps:
- name: Notify that the PR was removed from the merge queue
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REASON: ${{ github.event.reason }}
USER: ${{ github.event.pull_request.user.login }}
run: |
echo "@$USER The pull request was removed from the merge queue (\`$REASON\`)." \
"Please resolve any issues if required and enqueue the PR again." |
gh pr comment --body-file - --repo github/codeql-action "$PR_NUMBER"
+1 -1
View File
@@ -139,7 +139,7 @@ jobs:
- name: Generate token
if: github.event_name == 'workflow_dispatch'
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
id: app-token
with:
app-id: ${{ vars.AUTOMATION_APP_ID }}
+1 -1
View File
@@ -95,7 +95,7 @@ jobs:
pull-requests: write # needed to create pull request
steps:
- name: Generate token
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
id: app-token
with:
app-id: ${{ vars.AUTOMATION_APP_ID }}
-4
View File
@@ -4,10 +4,6 @@ See the [releases page](https://github.com/github/codeql-action/releases) for th
## [UNRELEASED]
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
+769 -718
View File
File diff suppressed because it is too large. Load diff
+72 -73
View File
@@ -1,12 +1,12 @@
{
"name": "codeql",
"version": "4.39.2",
"version": "4.39.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "codeql",
"version": "4.39.2",
"version": "4.39.1",
"license": "MIT",
"workspaces": [
"pr-checks"
@@ -36,7 +36,7 @@
"node-forge": "^1.4.0",
"semver": "^7.8.5",
"undici": "^6.28.0",
"uuid": "^14.0.3"
"uuid": "^14.0.2"
},
"devDependencies": {
"@ava/typescript": "6.0.0",
@@ -57,14 +57,14 @@
"eslint-import-resolver-typescript": "^4.4.5",
"eslint-plugin-github": "^6.1.2",
"eslint-plugin-import-x": "^4.17.1",
"eslint-plugin-jsdoc": "^65.3.4",
"eslint-plugin-jsdoc": "^65.2.0",
"eslint-plugin-no-async-foreach": "^0.1.1",
"glob": "^13.0.6",
"globals": "^17.13.0",
"nock": "^14.0.17",
"sinon": "^22.1.0",
"typescript": "^6.0.3",
"typescript-eslint": "^8.71.2"
"typescript-eslint": "^8.71.1"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@@ -2702,17 +2702,17 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.71.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.71.2.tgz",
"integrity": "sha512-K72/pvT7qpm8q3k8G5u93NCSN1RcLxX5h7Gw0bQpbMXviBHeY6Nx1WEwmk9wP+eKTSQ8ecxvcgt/ZX/rB938xQ==",
"version": "8.71.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.71.1.tgz",
"integrity": "sha512-nNlBf6HuotqMgZvSkWD5hNdemiBIxQ2NmrlgfcUIeZRs+BbakjscCQRq/Hb74EFVnFiSWWdL12JruqwBy3ERDw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.12.2",
"@typescript-eslint/scope-manager": "8.71.2",
"@typescript-eslint/type-utils": "8.71.2",
"@typescript-eslint/utils": "8.71.2",
"@typescript-eslint/visitor-keys": "8.71.2",
"@typescript-eslint/scope-manager": "8.71.1",
"@typescript-eslint/type-utils": "8.71.1",
"@typescript-eslint/utils": "8.71.1",
"@typescript-eslint/visitor-keys": "8.71.1",
"ignore": "^7.0.5",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.5.0"
@@ -2725,7 +2725,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.71.2",
"@typescript-eslint/parser": "^8.71.1",
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
@@ -2741,16 +2741,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.71.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.71.2.tgz",
"integrity": "sha512-aTtmZjp/x3gsGHkDs04/1I8kNogBJkpQaeGaN0PfiG43NzDEmW2n+OP5im9wfsvyX0yP1UZWBrAGtDBfH9jEVw==",
"version": "8.71.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.71.1.tgz",
"integrity": "sha512-oM/5sAqz/l1v/zYK1qTOPrn2+JIrBGC1V6uNYucRO5gh6eT9vWJ/RhPbGE59byvZEUQ4VP6XkLBaS5Jti5jpvw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.71.2",
"@typescript-eslint/types": "8.71.2",
"@typescript-eslint/typescript-estree": "8.71.2",
"@typescript-eslint/visitor-keys": "8.71.2",
"@typescript-eslint/scope-manager": "8.71.1",
"@typescript-eslint/types": "8.71.1",
"@typescript-eslint/typescript-estree": "8.71.1",
"@typescript-eslint/visitor-keys": "8.71.1",
"debug": "^4.4.3"
},
"engines": {
@@ -2784,14 +2784,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.71.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.71.2.tgz",
"integrity": "sha512-Pd68GsLVh5ZXlcEccFPdhSOvtHZ/2WU0XsA8zmXOS3F158lVlcTLhfQ6qvtL1RzbMie0Kp8xtbVwAg4nejKPHw==",
"version": "8.71.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.71.1.tgz",
"integrity": "sha512-Oijc9RUsohHjncg6iGi7gCtjnEjYt9FWv9u6ygK8uKcHK5eLjLAkqERQ9BaytbpceOFdk6kifPCS/zAZE+A99g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.71.2",
"@typescript-eslint/types": "^8.71.2",
"@typescript-eslint/tsconfig-utils": "^8.71.1",
"@typescript-eslint/types": "^8.71.1",
"debug": "^4.4.3"
},
"engines": {
@@ -2824,14 +2824,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.71.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.71.2.tgz",
"integrity": "sha512-YF4wgg5iUX5Obywoac27B56FhozMNFhdJSbh11H5GBAwmBtfyRhmgTQqW9tRNNmHDvsZ2CX6UGzoz+YfYn2Leg==",
"version": "8.71.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.71.1.tgz",
"integrity": "sha512-1YUDZXdTnXLlob03SFHBipdyMBsDVm7zoSz0ytTJ39dk5J1TgsTK03VlR/dLXG/RLLUrPcYbD4rp23BmHxHP0w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.71.2",
"@typescript-eslint/visitor-keys": "8.71.2"
"@typescript-eslint/types": "8.71.1",
"@typescript-eslint/visitor-keys": "8.71.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2842,9 +2842,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.71.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.71.2.tgz",
"integrity": "sha512-jz49TAWokm/7yWD9/o837nYax9DyALxIPOImdImvVzo6canBCZYzin8zJyqNR5k1j9K1DE0bVvr0XYB+99uAdg==",
"version": "8.71.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.71.1.tgz",
"integrity": "sha512-oTvkml5SxXhgg+WWKhod5qWQICDXtOB+/Hi9VdPKFAANJ2b7YOgMffgvzXuqog7R6A/JtrzRf128dsxummyWhg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2859,15 +2859,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.71.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.71.2.tgz",
"integrity": "sha512-cC8UaalCViJkb3rH/xLIvidEwoFRtMZMUqDjjJMGEKaTFwYRHYBcoczlEGHuvkHEO8svRDrgZ0yQgOArFmwang==",
"version": "8.71.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.71.1.tgz",
"integrity": "sha512-kH3t3jZCYv2PZSBEWPKTariLrCaoF1CLoPitYFBJOhAGEwWnSRAtGEifrfrpBa4K9JlwWNZotgA5w3/wLSk/FQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.71.2",
"@typescript-eslint/typescript-estree": "8.71.2",
"@typescript-eslint/utils": "8.71.2",
"@typescript-eslint/types": "8.71.1",
"@typescript-eslint/typescript-estree": "8.71.1",
"@typescript-eslint/utils": "8.71.1",
"debug": "^4.4.3",
"ts-api-utils": "^2.5.0"
},
@@ -2902,9 +2902,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.71.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.71.2.tgz",
"integrity": "sha512-y5Ukl3oVp7e/VGxqIoEXoeb/Roj71PAJ4WG/N/RGj+eeDvKRLM15kGr9MijLbN0i03S52zxu31Z98E5gWChJKw==",
"version": "8.71.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.71.1.tgz",
"integrity": "sha512-ybYnPTUwg3VQQOFEiBPLs8Jxl1ry2vXdn1kVKguomBbvxpetdu3MKTNDqFNUrlG8ylP9MpZosmXMFYlGQHk1oQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2916,16 +2916,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.71.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.71.2.tgz",
"integrity": "sha512-DgNOEsl4pdYzwSL+N/Cl33tX1Hlw3FvId73LnKXfq7jnrF2viOEn7X+P7RBkkSWCVm92gnWd28GIC5vJuLREeA==",
"version": "8.71.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.71.1.tgz",
"integrity": "sha512-pSKSEK1JJpHs6KiKVtABCE4iJK5d7FLFzDXQNBQ2oRY7LEd3UwOt3TZemTgu9syTIf56dJWlTn/pdmrO9oKUXQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.71.2",
"@typescript-eslint/tsconfig-utils": "8.71.2",
"@typescript-eslint/types": "8.71.2",
"@typescript-eslint/visitor-keys": "8.71.2",
"@typescript-eslint/project-service": "8.71.1",
"@typescript-eslint/tsconfig-utils": "8.71.1",
"@typescript-eslint/types": "8.71.1",
"@typescript-eslint/visitor-keys": "8.71.1",
"debug": "^4.4.3",
"minimatch": "^10.2.2",
"semver": "^7.7.3",
@@ -3001,16 +3001,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.71.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.71.2.tgz",
"integrity": "sha512-RyKa1G1M9rKHDhrk+HJtiF1MvLD5Ux2JW3TSkzw+02pRALdpJb+QzG1Y5em+LvCgzGwgg8H7mBRFKljNz7ImPw==",
"version": "8.71.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.71.1.tgz",
"integrity": "sha512-CBjT6gfAz3DW2j9Q4moFnqt208Aq+506sVGXfpRcyAeqn0EpayL0mjdWoCO2t3RC7GewNrFxdeehuEaPg6DMzQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
"@typescript-eslint/scope-manager": "8.71.2",
"@typescript-eslint/types": "8.71.2",
"@typescript-eslint/typescript-estree": "8.71.2"
"@typescript-eslint/scope-manager": "8.71.1",
"@typescript-eslint/types": "8.71.1",
"@typescript-eslint/typescript-estree": "8.71.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3025,13 +3025,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.71.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.71.2.tgz",
"integrity": "sha512-zozlN0UA32bSG2zkKAgQyRYSbEISVoyU6vvkKYo2aSny74D8Jh4e+POkJ6Lk0dlezZi5x7TtD+88Xb06IB8nhg==",
"version": "8.71.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.71.1.tgz",
"integrity": "sha512-0GxiUGqMU0qXJt58cBsiG3DP8lUmBaFo2vChtUX+CT7PnR3v/VGLLrzVP4K9r1lNiZXjHYefMBbPunplr09v6w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.71.2",
"@typescript-eslint/types": "8.71.1",
"eslint-visitor-keys": "^5.0.0"
},
"engines": {
@@ -5365,9 +5365,9 @@
}
},
"node_modules/eslint-plugin-jsdoc": {
"version": "65.3.4",
"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-65.3.4.tgz",
"integrity": "sha512-Z3/ro8sZPqnXKhyAzCNa3F7KCZ3js5xVx/q9BEvTjUGzN6OIQfdb+q3Sc9lJ3zCfyPnN0tI524ulZjYRxAwnAw==",
"version": "65.2.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-65.2.0.tgz",
"integrity": "sha512-IMZaNwdDa5otUQYGWN1pzywZLsGKswitvFFNhcO8IOsBfaWpWIDXYuZh2sYhe9MNyKN9hz0YryOFaKdpbTP5Bw==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -9981,16 +9981,16 @@
}
},
"node_modules/typescript-eslint": {
"version": "8.71.2",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.71.2.tgz",
"integrity": "sha512-Rz4effbMy2g9wnvOJZG6SbVavX313Lap1vNEbk1RUochzickvzP8P4HB+bkNg5e50eVz1hQGjh9K1omI1xB4Bw==",
"version": "8.71.1",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.71.1.tgz",
"integrity": "sha512-oiNVPC3/NbV1FpaHU07l8O6ynqG9v4PAiW2WvTrAmZdR0f2HI/gE668ElMLCK7Xbsu5k2fK5C4fXNiq05j7/nQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.71.2",
"@typescript-eslint/parser": "8.71.2",
"@typescript-eslint/typescript-estree": "8.71.2",
"@typescript-eslint/utils": "8.71.2"
"@typescript-eslint/eslint-plugin": "8.71.1",
"@typescript-eslint/parser": "8.71.1",
"@typescript-eslint/typescript-estree": "8.71.1",
"@typescript-eslint/utils": "8.71.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -10167,9 +10167,9 @@
"license": "MIT"
},
"node_modules/uuid": {
"version": "14.0.3",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.3.tgz",
"integrity": "sha512-Sc5u1VVln93y1Xnz4R+CunOZc7R9LUcTmhj7Rc7Eg3HVcf7rVn2FOoa812fe0W8p3jD7xeoGSYbfqCGGH2B6xw==",
"version": "14.0.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.2.tgz",
"integrity": "sha512-xZe/16rV4aa+HGSOCiY2YeLT1OybRLrrkL/Rqaq7p7GMVXjFh+6wN4oMYgjFmnSnhY8t6Xpdl2l9qmnHYuMHwQ==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
@@ -10484,7 +10484,6 @@
"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",
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "codeql",
"version": "4.39.2",
"version": "4.39.1",
"private": true,
"description": "CodeQL action",
"scripts": {
@@ -44,7 +44,7 @@
"node-forge": "^1.4.0",
"semver": "^7.8.5",
"undici": "^6.28.0",
"uuid": "^14.0.3"
"uuid": "^14.0.2"
},
"devDependencies": {
"@ava/typescript": "6.0.0",
@@ -65,14 +65,14 @@
"eslint-import-resolver-typescript": "^4.4.5",
"eslint-plugin-github": "^6.1.2",
"eslint-plugin-import-x": "^4.17.1",
"eslint-plugin-jsdoc": "^65.3.4",
"eslint-plugin-jsdoc": "^65.2.0",
"eslint-plugin-no-async-foreach": "^0.1.1",
"glob": "^13.0.6",
"globals": "^17.13.0",
"nock": "^14.0.17",
"sinon": "^22.1.0",
"typescript": "^6.0.3",
"typescript-eslint": "^8.71.2"
"typescript-eslint": "^8.71.1"
},
"overrides": {
"@actions/tool-cache": {
+14 -29
View File
@@ -4,32 +4,16 @@ import * as core from "@actions/core";
import * as sarif from "../src/sarif";
const DIAGNOSTIC_ID =
process.env["DIAGNOSTIC_ID"] ?? "lang/diagnostics/example";
const DIAGNOSTIC_NAME = process.env["DIAGNOSTIC_NAME"] ?? "Diagnostic name";
const EXPECTED_MESSAGE = process.env["EXPECTED_MESSAGE"] ?? "Plaintext message";
const EXPECTED_LOCATIONS = Number.parseInt(
process.env["EXPECTED_LOCATIONS"] ?? "1",
);
function checkStatusPageNotification(n: sarif.Notification) {
const expectedMessage = EXPECTED_MESSAGE.trim();
if (!n.message.text?.includes(expectedMessage)) {
if (n.message.text !== undefined) {
core.setFailed(
`Expected the status page diagnostic to contain the message '${expectedMessage}', but found '${n.message.text.replaceAll("\n", "\\n")}'.`,
);
} else {
core.setFailed(
`Expected the status page diagnostic to contain the message '${expectedMessage}', but found nothing.`,
);
}
}
const locationCount = n.locations?.length ?? 0;
if (locationCount !== EXPECTED_LOCATIONS) {
function checkStatusPageNotification(n) {
const expectedMessage = "Plaintext message";
if (n.message.text !== expectedMessage) {
core.setFailed(
`Expected the status page diagnostic to have exactly ${EXPECTED_LOCATIONS} location(s), but found ${locationCount}.`,
`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}.`,
);
}
}
@@ -56,7 +40,8 @@ if (toolExecutionNotifications === undefined) {
const statusPageNotifications = toolExecutionNotifications.filter(
(n) =>
n.descriptor?.id === DIAGNOSTIC_ID && n.properties?.visibility?.statusPage,
n.descriptor?.id === "lang/diagnostics/example" &&
n.properties?.visibility?.statusPage,
);
if (statusPageNotifications.length !== 1) {
core.setFailed(
@@ -76,9 +61,9 @@ if (notifications === undefined) {
const diagnosticNotification = notifications.filter(
(n) =>
n.id === DIAGNOSTIC_ID &&
n.name === DIAGNOSTIC_ID &&
n.fullDescription?.text === DIAGNOSTIC_NAME,
n.id === "lang/diagnostics/example" &&
n.name === "lang/diagnostics/example" &&
n.fullDescription?.text === "Diagnostic name",
);
if (diagnosticNotification.length !== 1) {
core.setFailed(
+19 -9
View File
@@ -2,20 +2,30 @@ 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
run: |
cd ./../action
npx tsx ./pr-checks/toolcache.ts installed
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`);
}
- id: setup-codeql
uses: ./../action/setup-codeql
with:
tools: ${{ steps.prepare-test.outputs.tools-url }}
- name: Check CodeQL is installed within the toolcache
run: |
cd ./../action
npx tsx ./pr-checks/toolcache.ts installed
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');
}
+30 -14
View File
@@ -6,31 +6,47 @@ operatingSystems:
- ubuntu
- macos
- windows
installNode: true
steps:
- name: Remove CodeQL from toolcache
run: |
cd ./../action
npx tsx ./pr-checks/toolcache.ts clear
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
- name: Check toolcache does not contain CodeQL
run: |
cd ./../action
npx tsx ./pr-checks/toolcache.ts absent
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');
- 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
run: |
cd ./../action
npx tsx ./pr-checks/toolcache.ts installed -n 1
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');
}
@@ -1,72 +0,0 @@
name: "Diagnostic export without database"
description: "Tests that temporary diagnostics are correctly included in the failed SARIF."
versions:
# Overridden by the FF in the `init` step below.
- linked
installNode: true
env:
# Allow the failed SARIF file to be generated.
CODEQL_ACTION_SKIP_GENERATE_FAILED_SARIF: false
# But don't actually upload it.
CODEQL_ACTION_SKIP_SARIF_UPLOAD: true
# Mark telemetry for this workflow so it can be treated separately.
CODEQL_ACTION_TESTING_ENVIRONMENT: codeql-action-pr-checks
steps:
# We deliberately need to make this step fail before the database is initialised, but after
# some diagnostic has been produced. The simplest way to do that right now is to:
#
# - Produce a diagnostic before the db is initialised by forcing the use of a nightly release
# with the `CODEQL_ACTION_FORCE_NIGHTLY` feature flag.
# - Cause `init` to abort because we are attempting to analyse Swift on a non-macOS runner.
#
- name: Initialise failing analysis
id: init
uses: ./../action/init
continue-on-error: true
env:
CODEQL_ACTION_FORCE_NIGHTLY: true
with:
languages: swift
tools: ${{ steps.prepare-test.outputs.tools-url }}
- name: Analyze
uses: ./analyze
# In a real workflow, this step wouldn't run. Since we used `continue-on-error`
# above, we manually disable it with an `if` condition. Having this step is required
# for the init post-action because it checks the workflow definition for e.g. the
# `category` input. Without this step, that part of the init post-action would fail.
if: false
with:
category: "/test-codeql-version:${{ matrix.version }}"
- name: Check diagnostics appear on disk
env:
DIAGNOSTICS_DIR: "${{ runner.temp }}/codeql-action-temp-diagnostics/"
run: |
cd ../action
npx tsx ./pr-checks/diagnostics-without-db.ts
validationJobs:
diagnostics-without-db-validation:
checkout: true
installNode: true
steps:
- name: Download workflow artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: test-mode-sarif-*
path: sarif
- name: List artifact contents
run: ls -laR ./sarif
- name: Check diagnostics appear in SARIF
env:
SARIF_PATH: "./sarif/codeql-failed-run.sarif"
DIAGNOSTIC_ID: "codeql-action/forced-nightly-cli"
DIAGNOSTIC_NAME: "A nightly release of CodeQL was used"
EXPECTED_LOCATIONS: "0"
EXPECTED_MESSAGE: |
GitHub configured this analysis to use a nightly release of CodeQL to allow you to preview changes from an upcoming release.
run: npx tsx ./pr-checks/check-diagnostics.ts
@@ -11,17 +11,16 @@ installDotNet: true
env:
CODEQL_ACTION_SKIP_FILE_COVERAGE_ON_PRS: false
CODEQL_ACTION_SUBLANGUAGE_FILE_COVERAGE: true
# To balance speed and coverage, we analyze only a single language (JavaScript), but use the
# combined bundle so we can test that baseline information is reported for each language in the
# multi-language source directory.
CODEQL_ACTION_PER_LANGUAGE_BUNDLES: false
steps:
- uses: ./../action/init
id: init
with:
languages: javascript
tools: ${{ steps.prepare-test.outputs.tools-url }}
# To balance speed and coverage, analyze only JavaScript but use the combined bundle so
# baseline information is reported for every language in the multi-language repository. A
# custom configuration prevents per-language bundles from being used.
config: |
paths-ignore: []
- name: Build code
run: ./build.sh
- uses: ./../action/analyze
@@ -1,51 +1,49 @@
name: Per-language bundles
description: Validates extraction and analysis using each per-language CodeQL bundle.
# TODO: Use a released bundle once releases include per-language bundles.
matrix:
include:
- language: actions
os: ubuntu-latest
version: linked
version: nightly-latest
# Actions also needs the JavaScript extractor.
expected-extractors: actions javascript
- language: cpp
os: ubuntu-latest
version: linked
version: nightly-latest
build-mode: manual
build-command: gcc -o main main.c
- language: csharp
os: ubuntu-latest
version: linked
version: nightly-latest
build-mode: none
- language: go
os: ubuntu-latest
version: linked
version: nightly-latest
build-mode: autobuild
- language: java
os: ubuntu-latest
version: linked
version: nightly-latest
build-mode: none
- language: javascript
os: ubuntu-latest
version: linked
version: nightly-latest
- language: python
os: ubuntu-latest
version: linked
version: nightly-latest
- language: ruby
os: ubuntu-latest
version: linked
version: nightly-latest
- language: rust
os: ubuntu-latest
version: linked
version: nightly-latest
- language: swift
os: macos-latest-xlarge
version: linked
version: nightly-latest
build-mode: autobuild
installNode: true
env:
CODEQL_ACTION_PER_LANGUAGE_BUNDLES: true
steps:
- name: Remove CodeQL from toolcache
run: |
cd ./../action
npx tsx ./pr-checks/toolcache.ts clear
- uses: ./../action/init
id: init
with:
@@ -86,8 +84,9 @@ steps:
CODEQL_PATH: ${{ steps.init.outputs.codeql-path }}
run: |
# A bundle that is missing most of its extractors must never be left in the toolcache,
# where a later job analyzing a different language could pick it up. Per-language bundles
# must instead be extracted under the runner's temporary directory.
# where a later job analyzing a different language could pick it up. The runner image
# ships with its own CodeQL in the toolcache, so check where this bundle was extracted to
# rather than whether the toolcache contains CodeQL at all.
echo "CodeQL is at $CODEQL_PATH"
if [[ "$CODEQL_PATH" == "$RUNNER_TOOL_CACHE"/* ]]; then
echo "::error::The per-language bundle was added to the toolcache at $CODEQL_PATH."
+1 -1
View File
@@ -5,7 +5,7 @@ versions:
- default
steps:
- name: Set up Ruby
uses: ruby/setup-ruby@651ff0d62c5c79bed6d53ec518387aae20e72c1a # v1.329.0
uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
with:
ruby-version: 2.6
- name: Install Code Scanning integration
+1 -3
View File
@@ -35,9 +35,7 @@ steps:
run: exit 1
- uses: ./analyze
# In a real workflow, this step wouldn't run. Since we used `continue-on-error`
# above, we manually disable it with an `if` condition. Having this step is required
# for the init post-action because it checks the workflow definition for e.g. the
# `category` input. Without this step, that part of the init post-action would fail.
# above, we manually disable it with an `if` condition.
if: false
with:
category: "/test-codeql-version:${{ matrix.version }}"
-48
View File
@@ -1,48 +0,0 @@
import * as fs from "fs";
import * as path from "path";
import * as core from "@actions/core";
import type * as diagnostics from "../src/diagnostics";
const DIAGNOSTIC_ID = "codeql-action/forced-nightly-cli";
const diagnosticDirectory = process.env["DIAGNOSTICS_DIR"];
if (diagnosticDirectory === undefined) {
throw new Error("DIAGNOSTICS_DIR is undefined.");
}
const paths = fs
.readdirSync(diagnosticDirectory, {
recursive: true,
encoding: "utf-8",
})
.filter((p) => p.endsWith(".json"));
core.info(`Found ${paths.length} paths in '${diagnosticDirectory}'.`);
core.info(`Searching for a diagnostic with id '${DIAGNOSTIC_ID}'...`);
let found = false;
for (const diagnosticPath of paths) {
if (path.extname(diagnosticPath) !== ".json") {
core.info(`Skipping ${diagnosticPath}.`);
continue;
}
core.info(`Found ${diagnosticPath}.`);
const contents = JSON.parse(
fs.readFileSync(path.resolve(diagnosticDirectory, diagnosticPath), "utf-8"),
) as diagnostics.DiagnosticMessage;
if (contents.source.id === DIAGNOSTIC_ID) {
core.info(
`Found '${DIAGNOSTIC_ID}' in '${diagnosticPath}'. Skipping remaining files.`,
);
found = true;
break;
}
}
if (!found) {
throw new Error(`Didn't find '${DIAGNOSTIC_ID}' diagnostic.`);
}
-4
View File
@@ -20,7 +20,3 @@ is:
- "Release info"
- "Upload results"
- "Update release branch"
# These checks are known not to run on every PR (for example, some release
# PRs), so requiring them would block the PRs they don't run on.
- "github-advanced-security"
- "License compliance check"
-1
View File
@@ -5,7 +5,6 @@
"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",
+14 -28
View File
@@ -90,7 +90,7 @@ interface Specification extends JobSpecification {
services?: any;
/** Additional jobs to run after the main PR check job. */
validationJobs?: Record<string, ValidationJobSpecification>;
validationJobs?: Record<string, JobSpecification>;
/** If set, this check is part of a named collection that gets its own caller workflow. */
collection?: string;
@@ -122,10 +122,6 @@ interface JobSpecification {
installYq?: boolean;
}
type ValidationJobSpecification = JobSpecification & {
checkout?: boolean;
};
/** Describes language/framework-specific steps and inputs. */
interface LanguageSetup {
specProperty: keyof JobSpecification;
@@ -267,8 +263,8 @@ const languageSetups: LanguageSetups = {
name: "Install Java",
uses: pinnedUses(
"actions/setup-java",
"b843ebf9b913146b38da2eaa7aeddf76074ae1ad",
"v6.1.0",
"de7274f081f381c8f8158605e0321c36c376e2e6",
"v6.0.1",
),
with: {
"java-version": `\${{ inputs.java-version || '${defaultLanguageVersions.java}' }}`,
@@ -466,20 +462,6 @@ function generateJobMatrix(
return matrix;
}
/**
* Gets the `actions/checkout` step.
*/
function getCheckoutStep(): Step {
return {
name: "Check out repository",
uses: pinnedUses(
"actions/checkout",
"3d3c42e5aac5ba805825da76410c181273ba90b1",
"v7.0.1",
),
};
}
/**
* Retrieves setup steps and additional input definitions based on specific languages or frameworks
* that are requested by the `checkSpecification`.
@@ -549,7 +531,14 @@ function generateJob(
// Construct the workflow steps needed for this check.
const steps: Step[] = [
getCheckoutStep(),
{
name: "Check out repository",
uses: pinnedUses(
"actions/checkout",
"3d3c42e5aac5ba805825da76410c181273ba90b1",
"v7.0.1",
),
},
...setupInfo.steps,
{
name: "Prepare test",
@@ -620,16 +609,13 @@ function generateJob(
/** Generates a validation job. */
function generateValidationJob(
specDocument: yaml.Document,
jobSpecification: ValidationJobSpecification,
jobSpecification: JobSpecification,
checkName: string,
name: string,
) {
// Determine which languages or frameworks have to be installed.
const { inputs, steps } = getSetupSteps(jobSpecification);
// Construct a checkout step if requested.
const checkoutStep = jobSpecification.checkout ? [getCheckoutStep()] : [];
// Extract the sequence of steps from the YAML document to persist as much formatting as possible.
const specSteps = specDocument.getIn([
"validationJobs",
@@ -638,7 +624,7 @@ function generateValidationJob(
]) as yaml.YAMLSeq;
// Add the generated steps in front of the ones from the specification.
specSteps.items.unshift(...checkoutStep, ...steps);
specSteps.items.unshift(...steps);
const validationJob: Record<string, any> = {
name: jobSpecification.name,
@@ -649,7 +635,7 @@ function generateValidationJob(
"security-events": "read",
},
"timeout-minutes": 5,
"runs-on": "ubuntu-latest",
"runs-on": "ubuntu-slim",
steps: specSteps,
};
-94
View File
@@ -1,94 +0,0 @@
#!/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));
}
+7 -5
View File
@@ -11,8 +11,9 @@ import {
} from "./actions-util";
import { computeAutomationID } from "./api-client";
import { EnvVar } from "./environment";
import { RecordingLogger, setupTests } from "./testing-utils";
import { initializeEnvironment, withTmpDir } from "./util";
import { getRunnerLogger } from "./logging";
import { getTestEnv, RecordingLogger, setupTests } from "./testing-utils";
import { withTmpDir } from "./util";
setupTests(test);
@@ -227,8 +228,9 @@ test.serial(
);
test.serial("initializeEnvironment", (t) => {
initializeEnvironment("1.2.3");
t.deepEqual(process.env[EnvVar.VERSION], "1.2.3");
const env = getTestEnv();
env.initialise("1.2.3");
t.deepEqual(env.getOptional(EnvVar.VERSION), "1.2.3");
});
test.serial("fixCodeQualityCategory", (t) => {
@@ -237,7 +239,7 @@ test.serial("fixCodeQualityCategory", (t) => {
GITHUB_EVENT_NAME: "dynamic",
},
() => {
const logger = new RecordingLogger();
const logger = getRunnerLogger(true);
// Categories that should get adjusted.
t.is(fixCodeQualityCategory(logger, "/language:c#"), "/language:csharp");
+6 -5
View File
@@ -15,6 +15,7 @@ import {
supportedAnalysisKinds,
} from "./analyses";
import { EnvVar } from "./environment";
import { getRunnerLogger } from "./logging";
import {
createFeatures,
RecordingLogger,
@@ -64,7 +65,7 @@ test.serial(
.withArgs("analysis-kinds")
.returns("code-scanning,code-quality");
const result = await getAnalysisKinds(
new RecordingLogger(),
getRunnerLogger(true),
features,
true,
);
@@ -138,7 +139,7 @@ test.serial(
const optionalInputStub = sinon.stub(actionsUtil, "getOptionalInput");
optionalInputStub.withArgs("quality-queries").returns("code-quality");
const result = await getAnalysisKinds(
new RecordingLogger(),
getRunnerLogger(true),
features,
true,
);
@@ -154,7 +155,7 @@ test.serial(
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
requiredInputStub.withArgs("analysis-kinds").returns("no-such-thing");
await t.throwsAsync(
getAnalysisKinds(new RecordingLogger(), features, true),
getAnalysisKinds(getRunnerLogger(true), features, true),
);
},
);
@@ -180,7 +181,7 @@ for (let i = 0; i < analysisKinds.length; i++) {
.withArgs("analysis-kinds")
.returns([analysisKind, otherAnalysis].join(","));
const result = await getAnalysisKinds(
new RecordingLogger(),
getRunnerLogger(true),
features,
true,
);
@@ -198,7 +199,7 @@ for (let i = 0; i < analysisKinds.length; i++) {
.withArgs("analysis-kinds")
.returns([analysisKind, otherAnalysis].join(","));
await t.throwsAsync(
getAnalysisKinds(new RecordingLogger(), features, true),
getAnalysisKinds(getRunnerLogger(true), features, true),
{
instanceOf: ConfigurationError,
message: `${analysisKind} and ${otherAnalysis} cannot be enabled at the same time`,
+18 -4
View File
@@ -50,8 +50,9 @@ import {
StatusReportBase,
} from "./status-report";
import {
cleanupTrapCaches,
TrapCacheCleanupStatusReport,
uploadTrapCaches,
warnIfTrapCacheCleanupEnvVarIsSet,
} from "./trap-caching";
import * as uploadLib from "./upload-lib";
import { UploadResult } from "./upload-lib";
@@ -85,6 +86,7 @@ async function sendStatusReport(
trapCacheUploadTime: number | undefined,
dbCreationTimings: DatabaseCreationTimings | undefined,
didUploadTrapCaches: boolean,
trapCacheCleanup: TrapCacheCleanupStatusReport | undefined,
dependencyCacheResults: DependencyCacheUploadStatusReport | undefined,
databaseUploadResults: DatabaseUploadResult[],
logger: Logger,
@@ -105,6 +107,7 @@ async function sendStatusReport(
...statusReportBase,
...(stats || {}),
...(dbCreationTimings || {}),
...(trapCacheCleanup || {}),
dependency_caching_upload_results: dependencyCacheResults,
database_upload_results: databaseUploadResults,
};
@@ -226,6 +229,8 @@ 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;
@@ -233,7 +238,7 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
let databaseUploadResults: DatabaseUploadResult[] = [];
try {
util.initializeEnvironment(actionsUtil.getActionVersion());
action.env.initialise(actionsUtil.getActionVersion());
// Make inputs accessible in the `post` step, details at
// https://github.com/github/codeql-action/issues/2553
@@ -273,8 +278,6 @@ 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);
@@ -413,6 +416,13 @@ 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(
@@ -464,6 +474,7 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
trapCacheUploadTime,
dbCreationTimings,
didUploadTrapCaches,
trapCacheCleanupTelemetry,
dependencyCacheResults,
databaseUploadResults,
logger,
@@ -486,6 +497,7 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
trapCacheUploadTime,
dbCreationTimings,
didUploadTrapCaches,
trapCacheCleanupTelemetry,
dependencyCacheResults,
databaseUploadResults,
logger,
@@ -499,6 +511,7 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
trapCacheUploadTime,
dbCreationTimings,
didUploadTrapCaches,
trapCacheCleanupTelemetry,
dependencyCacheResults,
databaseUploadResults,
logger,
@@ -512,6 +525,7 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
trapCacheUploadTime,
dbCreationTimings,
didUploadTrapCaches,
trapCacheCleanupTelemetry,
dependencyCacheResults,
databaseUploadResults,
logger,
+2 -2
View File
@@ -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,
new RecordingLogger(),
getRunnerLogger(true),
createFeatures([Feature.QaTelemetryEnabled]),
);
t.deepEqual(Object.keys(statusReport).sort(), [
+2 -2
View File
@@ -7,7 +7,7 @@ import { ProxyAgent } from "undici";
import * as actionsUtil from "./actions-util";
import * as api from "./api-client";
import { DO_NOT_RETRY_STATUSES } from "./api-client";
import { ActionsEnvVars, RegistryProxyVars } from "./environment";
import { ActionsEnvVars, RegistryProxyVars, getEnv } from "./environment";
import { GITHUB_DOTCOM_API_URL, GITHUB_DOTCOM_URL } from "./github";
import {
callee,
@@ -21,7 +21,7 @@ import * as util from "./util";
setupTests(test);
test.beforeEach(() => {
util.initializeEnvironment(actionsUtil.getActionVersion());
getEnv().initialise(actionsUtil.getActionVersion());
});
test.serial("getApiClient", async (t) => {
+15
View File
@@ -401,6 +401,21 @@ 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.
*
+2 -2
View File
@@ -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 = new RecordingLogger();
const logger = getRunnerLogger(true);
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "scanner-test-"));
try {
+11 -4
View File
@@ -10,7 +10,7 @@ import { getGitHubVersion } from "./api-client";
import { determineAutobuildLanguages, runAutobuild } from "./autobuild";
import { getCodeQL } from "./codeql";
import { Config, getConfig } from "./config-utils";
import { EnvVar } from "./environment";
import { EnvVar, type Env } from "./environment";
import { Language } from "./languages";
import { Logger } from "./logging";
import {
@@ -26,7 +26,6 @@ import {
checkDiskUsage,
checkGitHubVersionInRange,
ConfigurationError,
initializeEnvironment,
wrapError,
} from "./util";
@@ -38,6 +37,7 @@ interface AutobuildStatusReport extends StatusReportBase {
}
async function sendCompletedStatusReport(
env: Env,
config: Config | undefined,
logger: Logger,
startedAt: Date,
@@ -45,7 +45,7 @@ async function sendCompletedStatusReport(
failingLanguage?: string,
cause?: Error,
) {
initializeEnvironment(getActionVersion());
env.initialise(getActionVersion());
const status = getActionsStatus(cause, failingLanguage);
const statusReportBase = await createStatusReportBase(
@@ -129,6 +129,7 @@ async function run({
`We were unable to automatically build your code. Please replace the call to the autobuild action with your custom build steps. ${error.message}`,
);
await sendCompletedStatusReport(
env,
config,
logger,
startedAt,
@@ -141,7 +142,13 @@ async function run({
env.export(EnvVar.AUTOBUILD_DID_COMPLETE_SUCCESSFULLY, "true");
await sendCompletedStatusReport(config, logger, startedAt, languages ?? []);
await sendCompletedStatusReport(
env,
config,
logger,
startedAt,
languages ?? [],
);
}
/** Defines the `autobuild` Action. */
+3 -2
View File
@@ -3,14 +3,15 @@ import path from "path";
import test from "ava";
import { RecordingLogger, setupTests } from "../testing-utils";
import { getRunnerLogger } from "../logging";
import { setupTests } from "../testing-utils";
import * as util from "../util";
import { getCachedCodeQlVersion } from "./output-cache";
setupTests(test);
const logger = new RecordingLogger();
const logger = getRunnerLogger(true);
test.serial(
"getCachedCodeQlVersion reuses a version persisted by an earlier step",
+17 -18
View File
@@ -21,7 +21,9 @@ import {
import type { Config } from "./config-utils";
import * as defaults from "./defaults.json";
import { DocUrl } from "./doc-url";
import { getEnv } from "./environment";
import { BuiltInLanguage } from "./languages";
import { getRunnerLogger } from "./logging";
import { ToolsSource } from "./setup-codeql";
import {
setupTests,
@@ -33,19 +35,16 @@ import {
makeVersionInfo,
createTestConfig,
makeMacro,
getCodeQLForTesting,
RecordingLogger,
} from "./testing-utils";
import { ToolsDownloadStatusReport } from "./tools-download";
import * as util from "./util";
import { initializeEnvironment } from "./util";
setupTests(test);
let stubConfig: Config;
test.beforeEach(() => {
initializeEnvironment("1.2.3");
getEnv().initialise("1.2.3");
stubConfig = createTestConfig({
languages: [BuiltInLanguage.cpp],
@@ -103,7 +102,7 @@ async function installIntoToolcache({
undefined, // otherLanguagePacksReason
false, // useOverlayAwareDefaultCliVersion
createFeatures([]),
new RecordingLogger(),
getRunnerLogger(true),
false,
);
}
@@ -141,7 +140,7 @@ function mockApiDetails(apiDetails: Required<GitHubApiDetails>) {
}
async function stubCodeql(): Promise<codeql.CodeQL> {
const codeqlObject = await getCodeQLForTesting();
const codeqlObject = await codeql.getCodeQLForTesting();
sinon.stub(codeqlObject, "getVersion").resolves(makeVersionInfo("2.17.6"));
sinon
.stub(codeqlObject, "isTracedLanguage")
@@ -177,7 +176,7 @@ test.serial(
undefined, // otherLanguagePacksReason
false, // useOverlayAwareDefaultCliVersion
features,
new RecordingLogger(),
getRunnerLogger(true),
false,
);
@@ -213,7 +212,7 @@ test.serial(
undefined, // otherLanguagePacksReason
false, // useOverlayAwareDefaultCliVersion
features,
new RecordingLogger(),
getRunnerLogger(true),
false,
);
@@ -253,7 +252,7 @@ test.serial(
undefined, // otherLanguagePacksReason
false, // useOverlayAwareDefaultCliVersion
features,
new RecordingLogger(),
getRunnerLogger(true),
false,
);
t.assert(toolcache.find("CodeQL", "0.0.0-20200610"));
@@ -363,7 +362,7 @@ for (const {
undefined, // otherLanguagePacksReason
false, // useOverlayAwareDefaultCliVersion
features,
new RecordingLogger(),
getRunnerLogger(true),
false,
);
t.assert(toolcache.find("CodeQL", expectedToolcacheVersion));
@@ -406,7 +405,7 @@ for (const toolcacheVersion of [
undefined, // otherLanguagePacksReason
false, // useOverlayAwareDefaultCliVersion
features,
new RecordingLogger(),
getRunnerLogger(true),
false,
);
t.is(
@@ -451,7 +450,7 @@ test.serial(
undefined, // otherLanguagePacksReason
false, // useOverlayAwareDefaultCliVersion
features,
new RecordingLogger(),
getRunnerLogger(true),
false,
);
t.deepEqual(result.toolsVersion, "0.0.0-20200601");
@@ -498,7 +497,7 @@ test.serial(
undefined, // otherLanguagePacksReason
false, // useOverlayAwareDefaultCliVersion
features,
new RecordingLogger(),
getRunnerLogger(true),
false,
);
t.deepEqual(result.toolsVersion, defaults.cliVersion);
@@ -538,7 +537,7 @@ test.serial(
undefined, // otherLanguagePacksReason
false, // useOverlayAwareDefaultCliVersion
features,
new RecordingLogger(),
getRunnerLogger(true),
false,
);
t.deepEqual(result.toolsVersion, defaults.cliVersion);
@@ -580,7 +579,7 @@ test.serial(
undefined, // otherLanguagePacksReason
false, // useOverlayAwareDefaultCliVersion
features,
new RecordingLogger(),
getRunnerLogger(true),
false,
);
@@ -665,7 +664,7 @@ const injectedConfigMacro = makeMacro({
tempDir,
};
thisStubConfig.computedConfig = generateCodeScanningConfig(
new RecordingLogger(),
getRunnerLogger(true),
thisStubConfig.originalUserInput,
augmentationProperties,
);
@@ -1040,7 +1039,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 getCodeQLForTesting();
const codeqlObject = await codeql.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.
@@ -1096,7 +1095,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 getCodeQLForTesting();
const codeqlObject = await codeql.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.
+48 -60
View File
@@ -4,6 +4,7 @@ import * as path from "path";
import * as core from "@actions/core";
import * as toolrunner from "@actions/exec/lib/toolrunner";
import * as yaml from "js-yaml";
import * as semver from "semver";
import {
CommandInvocationError,
@@ -19,6 +20,7 @@ import type { ActionConfig } from "./config/action-config";
import { appendExtraQueryExclusions, type Config } from "./config-utils";
import { DocUrl } from "./doc-url";
import { EnvVar, getEnv, exportEnvVar } from "./environment";
import { getErrorMessage, isDiskConfigurationError } from "./errors";
import {
CodeQLDefaultVersionInfo,
Feature,
@@ -26,7 +28,7 @@ import {
} from "./feature-flags";
import { isAnalyzingDefaultBranch } from "./git-utils";
import { Language } from "./languages";
import type { Logger } from "./logging";
import { getRunnerLogger, Logger } from "./logging";
import { writeBaseDatabaseOidsFile, writeOverlayChangesFile } from "./overlay";
import { OverlayDatabaseMode } from "./overlay/overlay-database-mode";
import * as setupCodeql from "./setup-codeql";
@@ -34,7 +36,9 @@ import { ToolsDownloadStatusReport } from "./tools-download";
import { ToolsFeature, isSupportedToolsFeature } from "./tools-features";
import { shouldEnableIndirectTracing } from "./tracer-config";
import * as util from "./util";
import { BuildMode, CleanupLevel, getErrorMessage } from "./util";
import { BuildMode, CleanupLevel } from "./util";
export { isDiskConfigurationError } from "./errors";
type Options = Array<string | number | boolean>;
@@ -70,6 +74,11 @@ export interface CodeQL {
* Get a string containing the semver version of the CodeQL executable.
*/
getVersion(): Promise<VersionInfo>;
/**
* Determines whether the CLI installation represented by this {@link CodeQL} instance
* has at least the {@link requiredVersion}.
*/
isVersionAtLeast(requiredVersion: string): Promise<boolean>;
/**
* Print version information about CodeQL.
*/
@@ -199,7 +208,6 @@ export interface CodeQL {
sarifFile: string,
automationDetailsId: string | undefined,
config: ActionConfig<"tempDir">,
diagnosticDir: string | undefined,
): Promise<void>;
/** Get the location of an extractor for the specified language. */
resolveExtractor(language: Language): Promise<string>;
@@ -275,26 +283,6 @@ const GHES_MOST_RECENT_DEPRECATION_DATE = "2026-09-24";
/** The CLI verbosity level to use for extraction in debug mode. */
const EXTRACTION_DEBUG_MODE_VERBOSITY = "progress++";
/**
* Decides whether `e` is a disk-related error outside of our control
* that should be classified as a `ConfigurationError`.
*
* @param e The error to check.
* @returns True if the error should be treated as a `ConfigurationError` or false if not.
*/
export function isDiskConfigurationError(e: unknown): boolean {
if (!(e instanceof Error)) {
return false;
}
return (
// out of disk space
e.message.includes("ENOSPC") ||
// access denied
e.message.includes("EACCES")
);
}
/**
* Set up CodeQL CLI access.
*
@@ -403,9 +391,9 @@ export function setCodeQL(codeql: Partial<CodeQL>): void {
cachedCodeQL = createStubCodeQL(codeql);
}
function resolveFunction<T>(
function resolveFunction<K extends keyof CodeQL, T extends CodeQL[K]>(
partialCodeql: Partial<CodeQL>,
methodName: string,
methodName: K,
defaultImplementation?: T,
): T {
if (typeof partialCodeql[methodName] !== "function") {
@@ -415,7 +403,7 @@ function resolveFunction<T>(
const dummyMethod = () => {
throw new Error(`CodeQL ${methodName} method not correctly defined`);
};
return dummyMethod as T;
return dummyMethod as unknown as T;
}
return partialCodeql[methodName] as T;
}
@@ -432,6 +420,19 @@ export function createStubCodeQL(partialCodeql: Partial<CodeQL>): CodeQL {
getVersion: resolveFunction(partialCodeql, "getVersion", async () => ({
version: "1.0.0",
})),
isVersionAtLeast: resolveFunction(
partialCodeql,
"isVersionAtLeast",
async (requiredVersion) => {
return (
!!partialCodeql.getVersion &&
semver.gte(
(await partialCodeql.getVersion()).version,
requiredVersion,
)
);
},
),
printVersion: resolveFunction(partialCodeql, "printVersion"),
supportsFeature: resolveFunction(
partialCodeql,
@@ -487,6 +488,18 @@ export function createStubCodeQL(partialCodeql: Partial<CodeQL>): 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<CodeQL> {
return getCodeQLForCmd(logger, cmd, false);
}
/**
* Return a CodeQL object for CodeQL CLI access.
*
@@ -495,7 +508,7 @@ export function createStubCodeQL(partialCodeql: Partial<CodeQL>): CodeQL {
* version requirement. Must be set to true outside tests.
* @returns A new CodeQL object
*/
export async function getCodeQLForCmd(
async function getCodeQLForCmd(
logger: Logger,
cmd: string,
checkVersion: boolean,
@@ -523,6 +536,9 @@ export async function getCodeQLForCmd(
}
return result;
},
async isVersionAtLeast(requiredVersion: string) {
return semver.gte((await this.getVersion()).version, requiredVersion);
},
async printVersion() {
// Reuse the cached version information rather than invoking the CLI again.
core.info(JSON.stringify(await this.getVersion(), null, 2));
@@ -904,43 +920,15 @@ export async function getCodeQLForCmd(
sarifFile: string,
automationDetailsId: string | undefined,
config: ActionConfig<"tempDir">,
diagnosticDir: string | undefined,
): Promise<void> {
// We may be exporting diagnostics for a failed SARIF, in which case the configuration
// file may not have been written to disk and there may not be a database. The CLI
// will produce a SARIF for diagnostics from `diagnosticDir` without it. Therefore,
// we check whether `configPath` exists and only provide it to the CLI if it does.
// Although not needed, the CLI will produce an error if it
// is provided with an argument to `--sarif-codescanning-config` that does not exist.
let configPath: string | undefined =
getGeneratedCodeScanningConfigPath(config);
if (!fs.existsSync(configPath)) {
// Warn if there is no configuration file, even though `config` claims that `init`
// was successful. This shouldn't be happening.
if (config.successfullyInitialised) {
logger.warning(
[
`Expected a configuration file at '${configPath}', which does not exist.`,
"Continuing with diagnostics export anyway, without a configuration file.",
].join(" "),
);
}
configPath = undefined;
}
const args = [
"diagnostics",
"export",
"--format=sarif-latest",
`--output=${sarifFile}`,
...(configPath !== undefined
? [`--sarif-codescanning-config=${configPath}`]
: []),
...(diagnosticDir !== undefined
? [`--diagnostic-dir=${diagnosticDir}`]
: []),
`--sarif-codescanning-config=${getGeneratedCodeScanningConfigPath(
config,
)}`,
...getExtraOptionsFromEnv(["diagnostics", "export"]),
];
if (automationDetailsId !== undefined) {
@@ -1045,7 +1033,7 @@ export async function getCodeQLForCmd(
// CodeQL object is created.
if (
checkVersion &&
!(await util.codeQlVersionAtLeast(codeql, CODEQL_MINIMUM_VERSION))
!(await codeql.isVersionAtLeast(CODEQL_MINIMUM_VERSION))
) {
throw new util.ConfigurationError(
`Expected a CodeQL CLI with version at least ${CODEQL_MINIMUM_VERSION} but got version ${
@@ -1055,7 +1043,7 @@ export async function getCodeQLForCmd(
} else if (
checkVersion &&
process.env[EnvVar.SUPPRESS_DEPRECATED_SOON_WARNING] !== "true" &&
!(await util.codeQlVersionAtLeast(codeql, CODEQL_NEXT_MINIMUM_VERSION))
!(await codeql.isVersionAtLeast(CODEQL_NEXT_MINIMUM_VERSION))
) {
const result = await codeql.getVersion();
core.warning(
+16 -16
View File
@@ -21,6 +21,7 @@ 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";
@@ -41,7 +42,6 @@ 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: new RecordingLogger(),
logger: getRunnerLogger(true),
} 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 = new RecordingLogger();
const logger = getRunnerLogger(true);
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 = new RecordingLogger();
const logger = getRunnerLogger(true);
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 = new RecordingLogger();
const logger = getRunnerLogger(true);
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 = new RecordingLogger();
const logger = getRunnerLogger(true);
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 = new RecordingLogger();
const logger = getRunnerLogger(true);
const codeql = createStubCodeQL({
async resolveLanguages() {
@@ -788,12 +788,12 @@ test.serial("Unknown languages", async (t) => {
});
});
const mockLogger = new RecordingLogger();
const mockLogger = getRunnerLogger(true);
test.serial("no generateRegistries when registries is undefined", async (t) => {
return await withTmpDir(async (tmpDir) => {
const registriesInput = undefined;
const logger = new RecordingLogger();
const logger = getRunnerLogger(true);
const { registriesAuthTokens, qlconfigFile } =
await configUtils.generateRegistries(registriesInput, tmpDir, logger);
@@ -814,7 +814,7 @@ test.serial(
token: "not-a-token",
},
]);
const logger = new RecordingLogger();
const logger = getRunnerLogger(true);
const { registriesAuthTokens, qlconfigFile } =
await configUtils.generateRegistries(registriesInput, tmpDir, logger);
@@ -2225,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 = new RecordingLogger();
const logger = getRunnerLogger(true);
await configUtils.applyIncrementalAnalysisSettings(
config,
@@ -2243,7 +2243,7 @@ test("applyIncrementalAnalysisSettings: keeps overlay mode and adds exclusions w
overlayDatabaseMode: OverlayDatabaseMode.Overlay,
});
const codeql = createStubCodeQL({});
const logger = new RecordingLogger();
const logger = getRunnerLogger(true);
await configUtils.applyIncrementalAnalysisSettings(
config,
@@ -2266,7 +2266,7 @@ test.serial(
});
config.useOverlayDatabaseCaching = true;
const codeql = createStubCodeQL({});
const logger = new RecordingLogger();
const logger = getRunnerLogger(true);
const addDiagnosticsStub = sinon
.stub(overlayDiagnostics, "addOverlayDisablementDiagnostics")
.resolves();
@@ -2298,7 +2298,7 @@ test.serial(
config.useOverlayDatabaseCaching = false;
config.overlayModeSetExplicitly = true;
const codeql = createStubCodeQL({});
const logger = new RecordingLogger();
const logger = getRunnerLogger(true);
const addDiagnosticsStub = sinon
.stub(overlayDiagnostics, "addOverlayDisablementDiagnostics")
.resolves();
@@ -2321,7 +2321,7 @@ test("applyIncrementalAnalysisSettings: adds exclusions for diff-informed-only r
const config = createTestConfig({});
config.overlayDatabaseMode = OverlayDatabaseMode.None;
const codeql = createStubCodeQL({});
const logger = new RecordingLogger();
const logger = getRunnerLogger(true);
await configUtils.applyIncrementalAnalysisSettings(
config,
@@ -2535,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(
new RecordingLogger(),
getRunnerLogger(true),
expectedConfigPath,
false,
);
+2 -8
View File
@@ -82,7 +82,6 @@ import {
GitHubVersion,
ConfigurationError,
BuildMode,
codeQlVersionAtLeast,
cloneObject,
isDefined,
checkDiskUsage,
@@ -465,8 +464,6 @@ export async function initActionState(
overlayModeSetExplicitly: false,
repositoryProperties,
enableFileCoverageInformation,
// We don't set this to `true` until `init` is successful.
successfullyInitialised: false,
};
}
@@ -654,10 +651,7 @@ async function runnerHasSufficientMemory(
logger: Logger,
): Promise<boolean> {
if (
await codeQlVersionAtLeast(
codeql,
CODEQL_VERSION_REDUCED_OVERLAY_MEMORY_USAGE,
)
await codeql.isVersionAtLeast(CODEQL_VERSION_REDUCED_OVERLAY_MEMORY_USAGE)
) {
logger.debug(
`Skipping memory check for overlay analysis because CodeQL version is at least ${CODEQL_VERSION_REDUCED_OVERLAY_MEMORY_USAGE}.`,
@@ -901,7 +895,7 @@ async function validateOverlayDatabaseMode(
);
return new Failure(OverlayDisabledReason.IncompatibleBuildMode);
}
if (!(await codeQlVersionAtLeast(codeql, CODEQL_OVERLAY_MINIMUM_VERSION))) {
if (!(await codeql.isVersionAtLeast(CODEQL_OVERLAY_MINIMUM_VERSION))) {
logger.warning(
`Cannot build an ${overlayDatabaseMode} database because ` +
`the CodeQL CLI is older than ${CODEQL_OVERLAY_MINIMUM_VERSION}. ` +
-3
View File
@@ -130,9 +130,6 @@ export interface Config {
* Whether to enable file coverage information.
*/
enableFileCoverageInformation: boolean;
/** Whether the analysis was successfully initialised using the configuration. */
successfullyInitialised: boolean;
}
/** Properties available in `Config`. */
+59 -74
View File
@@ -2,15 +2,15 @@ import test, { ExecutionContext } from "ava";
import { RepositoryProperties } from "../feature-flags/properties";
import { BuiltInLanguage, Language } from "../languages";
import { getRunnerLogger } from "../logging";
import {
checkExpectedLogMessages,
getRecordingLogger,
LoggedMessage,
makeMacro,
RecordingLogger,
setupActionsVars,
} from "../testing-utils";
import { ConfigurationError, prettyPrintPack, withTmpDir } from "../util";
import { ConfigurationError, prettyPrintPack } from "../util";
import * as dbConfig from "./db-config";
@@ -384,7 +384,7 @@ calculateAugmentationErrorMacro(
test("parseUserConfig - successfully parses valid YAML", (t) => {
const result = dbConfig.parseUserConfig(
new RecordingLogger(),
getRunnerLogger(true),
"test",
`
paths-ignore:
@@ -410,7 +410,7 @@ test("parseUserConfig - throws a ConfigurationError if the file is not valid YAM
t.throws(
() =>
dbConfig.parseUserConfig(
new RecordingLogger(),
getRunnerLogger(true),
"test",
`
paths-ignore:
@@ -562,89 +562,74 @@ test("mergeDefaultSetupAndUserConfigs - keeps other properties from user-supplie
t.deepEqual(result, configFile);
});
test.serial(
"mergeDefaultSetupAndUserConfigs - ignores, but warns about, unknown keys from Default Setup",
async (t) => {
const logger = new RecordingLogger();
const configFile: dbConfig.UserConfig = {
"query-filters": [{ exclude: { a: "b" } }],
"paths-ignore": ["path"],
};
test("mergeDefaultSetupAndUserConfigs - ignores, but warns about, unknown keys from Default Setup", async (t) => {
const logger = new RecordingLogger();
const configFile: dbConfig.UserConfig = {
"query-filters": [{ exclude: { a: "b" } }],
"paths-ignore": ["path"],
};
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
const result = dbConfig.mergeDefaultSetupAndUserConfigs(
logger,
{
"default-setup": {
borg: [],
org: {
unknown: "foo",
"model-packs": [],
},
} as unknown as dbConfig.DefaultSetupConfig,
"paths-ignore": ["other-path"],
const result = dbConfig.mergeDefaultSetupAndUserConfigs(
logger,
{
"default-setup": {
borg: [],
org: {
unknown: "foo",
"model-packs": [],
},
configFile,
);
} as unknown as dbConfig.DefaultSetupConfig,
"paths-ignore": ["other-path"],
},
configFile,
);
t.deepEqual(result, {
...configFile,
"default-setup": { org: { "model-packs": [] } },
});
t.deepEqual(result, {
...configFile,
"default-setup": { org: { "model-packs": [] } },
});
const expectedUnrecognisedKeys = [
".default-setup.org.unknown",
".default-setup.borg",
".paths-ignore",
].join(", ");
checkExpectedLogMessages(t, logger.messages, [
`Unrecognised keys in Default Setup configuration: ${expectedUnrecognisedKeys}`,
]);
});
},
);
const expectedUnrecognisedKeys = [
".default-setup.org.unknown",
".default-setup.borg",
".paths-ignore",
].join(", ");
checkExpectedLogMessages(t, logger.messages, [
`Unrecognised keys in Default Setup configuration: ${expectedUnrecognisedKeys}`,
]);
});
test.serial(
"mergeDefaultSetupAndUserConfigs - warns about invalid keys from Default Setup",
async (t) => {
const logger = new RecordingLogger();
const configFile: dbConfig.UserConfig = {};
test("mergeDefaultSetupAndUserConfigs - warns about invalid keys from Default Setup", async (t) => {
const logger = new RecordingLogger();
const configFile: dbConfig.UserConfig = {};
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
const result = dbConfig.mergeDefaultSetupAndUserConfigs(
logger,
{
"default-setup": {
org: {
"model-packs": [123],
},
} as unknown as dbConfig.DefaultSetupConfig,
const result = dbConfig.mergeDefaultSetupAndUserConfigs(
logger,
{
"default-setup": {
org: {
"model-packs": [123],
},
configFile,
);
} as unknown as dbConfig.DefaultSetupConfig,
},
configFile,
);
t.deepEqual(result, {
...configFile,
"default-setup": { org: { "model-packs": [123] } },
});
t.deepEqual(result, {
...configFile,
"default-setup": { org: { "model-packs": [123] } },
});
const expectedInvalidKeys = [".default-setup.org.model-packs[0]"].join(
", ",
);
checkExpectedLogMessages(t, logger.messages, [
`Invalid keys in Default Setup configuration: ${expectedInvalidKeys}`,
]);
});
},
);
const expectedInvalidKeys = [".default-setup.org.model-packs[0]"].join(", ");
checkExpectedLogMessages(t, logger.messages, [
`Invalid keys in Default Setup configuration: ${expectedInvalidKeys}`,
]);
});
/** Parses `contents` as a configuration without validating it. */
function parseUnvalidatedConfig(contents: string): dbConfig.UserConfig {
return dbConfig.parseUserConfig(
new RecordingLogger(),
getRunnerLogger(true),
"test",
contents,
false,
+3 -8
View File
@@ -15,6 +15,7 @@ import {
cleanupAndUploadDatabases,
uploadBundledDatabase,
} from "./database-upload";
import { getEnv } from "./environment";
import { Feature } from "./feature-flags";
import * as gitUtils from "./git-utils";
import { BuiltInLanguage } from "./languages";
@@ -31,18 +32,12 @@ import {
setupActionsVars,
setupTests,
} from "./testing-utils";
import {
CleanupLevel,
GitHubVariant,
HTTPError,
initializeEnvironment,
withTmpDir,
} from "./util";
import { CleanupLevel, GitHubVariant, HTTPError, withTmpDir } from "./util";
setupTests(test);
test.beforeEach(() => {
initializeEnvironment("1.2.3");
getEnv().initialise("1.2.3");
});
const testRepoName: RepositoryNwo = { owner: "github", repo: "example" };
-165
View File
@@ -1,165 +0,0 @@
import * as fs from "fs";
import path from "path";
import test, { type ExecutionContext } from "ava";
import * as diagnostics from "./diagnostics";
import { BuiltInLanguage } from "./languages";
import {
checkExpectedLogMessages,
createTestConfig,
initAllState,
RecordingLogger,
setupTests,
} from "./testing-utils";
import { withTmpDir } from "./util";
setupTests(test);
const id = "codeql-action/test-diagnostic";
const name = "Test title";
test("makeDiagnostic - adds expected properties", (t) => {
const diagnostic = diagnostics.makeDiagnostic(id, name, {});
t.truthy(diagnostic.timestamp);
t.is(diagnostic.source.id, id);
t.is(diagnostic.source.name, name);
});
test("getLanguageDiagnosticsPath gets language-specific path", async (t) =>
withTmpDir(async (tmpDir) => {
for (const language of Object.values(BuiltInLanguage)) {
t.is(
diagnostics.getLanguageDiagnosticsPath(tmpDir, language),
path.resolve(
tmpDir,
language,
...diagnostics.CODEQL_ACTION_DIAGNOSTIC_SUBDIR_PARTS,
),
);
}
}));
test("getTempLanguageDiagnosticsPath returns path without language", async (t) =>
withTmpDir(async (tmpDir) => {
t.is(
diagnostics.getTempLanguageDiagnosticsPath(tmpDir),
path.resolve(
tmpDir,
diagnostics.TEMP_DIAGNOSTIC_DIR_NAME,
...diagnostics.CODEQL_ACTION_DIAGNOSTIC_SUBDIR_PARTS,
),
);
}));
test("getTempLanguageDiagnosticsPath returns language-specific path if a language is provided", async (t) =>
withTmpDir(async (tmpDir) => {
t.is(
diagnostics.getTempLanguageDiagnosticsPath(tmpDir, BuiltInLanguage.swift),
path.resolve(
tmpDir,
diagnostics.TEMP_DIAGNOSTIC_DIR_NAME,
BuiltInLanguage.swift,
...diagnostics.CODEQL_ACTION_DIAGNOSTIC_SUBDIR_PARTS,
),
);
}));
test("getDatabaseDiagnosticsPath returns language-specific path", async (t) =>
withTmpDir(async (tmpDir) => {
t.is(
diagnostics.getDatabaseDiagnosticsPath(
createTestConfig({ dbLocation: tmpDir }),
BuiltInLanguage.swift,
),
path.resolve(
tmpDir,
BuiltInLanguage.swift,
...diagnostics.CODEQL_ACTION_DIAGNOSTIC_SUBDIR_PARTS,
),
);
}));
/**
* Diagnostic files that are written to disk don't have deterministic names.
* This function recursively finds all `.json` files at {@link rootPath},
* asserts that there is exactly one, and that it matches {@link expected}.
*
* @param rootPath The path to start searching from.
*/
function assertSingleDiagnosticIn(
t: ExecutionContext<unknown>,
rootPath: string,
expected: diagnostics.DiagnosticMessage,
) {
// Find all `.json` files in `rootPath` and check that there is exactly one.
const filePaths = fs
.readdirSync(rootPath, {
recursive: true,
encoding: "utf-8",
})
.filter((p) => p.endsWith(".json"));
t.is(filePaths.length, 1);
// Check that the written diagnostic matches the one we added.
const contents = fs.readFileSync(
path.resolve(rootPath, filePaths[0]),
"utf-8",
);
const readDiagnostic = JSON.parse(contents) as diagnostics.DiagnosticMessage;
t.deepEqual(readDiagnostic, expected);
}
test("addDiagnostic writes temporary diagnostics and flushDiagnostics moves them to the database", async (t) =>
withTmpDir(async (tmpDir) => {
const logger = new RecordingLogger();
const tempDiagDir = path.resolve(tmpDir, "temp");
const testDbDir = path.resolve(tmpDir, "test", "database");
const diagnostic = diagnostics.makeDiagnostic(id, name, {});
// Write the diagnostic. The path provided for `dbLocation` doesn't exist,
// so the diagnostic will be written to a temporary location.
t.notThrows(() => {
diagnostics.addDiagnostic(
createTestConfig({ tempDir: tempDiagDir, dbLocation: testDbDir }),
BuiltInLanguage.actions,
diagnostic,
logger,
);
});
// Check that `tempDiagDir` was created.
t.true(fs.existsSync(tempDiagDir));
// Check that the diagnostic exists under `tempDiagDir`.
assertSingleDiagnosticIn(t, tempDiagDir, diagnostic);
// Flush the diagnostics. This will cause the diagnostic we wrote previously to
// be moved to a location under `dbLocation`.
t.notThrows(() => {
diagnostics.flushDiagnostics(
initAllState({ logger }),
createTestConfig({
tempDir: tempDiagDir,
dbLocation: testDbDir,
}),
);
});
// Check that `testDbDir` was created.
t.true(fs.existsSync(testDbDir));
// Check that the diagnostic exists under `testDbDir`.
assertSingleDiagnosticIn(t, testDbDir, diagnostic);
// Check that we logged that the database directory doesn't exist yet
// and that we moved one diagnostic.
checkExpectedLogMessages(t, logger.messages, [
`but the database at ${path.join(testDbDir, "actions")} does not exist yet`,
"Moving 1 diagnostic(s) to their respective databases.",
]);
}));
+83 -218
View File
@@ -1,24 +1,10 @@
import * as fs from "fs";
import { existsSync, mkdirSync, writeFileSync } from "fs";
import path from "path";
import type { ActionState } from "./action-common";
import { getTemporaryDirectory } from "./actions-util";
import type { Config } from "./config-utils";
import { Language } from "./languages";
import { getActionsLogger, type Logger } from "./logging";
import {
getCodeQLDatabasePath,
getErrorMessage,
isErrnoException,
} from "./util";
/** The name of the directory we use to temporarily store diagnostics. */
export const TEMP_DIAGNOSTIC_DIR_NAME = "codeql-action-temp-diagnostics";
export const CODEQL_ACTION_DIAGNOSTIC_SUBDIR_PARTS = [
"diagnostic",
"codeql-action",
];
import { getActionsLogger } from "./logging";
import { getCodeQLDatabasePath } from "./util";
/**
* Known tags for diagnostics. There is currently only "internal-error",
@@ -98,16 +84,22 @@ export type DiagnosticMessage = DiagnosticMessageOptions & {
source: DiagnosticSource;
};
/** Represents a diagnostic message that has not yet been saved to the database. */
interface TemporaryDiagnostic {
/** The path to which the diagnostic has temporarily been written to. */
path: string;
/** The language the diagnostic is for, if any. */
language?: Language;
/** Represents a diagnostic message that has not yet been written to the database. */
interface UnwrittenDiagnostic {
/** The diagnostic message that has not yet been written. */
diagnostic: DiagnosticMessage;
/** The language the diagnostic is for. */
language: Language;
}
/** A list of diagnostics which have not yet been saved to the database. */
let temporaryDiagnostics: TemporaryDiagnostic[] = [];
/** A list of diagnostics which have not yet been written to disk. */
let unwrittenDiagnostics: UnwrittenDiagnostic[] = [];
/**
* A list of diagnostics which have not yet been written to disk,
* and where the language does not matter.
*/
let unwrittenDefaultLanguageDiagnostics: DiagnosticMessage[] = [];
/**
* Counter used to generate a unique suffix for each diagnostic filename, so that
@@ -136,51 +128,6 @@ export function makeDiagnostic(
};
}
/**
* Gets a path relative to {@link basePath} where diagnostics for {@link language} should be stored at.
*/
export function getLanguageDiagnosticsPath(
basePath: string,
language: Language,
) {
return path.resolve(
basePath,
language,
...CODEQL_ACTION_DIAGNOSTIC_SUBDIR_PARTS,
);
}
/**
* Gets a path relative to {@link tmpDir} where diagnostics should temporarily be stored
* until they can be moved to the database.
*/
export function getTempDiagnosticsRoot(tmpDir: string): string {
return path.resolve(tmpDir, TEMP_DIAGNOSTIC_DIR_NAME);
}
/**
* Gets a path relative to {@link tmpDir} where diagnostics for {@link language}
* can temporarily be stored at before the database is initialised. If {@link language}
* is `undefined`, then a non-language-specific path is returned.
*/
export function getTempLanguageDiagnosticsPath(
tmpDir: string,
language?: Language,
) {
const root = getTempDiagnosticsRoot(tmpDir);
if (language !== undefined) {
return getLanguageDiagnosticsPath(root, language);
}
return path.resolve(root, ...CODEQL_ACTION_DIAGNOSTIC_SUBDIR_PARTS);
}
/** Gets the path where diagnostics for {@link language} should be stored in the database. */
export function getDatabaseDiagnosticsPath(config: Config, language: Language) {
return getLanguageDiagnosticsPath(config.dbLocation, language);
}
/**
* Adds the given diagnostic to the database. If the database does not yet exist,
* the diagnostic will be written to it once it has been created.
@@ -193,23 +140,22 @@ export function addDiagnostic(
config: Config,
language: Language,
diagnostic: DiagnosticMessage,
logger: Logger = getActionsLogger(),
) {
const logger = getActionsLogger();
const databasePath = language
? getCodeQLDatabasePath(config, language)
: config.dbLocation;
// Check that the database exists before writing to it. If the database does not yet exist,
// store the diagnostic in a temporary location and move it to the database later.
if (fs.existsSync(databasePath)) {
writeDiagnostic({ logger }, config, language, diagnostic);
// store the diagnostic in memory and write it later.
if (existsSync(databasePath)) {
writeDiagnostic(config, language, diagnostic);
} else {
logger.debug(
`Writing a diagnostic for ${language}, but the database at ${databasePath} does not exist yet.`,
);
// Write the diagnostic to a temporary location.
writeTemporaryDiagnosticFile(logger, config.tempDir, language, diagnostic);
unwrittenDiagnostics.push({ diagnostic, language });
}
}
@@ -217,7 +163,6 @@ export function addDiagnostic(
export function addNoLanguageDiagnostic(
config: Config | undefined,
diagnostic: DiagnosticMessage,
logger: Logger = getActionsLogger(),
) {
if (config !== undefined) {
addDiagnostic(
@@ -228,74 +173,10 @@ export function addNoLanguageDiagnostic(
diagnostic,
);
} else {
logger.debug(
`Writing a diagnostic not associated with any language, but we have no configuration to tell us where the database is yet.`,
);
// Write the diagnostic to a temporary location.
writeTemporaryDiagnosticFile(
logger,
getTemporaryDirectory(),
undefined,
diagnostic,
);
unwrittenDefaultLanguageDiagnostics.push(diagnostic);
}
}
/**
* Writes {@link diagnostic} for an optional {@link language} to a subdirectory of {@link tempDir}
* and tracks it as a diagnostic that was written to a temporary location.
*/
function writeTemporaryDiagnosticFile(
logger: Logger,
tempDir: string,
language: Language | undefined,
diagnostic: DiagnosticMessage,
) {
const tempDiagnosticsPath = getTempLanguageDiagnosticsPath(tempDir, language);
try {
// Write the diagnostic to a temporary location.
const diagnosticPath = writeDiagnosticFile(tempDiagnosticsPath, diagnostic);
// Track the temporary file.
temporaryDiagnostics.push({ path: diagnosticPath, language });
} catch (err) {
logger.warning(
`Unable to write temporary diagnostic to '${tempDiagnosticsPath}': ${getErrorMessage(err)}\n\n${JSON.stringify(diagnostic)}`,
);
}
}
/**
* Writes {@link diagnostic} to a file in {@link diagnosticsPath}.
*/
function writeDiagnosticFile(
diagnosticsPath: string,
diagnostic: DiagnosticMessage,
): string {
// Create the directory if it doesn't exist yet.
fs.mkdirSync(diagnosticsPath, { recursive: true });
// Include a monotonically increasing suffix to avoid filename collisions
// between diagnostics produced within the same millisecond.
const uniqueSuffix = (diagnosticCounter++).toString();
// We should only need to remove colons, but to be defensive, only allow a restricted set of
// characters.
const sanitizedTimestamp = diagnostic.timestamp.replace(
/[^a-zA-Z0-9.-]/g,
"",
);
const jsonPath = path.resolve(
diagnosticsPath,
`codeql-action-${sanitizedTimestamp}-${uniqueSuffix}.json`,
);
fs.writeFileSync(jsonPath, JSON.stringify(diagnostic));
return jsonPath;
}
/**
* Writes the given diagnostic to the database.
*
@@ -304,94 +185,78 @@ function writeDiagnosticFile(
* @param diagnostic The diagnostic message to add to the database.
*/
function writeDiagnostic(
action: ActionState<["Logger"]>,
config: Config,
language: Language,
language: Language | undefined,
diagnostic: DiagnosticMessage,
) {
const diagnosticsPath = getDatabaseDiagnosticsPath(config, language);
try {
writeDiagnosticFile(diagnosticsPath, diagnostic);
} catch (err) {
action.logger.warning(
`Unable to write diagnostic message to database: ${err}`,
);
action.logger.debug(JSON.stringify(diagnostic));
}
}
/** Report if there are temporary diagnostics and write them to the log. */
export function logTemporaryDiagnostics(action: ActionState<["Logger"]>) {
const num = temporaryDiagnostics.length;
if (num > 0 && action.logger.isDebug()) {
action.logger.startGroup("Diagnostics in temporary location");
action.logger.debug(
`${num} diagnostic(s) were not moved to the database and remain in the temporary directory.`,
);
for (const temporary of temporaryDiagnostics) {
try {
action.logger.debug(fs.readFileSync(temporary.path, "utf-8"));
} catch (err) {
action.logger.debug(
`Failed to read diagnostics from temporary location at '${temporary.path}': ${getErrorMessage(err)}`,
);
}
}
action.logger.endGroup();
}
}
/** Relocates all temporary diagnostics to the respective databases. */
export function flushDiagnostics(
action: ActionState<["Logger"]>,
config: Config,
) {
const diagnosticsCount = temporaryDiagnostics.length;
action.logger.debug(
`Moving ${diagnosticsCount} diagnostic(s) to their respective databases.`,
const logger = getActionsLogger();
const databasePath = language
? getCodeQLDatabasePath(config, language)
: config.dbLocation;
const diagnosticsPath = path.resolve(
databasePath,
"diagnostic",
"codeql-action",
);
const failedFlushDiagnostics: TemporaryDiagnostic[] = [];
for (const temporary of temporaryDiagnostics) {
// If `temporary.language` is `undefined`, then we didn't have a `config` at the time that
// `addNoLanguageDiagnostic` was called. In that case, we arbitrarily choose the first
// configured language here like we would have done in `addNoLanguageDiagnostic`.
const directory = getDatabaseDiagnosticsPath(
config,
temporary.language ?? config.languages[0],
try {
// Create the directory if it doesn't exist yet.
mkdirSync(diagnosticsPath, { recursive: true });
// Include a monotonically increasing suffix to avoid filename collisions
// between diagnostics produced within the same millisecond.
const uniqueSuffix = (diagnosticCounter++).toString();
// We should only need to remove colons, but to be defensive, only allow a restricted set of
// characters.
const sanitizedTimestamp = diagnostic.timestamp.replace(
/[^a-zA-Z0-9.-]/g,
"",
);
const jsonPath = path.resolve(
diagnosticsPath,
`codeql-action-${sanitizedTimestamp}-${uniqueSuffix}.json`,
);
const filename = path.basename(temporary.path);
const destination = path.join(directory, filename);
try {
fs.mkdirSync(directory, { recursive: true });
try {
fs.renameSync(temporary.path, destination);
} catch (renameErr: unknown) {
// Rethrow the error if it isn't an EXDEV one.
if (!isErrnoException(renameErr) || renameErr.code !== "EXDEV") {
throw renameErr;
}
writeFileSync(jsonPath, JSON.stringify(diagnostic));
} catch (err) {
logger.warning(`Unable to write diagnostic message to database: ${err}`);
logger.debug(JSON.stringify(diagnostic));
}
}
// The error was an EXDEV one: try to copy the file and delete the temporary one.
fs.copyFileSync(temporary.path, destination);
fs.unlinkSync(temporary.path);
}
} catch (err) {
action.logger.warning(
`Failed to relocate '${temporary.path}' to '${destination}': ${getErrorMessage(err)}`,
);
/** Report if there are unwritten diagnostics and write them to the log. */
export function logUnwrittenDiagnostics() {
const logger = getActionsLogger();
const num = unwrittenDiagnostics.length;
if (num > 0) {
logger.warning(
`${num} diagnostic(s) could not be written to the database and will not appear on the Tool Status Page.`,
);
failedFlushDiagnostics.push(temporary);
for (const unwritten of unwrittenDiagnostics) {
logger.debug(JSON.stringify(unwritten.diagnostic));
}
}
}
// Reset the temporary diagnostics arrays.
temporaryDiagnostics = failedFlushDiagnostics;
/** Writes all unwritten diagnostics to disk. */
export function flushDiagnostics(config: Config) {
const logger = getActionsLogger();
const diagnosticsCount =
unwrittenDiagnostics.length + unwrittenDefaultLanguageDiagnostics.length;
logger.debug(`Writing ${diagnosticsCount} diagnostic(s) to database.`);
for (const unwritten of unwrittenDiagnostics) {
writeDiagnostic(config, unwritten.language, unwritten.diagnostic);
}
for (const unwritten of unwrittenDefaultLanguageDiagnostics) {
addNoLanguageDiagnostic(config, unwritten);
}
// Reset the unwritten diagnostics arrays.
unwrittenDiagnostics = [];
unwrittenDefaultLanguageDiagnostics = [];
}
/**
+7 -7
View File
@@ -10,6 +10,7 @@ import {
exportedForTesting,
} from "./diff-informed-analysis-utils";
import { Feature, FeatureEnablement, initFeatures } from "./feature-flags";
import { getRunnerLogger } from "./logging";
import { parseRepositoryNwo } from "./repository";
import {
setupTests,
@@ -18,7 +19,6 @@ 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 = new RecordingLogger();
const logger = getRunnerLogger(true);
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 = new RecordingLogger();
const logger = getRunnerLogger(true);
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 = new RecordingLogger();
const logger = getRunnerLogger(true);
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 = new RecordingLogger();
const logger = getRunnerLogger(true);
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 = new RecordingLogger();
const logger = getRunnerLogger(true);
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"),
},
new RecordingLogger(),
getRunnerLogger(true),
);
}
+1
View File
@@ -3,6 +3,7 @@
*/
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",
+20 -10
View File
@@ -293,8 +293,8 @@ export function getOptionalEnvVar(paramName: string): string | undefined {
* in tests, while clearly signalling in regular code that the consumer of the `ReadOnlyEnv` instance
* will only read from it.
*/
export class ReadOnlyEnv<T extends string | undefined = string | undefined> {
constructor(protected readonly vars: Record<string, T>) {}
export class ReadOnlyEnv {
constructor(protected readonly vars: Record<string, string | undefined>) {}
/** Clones the object while detaching the underlying environment from the original. */
public clone(): this {
@@ -302,7 +302,7 @@ export class ReadOnlyEnv<T extends string | undefined = string | undefined> {
}
/** Gets a copy of the underlying environment. */
public get(): Record<string, T> {
public get(): Record<string, string | undefined> {
return { ...this.vars };
}
@@ -317,7 +317,7 @@ export class ReadOnlyEnv<T extends string | undefined = string | undefined> {
}
/** Gets the entries of the underlying `ProcessEnv`. */
public entries(): Array<[string, T]> {
public entries(): Array<[string, string | undefined]> {
return Object.entries(this.vars);
}
@@ -347,19 +347,17 @@ export class ReadOnlyEnv<T extends string | undefined = string | undefined> {
* Use `ReadOnlyEnv` instead if you only plan to read from the environment.
* This type allows writing to the environment.
*/
export class Env<
T extends string | undefined = string | undefined,
> extends ReadOnlyEnv<T> {
export class Env extends ReadOnlyEnv {
private changed: boolean = false;
/** Sets an environment variable. */
public set(name: string, value: T): void {
public set(name: string, value: string | undefined): void {
this.vars[name] = value;
this.changed = true;
}
/** Sets all environment variables given by `vars`. */
public setAll(vars: Record<string, T>): void {
public setAll(vars: Record<string, string | undefined>): void {
for (const [key, val] of Object.entries(vars)) {
this.set(key, val);
}
@@ -373,7 +371,7 @@ export class Env<
* @param name The name of the environment variable to set and export.
* @param val The value to set and export for the environment variable.
*/
public export(name: string, val: T): void {
public export(name: string, val: string): void {
// Setting the environment variable for this instance is always OK, including
// in tests, since we use fresh `Env` instances whenever needed. This allows
// tests to pass that rely on that part of the `core.exportVariable` behaviour.
@@ -385,6 +383,18 @@ export class Env<
}
}
/**
* Set some initial environment variables that we can set even without
* knowing what version of CodeQL we're running.
*/
public initialise(version: string) {
this.export(EnvVar.FEATURE_MULTI_LANGUAGE, "false");
this.export(EnvVar.FEATURE_SANDWICH, "false");
this.export(EnvVar.FEATURE_SARIF_COMBINE, "true");
this.export(EnvVar.FEATURE_WILL_UPLOAD, "true");
this.export(EnvVar.VERSION, version);
}
/** Gets a value indicating whether `set` was called at least once. */
public hasChanged(): boolean {
return this.changed;
+73
View File
@@ -0,0 +1,73 @@
import * as json from "../json";
/**
* If {@link error} is not already an instance of {@link Error}, then this function constructs
* an {@link Error} with a string representation of {@link error} as the message.
*/
export function wrapError(error: unknown): Error {
return error instanceof Error ? error : new Error(String(error));
}
/**
* Returns an appropriate message for the error.
*
* If the error is an {@link Error} instance, this returns the error message without
* an `Error: ` prefix.
*/
export function getErrorMessage(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
/**
* Decides whether `e` is a disk-related error outside of our control
* that should be classified as a `ConfigurationError`.
*
* @param e The error to check.
* @returns True if the error should be treated as a `ConfigurationError` or false if not.
*/
export function isDiskConfigurationError(e: unknown): boolean {
if (!(e instanceof Error)) {
return false;
}
return (
// out of disk space
e.message.includes("ENOSPC") ||
// access denied
e.message.includes("EACCES")
);
}
/**
* An Error class that indicates an error that occurred due to
* a misconfiguration of the action or the CodeQL CLI.
*/
export class ConfigurationError extends Error {}
/** Minimal representation of errors resulting from HTTP requests. */
export class HTTPError extends Error {
public status: number;
constructor(message: string, status: number) {
super(message);
this.status = status;
}
}
/**
* Tries to convert {@link arg} to a {@link HTTPError}, but returns `undefined` if
* {@link arg} is not like a {@link HTTPError}.
*/
export function asHTTPError(arg: any): HTTPError | undefined {
if (!json.isObject<any>(arg) || !json.isString(arg.message)) {
return undefined;
}
if (Number.isInteger(arg.status)) {
return new HTTPError(arg.message, arg.status as number);
}
// See https://github.com/actions/toolkit/blob/acb230b99a46ed33a3f04a758cd68b47b9a82908/packages/tool-cache/src/tool-cache.ts#L19
if (Number.isInteger(arg.httpStatusCode)) {
return new HTTPError(arg.message, arg.httpStatusCode as number);
}
return undefined;
}
+3 -2
View File
@@ -4,6 +4,7 @@ import * as path from "path";
import test from "ava";
import * as defaults from "./defaults.json";
import { getEnv } from "./environment";
import {
Feature,
featureConfig,
@@ -26,12 +27,12 @@ import {
setupTests,
stubFeatureFlagApiEndpoint,
} from "./testing-utils";
import { GitHubVariant, initializeEnvironment, withTmpDir } from "./util";
import { GitHubVariant, withTmpDir } from "./util";
setupTests(test);
test.beforeEach(() => {
initializeEnvironment("1.2.3");
getEnv().initialise("1.2.3");
});
test.serial(
+40 -1
View File
@@ -74,6 +74,7 @@ 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",
@@ -105,6 +106,19 @@ export enum Feature {
OverlayAnalysisGo = "overlay_analysis_go",
OverlayAnalysisJava = "overlay_analysis_java",
OverlayAnalysisJavascript = "overlay_analysis_javascript",
/**
* When set, chooses the default CodeQL CLI version as the highest version that is both enabled by
* feature flags and present as an overlay-base database in the Actions cache for the configured
* languages. Falls back to the highest feature flagged version if no intersecting overlay-base
* database exists in the cache.
*/
OverlayAnalysisMatchCodeqlVersion = "overlay_analysis_match_codeql_version",
/**
* Like `OverlayAnalysisMatchCodeqlVersion`, but only logs a diagnostic with the version that
* would have been chosen instead of actually changing the default CodeQL CLI version.
* `OverlayAnalysisMatchCodeqlVersion` overrides this flag.
*/
OverlayAnalysisMatchCodeqlVersionDryRun = "overlay_analysis_match_codeql_version_dry_run",
/**
* Lowers the overlay minimum available disk space to 8 GB. The lowest enabled limit wins; if
* none are enabled, the default applies.
@@ -143,6 +157,11 @@ export enum Feature {
OverlayAnalysisStatusCheck = "overlay_analysis_status_check",
/** Controls whether overlay build failures on the default branch are stored in the Actions cache. */
OverlayAnalysisStatusSave = "overlay_analysis_status_save",
/**
* Controls whether we may download a bundle containing only the single language being analysed,
* rather than the combined bundle that contains every language.
*/
PerLanguageBundles = "per_language_bundles_v2",
QaTelemetryEnabled = "qa_telemetry_enabled",
/** Routes (some) API requests through the registry proxy. */
ProxyApiRequests = "proxy_api_requests",
@@ -195,6 +214,11 @@ 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",
@@ -343,6 +367,16 @@ export const featureConfig = {
minimumVersion: CODEQL_OVERLAY_MINIMUM_VERSION_RUBY,
},
// Other overlay-related feature flags
[Feature.OverlayAnalysisMatchCodeqlVersion]: {
defaultValue: false,
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_MATCH_CODEQL_VERSION",
minimumVersion: undefined,
},
[Feature.OverlayAnalysisMatchCodeqlVersionDryRun]: {
defaultValue: false,
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_MATCH_CODEQL_VERSION_DRY_RUN",
minimumVersion: undefined,
},
[Feature.OverlayAnalysisMinDisk8Gb]: {
defaultValue: false,
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_MIN_DISK_8_GB",
@@ -388,6 +422,11 @@ export const featureConfig = {
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_SKIP_RESOURCE_CHECKS",
minimumVersion: undefined,
},
[Feature.PerLanguageBundles]: {
defaultValue: false,
envVar: "CODEQL_ACTION_PER_LANGUAGE_BUNDLES",
minimumVersion: undefined,
},
[Feature.QaTelemetryEnabled]: {
defaultValue: false,
envVar: "CODEQL_ACTION_QA_TELEMETRY",
@@ -543,7 +582,7 @@ class OfflineFeatures implements FeatureEnablement {
// Never use this feature if the CLI version explicitly can't support it.
const minimumVersion = config.minimumVersion;
if (codeql && minimumVersion) {
if (!(await util.codeQlVersionAtLeast(codeql, minimumVersion))) {
if (!(await codeql.isVersionAtLeast(minimumVersion))) {
this.logger.debug(
`Feature ${feature} is disabled because the CodeQL CLI version is older than the minimum ` +
`version ${minimumVersion}.`,
+2 -2
View File
@@ -8,7 +8,7 @@ import {
LoggedMessage,
setupTests,
} from "../testing-utils";
import { GitHubVariant, initializeEnvironment, withTmpDir } from "../util";
import { getEnv, GitHubVariant, withTmpDir } from "../util";
import {
assertAllFeaturesHaveDefaultValues,
@@ -18,7 +18,7 @@ import {
setupTests(test);
test.beforeEach(() => {
initializeEnvironment("1.2.3");
getEnv().initialise("1.2.3");
});
test("OfflineFeatures makes no API requests", async (t) => {
+9 -8
View File
@@ -2,6 +2,7 @@ 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";
@@ -18,7 +19,7 @@ test.serial(
url: "",
data: {},
});
const logger = new RecordingLogger();
const logger = getRunnerLogger(true);
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
await t.throwsAsync(
properties.loadPropertiesFromApi(logger, mockRepositoryNwo),
@@ -38,7 +39,7 @@ test.serial(
url: "",
data: [{}],
});
const logger = new RecordingLogger();
const logger = getRunnerLogger(true);
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
await t.throwsAsync(
properties.loadPropertiesFromApi(logger, mockRepositoryNwo),
@@ -62,7 +63,7 @@ test.serial(
{ property_name: "also-not-used-by-us", value: ["A", "B", "C"] },
],
});
const logger = new RecordingLogger();
const logger = getRunnerLogger(true);
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
await t.notThrowsAsync(
properties.loadPropertiesFromApi(logger, mockRepositoryNwo),
@@ -85,7 +86,7 @@ test.serial("loadPropertiesFromApi loads known properties", async (t) => {
{ property_name: "unknown-property", value: "something" },
] satisfies properties.GitHubPropertiesResponse,
});
const logger = new RecordingLogger();
const logger = getRunnerLogger(true);
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
const response = await properties.loadPropertiesFromApi(
logger,
@@ -112,7 +113,7 @@ test.serial("loadPropertiesFromApi parses true boolean property", async (t) => {
{ property_name: "github-codeql-extra-queries", value: "+queries" },
] satisfies properties.GitHubPropertiesResponse,
});
const logger = new RecordingLogger();
const logger = getRunnerLogger(true);
const warningSpy = sinon.spy(logger, "warning");
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
const response = await properties.loadPropertiesFromApi(
@@ -140,7 +141,7 @@ test.serial(
},
] satisfies properties.GitHubPropertiesResponse,
});
const logger = new RecordingLogger();
const logger = getRunnerLogger(true);
const warningSpy = sinon.spy(logger, "warning");
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
const response = await properties.loadPropertiesFromApi(
@@ -163,7 +164,7 @@ test.serial(
url: "",
data: [{ property_name: "github-codeql-extra-queries", value: 123 }],
});
const logger = new RecordingLogger();
const logger = getRunnerLogger(true);
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
await t.throwsAsync(
properties.loadPropertiesFromApi(logger, mockRepositoryNwo),
@@ -189,7 +190,7 @@ test.serial(
},
] satisfies properties.GitHubPropertiesResponse,
});
const logger = new RecordingLogger();
const logger = getRunnerLogger(true);
const warningSpy = sinon.spy(logger, "warning");
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
const response = await properties.loadPropertiesFromApi(
+2 -3
View File
@@ -8,12 +8,11 @@ import {
FeatureWithoutCLI,
initFeatures,
} from "../feature-flags";
import type { Logger } from "../logging";
import { getRunnerLogger } from "../logging";
import { parseRepositoryNwo } from "../repository";
import {
LoggedMessage,
mockCodeQLVersion,
RecordingLogger,
setupActionsVars,
} from "../testing-utils";
import { ToolsFeature } from "../tools-features";
@@ -52,7 +51,7 @@ export function assertAllFeaturesUndefinedInApi(
export function setUpFeatureFlagTests(
tmpDir: string,
logger: Logger = new RecordingLogger(),
logger = getRunnerLogger(true),
gitHubVersion = { type: GitHubVariant.DOTCOM } as util.GitHubVersion,
): FeatureEnablement {
setupActionsVars(tmpDir, tmpDir);
+5 -4
View File
@@ -5,8 +5,9 @@ import * as ava from "ava";
import test from "ava";
import * as fingerprints from "./fingerprints";
import { getRunnerLogger } from "./logging";
import * as sarif from "./sarif";
import { RecordingLogger, setupTests } from "./testing-utils";
import { setupTests } from "./testing-utils";
import * as util from "./util";
setupTests(test);
@@ -130,7 +131,7 @@ function testResolveUriToFile(uri: any, index: any, artifactsURIs: any[]) {
location,
artifacts,
process.cwd(),
new RecordingLogger(),
getRunnerLogger(true),
);
}
@@ -217,7 +218,7 @@ test("addFingerprints", async (t) => {
await fingerprints.addFingerprints(
input,
sourceRoot,
new RecordingLogger(),
getRunnerLogger(true),
),
expected,
);
@@ -245,7 +246,7 @@ test("missingRegions", async (t) => {
await fingerprints.addFingerprints(
input,
sourceRoot,
new RecordingLogger(),
getRunnerLogger(true),
),
expected,
);
+15 -1
View File
@@ -1,5 +1,19 @@
import { ActionsEnvVars, type ReadOnlyEnv } from "./environment";
import { ConfigurationError } from "./util";
import { ConfigurationError } from "./errors";
export enum GitHubVariant {
/** [GitHub.com](https://github.com) */
DOTCOM = "GitHub.com",
/** [GitHub Enterprise Server](https://docs.github.com/en/enterprise-server@latest/admin/overview/about-github-enterprise-server) */
GHES = "GitHub Enterprise Server",
/** [GitHub Enterprise Cloud with data residency](https://docs.github.com/en/enterprise-cloud@latest/admin/data-residency/about-github-enterprise-cloud-with-data-residency) */
GHEC_DR = "GitHub Enterprise Cloud with data residency",
}
export type GitHubVersion =
| { type: GitHubVariant.DOTCOM }
| { type: GitHubVariant.GHEC_DR }
| { type: GitHubVariant.GHES; version: string };
/** A string representing a GitHub instance address. */
export type InstanceURL = string;
+11 -56
View File
@@ -1,6 +1,3 @@
import * as fs from "fs";
import * as path from "path";
import * as core from "@actions/core";
import test, { ExecutionContext } from "ava";
import * as sinon from "sinon";
@@ -11,10 +8,10 @@ import * as apiClient from "./api-client";
import * as codeql from "./codeql";
import * as configUtils from "./config-utils";
import * as debugArtifacts from "./debug-artifacts";
import { getTempDiagnosticsRoot } from "./diagnostics";
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";
@@ -23,7 +20,6 @@ import {
createFeatures,
createTestConfig,
DEFAULT_ACTIONS_VARS,
getCodeQLForTesting,
getTestEnv,
makeMacro,
makeVersionInfo,
@@ -39,47 +35,6 @@ const NUM_BYTES_PER_GIB = 1024 * 1024 * 1024;
setupTests(test);
test.serial("generateFailedSarif picks up unwritten diagnostics", async (t) =>
util.withTmpDir(async (tmpDir) => {
const logger = new RecordingLogger();
const tempDiagDir = getTempDiagnosticsRoot(tmpDir);
fs.mkdirSync(tempDiagDir, { recursive: true });
const codeqlObject = await getCodeQLForTesting();
const diagnosticsExport = sinon
.stub(codeqlObject, "diagnosticsExport")
.resolves();
sinon.stub(codeql, "getCodeQL").resolves(codeqlObject);
const sarifFile = path.join(tmpDir, "failed.sarif");
const result = await initActionPostHelper.generateFailedSarif(
logger,
createFeatures([Feature.ExportDiagnosticsEnabled]),
createTestConfig({
tempDir: tmpDir,
// Explicitly undefined to ensure we get the "no database" path.
dbLocation: undefined,
}),
"category",
"",
sarifFile,
);
t.truthy(result);
t.is(diagnosticsExport.callCount, 1);
t.true(
diagnosticsExport.calledOnceWith(
sarifFile,
"category",
sinon.match.any,
tempDiagDir,
),
);
}),
);
test.serial("init-post action with debug mode off", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
@@ -107,7 +62,7 @@ test.serial("init-post action with debug mode off", async (t) => {
createFeatures([]),
"success",
getTestEnv(),
new RecordingLogger(),
getRunnerLogger(true),
);
t.assert(uploadAllAvailableDebugArtifactsSpy.notCalled);
@@ -131,7 +86,7 @@ test.serial("init-post action with debug mode on", async (t) => {
createFeatures([]),
"success",
getTestEnv(),
new RecordingLogger(),
getRunnerLogger(true),
);
t.assert(uploadAllAvailableDebugArtifactsSpy.called);
@@ -428,7 +383,7 @@ test.serial(
createFeatures([Feature.OverlayAnalysisStatusSave]),
"success",
getTestEnv(),
new RecordingLogger(),
getRunnerLogger(true),
);
t.true(
@@ -498,7 +453,7 @@ test.serial(
createFeatures([]),
"success",
getTestEnv(),
new RecordingLogger(),
getRunnerLogger(true),
);
t.true(
@@ -542,7 +497,7 @@ test.serial("does not save overlay status when build successful", async (t) => {
createFeatures([Feature.OverlayAnalysisStatusSave]),
"success",
env,
new RecordingLogger(),
getRunnerLogger(true),
);
t.true(
@@ -581,7 +536,7 @@ test.serial(
createFeatures([]),
"success",
getTestEnv(),
new RecordingLogger(),
getRunnerLogger(true),
);
t.true(
@@ -634,7 +589,7 @@ async function runOverlayPostStep({
createFeatures([Feature.OverlayAnalysisStatusSave]),
jobStatus,
env,
new RecordingLogger(),
getRunnerLogger(true),
);
return { saveOverlayStatusStub };
@@ -782,7 +737,7 @@ async function testFailedSarifUpload(
.withArgs("matrix")
.returns(JSON.stringify(matrix));
const codeqlObject = await getCodeQLForTesting();
const codeqlObject = await codeql.getCodeQLForTesting();
sinon.stub(codeql, "getCodeQL").resolves(codeqlObject);
sinon.stub(codeqlObject, "getVersion").resolves(makeVersionInfo("2.17.6"));
const databaseExportDiagnosticsStub = sinon.stub(
@@ -809,7 +764,7 @@ async function testFailedSarifUpload(
config,
parseRepositoryNwo("github/codeql-action"),
createFeatures(features),
new RecordingLogger(),
getRunnerLogger(true),
);
if (expectUpload) {
t.deepEqual(result, {
@@ -878,7 +833,7 @@ async function mockRiskAssessmentEnv(matrix: string) {
.stub(apiClient, "getGitHubVersion")
.resolves({ type: util.GitHubVariant.GHES, version: "3.0.0" });
const codeqlObject = await getCodeQLForTesting();
const codeqlObject = await codeql.getCodeQLForTesting();
const databaseExportDiagnostics = sinon
.stub(codeqlObject, "databaseExportDiagnostics")
.resolves();
+6 -31
View File
@@ -24,7 +24,6 @@ import {
type UploadDebugArtifactsConfig,
} from "./debug-artifacts";
import * as dependencyCaching from "./dependency-caching";
import { getTempDiagnosticsRoot } from "./diagnostics";
import { EnvVar, ReadOnlyEnv } from "./environment";
import { Feature, FeatureEnablement } from "./feature-flags";
import { Logger } from "./logging";
@@ -178,16 +177,7 @@ async function prepareFailedSarif(
}
}
/**
* Generates a SARIF file for a failed run, i.e. an analysis where no SARIF was produced as a result of the analysis.
*
* Not intended to be called directly, other than by `prepareFailedSarif`.
*
* @param category The analysis category to include in the SARIF.
* @param checkoutPath The path where the repository was checked out at.
* @param sarifFile The SARIF file to produce.
*/
export async function generateFailedSarif(
async function generateFailedSarif(
logger: Logger,
features: FeatureEnablement,
config: ActionConfig<"codeQLCmd" | "tempDir">,
@@ -204,26 +194,11 @@ export async function generateFailedSarif(
}
// If there is no database or the feature flag is off, we run 'export diagnostics'
const exportDiagnosticsEnabled = await features.getValue(
Feature.ExportDiagnosticsEnabled,
codeql,
);
if (databasePath === undefined || !exportDiagnosticsEnabled) {
// If we don't have a database path, then we may have diagnostics that were written to a
// temporary location. Get the path that they would have been written to and check whether
// it exists. If so, pass it on to the CLI. The CLI will not complain if we give it a
// path that doesn't exist, so we are just being extra defensive here.
// We also don't pass the path to the CLI if `exportDiagnosticsEnabled` is not enabled, to preserve
// its intended behaviour of controlling whether diagnostics are included in the SARIF or not.
let diagnosticDir: string | undefined = getTempDiagnosticsRoot(
config.tempDir,
);
if (!exportDiagnosticsEnabled || !fs.existsSync(diagnosticDir)) {
diagnosticDir = undefined;
}
await codeql.diagnosticsExport(sarifFile, category, config, diagnosticDir);
if (
databasePath === undefined ||
!(await features.getValue(Feature.ExportDiagnosticsEnabled, codeql))
) {
await codeql.diagnosticsExport(sarifFile, category, config);
} else {
// We call 'database export-diagnostics' to find any per-database diagnostics.
await codeql.databaseExportDiagnostics(databasePath, sarifFile, category);
+7 -11
View File
@@ -40,7 +40,7 @@ import {
addDiagnostic,
addNoLanguageDiagnostic,
flushDiagnostics,
logTemporaryDiagnostics,
logUnwrittenDiagnostics,
makeDiagnostic,
makeTelemetryDiagnostic,
} from "./diagnostics";
@@ -88,12 +88,10 @@ import {
checkDiskUsage,
checkForTimeout,
checkGitHubVersionInRange,
codeQlVersionAtLeast,
DEFAULT_DEBUG_ARTIFACT_NAME,
DEFAULT_DEBUG_DATABASE_NAME,
getCodeQLMemoryLimit,
getThreadsFlagValue,
initializeEnvironment,
ConfigurationError,
wrapError,
checkActionVersion,
@@ -226,7 +224,7 @@ async function run(
let toolsVersion: string;
try {
initializeEnvironment(getActionVersion());
actionState.env.initialise(getActionVersion());
// Make inputs accessible in the `post` step.
persistInputs();
@@ -484,7 +482,6 @@ async function run(
if (statusReportBase !== undefined) {
await sendStatusReport(statusReportBase);
}
logTemporaryDiagnostics(actionState);
return;
}
@@ -687,7 +684,7 @@ async function run(
`${EnvVar.JAVA_EXTRACTOR_MINIMIZE_DEPENDENCY_JARS} is already set to '${process.env[EnvVar.JAVA_EXTRACTOR_MINIMIZE_DEPENDENCY_JARS]}', so the Action will not override it.`,
);
} else if (
(await codeQlVersionAtLeast(codeql, CODEQL_VERSION_JAR_MINIMIZATION)) &&
(await codeql.isVersionAtLeast(CODEQL_VERSION_JAR_MINIMIZATION)) &&
config.dependencyCachingEnabled &&
config.buildMode === BuildMode.None &&
config.languages.includes(BuiltInLanguage.java)
@@ -767,15 +764,14 @@ async function run(
);
}
// Move diagnostics to the database that we previously kept in a temporary location
// because the database did not exist until now
flushDiagnostics(actionState, config);
// Write diagnostics to the database that we previously stored in memory because the database
// did not exist until now.
flushDiagnostics(config);
// We save the config here instead of at the end of `initConfig` because we
// may have updated the config returned from `initConfig`, e.g. to revert to
// `OverlayDatabaseMode.None` if we failed to download an overlay-base
// database.
config.successfullyInitialised = true;
await configUtils.saveConfig(config, logger);
core.setOutput("codeql-path", config.codeQLCmd);
@@ -799,7 +795,7 @@ async function run(
);
return;
} finally {
logTemporaryDiagnostics(actionState);
logUnwrittenDiagnostics();
}
await sendCompletedStatusReport(
startedAt,
-17
View File
@@ -1,17 +0,0 @@
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");
});
+25 -72
View File
@@ -1,92 +1,45 @@
import * as core from "@actions/core";
/** 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.
*/
debug: (message: string) => void;
info: (message: string) => void;
warning: (message: string | Error) => void;
error: (message: string | Error) => void;
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 = <T>(
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 = <T>(fn: (msg: string | T) => void) => {
return (message: string[] | string | T) => {
return fn(joinMessageStrings(message));
};
};
export function getActionsLogger(): Logger {
return {
debug: autoJoinMessageStrings(core.debug),
info: autoJoinMessageStrings(core.info),
warning: autoJoinMessageStrings(core.warning),
error: autoJoinMessageStrings(core.error),
debug: core.debug,
info: core.info,
warning: core.warning,
error: 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<T>(groupName: string, f: () => T): T {
core.startGroup(groupName);
try {
+10 -10
View File
@@ -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 = new RecordingLogger();
const logger = getRunnerLogger(true);
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 = new RecordingLogger();
const logger = getRunnerLogger(true);
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 = new RecordingLogger();
const logger = getRunnerLogger(true);
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 = new RecordingLogger();
const logger = getRunnerLogger(true);
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 = new RecordingLogger();
const logger = getRunnerLogger(true);
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 = new RecordingLogger();
const logger = getRunnerLogger(true);
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 = new RecordingLogger();
const logger = getRunnerLogger(true);
// 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 = new RecordingLogger();
const logger = getRunnerLogger(true);
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 = new RecordingLogger();
const logger = getRunnerLogger(true);
sinon.stub(apiClient, "getAutomationID").resolves("test-automation-id/");
sinon.stub(apiClient, "listActionsCaches").resolves([
+6 -9
View File
@@ -6,11 +6,8 @@ import * as sinon from "sinon";
import * as actionsUtil from "../actions-util";
import * as gitUtils from "../git-utils";
import {
createTestConfig,
RecordingLogger,
setupTests,
} from "../testing-utils";
import { getRunnerLogger } from "../logging";
import { createTestConfig, setupTests } from "../testing-utils";
import { withTmpDir } from "../util";
import { writeBaseDatabaseOidsFile, writeOverlayChangesFile } from ".";
@@ -30,7 +27,7 @@ test.serial(
),
);
const logger = new RecordingLogger();
const logger = getRunnerLogger(true);
const config = createTestConfig({ dbLocation });
// Mock the getFileOidsUnderPath function to return base OIDs
@@ -94,7 +91,7 @@ test.serial(
),
);
const logger = new RecordingLogger();
const logger = getRunnerLogger(true);
const config = createTestConfig({ dbLocation });
// Mock the getFileOidsUnderPath function to return base OIDs
@@ -172,7 +169,7 @@ test.serial(
),
);
const logger = new RecordingLogger();
const logger = getRunnerLogger(true);
const config = createTestConfig({ dbLocation });
// Mock the getFileOidsUnderPath function to return base OIDs
@@ -235,7 +232,7 @@ test.serial(
),
);
const logger = new RecordingLogger();
const logger = getRunnerLogger(true);
const config = createTestConfig({ dbLocation });
// Base OIDs (sourceRoot-relative paths)
+28
View File
@@ -1,6 +1,7 @@
import test from "ava";
import { ActionsEnvVars } from "./environment";
import { Feature } from "./feature-flags";
import { BuiltInLanguage } from "./languages";
import {
getOtherLanguagePacksReason,
@@ -11,6 +12,7 @@ import {
} from "./per-language-bundles";
import { BundlePlatform } from "./platform";
import {
createFeatures,
getRecordingLogger,
getTestEnv,
initAllState,
@@ -38,6 +40,7 @@ async function checkEligibility(
env: getTestEnv({
[ActionsEnvVars.RUNNER_ENVIRONMENT]: "github-hosted",
}),
features: createFeatures([Feature.PerLanguageBundles]),
...stateOverrides,
}),
{ ...ELIGIBLE_OPTIONS, ...overrides },
@@ -142,6 +145,27 @@ test("getPerLanguageBundleLanguage requires a supported release version", async
t.is(await checkEligibility({ cliVersion: "2.27.1" }), BuiltInLanguage.java);
});
test("getPerLanguageBundleLanguage requires the feature flag", async (t) => {
t.is(await checkEligibility({}, { features: createFeatures([]) }), undefined);
});
test("getPerLanguageBundleLanguage explains a disabled feature before checking eligibility", async (t) => {
const messages: LoggedMessage[] = [];
const language = await getPerLanguageBundleLanguage(
initAllState({
features: createFeatures([]),
logger: getRecordingLogger(messages, { logToConsole: false }),
}),
{ ...ELIGIBLE_OPTIONS, rawLanguages: undefined, cliVersion: undefined },
);
t.is(language, undefined);
t.deepEqual(
messages.map((message) => message.message),
["Not using a per-language CodeQL bundle since the feature is disabled."],
);
});
test("getPerLanguageBundleLanguage skips only the release version check for the latest nightly", async (t) => {
const nightly = { isLatestNightly: true, cliVersion: undefined };
t.is(await checkEligibility(nightly), BuiltInLanguage.java);
@@ -157,6 +181,10 @@ test("getPerLanguageBundleLanguage skips only the release version check for the
]) {
t.is(await checkEligibility({ ...nightly, ...overrides }), undefined);
}
t.is(
await checkEligibility(nightly, { features: createFeatures([]) }),
undefined,
);
t.is(
await checkEligibility(nightly, {
env: getTestEnv({ [ActionsEnvVars.RUNNER_ENVIRONMENT]: "self-hosted" }),
+14 -7
View File
@@ -9,6 +9,7 @@ import {
QuerySpec,
UserConfig,
} from "./config/db-config";
import { Feature } from "./feature-flags";
import { RepositoryPropertyName } from "./feature-flags/properties";
import { BuiltInLanguage, parseBuiltInLanguage } from "./languages";
import { BundlePlatform } from "./platform";
@@ -138,10 +139,14 @@ export interface PerLanguageBundleOptions {
}
/** Returns the eligible bundle language, or undefined for the combined bundle. */
export function getPerLanguageBundleLanguage(
action: ActionState<["Logger", "ReadOnlyEnv"]>,
export async function getPerLanguageBundleLanguage(
{
env,
features,
logger,
}: ActionState<["Logger", "ReadOnlyEnv", "FeatureFlags"]>,
options: PerLanguageBundleOptions,
): BuiltInLanguage | undefined {
): Promise<BuiltInLanguage | undefined> {
const {
rawLanguages,
otherLanguagePacksReason,
@@ -153,12 +158,14 @@ export function getPerLanguageBundleLanguage(
} = options;
const explain = (reason: string) => {
action.logger.debug(
`Not using a per-language CodeQL bundle since ${reason}.`,
);
logger.debug(`Not using a per-language CodeQL bundle since ${reason}.`);
return undefined;
};
if (!(await features.getValue(Feature.PerLanguageBundles))) {
return explain("the feature is disabled");
}
// A defined reason means the CodeQL CLI may need packs for other languages, for example because
// of the configured queries. That applies whichever languages were requested, so check it first
// to avoid suggesting that requesting a single language would be enough.
@@ -190,7 +197,7 @@ export function getPerLanguageBundleLanguage(
return explain(`we are running against ${variant}`);
}
if (!isGitHubHostedRunner(action.env)) {
if (!isGitHubHostedRunner(env)) {
// Per-language installs stay out of the toolcache; self-hosted runners should retain
// the reusable combined bundle instead.
return explain("the job is not running on a GitHub-hosted runner");
+1 -2
View File
@@ -38,7 +38,6 @@ import {
checkDiskUsage,
checkForTimeout,
checkGitHubVersionInRange,
initializeEnvironment,
ConfigurationError,
wrapError,
checkActionVersion,
@@ -108,7 +107,7 @@ async function run(
let toolsVersion: string;
try {
initializeEnvironment(getActionVersion());
actionState.env.initialise(getActionVersion());
const apiDetails = {
auth: getRequiredInput("token"),
+198 -200
View File
@@ -11,9 +11,16 @@ import * as sinon from "sinon";
import * as actionsUtil from "./actions-util";
import * as api from "./api-client";
import * as diagnostics from "./diagnostics";
import { ActionsEnvVars, Env, EnvVar, ReadOnlyEnv } from "./environment";
import {
ActionsEnvVars,
Env,
EnvVar,
getEnv,
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";
@@ -21,7 +28,6 @@ import * as tar from "./tar";
import {
LINKED_CLI_VERSION,
LoggedMessage,
RecordingLogger,
SAMPLE_DEFAULT_CLI_VERSION,
SAMPLE_DOTCOM_API_DETAILS,
checkExpectedLogMessages,
@@ -37,18 +43,12 @@ import {
setupTests,
} from "./testing-utils";
import * as toolsDownload from "./tools-download";
import {
getErrorMessage,
GitHubVariant,
HTTPError,
initializeEnvironment,
withTmpDir,
} from "./util";
import { getErrorMessage, GitHubVariant, HTTPError, withTmpDir } from "./util";
setupTests(test);
test.beforeEach(() => {
initializeEnvironment("1.2.3");
getEnv().initialise("1.2.3");
});
function stubDownloadAndExtract() {
@@ -117,7 +117,7 @@ test.serial("convert to semver", (t) => {
try {
const parsedVersion = setupCodeql.convertToSemVer(
version,
new RecordingLogger(),
getRunnerLogger(true),
);
t.deepEqual(parsedVersion, expectedVersion);
} catch (e) {
@@ -145,7 +145,7 @@ test.serial(
GitHubVariant.DOTCOM,
false,
features,
new RecordingLogger(),
getRunnerLogger(true),
);
t.deepEqual(source, {
@@ -231,7 +231,7 @@ for (const {
GitHubVariant.DOTCOM,
tarSupportsZstd,
features,
new RecordingLogger(),
getRunnerLogger(true),
);
t.is(source.toolsVersion, LINKED_CLI_VERSION.cliVersion);
@@ -439,7 +439,7 @@ test.serial(
tmpDir,
GitHubVariant.DOTCOM,
SAMPLE_DEFAULT_CLI_VERSION,
undefined, // rawLanguages
["javascript"],
undefined, // otherLanguagePacksReason
false, // useOverlayAwareDefaultCliVersion
features,
@@ -597,7 +597,7 @@ test.serial(
undefined, // otherLanguagePacksReason
false, // useOverlayAwareDefaultCliVersion
createFeatures([]),
new RecordingLogger(),
getRunnerLogger(true),
);
t.is(result.toolsVersion, "local");
@@ -633,8 +633,8 @@ for (const toolsInput of ["nightly", "nightly-latest"]) {
SAMPLE_DOTCOM_API_DETAILS,
GitHubVariant.DOTCOM,
true, // tarSupportsZstd
createFeatures([]),
new RecordingLogger(),
createFeatures([Feature.PerLanguageBundles]),
getRunnerLogger(true),
);
t.deepEqual(source, {
@@ -664,16 +664,24 @@ test.serial(
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
for (const { languages, otherLanguagePacksReason } of [
for (const { languages, otherLanguagePacksReason, features } of [
// The per-language feature is disabled.
{
languages: ["java"],
otherLanguagePacksReason: undefined,
features: createFeatures([]),
},
// More than one language requires a combined bundle.
{
languages: ["java", "python"],
otherLanguagePacksReason: undefined,
features: createFeatures([Feature.PerLanguageBundles]),
},
// The configured queries may need library packs for other languages.
{
languages: ["java"],
otherLanguagePacksReason: "an example reason",
features: createFeatures([Feature.PerLanguageBundles]),
},
]) {
const source = await setupCodeql.getCodeQLSource(
@@ -685,8 +693,8 @@ test.serial(
SAMPLE_DOTCOM_API_DETAILS,
GitHubVariant.DOTCOM,
true, // tarSupportsZstd
createFeatures([]),
new RecordingLogger(),
features,
getRunnerLogger(true),
);
t.is(source.sourceType, "download");
@@ -701,48 +709,58 @@ test.serial(
},
);
test.serial(
"getCodeQLSource uses the latest per-language bundle for a forced nightly",
async (t) => {
const expectedTag = "codeql-bundle-30260213";
const baseURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}`;
const latestNightlyRequest = stubHostedNightly(expectedTag);
for (const perLanguageBundles of [false, true]) {
test.serial(
`getCodeQLSource uses the latest ${perLanguageBundles ? "per-language" : "combined"} bundle for a forced nightly`,
async (t) => {
const expectedTag = "codeql-bundle-30260213";
const baseURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}`;
const latestNightlyRequest = stubHostedNightly(expectedTag);
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir, { GITHUB_EVENT_NAME: "dynamic" });
const source = await setupCodeql.getCodeQLSource(
undefined, // toolsInput: the nightly is selected by ForceNightly
SAMPLE_DEFAULT_CLI_VERSION,
["java"],
undefined, // otherLanguagePacksReason
false, // useOverlayAwareDefaultCliVersion
SAMPLE_DOTCOM_API_DETAILS,
GitHubVariant.DOTCOM,
true, // tarSupportsZstd
createFeatures([Feature.ForceNightly]),
new RecordingLogger(),
);
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir, { GITHUB_EVENT_NAME: "dynamic" });
const source = await setupCodeql.getCodeQLSource(
undefined, // toolsInput: the nightly is selected by ForceNightly
SAMPLE_DEFAULT_CLI_VERSION,
["java"],
undefined, // otherLanguagePacksReason
false, // useOverlayAwareDefaultCliVersion
SAMPLE_DOTCOM_API_DETAILS,
GitHubVariant.DOTCOM,
true, // tarSupportsZstd
createFeatures(
perLanguageBundles
? [Feature.ForceNightly, Feature.PerLanguageBundles]
: [Feature.ForceNightly],
),
getRunnerLogger(true),
);
t.is(source.sourceType, "download");
t.true(latestNightlyRequest.calledOnce);
if (source.sourceType === "download") {
t.deepEqual(source.bundle, {
kind: "per-language",
language: BuiltInLanguage.java,
url: `${baseURL}/codeql-bundle-java-linux64.tar.zst`,
combinedBundleURL: `${baseURL}/codeql-bundle-linux64.tar.zst`,
});
}
});
},
);
t.is(source.sourceType, "download");
t.true(latestNightlyRequest.calledOnce);
if (source.sourceType === "download") {
const combinedURL = `${baseURL}/codeql-bundle-linux64.tar.zst`;
t.deepEqual(
source.bundle,
perLanguageBundles
? {
kind: "per-language",
language: BuiltInLanguage.java,
url: `${baseURL}/codeql-bundle-java-linux64.tar.zst`,
combinedBundleURL: combinedURL,
}
: { kind: "combined", url: combinedURL },
);
}
});
},
);
}
for (const date of ["20200101", "30260213"]) {
for (const bundle of ["combined", "per-language"] as const) {
test.serial(
`getCodeQLSource preserves an explicit ${bundle} nightly URL for ${date}${
bundle === "per-language" ? " on GHES" : ""
}`,
`getCodeQLSource preserves an explicit ${bundle} nightly URL for ${date}`,
async (t) => {
const latestNightlyRequest = stubHostedNightly(
"codeql-bundle-30260213",
@@ -752,9 +770,7 @@ for (const date of ["20200101", "30260213"]) {
? "codeql-bundle-linux64.tar.zst"
: "codeql-bundle-java-linux64.tar.zst";
const url = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/codeql-bundle-${date}/${asset}`;
const features = createFeatures([]);
const variant =
bundle === "per-language" ? GitHubVariant.GHES : GitHubVariant.DOTCOM;
const features = createFeatures([Feature.PerLanguageBundles]);
const logger = getRecordingLogger([], { logToConsole: false });
const error = new HTTPError("Not Found", 404);
const extractStub = sinon
@@ -770,7 +786,7 @@ for (const date of ["20200101", "30260213"]) {
undefined, // otherLanguagePacksReason
false,
SAMPLE_DOTCOM_API_DETAILS,
variant,
GitHubVariant.DOTCOM,
true,
features,
logger,
@@ -792,7 +808,7 @@ for (const date of ["20200101", "30260213"]) {
url,
SAMPLE_DOTCOM_API_DETAILS,
tmpDir,
variant,
GitHubVariant.DOTCOM,
SAMPLE_DEFAULT_CLI_VERSION,
["java"],
undefined, // otherLanguagePacksReason
@@ -832,7 +848,7 @@ test.serial(
GitHubVariant.DOTCOM,
true,
createFeatures([]),
new RecordingLogger(),
getRunnerLogger(true),
),
{
message:
@@ -1004,7 +1020,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",
new RecordingLogger(),
getRunnerLogger(true),
),
"codeql-bundle-v2.19.0",
);
@@ -1016,7 +1032,7 @@ test.serial(
(t) => {
sinon.stub(toolcache, "findAllVersions").returns([]);
t.is(
setupCodeql.getLatestToolcacheVersion(new RecordingLogger()),
setupCodeql.getLatestToolcacheVersion(getRunnerLogger(true)),
undefined,
);
},
@@ -1028,7 +1044,7 @@ test.serial(
const testVersions = ["2.3.1", "3.2.1", "1.2.3"];
sinon.stub(toolcache, "findAllVersions").returns(testVersions);
t.is(setupCodeql.getLatestToolcacheVersion(new RecordingLogger()), "3.2.1");
t.is(setupCodeql.getLatestToolcacheVersion(getRunnerLogger(true)), "3.2.1");
},
);
@@ -1054,7 +1070,7 @@ async function fakeOverlayBaseCacheKey(
}
test.serial(
"getCodeQLSource uses overlay-aware default version for a Code Scanning PR",
"getCodeQLSource uses overlay-aware default version when requested for a PR",
async (t) => {
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
@@ -1081,8 +1097,8 @@ test.serial(
SAMPLE_DOTCOM_API_DETAILS,
GitHubVariant.DOTCOM,
false,
createFeatures([]),
new RecordingLogger(),
createFeatures([Feature.OverlayAnalysisMatchCodeqlVersion]),
getRunnerLogger(true),
);
t.assert(listStub.calledOnce);
@@ -1093,95 +1109,7 @@ test.serial(
);
test.serial(
"getCodeQLSource skips the Actions cache when only one default version is enabled",
async (t) => {
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
process.env[EnvVar.CODE_SCANNING_REF] = "refs/heads/feature-branch";
process.env[EnvVar.CODE_SCANNING_BASE_BRANCH] = "main";
sinon.stub(api, "getAutomationID").resolves("test/");
const listStub = sinon.stub(api, "listActionsCaches").resolves([
{
key: await fakeOverlayBaseCacheKey(
"javascript",
SAMPLE_DEFAULT_CLI_VERSION.enabledVersions[0].cliVersion,
"abc-1-1",
),
},
]);
sinon
.stub(toolcache, "find")
.withArgs(
"CodeQL",
SAMPLE_DEFAULT_CLI_VERSION.enabledVersions[0].cliVersion,
)
.returns("/path/to/codeql");
const source = await setupCodeql.getCodeQLSource(
undefined,
SAMPLE_DEFAULT_CLI_VERSION,
["javascript"],
undefined, // otherLanguagePacksReason
true,
SAMPLE_DOTCOM_API_DETAILS,
GitHubVariant.DOTCOM,
false,
createFeatures([]),
new RecordingLogger(),
);
t.assert(listStub.notCalled);
t.is(source.sourceType, "toolcache");
t.is(
source.toolsVersion,
SAMPLE_DEFAULT_CLI_VERSION.enabledVersions[0].cliVersion,
);
});
},
);
test.serial(
"getCodeQLSource skips overlay-aware default version outside pull request analysis",
async (t) => {
sinon.stub(actionsUtil, "isAnalyzingPullRequest").returns(false);
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
sinon.stub(api, "getAutomationID").resolves("test/");
const listStub = sinon.stub(api, "listActionsCaches").resolves([
{
key: await fakeOverlayBaseCacheKey("javascript", "2.20.1", "abc-1-1"),
},
]);
sinon
.stub(toolcache, "find")
.withArgs("CodeQL", "2.20.2")
.returns("/path/to/codeql-2.20.2");
const source = await setupCodeql.getCodeQLSource(
undefined,
overlayMatchEnabledVersions,
["javascript"],
undefined, // otherLanguagePacksReason
true,
SAMPLE_DOTCOM_API_DETAILS,
GitHubVariant.DOTCOM,
false,
createFeatures([]),
new RecordingLogger(),
);
t.assert(listStub.notCalled);
t.is(source.sourceType, "toolcache");
t.is(source.toolsVersion, "2.20.2");
});
},
);
test.serial(
"getCodeQLSource skips overlay-aware default version when not requested for Code Scanning",
"getCodeQLSource skips overlay-aware default version when not requested",
async (t) => {
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
@@ -1208,8 +1136,8 @@ test.serial(
SAMPLE_DOTCOM_API_DETAILS,
GitHubVariant.DOTCOM,
false,
createFeatures([]),
new RecordingLogger(),
createFeatures([Feature.OverlayAnalysisMatchCodeqlVersion]),
getRunnerLogger(true),
);
t.assert(listStub.notCalled);
@@ -1246,8 +1174,8 @@ test.serial(
SAMPLE_DOTCOM_API_DETAILS,
GitHubVariant.DOTCOM,
true, // tarSupportsZstd
createFeatures([]),
new RecordingLogger(),
createFeatures([Feature.PerLanguageBundles]),
getRunnerLogger(true),
);
t.is(source.sourceType, "download");
@@ -1279,16 +1207,13 @@ test.serial(
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
for (const { otherLanguagePacksReason, variant } of [
// Automatic per-language selection is limited to GitHub.com.
{
otherLanguagePacksReason: undefined,
variant: GitHubVariant.GHES,
},
for (const { otherLanguagePacksReason, features } of [
// The per-language feature is disabled.
{ otherLanguagePacksReason: undefined, features: createFeatures([]) },
// The configured queries may need library packs for other languages.
{
otherLanguagePacksReason: "an example reason",
variant: GitHubVariant.DOTCOM,
features: createFeatures([Feature.PerLanguageBundles]),
},
]) {
const source = await setupCodeql.getCodeQLSource(
@@ -1298,10 +1223,10 @@ test.serial(
otherLanguagePacksReason,
false, // useOverlayAwareDefaultCliVersion
SAMPLE_DOTCOM_API_DETAILS,
variant,
GitHubVariant.DOTCOM,
true, // tarSupportsZstd
createFeatures([]),
new RecordingLogger(),
features,
getRunnerLogger(true),
);
t.is(source.sourceType, "download");
@@ -1370,8 +1295,8 @@ for (const fallback of [false, true]) {
["java"],
undefined, // otherLanguagePacksReason
false, // useOverlayAwareDefaultCliVersion
createFeatures([]),
new RecordingLogger(),
createFeatures([Feature.PerLanguageBundles]),
getRunnerLogger(true),
);
t.true(fetchRelease.calledTwice);
@@ -1450,7 +1375,7 @@ for (const bundle of ["per-language", "combined", "fallback"] as const) {
};
});
const addDiagnostic = sinon.stub(diagnostics, "addNoLanguageDiagnostic");
const features = createFeatures([]);
const features = createFeatures([Feature.PerLanguageBundles]);
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
@@ -1635,8 +1560,8 @@ for (const error of [
["java"],
undefined, // otherLanguagePacksReason
false, // useOverlayAwareDefaultCliVersion
createFeatures([]),
new RecordingLogger(),
createFeatures([Feature.PerLanguageBundles]),
getRunnerLogger(true),
),
{ is: error },
);
@@ -1672,7 +1597,7 @@ test.serial(
undefined, // otherLanguagePacksReason
false, // useOverlayAwareDefaultCliVersion
createFeatures([]),
new RecordingLogger(),
getRunnerLogger(true),
),
{ is: error },
);
@@ -1683,7 +1608,7 @@ test.serial(
);
test.serial(
"getEnabledVersionsWithOverlayBaseDatabases returns enabled versions present in cache, sorted desc",
"getEnabledVersionsWithOverlayBaseDatabases returns flag-enabled versions present in cache, sorted desc",
async (t) => {
sinon.stub(api, "getAutomationID").resolves("test/");
sinon.stub(api, "listActionsCaches").resolves([
@@ -1701,24 +1626,21 @@ test.serial(
},
]);
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
const result =
await setupCodeql.getEnabledVersionsWithOverlayBaseDatabases(
overlayMatchEnabledVersions,
["javascript"],
new RecordingLogger(),
);
t.deepEqual(result, [
{ cliVersion: "2.20.1", tagName: "codeql-bundle-v2.20.1" },
{ cliVersion: "2.20.0", tagName: "codeql-bundle-v2.20.0" },
]);
});
const result = await setupCodeql.getEnabledVersionsWithOverlayBaseDatabases(
overlayMatchEnabledVersions,
["javascript"],
createFeatures([Feature.OverlayAnalysisMatchCodeqlVersion]),
getRunnerLogger(true),
);
t.deepEqual(result, [
{ cliVersion: "2.20.1", tagName: "codeql-bundle-v2.20.1" },
{ cliVersion: "2.20.0", tagName: "codeql-bundle-v2.20.0" },
]);
},
);
test.serial(
"getEnabledVersionsWithOverlayBaseDatabases returns empty when no cached version is enabled",
"getEnabledVersionsWithOverlayBaseDatabases returns empty when no cached version is flag-enabled",
async (t) => {
sinon.stub(api, "getAutomationID").resolves("test/");
sinon.stub(api, "listActionsCaches").resolves([
@@ -1730,7 +1652,8 @@ test.serial(
const result = await setupCodeql.getEnabledVersionsWithOverlayBaseDatabases(
overlayMatchEnabledVersions,
["javascript"],
new RecordingLogger(),
createFeatures([Feature.OverlayAnalysisMatchCodeqlVersion]),
getRunnerLogger(true),
);
t.deepEqual(result, []);
},
@@ -1746,7 +1669,8 @@ const noLanguagesMacro = makeMacro({
const result = await setupCodeql.getEnabledVersionsWithOverlayBaseDatabases(
overlayMatchEnabledVersions,
rawLanguages,
new RecordingLogger(),
createFeatures([Feature.OverlayAnalysisMatchCodeqlVersion]),
getRunnerLogger(true),
);
t.deepEqual(result, []);
t.assert(
@@ -1770,7 +1694,8 @@ test.serial(
const result = await setupCodeql.getEnabledVersionsWithOverlayBaseDatabases(
overlayMatchEnabledVersions,
["javascript"],
new RecordingLogger(),
createFeatures([Feature.OverlayAnalysisMatchCodeqlVersion]),
getRunnerLogger(true),
);
t.deepEqual(result, []);
},
@@ -1789,7 +1714,8 @@ test.serial(
const result = await setupCodeql.getEnabledVersionsWithOverlayBaseDatabases(
overlayMatchEnabledVersions,
["javascript"],
new RecordingLogger(),
createFeatures([Feature.OverlayAnalysisMatchCodeqlVersion]),
getRunnerLogger(true),
);
t.deepEqual(result, [
{ cliVersion: "2.20.2", tagName: "codeql-bundle-v2.20.2" },
@@ -1797,6 +1723,78 @@ test.serial(
},
);
test.serial(
"getEnabledVersionsWithOverlayBaseDatabases does not list caches when both gates are off",
async (t) => {
const listStub = sinon.stub(api, "listActionsCaches").resolves([]);
const result = await setupCodeql.getEnabledVersionsWithOverlayBaseDatabases(
overlayMatchEnabledVersions,
["javascript"],
createFeatures([]),
getRunnerLogger(true),
);
t.deepEqual(result, []);
t.assert(
listStub.notCalled,
"Should not list Actions caches when both gating feature flags are off.",
);
},
);
test.serial(
"getEnabledVersionsWithOverlayBaseDatabases dry-run returns empty but lists caches",
async (t) => {
sinon.stub(api, "getAutomationID").resolves("test/");
const listStub = sinon.stub(api, "listActionsCaches").resolves([
{
key: await fakeOverlayBaseCacheKey("javascript", "2.20.1", "abc-1-1"),
},
]);
const result = await setupCodeql.getEnabledVersionsWithOverlayBaseDatabases(
overlayMatchEnabledVersions,
["javascript"],
createFeatures([Feature.OverlayAnalysisMatchCodeqlVersionDryRun]),
getRunnerLogger(true),
);
t.deepEqual(
result,
[],
"Dry-run should return an empty list so the caller falls back.",
);
t.assert(
listStub.calledOnce,
"Dry-run should still list Actions caches to populate the diagnostic.",
);
},
);
test.serial(
"getEnabledVersionsWithOverlayBaseDatabases match flag wins over dry-run",
async (t) => {
sinon.stub(api, "getAutomationID").resolves("test/");
sinon.stub(api, "listActionsCaches").resolves([
{
key: await fakeOverlayBaseCacheKey("javascript", "2.20.1", "abc-1-1"),
},
]);
const result = await setupCodeql.getEnabledVersionsWithOverlayBaseDatabases(
overlayMatchEnabledVersions,
["javascript"],
createFeatures([
Feature.OverlayAnalysisMatchCodeqlVersion,
Feature.OverlayAnalysisMatchCodeqlVersionDryRun,
]),
getRunnerLogger(true),
);
t.deepEqual(result, [
{ cliVersion: "2.20.1", tagName: "codeql-bundle-v2.20.1" },
]);
},
);
/** The CLI version that the toolcache cleanup tests download. */
const CLEANUP_CLI_VERSION = "2.21.0";
/** The bundle version that the toolcache cleanup tests download. */
@@ -2095,7 +2093,7 @@ test.serial(
env: new ReadOnlyEnv({
[ActionsEnvVars.RUNNER_TOOL_CACHE]: injectedRoot,
}),
logger: new RecordingLogger(),
logger: getRunnerLogger(true),
});
t.deepEqual(result, {
@@ -2250,7 +2248,7 @@ test.serial(
undefined, // otherLanguagePacksReason
false, // useOverlayAwareDefaultCliVersion
createFeatures([]),
new RecordingLogger(),
getRunnerLogger(true),
);
t.is(
@@ -2290,7 +2288,7 @@ test.serial(
undefined, // otherLanguagePacksReason
false, // useOverlayAwareDefaultCliVersion
createFeatures([]),
new RecordingLogger(),
getRunnerLogger(true),
),
{ is: error },
);
@@ -2405,7 +2403,7 @@ test.serial(
"isToolcacheOnWorkspaceFilesystem assumes a different filesystem when it cannot tell",
async (t) => {
await withTmpDir(async (tmpDir) => {
const logger = new RecordingLogger();
const logger = getRunnerLogger(true);
setupActionsVars(tmpDir, tmpDir);
t.true(toolsDownload.isToolcacheOnWorkspaceFilesystem(logger));
+71 -22
View File
@@ -183,16 +183,29 @@ async function findOverridingToolsInCache(
/**
* Returns the sorted set of enabled versions that have cached overlay-base databases for the
* given languages.
* given languages, or an empty list if neither the `OverlayAnalysisMatchCodeqlVersion` nor the
* `OverlayAnalysisMatchCodeqlVersionDryRun` feature flag is enabled. When only the dry-run flag
* is enabled, this performs the lookup and emits a telemetry diagnostic with the version that
* would have been chosen, but still returns an empty list so the caller falls back.
*/
export async function getEnabledVersionsWithOverlayBaseDatabases(
defaultCliVersion: CodeQLDefaultVersionInfo,
rawLanguages: string[] | undefined,
features: FeatureEnablement,
logger: Logger,
): Promise<CodeQLVersionInfo[]> {
if (rawLanguages === undefined || rawLanguages.length === 0) {
return [];
}
const isEnabled = await features.getValue(
Feature.OverlayAnalysisMatchCodeqlVersion,
);
const isDryRun =
!isEnabled &&
(await features.getValue(Feature.OverlayAnalysisMatchCodeqlVersionDryRun));
if (!isEnabled && !isDryRun) {
return [];
}
let cachedVersions: string[] | undefined;
try {
@@ -213,35 +226,66 @@ export async function getEnabledVersionsWithOverlayBaseDatabases(
}
const cachedVersionsSet = new Set(cachedVersions);
return defaultCliVersion.enabledVersions.filter((v) =>
const overlayVersions = defaultCliVersion.enabledVersions.filter((v) =>
cachedVersionsSet.has(v.cliVersion),
);
if (overlayVersions.length === 0) {
return [];
}
const isCachedVersionDifferent =
overlayVersions[0].cliVersion !==
defaultCliVersion.enabledVersions[0].cliVersion;
if (isCachedVersionDifferent) {
addNoLanguageDiagnostic(
undefined,
makeTelemetryDiagnostic(
"codeql-action/overlay-aware-default-codeql-version",
"Overlay-aware default CodeQL version selection",
{
cachedVersions,
enabledVersions: defaultCliVersion.enabledVersions.map(
(v) => v.cliVersion,
),
isDryRun,
overlayAwareVersion: overlayVersions[0].cliVersion,
},
),
);
}
if (isDryRun) {
logger.debug(
`Overlay-aware default CodeQL version selection is running in dry-run mode. Would have used version ${overlayVersions[0].cliVersion}.`,
);
return [];
}
return overlayVersions;
}
/**
* Resolves the newest enabled default CLI version that has a cached overlay-base database for the
* relevant languages, if this is a Code Scanning analysis of a pull request, multiple default
* versions are enabled, and such a version exists. Otherwise, falls back to the newest enabled
* default CLI version.
* relevant languages, if running a Code Scanning analysis for a pull request and one exists.
* Otherwise, falls back to the newest enabled default CLI version.
*/
async function resolveDefaultCliVersion(
defaultCliVersion: CodeQLDefaultVersionInfo,
rawLanguages: string[] | undefined,
useOverlayAwareDefaultCliVersion: boolean,
features: FeatureEnablement,
logger: Logger,
): Promise<CodeQLVersionInfo> {
if (
!useOverlayAwareDefaultCliVersion ||
// Listing the Actions cache can't change which version we use if only one version is enabled.
defaultCliVersion.enabledVersions.length === 1 ||
!isAnalyzingPullRequest()
) {
if (!useOverlayAwareDefaultCliVersion || !isAnalyzingPullRequest()) {
return defaultCliVersion.enabledVersions[0];
}
const overlayVersions = await getEnabledVersionsWithOverlayBaseDatabases(
defaultCliVersion,
rawLanguages,
features,
logger,
);
if (overlayVersions.length > 0) {
@@ -364,7 +408,7 @@ export async function getCodeQLSource(
);
}
bundle = await getLatestNightlyBundle(
{ env: getEnv(), logger },
{ env: getEnv(), features, logger },
rawLanguages,
otherLanguagePacksReason,
variant,
@@ -438,6 +482,7 @@ export async function getCodeQLSource(
defaultCliVersion,
rawLanguages,
useOverlayAwareDefaultCliVersion,
features,
logger,
);
cliVersion = version.cliVersion;
@@ -460,6 +505,7 @@ export async function getCodeQLSource(
defaultCliVersion,
rawLanguages,
useOverlayAwareDefaultCliVersion,
features,
logger,
);
cliVersion = version.cliVersion;
@@ -605,14 +651,17 @@ export async function getCodeQLSource(
const action = { env: getEnv(), logger };
const platform = getBundlePlatform();
const perLanguageBundleLanguage = getPerLanguageBundleLanguage(action, {
rawLanguages,
otherLanguagePacksReason,
cliVersion,
compressionMethod,
platform,
variant,
});
const perLanguageBundleLanguage = await getPerLanguageBundleLanguage(
{ ...action, features },
{
rawLanguages,
otherLanguagePacksReason,
cliVersion,
compressionMethod,
platform,
variant,
},
);
// Resolves the combined or per-language bundle URL for the requested release.
const resolveBundleURL = (language?: BuiltInLanguage) =>
@@ -1040,7 +1089,7 @@ function getTempExtractionDir(tempDir: string) {
* Records the combined bundle URL from that release for use if the selected asset is missing.
*/
async function getLatestNightlyBundle(
action: ActionState<["Logger", "ReadOnlyEnv"]>,
action: ActionState<["Logger", "ReadOnlyEnv", "FeatureFlags"]>,
rawLanguages: string[] | undefined,
otherLanguagePacksReason: string | undefined,
variant: util.GitHubVariant,
@@ -1056,7 +1105,7 @@ async function getLatestNightlyBundle(
: "gzip";
const platform = getBundlePlatform();
const language = getPerLanguageBundleLanguage(action, {
const language = await getPerLanguageBundleLanguage(action, {
rawLanguages,
otherLanguagePacksReason,
cliVersion: undefined,
+1 -2
View File
@@ -6,7 +6,6 @@ import * as sinon from "sinon";
import * as actionsUtil from "../actions-util";
import { ActionsEnvVars } from "../environment";
import { callee, getTestEnv, setupTests } from "../testing-utils";
import { initializeEnvironment } from "../util";
import { getCodeQLActionRepository } from "./repository";
@@ -16,7 +15,7 @@ test.serial("getCodeQLActionRepository", async (t) => {
const initialEnv = getTestEnv({
[ActionsEnvVars.RUNNER_TEMP]: path.dirname(__dirname),
});
initializeEnvironment("1.2.3", initialEnv);
initialEnv.initialise("1.2.3");
const target = callee(getCodeQLActionRepository)
.withArgs()
+1 -2
View File
@@ -2,13 +2,12 @@ import { ActionState } from "../action-common";
import { isRunningLocalAction } from "../actions-util";
import * as api from "../api-client";
import { ActionsEnvVars } from "../environment";
import { GITHUB_DOTCOM_URL } from "../github";
import { GITHUB_DOTCOM_URL, type InstanceURL } from "../github";
import {
isRepositoryNwoStr,
parseRepositoryNwo,
type RepositoryNwoStr,
} from "../repository";
import { type InstanceURL } from "../util";
/** The NWO of the standard CodeQL Action repository. */
export const CODEQL_DEFAULT_ACTION_REPOSITORY = "github/codeql-action";
+20 -20
View File
@@ -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 { Logger } from "./logging";
import { getRunnerLogger, 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(
new RecordingLogger(),
getRunnerLogger(true),
registrySecrets,
registryCredentials,
undefined,
@@ -84,7 +84,7 @@ test("getCredentials throws an error when configurations are not an array", asyn
t.throws(
() =>
startProxyExports.getCredentials(
new RecordingLogger(),
getRunnerLogger(true),
undefined,
registryCredentials,
undefined,
@@ -103,7 +103,7 @@ test("getCredentials throws error when credential is not an object", async (t) =
t.throws(
() =>
startProxyExports.getCredentials(
new RecordingLogger(),
getRunnerLogger(true),
undefined,
testCredential,
undefined,
@@ -124,7 +124,7 @@ test("getCredentials throws error when credential is missing type", async (t) =>
t.throws(
() =>
startProxyExports.getCredentials(
new RecordingLogger(),
getRunnerLogger(true),
undefined,
testCredential,
undefined,
@@ -147,7 +147,7 @@ test("getCredentials throws error when credential missing host and url", async (
t.throws(
() =>
startProxyExports.getCredentials(
new RecordingLogger(),
getRunnerLogger(true),
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(
new RecordingLogger(),
getRunnerLogger(true),
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(
new RecordingLogger(),
getRunnerLogger(true),
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(
new RecordingLogger(),
getRunnerLogger(true),
undefined,
toEncodedJSON(multipleGoproxyServers),
BuiltInLanguage.go,
@@ -221,7 +221,7 @@ test("getCredentials returns all maven_repositories for Java when specified", as
];
const credentials = startProxyExports.getCredentials(
new RecordingLogger(),
getRunnerLogger(true),
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(
new RecordingLogger(),
getRunnerLogger(true),
undefined,
credentialsInput,
undefined,
@@ -269,7 +269,7 @@ test("getCredentials throws an error when non-printable characters are used", as
t.throws(
() =>
startProxyExports.getCredentials(
new RecordingLogger(),
getRunnerLogger(true),
undefined,
credentialsInput,
undefined,
@@ -300,7 +300,7 @@ for (const oidcSchemaInfo of startProxyExports.oidcSchemas) {
t.throws(
() =>
startProxyExports.getCredentials(
new RecordingLogger(),
getRunnerLogger(true),
undefined,
credentialsInput,
undefined,
@@ -324,7 +324,7 @@ test("getCredentials accepts OIDC configurations", (t) => {
);
const credentials = startProxyExports.getCredentials(
new RecordingLogger(),
getRunnerLogger(true),
undefined,
toEncodedJSON(oidcConfigurations),
BuiltInLanguage.csharp,
@@ -514,7 +514,7 @@ test("getCredentials validates 'replaces-base' correctly", async (t) => {
]);
const credentials = startProxyExports.getCredentials(
new RecordingLogger(),
getRunnerLogger(true),
undefined,
credentialsInput,
BuiltInLanguage.java,
@@ -533,7 +533,7 @@ test("getCredentials validates 'replaces-base' correctly", async (t) => {
};
t.throws(() =>
startProxyExports.getCredentials(
new RecordingLogger(),
getRunnerLogger(true),
undefined,
toEncodedJSON([{ ...baseInvalid, "replaces-base": null }]),
BuiltInLanguage.java,
@@ -541,7 +541,7 @@ test("getCredentials validates 'replaces-base' correctly", async (t) => {
);
t.throws(() =>
startProxyExports.getCredentials(
new RecordingLogger(),
getRunnerLogger(true),
undefined,
toEncodedJSON([{ ...baseInvalid, "replaces-base": 123 }]),
BuiltInLanguage.java,
@@ -549,7 +549,7 @@ test("getCredentials validates 'replaces-base' correctly", async (t) => {
);
t.throws(() =>
startProxyExports.getCredentials(
new RecordingLogger(),
getRunnerLogger(true),
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(
new RecordingLogger(),
getRunnerLogger(true),
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(
new RecordingLogger(),
getRunnerLogger(true),
undefined,
credentialsInput,
language,
+11 -10
View File
@@ -6,6 +6,7 @@ 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 {
@@ -153,7 +154,7 @@ test.serial("createStatusReportBase", async (t) => {
languages: [BuiltInLanguage.java, BuiltInLanguage.swift],
}),
{ numAvailableBytes: 100, numTotalBytes: 500 },
new RecordingLogger(),
getRunnerLogger(false),
"failure cause",
"exception stack trace",
);
@@ -202,7 +203,7 @@ test.serial("createStatusReportBase - empty configuration", async (t) => {
new Date("May 19, 2023 05:19:00"),
{},
{ numAvailableBytes: 100, numTotalBytes: 500 },
new RecordingLogger(),
getRunnerLogger(false),
);
if (t.truthy(statusReport)) {
@@ -224,7 +225,7 @@ test.serial("createStatusReportBase - partial configuration", async (t) => {
languages: ["go"],
},
{ numAvailableBytes: 100, numTotalBytes: 500 },
new RecordingLogger(),
getRunnerLogger(false),
);
if (t.truthy(statusReport)) {
@@ -247,7 +248,7 @@ test.serial("createStatusReportBase_firstParty", async (t) => {
new Date("May 19, 2023 05:19:00"),
createTestConfig({}),
{ numAvailableBytes: 100, numTotalBytes: 500 },
new RecordingLogger(),
getRunnerLogger(false),
"failure cause",
"exception stack trace",
)
@@ -263,7 +264,7 @@ test.serial("createStatusReportBase_firstParty", async (t) => {
new Date("May 19, 2023 05:19:00"),
createTestConfig({}),
{ numAvailableBytes: 100, numTotalBytes: 500 },
new RecordingLogger(),
getRunnerLogger(false),
"failure cause",
"exception stack trace",
)
@@ -280,7 +281,7 @@ test.serial("createStatusReportBase_firstParty", async (t) => {
new Date("May 19, 2023 05:19:00"),
createTestConfig({}),
{ numAvailableBytes: 100, numTotalBytes: 500 },
new RecordingLogger(),
getRunnerLogger(false),
"failure cause",
"exception stack trace",
)
@@ -296,7 +297,7 @@ test.serial("createStatusReportBase_firstParty", async (t) => {
new Date("May 19, 2023 05:19:00"),
createTestConfig({}),
{ numAvailableBytes: 100, numTotalBytes: 500 },
new RecordingLogger(),
getRunnerLogger(false),
"failure cause",
"exception stack trace",
)
@@ -313,7 +314,7 @@ test.serial("createStatusReportBase_firstParty", async (t) => {
new Date("May 19, 2023 05:19:00"),
createTestConfig({}),
{ numAvailableBytes: 100, numTotalBytes: 500 },
new RecordingLogger(),
getRunnerLogger(false),
"failure cause",
"exception stack trace",
)
@@ -329,7 +330,7 @@ test.serial("createStatusReportBase_firstParty", async (t) => {
new Date("May 19, 2023 05:19:00"),
createTestConfig({}),
{ numAvailableBytes: 100, numTotalBytes: 500 },
new RecordingLogger(),
getRunnerLogger(false),
"failure cause",
"exception stack trace",
)
@@ -410,7 +411,7 @@ const testCreateInitWithConfigStatusReport = makeMacro({
new Date("May 19, 2023 05:19:00"),
config,
{ numAvailableBytes: 100, numTotalBytes: 500 },
new RecordingLogger(),
getRunnerLogger(false),
"failure cause",
"exception stack trace",
);
+3 -2
View File
@@ -3,8 +3,9 @@ import * as stream from "stream";
import test from "ava";
import { getRunnerLogger } from "./logging";
import { extractTarZst } from "./tar";
import { RecordingLogger, setupTests } from "./testing-utils";
import { setupTests } from "./testing-utils";
import { withTmpDir } from "./util";
setupTests(test);
@@ -16,7 +17,7 @@ test("extractTarZst rejects if the input stream errors", async (t) => {
archive,
path.join(tmpDir, "dest"),
{ type: "gnu", version: "1.34" },
new RecordingLogger(),
getRunnerLogger(true),
);
archive.destroy(
+8 -22
View File
@@ -30,7 +30,7 @@ import {
featureConfig,
FeatureEnablement,
} from "./feature-flags";
import { joinMessageStrings, Loggable, LoggableError, Logger } from "./logging";
import { Logger } from "./logging";
import { OverlayDatabaseMode } from "./overlay/overlay-database-mode";
import { getBundlePlatform } from "./platform";
import { ActionName } from "./status-report";
@@ -658,13 +658,12 @@ export class RecordingLogger implements Logger {
constructor(private readonly logToConsole: boolean = true) {}
private addMessage(level: LogLevel, message: LoggableError): void {
const joinedMessage = joinMessageStrings(message);
this.messages.push({ type: level, message: joinedMessage });
private addMessage(level: LogLevel, message: string | Error): void {
this.messages.push({ type: level, message });
if (this.logToConsole) {
// eslint-disable-next-line no-console
console.debug(joinedMessage);
console.debug(message);
}
}
@@ -685,19 +684,19 @@ export class RecordingLogger implements Logger {
return true;
}
debug(message: Loggable) {
debug(message: string) {
this.addMessage("debug", message);
}
info(message: Loggable) {
info(message: string) {
this.addMessage("info", message);
}
warning(message: LoggableError) {
warning(message: string | Error) {
this.addMessage("warning", message);
}
error(message: LoggableError) {
error(message: string | Error) {
this.addMessage("error", message);
}
@@ -1032,7 +1031,6 @@ export function createTestConfig(overrides: Partial<Config>): Config {
overlayModeSetExplicitly: false,
repositoryProperties: {},
enableFileCoverageInformation: true,
successfullyInitialised: false,
} satisfies Config,
overrides,
);
@@ -1043,15 +1041,3 @@ 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<codeql.CodeQL> {
return codeql.getCodeQLForCmd(logger, cmd, false);
}
+10 -8
View File
@@ -10,8 +10,10 @@ import { https } from "follow-redirects";
import nock from "nock";
import * as sinon from "sinon";
import * as errors from "./errors";
import { getRunnerLogger } from "./logging";
import * as tar from "./tar";
import { RecordingLogger, setupTests } from "./testing-utils";
import { setupTests } from "./testing-utils";
import { downloadAndExtract } from "./tools-download";
import * as util from "./util";
@@ -33,7 +35,7 @@ test.serial(
undefined,
{},
undefined,
new RecordingLogger(),
getRunnerLogger(true),
);
t.assert(Number.isInteger(statusReport.downloadDurationMs));
@@ -71,7 +73,7 @@ test.serial(
undefined,
{},
{ type: "gnu", version: "1.34" },
new RecordingLogger(),
getRunnerLogger(true),
);
t.assert(Number.isInteger(statusReport.downloadDurationMs));
@@ -104,7 +106,7 @@ test.serial(
undefined,
{},
{ type: "gnu", version: "1.34" },
new RecordingLogger(),
getRunnerLogger(true),
),
{
instanceOf: util.HTTPError,
@@ -148,7 +150,7 @@ test.serial(
undefined,
{},
{ type: "gnu", version: "1.34" },
new RecordingLogger(),
getRunnerLogger(true),
);
t.assert(Number.isInteger(statusReport.downloadDurationMs));
@@ -163,7 +165,7 @@ test.serial(
test.serial(
"downloadAndExtract handles an unknown status as a non-HTTP error",
async (t) => {
const asHTTPError = sinon.spy(util, "asHTTPError");
const asHTTPError = sinon.spy(errors, "asHTTPError");
await util.withTmpDir(async (tmpDir) => {
sinon.stub(process, "platform").value("linux");
const archivePath = path.join(tmpDir, "codeql-bundle.tar.zst");
@@ -188,7 +190,7 @@ test.serial(
undefined,
{},
{ type: "gnu", version: "1.34" },
new RecordingLogger(),
getRunnerLogger(true),
);
t.is(
@@ -236,7 +238,7 @@ test.serial(
undefined,
{},
{ type: "gnu", version: "1.34" },
new RecordingLogger(),
getRunnerLogger(true),
);
t.assert(Number.isInteger(statusReport.totalDurationMs));
+1 -2
View File
@@ -4,12 +4,11 @@ import * as path from "path";
import test from "ava";
import * as sinon from "sinon";
import type { CodeQL } from "./codeql";
import { CodeQL, getCodeQLForTesting } from "./codeql";
import * as configUtils from "./config-utils";
import { BuiltInLanguage } from "./languages";
import {
createTestConfig,
getCodeQLForTesting,
makeVersionInfo,
RecordingLogger,
setupTests,
+83 -24
View File
@@ -6,26 +6,29 @@ 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 {
callee,
createFeatures,
createTestConfig,
getRecordingLogger,
makeVersionInfo,
setupTests,
} from "./testing-utils";
import {
cleanupTrapCaches,
downloadTrapCaches,
getLanguagesSupportingCaching,
uploadTrapCaches,
warnIfTrapCacheCleanupEnvVarIsSet,
} from "./trap-caching";
import * as util from "./util";
@@ -197,27 +200,83 @@ test.serial(
},
);
// `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"],
);
});
}
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);
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, []);
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),
);
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));
});
});
+114 -18
View File
@@ -3,14 +3,22 @@ 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 { getEnv, tryGetFolderBytes, waitForResultWithTimeLimit } from "./util";
import {
asHTTPError,
getEnv,
getErrorMessage,
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,
@@ -170,23 +178,111 @@ export async function uploadTrapCaches(
return true;
}
/**
* 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.",
);
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<TrapCacheCleanupStatusReport> {
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),
);
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<Array<Required<apiClient.ActionsCacheItem>>> {
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<Required<apiClient.ActionsCacheItem>>;
}
export async function getLanguagesSupportingCaching(
+14 -13
View File
@@ -10,17 +10,18 @@ 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 { Logger } from "./logging";
import { getEnv } from "./environment";
import { getRunnerLogger, Logger } from "./logging";
import * as sarif from "./sarif";
import { RecordingLogger, setupTests } from "./testing-utils";
import { setupTests } from "./testing-utils";
import * as uploadLib from "./upload-lib";
import { UploadPayload } from "./upload-lib/types";
import { GitHubVariant, initializeEnvironment, withTmpDir } from "./util";
import { GitHubVariant, withTmpDir } from "./util";
setupTests(test);
test.beforeEach(() => {
initializeEnvironment("1.2.3");
getEnv().initialise("1.2.3");
});
test.serial("validateSarifFileSchema - valid", (t) => {
@@ -29,7 +30,7 @@ test.serial("validateSarifFileSchema - valid", (t) => {
uploadLib.validateSarifFileSchema(
uploadLib.readSarifFileOrThrow(inputFile),
inputFile,
new RecordingLogger(),
getRunnerLogger(true),
),
);
});
@@ -40,7 +41,7 @@ test.serial("validateSarifFileSchema - invalid", (t) => {
uploadLib.validateSarifFileSchema(
uploadLib.readSarifFileOrThrow(inputFile),
inputFile,
new RecordingLogger(),
getRunnerLogger(true),
),
);
});
@@ -179,7 +180,7 @@ test.serial("finding SARIF files", async (t) => {
}
const groupedSarifFiles = await uploadLib.getGroupedSarifFilePaths(
new RecordingLogger(),
getRunnerLogger(true),
tmpDir,
);
@@ -200,7 +201,7 @@ test.serial("getGroupedSarifFilePaths - Risk Assessment files", async (t) => {
fs.writeFileSync(sarifPath, "");
const groupedSarifFiles = await uploadLib.getGroupedSarifFilePaths(
new RecordingLogger(),
getRunnerLogger(true),
sarifPath,
);
@@ -218,7 +219,7 @@ test.serial("getGroupedSarifFilePaths - Code Quality file", async (t) => {
fs.writeFileSync(sarifPath, "");
const groupedSarifFiles = await uploadLib.getGroupedSarifFilePaths(
new RecordingLogger(),
getRunnerLogger(true),
sarifPath,
);
@@ -236,7 +237,7 @@ test.serial("getGroupedSarifFilePaths - Code Scanning file", async (t) => {
fs.writeFileSync(sarifPath, "");
const groupedSarifFiles = await uploadLib.getGroupedSarifFilePaths(
new RecordingLogger(),
getRunnerLogger(true),
sarifPath,
);
@@ -254,7 +255,7 @@ test.serial("getGroupedSarifFilePaths - Other file", async (t) => {
fs.writeFileSync(sarifPath, "");
const groupedSarifFiles = await uploadLib.getGroupedSarifFilePaths(
new RecordingLogger(),
getRunnerLogger(true),
sarifPath,
);
@@ -932,7 +933,7 @@ function uploadPayloadFixtures(analysis: analyses.AnalysisConfig) {
owner: mockData.owner,
repo: mockData.repo,
},
new RecordingLogger(),
getRunnerLogger(true),
analysis,
);
@@ -1019,7 +1020,7 @@ function runFilterAlertsByDiffRange(
diffRanges: diffUtils.DiffThunkRange[],
): Partial<sarif.Log> {
sinon.stub(diffUtils, "readDiffRangesJsonFile").returns(diffRanges);
return uploadLib.filterAlertsByDiffRange(new RecordingLogger(), input);
return uploadLib.filterAlertsByDiffRange(getRunnerLogger(true), input);
}
test.serial(
+7 -6
View File
@@ -24,7 +24,6 @@ import {
ConfigurationError,
checkActionVersion,
checkDiskUsage,
initializeEnvironment,
shouldSkipSarifUpload,
wrapError,
} from "./util";
@@ -55,11 +54,13 @@ async function sendSuccessStatusReport(
}
}
async function run({ startedAt, logger }: ActionState<["Base", "Logger"]>) {
async function run(action: ActionState<["Base", "Env", "Logger"]>) {
// To capture errors appropriately, keep as much code within the try-catch as
// possible, and only use safe functions outside.
const logger = action.logger;
try {
initializeEnvironment(getActionVersion());
action.env.initialise(getActionVersion());
const gitHubVersion = await getGitHubVersion();
checkActionVersion(getActionVersion(), gitHubVersion);
@@ -79,7 +80,7 @@ async function run({ startedAt, logger }: ActionState<["Base", "Logger"]>) {
const startingStatusReportBase = await createStatusReportBase(
ActionName.UploadSarif,
"starting",
startedAt,
action.startedAt,
undefined,
await checkDiskUsage(logger),
logger,
@@ -137,7 +138,7 @@ async function run({ startedAt, logger }: ActionState<["Base", "Logger"]>) {
// so we can't wait for that here.
}
await sendSuccessStatusReport(
startedAt,
action.startedAt,
codeScanningResult?.statusReport || {},
logger,
);
@@ -153,7 +154,7 @@ async function run({ startedAt, logger }: ActionState<["Base", "Logger"]>) {
const errorStatusReportBase = await createStatusReportBase(
ActionName.UploadSarif,
getActionsStatus(error),
startedAt,
action.startedAt,
undefined,
await checkDiskUsage(logger),
logger,
+5 -9
View File
@@ -5,12 +5,8 @@ import test, { ExecutionContext } from "ava";
import * as sinon from "sinon";
import { AnalysisKind, getAnalysisConfig } from "./analyses";
import {
createFeatures,
makeMacro,
RecordingLogger,
setupTests,
} from "./testing-utils";
import { getRunnerLogger } from "./logging";
import { createFeatures, makeMacro, setupTests } from "./testing-utils";
import { UploadResult } from "./upload-lib";
import * as uploadLib from "./upload-lib";
import { postProcessAndUploadSarif } from "./upload-sarif";
@@ -56,7 +52,7 @@ const postProcessAndUploadSarifMacro = makeMacro({
expectedResult: Partial<Record<AnalysisKind, UploadSarifExpectedResult>>,
) => {
await util.withTmpDir(async (tempDir) => {
const logger = new RecordingLogger();
const logger = getRunnerLogger(true);
const testPath = sarifPath(tempDir);
const features = createFeatures([]);
@@ -214,7 +210,7 @@ test.serial(
"postProcessAndUploadSarif doesn't upload if upload is disabled",
async (t) => {
await util.withTmpDir(async (tempDir) => {
const logger = new RecordingLogger();
const logger = getRunnerLogger(true);
const features = createFeatures([]);
const toFullPath = (filename: string) => path.join(tempDir, filename);
@@ -248,7 +244,7 @@ test.serial(
"postProcessAndUploadSarif writes post-processed SARIF files if output directory is provided",
async (t) => {
await util.withTmpDir(async (tempDir) => {
const logger = new RecordingLogger();
const logger = getRunnerLogger(true);
const features = createFeatures([]);
const toFullPath = (filename: string) => path.join(tempDir, filename);
+9 -8
View File
@@ -10,7 +10,8 @@ import * as sinon from "sinon";
import * as api from "./api-client";
import { EnvVar } from "./environment";
import { RecordingLogger, setupTests, getTestEnv } from "./testing-utils";
import { getRunnerLogger } from "./logging";
import { getTestEnv, setupTests } from "./testing-utils";
import * as util from "./util";
setupTests(test);
@@ -91,7 +92,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, new RecordingLogger()));
t.throws(() => util.getMemoryFlag(input, getRunnerLogger(true)));
}
});
@@ -108,13 +109,13 @@ test("getThreadsFlag() should return the correct --threads flag", (t) => {
];
for (const [input, expectedFlag] of tests) {
const flag = util.getThreadsFlag(input, new RecordingLogger());
const flag = util.getThreadsFlag(input, getRunnerLogger(true));
t.deepEqual(flag, expectedFlag);
}
});
test("getThreadsFlag() throws if the threads input is not an integer", (t) => {
t.throws(() => util.getThreadsFlag("hello!", new RecordingLogger()));
t.throws(() => util.getThreadsFlag("hello!", getRunnerLogger(true)));
});
test.serial(
@@ -384,7 +385,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, new RecordingLogger()),
util.getCgroupCpuCountFromCpus(testCpuFile, getRunnerLogger(true)),
3,
);
});
@@ -395,7 +396,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, new RecordingLogger()),
util.getCgroupCpuCountFromCpus(testCpuFile, getRunnerLogger(true)),
undefined,
);
});
@@ -406,7 +407,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, new RecordingLogger()),
util.getCgroupCpuCountFromCpus(testCpuFile, getRunnerLogger(true)),
undefined,
);
});
@@ -416,7 +417,7 @@ test.serial(
"checkDiskUsage succeeds and produces positive numbers",
async (t) => {
process.env["GITHUB_WORKSPACE"] = os.tmpdir();
const diskUsage = await util.checkDiskUsage(new RecordingLogger());
const diskUsage = await util.checkDiskUsage(getRunnerLogger(true));
if (t.truthy(diskUsage)) {
t.true(diskUsage.numAvailableBytes > 0);
t.true(diskUsage.numTotalBytes > 0);
+6 -98
View File
@@ -20,20 +20,22 @@ import {
getRequiredEnvParam,
exportEnvVar,
getEnv,
type Env,
type ReadOnlyEnv,
} from "./environment";
import * as json from "./json";
import { Language } from "./languages";
import { Logger } from "./logging";
import { ConfigurationError, getErrorMessage, wrapError } from "./errors";
import { GitHubVariant, type GitHubVersion } from "./github";
import type { Language } from "./languages";
import type { Logger } from "./logging";
// Re-export for backwards compatibility to avoid updating a lot of imports elsewhere.
export * from "./errors";
export {
getRequiredEnvParam,
getOptionalEnvVar,
getEnv,
isInTestMode,
} from "./environment";
export { GitHubVariant, type GitHubVersion } from "./github";
/**
* The name of the file containing the base database OIDs, as stored in the
@@ -47,9 +49,6 @@ const BASE_DATABASE_OIDS_FILE_NAME = "base-database-oids.json";
*/
const BROKEN_VERSIONS = ["0.0.0-20211207"];
/** A string representing a GitHub instance. */
export type InstanceURL = string;
/**
* Default name of the debugging artifact.
*/
@@ -470,20 +469,6 @@ const CODEQL_ACTION_WARNED_ABOUT_VERSION_ENV_VAR =
let hasBeenWarnedAboutVersion = false;
export enum GitHubVariant {
/** [GitHub.com](https://github.com) */
DOTCOM = "GitHub.com",
/** [GitHub Enterprise Server](https://docs.github.com/en/enterprise-server@latest/admin/overview/about-github-enterprise-server) */
GHES = "GitHub Enterprise Server",
/** [GitHub Enterprise Cloud with data residency](https://docs.github.com/en/enterprise-cloud@latest/admin/data-residency/about-github-enterprise-cloud-with-data-residency) */
GHEC_DR = "GitHub Enterprise Cloud with data residency",
}
export type GitHubVersion =
| { type: GitHubVariant.DOTCOM }
| { type: GitHubVariant.GHEC_DR }
| { type: GitHubVariant.GHES; version: string };
export function checkGitHubVersionInRange(
version: GitHubVersion,
logger: Logger,
@@ -552,54 +537,6 @@ export function assertNever(value: never): never {
throw new ExhaustivityCheckingError(value);
}
/**
* Set some initial environment variables that we can set even without
* knowing what version of CodeQL we're running.
*/
export function initializeEnvironment(version: string, env: Env = getEnv()) {
env.export(EnvVar.FEATURE_MULTI_LANGUAGE, "false");
env.export(EnvVar.FEATURE_SANDWICH, "false");
env.export(EnvVar.FEATURE_SARIF_COMBINE, "true");
env.export(EnvVar.FEATURE_WILL_UPLOAD, "true");
env.export(EnvVar.VERSION, version);
}
export class HTTPError extends Error {
public status: number;
constructor(message: string, status: number) {
super(message);
this.status = status;
}
}
/**
* An Error class that indicates an error that occurred due to
* a misconfiguration of the action or the CodeQL CLI.
*/
export class ConfigurationError extends Error {}
export function asHTTPError(arg: any): HTTPError | undefined {
if (!json.isObject<any>(arg) || !json.isString(arg.message)) {
return undefined;
}
if (Number.isInteger(arg.status)) {
return new HTTPError(arg.message, arg.status as number);
}
// See https://github.com/actions/toolkit/blob/acb230b99a46ed33a3f04a758cd68b47b9a82908/packages/tool-cache/src/tool-cache.ts#L19
if (Number.isInteger(arg.httpStatusCode)) {
return new HTTPError(arg.message, arg.httpStatusCode as number);
}
return undefined;
}
export async function codeQlVersionAtLeast(
codeql: CodeQL,
requiredVersion: string,
): Promise<boolean> {
return semver.gte((await codeql.getVersion()).version, requiredVersion);
}
export function getBaseDatabaseOidsFilePath(
config: ActionConfig<"dbLocation">,
): string {
@@ -886,35 +823,6 @@ export function parseMatrixInput(
}
}
export function wrapError(error: unknown): Error {
return error instanceof Error ? error : new Error(String(error));
}
/**
* Returns an appropriate message for the error.
*
* If the error is an `Error` instance, this returns the error message without
* an `Error: ` prefix.
*/
export function getErrorMessage(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
/**
* Approximates whether {@link error} is like a {@link NodeJS.ErrnoException}.
* We only typically care about the `code` property, so that's what we check for here.
*/
export function isErrnoException(
error: unknown,
): error is NodeJS.ErrnoException {
return (
typeof error === "object" &&
error !== null &&
"code" in error &&
typeof error.code === "string"
);
}
export function prettyPrintPack(pack: Pack) {
return `${pack.name}${pack.version ? `@${pack.version}` : ""}${
pack.path ? `:${pack.path}` : ""
+1 -2
View File
@@ -3,11 +3,10 @@ import * as yaml from "js-yaml";
import * as sinon from "sinon";
import * as actionsUtil from "./actions-util";
import { createStubCodeQL } from "./codeql";
import { createStubCodeQL, getCodeQLForTesting } from "./codeql";
import { ActionsEnvVars, EnvVar } from "./environment";
import {
checkExpectedLogMessages,
getCodeQLForTesting,
createTestConfig,
getRecordingLogger,
getTestEnv,
@@ -1 +0,0 @@
- 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)