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 ae43d991b..c6e833cfd 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 String agentInjectionImage; + /** * Persisted yaml fragment */ @@ -649,6 +651,16 @@ public void setAgentInjection(boolean agentInjection) { this.agentInjection = agentInjection; } + @CheckForNull + public String getAgentInjectionImage() { + return agentInjectionImage; + } + + @DataBoundSetter + public void setAgentInjectionImage(@CheckForNull String agentInjectionImage) { + this.agentInjectionImage = Util.fixEmptyAndTrim(agentInjectionImage); + } + public List getEnvVars() { if (envVars == null) { return Collections.emptyList(); @@ -1199,6 +1211,7 @@ public String toString() { + (!unwrapped ? "" : ", unwrapped=" + unwrapped) + (agentContainer == null ? "" : ", agentContainer='" + agentContainer + '\'') + (!agentInjection ? "" : ", agentInjection=" + agentInjection) + + (agentInjectionImage == null ? "" : ", agentInjectionImage='" + agentInjectionImage + '\'') + '}'; } } 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 622d79b1a..b5ccb6205 100644 --- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateBuilder.java +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateBuilder.java @@ -324,10 +324,18 @@ public Pod build() { .filter(c -> c.getWorkingDir() == null) .forEach(c -> c.setWorkingDir(workingDir)); String agentImage = DEFAULT_AGENT_IMAGE; - if (cloud != null && StringUtils.isNotEmpty(cloud.getJnlpregistry())) { - agentImage = Util.ensureEndsWith(cloud.getJnlpregistry(), "/") + agentImage; - } else if (StringUtils.isNotEmpty(DEFAULT_JNLP_DOCKER_REGISTRY_PREFIX)) { - agentImage = Util.ensureEndsWith(DEFAULT_JNLP_DOCKER_REGISTRY_PREFIX, "/") + agentImage; + + // Priority 1: Template-level custom image (most specific) + if (StringUtils.isNotEmpty(template.getAgentInjectionImage())) { + agentImage = template.getAgentInjectionImage(); + } else { + // Priority 2: Cloud-level registry prefix + if (cloud != null && StringUtils.isNotEmpty(cloud.getJnlpregistry())) { + agentImage = Util.ensureEndsWith(cloud.getJnlpregistry(), "/") + agentImage; + } else if (StringUtils.isNotEmpty(DEFAULT_JNLP_DOCKER_REGISTRY_PREFIX)) { + // Priority 3: System property prefix + agentImage = Util.ensureEndsWith(DEFAULT_JNLP_DOCKER_REGISTRY_PREFIX, "/") + agentImage; + } } if (StringUtils.isBlank(agentContainer.getImage())) { agentContainer.setImage(agentImage); 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 ac5b98c90..a01b440c2 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.setAgentInjectionImage(h.resolve(PodTemplate::getAgentInjectionImage, PodTemplateUtils::isNullOrEmpty)); 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 7547cbea5..29cff8247 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-agentInjectionImage.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-agentInjectionImage.html new file mode 100644 index 000000000..7c780d1fd --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-agentInjectionImage.html @@ -0,0 +1,29 @@ +

+Specifies the container image to use for agent injection when Inject Jenkins agent in agent container is enabled. +

+ +

+This is useful when your agent container is based on Alpine Linux (musl libc) and needs an Alpine-compatible agent image. +

+ +

+If not specified, the plugin uses the default agent image (jenkins/inbound-agent:3355.v388858a_47b_33-3-jdk21) +with any configured registry prefix. +

+ +

Examples:

+
    +
  • jenkins/inbound-agent:3355.v388858a_47b_33-3-alpine-jdk21 - Use the official Alpine variant
  • +
  • my-registry.com/custom-agent:latest - Use a custom registry and image
  • +
+ +

+Important: The jenkins-agent script requires bash. +If your agent container is Alpine-based and doesn't include bash, add it to your container: +

+
RUN apk add --no-cache bash
+ +

+Note: This setting only applies when agent injection is enabled. +It does not affect the agent container's base image. +

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 84b4fac6b..b69963cbe 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,57 @@ public void testValidateDockerRegistryPrefixOverrideForInitContainer(boolean dir assertThat(pod.getMetadata().getLabels(), hasEntry("jenkins", "slave")); } + @Test + public void testAgentInjectionWithCustomImage() throws Exception { + PodTemplate template = new PodTemplate(); + template.setAgentInjection(true); + template.setAgentInjectionImage("jenkins/inbound-agent:alpine-jdk21"); + template.setYaml(loadYamlFile("pod-busybox.yaml")); + setupStubs(); + + Pod pod = new PodTemplateBuilder(template, slave).build(); + + assertNotNull(pod.getSpec().getInitContainers()); + assertEquals(1, pod.getSpec().getInitContainers().size()); + Container initContainer = pod.getSpec().getInitContainers().get(0); + assertEquals("set-up-jenkins-agent", initContainer.getName()); + assertEquals("jenkins/inbound-agent:alpine-jdk21", initContainer.getImage()); + } + + @Test + public void testAgentInjectionImageOverridesRegistry() throws Exception { + cloud.setJnlpregistry("registry.example.com"); + + PodTemplate template = new PodTemplate(); + template.setAgentInjection(true); + template.setAgentInjectionImage("custom-registry.io/my-agent:alpine"); + template.setYaml(loadYamlFile("pod-busybox.yaml")); + setupStubs(); + + Pod pod = new PodTemplateBuilder(template, slave).build(); + + assertNotNull(pod.getSpec().getInitContainers()); + Container initContainer = pod.getSpec().getInitContainers().get(0); + assertEquals("custom-registry.io/my-agent:alpine", initContainer.getImage()); + } + + @Test + public void testAgentInjectionWithoutCustomImageUsesDefault() throws Exception { + cloud.setJnlpregistry("registry.example.com"); + + PodTemplate template = new PodTemplate(); + template.setAgentInjection(true); + // agentInjectionImage NOT set + template.setYaml(loadYamlFile("pod-busybox.yaml")); + setupStubs(); + + Pod pod = new PodTemplateBuilder(template, slave).build(); + + assertNotNull(pod.getSpec().getInitContainers()); + Container initContainer = pod.getSpec().getInitContainers().get(0); + assertEquals("registry.example.com/" + DEFAULT_AGENT_IMAGE, initContainer.getImage()); + } + @Test @Issue("JENKINS-50525") public void testBuildWithCustomWorkspaceVolume() throws Exception {