From 23943ce91dc29290b7037ee4dfb8345cff3ecfd6 Mon Sep 17 00:00:00 2001 From: Jack Zhang Date: Sat, 11 Apr 2026 11:03:35 +0800 Subject: [PATCH] support agent jdk injection --- .../plugins/kubernetes/PodTemplate.java | 12 +++++ .../kubernetes/PodTemplateBuilder.java | 19 +++++-- .../plugins/kubernetes/PodTemplateUtils.java | 1 + .../kubernetes/PodTemplate/config.jelly | 4 ++ .../PodTemplate/help-agentJdkInjection.html | 29 +++++++++++ .../kubernetes/PodTemplateBuilderTest.java | 52 +++++++++++++++++++ 6 files changed, 112 insertions(+), 5 deletions(-) create mode 100644 src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-agentJdkInjection.html diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplate.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplate.java index ae43d991b8..108689d33a 100644 --- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplate.java +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplate.java @@ -192,6 +192,8 @@ protected static MessageDigest getLabelDigestFunction() { private boolean agentInjection; + private boolean agentJdkInjection; + /** * Persisted yaml fragment */ @@ -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 getEnvVars() { if (envVars == null) { return Collections.emptyList(); @@ -1199,6 +1210,7 @@ public String toString() { + (!unwrapped ? "" : ", unwrapped=" + unwrapped) + (agentContainer == null ? "" : ", agentContainer='" + agentContainer + '\'') + (!agentInjection ? "" : ", agentInjection=" + agentInjection) + + (!agentJdkInjection ? "" : ", agentJdkInjection=" + agentJdkInjection) + '}'; } } diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateBuilder.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateBuilder.java index 622d79b1a9..6706e021db 100644 --- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateBuilder.java +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateBuilder.java @@ -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) { @@ -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) { diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateUtils.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateUtils.java index ac5b98c90f..e57cb7411a 100644 --- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateUtils.java +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateUtils.java @@ -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()) { diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/config.jelly b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/config.jelly index 7547cbea5e..4ecb47982b 100644 --- a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/config.jelly +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/config.jelly @@ -53,6 +53,10 @@ THE SOFTWARE. + + + + diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-agentJdkInjection.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-agentJdkInjection.html new file mode 100644 index 0000000000..c379647e7a --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-agentJdkInjection.html @@ -0,0 +1,29 @@ +

+Injects the JDK from the agent injection image into the agent container when Inject Jenkins agent in agent container is enabled. +

+ +

+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. +

+ +

+When enabled, the init container copies the JDK from /opt/java/openjdk to /jenkins-agent/jdk +and sets JENKINS_JAVA_BIN=/jenkins-agent/jdk/bin/java. +

+ +

Example use case:

+
Agent container: python:3.11-slim (no Java)
+Agent injection image: jenkins/inbound-agent:alpine-jdk21 (has Java)
+Result: Agent runs with injected JDK
+ +

Trade-offs:

+
    +
  • Adds 10-20 seconds to pod startup (copying ~100-200MB JDK)
  • +
  • Requires additional volume space (~200MB per pod)
  • +
+ +

+Note: Only applies when agent injection is enabled. +If your agent container already has Java installed, leave this disabled. +

diff --git a/src/test/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateBuilderTest.java b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateBuilderTest.java index 84b4fac6b5..0f40941867 100644 --- a/src/test/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateBuilderTest.java +++ b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateBuilderTest.java @@ -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 {