Compare commits

..

5 Commits

Author SHA1 Message Date
Michael B. Gale 9fe90880f7 Report undefined when FFs are unavailable 2026-01-28 13:14:57 +00:00
Michael B. Gale 1bfa34a866 Fix keys instead of values in test 2026-01-28 13:12:43 +00:00
Michael B. Gale 7159d66148 Fix typo in comment 2026-01-28 13:11:42 +00:00
Michael B. Gale f1779e26a8 Report queried feature flags in status reports 2026-01-28 12:57:45 +00:00
Michael B. Gale 9d2031faa4 Track outcomes of querying FFs with getValue 2026-01-28 12:03:10 +00:00
73 changed files with 164314 additions and 439440 deletions
+4 -13
View File
@@ -71,9 +71,8 @@ def open_pr(
body.append('')
body.append('Contains the following pull requests:')
for pr in pull_requests:
# Use PR author if they are GitHub staff, otherwise use the merger
display_user = get_pr_author_if_staff(pr) or get_merger_of_pr(repo, pr)
body.append(f'- #{pr.number} (@{display_user})')
merger = get_merger_of_pr(repo, pr)
body.append(f'- #{pr.number} (@{merger})')
# List all commits not part of a PR
if len(commits_without_pull_requests) > 0:
@@ -169,14 +168,6 @@ def get_pr_for_commit(commit):
def get_merger_of_pr(repo, pr):
return repo.get_commit(pr.merge_commit_sha).author.login
# Get the PR author if they are GitHub staff, otherwise None.
def get_pr_author_if_staff(pr):
if pr.user is None:
return None
if getattr(pr.user, 'site_admin', False):
return pr.user.login
return None
def get_current_version():
with open('package.json', 'r') as f:
return json.load(f)['version']
@@ -190,9 +181,9 @@ def replace_version_package_json(prev_version, new_version):
print(line.replace(prev_version, new_version), end='')
else:
prev_line_is_codeql = False
print(line, end='')
print(line, end='')
if '\"name\": \"codeql\",' in line:
prev_line_is_codeql = True
prev_line_is_codeql = True
def get_today_string():
today = datetime.datetime.today()
-69
View File
@@ -1,69 +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 - Bundle: From nightly'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GO111MODULE: auto
on:
push:
branches:
- main
- releases/v*
pull_request:
types:
- opened
- synchronize
- reopened
- ready_for_review
schedule:
- cron: '0 5 * * *'
workflow_dispatch:
inputs: {}
workflow_call:
inputs: {}
defaults:
run:
shell: bash
concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' || false }}
group: bundle-from-nightly-${{github.ref}}
jobs:
bundle-from-nightly:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
version: linked
name: 'Bundle: From nightly'
if: github.triggering_actor != 'dependabot[bot]'
permissions:
contents: read
security-events: read
timeout-minutes: 45
runs-on: ${{ matrix.os }}
steps:
- name: Check out repository
uses: actions/checkout@v6
- name: Prepare test
id: prepare-test
uses: ./.github/actions/prepare-test
with:
version: ${{ matrix.version }}
use-all-platform-bundle: 'false'
setup-kotlin: 'true'
- id: init
uses: ./../action/init
env:
CODEQL_ACTION_FORCE_NIGHTLY: true
with:
tools: ${{ steps.prepare-test.outputs.tools-url }}
languages: javascript
- name: Fail if the CodeQL version is not a nightly
if: "!contains(steps.init.outputs.codeql-version, '+')"
run: exit 1
env:
CODEQL_ACTION_TEST_MODE: true
+1 -1
View File
@@ -56,7 +56,7 @@ jobs:
use-all-platform-bundle: 'false'
setup-kotlin: 'true'
- name: Install @actions/tool-cache
run: npm install @actions/tool-cache@3
run: npm install @actions/tool-cache
- name: Check toolcache contains CodeQL
continue-on-error: true
uses: actions/github-script@v8
+1 -1
View File
@@ -68,7 +68,7 @@ jobs:
const codeqlPath = path.join(process.env['RUNNER_TOOL_CACHE'], 'CodeQL');
fs.rmdirSync(codeqlPath, { recursive: true });
- name: Install @actions/tool-cache
run: npm install @actions/tool-cache@3
run: npm install @actions/tool-cache
- name: Check toolcache does not contain CodeQL
uses: actions/github-script@v8
with:
+12
View File
@@ -48,6 +48,18 @@ jobs:
timeout-minutes: 45
runs-on: ${{ matrix.os }}
steps:
# These steps are required to initialise the `gh` cli in a container that doesn't
# come pre-installed with it. The reason for that is that this is later
# needed by the `prepare-test` workflow to find the latest release of CodeQL.
- name: Set up GitHub CLI
run: |
apt update
apt install -y curl libreadline8 gnupg2 software-properties-common zstd
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
apt-key add /usr/share/keyrings/githubcli-archive-keyring.gpg
apt-add-repository https://cli.github.com/packages
apt install -y gh
env: {}
- name: Check out repository
uses: actions/checkout@v6
- name: Prepare test
@@ -3,7 +3,7 @@
# pr-checks/sync.sh
# to regenerate this file.
name: PR Check - Analysis kinds
name: PR Check - Quality queries input
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GO111MODULE: auto
@@ -29,9 +29,9 @@ defaults:
shell: bash
concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' || false }}
group: analysis-kinds-${{github.ref}}
group: quality-queries-${{github.ref}}
jobs:
analysis-kinds:
quality-queries:
strategy:
fail-fast: false
matrix:
@@ -45,9 +45,6 @@ jobs:
- os: ubuntu-latest
version: linked
analysis-kinds: code-scanning,code-quality
- os: ubuntu-latest
version: linked
analysis-kinds: risk-assessment
- os: ubuntu-latest
version: nightly-latest
analysis-kinds: code-scanning
@@ -57,10 +54,7 @@ jobs:
- os: ubuntu-latest
version: nightly-latest
analysis-kinds: code-scanning,code-quality
- os: ubuntu-latest
version: nightly-latest
analysis-kinds: risk-assessment
name: Analysis kinds
name: Quality queries input
if: github.triggering_actor != 'dependabot[bot]'
permissions:
contents: read
@@ -87,24 +81,30 @@ jobs:
output: ${{ runner.temp }}/results
upload-database: false
post-processed-sarif-path: ${{ runner.temp }}/post-processed
- name: Upload SARIF files
- name: Upload security SARIF
if: contains(matrix.analysis-kinds, 'code-scanning')
uses: actions/upload-artifact@v6
with:
name: |
analysis-kinds-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}
path: ${{ runner.temp }}/results/*.sarif
quality-queries-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}.sarif.json
path: ${{ runner.temp }}/results/javascript.sarif
retention-days: 7
- name: Upload quality SARIF
if: contains(matrix.analysis-kinds, 'code-quality')
uses: actions/upload-artifact@v6
with:
name: |
quality-queries-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}.quality.sarif.json
path: ${{ runner.temp }}/results/javascript.quality.sarif
retention-days: 7
- name: Upload post-processed SARIF
uses: actions/upload-artifact@v6
with:
name: |
post-processed-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}
post-processed-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}.sarif.json
path: ${{ runner.temp }}/post-processed
retention-days: 7
if-no-files-found: error
- name: Check quality query does not appear in security SARIF
if: contains(matrix.analysis-kinds, 'code-scanning')
uses: actions/github-script@v8
@@ -122,7 +122,6 @@ jobs:
with:
script: ${{ env.CHECK_SCRIPT }}
env:
CODEQL_ACTION_RISK_ASSESSMENT_ID: 1
CHECK_SCRIPT: |
const fs = require('fs');
+1 -1
View File
@@ -56,7 +56,7 @@ jobs:
use-all-platform-bundle: 'false'
setup-kotlin: 'true'
- name: Set up Ruby
uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0
uses: ruby/setup-ruby@90be1154f987f4dc0fe0dd0feedac9e473aa4ba8 # v1.286.0
with:
ruby-version: 2.6
- name: Install Code Scanning integration
+1 -1
View File
@@ -111,7 +111,7 @@ jobs:
# Otherwise, just commit the changes.
if git rev-parse --verify MERGE_HEAD >/dev/null 2>&1; then
echo "In progress merge detected, finishing it up."
git merge --continue --no-edit
git merge --continue
else
echo "No in-progress merge detected, committing changes."
git commit -m "Rebuild"
-13
View File
@@ -6,19 +6,6 @@ See the [releases page](https://github.com/github/codeql-action/releases) for th
No user facing changes.
## 4.32.3 - 13 Feb 2026
- Added experimental support for testing connections to [private package registries](https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries). This feature is not currently enabled for any analysis. In the future, it may be enabled by default for Default Setup. [#3466](https://github.com/github/codeql-action/pull/3466)
## 4.32.2 - 05 Feb 2026
- Update default CodeQL bundle version to [2.24.1](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.24.1). [#3460](https://github.com/github/codeql-action/pull/3460)
## 4.32.1 - 02 Feb 2026
- A warning is now shown in Default Setup workflow logs if a [private package registry is configured](https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries) using a GitHub Personal Access Token (PAT), but no username is configured. [#3422](https://github.com/github/codeql-action/pull/3422)
- Fixed a bug which caused the CodeQL Action to fail when repository properties cannot successfully be retrieved. [#3421](https://github.com/github/codeql-action/pull/3421)
## 4.32.0 - 26 Jan 2026
- Update default CodeQL bundle version to [2.24.0](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.24.0). [#3425](https://github.com/github/codeql-action/pull/3425)
+11646 -45411
View File
File diff suppressed because one or more lines are too long
+11342 -28477
View File
File diff suppressed because one or more lines are too long
+11308 -28377
View File
File diff suppressed because one or more lines are too long
+4 -4
View File
@@ -1,6 +1,6 @@
{
"bundleVersion": "codeql-bundle-v2.24.1",
"cliVersion": "2.24.1",
"priorBundleVersion": "codeql-bundle-v2.24.0",
"priorCliVersion": "2.24.0"
"bundleVersion": "codeql-bundle-v2.24.0",
"cliVersion": "2.24.0",
"priorBundleVersion": "codeql-bundle-v2.23.9",
"priorCliVersion": "2.23.9"
}
+12110 -45976
View File
File diff suppressed because one or more lines are too long
+13315 -30508
View File
File diff suppressed because one or more lines are too long
+11295 -28363
View File
File diff suppressed because one or more lines are too long
+11489 -28640
View File
File diff suppressed because one or more lines are too long
+11646 -45411
View File
File diff suppressed because one or more lines are too long
+34754 -52534
View File
File diff suppressed because one or more lines are too long
+11493 -28703
View File
File diff suppressed because one or more lines are too long
+11612 -45377
View File
File diff suppressed because one or more lines are too long
+11522 -28704
View File
File diff suppressed because one or more lines are too long
+216 -202
View File
@@ -1,31 +1,30 @@
{
"name": "codeql",
"version": "4.32.4",
"version": "4.32.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "codeql",
"version": "4.32.4",
"version": "4.32.1",
"license": "MIT",
"dependencies": {
"@actions/artifact": "^5.0.3",
"@actions/artifact": "^5.0.2",
"@actions/artifact-legacy": "npm:@actions/artifact@^1.1.2",
"@actions/cache": "^5.0.5",
"@actions/core": "^2.0.3",
"@actions/cache": "^5.0.3",
"@actions/core": "^2.0.2",
"@actions/exec": "^2.0.0",
"@actions/github": "^8.0.1",
"@actions/github": "^8.0.0",
"@actions/glob": "^0.5.0",
"@actions/http-client": "^3.0.0",
"@actions/io": "^2.0.0",
"@actions/tool-cache": "^3.0.1",
"@actions/tool-cache": "^3.0.0",
"@octokit/plugin-retry": "^8.0.0",
"@schemastore/package": "0.0.10",
"archiver": "^7.0.1",
"fast-deep-equal": "^3.1.3",
"follow-redirects": "^1.15.11",
"get-folder-size": "^5.0.0",
"https-proxy-agent": "^7.0.6",
"js-yaml": "^4.1.1",
"jsonschema": "1.4.1",
"long": "^5.3.2",
@@ -35,7 +34,7 @@
},
"devDependencies": {
"@ava/typescript": "6.0.0",
"@eslint/compat": "^2.0.2",
"@eslint/compat": "^2.0.1",
"@eslint/eslintrc": "^3.3.3",
"@eslint/js": "^9.39.2",
"@microsoft/eslint-formatter-sarif": "^3.1.0",
@@ -47,7 +46,7 @@
"@types/node-forge": "^1.3.14",
"@types/semver": "^7.7.1",
"@types/sinon": "^21.0.0",
"@typescript-eslint/eslint-plugin": "^8.54.0",
"@typescript-eslint/eslint-plugin": "^8.53.1",
"@typescript-eslint/parser": "^8.48.0",
"ava": "^6.4.1",
"esbuild": "^0.27.2",
@@ -56,7 +55,7 @@
"eslint-plugin-filenames": "^1.3.2",
"eslint-plugin-github": "^5.1.8",
"eslint-plugin-import": "2.29.1",
"eslint-plugin-jsdoc": "^62.5.0",
"eslint-plugin-jsdoc": "^62.2.0",
"eslint-plugin-no-async-foreach": "^0.1.1",
"glob": "^11.1.0",
"nock": "^14.0.10",
@@ -73,14 +72,14 @@
}
},
"node_modules/@actions/artifact": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/@actions/artifact/-/artifact-5.0.3.tgz",
"integrity": "sha512-FIEG8Kum0wABZnktJvFi1xuVPc31xrunhZwLCvjrCGISQOm0ifyo7cjqf6PHiEeqoWMa5HIGOsB+lGM4aKCseA==",
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@actions/artifact/-/artifact-5.0.2.tgz",
"integrity": "sha512-NmA+THO1s/Ubqf5y34EZofB1FRORcToa3T8Mb3/Z87TCCozTtvBUaxDn8OPPwgyYQ4ft0nd01Z4kFSu2DTWqGw==",
"license": "MIT",
"dependencies": {
"@actions/core": "^2.0.0",
"@actions/github": "^6.0.1",
"@actions/http-client": "^3.0.2",
"@actions/http-client": "^3.0.1",
"@azure/storage-blob": "^12.29.1",
"@octokit/core": "^5.2.1",
"@octokit/plugin-request-log": "^1.0.4",
@@ -352,15 +351,15 @@
"license": "Apache-2.0"
},
"node_modules/@actions/cache": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/@actions/cache/-/cache-5.0.5.tgz",
"integrity": "sha512-jiQSg0gfd+C2KPgcmdCOq7dCuCIQQWQ4b1YfGIRaaA9w7PJbRwTOcCz4LiFEUnqZGf0ha/8OKL3BeNwetHzYsQ==",
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/@actions/cache/-/cache-5.0.3.tgz",
"integrity": "sha512-9joY8Oup+nIpksSBlkuf9/mltnhWx3lydk1tA2PVnXaxFLIIrKqrWDN2CZXlJ+PEErcBARKYn4mHiUCTyMh4Vg==",
"license": "MIT",
"dependencies": {
"@actions/core": "^2.0.0",
"@actions/exec": "^2.0.0",
"@actions/glob": "^0.5.1",
"@actions/http-client": "^3.0.2",
"@actions/glob": "^0.5.0",
"@actions/http-client": "^3.0.1",
"@actions/io": "^2.0.0",
"@azure/abort-controller": "^1.1.0",
"@azure/core-rest-pipeline": "^1.22.0",
@@ -377,13 +376,13 @@
}
},
"node_modules/@actions/core": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-2.0.3.tgz",
"integrity": "sha512-Od9Thc3T1mQJYddvVPM4QGiLUewdh+3txmDYHHxoNdkqysR1MbCT+rFOtNUxYAz+7+6RIsqipVahY2GJqGPyxA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-2.0.2.tgz",
"integrity": "sha512-Ast1V7yHbGAhplAsuVlnb/5J8Mtr/Zl6byPPL+Qjq3lmfIgWF1ak1iYfF/079cRERiuTALTXkSuEUdZeDCfGtA==",
"license": "MIT",
"dependencies": {
"@actions/exec": "^2.0.0",
"@actions/http-client": "^3.0.2"
"@actions/http-client": "^3.0.1"
}
},
"node_modules/@actions/exec": {
@@ -396,18 +395,18 @@
}
},
"node_modules/@actions/github": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-8.0.1.tgz",
"integrity": "sha512-cue7mS+kx1/2Dnc/094pitRUm+0uPXVXYVaqOdZwD15BsXATWYHW3idJDYOlyBc5gJlzAQ/w5YLU4LR8D7hjVg==",
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-8.0.0.tgz",
"integrity": "sha512-bBukvVRvIf7NshWEXFVnBaXs/ZyGzDvx+jbB5AdJij9CUG4VFEYJJD6T6y06dDkuULalQQUNvKXIH71yqw7SoQ==",
"license": "MIT",
"dependencies": {
"@actions/http-client": "^3.0.2",
"@actions/http-client": "^3.0.1",
"@octokit/core": "^7.0.6",
"@octokit/plugin-paginate-rest": "^14.0.0",
"@octokit/plugin-rest-endpoint-methods": "^17.0.0",
"@octokit/request": "^10.0.7",
"@octokit/request-error": "^7.1.0",
"undici": "^6.23.0"
"undici": "^5.28.5"
}
},
"node_modules/@actions/github/node_modules/@octokit/plugin-paginate-rest": {
@@ -440,42 +439,58 @@
"@octokit/core": ">=6"
}
},
"node_modules/@actions/github/node_modules/undici": {
"version": "6.23.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz",
"integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==",
"license": "MIT",
"engines": {
"node": ">=18.17"
}
},
"node_modules/@actions/glob": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/@actions/glob/-/glob-0.5.1.tgz",
"integrity": "sha512-+dv/t2aKQdKp9WWSp+1yIXVJzH5Q38M0Mta26pzIbeec14EcIleMB7UU6N7sNgbEuYfyuVGpE5pOKjl6j1WXkA==",
"license": "MIT",
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@actions/glob/-/glob-0.5.0.tgz",
"integrity": "sha512-tST2rjPvJLRZLuT9NMUtyBjvj9Yo0MiJS3ow004slMvm8GFM+Zv9HvMJ7HWzfUyJnGrJvDsYkWBaaG3YKXRtCw==",
"dependencies": {
"@actions/core": "^2.0.3",
"@actions/core": "^1.9.1",
"minimatch": "^3.0.4"
}
},
"node_modules/@actions/http-client": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-3.0.2.tgz",
"integrity": "sha512-JP38FYYpyqvUsz+Igqlc/JG6YO9PaKuvqjM3iGvaLqFnJ7TFmcLyy2IDrY0bI0qCQug8E9K+elv5ZNfw62ZJzA==",
"node_modules/@actions/glob/node_modules/@actions/core": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz",
"integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==",
"license": "MIT",
"dependencies": {
"@actions/exec": "^1.1.1",
"@actions/http-client": "^2.0.1"
}
},
"node_modules/@actions/glob/node_modules/@actions/exec": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
"license": "MIT",
"dependencies": {
"@actions/io": "^1.0.1"
}
},
"node_modules/@actions/glob/node_modules/@actions/http-client": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz",
"integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==",
"license": "MIT",
"dependencies": {
"tunnel": "^0.0.6",
"undici": "^6.23.0"
"undici": "^5.25.4"
}
},
"node_modules/@actions/http-client/node_modules/undici": {
"version": "6.23.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz",
"integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==",
"node_modules/@actions/glob/node_modules/@actions/io": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==",
"license": "MIT"
},
"node_modules/@actions/http-client": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-3.0.1.tgz",
"integrity": "sha512-SbGS8c/vySbNO3kjFgSW77n83C4MQx/Yoe+b1hAdpuvfHxnkHzDq2pWljUpAA56Si1Gae/7zjeZsV0CYjmLo/w==",
"license": "MIT",
"engines": {
"node": ">=18.17"
"dependencies": {
"tunnel": "^0.0.6",
"undici": "^5.28.5"
}
},
"node_modules/@actions/io": {
@@ -485,14 +500,14 @@
"license": "MIT"
},
"node_modules/@actions/tool-cache": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-3.0.1.tgz",
"integrity": "sha512-euK7sID37jMg1yWGkdXkLPI5Te7x/+2QMUPeHXogcpzUZ81mqlDZ+CgYhQo3LtB8LpVnnQyjs+hTTU0Ir4Y0RQ==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-3.0.0.tgz",
"integrity": "sha512-JBx8gEWuu8Lqaqx/hEnL6QdKvF06suBR4y+dBDi9vJbHx1r+p6QtmBKhQYhiKjDUYIoDX1bUrbyAYPChoPK+XA==",
"license": "MIT",
"dependencies": {
"@actions/core": "^2.0.1",
"@actions/exec": "^2.0.0",
"@actions/http-client": "^3.0.2",
"@actions/http-client": "^3.0.1",
"@actions/io": "^2.0.0",
"semver": "^6.1.0"
}
@@ -818,26 +833,26 @@
}
},
"node_modules/@es-joy/jsdoccomment": {
"version": "0.83.0",
"resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.83.0.tgz",
"integrity": "sha512-e1MHSEPJ4m35zkBvNT6kcdeH1SvMaJDsPC3Xhfseg3hvF50FUE3f46Yn36jgbrPYYXezlWUQnevv23c+lx2MCA==",
"version": "0.81.0",
"resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.81.0.tgz",
"integrity": "sha512-4V4A0hFAB19id7w9iwiosV/rqwlH+PXEuYnnu1Cyc5jUjTwsE2G1qsX9TOCmfCmsWYBg6xeDC/XDFUzXAxDg3A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.8",
"@typescript-eslint/types": "^8.53.1",
"comment-parser": "1.4.5",
"@typescript-eslint/types": "^8.53.0",
"comment-parser": "1.4.4",
"esquery": "^1.7.0",
"jsdoc-type-pratt-parser": "~7.1.0"
"jsdoc-type-pratt-parser": "~7.0.0"
},
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
}
},
"node_modules/@es-joy/jsdoccomment/node_modules/@typescript-eslint/types": {
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz",
"integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==",
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.1.tgz",
"integrity": "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1342,19 +1357,19 @@
}
},
"node_modules/@eslint/compat": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-2.0.2.tgz",
"integrity": "sha512-pR1DoD0h3HfF675QZx0xsyrsU8q70Z/plx7880NOhS02NuWLgBCOMDL787nUeQ7EWLkxv3bPQJaarjcPQb2Dwg==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-2.0.1.tgz",
"integrity": "sha512-yl/JsgplclzuvGFNqwNYV4XNPhP3l62ZOP9w/47atNAdmDtIFCx6X7CSk/SlWUuBGkT4Et/5+UD+WyvX2iiIWA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@eslint/core": "^1.1.0"
"@eslint/core": "^1.0.1"
},
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
},
"peerDependencies": {
"eslint": "^8.40 || 9 || 10"
"eslint": "^8.40 || 9"
},
"peerDependenciesMeta": {
"eslint": {
@@ -1363,9 +1378,9 @@
}
},
"node_modules/@eslint/core": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz",
"integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.0.1.tgz",
"integrity": "sha512-r18fEAj9uCk+VjzGt2thsbOmychS+4kxI14spVNibUO2vqKX7obOG+ymZljAwuPZl+S3clPGwCwTDtrdqTiY6Q==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -1510,9 +1525,9 @@
}
},
"node_modules/@isaacs/brace-expansion": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz",
"integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==",
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
"integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
"license": "MIT",
"dependencies": {
"@isaacs/balanced-match": "^4.0.1"
@@ -2193,17 +2208,17 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz",
"integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==",
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.1.tgz",
"integrity": "sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.12.2",
"@typescript-eslint/scope-manager": "8.54.0",
"@typescript-eslint/type-utils": "8.54.0",
"@typescript-eslint/utils": "8.54.0",
"@typescript-eslint/visitor-keys": "8.54.0",
"@typescript-eslint/scope-manager": "8.53.1",
"@typescript-eslint/type-utils": "8.53.1",
"@typescript-eslint/utils": "8.53.1",
"@typescript-eslint/visitor-keys": "8.53.1",
"ignore": "^7.0.5",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.4.0"
@@ -2216,7 +2231,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.54.0",
"@typescript-eslint/parser": "^8.53.1",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
@@ -2241,14 +2256,14 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": {
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz",
"integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==",
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.1.tgz",
"integrity": "sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.54.0",
"@typescript-eslint/visitor-keys": "8.54.0"
"@typescript-eslint/types": "8.53.1",
"@typescript-eslint/visitor-keys": "8.53.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2259,9 +2274,9 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": {
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz",
"integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==",
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.1.tgz",
"integrity": "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2273,16 +2288,16 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": {
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz",
"integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==",
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.1.tgz",
"integrity": "sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.54.0",
"@typescript-eslint/tsconfig-utils": "8.54.0",
"@typescript-eslint/types": "8.54.0",
"@typescript-eslint/visitor-keys": "8.54.0",
"@typescript-eslint/project-service": "8.53.1",
"@typescript-eslint/tsconfig-utils": "8.53.1",
"@typescript-eslint/types": "8.53.1",
"@typescript-eslint/visitor-keys": "8.53.1",
"debug": "^4.4.3",
"minimatch": "^9.0.5",
"semver": "^7.7.3",
@@ -2301,16 +2316,16 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": {
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz",
"integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==",
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.53.1.tgz",
"integrity": "sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
"@typescript-eslint/scope-manager": "8.54.0",
"@typescript-eslint/types": "8.54.0",
"@typescript-eslint/typescript-estree": "8.54.0"
"@typescript-eslint/scope-manager": "8.53.1",
"@typescript-eslint/types": "8.53.1",
"@typescript-eslint/typescript-estree": "8.53.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2325,13 +2340,13 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": {
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz",
"integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==",
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.1.tgz",
"integrity": "sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.54.0",
"@typescript-eslint/types": "8.53.1",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
@@ -2423,16 +2438,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz",
"integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==",
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.1.tgz",
"integrity": "sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.54.0",
"@typescript-eslint/types": "8.54.0",
"@typescript-eslint/typescript-estree": "8.54.0",
"@typescript-eslint/visitor-keys": "8.54.0",
"@typescript-eslint/scope-manager": "8.53.1",
"@typescript-eslint/types": "8.53.1",
"@typescript-eslint/typescript-estree": "8.53.1",
"@typescript-eslint/visitor-keys": "8.53.1",
"debug": "^4.4.3"
},
"engines": {
@@ -2448,14 +2463,14 @@
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": {
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz",
"integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==",
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.1.tgz",
"integrity": "sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.54.0",
"@typescript-eslint/visitor-keys": "8.54.0"
"@typescript-eslint/types": "8.53.1",
"@typescript-eslint/visitor-keys": "8.53.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2466,9 +2481,9 @@
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": {
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz",
"integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==",
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.1.tgz",
"integrity": "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2480,16 +2495,16 @@
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": {
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz",
"integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==",
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.1.tgz",
"integrity": "sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.54.0",
"@typescript-eslint/tsconfig-utils": "8.54.0",
"@typescript-eslint/types": "8.54.0",
"@typescript-eslint/visitor-keys": "8.54.0",
"@typescript-eslint/project-service": "8.53.1",
"@typescript-eslint/tsconfig-utils": "8.53.1",
"@typescript-eslint/types": "8.53.1",
"@typescript-eslint/visitor-keys": "8.53.1",
"debug": "^4.4.3",
"minimatch": "^9.0.5",
"semver": "^7.7.3",
@@ -2508,13 +2523,13 @@
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": {
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz",
"integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==",
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.1.tgz",
"integrity": "sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.54.0",
"@typescript-eslint/types": "8.53.1",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
@@ -2596,14 +2611,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz",
"integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==",
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.53.1.tgz",
"integrity": "sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.54.0",
"@typescript-eslint/types": "^8.54.0",
"@typescript-eslint/tsconfig-utils": "^8.53.1",
"@typescript-eslint/types": "^8.53.1",
"debug": "^4.4.3"
},
"engines": {
@@ -2618,9 +2633,9 @@
}
},
"node_modules/@typescript-eslint/project-service/node_modules/@typescript-eslint/types": {
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz",
"integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==",
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.1.tgz",
"integrity": "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2668,9 +2683,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz",
"integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==",
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.1.tgz",
"integrity": "sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2685,15 +2700,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz",
"integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==",
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.53.1.tgz",
"integrity": "sha512-MOrdtNvyhy0rHyv0ENzub1d4wQYKb2NmIqG7qEqPWFW7Mpy2jzFC3pQ2yKDvirZB7jypm5uGjF2Qqs6OIqu47w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.54.0",
"@typescript-eslint/typescript-estree": "8.54.0",
"@typescript-eslint/utils": "8.54.0",
"@typescript-eslint/types": "8.53.1",
"@typescript-eslint/typescript-estree": "8.53.1",
"@typescript-eslint/utils": "8.53.1",
"debug": "^4.4.3",
"ts-api-utils": "^2.4.0"
},
@@ -2729,14 +2744,14 @@
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/scope-manager": {
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz",
"integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==",
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.1.tgz",
"integrity": "sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.54.0",
"@typescript-eslint/visitor-keys": "8.54.0"
"@typescript-eslint/types": "8.53.1",
"@typescript-eslint/visitor-keys": "8.53.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2747,9 +2762,9 @@
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": {
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz",
"integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==",
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.1.tgz",
"integrity": "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2761,16 +2776,16 @@
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": {
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz",
"integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==",
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.1.tgz",
"integrity": "sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.54.0",
"@typescript-eslint/tsconfig-utils": "8.54.0",
"@typescript-eslint/types": "8.54.0",
"@typescript-eslint/visitor-keys": "8.54.0",
"@typescript-eslint/project-service": "8.53.1",
"@typescript-eslint/tsconfig-utils": "8.53.1",
"@typescript-eslint/types": "8.53.1",
"@typescript-eslint/visitor-keys": "8.53.1",
"debug": "^4.4.3",
"minimatch": "^9.0.5",
"semver": "^7.7.3",
@@ -2789,16 +2804,16 @@
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": {
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz",
"integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==",
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.53.1.tgz",
"integrity": "sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
"@typescript-eslint/scope-manager": "8.54.0",
"@typescript-eslint/types": "8.54.0",
"@typescript-eslint/typescript-estree": "8.54.0"
"@typescript-eslint/scope-manager": "8.53.1",
"@typescript-eslint/types": "8.53.1",
"@typescript-eslint/typescript-estree": "8.53.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2813,13 +2828,13 @@
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": {
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz",
"integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==",
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.1.tgz",
"integrity": "sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.54.0",
"@typescript-eslint/types": "8.53.1",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
@@ -3976,9 +3991,9 @@
"license": "MIT"
},
"node_modules/comment-parser": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.5.tgz",
"integrity": "sha512-aRDkn3uyIlCFfk5NUA+VdwMmMsh8JGhc4hapfV4yxymHGQ3BVskMQfoXGpCo5IoBuQ9tS5iiVKhCpTcB4pW4qw==",
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.4.tgz",
"integrity": "sha512-0D6qSQ5IkeRrGJFHRClzaMOenMeT0gErz3zIw3AprKMqhRN6LNU2jQOdkPG/FZ+8bCgXE1VidrgSzlBBDZRr8A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -4886,19 +4901,19 @@
}
},
"node_modules/eslint-plugin-jsdoc": {
"version": "62.5.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-62.5.0.tgz",
"integrity": "sha512-D+1haMVDzW/ZMoPwOnsbXCK07rJtsq98Z1v+ApvDKxSzYTTcPgmFc/nyUDCGmxm2cP7g7hszyjYHO7Zodl/43w==",
"version": "62.2.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-62.2.0.tgz",
"integrity": "sha512-juIekE88Gy1FnjsQuwkrZ3OW4UCkQuhiBf9QqF9U1xgqtan4rQFCOUn4cKeHR1ObBqJ8/pG8a0lZlAuKlDLpdw==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"@es-joy/jsdoccomment": "~0.83.0",
"@es-joy/jsdoccomment": "~0.81.0",
"@es-joy/resolve.exports": "1.2.0",
"are-docs-informative": "^0.0.2",
"comment-parser": "1.4.5",
"comment-parser": "1.4.4",
"debug": "^4.4.3",
"escape-string-regexp": "^4.0.0",
"espree": "^11.1.0",
"espree": "^11.0.0",
"esquery": "^1.7.0",
"html-entities": "^2.6.0",
"object-deep-merge": "^2.0.0",
@@ -4959,9 +4974,9 @@
}
},
"node_modules/eslint-plugin-jsdoc/node_modules/espree": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-11.1.0.tgz",
"integrity": "sha512-WFWYhO1fV4iYkqOOvq8FbqIhr2pYfoDY0kCotMkDeNtGpiGGkZ1iov2u8ydjtgM8yF8rzK7oaTbw2NAzbAbehw==",
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-11.0.0.tgz",
"integrity": "sha512-+gMeWRrIh/NsG+3NaLeWHuyeyk70p2tbvZIWBYcqQ4/7Xvars6GYTZNhF1sIeLcc6Wb11He5ffz3hsHyXFrw5A==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
@@ -5452,9 +5467,9 @@
"license": "MIT"
},
"node_modules/fast-xml-parser": {
"version": "5.3.6",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.6.tgz",
"integrity": "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA==",
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.3.tgz",
"integrity": "sha512-2O3dkPAAC6JavuMm8+4+pgTk+5hoAs+CjZ+sWcQLkX9+/tHRuTkQh/Oaifr8qDmZ8iEHb771Ea6G8CdwkrgvYA==",
"funding": [
{
"type": "github",
@@ -5463,7 +5478,7 @@
],
"license": "MIT",
"dependencies": {
"strnum": "^2.1.2"
"strnum": "^2.1.0"
},
"bin": {
"fxparser": "src/cli/cli.js"
@@ -6000,7 +6015,6 @@
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "4"
@@ -6497,9 +6511,9 @@
}
},
"node_modules/jsdoc-type-pratt-parser": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-7.1.0.tgz",
"integrity": "sha512-SX7q7XyCwzM/MEDCYz0l8GgGbJAACGFII9+WfNYr5SLEKukHWRy2Jk3iWRe7P+lpYJNs7oQ+OSei4JtKGUjd7A==",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-7.0.0.tgz",
"integrity": "sha512-c7YbokssPOSHmqTbSAmTtnVgAVa/7lumWNYqomgd5KOMyPrRve2anx6lonfOsXEQacqF9FKVUj7bLg4vRSvdYA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -8145,9 +8159,9 @@
}
},
"node_modules/tar": {
"version": "7.5.7",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz",
"integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==",
"version": "7.5.6",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.6.tgz",
"integrity": "sha512-xqUeu2JAIJpXyvskvU3uvQW8PAmHrtXp2KDuMJwQqW8Sqq0CaZBAQ+dKS3RBXVhU4wC5NjAdKrmh84241gO9cA==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
+9 -10
View File
@@ -1,6 +1,6 @@
{
"name": "codeql",
"version": "4.32.4",
"version": "4.32.1",
"private": true,
"description": "CodeQL action",
"scripts": {
@@ -24,23 +24,22 @@
},
"license": "MIT",
"dependencies": {
"@actions/artifact": "^5.0.3",
"@actions/artifact": "^5.0.2",
"@actions/artifact-legacy": "npm:@actions/artifact@^1.1.2",
"@actions/cache": "^5.0.5",
"@actions/core": "^2.0.3",
"@actions/cache": "^5.0.3",
"@actions/core": "^2.0.2",
"@actions/exec": "^2.0.0",
"@actions/github": "^8.0.1",
"@actions/github": "^8.0.0",
"@actions/glob": "^0.5.0",
"@actions/http-client": "^3.0.0",
"@actions/io": "^2.0.0",
"@actions/tool-cache": "^3.0.1",
"@actions/tool-cache": "^3.0.0",
"@octokit/plugin-retry": "^8.0.0",
"@schemastore/package": "0.0.10",
"archiver": "^7.0.1",
"fast-deep-equal": "^3.1.3",
"follow-redirects": "^1.15.11",
"get-folder-size": "^5.0.0",
"https-proxy-agent": "^7.0.6",
"js-yaml": "^4.1.1",
"jsonschema": "1.4.1",
"long": "^5.3.2",
@@ -50,7 +49,7 @@
},
"devDependencies": {
"@ava/typescript": "6.0.0",
"@eslint/compat": "^2.0.2",
"@eslint/compat": "^2.0.1",
"@eslint/eslintrc": "^3.3.3",
"@eslint/js": "^9.39.2",
"@microsoft/eslint-formatter-sarif": "^3.1.0",
@@ -62,7 +61,7 @@
"@types/node-forge": "^1.3.14",
"@types/semver": "^7.7.1",
"@types/sinon": "^21.0.0",
"@typescript-eslint/eslint-plugin": "^8.54.0",
"@typescript-eslint/eslint-plugin": "^8.53.1",
"@typescript-eslint/parser": "^8.48.0",
"ava": "^6.4.1",
"esbuild": "^0.27.2",
@@ -71,7 +70,7 @@
"eslint-plugin-filenames": "^1.3.2",
"eslint-plugin-github": "^5.1.8",
"eslint-plugin-import": "2.29.1",
"eslint-plugin-jsdoc": "^62.5.0",
"eslint-plugin-jsdoc": "^62.2.0",
"eslint-plugin-no-async-foreach": "^0.1.1",
"glob": "^11.1.0",
"nock": "^14.0.10",
-15
View File
@@ -1,15 +0,0 @@
name: "Bundle: From nightly"
description: "The nightly CodeQL bundle should be used when forced"
versions:
- linked # overruled by the FF set below
steps:
- id: init
uses: ./../action/init
env:
CODEQL_ACTION_FORCE_NIGHTLY: true
with:
tools: ${{ steps.prepare-test.outputs.tools-url }}
languages: javascript
- name: Fail if the CodeQL version is not a nightly
if: "!contains(steps.init.outputs.codeql-version, '+')"
run: exit 1
+1 -1
View File
@@ -4,7 +4,7 @@ versions:
- toolcache
steps:
- name: Install @actions/tool-cache
run: npm install @actions/tool-cache@3
run: npm install @actions/tool-cache
- name: Check toolcache contains CodeQL
continue-on-error: true
uses: actions/github-script@v8
+1 -1
View File
@@ -16,7 +16,7 @@ steps:
const codeqlPath = path.join(process.env['RUNNER_TOOL_CACHE'], 'CodeQL');
fs.rmdirSync(codeqlPath, { recursive: true });
- name: Install @actions/tool-cache
run: npm install @actions/tool-cache@3
run: npm install @actions/tool-cache
- name: Check toolcache does not contain CodeQL
uses: actions/github-script@v8
with:
+13
View File
@@ -3,6 +3,19 @@ description: "Tests using a proxy specified by the https_proxy environment varia
versions: ["linked", "nightly-latest"]
container:
image: ubuntu:22.04
container-init-steps:
# These steps are required to initialise the `gh` cli in a container that doesn't
# come pre-installed with it. The reason for that is that this is later
# needed by the `prepare-test` workflow to find the latest release of CodeQL.
name: Set up GitHub CLI
run: |
apt update
apt install -y curl libreadline8 gnupg2 software-properties-common zstd
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
apt-key add /usr/share/keyrings/githubcli-archive-keyring.gpg
apt-add-repository https://cli.github.com/packages
apt install -y gh
env: {}
services:
squid-proxy:
image: ubuntu/squid:latest
@@ -1,9 +1,8 @@
name: "Analysis kinds"
description: "Tests basic functionality for different `analysis-kinds` inputs."
name: "Quality queries input"
description: "Tests that queries specified in the quality-queries input are used."
versions: ["linked", "nightly-latest"]
analysisKinds: ["code-scanning", "code-quality", "code-scanning,code-quality", "risk-assessment"]
analysisKinds: ["code-scanning", "code-quality", "code-scanning,code-quality"]
env:
CODEQL_ACTION_RISK_ASSESSMENT_ID: 1
CHECK_SCRIPT: |
const fs = require('fs');
@@ -38,24 +37,30 @@ steps:
output: "${{ runner.temp }}/results"
upload-database: false
post-processed-sarif-path: "${{ runner.temp }}/post-processed"
- name: Upload SARIF files
- name: Upload security SARIF
if: contains(matrix.analysis-kinds, 'code-scanning')
uses: actions/upload-artifact@v6
with:
name: |
analysis-kinds-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}
path: "${{ runner.temp }}/results/*.sarif"
quality-queries-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}.sarif.json
path: "${{ runner.temp }}/results/javascript.sarif"
retention-days: 7
- name: Upload quality SARIF
if: contains(matrix.analysis-kinds, 'code-quality')
uses: actions/upload-artifact@v6
with:
name: |
quality-queries-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}.quality.sarif.json
path: "${{ runner.temp }}/results/javascript.quality.sarif"
retention-days: 7
- name: Upload post-processed SARIF
uses: actions/upload-artifact@v6
with:
name: |
post-processed-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}
post-processed-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}.sarif.json
path: "${{ runner.temp }}/post-processed"
retention-days: 7
if-no-files-found: error
- name: Check quality query does not appear in security SARIF
if: contains(matrix.analysis-kinds, 'code-scanning')
uses: actions/github-script@v8
+1 -1
View File
@@ -4,7 +4,7 @@ description: "Tests using RuboCop to analyze a multi-language repository and the
versions: ["default"]
steps:
- name: Set up Ruby
uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0
uses: ruby/setup-ruby@90be1154f987f4dc0fe0dd0feedac9e473aa4ba8 # v1.286.0
with:
ruby-version: 2.6
- name: Install Code Scanning integration
-112
View File
@@ -1,23 +1,15 @@
import path from "path";
import test from "ava";
import * as sinon from "sinon";
import * as actionsUtil from "./actions-util";
import {
AnalysisKind,
CodeScanning,
compatibilityMatrix,
RiskAssessment,
getAnalysisConfig,
getAnalysisKinds,
parseAnalysisKinds,
supportedAnalysisKinds,
} from "./analyses";
import { EnvVar } from "./environment";
import { getRunnerLogger } from "./logging";
import { setupTests } from "./testing-utils";
import { AssessmentPayload } from "./upload-lib/types";
import { ConfigurationError } from "./util";
setupTests(test);
@@ -75,107 +67,3 @@ test("getAnalysisKinds - throws if `analysis-kinds` input is invalid", async (t)
requiredInputStub.withArgs("analysis-kinds").returns("no-such-thing");
await t.throwsAsync(getAnalysisKinds(getRunnerLogger(true), true));
});
// Test the compatibility matrix by looping through all analysis kinds.
const analysisKinds = Object.values(AnalysisKind);
for (let i = 0; i < analysisKinds.length; i++) {
const analysisKind = analysisKinds[i];
for (let j = i + 1; j < analysisKinds.length; j++) {
const otherAnalysis = analysisKinds[j];
if (analysisKind === otherAnalysis) continue;
if (compatibilityMatrix[analysisKind].has(otherAnalysis)) {
test(`getAnalysisKinds - allows ${analysisKind} with ${otherAnalysis}`, async (t) => {
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
requiredInputStub
.withArgs("analysis-kinds")
.returns([analysisKind, otherAnalysis].join(","));
const result = await getAnalysisKinds(getRunnerLogger(true), true);
t.is(result.length, 2);
});
} else {
test(`getAnalysisKinds - throws if ${analysisKind} is enabled with ${otherAnalysis}`, async (t) => {
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
requiredInputStub
.withArgs("analysis-kinds")
.returns([analysisKind, otherAnalysis].join(","));
await t.throwsAsync(getAnalysisKinds(getRunnerLogger(true), true), {
instanceOf: ConfigurationError,
message: `${analysisKind} and ${otherAnalysis} cannot be enabled at the same time`,
});
});
}
}
}
test("Code Scanning configuration does not accept other SARIF extensions", (t) => {
for (const analysisKind of supportedAnalysisKinds) {
if (analysisKind === AnalysisKind.CodeScanning) continue;
const analysis = getAnalysisConfig(analysisKind);
const sarifPath = path.join("path", "to", `file${analysis.sarifExtension}`);
// The Code Scanning configuration's `sarifPredicate` should not accept a path which
// ends in a different configuration's `sarifExtension`.
t.false(CodeScanning.sarifPredicate(sarifPath));
}
});
test("Risk Assessment configuration transforms SARIF upload payload", (t) => {
process.env[EnvVar.RISK_ASSESSMENT_ID] = "1";
const payload = RiskAssessment.transformPayload({
commit_oid: "abc",
sarif: "sarif",
ref: "ref",
workflow_run_attempt: 1,
workflow_run_id: 1,
checkout_uri: "uri",
tool_names: [],
}) as AssessmentPayload;
const expected: AssessmentPayload = { sarif: "sarif", assessment_id: 1 };
t.deepEqual(expected, payload);
});
test("Risk Assessment configuration throws for negative assessment IDs", (t) => {
process.env[EnvVar.RISK_ASSESSMENT_ID] = "-1";
t.throws(
() =>
RiskAssessment.transformPayload({
commit_oid: "abc",
sarif: "sarif",
ref: "ref",
workflow_run_attempt: 1,
workflow_run_id: 1,
checkout_uri: "uri",
tool_names: [],
}),
{
instanceOf: Error,
message: (msg) =>
msg.startsWith(`${EnvVar.RISK_ASSESSMENT_ID} must not be negative: `),
},
);
});
test("Risk Assessment configuration throws for invalid IDs", (t) => {
process.env[EnvVar.RISK_ASSESSMENT_ID] = "foo";
t.throws(
() =>
RiskAssessment.transformPayload({
commit_oid: "abc",
sarif: "sarif",
ref: "ref",
workflow_run_attempt: 1,
workflow_run_id: 1,
checkout_uri: "uri",
tool_names: [],
}),
{
instanceOf: Error,
message: (msg) =>
msg.startsWith(`${EnvVar.RISK_ASSESSMENT_ID} must not be NaN: `),
},
);
});
+6 -80
View File
@@ -3,30 +3,14 @@ import {
getOptionalInput,
getRequiredInput,
} from "./actions-util";
import { EnvVar } from "./environment";
import { Logger } from "./logging";
import {
AssessmentPayload,
BasePayload,
UploadPayload,
} from "./upload-lib/types";
import { ConfigurationError, getRequiredEnvParam } from "./util";
import { ConfigurationError } from "./util";
export enum AnalysisKind {
CodeScanning = "code-scanning",
CodeQuality = "code-quality",
RiskAssessment = "risk-assessment",
}
export type CompatibilityMatrix = Record<AnalysisKind, Set<AnalysisKind>>;
/** A mapping from analysis kinds to other analysis kinds which can be enabled concurrently. */
export const compatibilityMatrix: CompatibilityMatrix = {
[AnalysisKind.CodeScanning]: new Set([AnalysisKind.CodeQuality]),
[AnalysisKind.CodeQuality]: new Set([AnalysisKind.CodeScanning]),
[AnalysisKind.RiskAssessment]: new Set(),
};
// Exported for testing. A set of all known analysis kinds.
export const supportedAnalysisKinds = new Set(Object.values(AnalysisKind));
@@ -83,7 +67,7 @@ export async function getAnalysisKinds(
return cachedAnalysisKinds;
}
const analysisKinds = await parseAnalysisKinds(
cachedAnalysisKinds = await parseAnalysisKinds(
getRequiredInput("analysis-kinds"),
);
@@ -101,27 +85,12 @@ export async function getAnalysisKinds(
// if an input to `quality-queries` was specified. We should remove this once
// `quality-queries` is no longer used.
if (
!analysisKinds.includes(AnalysisKind.CodeQuality) &&
!cachedAnalysisKinds.includes(AnalysisKind.CodeQuality) &&
qualityQueriesInput !== undefined
) {
analysisKinds.push(AnalysisKind.CodeQuality);
cachedAnalysisKinds.push(AnalysisKind.CodeQuality);
}
// Check that all enabled analysis kinds are compatible with each other.
for (const analysisKind of analysisKinds) {
for (const otherAnalysisKind of analysisKinds) {
if (analysisKind === otherAnalysisKind) continue;
if (!compatibilityMatrix[analysisKind].has(otherAnalysisKind)) {
throw new ConfigurationError(
`${analysisKind} and ${otherAnalysisKind} cannot be enabled at the same time`,
);
}
}
}
// Cache the analysis kinds and return them.
cachedAnalysisKinds = analysisKinds;
return cachedAnalysisKinds;
}
@@ -132,7 +101,6 @@ export const codeQualityQueries: string[] = ["code-quality"];
enum SARIF_UPLOAD_ENDPOINT {
CODE_SCANNING = "PUT /repos/:owner/:repo/code-scanning/analysis",
CODE_QUALITY = "PUT /repos/:owner/:repo/code-quality/analysis",
RISK_ASSESSMENT = "PUT /repos/:owner/:repo/code-scanning/risk-assessment",
}
// Represents configurations for different analysis kinds.
@@ -152,8 +120,6 @@ export interface AnalysisConfig {
fixCategory: (logger: Logger, category?: string) => string | undefined;
/** A prefix for environment variables used to track the uniqueness of SARIF uploads. */
sentinelPrefix: string;
/** Transforms the upload payload in an analysis-specific way. */
transformPayload: (payload: UploadPayload) => BasePayload;
}
// Represents the Code Scanning analysis configuration.
@@ -164,11 +130,9 @@ export const CodeScanning: AnalysisConfig = {
sarifExtension: ".sarif",
sarifPredicate: (name) =>
name.endsWith(CodeScanning.sarifExtension) &&
!CodeQuality.sarifPredicate(name) &&
!RiskAssessment.sarifPredicate(name),
!CodeQuality.sarifPredicate(name),
fixCategory: (_, category) => category,
sentinelPrefix: "CODEQL_UPLOAD_SARIF_",
transformPayload: (payload) => payload,
};
// Represents the Code Quality analysis configuration.
@@ -180,38 +144,6 @@ export const CodeQuality: AnalysisConfig = {
sarifPredicate: (name) => name.endsWith(CodeQuality.sarifExtension),
fixCategory: fixCodeQualityCategory,
sentinelPrefix: "CODEQL_UPLOAD_QUALITY_SARIF_",
transformPayload: (payload) => payload,
};
/**
* Retrieves the CSRA assessment id from an environment variable and adds it to the payload.
* @param payload The base payload.
*/
function addAssessmentId(payload: UploadPayload): AssessmentPayload {
const rawAssessmentId = getRequiredEnvParam(EnvVar.RISK_ASSESSMENT_ID);
const assessmentId = parseInt(rawAssessmentId, 10);
if (Number.isNaN(assessmentId)) {
throw new Error(
`${EnvVar.RISK_ASSESSMENT_ID} must not be NaN: ${rawAssessmentId}`,
);
}
if (assessmentId < 0) {
throw new Error(
`${EnvVar.RISK_ASSESSMENT_ID} must not be negative: ${rawAssessmentId}`,
);
}
return { sarif: payload.sarif, assessment_id: assessmentId };
}
export const RiskAssessment: AnalysisConfig = {
kind: AnalysisKind.RiskAssessment,
name: "code scanning risk assessment",
target: SARIF_UPLOAD_ENDPOINT.RISK_ASSESSMENT,
sarifExtension: ".csra.sarif",
sarifPredicate: (name) => name.endsWith(RiskAssessment.sarifExtension),
fixCategory: (_, category) => category,
sentinelPrefix: "CODEQL_UPLOAD_CSRA_SARIF_",
transformPayload: addAssessmentId,
};
/**
@@ -228,8 +160,6 @@ export function getAnalysisConfig(kind: AnalysisKind): AnalysisConfig {
return CodeScanning;
case AnalysisKind.CodeQuality:
return CodeQuality;
case AnalysisKind.RiskAssessment:
return RiskAssessment;
}
}
@@ -237,8 +167,4 @@ export function getAnalysisConfig(kind: AnalysisKind): AnalysisConfig {
// we want to scan a folder containing SARIF files in an order that finds the more
// specific extensions first. This constant defines an array in the order of analyis
// configurations with more specific extensions to less specific extensions.
export const SarifScanOrder: AnalysisConfig[] = [
RiskAssessment,
CodeQuality,
CodeScanning,
];
export const SarifScanOrder = [CodeQuality, CodeScanning];
+9 -1
View File
@@ -76,6 +76,7 @@ interface FinishWithTrapUploadStatusReport extends FinishStatusReport {
async function sendStatusReport(
startedAt: Date,
config: Config | undefined,
features: Features | undefined,
stats: AnalysisStatusReport | undefined,
error: Error | undefined,
trapCacheUploadTime: number | undefined,
@@ -92,6 +93,7 @@ async function sendStatusReport(
status,
startedAt,
config,
features?.getQueriedFeatures(),
await util.checkDiskUsage(logger),
logger,
error?.message,
@@ -218,6 +220,7 @@ async function run(startedAt: Date) {
| undefined = undefined;
let runStats: QueriesStatusReport | undefined = undefined;
let config: Config | undefined = undefined;
let features: Features | undefined = undefined;
let trapCacheCleanupTelemetry: TrapCacheCleanupStatusReport | undefined =
undefined;
let trapCacheUploadTime: number | undefined = undefined;
@@ -239,6 +242,7 @@ async function run(startedAt: Date) {
"starting",
startedAt,
config,
undefined,
await util.checkDiskUsage(logger),
logger,
);
@@ -293,7 +297,7 @@ async function run(startedAt: Date) {
util.checkActionVersion(actionsUtil.getActionVersion(), gitHubVersion);
const features = new Features(
features = new Features(
gitHubVersion,
repositoryNwo,
actionsUtil.getTemporaryDirectory(),
@@ -460,6 +464,7 @@ async function run(startedAt: Date) {
await sendStatusReport(
startedAt,
config,
features,
error instanceof CodeQLAnalysisError
? error.queriesStatusReport
: undefined,
@@ -482,6 +487,7 @@ async function run(startedAt: Date) {
await sendStatusReport(
startedAt,
config,
features,
{
...runStats,
...uploadResults[analyses.AnalysisKind.CodeScanning].statusReport,
@@ -499,6 +505,7 @@ async function run(startedAt: Date) {
await sendStatusReport(
startedAt,
config,
features,
{ ...runStats },
undefined,
trapCacheUploadTime,
@@ -513,6 +520,7 @@ async function run(startedAt: Date) {
await sendStatusReport(
startedAt,
config,
features,
undefined,
undefined,
trapCacheUploadTime,
+1 -2
View File
@@ -4,7 +4,7 @@ import * as path from "path";
import test from "ava";
import * as sinon from "sinon";
import { CodeQuality, CodeScanning, RiskAssessment } from "./analyses";
import { CodeQuality, CodeScanning } from "./analyses";
import {
runQueries,
defaultSuites,
@@ -155,6 +155,5 @@ test("addSarifExtension", (t) => {
addSarifExtension(CodeQuality, language),
`${language}.quality.sarif`,
);
t.is(addSarifExtension(RiskAssessment, language), `${language}.csra.sarif`);
}
});
+6 -3
View File
@@ -549,9 +549,12 @@ export async function runQueries(
): Promise<{ summary: string; sarifFile: string }> {
logger.info(`Interpreting ${analysis.name} results for ${language}`);
// Apply the analysis configuration's `fixCategory` function to adjust the category if needed.
// This is a no-op for Code Scanning.
const category = analysis.fixCategory(logger, automationDetailsId);
// If this is a Code Quality analysis, correct the category to one
// accepted by the Code Quality backend.
let category = automationDetailsId;
if (analysis.kind === analyses.AnalysisKind.CodeQuality) {
category = analysis.fixCategory(logger, automationDetailsId);
}
const sarifFile = path.join(
sarifFolder,
-3
View File
@@ -36,9 +36,6 @@ test("getApiClient", async (t) => {
baseUrl: "http://api.github.localhost",
log: sinon.match.any,
userAgent: `CodeQL-Action/${actionsUtil.getActionVersion()}`,
retry: {
doNotRetry: [400, 410, 422, 451],
},
}),
);
});
+1 -8
View File
@@ -51,12 +51,6 @@ function createApiClientWithDetails(
warn: core.warning,
error: core.error,
},
retry: {
// The default is 400, 401, 403, 404, 410, 422, and 451. We have observed transient errors
// with authentication, so we remove 401, 403, and 404 from the default list to ensure that
// these errors are retried.
doNotRetry: [400, 410, 422, 451],
},
}),
);
}
@@ -312,8 +306,7 @@ export function wrapApiConfigurationError(e: unknown) {
}
if (
httpError.message.includes("Bad credentials") ||
httpError.message.includes("Not Found") ||
httpError.message.includes("Requires authentication")
httpError.message.includes("Not Found")
) {
return new ConfigurationError(
"Please check that your token is valid and has the required permissions: contents: read, security-events: write",
+2
View File
@@ -54,6 +54,7 @@ async function sendCompletedStatusReport(
status,
startedAt,
config,
undefined,
await checkDiskUsage(logger),
logger,
cause?.message,
@@ -83,6 +84,7 @@ async function run(startedAt: Date) {
"starting",
startedAt,
config,
undefined,
await checkDiskUsage(logger),
logger,
);
+1 -20
View File
@@ -7,7 +7,7 @@ import * as yaml from "js-yaml";
import * as sinon from "sinon";
import * as actionsUtil from "./actions-util";
import { AnalysisKind, supportedAnalysisKinds } from "./analyses";
import { AnalysisKind } from "./analyses";
import * as api from "./api-client";
import { CachingKind } from "./caching-utils";
import { createStubCodeQL } from "./codeql";
@@ -1829,22 +1829,3 @@ test("hasActionsWorkflows doesn't throw if workflows folder doesn't exist", asyn
t.notThrows(() => configUtils.hasActionsWorkflows(tmpDir));
});
});
test("getPrimaryAnalysisConfig - single analysis kind", (t) => {
// If only one analysis kind is configured, we expect to get the matching configuration.
for (const analysisKind of supportedAnalysisKinds) {
const singleKind = createTestConfig({ analysisKinds: [analysisKind] });
t.is(configUtils.getPrimaryAnalysisConfig(singleKind).kind, analysisKind);
}
});
test("getPrimaryAnalysisConfig - Code Scanning + Code Quality", (t) => {
// For CS+CQ, we expect to get the Code Scanning configuration.
const codeScanningAndCodeQuality = createTestConfig({
analysisKinds: [AnalysisKind.CodeScanning, AnalysisKind.CodeQuality],
});
t.is(
configUtils.getPrimaryAnalysisConfig(codeScanningAndCodeQuality).kind,
AnalysisKind.CodeScanning,
);
});
+22 -17
View File
@@ -12,8 +12,9 @@ import {
import {
AnalysisConfig,
AnalysisKind,
CodeQuality,
codeQualityQueries,
getAnalysisConfig,
CodeScanning,
} from "./analyses";
import * as api from "./api-client";
import { CachingKind, getCachingKind } from "./caching-utils";
@@ -25,10 +26,7 @@ import {
parseUserConfig,
UserConfig,
} from "./config/db-config";
import {
addNoLanguageDiagnostic,
makeTelemetryDiagnostic,
} from "./diagnostics";
import { addDiagnostic, makeTelemetryDiagnostic } from "./diagnostics";
import { shouldPerformDiffInformedAnalysis } from "./diff-informed-analysis-utils";
import { EnvVar } from "./environment";
import * as errorMessages from "./error-messages";
@@ -1388,27 +1386,28 @@ export function isCodeQualityEnabled(config: Config): boolean {
}
/**
* Returns the primary analysis kind that the Action is initialised with. If there is only
* one analysis kind, then that is returned.
* Returns the primary analysis kind that the Action is initialised with. This is
* always `AnalysisKind.CodeScanning` unless `AnalysisKind.CodeScanning` is not enabled.
*
* The special case is Code Scanning + Code Quality, which can be enabled at the same time.
* In that case, this function returns Code Scanning.
* @returns Returns `AnalysisKind.CodeScanning` if `AnalysisKind.CodeScanning` is enabled;
* otherwise `AnalysisKind.CodeQuality`.
*/
function getPrimaryAnalysisKind(config: Config): AnalysisKind {
if (config.analysisKinds.length === 1) {
return config.analysisKinds[0];
}
return isCodeScanningEnabled(config)
? AnalysisKind.CodeScanning
: AnalysisKind.CodeQuality;
}
/**
* Returns the primary analysis configuration that the Action is initialised with.
* Returns the primary analysis configuration that the Action is initialised with. This is
* always `CodeScanning` unless `CodeScanning` is not enabled.
*
* @returns Returns `CodeScanning` if `AnalysisKind.CodeScanning` is enabled; otherwise `CodeQuality`.
*/
export function getPrimaryAnalysisConfig(config: Config): AnalysisConfig {
return getAnalysisConfig(getPrimaryAnalysisKind(config));
return getPrimaryAnalysisKind(config) === AnalysisKind.CodeScanning
? CodeScanning
: CodeQuality;
}
/** Logs the Git version as a telemetry diagnostic. */
@@ -1417,8 +1416,11 @@ async function logGitVersionTelemetry(
gitVersion: GitVersionInfo,
): Promise<void> {
if (config.languages.length > 0) {
addNoLanguageDiagnostic(
addDiagnostic(
config,
// Arbitrarily choose the first language. We could also choose all languages, but that
// increases the risk of misinterpreting the data.
config.languages[0],
makeTelemetryDiagnostic(
"codeql-action/git-version-telemetry",
"Git version telemetry",
@@ -1444,8 +1446,11 @@ async function logGeneratedFilesTelemetry(
return;
}
addNoLanguageDiagnostic(
addDiagnostic(
config,
// Arbitrarily choose the first language. We could also choose all languages, but that
// increases the risk of misinterpreting the data.
config.languages[0],
makeTelemetryDiagnostic(
"codeql-action/generated-files-telemetry",
"Generated files telemetry",
+4 -4
View File
@@ -1,6 +1,6 @@
{
"bundleVersion": "codeql-bundle-v2.24.1",
"cliVersion": "2.24.1",
"priorBundleVersion": "codeql-bundle-v2.24.0",
"priorCliVersion": "2.24.0"
"bundleVersion": "codeql-bundle-v2.24.0",
"cliVersion": "2.24.0",
"priorBundleVersion": "codeql-bundle-v2.23.9",
"priorCliVersion": "2.23.9"
}
+4 -33
View File
@@ -66,12 +66,6 @@ interface UnwrittenDiagnostic {
/** 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[] = [];
/**
* Constructs a new diagnostic message with the specified id and name, as well as optional additional data.
*
@@ -123,24 +117,6 @@ export function addDiagnostic(
}
}
/** Adds a diagnostic that is not specific to any language. */
export function addNoLanguageDiagnostic(
config: Config | undefined,
diagnostic: DiagnosticMessage,
) {
if (config !== undefined) {
addDiagnostic(
config,
// Arbitrarily choose the first language. We could also choose all languages, but that
// increases the risk of misinterpreting the data.
config.languages[0],
diagnostic,
);
} else {
unwrittenDefaultLanguageDiagnostics.push(diagnostic);
}
}
/**
* Writes the given diagnostic to the database.
*
@@ -198,21 +174,16 @@ export function logUnwrittenDiagnostics() {
/** 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.`);
logger.debug(
`Writing ${unwrittenDiagnostics.length} 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.
// Reset the unwritten diagnostics array.
unwrittenDiagnostics = [];
unwrittenDefaultLanguageDiagnostics = [];
}
/**
-3
View File
@@ -141,7 +141,4 @@ export enum EnvVar {
* `getAnalysisKey`, but can also be set manually for testing and non-standard applications.
*/
ANALYSIS_KEY = "CODEQL_ACTION_ANALYSIS_KEY",
/** Used by Code Scanning Risk Assessment to communicate the assessment ID to the CodeQL Action. */
RISK_ASSESSMENT_ID = "CODEQL_ACTION_RISK_ASSESSMENT_ID",
}
+41 -51
View File
@@ -4,7 +4,6 @@ import * as path from "path";
import test, { ExecutionContext } from "ava";
import * as defaults from "./defaults.json";
import { EnvVar } from "./environment";
import {
Feature,
featureConfig,
@@ -38,39 +37,29 @@ test.beforeEach(() => {
const testRepositoryNwo = parseRepositoryNwo("github/example");
test(`All features use default values if running against GHES`, async (t) => {
test(`All features are disabled if running against GHES`, async (t) => {
await withTmpDir(async (tmpDir) => {
const loggedMessages: LoggedMessage[] = [];
const loggedMessages = [];
const features = setUpFeatureFlagTests(
tmpDir,
getRecordingLogger(loggedMessages),
{ type: GitHubVariant.GHES, version: "3.0.0" },
);
await assertAllFeaturesHaveDefaultValues(t, features);
assertLoggedMessage(
t,
loggedMessages,
"Not running against github.com. Using default values for all features.",
);
});
});
for (const feature of Object.values(Feature)) {
t.deepEqual(
await getFeatureIncludingCodeQlIfRequired(features, feature),
featureConfig[feature].defaultValue,
);
}
test(`All features use default values if running in CCR`, async (t) => {
await withTmpDir(async (tmpDir) => {
const loggedMessages: LoggedMessage[] = [];
const features = setUpFeatureFlagTests(
tmpDir,
getRecordingLogger(loggedMessages),
);
process.env[EnvVar.ANALYSIS_KEY] = "dynamic/copilot-pull-request-reviewer";
await assertAllFeaturesHaveDefaultValues(t, features);
assertLoggedMessage(
t,
loggedMessages,
"Feature flags are not supported in Copilot Code Review. Using default values for all features.",
t.assert(
loggedMessages.find(
(v: LoggedMessage) =>
v.type === "debug" &&
v.message ===
"Not running against github.com. Disabling all toggleable features.",
) !== undefined,
);
});
});
@@ -103,6 +92,31 @@ test(`Feature flags are requested in GHEC-DR`, async (t) => {
});
});
test("Queried feature flags are recorded", async (t) => {
await withTmpDir(async (tmpDir) => {
const loggedMessages = [];
const features = setUpFeatureFlagTests(
tmpDir,
getRecordingLogger(loggedMessages),
{ type: GitHubVariant.DOTCOM },
);
mockFeatureFlagApiEndpoint(200, initializeFeatures(true));
// No features should have been queried initially.
t.is(Object.keys(features.getQueriedFeatures()).length, 0);
// Query all features.
const allFeatures = Object.values(Feature);
for (const feature of allFeatures) {
await getFeatureIncludingCodeQlIfRequired(features, feature);
}
// All features should have a been queried.
t.is(Object.keys(features.getQueriedFeatures()).length, allFeatures.length);
});
});
test("API response missing and features use default value", async (t) => {
await withTmpDir(async (tmpDir) => {
const loggedMessages: LoggedMessage[] = [];
@@ -553,30 +567,6 @@ test("non-legacy feature flags should not start with codeql_action_", async (t)
}
});
async function assertAllFeaturesHaveDefaultValues(
t: ExecutionContext<unknown>,
features: FeatureEnablement,
) {
for (const feature of Object.values(Feature)) {
t.deepEqual(
await getFeatureIncludingCodeQlIfRequired(features, feature),
featureConfig[feature].defaultValue,
);
}
}
function assertLoggedMessage(
t: ExecutionContext<unknown>,
loggedMessages: LoggedMessage[],
expectedMessage: string,
) {
t.assert(
loggedMessages.find(
(v: LoggedMessage) => v.type === "debug" && v.message === expectedMessage,
) !== undefined,
);
}
function assertAllFeaturesUndefinedInApi(
t: ExecutionContext<unknown>,
loggedMessages: LoggedMessage[],
@@ -597,7 +587,7 @@ function setUpFeatureFlagTests(
tmpDir: string,
logger = getRunnerLogger(true),
gitHubVersion = { type: GitHubVariant.DOTCOM } as util.GitHubVersion,
): FeatureEnablement {
): Features {
setupActionsVars(tmpDir, tmpDir);
return new Features(gitHubVersion, testRepositoryNwo, tmpDir, logger);
+29 -33
View File
@@ -3,7 +3,6 @@ import * as path from "path";
import * as semver from "semver";
import { isCCR } from "./actions-util";
import { getApiClient } from "./api-client";
import type { CodeQL } from "./codeql";
import * as defaults from "./defaults.json";
@@ -46,10 +45,7 @@ export enum Feature {
DisableJavaBuildlessEnabled = "disable_java_buildless_enabled",
DisableKotlinAnalysisEnabled = "disable_kotlin_analysis_enabled",
ExportDiagnosticsEnabled = "export_diagnostics_enabled",
ForceNightly = "force_nightly",
IgnoreGeneratedFiles = "ignore_generated_files",
ImprovedProxyCertificates = "improved_proxy_certificates",
JavaNetworkDebugging = "java_network_debugging",
OverlayAnalysis = "overlay_analysis",
OverlayAnalysisActions = "overlay_analysis_actions",
OverlayAnalysisCodeScanningActions = "overlay_analysis_code_scanning_actions",
@@ -76,7 +72,6 @@ export enum Feature {
QaTelemetryEnabled = "qa_telemetry_enabled",
/** Note that this currently only disables baseline file coverage information. */
SkipFileCoverageOnPrs = "skip_file_coverage_on_prs",
StartProxyConnectionChecks = "start_proxy_connection_checks",
UploadOverlayDbToApi = "upload_overlay_db_to_api",
UseRepositoryProperties = "use_repository_properties",
ValidateDbConfig = "validate_db_config",
@@ -166,26 +161,11 @@ export const featureConfig = {
legacyApi: true,
minimumVersion: undefined,
},
[Feature.ForceNightly]: {
defaultValue: false,
envVar: "CODEQL_ACTION_FORCE_NIGHTLY",
minimumVersion: undefined,
},
[Feature.IgnoreGeneratedFiles]: {
defaultValue: false,
envVar: "CODEQL_ACTION_IGNORE_GENERATED_FILES",
minimumVersion: undefined,
},
[Feature.ImprovedProxyCertificates]: {
defaultValue: false,
envVar: "CODEQL_ACTION_IMPROVED_PROXY_CERTIFICATES",
minimumVersion: undefined,
},
[Feature.JavaNetworkDebugging]: {
defaultValue: false,
envVar: "CODEQL_ACTION_JAVA_NETWORK_DEBUGGING",
minimumVersion: undefined,
},
[Feature.OverlayAnalysis]: {
defaultValue: false,
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS",
@@ -317,11 +297,6 @@ export const featureConfig = {
// cannot be found when interpreting results.
minimumVersion: undefined,
},
[Feature.StartProxyConnectionChecks]: {
defaultValue: false,
envVar: "CODEQL_ACTION_START_PROXY_CONNECTION_CHECKS",
minimumVersion: undefined,
},
[Feature.UploadOverlayDbToApi]: {
defaultValue: false,
envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API",
@@ -370,6 +345,17 @@ export interface FeatureEnablement {
*/
type GitHubFeatureFlagsApiResponse = Partial<Record<Feature, boolean>>;
// Even though we are currently only tracking the value of queried features, we use an object
// here rather than just a boolean to keep open the possibility of also tracking the reason
// for why a particular value was resolved (i.e. because of an environment variable or API result)
// in the future.
export interface QueriedFeatureStatus {
value: boolean;
}
/** A partial mapping of feature flags to the outcome of querying them. */
export type QueriedFeatures = Partial<Record<Feature, QueriedFeatureStatus>>;
export const FEATURE_FLAGS_FILE_NAME = "cached-feature-flags.json";
/**
@@ -380,6 +366,9 @@ export const FEATURE_FLAGS_FILE_NAME = "cached-feature-flags.json";
export class Features implements FeatureEnablement {
private gitHubFeatureFlags: GitHubFeatureFlags;
// Tracks features that have been queried at some point and the outcome.
private queriedFeatures: QueriedFeatures = {};
constructor(
gitHubVersion: util.GitHubVersion,
repositoryNwo: RepositoryNwo,
@@ -394,6 +383,11 @@ export class Features implements FeatureEnablement {
);
}
/** Gets a record of features that were queried and the corresponding outcomes. */
public getQueriedFeatures() {
return this.queriedFeatures;
}
async getDefaultCliVersion(
variant: util.GitHubVariant,
): Promise<CodeQLDefaultVersionInfo> {
@@ -413,6 +407,15 @@ export class Features implements FeatureEnablement {
* @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided.
*/
async getValue(feature: Feature, codeql?: CodeQL): Promise<boolean> {
const value = await this.getValueInternal(feature, codeql);
this.queriedFeatures[feature] = { value };
return value;
}
private async getValueInternal(
feature: Feature,
codeql?: CodeQL,
): Promise<boolean> {
// Narrow the type to FeatureConfig to avoid type errors. To avoid unsafe use of `as`, we
// check that the required properties exist using `satisfies`.
const config = featureConfig[
@@ -683,14 +686,7 @@ class GitHubFeatureFlags {
// Do nothing when not running against github.com
if (!supportsFeatureFlags(this.gitHubVersion.type)) {
this.logger.debug(
"Not running against github.com. Using default values for all features.",
);
this.hasAccessedRemoteFeatureFlags = false;
return {};
}
if (isCCR()) {
this.logger.debug(
"Feature flags are not supported in Copilot Code Review. Using default values for all features.",
"Not running against github.com. Disabling all toggleable features.",
);
this.hasAccessedRemoteFeatureFlags = false;
return {};
+5 -11
View File
@@ -78,17 +78,11 @@ export async function loadPropertiesFromApi(
}
}
if (Object.keys(properties).length === 0) {
logger.debug("No known repository properties were found.");
} else {
logger.debug(
"Loaded the following values for the repository properties:",
);
for (const [property, value] of Object.entries(properties).sort(
([nameA], [nameB]) => nameA.localeCompare(nameB),
)) {
logger.debug(` ${property}: ${value}`);
}
logger.debug("Loaded the following values for the repository properties:");
for (const [property, value] of Object.entries(properties).sort(
([nameA], [nameB]) => nameA.localeCompare(nameB),
)) {
logger.debug(` ${property}: ${value}`);
}
return properties;
+4 -1
View File
@@ -50,6 +50,7 @@ async function run(startedAt: Date) {
const logger = getActionsLogger();
let config: Config | undefined;
let features: Features | undefined = undefined;
let uploadFailedSarifResult:
| initActionPostHelper.UploadFailedSarifResult
| undefined;
@@ -62,7 +63,7 @@ async function run(startedAt: Date) {
checkGitHubVersionInRange(gitHubVersion, logger);
const repositoryNwo = getRepositoryNwo();
const features = new Features(
features = new Features(
gitHubVersion,
repositoryNwo,
getTemporaryDirectory(),
@@ -107,6 +108,7 @@ async function run(startedAt: Date) {
getActionsStatus(error),
startedAt,
config,
features?.getQueriedFeatures(),
await checkDiskUsage(logger),
logger,
error.message,
@@ -125,6 +127,7 @@ async function run(startedAt: Date) {
"success",
startedAt,
config,
features.getQueriedFeatures(),
await checkDiskUsage(logger),
logger,
);
+32 -91
View File
@@ -2,7 +2,6 @@ import * as fs from "fs";
import * as path from "path";
import * as core from "@actions/core";
import * as github from "@actions/github";
import * as io from "@actions/io";
import * as semver from "semver";
import { v4 as uuidV4 } from "uuid";
@@ -31,18 +30,14 @@ import {
} from "./dependency-caching";
import {
addDiagnostic,
addNoLanguageDiagnostic,
flushDiagnostics,
logUnwrittenDiagnostics,
makeDiagnostic,
makeTelemetryDiagnostic,
} from "./diagnostics";
import { EnvVar } from "./environment";
import { Feature, FeatureEnablement, Features } from "./feature-flags";
import {
loadPropertiesFromApi,
RepositoryProperties,
} from "./feature-flags/properties";
import { Feature, Features } from "./feature-flags";
import { loadPropertiesFromApi } from "./feature-flags/properties";
import {
checkInstallPython311,
checkPacksForOverlayCompatibility,
@@ -52,14 +47,14 @@ import {
initConfig,
runDatabaseInitCluster,
} from "./init";
import { JavaEnvVars, KnownLanguage } from "./languages";
import { KnownLanguage } from "./languages";
import { getActionsLogger, Logger } from "./logging";
import {
downloadOverlayBaseDatabaseFromCache,
OverlayBaseDatabaseDownloadStats,
OverlayDatabaseMode,
} from "./overlay-database-utils";
import { getRepositoryNwo, RepositoryNwo } from "./repository";
import { getRepositoryNwo } from "./repository";
import { ToolsSource } from "./setup-codeql";
import {
ActionName,
@@ -93,9 +88,6 @@ import {
checkActionVersion,
getErrorMessage,
BuildMode,
GitHubVersion,
Result,
getOptionalEnvVar,
} from "./util";
import { checkWorkflow } from "./workflow";
@@ -116,6 +108,7 @@ export const CODEQL_VERSION_JAR_MINIMIZATION = "2.23.0";
async function sendStartingStatusReport(
startedAt: Date,
config: Partial<configUtils.Config> | undefined,
features: Features | undefined,
logger: Logger,
) {
const statusReportBase = await createStatusReportBase(
@@ -123,6 +116,7 @@ async function sendStartingStatusReport(
"starting",
startedAt,
config,
features?.getQueriedFeatures(),
await checkDiskUsage(logger),
logger,
);
@@ -134,6 +128,7 @@ async function sendStartingStatusReport(
async function sendCompletedStatusReport(
startedAt: Date,
config: configUtils.Config | undefined,
features: Features | undefined,
configFile: string | undefined,
toolsDownloadStatusReport: ToolsDownloadStatusReport | undefined,
toolsFeatureFlagsValid: boolean | undefined,
@@ -149,6 +144,7 @@ async function sendCompletedStatusReport(
getActionsStatus(error),
startedAt,
config,
features?.getQueriedFeatures(),
await checkDiskUsage(logger),
logger,
error?.message,
@@ -211,7 +207,7 @@ async function run(startedAt: Date) {
let config: configUtils.Config | undefined;
let configFile: string | undefined;
let codeql: CodeQL;
let features: Features;
let features: Features | undefined = undefined;
let sourceRoot: string;
let toolsDownloadStatusReport: ToolsDownloadStatusReport | undefined;
let toolsFeatureFlagsValid: boolean | undefined;
@@ -246,12 +242,12 @@ async function run(startedAt: Date) {
);
// Fetch the values of known repository properties that affect us.
const repositoryPropertiesResult = await loadRepositoryProperties(
repositoryNwo,
gitHubVersion,
features,
logger,
const enableRepoProps = await features.getValue(
Feature.UseRepositoryProperties,
);
const repositoryProperties = enableRepoProps
? await loadPropertiesFromApi(gitHubVersion, logger, repositoryNwo)
: {};
// Create a unique identifier for this run.
const jobRunUuid = uuidV4();
@@ -285,7 +281,12 @@ async function run(startedAt: Date) {
}
// Send a status report indicating that an analysis is starting.
await sendStartingStatusReport(startedAt, { analysisKinds }, logger);
await sendStartingStatusReport(
startedAt,
{ analysisKinds },
features,
logger,
);
// Throw a `ConfigurationError` if the `setup-codeql` action has been run.
if (process.env[EnvVar.SETUP_CODEQL_ACTION_HAS_RUN] === "true") {
@@ -373,7 +374,7 @@ async function run(startedAt: Date) {
githubVersion: gitHubVersion,
apiDetails,
features,
repositoryProperties: repositoryPropertiesResult.orElse({}),
repositoryProperties,
enableFileCoverageInformation: await getFileCoverageInformationEnabled(
debugMode,
repositoryNwo,
@@ -382,19 +383,6 @@ async function run(startedAt: Date) {
logger,
});
if (repositoryPropertiesResult.isFailure()) {
addNoLanguageDiagnostic(
config,
makeTelemetryDiagnostic(
"codeql-action/repository-properties-load-failure",
"Failed to load repository properties",
{
error: getErrorMessage(repositoryPropertiesResult.value),
},
),
);
}
await checkInstallPython311(config.languages, codeql);
} catch (unwrappedError) {
const error = wrapError(unwrappedError);
@@ -404,6 +392,7 @@ async function run(startedAt: Date) {
error instanceof ConfigurationError ? "user-error" : "aborted",
startedAt,
config,
features?.getQueriedFeatures(),
await checkDiskUsage(logger),
logger,
error.message,
@@ -457,8 +446,11 @@ async function run(startedAt: Date) {
// Log CodeQL download telemetry, if appropriate
if (toolsDownloadStatusReport) {
addNoLanguageDiagnostic(
addDiagnostic(
config,
// Arbitrarily choose the first language. We could also choose all languages, but that
// increases the risk of misinterpreting the data.
config.languages[0],
makeTelemetryDiagnostic(
"codeql-action/bundle-download-telemetry",
"CodeQL bundle download telemetry",
@@ -754,19 +746,6 @@ async function run(startedAt: Date) {
}
}
// Enable Java network debugging if the FF is enabled.
if (await features.getValue(Feature.JavaNetworkDebugging)) {
// Get the existing value of `JAVA_TOOL_OPTIONS`, if any.
const existingJavaToolOptions =
getOptionalEnvVar(JavaEnvVars.JAVA_TOOL_OPTIONS) || "";
// Add the network debugging options.
core.exportVariable(
JavaEnvVars.JAVA_TOOL_OPTIONS,
`${existingJavaToolOptions} -Djavax.net.debug=all`,
);
}
// Write diagnostics to the database that we previously stored in memory because the database
// did not exist until now.
flushDiagnostics(config);
@@ -785,6 +764,7 @@ async function run(startedAt: Date) {
await sendCompletedStatusReport(
startedAt,
config,
features,
undefined, // We only report config info on success.
toolsDownloadStatusReport,
toolsFeatureFlagsValid,
@@ -802,6 +782,7 @@ async function run(startedAt: Date) {
await sendCompletedStatusReport(
startedAt,
config,
features,
configFile,
toolsDownloadStatusReport,
toolsFeatureFlagsValid,
@@ -813,49 +794,6 @@ async function run(startedAt: Date) {
);
}
/**
* Loads [repository properties](https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization) if applicable.
*/
async function loadRepositoryProperties(
repositoryNwo: RepositoryNwo,
gitHubVersion: GitHubVersion,
features: FeatureEnablement,
logger: Logger,
): Promise<Result<RepositoryProperties, unknown>> {
// See if we can skip loading repository properties early. In particular,
// repositories owned by users cannot have repository properties, so we can
// skip the API call entirely in that case.
const repositoryOwnerType = github.context.payload.repository?.owner.type;
logger.debug(
`Repository owner type is '${repositoryOwnerType ?? "unknown"}'.`,
);
if (repositoryOwnerType === "User") {
logger.debug(
"Skipping loading repository properties because the repository is owned by a user and " +
"therefore cannot have repository properties.",
);
return Result.success({});
}
if (!(await features.getValue(Feature.UseRepositoryProperties))) {
logger.debug(
"Skipping loading repository properties because the UseRepositoryProperties feature flag is disabled.",
);
return Result.success({});
}
try {
return Result.success(
await loadPropertiesFromApi(gitHubVersion, logger, repositoryNwo),
);
} catch (error) {
logger.warning(
`Failed to load repository properties: ${getErrorMessage(error)}`,
);
return Result.failure(error);
}
}
function getTrapCachingEnabled(): boolean {
// If the workflow specified something always respect that
const trapCaching = getOptionalInput("trap-caching");
@@ -872,8 +810,11 @@ async function recordZstdAvailability(
config: configUtils.Config,
zstdAvailability: ZstdAvailability,
) {
addNoLanguageDiagnostic(
addDiagnostic(
config,
// Arbitrarily choose the first language. We could also choose all languages, but that
// increases the risk of misinterpreting the data.
config.languages[0],
makeTelemetryDiagnostic(
"codeql-action/zstd-availability",
"Zstandard availability",
-8
View File
@@ -19,11 +19,3 @@ export enum KnownLanguage {
rust = "rust",
swift = "swift",
}
/** Java-specific environment variable names that we may care about. */
export enum JavaEnvVars {
JAVA_HOME = "JAVA_HOME",
JAVA_TOOL_OPTIONS = "JAVA_TOOL_OPTIONS",
JDK_JAVA_OPTIONS = "JDK_JAVA_OPTIONS",
_JAVA_OPTIONS = "_JAVA_OPTIONS",
}
+3
View File
@@ -44,6 +44,7 @@ async function run(startedAt: Date) {
"starting",
startedAt,
config,
undefined,
await checkDiskUsage(logger),
logger,
);
@@ -91,6 +92,7 @@ async function run(startedAt: Date) {
getActionsStatus(error),
startedAt,
config,
undefined,
await checkDiskUsage(logger),
logger,
error.message,
@@ -109,6 +111,7 @@ async function run(startedAt: Date) {
"success",
startedAt,
config,
undefined,
await checkDiskUsage(logger),
logger,
);
+7 -1
View File
@@ -42,6 +42,7 @@ import {
*/
async function sendCompletedStatusReport(
startedAt: Date,
features: Features | undefined,
toolsDownloadStatusReport: ToolsDownloadStatusReport | undefined,
toolsFeatureFlagsValid: boolean | undefined,
toolsSource: ToolsSource,
@@ -54,6 +55,7 @@ async function sendCompletedStatusReport(
getActionsStatus(error),
startedAt,
undefined,
features?.getQueriedFeatures(),
await checkDiskUsage(logger),
logger,
error?.message,
@@ -97,6 +99,7 @@ async function run(startedAt: Date): Promise<void> {
let toolsFeatureFlagsValid: boolean | undefined;
let toolsSource: ToolsSource;
let toolsVersion: string;
let features: Features | undefined = undefined;
try {
initializeEnvironment(getActionVersion());
@@ -114,7 +117,7 @@ async function run(startedAt: Date): Promise<void> {
const repositoryNwo = getRepositoryNwo();
const features = new Features(
features = new Features(
gitHubVersion,
repositoryNwo,
getTemporaryDirectory(),
@@ -130,6 +133,7 @@ async function run(startedAt: Date): Promise<void> {
"starting",
startedAt,
undefined,
features.getQueriedFeatures(),
await checkDiskUsage(logger),
logger,
);
@@ -166,6 +170,7 @@ async function run(startedAt: Date): Promise<void> {
error instanceof ConfigurationError ? "user-error" : "failure",
startedAt,
undefined,
features?.getQueriedFeatures(),
await checkDiskUsage(logger),
logger,
error.message,
@@ -179,6 +184,7 @@ async function run(startedAt: Date): Promise<void> {
await sendCompletedStatusReport(
startedAt,
features,
toolsDownloadStatusReport,
toolsFeatureFlagsValid,
toolsSource,
+6 -127
View File
@@ -1,22 +1,18 @@
import * as path from "path";
import * as github from "@actions/github";
import * as toolcache from "@actions/tool-cache";
import test, { ExecutionContext } from "ava";
import * as sinon from "sinon";
import * as actionsUtil from "./actions-util";
import * as api from "./api-client";
import { Feature, FeatureEnablement } from "./feature-flags";
import { getRunnerLogger } from "./logging";
import * as setupCodeql from "./setup-codeql";
import * as tar from "./tar";
import {
LINKED_CLI_VERSION,
LoggedMessage,
SAMPLE_DEFAULT_CLI_VERSION,
SAMPLE_DOTCOM_API_DETAILS,
checkExpectedLogMessages,
createFeatures,
getRecordingLogger,
initializeFeatures,
@@ -272,127 +268,13 @@ test("setupCodeQLBundle logs the CodeQL CLI version being used when asked to dow
});
});
test("getCodeQLSource correctly returns nightly CLI version when tools == nightly", async (t) => {
const loggedMessages: LoggedMessage[] = [];
const logger = getRecordingLogger(loggedMessages);
const features = createFeatures([]);
const expectedDate = "30260213";
const expectedTag = `codeql-bundle-${expectedDate}`;
// Ensure that we consistently select "zstd" for the test.
sinon.stub(process, "platform").value("linux");
sinon.stub(tar, "isZstdAvailable").resolves({
available: true,
foundZstdBinary: true,
});
const client = github.getOctokit("123");
const listReleases = sinon.stub(client.rest.repos, "listReleases");
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
listReleases.resolves({
data: [{ tag_name: expectedTag }],
} as any);
sinon.stub(api, "getApiClient").value(() => client);
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
const source = await setupCodeql.getCodeQLSource(
"nightly",
SAMPLE_DEFAULT_CLI_VERSION,
SAMPLE_DOTCOM_API_DETAILS,
GitHubVariant.DOTCOM,
false,
features,
logger,
);
// Check that the `CodeQLToolsSource` object matches our expectations.
const expectedVersion = `0.0.0-${expectedDate}`;
const expectedURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}/${setupCodeql.getCodeQLBundleName("zstd")}`;
t.deepEqual(source, {
bundleVersion: expectedDate,
cliVersion: undefined,
codeqlURL: expectedURL,
compressionMethod: "zstd",
sourceType: "download",
toolsVersion: expectedVersion,
} satisfies setupCodeql.CodeQLToolsSource);
// Afterwards, ensure that we see the expected messages in the log.
checkExpectedLogMessages(t, loggedMessages, [
"Using the latest CodeQL CLI nightly, as requested by 'tools: nightly'.",
`Bundle version ${expectedDate} is not in SemVer format. Will treat it as pre-release ${expectedVersion}.`,
`Attempting to obtain CodeQL tools. CLI version: unknown, bundle tag name: ${expectedTag}`,
`Using CodeQL CLI sourced from ${expectedURL}`,
]);
});
});
test("getCodeQLSource correctly returns nightly CLI version when forced by FF", async (t) => {
const loggedMessages: LoggedMessage[] = [];
const logger = getRecordingLogger(loggedMessages);
const features = createFeatures([Feature.ForceNightly]);
const expectedDate = "30260213";
const expectedTag = `codeql-bundle-${expectedDate}`;
// Ensure that we consistently select "zstd" for the test.
sinon.stub(process, "platform").value("linux");
sinon.stub(tar, "isZstdAvailable").resolves({
available: true,
foundZstdBinary: true,
});
const client = github.getOctokit("123");
const listReleases = sinon.stub(client.rest.repos, "listReleases");
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
listReleases.resolves({
data: [{ tag_name: expectedTag }],
} as any);
sinon.stub(api, "getApiClient").value(() => client);
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
process.env["GITHUB_EVENT_NAME"] = "dynamic";
const source = await setupCodeql.getCodeQLSource(
undefined,
SAMPLE_DEFAULT_CLI_VERSION,
SAMPLE_DOTCOM_API_DETAILS,
GitHubVariant.DOTCOM,
false,
features,
logger,
);
// Check that the `CodeQLToolsSource` object matches our expectations.
const expectedVersion = `0.0.0-${expectedDate}`;
const expectedURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}/${setupCodeql.getCodeQLBundleName("zstd")}`;
t.deepEqual(source, {
bundleVersion: expectedDate,
cliVersion: undefined,
codeqlURL: expectedURL,
compressionMethod: "zstd",
sourceType: "download",
toolsVersion: expectedVersion,
} satisfies setupCodeql.CodeQLToolsSource);
// Afterwards, ensure that we see the expected messages in the log.
checkExpectedLogMessages(t, loggedMessages, [
`Using the latest CodeQL CLI nightly, as forced by the ${Feature.ForceNightly} feature flag.`,
`Bundle version ${expectedDate} is not in SemVer format. Will treat it as pre-release ${expectedVersion}.`,
`Attempting to obtain CodeQL tools. CLI version: unknown, bundle tag name: ${expectedTag}`,
`Using CodeQL CLI sourced from ${expectedURL}`,
]);
});
});
test("getCodeQLSource correctly returns latest version from toolcache when tools == toolcache", async (t) => {
const loggedMessages: LoggedMessage[] = [];
const logger = getRecordingLogger(loggedMessages);
const features = createFeatures([Feature.AllowToolcacheInput]);
process.env["GITHUB_EVENT_NAME"] = "dynamic";
const latestToolcacheVersion = "3.2.1";
const latestVersionPath = "/path/to/latest";
const testVersions = ["2.3.1", latestToolcacheVersion, "1.2.3"];
@@ -406,8 +288,6 @@ test("getCodeQLSource correctly returns latest version from toolcache when tools
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
process.env["GITHUB_EVENT_NAME"] = "dynamic";
const source = await setupCodeql.getCodeQLSource(
"toolcache",
SAMPLE_DEFAULT_CLI_VERSION,
@@ -463,17 +343,16 @@ const toolcacheInputFallbackMacro = test.macro({
const logger = getRecordingLogger(loggedMessages);
const features = createFeatures(featureList);
for (const [k, v] of Object.entries(environment)) {
process.env[k] = v;
}
const findAllVersionsStub = sinon
.stub(toolcache, "findAllVersions")
.returns(testVersions);
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
for (const [k, v] of Object.entries(environment)) {
process.env[k] = v;
}
const source = await setupCodeql.getCodeQLSource(
"toolcache",
SAMPLE_DEFAULT_CLI_VERSION,
+8 -62
View File
@@ -10,7 +10,6 @@ import { v4 as uuidV4 } from "uuid";
import { isDynamicWorkflow, isRunningLocalAction } from "./actions-util";
import * as api from "./api-client";
import * as defaults from "./defaults.json";
import { addNoLanguageDiagnostic, makeDiagnostic } from "./diagnostics";
import {
CODEQL_VERSION_ZSTD_BUNDLE,
CodeQLDefaultVersionInfo,
@@ -56,9 +55,7 @@ function getCodeQLBundleExtension(
}
}
export function getCodeQLBundleName(
compressionMethod: tar.CompressionMethod,
): string {
function getCodeQLBundleName(compressionMethod: tar.CompressionMethod): string {
const extension = getCodeQLBundleExtension(compressionMethod);
let platform: string;
@@ -199,7 +196,7 @@ export function convertToSemVer(version: string, logger: Logger): string {
return s;
}
export type CodeQLToolsSource =
type CodeQLToolsSource =
| {
codeqlTarPath: string;
compressionMethod: tar.CompressionMethod;
@@ -264,20 +261,6 @@ async function findOverridingToolsInCache(
return undefined;
}
/**
* Determines where the CodeQL CLI we want to use comes from. This can be from a local file,
* the Actions toolcache, or a download.
*
* @param toolsInput The argument provided for the `tools` input, if any.
* @param defaultCliVersion The default CLI version that's linked to the CodeQL Action.
* @param apiDetails Information about the GitHub API.
* @param variant The GitHub variant we are running on.
* @param tarSupportsZstd Whether zstd is supported by `tar`.
* @param features Information about enabled features.
* @param logger The logger to use.
*
* @returns Information about where the CodeQL CLI we want to use comes from.
*/
export async function getCodeQLSource(
toolsInput: string | undefined,
defaultCliVersion: CodeQLDefaultVersionInfo,
@@ -287,9 +270,6 @@ export async function getCodeQLSource(
features: FeatureEnablement,
logger: Logger,
): Promise<CodeQLToolsSource> {
// If there is an explicit `tools` input, it's not one of the reserved values, and it doesn't appear
// to point to a URL, then we assume it is a local path and use the CLI from there.
// TODO: This appears to misclassify filenames that happen to start with `http` as URLs.
if (
toolsInput &&
!isReservedToolsValue(toolsInput) &&
@@ -322,47 +302,13 @@ export async function getCodeQLSource(
*/
let url: string | undefined;
// We allow forcing the nightly CLI via the FF for `dynamic` events (or in test mode) where the
// `tools` input cannot be adjusted to explicitly request it.
const canForceNightlyWithFF = isDynamicWorkflow() || util.isInTestMode();
const forceNightlyValueFF = await features.getValue(Feature.ForceNightly);
const forceNightly = forceNightlyValueFF && canForceNightlyWithFF;
// For advanced workflows, a value from `CODEQL_NIGHTLY_TOOLS_INPUTS` can be specified explicitly
// for the `tools` input in the workflow file.
const nightlyRequestedByToolsInput =
if (
toolsInput !== undefined &&
CODEQL_NIGHTLY_TOOLS_INPUTS.includes(toolsInput);
if (forceNightly || nightlyRequestedByToolsInput) {
if (forceNightly) {
logger.info(
`Using the latest CodeQL CLI nightly, as forced by the ${Feature.ForceNightly} feature flag.`,
);
addNoLanguageDiagnostic(
undefined,
makeDiagnostic(
"codeql-action/forced-nightly-cli",
"A nightly release of CodeQL was used",
{
markdownMessage:
"GitHub configured this analysis to use a nightly release of CodeQL to allow you to preview changes from an upcoming release.\n\n" +
"Nightly releases do not undergo the same validation as regular releases and may lead to analysis instability.\n\n" +
"If use of a nightly CodeQL release for this analysis is unexpected, please contact GitHub support.",
visibility: {
cliSummaryTable: true,
statusPage: true,
telemetry: true,
},
severity: "note",
},
),
);
} else {
logger.info(
`Using the latest CodeQL CLI nightly, as requested by 'tools: ${toolsInput}'.`,
);
}
CODEQL_NIGHTLY_TOOLS_INPUTS.includes(toolsInput)
) {
logger.info(
`Using the latest CodeQL CLI nightly, as requested by 'tools: ${toolsInput}'.`,
);
toolsInput = await getNightlyToolsUrl(logger);
}
+175 -61
View File
@@ -2,37 +2,133 @@ import { ChildProcess, spawn } from "child_process";
import * as path from "path";
import * as core from "@actions/core";
import * as toolcache from "@actions/tool-cache";
import { pki } from "node-forge";
import * as actionsUtil from "./actions-util";
import { getGitHubVersion } from "./api-client";
import { Feature, Features } from "./feature-flags";
import { getApiDetails, getAuthorizationHeaderFor } from "./api-client";
import { Config } from "./config-utils";
import { KnownLanguage } from "./languages";
import { getActionsLogger, Logger } from "./logging";
import { getRepositoryNwo } from "./repository";
import {
credentialToStr,
Credential,
getCredentials,
getProxyBinaryPath,
getSafeErrorMessage,
getDownloadUrl,
parseLanguage,
ProxyInfo,
sendFailedStatusReport,
sendSuccessStatusReport,
Registry,
ProxyConfig,
UPDATEJOB_PROXY,
} from "./start-proxy";
import { generateCertificateAuthority } from "./start-proxy/ca";
import { checkProxyEnvironment } from "./start-proxy/environment";
import { checkConnections } from "./start-proxy/reachability";
import { ActionName, sendUnhandledErrorStatusReport } from "./status-report";
import {
ActionName,
createStatusReportBase,
getActionsStatus,
sendStatusReport,
sendUnhandledErrorStatusReport,
StatusReportBase,
} from "./status-report";
import * as util from "./util";
const KEY_SIZE = 2048;
const KEY_EXPIRY_YEARS = 2;
type CertificateAuthority = {
cert: string;
key: string;
};
type BasicAuthCredentials = {
username: string;
password: string;
};
type ProxyConfig = {
all_credentials: Credential[];
ca: CertificateAuthority;
proxy_auth?: BasicAuthCredentials;
};
const CERT_SUBJECT = [
{
name: "commonName",
value: "Dependabot Internal CA",
},
{
name: "organizationName",
value: "GitHub inc.",
},
{
shortName: "OU",
value: "Dependabot",
},
{
name: "countryName",
value: "US",
},
{
shortName: "ST",
value: "California",
},
{
name: "localityName",
value: "San Francisco",
},
];
function generateCertificateAuthority(): CertificateAuthority {
const keys = pki.rsa.generateKeyPair(KEY_SIZE);
const cert = pki.createCertificate();
cert.publicKey = keys.publicKey;
cert.serialNumber = "01";
cert.validity.notBefore = new Date();
cert.validity.notAfter = new Date();
cert.validity.notAfter.setFullYear(
cert.validity.notBefore.getFullYear() + KEY_EXPIRY_YEARS,
);
cert.setSubject(CERT_SUBJECT);
cert.setIssuer(CERT_SUBJECT);
cert.setExtensions([{ name: "basicConstraints", cA: true }]);
cert.sign(keys.privateKey);
const pem = pki.certificateToPem(cert);
const key = pki.privateKeyToPem(keys.privateKey);
return { cert: pem, key };
}
interface StartProxyStatus extends StatusReportBase {
// A comma-separated list of registry types which are configured for CodeQL.
// This only includes registry types we support, not all that are configured.
registry_types: string;
}
async function sendSuccessStatusReport(
startedAt: Date,
config: Partial<Config>,
registry_types: string[],
logger: Logger,
) {
const statusReportBase = await createStatusReportBase(
ActionName.StartProxy,
"success",
startedAt,
config,
undefined,
await util.checkDiskUsage(logger),
logger,
);
if (statusReportBase !== undefined) {
const statusReport: StartProxyStatus = {
...statusReportBase,
registry_types: registry_types.join(","),
};
await sendStatusReport(statusReport);
}
}
async function run(startedAt: Date) {
// To capture errors appropriately, keep as much code within the try-catch as
// possible, and only use safe functions outside.
const logger = getActionsLogger();
let features: Features | undefined;
let language: KnownLanguage | undefined;
try {
@@ -44,21 +140,9 @@ async function run(startedAt: Date) {
const proxyLogFilePath = path.resolve(tempDir, "proxy.log");
core.saveState("proxy-log-file", proxyLogFilePath);
// Initialise FFs.
const repositoryNwo = getRepositoryNwo();
const gitHubVersion = await getGitHubVersion();
features = new Features(
gitHubVersion,
repositoryNwo,
actionsUtil.getTemporaryDirectory(),
logger,
);
// Get the language input.
// Get the configuration options
const languageInput = actionsUtil.getOptionalInput("language");
language = languageInput ? parseLanguage(languageInput) : undefined;
// Get the registry configurations from one of the inputs.
const credentials = getCredentials(
logger,
actionsUtil.getOptionalInput("registry_secrets"),
@@ -77,22 +161,7 @@ async function run(startedAt: Date) {
.join("\n")}`,
);
// Check the environment for any configurations which may affect the proxy.
// This is a best effort process to give us insights into potential factors
// which may affect the operation of our proxy.
if (core.isDebug() || util.isInTestMode()) {
try {
await checkProxyEnvironment(logger, language);
} catch (err) {
logger.debug(
`Unable to inspect runner environment: ${util.getErrorMessage(err)}`,
);
}
}
const ca = generateCertificateAuthority(
await features.getValue(Feature.ImprovedProxyCertificates),
);
const ca = generateCertificateAuthority();
const proxyConfig: ProxyConfig = {
all_credentials: credentials,
@@ -101,17 +170,7 @@ async function run(startedAt: Date) {
// Start the Proxy
const proxyBin = await getProxyBinaryPath(logger);
const proxyInfo = await startProxy(
proxyBin,
proxyConfig,
proxyLogFilePath,
logger,
);
// Check that the private registries are reachable.
if (await features.getValue(Feature.StartProxyConnectionChecks)) {
await checkConnections(logger, proxyInfo);
}
await startProxy(proxyBin, proxyConfig, proxyLogFilePath, logger);
// Report success if we have reached this point.
await sendSuccessStatusReport(
@@ -123,7 +182,26 @@ async function run(startedAt: Date) {
logger,
);
} catch (unwrappedError) {
await sendFailedStatusReport(logger, startedAt, language, unwrappedError);
const error = util.wrapError(unwrappedError);
core.setFailed(`start-proxy action failed: ${error.message}`);
// We skip sending the error message and stack trace here to avoid the possibility
// of leaking any sensitive information into the telemetry.
const errorStatusReportBase = await createStatusReportBase(
ActionName.StartProxy,
getActionsStatus(error),
startedAt,
{
languages: language && [language],
},
undefined,
await util.checkDiskUsage(logger),
logger,
"Error from start-proxy Action omitted",
);
if (errorStatusReportBase !== undefined) {
await sendStatusReport(errorStatusReportBase);
}
}
}
@@ -138,7 +216,7 @@ async function runWrapper() {
await sendUnhandledErrorStatusReport(
ActionName.StartProxy,
startedAt,
getSafeErrorMessage(util.wrapError(error)),
new Error("Error from start-proxy Action omitted"),
logger,
);
}
@@ -149,7 +227,7 @@ async function startProxy(
config: ProxyConfig,
logFilePath: string,
logger: Logger,
): Promise<ProxyInfo> {
) {
const host = "127.0.0.1";
let port = 49152;
let subprocess: ChildProcess | undefined = undefined;
@@ -192,15 +270,51 @@ async function startProxy(
core.setOutput("proxy_port", port.toString());
core.setOutput("proxy_ca_certificate", config.ca.cert);
const registry_urls: Registry[] = config.all_credentials
const registry_urls = config.all_credentials
.filter((credential) => credential.url !== undefined)
.map((credential) => ({
type: credential.type,
url: credential.url,
}));
core.setOutput("proxy_urls", JSON.stringify(registry_urls));
}
return { host, port, cert: config.ca.cert, registries: registry_urls };
async function getProxyBinaryPath(logger: Logger): Promise<string> {
const proxyFileName =
process.platform === "win32" ? `${UPDATEJOB_PROXY}.exe` : UPDATEJOB_PROXY;
const proxyInfo = await getDownloadUrl(logger);
let proxyBin = toolcache.find(proxyFileName, proxyInfo.version);
if (!proxyBin) {
const apiDetails = getApiDetails();
const authorization = getAuthorizationHeaderFor(
logger,
apiDetails,
proxyInfo.url,
);
const temp = await toolcache.downloadTool(
proxyInfo.url,
undefined,
authorization,
{
accept: "application/octet-stream",
},
);
const extracted = await toolcache.extractTar(temp);
proxyBin = await toolcache.cacheDir(
extracted,
proxyFileName,
proxyInfo.version,
);
}
proxyBin = path.join(proxyBin, proxyFileName);
return proxyBin;
}
function credentialToStr(c: Credential): string {
return `Type: ${c.type}; Host: ${c.host}; Url: ${c.url} Username: ${
c.username
}; Password: ${c.password !== undefined}; Token: ${c.token !== undefined}`;
}
void runWrapper();
+2 -320
View File
@@ -1,110 +1,21 @@
import * as filepath from "path";
import * as core from "@actions/core";
import * as toolcache from "@actions/tool-cache";
import test, { ExecutionContext } from "ava";
import test from "ava";
import sinon from "sinon";
import * as apiClient from "./api-client";
import * as defaults from "./defaults.json";
import { KnownLanguage } from "./languages";
import { getRunnerLogger, Logger } from "./logging";
import { getRunnerLogger } from "./logging";
import * as startProxyExports from "./start-proxy";
import { parseLanguage } from "./start-proxy";
import * as statusReport from "./status-report";
import {
checkExpectedLogMessages,
getRecordingLogger,
makeTestToken,
setupTests,
withRecordingLoggerAsync,
} from "./testing-utils";
import { ConfigurationError } from "./util";
setupTests(test);
const sendFailedStatusReportTest = test.macro({
exec: async (
t: ExecutionContext<unknown>,
err: Error,
expectedMessage: string,
expectedStatus: statusReport.ActionStatus = "failure",
) => {
const now = new Date();
// Override core.setFailed to avoid it setting the program's exit code
sinon.stub(core, "setFailed").returns();
const createStatusReportBase = sinon.stub(
statusReport,
"createStatusReportBase",
);
createStatusReportBase.resolves(undefined);
await withRecordingLoggerAsync(async (logger) => {
await startProxyExports.sendFailedStatusReport(
logger,
now,
undefined,
err,
);
// Check that the stub has been called exactly once, with the expected arguments,
// but not with the message from the error.
sinon.assert.calledOnceWithExactly(
createStatusReportBase,
statusReport.ActionName.StartProxy,
expectedStatus,
now,
sinon.match.any,
sinon.match.any,
sinon.match.any,
expectedMessage,
);
t.false(
createStatusReportBase.calledWith(
statusReport.ActionName.StartProxy,
expectedStatus,
now,
sinon.match.any,
sinon.match.any,
sinon.match.any,
sinon.match((msg: string) => msg.includes(err.message)),
),
"createStatusReportBase was called with the error message",
);
});
},
title: (providedTitle = "") => `sendFailedStatusReport - ${providedTitle}`,
});
test(
"reports generic error message for non-StartProxyError error",
sendFailedStatusReportTest,
new Error("Something went wrong today"),
"Error from start-proxy Action omitted (Error).",
);
test(
"reports generic error message for non-StartProxyError error with safe error message",
sendFailedStatusReportTest,
new Error(
startProxyExports.getStartProxyErrorMessage(
startProxyExports.StartProxyErrorType.DownloadFailed,
),
),
"Error from start-proxy Action omitted (Error).",
);
test(
"reports generic error message for ConfigurationError error",
sendFailedStatusReportTest,
new ConfigurationError("Something went wrong today"),
"Error from start-proxy Action omitted (ConfigurationError).",
"user-error",
);
const toEncodedJSON = (data: any) =>
Buffer.from(JSON.stringify(data)).toString("base64");
@@ -175,27 +86,6 @@ test("getCredentials throws error when credential is not an object", async (t) =
}
});
test("getCredentials throws error when credential is missing type", async (t) => {
const testCredentials = [[{ token: "abc", url: "https://localhost" }]].map(
toEncodedJSON,
);
for (const testCredential of testCredentials) {
t.throws(
() =>
startProxyExports.getCredentials(
getRunnerLogger(true),
undefined,
testCredential,
undefined,
),
{
message: "Invalid credentials - must have a type",
},
);
}
});
test("getCredentials throws error when credential missing host and url", async (t) => {
const testCredentials = [
[{ type: "npm_registry", token: "abc" }],
@@ -411,211 +301,3 @@ test("getDownloadUrl returns matching release asset", async (t) => {
t.is(info.version, defaults.cliVersion);
t.is(info.url, "url-we-want");
});
test("credentialToStr - hides passwords", (t) => {
const secret = "password123";
const credential = {
type: "maven_credential",
password: secret,
url: "https://localhost",
};
const str = startProxyExports.credentialToStr(credential);
t.false(str.includes(secret));
t.is(
"Type: maven_credential; Host: undefined; Url: https://localhost Username: undefined; Password: true; Token: false",
str,
);
});
test("credentialToStr - hides tokens", (t) => {
const secret = "password123";
const credential = {
type: "maven_credential",
token: secret,
url: "https://localhost",
};
const str = startProxyExports.credentialToStr(credential);
t.false(str.includes(secret));
t.is(
"Type: maven_credential; Host: undefined; Url: https://localhost Username: undefined; Password: false; Token: true",
str,
);
});
test("getSafeErrorMessage - returns actual message for `StartProxyError`", (t) => {
const error = new startProxyExports.StartProxyError(
startProxyExports.StartProxyErrorType.DownloadFailed,
);
t.is(
startProxyExports.getSafeErrorMessage(error),
startProxyExports.getStartProxyErrorMessage(error.errorType),
);
});
test("getSafeErrorMessage - does not return message for arbitrary errors", (t) => {
const error = new Error(
startProxyExports.getStartProxyErrorMessage(
startProxyExports.StartProxyErrorType.DownloadFailed,
),
);
const message = startProxyExports.getSafeErrorMessage(error);
t.not(message, error.message);
t.assert(message.startsWith("Error from start-proxy Action omitted"));
t.assert(message.includes(error.name));
});
const wrapFailureTest = test.macro({
exec: async (
t: ExecutionContext<unknown>,
setup: () => void,
fn: (logger: Logger) => Promise<void>,
) => {
await withRecordingLoggerAsync(async (logger) => {
setup();
await t.throwsAsync(fn(logger), {
instanceOf: startProxyExports.StartProxyError,
});
});
},
title: (providedTitle) => `${providedTitle} - wraps errors on failure`,
});
test("downloadProxy - returns file path on success", async (t) => {
await withRecordingLoggerAsync(async (logger) => {
const testPath = "/some/path";
sinon.stub(toolcache, "downloadTool").resolves(testPath);
const result = await startProxyExports.downloadProxy(
logger,
"url",
undefined,
);
t.is(result, testPath);
});
});
test(
"downloadProxy",
wrapFailureTest,
() => {
sinon.stub(toolcache, "downloadTool").throws();
},
async (logger) => {
await startProxyExports.downloadProxy(logger, "url", undefined);
},
);
test("extractProxy - returns file path on success", async (t) => {
await withRecordingLoggerAsync(async (logger) => {
const testPath = "/some/path";
sinon.stub(toolcache, "extractTar").resolves(testPath);
const result = await startProxyExports.extractProxy(logger, "/other/path");
t.is(result, testPath);
});
});
test(
"extractProxy",
wrapFailureTest,
() => {
sinon.stub(toolcache, "extractTar").throws();
},
async (logger) => {
await startProxyExports.extractProxy(logger, "path");
},
);
test("cacheProxy - returns file path on success", async (t) => {
await withRecordingLoggerAsync(async (logger) => {
const testPath = "/some/path";
sinon.stub(toolcache, "cacheDir").resolves(testPath);
const result = await startProxyExports.cacheProxy(
logger,
"/other/path",
"proxy",
"1.0",
);
t.is(result, testPath);
});
});
test(
"cacheProxy",
wrapFailureTest,
() => {
sinon.stub(toolcache, "cacheDir").throws();
},
async (logger) => {
await startProxyExports.cacheProxy(logger, "/other/path", "proxy", "1.0");
},
);
test("getProxyBinaryPath - returns path from tool cache if available", async (t) => {
mockGetReleaseByTag();
await withRecordingLoggerAsync(async (logger) => {
const toolcachePath = "/path/to/proxy/dir";
sinon.stub(toolcache, "find").returns(toolcachePath);
const path = await startProxyExports.getProxyBinaryPath(logger);
t.assert(path);
t.is(
path,
filepath.join(toolcachePath, startProxyExports.getProxyFilename()),
);
});
});
test("getProxyBinaryPath - downloads proxy if not in cache", async (t) => {
const downloadUrl = "url-we-want";
mockGetReleaseByTag([
{ name: startProxyExports.getProxyPackage(), url: downloadUrl },
]);
await withRecordingLoggerAsync(async (logger) => {
const toolcachePath = "/path/to/proxy/dir";
const find = sinon.stub(toolcache, "find").returns("");
const getApiDetails = sinon.stub(apiClient, "getApiDetails").returns({
auth: "",
url: "",
apiURL: "",
});
const getAuthorizationHeaderFor = sinon
.stub(apiClient, "getAuthorizationHeaderFor")
.returns(undefined);
const archivePath = "/path/to/archive";
const downloadTool = sinon
.stub(toolcache, "downloadTool")
.resolves(archivePath);
const extractedPath = "/path/to/extracted";
const extractTar = sinon
.stub(toolcache, "extractTar")
.resolves(extractedPath);
const cacheDir = sinon.stub(toolcache, "cacheDir").resolves(toolcachePath);
const path = await startProxyExports.getProxyBinaryPath(logger);
t.assert(find.calledOnce);
t.assert(getApiDetails.calledOnce);
t.assert(getAuthorizationHeaderFor.calledOnce);
t.assert(downloadTool.calledOnceWith(downloadUrl));
t.assert(extractTar.calledOnceWith(archivePath));
t.assert(cacheDir.calledOnceWith(extractedPath));
t.assert(path);
t.is(
path,
filepath.join(toolcachePath, startProxyExports.getProxyFilename()),
);
});
});
+20 -314
View File
@@ -1,174 +1,26 @@
import * as path from "path";
import * as core from "@actions/core";
import * as toolcache from "@actions/tool-cache";
import {
getApiClient,
getApiDetails,
getAuthorizationHeaderFor,
} from "./api-client";
import { getApiClient } from "./api-client";
import * as artifactScanner from "./artifact-scanner";
import { Config } from "./config-utils";
import * as defaults from "./defaults.json";
import { KnownLanguage } from "./languages";
import { Logger } from "./logging";
import {
Address,
RawCredential,
Registry,
Credential,
} from "./start-proxy/types";
import {
ActionName,
createStatusReportBase,
getActionsStatus,
sendStatusReport,
StatusReportBase,
} from "./status-report";
import * as util from "./util";
import { ConfigurationError, getErrorMessage, isDefined } from "./util";
export * from "./start-proxy/types";
/**
* Enumerates specific error types for which we have corresponding error messages that
* are safe to include in status reports.
*/
export enum StartProxyErrorType {
DownloadFailed,
ExtractionFailed,
CacheFailed,
}
/**
* @returns The error message corresponding to the error type.
*/
export function getStartProxyErrorMessage(
errorType: StartProxyErrorType,
): string {
switch (errorType) {
case StartProxyErrorType.DownloadFailed:
return "Failed to download proxy archive.";
case StartProxyErrorType.ExtractionFailed:
return "Failed to extract proxy archive.";
case StartProxyErrorType.CacheFailed:
return "Failed to add proxy to toolcache";
}
}
/**
* We want to avoid accidentally leaking secrets that may be contained in exception
* messages in the `start-proxy` action. Consequently, we don't report the messages
* of arbitrary exceptions. This type of error ensures that the message is one from
* `StartProxyErrorType` and therefore safe to include in a status report.
*/
export class StartProxyError extends Error {
public readonly errorType: StartProxyErrorType;
constructor(errorType: StartProxyErrorType) {
super();
this.errorType = errorType;
}
}
interface StartProxyStatus extends StatusReportBase {
// A comma-separated list of registry types which are configured for CodeQL.
// This only includes registry types we support, not all that are configured.
registry_types: string;
}
/**
* Sends a status report for the `start-proxy` action indicating a successful outcome.
*
* @param startedAt When the action was started.
* @param config The configuration used.
* @param registry_types The types of registries that are configured.
* @param logger The logger to use.
*/
export async function sendSuccessStatusReport(
startedAt: Date,
config: Partial<Config>,
registry_types: string[],
logger: Logger,
) {
const statusReportBase = await createStatusReportBase(
ActionName.StartProxy,
"success",
startedAt,
config,
await util.checkDiskUsage(logger),
logger,
);
if (statusReportBase !== undefined) {
const statusReport: StartProxyStatus = {
...statusReportBase,
registry_types: registry_types.join(","),
};
await sendStatusReport(statusReport);
}
}
/**
* Returns an error message for `error` that can safely be reported in a status report,
* i.e. that does not contain sensitive information.
*
* @param error The error for which to get an error message.
*/
export function getSafeErrorMessage(error: Error): string {
// If the error is a `StartProxyError`, resolve the error type to the corresponding
// error message.
if (error instanceof StartProxyError) {
return getStartProxyErrorMessage(error.errorType);
}
// Otherwise, omit the actual error message.
return `Error from start-proxy Action omitted (${error.constructor.name}).`;
}
/**
* Sends a status report for the `start-proxy` action indicating a failure.
*
* @param logger The logger to use.
* @param startedAt When the action was started.
* @param language The language provided as input, if any.
* @param unwrappedError The exception that was thrown.
*/
export async function sendFailedStatusReport(
logger: Logger,
startedAt: Date,
language: KnownLanguage | undefined,
unwrappedError: unknown,
) {
const error = util.wrapError(unwrappedError);
core.setFailed(`start-proxy action failed: ${error.message}`);
// To avoid the possibility of leaking sensitive information into the telemetry,
// we don't include arbitrary error messages. Instead, `getSafeErrorMessage` will
// return a generic message that includes the type of the error, unless it can decide
// that the message is safe to include.
const statusReportMessage = getSafeErrorMessage(error);
const errorStatusReportBase = await createStatusReportBase(
ActionName.StartProxy,
getActionsStatus(error),
startedAt,
{
languages: language && [language],
},
await util.checkDiskUsage(logger),
logger,
statusReportMessage,
);
if (errorStatusReportBase !== undefined) {
await sendStatusReport(errorStatusReportBase);
}
}
export const UPDATEJOB_PROXY = "update-job-proxy";
export const UPDATEJOB_PROXY_VERSION = "v2.0.20250624110901";
const UPDATEJOB_PROXY_URL_PREFIX =
"https://github.com/github/codeql-action/releases/download/codeql-bundle-v2.22.0/";
export type Credential = {
type: string;
host?: string;
url?: string;
username?: string;
password?: string;
token?: string;
};
/*
* Language aliases supported by the start-proxy Action.
*
@@ -228,31 +80,6 @@ const LANGUAGE_TO_REGISTRY_TYPE: Partial<Record<KnownLanguage, string[]>> = {
go: ["goproxy_server", "git_source"],
} as const;
/**
* Extracts an `Address` value from the given `Registry` value by determining whether it has
* a `url` value, or no `url` value but a `host` value.
*
* @throws A `ConfigurationError` if the `Registry` value contains neither a `url` or `host` field.
*/
function getRegistryAddress(registry: Partial<Registry>): Address {
if (isDefined(registry.url)) {
return {
url: registry.url,
host: registry.host,
};
} else if (isDefined(registry.host)) {
return {
url: undefined,
host: registry.host,
};
} else {
// The proxy needs one of these to work. If both are defined, the url has the precedence.
throw new ConfigurationError(
"Invalid credentials - must specify host or url",
);
}
}
// getCredentials returns registry credentials from action inputs.
// It prefers `registries_credentials` over `registry_secrets`.
// If neither is set, it returns an empty array.
@@ -279,9 +106,9 @@ export function getCredentials(
}
// Parse and validate the credentials
let parsed: RawCredential[];
let parsed: Credential[];
try {
parsed = JSON.parse(credentialsStr) as RawCredential[];
parsed = JSON.parse(credentialsStr) as Credential[];
} catch {
// Don't log the error since it might contain sensitive information.
logger.error("Failed to parse the credentials data.");
@@ -301,11 +128,6 @@ export function getCredentials(
throw new ConfigurationError("Invalid credentials - must be an object");
}
// The configuration must have a type.
if (!isDefined(e.type)) {
throw new ConfigurationError("Invalid credentials - must have a type");
}
// Mask credentials to reduce chance of accidental leakage in logs.
if (isDefined(e.password)) {
core.setSecret(e.password);
@@ -314,7 +136,12 @@ export function getCredentials(
core.setSecret(e.token);
}
const address = getRegistryAddress(e);
if (!isDefined(e.url) && !isDefined(e.host)) {
// The proxy needs one of these to work. If both are defined, the url has the precedence.
throw new ConfigurationError(
"Invalid credentials - must specify host or url",
);
}
// Filter credentials based on language if specified. `type` is the registry type.
// E.g., "maven_feed" for Java/Kotlin, "nuget_repository" for C#.
@@ -357,10 +184,11 @@ export function getCredentials(
out.push({
type: e.type,
host: e.host,
url: e.url,
username: e.username,
password: e.password,
token: e.token,
...address,
});
}
return out;
@@ -449,125 +277,3 @@ export async function getDownloadUrl(
version: UPDATEJOB_PROXY_VERSION,
};
}
/**
* Pretty-prints a `Credential` value to a string, but hides the actual password or token values.
*
* @param c The credential to convert to a string.
*/
export function credentialToStr(c: Credential): string {
return `Type: ${c.type}; Host: ${c.host}; Url: ${c.url} Username: ${
c.username
}; Password: ${c.password !== undefined}; Token: ${c.token !== undefined}`;
}
/**
* Attempts to download a file from `url` into the toolcache.
*
* @param logger The logger to use.
* @param url The URL to download the proxy binary from.
* @param authorization The authorization information to use.
* @returns If successful, the path to the downloaded file.
*/
export async function downloadProxy(
logger: Logger,
url: string,
authorization: string | undefined,
) {
try {
// Download the proxy archive from `url`. We let `downloadTool` choose where
// to store it. The path to the downloaded file will be returned if successful.
return toolcache.downloadTool(url, /* dest: */ undefined, authorization, {
accept: "application/octet-stream",
});
} catch (error) {
logger.error(
`Failed to download proxy archive from ${url}: ${getErrorMessage(error)}`,
);
throw new StartProxyError(StartProxyErrorType.DownloadFailed);
}
}
/**
* Attempts to extract the proxy binary from the `archive`.
*
* @param logger The logger to use.
* @param archive The archive to extract.
* @returns The path to the extracted file(s).
*/
export async function extractProxy(logger: Logger, archive: string) {
try {
return await toolcache.extractTar(archive);
} catch (error) {
logger.error(
`Failed to extract proxy archive from ${archive}: ${getErrorMessage(error)}`,
);
throw new StartProxyError(StartProxyErrorType.ExtractionFailed);
}
}
/**
* Attempts to store the proxy in the toolcache.
*
* @param logger The logger to use.
* @param source The source path to add to the toolcache.
* @param filename The filename of the proxy binary.
* @param version The version of the proxy.
* @returns The path to the directory in the toolcache.
*/
export async function cacheProxy(
logger: Logger,
source: string,
filename: string,
version: string,
) {
try {
return await toolcache.cacheDir(source, filename, version);
} catch (error) {
logger.error(
`Failed to add proxy archive from ${source} to toolcache: ${getErrorMessage(error)}`,
);
throw new StartProxyError(StartProxyErrorType.CacheFailed);
}
}
/**
* Returns the platform-specific filename of the proxy binary.
*/
export function getProxyFilename() {
return process.platform === "win32"
? `${UPDATEJOB_PROXY}.exe`
: UPDATEJOB_PROXY;
}
/**
* Gets a path to the proxy binary. If possible, this function will find the proxy in the
* runner's tool cache. Otherwise, it downloads and extracts the proxy binary,
* and stores it in the tool cache.
*
* @param logger The logger to use.
* @returns The path to the proxy binary.
*/
export async function getProxyBinaryPath(logger: Logger): Promise<string> {
const proxyFileName = getProxyFilename();
const proxyInfo = await getDownloadUrl(logger);
let proxyBin = toolcache.find(proxyFileName, proxyInfo.version);
if (!proxyBin) {
const apiDetails = getApiDetails();
const authorization = getAuthorizationHeaderFor(
logger,
apiDetails,
proxyInfo.url,
);
const temp = await downloadProxy(logger, proxyInfo.url, authorization);
const extracted = await extractProxy(logger, temp);
proxyBin = await cacheProxy(
logger,
extracted,
proxyFileName,
proxyInfo.version,
);
}
return path.join(proxyBin, proxyFileName);
}
-93
View File
@@ -1,93 +0,0 @@
import test, { ExecutionContext } from "ava";
import { pki } from "node-forge";
import { setupTests } from "../testing-utils";
import * as ca from "./ca";
setupTests(test);
const toMap = <T>(array: T[], func: (e: T) => string) =>
new Map<string, T>(array.map((val) => [func(val), val]));
function checkCertAttributes(
t: ExecutionContext<unknown>,
cert: pki.Certificate,
) {
const subjectMap = toMap(
cert.subject.attributes,
(attr) => attr.name as string,
);
const issuerMap = toMap(
cert.issuer.attributes,
(attr) => attr.name as string,
);
t.is(subjectMap.get("commonName")?.value, "Dependabot Internal CA");
t.is(issuerMap.get("commonName")?.value, "Dependabot Internal CA");
for (const attrName of subjectMap.keys()) {
t.deepEqual(subjectMap.get(attrName), issuerMap.get(attrName));
}
}
test("generateCertificateAuthority - generates certificates", (t) => {
const result = ca.generateCertificateAuthority(false);
const cert = pki.certificateFromPem(result.cert);
const key = pki.privateKeyFromPem(result.key);
t.truthy(cert);
t.truthy(key);
checkCertAttributes(t, cert);
// Check the validity.
t.true(
cert.validity.notBefore <= new Date(),
"notBefore date is in the future",
);
t.true(cert.validity.notAfter > new Date(), "notAfter date is in the past");
// Check that the extensions are set as we'd expect.
const exts = cert.extensions as ca.Extension[];
t.is(exts.length, 1);
t.is(exts[0].name, "basicConstraints");
t.is(exts[0].cA, true);
t.truthy(cert.siginfo);
});
test("generateCertificateAuthority - generates certificates with FF", (t) => {
const result = ca.generateCertificateAuthority(true);
const cert = pki.certificateFromPem(result.cert);
const key = pki.privateKeyFromPem(result.key);
t.truthy(cert);
t.truthy(key);
checkCertAttributes(t, cert);
// Check the validity.
t.true(
cert.validity.notBefore <= new Date(),
"notBefore date is in the future",
);
t.true(cert.validity.notAfter > new Date(), "notAfter date is in the past");
// Check that the extensions are set as we'd expect.
const exts = toMap(cert.extensions as ca.Extension[], (ext) => ext.name);
t.is(exts.size, 4);
t.true(exts.get("basicConstraints")?.cA);
t.truthy(exts.get("subjectKeyIdentifier"));
t.truthy(exts.get("authorityKeyIdentifier"));
const keyUsage = exts.get("keyUsage");
if (t.truthy(keyUsage)) {
t.true(keyUsage.critical);
t.true(keyUsage.keyCertSign);
t.true(keyUsage.cRLSign);
t.true(keyUsage.digitalSignature);
}
t.truthy(cert.siginfo);
});
-93
View File
@@ -1,93 +0,0 @@
import { md, pki } from "node-forge";
import { CertificateAuthority } from "./types";
const KEY_SIZE = 2048;
const KEY_EXPIRY_YEARS = 2;
const CERT_SUBJECT = [
{
name: "commonName",
value: "Dependabot Internal CA",
},
{
name: "organizationName",
value: "GitHub inc.",
},
{
shortName: "OU",
value: "Dependabot",
},
{
name: "countryName",
value: "US",
},
{
shortName: "ST",
value: "California",
},
{
name: "localityName",
value: "San Francisco",
},
];
export type Extension = {
name: string;
[key: string]: unknown;
};
const extraExtensions: Extension[] = [
{
name: "keyUsage",
critical: true,
keyCertSign: true,
cRLSign: true,
digitalSignature: true,
},
{ name: "subjectKeyIdentifier" },
{ name: "authorityKeyIdentifier", keyIdentifier: true },
];
/**
* Generates a CA certificate for the proxy.
*
* @param newCertGenFF Whether to use the updated certificate generation.
* @returns The private and public keys.
*/
export function generateCertificateAuthority(
newCertGenFF: boolean,
): CertificateAuthority {
const keys = pki.rsa.generateKeyPair(KEY_SIZE);
const cert = pki.createCertificate();
cert.publicKey = keys.publicKey;
cert.serialNumber = "01";
cert.validity.notBefore = new Date();
cert.validity.notAfter = new Date();
cert.validity.notAfter.setFullYear(
cert.validity.notBefore.getFullYear() + KEY_EXPIRY_YEARS,
);
cert.setSubject(CERT_SUBJECT);
cert.setIssuer(CERT_SUBJECT);
const extensions: Extension[] = [{ name: "basicConstraints", cA: true }];
// Add the extra CA extensions if the FF is enabled.
if (newCertGenFF) {
extensions.push(...extraExtensions);
}
cert.setExtensions(extensions);
// Specifically use SHA256 when the FF is enabled.
if (newCertGenFF) {
cert.sign(keys.privateKey, md.sha256.create());
} else {
cert.sign(keys.privateKey);
}
const pem = pki.certificateToPem(cert);
const key = pki.privateKeyToPem(keys.privateKey);
return { cert: pem, key };
}
-213
View File
@@ -1,213 +0,0 @@
import * as fs from "fs";
import * as os from "os";
import path from "path";
import * as toolrunner from "@actions/exec/lib/toolrunner";
import * as io from "@actions/io";
import test, { ExecutionContext } from "ava";
import sinon from "sinon";
import { JavaEnvVars, KnownLanguage } from "../languages";
import {
checkExpectedLogMessages,
getRecordingLogger,
LoggedMessage,
setupTests,
} from "../testing-utils";
import { withTmpDir } from "../util";
import {
checkJavaEnvVars,
checkJdkSettings,
checkProxyEnvironment,
checkProxyEnvVars,
discoverActionsJdks,
JAVA_PROXY_ENV_VARS,
ProxyEnvVars,
} from "./environment";
setupTests(test);
function stubToolrunner() {
sinon.stub(io, "which").throws(new Error("Java not installed"));
sinon.stub(toolrunner, "ToolRunner").returns({
exec: async () => {
return 0;
},
});
}
function assertEnvVarLogMessages(
t: ExecutionContext<any>,
envVars: string[],
messages: LoggedMessage[],
expectSet: boolean | string,
) {
const template = (envVar: string) => {
if (typeof expectSet === "string") {
return `Environment variable '${envVar}' is set to '${expectSet}'`;
}
return expectSet
? `Environment variable '${envVar}' is set to '${envVar}'`
: `Environment variable '${envVar}' is not set`;
};
const expected: string[] = [];
for (const envVar of envVars) {
expected.push(template(envVar));
}
checkExpectedLogMessages(t, messages, expected);
}
test("checkJavaEnvironment - none set", (t) => {
const messages: LoggedMessage[] = [];
const logger = getRecordingLogger(messages);
checkJavaEnvVars(logger);
assertEnvVarLogMessages(t, JAVA_PROXY_ENV_VARS, messages, false);
});
test("checkJavaEnvironment - logs values when variables are set", (t) => {
const messages: LoggedMessage[] = [];
const logger = getRecordingLogger(messages);
for (const envVar of Object.values(JavaEnvVars)) {
process.env[envVar] = envVar;
}
checkJavaEnvVars(logger);
assertEnvVarLogMessages(t, JAVA_PROXY_ENV_VARS, messages, true);
});
test("discoverActionsJdks - discovers JDK paths", (t) => {
// Clear GHA variables that may interfere with this test in CI.
for (const envVar of Object.keys(process.env)) {
if (envVar.startsWith("JAVA_HOME_")) {
delete process.env[envVar];
}
}
const jdk8 = "/usr/lib/jvm/temurin-8-jdk-amd64";
const jdk17 = "/usr/lib/jvm/temurin-17-jdk-amd64";
const jdk21 = "/usr/lib/jvm/temurin-21-jdk-amd64";
process.env[JavaEnvVars.JAVA_HOME] = jdk17;
process.env["JAVA_HOME_8_X64"] = jdk8;
process.env["JAVA_HOME_17_X64"] = jdk17;
process.env["JAVA_HOME_21_X64"] = jdk21;
const results = discoverActionsJdks();
t.is(results.size, 3);
t.true(results.has(jdk8));
t.true(results.has(jdk17));
t.true(results.has(jdk21));
});
test("checkJdkSettings - does not throw for an empty directory", async (t) => {
const messages: LoggedMessage[] = [];
const logger = getRecordingLogger(messages);
await withTmpDir(async (tmpDir) => {
t.notThrows(() => checkJdkSettings(logger, tmpDir));
});
});
test("checkJdkSettings - finds files and logs relevant properties", async (t) => {
const messages: LoggedMessage[] = [];
const logger = getRecordingLogger(messages);
await withTmpDir(async (tmpDir) => {
const dir = path.join(tmpDir, "conf");
fs.mkdirSync(dir);
const file = path.join(dir, "net.properties");
fs.writeFileSync(
file,
[
"irrelevant.property=foo",
"http.proxyHost=proxy.example.com",
"http.unrelated=bar",
].join(os.EOL),
{},
);
checkJdkSettings(logger, tmpDir);
checkExpectedLogMessages(t, messages, [
`Found '${file}'.`,
`Found 'http.proxyHost=proxy.example.com' in '${file}'`,
]);
});
});
test("checkProxyEnvVars - none set", (t) => {
const messages: LoggedMessage[] = [];
const logger = getRecordingLogger(messages);
checkProxyEnvVars(logger);
assertEnvVarLogMessages(t, Object.values(ProxyEnvVars), messages, false);
});
test("checkProxyEnvVars - logs values when variables are set", (t) => {
const messages: LoggedMessage[] = [];
const logger = getRecordingLogger(messages);
for (const envVar of Object.values(ProxyEnvVars)) {
process.env[envVar] = envVar;
}
checkProxyEnvVars(logger);
assertEnvVarLogMessages(t, Object.values(ProxyEnvVars), messages, true);
});
test("checkProxyEnvVars - credentials are removed from URLs", (t) => {
const messages: LoggedMessage[] = [];
const logger = getRecordingLogger(messages);
for (const envVar of Object.values(ProxyEnvVars)) {
process.env[envVar] = "https://secret:password@proxy.local";
}
checkProxyEnvVars(logger);
assertEnvVarLogMessages(
t,
Object.values(ProxyEnvVars),
messages,
"https://proxy.local/",
);
});
test("checkProxyEnvironment - includes base checks for all known languages", async (t) => {
stubToolrunner();
for (const language of Object.values(KnownLanguage)) {
const messages: LoggedMessage[] = [];
const logger = getRecordingLogger(messages);
await checkProxyEnvironment(logger, language);
assertEnvVarLogMessages(t, Object.keys(ProxyEnvVars), messages, false);
}
});
test("checkProxyEnvironment - includes Java checks for Java", async (t) => {
const messages: LoggedMessage[] = [];
const logger = getRecordingLogger(messages);
stubToolrunner();
await checkProxyEnvironment(logger, KnownLanguage.java);
assertEnvVarLogMessages(t, Object.keys(ProxyEnvVars), messages, false);
assertEnvVarLogMessages(t, JAVA_PROXY_ENV_VARS, messages, false);
});
test("checkProxyEnvironment - includes language-specific checks if the language is undefined", async (t) => {
const messages: LoggedMessage[] = [];
const logger = getRecordingLogger(messages);
stubToolrunner();
await checkProxyEnvironment(logger, undefined);
assertEnvVarLogMessages(t, Object.keys(ProxyEnvVars), messages, false);
assertEnvVarLogMessages(t, JAVA_PROXY_ENV_VARS, messages, false);
});
-209
View File
@@ -1,209 +0,0 @@
import * as fs from "fs";
import * as path from "path";
import * as toolrunner from "@actions/exec/lib/toolrunner";
import * as io from "@actions/io";
import { JavaEnvVars, KnownLanguage, Language } from "../languages";
import { Logger } from "../logging";
import { getErrorMessage, isDefined } from "../util";
/**
* Checks whether an environment variable named `name` is set and logs its value if set.
*
* @param logger The logger to use.
* @param name The name of the environment variable.
* @returns True if set or false otherwise.
*/
function checkEnvVar(logger: Logger, name: string): boolean {
const value = process.env[name];
if (isDefined(value)) {
const url = URL.parse(value);
if (isDefined(url)) {
url.username = "";
url.password = "";
logger.info(`Environment variable '${name}' is set to '${url}'.`);
} else {
logger.info(`Environment variable '${name}' is set to '${value}'.`);
}
return true;
} else {
logger.debug(`Environment variable '${name}' is not set.`);
return false;
}
}
// The JRE properties that may affect the proxy.
const javaProperties = [
"http.proxyHost",
"http.proxyPort",
"https.proxyHost",
"https.proxyPort",
"http.nonProxyHosts",
"java.net.useSystemProxies",
"javax.net.ssl.trustStore",
"javax.net.ssl.trustStoreType",
"javax.net.ssl.trustStoreProvider",
"jdk.tls.client.protocols",
"jdk.tls.disabledAlgorithms",
"jdk.security.allowNonCaAnchor",
"https.protocols",
"com.sun.net.ssl.enableAIAcaIssuers",
"com.sun.net.ssl.checkRevocation",
"com.sun.security.enableCRLDP",
"ocsp.enable",
];
/** Java-specific environment variables which may contain information about proxy settings. */
export const JAVA_PROXY_ENV_VARS: JavaEnvVars[] = [
JavaEnvVars.JAVA_TOOL_OPTIONS,
JavaEnvVars.JDK_JAVA_OPTIONS,
JavaEnvVars._JAVA_OPTIONS,
];
/**
* Checks whether any Java-specific environment variables which may contain proxy
* configurations are set and logs their values if so.
*/
export function checkJavaEnvVars(logger: Logger) {
for (const envVar of JAVA_PROXY_ENV_VARS) {
checkEnvVar(logger, envVar);
}
}
/**
* Discovers paths to JDK directories based on JAVA_HOME and GHA-specific environment variables.
* @returns A set of JDK paths.
*/
export function discoverActionsJdks(): Set<string> {
const paths: Set<string> = new Set();
// Check whether JAVA_HOME is set.
const javaHome = process.env[JavaEnvVars.JAVA_HOME];
if (isDefined(javaHome)) {
paths.add(javaHome);
}
for (const [envVar, value] of Object.entries(process.env)) {
if (isDefined(value) && envVar.match(/^JAVA_HOME_\d+_/)) {
paths.add(value);
}
}
return paths;
}
/**
* Tries to inspect JDK configuration files for the specified JDK path which may contain proxy settings.
*
* @param logger The logger to use.
* @param jdkHome The JDK home directory.
*/
export function checkJdkSettings(logger: Logger, jdkHome: string) {
const filesToCheck = [
// JDK 9+
path.join("conf", "net.properties"),
// JDK 8 and below
path.join("lib", "net.properties"),
];
for (const fileToCheck of filesToCheck) {
const file = path.join(jdkHome, fileToCheck);
try {
if (fs.existsSync(file)) {
logger.debug(`Found '${file}'.`);
const lines = String(fs.readFileSync(file)).split("\n");
for (const line of lines) {
for (const property of javaProperties) {
if (line.startsWith(`${property}=`)) {
logger.info(`Found '${line.trimEnd()}' in '${file}'.`);
}
}
}
} else {
logger.debug(`'${file}' does not exist.`);
}
} catch (err) {
logger.debug(`Failed to read '${file}': ${getErrorMessage(err)}`);
}
}
}
/** Invokes `java` to get it to show us the active configuration. */
async function showJavaSettings(logger: Logger): Promise<void> {
try {
const java = await io.which("java", true);
let output = "";
await new toolrunner.ToolRunner(
java,
["-XshowSettings:all", "-XshowSettings:security:all", "-version"],
{
silent: true,
listeners: {
stdout: (data) => {
output += String(data);
},
stderr: (data) => {
output += String(data);
},
},
},
).exec();
logger.startGroup("Java settings");
logger.info(output);
logger.endGroup();
} catch (err) {
logger.debug(`Failed to query java settings: ${getErrorMessage(err)}`);
}
}
/** Enumerates environment variable names which may contain information about proxy settings. */
export enum ProxyEnvVars {
HTTP_PROXY = "HTTP_PROXY",
HTTPS_PROXY = "HTTPS_PROXY",
ALL_PROXY = "ALL_PROXY",
}
/**
* Checks whether any proxy-related environment variables are set and logs their values if so.
*/
export function checkProxyEnvVars(logger: Logger) {
// Both upper-case and lower-case variants of these environment variables are used.
for (const envVar of Object.values(ProxyEnvVars)) {
checkEnvVar(logger, envVar);
checkEnvVar(logger, envVar.toLowerCase());
}
}
/**
* Inspects environment variables and other configurations on the runner to determine whether
* any settings that may affect the operation of the proxy are present. All relevant information
* is written to the log.
*
* @param logger The logger to use.
* @param language The enabled language, if known.
*/
export async function checkProxyEnvironment(
logger: Logger,
language: Language | undefined,
): Promise<void> {
// Determine whether there is an existing proxy configured.
checkProxyEnvVars(logger);
// Check language-specific configurations. If we don't know the language,
// then we perform all checks.
if (language === undefined || language === KnownLanguage.java) {
checkJavaEnvVars(logger);
await showJavaSettings(logger);
const jdks = discoverActionsJdks();
for (const jdk of jdks) {
checkJdkSettings(logger, jdk);
}
}
}
-120
View File
@@ -1,120 +0,0 @@
import test from "ava";
import * as sinon from "sinon";
import {
checkExpectedLogMessages,
setupTests,
withRecordingLoggerAsync,
} from "./../testing-utils";
import {
checkConnections,
ReachabilityBackend,
ReachabilityError,
} from "./reachability";
import { ProxyInfo, Registry } from "./types";
setupTests(test);
class MockReachabilityBackend implements ReachabilityBackend {
public async checkConnection(_url: URL): Promise<number> {
return 200;
}
}
const mavenRegistry: Registry = {
type: "maven_registry",
url: "https://repo.maven.apache.org/maven2/",
};
const nugetFeed: Registry = {
type: "nuget_feed",
url: "https://api.nuget.org/v3/index.json",
};
const proxyInfo: ProxyInfo = {
host: "127.0.0.1",
port: 1080,
cert: "",
registries: [mavenRegistry, nugetFeed],
};
test("checkConnections - basic functionality", async (t) => {
const backend = new MockReachabilityBackend();
const messages = await withRecordingLoggerAsync(async (logger) => {
const reachable = await checkConnections(logger, proxyInfo, backend);
t.is(reachable.size, proxyInfo.registries.length);
t.true(reachable.has(mavenRegistry));
t.true(reachable.has(nugetFeed));
});
checkExpectedLogMessages(t, messages, [
`Testing connection to ${mavenRegistry.url}`,
`Successfully tested connection to ${mavenRegistry.url}`,
`Testing connection to ${nugetFeed.url}`,
`Successfully tested connection to ${nugetFeed.url}`,
`Finished testing connections`,
]);
});
test("checkConnections - excludes failed status codes", async (t) => {
const backend = new MockReachabilityBackend();
sinon
.stub(backend, "checkConnection")
.onSecondCall()
.throws(new ReachabilityError(400));
const messages = await withRecordingLoggerAsync(async (logger) => {
const reachable = await checkConnections(logger, proxyInfo, backend);
t.is(reachable.size, 1);
t.true(reachable.has(mavenRegistry));
});
checkExpectedLogMessages(t, messages, [
`Testing connection to ${mavenRegistry.url}`,
`Successfully tested connection to ${mavenRegistry.url}`,
`Testing connection to ${nugetFeed.url}`,
`Connection test to ${nugetFeed.url} failed. (400)`,
`Finished testing connections`,
]);
});
test("checkConnections - handles other exceptions", async (t) => {
const backend = new MockReachabilityBackend();
sinon
.stub(backend, "checkConnection")
.onSecondCall()
.throws(new Error("Some generic error"));
const messages = await withRecordingLoggerAsync(async (logger) => {
const reachable = await checkConnections(logger, proxyInfo, backend);
t.is(reachable.size, 1);
t.true(reachable.has(mavenRegistry));
});
checkExpectedLogMessages(t, messages, [
`Testing connection to ${mavenRegistry.url}`,
`Successfully tested connection to ${mavenRegistry.url}`,
`Testing connection to ${nugetFeed.url}`,
`Connection test to ${nugetFeed.url} failed: Some generic error`,
`Finished testing connections`,
]);
});
test("checkConnections - handles invalid URLs", async (t) => {
const backend = new MockReachabilityBackend();
const messages = await withRecordingLoggerAsync(async (logger) => {
const reachable = await checkConnections(
logger,
{
...proxyInfo,
registries: [
{
type: "nuget_feed",
url: "localhost",
},
],
},
backend,
);
t.is(reachable.size, 0);
});
checkExpectedLogMessages(t, messages, [
`Skipping check for localhost since it is not a valid URL.`,
`Finished testing connections`,
]);
});
-130
View File
@@ -1,130 +0,0 @@
import * as https from "https";
import { HttpsProxyAgent } from "https-proxy-agent";
import { Logger } from "../logging";
import { getErrorMessage } from "../util";
import { getAddressString, ProxyInfo, Registry } from "./types";
export class ReachabilityError extends Error {
constructor(public readonly statusCode?: number | undefined) {
super();
}
}
/**
* Abstracts over the backend for the reachability checks,
* to allow actual networking to be replaced with stubs.
*/
export interface ReachabilityBackend {
/**
* Performs a test HTTP request to the specified `url`. Resolves to the status code,
* if a successful status code was obtained. Otherwise throws
*
* @param url The URL of the registry to try and reach.
* @returns The successful status code (in the `<400` range).
*/
checkConnection: (url: URL) => Promise<number>;
}
class NetworkReachabilityBackend implements ReachabilityBackend {
private agent: https.Agent;
constructor(private readonly proxy: ProxyInfo) {
this.agent = new HttpsProxyAgent(`http://${proxy.host}:${proxy.port}`);
}
public async checkConnection(url: URL): Promise<number> {
return new Promise((resolve, reject) => {
const req = https.request(
url,
{
agent: this.agent,
method: "HEAD",
ca: this.proxy.cert,
timeout: 5 * 1000, // 5 seconds
},
(res) => {
res.destroy();
if (res.statusCode !== undefined && res.statusCode < 400) {
resolve(res.statusCode);
} else {
reject(new ReachabilityError(res.statusCode));
}
},
);
req.on("error", (e) => {
reject(e);
});
req.on("timeout", () => {
req.destroy();
reject(new Error("Connection timeout."));
});
req.end();
});
}
}
/**
* Determines which configured registries can be reached by performing test requests to them.
*
* @param logger The logger to use.
* @param proxy Information about the proxy, including the configured registries.
* @param backend Optionally for testing, a `ReachabilityBackend` to use.
* @returns The set of registries which passed the checks.
*/
export async function checkConnections(
logger: Logger,
proxy: ProxyInfo,
backend?: ReachabilityBackend,
): Promise<Set<Registry>> {
const result: Set<Registry> = new Set();
// Don't do anything if there are no registries.
if (proxy.registries.length === 0) return result;
try {
// Initialise a networking backend if no backend was provided.
if (backend === undefined) {
backend = new NetworkReachabilityBackend(proxy);
}
for (const registry of proxy.registries) {
const address = getAddressString(registry);
const url = URL.parse(address);
if (url === null) {
logger.info(
`Skipping check for ${address} since it is not a valid URL.`,
);
continue;
}
try {
logger.debug(`Testing connection to ${url}...`);
const statusCode = await backend.checkConnection(url);
logger.info(`Successfully tested connection to ${url} (${statusCode})`);
result.add(registry);
} catch (e) {
if (e instanceof ReachabilityError && e.statusCode !== undefined) {
logger.error(`Connection test to ${url} failed. (${e.statusCode})`);
} else {
logger.error(
`Connection test to ${url} failed: ${getErrorMessage(e)}`,
);
}
}
}
logger.debug(`Finished testing connections to private registries.`);
} catch (e) {
logger.error(
`Failed to test connections to private registries: ${getErrorMessage(e)}`,
);
}
return result;
}
-81
View File
@@ -1,81 +0,0 @@
/**
* After parsing configurations from JSON, we don't know whether all the keys we expect are
* present or not. This type is used to represent such values, which we expect to be
* `Credential` values, but haven't validated yet.
*/
export type RawCredential = Partial<Credential>;
/**
* A package registry configuration includes identifying information as well as
* authentication credentials.
*/
export type Credential = {
/** The username needed to authenticate to the package registry, if any. */
username?: string;
/** The password needed to authenticate to the package registry, if any. */
password?: string;
/** The token needed to authenticate to the package registry, if any. */
token?: string;
} & Registry;
/** A package registry is identified by its type and address. */
export type Registry = {
/** The type of the package registry. */
type: string;
} & Address;
// If a registry has an `url`, then that takes precedence over the `host` which may or may
// not be defined.
interface HasUrl {
url: string;
host?: string;
}
// If a registry does not have an `url`, then it must have a `host`.
interface WithoutUrl {
url: undefined;
host: string;
}
/**
* A valid `Registry` value must either have a `url` or a `host` value. If it has a `url` value,
* then that takes precedence over the `host` value. If there is no `url` value, then it must
* have a `host` value.
*/
export type Address = HasUrl | WithoutUrl;
/** Gets the address as a string. This will either be the `url` if present, or the `host` if not. */
export function getAddressString(address: Address): string {
if (address.url === undefined) {
return address.host;
} else {
return address.url;
}
}
export interface ProxyInfo {
host: string;
port: number;
cert: string;
registries: Registry[];
}
export type CertificateAuthority = {
cert: string;
key: string;
};
export type BasicAuthCredentials = {
username: string;
password: string;
};
/**
* Represents configurations for the authentication proxy.
*/
export type ProxyConfig = {
/** The validated configurations for the proxy. */
all_credentials: Credential[];
ca: CertificateAuthority;
proxy_auth?: BasicAuthCredentials;
};
+18
View File
@@ -4,6 +4,7 @@ import * as sinon from "sinon";
import * as actionsUtil from "./actions-util";
import { Config } from "./config-utils";
import { EnvVar } from "./environment";
import { Feature, QueriedFeatures } from "./feature-flags";
import { KnownLanguage } from "./languages";
import { getRunnerLogger } from "./logging";
import { ToolsSource } from "./setup-codeql";
@@ -46,6 +47,10 @@ test("createStatusReportBase", async (t) => {
await withTmpDir(async (tmpDir: string) => {
setupEnvironmentAndStub(tmpDir);
const features: QueriedFeatures = Object.fromEntries(
Object.values(Feature).map((key) => [key, { value: true }]),
);
const statusReport = await createStatusReportBase(
ActionName.Init,
"failure",
@@ -54,6 +59,7 @@ test("createStatusReportBase", async (t) => {
buildMode: BuildMode.None,
languages: [KnownLanguage.java, KnownLanguage.swift],
}),
features,
{ numAvailableBytes: 100, numTotalBytes: 500 },
getRunnerLogger(false),
"failure cause",
@@ -75,6 +81,9 @@ test("createStatusReportBase", async (t) => {
t.is(statusReport.cause, "failure cause");
t.is(statusReport.commit_oid, process.env["GITHUB_SHA"]!);
t.is(statusReport.exception, "exception stack trace");
if (t.assert(statusReport.feature_flags)) {
t.is(statusReport.feature_flags.length, Object.keys(features).length);
}
t.is(statusReport.job_name, process.env["GITHUB_JOB"] || "");
t.is(typeof statusReport.job_run_uuid, "string");
t.is(statusReport.languages, "java,swift");
@@ -101,6 +110,7 @@ test("createStatusReportBase - empty configuration", async (t) => {
"success",
new Date("May 19, 2023 05:19:00"),
{},
undefined,
{ numAvailableBytes: 100, numTotalBytes: 500 },
getRunnerLogger(false),
);
@@ -123,6 +133,7 @@ test("createStatusReportBase - partial configuration", async (t) => {
{
languages: ["go"],
},
undefined,
{ numAvailableBytes: 100, numTotalBytes: 500 },
getRunnerLogger(false),
);
@@ -146,6 +157,7 @@ test("createStatusReportBase_firstParty", async (t) => {
"failure",
new Date("May 19, 2023 05:19:00"),
createTestConfig({}),
undefined,
{ numAvailableBytes: 100, numTotalBytes: 500 },
getRunnerLogger(false),
"failure cause",
@@ -162,6 +174,7 @@ test("createStatusReportBase_firstParty", async (t) => {
"failure",
new Date("May 19, 2023 05:19:00"),
createTestConfig({}),
undefined,
{ numAvailableBytes: 100, numTotalBytes: 500 },
getRunnerLogger(false),
"failure cause",
@@ -179,6 +192,7 @@ test("createStatusReportBase_firstParty", async (t) => {
"failure",
new Date("May 19, 2023 05:19:00"),
createTestConfig({}),
undefined,
{ numAvailableBytes: 100, numTotalBytes: 500 },
getRunnerLogger(false),
"failure cause",
@@ -195,6 +209,7 @@ test("createStatusReportBase_firstParty", async (t) => {
"failure",
new Date("May 19, 2023 05:19:00"),
createTestConfig({}),
undefined,
{ numAvailableBytes: 100, numTotalBytes: 500 },
getRunnerLogger(false),
"failure cause",
@@ -212,6 +227,7 @@ test("createStatusReportBase_firstParty", async (t) => {
"failure",
new Date("May 19, 2023 05:19:00"),
createTestConfig({}),
undefined,
{ numAvailableBytes: 100, numTotalBytes: 500 },
getRunnerLogger(false),
"failure cause",
@@ -228,6 +244,7 @@ test("createStatusReportBase_firstParty", async (t) => {
"failure",
new Date("May 19, 2023 05:19:00"),
createTestConfig({}),
undefined,
{ numAvailableBytes: 100, numTotalBytes: 500 },
getRunnerLogger(false),
"failure cause",
@@ -307,6 +324,7 @@ const testCreateInitWithConfigStatusReport = test.macro({
"failure",
new Date("May 19, 2023 05:19:00"),
config,
undefined,
{ numAvailableBytes: 100, numTotalBytes: 500 },
getRunnerLogger(false),
"failure cause",
+24 -1
View File
@@ -16,6 +16,7 @@ import { parseRegistriesWithoutCredentials, type Config } from "./config-utils";
import { DependencyCacheRestoreStatusReport } from "./dependency-caching";
import { DocUrl } from "./doc-url";
import { EnvVar } from "./environment";
import { QueriedFeatures, QueriedFeatureStatus } from "./feature-flags";
import { getRef } from "./git-utils";
import { Logger } from "./logging";
import { OverlayBaseDatabaseDownloadStats } from "./overlay-database-utils";
@@ -83,6 +84,12 @@ export enum JobStatus {
ConfigErrorStatus = "JOB_STATUS_CONFIGURATION_ERROR",
}
/** Information about the outcome of a querying a feature flag during this step. */
export interface QueriedFeatureStatusReport extends QueriedFeatureStatus {
// The name of the feature.
feature: string;
}
export interface StatusReportBase {
/** Name of the action being executed. */
action_name: ActionName;
@@ -112,6 +119,8 @@ export interface StatusReportBase {
completed_at?: string;
/** Stack trace of the failure (or undefined if status is not failure). */
exception?: string;
/** Outcomes of querying feature flags during this step. */
feature_flags?: QueriedFeatureStatusReport[];
/** Whether this is a first-party (CodeQL) run of the action. */
first_party_analysis: boolean;
/** Job name from the workflow. */
@@ -253,6 +262,10 @@ export interface EventReport {
* @param actionName The name of the action, e.g. 'init', 'finish', 'upload-sarif'
* @param status The status. Must be 'success', 'failure', or 'starting'
* @param actionStartedAt The time this action started executing.
* @param config The configuration for the action, even partially, if any.
* @param queriedFeatures Information about queried feature flags, if available.
* @param diskInfo Information about disk usage, if available.
* @param logger The logger to use.
* @param cause Cause of failure (only supply if status is 'failure')
* @param exception Exception (only supply if status is 'failure')
* @returns undefined if an exception was thrown.
@@ -262,6 +275,7 @@ export async function createStatusReportBase(
status: ActionStatus,
actionStartedAt: Date,
config: Partial<Config> | undefined,
queriedFeatures: QueriedFeatures | undefined,
diskInfo: DiskUsage | undefined,
logger: Logger,
cause?: string,
@@ -294,6 +308,13 @@ export async function createStatusReportBase(
const isSteadyStateDefaultSetupRun =
process.env["CODE_SCANNING_IS_STEADY_STATE_DEFAULT_SETUP"] === "true";
let featureFlags: QueriedFeatureStatusReport[] | undefined = undefined;
if (queriedFeatures) {
featureFlags = Object.entries(queriedFeatures).map(
([feature, outcome]) => ({ feature, ...outcome }),
);
}
const statusReport: StatusReportBase = {
action_name: actionName,
action_oid: "unknown", // TODO decide if it's possible to fill this in
@@ -304,6 +325,7 @@ export async function createStatusReportBase(
analysis_key,
build_mode: config?.buildMode,
commit_oid: commitOid,
feature_flags: featureFlags,
first_party_analysis: isFirstPartyAnalysis(actionName),
job_name: jobName,
job_run_uuid: jobRunUUID,
@@ -518,7 +540,7 @@ export interface InitWithConfigStatusReport extends InitStatusReport {
/** Stringified JSON array of registry configuration objects, from the 'registries' config field
or workflow input. **/
registries: string;
/** Stringified JSON object representing a query-filters, from the 'query-filters' config field. **/
/** Stringified JSON object representing query-filters, from the 'query-filters' config field. **/
query_filters: string;
/** Path to the specified code scanning config file, from the 'config-file' config field. */
config_file: string;
@@ -623,6 +645,7 @@ export async function sendUnhandledErrorStatusReport(
"failure",
actionStartedAt,
undefined,
{},
undefined,
logger,
`Unhandled CodeQL Action error: ${getErrorMessage(error)}`,
+5 -90
View File
@@ -145,67 +145,13 @@ export function setupActionsVars(tempDir: string, toolsDir: string) {
process.env["RUNNER_TEMP"] = tempDir;
process.env["RUNNER_TOOL_CACHE"] = toolsDir;
process.env["GITHUB_WORKSPACE"] = tempDir;
process.env["GITHUB_EVENT_NAME"] = "push";
}
type LogLevel = "debug" | "info" | "warning" | "error";
export interface LoggedMessage {
type: LogLevel;
type: "debug" | "info" | "warning" | "error";
message: string | Error;
}
export class RecordingLogger implements Logger {
messages: LoggedMessage[] = [];
groups: string[] = [];
unfinishedGroups: Set<string> = new Set();
private currentGroup: string | undefined = undefined;
constructor(private readonly logToConsole: boolean = true) {}
private addMessage(level: LogLevel, message: string | Error): void {
this.messages.push({ type: level, message });
if (this.logToConsole) {
// eslint-disable-next-line no-console
console.debug(message);
}
}
isDebug() {
return true;
}
debug(message: string) {
this.addMessage("debug", message);
}
info(message: string) {
this.addMessage("info", message);
}
warning(message: string | Error) {
this.addMessage("warning", message);
}
error(message: string | Error) {
this.addMessage("error", message);
}
startGroup(name: string) {
this.groups.push(name);
this.currentGroup = name;
this.unfinishedGroups.add(name);
}
endGroup() {
if (this.currentGroup !== undefined) {
this.unfinishedGroups.delete(this.currentGroup);
}
this.currentGroup = undefined;
}
}
export function getRecordingLogger(
messages: LoggedMessage[],
{ logToConsole }: { logToConsole?: boolean } = { logToConsole: true },
@@ -250,49 +196,18 @@ export function checkExpectedLogMessages(
messages: LoggedMessage[],
expectedMessages: string[],
) {
const missingMessages: string[] = [];
for (const expectedMessage of expectedMessages) {
if (
!messages.some(
t.assert(
messages.some(
(msg) =>
typeof msg.message === "string" &&
msg.message.includes(expectedMessage),
)
) {
missingMessages.push(expectedMessage);
}
}
if (missingMessages.length > 0) {
const listify = (lines: string[]) =>
lines.map((m) => ` - '${m}'`).join("\n");
t.fail(
`Expected\n\n${listify(missingMessages)}\n\nin the logger output, but didn't find it in:\n\n${messages.map((m) => ` - '${m.message}'`).join("\n")}`,
),
`Expected '${expectedMessage}' in the logger output, but didn't find it in:\n ${messages.map((m) => ` - '${m.message}'`).join("\n")}`,
);
} else {
t.pass();
}
}
/**
* Initialises a recording logger and calls `body` with it.
*
* @param body The test that requires a recording logger.
* @returns The logged messages.
*/
export async function withRecordingLoggerAsync(
body: (logger: Logger) => Promise<void>,
): Promise<LoggedMessage[]> {
const messages = [];
const logger = getRecordingLogger(messages);
await body(logger);
return messages;
}
/** Mock the HTTP request to the feature flags enablement API endpoint. */
export function mockFeatureFlagApiEndpoint(
responseStatusCode: number,
+25 -70
View File
@@ -12,7 +12,6 @@ import * as api from "./api-client";
import { getRunnerLogger, Logger } from "./logging";
import { setupTests } from "./testing-utils";
import * as uploadLib from "./upload-lib";
import { UploadPayload } from "./upload-lib/types";
import { GitHubVariant, initializeEnvironment, withTmpDir } from "./util";
setupTests(test);
@@ -129,21 +128,11 @@ test("finding SARIF files", async (t) => {
"file",
);
// add some non-Code Scanning files that should be ignored, unless we look for them specifically
for (const analysisKind of analyses.supportedAnalysisKinds) {
if (analysisKind === AnalysisKind.CodeScanning) continue;
// add some `.quality.sarif` files that should be ignored, unless we look for them specifically
fs.writeFileSync(path.join(tmpDir, "a.quality.sarif"), "");
fs.writeFileSync(path.join(tmpDir, "dir1", "b.quality.sarif"), "");
const analysis = analyses.getAnalysisConfig(analysisKind);
fs.writeFileSync(path.join(tmpDir, `a${analysis.sarifExtension}`), "");
fs.writeFileSync(
path.join(tmpDir, "dir1", `b${analysis.sarifExtension}`),
"",
);
}
const expectedSarifFiles: Partial<Record<AnalysisKind, string[]>> = {};
expectedSarifFiles[AnalysisKind.CodeScanning] = [
const expectedSarifFiles = [
path.join(tmpDir, "a.sarif"),
path.join(tmpDir, "b.sarif"),
path.join(tmpDir, "dir1", "d.sarif"),
@@ -154,24 +143,18 @@ test("finding SARIF files", async (t) => {
CodeScanning.sarifPredicate,
);
t.deepEqual(sarifFiles, expectedSarifFiles[AnalysisKind.CodeScanning]);
t.deepEqual(sarifFiles, expectedSarifFiles);
for (const analysisKind of analyses.supportedAnalysisKinds) {
if (analysisKind === AnalysisKind.CodeScanning) continue;
const expectedQualitySarifFiles = [
path.join(tmpDir, "a.quality.sarif"),
path.join(tmpDir, "dir1", "b.quality.sarif"),
];
const qualitySarifFiles = uploadLib.findSarifFilesInDir(
tmpDir,
CodeQuality.sarifPredicate,
);
const analysis = analyses.getAnalysisConfig(analysisKind);
expectedSarifFiles[analysisKind] = [
path.join(tmpDir, `a${analysis.sarifExtension}`),
path.join(tmpDir, "dir1", `b${analysis.sarifExtension}`),
];
const foundSarifFiles = uploadLib.findSarifFilesInDir(
tmpDir,
analysis.sarifPredicate,
);
t.deepEqual(foundSarifFiles, expectedSarifFiles[analysisKind]);
}
t.deepEqual(qualitySarifFiles, expectedQualitySarifFiles);
const groupedSarifFiles = await uploadLib.getGroupedSarifFilePaths(
getRunnerLogger(true),
@@ -179,31 +162,16 @@ test("finding SARIF files", async (t) => {
);
t.not(groupedSarifFiles, undefined);
for (const analysisKind of analyses.supportedAnalysisKinds) {
t.not(groupedSarifFiles[analysisKind], undefined);
t.deepEqual(
groupedSarifFiles[analysisKind],
expectedSarifFiles[analysisKind],
);
}
});
});
test("getGroupedSarifFilePaths - Risk Assessment files", async (t) => {
await withTmpDir(async (tmpDir) => {
const sarifPath = path.join(tmpDir, "a.csra.sarif");
fs.writeFileSync(sarifPath, "");
const groupedSarifFiles = await uploadLib.getGroupedSarifFilePaths(
getRunnerLogger(true),
sarifPath,
t.not(groupedSarifFiles[AnalysisKind.CodeScanning], undefined);
t.not(groupedSarifFiles[AnalysisKind.CodeQuality], undefined);
t.deepEqual(
groupedSarifFiles[AnalysisKind.CodeScanning],
expectedSarifFiles,
);
t.deepEqual(
groupedSarifFiles[AnalysisKind.CodeQuality],
expectedQualitySarifFiles,
);
t.not(groupedSarifFiles, undefined);
t.is(groupedSarifFiles[AnalysisKind.CodeScanning], undefined);
t.is(groupedSarifFiles[AnalysisKind.CodeQuality], undefined);
t.not(groupedSarifFiles[AnalysisKind.RiskAssessment], undefined);
t.deepEqual(groupedSarifFiles[AnalysisKind.RiskAssessment], [sarifPath]);
});
});
@@ -220,7 +188,6 @@ test("getGroupedSarifFilePaths - Code Quality file", async (t) => {
t.not(groupedSarifFiles, undefined);
t.is(groupedSarifFiles[AnalysisKind.CodeScanning], undefined);
t.not(groupedSarifFiles[AnalysisKind.CodeQuality], undefined);
t.is(groupedSarifFiles[AnalysisKind.RiskAssessment], undefined);
t.deepEqual(groupedSarifFiles[AnalysisKind.CodeQuality], [sarifPath]);
});
});
@@ -238,7 +205,6 @@ test("getGroupedSarifFilePaths - Code Scanning file", async (t) => {
t.not(groupedSarifFiles, undefined);
t.not(groupedSarifFiles[AnalysisKind.CodeScanning], undefined);
t.is(groupedSarifFiles[AnalysisKind.CodeQuality], undefined);
t.is(groupedSarifFiles[AnalysisKind.RiskAssessment], undefined);
t.deepEqual(groupedSarifFiles[AnalysisKind.CodeScanning], [sarifPath]);
});
});
@@ -256,7 +222,6 @@ test("getGroupedSarifFilePaths - Other file", async (t) => {
t.not(groupedSarifFiles, undefined);
t.not(groupedSarifFiles[AnalysisKind.CodeScanning], undefined);
t.is(groupedSarifFiles[AnalysisKind.CodeQuality], undefined);
t.is(groupedSarifFiles[AnalysisKind.RiskAssessment], undefined);
t.deepEqual(groupedSarifFiles[AnalysisKind.CodeScanning], [sarifPath]);
});
});
@@ -910,15 +875,7 @@ function createMockSarif(id?: string, tool?: string) {
function uploadPayloadFixtures(analysis: analyses.AnalysisConfig) {
const mockData = {
payload: {
commit_oid: "abc123",
ref: "ref",
sarif: "base64data",
workflow_run_id: 1,
workflow_run_attempt: 1,
checkout_uri: "uri",
tool_names: ["codeql"],
} satisfies UploadPayload,
payload: { sarif: "base64data", commit_sha: "abc123" },
owner: "test-owner",
repo: "test-repo",
response: {
@@ -950,9 +907,7 @@ function uploadPayloadFixtures(analysis: analyses.AnalysisConfig) {
};
}
for (const analysisKind of analyses.supportedAnalysisKinds) {
const analysis = analyses.getAnalysisConfig(analysisKind);
for (const analysis of [CodeScanning, CodeQuality]) {
test(`uploadPayload on ${analysis.name} uploads successfully`, async (t) => {
const { upload, requestStub, mockData } = uploadPayloadFixtures(analysis);
requestStub
+15 -18
View File
@@ -21,7 +21,6 @@ import * as gitUtils from "./git-utils";
import { initCodeQL } from "./init";
import { Logger } from "./logging";
import { getRepositoryNwo, RepositoryNwo } from "./repository";
import { BasePayload, UploadPayload } from "./upload-lib/types";
import * as util from "./util";
import {
ConfigurationError,
@@ -327,7 +326,7 @@ function getAutomationID(
* This is exported for testing purposes only.
*/
export async function uploadPayload(
payload: BasePayload,
payload: any,
repositoryNwo: RepositoryNwo,
logger: Logger,
analysis: analyses.AnalysisConfig,
@@ -619,8 +618,8 @@ export function buildPayload(
environment: string | undefined,
toolNames: string[],
mergeBaseCommitOid: string | undefined,
): UploadPayload {
const payloadObj: UploadPayload = {
) {
const payloadObj = {
commit_oid: commitOid,
ref,
analysis_key: analysisKey,
@@ -848,20 +847,18 @@ export async function uploadPostProcessedFiles(
const zippedSarif = zlib.gzipSync(sarifPayload).toString("base64");
const checkoutURI = url.pathToFileURL(checkoutPath).href;
const payload = uploadTarget.transformPayload(
buildPayload(
await gitUtils.getCommitOid(checkoutPath),
await gitUtils.getRef(),
postProcessingResults.analysisKey,
util.getRequiredEnvParam("GITHUB_WORKFLOW"),
zippedSarif,
actionsUtil.getWorkflowRunID(),
actionsUtil.getWorkflowRunAttempt(),
checkoutURI,
postProcessingResults.environment,
toolNames,
await gitUtils.determineBaseBranchHeadCommitOid(),
),
const payload = buildPayload(
await gitUtils.getCommitOid(checkoutPath),
await gitUtils.getRef(),
postProcessingResults.analysisKey,
util.getRequiredEnvParam("GITHUB_WORKFLOW"),
zippedSarif,
actionsUtil.getWorkflowRunID(),
actionsUtil.getWorkflowRunAttempt(),
checkoutURI,
postProcessingResults.environment,
toolNames,
await gitUtils.determineBaseBranchHeadCommitOid(),
);
// Log some useful debug info about the info
-45
View File
@@ -1,45 +0,0 @@
/**
* Represents the minimum, common payload for SARIF upload endpoints that we support.
*/
export interface BasePayload {
/** The gzipped contents of a SARIF file. */
sarif: string;
}
/**
* Represents the payload expected for Code Scanning and Code Quality SARIF uploads.
*/
export interface UploadPayload extends BasePayload {
/** The SHA of the commit that was analysed. */
commit_oid: string;
/** The ref that was analysed. */
ref: string;
/** The analysis key that identifies the analysis. */
analysis_key?: string;
/** The name of the analysis. */
analysis_name?: string;
/** The ID of the workflow run that performed the analysis. */
workflow_run_id: number;
/** The attempt number. */
workflow_run_attempt: number;
/** The URI where the repository was checked out. */
checkout_uri: string;
/** The matrix value. */
environment?: string;
/** A string representation of when the analysis was started. */
started_at?: string;
/** The names of the tools that performed the analysis. */
tool_names: string[];
/** For a pull request, the ref of the base the PR is targeting. */
base_ref?: string;
/** For a pull request, the commit SHA of the merge base. */
base_sha?: string;
}
/**
* Represents the payload expected for Code Scanning Risk Assessment SARIF uploads.
*/
export interface AssessmentPayload extends BasePayload {
/** The ID of the assessment for which the SARIF is for. */
assessment_id: number;
}
+7 -1
View File
@@ -34,6 +34,7 @@ interface UploadSarifStatusReport
async function sendSuccessStatusReport(
startedAt: Date,
features: Features | undefined,
uploadStats: upload_lib.UploadStatusReport,
logger: Logger,
) {
@@ -42,6 +43,7 @@ async function sendSuccessStatusReport(
"success",
startedAt,
undefined,
features?.getQueriedFeatures(),
await checkDiskUsage(logger),
logger,
);
@@ -59,6 +61,7 @@ async function run(startedAt: Date) {
// possible, and only use safe functions outside.
const logger = getActionsLogger();
let features: Features | undefined = undefined;
try {
initializeEnvironment(getActionVersion());
@@ -70,7 +73,7 @@ async function run(startedAt: Date) {
actionsUtil.persistInputs();
const repositoryNwo = getRepositoryNwo();
const features = new Features(
features = new Features(
gitHubVersion,
repositoryNwo,
getTemporaryDirectory(),
@@ -82,6 +85,7 @@ async function run(startedAt: Date) {
"starting",
startedAt,
undefined,
features.getQueriedFeatures(),
await checkDiskUsage(logger),
logger,
);
@@ -135,6 +139,7 @@ async function run(startedAt: Date) {
}
await sendSuccessStatusReport(
startedAt,
features,
codeScanningResult?.statusReport || {},
logger,
);
@@ -152,6 +157,7 @@ async function run(startedAt: Date) {
getActionsStatus(error),
startedAt,
undefined,
features?.getQueriedFeatures(),
await checkDiskUsage(logger),
logger,
message,
-25
View File
@@ -563,28 +563,3 @@ test("joinAtMost - truncates list if array is > than limit", (t) => {
t.assert(result.includes("test5"));
t.false(result.includes("test6"));
});
test("Result.success creates a success result", (t) => {
const result = util.Result.success("test value");
t.true(result.isSuccess());
t.false(result.isFailure());
t.is(result.value, "test value");
});
test("Result.failure creates a failure result", (t) => {
const error = new Error("test error");
const result = util.Result.failure(error);
t.false(result.isSuccess());
t.true(result.isFailure());
t.is(result.value, error);
});
test("Result.orElse returns the value for a success result", (t) => {
const result = util.Result.success("success value");
t.is(result.orElse("default value"), "success value");
});
test("Result.orElse returns the default value for a failure result", (t) => {
const result = util.Result.failure(new Error("test error"));
t.is(result.orElse("default value"), "default value");
});
-40
View File
@@ -1292,43 +1292,3 @@ export function joinAtMost(
return array.join(separator);
}
/** A success result. */
type Success<T> = Result<T, never>;
/** A failure result. */
type Failure<E> = Result<never, E>;
/**
* A simple result type representing either a success or a failure.
*/
export class Result<T, E> {
private constructor(
private readonly _ok: boolean,
public readonly value: T | E,
) {}
/** Creates a success result. */
static success<T>(value: T): Success<T> {
return new Result(true, value) as Success<T>;
}
/** Creates a failure result. */
static failure<E>(value: E): Failure<E> {
return new Result(false, value) as Failure<E>;
}
/** Whether this result represents a success. */
isSuccess(): this is Success<T> {
return this._ok;
}
/** Whether this result represents a failure. */
isFailure(): this is Failure<E> {
return !this._ok;
}
/** Get the value if this is a success, or return the default value if this is a failure. */
orElse<U>(defaultValue: U): T | U {
return this.isSuccess() ? this.value : defaultValue;
}
}
+2 -2
View File
@@ -19,8 +19,8 @@
"alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
"noUnusedLocals": false, /* Report errors on unused locals. */
"noUnusedParameters": false, /* Report errors on unused parameters. */
"noUnusedLocals": true, /* Report errors on unused locals. */
"noUnusedParameters": true, /* Report errors on unused parameters. */
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
"noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */