Skip to content

Commit e05368a

Browse files
authored
Add PodContainerSource Extension Point (#1693)
1 parent 679d669 commit e05368a

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)