From 6957918e1dcd78c6cee890f6efd7b391add2c70a Mon Sep 17 00:00:00 2001 From: Moshe Atlow Date: Tue, 19 Jul 2022 16:59:04 +0300 Subject: [PATCH] Add `ncu-cu resume ` command --- bin/ncu-ci.js | 66 ++++++++++++++++++++++++++++++++++--- lib/ci/ci_type_parser.js | 7 ++++ lib/ci/jenkins_constants.js | 1 + lib/ci/run_ci.js | 62 ++++++++++++++++++++++++++++++++-- 4 files changed, 128 insertions(+), 8 deletions(-) diff --git a/bin/ncu-ci.js b/bin/ncu-ci.js index 5e865927..77cadc9d 100755 --- a/bin/ncu-ci.js +++ b/bin/ncu-ci.js @@ -110,6 +110,26 @@ const args = yargs(hideBin(process.argv)) }, handler }) + .command({ + command: 'resume ', + desc: 'Resume CI for given PR', + builder: (yargs) => { + yargs + .positional('prid', { + describe: 'ID of the PR', + type: 'number' + }) + .option('owner', { + default: '', + describe: 'GitHub repository owner' + }) + .option('repo', { + default: '', + describe: 'GitHub repository name' + }); + }, + handler + }) .command({ command: 'url ', desc: 'Automatically detect CI type and show results', @@ -253,10 +273,8 @@ class RunPRJobCommand { return this.argv.prid; } - async start() { - const { - cli, request, prid, repo, owner - } = this; + validate() { + const { cli, repo, owner } = this; let validArgs = true; if (!repo) { validArgs = false; @@ -270,10 +288,44 @@ class RunPRJobCommand { } if (!validArgs) { this.cli.setExitCode(1); + } + return validArgs; + } + + async start() { + const { + cli, request, prid, repo, owner + } = this; + if (!this.validate()) { return; } const jobRunner = new RunPRJob(cli, request, owner, repo, prid); - if (!jobRunner.start()) { + if (!await jobRunner.start()) { + this.cli.setExitCode(1); + process.exitCode = 1; + } + } +} + +class ResumePRJobCommand extends RunPRJobCommand { + async start() { + const { + cli, request, prid, repo, owner + } = this; + if (!this.validate()) { + return; + } + // Parse CI links from PR. + const parser = await JobParser.fromPRId(this, cli, request); + const ciMap = parser.parse(); + + if (!ciMap.has(PR)) { + cli.info(`No CI run detected from pull request ${prid}`); + } + + const { jobid } = ciMap.get(PR); + const jobRunner = new RunPRJob(cli, request, owner, repo, prid, jobid); + if (!await jobRunner.resume()) { this.cli.setExitCode(1); process.exitCode = 1; } @@ -539,6 +591,10 @@ async function main(command, argv) { const jobRunner = new RunPRJobCommand(cli, request, argv); return jobRunner.start(); } + case 'resume': { + const jobResumer = new ResumePRJobCommand(cli, request, argv); + return jobResumer.start(); + } case 'rate': { commandHandler = new RateCommand(cli, request, argv); break; diff --git a/lib/ci/ci_type_parser.js b/lib/ci/ci_type_parser.js index 9850fa9b..742c29cb 100644 --- a/lib/ci/ci_type_parser.js +++ b/lib/ci/ci_type_parser.js @@ -189,6 +189,13 @@ JobParser.fromPR = async function(url, cli, request) { return new JobParser(thread); }; +JobParser.fromPRId = async function({ owner, repo, prid }, cli, request) { + const data = new PRData({ owner, repo, prid }, cli, request); + await data.getThreadData(); + const thread = data.getThread(); + return new JobParser(thread); +}; + export const CI_TYPES_KEYS = { CITGM, CITGM_NOBUILD, diff --git a/lib/ci/jenkins_constants.js b/lib/ci/jenkins_constants.js index ca896d15..c3002d7a 100644 --- a/lib/ci/jenkins_constants.js +++ b/lib/ci/jenkins_constants.js @@ -4,6 +4,7 @@ const ACTION_TREE = 'actions[parameters[name,value]]'; const CHANGE_FIELDS = 'commitId,author[absoluteUrl,fullName],authorEmail,' + 'msg,date'; const CHANGE_TREE = `changeSet[items[${CHANGE_FIELDS}]]`; +export const BASIC_TREE = 'result,url,number'; export const PR_TREE = `result,url,number,${ACTION_TREE},${CHANGE_TREE},builtOn,` + `subBuilds[${BUILD_FIELDS},build[subBuilds[${BUILD_FIELDS}]]]`; diff --git a/lib/ci/run_ci.js b/lib/ci/run_ci.js index beead3dc..4929d5d2 100644 --- a/lib/ci/run_ci.js +++ b/lib/ci/run_ci.js @@ -1,5 +1,7 @@ import FormData from 'form-data'; +import { BASIC_TREE } from './jenkins_constants.js'; +import { TestBuild } from './build-types/test_build.js'; import { CI_DOMAIN, CI_TYPES, @@ -9,14 +11,16 @@ import { export const CI_CRUMB_URL = `https://${CI_DOMAIN}/crumbIssuer/api/json`; const CI_PR_NAME = CI_TYPES.get(CI_TYPES_KEYS.PR).jobName; export const CI_PR_URL = `https://${CI_DOMAIN}/job/${CI_PR_NAME}/build`; +export const CI_PR_RESUME_URL = `https://${CI_DOMAIN}/job/${CI_PR_NAME}/`; export class RunPRJob { - constructor(cli, request, owner, repo, prid) { + constructor(cli, request, owner, repo, prid, jobid) { this.cli = cli; this.request = request; this.owner = owner; this.repo = repo; this.prid = prid; + this.jobid = jobid; } async getCrumb() { @@ -43,7 +47,7 @@ export class RunPRJob { return payload; } - async start() { + async #validateJenkinsCredentials() { const { cli } = this; cli.startSpinner('Validating Jenkins credentials'); const crumb = await this.getCrumb(); @@ -51,10 +55,20 @@ export class RunPRJob { if (crumb === false) { cli.stopSpinner('Jenkins credentials invalid', this.cli.SPINNER_STATUS.FAILED); - return false; + return { crumb, success: false }; } cli.stopSpinner('Jenkins credentials valid'); + return { crumb, success: true }; + } + + async start() { + const { cli } = this; + const { crumb, success } = await this.#validateJenkinsCredentials(); + if (success === false) { + return false; + } + try { cli.startSpinner('Starting PR CI job'); const response = await this.request.fetch(CI_PR_URL, { @@ -77,4 +91,46 @@ export class RunPRJob { } return true; } + + async resume() { + const { cli, request, jobid } = this; + const { crumb, success } = await this.#validateJenkinsCredentials(); + if (success === false) { + return false; + } + + try { + cli.startSpinner('Resuming PR CI job'); + const path = `job/${CI_PR_NAME}/${jobid}/`; + const testBuild = new TestBuild(cli, request, path, BASIC_TREE); + const { result } = await testBuild.getBuildData(); + + if (result !== 'FAILURE') { + cli.stopSpinner( + `CI Job is in status ${result ?? 'RUNNING'}, skipping resume`, + this.cli.SPINNER_STATUS.FAILED); + return false; + } + + const resume_url = `${CI_PR_RESUME_URL}${jobid}/resume`; + const response = await this.request.fetch(resume_url, { + method: 'POST', + headers: { + 'Jenkins-Crumb': crumb + } + }); + if (response.status !== 201) { + cli.stopSpinner( + `Failed to resume PR CI: ${response.status} ${response.statusText}`, + this.cli.SPINNER_STATUS.FAILED); + return false; + } + + cli.stopSpinner('PR CI job successfully resumed'); + } catch (err) { + cli.stopSpinner('Failed to resume CI', this.cli.SPINNER_STATUS.FAILED); + return false; + } + return true; + } }