Compare commits

...

119 Commits

Author SHA1 Message Date
David Verdeguer 1e83c72db7 pre-hook simple try 2020-07-31 14:30:35 +02:00
Chris Gavin e8896a906a Merge pull request #123 from github/search-for-bundle
Search for the CodeQL bundle in multiple places.
2020-07-30 10:56:03 +01:00
Chris Gavin f5ccce0c86 Merge branch 'main' into search-for-bundle 2020-07-30 10:35:09 +01:00
Robert 74268130c6 Merge pull request #116 from github/status_reports_v2
Upload much more data in status reports
2020-07-27 15:58:17 +01:00
Robert ae2d7afe3b Merge branch 'main' into status_reports_v2 2020-07-27 14:58:04 +01:00
Robert 12a37237d2 Merge pull request #124 from github/lodash_update
Update lodash
2020-07-27 14:57:28 +01:00
Chris Gavin 5587e128ff Add a missing space. 2020-07-27 11:06:36 +01:00
Chris Gavin 74b4d8a6db Remove DEFAULT_ from some constant names. 2020-07-27 11:04:49 +01:00
Chris Gavin 175d681835 Factor out reimplementation of toolcache download into its own method. 2020-07-27 11:02:37 +01:00
Chris Gavin f4e72f4a09 Add a comment explaining an edge-case. 2020-07-27 10:51:51 +01:00
Chris Gavin 5f057318b6 Fix a typo. 2020-07-27 10:48:00 +01:00
Chris Gavin 0f88c0111f Change INSTANCE_API_URL constant to a method. 2020-07-27 10:47:45 +01:00
Chris Gavin bfaa0cf943 Restructure code to remove an error that can never be thrown. 2020-07-27 10:43:03 +01:00
Chris Gavin 337dbe5618 Remove an unneeded check. 2020-07-27 10:41:39 +01:00
Robert 6f42543a85 update lodash 2020-07-24 16:50:31 +01:00
Andrew Eisenberg 93dd64d351 Use templated strings for better readability 2020-07-24 08:29:04 -07:00
Robert 87758a1402 Upload much more data in status reports 2020-07-24 15:01:44 +01:00
Chris Gavin 813cb0479f Search for the CodeQL bundle in multiple places. 2020-07-24 14:24:44 +01:00
Robert 9769e4a6df Merge pull request #120 from github/default_queries_fix
Default queries fix + reset env vars in tests
2020-07-21 13:11:49 +01:00
Robert 315a9f4b3c fix typo 2020-07-21 12:46:19 +01:00
Robert 0446cb0aff address comments 2020-07-21 12:19:37 +01:00
Robert 29cf06569d reset environment variables between tests 2020-07-21 11:39:55 +01:00
Robert ee63f4ee4b fix parsing of disable-default-queries field 2020-07-21 11:05:23 +01:00
Robert 5b4f4e40af introduce tests 2020-07-21 10:53:14 +01:00
Robert 58a0034549 Merge pull request #112 from github/undeclared-action-input
Fix undeclared action inputs
2020-07-20 09:30:31 +01:00
Robert Brignull c7c1aa8045 fix undeclared action inputs 2020-07-16 14:54:15 +01:00
Robert b673c57b89 Merge pull request #109 from github/codeql_search_path
Move config parsing earlier + add to codeql search path
2020-07-16 09:17:08 +01:00
Robert Brignull d138b00811 await promise 2020-07-16 09:04:16 +01:00
Robert Brignull b86c3701ed address review comments 2020-07-15 17:37:37 +01:00
Robert Brignull 7bb6ac6c60 Merge branch 'main' into codeql_search_path 2020-07-15 17:14:38 +01:00
Sam Partington d1d80761ef Merge pull request #111 from github/vscode-debugging
Add support for debugging from VSCode
2020-07-14 10:32:08 +01:00
Sam Partington 7a78ec0a54 Add support for debugging from VSCode
https://github.com/avajs/ava/blob/master/docs/recipes/debugging-with-vscode.md recommends passing the `debug` option to Ava.  However, in my experience Ava waits indefinitely when this option is used, without actually running any tests.

