diff --git a/lib/github/index.js b/lib/github/index.js index 4485f1c..99f760d 100644 --- a/lib/github/index.js +++ b/lib/github/index.js @@ -7,6 +7,7 @@ const chalk = require('chalk') const getBatchIssue = require('./batch') const { conf } = require('../config') const { getProjectName, getGraph } = require('./utils') +const { dressError } = require('../utils') const { getLabels, ensureLabelsAreCreated } = require('./labels') @@ -22,19 +23,27 @@ module.exports = { // retrieve issue IDs already created in GitHub async existingIssues () { - return await octokit.paginate( - `GET /search/issues?q=repo%3A${conf.ghOwner}/${conf.ghRepo}+is%3Aissue+label%3Asnyk`, - (response) => - response.data.map((existingIssue) => [ - existingIssue.title, - existingIssue.number - ]) - ) + try { + return await octokit.paginate( + `GET /search/issues?q=repo%3A${conf.ghOwner}/${conf.ghRepo}+is%3Aissue+label%3Asnyk`, + (response) => + response.data.map((existingIssue) => [ + existingIssue.title, + existingIssue.number + ]) + ) + } catch (err) { + throw new Error(dressError(err, `Failed to paginate octokit request for existing issues in repository ${conf.ghRepo}`)) + } }, async createIssue (options) { if (conf.dryRun) return - return await this.client.issues.create(options) + try { + return await this.client.issues.create(options) + } catch (err) { + throw new Error(dressError(err, `Failed to ${options.issue_number ? 'update' : 'create'} issue: ${options.issue_number ? options.issue_number : options.title}`)) + } }, async createIssues (issues, existingIssues) { diff --git a/lib/github/labels.js b/lib/github/labels.js index dae2aec..77a61fa 100644 --- a/lib/github/labels.js +++ b/lib/github/labels.js @@ -1,7 +1,7 @@ 'use strict' const { conf } = require('../config') -const { uniq } = require('../utils') +const { uniq, dressError } = require('../utils') const LABELS = { snyk: { @@ -50,14 +50,14 @@ const getLabelAttributes = (name) => { const ensureLabelsAreCreated = async (octokit, client, ghOwner, ghRepo, issues) => { const labels = getLabels(issues) - const responseData = await octokit.paginate( - await client.issues.listLabelsForRepo({ - owner: ghOwner, - repo: ghRepo, - per_page: 100 - }), - (response) => response.data - ) + + let responseData + try { + responseData = await octokit.paginate(`GET /repos/${conf.ghOwner}/${conf.ghRepo}/labels?per_page=100`) + } catch (err) { + throw new Error(dressError(err, `Failed to paginate octokit request for labels in repository: ${ghRepo}`)) + } + const currentLabels = responseData.map((x) => x.name) const labelsToCreate = labels.filter((x) => !currentLabels.includes(x)) if (!labelsToCreate.length || conf.dryRun) { @@ -65,17 +65,21 @@ const ensureLabelsAreCreated = async (octokit, client, ghOwner, ghRepo, issues) } await Promise.all( - labelsToCreate.map((name) => - client.issues - .createLabel({ - owner: ghOwner, - repo: ghRepo, - ...getLabelAttributes(name) - }) - .then(() => { - console.log(`Created GitHub label: "${name}"`) - }) - ) + labelsToCreate.map((name) => { + try { + return client.issues + .createLabel({ + owner: ghOwner, + repo: ghRepo, + ...getLabelAttributes(name) + }) + .then(() => { + console.log(`Created GitHub label: "${name}"`) + }) + } catch (err) { + throw new Error(dressError(err, `Failed to create GitHub label '${name}' in repository: ${ghRepo}`)) + } + }) ) } diff --git a/lib/snyk.js b/lib/snyk.js index e31aa9a..685f8f1 100644 --- a/lib/snyk.js +++ b/lib/snyk.js @@ -1,9 +1,10 @@ 'use strict' const request = require('request-promise-native') +const { dressError } = require('./utils') const baseV1Url = 'https://snyk.io/api/v1' -const baseRestUrl = 'https://api.snyk.io' +const baseRestUrl = 'https://api.snyk.io/rest' module.exports = class Snyk { constructor ({ token, orgId, minimumSeverity }) { @@ -29,25 +30,47 @@ module.exports = class Snyk { ).orgs } + async queryProjectDetails (organizationId, projectId) { + try { + return await request({ + method: 'get', + url: `${baseV1Url}/org/${organizationId}/project/${projectId}`, + headers: this._headers, + json: true + }) + } catch (err) { + throw new Error(dressError(err, `Failed to query snyk project details. Organization ID: ${organizationId}, Project ID: ${projectId}`)) + } + } + async projects (orgId, selectedProjects = []) { const organizationId = orgId || this._orgId const responseData = await paginateRestResponseData( - `${baseRestUrl}/rest/orgs/${organizationId}/projects?version=2023-11-27&meta.latest_issue_counts=true&limit=20`, + `${baseRestUrl}/orgs/${organizationId}/projects?version=2023-11-27&meta.latest_issue_counts=true&limit=20`, this._headers ) - return responseData.map((project) => { - const { critical, high, medium, low } = project.meta.latest_issue_counts - const issueCountTotal = critical + high + medium + low - return { - id: project.id, - name: project.attributes.name, - isMonitored: - project.attributes.status === 'active', - issueCountTotal - } - }).filter(({ id, isMonitored, issueCountTotal }) => { + const projects = await Promise.all( + responseData.map(async (project) => { + const { critical, high, medium, low } = project.meta.latest_issue_counts + const issueCountTotal = critical + high + medium + low + + const projectDetails = await this.queryProjectDetails(organizationId, project.id) + + return { + id: project.id, + name: project.attributes.name, + isMonitored: + project.attributes.status === 'active', + issueCountTotal, + browseUrl: projectDetails.browseUrl, + imageTag: projectDetails.imageTag + } + }) + ) + + return projects.filter(({ id, isMonitored }) => { if (selectedProjects.includes(id)) { return true } @@ -99,18 +122,22 @@ function getSeverities (minimumSeverity) { } async function paginateRestResponseData (url, headers, method = 'get') { - const reponseData = [] - do { - const response = await request({ - method, - url, - headers, - json: true - }) - reponseData.push(...response.data) - if (response.links.next) url = baseRestUrl + response.links.next - else url = undefined - } while (url) + try { + const reponseData = [] + do { + const response = await request({ + method, + url, + headers, + json: true + }) + reponseData.push(...response.data) + if (response.links.next) url = baseRestUrl + response.links.next.trimStart('/rest') + else url = undefined + } while (url) - return reponseData + return reponseData + } catch (err) { + throw new Error(dressError(err, `Failed to paginate request for ${method} ${url}`)) + } } diff --git a/lib/utils.js b/lib/utils.js index c245224..fdb2c10 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -73,6 +73,10 @@ const capitalize = (s) => { const uniq = (array) => [...new Set(array)] +const dressError = (err, msg) => { + return `${msg}, error: ${err.status ? err.status + ' - ' : ''}${err.message}, response: ${err.response}` +} + module.exports = { capitalize, compare: { @@ -82,5 +86,6 @@ module.exports = { versionArrays: compareVersionArrays, arrays: compareArrays }, - uniq + uniq, + dressError }