Skip to content

Commit 58ea43c

Browse files
committed
Merge branch 'master' into JENKINS-50392-exit-code
2 parents e188c7b + 2996087 commit 58ea43c

File tree

17 files changed

+355
-64
lines changed

17 files changed

+355
-64
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ Known issues
77

88
See the full list of issues at [JIRA](https://issues.jenkins-ci.org/issues/?filter=15575)
99

10+
1.8.0
11+
-----
12+
* Validate label and container names with regex [#332](https://github.com/jenkinsci/kubernetes-plugin/pull/332) [#343](https://github.com/jenkinsci/kubernetes-plugin/pull/343) [JENKINS-51248](https://issues.jenkins-ci.org/browse/JENKINS-51248)
13+
1014
1.7.1
1115
-----
1216
* Do not print credentials in build output or logs. Only affects certain pipeline steps like `withDockerRegistry`. `sh` step is not affected [SECURITY-883](https://issues.jenkins-ci.org/browse/SECURITY-883)

pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
<properties>
4141
<!-- in minikube
4242
minikube ip | sed -e 's/\([0-9]*\.[0-9]*\.[0-9]*\).*/\1.1/' -->
43-
<connectorHost>192.168.64.1</connectorHost>
43+
<connectorHost></connectorHost>
4444
<java.level>8</java.level>
4545

4646
<!-- dependency versions -->

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

+9
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
import hudson.model.AbstractDescribableImpl;
2222
import hudson.model.Descriptor;
2323
import hudson.model.DescriptorVisibilityFilter;
24+
import hudson.util.FormValidation;
2425
import jenkins.model.Jenkins;
26+
import org.kohsuke.stapler.QueryParameter;
2527

2628
public class ContainerTemplate extends AbstractDescribableImpl<ContainerTemplate> implements Serializable {
2729

@@ -262,6 +264,13 @@ public String getDisplayName() {
262264
public List<? extends Descriptor> getEnvVarsDescriptors() {
263265
return DescriptorVisibilityFilter.apply(null, Jenkins.getInstance().getDescriptorList(TemplateEnvVar.class));
264266
}
267+
268+
public FormValidation doCheckName(@QueryParameter String value) {
269+
if(!PodTemplateUtils.validateContainerName(value)) {
270+
return FormValidation.error(Messages.RFC1123_error(value));
271+
}
272+
return FormValidation.ok();
273+
}
265274
}
266275

267276
@Override

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

+4-3
Original file line numberDiff line numberDiff line change
@@ -629,8 +629,8 @@ protected Object readResolve() {
629629

630630
/**
631631
* Build a Pod object from a PodTemplate
632-
*
633-
* @param client
632+
*
633+
* @param client
634634
* @param slave
635635
*/
636636
public Pod build(KubernetesClient client, KubernetesSlave slave) {
@@ -684,6 +684,7 @@ public String getDisplayName() {
684684
public List<? extends Descriptor> getEnvVarsDescriptors() {
685685
return DescriptorVisibilityFilter.apply(null, Jenkins.getInstance().getDescriptorList(TemplateEnvVar.class));
686686
}
687+
687688
}
688689

689690
@Override
@@ -718,7 +719,7 @@ public String toString() {
718719
(annotations == null || annotations.isEmpty() ? "" : ", annotations=" + annotations) +
719720
(imagePullSecrets == null || imagePullSecrets.isEmpty() ? "" : ", imagePullSecrets=" + imagePullSecrets) +
720721
(nodeProperties == null || nodeProperties.isEmpty() ? "" : ", nodeProperties=" + nodeProperties) +
721-
(yaml == null ? "" : ", yaml=" + yaml) +
722+
(yaml == null ? "" : ", yaml=" + yaml) +
722723
'}';
723724
}
724725
}

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

+2-19
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,9 @@
2424

2525
package org.csanchez.jenkins.plugins.kubernetes;
2626

27-
import static java.nio.charset.StandardCharsets.*;
2827
import static org.csanchez.jenkins.plugins.kubernetes.KubernetesCloud.*;
2928
import static org.csanchez.jenkins.plugins.kubernetes.PodTemplateUtils.*;
3029

31-
import java.io.ByteArrayInputStream;
3230
import java.nio.file.Paths;
3331
import java.util.ArrayList;
3432
import java.util.Collection;
@@ -61,20 +59,16 @@
6159
import io.fabric8.kubernetes.api.model.EnvVar;
6260
import io.fabric8.kubernetes.api.model.ExecAction;
6361
import io.fabric8.kubernetes.api.model.LocalObjectReference;
64-
import io.fabric8.kubernetes.api.model.ObjectMeta;
6562
import io.fabric8.kubernetes.api.model.Pod;
6663
import io.fabric8.kubernetes.api.model.PodBuilder;
6764
import io.fabric8.kubernetes.api.model.PodFluent.MetadataNested;
6865
import io.fabric8.kubernetes.api.model.PodFluent.SpecNested;
69-
import io.fabric8.kubernetes.api.model.PodSpec;
7066
import io.fabric8.kubernetes.api.model.Probe;
7167
import io.fabric8.kubernetes.api.model.ProbeBuilder;
7268
import io.fabric8.kubernetes.api.model.Quantity;
7369
import io.fabric8.kubernetes.api.model.Volume;
7470
import io.fabric8.kubernetes.api.model.VolumeBuilder;
7571
import io.fabric8.kubernetes.api.model.VolumeMount;
76-
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
77-
import io.fabric8.kubernetes.client.KubernetesClient;
7872

7973
/**
8074
* Helper class to build Pods from PodTemplates
@@ -201,19 +195,8 @@ public Pod build() {
201195
// merge with the yaml
202196
String yaml = template.getYaml();
203197
if (!StringUtils.isBlank(yaml)) {
204-
try (KubernetesClient client = new DefaultKubernetesClient()) {
205-
Pod podFromYaml = client.pods()
206-
.load(new ByteArrayInputStream((yaml == null ? "" : yaml).getBytes(UTF_8))).get();
207-
LOGGER.log(Level.FINEST, "Parsed pod template from yaml: {0}", podFromYaml);
208-
// yaml can be just a fragment, avoid NPEs
209-
if (podFromYaml.getMetadata() == null) {
210-
podFromYaml.setMetadata(new ObjectMeta());
211-
}
212-
if (podFromYaml.getSpec() == null) {
213-
podFromYaml.setSpec(new PodSpec());
214-
}
215-
pod = combine(podFromYaml, pod);
216-
}
198+
Pod podFromYaml = parseFromYaml(yaml);
199+
pod = combine(podFromYaml, pod);
217200
}
218201

219202
// Apply defaults

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

+74-13
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package org.csanchez.jenkins.plugins.kubernetes;
22

33
import static hudson.Util.*;
4+
import static java.nio.charset.StandardCharsets.*;
45
import static java.util.stream.Collectors.*;
56
import static org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate.*;
67

8+
import java.io.ByteArrayInputStream;
79
import java.util.ArrayList;
810
import java.util.Collection;
11+
import java.util.Collections;
912
import java.util.HashMap;
1013
import java.util.LinkedHashSet;
1114
import java.util.List;
@@ -15,11 +18,14 @@
1518
import java.util.function.Function;
1619
import java.util.logging.Level;
1720
import java.util.logging.Logger;
21+
import java.util.regex.Matcher;
22+
import java.util.regex.Pattern;
1823
import java.util.stream.Collectors;
1924

2025
import javax.annotation.CheckForNull;
2126
import javax.annotation.Nonnull;
2227

28+
import org.apache.commons.lang.StringUtils;
2329
import org.csanchez.jenkins.plugins.kubernetes.model.TemplateEnvVar;
2430
import org.csanchez.jenkins.plugins.kubernetes.volumes.PodVolume;
2531
import org.csanchez.jenkins.plugins.kubernetes.volumes.workspace.WorkspaceVolume;
@@ -37,20 +43,26 @@
3743
import io.fabric8.kubernetes.api.model.ContainerBuilder;
3844
import io.fabric8.kubernetes.api.model.EnvVar;
3945
import io.fabric8.kubernetes.api.model.LocalObjectReference;
46+
import io.fabric8.kubernetes.api.model.ObjectMeta;
4047
import io.fabric8.kubernetes.api.model.Pod;
4148
import io.fabric8.kubernetes.api.model.PodBuilder;
4249
import io.fabric8.kubernetes.api.model.PodFluent.MetadataNested;
4350
import io.fabric8.kubernetes.api.model.PodFluent.SpecNested;
51+
import io.fabric8.kubernetes.api.model.PodSpec;
4452
import io.fabric8.kubernetes.api.model.Quantity;
4553
import io.fabric8.kubernetes.api.model.ResourceRequirements;
4654
import io.fabric8.kubernetes.api.model.Toleration;
4755
import io.fabric8.kubernetes.api.model.Volume;
4856
import io.fabric8.kubernetes.api.model.VolumeMount;
57+
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
58+
import io.fabric8.kubernetes.client.KubernetesClient;
4959

5060
public class PodTemplateUtils {
5161

5262
private static final Logger LOGGER = Logger.getLogger(PodTemplateUtils.class.getName());
5363

64+
private static final Pattern LABEL_VALIDATION = Pattern.compile("[a-zA-Z0-9]([_\\.\\-a-zA-Z0-9]*[a-zA-Z0-9])?");
65+
5466
/**
5567
* Combines a {@link ContainerTemplate} with its parent.
5668
* @param parent The parent container template (nullable).
@@ -95,7 +107,7 @@ public static ContainerTemplate combine(@CheckForNull ContainerTemplate parent,
95107

96108
/**
97109
* Combines a Container with its parent.
98-
*
110+
*
99111
* @param parent
100112
* The parent container (nullable).
101113
* @param template
@@ -204,7 +216,7 @@ public static Pod combine(Pod parent, Pod template) {
204216
List<Volume> combinedVolumes = Lists.newLinkedList();
205217
Optional.ofNullable(parent.getSpec().getVolumes()).ifPresent(combinedVolumes::addAll);
206218
Optional.ofNullable(template.getSpec().getVolumes()).ifPresent(combinedVolumes::addAll);
207-
219+
208220
// Tolerations
209221
List<Toleration> combinedTolerations = Lists.newLinkedList();
210222
Optional.ofNullable(parent.getSpec().getTolerations()).ifPresent(combinedTolerations::addAll);
@@ -310,26 +322,26 @@ public static PodTemplate combine(PodTemplate parent, PodTemplate template) {
310322
podTemplate.setAnnotations(new ArrayList<>(podAnnotations));
311323
podTemplate.setNodeProperties(toolLocationNodeProperties);
312324
podTemplate.setNodeUsageMode(nodeUsageMode);
313-
podTemplate.setInheritFrom(!Strings.isNullOrEmpty(template.getInheritFrom()) ?
325+
podTemplate.setInheritFrom(!Strings.isNullOrEmpty(template.getInheritFrom()) ?
314326
template.getInheritFrom() : parent.getInheritFrom());
315-
316-
podTemplate.setInstanceCap(template.getInstanceCap() != Integer.MAX_VALUE ?
327+
328+
podTemplate.setInstanceCap(template.getInstanceCap() != Integer.MAX_VALUE ?
317329
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 ?
320332
template.getSlaveConnectTimeout() : parent.getSlaveConnectTimeout());
321333

322-
podTemplate.setIdleMinutes(template.getIdleMinutes() != 0 ?
323-
template.getIdleMinutes() : parent.getIdleMinutes());
334+
podTemplate.setIdleMinutes(template.getIdleMinutes() != 0 ?
335+
template.getIdleMinutes() : parent.getIdleMinutes());
324336

325337
podTemplate.setActiveDeadlineSeconds(template.getActiveDeadlineSeconds() != 0 ?
326-
template.getActiveDeadlineSeconds() : parent.getActiveDeadlineSeconds());
338+
template.getActiveDeadlineSeconds() : parent.getActiveDeadlineSeconds());
339+
327340

328-
329-
podTemplate.setServiceAccount(!Strings.isNullOrEmpty(template.getServiceAccount()) ?
341+
podTemplate.setServiceAccount(!Strings.isNullOrEmpty(template.getServiceAccount()) ?
330342
template.getServiceAccount() : parent.getServiceAccount());
331343

332-
podTemplate.setCustomWorkspaceVolumeEnabled(template.isCustomWorkspaceVolumeEnabled() ?
344+
podTemplate.setCustomWorkspaceVolumeEnabled(template.isCustomWorkspaceVolumeEnabled() ?
333345
template.isCustomWorkspaceVolumeEnabled() : parent.isCustomWorkspaceVolumeEnabled());
334346

335347
podTemplate.setYaml(template.getYaml() == null ? parent.getYaml() : template.getYaml());
@@ -466,6 +478,55 @@ public static String substitute(String s, Map<String, String> properties, String
466478
return Strings.isNullOrEmpty(s) ? defaultValue : replaceMacro(s, properties);
467479
}
468480

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+
469530
private static List<EnvVar> combineEnvVars(Container parent, Container template) {
470531
List<EnvVar> combinedEnvVars = new ArrayList<>();
471532
combinedEnvVars.addAll(parent.getEnv());

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

+19-1
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,30 @@
22

33
import static java.util.stream.Collectors.*;
44

5+
import java.util.Collection;
56
import java.util.logging.Level;
67
import java.util.logging.Logger;
78

89
import edu.umd.cs.findbugs.annotations.CheckForNull;
9-
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
1010
import org.apache.commons.lang.RandomStringUtils;
1111
import org.csanchez.jenkins.plugins.kubernetes.KubernetesCloud;
12+
import org.csanchez.jenkins.plugins.kubernetes.Messages;
1213
import org.csanchez.jenkins.plugins.kubernetes.PodImagePullSecret;
1314
import org.csanchez.jenkins.plugins.kubernetes.PodTemplate;
1415
import org.jenkinsci.plugins.workflow.steps.AbstractStepExecutionImpl;
1516
import org.jenkinsci.plugins.workflow.steps.BodyExecutionCallback;
1617
import org.jenkinsci.plugins.workflow.steps.StepContext;
1718

1819
import com.google.common.base.Strings;
20+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
1921

2022
import hudson.AbortException;
2123
import hudson.model.Run;
2224
import hudson.slaves.Cloud;
2325
import io.fabric8.kubernetes.client.KubernetesClient;
2426
import jenkins.model.Jenkins;
27+
import org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate;
28+
import org.csanchez.jenkins.plugins.kubernetes.PodTemplateUtils;
2529

2630
public class PodTemplateStepExecution extends AbstractStepExecutionImpl {
2731

@@ -92,6 +96,20 @@ public boolean start() throws Exception {
9296
newTemplate.setActiveDeadlineSeconds(step.getActiveDeadlineSeconds());
9397
}
9498

99+
for (ContainerTemplate container : newTemplate.getContainers()) {
100+
if (!PodTemplateUtils.validateContainerName(container.getName())) {
101+
throw new AbortException(Messages.RFC1123_error(container.getName()));
102+
}
103+
}
104+
Collection<String> errors = PodTemplateUtils.validateYamlContainerNames(newTemplate.getYaml());
105+
if (!errors.isEmpty()) {
106+
throw new AbortException(Messages.RFC1123_error(String.join(", ", errors)));
107+
}
108+
109+
if (!PodTemplateUtils.validateLabel(newTemplate.getLabel())) {
110+
throw new AbortException(Messages.label_error(newTemplate.getLabel()));
111+
}
112+
95113
kubernetesCloud.addDynamicTemplate(newTemplate);
96114
getContext().newBodyInvoker().withContext(step).withCallback(new PodTemplateCallback(newTemplate)).start();
97115

Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
offline=Kubernetes agent is going offline
22
NonConfigurableKubernetesCloud.displayName=Kubernetes (predefined settings)
33
KubernetesSlave.AgentIsProvisionedFromTemplate=Agent {0} is provisioned from template {1}
4+
RFC1123.error=Container Names MUST match RFC 1123 - They can only contain lowercase letters, numbers or dashes: {0}
5+
label.error=Labels must follow required specs - https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set: {0}

0 commit comments

Comments
 (0)