Skip to content
Draft
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 String agentInjectionImage;

/**
* Persisted yaml fragment
*/
Expand Down Expand Up @@ -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<TemplateEnvVar> getEnvVars() {
if (envVars == null) {
return Collections.emptyList();
Expand Down Expand Up @@ -1199,6 +1211,7 @@ public String toString() {
+ (!unwrapped ? "" : ", unwrapped=" + unwrapped)
+ (agentContainer == null ? "" : ", agentContainer='" + agentContainer + '\'')
+ (!agentInjection ? "" : ", agentInjection=" + agentInjection)
+ (agentInjectionImage == null ? "" : ", agentInjectionImage='" + agentInjectionImage + '\'')
+ '}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
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.setAgentInjectionImage(h.resolve(PodTemplate::getAgentInjectionImage, PodTemplateUtils::isNullOrEmpty));
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="agentInjectionImage" title="${%Agent injection image}">
<f:textbox/>
</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>
Specifies the container image to use for agent injection when <strong>Inject Jenkins agent in agent container</strong> is enabled.
</p>

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

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

<p><strong>Examples:</strong></p>
<ul>
<li><code>jenkins/inbound-agent:3355.v388858a_47b_33-3-alpine-jdk21</code> - Use the official Alpine variant</li>
<li><code>my-registry.com/custom-agent:latest</code> - Use a custom registry and image</li>
</ul>

<p>
<strong>Important:</strong> The <code>jenkins-agent</code> script requires <code>bash</code>.
If your agent container is Alpine-based and doesn't include bash, add it to your container:
</p>
<pre>RUN apk add --no-cache bash</pre>

<p>
<strong>Note:</strong> This setting only applies when agent injection is enabled.
It does not affect the agent container's base image.
</p>
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading