mirror of
https://github.com/github/codeql-action.git
synced 2026-11-05 04:29:40 +00:00
Merge remote-tracking branch 'origin/main' into henrymercer/graduate-overlay-version-match
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
17
.github/copilot-instructions.md
vendored
17
.github/copilot-instructions.md
vendored
@@ -11,9 +11,12 @@ The CodeQL Action is used in GitHub Actions workflows to run CodeQL scans using
|
||||
|
||||
## Review instructions
|
||||
|
||||
- When wording review comments, be helpful and friendly. Assume that the PR author has written the code with the best of intentions. Word your comments constructively as suggestions for improvements. Do not word suggestions as commands.
|
||||
- Prioritise identifying critical issues, such as logic bugs, over comments on documentation or typos.
|
||||
- When wording review comments, be helpful and friendly. Assume that the PR author has written the code with the best of intentions. Never word suggestions as commands or using the imperative. Examples of what you should never write: "Preserve that non-fatal behaviour here" or "Please preserve that non-fatal behaviour here".
|
||||
- When writing feedback, assume that the PR author is competent and is able to understand whether the point you are making is valid or not, why you are raising it, and how to best address it. While you can suggest how to address a point you are making, don't phrase it as a requirement or assume that it is the only solution.
|
||||
- Always word your comments constructively as suggestions for improvements, for example: "It would make sense to ..." or "Could you add ...?" or "Would it make sense to ...?"
|
||||
- If you want to comment on a change that you believe will fail a CI check, do not present the CI failure you expect as a fact. Instead, write that you think a change "may" lead to a failure in CI. Suggest that, if such a failure manifests, the changes you are commenting on may be the place responsible for the failure and are worth looking at.
|
||||
- If a suggestion you make is suitable for a follow-up, such as a refactoring that doesn't change the behaviour or fixing a typo in a comment, mention that it can be addressed in a later PR rather than blocking this one.
|
||||
- If a suggestion you make is suitable for a follow-up, such as a refactoring that doesn't change the behaviour or fixing a typo in a comment, mention that it can be addressed in a later PR rather than blocking this one or prefix your comment with "Minor: ".
|
||||
- If a change is a net improvement, for example because it improves on an existing limitation of existing code, do not complain about pre-existing problems that remain. You may comment on them, but you should make it clear that the thing you are commenting on is not new by writing e.g. "Not new in this PR, but [..]" followed by your description of the issue and a suggestion that it could be improved at the same time with e.g. "Consider whether this is worth addressing as part of this PR as well."
|
||||
|
||||
## Generated code
|
||||
@@ -22,6 +25,16 @@ The main codebase of the CodeQL Action is written in TypeScript and compiled to
|
||||
|
||||
GitHub Actions workflows in the `.github/workflows` directory whose filenames start with two underscores (e.g. `__all-platform-bundle.yml`) are automatically generated using the `pr-checks/sync.sh` script from template files in the `pr-checks/checks` directory. Therefore, you do not need to review files in the `.github/workflows` directory that start with two underscores. However, you should review changes to the `pr-checks` directory as well as workflows in the `.github/workflows` directory that do not start with underscores.
|
||||
|
||||
## Feature flags
|
||||
|
||||
Some functionality in the code may be gated behind feature flags, which are controlled server-side. If the functionality that is gated behind them is user-facing, then this should be documented in a change note at the time that the gated functionality is added. See `CHANGELOG.md` for existing notes that mention "experimental" for examples. These typically include information about when users may expect to see the feature rolled out.
|
||||
|
||||
When a feature flag that gates some functionality is removed, this does not need to be documented in a change note since the feature is already rolled out and the notes for the release which introduced the feature already describe it. The only exception is functionality which will start having an effect on GHES as a result of the feature flag removal:
|
||||
|
||||
- Most functionality gated behind feature flags does not affect GHES.
|
||||
- We can make functionality available on GHES by setting `defaultValue` to `true` first and waiting for this to have had an effect on GHES. No change note is needed if a feature flag is removed where `defaultValue` was `true`.
|
||||
- When removing a feature flag where `defaultValue` was `false` and the functionality starts to affect GHES, a change note may be appropriate.
|
||||
|
||||
## Release process
|
||||
|
||||
Changes that are merged to the `main` branch are not used in production straight away. Instead, changes from `main` must first be released using the release process, which has to be manually triggered by a staff member. Therefore, it is possible for further changes to be made to the code after some changes have been merged to `main` but before they are released. Once the release process automation is started, a series of PRs are automatically generated:
|
||||
|
||||
4
.github/workflows/__analysis-kinds.yml
generated
vendored
4
.github/workflows/__analysis-kinds.yml
generated
vendored
@@ -87,7 +87,7 @@ jobs:
|
||||
post-processed-sarif-path: '${{ runner.temp }}/post-processed'
|
||||
|
||||
- name: Upload SARIF files
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: |
|
||||
analysis-kinds-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}
|
||||
@@ -95,7 +95,7 @@ jobs:
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload post-processed SARIF
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: |
|
||||
post-processed-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}
|
||||
|
||||
2
.github/workflows/__config-export.yml
generated
vendored
2
.github/workflows/__config-export.yml
generated
vendored
@@ -65,7 +65,7 @@ jobs:
|
||||
output: '${{ runner.temp }}/results'
|
||||
upload-database: false
|
||||
- name: Upload SARIF
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: config-export-${{ matrix.os }}-${{ matrix.version }}.sarif.json
|
||||
path: '${{ runner.temp }}/results/javascript.sarif'
|
||||
|
||||
2
.github/workflows/__config-input.yml
generated
vendored
2
.github/workflows/__config-input.yml
generated
vendored
@@ -47,7 +47,7 @@ jobs:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: npm
|
||||
|
||||
62
.github/workflows/__diagnostics-export.yml
generated
vendored
62
.github/workflows/__diagnostics-export.yml
generated
vendored
@@ -48,6 +48,15 @@ jobs:
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: npm
|
||||
- name: Install newer npm
|
||||
run: npm install -g npm@11.19.1
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Prepare test
|
||||
id: prepare-test
|
||||
uses: ./.github/actions/prepare-test
|
||||
@@ -76,61 +85,18 @@ jobs:
|
||||
output: '${{ runner.temp }}/results'
|
||||
upload-database: false
|
||||
- name: Upload SARIF
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: diagnostics-export-${{ matrix.os }}-${{ matrix.version }}.sarif.json
|
||||
path: '${{ runner.temp }}/results/javascript.sarif'
|
||||
retention-days: 7
|
||||
|
||||
- name: Check diagnostics appear in SARIF
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
env:
|
||||
SARIF_PATH: '${{ runner.temp }}/results/javascript.sarif'
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
function checkStatusPageNotification(n) {
|
||||
const expectedMessage = 'Plaintext message';
|
||||
if (n.message.text !== expectedMessage) {
|
||||
core.setFailed(`Expected the status page diagnostic to have the message '${expectedMessage}', but found '${n.message.text}'.`);
|
||||
}
|
||||
if (n.locations.length !== 1) {
|
||||
core.setFailed(`Expected the status page diagnostic to have exactly 1 location, but found ${n.locations.length}.`);
|
||||
}
|
||||
}
|
||||
|
||||
const sarif = JSON.parse(fs.readFileSync(process.env['SARIF_PATH'], 'utf8'));
|
||||
const run = sarif.runs[0];
|
||||
|
||||
const toolExecutionNotifications = run.invocations[0].toolExecutionNotifications;
|
||||
const statusPageNotifications = toolExecutionNotifications.filter(n =>
|
||||
n.descriptor.id === 'lang/diagnostics/example' && n.properties?.visibility?.statusPage
|
||||
);
|
||||
if (statusPageNotifications.length !== 1) {
|
||||
core.setFailed(
|
||||
'Expected exactly one status page reporting descriptor for this diagnostic in the ' +
|
||||
`'runs[].invocations[].toolExecutionNotifications[]' SARIF property, but found ` +
|
||||
`${statusPageNotifications.length}. All notification reporting descriptors: ` +
|
||||
`${JSON.stringify(toolExecutionNotifications)}.`
|
||||
);
|
||||
}
|
||||
checkStatusPageNotification(statusPageNotifications[0]);
|
||||
|
||||
const notifications = run.tool.driver.notifications;
|
||||
const diagnosticNotification = notifications.filter(n =>
|
||||
n.id === 'lang/diagnostics/example' && n.name === 'lang/diagnostics/example' &&
|
||||
n.fullDescription.text === 'Diagnostic name'
|
||||
);
|
||||
if (diagnosticNotification.length !== 1) {
|
||||
core.setFailed(
|
||||
'Expected exactly one notification for this diagnostic in the ' +
|
||||
`'runs[].tool.driver.notifications[]' SARIF property, but found ` +
|
||||
`${diagnosticNotification.length}. All notifications: ` +
|
||||
`${JSON.stringify(notifications)}.`
|
||||
);
|
||||
}
|
||||
|
||||
core.info('Finished diagnostic export test');
|
||||
run: |
|
||||
cd ../action
|
||||
npx tsx ./pr-checks/check-diagnostics.ts
|
||||
env:
|
||||
CODEQL_ACTION_EXPORT_DIAGNOSTICS: true
|
||||
CODEQL_ACTION_TEST_MODE: true
|
||||
|
||||
2
.github/workflows/__export-file-baseline-information.yml
generated
vendored
2
.github/workflows/__export-file-baseline-information.yml
generated
vendored
@@ -97,7 +97,7 @@ jobs:
|
||||
with:
|
||||
output: '${{ runner.temp }}/results'
|
||||
- name: Upload SARIF
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: with-baseline-information-${{ matrix.os }}-${{ matrix.version }}.sarif.json
|
||||
path: '${{ runner.temp }}/results/javascript.sarif'
|
||||
|
||||
2
.github/workflows/__job-run-uuid-sarif.yml
generated
vendored
2
.github/workflows/__job-run-uuid-sarif.yml
generated
vendored
@@ -62,7 +62,7 @@ jobs:
|
||||
with:
|
||||
output: '${{ runner.temp }}/results'
|
||||
- name: Upload SARIF
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: ${{ matrix.os }}-${{ matrix.version }}.sarif.json
|
||||
path: '${{ runner.temp }}/results/javascript.sarif'
|
||||
|
||||
1
.github/workflows/__multi-language-autodetect.yml
generated
vendored
1
.github/workflows/__multi-language-autodetect.yml
generated
vendored
@@ -192,6 +192,5 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
CODEQL_ACTION_CLEANUP_TOOLCACHE_BUNDLES: true
|
||||
CODEQL_ACTION_RESOLVE_SUPPORTED_LANGUAGES_USING_CLI: true
|
||||
CODEQL_ACTION_TEST_MODE: true
|
||||
|
||||
2
.github/workflows/__packaging-codescanning-config-inputs-js.yml
generated
vendored
2
.github/workflows/__packaging-codescanning-config-inputs-js.yml
generated
vendored
@@ -80,7 +80,7 @@ jobs:
|
||||
go-version: ${{ inputs.go-version || '>=1.21.0' }}
|
||||
cache: false
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: npm
|
||||
|
||||
2
.github/workflows/__packaging-config-inputs-js.yml
generated
vendored
2
.github/workflows/__packaging-config-inputs-js.yml
generated
vendored
@@ -80,7 +80,7 @@ jobs:
|
||||
go-version: ${{ inputs.go-version || '>=1.21.0' }}
|
||||
cache: false
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: npm
|
||||
|
||||
2
.github/workflows/__packaging-config-js.yml
generated
vendored
2
.github/workflows/__packaging-config-js.yml
generated
vendored
@@ -80,7 +80,7 @@ jobs:
|
||||
go-version: ${{ inputs.go-version || '>=1.21.0' }}
|
||||
cache: false
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: npm
|
||||
|
||||
2
.github/workflows/__packaging-inputs-js.yml
generated
vendored
2
.github/workflows/__packaging-inputs-js.yml
generated
vendored
@@ -80,7 +80,7 @@ jobs:
|
||||
go-version: ${{ inputs.go-version || '>=1.21.0' }}
|
||||
cache: false
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: npm
|
||||
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 24
|
||||
cache: 'npm'
|
||||
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
- name: Check expected artifacts exist
|
||||
run: |
|
||||
LANGUAGES="cpp csharp go java javascript python"
|
||||
|
||||
2
.github/workflows/debug-artifacts-safe.yml
vendored
2
.github/workflows/debug-artifacts-safe.yml
vendored
@@ -82,7 +82,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
- name: Check expected artifacts exist
|
||||
run: |
|
||||
VERSIONS="stable-v2.20.7 default linked nightly-latest"
|
||||
|
||||
2
.github/workflows/post-release-mergeback.yml
vendored
2
.github/workflows/post-release-mergeback.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0 # ensure we have all tags and can push commits
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 24
|
||||
cache: 'npm'
|
||||
|
||||
20
.github/workflows/pr-checks.yml
vendored
20
.github/workflows/pr-checks.yml
vendored
@@ -2,6 +2,9 @@ name: PR Checks
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
- "releases/v*"
|
||||
pull_request:
|
||||
merge_group:
|
||||
types: [checks_requested]
|
||||
@@ -42,7 +45,7 @@ jobs:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: "npm"
|
||||
@@ -110,7 +113,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 24
|
||||
cache: "npm"
|
||||
@@ -145,12 +148,11 @@ jobs:
|
||||
|
||||
- name: Fetch base commit
|
||||
id: fetch-base
|
||||
# Forks and Dependabot PRs don't have permission to write comments, so skip the repo size
|
||||
# check in those cases.
|
||||
# Skip this (and subsequent steps) if the PR originates from a fork, since this workflow
|
||||
# won't have the necessary permissions to post comments on the PR.
|
||||
if: >-
|
||||
github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository &&
|
||||
github.event.pull_request.user.login != 'dependabot[bot]'
|
||||
github.event.pull_request.head.repo.full_name == github.repository
|
||||
env:
|
||||
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
@@ -175,7 +177,7 @@ jobs:
|
||||
|
||||
- name: Upload repo size comment
|
||||
if: steps.fetch-base.outcome == 'success'
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: repo-size-comment
|
||||
path: ${{ runner.temp }}/repo-size/
|
||||
@@ -209,7 +211,7 @@ jobs:
|
||||
if: >-
|
||||
github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository &&
|
||||
github.event.pull_request.user.login != 'dependabot[bot]' &&
|
||||
github.actor != 'dependabot[bot]' &&
|
||||
needs.other-checks.result == 'success'
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -223,7 +225,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Download repo size comment
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
name: repo-size-comment
|
||||
path: repo-size-comment
|
||||
|
||||
2
.github/workflows/query-filters.yml
vendored
2
.github/workflows/query-filters.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 24
|
||||
cache: npm
|
||||
|
||||
2
.github/workflows/rebuild.yml
vendored
2
.github/workflows/rebuild.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
ref: ${{ env.HEAD_REF }}
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 24
|
||||
cache: 'npm'
|
||||
|
||||
2
.github/workflows/update-bundle.yml
vendored
2
.github/workflows/update-bundle.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
||||
git config --global user.name "github-actions[bot]"
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 24
|
||||
cache: 'npm'
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 24
|
||||
cache: 'npm'
|
||||
|
||||
@@ -4,7 +4,14 @@ See the [releases page](https://github.com/github/codeql-action/releases) for th
|
||||
|
||||
## [UNRELEASED]
|
||||
|
||||
- Update default CodeQL bundle version to [2.27.3](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.27.3). [#4256](https://github.com/github/codeql-action/pull/4256)
|
||||
|
||||
## 4.39.0 - 15 Oct 2026
|
||||
|
||||
- _Breaking change_: Bump the minimum required CodeQL bundle version to 2.20.7. [#4205](https://github.com/github/codeql-action/pull/4205)
|
||||
- The `codeql-action/analyze` step now validates that a correct argument for the `checkout_path` input has been provided. The `checkout_path` input must be set to the path where the repository that is being analyzed is checked out at on the runner, if that path is something other than the workspace root. If the path does not point to a repository checkout or is different from the path that was used by `codeql-action/init`, then the CodeQL Action now produces a warning. [#4148](https://github.com/github/codeql-action/pull/4148)
|
||||
- Resolved an issue with third-party caching implementations that prevented caches from being restored correctly. [#4206](https://github.com/github/codeql-action/pull/4206)
|
||||
- The `codeql-action/start-proxy` step that is used in Code Scanning Default Setup now finds CodeQL releases using the same logic as `codeql-action/init`. This primarily benefits customers who are using GitHub instances other than `github.com` where the `codeql-action/start-proxy` step may previously have been unable to locate recent releases and ended up using outdated versions of the private registry authentication proxy as a result. [#4198](https://github.com/github/codeql-action/pull/4198)
|
||||
|
||||
## 4.38.3 - 08 Oct 2026
|
||||
|
||||
|
||||
@@ -153,7 +153,10 @@ inputs:
|
||||
required: false
|
||||
trap-caching:
|
||||
description: >-
|
||||
Explicitly enable or disable TRAP caching rather than respecting the feature flag for it.
|
||||
Explicitly enable or disable TRAP caching instead of using the automatically determined default.
|
||||
TRAP caching is disabled by default when improved incremental analysis is enabled,
|
||||
because improved incremental analysis of a pull request only extracts changed files,
|
||||
which rarely benefit from TRAP caching.
|
||||
required: false
|
||||
dependency-caching:
|
||||
description: >-
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"bundleVersion": "codeql-bundle-v2.27.2",
|
||||
"cliVersion": "2.27.2",
|
||||
"priorBundleVersion": "codeql-bundle-v2.27.1",
|
||||
"priorCliVersion": "2.27.1"
|
||||
"bundleVersion": "codeql-bundle-v2.27.3",
|
||||
"cliVersion": "2.27.3",
|
||||
"priorBundleVersion": "codeql-bundle-v2.27.2",
|
||||
"priorCliVersion": "2.27.2"
|
||||
}
|
||||
|
||||
397
lib/entry-points.js
generated
397
lib/entry-points.js
generated
@@ -26401,7 +26401,7 @@ var require_utils4 = __commonJS({
|
||||
})();
|
||||
Object.defineProperty(exports2, "__esModule", { value: true });
|
||||
exports2.GitHub = exports2.defaults = exports2.context = void 0;
|
||||
exports2.getOctokitOptions = getOctokitOptions2;
|
||||
exports2.getOctokitOptions = getOctokitOptions;
|
||||
var Context = __importStar2(require_context());
|
||||
var Utils = __importStar2(require_utils3());
|
||||
var core_1 = (init_dist_src2(), __toCommonJS(dist_src_exports));
|
||||
@@ -26417,7 +26417,7 @@ var require_utils4 = __commonJS({
|
||||
}
|
||||
};
|
||||
exports2.GitHub = core_1.Octokit.plugin(plugin_rest_endpoint_methods_1.restEndpointMethods, plugin_paginate_rest_1.paginateRest).defaults(exports2.defaults);
|
||||
function getOctokitOptions2(token, options) {
|
||||
function getOctokitOptions(token, options) {
|
||||
const opts = Object.assign({}, options || {});
|
||||
const auth2 = Utils.getAuthString(token, opts);
|
||||
if (auth2) {
|
||||
@@ -140010,7 +140010,7 @@ var require_utils10 = __commonJS({
|
||||
}
|
||||
};
|
||||
exports2.GitHub = core_1.Octokit.plugin(plugin_rest_endpoint_methods_1.restEndpointMethods, plugin_paginate_rest_1.paginateRest).defaults(exports2.defaults);
|
||||
function getOctokitOptions2(token, options) {
|
||||
function getOctokitOptions(token, options) {
|
||||
const opts = Object.assign({}, options || {});
|
||||
const auth2 = Utils.getAuthString(token, opts);
|
||||
if (auth2) {
|
||||
@@ -140018,7 +140018,7 @@ var require_utils10 = __commonJS({
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
exports2.getOctokitOptions = getOctokitOptions2;
|
||||
exports2.getOctokitOptions = getOctokitOptions;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -169558,7 +169558,6 @@ function checkSchema(schema, obj, options = {}, path30 = "") {
|
||||
// src/util.ts
|
||||
var BASE_DATABASE_OIDS_FILE_NAME = "base-database-oids.json";
|
||||
var BROKEN_VERSIONS = ["0.0.0-20211207"];
|
||||
var GITHUB_DOTCOM_URL = "https://github.com";
|
||||
var DEFAULT_DEBUG_ARTIFACT_NAME = "debug-artifacts";
|
||||
var DEFAULT_DEBUG_DATABASE_NAME = "db";
|
||||
var DEFAULT_RESERVED_RAM_SCALING_FACTOR = 0.05;
|
||||
@@ -169774,34 +169773,6 @@ function getGeneratedSuitePath(config, language) {
|
||||
"config-queries.qls"
|
||||
);
|
||||
}
|
||||
function parseGitHubUrl(inputUrl) {
|
||||
const originalUrl = inputUrl;
|
||||
if (inputUrl.indexOf("://") === -1) {
|
||||
inputUrl = `https://${inputUrl}`;
|
||||
}
|
||||
if (!inputUrl.startsWith("http://") && !inputUrl.startsWith("https://")) {
|
||||
throw new ConfigurationError(`"${originalUrl}" is not a http or https URL`);
|
||||
}
|
||||
let url2;
|
||||
try {
|
||||
url2 = new URL(inputUrl);
|
||||
} catch {
|
||||
throw new ConfigurationError(`"${originalUrl}" is not a valid URL`);
|
||||
}
|
||||
if (url2.hostname === "github.com" || url2.hostname === "api.github.com") {
|
||||
return GITHUB_DOTCOM_URL;
|
||||
}
|
||||
if (url2.pathname.indexOf("/api/v3") !== -1) {
|
||||
url2.pathname = url2.pathname.substring(0, url2.pathname.indexOf("/api/v3"));
|
||||
}
|
||||
if (url2.hostname.startsWith("api.")) {
|
||||
url2.hostname = url2.hostname.substring(4);
|
||||
}
|
||||
if (!url2.pathname.endsWith("/")) {
|
||||
url2.pathname = `${url2.pathname}/`;
|
||||
}
|
||||
return url2.toString();
|
||||
}
|
||||
var CODEQL_ACTION_WARNED_ABOUT_VERSION_ENV_VAR = "CODEQL_ACTION_WARNED_ABOUT_VERSION";
|
||||
var hasBeenWarnedAboutVersion = false;
|
||||
function checkGitHubVersionInRange(version, logger2) {
|
||||
@@ -169918,8 +169889,15 @@ async function delay(milliseconds, opts) {
|
||||
function isGoodVersion(versionSpec) {
|
||||
return !BROKEN_VERSIONS.includes(versionSpec);
|
||||
}
|
||||
function shouldSkipSarifUpload() {
|
||||
return isInTestMode() || process.env["CODEQL_ACTION_SKIP_SARIF_UPLOAD" /* SKIP_SARIF_UPLOAD */] === "true";
|
||||
function shouldSkipGenerateFailedSarif(env = getEnv()) {
|
||||
const skipGenerateFailedSarif = env.getOptional(
|
||||
"CODEQL_ACTION_SKIP_GENERATE_FAILED_SARIF" /* SKIP_GENERATE_FAILED_SARIF */
|
||||
);
|
||||
return skipGenerateFailedSarif !== "false" && (shouldSkipSarifUpload(env) || skipGenerateFailedSarif === "true");
|
||||
}
|
||||
function shouldSkipSarifUpload(env = getEnv()) {
|
||||
const skipSarifUpload = env.getOptional("CODEQL_ACTION_SKIP_SARIF_UPLOAD" /* SKIP_SARIF_UPLOAD */);
|
||||
return skipSarifUpload !== "false" && (env.isSkippingUploadsInTests() || skipSarifUpload === "true");
|
||||
}
|
||||
function getTestingEnvironment() {
|
||||
const testingEnvironment = process.env["CODEQL_ACTION_TESTING_ENVIRONMENT" /* TESTING_ENVIRONMENT */] || "";
|
||||
@@ -170183,7 +170161,7 @@ function getDiffRangesJsonFilePath(env = getEnv()) {
|
||||
return path2.join(getTemporaryDirectory(env), PR_DIFF_RANGE_JSON_FILENAME);
|
||||
}
|
||||
function getActionVersion() {
|
||||
return "4.39.0";
|
||||
return "4.39.1";
|
||||
}
|
||||
function getWorkflowEventName(env = getEnv()) {
|
||||
return env.getRequired("GITHUB_EVENT_NAME" /* GITHUB_EVENT_NAME */);
|
||||
@@ -170659,6 +170637,43 @@ retry.VERSION = VERSION7;
|
||||
// src/api-client.ts
|
||||
var import_undici = __toESM(require_undici());
|
||||
|
||||
// src/github.ts
|
||||
var GITHUB_DOTCOM_URL = "https://github.com";
|
||||
function parseGitHubUrl(inputUrl) {
|
||||
const originalUrl = inputUrl;
|
||||
if (inputUrl.indexOf("://") === -1) {
|
||||
inputUrl = `https://${inputUrl}`;
|
||||
}
|
||||
if (!inputUrl.startsWith("http://") && !inputUrl.startsWith("https://")) {
|
||||
throw new ConfigurationError(`"${originalUrl}" is not a http or https URL`);
|
||||
}
|
||||
let url2;
|
||||
try {
|
||||
url2 = new URL(inputUrl);
|
||||
} catch {
|
||||
throw new ConfigurationError(`"${originalUrl}" is not a valid URL`);
|
||||
}
|
||||
if (url2.hostname === "github.com" || url2.hostname === "api.github.com") {
|
||||
return GITHUB_DOTCOM_URL;
|
||||
}
|
||||
if (url2.pathname.indexOf("/api/v3") !== -1) {
|
||||
url2.pathname = url2.pathname.substring(0, url2.pathname.indexOf("/api/v3"));
|
||||
}
|
||||
if (url2.hostname.startsWith("api.")) {
|
||||
url2.hostname = url2.hostname.substring(4);
|
||||
}
|
||||
if (!url2.pathname.endsWith("/")) {
|
||||
url2.pathname = `${url2.pathname}/`;
|
||||
}
|
||||
return url2.toString();
|
||||
}
|
||||
function getCurrentInstance(env) {
|
||||
return {
|
||||
url: env.getRequired("GITHUB_SERVER_URL" /* GITHUB_SERVER_URL */),
|
||||
apiURL: env.getRequired("GITHUB_API_URL" /* GITHUB_API_URL */)
|
||||
};
|
||||
}
|
||||
|
||||
// src/repository.ts
|
||||
function isRepositoryNwoStr(val) {
|
||||
return val.includes("/");
|
||||
@@ -170724,43 +170739,46 @@ function makeProxyRequestOptions(dispatcher) {
|
||||
};
|
||||
}
|
||||
function createApiClientWithDetails(apiDetails, { allowExternal = false, proxy = void 0 } = {}) {
|
||||
const auth2 = allowExternal && apiDetails.externalRepoAuth || apiDetails.auth;
|
||||
const auth2 = allowExternal && apiDetails.externalRepoAuth !== void 0 ? apiDetails.externalRepoAuth : apiDetails.auth;
|
||||
const retryingOctokit = githubUtils.GitHub.plugin(retry);
|
||||
const requestOptions = makeProxyRequestOptions(proxy);
|
||||
return new retryingOctokit(
|
||||
githubUtils.getOctokitOptions(auth2, {
|
||||
baseUrl: apiDetails.apiURL,
|
||||
userAgent: `CodeQL-Action/${getActionVersion()}`,
|
||||
log: {
|
||||
debug: core6.debug,
|
||||
info: core6.info,
|
||||
warn: core6.warning,
|
||||
error: core6.error
|
||||
},
|
||||
request: requestOptions,
|
||||
retry: {
|
||||
doNotRetry: DO_NOT_RETRY_STATUSES
|
||||
}
|
||||
})
|
||||
);
|
||||
const opts = {
|
||||
auth: auth2 !== void 0 && auth2.trim() !== "" ? `token ${auth2}` : void 0,
|
||||
baseUrl: apiDetails.apiURL,
|
||||
userAgent: `CodeQL-Action/${getActionVersion()}`,
|
||||
log: {
|
||||
debug: core6.debug,
|
||||
info: core6.info,
|
||||
warn: core6.warning,
|
||||
error: core6.error
|
||||
},
|
||||
request: requestOptions,
|
||||
retry: {
|
||||
doNotRetry: DO_NOT_RETRY_STATUSES
|
||||
}
|
||||
};
|
||||
return new retryingOctokit(opts);
|
||||
}
|
||||
function getApiDetails(env = getEnv()) {
|
||||
return {
|
||||
auth: getRequiredInput("token"),
|
||||
url: env.getRequired("GITHUB_SERVER_URL" /* GITHUB_SERVER_URL */),
|
||||
apiURL: env.getRequired("GITHUB_API_URL" /* GITHUB_API_URL */)
|
||||
...getCurrentInstance(env)
|
||||
};
|
||||
}
|
||||
function getApiClient(env = getEnv()) {
|
||||
return createApiClientWithDetails(getApiDetails(env));
|
||||
function getApiClient(env = getEnv(), apiDetails) {
|
||||
return createApiClientWithDetails(apiDetails ?? getApiDetails(env));
|
||||
}
|
||||
function getApiClientWithExternalAuth(apiDetails, proxy) {
|
||||
return createApiClientWithDetails(apiDetails, { allowExternal: true, proxy });
|
||||
}
|
||||
function getAuthorizationHeaderFor(logger2, apiDetails, url2) {
|
||||
const token = apiDetails.auth?.trim();
|
||||
if (token === void 0 || token === "") {
|
||||
return void 0;
|
||||
}
|
||||
if (url2.startsWith(`${apiDetails.url}/`) || apiDetails.apiURL && url2.startsWith(`${apiDetails.apiURL}/`)) {
|
||||
logger2.debug(`Providing an authorization token.`);
|
||||
return `token ${apiDetails.auth}`;
|
||||
return `token ${token}`;
|
||||
}
|
||||
logger2.debug(`Not using an authorization token.`);
|
||||
return void 0;
|
||||
@@ -171781,8 +171799,8 @@ var path6 = __toESM(require("path"));
|
||||
var semver3 = __toESM(require_semver2());
|
||||
|
||||
// src/defaults.json
|
||||
var bundleVersion = "codeql-bundle-v2.27.2";
|
||||
var cliVersion = "2.27.2";
|
||||
var bundleVersion = "codeql-bundle-v2.27.3";
|
||||
var cliVersion = "2.27.3";
|
||||
|
||||
// src/overlay/index.ts
|
||||
var fs5 = __toESM(require("fs"));
|
||||
@@ -171921,11 +171939,6 @@ var featureConfig = {
|
||||
envVar: "CODEQL_ACTION_ALLOW_MULTIPLE_ANALYSIS_KINDS",
|
||||
minimumVersion: void 0
|
||||
},
|
||||
["cleanup_toolcache_bundles" /* CleanupToolcacheBundles */]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_CLEANUP_TOOLCACHE_BUNDLES",
|
||||
minimumVersion: void 0
|
||||
},
|
||||
["cleanup_trap_caches" /* CleanupTrapCaches */]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_CLEANUP_TRAP_CACHES",
|
||||
@@ -172079,11 +172092,6 @@ var featureConfig = {
|
||||
minimumVersion: CODEQL_OVERLAY_MINIMUM_VERSION_RUBY
|
||||
},
|
||||
// Other overlay-related feature flags
|
||||
["overlay_analysis_disable_trap_caching" /* OverlayAnalysisDisableTrapCaching */]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_DISABLE_TRAP_CACHING",
|
||||
minimumVersion: void 0
|
||||
},
|
||||
["overlay_analysis_min_disk_8_gb" /* OverlayAnalysisMinDisk8Gb */]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_MIN_DISK_8_GB",
|
||||
@@ -174818,11 +174826,11 @@ async function validateOverlayDatabaseMode(overlayDatabaseMode, useOverlayDataba
|
||||
overlayModeSetExplicitly
|
||||
});
|
||||
}
|
||||
async function isTrapCachingEnabled(features, overlayDatabaseMode) {
|
||||
function isTrapCachingEnabled(overlayDatabaseMode) {
|
||||
const trapCaching = getOptionalInput("trap-caching");
|
||||
if (trapCaching !== void 0) return trapCaching === "true";
|
||||
if (!looksLikeHostedRunner()) return false;
|
||||
if (overlayDatabaseMode !== "none" /* None */ && await features.getValue("overlay_analysis_disable_trap_caching" /* OverlayAnalysisDisableTrapCaching */)) {
|
||||
if (overlayDatabaseMode !== "none" /* None */) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -175025,7 +175033,7 @@ async function initConfig(actionState, inputs) {
|
||||
inputs.codeql,
|
||||
logger2
|
||||
);
|
||||
if (await isTrapCachingEnabled(features, config.overlayDatabaseMode)) {
|
||||
if (isTrapCachingEnabled(config.overlayDatabaseMode)) {
|
||||
const { trapCaches, trapCacheDownloadTime } = await downloadCacheWithTime(
|
||||
inputs.codeql,
|
||||
config.languages,
|
||||
@@ -176672,7 +176680,6 @@ function getToolcacheDestination({ logger: logger2 }, source) {
|
||||
}
|
||||
async function tryDeleteToolcacheBundles({
|
||||
env,
|
||||
features,
|
||||
logger: logger2
|
||||
}) {
|
||||
if (env.getOptional("CODEQL_ACTION_HAS_SET_UP_CODEQL" /* HAS_SET_UP_CODEQL */) !== void 0) {
|
||||
@@ -176681,7 +176688,7 @@ async function tryDeleteToolcacheBundles({
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!isGitHubHostedRunner() || !isToolcacheOnWorkspaceFilesystem(logger2) || !await features.getValue("cleanup_toolcache_bundles" /* CleanupToolcacheBundles */)) {
|
||||
if (!isGitHubHostedRunner(env) || !isToolcacheOnWorkspaceFilesystem(logger2)) {
|
||||
return;
|
||||
}
|
||||
const result = await deleteToolcacheBundles({ env, logger: logger2 });
|
||||
@@ -176741,7 +176748,7 @@ async function setupCodeQLBundle(toolsInput, apiDetails, tempDir, variant, defau
|
||||
break;
|
||||
case "download": {
|
||||
const result = await downloadCodeQLBundle(
|
||||
{ env: getEnv(), features, logger: logger2 },
|
||||
{ env: getEnv(), logger: logger2 },
|
||||
source,
|
||||
apiDetails,
|
||||
zstdAvailability.version,
|
||||
@@ -178445,7 +178452,7 @@ async function recordClearCleanupSizes(codeql, config, reports, logger2) {
|
||||
}
|
||||
}
|
||||
async function uploadBundledDatabase(repositoryNwo, language, commitOid, bundledDb, bundledDbSize, apiDetails) {
|
||||
const client = getApiClient();
|
||||
const client = getApiClient(getEnv(), apiDetails);
|
||||
const uploadsUrl = new URL(parseGitHubUrl(apiDetails.url));
|
||||
uploadsUrl.hostname = `uploads.${uploadsUrl.hostname}`;
|
||||
let uploadsBaseUrl = uploadsUrl.toString();
|
||||
@@ -178466,7 +178473,6 @@ async function uploadBundledDatabase(repositoryNwo, language, commitOid, bundled
|
||||
commit_oid: commitOid,
|
||||
data: bundledDbReadStream,
|
||||
headers: {
|
||||
authorization: `token ${apiDetails.auth}`,
|
||||
"Content-Type": "application/zip",
|
||||
"Content-Length": bundledDbSize
|
||||
},
|
||||
@@ -178489,6 +178495,7 @@ __export(upload_lib_exports, {
|
||||
filterAlertsByDiffRange: () => filterAlertsByDiffRange,
|
||||
findSarifFilesInDir: () => findSarifFilesInDir,
|
||||
getGroupedSarifFilePaths: () => getGroupedSarifFilePaths,
|
||||
getSavedPayloadPath: () => getSavedPayloadPath,
|
||||
populateRunAutomationDetails: () => populateRunAutomationDetails,
|
||||
postProcessSarifFiles: () => postProcessSarifFiles,
|
||||
readSarifFileOrThrow: () => readSarifFileOrThrow,
|
||||
@@ -179964,7 +179971,8 @@ async function shouldDisableCombineSarifFiles(sarifObjects, githubVersion) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
async function combineSarifFilesUsingCLI(sarifFiles, gitHubVersion, features, logger2) {
|
||||
async function combineSarifFilesUsingCLI(sarifFiles, gitHubVersion, features, logger2, config) {
|
||||
const env = getEnv();
|
||||
logger2.info("Combining SARIF files using the CodeQL CLI");
|
||||
const sarifObjects = sarifFiles.map(readSarifFile);
|
||||
const deprecationWarningMessage = gitHubVersion.type === "GitHub Enterprise Server" /* GHES */ ? "and will be removed in GitHub Enterprise Server 3.18" : "and will be removed in July 2025";
|
||||
@@ -179984,7 +179992,6 @@ async function combineSarifFilesUsingCLI(sarifFiles, gitHubVersion, features, lo
|
||||
}
|
||||
let codeQL;
|
||||
let tempDir = getTemporaryDirectory();
|
||||
const config = await getConfig(tempDir, logger2);
|
||||
if (config !== void 0) {
|
||||
codeQL = await getCodeQL(logger2, config.codeQLCmd);
|
||||
tempDir = config.tempDir;
|
||||
@@ -179997,8 +180004,7 @@ async function combineSarifFilesUsingCLI(sarifFiles, gitHubVersion, features, lo
|
||||
externalRepoAuth: getOptionalInput(
|
||||
"external-repository-token"
|
||||
),
|
||||
url: getRequiredEnvParam("GITHUB_SERVER_URL"),
|
||||
apiURL: getRequiredEnvParam("GITHUB_API_URL")
|
||||
...getCurrentInstance(env)
|
||||
};
|
||||
const codeQLDefaultVersionInfo = await features.getEnabledDefaultCliVersions(gitHubVersion.type);
|
||||
const initCodeQLResult = await initCodeQL(
|
||||
@@ -180052,13 +180058,16 @@ function getAutomationID2(category, analysis_key, environment) {
|
||||
}
|
||||
return computeAutomationID(analysis_key, environment);
|
||||
}
|
||||
function getSavedPayloadPath(analysisKind) {
|
||||
return path19.join(
|
||||
getTemporaryDirectory(),
|
||||
`payload-${analysisKind}.json`
|
||||
);
|
||||
}
|
||||
async function uploadPayload(payload, repositoryNwo, logger2, analysis) {
|
||||
logger2.info("Uploading results");
|
||||
if (shouldSkipSarifUpload()) {
|
||||
const payloadSaveFile = path19.join(
|
||||
getTemporaryDirectory(),
|
||||
`payload-${analysis.kind}.json`
|
||||
);
|
||||
const payloadSaveFile = getSavedPayloadPath(analysis.kind);
|
||||
logger2.info(
|
||||
`SARIF upload disabled by an environment variable. Saving to ${payloadSaveFile}`
|
||||
);
|
||||
@@ -180270,7 +180279,7 @@ function buildPayload(commitOid, ref, analysisKey, analysisName, zippedSarif, wo
|
||||
}
|
||||
return payloadObj;
|
||||
}
|
||||
async function postProcessSarifFiles(logger2, features, checkoutPath, sarifPaths, category, analysis) {
|
||||
async function postProcessSarifFiles(logger2, features, config, checkoutPath, sarifPaths, category, analysis) {
|
||||
logger2.info(`Post-processing sarif files: ${JSON.stringify(sarifPaths)}`);
|
||||
const gitHubVersion = await getGitHubVersion();
|
||||
let sarifLog;
|
||||
@@ -180284,7 +180293,8 @@ async function postProcessSarifFiles(logger2, features, checkoutPath, sarifPaths
|
||||
sarifPaths,
|
||||
gitHubVersion,
|
||||
features,
|
||||
logger2
|
||||
logger2,
|
||||
config
|
||||
);
|
||||
} else {
|
||||
const sarifPath = sarifPaths[0];
|
||||
@@ -180317,13 +180327,14 @@ async function writePostProcessedFiles(logger2, pathInput, uploadTarget, postPro
|
||||
logger2.debug(`Not writing post-processed SARIF files.`);
|
||||
}
|
||||
}
|
||||
async function uploadFiles(inputSarifPath, checkoutPath, category, features, logger2, uploadTarget) {
|
||||
async function uploadFiles(config, inputSarifPath, checkoutPath, category, features, logger2, uploadTarget) {
|
||||
const sarifPaths = getSarifFilePaths(
|
||||
inputSarifPath,
|
||||
uploadTarget.sarifPredicate
|
||||
);
|
||||
return uploadSpecifiedFiles(
|
||||
sarifPaths,
|
||||
config,
|
||||
checkoutPath,
|
||||
category,
|
||||
features,
|
||||
@@ -180331,10 +180342,11 @@ async function uploadFiles(inputSarifPath, checkoutPath, category, features, log
|
||||
uploadTarget
|
||||
);
|
||||
}
|
||||
async function uploadSpecifiedFiles(sarifPaths, checkoutPath, category, features, logger2, uploadTarget) {
|
||||
async function uploadSpecifiedFiles(sarifPaths, config, checkoutPath, category, features, logger2, uploadTarget) {
|
||||
const processingResults = await postProcessSarifFiles(
|
||||
logger2,
|
||||
features,
|
||||
config,
|
||||
checkoutPath,
|
||||
sarifPaths,
|
||||
category,
|
||||
@@ -180419,6 +180431,12 @@ async function waitForProcessing(repositoryNwo, sarifID, logger2, options = {
|
||||
}) {
|
||||
logger2.startGroup("Waiting for processing to finish");
|
||||
try {
|
||||
if (shouldSkipSarifUpload()) {
|
||||
logger2.info(
|
||||
"Nothing to wait for, because SARIF upload is disabled by an environment variable and the SARIF was saved to disk."
|
||||
);
|
||||
return;
|
||||
}
|
||||
const client = getApiClient();
|
||||
let statusCheckBackoff = STATUS_CHECK_INITIAL_BACKOFF_MILLISECONDS;
|
||||
if (process.env["NODE_ENV"] !== "test") {
|
||||
@@ -180564,7 +180582,7 @@ function filterAlertsByDiffRange(logger2, sarifLog) {
|
||||
}
|
||||
|
||||
// src/upload-sarif.ts
|
||||
async function postProcessAndUploadSarif(logger2, features, uploadKind, checkoutPath, sarifPath, category, postProcessedOutputPath) {
|
||||
async function postProcessAndUploadSarif(logger2, features, config, uploadKind, checkoutPath, sarifPath, category, postProcessedOutputPath) {
|
||||
const sarifGroups = await getGroupedSarifFilePaths(
|
||||
logger2,
|
||||
sarifPath
|
||||
@@ -180577,6 +180595,7 @@ async function postProcessAndUploadSarif(logger2, features, uploadKind, checkout
|
||||
const postProcessingResults = await postProcessSarifFiles(
|
||||
logger2,
|
||||
features,
|
||||
config,
|
||||
checkoutPath,
|
||||
sarifFiles,
|
||||
category,
|
||||
@@ -180807,6 +180826,7 @@ async function run(action) {
|
||||
uploadResults = await postProcessAndUploadSarif(
|
||||
logger2,
|
||||
features,
|
||||
config,
|
||||
uploadKind,
|
||||
checkoutPath,
|
||||
outputDir,
|
||||
@@ -180859,8 +180879,10 @@ async function run(action) {
|
||||
logger2
|
||||
);
|
||||
}
|
||||
if (isInTestMode()) {
|
||||
logger2.debug("In test mode. Waiting for processing is disabled.");
|
||||
if (shouldSkipSarifUpload()) {
|
||||
logger2.debug(
|
||||
"SARIF upload is disabled and nothing was uploaded, so we do not need to wait for processing to finish."
|
||||
);
|
||||
} else if (uploadResults?.["code-scanning" /* CodeScanning */] !== void 0 && getRequiredInput("wait-for-processing") === "true") {
|
||||
await waitForProcessing(
|
||||
getRepositoryNwo(),
|
||||
@@ -186404,8 +186426,7 @@ async function run3(actionState) {
|
||||
apiDetails = {
|
||||
auth: getRequiredInput("token"),
|
||||
externalRepoAuth: getOptionalInput("external-repository-token"),
|
||||
url: actionState.env.getRequired("GITHUB_SERVER_URL" /* GITHUB_SERVER_URL */),
|
||||
apiURL: actionState.env.getRequired("GITHUB_API_URL" /* GITHUB_API_URL */)
|
||||
...getCurrentInstance(actionState.env)
|
||||
};
|
||||
const gitHubVersion = await getGitHubVersion();
|
||||
checkGitHubVersionInRange(gitHubVersion, logger2);
|
||||
@@ -186854,7 +186875,7 @@ async function prepareFailedSarif(logger2, features, config) {
|
||||
}
|
||||
const jobName = getRequiredEnvParam("GITHUB_JOB");
|
||||
const matrix = parseMatrixInput(getRequiredInput("matrix"));
|
||||
if (shouldSkipSarifUpload()) {
|
||||
if (shouldSkipGenerateFailedSarif()) {
|
||||
return new Failure({
|
||||
upload_failed_run_skipped_because: "SARIF upload is disabled"
|
||||
});
|
||||
@@ -186925,6 +186946,7 @@ async function maybeUploadFailedSarif(config, repositoryNwo, features, logger2)
|
||||
const failedSarif = failedSarifResult.value;
|
||||
logger2.info(`Uploading failed SARIF file ${failedSarif.sarifFile}`);
|
||||
const uploadResult = await uploadFiles(
|
||||
config,
|
||||
failedSarif.sarifFile,
|
||||
failedSarif.checkoutPath,
|
||||
failedSarif.category,
|
||||
@@ -186938,6 +186960,9 @@ async function maybeUploadFailedSarif(config, repositoryNwo, features, logger2)
|
||||
logger2,
|
||||
{ isUnsuccessfulExecution: true }
|
||||
);
|
||||
if (uploadResult.sarifID && shouldSkipSarifUpload()) {
|
||||
await uploadSarifArtifact(logger2, "test-mode-sarif", failedSarif);
|
||||
}
|
||||
return uploadResult ? { ...uploadResult.statusReport, sarifID: uploadResult.sarifID } : {};
|
||||
}
|
||||
async function maybeUploadFailedSarifArtifact(config, features, logger2) {
|
||||
@@ -186946,19 +186971,25 @@ async function maybeUploadFailedSarifArtifact(config, features, logger2) {
|
||||
return failedSarifResult.value;
|
||||
}
|
||||
const failedSarif = failedSarifResult.value;
|
||||
logger2.info(
|
||||
`Uploading failed SARIF file ${failedSarif.sarifFile} as artifact`
|
||||
);
|
||||
const gitHubVersion = await getGitHubVersion();
|
||||
const client = await getArtifactUploaderClient(logger2, gitHubVersion.type);
|
||||
const suffix = getArtifactSuffix(getOptionalInput("matrix"));
|
||||
const name = sanitizeArtifactName(`sarif-artifact-${suffix}`);
|
||||
await client.uploadArtifact(
|
||||
name,
|
||||
[import_path8.default.normalize(failedSarif.sarifFile)],
|
||||
import_path8.default.normalize("..")
|
||||
);
|
||||
return { sarifID: name };
|
||||
return uploadSarifArtifact(logger2, "sarif-artifact", failedSarif);
|
||||
}
|
||||
async function uploadSarifArtifact(logger2, prefix, failedSarif) {
|
||||
logger2.startGroup(`Uploading failed SARIF file as artifact`);
|
||||
logger2.info(`Uploading '${failedSarif.sarifFile}' as artifact`);
|
||||
try {
|
||||
const gitHubVersion = await getGitHubVersion();
|
||||
const client = await getArtifactUploaderClient(logger2, gitHubVersion.type);
|
||||
const suffix = getArtifactSuffix(getOptionalInput("matrix"));
|
||||
const name = sanitizeArtifactName(`${prefix}-${suffix}`);
|
||||
await client.uploadArtifact(
|
||||
name,
|
||||
[import_path8.default.normalize(failedSarif.sarifFile)],
|
||||
import_path8.default.normalize(import_path8.default.dirname(failedSarif.sarifFile))
|
||||
);
|
||||
return { sarifID: name };
|
||||
} finally {
|
||||
logger2.endGroup();
|
||||
}
|
||||
}
|
||||
async function tryUploadSarifIfRunFailed(config, repositoryNwo, features, logger2) {
|
||||
if (process.env["CODEQL_ACTION_ANALYZE_DID_COMPLETE_SUCCESSFULLY" /* ANALYZE_DID_COMPLETE_SUCCESSFULLY */] === "true") {
|
||||
@@ -187183,6 +187214,9 @@ async function run4(action) {
|
||||
const tmpDir = getTemporaryDirectory();
|
||||
config = await getConfig(tmpDir, logger2);
|
||||
if (config === void 0) {
|
||||
logger2.debug(
|
||||
"Will try to load the configuration file from an aborted 'init' step, since there is no regular one."
|
||||
);
|
||||
config = getAbortedInitConfig(action, tmpDir);
|
||||
}
|
||||
const gitHubVersion = config?.gitHubVersion ?? await getGitHubVersion();
|
||||
@@ -187447,8 +187481,7 @@ async function run6(actionState) {
|
||||
const apiDetails = {
|
||||
auth: getRequiredInput("token"),
|
||||
externalRepoAuth: getOptionalInput("external-repository-token"),
|
||||
url: actionState.env.getRequired("GITHUB_SERVER_URL" /* GITHUB_SERVER_URL */),
|
||||
apiURL: actionState.env.getRequired("GITHUB_API_URL" /* GITHUB_API_URL */)
|
||||
...getCurrentInstance(actionState.env)
|
||||
};
|
||||
const gitHubVersion = await getGitHubVersion();
|
||||
checkGitHubVersionInRange(gitHubVersion, logger2);
|
||||
@@ -187556,11 +187589,73 @@ var core25 = __toESM(require_core());
|
||||
|
||||
// src/start-proxy.ts
|
||||
var path27 = __toESM(require("path"));
|
||||
var core24 = __toESM(require_core());
|
||||
var toolcache4 = __toESM(require_tool_cache());
|
||||
|
||||
// src/start-proxy/validation.ts
|
||||
// src/start-proxy/status-report.ts
|
||||
var core23 = __toESM(require_core());
|
||||
async function sendSuccessStatusReport(startedAt, config, registry_types, status, logger2) {
|
||||
const statusReportBase = await createStatusReportBase(
|
||||
"start-proxy" /* StartProxy */,
|
||||
"success",
|
||||
startedAt,
|
||||
config,
|
||||
await checkDiskUsage(logger2),
|
||||
logger2
|
||||
);
|
||||
if (statusReportBase !== void 0) {
|
||||
const statusReport = {
|
||||
...statusReportBase,
|
||||
registry_types: registry_types.sort().join(","),
|
||||
start_proxy: status
|
||||
};
|
||||
await sendStatusReport(statusReport);
|
||||
}
|
||||
}
|
||||
function getStartProxyErrorMessage(errorType) {
|
||||
switch (errorType) {
|
||||
case 0 /* DownloadFailed */:
|
||||
return "Failed to download proxy archive.";
|
||||
case 1 /* ExtractionFailed */:
|
||||
return "Failed to extract proxy archive.";
|
||||
case 2 /* CacheFailed */:
|
||||
return "Failed to add proxy to toolcache";
|
||||
}
|
||||
}
|
||||
var StartProxyError = class extends Error {
|
||||
errorType;
|
||||
constructor(errorType) {
|
||||
super();
|
||||
this.errorType = errorType;
|
||||
}
|
||||
};
|
||||
function getSafeErrorMessage(error3) {
|
||||
if (error3 instanceof StartProxyError) {
|
||||
return getStartProxyErrorMessage(error3.errorType);
|
||||
}
|
||||
return `Error from start-proxy Action omitted (${error3.constructor.name}).`;
|
||||
}
|
||||
async function sendFailedStatusReport(logger2, startedAt, language, unwrappedError) {
|
||||
const error3 = wrapError(unwrappedError);
|
||||
core23.setFailed(`start-proxy action failed: ${error3.message}`);
|
||||
const statusReportMessage = getSafeErrorMessage(error3);
|
||||
const errorStatusReportBase = await createStatusReportBase(
|
||||
"start-proxy" /* StartProxy */,
|
||||
getActionsStatus(error3),
|
||||
startedAt,
|
||||
{
|
||||
languages: language === void 0 ? void 0 : [language]
|
||||
},
|
||||
await checkDiskUsage(logger2),
|
||||
logger2,
|
||||
statusReportMessage
|
||||
);
|
||||
if (errorStatusReportBase !== void 0) {
|
||||
await sendStatusReport(errorStatusReportBase);
|
||||
}
|
||||
}
|
||||
|
||||
// src/start-proxy/validation.ts
|
||||
var core24 = __toESM(require_core());
|
||||
function cloneCredential(schema, obj) {
|
||||
const result = {};
|
||||
for (const key of Object.keys(schema)) {
|
||||
@@ -187579,14 +187674,14 @@ function getAuthConfig(config) {
|
||||
}
|
||||
if (isToken(config)) {
|
||||
if (isDefined2(config.token)) {
|
||||
core23.setSecret(config.token);
|
||||
core24.setSecret(config.token);
|
||||
}
|
||||
return cloneCredential(tokenSchema, config);
|
||||
} else {
|
||||
let username = void 0;
|
||||
let password = void 0;
|
||||
if ("password" in config && isString(config.password)) {
|
||||
core23.setSecret(config.password);
|
||||
core24.setSecret(config.password);
|
||||
password = config.password;
|
||||
}
|
||||
if ("username" in config && isString(config.username)) {
|
||||
@@ -187600,65 +187695,6 @@ function getAuthConfig(config) {
|
||||
}
|
||||
|
||||
// src/start-proxy.ts
|
||||
function getStartProxyErrorMessage(errorType) {
|
||||
switch (errorType) {
|
||||
case 0 /* DownloadFailed */:
|
||||
return "Failed to download proxy archive.";
|
||||
case 1 /* ExtractionFailed */:
|
||||
return "Failed to extract proxy archive.";
|
||||
case 2 /* CacheFailed */:
|
||||
return "Failed to add proxy to toolcache";
|
||||
}
|
||||
}
|
||||
var StartProxyError = class extends Error {
|
||||
errorType;
|
||||
constructor(errorType) {
|
||||
super();
|
||||
this.errorType = errorType;
|
||||
}
|
||||
};
|
||||
async function sendSuccessStatusReport(startedAt, config, registry_types, logger2) {
|
||||
const statusReportBase = await createStatusReportBase(
|
||||
"start-proxy" /* StartProxy */,
|
||||
"success",
|
||||
startedAt,
|
||||
config,
|
||||
await checkDiskUsage(logger2),
|
||||
logger2
|
||||
);
|
||||
if (statusReportBase !== void 0) {
|
||||
const statusReport = {
|
||||
...statusReportBase,
|
||||
registry_types: registry_types.join(",")
|
||||
};
|
||||
await sendStatusReport(statusReport);
|
||||
}
|
||||
}
|
||||
function getSafeErrorMessage(error3) {
|
||||
if (error3 instanceof StartProxyError) {
|
||||
return getStartProxyErrorMessage(error3.errorType);
|
||||
}
|
||||
return `Error from start-proxy Action omitted (${error3.constructor.name}).`;
|
||||
}
|
||||
async function sendFailedStatusReport(logger2, startedAt, language, unwrappedError) {
|
||||
const error3 = wrapError(unwrappedError);
|
||||
core24.setFailed(`start-proxy action failed: ${error3.message}`);
|
||||
const statusReportMessage = getSafeErrorMessage(error3);
|
||||
const errorStatusReportBase = await createStatusReportBase(
|
||||
"start-proxy" /* StartProxy */,
|
||||
getActionsStatus(error3),
|
||||
startedAt,
|
||||
{
|
||||
languages: language === void 0 ? void 0 : [language]
|
||||
},
|
||||
await checkDiskUsage(logger2),
|
||||
logger2,
|
||||
statusReportMessage
|
||||
);
|
||||
if (errorStatusReportBase !== void 0) {
|
||||
await sendStatusReport(errorStatusReportBase);
|
||||
}
|
||||
}
|
||||
var UPDATEJOB_PROXY = "update-job-proxy";
|
||||
function isPAT(value) {
|
||||
return isAuthToken(value, [
|
||||
@@ -187889,7 +187925,10 @@ async function getProxyBinaryPath(action, apiDetails) {
|
||||
);
|
||||
logger2.info(`Extracted the authentication proxy to '${proxyDir}'.`);
|
||||
}
|
||||
return path27.join(proxyDir, proxyFileName);
|
||||
return {
|
||||
path: path27.join(proxyDir, proxyFileName),
|
||||
version: proxyInfo.version
|
||||
};
|
||||
} finally {
|
||||
logger2.endGroup();
|
||||
}
|
||||
@@ -188260,7 +188299,7 @@ async function run7(action) {
|
||||
getApiDetails(action.env)
|
||||
);
|
||||
const proxyInfo = await startProxy(
|
||||
proxyBin,
|
||||
proxyBin.path,
|
||||
proxyConfig,
|
||||
proxyLogFilePath,
|
||||
logger2
|
||||
@@ -188272,6 +188311,7 @@ async function run7(action) {
|
||||
languages: language === void 0 ? void 0 : [language]
|
||||
},
|
||||
proxyConfig.all_credentials.map((c) => c.type),
|
||||
{ proxy_version: proxyBin.version },
|
||||
logger2
|
||||
);
|
||||
} catch (unwrappedError) {
|
||||
@@ -188395,11 +188435,12 @@ async function run8({ startedAt, logger: logger2 }) {
|
||||
const gitHubVersion = await getGitHubVersion();
|
||||
checkActionVersion(getActionVersion(), gitHubVersion);
|
||||
persistInputs();
|
||||
const tempDir = getTemporaryDirectory();
|
||||
const repositoryNwo = getRepositoryNwo();
|
||||
const features = initFeatures(
|
||||
gitHubVersion,
|
||||
repositoryNwo,
|
||||
getTemporaryDirectory(),
|
||||
tempDir,
|
||||
logger2
|
||||
);
|
||||
const startingStatusReportBase = await createStatusReportBase(
|
||||
@@ -188416,9 +188457,11 @@ async function run8({ startedAt, logger: logger2 }) {
|
||||
const sarifPath = getRequiredInput("sarif_file");
|
||||
const checkoutPath = getRequiredInput("checkout_path");
|
||||
const category = getOptionalInput("category");
|
||||
const maybeConfig = await getConfig(tempDir, logger2);
|
||||
const uploadResults = await postProcessAndUploadSarif(
|
||||
logger2,
|
||||
features,
|
||||
maybeConfig,
|
||||
"always",
|
||||
checkoutPath,
|
||||
sarifPath,
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "codeql",
|
||||
"version": "4.39.0",
|
||||
"version": "4.39.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "codeql",
|
||||
"version": "4.39.0",
|
||||
"version": "4.39.1",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"pr-checks"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "codeql",
|
||||
"version": "4.39.0",
|
||||
"version": "4.39.1",
|
||||
"private": true,
|
||||
"description": "CodeQL action",
|
||||
"scripts": {
|
||||
|
||||
77
pr-checks/check-diagnostics.ts
Normal file
77
pr-checks/check-diagnostics.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import fs from "fs";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import * as sarif from "../src/sarif";
|
||||
|
||||
function checkStatusPageNotification(n) {
|
||||
const expectedMessage = "Plaintext message";
|
||||
if (n.message.text !== expectedMessage) {
|
||||
core.setFailed(
|
||||
`Expected the status page diagnostic to have the message '${expectedMessage}', but found '${n.message.text}'.`,
|
||||
);
|
||||
}
|
||||
if (n.locations.length !== 1) {
|
||||
core.setFailed(
|
||||
`Expected the status page diagnostic to have exactly 1 location, but found ${n.locations.length}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const sarifPath = process.env["SARIF_PATH"];
|
||||
|
||||
if (sarifPath === undefined) {
|
||||
throw new Error("The path to a SARIF file must be set in 'SARIF_PATH'.");
|
||||
}
|
||||
|
||||
const log = JSON.parse(fs.readFileSync(sarifPath, "utf8")) as sarif.Log;
|
||||
const run = log.runs[0];
|
||||
|
||||
if (run.invocations === undefined) {
|
||||
throw new Error("run.invocations is undefined");
|
||||
}
|
||||
|
||||
const toolExecutionNotifications =
|
||||
run.invocations[0].toolExecutionNotifications;
|
||||
|
||||
if (toolExecutionNotifications === undefined) {
|
||||
throw new Error("toolExecutionNotifications is undefined");
|
||||
}
|
||||
|
||||
const statusPageNotifications = toolExecutionNotifications.filter(
|
||||
(n) =>
|
||||
n.descriptor?.id === "lang/diagnostics/example" &&
|
||||
n.properties?.visibility?.statusPage,
|
||||
);
|
||||
if (statusPageNotifications.length !== 1) {
|
||||
core.setFailed(
|
||||
"Expected exactly one status page reporting descriptor for this diagnostic in the " +
|
||||
`'runs[].invocations[].toolExecutionNotifications[]' SARIF property, but found ` +
|
||||
`${statusPageNotifications.length}. All notification reporting descriptors: ` +
|
||||
`${JSON.stringify(toolExecutionNotifications)}.`,
|
||||
);
|
||||
}
|
||||
checkStatusPageNotification(statusPageNotifications[0]);
|
||||
|
||||
const notifications = run.tool.driver.notifications;
|
||||
|
||||
if (notifications === undefined) {
|
||||
throw new Error("notifications is undefined");
|
||||
}
|
||||
|
||||
const diagnosticNotification = notifications.filter(
|
||||
(n) =>
|
||||
n.id === "lang/diagnostics/example" &&
|
||||
n.name === "lang/diagnostics/example" &&
|
||||
n.fullDescription?.text === "Diagnostic name",
|
||||
);
|
||||
if (diagnosticNotification.length !== 1) {
|
||||
core.setFailed(
|
||||
"Expected exactly one notification for this diagnostic in the " +
|
||||
`'runs[].tool.driver.notifications[]' SARIF property, but found ` +
|
||||
`${diagnosticNotification.length}. All notifications: ` +
|
||||
`${JSON.stringify(notifications)}.`,
|
||||
);
|
||||
}
|
||||
|
||||
core.info("Finished diagnostic export test");
|
||||
@@ -46,7 +46,7 @@ steps:
|
||||
post-processed-sarif-path: "${{ runner.temp }}/post-processed"
|
||||
|
||||
- name: Upload SARIF files
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: |
|
||||
analysis-kinds-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}
|
||||
@@ -54,7 +54,7 @@ steps:
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload post-processed SARIF
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: |
|
||||
post-processed-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}
|
||||
|
||||
@@ -14,7 +14,7 @@ steps:
|
||||
output: "${{ runner.temp }}/results"
|
||||
upload-database: false
|
||||
- name: Upload SARIF
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: config-export-${{ matrix.os }}-${{ matrix.version }}.sarif.json
|
||||
path: "${{ runner.temp }}/results/javascript.sarif"
|
||||
|
||||
@@ -5,6 +5,7 @@ versions:
|
||||
- nightly-latest
|
||||
env:
|
||||
CODEQL_ACTION_EXPORT_DIAGNOSTICS: true
|
||||
installNode: true
|
||||
steps:
|
||||
- uses: ./../action/init
|
||||
id: init
|
||||
@@ -27,58 +28,15 @@ steps:
|
||||
output: "${{ runner.temp }}/results"
|
||||
upload-database: false
|
||||
- name: Upload SARIF
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: diagnostics-export-${{ matrix.os }}-${{ matrix.version }}.sarif.json
|
||||
path: "${{ runner.temp }}/results/javascript.sarif"
|
||||
retention-days: 7
|
||||
|
||||
- name: Check diagnostics appear in SARIF
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
env:
|
||||
SARIF_PATH: "${{ runner.temp }}/results/javascript.sarif"
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
function checkStatusPageNotification(n) {
|
||||
const expectedMessage = 'Plaintext message';
|
||||
if (n.message.text !== expectedMessage) {
|
||||
core.setFailed(`Expected the status page diagnostic to have the message '${expectedMessage}', but found '${n.message.text}'.`);
|
||||
}
|
||||
if (n.locations.length !== 1) {
|
||||
core.setFailed(`Expected the status page diagnostic to have exactly 1 location, but found ${n.locations.length}.`);
|
||||
}
|
||||
}
|
||||
|
||||
const sarif = JSON.parse(fs.readFileSync(process.env['SARIF_PATH'], 'utf8'));
|
||||
const run = sarif.runs[0];
|
||||
|
||||
const toolExecutionNotifications = run.invocations[0].toolExecutionNotifications;
|
||||
const statusPageNotifications = toolExecutionNotifications.filter(n =>
|
||||
n.descriptor.id === 'lang/diagnostics/example' && n.properties?.visibility?.statusPage
|
||||
);
|
||||
if (statusPageNotifications.length !== 1) {
|
||||
core.setFailed(
|
||||
'Expected exactly one status page reporting descriptor for this diagnostic in the ' +
|
||||
`'runs[].invocations[].toolExecutionNotifications[]' SARIF property, but found ` +
|
||||
`${statusPageNotifications.length}. All notification reporting descriptors: ` +
|
||||
`${JSON.stringify(toolExecutionNotifications)}.`
|
||||
);
|
||||
}
|
||||
checkStatusPageNotification(statusPageNotifications[0]);
|
||||
|
||||
const notifications = run.tool.driver.notifications;
|
||||
const diagnosticNotification = notifications.filter(n =>
|
||||
n.id === 'lang/diagnostics/example' && n.name === 'lang/diagnostics/example' &&
|
||||
n.fullDescription.text === 'Diagnostic name'
|
||||
);
|
||||
if (diagnosticNotification.length !== 1) {
|
||||
core.setFailed(
|
||||
'Expected exactly one notification for this diagnostic in the ' +
|
||||
`'runs[].tool.driver.notifications[]' SARIF property, but found ` +
|
||||
`${diagnosticNotification.length}. All notifications: ` +
|
||||
`${JSON.stringify(notifications)}.`
|
||||
);
|
||||
}
|
||||
|
||||
core.info('Finished diagnostic export test');
|
||||
run: |
|
||||
cd ../action
|
||||
npx tsx ./pr-checks/check-diagnostics.ts
|
||||
|
||||
@@ -27,7 +27,7 @@ steps:
|
||||
with:
|
||||
output: "${{ runner.temp }}/results"
|
||||
- name: Upload SARIF
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: with-baseline-information-${{ matrix.os }}-${{ matrix.version }}.sarif.json
|
||||
path: "${{ runner.temp }}/results/javascript.sarif"
|
||||
|
||||
@@ -12,7 +12,7 @@ steps:
|
||||
with:
|
||||
output: "${{ runner.temp }}/results"
|
||||
- name: Upload SARIF
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: ${{ matrix.os }}-${{ matrix.version }}.sarif.json
|
||||
path: "${{ runner.temp }}/results/javascript.sarif"
|
||||
|
||||
@@ -14,7 +14,6 @@ operatingSystems:
|
||||
- stable-v2.21.4
|
||||
- stable-v2.22.4
|
||||
env:
|
||||
CODEQL_ACTION_CLEANUP_TOOLCACHE_BUNDLES: true
|
||||
CODEQL_ACTION_RESOLVE_SUPPORTED_LANGUAGES_USING_CLI: true
|
||||
installGo: true
|
||||
installDotNet: true
|
||||
|
||||
@@ -215,8 +215,8 @@ const languageSetups: LanguageSetups = {
|
||||
name: "Install Node.js",
|
||||
uses: pinnedUses(
|
||||
"actions/setup-node",
|
||||
"820762786026740c76f36085b0efc47a31fe5020",
|
||||
"v7.0.0",
|
||||
"949feb2413d6458794dcd2491c4babbbce0c15c1",
|
||||
"v7.1.0",
|
||||
),
|
||||
with: {
|
||||
"node-version": defaultLanguageVersions.javascript,
|
||||
|
||||
@@ -361,6 +361,7 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
|
||||
uploadResults = await postProcessAndUploadSarif(
|
||||
logger,
|
||||
features,
|
||||
config,
|
||||
uploadKind,
|
||||
checkoutPath,
|
||||
outputDir,
|
||||
@@ -433,8 +434,10 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
|
||||
}
|
||||
|
||||
// We don't upload results in test mode, so don't wait for processing
|
||||
if (util.isInTestMode()) {
|
||||
logger.debug("In test mode. Waiting for processing is disabled.");
|
||||
if (util.shouldSkipSarifUpload()) {
|
||||
logger.debug(
|
||||
"SARIF upload is disabled and nothing was uploaded, so we do not need to wait for processing to finish.",
|
||||
);
|
||||
} else if (
|
||||
uploadResults?.[analyses.AnalysisKind.CodeScanning] !== undefined &&
|
||||
actionsUtil.getRequiredInput("wait-for-processing") === "true"
|
||||
|
||||
@@ -8,7 +8,14 @@ import * as actionsUtil from "./actions-util";
|
||||
import * as api from "./api-client";
|
||||
import { DO_NOT_RETRY_STATUSES } from "./api-client";
|
||||
import { ActionsEnvVars, RegistryProxyVars } from "./environment";
|
||||
import { callee, getTestEnv, setupTests } from "./testing-utils";
|
||||
import { GITHUB_DOTCOM_API_URL, GITHUB_DOTCOM_URL } from "./github";
|
||||
import {
|
||||
callee,
|
||||
getTestEnv,
|
||||
RecordingLogger,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
setupTests,
|
||||
} from "./testing-utils";
|
||||
import * as util from "./util";
|
||||
|
||||
setupTests(test);
|
||||
@@ -284,3 +291,87 @@ test("makeProxyRequestOptions - returns fetch with custom proxy", async (t) => {
|
||||
githubUtils.defaults.request,
|
||||
);
|
||||
});
|
||||
|
||||
test.serial(
|
||||
"getApiClientWithExternalAuth constructs clients with or without tokens",
|
||||
(t) => {
|
||||
t.notThrows(() => {
|
||||
api.getApiClientWithExternalAuth(SAMPLE_DOTCOM_API_DETAILS);
|
||||
api.getApiClientWithExternalAuth({
|
||||
...SAMPLE_DOTCOM_API_DETAILS,
|
||||
auth: undefined,
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("getAuthorizationHeaderFor - returns undefined if there's no token", (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
t.is(
|
||||
api.getAuthorizationHeaderFor(
|
||||
logger,
|
||||
{ ...SAMPLE_DOTCOM_API_DETAILS, auth: undefined },
|
||||
`${GITHUB_DOTCOM_URL}/github`,
|
||||
),
|
||||
undefined,
|
||||
);
|
||||
t.is(
|
||||
api.getAuthorizationHeaderFor(
|
||||
logger,
|
||||
{ ...SAMPLE_DOTCOM_API_DETAILS, auth: "" },
|
||||
`${GITHUB_DOTCOM_URL}/github`,
|
||||
),
|
||||
undefined,
|
||||
);
|
||||
t.is(
|
||||
api.getAuthorizationHeaderFor(
|
||||
logger,
|
||||
{ ...SAMPLE_DOTCOM_API_DETAILS, auth: " " },
|
||||
`${GITHUB_DOTCOM_URL}/github`,
|
||||
),
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
test("getAuthorizationHeaderFor - returns header value with token if url is allowed", (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
t.is(
|
||||
api.getAuthorizationHeaderFor(
|
||||
logger,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
`${GITHUB_DOTCOM_URL}/github`,
|
||||
),
|
||||
`token ${SAMPLE_DOTCOM_API_DETAILS.auth}`,
|
||||
);
|
||||
t.is(
|
||||
api.getAuthorizationHeaderFor(
|
||||
logger,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
`${GITHUB_DOTCOM_API_URL}/github`,
|
||||
),
|
||||
`token ${SAMPLE_DOTCOM_API_DETAILS.auth}`,
|
||||
);
|
||||
});
|
||||
|
||||
test("getAuthorizationHeaderFor - returns undefined if url is not allowed", (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
t.is(
|
||||
api.getAuthorizationHeaderFor(
|
||||
logger,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
`https://localhost/github`,
|
||||
),
|
||||
undefined,
|
||||
);
|
||||
t.is(
|
||||
api.getAuthorizationHeaderFor(
|
||||
logger,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
`https://api.localhost/github`,
|
||||
),
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as core from "@actions/core";
|
||||
import * as githubUtils from "@actions/github/lib/utils";
|
||||
import type { OctokitOptions } from "@octokit/core";
|
||||
import * as retry from "@octokit/plugin-retry";
|
||||
import { RequestRequestOptions } from "@octokit/types";
|
||||
import {
|
||||
@@ -12,26 +13,32 @@ import {
|
||||
import type { ActionState } from "./action-common";
|
||||
import { getActionVersion, getRequiredInput } from "./actions-util";
|
||||
import {
|
||||
ActionsEnvVars,
|
||||
EnvVar,
|
||||
ReadOnlyEnv,
|
||||
RegistryProxyVars,
|
||||
getEnv,
|
||||
exportEnvVar,
|
||||
} from "./environment";
|
||||
import {
|
||||
getCurrentInstance,
|
||||
GITHUB_DOTCOM_URL,
|
||||
parseGitHubUrl,
|
||||
type GitHubApiCombinedDetails,
|
||||
type GitHubApiDetails,
|
||||
} from "./github";
|
||||
import { Logger } from "./logging";
|
||||
import { getRepositoryNwo, RepositoryNwo } from "./repository";
|
||||
import {
|
||||
asHTTPError,
|
||||
ConfigurationError,
|
||||
getRequiredEnvParam,
|
||||
GITHUB_DOTCOM_URL,
|
||||
GitHubVariant,
|
||||
GitHubVersion,
|
||||
parseGitHubUrl,
|
||||
parseMatrixInput,
|
||||
} from "./util";
|
||||
|
||||
export type { GitHubApiDetails, GitHubApiCombinedDetails } from "./github";
|
||||
|
||||
const GITHUB_ENTERPRISE_VERSION_HEADER = "x-github-enterprise-version";
|
||||
|
||||
/**
|
||||
@@ -43,21 +50,6 @@ const GITHUB_ENTERPRISE_VERSION_HEADER = "x-github-enterprise-version";
|
||||
*/
|
||||
export const DO_NOT_RETRY_STATUSES = [400, 410, 422, 451];
|
||||
|
||||
export type GitHubApiCombinedDetails = GitHubApiDetails &
|
||||
GitHubApiExternalRepoDetails;
|
||||
|
||||
export interface GitHubApiDetails {
|
||||
auth: string;
|
||||
url: string;
|
||||
apiURL: string | undefined;
|
||||
}
|
||||
|
||||
export interface GitHubApiExternalRepoDetails {
|
||||
externalRepoAuth?: string;
|
||||
url: string;
|
||||
apiURL: string | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configuration for the private registry authentication proxy,
|
||||
* if it is available in the environment.
|
||||
@@ -139,37 +131,60 @@ function createApiClientWithDetails(
|
||||
{ allowExternal = false, proxy = undefined }: CreateApiClientOptions = {},
|
||||
): ApiClient {
|
||||
const auth =
|
||||
(allowExternal && apiDetails.externalRepoAuth) || apiDetails.auth;
|
||||
allowExternal && apiDetails.externalRepoAuth !== undefined
|
||||
? apiDetails.externalRepoAuth
|
||||
: apiDetails.auth;
|
||||
const retryingOctokit = githubUtils.GitHub.plugin(retry.retry);
|
||||
const requestOptions = makeProxyRequestOptions(proxy);
|
||||
return new retryingOctokit(
|
||||
githubUtils.getOctokitOptions(auth, {
|
||||
baseUrl: apiDetails.apiURL,
|
||||
userAgent: `CodeQL-Action/${getActionVersion()}`,
|
||||
log: {
|
||||
debug: core.debug,
|
||||
info: core.info,
|
||||
warn: core.warning,
|
||||
error: core.error,
|
||||
},
|
||||
request: requestOptions,
|
||||
retry: {
|
||||
doNotRetry: DO_NOT_RETRY_STATUSES,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const opts: OctokitOptions = {
|
||||
auth:
|
||||
auth !== undefined && auth.trim() !== "" ? `token ${auth}` : undefined,
|
||||
baseUrl: apiDetails.apiURL,
|
||||
userAgent: `CodeQL-Action/${getActionVersion()}`,
|
||||
log: {
|
||||
debug: core.debug,
|
||||
info: core.info,
|
||||
warn: core.warning,
|
||||
error: core.error,
|
||||
},
|
||||
request: requestOptions,
|
||||
retry: {
|
||||
doNotRetry: DO_NOT_RETRY_STATUSES,
|
||||
},
|
||||
};
|
||||
|
||||
return new retryingOctokit(opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a `GitHubApiDetails` object describing the GitHub instance we are currently running on.
|
||||
*
|
||||
* @param env The environment variables to read.
|
||||
*/
|
||||
export function getApiDetails(env: ReadOnlyEnv = getEnv()): GitHubApiDetails {
|
||||
return {
|
||||
auth: getRequiredInput("token"),
|
||||
url: env.getRequired(ActionsEnvVars.GITHUB_SERVER_URL),
|
||||
apiURL: env.getRequired(ActionsEnvVars.GITHUB_API_URL),
|
||||
...getCurrentInstance(env),
|
||||
};
|
||||
}
|
||||
|
||||
export function getApiClient(env: ReadOnlyEnv = getEnv()) {
|
||||
return createApiClientWithDetails(getApiDetails(env));
|
||||
/**
|
||||
* Constructs an {@link ApiClient} using {@link apiDetails}.
|
||||
*
|
||||
* If {@link apiDetails} is not specified, then the GitHub instance URLs and auth token
|
||||
* are taken from {@link env}.
|
||||
*
|
||||
* If {@link env} is not specified, then the process environment is used.
|
||||
*
|
||||
* @param env The environment to obtain the GitHub instance URLs from.
|
||||
* @param apiDetails The {@link GitHubApiDetails} to construct the {@link ApiClient} for.
|
||||
*/
|
||||
export function getApiClient(
|
||||
env: ReadOnlyEnv = getEnv(),
|
||||
apiDetails?: GitHubApiDetails,
|
||||
) {
|
||||
return createApiClientWithDetails(apiDetails ?? getApiDetails(env));
|
||||
}
|
||||
|
||||
export function getApiClientWithExternalAuth(
|
||||
@@ -194,6 +209,12 @@ export function getAuthorizationHeaderFor(
|
||||
apiDetails: GitHubApiDetails,
|
||||
url: string,
|
||||
): string | undefined {
|
||||
// We don't have to check the `url` if we don't have a token to begin with.
|
||||
const token = apiDetails.auth?.trim();
|
||||
if (token === undefined || token === "") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// We only want to provide an authorization header if we are downloading
|
||||
// from the same GitHub instance the Action is running on.
|
||||
// This avoids leaking Enterprise tokens to dotcom.
|
||||
@@ -202,7 +223,7 @@ export function getAuthorizationHeaderFor(
|
||||
(apiDetails.apiURL && url.startsWith(`${apiDetails.apiURL}/`))
|
||||
) {
|
||||
logger.debug(`Providing an authorization token.`);
|
||||
return `token ${apiDetails.auth}`;
|
||||
return `token ${token}`;
|
||||
}
|
||||
|
||||
logger.debug(`Not using an authorization token.`);
|
||||
|
||||
@@ -126,7 +126,7 @@ function mockReleaseApi({
|
||||
});
|
||||
}
|
||||
|
||||
function mockApiDetails(apiDetails: GitHubApiDetails) {
|
||||
function mockApiDetails(apiDetails: Required<GitHubApiDetails>) {
|
||||
// This is a workaround to mock `api.getApiDetails()` since it doesn't seem to be possible to
|
||||
// mock this directly. The difficulty is that `getApiDetails()` is called locally in
|
||||
// `api-client.ts`, but `sinon.stub(api, "getApiDetails")` only affects calls to
|
||||
|
||||
@@ -2126,7 +2126,7 @@ test.serial("getPrimaryAnalysisConfig - Code Scanning + Code Quality", (t) => {
|
||||
});
|
||||
|
||||
test.serial(
|
||||
"isTrapCachingEnabled: explicit input true is respected",
|
||||
"isTrapCachingEnabled: explicit true input overrides self-hosted default",
|
||||
async (t) => {
|
||||
return await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
@@ -2134,12 +2134,40 @@ test.serial(
|
||||
.stub(actionsUtil, "getOptionalInput")
|
||||
.withArgs("trap-caching")
|
||||
.returns("true");
|
||||
t.true(
|
||||
await configUtils.isTrapCachingEnabled(
|
||||
createFeatures([]),
|
||||
OverlayDatabaseMode.None,
|
||||
),
|
||||
);
|
||||
t.true(configUtils.isTrapCachingEnabled(OverlayDatabaseMode.None));
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"isTrapCachingEnabled: explicit true input overrides overlay defaults",
|
||||
async (t) => {
|
||||
return await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, path.join(tmpDir, "hostedtoolcache"));
|
||||
sinon
|
||||
.stub(actionsUtil, "getOptionalInput")
|
||||
.withArgs("trap-caching")
|
||||
.returns("true");
|
||||
for (const overlayDatabaseMode of [
|
||||
OverlayDatabaseMode.Overlay,
|
||||
OverlayDatabaseMode.OverlayBase,
|
||||
]) {
|
||||
t.true(configUtils.isTrapCachingEnabled(overlayDatabaseMode));
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"isTrapCachingEnabled: explicit false input overrides hosted default",
|
||||
async (t) => {
|
||||
return await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, path.join(tmpDir, "hostedtoolcache"));
|
||||
sinon
|
||||
.stub(actionsUtil, "getOptionalInput")
|
||||
.withArgs("trap-caching")
|
||||
.returns("false");
|
||||
t.false(configUtils.isTrapCachingEnabled(OverlayDatabaseMode.None));
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -2153,18 +2181,13 @@ test.serial(
|
||||
.stub(actionsUtil, "getOptionalInput")
|
||||
.withArgs("trap-caching")
|
||||
.returns(undefined);
|
||||
t.false(
|
||||
await configUtils.isTrapCachingEnabled(
|
||||
createFeatures([]),
|
||||
OverlayDatabaseMode.None,
|
||||
),
|
||||
);
|
||||
t.false(configUtils.isTrapCachingEnabled(OverlayDatabaseMode.None));
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"isTrapCachingEnabled: enabled on hosted runner by default",
|
||||
"isTrapCachingEnabled: enabled on hosted runner without overlay analysis",
|
||||
async (t) => {
|
||||
return await withTmpDir(async (tmpDir) => {
|
||||
const hostedToolCache = path.join(tmpDir, "hostedtoolcache");
|
||||
@@ -2173,18 +2196,13 @@ test.serial(
|
||||
.stub(actionsUtil, "getOptionalInput")
|
||||
.withArgs("trap-caching")
|
||||
.returns(undefined);
|
||||
t.true(
|
||||
await configUtils.isTrapCachingEnabled(
|
||||
createFeatures([]),
|
||||
OverlayDatabaseMode.None,
|
||||
),
|
||||
);
|
||||
t.true(configUtils.isTrapCachingEnabled(OverlayDatabaseMode.None));
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"isTrapCachingEnabled: enabled on hosted runner when overlay enabled but feature flag off",
|
||||
"isTrapCachingEnabled: disabled on hosted runner with overlay analysis",
|
||||
async (t) => {
|
||||
return await withTmpDir(async (tmpDir) => {
|
||||
const hostedToolCache = path.join(tmpDir, "hostedtoolcache");
|
||||
@@ -2193,52 +2211,12 @@ test.serial(
|
||||
.stub(actionsUtil, "getOptionalInput")
|
||||
.withArgs("trap-caching")
|
||||
.returns(undefined);
|
||||
t.true(
|
||||
await configUtils.isTrapCachingEnabled(
|
||||
createFeatures([]),
|
||||
OverlayDatabaseMode.Overlay,
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"isTrapCachingEnabled: disabled on hosted runner when overlay enabled and feature flag on",
|
||||
async (t) => {
|
||||
return await withTmpDir(async (tmpDir) => {
|
||||
const hostedToolCache = path.join(tmpDir, "hostedtoolcache");
|
||||
setupActionsVars(tmpDir, hostedToolCache);
|
||||
sinon
|
||||
.stub(actionsUtil, "getOptionalInput")
|
||||
.withArgs("trap-caching")
|
||||
.returns(undefined);
|
||||
t.false(
|
||||
await configUtils.isTrapCachingEnabled(
|
||||
createFeatures([Feature.OverlayAnalysisDisableTrapCaching]),
|
||||
OverlayDatabaseMode.Overlay,
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"isTrapCachingEnabled: enabled on hosted runner when overlay is None even with feature flag on",
|
||||
async (t) => {
|
||||
return await withTmpDir(async (tmpDir) => {
|
||||
const hostedToolCache = path.join(tmpDir, "hostedtoolcache");
|
||||
setupActionsVars(tmpDir, hostedToolCache);
|
||||
sinon
|
||||
.stub(actionsUtil, "getOptionalInput")
|
||||
.withArgs("trap-caching")
|
||||
.returns(undefined);
|
||||
t.true(
|
||||
await configUtils.isTrapCachingEnabled(
|
||||
createFeatures([Feature.OverlayAnalysisDisableTrapCaching]),
|
||||
OverlayDatabaseMode.None,
|
||||
),
|
||||
);
|
||||
for (const overlayDatabaseMode of [
|
||||
OverlayDatabaseMode.Overlay,
|
||||
OverlayDatabaseMode.OverlayBase,
|
||||
]) {
|
||||
t.false(configUtils.isTrapCachingEnabled(overlayDatabaseMode));
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -944,10 +944,14 @@ async function validateOverlayDatabaseMode(
|
||||
});
|
||||
}
|
||||
|
||||
export async function isTrapCachingEnabled(
|
||||
features: FeatureEnablement,
|
||||
/**
|
||||
* Determines whether TRAP caching is enabled for the current run.
|
||||
*
|
||||
* @returns Whether TRAP caches should be downloaded and uploaded.
|
||||
*/
|
||||
export function isTrapCachingEnabled(
|
||||
overlayDatabaseMode: OverlayDatabaseMode,
|
||||
): Promise<boolean> {
|
||||
): boolean {
|
||||
// If the workflow specified something, always respect that.
|
||||
const trapCaching = getOptionalInput("trap-caching");
|
||||
if (trapCaching !== undefined) return trapCaching === "true";
|
||||
@@ -955,12 +959,8 @@ export async function isTrapCachingEnabled(
|
||||
// On self-hosted runners which may have slow network access, disable TRAP caching by default.
|
||||
if (!looksLikeHostedRunner()) return false;
|
||||
|
||||
// If overlay analysis is enabled, then disable TRAP caching since overlay analysis supersedes it.
|
||||
// This change is gated behind a feature flag.
|
||||
if (
|
||||
overlayDatabaseMode !== OverlayDatabaseMode.None &&
|
||||
(await features.getValue(Feature.OverlayAnalysisDisableTrapCaching))
|
||||
) {
|
||||
// Overlay analysis supersedes TRAP caching, so disable TRAP caching by default when it is active.
|
||||
if (overlayDatabaseMode !== OverlayDatabaseMode.None) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1302,7 +1302,7 @@ export async function initConfig(
|
||||
logger,
|
||||
);
|
||||
|
||||
if (await isTrapCachingEnabled(features, config.overlayDatabaseMode)) {
|
||||
if (isTrapCachingEnabled(config.overlayDatabaseMode)) {
|
||||
const { trapCaches, trapCacheDownloadTime } = await downloadCacheWithTime(
|
||||
inputs.codeql,
|
||||
config.languages,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as fs from "fs";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
import * as github from "@actions/github";
|
||||
import test from "ava";
|
||||
@@ -10,7 +11,10 @@ import { GitHubApiDetails } from "./api-client";
|
||||
import * as apiClient from "./api-client";
|
||||
import { createStubCodeQL } from "./codeql";
|
||||
import { Config } from "./config-utils";
|
||||
import { cleanupAndUploadDatabases } from "./database-upload";
|
||||
import {
|
||||
cleanupAndUploadDatabases,
|
||||
uploadBundledDatabase,
|
||||
} from "./database-upload";
|
||||
import { Feature } from "./feature-flags";
|
||||
import * as gitUtils from "./git-utils";
|
||||
import { BuiltInLanguage } from "./languages";
|
||||
@@ -23,6 +27,7 @@ import {
|
||||
getTestEnv,
|
||||
initAllState,
|
||||
RecordingLogger,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
setupActionsVars,
|
||||
setupTests,
|
||||
} from "./testing-utils";
|
||||
@@ -550,3 +555,86 @@ test.serial(
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"uploadBundledDatabase provides expected request arguments",
|
||||
async (t) =>
|
||||
withTmpDir(async (tmpDir) => {
|
||||
// Write some file that `uploadBundledDatabase` can use for its `fs.createReadStream(bundledDb)` call
|
||||
// so we don't have to stub that or create a fake `ReadStream`.
|
||||
const dbPath = path.join(tmpDir, "db");
|
||||
const content = JSON.stringify({});
|
||||
fs.writeFileSync(dbPath, content);
|
||||
|
||||
// A simple stub for `fetch` that satisfies Octokit when it inspects the response.
|
||||
const fetchStub = sinon.stub().resolves({
|
||||
status: 200,
|
||||
headers: new Headers(),
|
||||
text: async () => "",
|
||||
});
|
||||
|
||||
// Construct a real `ApiClient`.
|
||||
const client = apiClient.getApiClient(
|
||||
getTestEnv(),
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
);
|
||||
|
||||
// Then wrap the real `request` method in a stub that calls it with our stubbed `fetch`.
|
||||
// This ensures that we have a real `ApiClient` with a mostly real `request`, but which
|
||||
// does not perform any actual requests.
|
||||
const request = client.request;
|
||||
sinon.stub(client, "request").callsFake(async (route, options) =>
|
||||
request(route, {
|
||||
...options,
|
||||
request: { ...options?.request, fetch: fetchStub },
|
||||
}),
|
||||
);
|
||||
|
||||
// Stub `getApiClient` to return our modified `ApiClient`.
|
||||
const getApiClient = sinon
|
||||
.stub(apiClient, "getApiClient")
|
||||
.returns(client);
|
||||
|
||||
// Calling `uploadBundledDatabase` shouldn't throw.
|
||||
const nwo: RepositoryNwo = { owner: "test", repo: "foo" };
|
||||
const language: BuiltInLanguage = BuiltInLanguage.java;
|
||||
|
||||
await t.notThrowsAsync(async () => {
|
||||
await uploadBundledDatabase(
|
||||
nwo,
|
||||
language,
|
||||
"123",
|
||||
dbPath,
|
||||
content.length,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
);
|
||||
});
|
||||
|
||||
// Check that our stubs were called and that they received the expected arguments.
|
||||
t.is(getApiClient.callCount, 1);
|
||||
t.is(fetchStub.callCount, 1);
|
||||
|
||||
const args = fetchStub.args[0];
|
||||
t.is(args.length, 2);
|
||||
|
||||
const url = args[0];
|
||||
t.is(
|
||||
url,
|
||||
`https://uploads.github.com/repos/${nwo.owner}/${nwo.repo}/code-scanning/codeql/databases/${language}?name=${language}-database&commit_oid=123`,
|
||||
);
|
||||
|
||||
const options = args[1];
|
||||
t.like(options, {
|
||||
// Going via `new Headers` and `Object.fromEntries` ensures we get the same cases
|
||||
// for the header keys as we are given.
|
||||
headers: Object.fromEntries(
|
||||
new Headers({
|
||||
authorization: `token ${SAMPLE_DOTCOM_API_DETAILS.auth}`,
|
||||
"Content-Type": "application/zip",
|
||||
"Content-Length": content.length.toString(),
|
||||
}).entries(),
|
||||
),
|
||||
method: "POST",
|
||||
} satisfies RequestInit);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -12,11 +12,12 @@ import { type CodeQL } from "./codeql";
|
||||
import { Config } from "./config-utils";
|
||||
import { Feature } from "./feature-flags";
|
||||
import * as gitUtils from "./git-utils";
|
||||
import { parseGitHubUrl } from "./github";
|
||||
import { Logger, withGroupAsync } from "./logging";
|
||||
import { OverlayDatabaseMode } from "./overlay/overlay-database-mode";
|
||||
import { RepositoryNwo } from "./repository";
|
||||
import * as util from "./util";
|
||||
import { asHTTPError, bundleDb, CleanupLevel, parseGitHubUrl } from "./util";
|
||||
import { asHTTPError, bundleDb, CleanupLevel } from "./util";
|
||||
|
||||
/** Information about a database upload. */
|
||||
export interface DatabaseUploadResult {
|
||||
@@ -258,7 +259,7 @@ async function recordClearCleanupSizes(
|
||||
*
|
||||
* @returns the duration of the upload in milliseconds
|
||||
*/
|
||||
async function uploadBundledDatabase(
|
||||
export async function uploadBundledDatabase(
|
||||
repositoryNwo: RepositoryNwo,
|
||||
language: string,
|
||||
commitOid: string,
|
||||
@@ -266,7 +267,10 @@ async function uploadBundledDatabase(
|
||||
bundledDbSize: number,
|
||||
apiDetails: GitHubApiDetails,
|
||||
): Promise<number> {
|
||||
const client = getApiClient();
|
||||
// Ensure that the `apiDetails` that `getApiClient` uses match the ones we were given here,
|
||||
// because we use `apiDetails.auth` below which must be used with the same instance that
|
||||
// `apiDetails` refers to.
|
||||
const client = getApiClient(util.getEnv(), apiDetails);
|
||||
|
||||
const uploadsUrl = new URL(parseGitHubUrl(apiDetails.url));
|
||||
uploadsUrl.hostname = `uploads.${uploadsUrl.hostname}`;
|
||||
@@ -292,7 +296,6 @@ async function uploadBundledDatabase(
|
||||
commit_oid: commitOid,
|
||||
data: bundledDbReadStream,
|
||||
headers: {
|
||||
authorization: `token ${apiDetails.auth}`,
|
||||
"Content-Type": "application/zip",
|
||||
"Content-Length": bundledDbSize,
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"bundleVersion": "codeql-bundle-v2.27.2",
|
||||
"cliVersion": "2.27.2",
|
||||
"priorBundleVersion": "codeql-bundle-v2.27.1",
|
||||
"priorCliVersion": "2.27.1"
|
||||
"bundleVersion": "codeql-bundle-v2.27.3",
|
||||
"cliVersion": "2.27.3",
|
||||
"priorBundleVersion": "codeql-bundle-v2.27.2",
|
||||
"priorCliVersion": "2.27.2"
|
||||
}
|
||||
|
||||
@@ -165,9 +165,21 @@ export enum EnvVar {
|
||||
*/
|
||||
SARIF_DUMP_DIR = "CODEQL_ACTION_SARIF_DUMP_DIR",
|
||||
|
||||
/**
|
||||
* Whether to skip generating a failed SARIF file. Intended for testing purposes.
|
||||
* This setting is more specific than `CODEQL_ACTION_TEST_MODE` and
|
||||
* `CODEQL_ACTION_SKIP_SARIF_UPLOAD`, which imply this option.
|
||||
*
|
||||
* Setting this to `false` takes precedence over `CODEQL_ACTION_TEST_MODE: true` or
|
||||
* `CODEQL_ACTION_SKIP_SARIF_UPLOAD: true`.
|
||||
*/
|
||||
SKIP_GENERATE_FAILED_SARIF = "CODEQL_ACTION_SKIP_GENERATE_FAILED_SARIF",
|
||||
|
||||
/**
|
||||
* Whether to skip uploading SARIF results to GitHub. Intended for testing purposes.
|
||||
* This setting is more specific than `CODEQL_ACTION_TEST_MODE`, which implies this option.
|
||||
*
|
||||
* Setting this to `false` takes precedence over `CODEQL_ACTION_TEST_MODE: true`.
|
||||
*/
|
||||
SKIP_SARIF_UPLOAD = "CODEQL_ACTION_SKIP_SARIF_UPLOAD",
|
||||
|
||||
|
||||
@@ -74,11 +74,6 @@ export enum Feature {
|
||||
AllowMergeConfigFiles = "allow_merge_config_files",
|
||||
/** Controls whether we allow multiple values for the `analysis-kinds` input. */
|
||||
AllowMultipleAnalysisKinds = "allow_multiple_analysis_kinds",
|
||||
/**
|
||||
* Controls whether we delete CodeQL bundles that we are not going to use from the toolcache
|
||||
* before downloading a different bundle, in order to reclaim disk space.
|
||||
*/
|
||||
CleanupToolcacheBundles = "cleanup_toolcache_bundles",
|
||||
CleanupTrapCaches = "cleanup_trap_caches",
|
||||
/** Whether to allow the `config-file` input to be specified via a repository property. */
|
||||
ConfigFileRepositoryProperty = "config_file_repository_property",
|
||||
@@ -108,8 +103,6 @@ export enum Feature {
|
||||
OverlayAnalysisCodeScanningRuby = "overlay_analysis_code_scanning_ruby",
|
||||
OverlayAnalysisCpp = "overlay_analysis_cpp",
|
||||
OverlayAnalysisCsharp = "overlay_analysis_csharp",
|
||||
/** Disable TRAP caching when overlay analysis is enabled. */
|
||||
OverlayAnalysisDisableTrapCaching = "overlay_analysis_disable_trap_caching",
|
||||
OverlayAnalysisGo = "overlay_analysis_go",
|
||||
OverlayAnalysisJava = "overlay_analysis_java",
|
||||
OverlayAnalysisJavascript = "overlay_analysis_javascript",
|
||||
@@ -208,11 +201,6 @@ export const featureConfig = {
|
||||
envVar: "CODEQL_ACTION_ALLOW_MULTIPLE_ANALYSIS_KINDS",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.CleanupToolcacheBundles]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_CLEANUP_TOOLCACHE_BUNDLES",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.CleanupTrapCaches]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_CLEANUP_TRAP_CACHES",
|
||||
@@ -366,11 +354,6 @@ export const featureConfig = {
|
||||
minimumVersion: CODEQL_OVERLAY_MINIMUM_VERSION_RUBY,
|
||||
},
|
||||
// Other overlay-related feature flags
|
||||
[Feature.OverlayAnalysisDisableTrapCaching]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_DISABLE_TRAP_CACHING",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.OverlayAnalysisMinDisk8Gb]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_MIN_DISK_8_GB",
|
||||
|
||||
111
src/github.test.ts
Normal file
111
src/github.test.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import test from "ava";
|
||||
|
||||
import { ActionsEnvVars } from "./environment";
|
||||
import * as github from "./github";
|
||||
import { getTestEnv, setupTests } from "./testing-utils";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
test("parseGitHubUrl", (t) => {
|
||||
t.deepEqual(github.parseGitHubUrl("github.com"), "https://github.com");
|
||||
t.deepEqual(
|
||||
github.parseGitHubUrl("https://github.com"),
|
||||
"https://github.com",
|
||||
);
|
||||
t.deepEqual(
|
||||
github.parseGitHubUrl("https://api.github.com"),
|
||||
"https://github.com",
|
||||
);
|
||||
t.deepEqual(
|
||||
github.parseGitHubUrl("https://github.com/foo/bar"),
|
||||
"https://github.com",
|
||||
);
|
||||
|
||||
t.deepEqual(
|
||||
github.parseGitHubUrl("github.example.com"),
|
||||
"https://github.example.com/",
|
||||
);
|
||||
t.deepEqual(
|
||||
github.parseGitHubUrl("https://github.example.com"),
|
||||
"https://github.example.com/",
|
||||
);
|
||||
t.deepEqual(
|
||||
github.parseGitHubUrl("https://api.github.example.com"),
|
||||
"https://github.example.com/",
|
||||
);
|
||||
t.deepEqual(
|
||||
github.parseGitHubUrl("https://github.example.com/api/v3"),
|
||||
"https://github.example.com/",
|
||||
);
|
||||
t.deepEqual(
|
||||
github.parseGitHubUrl("https://github.example.com:1234"),
|
||||
"https://github.example.com:1234/",
|
||||
);
|
||||
t.deepEqual(
|
||||
github.parseGitHubUrl("https://api.github.example.com:1234"),
|
||||
"https://github.example.com:1234/",
|
||||
);
|
||||
t.deepEqual(
|
||||
github.parseGitHubUrl("https://github.example.com:1234/api/v3"),
|
||||
"https://github.example.com:1234/",
|
||||
);
|
||||
t.deepEqual(
|
||||
github.parseGitHubUrl("https://github.example.com/base/path"),
|
||||
"https://github.example.com/base/path/",
|
||||
);
|
||||
t.deepEqual(
|
||||
github.parseGitHubUrl("https://github.example.com/base/path/api/v3"),
|
||||
"https://github.example.com/base/path/",
|
||||
);
|
||||
|
||||
t.throws(() => github.parseGitHubUrl(""), {
|
||||
message: '"" is not a valid URL',
|
||||
});
|
||||
t.throws(() => github.parseGitHubUrl("ssh://github.com"), {
|
||||
message: '"ssh://github.com" is not a http or https URL',
|
||||
});
|
||||
t.throws(() => github.parseGitHubUrl("http:///::::433"), {
|
||||
message: '"http:///::::433" is not a valid URL',
|
||||
});
|
||||
});
|
||||
|
||||
test("getCurrentInstance throws if required env vars aren't set", (t) => {
|
||||
t.throws(() => github.getCurrentInstance(getTestEnv()));
|
||||
t.throws(() => {
|
||||
const env = getTestEnv({
|
||||
[ActionsEnvVars.GITHUB_SERVER_URL]: "https://localhost",
|
||||
});
|
||||
github.getCurrentInstance(env);
|
||||
});
|
||||
t.throws(() => {
|
||||
const env = getTestEnv({
|
||||
[ActionsEnvVars.GITHUB_API_URL]: "https://api.localhost",
|
||||
});
|
||||
github.getCurrentInstance(env);
|
||||
});
|
||||
});
|
||||
|
||||
test("getCurrentInstance constructs instance from environment", (t) => {
|
||||
const url = "https://localhost";
|
||||
const apiUrl = "https://api.localhost";
|
||||
|
||||
const env = getTestEnv({
|
||||
[ActionsEnvVars.GITHUB_SERVER_URL]: url,
|
||||
[ActionsEnvVars.GITHUB_API_URL]: apiUrl,
|
||||
});
|
||||
const instance = github.getCurrentInstance(env);
|
||||
|
||||
t.is(instance.url, url);
|
||||
t.is(instance.apiURL, apiUrl);
|
||||
});
|
||||
|
||||
test("getCurrentInstance constructs dotcom instance with dotcom env vars", (t) => {
|
||||
const env = getTestEnv({
|
||||
[ActionsEnvVars.GITHUB_SERVER_URL]: github.GITHUB_DOTCOM_URL,
|
||||
[ActionsEnvVars.GITHUB_API_URL]: github.GITHUB_DOTCOM_API_URL,
|
||||
});
|
||||
const instance = github.getCurrentInstance(env);
|
||||
const dotcomInstance = github.getDotcomInstance();
|
||||
|
||||
t.deepEqual(dotcomInstance, instance);
|
||||
});
|
||||
110
src/github.ts
Normal file
110
src/github.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { ActionsEnvVars, type ReadOnlyEnv } from "./environment";
|
||||
import { ConfigurationError } from "./util";
|
||||
|
||||
/** A string representing a GitHub instance address. */
|
||||
export type InstanceURL = string;
|
||||
|
||||
/**
|
||||
* The URL for github.com.
|
||||
*/
|
||||
export const GITHUB_DOTCOM_URL: InstanceURL = "https://github.com";
|
||||
|
||||
/** A string representing a GitHub API instance address. */
|
||||
export type InstanceApiURL = string;
|
||||
|
||||
/**
|
||||
* The URL for api.github.com.
|
||||
*/
|
||||
export const GITHUB_DOTCOM_API_URL: InstanceApiURL = "https://api.github.com";
|
||||
|
||||
/**
|
||||
* Represents information about a GitHub instance, how to authenticate to it,
|
||||
* and how to authenticate to other repos on it.
|
||||
*/
|
||||
export type GitHubApiCombinedDetails = GitHubApiDetails &
|
||||
GitHubApiExternalRepoDetails;
|
||||
|
||||
/** Represents information about a GitHub instance. */
|
||||
export type GitHubInstance = {
|
||||
/** The base URL of the GitHub instance, such as `https://github.com`. */
|
||||
url: string;
|
||||
/**
|
||||
* The API base URL of the GitHub instance, such as `https://api.github.com`.
|
||||
* Typically this is the `api.` subdomain of the `url` domain.
|
||||
*/
|
||||
apiURL: string | undefined;
|
||||
};
|
||||
|
||||
/** Represents a {@link GitHubInstance} with a token for accessing the API. */
|
||||
export type GitHubApiDetails = GitHubInstance & {
|
||||
/** The token to use for accessing the API of the GitHub instance. */
|
||||
auth?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents information for accessing a repository on the same GitHub instance as the Action
|
||||
* is currently running on.
|
||||
*/
|
||||
export type GitHubApiExternalRepoDetails = {
|
||||
/** An optional token for accessing repo(s) on the same GitHub instance. */
|
||||
externalRepoAuth?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses user input of a github.com or GHES URL to a canonical form.
|
||||
* Removes any API prefix or suffix if one is present.
|
||||
*/
|
||||
export function parseGitHubUrl(inputUrl: string): string {
|
||||
const originalUrl = inputUrl;
|
||||
if (inputUrl.indexOf("://") === -1) {
|
||||
inputUrl = `https://${inputUrl}`;
|
||||
}
|
||||
if (!inputUrl.startsWith("http://") && !inputUrl.startsWith("https://")) {
|
||||
throw new ConfigurationError(`"${originalUrl}" is not a http or https URL`);
|
||||
}
|
||||
|
||||
let url: URL;
|
||||
try {
|
||||
url = new URL(inputUrl);
|
||||
} catch {
|
||||
throw new ConfigurationError(`"${originalUrl}" is not a valid URL`);
|
||||
}
|
||||
|
||||
// If we detect this is trying to be to github.com
|
||||
// then return with a fixed canonical URL.
|
||||
if (url.hostname === "github.com" || url.hostname === "api.github.com") {
|
||||
return GITHUB_DOTCOM_URL;
|
||||
}
|
||||
|
||||
// Remove the API prefix if it's present
|
||||
if (url.pathname.indexOf("/api/v3") !== -1) {
|
||||
url.pathname = url.pathname.substring(0, url.pathname.indexOf("/api/v3"));
|
||||
}
|
||||
// Also consider subdomain isolation on GHES
|
||||
if (url.hostname.startsWith("api.")) {
|
||||
url.hostname = url.hostname.substring(4);
|
||||
}
|
||||
|
||||
// Normalise path to having a trailing slash for consistency
|
||||
if (!url.pathname.endsWith("/")) {
|
||||
url.pathname = `${url.pathname}/`;
|
||||
}
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
/** Retrieves a {@link GitHubInstance} describing the instance we are running on. */
|
||||
export function getCurrentInstance(env: ReadOnlyEnv): GitHubInstance {
|
||||
return {
|
||||
url: env.getRequired(ActionsEnvVars.GITHUB_SERVER_URL),
|
||||
apiURL: env.getRequired(ActionsEnvVars.GITHUB_API_URL),
|
||||
};
|
||||
}
|
||||
|
||||
/** Retrieves a {@link GitHubInstance} describing the GitHub.com instance. */
|
||||
export function getDotcomInstance(): GitHubInstance {
|
||||
return {
|
||||
url: GITHUB_DOTCOM_URL,
|
||||
apiURL: GITHUB_DOTCOM_API_URL,
|
||||
};
|
||||
}
|
||||
@@ -793,6 +793,7 @@ async function testFailedSarifUpload(
|
||||
}
|
||||
t.true(
|
||||
uploadFiles.calledOnceWith(
|
||||
config,
|
||||
sinon.match.string,
|
||||
sinon.match.string,
|
||||
category,
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
getRequiredEnvParam,
|
||||
parseMatrixInput,
|
||||
Result,
|
||||
shouldSkipGenerateFailedSarif,
|
||||
shouldSkipSarifUpload,
|
||||
Success,
|
||||
wrapError,
|
||||
@@ -115,7 +116,7 @@ async function prepareFailedSarif(
|
||||
const jobName = getRequiredEnvParam("GITHUB_JOB");
|
||||
const matrix = parseMatrixInput(actionsUtil.getRequiredInput("matrix"));
|
||||
|
||||
if (shouldSkipSarifUpload()) {
|
||||
if (shouldSkipGenerateFailedSarif()) {
|
||||
return new Failure({
|
||||
upload_failed_run_skipped_because: "SARIF upload is disabled",
|
||||
});
|
||||
@@ -226,6 +227,7 @@ async function maybeUploadFailedSarif(
|
||||
|
||||
logger.info(`Uploading failed SARIF file ${failedSarif.sarifFile}`);
|
||||
const uploadResult = await uploadLib.uploadFiles(
|
||||
config,
|
||||
failedSarif.sarifFile,
|
||||
failedSarif.checkoutPath,
|
||||
failedSarif.category,
|
||||
@@ -239,6 +241,14 @@ async function maybeUploadFailedSarif(
|
||||
logger,
|
||||
{ isUnsuccessfulExecution: true },
|
||||
);
|
||||
|
||||
// If SARIF upload is disabled for testing, `uploadFiles` will not have uploaded the failed
|
||||
// SARIF file and instead will have written it to disk. Upload it as a workflow artifact so that
|
||||
// we can inspect it in validation jobs.
|
||||
if (uploadResult.sarifID && shouldSkipSarifUpload()) {
|
||||
await uploadSarifArtifact(logger, "test-mode-sarif", failedSarif);
|
||||
}
|
||||
|
||||
return uploadResult
|
||||
? { ...uploadResult.statusReport, sarifID: uploadResult.sarifID }
|
||||
: {};
|
||||
@@ -257,23 +267,34 @@ async function maybeUploadFailedSarifArtifact(
|
||||
}
|
||||
|
||||
const failedSarif = failedSarifResult.value;
|
||||
return uploadSarifArtifact(logger, "sarif-artifact", failedSarif);
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Uploading failed SARIF file ${failedSarif.sarifFile} as artifact`,
|
||||
);
|
||||
/** Uploads a failed SARIF file as workflow artifact. */
|
||||
async function uploadSarifArtifact(
|
||||
logger: Logger,
|
||||
prefix: string,
|
||||
failedSarif: FailedSarifInfo,
|
||||
): Promise<UploadFailedSarifResult> {
|
||||
logger.startGroup(`Uploading failed SARIF file as artifact`);
|
||||
logger.info(`Uploading '${failedSarif.sarifFile}' as artifact`);
|
||||
|
||||
const gitHubVersion = await getGitHubVersion();
|
||||
const client = await getArtifactUploaderClient(logger, gitHubVersion.type);
|
||||
try {
|
||||
const gitHubVersion = await getGitHubVersion();
|
||||
const client = await getArtifactUploaderClient(logger, gitHubVersion.type);
|
||||
|
||||
const suffix = getArtifactSuffix(actionsUtil.getOptionalInput("matrix"));
|
||||
const name = sanitizeArtifactName(`sarif-artifact-${suffix}`);
|
||||
await client.uploadArtifact(
|
||||
name,
|
||||
[path.normalize(failedSarif.sarifFile)],
|
||||
path.normalize(".."),
|
||||
);
|
||||
const suffix = getArtifactSuffix(actionsUtil.getOptionalInput("matrix"));
|
||||
const name = sanitizeArtifactName(`${prefix}-${suffix}`);
|
||||
await client.uploadArtifact(
|
||||
name,
|
||||
[path.normalize(failedSarif.sarifFile)],
|
||||
path.normalize(path.dirname(failedSarif.sarifFile)),
|
||||
);
|
||||
|
||||
return { sarifID: name };
|
||||
return { sarifID: name };
|
||||
} finally {
|
||||
logger.endGroup();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -76,6 +76,9 @@ async function run(action: ActionState<["Base", "Logger", "Env"]>) {
|
||||
// Try to load the "failed" configuration file instead which may have been written by the `init` step
|
||||
// with a minimal configuration for this post-action.
|
||||
if (config === undefined) {
|
||||
logger.debug(
|
||||
"Will try to load the configuration file from an aborted 'init' step, since there is no regular one.",
|
||||
);
|
||||
config = getAbortedInitConfig(action, tmpDir);
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ import {
|
||||
loadRepositoryProperties,
|
||||
RepositoryPropertyName,
|
||||
} from "./feature-flags/properties";
|
||||
import * as github from "./github";
|
||||
import {
|
||||
checkInstallPython311,
|
||||
checkPacksForOverlayCompatibility,
|
||||
@@ -233,8 +234,7 @@ async function run(
|
||||
apiDetails = {
|
||||
auth: getRequiredInput("token"),
|
||||
externalRepoAuth: getOptionalInput("external-repository-token"),
|
||||
url: actionState.env.getRequired(ActionsEnvVars.GITHUB_SERVER_URL),
|
||||
apiURL: actionState.env.getRequired(ActionsEnvVars.GITHUB_API_URL),
|
||||
...github.getCurrentInstance(actionState.env),
|
||||
};
|
||||
|
||||
const gitHubVersion = await getGitHubVersion();
|
||||
|
||||
@@ -17,9 +17,10 @@ import { getGitHubVersion } from "./api-client";
|
||||
import { CodeQL } from "./codeql";
|
||||
import { ComputedInput, getToolsInput } from "./config/inputs";
|
||||
import { getRawLanguagesNoAutodetect } from "./config-utils";
|
||||
import { ActionsEnvVars, EnvVar } from "./environment";
|
||||
import { EnvVar } from "./environment";
|
||||
import { initFeatures } from "./feature-flags";
|
||||
import { loadRepositoryProperties } from "./feature-flags/properties";
|
||||
import * as github from "./github";
|
||||
import { initCodeQL } from "./init";
|
||||
import { Logger } from "./logging";
|
||||
import { getRepositoryNwo } from "./repository";
|
||||
@@ -112,8 +113,7 @@ async function run(
|
||||
const apiDetails = {
|
||||
auth: getRequiredInput("token"),
|
||||
externalRepoAuth: getOptionalInput("external-repository-token"),
|
||||
url: actionState.env.getRequired(ActionsEnvVars.GITHUB_SERVER_URL),
|
||||
apiURL: actionState.env.getRequired(ActionsEnvVars.GITHUB_API_URL),
|
||||
...github.getCurrentInstance(actionState.env),
|
||||
};
|
||||
|
||||
const gitHubVersion = await getGitHubVersion();
|
||||
|
||||
@@ -596,7 +596,7 @@ test.serial(
|
||||
undefined, // rawLanguages
|
||||
undefined, // otherLanguagePacksReason
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
createFeatures([Feature.CleanupToolcacheBundles]),
|
||||
createFeatures([]),
|
||||
getRunnerLogger(true),
|
||||
);
|
||||
|
||||
@@ -1508,10 +1508,7 @@ for (const bundle of ["per-language", "combined", "fallback"] as const) {
|
||||
};
|
||||
});
|
||||
const addDiagnostic = sinon.stub(diagnostics, "addNoLanguageDiagnostic");
|
||||
const features = createFeatures([
|
||||
Feature.PerLanguageBundles,
|
||||
Feature.CleanupToolcacheBundles,
|
||||
]);
|
||||
const features = createFeatures([Feature.PerLanguageBundles]);
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
@@ -1882,11 +1879,15 @@ function createToolcacheEntry(
|
||||
*/
|
||||
async function runDownloadCodeQL(
|
||||
toolcacheRoot: string,
|
||||
features: Feature[],
|
||||
bundleVersion: string | undefined,
|
||||
env = getTestEnv({
|
||||
[ActionsEnvVars.RUNNER_TOOL_CACHE]: toolcacheRoot,
|
||||
}),
|
||||
{
|
||||
env = getTestEnv({
|
||||
[ActionsEnvVars.RUNNER_ENVIRONMENT]: "github-hosted",
|
||||
[ActionsEnvVars.RUNNER_TOOL_CACHE]: toolcacheRoot,
|
||||
}),
|
||||
}: {
|
||||
env?: Env;
|
||||
} = {},
|
||||
): Promise<{
|
||||
codeqlFolder: string;
|
||||
cleanupDiagnostic: toolsDownload.ToolcacheCleanupResult | undefined;
|
||||
@@ -1897,7 +1898,6 @@ async function runDownloadCodeQL(
|
||||
const { codeqlFolder } = await setupCodeql.downloadCodeQLBundle(
|
||||
initAllState({
|
||||
env,
|
||||
features: createFeatures(features),
|
||||
}),
|
||||
{
|
||||
bundle: {
|
||||
@@ -1935,11 +1935,9 @@ async function runDownloadCodeQL(
|
||||
async function testToolcacheCleanup(
|
||||
t: ExecutionContext<unknown>,
|
||||
{
|
||||
features,
|
||||
runnerEnvironment,
|
||||
setUp,
|
||||
}: {
|
||||
features: Feature[];
|
||||
runnerEnvironment: string | undefined;
|
||||
setUp?: (env: Env) => void;
|
||||
},
|
||||
@@ -1951,12 +1949,8 @@ async function testToolcacheCleanup(
|
||||
) {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
if (runnerEnvironment === undefined) {
|
||||
delete process.env[ActionsEnvVars.RUNNER_ENVIRONMENT];
|
||||
} else {
|
||||
process.env[ActionsEnvVars.RUNNER_ENVIRONMENT] = runnerEnvironment;
|
||||
}
|
||||
const env = getTestEnv({
|
||||
[ActionsEnvVars.RUNNER_ENVIRONMENT]: runnerEnvironment,
|
||||
[ActionsEnvVars.RUNNER_TOOL_CACHE]: tmpDir,
|
||||
});
|
||||
setUp?.(env);
|
||||
@@ -1976,9 +1970,8 @@ async function testToolcacheCleanup(
|
||||
|
||||
const { cleanupDiagnostic } = await runDownloadCodeQL(
|
||||
tmpDir,
|
||||
features,
|
||||
CLEANUP_BUNDLE_VERSION,
|
||||
env,
|
||||
{ env },
|
||||
);
|
||||
|
||||
t.true(
|
||||
@@ -1990,28 +1983,12 @@ async function testToolcacheCleanup(
|
||||
});
|
||||
}
|
||||
|
||||
test.serial(
|
||||
"downloadCodeQLBundle does not clean up the toolcache when the feature flag is disabled",
|
||||
async (t) => {
|
||||
await testToolcacheCleanup(
|
||||
t,
|
||||
{ features: [], runnerEnvironment: "github-hosted" },
|
||||
({ cleanupDiagnostic, destinationDirectory, staleDirectory }) => {
|
||||
t.true(fs.existsSync(staleDirectory));
|
||||
t.true(fs.existsSync(destinationDirectory));
|
||||
t.is(cleanupDiagnostic, undefined);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"downloadCodeQLBundle does not clean up the toolcache when the runner is not GitHub-hosted",
|
||||
async (t) => {
|
||||
await testToolcacheCleanup(
|
||||
t,
|
||||
{
|
||||
features: [Feature.CleanupToolcacheBundles],
|
||||
runnerEnvironment: "self-hosted",
|
||||
},
|
||||
({ cleanupDiagnostic, destinationDirectory, staleDirectory }) => {
|
||||
@@ -2031,7 +2008,6 @@ test.serial(
|
||||
await testToolcacheCleanup(
|
||||
t,
|
||||
{
|
||||
features: [Feature.CleanupToolcacheBundles],
|
||||
runnerEnvironment: undefined,
|
||||
},
|
||||
({ cleanupDiagnostic, destinationDirectory, staleDirectory }) => {
|
||||
@@ -2044,12 +2020,11 @@ test.serial(
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"downloadCodeQLBundle deletes other CodeQL bundles from the toolcache when enabled on a GitHub-hosted runner",
|
||||
"downloadCodeQLBundle deletes other CodeQL bundles from the toolcache on a GitHub-hosted runner",
|
||||
async (t) => {
|
||||
await testToolcacheCleanup(
|
||||
t,
|
||||
{
|
||||
features: [Feature.CleanupToolcacheBundles],
|
||||
runnerEnvironment: "github-hosted",
|
||||
},
|
||||
({ cleanupDiagnostic, destinationDirectory, staleDirectory }) => {
|
||||
@@ -2083,7 +2058,6 @@ test.serial(
|
||||
|
||||
const { cleanupDiagnostic } = await runDownloadCodeQL(
|
||||
tmpDir,
|
||||
[Feature.CleanupToolcacheBundles],
|
||||
CLEANUP_BUNDLE_VERSION,
|
||||
);
|
||||
|
||||
@@ -2113,7 +2087,6 @@ test.serial(
|
||||
|
||||
const { cleanupDiagnostic } = await runDownloadCodeQL(
|
||||
tmpDir,
|
||||
[Feature.CleanupToolcacheBundles],
|
||||
CLEANUP_BUNDLE_VERSION,
|
||||
);
|
||||
|
||||
@@ -2245,7 +2218,6 @@ test.serial(
|
||||
|
||||
const { cleanupDiagnostic } = await runDownloadCodeQL(
|
||||
toolcacheRoot,
|
||||
[Feature.CleanupToolcacheBundles],
|
||||
CLEANUP_BUNDLE_VERSION,
|
||||
);
|
||||
|
||||
@@ -2268,7 +2240,6 @@ test.serial(
|
||||
await testToolcacheCleanup(
|
||||
t,
|
||||
{
|
||||
features: [Feature.CleanupToolcacheBundles],
|
||||
runnerEnvironment: "github-hosted",
|
||||
setUp: (env) => {
|
||||
env.set(EnvVar.HAS_SET_UP_CODEQL, "true");
|
||||
@@ -2298,9 +2269,13 @@ test.serial(
|
||||
);
|
||||
const { codeqlFolder, cleanupDiagnostic } = await runDownloadCodeQL(
|
||||
tmpDir,
|
||||
[Feature.CleanupToolcacheBundles],
|
||||
CLEANUP_BUNDLE_VERSION,
|
||||
getTestEnv({ [EnvVar.HAS_SET_UP_CODEQL]: "true" }),
|
||||
{
|
||||
env: getTestEnv({
|
||||
[ActionsEnvVars.RUNNER_ENVIRONMENT]: "github-hosted",
|
||||
[EnvVar.HAS_SET_UP_CODEQL]: "true",
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
t.true(fs.existsSync(staleDirectory));
|
||||
@@ -2368,7 +2343,7 @@ test.serial(
|
||||
undefined, // rawLanguages
|
||||
undefined, // otherLanguagePacksReason
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
createFeatures([Feature.CleanupToolcacheBundles]),
|
||||
createFeatures([]),
|
||||
getRunnerLogger(true),
|
||||
),
|
||||
{ is: error },
|
||||
@@ -2403,7 +2378,6 @@ test.serial(
|
||||
|
||||
const { codeqlFolder, cleanupDiagnostic } = await runDownloadCodeQL(
|
||||
tmpDir,
|
||||
[Feature.CleanupToolcacheBundles],
|
||||
undefined, // bundleVersion
|
||||
);
|
||||
|
||||
@@ -2437,7 +2411,6 @@ test.serial(
|
||||
|
||||
const { cleanupDiagnostic } = await runDownloadCodeQL(
|
||||
tmpDir,
|
||||
[Feature.CleanupToolcacheBundles],
|
||||
CLEANUP_BUNDLE_VERSION,
|
||||
);
|
||||
|
||||
@@ -2473,7 +2446,6 @@ test.serial(
|
||||
|
||||
const { cleanupDiagnostic } = await runDownloadCodeQL(
|
||||
tmpDir,
|
||||
[Feature.CleanupToolcacheBundles],
|
||||
CLEANUP_BUNDLE_VERSION,
|
||||
);
|
||||
|
||||
@@ -2523,7 +2495,6 @@ test.serial(
|
||||
|
||||
const { cleanupDiagnostic } = await runDownloadCodeQL(
|
||||
toolcacheRoot,
|
||||
[Feature.CleanupToolcacheBundles],
|
||||
CLEANUP_BUNDLE_VERSION,
|
||||
);
|
||||
|
||||
|
||||
@@ -797,7 +797,7 @@ function getToolcacheDestination(
|
||||
}
|
||||
|
||||
/**
|
||||
* Reclaims disk space by deleting the CodeQL tools from the toolcache, if enabled.
|
||||
* Reclaims disk space by deleting the CodeQL tools from the toolcache on GitHub-hosted runners.
|
||||
*
|
||||
* On GitHub-hosted runners the toolcache shares a filesystem with the workspace, so tools left in
|
||||
* the toolcache take up space that the analysis could use instead. This holds wherever we extract
|
||||
@@ -805,9 +805,8 @@ function getToolcacheDestination(
|
||||
*/
|
||||
async function tryDeleteToolcacheBundles({
|
||||
env,
|
||||
features,
|
||||
logger,
|
||||
}: ActionState<["Logger", "ReadOnlyEnv", "FeatureFlags"]>): Promise<void> {
|
||||
}: ActionState<["Logger", "ReadOnlyEnv"]>): Promise<void> {
|
||||
// A step that has already set up CodeQL may hand out a path into the toolcache that a later step
|
||||
// runs, so only the first step to set it up can know that nothing else relies on the toolcache.
|
||||
if (env.getOptional(EnvVar.HAS_SET_UP_CODEQL) !== undefined) {
|
||||
@@ -818,11 +817,7 @@ async function tryDeleteToolcacheBundles({
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!isGitHubHostedRunner() ||
|
||||
!isToolcacheOnWorkspaceFilesystem(logger) ||
|
||||
!(await features.getValue(Feature.CleanupToolcacheBundles))
|
||||
) {
|
||||
if (!isGitHubHostedRunner(env) || !isToolcacheOnWorkspaceFilesystem(logger)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -941,7 +936,7 @@ export async function setupCodeQLBundle(
|
||||
break;
|
||||
case "download": {
|
||||
const result = await downloadCodeQLBundle(
|
||||
{ env: getEnv(), features, logger },
|
||||
{ env: getEnv(), logger },
|
||||
source,
|
||||
apiDetails,
|
||||
zstdAvailability.version,
|
||||
@@ -977,7 +972,7 @@ export async function setupCodeQLBundle(
|
||||
* @returns The extraction directory and download timings.
|
||||
*/
|
||||
export async function downloadCodeQLBundle(
|
||||
action: ActionState<["Logger", "ReadOnlyEnv", "FeatureFlags"]>,
|
||||
action: ActionState<["Logger", "ReadOnlyEnv"]>,
|
||||
source: CodeQLDownloadSource,
|
||||
apiDetails: api.GitHubApiDetails,
|
||||
tarVersion: tar.TarVersion | undefined,
|
||||
|
||||
@@ -2,12 +2,13 @@ import { ActionState } from "../action-common";
|
||||
import { isRunningLocalAction } from "../actions-util";
|
||||
import * as api from "../api-client";
|
||||
import { ActionsEnvVars } from "../environment";
|
||||
import { GITHUB_DOTCOM_URL } from "../github";
|
||||
import {
|
||||
isRepositoryNwoStr,
|
||||
parseRepositoryNwo,
|
||||
type RepositoryNwoStr,
|
||||
} from "../repository";
|
||||
import { GITHUB_DOTCOM_URL, type InstanceURL } from "../util";
|
||||
import { type InstanceURL } from "../util";
|
||||
|
||||
/** The NWO of the standard CodeQL Action repository. */
|
||||
export const CODEQL_DEFAULT_ACTION_REPOSITORY = "github/codeql-action";
|
||||
|
||||
@@ -109,7 +109,7 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
|
||||
getApiDetails(action.env),
|
||||
);
|
||||
const proxyInfo = await startProxy(
|
||||
proxyBin,
|
||||
proxyBin.path,
|
||||
proxyConfig,
|
||||
proxyLogFilePath,
|
||||
logger,
|
||||
@@ -125,6 +125,7 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
|
||||
languages: language === undefined ? undefined : [language],
|
||||
},
|
||||
proxyConfig.all_credentials.map((c) => c.type),
|
||||
{ proxy_version: proxyBin.version },
|
||||
logger,
|
||||
);
|
||||
} catch (unwrappedError) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as filepath from "path";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
import * as toolcache from "@actions/tool-cache";
|
||||
import test, { ExecutionContext } from "ava";
|
||||
import sinon from "sinon";
|
||||
@@ -18,7 +17,6 @@ import { getRunnerLogger, Logger } from "./logging";
|
||||
import { BundlePlatform, getBundlePlatform } from "./platform";
|
||||
import * as repository from "./setup/repository";
|
||||
import * as startProxyExports from "./start-proxy";
|
||||
import * as statusReport from "./status-report";
|
||||
import {
|
||||
addSinonAssertions,
|
||||
assertNotLogged,
|
||||
@@ -32,94 +30,10 @@ import {
|
||||
setupTests,
|
||||
withRecordingLoggerAsync,
|
||||
} from "./testing-utils";
|
||||
import {
|
||||
ConfigurationError,
|
||||
GitHubVariant,
|
||||
GitHubVersion,
|
||||
withTmpDir,
|
||||
} from "./util";
|
||||
import { GitHubVariant, GitHubVersion, withTmpDir } from "./util";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
const sendFailedStatusReportTest = makeMacro({
|
||||
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}`,
|
||||
});
|
||||
|
||||
sendFailedStatusReportTest.serial(
|
||||
"reports generic error message for non-StartProxyError error",
|
||||
new Error("Something went wrong today"),
|
||||
"Error from start-proxy Action omitted (Error).",
|
||||
);
|
||||
|
||||
sendFailedStatusReportTest.serial(
|
||||
"reports generic error message for non-StartProxyError error with safe error message",
|
||||
new Error(
|
||||
startProxyExports.getStartProxyErrorMessage(
|
||||
startProxyExports.StartProxyErrorType.DownloadFailed,
|
||||
),
|
||||
),
|
||||
"Error from start-proxy Action omitted (Error).",
|
||||
);
|
||||
|
||||
sendFailedStatusReportTest.serial(
|
||||
"reports generic error message for ConfigurationError error",
|
||||
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");
|
||||
|
||||
@@ -1021,16 +935,17 @@ test.serial(
|
||||
sinon.stub(actionsUtil, "isRunningLocalAction").returns(true);
|
||||
|
||||
const features = mockOfflineFeatures(tempDir, logger);
|
||||
const path = await startProxyExports.getProxyBinaryPath(
|
||||
const result = await startProxyExports.getProxyBinaryPath(
|
||||
initAllState({ logger, features }),
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
);
|
||||
|
||||
t.assert(path);
|
||||
t.assert(result);
|
||||
t.is(
|
||||
path,
|
||||
result.path,
|
||||
filepath.join(toolcachePath, startProxyExports.getProxyFilename()),
|
||||
);
|
||||
t.is(result.version, defaults.cliVersion);
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -1067,7 +982,7 @@ test.serial(
|
||||
sinon.stub(actionsUtil, "isRunningLocalAction").returns(false);
|
||||
state.env.set(ActionsEnvVars.GITHUB_ACTION_REPOSITORY, "test/repo");
|
||||
|
||||
const path = await startProxyExports.getProxyBinaryPath(
|
||||
const result = await startProxyExports.getProxyBinaryPath(
|
||||
{
|
||||
...state,
|
||||
features: createFeatures([]),
|
||||
@@ -1084,11 +999,12 @@ test.serial(
|
||||
st.onceWith(extractTar, archivePath);
|
||||
st.onceWith(cacheDir, extractedPath);
|
||||
|
||||
t.assert(path);
|
||||
t.assert(result);
|
||||
t.is(
|
||||
path,
|
||||
result.path,
|
||||
filepath.join(toolcachePath, startProxyExports.getProxyFilename()),
|
||||
);
|
||||
t.is(result.version, defaults.cliVersion);
|
||||
|
||||
checkExpectedLogMessages(t, logger.messages, [
|
||||
`Found private registry proxy ${startProxyExports.getProxyPackage(getTestPlatform(state))} in test/repo on https://github.com with URL ${downloadUrl}`,
|
||||
@@ -1101,7 +1017,8 @@ test.serial(
|
||||
async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
const state = initAllState({ logger });
|
||||
const expectedTag = "codeql-bundle-v2.20.1";
|
||||
const expectedVersion = "2.20.1";
|
||||
const expectedTag = `codeql-bundle-v${expectedVersion}`;
|
||||
const expectedParams = {
|
||||
owner: "test",
|
||||
repo: "repo",
|
||||
@@ -1154,14 +1071,16 @@ test.serial(
|
||||
const getDefaultCliVersion = sinon
|
||||
.stub(features, "getEnabledDefaultCliVersions")
|
||||
.resolves({
|
||||
enabledVersions: [{ cliVersion: "2.20.1", tagName: expectedTag }],
|
||||
enabledVersions: [
|
||||
{ cliVersion: expectedVersion, tagName: expectedTag },
|
||||
],
|
||||
});
|
||||
|
||||
// Stub this to always return `false` so we force `getCodeQLAssetDownloadURL` to use the (mocked) API.
|
||||
sinon.stub(actionsUtil, "isRunningLocalAction").returns(false);
|
||||
state.env.set(ActionsEnvVars.GITHUB_ACTION_REPOSITORY, "test/repo");
|
||||
|
||||
const path = await startProxyExports.getProxyBinaryPath(
|
||||
const result = await startProxyExports.getProxyBinaryPath(
|
||||
{
|
||||
...state,
|
||||
features,
|
||||
@@ -1179,11 +1098,12 @@ test.serial(
|
||||
st.onceWith(extractTar, archivePath);
|
||||
st.onceWith(cacheDir, extractedPath);
|
||||
|
||||
t.assert(path);
|
||||
t.assert(result);
|
||||
t.is(
|
||||
path,
|
||||
result.path,
|
||||
filepath.join(toolcachePath, startProxyExports.getProxyFilename()),
|
||||
);
|
||||
t.is(result.version, expectedVersion);
|
||||
});
|
||||
|
||||
checkExpectedLogMessages(t, logger.messages, [
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as path from "path";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
import * as toolcache from "@actions/tool-cache";
|
||||
|
||||
import { ActionState } from "./action-common";
|
||||
@@ -10,7 +9,6 @@ import {
|
||||
GitHubApiDetails,
|
||||
} from "./api-client";
|
||||
import * as artifactScanner from "./artifact-scanner";
|
||||
import { Config } from "./config-utils";
|
||||
import * as defaults from "./defaults.json";
|
||||
import {
|
||||
CodeQLDefaultVersionInfo,
|
||||
@@ -22,6 +20,10 @@ import { BuiltInLanguage } from "./languages";
|
||||
import { Logger } from "./logging";
|
||||
import { BundlePlatform, getBundlePlatform } from "./platform";
|
||||
import { getCodeQLAssetDownloadURL } from "./setup/repository";
|
||||
import {
|
||||
StartProxyError,
|
||||
StartProxyErrorType,
|
||||
} from "./start-proxy/status-report";
|
||||
import {
|
||||
Address,
|
||||
Registry,
|
||||
@@ -32,144 +34,10 @@ import {
|
||||
RawCredential,
|
||||
} from "./start-proxy/types";
|
||||
import { getAuthConfig } from "./start-proxy/validation";
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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: StatusReportBase = {
|
||||
...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: BuiltInLanguage | 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 === undefined ? undefined : [language],
|
||||
},
|
||||
await util.checkDiskUsage(logger),
|
||||
logger,
|
||||
statusReportMessage,
|
||||
);
|
||||
if (errorStatusReportBase !== undefined) {
|
||||
await sendStatusReport(errorStatusReportBase);
|
||||
}
|
||||
}
|
||||
export * from "./start-proxy/status-report";
|
||||
|
||||
/** The basename of the proxy artifacts. */
|
||||
export const UPDATEJOB_PROXY = "update-job-proxy";
|
||||
@@ -529,18 +397,28 @@ export function getProxyFilename() {
|
||||
: UPDATEJOB_PROXY;
|
||||
}
|
||||
|
||||
export type ProxyBinary = {
|
||||
/** The path to the proxy binary to run. */
|
||||
path: string;
|
||||
/**
|
||||
* The version associated with the proxy binary.
|
||||
* This usually corresponds to the CodeQL CLI release that the binary was obtained from.
|
||||
*/
|
||||
version: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 action The action state.
|
||||
* @returns The path to the proxy binary.
|
||||
* @returns The path to the proxy binary and its version.
|
||||
*/
|
||||
export async function getProxyBinaryPath(
|
||||
action: ActionState<["Base", "ReadOnlyEnv", "Logger", "FeatureFlags"]>,
|
||||
apiDetails: GitHubApiDetails,
|
||||
): Promise<string> {
|
||||
): Promise<ProxyBinary> {
|
||||
const logger = action.logger;
|
||||
logger.startGroup("Setting up authentication proxy");
|
||||
|
||||
@@ -578,7 +456,10 @@ export async function getProxyBinaryPath(
|
||||
|
||||
logger.info(`Extracted the authentication proxy to '${proxyDir}'.`);
|
||||
}
|
||||
return path.join(proxyDir, proxyFileName);
|
||||
return {
|
||||
path: path.join(proxyDir, proxyFileName),
|
||||
version: proxyInfo.version,
|
||||
};
|
||||
} finally {
|
||||
logger.endGroup();
|
||||
}
|
||||
|
||||
136
src/start-proxy/status-report.test.ts
Normal file
136
src/start-proxy/status-report.test.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import * as core from "@actions/core";
|
||||
import test, { ExecutionContext } from "ava";
|
||||
import sinon from "sinon";
|
||||
|
||||
import * as statusReport from "../status-report";
|
||||
import {
|
||||
createTestConfig,
|
||||
makeMacro,
|
||||
RecordingLogger,
|
||||
setupTests,
|
||||
withRecordingLoggerAsync,
|
||||
} from "../testing-utils";
|
||||
import { ConfigurationError } from "../util";
|
||||
|
||||
import * as startProxyExports from "./status-report";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
test.serial("sendSuccessStatusReport", async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
const now = new Date();
|
||||
const config = createTestConfig({});
|
||||
const proxy_version = "1.2.3";
|
||||
const registry_types = ["git_source", "docker_registry"];
|
||||
|
||||
const createStatusReportBase = sinon.stub(
|
||||
statusReport,
|
||||
"createStatusReportBase",
|
||||
);
|
||||
createStatusReportBase.resolves({
|
||||
action_name: statusReport.ActionName.StartProxy,
|
||||
} as statusReport.StatusReportBase);
|
||||
|
||||
const sendStatusReport = sinon
|
||||
.stub(statusReport, "sendStatusReport")
|
||||
.resolves();
|
||||
|
||||
await startProxyExports.sendSuccessStatusReport(
|
||||
now,
|
||||
config,
|
||||
registry_types,
|
||||
{ proxy_version },
|
||||
logger,
|
||||
);
|
||||
|
||||
t.is(createStatusReportBase.callCount, 1);
|
||||
|
||||
t.is(sendStatusReport.callCount, 1);
|
||||
t.is(sendStatusReport.args[0].length, 1);
|
||||
|
||||
const arg = sendStatusReport.args[0][0];
|
||||
t.like(arg, {
|
||||
action_name: statusReport.ActionName.StartProxy,
|
||||
registry_types: "docker_registry,git_source",
|
||||
start_proxy: { proxy_version },
|
||||
});
|
||||
});
|
||||
|
||||
const sendFailedStatusReportTest = makeMacro({
|
||||
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}`,
|
||||
});
|
||||
|
||||
sendFailedStatusReportTest.serial(
|
||||
"reports generic error message for non-StartProxyError error",
|
||||
new Error("Something went wrong today"),
|
||||
"Error from start-proxy Action omitted (Error).",
|
||||
);
|
||||
|
||||
sendFailedStatusReportTest.serial(
|
||||
"reports generic error message for non-StartProxyError error with safe error message",
|
||||
new Error(
|
||||
startProxyExports.getStartProxyErrorMessage(
|
||||
startProxyExports.StartProxyErrorType.DownloadFailed,
|
||||
),
|
||||
),
|
||||
"Error from start-proxy Action omitted (Error).",
|
||||
);
|
||||
|
||||
sendFailedStatusReportTest.serial(
|
||||
"reports generic error message for ConfigurationError error",
|
||||
new ConfigurationError("Something went wrong today"),
|
||||
"Error from start-proxy Action omitted (ConfigurationError).",
|
||||
"user-error",
|
||||
);
|
||||
153
src/start-proxy/status-report.ts
Normal file
153
src/start-proxy/status-report.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import type { Config } from "../config-utils";
|
||||
import type { BuiltInLanguage } from "../languages";
|
||||
import type { Logger } from "../logging";
|
||||
import {
|
||||
ActionName,
|
||||
createStatusReportBase,
|
||||
getActionsStatus,
|
||||
sendStatusReport,
|
||||
type StatusReportBase,
|
||||
} from "../status-report";
|
||||
import * as util from "../util";
|
||||
|
||||
/** Additional status report data from `start-proxy`. */
|
||||
export interface StartProxyStatus {
|
||||
proxy_version: string;
|
||||
}
|
||||
|
||||
/** The type of successful status reports from `start-proxy`. */
|
||||
type StartProxySuccessReport = StatusReportBase & {
|
||||
start_proxy: StartProxyStatus;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 status Status information.
|
||||
* @param logger The logger to use.
|
||||
*/
|
||||
export async function sendSuccessStatusReport(
|
||||
startedAt: Date,
|
||||
config: Partial<Config>,
|
||||
registry_types: string[],
|
||||
status: StartProxyStatus,
|
||||
logger: Logger,
|
||||
) {
|
||||
const statusReportBase = await createStatusReportBase(
|
||||
ActionName.StartProxy,
|
||||
"success",
|
||||
startedAt,
|
||||
config,
|
||||
await util.checkDiskUsage(logger),
|
||||
logger,
|
||||
);
|
||||
if (statusReportBase !== undefined) {
|
||||
const statusReport: StartProxySuccessReport = {
|
||||
...statusReportBase,
|
||||
registry_types: registry_types.sort().join(","),
|
||||
start_proxy: status,
|
||||
};
|
||||
await sendStatusReport(statusReport);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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: BuiltInLanguage | 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 === undefined ? undefined : [language],
|
||||
},
|
||||
await util.checkDiskUsage(logger),
|
||||
logger,
|
||||
statusReportMessage,
|
||||
);
|
||||
if (errorStatusReportBase !== undefined) {
|
||||
await sendStatusReport(errorStatusReportBase);
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
exportEnvVar,
|
||||
} from "./environment";
|
||||
import { getRef } from "./git-utils";
|
||||
import { GITHUB_DOTCOM_URL } from "./github";
|
||||
import * as json from "./json";
|
||||
import type { Logger } from "./logging";
|
||||
import type { OverlayBaseDatabaseDownloadStats } from "./overlay/caching";
|
||||
@@ -41,7 +42,6 @@ import {
|
||||
ConfigurationError,
|
||||
getRequiredEnvParam,
|
||||
isInTestMode,
|
||||
GITHUB_DOTCOM_URL,
|
||||
DiskUsage,
|
||||
assertNever,
|
||||
BuildMode,
|
||||
|
||||
@@ -12,12 +12,13 @@ import * as analyses from "./analyses";
|
||||
import * as api from "./api-client";
|
||||
import { getGitHubVersion, wrapApiConfigurationError } from "./api-client";
|
||||
import { CodeQL, getCodeQL } from "./codeql";
|
||||
import { getConfig } from "./config-utils";
|
||||
import type { ActionConfig } from "./config/action-config";
|
||||
import { readDiffRangesJsonFile } from "./diff-informed-analysis-utils";
|
||||
import { ActionsEnvVars, EnvVar, exportEnvVar } from "./environment";
|
||||
import { FeatureEnablement } from "./feature-flags";
|
||||
import * as fingerprints from "./fingerprints";
|
||||
import * as gitUtils from "./git-utils";
|
||||
import * as github from "./github";
|
||||
import { initCodeQL } from "./init";
|
||||
import { Logger } from "./logging";
|
||||
import { getRepositoryNwo, RepositoryNwo } from "./repository";
|
||||
@@ -33,7 +34,6 @@ import * as util from "./util";
|
||||
import {
|
||||
ConfigurationError,
|
||||
getErrorMessage,
|
||||
getRequiredEnvParam,
|
||||
GitHubVariant,
|
||||
GitHubVersion,
|
||||
satisfiesGHESVersion,
|
||||
@@ -94,16 +94,22 @@ async function shouldDisableCombineSarifFiles(
|
||||
return true;
|
||||
}
|
||||
|
||||
// Takes a list of paths to sarif files and combines them together using the
|
||||
// CLI `github merge-results` command when all SARIF files are produced by
|
||||
// CodeQL. Otherwise, it will fall back to combining the files in the action.
|
||||
// Returns the contents of the combined sarif file.
|
||||
/**
|
||||
* Takes a list of paths to sarif files and combines them together using the
|
||||
* CLI `github merge-results` command when all SARIF files are produced by
|
||||
* CodeQL. Otherwise, it will fall back to combining the files in the action.
|
||||
*
|
||||
* @returns The contents of the combined sarif file.
|
||||
*/
|
||||
async function combineSarifFilesUsingCLI(
|
||||
sarifFiles: string[],
|
||||
gitHubVersion: GitHubVersion,
|
||||
features: FeatureEnablement,
|
||||
logger: Logger,
|
||||
config: ActionConfig<"codeQLCmd" | "tempDir"> | undefined,
|
||||
): Promise<Partial<sarif.Log>> {
|
||||
const env = util.getEnv();
|
||||
|
||||
logger.info("Combining SARIF files using the CodeQL CLI");
|
||||
|
||||
const sarifObjects = sarifFiles.map(sarif.readSarifFile);
|
||||
@@ -138,7 +144,6 @@ async function combineSarifFilesUsingCLI(
|
||||
let codeQL: CodeQL;
|
||||
let tempDir: string = actionsUtil.getTemporaryDirectory();
|
||||
|
||||
const config = await getConfig(tempDir, logger);
|
||||
if (config !== undefined) {
|
||||
codeQL = await getCodeQL(logger, config.codeQLCmd);
|
||||
tempDir = config.tempDir;
|
||||
@@ -152,8 +157,7 @@ async function combineSarifFilesUsingCLI(
|
||||
externalRepoAuth: actionsUtil.getOptionalInput(
|
||||
"external-repository-token",
|
||||
),
|
||||
url: getRequiredEnvParam("GITHUB_SERVER_URL"),
|
||||
apiURL: getRequiredEnvParam("GITHUB_API_URL"),
|
||||
...github.getCurrentInstance(env),
|
||||
};
|
||||
|
||||
const codeQLDefaultVersionInfo =
|
||||
@@ -227,6 +231,14 @@ function getAutomationID(
|
||||
return api.computeAutomationID(analysis_key, environment);
|
||||
}
|
||||
|
||||
/** Gets the path to a saved payload file for {@link analysisKind}. */
|
||||
export function getSavedPayloadPath(analysisKind: analyses.AnalysisKind) {
|
||||
return path.join(
|
||||
actionsUtil.getTemporaryDirectory(),
|
||||
`payload-${analysisKind}.json`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload the given payload.
|
||||
* If the request fails then this will retry a small number of times.
|
||||
@@ -241,10 +253,7 @@ export async function uploadPayload(
|
||||
logger.info("Uploading results");
|
||||
|
||||
if (util.shouldSkipSarifUpload()) {
|
||||
const payloadSaveFile = path.join(
|
||||
actionsUtil.getTemporaryDirectory(),
|
||||
`payload-${analysis.kind}.json`,
|
||||
);
|
||||
const payloadSaveFile = getSavedPayloadPath(analysis.kind);
|
||||
logger.info(
|
||||
`SARIF upload disabled by an environment variable. Saving to ${payloadSaveFile}`,
|
||||
);
|
||||
@@ -598,6 +607,7 @@ export interface PostProcessingResults {
|
||||
export async function postProcessSarifFiles(
|
||||
logger: Logger,
|
||||
features: FeatureEnablement,
|
||||
config: ActionConfig<"codeQLCmd" | "tempDir"> | undefined,
|
||||
checkoutPath: string,
|
||||
sarifPaths: string[],
|
||||
category: string | undefined,
|
||||
@@ -622,6 +632,7 @@ export async function postProcessSarifFiles(
|
||||
gitHubVersion,
|
||||
features,
|
||||
logger,
|
||||
config,
|
||||
);
|
||||
} else {
|
||||
const sarifPath = sarifPaths[0];
|
||||
@@ -682,6 +693,7 @@ export async function writePostProcessedFiles(
|
||||
* to.
|
||||
*/
|
||||
export async function uploadFiles(
|
||||
config: ActionConfig<"codeQLCmd" | "tempDir"> | undefined,
|
||||
inputSarifPath: string,
|
||||
checkoutPath: string,
|
||||
category: string | undefined,
|
||||
@@ -696,6 +708,7 @@ export async function uploadFiles(
|
||||
|
||||
return uploadSpecifiedFiles(
|
||||
sarifPaths,
|
||||
config,
|
||||
checkoutPath,
|
||||
category,
|
||||
features,
|
||||
@@ -709,6 +722,7 @@ export async function uploadFiles(
|
||||
*/
|
||||
async function uploadSpecifiedFiles(
|
||||
sarifPaths: string[],
|
||||
config: ActionConfig<"codeQLCmd" | "tempDir"> | undefined,
|
||||
checkoutPath: string,
|
||||
category: string | undefined,
|
||||
features: FeatureEnablement,
|
||||
@@ -718,6 +732,7 @@ async function uploadSpecifiedFiles(
|
||||
const processingResults: PostProcessingResults = await postProcessSarifFiles(
|
||||
logger,
|
||||
features,
|
||||
config,
|
||||
checkoutPath,
|
||||
sarifPaths,
|
||||
category,
|
||||
@@ -856,6 +871,16 @@ export async function waitForProcessing(
|
||||
): Promise<void> {
|
||||
logger.startGroup("Waiting for processing to finish");
|
||||
try {
|
||||
// If SARIF upload is disabled and we reach this point, then the file was written to disk and
|
||||
// there is nothing to wait for. We still allow ourselves to get to this point so that we can
|
||||
// see in the log that we would have waited for processing to finish if the upload had happened.
|
||||
if (util.shouldSkipSarifUpload()) {
|
||||
logger.info(
|
||||
"Nothing to wait for, because SARIF upload is disabled by an environment variable and the SARIF was saved to disk.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const client = api.getApiClient();
|
||||
|
||||
// Do an initial wait because processing will always take a minimum of 2-3 seconds
|
||||
|
||||
@@ -5,6 +5,7 @@ import * as actionsUtil from "./actions-util";
|
||||
import { getActionVersion, getTemporaryDirectory } from "./actions-util";
|
||||
import * as analyses from "./analyses";
|
||||
import { getGitHubVersion } from "./api-client";
|
||||
import { getConfig } from "./config-utils";
|
||||
import { initFeatures } from "./feature-flags";
|
||||
import { Logger } from "./logging";
|
||||
import { getRepositoryNwo } from "./repository";
|
||||
@@ -66,11 +67,12 @@ async function run({ startedAt, logger }: ActionState<["Base", "Logger"]>) {
|
||||
// Make inputs accessible in the `post` step.
|
||||
actionsUtil.persistInputs();
|
||||
|
||||
const tempDir = getTemporaryDirectory();
|
||||
const repositoryNwo = getRepositoryNwo();
|
||||
const features = initFeatures(
|
||||
gitHubVersion,
|
||||
repositoryNwo,
|
||||
getTemporaryDirectory(),
|
||||
tempDir,
|
||||
logger,
|
||||
);
|
||||
|
||||
@@ -91,9 +93,13 @@ async function run({ startedAt, logger }: ActionState<["Base", "Logger"]>) {
|
||||
const checkoutPath = actionsUtil.getRequiredInput("checkout_path");
|
||||
const category = actionsUtil.getOptionalInput("category");
|
||||
|
||||
// Load the configuration from a previous step if there is one. This allows us to reuse the CLI that
|
||||
// may have been installed by a previous step.
|
||||
const maybeConfig = await getConfig(tempDir, logger);
|
||||
const uploadResults = await postProcessAndUploadSarif(
|
||||
logger,
|
||||
features,
|
||||
maybeConfig,
|
||||
"always",
|
||||
checkoutPath,
|
||||
sarifPath,
|
||||
|
||||
@@ -26,6 +26,7 @@ function mockPostProcessSarifFiles() {
|
||||
const analysisConfig = getAnalysisConfig(analysisKind);
|
||||
postProcessSarifFiles
|
||||
.withArgs(
|
||||
sinon.match.object,
|
||||
sinon.match.any,
|
||||
sinon.match.any,
|
||||
sinon.match.any,
|
||||
@@ -75,9 +76,11 @@ const postProcessAndUploadSarifMacro = makeMacro({
|
||||
fs.writeFileSync(sarifFile, "");
|
||||
}
|
||||
|
||||
const config = undefined;
|
||||
const actual = await postProcessAndUploadSarif(
|
||||
logger,
|
||||
features,
|
||||
config,
|
||||
"always",
|
||||
"",
|
||||
testPath,
|
||||
@@ -94,6 +97,7 @@ const postProcessAndUploadSarifMacro = makeMacro({
|
||||
postProcessSarifFiles.calledWith(
|
||||
logger,
|
||||
features,
|
||||
config,
|
||||
sinon.match.any,
|
||||
analysisKindResult.expectedFiles?.map(toFullPath) ??
|
||||
fullSarifPaths,
|
||||
@@ -223,6 +227,7 @@ test.serial(
|
||||
const actual = await postProcessAndUploadSarif(
|
||||
logger,
|
||||
features,
|
||||
undefined,
|
||||
"never",
|
||||
"",
|
||||
tempDir,
|
||||
@@ -253,6 +258,7 @@ test.serial(
|
||||
const actual = await postProcessAndUploadSarif(
|
||||
logger,
|
||||
features,
|
||||
undefined,
|
||||
"never",
|
||||
"",
|
||||
tempDir,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { UploadKind } from "./actions-util";
|
||||
import * as analyses from "./analyses";
|
||||
import type { ActionConfig } from "./config/action-config";
|
||||
import { FeatureEnablement } from "./feature-flags";
|
||||
import { Logger } from "./logging";
|
||||
import * as upload_lib from "./upload-lib";
|
||||
@@ -26,6 +27,7 @@ export type UploadSarifResults = Partial<
|
||||
export async function postProcessAndUploadSarif(
|
||||
logger: Logger,
|
||||
features: FeatureEnablement,
|
||||
config: ActionConfig<"codeQLCmd" | "tempDir"> | undefined,
|
||||
uploadKind: UploadKind,
|
||||
checkoutPath: string,
|
||||
sarifPath: string,
|
||||
@@ -45,6 +47,7 @@ export async function postProcessAndUploadSarif(
|
||||
const postProcessingResults = await upload_lib.postProcessSarifFiles(
|
||||
logger,
|
||||
features,
|
||||
config,
|
||||
checkoutPath,
|
||||
sarifFiles,
|
||||
category,
|
||||
|
||||
129
src/util.test.ts
129
src/util.test.ts
@@ -11,7 +11,7 @@ import * as sinon from "sinon";
|
||||
import * as api from "./api-client";
|
||||
import { EnvVar } from "./environment";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import { setupTests } from "./testing-utils";
|
||||
import { getTestEnv, setupTests } from "./testing-utils";
|
||||
import * as util from "./util";
|
||||
|
||||
setupTests(test);
|
||||
@@ -164,66 +164,6 @@ test.serial("getExtraOptionsEnvParam() fails on invalid JSON", (t) => {
|
||||
process.env.CODEQL_ACTION_EXTRA_OPTIONS = origExtraOptions;
|
||||
});
|
||||
|
||||
test("parseGitHubUrl", (t) => {
|
||||
t.deepEqual(util.parseGitHubUrl("github.com"), "https://github.com");
|
||||
t.deepEqual(util.parseGitHubUrl("https://github.com"), "https://github.com");
|
||||
t.deepEqual(
|
||||
util.parseGitHubUrl("https://api.github.com"),
|
||||
"https://github.com",
|
||||
);
|
||||
t.deepEqual(
|
||||
util.parseGitHubUrl("https://github.com/foo/bar"),
|
||||
"https://github.com",
|
||||
);
|
||||
|
||||
t.deepEqual(
|
||||
util.parseGitHubUrl("github.example.com"),
|
||||
"https://github.example.com/",
|
||||
);
|
||||
t.deepEqual(
|
||||
util.parseGitHubUrl("https://github.example.com"),
|
||||
"https://github.example.com/",
|
||||
);
|
||||
t.deepEqual(
|
||||
util.parseGitHubUrl("https://api.github.example.com"),
|
||||
"https://github.example.com/",
|
||||
);
|
||||
t.deepEqual(
|
||||
util.parseGitHubUrl("https://github.example.com/api/v3"),
|
||||
"https://github.example.com/",
|
||||
);
|
||||
t.deepEqual(
|
||||
util.parseGitHubUrl("https://github.example.com:1234"),
|
||||
"https://github.example.com:1234/",
|
||||
);
|
||||
t.deepEqual(
|
||||
util.parseGitHubUrl("https://api.github.example.com:1234"),
|
||||
"https://github.example.com:1234/",
|
||||
);
|
||||
t.deepEqual(
|
||||
util.parseGitHubUrl("https://github.example.com:1234/api/v3"),
|
||||
"https://github.example.com:1234/",
|
||||
);
|
||||
t.deepEqual(
|
||||
util.parseGitHubUrl("https://github.example.com/base/path"),
|
||||
"https://github.example.com/base/path/",
|
||||
);
|
||||
t.deepEqual(
|
||||
util.parseGitHubUrl("https://github.example.com/base/path/api/v3"),
|
||||
"https://github.example.com/base/path/",
|
||||
);
|
||||
|
||||
t.throws(() => util.parseGitHubUrl(""), {
|
||||
message: '"" is not a valid URL',
|
||||
});
|
||||
t.throws(() => util.parseGitHubUrl("ssh://github.com"), {
|
||||
message: '"ssh://github.com" is not a http or https URL',
|
||||
});
|
||||
t.throws(() => util.parseGitHubUrl("http:///::::433"), {
|
||||
message: '"http:///::::433" is not a valid URL',
|
||||
});
|
||||
});
|
||||
|
||||
test("allowed API versions", async (t) => {
|
||||
t.is(util.apiVersionInRange("1.33.0", "1.33", "2.0"), undefined);
|
||||
t.is(util.apiVersionInRange("1.33.1", "1.33", "2.0"), undefined);
|
||||
@@ -553,3 +493,70 @@ test("Failure.orElse returns the default value for a failure result", (t) => {
|
||||
const result = new util.Failure(new Error("test error"));
|
||||
t.is(result.orElse("default value"), "default value");
|
||||
});
|
||||
|
||||
test("shouldSkipGenerateFailedSarif", (t) => {
|
||||
const env = getTestEnv();
|
||||
|
||||
// Don't skip by default
|
||||
t.is(util.shouldSkipGenerateFailedSarif(env), false);
|
||||
|
||||
// Skip in test mode
|
||||
env.setAll({
|
||||
[EnvVar.TEST_MODE]: "true",
|
||||
[EnvVar.SKIP_SARIF_UPLOAD]: undefined,
|
||||
[EnvVar.SKIP_GENERATE_FAILED_SARIF]: undefined,
|
||||
});
|
||||
t.is(util.shouldSkipGenerateFailedSarif(env), true);
|
||||
|
||||
// Skip if explicitly requested
|
||||
env.setAll({
|
||||
[EnvVar.TEST_MODE]: undefined,
|
||||
[EnvVar.SKIP_SARIF_UPLOAD]: undefined,
|
||||
[EnvVar.SKIP_GENERATE_FAILED_SARIF]: "true",
|
||||
});
|
||||
t.is(util.shouldSkipGenerateFailedSarif(env), true);
|
||||
|
||||
// Skip if not uploading SARIF
|
||||
env.setAll({
|
||||
[EnvVar.TEST_MODE]: undefined,
|
||||
[EnvVar.SKIP_SARIF_UPLOAD]: "true",
|
||||
[EnvVar.SKIP_GENERATE_FAILED_SARIF]: undefined,
|
||||
});
|
||||
t.is(util.shouldSkipGenerateFailedSarif(env), true);
|
||||
|
||||
// Don't skip if explicitly requested
|
||||
env.setAll({
|
||||
[EnvVar.TEST_MODE]: "true",
|
||||
[EnvVar.SKIP_SARIF_UPLOAD]: "true",
|
||||
[EnvVar.SKIP_GENERATE_FAILED_SARIF]: "false",
|
||||
});
|
||||
t.is(util.shouldSkipGenerateFailedSarif(env), false);
|
||||
});
|
||||
|
||||
test("shouldSkipSarifUpload", (t) => {
|
||||
const env = getTestEnv();
|
||||
|
||||
// Don't skip by default
|
||||
t.is(util.shouldSkipSarifUpload(env), false);
|
||||
|
||||
// Skip in test mode
|
||||
env.setAll({
|
||||
[EnvVar.TEST_MODE]: "true",
|
||||
[EnvVar.SKIP_SARIF_UPLOAD]: undefined,
|
||||
});
|
||||
t.is(util.shouldSkipSarifUpload(env), true);
|
||||
|
||||
// Skip if explicitly requested
|
||||
env.setAll({
|
||||
[EnvVar.TEST_MODE]: undefined,
|
||||
[EnvVar.SKIP_SARIF_UPLOAD]: "true",
|
||||
});
|
||||
t.is(util.shouldSkipSarifUpload(env), true);
|
||||
|
||||
// Don't skip if explicitly requested
|
||||
env.setAll({
|
||||
[EnvVar.TEST_MODE]: "true",
|
||||
[EnvVar.SKIP_SARIF_UPLOAD]: "false",
|
||||
});
|
||||
t.is(util.shouldSkipSarifUpload(env), false);
|
||||
});
|
||||
|
||||
73
src/util.ts
73
src/util.ts
@@ -18,10 +18,10 @@ import type { Config } from "./config-utils";
|
||||
import {
|
||||
EnvVar,
|
||||
getRequiredEnvParam,
|
||||
isInTestMode,
|
||||
exportEnvVar,
|
||||
getEnv,
|
||||
type Env,
|
||||
type ReadOnlyEnv,
|
||||
} from "./environment";
|
||||
import * as json from "./json";
|
||||
import { Language } from "./languages";
|
||||
@@ -50,11 +50,6 @@ const BROKEN_VERSIONS = ["0.0.0-20211207"];
|
||||
/** A string representing a GitHub instance. */
|
||||
export type InstanceURL = string;
|
||||
|
||||
/**
|
||||
* The URL for github.com.
|
||||
*/
|
||||
export const GITHUB_DOTCOM_URL: InstanceURL = "https://github.com";
|
||||
|
||||
/**
|
||||
* Default name of the debugging artifact.
|
||||
*/
|
||||
@@ -470,49 +465,6 @@ export function getGeneratedSuitePath(config: Config, language: Language) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses user input of a github.com or GHES URL to a canonical form.
|
||||
* Removes any API prefix or suffix if one is present.
|
||||
*/
|
||||
export function parseGitHubUrl(inputUrl: string): string {
|
||||
const originalUrl = inputUrl;
|
||||
if (inputUrl.indexOf("://") === -1) {
|
||||
inputUrl = `https://${inputUrl}`;
|
||||
}
|
||||
if (!inputUrl.startsWith("http://") && !inputUrl.startsWith("https://")) {
|
||||
throw new ConfigurationError(`"${originalUrl}" is not a http or https URL`);
|
||||
}
|
||||
|
||||
let url: URL;
|
||||
try {
|
||||
url = new URL(inputUrl);
|
||||
} catch {
|
||||
throw new ConfigurationError(`"${originalUrl}" is not a valid URL`);
|
||||
}
|
||||
|
||||
// If we detect this is trying to be to github.com
|
||||
// then return with a fixed canonical URL.
|
||||
if (url.hostname === "github.com" || url.hostname === "api.github.com") {
|
||||
return GITHUB_DOTCOM_URL;
|
||||
}
|
||||
|
||||
// Remove the API prefix if it's present
|
||||
if (url.pathname.indexOf("/api/v3") !== -1) {
|
||||
url.pathname = url.pathname.substring(0, url.pathname.indexOf("/api/v3"));
|
||||
}
|
||||
// Also consider subdomain isolation on GHES
|
||||
if (url.hostname.startsWith("api.")) {
|
||||
url.hostname = url.hostname.substring(4);
|
||||
}
|
||||
|
||||
// Normalise path to having a trailing slash for consistency
|
||||
if (!url.pathname.endsWith("/")) {
|
||||
url.pathname = `${url.pathname}/`;
|
||||
}
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
const CODEQL_ACTION_WARNED_ABOUT_VERSION_ENV_VAR =
|
||||
"CODEQL_ACTION_WARNED_ABOUT_VERSION";
|
||||
|
||||
@@ -732,11 +684,30 @@ export function isGoodVersion(versionSpec: string) {
|
||||
return !BROKEN_VERSIONS.includes(versionSpec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether we specifically want to skip generating a failed SARIF.
|
||||
*/
|
||||
export function shouldSkipGenerateFailedSarif(
|
||||
env: ReadOnlyEnv = getEnv(),
|
||||
): boolean {
|
||||
const skipGenerateFailedSarif = env.getOptional(
|
||||
EnvVar.SKIP_GENERATE_FAILED_SARIF,
|
||||
);
|
||||
return (
|
||||
skipGenerateFailedSarif !== "false" &&
|
||||
(shouldSkipSarifUpload(env) || skipGenerateFailedSarif === "true")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether we specifically want to skip uploading SARIF files.
|
||||
*/
|
||||
export function shouldSkipSarifUpload(): boolean {
|
||||
return isInTestMode() || process.env[EnvVar.SKIP_SARIF_UPLOAD] === "true";
|
||||
export function shouldSkipSarifUpload(env: ReadOnlyEnv = getEnv()): boolean {
|
||||
const skipSarifUpload = env.getOptional(EnvVar.SKIP_SARIF_UPLOAD);
|
||||
return (
|
||||
skipSarifUpload !== "false" &&
|
||||
(env.isSkippingUploadsInTests() || skipSarifUpload === "true")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,6 +2,16 @@
|
||||
|
||||
Change-notes are Markdown files used to document user-facing changes. When making a change that affects users, create a Markdown file here that describes the change. During the next release, the change-note files in `unreleased-change-notes/` will automatically be added to `CHANGELOG.md`.
|
||||
|
||||
### What do document
|
||||
|
||||
Some guidelines to help you decide whether a change needs a change note:
|
||||
|
||||
- New functionality that is gated behind a feature flag should be documented when it is added, highlighting that the feature will only be rolled out later. Ideally, this should also include an estimate for when the feature is expected to become available.
|
||||
- When a feature flag is removed and the gated functionality becomes the default behaviour, this does not need to be documented.
|
||||
- Changes that may require users to make changes to their workflows must be documented.
|
||||
|
||||
If in doubt, see `CHANGELOG.md` for what we have included previously.
|
||||
|
||||
### Change-note file format
|
||||
|
||||
Change-note files must follow a certain format so that they can be automatically validated and processed. Failure to follow the format will result in a failed PR check.
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
- The `codeql-action/analyze` step now validates that a correct argument for the `checkout_path` input has been provided. The `checkout_path` input must be set to the path where the repository that is being analyzed is checked out at on the runner, if that path is something other than the workspace root. If the path does not point to a repository checkout or is different from the path that was used by `codeql-action/init`, then the CodeQL Action now produces a warning. [#4148](https://github.com/github/codeql-action/pull/4148)
|
||||
@@ -1 +0,0 @@
|
||||
- Resolved an issue with third-party caching implementations that prevented caches from being restored correctly. [#4206](https://github.com/github/codeql-action/pull/4206)
|
||||
@@ -1 +0,0 @@
|
||||
- The `codeql-action/start-proxy` step that is used in Code Scanning Default Setup now finds CodeQL releases using the same logic as `codeql-action/init`. This primarily benefits customers who are using GitHub instances other than `github.com` where the `codeql-action/start-proxy` step may previously have been unable to locate recent releases and ended up using outdated versions of the private registry authentication proxy as a result. [#4198](https://github.com/github/codeql-action/pull/4198)
|
||||
Reference in New Issue
Block a user