The increased timeout setting is so that tests won't fail prematurely because you're slowly stepping through code.
2020-07-14 09:49:21 +01:00
Robert Brignull da3d6d25eb move config parsing earlier + add to codeql search path 2020-07-13 14:49:01 +01:00
Robert c3dcf26eaf Merge pull request #103 from github/lgtm_filters
Add support for LGTM_INDEX_FILTERS
2020-07-10 15:47:16 +01:00
Robert Brignull 189b6ef4bf ban / as a path 2020-07-10 15:33:03 +01:00
Robert Brignull 1a4c658bbf Merge branch 'main' into lgtm_filters 2020-07-10 15:01:22 +01:00
Robert ec154779ac Merge pull request #100 from github/github_proxy
Use @actions/github as a wrapper around octokit in order to support proxies
2020-07-10 14:37:22 +01:00
Robert Brignull ca775cfb2e remove bypass-proxy test 2020-07-10 12:58:18 +01:00
Robert Brignull fb9f2af49f adjust comments 2020-07-10 11:08:29 +01:00
Robert Brignull 60126bfb39 ban backslashes 2020-07-09 18:27:46 +01:00
Robert Brignull 24367a89b5 move checking to when env vars are constructed 2020-07-09 18:05:54 +01:00
Robert Brignull 70980b9f32 Make characters completely illegal on windows 2020-07-09 17:40:26 +01:00
Robert Brignull bf5b437adb output better errors 2020-07-09 17:01:53 +01:00
Robert Brignull b6efd2e6de Merge remote-tracking branch 'origin/main' into github_proxy 2020-07-09 16:37:52 +01:00
Robert Brignull 8a6b404471 add tests 2020-07-09 16:36:49 +01:00
Robert Brignull d781c667b1 Merge branch 'main' into lgtm_filters 2020-07-09 10:30:20 +01:00
Robert Brignull 56417be251 filter ** paths 2020-07-08 16:33:00 +01:00
Robert Brignull abf6f239fa trim leading slashes 2020-07-08 16:22:29 +01:00
Robert Brignull 9fb69dda17 clarify error slightly more 2020-07-08 15:11:29 +01:00
Robert Brignull dcebdd6441 give better error messages 2020-07-08 15:06:45 +01:00
Robert 56e74b9096 Merge pull request #102 from github/exclude-lib
Exclude lib directory from analysis
2020-07-08 10:32:12 +01:00
Robert Brignull 13ee335beb Merge remote-tracking branch 'origin/main' into github_proxy 2020-07-07 18:34:09 +01:00
Robert Brignull 07caa0f5cf pull out mockGetContents method 2020-07-07 18:32:29 +01:00
Robert Brignull f77ab09bf4 add sinon types 2020-07-07 18:32:18 +01:00
Robert 8d908eeab3 Update codeql-config.yml 2020-07-07 13:09:32 +01:00
Robert cfcff89771 Merge pull request #101 from github/workflow_dispatch
Add workflow_dispatch event
2020-07-07 11:39:47 +01:00
Robert Brignull fe3dbb7e64 Add support for LGTM_INDEX_FILTERS 2020-07-07 11:37:56 +01:00
Robert 1aeb7665e7 Add workflow_dispatch event 2020-07-07 10:24:41 +01:00
Robert Brignull 0086c2ecdb use @actions/github 2020-07-06 16:25:26 +01:00
Robert 9da537eb33 Merge pull request #93 from github/codeql_mock
Introduce wrapper around codeql
2020-07-06 10:38:41 +01:00
Robert Brignull 5ab09ae291 Make parameter names more descriptive 2020-07-02 14:45:14 +01:00
Robert Brignull c41d287cae Introduce wrapper around codeql 2020-07-02 14:25:08 +01:00
Sam Partington 8947510a57 Merge pull request #96 from github/pr-template-test-builds
Remove test builds section of PR template
2020-07-02 14:23:33 +01:00
Sam Partington 5d84e87b3d Merge branch 'main' into pr-template-test-builds 2020-07-02 14:11:18 +01:00
Sam Partington 9bc459c5f1 Merge pull request #95 from github/merge-help
Add details of who will merge (CONTRIBUTING.md)
2020-07-02 13:46:52 +01:00
Sam Partington 77e9a735f6 Merge branch 'main' into merge-help 2020-07-02 13:42:20 +01:00
Sam Partington 57a57713c3 Remove test builds section of PR template
This is covered by the CI checks now
2020-07-02 13:37:46 +01:00
Sam Partington a0bf50cb7b Add details of who will merge (CONTRIBUTING.md) 2020-07-02 13:35:29 +01:00
Robert 72803c4251 Merge pull request #90 from github/analysis_key_status
add analysis_key to status reports
2020-07-02 11:48:14 +01:00
Robert eaf6649611 Merge branch 'main' into analysis_key_status 2020-07-02 11:34:08 +01:00
Sam Partington 55a6f9e0a8 Merge pull request #94 from github/developer-docs
Add development instructions to CONTRIBUTING.md
2020-07-02 11:07:06 +01:00
Sam Partington dfed1f7eea Point people at init/action.yml for node version
https://github.com/github/codeql-action/pull/94#discussion_r448502825
2020-07-02 10:55:00 +01:00
Sam Partington 580e603e94 Add development instructions to CONTRIBUTING.md 2020-07-01 14:32:04 +01:00
Robert de7ff148e5 Merge pull request #91 from github/codeql-bundle-20200630
Update codeql bundle
2020-07-01 09:55:41 +01:00
Robert 480467971e Merge branch 'main' into codeql-bundle-20200630 2020-06-30 16:03:24 +01:00
Sam Partington e2a8f32427 Merge pull request #87 from github/support-remote-config
Allow config files to be from a remote repository
2020-06-30 15:19:23 +01:00
Sam Partington 260a93fe06 Merge branch 'main' into support-remote-config 2020-06-30 14:57:35 +01:00
Sam Partington dc2678801a Use right SHA in integration test
https://github.com/github/codeql-action/commit/1bb294af6b763c1da9d0d010aa587eeb3b4cdb5c#r40266717
2020-06-30 14:37:27 +01:00
Sam Partington c953f77bb6 Merge pull request #92 from github/use-checkout-v2
Use v2 of checkout action
2020-06-30 14:33:21 +01:00
Sam Partington aa6c2c5bda Fix URL in integration test
https://github.com/github/codeql-action/runs/822529558?check_suite_focus=true#step:4:43
2020-06-30 14:32:36 +01:00
Sam Partington a52f1a55ed Fix workflow name 2020-06-30 14:17:37 +01:00
Sam Partington 1bb294af6b Combine integration tests for simplicity
https://github.com/github/codeql-action/pull/87#issuecomment-651757413
2020-06-30 14:15:51 +01:00
Sam Partington 25a0a6baed Use v2 of checkout action 2020-06-30 14:11:08 +01:00
Sam Partington 4b37db72e4 Add integration test for retrieval of remote config
https://github.com/github/codeql-action/pull/87#pullrequestreview-439357075
2020-06-30 12:22:08 +01:00
Robert 04b2540e30 Update codeql bundle 2020-06-30 11:39:56 +01:00
Robert e0299c3c04 Merge pull request #88 from github/fail-fast
Remove `strategy: fail-fast`
2020-06-29 12:49:28 +01:00
Robert Brignull 0e3f8311ed add analysis_key to status reports 2020-06-29 11:24:04 +01:00
Sam Partington ca76a2ca94 Add .DS_Store files from node module
https://github.com/github/codeql-action/pull/87/checks?check_run_id=811678038
2020-06-29 09:18:18 +01:00
Robert aad14bf2cb Remove strategy: fail-fast 2020-06-29 08:49:16 +01:00
Robin Neatherway a08742f199 Merge pull request #86 from github/correct-test-description
Correct copy/pasted test description
2020-06-26 18:41:17 +01:00
Sam Partington 6afe41036b Update node modules
https://github.com/github/codeql-action/pull/87/checks?check_run_id=811666672
2020-06-26 16:27:11 +01:00
Sam Partington ee4cc86b19 Merge branch 'main' into support-remote-config 2020-06-26 16:14:32 +01:00
Sam Partington 0607771cc2 Make remote config retrieval more robust 2020-06-26 16:14:15 +01:00
Robin Neatherway 151d531bd0 Correct copy/pasted test description 2020-06-26 16:01:59 +01:00
Sam Partington 51becd2cf8 Bring error message method name inline with others 2020-06-26 15:44:57 +01:00
Sam Partington a66f2b0b11 Remove redundant comment 2020-06-26 15:44:13 +01:00
Robin Neatherway 504c8cfc6f Merge pull request #85 from github/non-empty-env-vars
getRequiredEnvParams must be non-empty
2020-06-26 15:43:55 +01:00
Robin Neatherway a0d4330434 Simplify singleton tests by removing the loop 2020-06-26 15:25:32 +01:00
Sam Partington 7c00663f08 Remove tests of internal function now its behaviour has been verified 2020-06-26 14:46:53 +01:00
Sam Partington f8c87948ab Use new API client
From https://github.com/github/codeql-action/pull/82
2020-06-26 14:45:37 +01:00
Sam Partington 9566d8c220 Merge branch 'main' into support-remote-config 2020-06-26 13:48:59 +01:00
Sam Partington 366d8a32d1 Retrieve remote configuration where specified 2020-06-26 11:49:00 +01:00
Robin Neatherway bb9ed79f3d getRequiredEnvParams must be non-empty 2020-06-26 11:44:17 +01:00
Chris Gavin 17548064f9 Merge pull request #82 from github/octokit
Use a single Octokit client for everything rather than a bunch of Octokits and an HTTP client.
2020-06-26 11:03:01 +01:00
Chris Gavin ef507971e7 Merge branch 'main' into octokit 2020-06-26 10:29:51 +01:00
Robert 96d02d50f7 Merge pull request #84 from github/remove_git_dependency
Fall back to GITHUB_SHA env var if git is not available
2020-06-25 16:55:32 +01:00
Robert Brignull 0fdc2c71e4 fall back to GITHUB_SHA env var if git is not available 2020-06-25 15:53:24 +01:00
Robert 28944b580b Merge pull request #83 from github/aborted_completed_at
Include completed_at when action is aborted
2020-06-25 15:11:12 +01:00
Sam Partington 388403b46e Add a test that an Octokit request is made for remote config 2020-06-25 15:10:17 +01:00
Sam Partington 32c9898fa4 Fix regex 2020-06-25 15:10:17 +01:00
Sam Partington 56292b1fa3 Reset mocks in test so they don't leak into later test 2020-06-25 15:10:17 +01:00
Robert Brignull 50a2815790 Include completed_at when action is aborted 2020-06-25 14:10:28 +01:00
Sam Partington a19d19e0a3 Add validation of remote config location, no retrieval yet 2020-06-25 13:21:46 +01:00
Sam Partington 153a598a97 Add a new function used to determine where to retrieve config from 2020-06-24 16:15:25 +01:00
Sam Partington f4cf65ca2d Add a test that Octokit isn't used for local config 2020-06-24 14:42:29 +01:00
Sam Partington b0af5695e6 Add sinon package for mocking 2020-06-24 14:34:08 +01:00
Sam Partington 43c1bea680 Run npm install so have clear baseline 2020-06-24 14:33:14 +01:00
Chris Gavin 464ce1b43a Manually convert response.data to a JSON string. 2020-06-24 13:50:52 +01:00
Chris Gavin 74c48f71fa Use a single Octokit client for everything rather than a bunch of Octokits and an HTTP client. 2020-06-23 21:40:42 +01:00
442 changed files with 186238 additions and 7754 deletions
+1
View File
@@ -11,3 +11,4 @@ queries:
- uses: security-and-quality - uses: security-and-quality
paths-ignore: paths-ignore:
- tests - tests
- lib
-3
View File
@@ -1,7 +1,4 @@
### Merge / deployment checklist ### Merge / deployment checklist
- Run test builds as necessary. Can be on this repository or elsewhere as needed in order to test the change - please include links to tests in other repos!
- [ ] CodeQL using init/analyze actions
- [ ] 3rd party tool using upload action
- [ ] Confirm this change is backwards compatible with existing workflows. - [ ] Confirm this change is backwards compatible with existing workflows.
- [ ] Confirm the [readme](https://github.com/github/codeql-action/blob/master/README.md) has been updated if necessary. - [ ] Confirm the [readme](https://github.com/github/codeql-action/blob/master/README.md) has been updated if necessary.
+2 -3
View File
@@ -10,7 +10,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v2
with: with:
# Must fetch at least the immediate parents so that if this is # Must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head of the pull request. # a pull request then we can checkout the head of the pull request.
@@ -21,8 +21,7 @@ jobs:
- run: git checkout HEAD^2 - run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }} if: ${{ github.event_name == 'pull_request' }}
- uses: ./init - uses: github/codeql-action/full@pre-hook
with: with:
languages: javascript languages: javascript
config-file: ./.github/codeql/codeql-config.yml config-file: ./.github/codeql/codeql-config.yml
- uses: ./analyze
+29 -3
View File
@@ -36,7 +36,7 @@ jobs:
exit 1 exit 1
fi fi
multi-language-repo_test-custom-queries: multi-language-repo_test-custom-queries-and-remote-config:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -54,7 +54,7 @@ jobs:
- uses: ./../action/init - uses: ./../action/init
with: with:
languages: cpp,csharp,java,javascript,python languages: cpp,csharp,java,javascript,python
config-file: ./.github/codeql/custom-queries.yml config-file: github/codeql-action/tests/multi-language-repo/.github/codeql/custom-queries.yml@${{ github.sha }}
- name: Build code - name: Build code
shell: bash shell: bash
run: ./build.sh run: ./build.sh
@@ -93,7 +93,6 @@ jobs:
env: env:
TEST_MODE: true TEST_MODE: true
multi-language-repo_rubocop: multi-language-repo_rubocop:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -124,3 +123,30 @@ jobs:
sarif_file: rubocop.sarif sarif_file: rubocop.sarif
env: env:
TEST_MODE: true TEST_MODE: true
test-proxy:
runs-on: ubuntu-latest
container:
image: ubuntu:18.04
options: --dns 127.0.0.1
services:
squid-proxy:
image: datadog/squid:latest
ports:
- 3128:3128
env:
https_proxy: http://squid-proxy:3128
steps:
- uses: actions/checkout@v2
- name: Move codeql-action
shell: bash
run: |
mkdir ../action
mv * .github ../action/
mv ../action/tests/multi-language-repo/{*,.github} .
- uses: ./../action/init
with:
languages: javascript
- uses: ./../action/analyze
env:
TEST_MODE: true
@@ -7,6 +7,7 @@ on:
# curl -H "Authorization: Bearer <token>" -X POST https://api.github.com/repos/github/codeql-action/dispatches -d '{"event_type":"update-release-branch"}' # curl -H "Authorization: Bearer <token>" -X POST https://api.github.com/repos/github/codeql-action/dispatches -d '{"event_type":"update-release-branch"}'
# Replace <token> with a personal access token from this page: https://github.com/settings/tokens # Replace <token> with a personal access token from this page: https://github.com/settings/tokens
types: [update-release-branch] types: [update-release-branch]
workflow_dispatch:
jobs: jobs:
update: update:
+25
View File
@@ -0,0 +1,25 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug AVA test file",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/ava",
"runtimeArgs": [
"${file}",
"--break",
"--serial",
"--timeout=20m"
],
"port": 9229,
"outputCapture": "std",
"skipFiles": [
"<node_internals>/**/*.js"
]
}
]
}
+24 -2
View File
@@ -1,4 +1,4 @@
## Contributing # Contributing
[fork]: https://github.com/github/codeql-action/fork [fork]: https://github.com/github/codeql-action/fork
[pr]: https://github.com/github/codeql-action/compare [pr]: https://github.com/github/codeql-action/compare
@@ -10,13 +10,35 @@ Contributions to this project are [released](https://help.github.com/articles/gi
Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms. Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms.
## Development and Testing
Before you start, ensure that you have a recent version of node installed. You can see which version of node is used by the action in `init/action.yml`.
### Common tasks
* Transpile the TypeScript to JavaScript: `npm run build`. Note that the JavaScript files are committed to git.
* Run tests: `npm run test`. Youll need to ensure that the JavaScript files are up-to-date first by running the command above.
* Run the linter: `npm run lint`.
This project also includes configuration to run tests from VSCode (with support for breakpoints) - open the test file you wish to run and choose "Debug AVA test file" from the Run menu in the Run panel.
### Running the action
To see the effect of your changes and to test them, push your changes in a branch and then look at the [Actions output](https://github.com/github/codeql-action/actions) for that branch. You can also exercise the code locally by running the automated tests.
### Integration tests
As well as the unit tests (see _Common tasks_ above), there are integration tests, defined in `.github/workflows/integration-testing.yml`. These are run by a CI check. Depending on the change youre making, you may want to add a test to this file or extend an existing one.
## Submitting a pull request ## Submitting a pull request
1. [Fork][fork] and clone the repository 1. [Fork][fork] and clone the repository
2. Create a new branch: `git checkout -b my-branch-name` 2. Create a new branch: `git checkout -b my-branch-name`
3. Make your change, add tests, and make sure the tests still pass 3. Make your change, add tests, and make sure the tests still pass
4. Push to your fork and [submit a pull request][pr] 4. Push to your fork and [submit a pull request][pr]
5. Pat your self on the back and wait for your pull request to be reviewed and merged. 5. Pat yourself on the back and wait for your pull request to be reviewed and merged.
If you're a GitHub staff member, you can merge your own PR once it's approved; for external contributors, GitHub staff will merge your PR once it's approved.
Here are a few things you can do that will increase the likelihood of your pull request being accepted: Here are a few things you can do that will increase the likelihood of your pull request being accepted:
-4
View File
@@ -26,10 +26,6 @@ on:
jobs: jobs:
CodeQL-Build: CodeQL-Build:
strategy:
fail-fast: false
# CodeQL runs on ubuntu-latest, windows-latest, and macos-latest # CodeQL runs on ubuntu-latest, windows-latest, and macos-latest
runs-on: ubuntu-latest runs-on: ubuntu-latest
+4
View File
@@ -20,6 +20,10 @@ inputs:
description: The number of threads to be used by CodeQL. description: The number of threads to be used by CodeQL.
required: false required: false
default: "1" default: "1"
checkout_path:
description: "The path at which the analyzed repository was checked out. Used to relativeize any absolute paths in the uploaded SARIF file."
required: false
default: ${{ github.workspace }}
token: token:
default: ${{ github.token }} default: ${{ github.token }}
matrix: matrix:
+45
View File
@@ -0,0 +1,45 @@
name: 'CodeQL'
description: 'Setup the CodeQL tracer'
author: 'GitHub'
inputs:
tools:
description: URL of CodeQL tools
required: false
# If not specified the Action will check in several places until it finds the CodeQL tools.
languages:
description: The languages to be analysed
required: false
token:
default: ${{ github.token }}
matrix:
default: ${{ toJson(matrix) }}
config-file:
description: Path of the config file to use
required: false
check_name:
description: The name of the check run to add text to.
required: false
output:
description: The path of the directory in which to save the SARIF results
required: false
default: '../results'
upload:
description: Upload the SARIF file
required: false
default: "true"
ram:
description: Override the amount of memory in MB to be used by CodeQL. By default, almost all the memory of the machine is used.
required: false
threads:
description: The number of threads to be used by CodeQL.
required: false
default: "1"
checkout_path:
description: "The path at which the analyzed repository was checked out. Used to relativeize any absolute paths in the uploaded SARIF file."
required: false
default: ${{ github.workspace }}
runs:
using: 'node12'
pre: '../lib/setup-tracer.js'
main: '../lib/finalize-db.js'
+1 -1
View File
@@ -5,7 +5,7 @@ inputs:
tools: tools:
description: URL of CodeQL tools description: URL of CodeQL tools
required: false required: false
default: https://github.com/github/codeql-action/releases/download/codeql-bundle-20200601/codeql-bundle.tar.gz # If not specified the Action will check in several places until it finds the CodeQL tools.
languages: languages:
description: The languages to be analysed description: The languages to be analysed
required: false required: false
+39 -8
View File
@@ -8,19 +8,50 @@ var __importStar = (this && this.__importStar) || function (mod) {
}; };
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(require("@actions/core")); const core = __importStar(require("@actions/core"));
function includeAndExcludeAnalysisPaths(config, languages) { function isInterpretedLanguage(language) {
return language === 'javascript' || language === 'python';
}
// Matches a string containing only characters that are legal to include in paths on windows.
exports.legalWindowsPathCharactersRegex = /^[^<>:"\|?]*$/;
// Builds an environment variable suitable for LGTM_INDEX_INCLUDE or LGTM_INDEX_EXCLUDE
function buildIncludeExcludeEnvVar(paths) {
// Ignore anything containing a *
paths = paths.filter(p => p.indexOf('*') === -1);
// Some characters are illegal in path names in windows
if (process.platform === 'win32') {
paths = paths.filter(p => p.match(exports.legalWindowsPathCharactersRegex));
}
return paths.join('\n');
}
function includeAndExcludeAnalysisPaths(config) {
// The 'LGTM_INDEX_INCLUDE' and 'LGTM_INDEX_EXCLUDE' environment variables
// control which files/directories are traversed when scanning.
// This allows including files that otherwise would not be scanned, or
// excluding and not traversing entire file subtrees.
// It does not understand globs or double-globs because that would require it to
// traverse the entire file tree to determine which files are matched.
// Any paths containing "*" are not included in these.
if (config.paths.length !== 0) { if (config.paths.length !== 0) {
core.exportVariable('LGTM_INDEX_INCLUDE', config.paths.join('\n')); core.exportVariable('LGTM_INDEX_INCLUDE', buildIncludeExcludeEnvVar(config.paths));
} }
if (config.pathsIgnore.length !== 0) { if (config.pathsIgnore.length !== 0) {
core.exportVariable('LGTM_INDEX_EXCLUDE', config.pathsIgnore.join('\n')); core.exportVariable('LGTM_INDEX_EXCLUDE', buildIncludeExcludeEnvVar(config.pathsIgnore));
} }
function isInterpretedLanguage(language) { // The 'LGTM_INDEX_FILTERS' environment variable controls which files are
return language === 'javascript' || language === 'python'; // extracted or ignored. It does not control which directories are traversed.
// This does understand the glob and double-glob syntax.
const filters = [];
filters.push(...config.paths.map(p => 'include:' + p));
filters.push(...config.pathsIgnore.map(p => 'exclude:' + p));
if (filters.length !== 0) {
core.exportVariable('LGTM_INDEX_FILTERS', filters.join('\n'));
} }
// Index include/exclude only work in javascript and python // Index include/exclude/filters only work in javascript and python.
// If some other language is detected/configured show a warning // If any other languages are detected/configured then show a warning.
if ((config.paths.length !== 0 || config.pathsIgnore.length !== 0) && !languages.every(isInterpretedLanguage)) { if ((config.paths.length !== 0 ||
config.pathsIgnore.length !== 0 ||
filters.length !== 0) &&
!config.languages.every(isInterpretedLanguage)) {
core.warning('The "paths"/"paths-ignore" fields of the config only have effect for Javascript and Python'); core.warning('The "paths"/"paths-ignore" fields of the config only have effect for Javascript and Python');
} }
} }
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"analysis-paths.js","sourceRoot":"","sources":["../src/analysis-paths.ts"],"names":[],"mappings":";;;;;;;;;AAAA,oDAAsC;AAItC,SAAgB,8BAA8B,CAAC,MAA0B,EAAE,SAAmB;IAC5F,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE;QAC7B,IAAI,CAAC,cAAc,CAAC,oBAAoB,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;KACpE;IAED,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE;QACnC,IAAI,CAAC,cAAc,CAAC,oBAAoB,EAAE,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;KAC1E;IAED,SAAS,qBAAqB,CAAC,QAAQ;QACrC,OAAO,QAAQ,KAAK,YAAY,IAAI,QAAQ,KAAK,QAAQ,CAAC;IAC5D,CAAC;IAED,2DAA2D;IAC3D,+DAA+D;IAC/D,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,qBAAqB,CAAC,EAAE;QAC7G,IAAI,CAAC,OAAO,CAAC,4FAA4F,CAAC,CAAC;KAC5G;AACH,CAAC;AAlBD,wEAkBC"} {"version":3,"file":"analysis-paths.js","sourceRoot":"","sources":["../src/analysis-paths.ts"],"names":[],"mappings":";;;;;;;;;AAAA,oDAAsC;AAItC,SAAS,qBAAqB,CAAC,QAAQ;IACrC,OAAO,QAAQ,KAAK,YAAY,IAAI,QAAQ,KAAK,QAAQ,CAAC;AAC5D,CAAC;AAED,6FAA6F;AAChF,QAAA,+BAA+B,GAAG,eAAe,CAAC;AAE/D,uFAAuF;AACvF,SAAS,yBAAyB,CAAC,KAAe;IAChD,iCAAiC;IACjC,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAEjD,uDAAuD;IACvD,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE;QAChC,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,uCAA+B,CAAC,CAAC,CAAC;KACrE;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,SAAgB,8BAA8B,CAAC,MAA0B;IACvE,0EAA0E;IAC1E,+DAA+D;IAC/D,sEAAsE;IACtE,qDAAqD;IACrD,gFAAgF;IAChF,sEAAsE;IACtE,sDAAsD;IACtD,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE;QAC7B,IAAI,CAAC,cAAc,CAAC,oBAAoB,EAAE,yBAAyB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;KACpF;IACD,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE;QACnC,IAAI,CAAC,cAAc,CAAC,oBAAoB,EAAE,yBAAyB,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;KAC1F;IAED,yEAAyE;IACzE,6EAA6E;IAC7E,wDAAwD;IACxD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC;IACvD,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC;IAC7D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE;QACxB,IAAI,CAAC,cAAc,CAAC,oBAAoB,EAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;KAC/D;IAED,oEAAoE;IACpE,sEAAsE;IACtE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;QACxB,MAAM,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC;QAC/B,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC;QACvB,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,qBAAqB,CAAC,EAAE;QAClD,IAAI,CAAC,OAAO,CAAC,4FAA4F,CAAC,CAAC;KAC5G;AACH,CAAC;AAjCD,wEAiCC"}
+20 -9
View File
@@ -12,21 +12,32 @@ var __importStar = (this && this.__importStar) || function (mod) {
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const ava_1 = __importDefault(require("ava")); const ava_1 = __importDefault(require("ava"));
const analysisPaths = __importStar(require("./analysis-paths")); const analysisPaths = __importStar(require("./analysis-paths"));
const configUtils = __importStar(require("./config-utils"));
const testing_utils_1 = require("./testing-utils"); const testing_utils_1 = require("./testing-utils");
testing_utils_1.silenceDebugOutput(ava_1.default); testing_utils_1.setupTests(ava_1.default);
ava_1.default("emptyPaths", async (t) => { ava_1.default("emptyPaths", async (t) => {
let config = new configUtils.Config(); const config = {
analysisPaths.includeAndExcludeAnalysisPaths(config, []); languages: [],
queries: {},
pathsIgnore: [],
paths: [],
originalUserInput: {},
};
analysisPaths.includeAndExcludeAnalysisPaths(config);
t.is(process.env['LGTM_INDEX_INCLUDE'], undefined); t.is(process.env['LGTM_INDEX_INCLUDE'], undefined);
t.is(process.env['LGTM_INDEX_EXCLUDE'], undefined); t.is(process.env['LGTM_INDEX_EXCLUDE'], undefined);
t.is(process.env['LGTM_INDEX_FILTERS'], undefined);
}); });
ava_1.default("nonEmptyPaths", async (t) => { ava_1.default("nonEmptyPaths", async (t) => {
let config = new configUtils.Config(); const config = {
config.paths.push('path1', 'path2'); languages: [],
config.pathsIgnore.push('path3', 'path4'); queries: {},
analysisPaths.includeAndExcludeAnalysisPaths(config, []); paths: ['path1', 'path2', '**/path3'],
pathsIgnore: ['path4', 'path5', 'path6/**'],
originalUserInput: {},
};
analysisPaths.includeAndExcludeAnalysisPaths(config);
t.is(process.env['LGTM_INDEX_INCLUDE'], 'path1\npath2'); t.is(process.env['LGTM_INDEX_INCLUDE'], 'path1\npath2');
t.is(process.env['LGTM_INDEX_EXCLUDE'], 'path3\npath4'); t.is(process.env['LGTM_INDEX_EXCLUDE'], 'path4\npath5');
t.is(process.env['LGTM_INDEX_FILTERS'], 'include:path1\ninclude:path2\ninclude:**/path3\nexclude:path4\nexclude:path5\nexclude:path6/**');
}); });
//# sourceMappingURL=analysis-paths.test.js.map //# sourceMappingURL=analysis-paths.test.js.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"analysis-paths.test.js","sourceRoot":"","sources":["../src/analysis-paths.test.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,8CAAuB;AAEvB,gEAAkD;AAClD,4DAA8C;AAC9C,mDAAmD;AAEnD,kCAAkB,CAAC,aAAI,CAAC,CAAC;AAEzB,aAAI,CAAC,YAAY,EAAE,KAAK,EAAC,CAAC,EAAC,EAAE;IAC3B,IAAI,MAAM,GAAG,IAAI,WAAW,CAAC,MAAM,EAAE,CAAC;IACtC,aAAa,CAAC,8BAA8B,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACzD,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,EAAE,SAAS,CAAC,CAAC;IACnD,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,EAAE,SAAS,CAAC,CAAC;AACrD,CAAC,CAAC,CAAC;AAEH,aAAI,CAAC,eAAe,EAAE,KAAK,EAAC,CAAC,EAAC,EAAE;IAC9B,IAAI,MAAM,GAAG,IAAI,WAAW,CAAC,MAAM,EAAE,CAAC;IACtC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACpC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC1C,aAAa,CAAC,8BAA8B,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACzD,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,EAAE,cAAc,CAAC,CAAC;IACxD,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,EAAE,cAAc,CAAC,CAAC;AAC1D,CAAC,CAAC,CAAC"} {"version":3,"file":"analysis-paths.test.js","sourceRoot":"","sources":["../src/analysis-paths.test.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,8CAAuB;AAEvB,gEAAkD;AAClD,mDAA2C;AAE3C,0BAAU,CAAC,aAAI,CAAC,CAAC;AAEjB,aAAI,CAAC,YAAY,EAAE,KAAK,EAAC,CAAC,EAAC,EAAE;IAC3B,MAAM,MAAM,GAAG;QACb,SAAS,EAAE,EAAE;QACb,OAAO,EAAE,EAAE;QACX,WAAW,EAAE,EAAE;QACf,KAAK,EAAE,EAAE;QACT,iBAAiB,EAAE,EAAE;KACtB,CAAC;IACF,aAAa,CAAC,8BAA8B,CAAC,MAAM,CAAC,CAAC;IACrD,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,EAAE,SAAS,CAAC,CAAC;IACnD,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,EAAE,SAAS,CAAC,CAAC;IACnD,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,EAAE,SAAS,CAAC,CAAC;AACrD,CAAC,CAAC,CAAC;AAEH,aAAI,CAAC,eAAe,EAAE,KAAK,EAAC,CAAC,EAAC,EAAE;IAC9B,MAAM,MAAM,GAAG;QACb,SAAS,EAAE,EAAE;QACb,OAAO,EAAE,EAAE;QACX,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,CAAC;QACrC,WAAW,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,CAAC;QAC3C,iBAAiB,EAAE,EAAE;KACtB,CAAC;IACF,aAAa,CAAC,8BAA8B,CAAC,MAAM,CAAC,CAAC;IACrD,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,EAAE,cAAc,CAAC,CAAC;IACxD,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,EAAE,cAAc,CAAC,CAAC;IACxD,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,EAAE,gGAAgG,CAAC,CAAC;AAC5I,CAAC,CAAC,CAAC"}
+22
View File
@@ -0,0 +1,22 @@
"use strict";
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(require("@actions/core"));
const github = __importStar(require("@actions/github"));
const console_log_level_1 = __importDefault(require("console-log-level"));
exports.getApiClient = function () {
return new github.GitHub(core.getInput('token'), {
userAgent: "CodeQL Action",
log: console_log_level_1.default({ level: "debug" })
});
};
//# sourceMappingURL=api-client.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,oDAAsC;AACtC,wDAA0C;AAC1C,0EAAgD;AAEnC,QAAA,YAAY,GAAG;IAC1B,OAAO,IAAI,MAAM,CAAC,MAAM,CACtB,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EACtB;QACE,SAAS,EAAE,eAAe;QAC1B,GAAG,EAAE,2BAAe,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;KACzC,CAAC,CAAC;AACP,CAAC,CAAC"}
+21 -18
View File
@@ -8,14 +8,27 @@ var __importStar = (this && this.__importStar) || function (mod) {
}; };
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(require("@actions/core")); const core = __importStar(require("@actions/core"));
const exec = __importStar(require("@actions/exec")); const codeql_1 = require("./codeql");
const path = __importStar(require("path"));
const sharedEnv = __importStar(require("./shared-environment")); const sharedEnv = __importStar(require("./shared-environment"));
const util = __importStar(require("./util")); const util = __importStar(require("./util"));
async function sendCompletedStatusReport(startedAt, allLanguages, failingLanguage, cause) {
var _a, _b;
const status = failingLanguage !== undefined || cause !== undefined ? 'failure' : 'success';
const statusReportBase = await util.createStatusReportBase('autobuild', status, startedAt, (_a = cause) === null || _a === void 0 ? void 0 : _a.message, (_b = cause) === null || _b === void 0 ? void 0 : _b.stack);
const statusReport = {
...statusReportBase,
autobuild_languages: allLanguages.join(','),
autobuild_failure: failingLanguage,
};
await util.sendStatusReport(statusReport);
}
async function run() { async function run() {
var _a; var _a;
const startedAt = new Date();
let language;
try { try {
if (util.should_abort('autobuild', true) || !await util.reportActionStarting('autobuild')) { if (util.should_abort('autobuild', true) ||
!await util.sendStatusReport(await util.createStatusReportBase('autobuild', 'starting', startedAt), true)) {
return; return;
} }
// Attempt to find a language to autobuild // Attempt to find a language to autobuild
@@ -23,7 +36,7 @@ async function run() {
// The languages are sorted in order specified by user or by lines of code if we got // The languages are sorted in order specified by user or by lines of code if we got
// them from the GitHub API, so try to build the first language on the list. // them from the GitHub API, so try to build the first language on the list.
const autobuildLanguages = ((_a = process.env[sharedEnv.CODEQL_ACTION_TRACED_LANGUAGES]) === null || _a === void 0 ? void 0 : _a.split(',')) || []; const autobuildLanguages = ((_a = process.env[sharedEnv.CODEQL_ACTION_TRACED_LANGUAGES]) === null || _a === void 0 ? void 0 : _a.split(',')) || [];
const language = autobuildLanguages[0]; language = autobuildLanguages[0];
if (!language) { if (!language) {
core.info("None of the languages in this project require extra build steps"); core.info("None of the languages in this project require extra build steps");
return; return;
@@ -33,26 +46,16 @@ async function run() {
core.warning(`We will only automatically build ${language} code. If you wish to scan ${autobuildLanguages.slice(1).join(' and ')}, you must replace this block with custom build steps.`); core.warning(`We will only automatically build ${language} code. If you wish to scan ${autobuildLanguages.slice(1).join(' and ')}, you must replace this block with custom build steps.`);
} }
core.startGroup(`Attempting to automatically build ${language} code`); core.startGroup(`Attempting to automatically build ${language} code`);
// TODO: share config accross actions better via env variables const codeQL = codeql_1.getCodeQL();
const codeqlCmd = util.getRequiredEnvParam(sharedEnv.CODEQL_ACTION_CMD); await codeQL.runAutobuild(language);
const cmdName = process.platform === 'win32' ? 'autobuild.cmd' : 'autobuild.sh';
const autobuildCmd = path.join(path.dirname(codeqlCmd), language, 'tools', cmdName);
// Update JAVA_TOOL_OPTIONS to contain '-Dhttp.keepAlive=false'
// This is because of an issue with Azure pipelines timing out connections after 4 minutes
// and Maven not properly handling closed connections
// Otherwise long build processes will timeout when pulling down Java packages
// https://developercommunity.visualstudio.com/content/problem/292284/maven-hosted-agent-connection-timeout.html
let javaToolOptions = process.env['JAVA_TOOL_OPTIONS'] || "";
process.env['JAVA_TOOL_OPTIONS'] = [...javaToolOptions.split(/\s+/), '-Dhttp.keepAlive=false', '-Dmaven.wagon.http.pool=false'].join(' ');
await exec.exec(autobuildCmd);
core.endGroup(); core.endGroup();
} }
catch (error) { catch (error) {
core.setFailed("We were unable to automatically build your code. Please replace the call to the autobuild action with your custom build steps. " + error.message); core.setFailed("We were unable to automatically build your code. Please replace the call to the autobuild action with your custom build steps. " + error.message);
await util.reportActionFailed('autobuild', error.message, error.stack); await sendCompletedStatusReport(startedAt, [language], language, error);
return; return;
} }
await util.reportActionSucceeded('autobuild'); await sendCompletedStatusReport(startedAt, [language]);
} }
run().catch(e => { run().catch(e => {
core.setFailed("autobuild action failed. " + e); core.setFailed("autobuild action failed. " + e);
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"autobuild.js","sourceRoot":"","sources":["../src/autobuild.ts"],"names":[],"mappings":";;;;;;;;;AAAA,oDAAsC;AACtC,oDAAsC;AACtC,2CAA6B;AAE7B,gEAAkD;AAClD,6CAA+B;AAE/B,KAAK,UAAU,GAAG;;IAChB,IAAI;QACF,IAAI,IAAI,CAAC,YAAY,CAAC,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,oBAAoB,CAAC,WAAW,CAAC,EAAE;YACzF,OAAO;SACR;QAED,0CAA0C;QAC1C,mFAAmF;QACnF,oFAAoF;QACpF,4EAA4E;QAC5E,MAAM,kBAAkB,GAAG,OAAA,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,8BAA8B,CAAC,0CAAE,KAAK,CAAC,GAAG,MAAK,EAAE,CAAC;QACnG,MAAM,QAAQ,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAAC;QAEvC,IAAI,CAAC,QAAQ,EAAE;YACb,IAAI,CAAC,IAAI,CAAC,iEAAiE,CAAC,CAAC;YAC7E,OAAO;SACR;QAED,IAAI,CAAC,KAAK,CAAC,sCAAsC,QAAQ,EAAE,CAAC,CAAC;QAE7D,IAAI,kBAAkB,CAAC,MAAM,GAAG,CAAC,EAAE;YACjC,IAAI,CAAC,OAAO,CAAC,oCAAoC,QAAQ,8BAA8B,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,wDAAwD,CAAC,CAAC;SAC3L;QAED,IAAI,CAAC,UAAU,CAAC,qCAAqC,QAAQ,OAAO,CAAC,CAAC;QACtE,8DAA8D;QAC9D,MAAM,SAAS,GAAG,IAAI,CAAC,mBAAmB,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;QAExE,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,cAAc,CAAC;QAChF,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;QAGpF,+DAA+D;QAC/D,0FAA0F;QAC1F,qDAAqD;QACrD,8EAA8E;QAC9E,gHAAgH;QAChH,IAAI,eAAe,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,IAAI,EAAE,CAAC;QAC7D,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,GAAG,CAAC,GAAG,eAAe,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,wBAAwB,EAAE,+BAA+B,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAE1I,MAAM,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC9B,IAAI,CAAC,QAAQ,EAAE,CAAC;KAEjB;IAAC,OAAO,KAAK,EAAE;QACd,IAAI,CAAC,SAAS,CAAC,kIAAkI,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;QACnK,MAAM,IAAI,CAAC,kBAAkB,CAAC,WAAW,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QACvE,OAAO;KACR;IAED,MAAM,IAAI,CAAC,qBAAqB,CAAC,WAAW,CAAC,CAAC;AAChD,CAAC;AAED,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;IACd,IAAI,CAAC,SAAS,CAAC,4BAA4B,GAAG,CAAC,CAAC,CAAC;IACjD,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC,CAAC,CAAC"} {"version":3,"file":"autobuild.js","sourceRoot":"","sources":["../src/autobuild.ts"],"names":[],"mappings":";;;;;;;;;AAAA,oDAAsC;AAEtC,qCAAqC;AACrC,gEAAkD;AAClD,6CAA+B;AAS/B,KAAK,UAAU,yBAAyB,CACtC,SAAe,EACf,YAAsB,EACtB,eAAwB,EACxB,KAAa;;IAEb,MAAM,MAAM,GAAG,eAAe,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;IAC5F,MAAM,gBAAgB,GAAG,MAAM,IAAI,CAAC,sBAAsB,CACxD,WAAW,EACX,MAAM,EACN,SAAS,QACT,KAAK,0CAAE,OAAO,QACd,KAAK,0CAAE,KAAK,CAAC,CAAC;IAChB,MAAM,YAAY,GAA0B;QAC1C,GAAG,gBAAgB;QACnB,mBAAmB,EAAE,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC;QAC3C,iBAAiB,EAAE,eAAe;KACnC,CAAC;IACF,MAAM,IAAI,CAAC,gBAAgB,CAAC,YAAY,CAAC,CAAC;AAC5C,CAAC;AAED,KAAK,UAAU,GAAG;;IAChB,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC;IAC7B,IAAI,QAAQ,CAAC;IACb,IAAI;QACF,IAAI,IAAI,CAAC,YAAY,CAAC,WAAW,EAAE,IAAI,CAAC;YACpC,CAAC,MAAM,IAAI,CAAC,gBAAgB,CAAC,MAAM,IAAI,CAAC,sBAAsB,CAAC,WAAW,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,IAAI,CAAC,EAAE;YAC7G,OAAO;SACR;QAED,0CAA0C;QAC1C,mFAAmF;QACnF,oFAAoF;QACpF,4EAA4E;QAC5E,MAAM,kBAAkB,GAAG,OAAA,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,8BAA8B,CAAC,0CAAE,KAAK,CAAC,GAAG,MAAK,EAAE,CAAC;QACnG,QAAQ,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAAC;QAEjC,IAAI,CAAC,QAAQ,EAAE;YACb,IAAI,CAAC,IAAI,CAAC,iEAAiE,CAAC,CAAC;YAC7E,OAAO;SACR;QAED,IAAI,CAAC,KAAK,CAAC,sCAAsC,QAAQ,EAAE,CAAC,CAAC;QAE7D,IAAI,kBAAkB,CAAC,MAAM,GAAG,CAAC,EAAE;YACjC,IAAI,CAAC,OAAO,CAAC,oCAAoC,QAAQ,8BAA8B,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,wDAAwD,CAAC,CAAC;SAC3L;QAED,IAAI,CAAC,UAAU,CAAC,qCAAqC,QAAQ,OAAO,CAAC,CAAC;QACtE,MAAM,MAAM,GAAG,kBAAS,EAAE,CAAC;QAC3B,MAAM,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QAEpC,IAAI,CAAC,QAAQ,EAAE,CAAC;KAEjB;IAAC,OAAO,KAAK,EAAE;QACd,IAAI,CAAC,SAAS,CAAC,kIAAkI,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;QACnK,MAAM,yBAAyB,CAAC,SAAS,EAAE,CAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;QACxE,OAAO;KACR;IAED,MAAM,yBAAyB,CAAC,SAAS,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;AACzD,CAAC;AAED,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;IACd,IAAI,CAAC,SAAS,CAAC,4BAA4B,GAAG,CAAC,CAAC,CAAC;IACjD,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC,CAAC,CAAC"}
Generated
+328
View File
@@ -0,0 +1,328 @@
"use strict";
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(require("@actions/core"));
const exec = __importStar(require("@actions/exec"));
const http = __importStar(require("@actions/http-client"));
const io = __importStar(require("@actions/io"));
const toolcache = __importStar(require("@actions/tool-cache"));
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const semver = __importStar(require("semver"));
const stream = __importStar(require("stream"));
const globalutil = __importStar(require("util"));
const v4_1 = __importDefault(require("uuid/v4"));
const api = __importStar(require("./api-client"));
const util = __importStar(require("./util"));
/**
* Stores the CodeQL object, and is populated by `setupCodeQL` or `getCodeQL`.
* Can be overridden in tests using `setCodeQL`.
*/
let cachedCodeQL = undefined;
/**
* Environment variable used to store the location of the CodeQL CLI executable.
* Value is set by setupCodeQL and read by getCodeQL.
*/
const CODEQL_ACTION_CMD = "CODEQL_ACTION_CMD";
const CODEQL_BUNDLE_VERSION = "codeql-bundle-20200630";
const CODEQL_BUNDLE_NAME = "codeql-bundle.tar.gz";
const GITHUB_DOTCOM_API_URL = "https://api.github.com";
const CODEQL_DEFAULT_ACTION_REPOSITORY = "github/codeql-action";
function getInstanceAPIURL() {
return process.env["GITHUB_API_URL"] || GITHUB_DOTCOM_API_URL;
}
function getCodeQLActionRepository() {
// Actions do not know their own repository name,
// so we currently use this hack to find the name based on where our files are.
// This can be removed once the change to the runner in https://github.com/actions/runner/pull/585 is deployed.
const runnerTemp = util.getRequiredEnvParam("RUNNER_TEMP");
const actionsDirectory = path.join(path.dirname(runnerTemp), "_actions");
const relativeScriptPath = path.relative(actionsDirectory, __filename);
// This handles the case where the Action does not come from an Action repository,
// e.g. our integration tests which use the Action code from the current checkout.
if (relativeScriptPath.startsWith("..") || path.isAbsolute(relativeScriptPath)) {
return CODEQL_DEFAULT_ACTION_REPOSITORY;
}
const relativeScriptPathParts = relativeScriptPath.split(path.sep);
return relativeScriptPathParts[0] + "/" + relativeScriptPathParts[1];
}
async function getCodeQLBundleDownloadURL() {
const codeQLActionRepository = getCodeQLActionRepository();
const potentialDownloadSources = [
// This GitHub instance, and this Action.
[getInstanceAPIURL(), codeQLActionRepository],
// This GitHub instance, and the canonical Action.
[getInstanceAPIURL(), CODEQL_DEFAULT_ACTION_REPOSITORY],
// GitHub.com, and the canonical Action.
[GITHUB_DOTCOM_API_URL, CODEQL_DEFAULT_ACTION_REPOSITORY],
];
// We now filter out any duplicates.
// Duplicates will happen either because the GitHub instance is GitHub.com, or because the Action is not a fork.
const uniqueDownloadSources = potentialDownloadSources.filter((url, index, self) => index === self.indexOf(url));
for (let downloadSource of uniqueDownloadSources) {
let [apiURL, repository] = downloadSource;
// If we've reached the final case, short-circuit the API check since we know the bundle exists and is public.
if (apiURL === GITHUB_DOTCOM_API_URL && repository === CODEQL_DEFAULT_ACTION_REPOSITORY) {
break;
}
let [repositoryOwner, repositoryName] = repository.split("/");
try {
const release = await api.getApiClient().repos.getReleaseByTag({
owner: repositoryOwner,
repo: repositoryName,
tag: CODEQL_BUNDLE_VERSION
});
for (let asset of release.data.assets) {
if (asset.name === CODEQL_BUNDLE_NAME) {
core.info(`Found CodeQL bundle in ${downloadSource[1]} on ${downloadSource[0]} with URL ${asset.url}.`);
return asset.url;
}
}
}
catch (e) {
core.info(`Looked for CodeQL bundle in ${downloadSource[1]} on ${downloadSource[0]} but got error ${e}.`);
}
}
return `https://github.com/${CODEQL_DEFAULT_ACTION_REPOSITORY}/releases/download/${CODEQL_BUNDLE_VERSION}/${CODEQL_BUNDLE_NAME}`;
}
// We have to download CodeQL manually because the toolcache doesn't support Accept headers.
// This can be removed once https://github.com/actions/toolkit/pull/530 is merged and released.
async function toolcacheDownloadTool(url, headers) {
const client = new http.HttpClient('CodeQL Action');
const dest = path.join(util.getRequiredEnvParam('RUNNER_TEMP'), v4_1.default());
const response = await client.get(url, headers);
if (response.message.statusCode !== 200) {
const err = new toolcache.HTTPError(response.message.statusCode);
core.info(`Failed to download from "${url}". Code(${response.message.statusCode}) Message(${response.message.statusMessage})`);
throw err;
}
const pipeline = globalutil.promisify(stream.pipeline);
await io.mkdirP(path.dirname(dest));
await pipeline(response.message, fs.createWriteStream(dest));
return dest;
}
async function setupCodeQL() {
try {
let codeqlURL = core.getInput('tools');
const codeqlURLVersion = getCodeQLURLVersion(codeqlURL || `/${CODEQL_BUNDLE_VERSION}/`);
let codeqlFolder = toolcache.find('CodeQL', codeqlURLVersion);
if (codeqlFolder) {
core.debug(`CodeQL found in cache ${codeqlFolder}`);
}
else {
if (!codeqlURL) {
codeqlURL = await getCodeQLBundleDownloadURL();
}
const headers = { accept: 'application/octet-stream' };
// 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.
if (codeqlURL.startsWith(getInstanceAPIURL() + "/")) {
core.debug('Downloading CodeQL bundle with token.');
let token = core.getInput('token', { required: true });
headers.authorization = `token ${token}`;
}
else {
core.debug('Downloading CodeQL bundle without token.');
}
let codeqlPath = await toolcacheDownloadTool(codeqlURL, headers);
core.debug(`CodeQL bundle download to ${codeqlPath} complete.`);
const codeqlExtracted = await toolcache.extractTar(codeqlPath);
codeqlFolder = await toolcache.cacheDir(codeqlExtracted, 'CodeQL', codeqlURLVersion);
}
let codeqlCmd = path.join(codeqlFolder, 'codeql', 'codeql');
if (process.platform === 'win32') {
codeqlCmd += ".exe";
}
else if (process.platform !== 'linux' && process.platform !== 'darwin') {
throw new Error("Unsupported platform: " + process.platform);
}
cachedCodeQL = getCodeQLForCmd(codeqlCmd);
core.exportVariable(CODEQL_ACTION_CMD, codeqlCmd);
return cachedCodeQL;
}
catch (e) {
core.error(e);
throw new Error("Unable to download and extract CodeQL CLI");
}
}
exports.setupCodeQL = setupCodeQL;
function getCodeQLURLVersion(url) {
const match = url.match(/\/codeql-bundle-(.*)\//);
if (match === null || match.length < 2) {
throw new Error(`Malformed tools url: ${url}. Version could not be inferred`);
}
let version = match[1];
if (!semver.valid(version)) {
core.debug(`Bundle version ${version} is not in SemVer format. Will treat it as pre-release 0.0.0-${version}.`);
version = '0.0.0-' + version;
}
const s = semver.clean(version);
if (!s) {
throw new Error(`Malformed tools url ${url}. Version should be in SemVer format but have ${version} instead`);
}
return s;
}
exports.getCodeQLURLVersion = getCodeQLURLVersion;
function getCodeQL() {
if (cachedCodeQL === undefined) {
const codeqlCmd = util.getRequiredEnvParam(CODEQL_ACTION_CMD);
cachedCodeQL = getCodeQLForCmd(codeqlCmd);
}
return cachedCodeQL;
}
exports.getCodeQL = getCodeQL;
function resolveFunction(partialCodeql, methodName) {
if (typeof partialCodeql[methodName] !== 'function') {
const dummyMethod = () => {
throw new Error('CodeQL ' + methodName + ' method not correctly defined');
};
return dummyMethod;
}
return partialCodeql[methodName];
}
/**
* Set the functionality for CodeQL methods. Only for use in tests.
*
* Accepts a partial object and any undefined methods will be implemented
* to immediately throw an exception indicating which method is missing.
*/
function setCodeQL(partialCodeql) {
cachedCodeQL = {
getDir: resolveFunction(partialCodeql, 'getDir'),
printVersion: resolveFunction(partialCodeql, 'printVersion'),
getTracerEnv: resolveFunction(partialCodeql, 'getTracerEnv'),
databaseInit: resolveFunction(partialCodeql, 'databaseInit'),
runAutobuild: resolveFunction(partialCodeql, 'runAutobuild'),
extractScannedLanguage: resolveFunction(partialCodeql, 'extractScannedLanguage'),
finalizeDatabase: resolveFunction(partialCodeql, 'finalizeDatabase'),
resolveQueries: resolveFunction(partialCodeql, 'resolveQueries'),
databaseAnalyze: resolveFunction(partialCodeql, 'databaseAnalyze')
};
}
exports.setCodeQL = setCodeQL;
function getCodeQLForCmd(cmd) {
return {
getDir: function () {
return path.dirname(cmd);
},
printVersion: async function () {
await exec.exec(cmd, [
'version',
'--format=json'
]);
},
getTracerEnv: async function (databasePath, compilerSpec) {
let envFile = path.resolve(databasePath, 'working', 'env.tmp');
const compilerSpecArg = compilerSpec ? ["--compiler-spec=" + compilerSpec] : [];
await exec.exec(cmd, [
'database',
'trace-command',
databasePath,
...compilerSpecArg,
process.execPath,
path.resolve(__dirname, 'tracer-env.js'),
envFile
]);
return JSON.parse(fs.readFileSync(envFile, 'utf-8'));
},
databaseInit: async function (databasePath, language, sourceRoot) {
await exec.exec(cmd, [
'database',
'init',
databasePath,
'--language=' + language,
'--source-root=' + sourceRoot,
]);
},
runAutobuild: async function (language) {
const cmdName = process.platform === 'win32' ? 'autobuild.cmd' : 'autobuild.sh';
const autobuildCmd = path.join(path.dirname(cmd), language, 'tools', cmdName);
// Update JAVA_TOOL_OPTIONS to contain '-Dhttp.keepAlive=false'
// This is because of an issue with Azure pipelines timing out connections after 4 minutes
// and Maven not properly handling closed connections
// Otherwise long build processes will timeout when pulling down Java packages
// https://developercommunity.visualstudio.com/content/problem/292284/maven-hosted-agent-connection-timeout.html
let javaToolOptions = process.env['JAVA_TOOL_OPTIONS'] || "";
process.env['JAVA_TOOL_OPTIONS'] = [...javaToolOptions.split(/\s+/), '-Dhttp.keepAlive=false', '-Dmaven.wagon.http.pool=false'].join(' ');
await exec.exec(autobuildCmd);
},
extractScannedLanguage: async function (databasePath, language) {
// Get extractor location
let extractorPath = '';
await exec.exec(cmd, [
'resolve',
'extractor',
'--format=json',
'--language=' + language
], {
silent: true,
listeners: {
stdout: (data) => { extractorPath += data.toString(); },
stderr: (data) => { process.stderr.write(data); }
}
});
// Set trace command
const ext = process.platform === 'win32' ? '.cmd' : '.sh';
const traceCommand = path.resolve(JSON.parse(extractorPath), 'tools', 'autobuild' + ext);
// Run trace command
await exec.exec(cmd, [
'database',
'trace-command',
databasePath,
'--',
traceCommand
]);
},
finalizeDatabase: async function (databasePath) {
await exec.exec(cmd, [
'database',
'finalize',
databasePath
]);
},
resolveQueries: async function (queries, extraSearchPath) {
const codeqlArgs = [
'resolve',
'queries',
...queries,
'--format=bylanguage'
];
if (extraSearchPath !== undefined) {
codeqlArgs.push('--search-path', extraSearchPath);
}
let output = '';
await exec.exec(cmd, codeqlArgs, {
listeners: {
stdout: (data) => {
output += data.toString();
}
}
});
return JSON.parse(output);
},
databaseAnalyze: async function (databasePath, sarifFile, querySuite) {
await exec.exec(cmd, [
'database',
'analyze',
util.getMemoryFlag(),
util.getThreadsFlag(),
databasePath,
'--format=sarif-latest',
'--output=' + sarifFile,
'--no-sarif-add-snippets',
querySuite
]);
}
};
}
//# sourceMappingURL=codeql.js.map
File diff suppressed because one or more lines are too long
+60
View File
@@ -0,0 +1,60 @@
"use strict";
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const toolcache = __importStar(require("@actions/tool-cache"));
const ava_1 = __importDefault(require("ava"));
const nock_1 = __importDefault(require("nock"));
const path = __importStar(require("path"));
const codeql = __importStar(require("./codeql"));
const testing_utils_1 = require("./testing-utils");
const util = __importStar(require("./util"));
testing_utils_1.setupTests(ava_1.default);
ava_1.default('download codeql bundle cache', async (t) => {
await util.withTmpDir(async (tmpDir) => {
process.env['GITHUB_WORKSPACE'] = tmpDir;
process.env['RUNNER_TEMP'] = path.join(tmpDir, 'temp');
process.env['RUNNER_TOOL_CACHE'] = path.join(tmpDir, 'cache');
const versions = ['20200601', '20200610'];
for (let i = 0; i < versions.length; i++) {
const version = versions[i];
nock_1.default('https://example.com')
.get(`/download/codeql-bundle-${version}/codeql-bundle.tar.gz`)
.replyWithFile(200, path.join(__dirname, `/../src/testdata/codeql-bundle.tar.gz`));
process.env['INPUT_TOOLS'] = `https://example.com/download/codeql-bundle-${version}/codeql-bundle.tar.gz`;
await codeql.setupCodeQL();
t.assert(toolcache.find('CodeQL', `0.0.0-${version}`));
}
const cachedVersions = toolcache.findAllVersions('CodeQL');
t.is(cachedVersions.length, 2);
});
});
ava_1.default('parse codeql bundle url version', t => {
const tests = {
'20200601': '0.0.0-20200601',
'20200601.0': '0.0.0-20200601.0',
'20200601.0.0': '20200601.0.0',
'1.2.3': '1.2.3',
'1.2.3-alpha': '1.2.3-alpha',
'1.2.3-beta.1': '1.2.3-beta.1',
};
for (const [version, expectedVersion] of Object.entries(tests)) {
const url = `https://github.com/.../codeql-bundle-${version}/...`;
try {
const parsedVersion = codeql.getCodeQLURLVersion(url);
t.deepEqual(parsedVersion, expectedVersion);
}
catch (e) {
t.fail(e.message);
}
}
});
//# sourceMappingURL=codeql.test.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"codeql.test.js","sourceRoot":"","sources":["../src/codeql.test.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,+DAAiD;AACjD,8CAAuB;AACvB,gDAAwB;AACxB,2CAA6B;AAE7B,iDAAmC;AACnC,mDAA2C;AAC3C,6CAA+B;AAE/B,0BAAU,CAAC,aAAI,CAAC,CAAC;AAEjB,aAAI,CAAC,8BAA8B,EAAE,KAAK,EAAC,CAAC,EAAC,EAAE;IAE7C,MAAM,IAAI,CAAC,UAAU,CAAC,KAAK,EAAC,MAAM,EAAC,EAAE;QAEnC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,GAAG,MAAM,CAAC;QAEzC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACvD,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAE9D,MAAM,QAAQ,GAAG,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;QAE1C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;YACxC,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;YAE5B,cAAI,CAAC,qBAAqB,CAAC;iBACxB,GAAG,CAAC,2BAA2B,OAAO,uBAAuB,CAAC;iBAC9D,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,uCAAuC,CAAC,CAAC,CAAC;YAGrF,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,GAAG,8CAA8C,OAAO,uBAAuB,CAAC;YAE1G,MAAM,MAAM,CAAC,WAAW,EAAE,CAAC;YAE3B,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC,CAAC,CAAC;SACxD;QAED,MAAM,cAAc,GAAG,SAAS,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;QAE3D,CAAC,CAAC,EAAE,CAAC,cAAc,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,aAAI,CAAC,iCAAiC,EAAE,CAAC,CAAC,EAAE;IAE1C,MAAM,KAAK,GAAG;QACZ,UAAU,EAAE,gBAAgB;QAC5B,YAAY,EAAE,kBAAkB;QAChC,cAAc,EAAE,cAAc;QAC9B,OAAO,EAAE,OAAO;QAChB,aAAa,EAAE,aAAa;QAC5B,cAAc,EAAE,cAAc;KAC/B,CAAC;IAEF,KAAK,MAAM,CAAC,OAAO,EAAE,eAAe,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;QAC9D,MAAM,GAAG,GAAG,wCAAwC,OAAO,MAAM,CAAC;QAElE,IAAI;YACF,MAAM,aAAa,GAAG,MAAM,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC;YACtD,CAAC,CAAC,SAAS,CAAC,aAAa,EAAE,eAAe,CAAC,CAAC;SAC7C;QAAC,OAAO,CAAC,EAAE;YACV,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;SACnB;KACF;AACH,CAAC,CAAC,CAAC"}
+429 -128
View File
@@ -12,104 +12,221 @@ const io = __importStar(require("@actions/io"));
const fs = __importStar(require("fs")); const fs = __importStar(require("fs"));
const yaml = __importStar(require("js-yaml")); const yaml = __importStar(require("js-yaml"));
const path = __importStar(require("path")); const path = __importStar(require("path"));
const api = __importStar(require("./api-client"));
const codeql_1 = require("./codeql");
const externalQueries = __importStar(require("./external-queries"));
const util = __importStar(require("./util")); const util = __importStar(require("./util"));
// Property names from the user-supplied config file.
const NAME_PROPERTY = 'name'; const NAME_PROPERTY = 'name';
const DISPLAY_DEFAULT_QUERIES_PROPERTY = 'disable-default-queries'; const DISABLE_DEFAULT_QUERIES_PROPERTY = 'disable-default-queries';
const QUERIES_PROPERTY = 'queries'; const QUERIES_PROPERTY = 'queries';
const QUERIES_USES_PROPERTY = 'uses'; const QUERIES_USES_PROPERTY = 'uses';
const PATHS_IGNORE_PROPERTY = 'paths-ignore'; const PATHS_IGNORE_PROPERTY = 'paths-ignore';
const PATHS_PROPERTY = 'paths'; const PATHS_PROPERTY = 'paths';
class ExternalQuery { /**
constructor(repository, ref) { * A list of queries from https://github.com/github/codeql that
this.path = ''; * we don't want to run. Disabling them here is a quicker alternative to
this.repository = repository; * disabling them in the code scanning query suites. Queries should also
this.ref = ref; * be disabled in the suites, and removed from this list here once the
* bundle is updated to make those suite changes live.
*
* Format is a map from language to an array of path suffixes of .ql files.
*/
const DISABLED_BUILTIN_QUERIES = {
'csharp': [
'ql/src/Security Features/CWE-937/VulnerablePackage.ql',
'ql/src/Security Features/CWE-451/MissingXFrameOptions.ql',
]
};
function queryIsDisabled(language, query) {
return (DISABLED_BUILTIN_QUERIES[language] || [])
.some(disabledQuery => query.endsWith(disabledQuery));
}
/**
* Asserts that the noDeclaredLanguage and multipleDeclaredLanguages fields are
* both empty and errors if they are not.
*/
function validateQueries(resolvedQueries) {
const noDeclaredLanguage = resolvedQueries.noDeclaredLanguage;
const noDeclaredLanguageQueries = Object.keys(noDeclaredLanguage);
if (noDeclaredLanguageQueries.length !== 0) {
throw new Error('The following queries do not declare a language. ' +
'Their qlpack.yml files are either missing or is invalid.\n' +
noDeclaredLanguageQueries.join('\n'));
}
const multipleDeclaredLanguages = resolvedQueries.multipleDeclaredLanguages;
const multipleDeclaredLanguagesQueries = Object.keys(multipleDeclaredLanguages);
if (multipleDeclaredLanguagesQueries.length !== 0) {
throw new Error('The following queries declare multiple languages. ' +
'Their qlpack.yml files are either missing or is invalid.\n' +
multipleDeclaredLanguagesQueries.join('\n'));
} }
} }
exports.ExternalQuery = ExternalQuery; /**
* Run 'codeql resolve queries' and add the results to resultMap
*/
async function runResolveQueries(resultMap, toResolve, extraSearchPath, errorOnInvalidQueries) {
const codeQl = codeql_1.getCodeQL();
const resolvedQueries = await codeQl.resolveQueries(toResolve, extraSearchPath);
for (const [language, queries] of Object.entries(resolvedQueries.byLanguage)) {
if (resultMap[language] === undefined) {
resultMap[language] = [];
}
resultMap[language].push(...Object.keys(queries).filter(q => !queryIsDisabled(language, q)));
}
if (errorOnInvalidQueries) {
validateQueries(resolvedQueries);
}
}
/**
* Get the set of queries included by default.
*/
async function addDefaultQueries(languages, resultMap) {
const suites = languages.map(l => l + '-code-scanning.qls');
await runResolveQueries(resultMap, suites, undefined, false);
}
// The set of acceptable values for built-in suites from the codeql bundle // The set of acceptable values for built-in suites from the codeql bundle
const builtinSuites = ['security-extended', 'security-and-quality']; const builtinSuites = ['security-extended', 'security-and-quality'];
class Config { /**
constructor() { * Determine the set of queries associated with suiteName's suites and add them to resultMap.
this.name = ""; * Throws an error if suiteName is not a valid builtin suite.
this.disableDefaultQueries = false; */
this.additionalQueries = []; async function addBuiltinSuiteQueries(configFile, languages, resultMap, suiteName) {
this.externalQueries = []; const suite = builtinSuites.find((suite) => suite === suiteName);
this.additionalSuites = []; if (!suite) {
this.pathsIgnore = []; throw new Error(getQueryUsesInvalid(configFile, suiteName));
this.paths = [];
}
addQuery(configFile, queryUses) {
// The logic for parsing the string is based on what actions does for
// parsing the 'uses' actions in the workflow file
queryUses = queryUses.trim();
if (queryUses === "") {
throw new Error(getQueryUsesInvalid(configFile));
}
// Check for the local path case before we start trying to parse the repository name
if (queryUses.startsWith("./")) {
const localQueryPath = queryUses.slice(2);
// Resolve the local path against the workspace so that when this is
// passed to codeql it resolves to exactly the path we expect it to resolve to.
const workspacePath = fs.realpathSync(util.getRequiredEnvParam('GITHUB_WORKSPACE'));
let absoluteQueryPath = path.join(workspacePath, localQueryPath);
// Check the file exists
if (!fs.existsSync(absoluteQueryPath)) {
throw new Error(getLocalPathDoesNotExist(configFile, localQueryPath));
}
// Call this after checking file exists, because it'll fail if file doesn't exist
absoluteQueryPath = fs.realpathSync(absoluteQueryPath);
// Check the local path doesn't jump outside the repo using '..' or symlinks
if (!(absoluteQueryPath + path.sep).startsWith(workspacePath + path.sep)) {
throw new Error(getLocalPathOutsideOfRepository(configFile, localQueryPath));
}
this.additionalQueries.push(absoluteQueryPath);
return;
}
// Check for one of the builtin suites
if (queryUses.indexOf('/') === -1 && queryUses.indexOf('@') === -1) {
const suite = builtinSuites.find((suite) => suite === queryUses);
if (suite) {
this.additionalSuites.push(suite);
return;
}
else {
throw new Error(getQueryUsesInvalid(configFile, queryUses));
}
}
let tok = queryUses.split('@');
if (tok.length !== 2) {
throw new Error(getQueryUsesInvalid(configFile, queryUses));
}
const ref = tok[1];
tok = tok[0].split('/');
// The first token is the owner
// The second token is the repo
// The rest is a path, if there is more than one token combine them to form the full path
if (tok.length < 2) {
throw new Error(getQueryUsesInvalid(configFile, queryUses));
}
if (tok.length > 3) {
tok = [tok[0], tok[1], tok.slice(2).join('/')];
}
// Check none of the parts of the repository name are empty
if (tok[0].trim() === '' || tok[1].trim() === '') {
throw new Error(getQueryUsesInvalid(configFile, queryUses));
}
let external = new ExternalQuery(tok[0] + '/' + tok[1], ref);
if (tok.length === 3) {
external.path = tok[2];
}
this.externalQueries.push(external);
} }
const suites = languages.map(l => l + '-' + suiteName + '.qls');
await runResolveQueries(resultMap, suites, undefined, false);
} }
exports.Config = Config; /**
* Retrieve the set of queries at localQueryPath and add them to resultMap.
*/
async function addLocalQueries(configFile, resultMap, localQueryPath) {
// Resolve the local path against the workspace so that when this is
// passed to codeql it resolves to exactly the path we expect it to resolve to.
const workspacePath = fs.realpathSync(util.getRequiredEnvParam('GITHUB_WORKSPACE'));
let absoluteQueryPath = path.join(workspacePath, localQueryPath);
// Check the file exists
if (!fs.existsSync(absoluteQueryPath)) {
throw new Error(getLocalPathDoesNotExist(configFile, localQueryPath));
}
// Call this after checking file exists, because it'll fail if file doesn't exist
absoluteQueryPath = fs.realpathSync(absoluteQueryPath);
// Check the local path doesn't jump outside the repo using '..' or symlinks
if (!(absoluteQueryPath + path.sep).startsWith(workspacePath + path.sep)) {
throw new Error(getLocalPathOutsideOfRepository(configFile, localQueryPath));
}
// Get the root of the current repo to use when resolving query dependencies
const rootOfRepo = util.getRequiredEnvParam('GITHUB_WORKSPACE');
await runResolveQueries(resultMap, [absoluteQueryPath], rootOfRepo, true);
}
/**
* Retrieve the set of queries at the referenced remote repo and add them to resultMap.
*/
async function addRemoteQueries(configFile, resultMap, queryUses) {
let tok = queryUses.split('@');
if (tok.length !== 2) {
throw new Error(getQueryUsesInvalid(configFile, queryUses));
}
const ref = tok[1];
tok = tok[0].split('/');
// The first token is the owner
// The second token is the repo
// The rest is a path, if there is more than one token combine them to form the full path
if (tok.length < 2) {
throw new Error(getQueryUsesInvalid(configFile, queryUses));
}
// Check none of the parts of the repository name are empty
if (tok[0].trim() === '' || tok[1].trim() === '') {
throw new Error(getQueryUsesInvalid(configFile, queryUses));
}
const nwo = tok[0] + '/' + tok[1];
// Checkout the external repository
const rootOfRepo = await externalQueries.checkoutExternalRepository(nwo, ref);
const queryPath = tok.length > 2
? path.join(rootOfRepo, tok.slice(2).join('/'))
: rootOfRepo;
await runResolveQueries(resultMap, [queryPath], rootOfRepo, true);
}
/**
* Parse a query 'uses' field to a discrete set of query files and update resultMap.
*
* The logic for parsing the string is based on what actions does for
* parsing the 'uses' actions in the workflow file. So it can handle
* local paths starting with './', or references to remote repos, or
* a finite set of hardcoded terms for builtin suites.
*/
async function parseQueryUses(configFile, languages, resultMap, queryUses) {
queryUses = queryUses.trim();
if (queryUses === "") {
throw new Error(getQueryUsesInvalid(configFile));
}
// Check for the local path case before we start trying to parse the repository name
if (queryUses.startsWith("./")) {
await addLocalQueries(configFile, resultMap, queryUses.slice(2));
return;
}
// Check for one of the builtin suites
if (queryUses.indexOf('/') === -1 && queryUses.indexOf('@') === -1) {
await addBuiltinSuiteQueries(configFile, languages, resultMap, queryUses);
return;
}
// Otherwise, must be a reference to another repo
await addRemoteQueries(configFile, resultMap, queryUses);
}
// Regex validating stars in paths or paths-ignore entries.
// The intention is to only allow ** to appear when immediately
// preceded and followed by a slash.
const pathStarsRegex = /.*(?:\*\*[^/].*|\*\*$|[^/]\*\*.*)/;
// Characters that are supported by filters in workflows, but not by us.
// See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet
const filterPatternCharactersRegex = /.*[\?\+\[\]!].*/;
// Checks that a paths of paths-ignore entry is valid, possibly modifying it
// to make it valid, or if not possible then throws an error.
function validateAndSanitisePath(originalPath, propertyName, configFile) {
// Take a copy so we don't modify the original path, so we can still construct error messages
let path = originalPath;
// All paths are relative to the src root, so strip off leading slashes.
while (path.charAt(0) === '/') {
path = path.substring(1);
}
// Trailing ** are redundant, so strip them off
if (path.endsWith('/**')) {
path = path.substring(0, path.length - 2);
}
// An empty path is not allowed as it's meaningless
if (path === '') {
throw new Error(getConfigFilePropertyError(configFile, propertyName, '"' + originalPath + '" is not an invalid path. ' +
'It is not necessary to include it, and it is not allowed to exclude it.'));
}
// Check for illegal uses of **
if (path.match(pathStarsRegex)) {
throw new Error(getConfigFilePropertyError(configFile, propertyName, '"' + originalPath + '" contains an invalid "**" wildcard. ' +
'They must be immediately preceeded and followed by a slash as in "/**/", or come at the start or end.'));
}
// Check for other regex characters that we don't support.
// Output a warning so the user knows, but otherwise continue normally.
if (path.match(filterPatternCharactersRegex)) {
core.warning(getConfigFilePropertyError(configFile, propertyName, '"' + originalPath + '" contains an unsupported character. ' +
'The filter pattern characters ?, +, [, ], ! are not supported and will be matched literally.'));
}
// Ban any uses of backslash for now.
// This may not play nicely with project layouts.
// This restriction can be lifted later if we determine they are ok.
if (path.indexOf('\\') !== -1) {
throw new Error(getConfigFilePropertyError(configFile, propertyName, '"' + originalPath + '" contains an "\\" character. These are not allowed in filters. ' +
'If running on windows we recommend using "/" instead for path filters.'));
}
return path;
}
exports.validateAndSanitisePath = validateAndSanitisePath;
function getNameInvalid(configFile) { function getNameInvalid(configFile) {
return getConfigFilePropertyError(configFile, NAME_PROPERTY, 'must be a non-empty string'); return getConfigFilePropertyError(configFile, NAME_PROPERTY, 'must be a non-empty string');
} }
exports.getNameInvalid = getNameInvalid; exports.getNameInvalid = getNameInvalid;
function getDisableDefaultQueriesInvalid(configFile) { function getDisableDefaultQueriesInvalid(configFile) {
return getConfigFilePropertyError(configFile, DISPLAY_DEFAULT_QUERIES_PROPERTY, 'must be a boolean'); return getConfigFilePropertyError(configFile, DISABLE_DEFAULT_QUERIES_PROPERTY, 'must be a boolean');
} }
exports.getDisableDefaultQueriesInvalid = getDisableDefaultQueriesInvalid; exports.getDisableDefaultQueriesInvalid = getDisableDefaultQueriesInvalid;
function getQueriesInvalid(configFile) { function getQueriesInvalid(configFile) {
@@ -146,29 +263,118 @@ function getConfigFileDoesNotExistErrorMessage(configFile) {
return 'The configuration file "' + configFile + '" does not exist'; return 'The configuration file "' + configFile + '" does not exist';
} }
exports.getConfigFileDoesNotExistErrorMessage = getConfigFileDoesNotExistErrorMessage; exports.getConfigFileDoesNotExistErrorMessage = getConfigFileDoesNotExistErrorMessage;
function getConfigFileRepoFormatInvalidMessage(configFile) {
let error = 'The configuration file "' + configFile + '" is not a supported remote file reference.';
error += ' Expected format <owner>/<repository>/<file-path>@<ref>';
return error;
}
exports.getConfigFileRepoFormatInvalidMessage = getConfigFileRepoFormatInvalidMessage;
function getConfigFileFormatInvalidMessage(configFile) {
return 'The configuration file "' + configFile + '" could not be read';
}
exports.getConfigFileFormatInvalidMessage = getConfigFileFormatInvalidMessage;
function getConfigFileDirectoryGivenMessage(configFile) {
return 'The configuration file "' + configFile + '" looks like a directory, not a file';
}
exports.getConfigFileDirectoryGivenMessage = getConfigFileDirectoryGivenMessage;
function getConfigFilePropertyError(configFile, property, error) { function getConfigFilePropertyError(configFile, property, error) {
return 'The configuration file "' + configFile + '" is invalid: property "' + property + '" ' + error; return 'The configuration file "' + configFile + '" is invalid: property "' + property + '" ' + error;
} }
function initConfig() { /**
let configFile = core.getInput('config-file'); * Gets the set of languages in the current repository
const config = new Config(); */
// If no config file was provided create an empty one async function getLanguagesInRepo() {
if (configFile === '') { var _a;
core.debug('No configuration file was provided'); // Translate between GitHub's API names for languages and ours
return config; const codeqlLanguages = {
'C': 'cpp',
'C++': 'cpp',
'C#': 'csharp',
'Go': 'go',
'Java': 'java',
'JavaScript': 'javascript',
'TypeScript': 'javascript',
'Python': 'python',
};
let repo_nwo = (_a = process.env['GITHUB_REPOSITORY']) === null || _a === void 0 ? void 0 : _a.split("/");
if (repo_nwo) {
let owner = repo_nwo[0];
let repo = repo_nwo[1];
core.debug(`GitHub repo ${owner} ${repo}`);
const response = await api.getApiClient().request("GET /repos/:owner/:repo/languages", ({
owner,
repo
}));
core.debug("Languages API response: " + JSON.stringify(response));
// The GitHub API is going to return languages in order of popularity,
// When we pick a language to autobuild we want to pick the most popular traced language
// Since sets in javascript maintain insertion order, using a set here and then splatting it
// into an array gives us an array of languages ordered by popularity
let languages = new Set();
for (let lang in response.data) {
if (lang in codeqlLanguages) {
languages.add(codeqlLanguages[lang]);
}
}
return [...languages];
} }
// Treat the config file as relative to the workspace else {
const workspacePath = util.getRequiredEnvParam('GITHUB_WORKSPACE'); return [];
configFile = path.resolve(workspacePath, configFile);
// Error if the config file is now outside of the workspace
if (!(configFile + path.sep).startsWith(workspacePath + path.sep)) {
throw new Error(getConfigFileOutsideWorkspaceErrorMessage(configFile));
} }
// Error if the file does not exist }
if (!fs.existsSync(configFile)) { /**
throw new Error(getConfigFileDoesNotExistErrorMessage(configFile)); * Get the languages to analyse.
*
* The result is obtained from the action input parameter 'languages' if that
* has been set, otherwise it is deduced as all languages in the repo that
* can be analysed.
*/
async function getLanguages() {
// Obtain from action input 'languages' if set
let languages = core.getInput('languages', { required: false })
.split(',')
.map(x => x.trim())
.filter(x => x.length > 0);
core.info("Languages from configuration: " + JSON.stringify(languages));
if (languages.length === 0) {
// Obtain languages as all languages in the repo that can be analysed
languages = await getLanguagesInRepo();
core.info("Automatically detected languages: " + JSON.stringify(languages));
} }
const parsedYAML = yaml.safeLoad(fs.readFileSync(configFile, 'utf8')); return languages;
}
/**
* Get the default config for when the user has not supplied one.
*/
async function getDefaultConfig() {
const languages = await getLanguages();
const queries = {};
await addDefaultQueries(languages, queries);
return {
languages: languages,
queries: queries,
pathsIgnore: [],
paths: [],
originalUserInput: {},
};
}
exports.getDefaultConfig = getDefaultConfig;
/**
* Load the config from the given file.
*/
async function loadConfig(configFile) {
let parsedYAML;
if (isLocal(configFile)) {
// Treat the config file as relative to the workspace
const workspacePath = util.getRequiredEnvParam('GITHUB_WORKSPACE');
configFile = path.resolve(workspacePath, configFile);
parsedYAML = getLocalConfig(configFile, workspacePath);
}
else {
parsedYAML = await getRemoteConfig(configFile);
}
// Validate that the 'name' property is syntactically correct,
// even though we don't use the value yet.
if (NAME_PROPERTY in parsedYAML) { if (NAME_PROPERTY in parsedYAML) {
if (typeof parsedYAML[NAME_PROPERTY] !== "string") { if (typeof parsedYAML[NAME_PROPERTY] !== "string") {
throw new Error(getNameInvalid(configFile)); throw new Error(getNameInvalid(configFile));
@@ -176,24 +382,36 @@ function initConfig() {
if (parsedYAML[NAME_PROPERTY].length === 0) { if (parsedYAML[NAME_PROPERTY].length === 0) {
throw new Error(getNameInvalid(configFile)); throw new Error(getNameInvalid(configFile));
} }
config.name = parsedYAML[NAME_PROPERTY];
} }
if (DISPLAY_DEFAULT_QUERIES_PROPERTY in parsedYAML) { const languages = await getLanguages();
if (typeof parsedYAML[DISPLAY_DEFAULT_QUERIES_PROPERTY] !== "boolean") { // If the languages parameter was not given and no languages were
// detected then fail here as this is a workflow configuration error.
if (languages.length === 0) {
throw new Error("Did not detect any languages to analyze. Please update input in workflow.");
}
const queries = {};
const pathsIgnore = [];
const paths = [];
let disableDefaultQueries = false;
if (DISABLE_DEFAULT_QUERIES_PROPERTY in parsedYAML) {
if (typeof parsedYAML[DISABLE_DEFAULT_QUERIES_PROPERTY] !== "boolean") {
throw new Error(getDisableDefaultQueriesInvalid(configFile)); throw new Error(getDisableDefaultQueriesInvalid(configFile));
} }
config.disableDefaultQueries = parsedYAML[DISPLAY_DEFAULT_QUERIES_PROPERTY]; disableDefaultQueries = parsedYAML[DISABLE_DEFAULT_QUERIES_PROPERTY];
}
if (!disableDefaultQueries) {
await addDefaultQueries(languages, queries);
} }
if (QUERIES_PROPERTY in parsedYAML) { if (QUERIES_PROPERTY in parsedYAML) {
if (!(parsedYAML[QUERIES_PROPERTY] instanceof Array)) { if (!(parsedYAML[QUERIES_PROPERTY] instanceof Array)) {
throw new Error(getQueriesInvalid(configFile)); throw new Error(getQueriesInvalid(configFile));
} }
parsedYAML[QUERIES_PROPERTY].forEach(query => { for (const query of parsedYAML[QUERIES_PROPERTY]) {
if (!(QUERIES_USES_PROPERTY in query) || typeof query[QUERIES_USES_PROPERTY] !== "string") { if (!(QUERIES_USES_PROPERTY in query) || typeof query[QUERIES_USES_PROPERTY] !== "string") {
throw new Error(getQueryUsesInvalid(configFile)); throw new Error(getQueryUsesInvalid(configFile));
} }
config.addQuery(configFile, query[QUERIES_USES_PROPERTY]); await parseQueryUses(configFile, languages, queries, query[QUERIES_USES_PROPERTY]);
}); }
} }
if (PATHS_IGNORE_PROPERTY in parsedYAML) { if (PATHS_IGNORE_PROPERTY in parsedYAML) {
if (!(parsedYAML[PATHS_IGNORE_PROPERTY] instanceof Array)) { if (!(parsedYAML[PATHS_IGNORE_PROPERTY] instanceof Array)) {
@@ -203,7 +421,7 @@ function initConfig() {
if (typeof path !== "string" || path === '') { if (typeof path !== "string" || path === '') {
throw new Error(getPathsIgnoreInvalid(configFile)); throw new Error(getPathsIgnoreInvalid(configFile));
} }
config.pathsIgnore.push(path); pathsIgnore.push(validateAndSanitisePath(path, PATHS_IGNORE_PROPERTY, configFile));
}); });
} }
if (PATHS_PROPERTY in parsedYAML) { if (PATHS_PROPERTY in parsedYAML) {
@@ -214,40 +432,123 @@ function initConfig() {
if (typeof path !== "string" || path === '') { if (typeof path !== "string" || path === '') {
throw new Error(getPathsInvalid(configFile)); throw new Error(getPathsInvalid(configFile));
} }
config.paths.push(path); paths.push(validateAndSanitisePath(path, PATHS_PROPERTY, configFile));
}); });
} }
return {
languages,
queries,
pathsIgnore,
paths,
originalUserInput: parsedYAML
};
}
/**
* Load and return the config.
*
* This will parse the config from the user input if present, or generate
* a default config. The parsed config is then stored to a known location.
*/
async function initConfig() {
const configFile = core.getInput('config-file');
let config;
// If no config file was provided create an empty one
if (configFile === '') {
core.debug('No configuration file was provided');
config = await getDefaultConfig();
}
else {
config = await loadConfig(configFile);
}
// Save the config so we can easily access it again in the future
await saveConfig(config);
return config; return config;
} }
function getConfigFolder() { exports.initConfig = initConfig;
function isLocal(configPath) {
// If the path starts with ./, look locally
if (configPath.indexOf("./") === 0) {
return true;
}
return (configPath.indexOf("@") === -1);
}
function getLocalConfig(configFile, workspacePath) {
// Error if the config file is now outside of the workspace
if (!(configFile + path.sep).startsWith(workspacePath + path.sep)) {
throw new Error(getConfigFileOutsideWorkspaceErrorMessage(configFile));
}
// Error if the file does not exist
if (!fs.existsSync(configFile)) {
throw new Error(getConfigFileDoesNotExistErrorMessage(configFile));
}
return yaml.safeLoad(fs.readFileSync(configFile, 'utf8'));
}
async function getRemoteConfig(configFile) {
// retrieve the various parts of the config location, and ensure they're present
const format = new RegExp('(?<owner>[^/]+)/(?<repo>[^/]+)/(?<path>[^@]+)@(?<ref>.*)');
const pieces = format.exec(configFile);
// 5 = 4 groups + the whole expression
if (pieces === null || pieces.groups === undefined || pieces.length < 5) {
throw new Error(getConfigFileRepoFormatInvalidMessage(configFile));
}
const response = await api.getApiClient().repos.getContents({
owner: pieces.groups.owner,
repo: pieces.groups.repo,
path: pieces.groups.path,
ref: pieces.groups.ref,
});
let fileContents;
if ("content" in response.data && response.data.content !== undefined) {
fileContents = response.data.content;
}
else if (Array.isArray(response.data)) {
throw new Error(getConfigFileDirectoryGivenMessage(configFile));
}
else {
throw new Error(getConfigFileFormatInvalidMessage(configFile));
}
return yaml.safeLoad(Buffer.from(fileContents, 'base64').toString('binary'));
}
/**
* Get the directory where the parsed config will be stored.
*/
function getPathToParsedConfigFolder() {
return util.getRequiredEnvParam('RUNNER_TEMP'); return util.getRequiredEnvParam('RUNNER_TEMP');
} }
function getConfigFile() { /**
return path.join(getConfigFolder(), 'config'); * Get the file path where the parsed config will be stored.
*/
function getPathToParsedConfigFile() {
return path.join(getPathToParsedConfigFolder(), 'config');
} }
exports.getConfigFile = getConfigFile; exports.getPathToParsedConfigFile = getPathToParsedConfigFile;
/**
* Store the given config to the path returned from getPathToParsedConfigFile.
*/
async function saveConfig(config) { async function saveConfig(config) {
const configString = JSON.stringify(config); const configString = JSON.stringify(config);
await io.mkdirP(getConfigFolder()); await io.mkdirP(getPathToParsedConfigFolder());
fs.writeFileSync(getConfigFile(), configString, 'utf8'); fs.writeFileSync(getPathToParsedConfigFile(), configString, 'utf8');
core.debug('Saved config:'); core.debug('Saved config:');
core.debug(configString); core.debug(configString);
} }
async function loadConfig() { /**
const configFile = getConfigFile(); * Get the config.
if (fs.existsSync(configFile)) { *
const configString = fs.readFileSync(configFile, 'utf8'); * If this is the first time in a workflow that this is being called then
core.debug('Loaded config:'); * this will parse the config from the user input. The parsed config is then
core.debug(configString); * stored to a known location. On the second and further calls, this will
return JSON.parse(configString); * return the contents of the parsed config from the known location.
} */
else { async function getConfig() {
const config = initConfig(); const configFile = getPathToParsedConfigFile();
core.debug('Initialized config:'); if (!fs.existsSync(configFile)) {
core.debug(JSON.stringify(config)); throw new Error("Config file could not be found at expected location. Has the 'init' action been called?");
await saveConfig(config);
return config;
} }
const configString = fs.readFileSync(configFile, 'utf8');
core.debug('Loaded config:');
core.debug(configString);
return JSON.parse(configString);
} }
exports.loadConfig = loadConfig; exports.getConfig = getConfig;
//# sourceMappingURL=config-utils.js.map //# sourceMappingURL=config-utils.js.map
File diff suppressed because one or more lines are too long
+263 -33
View File
@@ -1,7 +1,4 @@
"use strict"; "use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var __importStar = (this && this.__importStar) || function (mod) { var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod; if (mod && mod.__esModule) return mod;
var result = {}; var result = {};
@@ -9,14 +6,21 @@ var __importStar = (this && this.__importStar) || function (mod) {
result["default"] = mod; result["default"] = mod;
return result; return result;
}; };
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const github = __importStar(require("@actions/github"));
const ava_1 = __importDefault(require("ava")); const ava_1 = __importDefault(require("ava"));
const fs = __importStar(require("fs")); const fs = __importStar(require("fs"));
const path = __importStar(require("path")); const path = __importStar(require("path"));
const sinon_1 = __importDefault(require("sinon"));
const api = __importStar(require("./api-client"));
const CodeQL = __importStar(require("./codeql"));
const configUtils = __importStar(require("./config-utils")); const configUtils = __importStar(require("./config-utils"));
const testing_utils_1 = require("./testing-utils"); const testing_utils_1 = require("./testing-utils");
const util = __importStar(require("./util")); const util = __importStar(require("./util"));
testing_utils_1.silenceDebugOutput(ava_1.default); testing_utils_1.setupTests(ava_1.default);
function setInput(name, value) { function setInput(name, value) {
// Transformation copied from // Transformation copied from
// https://github.com/actions/toolkit/blob/05e39f551d33e1688f61b209ab5cdd335198f1b8/packages/core/src/core.ts#L69 // https://github.com/actions/toolkit/blob/05e39f551d33e1688f61b209ab5cdd335198f1b8/packages/core/src/core.ts#L69
@@ -28,27 +32,60 @@ function setInput(name, value) {
delete process.env[envVar]; delete process.env[envVar];
} }
} }
function mockGetContents(content) {
// Passing an auth token is required, so we just use a dummy value
let client = new github.GitHub('123');
const response = {
data: content
};
const spyGetContents = sinon_1.default.stub(client.repos, "getContents").resolves(response);
sinon_1.default.stub(api, "getApiClient").value(() => client);
return spyGetContents;
}
ava_1.default("load empty config", async (t) => { ava_1.default("load empty config", async (t) => {
return await util.withTmpDir(async (tmpDir) => { return await util.withTmpDir(async (tmpDir) => {
process.env['RUNNER_TEMP'] = tmpDir; process.env['RUNNER_TEMP'] = tmpDir;
process.env['GITHUB_WORKSPACE'] = tmpDir; process.env['GITHUB_WORKSPACE'] = tmpDir;
setInput('config-file', undefined); setInput('config-file', undefined);
const config = await configUtils.loadConfig(); setInput('languages', 'javascript,python');
t.deepEqual(config, new configUtils.Config()); CodeQL.setCodeQL({
resolveQueries: async function () {
return {
byLanguage: {},
noDeclaredLanguage: {},
multipleDeclaredLanguages: {},
};
},
});
const config = await configUtils.initConfig();
t.deepEqual(config, await configUtils.getDefaultConfig());
}); });
}); });
ava_1.default("loading config saves config", async (t) => { ava_1.default("loading config saves config", async (t) => {
return await util.withTmpDir(async (tmpDir) => { return await util.withTmpDir(async (tmpDir) => {
process.env['RUNNER_TEMP'] = tmpDir; process.env['RUNNER_TEMP'] = tmpDir;
process.env['GITHUB_WORKSPACE'] = tmpDir; process.env['GITHUB_WORKSPACE'] = tmpDir;
const configFile = configUtils.getConfigFile(); setInput('config-file', undefined);
setInput('languages', 'javascript,python');
CodeQL.setCodeQL({
resolveQueries: async function () {
return {
byLanguage: {},
noDeclaredLanguage: {},
multipleDeclaredLanguages: {},
};
},
});
// Sanity check the saved config file does not already exist // Sanity check the saved config file does not already exist
t.false(fs.existsSync(configFile)); t.false(fs.existsSync(configUtils.getPathToParsedConfigFile()));
const config = await configUtils.loadConfig(); // Sanity check that getConfig throws before we have called initConfig
await t.throwsAsync(configUtils.getConfig);
const config1 = await configUtils.initConfig();
// The saved config file should now exist // The saved config file should now exist
t.true(fs.existsSync(configFile)); t.true(fs.existsSync(configUtils.getPathToParsedConfigFile()));
// And the contents should parse correctly to the config that was returned // And that same newly-initialised config should now be returned by getConfig
t.deepEqual(fs.readFileSync(configFile, 'utf8'), JSON.stringify(config)); const config2 = await configUtils.getConfig();
t.deepEqual(config1, config2);
}); });
}); });
ava_1.default("load input outside of workspace", async (t) => { ava_1.default("load input outside of workspace", async (t) => {
@@ -57,23 +94,39 @@ ava_1.default("load input outside of workspace", async (t) => {
process.env['GITHUB_WORKSPACE'] = tmpDir; process.env['GITHUB_WORKSPACE'] = tmpDir;
setInput('config-file', '../input'); setInput('config-file', '../input');
try { try {
await configUtils.loadConfig(); await configUtils.initConfig();
throw new Error('loadConfig did not throw error'); throw new Error('initConfig did not throw error');
} }
catch (err) { catch (err) {
t.deepEqual(err, new Error(configUtils.getConfigFileOutsideWorkspaceErrorMessage(path.join(tmpDir, '../input')))); t.deepEqual(err, new Error(configUtils.getConfigFileOutsideWorkspaceErrorMessage(path.join(tmpDir, '../input'))));
} }
}); });
}); });
ava_1.default("load non-local input with invalid repo syntax", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
process.env['RUNNER_TEMP'] = tmpDir;
process.env['GITHUB_WORKSPACE'] = tmpDir;
// no filename given, just a repo
setInput('config-file', 'octo-org/codeql-config@main');
try {
await configUtils.initConfig();
throw new Error('initConfig did not throw error');
}
catch (err) {
t.deepEqual(err, new Error(configUtils.getConfigFileRepoFormatInvalidMessage('octo-org/codeql-config@main')));
}
});
});
ava_1.default("load non-existent input", async (t) => { ava_1.default("load non-existent input", async (t) => {
return await util.withTmpDir(async (tmpDir) => { return await util.withTmpDir(async (tmpDir) => {
process.env['RUNNER_TEMP'] = tmpDir; process.env['RUNNER_TEMP'] = tmpDir;
process.env['GITHUB_WORKSPACE'] = tmpDir; process.env['GITHUB_WORKSPACE'] = tmpDir;
t.false(fs.existsSync(path.join(tmpDir, 'input'))); t.false(fs.existsSync(path.join(tmpDir, 'input')));
setInput('config-file', 'input'); setInput('config-file', 'input');
setInput('languages', 'javascript');
try { try {
await configUtils.loadConfig(); await configUtils.initConfig();
throw new Error('loadConfig did not throw error'); throw new Error('initConfig did not throw error');
} }
catch (err) { catch (err) {
t.deepEqual(err, new Error(configUtils.getConfigFileDoesNotExistErrorMessage(path.join(tmpDir, 'input')))); t.deepEqual(err, new Error(configUtils.getConfigFileDoesNotExistErrorMessage(path.join(tmpDir, 'input'))));
@@ -84,10 +137,109 @@ ava_1.default("load non-empty input", async (t) => {
return await util.withTmpDir(async (tmpDir) => { return await util.withTmpDir(async (tmpDir) => {
process.env['RUNNER_TEMP'] = tmpDir; process.env['RUNNER_TEMP'] = tmpDir;
process.env['GITHUB_WORKSPACE'] = tmpDir; process.env['GITHUB_WORKSPACE'] = tmpDir;
CodeQL.setCodeQL({
resolveQueries: async function () {
return {
byLanguage: {
'javascript': {
'/foo/a.ql': {},
'/bar/b.ql': {},
},
},
noDeclaredLanguage: {},
multipleDeclaredLanguages: {},
};
},
});
// Just create a generic config object with non-default values for all fields // Just create a generic config object with non-default values for all fields
const inputFileContents = ` const inputFileContents = `
name: my config name: my config
disable-default-queries: true disable-default-queries: true
queries:
- uses: ./foo
paths-ignore:
- a
- b
paths:
- c/d`;
fs.mkdirSync(path.join(tmpDir, 'foo'));
// And the config we expect it to parse to
const expectedConfig = {
languages: ['javascript'],
queries: { 'javascript': ['/foo/a.ql', '/bar/b.ql'] },
pathsIgnore: ['a', 'b'],
paths: ['c/d'],
originalUserInput: {
name: 'my config',
'disable-default-queries': true,
queries: [{ uses: './foo' }],
'paths-ignore': ['a', 'b'],
paths: ['c/d'],
},
};
fs.writeFileSync(path.join(tmpDir, 'input'), inputFileContents, 'utf8');
setInput('config-file', 'input');
setInput('languages', 'javascript');
const actualConfig = await configUtils.initConfig();
// Should exactly equal the object we constructed earlier
t.deepEqual(actualConfig, expectedConfig);
});
});
ava_1.default("default queries are used", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
process.env['RUNNER_TEMP'] = tmpDir;
process.env['GITHUB_WORKSPACE'] = tmpDir;
// Check that the default behaviour is to add the default queries.
// In this case if a config file is specified but does not include
// the disable-default-queries field.
// We determine this by whether CodeQL.resolveQueries is called
// with the correct arguments.
const resolveQueriesArgs = [];
CodeQL.setCodeQL({
resolveQueries: async function (queries, extraSearchPath) {
resolveQueriesArgs.push({ queries, extraSearchPath });
return {
byLanguage: {
'javascript': {},
},
noDeclaredLanguage: {},
multipleDeclaredLanguages: {},
};
},
});
// The important point of this config is that it doesn't specify
// the disable-default-queries field.
// Any other details are hopefully irrelevant for this tetst.
const inputFileContents = `
paths:
- foo`;
fs.mkdirSync(path.join(tmpDir, 'foo'));
fs.writeFileSync(path.join(tmpDir, 'input'), inputFileContents, 'utf8');
setInput('config-file', 'input');
setInput('languages', 'javascript');
await configUtils.initConfig();
// Check resolve queries was called correctly
t.deepEqual(resolveQueriesArgs.length, 1);
t.deepEqual(resolveQueriesArgs[0].queries, ['javascript-code-scanning.qls']);
t.deepEqual(resolveQueriesArgs[0].extraSearchPath, undefined);
});
});
ava_1.default("API client used when reading remote config", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
process.env['RUNNER_TEMP'] = tmpDir;
process.env['GITHUB_WORKSPACE'] = tmpDir;
CodeQL.setCodeQL({
resolveQueries: async function () {
return {
byLanguage: {},
noDeclaredLanguage: {},
multipleDeclaredLanguages: {},
};
},
});
const inputFileContents = `
name: my config
disable-default-queries: true
queries: queries:
- uses: ./ - uses: ./
- uses: ./foo - uses: ./foo
@@ -97,21 +249,52 @@ ava_1.default("load non-empty input", async (t) => {
- b - b
paths: paths:
- c/d`; - c/d`;
fs.mkdirSync(path.join(tmpDir, 'foo')); const dummyResponse = {
// And the config we expect it to parse to content: Buffer.from(inputFileContents).toString("base64"),
const expectedConfig = new configUtils.Config(); };
expectedConfig.name = 'my config'; const spyGetContents = mockGetContents(dummyResponse);
expectedConfig.disableDefaultQueries = true; // Create checkout directory for remote queries repository
expectedConfig.additionalQueries.push(fs.realpathSync(tmpDir)); fs.mkdirSync(path.join(tmpDir, 'foo/bar'), { recursive: true });
expectedConfig.additionalQueries.push(fs.realpathSync(path.join(tmpDir, 'foo'))); setInput('config-file', 'octo-org/codeql-config/config.yaml@main');
expectedConfig.externalQueries = [new configUtils.ExternalQuery('foo/bar', 'dev')]; setInput('languages', 'javascript');
expectedConfig.pathsIgnore = ['a', 'b']; await configUtils.initConfig();
expectedConfig.paths = ['c/d']; t.assert(spyGetContents.called);
fs.writeFileSync(path.join(tmpDir, 'input'), inputFileContents, 'utf8'); });
setInput('config-file', 'input'); });
const actualConfig = await configUtils.loadConfig(); ava_1.default("Remote config handles the case where a directory is provided", async (t) => {
// Should exactly equal the object we constructed earlier return await util.withTmpDir(async (tmpDir) => {
t.deepEqual(actualConfig, expectedConfig); process.env['RUNNER_TEMP'] = tmpDir;
process.env['GITHUB_WORKSPACE'] = tmpDir;
const dummyResponse = []; // directories are returned as arrays
mockGetContents(dummyResponse);
const repoReference = 'octo-org/codeql-config/config.yaml@main';
setInput('config-file', repoReference);
try {
await configUtils.initConfig();
throw new Error('initConfig did not throw error');
}
catch (err) {
t.deepEqual(err, new Error(configUtils.getConfigFileDirectoryGivenMessage(repoReference)));
}
});
});
ava_1.default("Invalid format of remote config handled correctly", async (t) => {
return await util.withTmpDir(async (tmpDir) => {
process.env['RUNNER_TEMP'] = tmpDir;
process.env['GITHUB_WORKSPACE'] = tmpDir;
const dummyResponse = {
// note no "content" property here
};
mockGetContents(dummyResponse);
const repoReference = 'octo-org/codeql-config/config.yaml@main';
setInput('config-file', repoReference);
try {
await configUtils.initConfig();
throw new Error('initConfig did not throw error');
}
catch (err) {
t.deepEqual(err, new Error(configUtils.getConfigFileFormatInvalidMessage(repoReference)));
}
}); });
}); });
function doInvalidInputTest(testName, inputFileContents, expectedErrorMessageGenerator) { function doInvalidInputTest(testName, inputFileContents, expectedErrorMessageGenerator) {
@@ -119,12 +302,22 @@ function doInvalidInputTest(testName, inputFileContents, expectedErrorMessageGen
return await util.withTmpDir(async (tmpDir) => { return await util.withTmpDir(async (tmpDir) => {
process.env['RUNNER_TEMP'] = tmpDir; process.env['RUNNER_TEMP'] = tmpDir;
process.env['GITHUB_WORKSPACE'] = tmpDir; process.env['GITHUB_WORKSPACE'] = tmpDir;
CodeQL.setCodeQL({
resolveQueries: async function () {
return {
byLanguage: {},
noDeclaredLanguage: {},
multipleDeclaredLanguages: {},
};
},
});
const inputFile = path.join(tmpDir, 'input'); const inputFile = path.join(tmpDir, 'input');
fs.writeFileSync(inputFile, inputFileContents, 'utf8'); fs.writeFileSync(inputFile, inputFileContents, 'utf8');
setInput('config-file', 'input'); setInput('config-file', 'input');
setInput('languages', 'javascript');
try { try {
await configUtils.loadConfig(); await configUtils.initConfig();
throw new Error('loadConfig did not throw error'); throw new Error('initConfig did not throw error');
} }
catch (err) { catch (err) {
t.deepEqual(err, new Error(expectedErrorMessageGenerator(inputFile))); t.deepEqual(err, new Error(expectedErrorMessageGenerator(inputFile)));
@@ -161,4 +354,41 @@ doInvalidQueryUsesTest("foo@master", c => configUtils.getQueryUsesInvalid(c, "fo
doInvalidQueryUsesTest("https://github.com/foo/bar@master", c => configUtils.getQueryUsesInvalid(c, "https://github.com/foo/bar@master")); doInvalidQueryUsesTest("https://github.com/foo/bar@master", c => configUtils.getQueryUsesInvalid(c, "https://github.com/foo/bar@master"));
doInvalidQueryUsesTest("./foo", c => configUtils.getLocalPathDoesNotExist(c, "foo")); doInvalidQueryUsesTest("./foo", c => configUtils.getLocalPathDoesNotExist(c, "foo"));
doInvalidQueryUsesTest("./..", c => configUtils.getLocalPathOutsideOfRepository(c, "..")); doInvalidQueryUsesTest("./..", c => configUtils.getLocalPathOutsideOfRepository(c, ".."));
const validPaths = [
'foo',
'foo/',
'foo/**',
'foo/**/',
'foo/**/**',
'foo/**/bar/**/baz',
'**/',
'**/foo',
'/foo',
];
const invalidPaths = [
'a/***/b',
'a/**b',
'a/b**',
'**',
];
ava_1.default('path validations', t => {
// Dummy values to pass to validateAndSanitisePath
const propertyName = 'paths';
const configFile = './.github/codeql/config.yml';
for (const path of validPaths) {
t.truthy(configUtils.validateAndSanitisePath(path, propertyName, configFile));
}
for (const path of invalidPaths) {
t.throws(() => configUtils.validateAndSanitisePath(path, propertyName, configFile));
}
});
ava_1.default('path sanitisation', t => {
// Dummy values to pass to validateAndSanitisePath
const propertyName = 'paths';
const configFile = './.github/codeql/config.yml';
// Valid paths are not modified
t.deepEqual(configUtils.validateAndSanitisePath('foo/bar', propertyName, configFile), 'foo/bar');
// Trailing stars are stripped
t.deepEqual(configUtils.validateAndSanitisePath('foo/**', propertyName, configFile), 'foo/');
});
//# sourceMappingURL=config-utils.test.js.map //# sourceMappingURL=config-utils.test.js.map
File diff suppressed because one or more lines are too long
+16 -15
View File
@@ -12,22 +12,23 @@ const exec = __importStar(require("@actions/exec"));
const fs = __importStar(require("fs")); const fs = __importStar(require("fs"));
const path = __importStar(require("path")); const path = __importStar(require("path"));
const util = __importStar(require("./util")); const util = __importStar(require("./util"));
async function checkoutExternalQueries(config) { /**
* Check out repository at the given ref, and return the directory of the checkout.
*/
async function checkoutExternalRepository(repository, ref) {
const folder = util.getRequiredEnvParam('RUNNER_TEMP'); const folder = util.getRequiredEnvParam('RUNNER_TEMP');
for (const externalQuery of config.externalQueries) { core.info('Checking out ' + repository);
core.info('Checking out ' + externalQuery.repository); const checkoutLocation = path.join(folder, repository);
const checkoutLocation = path.join(folder, externalQuery.repository); if (!fs.existsSync(checkoutLocation)) {
if (!fs.existsSync(checkoutLocation)) { const repoURL = 'https://github.com/' + repository + '.git';
const repoURL = 'https://github.com/' + externalQuery.repository + '.git'; await exec.exec('git', ['clone', repoURL, checkoutLocation]);
await exec.exec('git', ['clone', repoURL, checkoutLocation]); await exec.exec('git', [
await exec.exec('git', [ '--work-tree=' + checkoutLocation,
'--work-tree=' + checkoutLocation, '--git-dir=' + checkoutLocation + '/.git',
'--git-dir=' + checkoutLocation + '/.git', 'checkout', ref,
'checkout', externalQuery.ref, ]);
]);
}
config.additionalQueries.push(path.join(checkoutLocation, externalQuery.path));
} }
return checkoutLocation;
} }
exports.checkoutExternalQueries = checkoutExternalQueries; exports.checkoutExternalRepository = checkoutExternalRepository;
//# sourceMappingURL=external-queries.js.map //# sourceMappingURL=external-queries.js.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"external-queries.js","sourceRoot":"","sources":["../src/external-queries.ts"],"names":[],"mappings":";;;;;;;;;AAAA,oDAAsC;AACtC,oDAAsC;AACtC,uCAAyB;AACzB,2CAA6B;AAG7B,6CAA+B;AAExB,KAAK,UAAU,uBAAuB,CAAC,MAA0B;IACtE,MAAM,MAAM,GAAG,IAAI,CAAC,mBAAmB,CAAC,aAAa,CAAC,CAAC;IAEvD,KAAK,MAAM,aAAa,IAAI,MAAM,CAAC,eAAe,EAAE;QAClD,IAAI,CAAC,IAAI,CAAC,eAAe,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;QAEtD,MAAM,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;QACrE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,gBAAgB,CAAC,EAAE;YACpC,MAAM,OAAO,GAAG,qBAAqB,GAAG,aAAa,CAAC,UAAU,GAAG,MAAM,CAAC;YAC1E,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC;YAC7D,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE;gBACrB,cAAc,GAAG,gBAAgB;gBACjC,YAAY,GAAG,gBAAgB,GAAG,OAAO;gBACzC,UAAU,EAAE,aAAa,CAAC,GAAG;aAC9B,CAAC,CAAC;SACJ;QAED,MAAM,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC;KAChF;AACH,CAAC;AAnBD,0DAmBC"} {"version":3,"file":"external-queries.js","sourceRoot":"","sources":["../src/external-queries.ts"],"names":[],"mappings":";;;;;;;;;AAAA,oDAAsC;AACtC,oDAAsC;AACtC,uCAAyB;AACzB,2CAA6B;AAE7B,6CAA+B;AAE/B;;GAEG;AACI,KAAK,UAAU,0BAA0B,CAAC,UAAkB,EAAE,GAAW;IAC9E,MAAM,MAAM,GAAG,IAAI,CAAC,mBAAmB,CAAC,aAAa,CAAC,CAAC;IAEvD,IAAI,CAAC,IAAI,CAAC,eAAe,GAAG,UAAU,CAAC,CAAC;IAExC,MAAM,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACvD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,gBAAgB,CAAC,EAAE;QACpC,MAAM,OAAO,GAAG,qBAAqB,GAAG,UAAU,GAAG,MAAM,CAAC;QAC5D,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC;QAC7D,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE;YACrB,cAAc,GAAG,gBAAgB;YACjC,YAAY,GAAG,gBAAgB,GAAG,OAAO;YACzC,UAAU,EAAE,GAAG;SAChB,CAAC,CAAC;KACJ;IAED,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAjBD,gEAiBC"}
+3 -8
View File
@@ -13,20 +13,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
const ava_1 = __importDefault(require("ava")); const ava_1 = __importDefault(require("ava"));
const fs = __importStar(require("fs")); const fs = __importStar(require("fs"));
const path = __importStar(require("path")); const path = __importStar(require("path"));
const configUtils = __importStar(require("./config-utils"));
const externalQueries = __importStar(require("./external-queries")); const externalQueries = __importStar(require("./external-queries"));
const testing_utils_1 = require("./testing-utils"); const testing_utils_1 = require("./testing-utils");
const util = __importStar(require("./util")); const util = __importStar(require("./util"));
testing_utils_1.silenceDebugOutput(ava_1.default); testing_utils_1.setupTests(ava_1.default);
ava_1.default("checkoutExternalQueries", async (t) => { ava_1.default("checkoutExternalQueries", async (t) => {
let config = new configUtils.Config();
config.externalQueries = [
new configUtils.ExternalQuery("github/codeql-go", "df4c6869212341b601005567381944ed90906b6b"),
];
await util.withTmpDir(async (tmpDir) => { await util.withTmpDir(async (tmpDir) => {
process.env["RUNNER_TEMP"] = tmpDir; process.env["RUNNER_TEMP"] = tmpDir;
await externalQueries.checkoutExternalQueries(config); await externalQueries.checkoutExternalRepository("github/codeql-go", "df4c6869212341b601005567381944ed90906b6b");
// COPYRIGHT file existed in df4c6869212341b601005567381944ed90906b6b but not in master // COPYRIGHT file existed in df4c6869212341b601005567381944ed90906b6b but not in the default branch
t.true(fs.existsSync(path.join(tmpDir, "github", "codeql-go", "COPYRIGHT"))); t.true(fs.existsSync(path.join(tmpDir, "github", "codeql-go", "COPYRIGHT")));
}); });
}); });
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"external-queries.test.js","sourceRoot":"","sources":["../src/external-queries.test.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,8CAAuB;AACvB,uCAAyB;AACzB,2CAA6B;AAE7B,4DAA8C;AAC9C,oEAAsD;AACtD,mDAAmD;AACnD,6CAA+B;AAE/B,kCAAkB,CAAC,aAAI,CAAC,CAAC;AAEzB,aAAI,CAAC,yBAAyB,EAAE,KAAK,EAAC,CAAC,EAAC,EAAE;IACxC,IAAI,MAAM,GAAG,IAAI,WAAW,CAAC,MAAM,EAAE,CAAC;IACtC,MAAM,CAAC,eAAe,GAAG;QACvB,IAAI,WAAW,CAAC,aAAa,CAAC,kBAAkB,EAAE,0CAA0C,CAAC;KAC9F,CAAC;IAEF,MAAM,IAAI,CAAC,UAAU,CAAC,KAAK,EAAC,MAAM,EAAC,EAAE;QACnC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,GAAG,MAAM,CAAC;QACpC,MAAM,eAAe,CAAC,uBAAuB,CAAC,MAAM,CAAC,CAAC;QAEtD,uFAAuF;QACvF,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;IAC/E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"} {"version":3,"file":"external-queries.test.js","sourceRoot":"","sources":["../src/external-queries.test.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,8CAAuB;AACvB,uCAAyB;AACzB,2CAA6B;AAE7B,oEAAsD;AACtD,mDAA2C;AAC3C,6CAA+B;AAE/B,0BAAU,CAAC,aAAI,CAAC,CAAC;AAEjB,aAAI,CAAC,yBAAyB,EAAE,KAAK,EAAC,CAAC,EAAC,EAAE;IACxC,MAAM,IAAI,CAAC,UAAU,CAAC,KAAK,EAAC,MAAM,EAAC,EAAE;QACnC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,GAAG,MAAM,CAAC;QACpC,MAAM,eAAe,CAAC,0BAA0B,CAAC,kBAAkB,EAAE,0CAA0C,CAAC,CAAC;QAEjH,mGAAmG;QACnG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;IAC/E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
+55 -136
View File
@@ -8,185 +8,104 @@ var __importStar = (this && this.__importStar) || function (mod) {
}; };
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(require("@actions/core")); const core = __importStar(require("@actions/core"));
const exec = __importStar(require("@actions/exec"));
const io = __importStar(require("@actions/io")); const io = __importStar(require("@actions/io"));
const fs = __importStar(require("fs")); const fs = __importStar(require("fs"));
const path = __importStar(require("path")); const path = __importStar(require("path"));
const codeql_1 = require("./codeql");
const configUtils = __importStar(require("./config-utils")); const configUtils = __importStar(require("./config-utils"));
const externalQueries = __importStar(require("./external-queries"));
const sharedEnv = __importStar(require("./shared-environment")); const sharedEnv = __importStar(require("./shared-environment"));
const upload_lib = __importStar(require("./upload-lib")); const upload_lib = __importStar(require("./upload-lib"));
const util = __importStar(require("./util")); const util = __importStar(require("./util"));
/** async function sendStatusReport(startedAt, queriesStats, uploadStats, error) {
* A list of queries from https://github.com/github/codeql that var _a, _b, _c;
* we don't want to run. Disabling them here is a quicker alternative to const status = ((_a = queriesStats) === null || _a === void 0 ? void 0 : _a.analyze_failure_language) !== undefined || error !== undefined ? 'failure' : 'success';
* disabling them in the code scanning query suites. Queries should also const statusReportBase = await util.createStatusReportBase('finish', status, startedAt, (_b = error) === null || _b === void 0 ? void 0 : _b.message, (_c = error) === null || _c === void 0 ? void 0 : _c.stack);
* be disabled in the suites, and removed from this list here once the const statusReport = {
* bundle is updated to make those suite changes live. ...statusReportBase,
* ...(queriesStats || {}),
* Format is a map from language to an array of path suffixes of .ql files. ...(uploadStats || {}),
*/ };
const DISABLED_BUILTIN_QUERIES = { await util.sendStatusReport(statusReport);
'csharp': [
'ql/src/Security Features/CWE-937/VulnerablePackage.ql',
'ql/src/Security Features/CWE-451/MissingXFrameOptions.ql',
]
};
function queryIsDisabled(language, query) {
return (DISABLED_BUILTIN_QUERIES[language] || [])
.some(disabledQuery => query.endsWith(disabledQuery));
} }
async function createdDBForScannedLanguages(codeqlCmd, databaseFolder) { async function createdDBForScannedLanguages(databaseFolder) {
const scannedLanguages = process.env[sharedEnv.CODEQL_ACTION_SCANNED_LANGUAGES]; const scannedLanguages = process.env[sharedEnv.CODEQL_ACTION_SCANNED_LANGUAGES];
if (scannedLanguages) { if (scannedLanguages) {
const codeql = codeql_1.getCodeQL();
for (const language of scannedLanguages.split(',')) { for (const language of scannedLanguages.split(',')) {
core.startGroup('Extracting ' + language); core.startGroup('Extracting ' + language);
// Get extractor location await codeql.extractScannedLanguage(path.join(databaseFolder, language), language);
let extractorPath = '';
await exec.exec(codeqlCmd, ['resolve', 'extractor', '--format=json', '--language=' + language], {
silent: true,
listeners: {
stdout: (data) => { extractorPath += data.toString(); },
stderr: (data) => { process.stderr.write(data); }
}
});
// Set trace command
const ext = process.platform === 'win32' ? '.cmd' : '.sh';
const traceCommand = path.resolve(JSON.parse(extractorPath), 'tools', 'autobuild' + ext);
// Run trace command
await exec.exec(codeqlCmd, ['database', 'trace-command', path.join(databaseFolder, language), '--', traceCommand]);
core.endGroup(); core.endGroup();
} }
} }
} }
async function finalizeDatabaseCreation(codeqlCmd, databaseFolder) { async function finalizeDatabaseCreation(databaseFolder, config) {
await createdDBForScannedLanguages(codeqlCmd, databaseFolder); await createdDBForScannedLanguages(databaseFolder);
const languages = process.env[sharedEnv.CODEQL_ACTION_LANGUAGES] || ''; const codeql = codeql_1.getCodeQL();
for (const language of languages.split(',')) { for (const language of config.languages) {
core.startGroup('Finalizing ' + language); core.startGroup('Finalizing ' + language);
await exec.exec(codeqlCmd, ['database', 'finalize', path.join(databaseFolder, language)]); await codeql.finalizeDatabase(path.join(databaseFolder, language));
core.endGroup(); core.endGroup();
} }
} }
async function runResolveQueries(codeqlCmd, queries) {
let output = '';
const options = {
listeners: {
stdout: (data) => {
output += data.toString();
}
}
};
await exec.exec(codeqlCmd, [
'resolve',
'queries',
...queries,
'--format=bylanguage'
], options);
return JSON.parse(output);
}
async function resolveQueryLanguages(codeqlCmd, config) {
let res = new Map();
if (!config.disableDefaultQueries || config.additionalSuites.length !== 0) {
const suites = [];
for (const language of await util.getLanguages()) {
if (!config.disableDefaultQueries) {
suites.push(language + '-code-scanning.qls');
}
for (const additionalSuite of config.additionalSuites) {
suites.push(language + '-' + additionalSuite + '.qls');
}
}
const resolveQueriesOutputObject = await runResolveQueries(codeqlCmd, suites);
for (const [language, queries] of Object.entries(resolveQueriesOutputObject.byLanguage)) {
if (res[language] === undefined) {
res[language] = [];
}
res[language].push(...Object.keys(queries).filter(q => !queryIsDisabled(language, q)));
}
}
if (config.additionalQueries.length !== 0) {
const resolveQueriesOutputObject = await runResolveQueries(codeqlCmd, config.additionalQueries);
for (const [language, queries] of Object.entries(resolveQueriesOutputObject.byLanguage)) {
if (res[language] === undefined) {
res[language] = [];
}
res[language].push(...Object.keys(queries));
}
const noDeclaredLanguage = resolveQueriesOutputObject.noDeclaredLanguage;
const noDeclaredLanguageQueries = Object.keys(noDeclaredLanguage);
if (noDeclaredLanguageQueries.length !== 0) {
throw new Error('Some queries do not declare a language, their qlpack.yml file is missing or is invalid');
}
const multipleDeclaredLanguages = resolveQueriesOutputObject.multipleDeclaredLanguages;
const multipleDeclaredLanguagesQueries = Object.keys(multipleDeclaredLanguages);
if (multipleDeclaredLanguagesQueries.length !== 0) {
throw new Error('Some queries declare multiple languages, their qlpack.yml file is missing or is invalid');
}
}
return res;
}
// Runs queries and creates sarif files in the given folder // Runs queries and creates sarif files in the given folder
async function runQueries(codeqlCmd, databaseFolder, sarifFolder, config) { async function runQueries(databaseFolder, sarifFolder, config) {
const queriesPerLanguage = await resolveQueryLanguages(codeqlCmd, config); const codeql = codeql_1.getCodeQL();
for (let database of fs.readdirSync(databaseFolder)) { for (let language of fs.readdirSync(databaseFolder)) {
core.startGroup('Analyzing ' + database); core.startGroup('Analyzing ' + language);
const queries = queriesPerLanguage[database] || []; const queries = config.queries[language] || [];
if (queries.length === 0) { if (queries.length === 0) {
throw new Error('Unable to analyse ' + database + ' as no queries were selected for this language'); throw new Error('Unable to analyse ' + language + ' as no queries were selected for this language');
}
try {
// Pass the queries to codeql using a file instead of using the command
// line to avoid command line length restrictions, particularly on windows.
const querySuite = path.join(databaseFolder, language + '-queries.qls');
const querySuiteContents = queries.map(q => '- query: ' + q).join('\n');
fs.writeFileSync(querySuite, querySuiteContents);
core.debug('Query suite file for ' + language + '...\n' + querySuiteContents);
const sarifFile = path.join(sarifFolder, language + '.sarif');
await codeql.databaseAnalyze(path.join(databaseFolder, language), sarifFile, querySuite);
core.debug('SARIF results for database ' + language + ' created at "' + sarifFile + '"');
core.endGroup();
}
catch (e) {
// For now the fields about query performance are not populated
return {
analyze_failure_language: language,
};
} }
// Pass the queries to codeql using a file instead of using the command
// line to avoid command line length restrictions, particularly on windows.
const querySuite = path.join(databaseFolder, database + '-queries.qls');
const querySuiteContents = queries.map(q => '- query: ' + q).join('\n');
fs.writeFileSync(querySuite, querySuiteContents);
core.debug('Query suite file for ' + database + '...\n' + querySuiteContents);
const sarifFile = path.join(sarifFolder, database + '.sarif');
await exec.exec(codeqlCmd, [
'database',
'analyze',
util.getMemoryFlag(),
util.getThreadsFlag(),
path.join(databaseFolder, database),
'--format=sarif-latest',
'--output=' + sarifFile,
'--no-sarif-add-snippets',
querySuite
]);
core.debug('SARIF results for database ' + database + ' created at "' + sarifFile + '"');
core.endGroup();
} }
return {};
} }
async function run() { async function run() {
const startedAt = new Date();
let queriesStats = undefined;
let uploadStats = undefined;
try { try {
if (util.should_abort('finish', true) || !await util.reportActionStarting('finish')) { if (util.should_abort('finish', true) ||
!await util.sendStatusReport(await util.createStatusReportBase('finish', 'starting', startedAt), true)) {
return; return;
} }
const config = await configUtils.loadConfig(); const config = await configUtils.getConfig();
core.exportVariable(sharedEnv.ODASA_TRACER_CONFIGURATION, ''); core.exportVariable(sharedEnv.ODASA_TRACER_CONFIGURATION, '');
delete process.env[sharedEnv.ODASA_TRACER_CONFIGURATION]; delete process.env[sharedEnv.ODASA_TRACER_CONFIGURATION];
const codeqlCmd = util.getRequiredEnvParam(sharedEnv.CODEQL_ACTION_CMD);
const databaseFolder = util.getRequiredEnvParam(sharedEnv.CODEQL_ACTION_DATABASE_DIR); const databaseFolder = util.getRequiredEnvParam(sharedEnv.CODEQL_ACTION_DATABASE_DIR);
const sarifFolder = core.getInput('output'); const sarifFolder = core.getInput('output');
await io.mkdirP(sarifFolder); await io.mkdirP(sarifFolder);
core.info('Finalizing database creation'); core.info('Finalizing database creation');
await finalizeDatabaseCreation(codeqlCmd, databaseFolder); await finalizeDatabaseCreation(databaseFolder, config);
await externalQueries.checkoutExternalQueries(config);
core.info('Analyzing database'); core.info('Analyzing database');
await runQueries(codeqlCmd, databaseFolder, sarifFolder, config); queriesStats = await runQueries(databaseFolder, sarifFolder, config);
if ('true' === core.getInput('upload')) { if ('true' === core.getInput('upload')) {
if (!await upload_lib.upload(sarifFolder)) { uploadStats = await upload_lib.upload(sarifFolder);
await util.reportActionFailed('finish', 'upload');
return;
}
} }
} }
catch (error) { catch (error) {
core.setFailed(error.message); core.setFailed(error.message);
await util.reportActionFailed('finish', error.message, error.stack); await sendStatusReport(startedAt, queriesStats, uploadStats, error);
return; return;
} }
await util.reportActionSucceeded('finish'); await sendStatusReport(startedAt, queriesStats, uploadStats);
} }
run().catch(e => { run().catch(e => {
core.setFailed("analyze action failed: " + e); core.setFailed("analyze action failed: " + e);
File diff suppressed because one or more lines are too long
+12 -13
View File
@@ -146,10 +146,10 @@ function locationUpdateCallback(result, location) {
result.partialFingerprints.primaryLocationLineHash = hash; result.partialFingerprints.primaryLocationLineHash = hash;
} }
else if (existingFingerprint !== hash) { else if (existingFingerprint !== hash) {
core.warning("Calculated fingerprint of " + hash + core.warning('Calculated fingerprint of ' + hash +
" for file " + location.physicalLocation.artifactLocation.uri + ' for file ' + location.physicalLocation.artifactLocation.uri +
" line " + lineNumber + ' line ' + lineNumber +
", but found existing inconsistent fingerprint value " + existingFingerprint); ', but found existing inconsistent fingerprint value ' + existingFingerprint);
} }
}; };
} }
@@ -164,14 +164,14 @@ function resolveUriToFile(location, artifacts) {
location.index < 0 || location.index < 0 ||
location.index >= artifacts.length || location.index >= artifacts.length ||
typeof artifacts[location.index].location !== 'object') { typeof artifacts[location.index].location !== 'object') {
core.debug('Ignoring location as index "' + location.index + '" is invalid'); core.debug(`Ignoring location as URI "${location.index}" is invalid`);
return undefined; return undefined;
} }
location = artifacts[location.index].location; location = artifacts[location.index].location;
} }
// Get the URI and decode // Get the URI and decode
if (typeof location.uri !== 'string') { if (typeof location.uri !== 'string') {
core.debug('Ignoring location as uri "' + location.uri + '" is invalid'); core.debug(`Ignoring location as index "${location.uri}" is invalid`);
return undefined; return undefined;
} }
let uri = decodeURIComponent(location.uri); let uri = decodeURIComponent(location.uri);
@@ -181,13 +181,13 @@ function resolveUriToFile(location, artifacts) {
uri = uri.substring(fileUriPrefix.length); uri = uri.substring(fileUriPrefix.length);
} }
if (uri.indexOf('://') !== -1) { if (uri.indexOf('://') !== -1) {
core.debug('Ignoring location URI "' + uri + "' as the scheme is not recognised"); core.debug(`Ignoring location URI "${uri}" as the scheme is not recognised`);
return undefined; return undefined;
} }
// Discard any absolute paths that aren't in the src root // Discard any absolute paths that aren't in the src root
const srcRootPrefix = process.env['GITHUB_WORKSPACE'] + '/'; const srcRootPrefix = process.env['GITHUB_WORKSPACE'] + '/';
if (uri.startsWith('/') && !uri.startsWith(srcRootPrefix)) { if (uri.startsWith('/') && !uri.startsWith(srcRootPrefix)) {
core.debug('Ignoring location URI "' + uri + "' as it is outside of the src root"); core.debug(`Ignoring location URI "${uri}" as it is outside of the src root`);
return undefined; return undefined;
} }
// Just assume a relative path is relative to the src root. // Just assume a relative path is relative to the src root.
@@ -198,7 +198,7 @@ function resolveUriToFile(location, artifacts) {
} }
// Check the file exists // Check the file exists
if (!fs.existsSync(uri)) { if (!fs.existsSync(uri)) {
core.debug("Unable to compute fingerprint for non-existent file: " + uri); core.debug(`Unable to compute fingerprint for non-existent file: ${uri}`);
return undefined; return undefined;
} }
return uri; return uri;
@@ -207,6 +207,7 @@ exports.resolveUriToFile = resolveUriToFile;
// Compute fingerprints for results in the given sarif file // Compute fingerprints for results in the given sarif file
// and return an updated sarif file contents. // and return an updated sarif file contents.
function addFingerprints(sarifContents) { function addFingerprints(sarifContents) {
var _a, _b;
let sarif = JSON.parse(sarifContents); let sarif = JSON.parse(sarifContents);
// Gather together results for the same file and construct // Gather together results for the same file and construct
// callbacks to accept hashes for that file and update the location // callbacks to accept hashes for that file and update the location
@@ -217,10 +218,8 @@ function addFingerprints(sarifContents) {
for (const result of run.results || []) { for (const result of run.results || []) {
// Check the primary location is defined correctly and is in the src root // Check the primary location is defined correctly and is in the src root
const primaryLocation = (result.locations || [])[0]; const primaryLocation = (result.locations || [])[0];
if (!primaryLocation || if (!((_b = (_a = primaryLocation) === null || _a === void 0 ? void 0 : _a.physicalLocation) === null || _b === void 0 ? void 0 : _b.artifactLocation)) {
!primaryLocation.physicalLocation || core.debug(`Unable to compute fingerprint for invalid location: ${JSON.stringify(primaryLocation)}`);
!primaryLocation.physicalLocation.artifactLocation) {
core.debug("Unable to compute fingerprint for invalid location: " + JSON.stringify(primaryLocation));
continue; continue;
} }
const filepath = resolveUriToFile(primaryLocation.physicalLocation.artifactLocation, artifacts); const filepath = resolveUriToFile(primaryLocation.physicalLocation.artifactLocation, artifacts);
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -15,7 +15,7 @@ const fs = __importStar(require("fs"));
const path = __importStar(require("path")); const path = __importStar(require("path"));
const fingerprints = __importStar(require("./fingerprints")); const fingerprints = __importStar(require("./fingerprints"));
const testing_utils_1 = require("./testing-utils"); const testing_utils_1 = require("./testing-utils");
testing_utils_1.silenceDebugOutput(ava_1.default); testing_utils_1.setupTests(ava_1.default);
function testHash(t, input, expectedHashes) { function testHash(t, input, expectedHashes) {
let index = 0; let index = 0;
let callback = function (lineNumber, hash) { let callback = function (lineNumber, hash) {
File diff suppressed because one or more lines are too long
+41 -37
View File
@@ -13,8 +13,8 @@ const io = __importStar(require("@actions/io"));
const fs = __importStar(require("fs")); const fs = __importStar(require("fs"));
const path = __importStar(require("path")); const path = __importStar(require("path"));
const analysisPaths = __importStar(require("./analysis-paths")); const analysisPaths = __importStar(require("./analysis-paths"));
const codeql_1 = require("./codeql");
const configUtils = __importStar(require("./config-utils")); const configUtils = __importStar(require("./config-utils"));
const setuptools = __importStar(require("./setup-tools"));
const sharedEnv = __importStar(require("./shared-environment")); const sharedEnv = __importStar(require("./shared-environment"));
const util = __importStar(require("./util")); const util = __importStar(require("./util"));
const CRITICAL_TRACER_VARS = new Set(['SEMMLE_PRELOAD_libtrace', const CRITICAL_TRACER_VARS = new Set(['SEMMLE_PRELOAD_libtrace',
@@ -28,12 +28,7 @@ const CRITICAL_TRACER_VARS = new Set(['SEMMLE_PRELOAD_libtrace',
'SEMMLE_JAVA_TOOL_OPTIONS' 'SEMMLE_JAVA_TOOL_OPTIONS'
]); ]);
async function tracerConfig(codeql, database, compilerSpec) { async function tracerConfig(codeql, database, compilerSpec) {
const compilerSpecArg = compilerSpec ? ["--compiler-spec=" + compilerSpec] : []; const env = await codeql.getTracerEnv(database, compilerSpec);
let envFile = path.resolve(database, 'working', 'env.tmp');
await exec.exec(codeql.cmd, ['database', 'trace-command', database,
...compilerSpecArg,
process.execPath, path.resolve(__dirname, 'tracer-env.js'), envFile]);
const env = JSON.parse(fs.readFileSync(envFile, 'utf-8'));
const config = env['ODASA_TRACER_CONFIGURATION']; const config = env['ODASA_TRACER_CONFIGURATION'];
const info = { spec: config, env: {} }; const info = { spec: config, env: {} };
// Extract critical tracer variables from the environment // Extract critical tracer variables from the environment
@@ -126,34 +121,50 @@ function concatTracerConfigs(configs) {
fs.writeFileSync(envPath, buffer); fs.writeFileSync(envPath, buffer);
return { env, spec }; return { env, spec };
} }
async function sendSuccessStatusReport(startedAt, config) {
const statusReportBase = await util.createStatusReportBase('init', 'success', startedAt);
const languages = config.languages.join(',');
const workflowLanguages = core.getInput('languages', { required: false });
const paths = (config.originalUserInput.paths || []).join(',');
const pathsIgnore = (config.originalUserInput['paths-ignore'] || []).join(',');
const disableDefaultQueries = config.originalUserInput['disable-default-queries'] ? languages : '';
const queries = (config.originalUserInput.queries || []).map(q => q.uses).join(',');
const statusReport = {
...statusReportBase,
languages: languages,
workflow_languages: workflowLanguages,
paths: paths,
paths_ignore: pathsIgnore,
disable_default_queries: disableDefaultQueries,
queries: queries,
};
await util.sendStatusReport(statusReport);
}
async function run() { async function run() {
let languages; const startedAt = new Date();
let config;
let codeql;
try { try {
if (util.should_abort('init', false) || !await util.reportActionStarting('init')) { if (util.should_abort('init', false) ||
!await util.sendStatusReport(await util.createStatusReportBase('init', 'starting', startedAt), true)) {
return; return;
} }
core.startGroup('Setup CodeQL tools');
codeql = await codeql_1.setupCodeQL();
await codeql.printVersion();
core.endGroup();
core.startGroup('Load language configuration'); core.startGroup('Load language configuration');
const config = await configUtils.loadConfig(); config = await configUtils.initConfig();
languages = await util.getLanguages(); analysisPaths.includeAndExcludeAnalysisPaths(config);
// If the languages parameter was not given and no languages were
// detected then fail here as this is a workflow configuration error.
if (languages.length === 0) {
throw new Error("Did not detect any languages to analyze. Please update input in workflow.");
}
analysisPaths.includeAndExcludeAnalysisPaths(config, languages);
core.endGroup(); core.endGroup();
} }
catch (e) { catch (e) {
core.setFailed(e.message); core.setFailed(e.message);
await util.reportActionAborted('init', e.message); await util.sendStatusReport(await util.createStatusReportBase('init', 'aborted', startedAt, e.message));
return; return;
} }
try { try {
const sourceRoot = path.resolve(); const sourceRoot = path.resolve();
core.startGroup('Setup CodeQL tools');
const codeqlSetup = await setuptools.setupCodeQL();
await exec.exec(codeqlSetup.cmd, ['version', '--format=json']);
core.endGroup();
// Forward Go flags // Forward Go flags
const goFlags = process.env['GOFLAGS']; const goFlags = process.env['GOFLAGS'];
if (goFlags) { if (goFlags) {
@@ -168,19 +179,13 @@ async function run() {
let tracedLanguages = {}; let tracedLanguages = {};
let scannedLanguages = []; let scannedLanguages = [];
// TODO: replace this code once CodeQL supports multi-language tracing // TODO: replace this code once CodeQL supports multi-language tracing
for (let language of languages) { for (let language of config.languages) {
const languageDatabase = path.join(databaseFolder, language); const languageDatabase = path.join(databaseFolder, language);
// Init language database // Init language database
await exec.exec(codeqlSetup.cmd, [ await codeql.databaseInit(languageDatabase, language, sourceRoot);
'database',
'init',
languageDatabase,
'--language=' + language,
'--source-root=' + sourceRoot,
]);
// TODO: add better detection of 'traced languages' instead of using a hard coded list // TODO: add better detection of 'traced languages' instead of using a hard coded list
if (['cpp', 'java', 'csharp'].includes(language)) { if (['cpp', 'java', 'csharp'].includes(language)) {
const config = await tracerConfig(codeqlSetup, languageDatabase); const config = await tracerConfig(codeql, languageDatabase);
tracedLanguages[language] = config; tracedLanguages[language] = config;
} }
else { else {
@@ -196,16 +201,16 @@ async function run() {
} }
core.exportVariable('ODASA_TRACER_CONFIGURATION', mainTracerConfig.spec); core.exportVariable('ODASA_TRACER_CONFIGURATION', mainTracerConfig.spec);
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
core.exportVariable('DYLD_INSERT_LIBRARIES', path.join(codeqlSetup.tools, 'osx64', 'libtrace.dylib')); core.exportVariable('DYLD_INSERT_LIBRARIES', path.join(codeql.getDir(), 'tools', 'osx64', 'libtrace.dylib'));
} }
else if (process.platform === 'win32') { else if (process.platform === 'win32') {
await exec.exec('powershell', [ await exec.exec('powershell', [
path.resolve(__dirname, '..', 'src', 'inject-tracer.ps1'), path.resolve(__dirname, '..', 'src', 'inject-tracer.ps1'),
path.resolve(codeqlSetup.tools, 'win64', 'tracer.exe'), path.resolve(codeql.getDir(), 'tools', 'win64', 'tracer.exe'),
], { env: { 'ODASA_TRACER_CONFIGURATION': mainTracerConfig.spec } }); ], { env: { 'ODASA_TRACER_CONFIGURATION': mainTracerConfig.spec } });
} }
else { else {
core.exportVariable('LD_PRELOAD', path.join(codeqlSetup.tools, 'linux64', '${LIB}trace.so')); core.exportVariable('LD_PRELOAD', path.join(codeql.getDir(), 'tools', 'linux64', '${LIB}trace.so'));
} }
} }
} }
@@ -213,14 +218,13 @@ async function run() {
core.exportVariable(sharedEnv.CODEQL_ACTION_TRACED_LANGUAGES, tracedLanguageKeys.join(',')); core.exportVariable(sharedEnv.CODEQL_ACTION_TRACED_LANGUAGES, tracedLanguageKeys.join(','));
// TODO: make this a "private" environment variable of the action // TODO: make this a "private" environment variable of the action
core.exportVariable(sharedEnv.CODEQL_ACTION_DATABASE_DIR, databaseFolder); core.exportVariable(sharedEnv.CODEQL_ACTION_DATABASE_DIR, databaseFolder);
core.exportVariable(sharedEnv.CODEQL_ACTION_CMD, codeqlSetup.cmd);
} }
catch (error) { catch (error) {
core.setFailed(error.message); core.setFailed(error.message);
await util.reportActionFailed('init', error.message, error.stack); await util.sendStatusReport(await util.createStatusReportBase('init', 'failure', startedAt, error.message, error.stack));
return; return;
} }
await util.reportActionSucceeded('init'); await sendSuccessStatusReport(startedAt, config);
core.exportVariable(sharedEnv.CODEQL_ACTION_INIT_COMPLETED, 'true'); core.exportVariable(sharedEnv.CODEQL_ACTION_INIT_COMPLETED, 'true');
} }
run().catch(e => { run().catch(e => {
File diff suppressed because one or more lines are too long
+1 -3
View File
@@ -1,8 +1,6 @@
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.CODEQL_ACTION_CMD = 'CODEQL_ACTION_CMD';
exports.CODEQL_ACTION_DATABASE_DIR = 'CODEQL_ACTION_DATABASE_DIR'; exports.CODEQL_ACTION_DATABASE_DIR = 'CODEQL_ACTION_DATABASE_DIR';
exports.CODEQL_ACTION_LANGUAGES = 'CODEQL_ACTION_LANGUAGES';
exports.CODEQL_ACTION_ANALYSIS_KEY = 'CODEQL_ACTION_ANALYSIS_KEY'; exports.CODEQL_ACTION_ANALYSIS_KEY = 'CODEQL_ACTION_ANALYSIS_KEY';
exports.ODASA_TRACER_CONFIGURATION = 'ODASA_TRACER_CONFIGURATION'; exports.ODASA_TRACER_CONFIGURATION = 'ODASA_TRACER_CONFIGURATION';
exports.CODEQL_ACTION_SCANNED_LANGUAGES = 'CODEQL_ACTION_SCANNED_LANGUAGES'; exports.CODEQL_ACTION_SCANNED_LANGUAGES = 'CODEQL_ACTION_SCANNED_LANGUAGES';
@@ -12,7 +10,7 @@ exports.CODEQL_ACTION_TRACED_LANGUAGES = 'CODEQL_ACTION_TRACED_LANGUAGES';
// action (i.e. the upload action is being used by a third-party integrator) // action (i.e. the upload action is being used by a third-party integrator)
// then this variable will be assigned the start time of the action invoked // then this variable will be assigned the start time of the action invoked
// rather that the init action. // rather that the init action.
exports.CODEQL_ACTION_STARTED_AT = 'CODEQL_ACTION_STARTED_AT'; exports.CODEQL_WORKFLOW_STARTED_AT = 'CODEQL_WORKFLOW_STARTED_AT';
// Populated when the init action completes successfully // Populated when the init action completes successfully
exports.CODEQL_ACTION_INIT_COMPLETED = 'CODEQL_ACTION_INIT_COMPLETED'; exports.CODEQL_ACTION_INIT_COMPLETED = 'CODEQL_ACTION_INIT_COMPLETED';
//# sourceMappingURL=shared-environment.js.map //# sourceMappingURL=shared-environment.js.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"shared-environment.js","sourceRoot":"","sources":["../src/shared-environment.ts"],"names":[],"mappings":";;AAAa,QAAA,iBAAiB,GAAG,mBAAmB,CAAC;AACxC,QAAA,0BAA0B,GAAG,4BAA4B,CAAC;AAC1D,QAAA,uBAAuB,GAAG,yBAAyB,CAAC;AACpD,QAAA,0BAA0B,GAAG,4BAA4B,CAAC;AAC1D,QAAA,0BAA0B,GAAG,4BAA4B,CAAC;AAC1D,QAAA,+BAA+B,GAAG,iCAAiC,CAAC;AACpE,QAAA,8BAA8B,GAAG,gCAAgC,CAAC;AAC/E,wEAAwE;AACxE,2EAA2E;AAC3E,4EAA4E;AAC5E,2EAA2E;AAC3E,+BAA+B;AAClB,QAAA,wBAAwB,GAAG,0BAA0B,CAAC;AACnE,wDAAwD;AAC3C,QAAA,4BAA4B,GAAG,8BAA8B,CAAC"} {"version":3,"file":"shared-environment.js","sourceRoot":"","sources":["../src/shared-environment.ts"],"names":[],"mappings":";;AAAa,QAAA,0BAA0B,GAAG,4BAA4B,CAAC;AAC1D,QAAA,0BAA0B,GAAG,4BAA4B,CAAC;AAC1D,QAAA,0BAA0B,GAAG,4BAA4B,CAAC;AAC1D,QAAA,+BAA+B,GAAG,iCAAiC,CAAC;AACpE,QAAA,8BAA8B,GAAG,gCAAgC,CAAC;AAC/E,wEAAwE;AACxE,2EAA2E;AAC3E,4EAA4E;AAC5E,2EAA2E;AAC3E,+BAA+B;AAClB,QAAA,0BAA0B,GAAG,4BAA4B,CAAC;AACvE,wDAAwD;AAC3C,QAAA,4BAA4B,GAAG,8BAA8B,CAAC"}
+29 -2
View File
@@ -1,5 +1,17 @@
"use strict"; "use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const sinon_1 = __importDefault(require("sinon"));
const CodeQL = __importStar(require("./codeql"));
function wrapOutput(context) { function wrapOutput(context) {
// Function signature taken from Socket.write. // Function signature taken from Socket.write.
// Note there are two overloads: // Note there are two overloads:
@@ -25,9 +37,13 @@ function wrapOutput(context) {
return true; return true;
}; };
} }
function silenceDebugOutput(test) { function setupTests(test) {
const typedTest = test; const typedTest = test;
typedTest.beforeEach(t => { typedTest.beforeEach(t => {
// Set an empty CodeQL object so that all method calls will fail
// unless the test explicitly sets one up.
CodeQL.setCodeQL({});
// Replace stdout and stderr so we can record output during tests
t.context.testOutput = ""; t.context.testOutput = "";
const processStdoutWrite = process.stdout.write.bind(process.stdout); const processStdoutWrite = process.stdout.write.bind(process.stdout);
t.context.stdoutWrite = processStdoutWrite; t.context.stdoutWrite = processStdoutWrite;
@@ -35,14 +51,25 @@ function silenceDebugOutput(test) {
const processStderrWrite = process.stderr.write.bind(process.stderr); const processStderrWrite = process.stderr.write.bind(process.stderr);
t.context.stderrWrite = processStderrWrite; t.context.stderrWrite = processStderrWrite;
process.stderr.write = wrapOutput(t.context); process.stderr.write = wrapOutput(t.context);
// Many tests modify environment variables. Take a copy now so that
// we reset them after the test to keep tests independent of each other.
// process.env only has strings fields, so a shallow copy is fine.
t.context.env = {};
Object.assign(t.context.env, process.env);
}); });
typedTest.afterEach.always(t => { typedTest.afterEach.always(t => {
// Restore stdout and stderr
// The captured output is only replayed if the test failed
process.stdout.write = t.context.stdoutWrite; process.stdout.write = t.context.stdoutWrite;
process.stderr.write = t.context.stderrWrite; process.stderr.write = t.context.stderrWrite;
if (!t.passed) { if (!t.passed) {
process.stdout.write(t.context.testOutput); process.stdout.write(t.context.testOutput);
} }
// Undo any modifications made by sinon
sinon_1.default.restore();
// Undo any modifications to the env
process.env = t.context.env;
}); });
} }
exports.silenceDebugOutput = silenceDebugOutput; exports.setupTests = setupTests;
//# sourceMappingURL=testing-utils.js.map //# sourceMappingURL=testing-utils.js.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"testing-utils.js","sourceRoot":"","sources":["../src/testing-utils.ts"],"names":[],"mappings":";;AAIA,SAAS,UAAU,CAAC,OAAoB;IACtC,8CAA8C;IAC9C,gCAAgC;IAChC,2EAA2E;IAC3E,2FAA2F;IAC3F,OAAO,CAAC,KAA0B,EAAE,QAAiB,EAAE,EAA0B,EAAW,EAAE;QAC5F,2CAA2C;QAC3C,IAAI,EAAE,KAAK,SAAS,IAAI,OAAO,QAAQ,KAAK,UAAU,EAAE;YACtD,EAAE,GAAG,QAAQ,CAAC;YACd,QAAQ,GAAG,SAAS,CAAC;SACtB;QAED,oBAAoB;QACpB,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;YAC7B,OAAO,CAAC,UAAU,IAAI,KAAK,CAAC;SAC7B;aAAM;YACL,OAAO,CAAC,UAAU,IAAI,IAAI,WAAW,CAAC,QAAQ,IAAI,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;SAC1E;QAED,iDAAiD;QACjD,IAAI,EAAE,KAAK,SAAS,IAAI,OAAO,EAAE,KAAK,UAAU,EAAE;YAChD,EAAE,EAAE,CAAC;SACN;QAED,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;AACJ,CAAC;AAED,SAAgB,kBAAkB,CAAC,IAAwB;IACzD,MAAM,SAAS,GAAG,IAAkC,CAAC;IAErD,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE;QACvB,CAAC,CAAC,OAAO,CAAC,UAAU,GAAG,EAAE,CAAC;QAE1B,MAAM,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACrE,CAAC,CAAC,OAAO,CAAC,WAAW,GAAG,kBAAkB,CAAC;QAC3C,OAAO,CAAC,MAAM,CAAC,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,OAAO,CAAQ,CAAC;QAEpD,MAAM,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACrE,CAAC,CAAC,OAAO,CAAC,WAAW,GAAG,kBAAkB,CAAC;QAC3C,OAAO,CAAC,MAAM,CAAC,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,OAAO,CAAQ,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;QAC7B,OAAO,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC;QAC7C,OAAO,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC;QAE7C,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE;YACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;SAC5C;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAvBD,gDAuBC"} {"version":3,"file":"testing-utils.js","sourceRoot":"","sources":["../src/testing-utils.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,kDAA0B;AAE1B,iDAAmC;AAInC,SAAS,UAAU,CAAC,OAAoB;IACtC,8CAA8C;IAC9C,gCAAgC;IAChC,2EAA2E;IAC3E,2FAA2F;IAC3F,OAAO,CAAC,KAA0B,EAAE,QAAiB,EAAE,EAA0B,EAAW,EAAE;QAC5F,2CAA2C;QAC3C,IAAI,EAAE,KAAK,SAAS,IAAI,OAAO,QAAQ,KAAK,UAAU,EAAE;YACtD,EAAE,GAAG,QAAQ,CAAC;YACd,QAAQ,GAAG,SAAS,CAAC;SACtB;QAED,oBAAoB;QACpB,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;YAC7B,OAAO,CAAC,UAAU,IAAI,KAAK,CAAC;SAC7B;aAAM;YACL,OAAO,CAAC,UAAU,IAAI,IAAI,WAAW,CAAC,QAAQ,IAAI,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;SAC1E;QAED,iDAAiD;QACjD,IAAI,EAAE,KAAK,SAAS,IAAI,OAAO,EAAE,KAAK,UAAU,EAAE;YAChD,EAAE,EAAE,CAAC;SACN;QAED,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;AACJ,CAAC;AAED,SAAgB,UAAU,CAAC,IAAwB;IACjD,MAAM,SAAS,GAAG,IAAkC,CAAC;IAErD,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE;QACvB,gEAAgE;QAChE,0CAA0C;QAC1C,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QAErB,iEAAiE;QACjE,CAAC,CAAC,OAAO,CAAC,UAAU,GAAG,EAAE,CAAC;QAC1B,MAAM,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACrE,CAAC,CAAC,OAAO,CAAC,WAAW,GAAG,kBAAkB,CAAC;QAC3C,OAAO,CAAC,MAAM,CAAC,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,OAAO,CAAQ,CAAC;QACpD,MAAM,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACrE,CAAC,CAAC,OAAO,CAAC,WAAW,GAAG,kBAAkB,CAAC;QAC3C,OAAO,CAAC,MAAM,CAAC,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,OAAO,CAAQ,CAAC;QAEpD,mEAAmE;QACnE,wEAAwE;QACxE,kEAAkE;QAClE,CAAC,CAAC,OAAO,CAAC,GAAG,GAAG,EAAE,CAAC;QACnB,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;QAC7B,4BAA4B;QAC5B,0DAA0D;QAC1D,OAAO,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC;QAC7C,OAAO,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC;QAC7C,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE;YACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;SAC5C;QAED,uCAAuC;QACvC,eAAK,CAAC,OAAO,EAAE,CAAC;QAEhB,oCAAoC;QACpC,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC;IAC9B,CAAC,CAAC,CAAC;AACL,CAAC;AAvCD,gCAuCC"}
+42 -45
View File
@@ -11,13 +11,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
}; };
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(require("@actions/core")); const core = __importStar(require("@actions/core"));
const http = __importStar(require("@actions/http-client"));
const auth = __importStar(require("@actions/http-client/auth"));
const file_url_1 = __importDefault(require("file-url")); const file_url_1 = __importDefault(require("file-url"));
const fs = __importStar(require("fs")); const fs = __importStar(require("fs"));
const jsonschema = __importStar(require("jsonschema")); const jsonschema = __importStar(require("jsonschema"));
const path = __importStar(require("path")); const path = __importStar(require("path"));
const zlib_1 = __importDefault(require("zlib")); const zlib_1 = __importDefault(require("zlib"));
const api = __importStar(require("./api-client"));
const fingerprints = __importStar(require("./fingerprints")); const fingerprints = __importStar(require("./fingerprints"));
const sharedEnv = __importStar(require("./shared-environment")); const sharedEnv = __importStar(require("./shared-environment"));
const util = __importStar(require("./util")); const util = __importStar(require("./util"));
@@ -49,37 +48,37 @@ async function uploadPayload(payload) {
// If in test mode we don't want to upload the results // If in test mode we don't want to upload the results
const testMode = process.env['TEST_MODE'] === 'true' || false; const testMode = process.env['TEST_MODE'] === 'true' || false;
if (testMode) { if (testMode) {
return true; return;
} }
const githubToken = core.getInput('token'); const [owner, repo] = util.getRequiredEnvParam("GITHUB_REPOSITORY").split("/");
const ph = new auth.BearerCredentialHandler(githubToken);
const client = new http.HttpClient('Code Scanning : Upload SARIF', [ph]);
const url = 'https://api.github.com/repos/' + process.env['GITHUB_REPOSITORY'] + '/code-scanning/analysis';
// Make up to 4 attempts to upload, and sleep for these // Make up to 4 attempts to upload, and sleep for these
// number of seconds between each attempt. // number of seconds between each attempt.
// We don't want to backoff too much to avoid wasting action // We don't want to backoff too much to avoid wasting action
// minutes, but just waiting a little bit could maybe help. // minutes, but just waiting a little bit could maybe help.
const backoffPeriods = [1, 5, 15]; const backoffPeriods = [1, 5, 15];
for (let attempt = 0; attempt <= backoffPeriods.length; attempt++) { for (let attempt = 0; attempt <= backoffPeriods.length; attempt++) {
const res = await client.put(url, payload); const response = await api.getApiClient().request("PUT /repos/:owner/:repo/code-scanning/analysis", ({
core.debug('response status: ' + res.message.statusCode); owner: owner,
const statusCode = res.message.statusCode; repo: repo,
data: payload,
}));
core.debug('response status: ' + response.status);
const statusCode = response.status;
if (statusCode === 202) { if (statusCode === 202) {
core.info("Successfully uploaded results"); core.info("Successfully uploaded results");
return true; return;
} }
const requestID = res.message.headers["x-github-request-id"]; const requestID = response.headers["x-github-request-id"];
// On any other status code that's not 5xx mark the upload as failed // On any other status code that's not 5xx mark the upload as failed
if (!statusCode || statusCode < 500 || statusCode >= 600) { if (!statusCode || statusCode < 500 || statusCode >= 600) {
core.setFailed('Upload failed (' + requestID + '): (' + statusCode + ') ' + await res.readBody()); throw new Error('Upload failed (' + requestID + '): (' + statusCode + ') ' + JSON.stringify(response.data));
return false;
} }
// On a 5xx status code we may retry the request // On a 5xx status code we may retry the request
if (attempt < backoffPeriods.length) { if (attempt < backoffPeriods.length) {
// Log the failure as a warning but don't mark the action as failed yet // Log the failure as a warning but don't mark the action as failed yet
core.warning('Upload attempt (' + (attempt + 1) + ' of ' + (backoffPeriods.length + 1) + core.warning('Upload attempt (' + (attempt + 1) + ' of ' + (backoffPeriods.length + 1) +
') failed (' + requestID + '). Retrying in ' + backoffPeriods[attempt] + ') failed (' + requestID + '). Retrying in ' + backoffPeriods[attempt] +
' seconds: (' + statusCode + ') ' + await res.readBody()); ' seconds: (' + statusCode + ') ' + JSON.stringify(response.data));
// Sleep for the backoff period // Sleep for the backoff period
await new Promise(r => setTimeout(r, backoffPeriods[attempt] * 1000)); await new Promise(r => setTimeout(r, backoffPeriods[attempt] * 1000));
continue; continue;
@@ -88,11 +87,12 @@ async function uploadPayload(payload) {
// If the upload fails with 5xx then we assume it is a temporary problem // If the upload fails with 5xx then we assume it is a temporary problem
// and not an error that the user has caused or can fix. // and not an error that the user has caused or can fix.
// We avoid marking the job as failed to avoid breaking CI workflows. // We avoid marking the job as failed to avoid breaking CI workflows.
core.error('Upload failed (' + requestID + '): (' + statusCode + ') ' + await res.readBody()); throw new Error('Upload failed (' + requestID + '): (' + statusCode + ') ' + JSON.stringify(response.data));
return false;
} }
} }
return false; // This case shouldn't ever happen as the final iteration of the loop
// will always throw an error instead of exiting to here.
throw new Error('Upload failed');
} }
// Uploads a single sarif file or a directory of sarif files // Uploads a single sarif file or a directory of sarif files
// depending on what the path happens to refer to. // depending on what the path happens to refer to.
@@ -103,8 +103,7 @@ async function upload(input) {
.filter(f => f.endsWith(".sarif")) .filter(f => f.endsWith(".sarif"))
.map(f => path.resolve(input, f)); .map(f => path.resolve(input, f));
if (sarifFiles.length === 0) { if (sarifFiles.length === 0) {
core.setFailed("No SARIF files found to upload in \"" + input + "\"."); throw new Error("No SARIF files found to upload in \"" + input + "\".");
return false;
} }
return await uploadFiles(sarifFiles); return await uploadFiles(sarifFiles);
} }
@@ -123,27 +122,22 @@ function countResultsInSarif(sarif) {
} }
exports.countResultsInSarif = countResultsInSarif; exports.countResultsInSarif = countResultsInSarif;
// Validates that the given file path refers to a valid SARIF file. // Validates that the given file path refers to a valid SARIF file.
// Returns a non-empty list of error message if the file is invalid, // Throws an error if the file is invalid.
// otherwise returns the empty list if the file is valid.
function validateSarifFileSchema(sarifFilePath) { function validateSarifFileSchema(sarifFilePath) {
const sarif = JSON.parse(fs.readFileSync(sarifFilePath, 'utf8')); const sarif = JSON.parse(fs.readFileSync(sarifFilePath, 'utf8'));
const schema = JSON.parse(fs.readFileSync(__dirname + '/../src/sarif_v2.1.0_schema.json', 'utf8')); const schema = JSON.parse(fs.readFileSync(__dirname + '/../src/sarif_v2.1.0_schema.json', 'utf8'));
const result = new jsonschema.Validator().validate(sarif, schema); const result = new jsonschema.Validator().validate(sarif, schema);
if (result.valid) { if (!result.valid) {
return true; // Output the more verbose error messages in groups as these may be very large.
}
else {
// Set the failure message to the stacks of all the errors.
// This should be of a manageable size and may even give enough to fix the error.
const errorMessages = result.errors.map(e => "- " + e.stack);
core.setFailed("Unable to upload \"" + sarifFilePath + "\" as it is not valid SARIF:\n" + errorMessages.join("\n"));
// Also output the more verbose error messages in groups as these may be very large.
for (const error of result.errors) { for (const error of result.errors) {
core.startGroup("Error details: " + error.stack); core.startGroup("Error details: " + error.stack);
core.info(JSON.stringify(error, null, 2)); core.info(JSON.stringify(error, null, 2));
core.endGroup(); core.endGroup();
} }
return false; // Set the main error message to the stacks of all the errors.
// This should be of a manageable size and may even give enough to fix the error.
const sarifErrors = result.errors.map(e => "- " + e.stack);
throw new Error("Unable to upload \"" + sarifFilePath + "\" as it is not valid SARIF:\n" + sarifErrors.join("\n"));
} }
} }
exports.validateSarifFileSchema = validateSarifFileSchema; exports.validateSarifFileSchema = validateSarifFileSchema;
@@ -154,22 +148,19 @@ async function uploadFiles(sarifFiles) {
core.info("Uploading sarif files: " + JSON.stringify(sarifFiles)); core.info("Uploading sarif files: " + JSON.stringify(sarifFiles));
const sentinelEnvVar = "CODEQL_UPLOAD_SARIF"; const sentinelEnvVar = "CODEQL_UPLOAD_SARIF";
if (process.env[sentinelEnvVar]) { if (process.env[sentinelEnvVar]) {
core.error("Aborting upload: only one run of the codeql/analyze or codeql/upload-sarif actions is allowed per job"); throw new Error("Aborting upload: only one run of the codeql/analyze or codeql/upload-sarif actions is allowed per job");
return false;
} }
core.exportVariable(sentinelEnvVar, sentinelEnvVar); core.exportVariable(sentinelEnvVar, sentinelEnvVar);
// Validate that the files we were asked to upload are all valid SARIF files // Validate that the files we were asked to upload are all valid SARIF files
for (const file of sarifFiles) { for (const file of sarifFiles) {
if (!validateSarifFileSchema(file)) { validateSarifFileSchema(file);
return false;
}
} }
const commitOid = await util.getCommitOid(); const commitOid = await util.getCommitOid();
const workflowRunIDStr = util.getRequiredEnvParam('GITHUB_RUN_ID'); const workflowRunIDStr = util.getRequiredEnvParam('GITHUB_RUN_ID');
const ref = util.getRef(); const ref = util.getRef();
const analysisKey = await util.getAnalysisKey(); const analysisKey = await util.getAnalysisKey();
const analysisName = util.getRequiredEnvParam('GITHUB_WORKFLOW'); const analysisName = util.getRequiredEnvParam('GITHUB_WORKFLOW');
const startedAt = process.env[sharedEnv.CODEQL_ACTION_STARTED_AT]; const startedAt = process.env[sharedEnv.CODEQL_WORKFLOW_STARTED_AT];
let sarifPayload = combineSarifFiles(sarifFiles); let sarifPayload = combineSarifFiles(sarifFiles);
sarifPayload = fingerprints.addFingerprints(sarifPayload); sarifPayload = fingerprints.addFingerprints(sarifPayload);
const zipped_sarif = zlib_1.default.gzipSync(sarifPayload).toString('base64'); const zipped_sarif = zlib_1.default.gzipSync(sarifPayload).toString('base64');
@@ -177,8 +168,7 @@ async function uploadFiles(sarifFiles) {
let checkoutURI = file_url_1.default(checkoutPath); let checkoutURI = file_url_1.default(checkoutPath);
const workflowRunID = parseInt(workflowRunIDStr, 10); const workflowRunID = parseInt(workflowRunIDStr, 10);
if (Number.isNaN(workflowRunID)) { if (Number.isNaN(workflowRunID)) {
core.setFailed('GITHUB_RUN_ID must define a non NaN workflow run ID'); throw new Error('GITHUB_RUN_ID must define a non NaN workflow run ID');
return false;
} }
let matrix = core.getInput('matrix'); let matrix = core.getInput('matrix');
if (matrix === "null" || matrix === "") { if (matrix === "null" || matrix === "") {
@@ -198,12 +188,19 @@ async function uploadFiles(sarifFiles) {
"tool_names": toolNames, "tool_names": toolNames,
}); });
// Log some useful debug info about the info // Log some useful debug info about the info
core.debug("Raw upload size: " + sarifPayload.length + " bytes"); const rawUploadSizeBytes = sarifPayload.length;
core.debug("Base64 zipped upload size: " + zipped_sarif.length + " bytes"); core.debug("Raw upload size: " + rawUploadSizeBytes + " bytes");
core.debug("Number of results in upload: " + countResultsInSarif(sarifPayload)); const zippedUploadSizeBytes = zipped_sarif.length;
core.debug("Base64 zipped upload size: " + zippedUploadSizeBytes + " bytes");
const numResultInSarif = countResultsInSarif(sarifPayload);
core.debug("Number of results in upload: " + numResultInSarif);
// Make the upload // Make the upload
const succeeded = await uploadPayload(payload); await uploadPayload(payload);
core.endGroup(); core.endGroup();
return succeeded; return {
raw_upload_size_bytes: rawUploadSizeBytes,
zipped_upload_size_bytes: zippedUploadSizeBytes,
num_results_in_sarif: numResultInSarif,
};
} }
//# sourceMappingURL=upload-lib.js.map //# sourceMappingURL=upload-lib.js.map
File diff suppressed because one or more lines are too long
+3 -5
View File
@@ -13,15 +13,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
const ava_1 = __importDefault(require("ava")); const ava_1 = __importDefault(require("ava"));
const testing_utils_1 = require("./testing-utils"); const testing_utils_1 = require("./testing-utils");
const uploadLib = __importStar(require("./upload-lib")); const uploadLib = __importStar(require("./upload-lib"));
testing_utils_1.silenceDebugOutput(ava_1.default); testing_utils_1.setupTests(ava_1.default);
ava_1.default('validateSarifFileSchema - valid', t => { ava_1.default('validateSarifFileSchema - valid', t => {
const inputFile = __dirname + '/../src/testdata/valid-sarif.sarif'; const inputFile = __dirname + '/../src/testdata/valid-sarif.sarif';
t.true(uploadLib.validateSarifFileSchema(inputFile)); t.notThrows(() => uploadLib.validateSarifFileSchema(inputFile));
}); });
ava_1.default('validateSarifFileSchema - invalid', t => { ava_1.default('validateSarifFileSchema - invalid', t => {
const inputFile = __dirname + '/../src/testdata/invalid-sarif.sarif'; const inputFile = __dirname + '/../src/testdata/invalid-sarif.sarif';
t.false(uploadLib.validateSarifFileSchema(inputFile)); t.throws(() => uploadLib.validateSarifFileSchema(inputFile));
// validateSarifFileSchema calls core.setFailed which sets the exit code on error
process.exitCode = 0;
}); });
//# sourceMappingURL=upload-lib.test.js.map //# sourceMappingURL=upload-lib.test.js.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"upload-lib.test.js","sourceRoot":"","sources":["../src/upload-lib.test.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,8CAAuB;AAEvB,mDAAmD;AACnD,wDAA0C;AAE1C,kCAAkB,CAAC,aAAI,CAAC,CAAC;AAEzB,aAAI,CAAC,iCAAiC,EAAE,CAAC,CAAC,EAAE;IAC1C,MAAM,SAAS,GAAG,SAAS,GAAG,oCAAoC,CAAC;IACnE,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC,CAAC;AACvD,CAAC,CAAC,CAAC;AAEH,aAAI,CAAC,mCAAmC,EAAE,CAAC,CAAC,EAAE;IAC5C,MAAM,SAAS,GAAG,SAAS,GAAG,sCAAsC,CAAC;IACrE,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC,CAAC;IACtD,iFAAiF;IACjF,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"} {"version":3,"file":"upload-lib.test.js","sourceRoot":"","sources":["../src/upload-lib.test.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,8CAAuB;AAEvB,mDAA2C;AAC3C,wDAA0C;AAE1C,0BAAU,CAAC,aAAI,CAAC,CAAC;AAEjB,aAAI,CAAC,iCAAiC,EAAE,CAAC,CAAC,EAAE;IAC1C,MAAM,SAAS,GAAG,SAAS,GAAG,oCAAoC,CAAC;IACnE,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC,CAAC;AAClE,CAAC,CAAC,CAAC;AAEH,aAAI,CAAC,mCAAmC,EAAE,CAAC,CAAC,EAAE;IAC5C,MAAM,SAAS,GAAG,SAAS,GAAG,sCAAsC,CAAC;IACrE,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC,CAAC;AAC/D,CAAC,CAAC,CAAC"}
+14 -8
View File
@@ -10,21 +10,27 @@ Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(require("@actions/core")); const core = __importStar(require("@actions/core"));
const upload_lib = __importStar(require("./upload-lib")); const upload_lib = __importStar(require("./upload-lib"));
const util = __importStar(require("./util")); const util = __importStar(require("./util"));
async function sendSuccessStatusReport(startedAt, uploadStats) {
const statusReportBase = await util.createStatusReportBase('upload-sarif', 'success', startedAt);
const statusReport = {
...statusReportBase,
...uploadStats,
};
await util.sendStatusReport(statusReport);
}
async function run() { async function run() {
if (util.should_abort('upload-sarif', false) || !await util.reportActionStarting('upload-sarif')) { const startedAt = new Date();
if (util.should_abort('upload-sarif', false) ||
!await util.sendStatusReport(await util.createStatusReportBase('upload-sarif', 'starting', startedAt), true)) {
return; return;
} }
try { try {
if (await upload_lib.upload(core.getInput('sarif_file'))) { const uploadStats = await upload_lib.upload(core.getInput('sarif_file'));
await util.reportActionSucceeded('upload-sarif'); await sendSuccessStatusReport(startedAt, uploadStats);
}
else {
await util.reportActionFailed('upload-sarif', 'upload');
}
} }
catch (error) { catch (error) {
core.setFailed(error.message); core.setFailed(error.message);
await util.reportActionFailed('upload-sarif', error.message, error.stack); await util.sendStatusReport(await util.createStatusReportBase('upload-sarif', 'failure', startedAt, error.message, error.stack));
return; return;
} }
} }
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"upload-sarif.js","sourceRoot":"","sources":["../src/upload-sarif.ts"],"names":[],"mappings":";;;;;;;;;AAAA,oDAAsC;AAEtC,yDAA2C;AAC3C,6CAA+B;AAE/B,KAAK,UAAU,GAAG;IAChB,IAAI,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,oBAAoB,CAAC,cAAc,CAAC,EAAE;QAChG,OAAO;KACR;IAED,IAAI;QACF,IAAI,MAAM,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,EAAE;YACxD,MAAM,IAAI,CAAC,qBAAqB,CAAC,cAAc,CAAC,CAAC;SAClD;aAAM;YACL,MAAM,IAAI,CAAC,kBAAkB,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;SACzD;KACF;IAAC,OAAO,KAAK,EAAE;QACd,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC9B,MAAM,IAAI,CAAC,kBAAkB,CAAC,cAAc,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QAC1E,OAAO;KACR;AACH,CAAC;AAED,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;IACd,IAAI,CAAC,SAAS,CAAC,qCAAqC,GAAG,CAAC,CAAC,CAAC;IAC1D,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC,CAAC,CAAC"} {"version":3,"file":"upload-sarif.js","sourceRoot":"","sources":["../src/upload-sarif.ts"],"names":[],"mappings":";;;;;;;;;AAAA,oDAAsC;AAEtC,yDAA2C;AAC3C,6CAA+B;AAI/B,KAAK,UAAU,uBAAuB,CAAC,SAAe,EAAE,WAA0C;IAChG,MAAM,gBAAgB,GAAG,MAAM,IAAI,CAAC,sBAAsB,CAAC,cAAc,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;IACjG,MAAM,YAAY,GAA4B;QAC5C,GAAG,gBAAgB;QACnB,GAAI,WAAW;KAChB,CAAC;IACF,MAAM,IAAI,CAAC,gBAAgB,CAAC,YAAY,CAAC,CAAC;AAC5C,CAAC;AAED,KAAK,UAAU,GAAG;IAChB,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC;IAC7B,IAAI,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,KAAK,CAAC;QACxC,CAAC,MAAM,IAAI,CAAC,gBAAgB,CAAC,MAAM,IAAI,CAAC,sBAAsB,CAAC,cAAc,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,IAAI,CAAC,EAAE;QAChH,OAAO;KACR;IAED,IAAI;QACF,MAAM,WAAW,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC;QACzE,MAAM,uBAAuB,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;KAEvD;IAAC,OAAO,KAAK,EAAE;QACd,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC9B,MAAM,IAAI,CAAC,gBAAgB,CAAC,MAAM,IAAI,CAAC,sBAAsB,CAC3D,cAAc,EACd,SAAS,EACT,SAAS,EACT,KAAK,CAAC,OAAO,EACb,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QAChB,OAAO;KACR;AACH,CAAC;AAED,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;IACd,IAAI,CAAC,SAAS,CAAC,qCAAqC,GAAG,CAAC,CAAC,CAAC;IAC1D,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC,CAAC,CAAC"}
Generated
+70 -181
View File
@@ -6,19 +6,13 @@ var __importStar = (this && this.__importStar) || function (mod) {
result["default"] = mod; result["default"] = mod;
return result; return result;
}; };
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(require("@actions/core")); const core = __importStar(require("@actions/core"));
const exec = __importStar(require("@actions/exec")); const exec = __importStar(require("@actions/exec"));
const http = __importStar(require("@actions/http-client"));
const auth = __importStar(require("@actions/http-client/auth"));
const octokit = __importStar(require("@octokit/rest"));
const console_log_level_1 = __importDefault(require("console-log-level"));
const fs = __importStar(require("fs")); const fs = __importStar(require("fs"));
const os = __importStar(require("os")); const os = __importStar(require("os"));
const path = __importStar(require("path")); const path = __importStar(require("path"));
const api = __importStar(require("./api-client"));
const sharedEnv = __importStar(require("./shared-environment")); const sharedEnv = __importStar(require("./shared-environment"));
/** /**
* Should the current action be aborted? * Should the current action be aborted?
@@ -47,108 +41,39 @@ exports.should_abort = should_abort;
*/ */
function getRequiredEnvParam(paramName) { function getRequiredEnvParam(paramName) {
const value = process.env[paramName]; const value = process.env[paramName];
if (value === undefined) { if (value === undefined || value.length === 0) {
throw new Error(paramName + ' environment variable must be set'); throw new Error(paramName + ' environment variable must be set');
} }
core.debug(paramName + '=' + value); core.debug(paramName + '=' + value);
return value; return value;
} }
exports.getRequiredEnvParam = getRequiredEnvParam; exports.getRequiredEnvParam = getRequiredEnvParam;
/**
* Gets the set of languages in the current repository
*/
async function getLanguagesInRepo() {
var _a;
// Translate between GitHub's API names for languages and ours
const codeqlLanguages = {
'C': 'cpp',
'C++': 'cpp',
'C#': 'csharp',
'Go': 'go',
'Java': 'java',
'JavaScript': 'javascript',
'TypeScript': 'javascript',
'Python': 'python',
};
let repo_nwo = (_a = process.env['GITHUB_REPOSITORY']) === null || _a === void 0 ? void 0 : _a.split("/");
if (repo_nwo) {
let owner = repo_nwo[0];
let repo = repo_nwo[1];
core.debug(`GitHub repo ${owner} ${repo}`);
let ok = new octokit.Octokit({
auth: core.getInput('token'),
userAgent: "CodeQL Action",
log: console_log_level_1.default({ level: "debug" })
});
const response = await ok.request("GET /repos/:owner/:repo/languages", ({
owner,
repo
}));
core.debug("Languages API response: " + JSON.stringify(response));
// The GitHub API is going to return languages in order of popularity,
// When we pick a language to autobuild we want to pick the most popular traced language
// Since sets in javascript maintain insertion order, using a set here and then splatting it
// into an array gives us an array of languages ordered by popularity
let languages = new Set();
for (let lang in response.data) {
if (lang in codeqlLanguages) {
languages.add(codeqlLanguages[lang]);
}
}
return [...languages];
}
else {
return [];
}
}
/**
* Get the languages to analyse.
*
* The result is obtained from the environment parameter CODEQL_ACTION_LANGUAGES
* if that has been set, otherwise it is obtained from the action input parameter
* 'languages' if that has been set, otherwise it is deduced as all languages in the
* repo that can be analysed.
*
* If the languages are obtained from either of the second choices, the
* CODEQL_ACTION_LANGUAGES environment variable will be exported with the
* deduced list.
*/
async function getLanguages() {
// Obtain from CODEQL_ACTION_LANGUAGES if set
const langsVar = process.env[sharedEnv.CODEQL_ACTION_LANGUAGES];
if (langsVar) {
return langsVar.split(',')
.map(x => x.trim())
.filter(x => x.length > 0);
}
// Obtain from action input 'languages' if set
let languages = core.getInput('languages', { required: false })
.split(',')
.map(x => x.trim())
.filter(x => x.length > 0);
core.info("Languages from configuration: " + JSON.stringify(languages));
if (languages.length === 0) {
// Obtain languages as all languages in the repo that can be analysed
languages = await getLanguagesInRepo();
core.info("Automatically detected languages: " + JSON.stringify(languages));
}
core.exportVariable(sharedEnv.CODEQL_ACTION_LANGUAGES, languages.join(','));
return languages;
}
exports.getLanguages = getLanguages;
/** /**
* Gets the SHA of the commit that is currently checked out. * Gets the SHA of the commit that is currently checked out.
*/ */
async function getCommitOid() { async function getCommitOid() {
let commitOid = ''; // Try to use git to get the current commit SHA. If that fails then
await exec.exec('git', ['rev-parse', 'HEAD'], { // log but otherwise silently fall back to using the SHA from the environment.
silent: true, // The only time these two values will differ is during analysis of a PR when
listeners: { // the workflow has changed the current commit to the head commit instead of
stdout: (data) => { commitOid += data.toString(); }, // the merge commit, which must mean that git is available.
stderr: (data) => { process.stderr.write(data); } // Even if this does go wrong, it's not a huge problem for the alerts to
} // reported on the merge commit.
}); try {
return commitOid.trim(); let commitOid = '';
await exec.exec('git', ['rev-parse', 'HEAD'], {
silent: true,
listeners: {
stdout: (data) => { commitOid += data.toString(); },
stderr: (data) => { process.stderr.write(data); }
}
});
return commitOid.trim();
}
catch (e) {
core.info("Failed to call git to get current commit. Continuing with data from environment: " + e);
return getRequiredEnvParam('GITHUB_SHA');
}
} }
exports.getCommitOid = getCommitOid; exports.getCommitOid = getCommitOid;
/** /**
@@ -158,19 +83,15 @@ async function getWorkflowPath() {
const repo_nwo = getRequiredEnvParam('GITHUB_REPOSITORY').split("/"); const repo_nwo = getRequiredEnvParam('GITHUB_REPOSITORY').split("/");
const owner = repo_nwo[0]; const owner = repo_nwo[0];
const repo = repo_nwo[1]; const repo = repo_nwo[1];
const run_id = getRequiredEnvParam('GITHUB_RUN_ID'); const run_id = Number(getRequiredEnvParam('GITHUB_RUN_ID'));
const ok = new octokit.Octokit({ const apiClient = api.getApiClient();
auth: core.getInput('token'), const runsResponse = await apiClient.request('GET /repos/:owner/:repo/actions/runs/:run_id', {
userAgent: "CodeQL Action",
log: console_log_level_1.default({ level: 'debug' })
});
const runsResponse = await ok.request('GET /repos/:owner/:repo/actions/runs/:run_id', {
owner, owner,
repo, repo,
run_id run_id
}); });
const workflowUrl = runsResponse.data.workflow_url; const workflowUrl = runsResponse.data.workflow_url;
const workflowResponse = await ok.request('GET ' + workflowUrl); const workflowResponse = await apiClient.request('GET ' + workflowUrl);
return workflowResponse.data.path; return workflowResponse.data.path;
} }
/** /**
@@ -217,10 +138,11 @@ exports.getRef = getRef;
* *
* @param actionName The name of the action, e.g. 'init', 'finish', 'upload-sarif' * @param actionName The name of the action, e.g. 'init', 'finish', 'upload-sarif'
* @param status The status. Must be 'success', 'failure', or 'starting' * @param status The status. Must be 'success', 'failure', or 'starting'
* @param startedAt The time this action started executing.
* @param cause Cause of failure (only supply if status is 'failure') * @param cause Cause of failure (only supply if status is 'failure')
* @param exception Exception (only supply if status is 'failure') * @param exception Exception (only supply if status is 'failure')
*/ */
async function createStatusReport(actionName, status, cause, exception) { async function createStatusReportBase(actionName, status, actionStartedAt, cause, exception) {
const commitOid = process.env['GITHUB_SHA'] || ''; const commitOid = process.env['GITHUB_SHA'] || '';
const ref = getRef(); const ref = getRef();
const workflowRunIDStr = process.env['GITHUB_RUN_ID']; const workflowRunIDStr = process.env['GITHUB_RUN_ID'];
@@ -230,19 +152,23 @@ async function createStatusReport(actionName, status, cause, exception) {
} }
const workflowName = process.env['GITHUB_WORKFLOW'] || ''; const workflowName = process.env['GITHUB_WORKFLOW'] || '';
const jobName = process.env['GITHUB_JOB'] || ''; const jobName = process.env['GITHUB_JOB'] || '';
const languages = (await getLanguages()).sort().join(','); const analysis_key = await getAnalysisKey();
const startedAt = process.env[sharedEnv.CODEQL_ACTION_STARTED_AT] || new Date().toISOString(); let workflowStartedAt = process.env[sharedEnv.CODEQL_WORKFLOW_STARTED_AT];
core.exportVariable(sharedEnv.CODEQL_ACTION_STARTED_AT, startedAt); if (workflowStartedAt === undefined) {
workflowStartedAt = actionStartedAt.toISOString();
core.exportVariable(sharedEnv.CODEQL_WORKFLOW_STARTED_AT, workflowStartedAt);
}
let statusReport = { let statusReport = {
workflow_run_id: workflowRunID, workflow_run_id: workflowRunID,
workflow_name: workflowName, workflow_name: workflowName,
job_name: jobName, job_name: jobName,
languages: languages, analysis_key: analysis_key,
commit_oid: commitOid, commit_oid: commitOid,
ref: ref, ref: ref,
action_name: actionName, action_name: actionName,
action_oid: "unknown", action_oid: "unknown",
started_at: startedAt, started_at: workflowStartedAt,
action_started_at: actionStartedAt.toISOString(),
status: status status: status
}; };
// Add optional parameters // Add optional parameters
@@ -252,7 +178,7 @@ async function createStatusReport(actionName, status, cause, exception) {
if (exception) { if (exception) {
statusReport.exception = exception; statusReport.exception = exception;
} }
if (status === 'success' || status === 'failure') { if (status === 'success' || status === 'failure' || status === 'aborted') {
statusReport.completed_at = new Date().toISOString(); statusReport.completed_at = new Date().toISOString();
} }
let matrix = core.getInput('matrix'); let matrix = core.getInput('matrix');
@@ -261,82 +187,45 @@ async function createStatusReport(actionName, status, cause, exception) {
} }
return statusReport; return statusReport;
} }
exports.createStatusReportBase = createStatusReportBase;
/** /**
* Send a status report to the code_scanning/analysis/status endpoint. * Send a status report to the code_scanning/analysis/status endpoint.
* *
* Returns the status code of the response to the status request, or * Optionally checks the response from the API endpoint and sets the action
* undefined if the given statusReport is undefined or no response was * as failed if the status report failed. This is only expected to be used
* received. * when sending a 'starting' report.
*
* Returns whether sending the status report was successful of not.
*/ */
async function sendStatusReport(statusReport) { async function sendStatusReport(statusReport, ignoreFailures) {
var _a;
const statusReportJSON = JSON.stringify(statusReport); const statusReportJSON = JSON.stringify(statusReport);
core.debug('Sending status report: ' + statusReportJSON); core.debug('Sending status report: ' + statusReportJSON);
const githubToken = core.getInput('token'); const nwo = getRequiredEnvParam("GITHUB_REPOSITORY");
const ph = new auth.BearerCredentialHandler(githubToken); const [owner, repo] = nwo.split("/");
const client = new http.HttpClient('Code Scanning : Status Report', [ph]); const statusResponse = await api.getApiClient().request('PUT /repos/:owner/:repo/code-scanning/analysis/status', {
const url = 'https://api.github.com/repos/' + process.env['GITHUB_REPOSITORY'] owner: owner,
+ '/code-scanning/analysis/status'; repo: repo,
const res = await client.put(url, statusReportJSON); data: statusReportJSON,
return (_a = res.message) === null || _a === void 0 ? void 0 : _a.statusCode; });
} if (!ignoreFailures) {
/** // If the status report request fails with a 403 or a 404, then this is a deliberate
* Send a status report that an action is starting. // message from the endpoint that the SARIF upload can be expected to fail too,
* // so the action should fail to avoid wasting actions minutes.
* If the action is `init` then this also records the start time in the environment, //
* and ensures that the analysed languages are also recorded in the envirenment. // Other failure responses (or lack thereof) could be transitory and should not
* // cause the action to fail.
* Returns true unless a problem occurred and the action should abort. if (statusResponse.status === 403) {
*/ core.setFailed('The repo on which this action is running is not opted-in to CodeQL code scanning.');
async function reportActionStarting(action) { return false;
const statusCode = await sendStatusReport(await createStatusReport(action, 'starting')); }
// If the status report request fails with a 403 or a 404, then this is a deliberate if (statusResponse.status === 404) {
// message from the endpoint that the SARIF upload can be expected to fail too, core.setFailed('Not authorized to used the CodeQL code scanning feature on this repo.');
// so the action should fail to avoid wasting actions minutes. return false;
// }
// Other failure responses (or lack thereof) could be transitory and should not
// cause the action to fail.
if (statusCode === 403) {
core.setFailed('The repo on which this action is running is not opted-in to CodeQL code scanning.');
return false;
}
if (statusCode === 404) {
core.setFailed('Not authorized to used the CodeQL code scanning feature on this repo.');
return false;
} }
return true; return true;
} }
exports.reportActionStarting = reportActionStarting; exports.sendStatusReport = sendStatusReport;
/**
* Report that an action has failed.
*
* Note that the started_at date is always that of the `init` action, since
* this is likely to give a more useful duration when inspecting events.
*/
async function reportActionFailed(action, cause, exception) {
await sendStatusReport(await createStatusReport(action, 'failure', cause, exception));
}
exports.reportActionFailed = reportActionFailed;
/**
* Report that an action has succeeded.
*
* Note that the started_at date is always that of the `init` action, since
* this is likely to give a more useful duration when inspecting events.
*/
async function reportActionSucceeded(action) {
await sendStatusReport(await createStatusReport(action, 'success'));
}
exports.reportActionSucceeded = reportActionSucceeded;
/**
* Report that an action has been aborted.
*
* Note that the started_at date is always that of the `init` action, since
* this is likely to give a more useful duration when inspecting events.
*/
async function reportActionAborted(action, cause) {
await sendStatusReport(await createStatusReport(action, 'aborted', cause));
}
exports.reportActionAborted = reportActionAborted;
/** /**
* Get the array of all the tool names contained in the given sarif contents. * Get the array of all the tool names contained in the given sarif contents.
* *
+1 -1
View File
File diff suppressed because one or more lines are too long
+8 -6
View File
@@ -15,7 +15,7 @@ const fs = __importStar(require("fs"));
const os = __importStar(require("os")); const os = __importStar(require("os"));
const testing_utils_1 = require("./testing-utils"); const testing_utils_1 = require("./testing-utils");
const util = __importStar(require("./util")); const util = __importStar(require("./util"));
testing_utils_1.silenceDebugOutput(ava_1.default); testing_utils_1.setupTests(ava_1.default);
ava_1.default('getToolNames', t => { ava_1.default('getToolNames', t => {
const input = fs.readFileSync(__dirname + '/../src/testdata/tool-names.sarif', 'utf8'); const input = fs.readFileSync(__dirname + '/../src/testdata/tool-names.sarif', 'utf8');
const toolNames = util.getToolNames(input); const toolNames = util.getToolNames(input);
@@ -53,10 +53,12 @@ ava_1.default('getThreadsFlag() should return the correct --threads flag', t =>
t.deepEqual(flag, expectedFlag); t.deepEqual(flag, expectedFlag);
} }
}); });
ava_1.default('getThreadsFlag() throws if the ram input is < 0 or NaN', t => { ava_1.default('getThreadsFlag() throws if the threads input is not an integer', t => {
for (const input of ["hello!"]) { process.env['INPUT_THREADS'] = "hello!";
process.env['INPUT_THREADS'] = input; t.throws(util.getThreadsFlag);
t.throws(util.getThreadsFlag); });
} ava_1.default('getRef() throws on the empty string', t => {
process.env["GITHUB_REF"] = "";
t.throws(util.getRef);
}); });
//# sourceMappingURL=util.test.js.map //# sourceMappingURL=util.test.js.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"util.test.js","sourceRoot":"","sources":["../src/util.test.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,8CAAuB;AACvB,uCAAyB;AACzB,uCAAyB;AAEzB,mDAAmD;AACnD,6CAA+B;AAE/B,kCAAkB,CAAC,aAAI,CAAC,CAAC;AAEzB,aAAI,CAAC,cAAc,EAAE,CAAC,CAAC,EAAE;IACvB,MAAM,KAAK,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,GAAG,mCAAmC,EAAE,MAAM,CAAC,CAAC;IACvF,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;IAC3C,CAAC,CAAC,SAAS,CAAC,SAAS,EAAE,CAAC,+BAA+B,EAAE,QAAQ,CAAC,CAAC,CAAC;AACtE,CAAC,CAAC,CAAC;AAEH,aAAI,CAAC,sDAAsD,EAAE,CAAC,CAAC,EAAE;IAE/D,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC;IAE3D,MAAM,KAAK,GAAG;QACZ,EAAE,EAAE,SAAS,QAAQ,GAAG,GAAG,EAAE;QAC7B,KAAK,EAAE,WAAW;KACnB,CAAC;IAEF,KAAK,MAAM,CAAC,KAAK,EAAE,YAAY,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;QAEzD,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,KAAK,CAAC;QAEjC,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QAClC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;KACjC;AACH,CAAC,CAAC,CAAC;AAEH,aAAI,CAAC,uDAAuD,EAAE,CAAC,CAAC,EAAE;IAChE,KAAK,MAAM,KAAK,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE;QACpC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,KAAK,CAAC;QACjC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;KAC9B;AACH,CAAC,CAAC,CAAC;AAEH,aAAI,CAAC,2DAA2D,EAAE,CAAC,CAAC,EAAE;IAEpE,MAAM,OAAO,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC;IAEjC,MAAM,KAAK,GAAG;QACZ,GAAG,EAAE,aAAa;QAClB,GAAG,EAAE,aAAa;QAClB,CAAC,GAAG,OAAO,GAAG,CAAC,EAAE,CAAC,EAAE,aAAa,OAAO,EAAE;QAC1C,CAAC,GAAG,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC,EAAE,aAAa,CAAC,OAAO,EAAE;KAC7C,CAAC;IAEF,KAAK,MAAM,CAAC,KAAK,EAAE,YAAY,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;QAEzD,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,GAAG,KAAK,CAAC;QAErC,MAAM,IAAI,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QACnC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;KACjC;AACH,CAAC,CAAC,CAAC;AAEH,aAAI,CAAC,wDAAwD,EAAE,CAAC,CAAC,EAAE;IACjE,KAAK,MAAM,KAAK,IAAI,CAAC,QAAQ,CAAC,EAAE;QAC9B,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,GAAG,KAAK,CAAC;QACrC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;KAC/B;AACH,CAAC,CAAC,CAAC"} {"version":3,"file":"util.test.js","sourceRoot":"","sources":["../src/util.test.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,8CAAuB;AACvB,uCAAyB;AACzB,uCAAyB;AAEzB,mDAA2C;AAC3C,6CAA+B;AAE/B,0BAAU,CAAC,aAAI,CAAC,CAAC;AAEjB,aAAI,CAAC,cAAc,EAAE,CAAC,CAAC,EAAE;IACvB,MAAM,KAAK,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,GAAG,mCAAmC,EAAE,MAAM,CAAC,CAAC;IACvF,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;IAC3C,CAAC,CAAC,SAAS,CAAC,SAAS,EAAE,CAAC,+BAA+B,EAAE,QAAQ,CAAC,CAAC,CAAC;AACtE,CAAC,CAAC,CAAC;AAEH,aAAI,CAAC,sDAAsD,EAAE,CAAC,CAAC,EAAE;IAE/D,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC;IAE3D,MAAM,KAAK,GAAG;QACZ,EAAE,EAAE,SAAS,QAAQ,GAAG,GAAG,EAAE;QAC7B,KAAK,EAAE,WAAW;KACnB,CAAC;IAEF,KAAK,MAAM,CAAC,KAAK,EAAE,YAAY,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;QAEzD,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,KAAK,CAAC;QAEjC,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QAClC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;KACjC;AACH,CAAC,CAAC,CAAC;AAEH,aAAI,CAAC,uDAAuD,EAAE,CAAC,CAAC,EAAE;IAChE,KAAK,MAAM,KAAK,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE;QACpC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,KAAK,CAAC;QACjC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;KAC9B;AACH,CAAC,CAAC,CAAC;AAEH,aAAI,CAAC,2DAA2D,EAAE,CAAC,CAAC,EAAE;IAEpE,MAAM,OAAO,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC;IAEjC,MAAM,KAAK,GAAG;QACZ,GAAG,EAAE,aAAa;QAClB,GAAG,EAAE,aAAa;QAClB,CAAC,GAAG,OAAO,GAAG,CAAC,EAAE,CAAC,EAAE,aAAa,OAAO,EAAE;QAC1C,CAAC,GAAG,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC,EAAE,aAAa,CAAC,OAAO,EAAE;KAC7C,CAAC;IAEF,KAAK,MAAM,CAAC,KAAK,EAAE,YAAY,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;QAEzD,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,GAAG,KAAK,CAAC;QAErC,MAAM,IAAI,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QACnC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;KACjC;AACH,CAAC,CAAC,CAAC;AAEH,aAAI,CAAC,gEAAgE,EAAE,CAAC,CAAC,EAAE;IACzE,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,GAAG,QAAQ,CAAC;IACxC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;AAChC,CAAC,CAAC,CAAC;AAEH,aAAI,CAAC,qCAAqC,EAAE,CAAC,CAAC,EAAE;IAC9C,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC;IAC/B,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AACxB,CAAC,CAAC,CAAC"}
+74
View File
@@ -0,0 +1,74 @@
# `@actions/github`
> A hydrated Octokit client.
## Usage
Returns an authenticated Octokit client that follows the machine [proxy settings](https://help.github.com/en/actions/hosting-your-own-runners/using-a-proxy-server-with-self-hosted-runners). See https://octokit.github.io/rest.js for the API.
```js
const github = require('@actions/github');
const core = require('@actions/core');
async function run() {
// This should be a token with access to your repository scoped in as a secret.
// The YML workflow will need to set myToken with the GitHub Secret Token
// myToken: ${{ secrets.GITHUB_TOKEN }}
// https://help.github.com/en/actions/automating-your-workflow-with-github-actions/authenticating-with-the-github_token#about-the-github_token-secret
const myToken = core.getInput('myToken');
const octokit = new github.GitHub(myToken);
const { data: pullRequest } = await octokit.pulls.get({
owner: 'octokit',
repo: 'rest.js',
pull_number: 123,
mediaType: {
format: 'diff'
}
});
console.log(pullRequest);
}
run();
```
You can pass client options, as specified by [Octokit](https://octokit.github.io/rest.js/), as a second argument to the `GitHub` constructor.
You can also make GraphQL requests. See https://github.com/octokit/graphql.js for the API.
```js
const result = await octokit.graphql(query, variables);
```
Finally, you can get the context of the current action:
```js
const github = require('@actions/github');
const context = github.context;
const newIssue = await octokit.issues.create({
...context.repo,
title: 'New issue!',
body: 'Hello Universe!'
});
```
## Webhook payload typescript definitions
The npm module `@octokit/webhooks` provides type definitions for the response payloads. You can cast the payload to these types for better type information.
First, install the npm module `npm install @octokit/webhooks`
Then, assert the type based on the eventName
```ts
import * as core from '@actions/core'
import * as github from '@actions/github'
import * as Webhooks from '@octokit/webhooks'
if (github.context.eventName === 'push') {
const pushPayload = github.context.payload as Webhooks.WebhookPayloadPush
core.info(`The head commit is: ${pushPayload.head}`)
}
```
+26
View File
@@ -0,0 +1,26 @@
import { WebhookPayload } from './interfaces';
export declare class Context {
/**
* Webhook payload object that triggered the workflow
*/
payload: WebhookPayload;
eventName: string;
sha: string;
ref: string;
workflow: string;
action: string;
actor: string;
/**
* Hydrate the context from the environment
*/
constructor();
get issue(): {
owner: string;
repo: string;
number: number;
};
get repo(): {
owner: string;
repo: string;
};
}
+46
View File
@@ -0,0 +1,46 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const fs_1 = require("fs");
const os_1 = require("os");
class Context {
/**
* Hydrate the context from the environment
*/
constructor() {
this.payload = {};
if (process.env.GITHUB_EVENT_PATH) {
if (fs_1.existsSync(process.env.GITHUB_EVENT_PATH)) {
this.payload = JSON.parse(fs_1.readFileSync(process.env.GITHUB_EVENT_PATH, { encoding: 'utf8' }));
}
else {
const path = process.env.GITHUB_EVENT_PATH;
process.stdout.write(`GITHUB_EVENT_PATH ${path} does not exist${os_1.EOL}`);
}
}
this.eventName = process.env.GITHUB_EVENT_NAME;
this.sha = process.env.GITHUB_SHA;
this.ref = process.env.GITHUB_REF;
this.workflow = process.env.GITHUB_WORKFLOW;
this.action = process.env.GITHUB_ACTION;
this.actor = process.env.GITHUB_ACTOR;
}
get issue() {
const payload = this.payload;
return Object.assign(Object.assign({}, this.repo), { number: (payload.issue || payload.pull_request || payload).number });
}
get repo() {
if (process.env.GITHUB_REPOSITORY) {
const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/');
return { owner, repo };
}
if (this.payload.repository) {
return {
owner: this.payload.repository.owner.login,
repo: this.payload.repository.name
};
}
throw new Error("context.repo requires a GITHUB_REPOSITORY environment variable like 'owner/repo'");
}
}
exports.Context = Context;
//# sourceMappingURL=context.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":";;AAEA,2BAA2C;AAC3C,2BAAsB;AAEtB,MAAa,OAAO;IAalB;;OAEG;IACH;QACE,IAAI,CAAC,OAAO,GAAG,EAAE,CAAA;QACjB,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE;YACjC,IAAI,eAAU,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,EAAE;gBAC7C,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CACvB,iBAAY,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,EAAC,QAAQ,EAAE,MAAM,EAAC,CAAC,CAChE,CAAA;aACF;iBAAM;gBACL,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAA;gBAC1C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qBAAqB,IAAI,kBAAkB,QAAG,EAAE,CAAC,CAAA;aACvE;SACF;QACD,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,iBAA2B,CAAA;QACxD,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,UAAoB,CAAA;QAC3C,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,UAAoB,CAAA;QAC3C,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,eAAyB,CAAA;QACrD,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,aAAuB,CAAA;QACjD,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,YAAsB,CAAA;IACjD,CAAC;IAED,IAAI,KAAK;QACP,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAA;QAE5B,uCACK,IAAI,CAAC,IAAI,KACZ,MAAM,EAAE,CAAC,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,YAAY,IAAI,OAAO,CAAC,CAAC,MAAM,IAClE;IACH,CAAC;IAED,IAAI,IAAI;QACN,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE;YACjC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;YAC9D,OAAO,EAAC,KAAK,EAAE,IAAI,EAAC,CAAA;SACrB;QAED,IAAI,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE;YAC3B,OAAO;gBACL,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK;gBAC1C,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI;aACnC,CAAA;SACF;QAED,MAAM,IAAI,KAAK,CACb,kFAAkF,CACnF,CAAA;IACH,CAAC;CACF;AA9DD,0BA8DC"}
+27
View File
@@ -0,0 +1,27 @@
import { graphql as GraphQL } from '@octokit/graphql/dist-types/types';
import { Octokit } from '@octokit/rest';
import * as Context from './context';
export declare const context: Context.Context;
export declare class GitHub extends Octokit {
graphql: GraphQL;
/**
* Sets up the REST client and GraphQL client with auth and proxy support.
* The parameter `token` or `opts.auth` must be supplied. The GraphQL client
* authorization is not setup when `opts.auth` is a function or object.
*
* @param token Auth token
* @param opts Octokit options
*/
constructor(token: string, opts?: Omit<Octokit.Options, 'auth'>);
constructor(opts: Octokit.Options);
/**
* Disambiguates the constructor overload parameters
*/
private static disambiguate;
private static getOctokitOptions;
private static getGraphQL;
private static getAuthString;
private static getProxyAgent;
private static getApiBaseUrl;
private static getGraphQLBaseUrl;
}
+108
View File
@@ -0,0 +1,108 @@
"use strict";
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
// Originally pulled from https://github.com/JasonEtco/actions-toolkit/blob/master/src/github.ts
const graphql_1 = require("@octokit/graphql");
const rest_1 = require("@octokit/rest");
const Context = __importStar(require("./context"));
const httpClient = __importStar(require("@actions/http-client"));
// We need this in order to extend Octokit
rest_1.Octokit.prototype = new rest_1.Octokit();
exports.context = new Context.Context();
class GitHub extends rest_1.Octokit {
constructor(token, opts) {
super(GitHub.getOctokitOptions(GitHub.disambiguate(token, opts)));
this.graphql = GitHub.getGraphQL(GitHub.disambiguate(token, opts));
}
/**
* Disambiguates the constructor overload parameters
*/
static disambiguate(token, opts) {
return [
typeof token === 'string' ? token : '',
typeof token === 'object' ? token : opts || {}
];
}
static getOctokitOptions(args) {
const token = args[0];
const options = Object.assign({}, args[1]); // Shallow clone - don't mutate the object provided by the caller
// Base URL - GHES or Dotcom
options.baseUrl = options.baseUrl || this.getApiBaseUrl();
// Auth
const auth = GitHub.getAuthString(token, options);
if (auth) {
options.auth = auth;
}
// Proxy
const agent = GitHub.getProxyAgent(options.baseUrl, options);
if (agent) {
// Shallow clone - don't mutate the object provided by the caller
options.request = options.request ? Object.assign({}, options.request) : {};
// Set the agent
options.request.agent = agent;
}
return options;
}
static getGraphQL(args) {
const defaults = {};
defaults.baseUrl = this.getGraphQLBaseUrl();
const token = args[0];
const options = args[1];
// Authorization
const auth = this.getAuthString(token, options);
if (auth) {
defaults.headers = {
authorization: auth
};
}
// Proxy
const agent = GitHub.getProxyAgent(defaults.baseUrl, options);
if (agent) {
defaults.request = { agent };
}
return graphql_1.graphql.defaults(defaults);
}
static getAuthString(token, options) {
// Validate args
if (!token && !options.auth) {
throw new Error('Parameter token or opts.auth is required');
}
else if (token && options.auth) {
throw new Error('Parameters token and opts.auth may not both be specified');
}
return typeof options.auth === 'string' ? options.auth : `token ${token}`;
}
static getProxyAgent(destinationUrl, options) {
var _a;
if (!((_a = options.request) === null || _a === void 0 ? void 0 : _a.agent)) {
if (httpClient.getProxyUrl(destinationUrl)) {
const hc = new httpClient.HttpClient();
return hc.getAgent(destinationUrl);
}
}
return undefined;
}
static getApiBaseUrl() {
return process.env['GITHUB_API_URL'] || 'https://api.github.com';
}
static getGraphQLBaseUrl() {
let url = process.env['GITHUB_GRAPHQL_URL'] || 'https://api.github.com/graphql';
// Shouldn't be a trailing slash, but remove if so
if (url.endsWith('/')) {
url = url.substr(0, url.length - 1);
}
// Remove trailing "/graphql"
if (url.toUpperCase().endsWith('/GRAPHQL')) {
url = url.substr(0, url.length - '/graphql'.length);
}
return url;
}
}
exports.GitHub = GitHub;
//# sourceMappingURL=github.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"github.js","sourceRoot":"","sources":["../src/github.ts"],"names":[],"mappings":";;;;;;;;;AAAA,gGAAgG;AAChG,8CAAwC;AAUxC,wCAAqC;AACrC,mDAAoC;AAEpC,iEAAkD;AAElD,0CAA0C;AAC1C,cAAO,CAAC,SAAS,GAAG,IAAI,cAAO,EAAE,CAAA;AAEpB,QAAA,OAAO,GAAG,IAAI,OAAO,CAAC,OAAO,EAAE,CAAA;AAE5C,MAAa,MAAO,SAAQ,cAAO;IAiBjC,YAAY,KAA+B,EAAE,IAAsB;QACjE,KAAK,CAAC,MAAM,CAAC,iBAAiB,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA;QAEjE,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAA;IACpE,CAAC;IAED;;OAEG;IACK,MAAM,CAAC,YAAY,CACzB,KAA+B,EAC/B,IAAsB;QAEtB,OAAO;YACL,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;YACtC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE;SAC/C,CAAA;IACH,CAAC;IAEO,MAAM,CAAC,iBAAiB,CAC9B,IAA+B;QAE/B,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;QACrB,MAAM,OAAO,qBAAO,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA,CAAC,iEAAiE;QAE9F,4BAA4B;QAC5B,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,IAAI,CAAC,aAAa,EAAE,CAAA;QAEzD,OAAO;QACP,MAAM,IAAI,GAAG,MAAM,CAAC,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;QACjD,IAAI,IAAI,EAAE;YACR,OAAO,CAAC,IAAI,GAAG,IAAI,CAAA;SACpB;QAED,QAAQ;QACR,MAAM,KAAK,GAAG,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QAC5D,IAAI,KAAK,EAAE;YACT,iEAAiE;YACjE,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,mBAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,CAAA;YAE7D,gBAAgB;YAChB,OAAO,CAAC,OAAO,CAAC,KAAK,GAAG,KAAK,CAAA;SAC9B;QAED,OAAO,OAAO,CAAA;IAChB,CAAC;IAEO,MAAM,CAAC,UAAU,CAAC,IAA+B;QACvD,MAAM,QAAQ,GAA6B,EAAE,CAAA;QAC7C,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC,iBAAiB,EAAE,CAAA;QAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;QACrB,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;QAEvB,gBAAgB;QAChB,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;QAC/C,IAAI,IAAI,EAAE;YACR,QAAQ,CAAC,OAAO,GAAG;gBACjB,aAAa,EAAE,IAAI;aACpB,CAAA;SACF;QAED,QAAQ;QACR,MAAM,KAAK,GAAG,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QAC7D,IAAI,KAAK,EAAE;YACT,QAAQ,CAAC,OAAO,GAAG,EAAC,KAAK,EAAC,CAAA;SAC3B;QAED,OAAO,iBAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;IACnC,CAAC;IAEO,MAAM,CAAC,aAAa,CAC1B,KAAa,EACb,OAAwB;QAExB,gBAAgB;QAChB,IAAI,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE;YAC3B,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAA;SAC5D;aAAM,IAAI,KAAK,IAAI,OAAO,CAAC,IAAI,EAAE;YAChC,MAAM,IAAI,KAAK,CACb,0DAA0D,CAC3D,CAAA;SACF;QAED,OAAO,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,KAAK,EAAE,CAAA;IAC3E,CAAC;IAEO,MAAM,CAAC,aAAa,CAC1B,cAAsB,EACtB,OAAwB;;QAExB,IAAI,QAAC,OAAO,CAAC,OAAO,0CAAE,KAAK,CAAA,EAAE;YAC3B,IAAI,UAAU,CAAC,WAAW,CAAC,cAAc,CAAC,EAAE;gBAC1C,MAAM,EAAE,GAAG,IAAI,UAAU,CAAC,UAAU,EAAE,CAAA;gBACtC,OAAO,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAA;aACnC;SACF;QAED,OAAO,SAAS,CAAA;IAClB,CAAC;IAEO,MAAM,CAAC,aAAa;QAC1B,OAAO,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,wBAAwB,CAAA;IAClE,CAAC;IAEO,MAAM,CAAC,iBAAiB;QAC9B,IAAI,GAAG,GACL,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,IAAI,gCAAgC,CAAA;QAEvE,kDAAkD;QAClD,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;YACrB,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;SACpC;QAED,6BAA6B;QAC7B,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE;YAC1C,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAAA;SACpD;QACD,OAAO,GAAG,CAAA;IACZ,CAAC;CACF;AAxID,wBAwIC"}
+36
View File
@@ -0,0 +1,36 @@
export interface PayloadRepository {
[key: string]: any;
full_name?: string;
name: string;
owner: {
[key: string]: any;
login: string;
name?: string;
};
html_url?: string;
}
export interface WebhookPayload {
[key: string]: any;
repository?: PayloadRepository;
issue?: {
[key: string]: any;
number: number;
html_url?: string;
body?: string;
};
pull_request?: {
[key: string]: any;
number: number;
html_url?: string;
body?: string;
};
sender?: {
[key: string]: any;
type: string;
};
action?: string;
installation?: {
id: number;
[key: string]: any;
};
}
+4
View File
@@ -0,0 +1,4 @@
"use strict";
/* eslint-disable @typescript-eslint/no-explicit-any */
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=interfaces.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":";AAAA,uDAAuD"}
@@ -99,17 +99,14 @@ If your target runtime environments supports async iterators (such as most moder
```js ```js
const parameters = { const parameters = {
owner: "octocat", owner: "octocat",
repo: "hello-world", repo: "hello-world",
since: "2010-10-01", since: "2010-10-01",
per_page: 100 per_page: 100
}; }
for await (const response of octokit.paginate.iterator( for await (const response of octokit.paginate.iterator("GET /repos/:owner/:repo/issues", parameters)) {
"GET /repos/:owner/:repo/issues",
parameters
)) {
// do whatever you want with each response, break out of the loop, etc. // do whatever you want with each response, break out of the loop, etc.
console.log(response.data.title); console.log(response.data.title)
} }
``` ```
@@ -2,26 +2,32 @@
Object.defineProperty(exports, '__esModule', { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const VERSION = "2.0.2"; const VERSION = "1.1.2";
/** /**
* Some list response that can be paginated have a different response structure * Some list response that can be paginated have a different response structure
* *
* They have a `total_count` key in the response (search also has `incomplete_results`, * They have a `total_count` key in the response (search also has `incomplete_results`,
* /installation/repositories also has `repository_selection`), as well as a key with * /installation/repositories also has `repository_selection`), as well as a key with
* the list of the items which name varies from endpoint to endpoint. * the list of the items which name varies from endpoint to endpoint:
*
* - https://developer.github.com/v3/search/#example (key `items`)
* - https://developer.github.com/v3/checks/runs/#response-3 (key: `check_runs`)
* - https://developer.github.com/v3/checks/suites/#response-1 (key: `check_suites`)
* - https://developer.github.com/v3/apps/installations/#list-repositories (key: `repositories`)
* - https://developer.github.com/v3/apps/installations/#list-installations-for-a-user (key `installations`)
* *
* Octokit normalizes these responses so that paginated results are always returned following * Octokit normalizes these responses so that paginated results are always returned following
* the same structure. One challenge is that if the list response has only one page, no Link * the same structure. One challenge is that if the list response has only one page, no Link
* header is provided, so this header alone is not sufficient to check wether a response is * header is provided, so this header alone is not sufficient to check wether a response is
* paginated or not. * paginated or not. For the exceptions with the namespace, a fallback check for the route
* * paths has to be added in order to normalize the response. We cannot check for the total_count
* We check if a "total_count" key is present in the response data, but also make sure that * property because it also exists in the response of Get the combined status for a specific ref.
* a "url" property is not, as the "Get the combined status for a specific ref" endpoint would
* otherwise match: https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref
*/ */
const REGEX = [/^\/search\//, /^\/repos\/[^/]+\/[^/]+\/commits\/[^/]+\/(check-runs|check-suites)([^/]|$)/, /^\/installation\/repositories([^/]|$)/, /^\/user\/installations([^/]|$)/, /^\/repos\/[^/]+\/[^/]+\/actions\/secrets([^/]|$)/, /^\/repos\/[^/]+\/[^/]+\/actions\/workflows(\/[^/]+\/runs)?([^/]|$)/, /^\/repos\/[^/]+\/[^/]+\/actions\/runs(\/[^/]+\/(artifacts|jobs))?([^/]|$)/];
function normalizePaginatedListResponse(octokit, url, response) { function normalizePaginatedListResponse(octokit, url, response) {
const responseNeedsNormalization = "total_count" in response.data && !("url" in response.data); const path = url.replace(octokit.request.endpoint.DEFAULTS.baseUrl, "");
const responseNeedsNormalization = REGEX.find(regex => regex.test(path));
if (!responseNeedsNormalization) return; // keep the additional properties intact as there is currently no other way if (!responseNeedsNormalization) return; // keep the additional properties intact as there is currently no other way
// to retrieve the same information. // to retrieve the same information.
@@ -44,6 +50,13 @@ function normalizePaginatedListResponse(octokit, url, response) {
} }
response.data.total_count = totalCount; response.data.total_count = totalCount;
Object.defineProperty(response.data, namespaceKey, {
get() {
octokit.log.warn(`[@octokit/paginate-rest] "response.data.${namespaceKey}" is deprecated for "GET ${path}". Get the results directly from "response.data"`);
return Array.from(data);
}
});
} }
function iterator(octokit, route, parameters) { function iterator(octokit, route, parameters) {
File diff suppressed because one or more lines are too long
@@ -3,19 +3,33 @@
* *
* They have a `total_count` key in the response (search also has `incomplete_results`, * They have a `total_count` key in the response (search also has `incomplete_results`,
* /installation/repositories also has `repository_selection`), as well as a key with * /installation/repositories also has `repository_selection`), as well as a key with
* the list of the items which name varies from endpoint to endpoint. * the list of the items which name varies from endpoint to endpoint:
*
* - https://developer.github.com/v3/search/#example (key `items`)
* - https://developer.github.com/v3/checks/runs/#response-3 (key: `check_runs`)
* - https://developer.github.com/v3/checks/suites/#response-1 (key: `check_suites`)
* - https://developer.github.com/v3/apps/installations/#list-repositories (key: `repositories`)
* - https://developer.github.com/v3/apps/installations/#list-installations-for-a-user (key `installations`)
* *
* Octokit normalizes these responses so that paginated results are always returned following * Octokit normalizes these responses so that paginated results are always returned following
* the same structure. One challenge is that if the list response has only one page, no Link * the same structure. One challenge is that if the list response has only one page, no Link
* header is provided, so this header alone is not sufficient to check wether a response is * header is provided, so this header alone is not sufficient to check wether a response is
* paginated or not. * paginated or not. For the exceptions with the namespace, a fallback check for the route
* * paths has to be added in order to normalize the response. We cannot check for the total_count
* We check if a "total_count" key is present in the response data, but also make sure that * property because it also exists in the response of Get the combined status for a specific ref.
* a "url" property is not, as the "Get the combined status for a specific ref" endpoint would
* otherwise match: https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref
*/ */
const REGEX = [
/^\/search\//,
/^\/repos\/[^/]+\/[^/]+\/commits\/[^/]+\/(check-runs|check-suites)([^/]|$)/,
/^\/installation\/repositories([^/]|$)/,
/^\/user\/installations([^/]|$)/,
/^\/repos\/[^/]+\/[^/]+\/actions\/secrets([^/]|$)/,
/^\/repos\/[^/]+\/[^/]+\/actions\/workflows(\/[^/]+\/runs)?([^/]|$)/,
/^\/repos\/[^/]+\/[^/]+\/actions\/runs(\/[^/]+\/(artifacts|jobs))?([^/]|$)/
];
export function normalizePaginatedListResponse(octokit, url, response) { export function normalizePaginatedListResponse(octokit, url, response) {
const responseNeedsNormalization = "total_count" in response.data && !("url" in response.data); const path = url.replace(octokit.request.endpoint.DEFAULTS.baseUrl, "");
const responseNeedsNormalization = REGEX.find(regex => regex.test(path));
if (!responseNeedsNormalization) if (!responseNeedsNormalization)
return; return;
// keep the additional properties intact as there is currently no other way // keep the additional properties intact as there is currently no other way
@@ -36,4 +50,10 @@ export function normalizePaginatedListResponse(octokit, url, response) {
response.data.repository_selection = repositorySelection; response.data.repository_selection = repositorySelection;
} }
response.data.total_count = totalCount; response.data.total_count = totalCount;
Object.defineProperty(response.data, namespaceKey, {
get() {
octokit.log.warn(`[@octokit/paginate-rest] "response.data.${namespaceKey}" is deprecated for "GET ${path}". Get the results directly from "response.data"`);
return Array.from(data);
}
});
} }
@@ -0,0 +1 @@
export const VERSION = "1.1.2";
@@ -3,16 +3,20 @@
* *
* They have a `total_count` key in the response (search also has `incomplete_results`, * They have a `total_count` key in the response (search also has `incomplete_results`,
* /installation/repositories also has `repository_selection`), as well as a key with * /installation/repositories also has `repository_selection`), as well as a key with
* the list of the items which name varies from endpoint to endpoint. * the list of the items which name varies from endpoint to endpoint:
*
* - https://developer.github.com/v3/search/#example (key `items`)
* - https://developer.github.com/v3/checks/runs/#response-3 (key: `check_runs`)
* - https://developer.github.com/v3/checks/suites/#response-1 (key: `check_suites`)
* - https://developer.github.com/v3/apps/installations/#list-repositories (key: `repositories`)
* - https://developer.github.com/v3/apps/installations/#list-installations-for-a-user (key `installations`)
* *
* Octokit normalizes these responses so that paginated results are always returned following * Octokit normalizes these responses so that paginated results are always returned following
* the same structure. One challenge is that if the list response has only one page, no Link * the same structure. One challenge is that if the list response has only one page, no Link
* header is provided, so this header alone is not sufficient to check wether a response is * header is provided, so this header alone is not sufficient to check wether a response is
* paginated or not. * paginated or not. For the exceptions with the namespace, a fallback check for the route
* * paths has to be added in order to normalize the response. We cannot check for the total_count
* We check if a "total_count" key is present in the response data, but also make sure that * property because it also exists in the response of Get the combined status for a specific ref.
* a "url" property is not, as the "Get the combined status for a specific ref" endpoint would
* otherwise match: https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref
*/ */
import { Octokit } from "@octokit/core"; import { Octokit } from "@octokit/core";
import { OctokitResponse } from "./types"; import { OctokitResponse } from "./types";
@@ -0,0 +1 @@
export declare const VERSION = "1.1.2";
@@ -1,23 +1,37 @@
const VERSION = "2.0.2"; const VERSION = "1.1.2";
/** /**
* Some list response that can be paginated have a different response structure * Some list response that can be paginated have a different response structure
* *
* They have a `total_count` key in the response (search also has `incomplete_results`, * They have a `total_count` key in the response (search also has `incomplete_results`,
* /installation/repositories also has `repository_selection`), as well as a key with * /installation/repositories also has `repository_selection`), as well as a key with
* the list of the items which name varies from endpoint to endpoint. * the list of the items which name varies from endpoint to endpoint:
*
* - https://developer.github.com/v3/search/#example (key `items`)
* - https://developer.github.com/v3/checks/runs/#response-3 (key: `check_runs`)
* - https://developer.github.com/v3/checks/suites/#response-1 (key: `check_suites`)
* - https://developer.github.com/v3/apps/installations/#list-repositories (key: `repositories`)
* - https://developer.github.com/v3/apps/installations/#list-installations-for-a-user (key `installations`)
* *
* Octokit normalizes these responses so that paginated results are always returned following * Octokit normalizes these responses so that paginated results are always returned following
* the same structure. One challenge is that if the list response has only one page, no Link * the same structure. One challenge is that if the list response has only one page, no Link
* header is provided, so this header alone is not sufficient to check wether a response is * header is provided, so this header alone is not sufficient to check wether a response is
* paginated or not. * paginated or not. For the exceptions with the namespace, a fallback check for the route
* * paths has to be added in order to normalize the response. We cannot check for the total_count
* We check if a "total_count" key is present in the response data, but also make sure that * property because it also exists in the response of Get the combined status for a specific ref.
* a "url" property is not, as the "Get the combined status for a specific ref" endpoint would
* otherwise match: https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref
*/ */
const REGEX = [
/^\/search\//,
/^\/repos\/[^/]+\/[^/]+\/commits\/[^/]+\/(check-runs|check-suites)([^/]|$)/,
/^\/installation\/repositories([^/]|$)/,
/^\/user\/installations([^/]|$)/,
/^\/repos\/[^/]+\/[^/]+\/actions\/secrets([^/]|$)/,
/^\/repos\/[^/]+\/[^/]+\/actions\/workflows(\/[^/]+\/runs)?([^/]|$)/,
/^\/repos\/[^/]+\/[^/]+\/actions\/runs(\/[^/]+\/(artifacts|jobs))?([^/]|$)/
];
function normalizePaginatedListResponse(octokit, url, response) { function normalizePaginatedListResponse(octokit, url, response) {
const responseNeedsNormalization = "total_count" in response.data && !("url" in response.data); const path = url.replace(octokit.request.endpoint.DEFAULTS.baseUrl, "");
const responseNeedsNormalization = REGEX.find(regex => regex.test(path));
if (!responseNeedsNormalization) if (!responseNeedsNormalization)
return; return;
// keep the additional properties intact as there is currently no other way // keep the additional properties intact as there is currently no other way
@@ -38,6 +52,12 @@ function normalizePaginatedListResponse(octokit, url, response) {
response.data.repository_selection = repositorySelection; response.data.repository_selection = repositorySelection;
} }
response.data.total_count = totalCount; response.data.total_count = totalCount;
Object.defineProperty(response.data, namespaceKey, {
get() {
octokit.log.warn(`[@octokit/paginate-rest] "response.data.${namespaceKey}" is deprecated for "GET ${path}". Get the results directly from "response.data"`);
return Array.from(data);
}
});
} }
function iterator(octokit, route, parameters) { function iterator(octokit, route, parameters) {
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
{ {
"name": "@octokit/plugin-paginate-rest", "name": "@octokit/plugin-paginate-rest",
"description": "Octokit plugin to paginate REST API endpoint responses", "description": "Octokit plugin to paginate REST API endpoint responses",
"version": "2.0.2", "version": "1.1.2",
"license": "MIT", "license": "MIT",
"files": [ "files": [
"dist-*/", "dist-*/",
@@ -22,18 +22,18 @@
"devDependencies": { "devDependencies": {
"@octokit/core": "^2.0.0", "@octokit/core": "^2.0.0",
"@pika/pack": "^0.5.0", "@pika/pack": "^0.5.0",
"@pika/plugin-build-node": "^0.9.0", "@pika/plugin-build-node": "^0.8.1",
"@pika/plugin-build-web": "^0.9.0", "@pika/plugin-build-web": "^0.8.1",
"@pika/plugin-ts-standard-pkg": "^0.9.0", "@pika/plugin-ts-standard-pkg": "^0.8.1",
"@types/fetch-mock": "^7.3.1", "@types/fetch-mock": "^7.3.1",
"@types/jest": "^25.1.0", "@types/jest": "^24.0.18",
"@types/node": "^13.1.0", "@types/node": "^13.1.0",
"fetch-mock": "^9.0.0", "fetch-mock": "^8.0.0",
"jest": "^24.9.0", "jest": "^24.9.0",
"prettier": "^1.18.2", "prettier": "^1.18.2",
"semantic-release": "^17.0.0", "semantic-release": "^17.0.0",
"semantic-release-plugin-update-version-in-files": "^1.0.0", "semantic-release-plugin-update-version-in-files": "^1.0.0",
"ts-jest": "^25.1.0", "ts-jest": "^24.1.0",
"typescript": "^3.7.2" "typescript": "^3.7.2"
}, },
"publishConfig": { "publishConfig": {
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,38 @@
import { Deprecation } from "deprecation";
import endpointsByScope from "./generated/endpoints";
import { VERSION } from "./version";
import { registerEndpoints } from "./register-endpoints";
/**
* This plugin is a 1:1 copy of internal @octokit/rest plugins. The primary
* goal is to rebuild @octokit/rest on top of @octokit/core. Once that is
* done, we will remove the registerEndpoints methods and return the methods
* directly as with the other plugins. At that point we will also remove the
* legacy workarounds and deprecations.
*
* See the plan at
* https://github.com/octokit/plugin-rest-endpoint-methods.js/pull/1
*/
export function restEndpointMethods(octokit) {
// @ts-ignore
octokit.registerEndpoints = registerEndpoints.bind(null, octokit);
registerEndpoints(octokit, endpointsByScope);
// Aliasing scopes for backward compatibility
// See https://github.com/octokit/rest.js/pull/1134
[
["gitdata", "git"],
["authorization", "oauthAuthorizations"],
["pullRequests", "pulls"]
].forEach(([deprecatedScope, scope]) => {
Object.defineProperty(octokit, deprecatedScope, {
get() {
octokit.log.warn(
// @ts-ignore
new Deprecation(`[@octokit/plugin-rest-endpoint-methods] "octokit.${deprecatedScope}.*" methods are deprecated, use "octokit.${scope}.*" instead`));
// @ts-ignore
return octokit[scope];
}
});
});
return {};
}
restEndpointMethods.VERSION = VERSION;
@@ -0,0 +1,60 @@
import { Deprecation } from "deprecation";
export function registerEndpoints(octokit, routes) {
Object.keys(routes).forEach(namespaceName => {
if (!octokit[namespaceName]) {
octokit[namespaceName] = {};
}
Object.keys(routes[namespaceName]).forEach(apiName => {
const apiOptions = routes[namespaceName][apiName];
const endpointDefaults = ["method", "url", "headers"].reduce((map, key) => {
if (typeof apiOptions[key] !== "undefined") {
map[key] = apiOptions[key];
}
return map;
}, {});
endpointDefaults.request = {
validate: apiOptions.params
};
let request = octokit.request.defaults(endpointDefaults);
// patch request & endpoint methods to support deprecated parameters.
// Not the most elegant solution, but we dont want to move deprecation
// logic into octokit/endpoint.js as its out of scope
const hasDeprecatedParam = Object.keys(apiOptions.params || {}).find(key => apiOptions.params[key].deprecated);
if (hasDeprecatedParam) {
const patch = patchForDeprecation.bind(null, octokit, apiOptions);
request = patch(octokit.request.defaults(endpointDefaults), `.${namespaceName}.${apiName}()`);
request.endpoint = patch(request.endpoint, `.${namespaceName}.${apiName}.endpoint()`);
request.endpoint.merge = patch(request.endpoint.merge, `.${namespaceName}.${apiName}.endpoint.merge()`);
}
if (apiOptions.deprecated) {
octokit[namespaceName][apiName] = Object.assign(function deprecatedEndpointMethod() {
octokit.log.warn(new Deprecation(`[@octokit/rest] ${apiOptions.deprecated}`));
octokit[namespaceName][apiName] = request;
return request.apply(null, arguments);
}, request);
return;
}
octokit[namespaceName][apiName] = request;
});
});
}
function patchForDeprecation(octokit, apiOptions, method, methodName) {
const patchedMethod = (options) => {
options = Object.assign({}, options);
Object.keys(options).forEach(key => {
if (apiOptions.params[key] && apiOptions.params[key].deprecated) {
const aliasKey = apiOptions.params[key].alias;
octokit.log.warn(new Deprecation(`[@octokit/rest] "${key}" parameter is deprecated for "${methodName}". Use "${aliasKey}" instead`));
if (!(aliasKey in options)) {
options[aliasKey] = options[key];
}
delete options[key];
}
});
return method(options);
};
Object.keys(method).forEach(key => {
patchedMethod[key] = method[key];
});
return patchedMethod;
}
@@ -0,0 +1 @@
export const VERSION = "2.4.0";
@@ -0,0 +1 @@
export declare function registerEndpoints(octokit: any, routes: any): void;
@@ -0,0 +1,4 @@
import { RestEndpointMethods } from "./generated/rest-endpoint-methods-types";
export declare type Api = {
registerEndpoints: (endpoints: any) => void;
} & RestEndpointMethods;
@@ -0,0 +1 @@
export declare const VERSION = "2.4.0";
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More