Jenkins shared library: Extracting the Sync blog stage - Part 2
~6 min read
With the scaffold in place, the first real candidate was obvious: the Sync blog stage that appears, near enough verbatim, in three Jenkinsfiles. This post is about what I moved into the library, what I deliberately left behind, and the two options I gave the new step.
What the stage does
Reading the three copies side by side, the work they do is the same:
- Take two Secret text credentials from Jenkins,
ghost-urlandghost-admin-key. - Run
blog-sync --apply, which creates missing posts as drafts and updates changed ones without touching a post's published or draft status. - Run
blog-sync --schedule --apply, which queues any posts still sitting as drafts, one every other day at 09:00 London time.
The order matters: scheduling skips posts that aren't on Ghost yet, so the content sync has to come first. Jenkins runs sh with set -e, so if the sync fails, the schedule step never runs. The library step keeps that comment, because it's the sort of thing someone will otherwise "tidy" away.
The finance, inventory and identity copies also wrap everything in catchError, so a Ghost outage or a bad API key marks the stage UNSTABLE rather than failing the build. A blog problem shouldn't hold up shipping the app.
What stays in the Jenkinsfile
The when { branch 'main' } guard stays where it is. when is a declarative pipeline directive that belongs to a stage, and a step in vars/ can't carry one. So the stage keeps its name and its guard, and the body becomes one line:
stage('Sync blog') {
when { branch 'main' }
steps { syncBlog() }
}
The alternative was a step that declares the whole stage itself, but that would hide the guard from anyone reading the Jenkinsfile, and the Jenkinsfile is where you expect to see when something runs.
Two options
The step takes a map, which the library's own docs recommend for anything with more than one option, because callers get named parameters and new options can be added without breaking anyone.
versionis theblog-syncrelease tag to run. Today all three projects pin the same one, so it has a default, and a project can still override it. Pinning matters for the same reason it did in the Jenkinsfiles: a change toblog-synccan't break a build until someone bumps the tag.softdefaults totrue, which keeps thecatchErrorbehaviour the three copies have. Passingsoft: falselets the sync fail the build.
soft: false exists because not every project is shaped like those three. blog-sync's own pipeline has nothing to ship, so the blog is the whole pipeline, and a failed sync should turn the build red.
What I didn't take
blog-sync's own Jenkinsfile runs the tool from its checkout (uv run blog-sync) so that it always tests the code it's about to publish, where the other projects install a pinned release with uvx. The shared step only covers the pinned case, so I've left that Jenkinsfile alone rather than bending the step to fit one caller.
Not switched over yet
The step exists, but the three Jenkinsfiles still carry their own copies. Moving them is a separate change per repository, and it needs the library registered in Jenkins and tagged first; otherwise their builds would fail looking for a step that isn't there.
Previous: ← Why a shared library · Next: Shared defaults and a package name → · Back to series index
