|
1 | 1 | package org.csanchez.jenkins.plugins.kubernetes;
|
2 | 2 |
|
3 | 3 | import static hudson.Util.*;
|
| 4 | +import static java.nio.charset.StandardCharsets.*; |
4 | 5 | import static java.util.stream.Collectors.*;
|
5 | 6 | import static org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate.*;
|
6 | 7 |
|
| 8 | +import java.io.ByteArrayInputStream; |
7 | 9 | import java.util.ArrayList;
|
8 | 10 | import java.util.Collection;
|
| 11 | +import java.util.Collections; |
9 | 12 | import java.util.HashMap;
|
10 | 13 | import java.util.LinkedHashSet;
|
11 | 14 | import java.util.List;
|
|
15 | 18 | import java.util.function.Function;
|
16 | 19 | import java.util.logging.Level;
|
17 | 20 | import java.util.logging.Logger;
|
| 21 | +import java.util.regex.Matcher; |
| 22 | +import java.util.regex.Pattern; |
18 | 23 | import java.util.stream.Collectors;
|
19 | 24 |
|
20 | 25 | import javax.annotation.CheckForNull;
|
21 | 26 | import javax.annotation.Nonnull;
|
22 | 27 |
|
| 28 | +import org.apache.commons.lang.StringUtils; |
23 | 29 | import org.csanchez.jenkins.plugins.kubernetes.model.TemplateEnvVar;
|
24 | 30 | import org.csanchez.jenkins.plugins.kubernetes.volumes.PodVolume;
|
25 | 31 | import org.csanchez.jenkins.plugins.kubernetes.volumes.workspace.WorkspaceVolume;
|
|
37 | 43 | import io.fabric8.kubernetes.api.model.ContainerBuilder;
|
38 | 44 | import io.fabric8.kubernetes.api.model.EnvVar;
|
39 | 45 | import io.fabric8.kubernetes.api.model.LocalObjectReference;
|
| 46 | +import io.fabric8.kubernetes.api.model.ObjectMeta; |
40 | 47 | import io.fabric8.kubernetes.api.model.Pod;
|
41 | 48 | import io.fabric8.kubernetes.api.model.PodBuilder;
|
42 | 49 | import io.fabric8.kubernetes.api.model.PodFluent.MetadataNested;
|
43 | 50 | import io.fabric8.kubernetes.api.model.PodFluent.SpecNested;
|
| 51 | +import io.fabric8.kubernetes.api.model.PodSpec; |
44 | 52 | import io.fabric8.kubernetes.api.model.Quantity;
|
45 | 53 | import io.fabric8.kubernetes.api.model.ResourceRequirements;
|
46 | 54 | import io.fabric8.kubernetes.api.model.Toleration;
|
47 | 55 | import io.fabric8.kubernetes.api.model.Volume;
|
48 | 56 | import io.fabric8.kubernetes.api.model.VolumeMount;
|
| 57 | +import io.fabric8.kubernetes.client.DefaultKubernetesClient; |
| 58 | +import io.fabric8.kubernetes.client.KubernetesClient; |
49 | 59 |
|
50 | 60 | public class PodTemplateUtils {
|
51 | 61 |
|
52 | 62 | private static final Logger LOGGER = Logger.getLogger(PodTemplateUtils.class.getName());
|
53 | 63 |
|
| 64 | + private static final Pattern LABEL_VALIDATION = Pattern.compile("[a-zA-Z0-9]([_\\.\\-a-zA-Z0-9]*[a-zA-Z0-9])?"); |
| 65 | + |
54 | 66 | /**
|
55 | 67 | * Combines a {@link ContainerTemplate} with its parent.
|
56 | 68 | * @param parent The parent container template (nullable).
|
@@ -95,7 +107,7 @@ public static ContainerTemplate combine(@CheckForNull ContainerTemplate parent,
|
95 | 107 |
|
96 | 108 | /**
|
97 | 109 | * Combines a Container with its parent.
|
98 |
| - * |
| 110 | + * |
99 | 111 | * @param parent
|
100 | 112 | * The parent container (nullable).
|
101 | 113 | * @param template
|
@@ -204,7 +216,7 @@ public static Pod combine(Pod parent, Pod template) {
|
204 | 216 | List<Volume> combinedVolumes = Lists.newLinkedList();
|
205 | 217 | Optional.ofNullable(parent.getSpec().getVolumes()).ifPresent(combinedVolumes::addAll);
|
206 | 218 | Optional.ofNullable(template.getSpec().getVolumes()).ifPresent(combinedVolumes::addAll);
|
207 |
| - |
| 219 | + |
208 | 220 | // Tolerations
|
209 | 221 | List<Toleration> combinedTolerations = Lists.newLinkedList();
|
210 | 222 | Optional.ofNullable(parent.getSpec().getTolerations()).ifPresent(combinedTolerations::addAll);
|
@@ -310,26 +322,26 @@ public static PodTemplate combine(PodTemplate parent, PodTemplate template) {
|
310 | 322 | podTemplate.setAnnotations(new ArrayList<>(podAnnotations));
|
311 | 323 | podTemplate.setNodeProperties(toolLocationNodeProperties);
|
312 | 324 | podTemplate.setNodeUsageMode(nodeUsageMode);
|
313 |
| - podTemplate.setInheritFrom(!Strings.isNullOrEmpty(template.getInheritFrom()) ? |
| 325 | + podTemplate.setInheritFrom(!Strings.isNullOrEmpty(template.getInheritFrom()) ? |
314 | 326 | template.getInheritFrom() : parent.getInheritFrom());
|
315 |
| - |
316 |
| - podTemplate.setInstanceCap(template.getInstanceCap() != Integer.MAX_VALUE ? |
| 327 | + |
| 328 | + podTemplate.setInstanceCap(template.getInstanceCap() != Integer.MAX_VALUE ? |
317 | 329 | template.getInstanceCap() : parent.getInstanceCap());
|
318 |
| - |
319 |
| - podTemplate.setSlaveConnectTimeout(template.getSlaveConnectTimeout() != PodTemplate.DEFAULT_SLAVE_JENKINS_CONNECTION_TIMEOUT ? |
| 330 | + |
| 331 | + podTemplate.setSlaveConnectTimeout(template.getSlaveConnectTimeout() != PodTemplate.DEFAULT_SLAVE_JENKINS_CONNECTION_TIMEOUT ? |
320 | 332 | template.getSlaveConnectTimeout() : parent.getSlaveConnectTimeout());
|
321 | 333 |
|
322 |
| - podTemplate.setIdleMinutes(template.getIdleMinutes() != 0 ? |
323 |
| - template.getIdleMinutes() : parent.getIdleMinutes()); |
| 334 | + podTemplate.setIdleMinutes(template.getIdleMinutes() != 0 ? |
| 335 | + template.getIdleMinutes() : parent.getIdleMinutes()); |
324 | 336 |
|
325 | 337 | podTemplate.setActiveDeadlineSeconds(template.getActiveDeadlineSeconds() != 0 ?
|
326 |
| - template.getActiveDeadlineSeconds() : parent.getActiveDeadlineSeconds()); |
| 338 | + template.getActiveDeadlineSeconds() : parent.getActiveDeadlineSeconds()); |
| 339 | + |
327 | 340 |
|
328 |
| - |
329 |
| - podTemplate.setServiceAccount(!Strings.isNullOrEmpty(template.getServiceAccount()) ? |
| 341 | + podTemplate.setServiceAccount(!Strings.isNullOrEmpty(template.getServiceAccount()) ? |
330 | 342 | template.getServiceAccount() : parent.getServiceAccount());
|
331 | 343 |
|
332 |
| - podTemplate.setCustomWorkspaceVolumeEnabled(template.isCustomWorkspaceVolumeEnabled() ? |
| 344 | + podTemplate.setCustomWorkspaceVolumeEnabled(template.isCustomWorkspaceVolumeEnabled() ? |
333 | 345 | template.isCustomWorkspaceVolumeEnabled() : parent.isCustomWorkspaceVolumeEnabled());
|
334 | 346 |
|
335 | 347 | podTemplate.setYaml(template.getYaml() == null ? parent.getYaml() : template.getYaml());
|
@@ -466,6 +478,55 @@ public static String substitute(String s, Map<String, String> properties, String
|
466 | 478 | return Strings.isNullOrEmpty(s) ? defaultValue : replaceMacro(s, properties);
|
467 | 479 | }
|
468 | 480 |
|
| 481 | + public static Pod parseFromYaml(String yaml) { |
| 482 | + try (KubernetesClient client = new DefaultKubernetesClient()) { |
| 483 | + Pod podFromYaml = client.pods().load(new ByteArrayInputStream((yaml == null ? "" : yaml).getBytes(UTF_8))) |
| 484 | + .get(); |
| 485 | + LOGGER.log(Level.FINEST, "Parsed pod template from yaml: {0}", podFromYaml); |
| 486 | + // yaml can be just a fragment, avoid NPEs |
| 487 | + if (podFromYaml.getMetadata() == null) { |
| 488 | + podFromYaml.setMetadata(new ObjectMeta()); |
| 489 | + } |
| 490 | + if (podFromYaml.getSpec() == null) { |
| 491 | + podFromYaml.setSpec(new PodSpec()); |
| 492 | + } |
| 493 | + return podFromYaml; |
| 494 | + } |
| 495 | + } |
| 496 | + |
| 497 | + public static Collection<String> validateYamlContainerNames(String yaml) { |
| 498 | + if (StringUtils.isBlank(yaml)) { |
| 499 | + return Collections.emptyList(); |
| 500 | + } |
| 501 | + Collection<String> errors = new ArrayList<>(); |
| 502 | + Pod pod = parseFromYaml(yaml); |
| 503 | + List<Container> containers = pod.getSpec().getContainers(); |
| 504 | + if (containers != null) { |
| 505 | + for (Container container : containers) { |
| 506 | + if (!PodTemplateUtils.validateContainerName(container.getName())) { |
| 507 | + errors.add(container.getName()); |
| 508 | + } |
| 509 | + } |
| 510 | + } |
| 511 | + return errors; |
| 512 | + } |
| 513 | + |
| 514 | + public static boolean validateContainerName(String name) { |
| 515 | + if (name != null && !name.isEmpty()) { |
| 516 | + Pattern p = Pattern.compile("[a-z0-9]([-a-z0-9]*[a-z0-9])?"); |
| 517 | + Matcher m = p.matcher(name); |
| 518 | + return m.matches(); |
| 519 | + } |
| 520 | + return true; |
| 521 | + } |
| 522 | + |
| 523 | + /* |
| 524 | + * Pulled from https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set |
| 525 | + */ |
| 526 | + public static boolean validateLabel(String label) { |
| 527 | + return StringUtils.isBlank(label) ? true : label.length() <= 63 && LABEL_VALIDATION.matcher(label).matches(); |
| 528 | + } |
| 529 | + |
469 | 530 | private static List<EnvVar> combineEnvVars(Container parent, Container template) {
|
470 | 531 | List<EnvVar> combinedEnvVars = new ArrayList<>();
|
471 | 532 | combinedEnvVars.addAll(parent.getEnv());
|
|
0 commit comments