Skip to content

Commit 17c5a69

Browse files
committed
Add PodContainerSource Extension Point
Add PodContainerSource extension point to lookup container working directory. This allows plugins to use ContainerExecDecorator with alternate container sources, like ephemeral containers.
1 parent 345364d commit 17c5a69

File tree

3 files changed

+172
-15
lines changed

3 files changed

+172
-15
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package org.csanchez.jenkins.plugins.kubernetes;
2+
3+
import edu.umd.cs.findbugs.annotations.NonNull;
4+
import hudson.Extension;
5+
import hudson.ExtensionList;
6+
import hudson.ExtensionPoint;
7+
import io.fabric8.kubernetes.api.model.Container;
8+
import io.fabric8.kubernetes.api.model.Pod;
9+
import io.fabric8.kubernetes.api.model.PodSpec;
10+
import java.util.List;
11+
import java.util.Objects;
12+
import java.util.Optional;
13+
14+
/**
15+
* Pod container sources are responsible to locating details about Pod containers.
16+
*/
17+
public abstract class PodContainerSource implements ExtensionPoint {
18+
19+
/**
20+
* Lookup the working directory of the named container.
21+
* @param pod pod reference to lookup container in
22+
* @param containerName name of container to lookup
23+
* @return working directory path if container found and working dir specified, otherwise empty
24+
*/
25+
public abstract Optional<String> getContainerWorkingDir(@NonNull Pod pod, @NonNull String containerName);
26+
27+
/**
28+
* Lookup all {@link PodContainerSource} extensions.
29+
* @return pod container source extension list
30+
*/
31+
@NonNull
32+
public static List<PodContainerSource> all() {
33+
return ExtensionList.lookup(PodContainerSource.class);
34+
}
35+
36+
/**
37+
* Lookup pod container working dir. Searches all {@link PodContainerSource} extensions and returns
38+
* the first non-empty result.
39+
* @param pod pod to inspect
40+
* @param containerName container to search for
41+
* @return optional working dir if container found and working dir, possibly empty
42+
*/
43+
public static Optional<String> lookupContainerWorkingDir(@NonNull Pod pod, @NonNull String containerName) {
44+
return all().stream()
45+
.map(cs -> cs.getContainerWorkingDir(pod, containerName))
46+
.filter(Optional::isPresent)
47+
.map(Optional::get)
48+
.findFirst();
49+
}
50+
51+
/**
52+
* Default implementation of {@link PodContainerSource} that only searches the primary
53+
* pod containers. Ephemeral or init containers are not included container lookups in
54+
* this implementation.
55+
* @see PodSpec#getContainers()
56+
*/
57+
@Extension
58+
public static final class DefaultPodContainerSource extends PodContainerSource {
59+
60+
@Override
61+
public Optional<String> getContainerWorkingDir(@NonNull Pod pod, @NonNull String containerName) {
62+
return pod.getSpec().getContainers().stream()
63+
.filter(c -> Objects.equals(c.getName(), containerName))
64+
.findAny()
65+
.map(Container::getWorkingDir);
66+
}
67+
}
68+
}

src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerExecDecorator.java

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import hudson.Proc;
2828
import hudson.model.Computer;
2929
import hudson.model.Node;
30-
import io.fabric8.kubernetes.api.model.Container;
3130
import io.fabric8.kubernetes.client.KubernetesClient;
3231
import io.fabric8.kubernetes.client.KubernetesClientException;
3332
import io.fabric8.kubernetes.client.dsl.ExecListener;
@@ -59,6 +58,7 @@
5958
import org.apache.commons.io.output.TeeOutputStream;
6059
import org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate;
6160
import org.csanchez.jenkins.plugins.kubernetes.KubernetesSlave;
61+
import org.csanchez.jenkins.plugins.kubernetes.PodContainerSource;
6262
import org.jenkinsci.plugins.workflow.steps.EnvironmentExpander;
6363

