Deployments
A declared step you invoke against what a build produced.
A deployment is a step you declare in build code and invoke on demand. It names a function and the options that function takes.
scope.addDeployment("staging", "deployment/local.shell/v0.0.0", {
commands: ["./deploy.sh"],
});Running one
bldr build --deploy site/staging --name "deploy staging" # after a build
bldr deploy <CID> # on its own
bldr deployments # past runs
bldr deployments get <ID>Either way you get the deployment's step tree — live while it runs, and a record you can read afterwards.
The step model
A deployment is a step function. Before anything runs, the daemon publishes
the plan: every block of a composite (sequence, parallel,
try/catch/finally, dag) and every leaf action (one function
call) as a node of the deployment's tree, labelled in human terms —
push registry.example.com/app:1.2.0, apply 2 resources → bldr-test,
wait job bldr-test/suite-…. Each step then moves through
pending → running → succeeded | failed | skippedwith its start and end time, its duration and, once finished, its output
(the function's result block) or its error. A step that the plan decided not
to run — a sequence step after a failure, a catch whose try succeeded —
ends skipped, with the reason; a step abandoned when its block timed out
ends cancelled. A block's status is derived from its children, and a
block's only error text is a reference to the step that failed: the tree
shows where.
Steps report progress as a fraction with a unit and a sentence — an image
push says 3 of 5 layers, 41.2 of 68.3 MiB, a Job wait 142 s of 3600 s
against its deadline — and the whole deployment's bar is derived from its
actions, weighted equally. Every block takes a timeoutSecs; so do
k8s.apply and k8s.job.wait. A step with a timeout carries its deadline,
and the bar is drawn against it.
Actions report what a person wants to inspect:
oci.image.push— one sub-step per layer (digest, size, pushed or already present), and as output the reference, the manifest digest, the layer count and the bytes pushed and skipped.k8s.apply— one sub-step per resource (Kind/namespace/name, whether it was created, configured or unchanged, and the server'sresourceVersion), with the rendered YAML it applied stored as a blob the console opens.k8s.job.wait— the Job's conditions as they change, the pod's phase, and the followed container log published on the step as soon as the follow starts, sobldr logs follow <stream>and the console show the lines while the Job runs.
Reading it
In the console, a deployment's page is its step tree: a header with the overall status, one progress bar and the elapsed time; then the blocks and actions, each row with its status, label, duration and — while it runs — a slim bar. Blocks fold; parallel branches read as parallel; a try block labels its phases. Clicking a step opens the inspector, a side panel with an overview (status, times, function, the options it was given), the step's output (its result, the per-layer table of a push, the per-resource list and YAML of an apply, the conditions of a wait) and its log. The tree shows labels, status and durations; everything else is one click away.
On the command line, bldr deployments get <ID> prints the same tree once,
and bldr deploy / bldr deployments watch render it live. A build that ran
a deployment (bldr build --deploy <filter>) links to it from its deploy
step, and the deployment links back to the build.
Composite deployments
deployment/composite/v0.0.0 runs other deployments as one deployment. Its
options are one block, and a block's steps are child deployments
({ function, options }) or further blocks written inline — so the three
forms nest, and each block is a deployment with its own node in the one task
tree. The helpers on Deployment spell the blocks:
import { Deployment } from "bldr";
const shell = (cmd: string) => ({
function: "deployment/local.shell/v0.0.0",
options: { commands: [cmd] },
});
scope.addDeployment(
"release",
Deployment.COMPOSITE_FUNCTION,
Deployment.tryCatch({
try: Deployment.sequence([
shell("./stage.sh"),
Deployment.parallel([shell("./push-a.sh"), shell("./push-b.sh")]),
shell("./switch-traffic.sh"),
]),
catch: shell("./rollback.sh"),
finally: shell("./notify.sh"),
}),
);The blocks and what each one does:
{ sequence: [...] }— the steps run one after another. The first failure stops the sequence; the later steps end skipped.{ parallel: [...] }— the steps run concurrently and the block waits for every one of them. It fails if any step failed; every failed step is visible in the tree, not just the first.{ try, catch?, finally? }—tryruns;catchruns only whentryfailed (otherwise it ends skipped);finallyruns in every case, aftertryor aftercatch, and also whencatchitself failed. A failedtryfails the block —catchreacts to the failure (a rollback, a notification), it does not undo it. Whentrysucceeded, afinallyfailure fails the block. The block succeeds only whentryandfinallyboth did.{ nodes: [{ name, function, options, deps }] }— a DAG: the nodes run one at a time in an order that respectsdeps, and the first failure stops it. Reach forparallelwhen branches should run at the same time.
Every block takes an optional timeoutSecs. When it passes, the block fails,
and whatever was still running or waiting in it ends cancelled.
A block is plain data, so the same shape works without the helpers:
scope.addDeployment("release", "deployment/composite/v0.0.0", {
sequence: [
{ function: "deployment/local.shell/v0.0.0", options: { commands: ["echo prepare"] } },
{ parallel: [
{ function: "deployment/local.shell/v0.0.0", options: { commands: ["echo a"] } },
{ function: "deployment/local.shell/v0.0.0", options: { commands: ["echo b"] } },
] },
],
});Deployment.composite(block) is the authored form — a Deployment value a
pipeline step can link to. bldr/api declares composite-blocks-demo and
composite-try-demo as runnable examples.
That is worth reaching for when a release is several steps with real ordering between them, and not worth it when it is one command.
Why declare it here
A deployment declared beside the build that produces the artifact cannot disagree with it about which version is live. The alternative, handing an artifact to a separate system, means two places that both believe they know what is deployed.