Publish a Jenkins controller image on merge to main - #10
Merged
Merged
Conversation
The plugin currently ships as a .hpi with manual install instructions, so
there is nothing a Control Plane template can deploy. This adds the image and
the workflow that publishes it.
docker/Dockerfile multi-stage: maven:3.9-eclipse-temurin-21 builds the
.hpi, jenkins/jenkins:lts-jdk21 bakes it in. The JDK is
pinned because the plugin's baseline is 2.462.3 and the
build must not depend on whatever JDK a runner has.
docker/plugins.txt JCasC, credentials, pipeline, git, matrix-auth and the
usual operability set.
docker/casc.yaml admin user, authorization, numExecutors 0 on the
built-in node per the README's guidance.
publish-image.yml on merge to main: newest vX.Y.Z tag -> patch bump ->
build -> push :version and :latest -> push the tag.
Seeds from the pom version when no tag exists.
workflow_dispatch takes an explicit version.
The Cloud.java change is the part to look at, because it is not a CI file.
JCasC could not configure this cloud at all. The descriptor carries no
@symbol, and JCasC rejects a jenkins.clouds entry it cannot name -- both
`cpln` and the fully-qualified class name were refused with "No
hudson.slaves.Cloud implementation found". Its own export is the clearest
evidence: given a cloud instance in memory it wrote
clouds:
- ? ''
: agentImage: "jenkins/inbound-agent:latest"
an EMPTY key, so the configuration does not round-trip in either direction.
@symbol("cpln") fixes it, and after the fix a mounted casc file produced a
cloud with every field intact, the Secret apiKey included.
The annotation is additive: it changes no existing behaviour and does not
affect config.xml serialisation, so existing installs are unaffected.
Verified by running it rather than by reading it, which is how three real
defects surfaced:
- jenkins-plugin-cli failed with "Unable to move json-api to the plugin
directory" because a COPY created the plugins directory root-owned before
the CLI (running as jenkins) could. Plugin resolution now runs first.
- --latest false resolved plugins to the oldest versions their dependencies
allowed, which the build log flagged with seven advisories including a
script-security sandbox bypass and an OS command injection in git-client.
Dropping the flag clears all seven.
- excludeClientIPFromCrumb is not an attribute JCasC accepts on
DefaultCrumbIssuer, and globalJobDslSecurityConfiguration needs a plugin
that is not installed. Either one aborts boot outright.
Also verified: anonymous 403, admin 200, bad password 401, the plugin active
at 2.0.0, and the controller answering on container loopback -- which is what
makes `cpln port-forward` usable for a privately deployed controller later.
The version logic was executed against real git tags rather than reviewed:
seeds 2.0.0 from the pom with no tags, v2.0.9 -> 2.0.10, v10.0.0 -> 10.0.1
(neither of which a lexicographic sort gets right), and it rejects a
malformed or already-tagged explicit version.
enk21
approved these changes
Aug 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The plugin ships as a
.hpiwith manual install instructions today, so there is nothing a Control Plane marketplace template can actually deploy. This adds the image and the workflow that publishes it.docker/Dockerfilemaven:3.9-eclipse-temurin-21builds the.hpi,jenkins/jenkins:lts-jdk21bakes it in. The JDK is pinned because the plugin's baseline is 2.462.3; the build must not depend on whichever JDK a runner happens to have.docker/plugins.txtdocker/casc.yamlnumExecutors: 0on the built-in node per the README's own guidance..github/workflows/publish-image.ymlmain: newestvX.Y.Ztag → patch bump → build → push:{version}and:latest→ push the tag..dockerignoretarget/and a 2 MB.hpiunderdocs/.Image:
ghcr.io/controlplane-com/cpln-jenkins. The first merge publishes2.0.0(seeded from the pom), and each merge after bumps the patch. A README- or docs-only merge publishes nothing.workflow_dispatchtakes an explicit version for a minor or major bump.The one change to look at:
Cloud.javaEverything else here is build tooling. This is plugin source, so it is your call whether it ships in this PR.
JCasC could not configure this cloud at all. The descriptor carries no
@Symbol, and JCasC refuses ajenkins.cloudsentry it cannot name — bothcplnand the fully-qualifiedio.jenkins.plugins.cpln.Cloudwere rejected withNo hudson.slaves.Cloud implementation found. Asking the running controller confirmed why:The clearest evidence is JCasC's own export. Given a cloud instance already in memory, it wrote:
An empty key — so the configuration does not round-trip in either direction.
@Symbol("cpln")fixes it. After the fix, a mounted casc file produced a cloud with every field intact including theSecretapiKey:The annotation is additive: it changes no existing behaviour and does not affect
config.xmlserialisation, so existing installs are unaffected.org.jenkinsci.Symbolresolves through the parent POM — no new dependency.Three defects that only running it would have found
jenkins-plugin-clifailed withUnable to move json-api to the plugin directory. ACOPYcreated/usr/share/jenkins/ref/pluginsroot-owned before the CLI — which runs asjenkins— could write to it. Plugin resolution now runs first.--latest falsewas actively harmful. It resolves each plugin to the oldest version its dependencies permit, and the build log listed seven advisories in the resolved set, including ascript-securitysandbox bypass and an OS command injection ingit-client. Dropping the flag clears all seven.excludeClientIPFromCrumbis not an attribute JCasC accepts onDefaultCrumbIssuer, andglobalJobDslSecurityConfigurationneeds the job-dsl plugin, which is not installed. Either one stops Jenkins outright.Verified
Behaviour, on a running container: anonymous
403, admin200, wrong password401, plugin active at2.0.0, and the controller answering on container loopback — which is what makescpln port-forwarda usable path for a privately deployed controller in the template that follows.Version logic, executed against real git tags rather than reviewed: seeds
2.0.0from the pom when no tag exists,v2.0.9→2.0.10andv10.0.0→10.0.1(neither of which a lexicographic sort gets right), and it rejects both a malformed and an already-tagged explicit version. Thegit tag | head -1pipe was also removed — underset -o pipefaila SIGPIPE there can abort the step.Not addressed here
GitHub reports 5 existing Dependabot vulnerabilities on
main(2 high, 3 moderate). Those predate this branch and I have not touched them.🤖 Generated with Claude Code