Skip to content

Commit a0283de

Browse files
cronikVlatombe
andauthored
Improved container logs (#1699)
Co-authored-by: Vincent Latombe <[email protected]>
1 parent 8fe8f2b commit a0283de

File tree

3 files changed

+166
-10
lines changed

3 files changed

+166
-10
lines changed

src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesComputer.java

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,17 @@
99
import hudson.security.Permission;
1010
import hudson.slaves.AbstractCloudComputer;
1111
import io.fabric8.kubernetes.api.model.Container;
12+
import io.fabric8.kubernetes.api.model.ContainerStatus;
1213
import io.fabric8.kubernetes.api.model.Event;
1314
import io.fabric8.kubernetes.api.model.EventList;
1415
import io.fabric8.kubernetes.api.model.ObjectMeta;
1516
import io.fabric8.kubernetes.api.model.Pod;
1617
import io.fabric8.kubernetes.client.KubernetesClient;
18+
import io.fabric8.kubernetes.client.KubernetesClientException;
19+
import io.fabric8.kubernetes.client.dsl.LogWatch;
20+
import io.fabric8.kubernetes.client.dsl.PodResource;
1721
import java.io.IOException;
22+
import java.nio.charset.StandardCharsets;
1823
import java.util.Collections;
1924
import java.util.HashMap;
2025
import java.util.List;
@@ -137,22 +142,51 @@ public void doContainerLog(@QueryParameter String containerId, StaplerRequest2 r
137142
Jenkins.get().checkPermission(Computer.EXTENDED_READ);
138143

139144
ByteBuffer outputStream = new ByteBuffer();
145+
LargeText text = new LargeText(outputStream, false);
140146
KubernetesSlave slave = getNode();
141147
if (slave != null) {
142148
KubernetesCloud cloud = slave.getKubernetesCloud();
143-
KubernetesClient client = cloud.connect();
149+
String namespace = StringUtils.defaultIfBlank(slave.getNamespace(), cloud.getNamespace());
150+
PodResource resource = cloud.getPodResource(namespace, containerId);
151+
152+
// check if pod exists
153+
Pod pod = resource.get();
154+
if (pod == null) {
155+
outputStream.write("Pod not found".getBytes(StandardCharsets.UTF_8));
156+
text.markAsComplete();
157+
text.doProgressText(req, rsp);
158+
return;
159+
}
144160

145-
String namespace = StringUtils.defaultIfBlank(slave.getNamespace(), client.getNamespace());
161+
// Check if container exists and is running (maybe terminated if ephemeral)
162+
Optional<ContainerStatus> status = PodContainerSource.lookupContainerStatus(pod, containerId);
163+
if (status.isPresent()) {
164+
ContainerStatus cs = status.get();
165+
if (cs.getState().getTerminated() != null) {
166+
outputStream.write("Container terminated".getBytes(StandardCharsets.UTF_8));
167+
text.markAsComplete();
168+
text.doProgressText(req, rsp);
169+
return;
170+
}
171+
} else {
172+
outputStream.write("Container not found".getBytes(StandardCharsets.UTF_8));
173+
text.markAsComplete();
174+
text.doProgressText(req, rsp);
175+
return;
176+
}
146177

147-
client.pods()
148-
.inNamespace(namespace)
149-
.withName(getName())
150-
.inContainer(containerId)
151-
.tailingLines(20)
152-
.watchLog(outputStream);
178+
// Get logs
179+
try (LogWatch ignore =
180+
resource.inContainer(containerId).tailingLines(20).watchLog(outputStream)) {
181+
text.doProgressText(req, rsp);
182+
} catch (KubernetesClientException kce) {
183+
LOGGER.log(Level.WARNING, "Failed getting container logs for " + containerId, kce);
184+
}
185+
} else {
186+
outputStream.write("Node not available".getBytes(StandardCharsets.UTF_8));
187+
text.markAsComplete();
188+
text.doProgressText(req, rsp);
153189
}
154-
155-
new LargeText(outputStream, false).doProgressText(req, rsp);
156190
}
157191

158192
// TODO delete after https://github.com/jenkinsci/jenkins/pull/10595

src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodContainerSource.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@
55
import hudson.ExtensionList;
66
import hudson.ExtensionPoint;
77
import io.fabric8.kubernetes.api.model.Container;
8+
import io.fabric8.kubernetes.api.model.ContainerStatus;
89
import io.fabric8.kubernetes.api.model.Pod;
910
import io.fabric8.kubernetes.api.model.PodSpec;
11+
import io.fabric8.kubernetes.api.model.PodStatus;
1012
import java.util.List;
1113
import java.util.Objects;
1214
import java.util.Optional;
15+
import org.apache.commons.lang.StringUtils;
1316

1417
/**
1518
* Pod container sources are responsible to locating details about Pod containers.
@@ -24,6 +27,14 @@ public abstract class PodContainerSource implements ExtensionPoint {
2427
*/
2528
public abstract Optional<String> getContainerWorkingDir(@NonNull Pod pod, @NonNull String containerName);
2629

30+
/**
31+
* Lookup the status of the named container.
32+
* @param pod pod reference to lookup container in
33+
* @param containerName name of container to lookup
34+
* @return container status if found, otherwise empty
35+
*/
36+
public abstract Optional<ContainerStatus> getContainerStatus(@NonNull Pod pod, @NonNull String containerName);
37+
2738
/**
2839
* Lookup all {@link PodContainerSource} extensions.
2940
* @return pod container source extension list
@@ -48,6 +59,20 @@ public static Optional<String> lookupContainerWorkingDir(@NonNull Pod pod, @NonN
4859
.findFirst();
4960
}
5061

62+
/**
63+
* Lookup container status (either main container or ephemeral container).
64+
* @param pod pod resource to inspect
65+
* @param containerName container to locate
66+
* @return container status if found
67+
*/
68+
public static Optional<ContainerStatus> lookupContainerStatus(Pod pod, String containerName) {
69+
return PodContainerSource.all().stream()
70+
.map(cs -> cs.getContainerStatus(pod, containerName))
71+
.filter(Optional::isPresent)
72+
.map(Optional::get)
73+
.findFirst();
74+
}
75+
5176
/**
5277
* Default implementation of {@link PodContainerSource} that only searches the primary
5378
* pod containers. Ephemeral or init containers are not included container lookups in
@@ -64,5 +89,17 @@ public Optional<String> getContainerWorkingDir(@NonNull Pod pod, @NonNull String
6489
.findAny()
6590
.map(Container::getWorkingDir);
6691
}
92+
93+
@Override
94+
public Optional<ContainerStatus> getContainerStatus(@NonNull Pod pod, @NonNull String containerName) {
95+
PodStatus podStatus = pod.getStatus();
96+
if (podStatus == null) {
97+
return Optional.empty();
98+
}
99+
100+
return podStatus.getContainerStatuses().stream()
101+
.filter(cs -> StringUtils.equals(cs.getName(), containerName))
102+
.findFirst();
103+
}
67104
}
68105
}

src/test/java/org/csanchez/jenkins/plugins/kubernetes/PodContainerSourceTest.java

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44

55
import edu.umd.cs.findbugs.annotations.NonNull;
66
import hudson.Extension;
7+
import io.fabric8.kubernetes.api.model.ContainerStatus;
78
import io.fabric8.kubernetes.api.model.EphemeralContainer;
89
import io.fabric8.kubernetes.api.model.Pod;
910
import io.fabric8.kubernetes.api.model.PodBuilder;
11+
import io.fabric8.kubernetes.api.model.PodStatus;
1012
import java.util.Optional;
1113
import org.apache.commons.lang3.StringUtils;
1214
import org.junit.Rule;
@@ -52,6 +54,41 @@ public void lookupContainerWorkingDir() {
5254
assertFalse(wd.isPresent());
5355
}
5456

57+
@Test
58+
public void lookupContainerStatus() {
59+
Pod pod = new PodBuilder()
60+
.withNewStatus()
61+
.addNewContainerStatus()
62+
.withName("foo")
63+
.withNewState()
64+
.withNewRunning()
65+
.endRunning()
66+
.endState()
67+
.endContainerStatus()
68+
.addNewEphemeralContainerStatus()
69+
.withName("bar")
70+
.withNewState()
71+
.withNewTerminated()
72+
.endTerminated()
73+
.endState()
74+
.endEphemeralContainerStatus()
75+
.endStatus()
76+
.build();
77+
78+
Optional<ContainerStatus> status = PodContainerSource.lookupContainerStatus(pod, "foo");
79+
assertTrue(status.isPresent());
80+
assertEquals("foo", status.get().getName());
81+
82+
// should use TestPodContainerSource to find ephemeral container
83+
status = PodContainerSource.lookupContainerStatus(pod, "bar");
84+
assertTrue(status.isPresent());
85+
assertEquals("bar", status.get().getName());
86+
87+
// no named container
88+
status = PodContainerSource.lookupContainerStatus(pod, "fish");
89+
assertFalse(status.isPresent());
90+
}
91+
5592
@WithoutJenkins
5693
@Test
5794
public void defaultPodContainerSourceGetContainerWorkingDir() {
@@ -82,6 +119,42 @@ public void defaultPodContainerSourceGetContainerWorkingDir() {
82119
assertFalse(wd.isPresent());
83120
}
84121

122+
@WithoutJenkins
123+
@Test
124+
public void defaultPodContainerSourceGetContainerStatus() {
125+
Pod pod = new PodBuilder()
126+
.withNewStatus()
127+
.addNewContainerStatus()
128+
.withName("foo")
129+
.withNewState()
130+
.withNewRunning()
131+
.endRunning()
132+
.endState()
133+
.endContainerStatus()
134+
.addNewEphemeralContainerStatus()
135+
.withName("bar")
136+
.withNewState()
137+
.withNewTerminated()
138+
.endTerminated()
139+
.endState()
140+
.endEphemeralContainerStatus()
141+
.endStatus()
142+
.build();
143+
144+
PodContainerSource.DefaultPodContainerSource source = new PodContainerSource.DefaultPodContainerSource();
145+
Optional<ContainerStatus> status = source.getContainerStatus(pod, "foo");
146+
assertTrue(status.isPresent());
147+
assertEquals("foo", status.get().getName());
148+
149+
// should not return ephemeral container
150+
status = source.getContainerStatus(pod, "bar");
151+
assertFalse(status.isPresent());
152+
153+
// no named container
154+
status = source.getContainerStatus(pod, "fish");
155+
assertFalse(status.isPresent());
156+
}
157+
85158
@Extension
86159
public static class TestPodContainerSource extends PodContainerSource {
87160

@@ -92,5 +165,17 @@ public Optional<String> getContainerWorkingDir(@NonNull Pod pod, @NonNull String
92165
.findAny()
93166
.map(EphemeralContainer::getWorkingDir);
94167
}
168+
169+
@Override
170+
public Optional<ContainerStatus> getContainerStatus(@NonNull Pod pod, @NonNull String containerName) {
171+
PodStatus podStatus = pod.getStatus();
172+
if (podStatus == null) {
173+
return Optional.empty();
174+
}
175+
176+
return podStatus.getEphemeralContainerStatuses().stream()
177+
.filter(cs -> StringUtils.equals(cs.getName(), containerName))
178+
.findFirst();
179+
}
95180
}
96181
}

0 commit comments

Comments
 (0)