Use API exposed by gerritchangequery-plugin
The gerritchangequery-plugin for Jenkins makes job runs working on
a given patchset queryable. This makes searching for jobs relevant
to the checks-API much more efficient.
[1] https://review.gerrithub.io/admin/repos/tdraebing/gerritchangequery-plugin,general
Change-Id: I96906668be85e998f9a2aedcd5e87531f393d966
diff --git a/src/main/java/com/google/gerrit/plugins/checks/jenkins/GetConfig.java b/src/main/java/com/google/gerrit/plugins/checks/jenkins/GetConfig.java
index 245b5de..c6d71a7 100644
--- a/src/main/java/com/google/gerrit/plugins/checks/jenkins/GetConfig.java
+++ b/src/main/java/com/google/gerrit/plugins/checks/jenkins/GetConfig.java
@@ -30,7 +30,6 @@
class GetConfig implements RestReadView<ProjectResource> {
private static final String JENKINS_SECTION = "jenkins";
private static final String JENKINS_URL_KEY = "url";
- private static final String JENKINS_JOB_KEY = "job";
private final PluginConfigFactory config;
private final String pluginName;
@@ -50,7 +49,6 @@
JenkinsChecksConfig jenkinsCfg = new JenkinsChecksConfig();
jenkinsCfg.name = instance;
jenkinsCfg.url = cfg.getString(JENKINS_SECTION, instance, JENKINS_URL_KEY);
- jenkinsCfg.jobs = cfg.getStringList(JENKINS_SECTION, instance, JENKINS_JOB_KEY);
result.add(jenkinsCfg);
}
return Response.ok(result);
@@ -59,9 +57,5 @@
static class JenkinsChecksConfig {
String name;
String url;
- //TODO(Thomas): It would be preferable to not have to configure any jobs, but
- // to let Jenkins know which Jobs worked on a PatchSet. This will require
- // additional changes in Jenkins however.
- String[] jobs;
}
}
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
index 6d4f006..5633218 100644
--- a/src/main/resources/Documentation/about.md
+++ b/src/main/resources/Documentation/about.md
@@ -1,23 +1,17 @@
Implementation of checks UI for Jenkins CI servers
+==================================================
This plugin registers a `ChecksProvider` with the Gerrit UI that will fetch
build results for a change from configured Jenkins servers and provide them to
the checks panel in a change screen.
-Limitations
+Requirements
-----------
-Currently, only multibranch-pipeline jobs using the Gerrit SCM-source provided
-by the link:https://plugins.jenkins.io/gerrit-code-review/[gerrit-code-review]-
-plugin are supported.
+The Jenkins servers have to support CORS-requests. This can for example be achieved
+by using a reverse proxy that sets the `Access-Control-Allow-Origin` and
+`Access-Control-Allow-Credentials` header.
-The Jenkins Remote Access API does not provide all the information that could
-be displayed in Gerrit, e.g. a result summary. Thus, as of now, this plugin
-does not make full use of the checks API. As of right now, it will display the
-following data in the UI:
-
-- Builds for the selected patchset including previous attempts
-- Status of the build
-- Result of the build
-- Link to the build and its logs
-- A result summary stating the result category used by the CI (e.g. `Result: UNSTABLE`)
+The Jenkins server additionally needs to install the
+[gerrit-change-query](https://review.gerrithub.io/admin/repos/tdraebing/gerritchangequery-plugin,general)
+plugin that enables querying for job runs working on a given patchset.
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index d87e2a0..50d9922 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -11,8 +11,3 @@
jenkins.NAME.url
: Base URL of Jenkins including protocol, e.g. https://gerrit-ci.gerritforge.com
-
-jenkins:NAME:job
-: Name of the multibranch pipeline job using the Gerrit SCM source that should
- be queried for a build for a patchset, e.g. Gerrit-verifier-pipeline. Can be
- defined multiple times.
diff --git a/web/fetcher.ts b/web/fetcher.ts
index 0eef435..a82eaca 100644
--- a/web/fetcher.ts
+++ b/web/fetcher.ts
@@ -15,12 +15,12 @@
* limitations under the License.
*/
import {
- Category,
+ Action,
+ ActionResult,
ChangeData,
CheckResult,
CheckRun,
ChecksProvider,
- LinkIcon,
ResponseCode,
RunStatus,
} from '@gerritcodereview/typescript-api/checks';
@@ -29,23 +29,35 @@
export declare interface Config {
name: string;
url: string;
- jobs: string[];
}
-export declare interface Job {
- exists: boolean;
- builds: Build[];
+export declare interface JenkinsCheckRun {
+ actions: JenkinsAction[];
+ attempt: number;
+ change: number;
+ checkDescription: string;
+ checkLink: string;
+ checkName: string;
+ externalId: string;
+ finishedTimestamp: string;
+ labelName: string;
+ patchset: number;
+ results: CheckResult[];
+ scheduledTimestamp: string;
+ startedTimestamp: string;
+ status: RunStatus;
+ statusDesciption: string;
+ statusLink: string;
}
-export declare interface Build {
- number: number;
- url: string;
-}
-
-export declare interface BuildDetail {
- number: number;
- building: boolean;
- result: string;
+export declare interface JenkinsAction {
+ data: string;
+ disabled: boolean;
+ method: string;
+ name: string;
+ primary: boolean;
+ summary: boolean;
+ tooltip: string;
url: string;
}
@@ -77,23 +89,17 @@
}
const checkRuns: CheckRun[] = [];
for (const jenkins of this.configs) {
- for (const jenkinsJob of jenkins.jobs) {
- // TODO: Requests to Jenkins should be proxied through the Gerrit backend
- // to avoid CORS requests.
- const job: Job = await this.fetchJobInfo(
- this.buildJobApiUrl(jenkins.url, jenkinsJob, changeData)
- );
-
- for (const build of job.builds) {
- checkRuns.push(
- this.convert(
- jenkinsJob,
- changeData,
- await this.fetchBuildInfo(this.buildBuildApiUrl(build.url))
- )
- );
- }
- }
+ // TODO: Requests to Jenkins should be proxied through the Gerrit backend
+ // to avoid CORS requests.
+ await this.fetchFromJenkins(
+ `${jenkins.url}/gerrit/check-runs?change=${changeData.changeNumber}&patchset=${changeData.patchsetNumber}`
+ )
+ .then(response => response.json())
+ .then(data => {
+ data.runs.forEach((run: JenkinsCheckRun) => {
+ checkRuns.push(this.convert(run));
+ });
+ });
}
return {
@@ -102,33 +108,6 @@
};
}
- buildJobApiUrl(
- jenkinsUrl: string,
- jenkinsJob: string,
- changeData: ChangeData
- ) {
- let changeShard: string = changeData.changeNumber.toString().slice(-2);
- if (changeShard.length === 1) {
- changeShard = '0' + changeShard;
- }
- return (
- jenkinsUrl +
- '/job/' +
- jenkinsJob +
- '/job/' +
- changeShard +
- '%252F' +
- changeData.changeNumber.toString() +
- '%252F' +
- changeData.patchsetNumber.toString() +
- '/api/json?tree=builds[number,url]'
- );
- }
-
- buildBuildApiUrl(baseUrl: string) {
- return baseUrl + 'api/json?tree=number,result,building,url';
- }
-
fetchConfig(changeData: ChangeData): Promise<Config[]> {
const pluginName = encodeURIComponent(this.plugin.getPluginName());
return this.plugin
@@ -138,86 +117,54 @@
);
}
- async fetchJobInfo(url: string): Promise<Job> {
- let response: Response;
- try {
- response = await this.fetchFromJenkins(url);
- if (!response.ok) {
- throw response.statusText;
- }
- } catch (e) {
- return {
- exists: false,
- builds: [],
- };
- }
- const job: Job = await response.json();
- return job;
- }
-
- async fetchBuildInfo(url: string): Promise<BuildDetail> {
- const response = await this.fetchFromJenkins(url);
- if (!response.ok) {
- throw response.statusText;
- }
- const build: BuildDetail = await response.json();
- return build;
- }
-
- convert(
- checkName: string,
- changeData: ChangeData,
- build: BuildDetail
- ): CheckRun {
- let status: RunStatus;
- const results: CheckResult[] = [];
-
- if (build.result !== null) {
- status = RunStatus.COMPLETED;
- let resultCategory: Category;
- switch (build.result) {
- case 'SUCCESS':
- resultCategory = Category.SUCCESS;
- break;
- case 'FAILURE':
- resultCategory = Category.ERROR;
- break;
- default:
- resultCategory = Category.WARNING;
- }
-
- const checkResult: CheckResult = {
- category: resultCategory,
- summary: `Result: ${build.result}`,
- links: [
- {
- url: build.url + '/console',
- primary: true,
- icon: LinkIcon.EXTERNAL,
- },
- ],
- };
- results.push(checkResult);
- } else if (build.building) {
- status = RunStatus.RUNNING;
- } else {
- status = RunStatus.RUNNABLE;
- }
-
- const run: CheckRun = {
- change: changeData.changeNumber,
- patchset: changeData.patchsetNumber,
- attempt: build.number,
- checkName,
- checkLink: build.url,
- status,
- results,
+ convert(run: JenkinsCheckRun): CheckRun {
+ const convertedRun: CheckRun = {
+ attempt: run.attempt,
+ change: run.change,
+ checkDescription: run.checkDescription,
+ checkLink: run.checkLink,
+ checkName: run.checkName,
+ externalId: run.externalId,
+ finishedTimestamp: new Date(run.finishedTimestamp),
+ labelName: run.labelName,
+ patchset: run.patchset,
+ results: run.results,
+ scheduledTimestamp: new Date(run.scheduledTimestamp),
+ startedTimestamp: new Date(run.startedTimestamp),
+ status: run.status,
+ statusDescription: run.statusDesciption,
+ statusLink: run.statusLink,
};
- return run;
+ const actions: Action[] = [];
+ for (const action of run.actions) {
+ actions.push({
+ name: action.name,
+ tooltip: action.tooltip,
+ primary: action.primary,
+ summary: action.summary,
+ disabled: action.disabled,
+ callback: () => this.rerun(action.url),
+ });
+ }
+ convertedRun.actions = actions;
+ return convertedRun;
}
private fetchFromJenkins(url: string): Promise<Response> {
- const options: RequestInit = { credentials: 'include' };
+ const options: RequestInit = {credentials: 'include'};
return fetch(url, options);
}
+
+ private rerun(url: string): Promise<ActionResult> {
+ return this.fetchFromJenkins(url)
+ .then(_ => {
+ return {
+ message: 'Run triggered.',
+ shouldReload: true,
+ };
+ })
+ .catch(e => {
+ return {message: `Triggering the run failed: ${e.message}`};
+ });
+ }
}