diff --git a/pom.xml b/pom.xml index 470545558e..5df5d12df2 100644 --- a/pom.xml +++ b/pom.xml @@ -46,7 +46,9 @@ - 2.387.3 + 2.401.1 + 2.401.x + 2357.v1043f8578392 false true jenkinsci/${project.artifactId}-plugin @@ -58,7 +60,7 @@ org.jenkins-ci.plugins kubernetes-client-api - 6.4.1-215.v2ed17097a_8e9 + 6.8.1-224.vd388fca_4db_3b_ org.jenkins-ci.plugins @@ -79,12 +81,12 @@ org.jenkinsci.plugins kubernetes-credentials - 0.10.0 + 0.11 org.jenkins-ci.plugins authentication-tokens - 1.4 + 1.53.v1c90fd9191a_b_ @@ -107,6 +109,7 @@ org.jenkins-ci.plugins.workflow workflow-api + 1267.vd9b_a_ddd9eb_47 org.jenkinsci.plugins @@ -238,7 +241,7 @@ io.fabric8 kubernetes-server-mock - 6.4.1 + 6.8.1 test @@ -247,6 +250,18 @@ + + org.awaitility + awaitility + 4.2.0 + test + + + org.hamcrest + hamcrest + + + @@ -254,8 +269,8 @@ io.jenkins.tools.bom - bom-2.387.x - 2163.v2d916d90c305 + bom-${bom} + ${bom.version} import pom 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 b0e789dc94..ebee925705 100644 --- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateBuilder.java +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateBuilder.java @@ -42,13 +42,11 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; -import java.util.stream.Stream; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.Util; -import io.fabric8.kubernetes.api.model.PodSpecFluent; import org.apache.commons.lang.StringUtils; import org.csanchez.jenkins.plugins.kubernetes.model.TemplateEnvVar; import org.csanchez.jenkins.plugins.kubernetes.pipeline.PodTemplateStepExecution; @@ -69,8 +67,6 @@ import io.fabric8.kubernetes.api.model.LocalObjectReference; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodBuilder; -import io.fabric8.kubernetes.api.model.PodFluent.MetadataNested; -import io.fabric8.kubernetes.api.model.PodFluent.SpecNested; import io.fabric8.kubernetes.api.model.Probe; import io.fabric8.kubernetes.api.model.ProbeBuilder; import io.fabric8.kubernetes.api.model.Quantity; @@ -221,7 +217,7 @@ public Pod build() { createContainer(containerTemplate, template.getEnvVars(), volumeMounts.values())); } - MetadataNested metadataBuilder = new PodBuilder().withNewMetadata(); + var metadataBuilder = new PodBuilder().withNewMetadata(); if (agent != null) { metadataBuilder.withName(agent.getPodName()); } @@ -240,7 +236,7 @@ public Pod build() { metadataBuilder.withAnnotations(annotations); } - SpecNested builder = metadataBuilder.endMetadata().withNewSpec(); + var builder = metadataBuilder.endMetadata().withNewSpec(); if (template.getActiveDeadlineSeconds() > 0) { builder = builder.withActiveDeadlineSeconds(Long.valueOf(template.getActiveDeadlineSeconds())); @@ -276,7 +272,7 @@ public Pod build() { Long runAsGroup = template.getRunAsGroupAsLong(); String supplementalGroups = template.getSupplementalGroups(); if (runAsUser != null || runAsGroup != null || supplementalGroups != null) { - PodSpecFluent.SecurityContextNested> securityContext = builder.editOrNewSecurityContext(); + var securityContext = builder.editOrNewSecurityContext(); if (runAsUser != null) { securityContext.withRunAsUser(runAsUser); } 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 8c656999f5..8650b250e8 100644 --- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateUtils.java +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateUtils.java @@ -1,5 +1,8 @@ package org.csanchez.jenkins.plugins.kubernetes; +import io.fabric8.kubernetes.api.model.ConfigMapProjection; +import io.fabric8.kubernetes.api.model.KeyToPath; +import io.fabric8.kubernetes.api.model.SecretProjection; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -292,7 +295,7 @@ public static Pod combine(Pod parent, Pod template) { // toolLocationNodeProperties.addAll(parent.getNodeProperties()); // toolLocationNodeProperties.addAll(template.getNodeProperties()); - MetadataNested metadataBuilder = new PodBuilder(parent).withNewMetadataLike(parent.getMetadata()) // + var metadataBuilder = new PodBuilder(parent).withNewMetadataLike(parent.getMetadata()) // .withAnnotations(podAnnotations).withLabels(podLabels); if (!isNullOrEmpty(template.getMetadata().getName())) { metadataBuilder.withName(template.getMetadata().getName()); @@ -301,7 +304,7 @@ public static Pod combine(Pod parent, Pod template) { metadataBuilder.withNamespace(template.getMetadata().getNamespace()); } - SpecNested specBuilder = metadataBuilder.endMetadata() // + var specBuilder = metadataBuilder.endMetadata() // .withNewSpecLike(parent.getSpec()) // .withNodeSelector(nodeSelector) // .withServiceAccount(serviceAccount) // @@ -619,10 +622,51 @@ public static Pod parseFromYaml(String yaml) { if (podFromYaml.getSpec() == null) { podFromYaml.setSpec(new PodSpec()); } + fixOctal(podFromYaml); return podFromYaml; } } + private static void fixOctal(@NonNull Pod podFromYaml) { + podFromYaml.getSpec().getVolumes().stream() + .map(Volume::getProjected) + .forEach(projected -> { + if (projected != null) { + Integer defaultMode = projected.getDefaultMode(); + if (defaultMode != null) { + projected.setDefaultMode(convertToOctal(defaultMode)); + } + projected.getSources() + .stream() + .forEach(source -> { + ConfigMapProjection configMap = source.getConfigMap(); + if (configMap != null) { + convertDecimalIntegersToOctal(configMap.getItems()); + } + SecretProjection secret = source.getSecret(); + if (secret != null) { + convertDecimalIntegersToOctal(secret.getItems()); + } + }); + } + }); + } + + private static void convertDecimalIntegersToOctal(List items) { + items + .stream() + .forEach(i -> { + Integer mode = i.getMode(); + if (mode != null) { + i.setMode(convertToOctal(mode)); + } + }); + } + + private static int convertToOctal(Integer defaultMode) { + return Integer.parseInt(Integer.toString(defaultMode, 10), 8); + } + public static Collection validateYamlContainerNames(List yamls) { Collection errors = new ArrayList<>(); for (String yaml : yamls) { diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pod/retention/Reaper.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pod/retention/Reaper.java index 07e7919e14..bac5a73179 100644 --- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pod/retention/Reaper.java +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pod/retention/Reaper.java @@ -240,6 +240,10 @@ boolean isWatchingCloud(String name) { return watchers.get(name) != null; } + public Map getWatchers() { + return watchers; + } + /** * Check if the given cloud pod watcher exists and is still valid. Watchers may become invalid * of the kubernetes client configuration changes. @@ -330,6 +334,7 @@ public void eventReceived(Action action, Pod pod) { */ void stop() { if (watch != null) { + LOGGER.info("Stopping watch for kubernetes cloud " + cloudName); this.watch.close(); } } @@ -529,4 +534,4 @@ public void onChange(Saveable o, XmlFile file) { } } } -} \ No newline at end of file +} diff --git a/src/spotbugs/excludesFilter.xml b/src/spotbugs/excludesFilter.xml new file mode 100644 index 0000000000..fc39a01caf --- /dev/null +++ b/src/spotbugs/excludesFilter.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/src/test/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateUtilsTest.java b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateUtilsTest.java index 24e6919ee6..e4912d68cb 100644 --- a/src/test/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateUtilsTest.java +++ b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateUtilsTest.java @@ -540,10 +540,6 @@ public void shouldCombineAllMounts() { assertThat(result.getVolumes(), containsInAnyOrder(hostPathVolume1, hostPathVolume2, hostPathVolume3, hostPathVolume4)); } - private SpecNested podBuilder() { - return new PodBuilder().withNewMetadata().endMetadata().withNewSpec(); - } - private ContainerBuilder containerBuilder() { Map limitMap = new HashMap<>(); limitMap.put("cpu", new Quantity()); @@ -567,9 +563,9 @@ public void shouldCombineAllPodMounts() { VolumeMount vm4 = new VolumeMountBuilder().withMountPath("/host/mnt1").withName("volume-4").withReadOnly(false) .build(); Container container1 = containerBuilder().withName("jnlp").withVolumeMounts(vm1, vm2).build(); - Pod pod1 = podBuilder().withContainers(container1).endSpec().build(); + Pod pod1 = new PodBuilder().withNewMetadata().endMetadata().withNewSpec().withContainers(container1).endSpec().build(); Container container2 = containerBuilder().withName("jnlp").withVolumeMounts(vm3, vm4).build(); - Pod pod2 = podBuilder().withContainers(container2).endSpec().build(); + Pod pod2 = new PodBuilder().withNewMetadata().endMetadata().withNewSpec().withContainers(container2).endSpec().build(); Pod result = combine(pod1, pod2); List containers = result.getSpec().getContainers(); @@ -648,12 +644,12 @@ public void shouldCombineAllResources() { public void shouldCombineContainersInOrder() { Container container1 = containerBuilder().withName("mysql").build(); Container container2 = containerBuilder().withName("jnlp").build(); - Pod pod1 = podBuilder().withContainers(container1, container2).endSpec().build(); + Pod pod1 = new PodBuilder().withNewMetadata().endMetadata().withNewSpec().withContainers(container1, container2).endSpec().build(); Container container3 = containerBuilder().withName("alpine").build(); Container container4 = containerBuilder().withName("node").build(); Container container5 = containerBuilder().withName("mvn").build(); - Pod pod2 = podBuilder().withContainers(container3, container4, container5).endSpec().build(); + Pod pod2 = new PodBuilder().withNewMetadata().endMetadata().withNewSpec().withContainers(container3, container4, container5).endSpec().build(); Pod result = combine(pod1, pod2); assertEquals(Arrays.asList("mysql", "jnlp", "alpine", "node", "mvn"), result.getSpec().getContainers().stream().map(Container::getName).collect(Collectors.toList())); diff --git a/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pod/retention/ReaperTest.java b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pod/retention/ReaperTest.java index ad2f29e5ea..4e6133060d 100644 --- a/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pod/retention/ReaperTest.java +++ b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pod/retention/ReaperTest.java @@ -24,10 +24,13 @@ package org.csanchez.jenkins.plugins.kubernetes.pod.retention; +import static org.awaitility.Awaitility.await; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import static org.mockito.Mockito.*; +import io.fabric8.kubernetes.client.utils.Utils; import java.io.IOException; import java.net.HttpURLConnection; import java.util.LinkedList; @@ -451,9 +454,9 @@ public void testCloseWatchersOnShutdown() throws InterruptedException { new Reaper.ReaperShutdownListener().onBeforeShutdown(); // watchers removed - assertFalse(r.isWatchingCloud(cloud.name)); - assertFalse(r.isWatchingCloud(cloud2.name)); - assertFalse(r.isWatchingCloud(cloud3.name)); + await().until(() -> r.isWatchingCloud(cloud.name), is(false)); + await().until(() -> r.isWatchingCloud(cloud2.name), is(false)); + await().until(() -> r.isWatchingCloud(cloud3.name), is(false)); } @Test(timeout = 10_000) @@ -513,6 +516,15 @@ public void testTerminateAgentOnContainerTerminated() throws IOException, Interr .always(); // don't remove pod on activate server.expect().withPath("/api/v1/namespaces/foo/pods/node-123").andReturn(200, node123).once(); + // Get logs + server.expect() + .withPath("/api/v1/namespaces/foo/pods?fieldSelector=" + Utils.toUrlEncoded("metadata.name=node-123")) + .andReturn(200, new PodListBuilder().withNewMetadata().endMetadata().withItems(node123).build()) + .always(); + server.expect() + .withPath("/api/v1/namespaces/foo/pods/node-123/log?pretty=false&tailLines=30") + .andReturn(200, "some log") + .always(); // activate reaper Reaper r = Reaper.getInstance(); @@ -521,13 +533,8 @@ public void testTerminateAgentOnContainerTerminated() throws IOException, Interr // verify node is still registered assertEquals("jenkins nodes", j.jenkins.getNodes().size(), 1); - // wait for the delete event to be processed - waitForKubeClientRequests(6) - .assertRequestCountAtLeast(watchPodsPath, 3); - // verify listener got notified - listener.waitForEvents() - .expectEvent(Watcher.Action.MODIFIED, node); + listener.waitForEvents().expectEvent(Watcher.Action.MODIFIED, node); // expect node to be terminated verify(node, atLeastOnce()).terminate(); @@ -628,6 +635,11 @@ private Pod withContainerImagePullBackoff(Pod pod) { } private Pod withContainerStatusTerminated(Pod pod) { + PodStatus podStatus = pod.getStatus(); + podStatus.getConditions().add(new PodConditionBuilder() + .withType("Ready") + .withStatus("True") + .build()); ContainerStatus status = new ContainerStatusBuilder() .withNewState() .withNewTerminated() @@ -637,7 +649,7 @@ private Pod withContainerStatusTerminated(Pod pod) { .endTerminated() .endState() .build(); - pod.getStatus().getContainerStatuses().add(status); + podStatus.getContainerStatuses().add(status); return pod; } @@ -745,7 +757,7 @@ CapturedRequests assertRequestCount(String path, long count) { } CapturedRequests assertRequestCountAtLeast(String path, long count) { - assertThat(path + " count at least", countByPath.getOrDefault(path, 0L), Matchers.greaterThanOrEqualTo(count)); + assertThat(path + " count at least", countByPath.getOrDefault(path, 0L), greaterThanOrEqualTo(count)); return this; } }