Arriving from Buildkite

On this page 3

A translation rather than a rewrite. Their format is public and most of it has a word here already - this product's job extensions were designed against that vocabulary - so the importer is mostly a rename, and what matters is the part that is not.

buddy import:buildkite .buildkite/pipeline.yml --out .reviewos/workflows/ci.yml

The workflow goes to the file; the report goes to the terminal, and it is the deliverable. A silent partial translation is worse than a refusal: a workflow with three attributes quietly dropped is one somebody trusts, pushes, and then debugs from the wrong end. Every attribute lands in one of three buckets - translated, translated with a change in meaning, or no equivalent - and the third is never silent.

The mapping

BuildkiteHereFidelity
labelthe job's namesame
keythe job idsame
command, commandssteps[].runsame; a list becomes one run: block, in order
depends_onneedssame
allow_dependency_failureif: always()changed: the job runs after a failed dependency rather than being unblocked by it
ififsame expression language
branchesreviewos.branchessame
skipreviewos.skipsame
soft_failcontinue-on-errorchanged: a list of tolerated exit statuses becomes a boolean, which tolerates every failure rather than the ones named
timeout_in_minutestimeout-minutessame
parallelismreviewos.parallelismsame
matrixstrategy.matrixsame; adjustments become reviewos.adjustments
agentsruns-on plus reviewos.agentssame: a queue becomes a label, the rest becomes a tag query
artifact_pathsreviewos.artifact-pathssame
envenvsame
retryreviewos.retrychanged: automatic retries carry a cap here, and a manual retry is a person pressing re-run
concurrency, concurrency_groupreviewos.concurrency, reviewos.concurrency-groupsame
cancel_on_build_failingreviewos.cancel-on-build-failingsame
notifyreviewos.notifychanged: a rule names somebody on this instance rather than an address or a channel
priorityreviewos.prioritysame
waitreviewos.waitsame: a barrier is a job here, and everything after it depends on it
blockreviewos.blocksame: a gate a person opens, with the run held at waiting
triggerreviewos.triggerchanged: it starts a run of another workflow on this instance
groupreviewos.groupchanged: the group's steps are flattened into jobs carrying its name
plugins-no equivalent, deliberately: see below
signature-no equivalent: this instance signs dispatched work itself

Steps between two barriers stay parallel. Chaining each step to the one before it would serialise a pipeline that was not, which is a translation slower than the original - and reads as this product being slow.

Plugins: the position

Their plugins do not run here, and this is a decision rather than a gap.

Buildkite's plugin interface is hook scripts plus a parameter schema, and this product's is hook scripts plus a manifest: close enough that compatibility was worth deciding rather than assuming. The decision is no, for one reason - BUILDKITE_PLUGIN_* environment variables and their agent's lifecycle are an interface, and implementing half of it produces plugins that mostly work. A plugin that mostly works is worse than one that does not: it fails on the day its author used the half nobody implemented, in somebody else's build, with no error that names the cause.

What to do with each is a short list:

  • A plugin that runs a command - docker-login, ecr, artifacts - is a step here. Usually two lines, and readable by whoever inherits it.
  • A plugin that wraps the whole job - a profiler, a proxy - is a runner hook, which is the fleet's mechanism for exactly that and is not editable by a repository.
  • A plugin your organisation wrote is a plugin here, which is hook scripts and a manifest: the port is mechanical.

The importer names every plugin it found rather than dropping it, so the list above is a list of decisions somebody makes with the pipeline in front of them.

The test history

The flaky verdict is the part a move cannot recreate: it took months of runs to accumulate, and starting empty starts by forgetting which tests to distrust. Buildkite Test Analytics exports one JSON object per execution, which imports through the ordinary ingestion endpoint:

curl -sX POST "$SERVER/api/repos/tests/ingest" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d @executions.json

Two things are converted rather than copied. Duration is seconds there and milliseconds here, which is the one unit that would be silently wrong - a suite of four-second tests imported as four-millisecond ones looks like a suite that got faster. And an execution whose result is unknown is dropped rather than counted as a pass: importing it as one is how a green history gets invented.