From b419ff6d7b5eaa802c5ac487d43f81187a272c23 Mon Sep 17 00:00:00 2001 From: David Verdeguer Date: Wed, 29 Apr 2020 11:43:39 +0200 Subject: [PATCH 01/15] Error on queries with missing/multiple languages --- lib/finalize-db.js | 4 ++-- src/finalize-db.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/finalize-db.js b/lib/finalize-db.js index 8679a7d0f..f73bb4951 100644 --- a/lib/finalize-db.js +++ b/lib/finalize-db.js @@ -73,12 +73,12 @@ async function resolveQueryLanguages(codeqlCmd, config) { const noDeclaredLanguage = resolveQueriesOutputObject.noDeclaredLanguage; const noDeclaredLanguageQueries = Object.keys(noDeclaredLanguage); if (noDeclaredLanguageQueries.length !== 0) { - core.warning('Some queries do not declare a language:\n' + noDeclaredLanguageQueries.join('\n')); + 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) { - core.warning('Some queries declare multiple languages:\n' + multipleDeclaredLanguagesQueries.join('\n')); + throw new Error('Some queries declare multiple languages, their qlpack.yml file is missing or is invalid'); } } return res; diff --git a/src/finalize-db.ts b/src/finalize-db.ts index a03e68a1b..b9605b0e2 100644 --- a/src/finalize-db.ts +++ b/src/finalize-db.ts @@ -82,13 +82,13 @@ async function resolveQueryLanguages(codeqlCmd: string, config: configUtils.Conf const noDeclaredLanguage = resolveQueriesOutputObject.noDeclaredLanguage; const noDeclaredLanguageQueries = Object.keys(noDeclaredLanguage); if (noDeclaredLanguageQueries.length !== 0) { - core.warning('Some queries do not declare a language:\n' + noDeclaredLanguageQueries.join('\n')); + 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) { - core.warning('Some queries declare multiple languages:\n' + multipleDeclaredLanguagesQueries.join('\n')); + throw new Error('Some queries declare multiple languages, their qlpack.yml file is missing or is invalid'); } } From 5c74b0f641d2a7814c866b545abdb60dcda04180 Mon Sep 17 00:00:00 2001 From: David Verdeguer Date: Wed, 29 Apr 2020 12:33:41 +0200 Subject: [PATCH 02/15] Parse ignoreDefaultQueries field --- lib/config-utils.js | 4 ++++ src/config-utils.ts | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/lib/config-utils.js b/lib/config-utils.js index ae4414430..ce032a2da 100644 --- a/lib/config-utils.js +++ b/lib/config-utils.js @@ -23,6 +23,7 @@ exports.ExternalQuery = ExternalQuery; class Config { constructor() { this.name = ""; + this.ignoreDefaultQueries = false; this.additionalQueries = []; this.externalQueries = []; this.pathsIgnore = []; @@ -75,6 +76,9 @@ function initConfig() { if (parsedYAML.name && typeof parsedYAML.name === "string") { config.name = parsedYAML.name; } + if (parsedYAML['ignore-default-queries'] && typeof parsedYAML['ignore-default-queries'] === "boolean") { + config.ignoreDefaultQueries = parsedYAML['ignore-default-queries']; + } const queries = parsedYAML.queries; if (queries && queries instanceof Array) { queries.forEach(query => { diff --git a/src/config-utils.ts b/src/config-utils.ts index 5221e5906..71d5edc12 100644 --- a/src/config-utils.ts +++ b/src/config-utils.ts @@ -17,6 +17,7 @@ export class ExternalQuery { export class Config { public name = ""; + public ignoreDefaultQueries = false; public additionalQueries: string[] = []; public externalQueries: ExternalQuery[] = []; public pathsIgnore: string[] = []; @@ -81,6 +82,10 @@ function initConfig(): Config { config.name = parsedYAML.name; } + if (parsedYAML['ignore-default-queries'] && typeof parsedYAML['ignore-default-queries'] === "boolean") { + config.ignoreDefaultQueries = parsedYAML['ignore-default-queries']; + } + const queries = parsedYAML.queries; if (queries && queries instanceof Array) { queries.forEach(query => { From 8bd6c1e5f0c025009c9a5576eb0df7a5fcf481ba Mon Sep 17 00:00:00 2001 From: David Verdeguer Date: Wed, 29 Apr 2020 12:41:28 +0200 Subject: [PATCH 03/15] Ignore default queries --- lib/finalize-db.js | 9 ++++++--- src/finalize-db.ts | 10 +++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/finalize-db.js b/lib/finalize-db.js index 8679a7d0f..b2c607c5a 100644 --- a/lib/finalize-db.js +++ b/lib/finalize-db.js @@ -88,7 +88,11 @@ async function runQueries(codeqlCmd, databaseFolder, sarifFolder, config) { const queriesPerLanguage = await resolveQueryLanguages(codeqlCmd, config); for (let database of fs.readdirSync(databaseFolder)) { core.startGroup('Analyzing ' + database); - const additionalQueries = queriesPerLanguage[database] || []; + const queries = []; + if (!config.ignoreDefaultQueries) { + queries.push(database + '-code-scanning.qls'); + } + queries.push(...queriesPerLanguage[database]); const sarifFile = path.join(sarifFolder, database + '.sarif'); await exec.exec(codeqlCmd, [ 'database', @@ -97,8 +101,7 @@ async function runQueries(codeqlCmd, databaseFolder, sarifFolder, config) { '--format=sarif-latest', '--output=' + sarifFile, '--no-sarif-add-snippets', - database + '-code-scanning.qls', - ...additionalQueries, + ...queries ]); core.debug('SARIF results for database ' + database + ' created at "' + sarifFile + '"'); core.endGroup(); diff --git a/src/finalize-db.ts b/src/finalize-db.ts index a03e68a1b..e1cc1fc3e 100644 --- a/src/finalize-db.ts +++ b/src/finalize-db.ts @@ -102,7 +102,12 @@ async function runQueries(codeqlCmd: string, databaseFolder: string, sarifFolder for (let database of fs.readdirSync(databaseFolder)) { core.startGroup('Analyzing ' + database); - const additionalQueries = queriesPerLanguage[database] || []; + const queries: string[] = []; + if (!config.ignoreDefaultQueries) { + queries.push(database + '-code-scanning.qls'); + } + queries.push(...queriesPerLanguage[database]); + const sarifFile = path.join(sarifFolder, database + '.sarif'); await exec.exec(codeqlCmd, [ @@ -112,8 +117,7 @@ async function runQueries(codeqlCmd: string, databaseFolder: string, sarifFolder '--format=sarif-latest', '--output=' + sarifFile, '--no-sarif-add-snippets', - database + '-code-scanning.qls', - ...additionalQueries, + ...queries ]); core.debug('SARIF results for database ' + database + ' created at "' + sarifFile + '"'); From 32ced8c9013ff2ff6f9652ab98596f494ca4ac1a Mon Sep 17 00:00:00 2001 From: David Verdeguer Date: Wed, 29 Apr 2020 14:05:40 +0200 Subject: [PATCH 04/15] Update README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 219864679..916cc8302 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,8 @@ The CodeQL action should be run on `push` events, and on a `schedule`. `Push` ev ### Configuration You may optionally specify additional queries for CodeQL to execute by using a config file. The queries must belong to a [QL pack](https://help.semmle.com/codeql/codeql-cli/reference/qlpack-overview.html) and can be in your repository or any public repository. You can choose a single .ql file, a folder containing multiple .ql files, a .qls [query suite](https://help.semmle.com/codeql/codeql-cli/procedures/query-suites.html) file, or any combination of the above. To use queries from other repositories use the same syntax as when [using an action](https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsuses). +You can disable the default queries using `ignore-default-queries: true`. + You can choose to ignore some files or folders from the analysis, or include additional files/folders for analysis. This *only* works for Javascript and Python analysis. Identifying potential files for extraction: - Scans each folder that's defined as `paths` in turn, traversing subfolders and looking for relevant files. @@ -98,6 +100,8 @@ A config file looks like this: ```yaml name: "My CodeQL config" +ignore-default-queries: true + queries: - name: In-repo queries (Runs the queries located in the my-queries folder of the repo) uses: ./my-queries From 20a0628ed73a0cbde47da41ab316fa9ba13a3fcc Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 29 Apr 2020 16:45:47 +0100 Subject: [PATCH 05/15] Fix typos / errors in PR template --- .github/pull_request_template.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index d6467871a..ca84204a3 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,7 +1,7 @@ ### 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 otehr repos! - - [ ] CodeQL using init/finish actions +- 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 the [readme](https://github.com/github/codeql-action/blob/master/README.md) has been updated if necessary. \ No newline at end of file +- [ ] Confirm the [readme](https://github.com/github/codeql-action/blob/master/README.md) has been updated if necessary. From 0cf8450c2400c10d330933a5102827114f907cbe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2020 18:03:56 +0000 Subject: [PATCH 06/15] Bump @actions/http-client from 1.0.4 to 1.0.8 Bumps [@actions/http-client](https://github.com/actions/http-client) from 1.0.4 to 1.0.8. - [Release notes](https://github.com/actions/http-client/releases) - [Changelog](https://github.com/actions/http-client/blob/master/RELEASES.md) - [Commits](https://github.com/actions/http-client/commits) Signed-off-by: dependabot[bot] --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 63573714f..f3d9d22af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,9 +15,9 @@ "integrity": "sha512-nvFkxwiicvpzNiCBF4wFBDfnBvi7xp/as7LE1hBxBxKG2L29+gkIPBiLKMVORL+Hg3JNf07AKRfl0V5djoypjQ==" }, "@actions/http-client": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.4.tgz", - "integrity": "sha512-6EzXhqapKKtYr21ZnFQVBYwfrYPKPCivuSkUN/66/BDakkH2EPjUZH8tZ3MgHdI+gQIdcsY0ybbxw9ZEOmJB6g==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.8.tgz", + "integrity": "sha512-G4JjJ6f9Hb3Zvejj+ewLLKLf99ZC+9v+yCxoYf9vSyH+WkzPLB2LuUtRMGNkooMqdugGBFStIKXOuvH1W+EctA==", "requires": { "tunnel": "0.0.6" }, diff --git a/package.json b/package.json index 922361cd6..00e36b072 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@actions/core": "^1.0.0", "@actions/exec": "^1.0.1", - "@actions/http-client": "^1.0.4", + "@actions/http-client": "^1.0.8", "@actions/io": "^1.0.1", "@actions/tool-cache": "^1.1.2", "@octokit/rest": "^17.1.0", From 34db3b0291dc246bf040e839a3cdd3f786dc4862 Mon Sep 17 00:00:00 2001 From: Justin Hutchings Date: Wed, 29 Apr 2020 14:09:20 -0700 Subject: [PATCH 07/15] Update README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index a296ea3d4..66f9ea2c5 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ This action runs GitHub's industry-leading static analysis engine, CodeQL, against a repository's source code to find security vulnerabilities. It then automatically uploads the results to GitHub so they can be displayed in the repository's security tab. CodeQL runs an extensible set of [queries](https://github.com/semmle/ql), which have been developed by the community and the [GitHub Security Lab](https://securitylab.github.com/) to find common vulnerabilities in your code. -[Sign up for the Advanced Security beta](https://github.com/features/security/advanced-security/signup) - ## Usage To get code scanning results from CodeQL analysis on your repo you can use the following workflow as a template: From 2809bdc3eef2c19ad883621be3c456c79672db60 Mon Sep 17 00:00:00 2001 From: David Verdeguer Date: Thu, 30 Apr 2020 09:37:04 +0200 Subject: [PATCH 08/15] ignore-default-queries -> disable-default-queries --- README.md | 2 +- lib/config-utils.js | 6 +++--- lib/finalize-db.js | 2 +- src/config-utils.ts | 6 +++--- src/finalize-db.ts | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 916cc8302..68030b5e1 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ The CodeQL action should be run on `push` events, and on a `schedule`. `Push` ev ### Configuration You may optionally specify additional queries for CodeQL to execute by using a config file. The queries must belong to a [QL pack](https://help.semmle.com/codeql/codeql-cli/reference/qlpack-overview.html) and can be in your repository or any public repository. You can choose a single .ql file, a folder containing multiple .ql files, a .qls [query suite](https://help.semmle.com/codeql/codeql-cli/procedures/query-suites.html) file, or any combination of the above. To use queries from other repositories use the same syntax as when [using an action](https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsuses). -You can disable the default queries using `ignore-default-queries: true`. +You can disable the default queries using `disable-default-queries: true`. You can choose to ignore some files or folders from the analysis, or include additional files/folders for analysis. This *only* works for Javascript and Python analysis. Identifying potential files for extraction: diff --git a/lib/config-utils.js b/lib/config-utils.js index ce032a2da..df70c6283 100644 --- a/lib/config-utils.js +++ b/lib/config-utils.js @@ -23,7 +23,7 @@ exports.ExternalQuery = ExternalQuery; class Config { constructor() { this.name = ""; - this.ignoreDefaultQueries = false; + this.disableDefaultQueries = false; this.additionalQueries = []; this.externalQueries = []; this.pathsIgnore = []; @@ -76,8 +76,8 @@ function initConfig() { if (parsedYAML.name && typeof parsedYAML.name === "string") { config.name = parsedYAML.name; } - if (parsedYAML['ignore-default-queries'] && typeof parsedYAML['ignore-default-queries'] === "boolean") { - config.ignoreDefaultQueries = parsedYAML['ignore-default-queries']; + if (parsedYAML['disable-default-queries'] && typeof parsedYAML['disable-default-queries'] === "boolean") { + config.disableDefaultQueries = parsedYAML['disable-default-queries']; } const queries = parsedYAML.queries; if (queries && queries instanceof Array) { diff --git a/lib/finalize-db.js b/lib/finalize-db.js index b2c607c5a..b9a33f155 100644 --- a/lib/finalize-db.js +++ b/lib/finalize-db.js @@ -89,7 +89,7 @@ async function runQueries(codeqlCmd, databaseFolder, sarifFolder, config) { for (let database of fs.readdirSync(databaseFolder)) { core.startGroup('Analyzing ' + database); const queries = []; - if (!config.ignoreDefaultQueries) { + if (!config.disableDefaultQueries) { queries.push(database + '-code-scanning.qls'); } queries.push(...queriesPerLanguage[database]); diff --git a/src/config-utils.ts b/src/config-utils.ts index 71d5edc12..fb74c4228 100644 --- a/src/config-utils.ts +++ b/src/config-utils.ts @@ -17,7 +17,7 @@ export class ExternalQuery { export class Config { public name = ""; - public ignoreDefaultQueries = false; + public disableDefaultQueries = false; public additionalQueries: string[] = []; public externalQueries: ExternalQuery[] = []; public pathsIgnore: string[] = []; @@ -82,8 +82,8 @@ function initConfig(): Config { config.name = parsedYAML.name; } - if (parsedYAML['ignore-default-queries'] && typeof parsedYAML['ignore-default-queries'] === "boolean") { - config.ignoreDefaultQueries = parsedYAML['ignore-default-queries']; + if (parsedYAML['disable-default-queries'] && typeof parsedYAML['disable-default-queries'] === "boolean") { + config.disableDefaultQueries = parsedYAML['disable-default-queries']; } const queries = parsedYAML.queries; diff --git a/src/finalize-db.ts b/src/finalize-db.ts index e1cc1fc3e..f0f60e828 100644 --- a/src/finalize-db.ts +++ b/src/finalize-db.ts @@ -103,7 +103,7 @@ async function runQueries(codeqlCmd: string, databaseFolder: string, sarifFolder core.startGroup('Analyzing ' + database); const queries: string[] = []; - if (!config.ignoreDefaultQueries) { + if (!config.disableDefaultQueries) { queries.push(database + '-code-scanning.qls'); } queries.push(...queriesPerLanguage[database]); From 6997a2170d427e931505374441bd8b9a45f1d983 Mon Sep 17 00:00:00 2001 From: David Verdeguer Date: Thu, 30 Apr 2020 11:03:44 +0200 Subject: [PATCH 09/15] Address comments --- README.md | 2 +- lib/finalize-db.js | 2 +- src/finalize-db.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 68030b5e1..dd5aa2dea 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ A config file looks like this: ```yaml name: "My CodeQL config" -ignore-default-queries: true +disable-default-queries: true queries: - name: In-repo queries (Runs the queries located in the my-queries folder of the repo) diff --git a/lib/finalize-db.js b/lib/finalize-db.js index b9a33f155..0082161f4 100644 --- a/lib/finalize-db.js +++ b/lib/finalize-db.js @@ -92,7 +92,7 @@ async function runQueries(codeqlCmd, databaseFolder, sarifFolder, config) { if (!config.disableDefaultQueries) { queries.push(database + '-code-scanning.qls'); } - queries.push(...queriesPerLanguage[database]); + queries.push(...(queriesPerLanguage[database] || [])); const sarifFile = path.join(sarifFolder, database + '.sarif'); await exec.exec(codeqlCmd, [ 'database', diff --git a/src/finalize-db.ts b/src/finalize-db.ts index f0f60e828..c897f95c8 100644 --- a/src/finalize-db.ts +++ b/src/finalize-db.ts @@ -106,7 +106,7 @@ async function runQueries(codeqlCmd: string, databaseFolder: string, sarifFolder if (!config.disableDefaultQueries) { queries.push(database + '-code-scanning.qls'); } - queries.push(...queriesPerLanguage[database]); + queries.push(...(queriesPerLanguage[database] || [])); const sarifFile = path.join(sarifFolder, database + '.sarif'); From 1da651c2196959a0403d7f49689ac6aabac909a9 Mon Sep 17 00:00:00 2001 From: Robert Brignull Date: Fri, 1 May 2020 10:39:23 +0100 Subject: [PATCH 10/15] Add retries to the upload --- lib/upload-lib.js | 56 ++++++++++++++++++++++++++--------------- src/upload-lib.ts | 63 +++++++++++++++++++++++++++++++++-------------- 2 files changed, 80 insertions(+), 39 deletions(-) diff --git a/lib/upload-lib.js b/lib/upload-lib.js index f28f085a4..62d49a065 100644 --- a/lib/upload-lib.js +++ b/lib/upload-lib.js @@ -54,6 +54,40 @@ function combineSarifFiles(sarifFiles) { return JSON.stringify(combinedSarif); } exports.combineSarifFiles = combineSarifFiles; +// Upload the given payload. +// If the request fails then this will be retry a small number of times. +async function uploadPayload(payload) { + core.info('Uploading results'); + const githubToken = core.getInput('token'); + 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 + // number of seconds between each attempt. + // We don't want to backoff too much to avoid wasting action + // minutes, but just waiting a little bit could maybe help. + const backoffPeriods = [1, 5, 15]; + for (let attempt = 0; attempt <= backoffPeriods.length; attempt++) { + const res = await client.put(url, payload); + core.debug('response status: ' + res.message.statusCode); + if (res.message.statusCode === 202) { + core.info("Successfully uploaded results"); + return; + } + const requestID = res.message.headers["x-github-request-id"]; + if (attempt < backoffPeriods.length) { + // 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) + + ') failed (' + requestID + '). Retrying in ' + backoffPeriods[attempt] + ' seconds: ' + + await res.readBody()); + // Sleep for the backoff period + await new Promise(r => setTimeout(r, backoffPeriods[attempt] * 1000)); + } + else { + core.setFailed('Upload failed (' + requestID + '): ' + await res.readBody()); + } + } +} // Uploads a single sarif file or a directory of sarif files // depending on what the path happens to refer to. async function upload(input) { @@ -112,26 +146,8 @@ async function uploadFiles(sarifFiles) { "started_at": startedAt, "tool_names": toolNames, }); - core.info('Uploading results'); - const githubToken = core.getInput('token'); - 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'; - const res = await client.put(url, payload); - const requestID = res.message.headers["x-github-request-id"]; - core.debug('response status: ' + res.message.statusCode); - if (res.message.statusCode === 500) { - // If the upload fails with 500 then we assume it is a temporary problem - // with turbo-scan and not an error that the user has caused or can fix. - // We avoid marking the job as failed to avoid breaking CI workflows. - core.error('Upload failed (' + requestID + '): ' + await res.readBody()); - } - else if (res.message.statusCode !== 202) { - core.setFailed('Upload failed (' + requestID + '): ' + await res.readBody()); - } - else { - core.info("Successfully uploaded results"); - } + // Make the upload + await uploadPayload(payload); // Mark that we have made an upload fs.writeFileSync(sentinelFile, ''); } diff --git a/src/upload-lib.ts b/src/upload-lib.ts index 8c6a31e4e..c9b69dfcb 100644 --- a/src/upload-lib.ts +++ b/src/upload-lib.ts @@ -47,6 +47,48 @@ export function combineSarifFiles(sarifFiles: string[]): string { return JSON.stringify(combinedSarif); } +// Upload the given payload. +// If the request fails then this will be retry a small number of times. +async function uploadPayload(payload) { + core.info('Uploading results'); + + const githubToken = core.getInput('token'); + const ph: auth.BearerCredentialHandler = 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 + // number of seconds between each attempt. + // We don't want to backoff too much to avoid wasting action + // minutes, but just waiting a little bit could maybe help. + const backoffPeriods = [1, 5, 15]; + + for (let attempt = 0; attempt <= backoffPeriods.length; attempt++) { + + const res: http.HttpClientResponse = await client.put(url, payload); + core.debug('response status: ' + res.message.statusCode); + + if (res.message.statusCode === 202) { + core.info("Successfully uploaded results"); + return; + } + + const requestID = res.message.headers["x-github-request-id"]; + + if (attempt < backoffPeriods.length) { + // 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) + + ') failed (' + requestID + '). Retrying in ' + backoffPeriods[attempt] + ' seconds: ' + + await res.readBody()); + // Sleep for the backoff period + await new Promise(r => setTimeout(r, backoffPeriods[attempt] * 1000)); + + } else { + core.setFailed('Upload failed (' + requestID + '): ' + await res.readBody()); + } + } +} + // Uploads a single sarif file or a directory of sarif files // depending on what the path happens to refer to. export async function upload(input: string) { @@ -112,25 +154,8 @@ async function uploadFiles(sarifFiles: string[]) { "tool_names": toolNames, }); - core.info('Uploading results'); - const githubToken = core.getInput('token'); - const ph: auth.BearerCredentialHandler = 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'; - const res: http.HttpClientResponse = await client.put(url, payload); - const requestID = res.message.headers["x-github-request-id"]; - - core.debug('response status: ' + res.message.statusCode); - if (res.message.statusCode === 500) { - // If the upload fails with 500 then we assume it is a temporary problem - // with turbo-scan and not an error that the user has caused or can fix. - // We avoid marking the job as failed to avoid breaking CI workflows. - core.error('Upload failed (' + requestID + '): ' + await res.readBody()); - } else if (res.message.statusCode !== 202) { - core.setFailed('Upload failed (' + requestID + '): ' + await res.readBody()); - } else { - core.info("Successfully uploaded results"); - } + // Make the upload + await uploadPayload(payload); // Mark that we have made an upload fs.writeFileSync(sentinelFile, ''); From cffc0f7b4e89a6b33851dd8f9033f870784b54ed Mon Sep 17 00:00:00 2001 From: Robert Brignull Date: Fri, 1 May 2020 11:19:39 +0100 Subject: [PATCH 11/15] fix typo --- lib/upload-lib.js | 2 +- src/upload-lib.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/upload-lib.js b/lib/upload-lib.js index 62d49a065..668f5f8cc 100644 --- a/lib/upload-lib.js +++ b/lib/upload-lib.js @@ -55,7 +55,7 @@ function combineSarifFiles(sarifFiles) { } exports.combineSarifFiles = combineSarifFiles; // Upload the given payload. -// If the request fails then this will be retry a small number of times. +// If the request fails then this will retry a small number of times. async function uploadPayload(payload) { core.info('Uploading results'); const githubToken = core.getInput('token'); diff --git a/src/upload-lib.ts b/src/upload-lib.ts index c9b69dfcb..18d43f861 100644 --- a/src/upload-lib.ts +++ b/src/upload-lib.ts @@ -48,7 +48,7 @@ export function combineSarifFiles(sarifFiles: string[]): string { } // Upload the given payload. -// If the request fails then this will be retry a small number of times. +// If the request fails then this will retry a small number of times. async function uploadPayload(payload) { core.info('Uploading results'); From e52e34ba17846a8305514ca2459c4102b88a10cf Mon Sep 17 00:00:00 2001 From: Robert Brignull Date: Fri, 1 May 2020 11:20:21 +0100 Subject: [PATCH 12/15] remove change to behaviour on 500 errors --- lib/upload-lib.js | 6 ++++++ src/upload-lib.ts | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/lib/upload-lib.js b/lib/upload-lib.js index 668f5f8cc..d8f4639d9 100644 --- a/lib/upload-lib.js +++ b/lib/upload-lib.js @@ -83,6 +83,12 @@ async function uploadPayload(payload) { // Sleep for the backoff period await new Promise(r => setTimeout(r, backoffPeriods[attempt] * 1000)); } + else if (res.message.statusCode === 500) { + // If the upload fails with 500 then we assume it is a temporary problem + // with turbo-scan and not an error that the user has caused or can fix. + // We avoid marking the job as failed to avoid breaking CI workflows. + core.error('Upload failed (' + requestID + '): ' + await res.readBody()); + } else { core.setFailed('Upload failed (' + requestID + '): ' + await res.readBody()); } diff --git a/src/upload-lib.ts b/src/upload-lib.ts index 18d43f861..1342ccfa8 100644 --- a/src/upload-lib.ts +++ b/src/upload-lib.ts @@ -83,6 +83,12 @@ async function uploadPayload(payload) { // Sleep for the backoff period await new Promise(r => setTimeout(r, backoffPeriods[attempt] * 1000)); + } else if (res.message.statusCode === 500) { + // If the upload fails with 500 then we assume it is a temporary problem + // with turbo-scan and not an error that the user has caused or can fix. + // We avoid marking the job as failed to avoid breaking CI workflows. + core.error('Upload failed (' + requestID + '): ' + await res.readBody()); + } else { core.setFailed('Upload failed (' + requestID + '): ' + await res.readBody()); } From 0c4fc16b490c4da59b8fa673963871f640c36ba5 Mon Sep 17 00:00:00 2001 From: Robert Brignull Date: Fri, 1 May 2020 11:32:09 +0100 Subject: [PATCH 13/15] only retry on 5xx status codes --- lib/upload-lib.js | 18 ++++++++++++------ src/upload-lib.ts | 19 +++++++++++++------ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/lib/upload-lib.js b/lib/upload-lib.js index d8f4639d9..a23f54c4b 100644 --- a/lib/upload-lib.js +++ b/lib/upload-lib.js @@ -70,11 +70,18 @@ async function uploadPayload(payload) { for (let attempt = 0; attempt <= backoffPeriods.length; attempt++) { const res = await client.put(url, payload); core.debug('response status: ' + res.message.statusCode); - if (res.message.statusCode === 202) { + const statusCode = res.message.statusCode; + if (statusCode === 202) { core.info("Successfully uploaded results"); return; } const requestID = res.message.headers["x-github-request-id"]; + // On any other status code that's not 5xx mark the upload as failed + if (!statusCode || statusCode < 500 || statusCode >= 600) { + core.setFailed('Upload failed (' + requestID + '): ' + await res.readBody()); + return; + } + // On a 5xx status code we may retry the request if (attempt < backoffPeriods.length) { // 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) + @@ -82,15 +89,14 @@ async function uploadPayload(payload) { await res.readBody()); // Sleep for the backoff period await new Promise(r => setTimeout(r, backoffPeriods[attempt] * 1000)); + continue; } - else if (res.message.statusCode === 500) { - // If the upload fails with 500 then we assume it is a temporary problem + else { + // If the upload fails with 5xx then we assume it is a temporary problem // with turbo-scan and not an error that the user has caused or can fix. // We avoid marking the job as failed to avoid breaking CI workflows. core.error('Upload failed (' + requestID + '): ' + await res.readBody()); - } - else { - core.setFailed('Upload failed (' + requestID + '): ' + await res.readBody()); + return; } } } diff --git a/src/upload-lib.ts b/src/upload-lib.ts index 1342ccfa8..d404b2685 100644 --- a/src/upload-lib.ts +++ b/src/upload-lib.ts @@ -68,13 +68,21 @@ async function uploadPayload(payload) { const res: http.HttpClientResponse = await client.put(url, payload); core.debug('response status: ' + res.message.statusCode); - if (res.message.statusCode === 202) { + const statusCode = res.message.statusCode; + if (statusCode === 202) { core.info("Successfully uploaded results"); return; } const requestID = res.message.headers["x-github-request-id"]; + // On any other status code that's not 5xx mark the upload as failed + if (!statusCode || statusCode < 500 || statusCode >= 600) { + core.setFailed('Upload failed (' + requestID + '): ' + await res.readBody()); + return; + } + + // On a 5xx status code we may retry the request if (attempt < backoffPeriods.length) { // 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) + @@ -82,15 +90,14 @@ async function uploadPayload(payload) { await res.readBody()); // Sleep for the backoff period await new Promise(r => setTimeout(r, backoffPeriods[attempt] * 1000)); + continue; - } else if (res.message.statusCode === 500) { - // If the upload fails with 500 then we assume it is a temporary problem + } else { + // If the upload fails with 5xx then we assume it is a temporary problem // with turbo-scan and not an error that the user has caused or can fix. // We avoid marking the job as failed to avoid breaking CI workflows. core.error('Upload failed (' + requestID + '): ' + await res.readBody()); - - } else { - core.setFailed('Upload failed (' + requestID + '): ' + await res.readBody()); + return; } } } From a23cb1d61aa40e508980e1112d465e92f44d7c76 Mon Sep 17 00:00:00 2001 From: Robert Brignull Date: Fri, 1 May 2020 11:45:00 +0100 Subject: [PATCH 14/15] include status code is error message --- lib/upload-lib.js | 8 ++++---- src/upload-lib.ts | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/upload-lib.js b/lib/upload-lib.js index a23f54c4b..e2dd3b32d 100644 --- a/lib/upload-lib.js +++ b/lib/upload-lib.js @@ -78,15 +78,15 @@ async function uploadPayload(payload) { const requestID = res.message.headers["x-github-request-id"]; // On any other status code that's not 5xx mark the upload as failed if (!statusCode || statusCode < 500 || statusCode >= 600) { - core.setFailed('Upload failed (' + requestID + '): ' + await res.readBody()); + core.setFailed('Upload failed (' + requestID + '): (' + statusCode + ') ' + await res.readBody()); return; } // On a 5xx status code we may retry the request if (attempt < backoffPeriods.length) { // 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) + - ') failed (' + requestID + '). Retrying in ' + backoffPeriods[attempt] + ' seconds: ' + - await res.readBody()); + ') failed (' + requestID + '). Retrying in ' + backoffPeriods[attempt] + + ' seconds: (' + statusCode + ') ' + await res.readBody()); // Sleep for the backoff period await new Promise(r => setTimeout(r, backoffPeriods[attempt] * 1000)); continue; @@ -95,7 +95,7 @@ async function uploadPayload(payload) { // If the upload fails with 5xx then we assume it is a temporary problem // with turbo-scan and not an error that the user has caused or can fix. // We avoid marking the job as failed to avoid breaking CI workflows. - core.error('Upload failed (' + requestID + '): ' + await res.readBody()); + core.error('Upload failed (' + requestID + '): (' + statusCode + ') ' + await res.readBody()); return; } } diff --git a/src/upload-lib.ts b/src/upload-lib.ts index d404b2685..bcb6056dd 100644 --- a/src/upload-lib.ts +++ b/src/upload-lib.ts @@ -78,7 +78,7 @@ async function uploadPayload(payload) { // On any other status code that's not 5xx mark the upload as failed if (!statusCode || statusCode < 500 || statusCode >= 600) { - core.setFailed('Upload failed (' + requestID + '): ' + await res.readBody()); + core.setFailed('Upload failed (' + requestID + '): (' + statusCode + ') ' + await res.readBody()); return; } @@ -86,8 +86,8 @@ async function uploadPayload(payload) { if (attempt < backoffPeriods.length) { // 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) + - ') failed (' + requestID + '). Retrying in ' + backoffPeriods[attempt] + ' seconds: ' + - await res.readBody()); + ') failed (' + requestID + '). Retrying in ' + backoffPeriods[attempt] + + ' seconds: (' + statusCode + ') ' + await res.readBody()); // Sleep for the backoff period await new Promise(r => setTimeout(r, backoffPeriods[attempt] * 1000)); continue; @@ -96,7 +96,7 @@ async function uploadPayload(payload) { // If the upload fails with 5xx then we assume it is a temporary problem // with turbo-scan and not an error that the user has caused or can fix. // We avoid marking the job as failed to avoid breaking CI workflows. - core.error('Upload failed (' + requestID + '): ' + await res.readBody()); + core.error('Upload failed (' + requestID + '): (' + statusCode + ') ' + await res.readBody()); return; } } From 129ce28897c35736adad3bd570afe34aa4ba9e05 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 1 May 2020 11:59:44 +0100 Subject: [PATCH 15/15] Update upload-lib.ts --- lib/upload-lib.js | 2 +- src/upload-lib.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/upload-lib.js b/lib/upload-lib.js index e2dd3b32d..631e8fe39 100644 --- a/lib/upload-lib.js +++ b/lib/upload-lib.js @@ -93,7 +93,7 @@ async function uploadPayload(payload) { } else { // If the upload fails with 5xx then we assume it is a temporary problem - // with turbo-scan 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. core.error('Upload failed (' + requestID + '): (' + statusCode + ') ' + await res.readBody()); return; diff --git a/src/upload-lib.ts b/src/upload-lib.ts index bcb6056dd..2e8139991 100644 --- a/src/upload-lib.ts +++ b/src/upload-lib.ts @@ -94,7 +94,7 @@ async function uploadPayload(payload) { } else { // If the upload fails with 5xx then we assume it is a temporary problem - // with turbo-scan 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. core.error('Upload failed (' + requestID + '): (' + statusCode + ') ' + await res.readBody()); return;