Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ protected static MessageDigest getLabelDigestFunction() {

private boolean agentInjection;

private boolean agentJdkInjection;

/**
* Persisted yaml fragment
*/
Expand Down Expand Up @@ -649,6 +651,15 @@ public void setAgentInjection(boolean agentInjection) {
this.agentInjection = agentInjection;
}

public boolean isAgentJdkInjection() {
return agentJdkInjection;
}

@DataBoundSetter
public void setAgentJdkInjection(boolean agentJdkInjection) {
this.agentJdkInjection = agentJdkInjection;
}

public List<TemplateEnvVar> getEnvVars() {
if (envVars == null) {
return Collections.emptyList();
Expand Down Expand Up @@ -1199,6 +1210,7 @@ public String toString() {
+ (!unwrapped ? "" : ", unwrapped=" + unwrapped)
+ (agentContainer == null ? "" : ", agentContainer='" + agentContainer + '\'')
+ (!agentInjection ? "" : ", agentInjection=" + agentInjection)
+ (!agentJdkInjection ? "" : ", agentJdkInjection=" + agentJdkInjection)
+ '}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -342,14 +342,15 @@ public Pod build() {
var agentVolumeMountBuilder =
new VolumeMountBuilder().withName("jenkins-agent").withMountPath("/jenkins-agent");
var oldInitContainers = pod.getSpec().getInitContainers();
String initCommand = "cp $(command -v jenkins-agent) " + JENKINS_AGENT + "/jenkins-agent" + ";"
+ "cp -R /usr/share/jenkins/. " + JENKINS_AGENT;
if (template.isAgentJdkInjection()) {
initCommand += ";" + "cp -R /opt/java/openjdk " + JENKINS_AGENT + "/jdk";
}
var jenkinsAgentInitContainer = new ContainerBuilder()
.withName("set-up-jenkins-agent")
.withImage(agentImage)
.withCommand(
"/bin/sh",
"-c",
"cp $(command -v jenkins-agent) " + JENKINS_AGENT + "/jenkins-agent" + ";"
+ "cp -R /usr/share/jenkins/. " + JENKINS_AGENT)
.withCommand("/bin/sh", "-c", initCommand)
.withVolumeMounts(agentVolumeMountBuilder.build())
.build();
if (oldInitContainers != null) {
Expand Down Expand Up @@ -390,6 +391,14 @@ public Pod build() {
.withName(JENKINS_AGENT_FILE_ENVVAR)
.withValue(JENKINS_AGENT + "/agent.jar")
.build());
if (template.isAgentJdkInjection()) {
envVars.put(
"JENKINS_JAVA_BIN",
new EnvVarBuilder()
.withName("JENKINS_JAVA_BIN")
.withValue(JENKINS_AGENT + "/jdk/bin/java")
.build());
}
}
agentContainer.setEnv(new ArrayList<>(envVars.values()));
if (agentContainer.getResources() == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,7 @@ public static PodTemplate combine(PodTemplate parent, PodTemplate template) {
podTemplate.setSupplementalGroups(h.resolve(PodTemplate::getSupplementalGroups, Objects::isNull));
podTemplate.setAgentContainer(h.resolve(PodTemplate::getAgentContainer, PodTemplateUtils::isNullOrEmpty));
podTemplate.setAgentInjection(h.resolve(PodTemplate::isAgentInjection, v -> !v));
podTemplate.setAgentJdkInjection(h.resolve(PodTemplate::isAgentJdkInjection, v -> !v));
if (template.isHostNetworkSet()) {
podTemplate.setHostNetwork(template.isHostNetwork());
} else if (parent.isHostNetworkSet()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ THE SOFTWARE.
<f:checkbox/>
</f:entry>

<f:entry field="agentJdkInjection" title="${%Inject JDK from agent image}">
<f:checkbox/>
</f:entry>

<f:entry field="containers" title="${%Containers}" description="${%List of container in the agent pod}">
<f:repeatableHeteroProperty field="containers" hasHeader="true" addCaption="${%Add Container}"
deleteCaption="${%Delete Container}" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<p>
Injects the JDK from the agent injection image into the agent container when <strong>Inject Jenkins agent in agent container</strong> is enabled.
</p>

<p>
This is useful when your agent container is a minimal toolchain image (python, node, golang) that doesn't include Java,
but the Jenkins agent needs Java to run.
</p>

<p>
When enabled, the init container copies the JDK from <code>/opt/java/openjdk</code> to <code>/jenkins-agent/jdk</code>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting thought but this assumes that

  • the stock agent image includes a JRE in the specified location (at least CloudBees CI overrides the default image with a different one based on UBI, just sharing the launcher script with the OSS version)
  • the JRE uses only shared libraries also present in the selected agent image, and in compatible versions

This seems fragile.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your review

The same concern actually applies to agentInjection itself — PR #2823 was opened specifically because the default agent image is glibc-based and breaks on Alpine (musl) containers. That was addressed by adding agentInjectionImage to let users pick a compatible image.

agentJdkInjection follows the same pattern: it's an opt-in feature, and users pairing it with an Alpine-compatible agentInjectionImage (e.g. jenkins/inbound-agent:...-alpine-jdk21) would naturally get a JDK at /opt/java/openjdk too, since that path is standard for Eclipse Temurin across both glibc and Alpine images.

Let me know if you still have concerns.

and sets <code>JENKINS_JAVA_BIN=/jenkins-agent/jdk/bin/java</code>.
</p>

<p><strong>Example use case:</strong></p>
<pre>Agent container: python:3.11-slim (no Java)
Agent injection image: jenkins/inbound-agent:alpine-jdk21 (has Java)
Result: Agent runs with injected JDK</pre>

<p><strong>Trade-offs:</strong></p>
<ul>
<li>Adds 10-20 seconds to pod startup (copying ~100-200MB JDK)</li>
<li>Requires additional volume space (~200MB per pod)</li>
</ul>

<p>
<strong>Note:</strong> Only applies when agent injection is enabled.
If your agent container already has Java installed, leave this disabled.
</p>
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,58 @@ public void testValidateDockerRegistryPrefixOverrideForInitContainer(boolean dir
assertThat(pod.getMetadata().getLabels(), hasEntry("jenkins", "slave"));
}

@Test
public void testAgentJdkInjection() throws Exception {
PodTemplate template = new PodTemplate();
template.setAgentInjection(true);
template.setAgentJdkInjection(true);
template.setYaml(loadYamlFile("pod-busybox.yaml"));
setupStubs();

Pod pod = new PodTemplateBuilder(template, slave).build();

// Verify init container command includes JDK copy
Container initContainer = pod.getSpec().getInitContainers().get(0);
String command = String.join(" ", initContainer.getCommand());
assertThat(command, containsString("cp -R /opt/java/openjdk /jenkins-agent/jdk"));

// Verify JENKINS_JAVA_BIN environment variable is set
Container jnlpContainer = pod.getSpec().getContainers().stream()
.filter(c -> "jnlp".equals(c.getName()))
.findFirst()
.orElseThrow();
EnvVar javaBindEnvVar = jnlpContainer.getEnv().stream()
.filter(env -> "JENKINS_JAVA_BIN".equals(env.getName()))
.findFirst()
.orElseThrow();
assertEquals("/jenkins-agent/jdk/bin/java", javaBindEnvVar.getValue());
}

@Test
public void testAgentJdkInjectionDisabled() throws Exception {
PodTemplate template = new PodTemplate();
template.setAgentInjection(true);
template.setAgentJdkInjection(false);
template.setYaml(loadYamlFile("pod-busybox.yaml"));
setupStubs();

Pod pod = new PodTemplateBuilder(template, slave).build();

// Verify init container command does NOT include JDK copy
Container initContainer = pod.getSpec().getInitContainers().get(0);
String command = String.join(" ", initContainer.getCommand());
assertThat(command, not(containsString("cp -R /opt/java/openjdk")));

// Verify JENKINS_JAVA_BIN is NOT set
Container jnlpContainer = pod.getSpec().getContainers().stream()
.filter(c -> "jnlp".equals(c.getName()))
.findFirst()
.orElseThrow();
boolean hasJavaBinEnv =
jnlpContainer.getEnv().stream().anyMatch(env -> "JENKINS_JAVA_BIN".equals(env.getName()));
assertFalse(hasJavaBinEnv);
}

@Test
@Issue("JENKINS-50525")
public void testBuildWithCustomWorkspaceVolume() throws Exception {
Expand Down
Loading