6464
/**
@@ -284,13 +284,8 @@ public Proc launch(ProcStarter starter) throws IOException {
284284
: ContainerTemplate.DEFAULT_WORKING_DIR;
285285
String containerWorkingDirStr = ContainerTemplate.DEFAULT_WORKING_DIR;
286286
if (slave != null && slave.getPod().isPresent() && containerName != null) {
287-
Optional<Container> container = slave.getPod().get().getSpec().getContainers().stream()
288-
.filter(container1 -> container1.getName().equals(containerName))
289-
.findAny();
290-
Optional<String> containerWorkingDir = Optional.empty();
291-
if (container.isPresent() && container.get().getWorkingDir() != null) {
292-
containerWorkingDir = Optional.of(container.get().getWorkingDir());
293-
}
287+
Optional<String> containerWorkingDir = PodContainerSource.lookupContainerWorkingDir(
288+
slave.getPod().get(), containerName);
294289
if (containerWorkingDir.isPresent()) {
295290
containerWorkingDirStr = containerWorkingDir.get();
296291
}
@@ -403,7 +398,7 @@ private Proc doLaunch(
403398
// Do not send this command to the output when in quiet mode
404399
if (quiet) {
405400
stream = toggleStdout;
406-
printStream = new PrintStream(stream, true, StandardCharsets.UTF_8.toString());
401+
printStream = new PrintStream(stream, true, StandardCharsets.UTF_8);
407402
} else {
408403
printStream = launcher.getListener().getLogger();
409404
stream = new TeeOutputStream(toggleStdout, printStream);
@@ -575,15 +570,15 @@ public void onClose(int i, String s) {
575570
}
576571
toggleStdout.disable();
577572
OutputStream stdin = watch.getInput();
578-
PrintStream in = new PrintStream(stdin, true, StandardCharsets.UTF_8.name());
573+
PrintStream in = new PrintStream(stdin, true, StandardCharsets.UTF_8);
579574
if (!launcher.isUnix()) {
580575
in.print("@echo off");
581576
in.print(newLine(true));
582577
}
583578
if (pwd != null) {
584579
// We need to get into the project workspace.
585580
// The workspace is not known in advance, so we have to execute a cd command.
586-
in.print(String.format("cd \"%s\"", pwd));
581+
in.printf("cd \"%s\"", pwd);
587582
in.print(newLine(!launcher.isUnix()));
588583
}
589584

@@ -644,10 +639,8 @@ public void onClose(int i, String s) {
644639
}
645640
doExec(in, !launcher.isUnix(), printStream, masks, commands);
646641

647-
LOGGER.log(
648-
Level.INFO,
649-
"Created process inside pod: [" + getPodName() + "], container: [" + containerName + "]"
650-
+ "[" + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startMethod) + " ms]");
642+
LOGGER.fine(() -> "Created process inside pod: [" + getPodName() + "], container: [" + containerName
643+
+ "]" + "[" + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startMethod) + " ms]");
651644
ContainerExecProc proc = new ContainerExecProc(watch, alive, finished, stdin, printStream);
652645
closables.add(proc);
653646
return proc;
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package org.csanchez.jenkins.plugins.kubernetes;
2+
3+
import static org.junit.Assert.*;
4+
5+
import edu.umd.cs.findbugs.annotations.NonNull;
6+
import hudson.Extension;
7+
import io.fabric8.kubernetes.api.model.EphemeralContainer;
8+
import io.fabric8.kubernetes.api.model.Pod;
9+
import io.fabric8.kubernetes.api.model.PodBuilder;
10+
import java.util.Optional;
11+
import org.apache.commons.lang3.StringUtils;
12+
import org.junit.Rule;
13+
import org.junit.Test;
14+
import org.jvnet.hudson.test.JenkinsRule;
15+
import org.jvnet.hudson.test.WithoutJenkins;
16+
17+
public class PodContainerSourceTest {
18+
19+
@Rule
20+
public JenkinsRule j = new JenkinsRule();
21+
22+
@Test
23+
public void lookupContainerWorkingDir() {
24+
Pod pod = new PodBuilder()
25+
.withNewSpec()
26+
.addNewContainer()
27+
.withName("foo")
28+
.withWorkingDir("/app/foo")
29+
.endContainer()
30+
.addNewEphemeralContainer()
31+
.withName("bar")
32+
.withWorkingDir("/app/bar")
33+
.endEphemeralContainer()
34+
.addNewEphemeralContainer()
35+
.withName("foo")
36+
.withWorkingDir("/app/ephemeral-foo")
37+
.endEphemeralContainer()
38+
.endSpec()
39+
.build();
40+
41+
Optional<String> wd = PodContainerSource.lookupContainerWorkingDir(pod, "foo");
42+
assertTrue(wd.isPresent());
43+
assertEquals("/app/foo", wd.get());
44+
45+
// should use TestPodContainerSource to find ephemeral container
46+
wd = PodContainerSource.lookupContainerWorkingDir(pod, "bar");
47+
assertTrue(wd.isPresent());
48+
assertEquals("/app/bar", wd.get());
49+
50+
// no named container
51+
wd = PodContainerSource.lookupContainerWorkingDir(pod, "fish");
52+
assertFalse(wd.isPresent());
53+
}
54+
55+
@WithoutJenkins
56+
@Test
57+
public void defaultPodContainerSourceGetContainerWorkingDir() {
58+
Pod pod = new PodBuilder()
59+
.withNewSpec()
60+
.addNewContainer()
61+
.withName("foo")
62+
.withWorkingDir("/app/foo")
63+
.endContainer()
64+
.addNewEphemeralContainer()
65+
.withName("bar")
66+
.withWorkingDir("/app/bar")
67+
.endEphemeralContainer()
68+
.endSpec()
69+
.build();
70+
71+
PodContainerSource.DefaultPodContainerSource source = new PodContainerSource.DefaultPodContainerSource();
72+
Optional<String> wd = source.getContainerWorkingDir(pod, "foo");
73+
assertTrue(wd.isPresent());
74+
assertEquals("/app/foo", wd.get());
75+
76+
// should not return ephemeral container
77+
wd = source.getContainerWorkingDir(pod, "bar");
78+
assertFalse(wd.isPresent());
79+
80+
// no named container
81+
wd = source.getContainerWorkingDir(pod, "fish");
82+
assertFalse(wd.isPresent());
83+
}
84+
85+
@Extension
86+
public static class TestPodContainerSource extends PodContainerSource {
87+
88+
@Override
89+
public Optional<String> getContainerWorkingDir(@NonNull Pod pod, @NonNull String containerName) {
90+
return pod.getSpec().getEphemeralContainers().stream()
91+
.filter(c -> StringUtils.equals(c.getName(), containerName))
92+
.findAny()
93+
.map(EphemeralContainer::getWorkingDir);
94+
}
95+
}
96+
}

0 commit comments

Comments
 (0)