Jenkins shared library: Extracting the Sync blog stage - Part 2

Jenkins shared library: Extracting the Sync blog stage - Part 2

Building Jenkins Shared Library

~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:

  1. Take two Secret text credentials from Jenkins, ghost-url and ghost-admin-key.
  2. Run blog-sync --apply, which creates missing posts as drafts and updates changed ones without touching a post's published or draft status.
  3. 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.

  • version is the blog-sync release 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 to blog-sync can't break a build until someone bumps the tag.
  • soft defaults to true, which keeps the catchError behaviour the three copies have. Passing soft: false lets 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