diff options
Diffstat (limited to 'ops/gerrit-tvl')
-rw-r--r-- | ops/gerrit-tvl/HttpModule.java | 14 | ||||
-rw-r--r-- | ops/gerrit-tvl/MANIFEST.MF | 2 | ||||
-rw-r--r-- | ops/gerrit-tvl/README.md | 6 | ||||
-rw-r--r-- | ops/gerrit-tvl/default.nix | 33 | ||||
-rw-r--r-- | ops/gerrit-tvl/static/tvl.js | 191 |
5 files changed, 246 insertions, 0 deletions
diff --git a/ops/gerrit-tvl/HttpModule.java b/ops/gerrit-tvl/HttpModule.java new file mode 100644 index 000000000000..6d785c0817fc --- /dev/null +++ b/ops/gerrit-tvl/HttpModule.java @@ -0,0 +1,14 @@ +package su.tvl.gerrit; + +import com.google.gerrit.extensions.registration.DynamicSet; +import com.google.gerrit.extensions.webui.JavaScriptPlugin; +import com.google.gerrit.extensions.webui.WebUiPlugin; +import com.google.inject.servlet.ServletModule; + +public final class HttpModule extends ServletModule { + + @Override + protected void configureServlets() { + DynamicSet.bind(binder(), WebUiPlugin.class).toInstance(new JavaScriptPlugin("tvl.js")); + } +} diff --git a/ops/gerrit-tvl/MANIFEST.MF b/ops/gerrit-tvl/MANIFEST.MF new file mode 100644 index 000000000000..bfe4eedeb6ac --- /dev/null +++ b/ops/gerrit-tvl/MANIFEST.MF @@ -0,0 +1,2 @@ +Gerrit-HttpModule: su.tvl.gerrit.HttpModule +Gerrit-PluginName: tvl diff --git a/ops/gerrit-tvl/README.md b/ops/gerrit-tvl/README.md new file mode 100644 index 000000000000..1b88600f197f --- /dev/null +++ b/ops/gerrit-tvl/README.md @@ -0,0 +1,6 @@ +# gerrit-tvl + +A Gerrit plugin that does TVL-specific things. + +You probably want to take inspiration from this rather than using it directly, +as it has a variety of TVL-ish assumptions baked into it. diff --git a/ops/gerrit-tvl/default.nix b/ops/gerrit-tvl/default.nix new file mode 100644 index 000000000000..f3bec7a3a242 --- /dev/null +++ b/ops/gerrit-tvl/default.nix @@ -0,0 +1,33 @@ +{ depot, pkgs, lib, ... }: + +let + classPath = lib.concatStringsSep ":" [ + "${depot.third_party.gerrit}/share/api/extension-api_deploy.jar" + ]; +in +pkgs.stdenvNoCC.mkDerivation rec { + name = "${pname}-${version}.jar"; + pname = "gerrit-tvl"; + version = "0.0.1"; + + src = ./.; + + nativeBuildInputs = with pkgs; [ + jdk + ]; + + buildPhase = '' + mkdir $NIX_BUILD_TOP/build + + # Build Java components. + export JAVAC="javac -cp ${classPath} -d $NIX_BUILD_TOP/build --release 11" + $JAVAC ./HttpModule.java + + # Install static files. + cp -R static $NIX_BUILD_TOP/build/static + ''; + + installPhase = '' + jar --create --file $out --manifest $src/MANIFEST.MF -C $NIX_BUILD_TOP/build . + ''; +} diff --git a/ops/gerrit-tvl/static/tvl.js b/ops/gerrit-tvl/static/tvl.js new file mode 100644 index 000000000000..2c4d7ee4739c --- /dev/null +++ b/ops/gerrit-tvl/static/tvl.js @@ -0,0 +1,191 @@ +// vim: set noai ts=2 sw=2 et: */ + +// This is a read-only Buildkite token: it was generated by lukegb@, and has +// read_builds, read_build_logs, and read_pipelines permissions. +const BUILDKITE_TOKEN = 'a150658fb61062e432f13a032962d70fa9352088'; + +function encodeParams(p) { + const pieces = []; + for (let k of Object.getOwnPropertyNames(p)) { + pieces.push(`${encodeURIComponent(k)}=${encodeURIComponent(p[k])}`); + } + return pieces.join('&'); +} + +function formatDuration(from, to) { + const millisecondsTook = Math.floor(to.valueOf() - from.valueOf()); + if (millisecondsTook < 2000) return `${millisecondsTook} ms`; + const secondsTook = Math.floor(millisecondsTook / 1000); + if (secondsTook < 100) return `${secondsTook} seconds`; + const minutesTook = Math.floor(secondsTook / 60); + if (minutesTook < 60) return `${minutesTook} minutes`; + const hoursTook = Math.floor(minutesTook / 60); + const minutesRemainder = minutesTook - (hoursTook * 60); + return `${hoursTook}hr ${minutesRemainder}min`; +} + +// Maps the status of a Buildkite *job* to the statuses available for +// a Gerrit check. +// +// Note that jobs can have statuses that, according to the Buildkite +// documentation, are only available for builds, and maybe vice-versa. +// To deal with this we simply cover all statuses for all types here. +// +// Buildkite job statuses: https://buildkite.com/docs/pipelines/notifications#job-states +// +// Gerrit check statuses: https://gerrit.googlesource.com/gerrit/+/v3.4.0/polygerrit-ui/app/api/checks.ts#167 +// +// TODO(tazjin): Use SCHEDULED status once we have upgraded Gerrit +// past 3.4 +function jobStateToCheckRunStatus(state) { + const status = { + // Statuses documented for both types + 'blocked': 'RUNNABLE', + 'canceled': 'COMPLETED', + 'canceling': 'RUNNING', + 'running': 'RUNNING', + 'scheduled': 'RUNNABLE', + 'skipped': 'COMPLETED', + + // Statuses only documented for builds + 'creating': 'RUNNABLE', + 'failed': 'COMPLETED', + 'not_run': 'COMPLETED', + 'passed': 'COMPLETED', + + // Statuses only documented for jobs + 'accepted': 'RUNNABLE', + 'assigned': 'RUNNABLE', + 'blocked_failed': 'COMPLETED', + 'broken': 'COMPLETED', + 'finished': 'COMPLETED', + 'limited': 'RUNNABLE', + 'limiting': 'RUNNABLE', + 'pending': 'RUNNABLE', + 'timed_out': 'COMPLETED', + 'timing_out': 'RUNNING', + 'unblocked': 'RUNNABLE', + 'unblocked_failed': 'COMPLETED', + 'waiting': 'RUNNABLE', + 'waiting_failed': 'COMPLETED', + }[state]; + + if (!status) { + console.log(`unknown Buildkite job state: ${state}`); + } + + return status; +} + +const tvlChecksProvider = { + async fetch(change) { + let {changeNumber, patchsetNumber, repo} = change; + + const experiments = window.ENABLED_EXPERIMENTS || []; + if (experiments.includes("UiFeature__tvl_check_debug")) { + changeNumber = 2872; + patchsetNumber = 4; + repo = 'depot'; + } + + if (repo !== 'depot') { + // We only handle TVL's depot at the moment. + return {responseCode: 'OK'}; + } + + const params = { + // besadii groups different patchsets of the same CL under this fake ref + branch: `cl/${changeNumber.toString()}`, + }; + const url = `https://api.buildkite.com/v2/organizations/tvl/pipelines/depot/builds?${encodeParams(params)}`; + const resp = await fetch(url, { + headers: { + Authorization: `Bearer ${BUILDKITE_TOKEN}`, + }, + }); + const respJSON = await resp.json(); + + const runs = []; + for (let i = 0; i < respJSON.length; i++) { + const attempt = respJSON.length - i; + const build = respJSON[i]; + + for (let job of build.jobs) { + // Skip non-command jobs (e.g. waiting/grouping jobs) + if (job.type !== 'script') { + continue; + } + + // Skip jobs marked as 'broken' (this means they were skipped + // intentionally) + if (job.state === 'broken') { + continue; + } + + // TODO(lukegb): add the ability to retry these + const checkRun = { + patchset: parseInt(build.env.GERRIT_PATCHSET, 10), + attempt: attempt, + externalId: job.id, + checkName: job.name, + checkDescription: job.command, + checkLink: job.web_url, + status: jobStateToCheckRunStatus(job.state), + labelName: 'Verified', + }; + + if (job.scheduled_at) { + checkRun.scheduledTimestamp = new Date(job.scheduled_at); + } + + if (job.started_at) { + checkRun.startedTimestamp = new Date(job.started_at); + } + + if (job.finished_at) { + checkRun.finishedTimestamp = new Date(job.finished_at); + } + + let statusDescription = job.state; + if (checkRun.startedTimestamp && checkRun.finishedTimestamp) { + statusDescription = `${statusDescription} in ${formatDuration(checkRun.startedTimestamp, checkRun.finishedTimestamp)}`; + } else if (checkRun.startedTimestamp) { + statusDescription = `${statusDescription} for ${formatDuration(checkRun.startedTimestamp, new Date())}`; + } else if (checkRun.scheduledTimestamp) { + statusDescription = `${statusDescription} for ${formatDuration(checkRun.scheduledTimestamp, new Date())}`; + } + checkRun.statusDescription = statusDescription; + + if (['failed', 'timed_out'].includes(job.state)) { + const result = { + // TODO(lukegb): get the log as the message here (the Gerrit + // implementation doesn't yet seem to support newlines in message + // strings...) + links: [{ + url: job.web_url, + tooltip: "Buildkite", + primary: true, + icon: 'EXTERNAL', + }], + category: 'ERROR', + summary: `${job.command} failed`, + }; + checkRun.results = [result]; + } + + runs.push(checkRun); + } + } + + return { + responseCode: 'OK', + runs: runs, + }; + }, +}; + +Gerrit.install(plugin => { + console.log('TVL plugin initialising'); + + plugin.checks().register(tvlChecksProvider); +}); |