Skip to content

Commit 547c5a8

Browse files
committed
feat: add ncu-cu resume <prid> command
1 parent bbad9a0 commit 547c5a8

File tree

4 files changed

+129
-9
lines changed

4 files changed

+129
-9
lines changed

bin/ncu-ci.js

+61-5
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,26 @@ const args = yargs(hideBin(process.argv))
110110
},
111111
handler
112112
})
113+
.command({
114+
command: 'resume <prid>',
115+
desc: 'Resume CI for given PR',
116+
builder: (yargs) => {
117+
yargs
118+
.positional('prid', {
119+
describe: 'ID of the PR',
120+
type: 'number'
121+
})
122+
.option('owner', {
123+
default: '',
124+
describe: 'GitHub repository owner'
125+
})
126+
.option('repo', {
127+
default: '',
128+
describe: 'GitHub repository name'
129+
});
130+
},
131+
handler
132+
})
113133
.command({
114134
command: 'url <url>',
115135
desc: 'Automatically detect CI type and show results',
@@ -253,10 +273,8 @@ class RunPRJobCommand {
253273
return this.argv.prid;
254274
}
255275

256-
async start() {
257-
const {
258-
cli, request, prid, repo, owner
259-
} = this;
276+
validate() {
277+
const { cli, repo, owner } = this;
260278
let validArgs = true;
261279
if (!repo) {
262280
validArgs = false;
@@ -270,10 +288,44 @@ class RunPRJobCommand {
270288
}
271289
if (!validArgs) {
272290
this.cli.setExitCode(1);
291+
}
292+
return validArgs;
293+
}
294+
295+
async start() {
296+
const {
297+
cli, request, prid, repo, owner
298+
} = this;
299+
if (!this.validate()) {
273300
return;
274301
}
275302
const jobRunner = new RunPRJob(cli, request, owner, repo, prid);
276-
if (!jobRunner.start()) {
303+
if (!await jobRunner.start()) {
304+
this.cli.setExitCode(1);
305+
process.exitCode = 1;
306+
}
307+
}
308+
}
309+
310+
class ResumePRJobCommand extends RunPRJobCommand {
311+
async start() {
312+
const {
313+
cli, request, prid, repo, owner
314+
} = this;
315+
if (!this.validate()) {
316+
return;
317+
}
318+
// Parse CI links from PR.
319+
const parser = await JobParser.fromPRId(this, cli, request);
320+
const ciMap = parser.parse();
321+
322+
if (!ciMap.has(PR)) {
323+
cli.info(`No CI run detected from pull request ${prid}`);
324+
}
325+
326+
const { jobid } = ciMap.get(PR);
327+
const jobRunner = new RunPRJob(cli, request, owner, repo, prid, jobid);
328+
if (!await jobRunner.resume()) {
277329
this.cli.setExitCode(1);
278330
process.exitCode = 1;
279331
}
@@ -539,6 +591,10 @@ async function main(command, argv) {
539591
const jobRunner = new RunPRJobCommand(cli, request, argv);
540592
return jobRunner.start();
541593
}
594+
case 'resume': {
595+
const jobResumer = new ResumePRJobCommand(cli, request, argv);
596+
return jobResumer.start();
597+
}
542598
case 'rate': {
543599
commandHandler = new RateCommand(cli, request, argv);
544600
break;

lib/ci/ci_type_parser.js

+7
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,13 @@ JobParser.fromPR = async function(url, cli, request) {
189189
return new JobParser(thread);
190190
};
191191

192+
JobParser.fromPRId = async function({ owner, repo, prid }, cli, request) {
193+
const data = new PRData({ owner, repo, prid }, cli, request);
194+
await data.getThreadData();
195+
const thread = data.getThread();
196+
return new JobParser(thread);
197+
};
198+
192199
export const CI_TYPES_KEYS = {
193200
CITGM,
194201
CITGM_NOBUILD,

lib/ci/jenkins_constants.js

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const ACTION_TREE = 'actions[parameters[name,value]]';
44
const CHANGE_FIELDS = 'commitId,author[absoluteUrl,fullName],authorEmail,' +
55
'msg,date';
66
const CHANGE_TREE = `changeSet[items[${CHANGE_FIELDS}]]`;
7+
export const BASIC_TREE = 'result,url,number';
78
export const PR_TREE =
89
`result,url,number,${ACTION_TREE},${CHANGE_TREE},builtOn,` +
910
`subBuilds[${BUILD_FIELDS},build[subBuilds[${BUILD_FIELDS}]]]`;

lib/ci/run_ci.js

+60-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import FormData from 'form-data';
22

3+
import { BASIC_TREE } from './jenkins_constants.js';
4+
import { TestBuild } from './build-types/test_build.js';
35
import {
46
CI_DOMAIN,
57
CI_TYPES,
@@ -9,14 +11,16 @@ import {
911
export const CI_CRUMB_URL = `https://${CI_DOMAIN}/crumbIssuer/api/json`;
1012
const CI_PR_NAME = CI_TYPES.get(CI_TYPES_KEYS.PR).jobName;
1113
export const CI_PR_URL = `https://${CI_DOMAIN}/job/${CI_PR_NAME}/build`;
14+
export const CI_PR_RESUME_URL = `https://${CI_DOMAIN}/job/${CI_PR_NAME}/`;
1215

1316
export class RunPRJob {
14-
constructor(cli, request, owner, repo, prid) {
17+
constructor(cli, request, owner, repo, prid, jobid) {
1518
this.cli = cli;
1619
this.request = request;
1720
this.owner = owner;
1821
this.repo = repo;
1922
this.prid = prid;
23+
this.jobid = jobid;
2024
}
2125

2226
async getCrumb() {
@@ -43,18 +47,28 @@ export class RunPRJob {
4347
return payload;
4448
}
4549

46-
async start() {
50+
async #validateJenkinsCredentials() {
4751
const { cli } = this;
4852
cli.startSpinner('Validating Jenkins credentials');
4953
const crumb = await this.getCrumb();
5054

5155
if (crumb === false) {
5256
cli.stopSpinner('Jenkins credentials invalid',
5357
this.cli.SPINNER_STATUS.FAILED);
54-
return false;
58+
return { crumb, success: false };
5559
}
5660
cli.stopSpinner('Jenkins credentials valid');
5761

62+
return { crumb, success: true };
63+
}
64+
65+
async start() {
66+
const { cli } = this;
67+
const { crumb, success } = await this.#validateJenkinsCredentials();
68+
if (success === false) {
69+
return false;
70+
}
71+
5872
try {
5973
cli.startSpinner('Starting PR CI job');
6074
const response = await this.request.fetch(CI_PR_URL, {
@@ -64,7 +78,7 @@ export class RunPRJob {
6478
},
6579
body: this.payload
6680
});
67-
if (response.status !== 201) {
81+
if (response.status !== 200) {
6882
cli.stopSpinner(
6983
`Failed to start PR CI: ${response.status} ${response.statusText}`,
7084
this.cli.SPINNER_STATUS.FAILED);
@@ -77,4 +91,46 @@ export class RunPRJob {
7791
}
7892
return true;
7993
}
94+
95+
async resume() {
96+
const { cli, request, jobid } = this;
97+
const { crumb, success } = await this.#validateJenkinsCredentials();
98+
if (success === false) {
99+
return false;
100+
}
101+
102+
try {
103+
cli.startSpinner('Resuming PR CI job');
104+
const path = `job/${CI_PR_NAME}/${jobid}/`;
105+
const testBuild = new TestBuild(cli, request, path, BASIC_TREE);
106+
const { result } = await testBuild.getBuildData();
107+
108+
if (result !== 'FAILURE') {
109+
cli.stopSpinner(
110+
`CI Job is in status ${result ?? 'RUNNING'}, skipping resume`,
111+
this.cli.SPINNER_STATUS.FAILED);
112+
return false;
113+
}
114+
115+
const resume_url = `${CI_PR_RESUME_URL}${jobid}/resume`;
116+
const response = await this.request.fetch(resume_url, {
117+
method: 'POST',
118+
headers: {
119+
'Jenkins-Crumb': crumb
120+
}
121+
});
122+
if (response.status !== 201) {
123+
cli.stopSpinner(
124+
`Failed to resume PR CI: ${response.status} ${response.statusText}`,
125+
this.cli.SPINNER_STATUS.FAILED);
126+
return false;
127+
}
128+
129+
cli.stopSpinner('PR CI job successfully resumed');
130+
} catch (err) {
131+
cli.stopSpinner('Failed to resume CI', this.cli.SPINNER_STATUS.FAILED);
132+
return false;
133+
}
134+
return true;
135+
}
80136
}

0 commit comments

Comments
 (0)