Skip to content

Commit 4230d0c

Browse files
authored
Merge pull request #1489 from idlefella/feature/add-generic-ephemeral-storage-support
2 parents 38bf4cc + b5092f4 commit 4230d0c

File tree

23 files changed

+452
-48
lines changed

23 files changed

+452
-48
lines changed

src/main/java/org/csanchez/jenkins/plugins/kubernetes/volumes/DynamicPVC.java

+3-28
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,16 @@
77
import io.fabric8.kubernetes.api.model.OwnerReferenceBuilder;
88
import io.fabric8.kubernetes.api.model.PersistentVolumeClaim;
99
import io.fabric8.kubernetes.api.model.PersistentVolumeClaimBuilder;
10-
import io.fabric8.kubernetes.api.model.Quantity;
1110
import io.fabric8.kubernetes.api.model.Volume;
1211
import io.fabric8.kubernetes.api.model.VolumeBuilder;
1312
import io.fabric8.kubernetes.client.KubernetesClient;
14-
import java.util.Collections;
15-
import java.util.Map;
1613
import java.util.logging.Level;
1714
import java.util.logging.Logger;
18-
import org.apache.commons.lang.StringUtils;
1915

2016
/**
2117
* Interface containing common code between {@link DynamicPVCVolume} and {@link org.csanchez.jenkins.plugins.kubernetes.volumes.workspace.DynamicPVCWorkspaceVolume}.
2218
*/
23-
public interface DynamicPVC {
19+
public interface DynamicPVC extends ProvisionedVolume {
2420
Logger LOGGER = Logger.getLogger(DynamicPVC.class.getName());
2521

2622
default Volume buildPVC(String volumeName, String podName) {
@@ -62,32 +58,11 @@ default PersistentVolumeClaim createPVC(KubernetesClient client, ObjectMeta podM
6258
.build();
6359
pvc = client.persistentVolumeClaims()
6460
.inNamespace(podMetaData.getNamespace())
65-
.create(pvc);
61+
.resource(pvc)
62+
.create();
6663
LOGGER.log(INFO, "Created PVC: {0}/{1}", new Object[] {namespace, pvcName});
6764
return pvc;
6865
}
6966

70-
default String getStorageClassNameOrDefault() {
71-
return getStorageClassName();
72-
}
73-
74-
String getStorageClassName();
75-
76-
default Map<String, Quantity> getResourceMap() {
77-
return Collections.singletonMap("storage", new Quantity(getRequestsSizeOrDefault()));
78-
}
79-
80-
default String getRequestsSizeOrDefault() {
81-
return StringUtils.defaultString(getRequestsSize(), "10Gi");
82-
}
83-
84-
String getRequestsSize();
85-
86-
default String getAccessModesOrDefault() {
87-
return StringUtils.defaultString(getAccessModes(), "ReadWriteOnce");
88-
}
89-
90-
String getAccessModes();
91-
9267
String getPvcName(String podName);
9368
}

src/main/java/org/csanchez/jenkins/plugins/kubernetes/volumes/DynamicPVCVolume.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,13 @@
2222

2323
/**
2424
* Implements a dynamic PVC volume, that is created before the agent pod is created, and terminated afterwards.
25+
*
26+
* @deprecated Use {@link GenericEphemeralVolume} instead.
2527
*/
2628
@SuppressFBWarnings(
2729
value = "SE_NO_SERIALVERSIONID",
2830
justification = "Serialization happens exclusively through XStream and not Java Serialization.")
31+
@Deprecated
2932
public class DynamicPVCVolume extends PodVolume implements DynamicPVC {
3033
private String id;
3134
private String storageClassName;
@@ -109,12 +112,12 @@ public int hashCode() {
109112
return Objects.hash(id, storageClassName, requestsSize, accessModes);
110113
}
111114

112-
@Extension
115+
@Extension(ordinal = -100) // Display at the end of the select list
113116
@Symbol("dynamicPVC")
114117
public static class DescriptorImpl extends Descriptor<PodVolume> {
115118
@Override
116119
public String getDisplayName() {
117-
return "Dynamic Persistent Volume Claim";
120+
return "Dynamic Persistent Volume Claim (deprecated)";
118121
}
119122

120123
@SuppressWarnings("unused") // by stapler
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.csanchez.jenkins.plugins.kubernetes.volumes;
2+
3+
import io.fabric8.kubernetes.api.model.Volume;
4+
import io.fabric8.kubernetes.api.model.VolumeBuilder;
5+
6+
/**
7+
* Interface containing common code between {@link GenericEphemeralVolume} and {@link org.csanchez.jenkins.plugins.kubernetes.volumes.workspace.GenericEphemeralWorkspaceVolume}.
8+
*/
9+
public interface EphemeralVolume extends ProvisionedVolume {
10+
default Volume buildEphemeralVolume(String volumeName) {
11+
return new VolumeBuilder()
12+
.withName(volumeName)
13+
.withNewEphemeral()
14+
.withNewVolumeClaimTemplate()
15+
.withNewSpec()
16+
.withAccessModes(getAccessModesOrDefault())
17+
.withStorageClassName(getStorageClassNameOrDefault())
18+
.withNewResources()
19+
.withRequests(getResourceMap())
20+
.endResources()
21+
.endSpec()
22+
.endVolumeClaimTemplate()
23+
.endEphemeral()
24+
.build();
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package org.csanchez.jenkins.plugins.kubernetes.volumes;
2+
3+
import edu.umd.cs.findbugs.annotations.CheckForNull;
4+
import edu.umd.cs.findbugs.annotations.NonNull;
5+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
6+
import hudson.Extension;
7+
import hudson.Util;
8+
import hudson.model.Descriptor;
9+
import hudson.util.ListBoxModel;
10+
import io.fabric8.kubernetes.api.model.Volume;
11+
import java.util.Objects;
12+
import org.jenkinsci.Symbol;
13+
import org.kohsuke.accmod.Restricted;
14+
import org.kohsuke.accmod.restrictions.DoNotUse;
15+
import org.kohsuke.stapler.DataBoundConstructor;
16+
import org.kohsuke.stapler.DataBoundSetter;
17+
import org.kohsuke.stapler.interceptor.RequirePOST;
18+
19+
/**
20+
* Uses a generic ephemeral volume, that is created before the agent pod is created, and terminated afterwards.
21+
* See <a href="https://kubernetes.io/docs/concepts/storage/ephemeral-volumes/#generic-ephemeral-volumes">Kubernetes documentation</a>
22+
*/
23+
@SuppressFBWarnings(
24+
value = "SE_NO_SERIALVERSIONID",
25+
justification = "Serialization happens exclusively through XStream and not Java Serialization.")
26+
public class GenericEphemeralVolume extends PodVolume implements EphemeralVolume {
27+
private String storageClassName;
28+
private String requestsSize;
29+
private String accessModes;
30+
private String mountPath;
31+
32+
@DataBoundConstructor
33+
public GenericEphemeralVolume() {}
34+
35+
@CheckForNull
36+
public String getAccessModes() {
37+
return accessModes;
38+
}
39+
40+
@DataBoundSetter
41+
public void setAccessModes(@CheckForNull String accessModes) {
42+
this.accessModes = accessModes;
43+
}
44+
45+
@CheckForNull
46+
public String getRequestsSize() {
47+
return requestsSize;
48+
}
49+
50+
@DataBoundSetter
51+
public void setRequestsSize(@CheckForNull String requestsSize) {
52+
this.requestsSize = Util.fixEmptyAndTrim(requestsSize);
53+
}
54+
55+
@CheckForNull
56+
public String getStorageClassName() {
57+
return storageClassName;
58+
}
59+
60+
@DataBoundSetter
61+
public void setStorageClassName(@CheckForNull String storageClassName) {
62+
this.storageClassName = Util.fixEmptyAndTrim(storageClassName);
63+
}
64+
65+
@Override
66+
public String getMountPath() {
67+
return mountPath;
68+
}
69+
70+
@Override
71+
public Volume buildVolume(String volumeName, String podName) {
72+
return buildEphemeralVolume(volumeName);
73+
}
74+
75+
@DataBoundSetter
76+
public void setMountPath(String mountPath) {
77+
this.mountPath = mountPath;
78+
}
79+
80+
@Override
81+
public boolean equals(Object o) {
82+
if (this == o) return true;
83+
if (o == null || getClass() != o.getClass()) return false;
84+
GenericEphemeralVolume that = (GenericEphemeralVolume) o;
85+
return Objects.equals(storageClassName, that.storageClassName)
86+
&& Objects.equals(requestsSize, that.requestsSize)
87+
&& Objects.equals(accessModes, that.accessModes)
88+
&& Objects.equals(mountPath, that.mountPath);
89+
}
90+
91+
@Override
92+
public int hashCode() {
93+
return Objects.hash(storageClassName, requestsSize, accessModes, mountPath);
94+
}
95+
96+
@Extension
97+
@Symbol("genericEphemeralVolume")
98+
public static class DescriptorImpl extends Descriptor<PodVolume> {
99+
@Override
100+
@NonNull
101+
public String getDisplayName() {
102+
return "Generic ephemeral volume";
103+
}
104+
105+
@SuppressWarnings("unused") // by stapler
106+
@RequirePOST
107+
@Restricted(DoNotUse.class) // stapler only
108+
public ListBoxModel doFillAccessModesItems() {
109+
return PVCVolumeUtils.ACCESS_MODES_BOX;
110+
}
111+
}
112+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package org.csanchez.jenkins.plugins.kubernetes.volumes;
2+
3+
import io.fabric8.kubernetes.api.model.Quantity;
4+
import java.util.Collections;
5+
import java.util.Map;
6+
import org.apache.commons.lang.StringUtils;
7+
8+
public interface ProvisionedVolume {
9+
default String getStorageClassNameOrDefault() {
10+
return getStorageClassName();
11+
}
12+
13+
String getStorageClassName();
14+
15+
default Map<String, Quantity> getResourceMap() {
16+
return Collections.singletonMap("storage", new Quantity(getRequestsSizeOrDefault()));
17+
}
18+
19+
default String getRequestsSizeOrDefault() {
20+
return StringUtils.defaultString(getRequestsSize(), "10Gi");
21+
}
22+
23+
String getRequestsSize();
24+
25+
default String getAccessModesOrDefault() {
26+
return StringUtils.defaultString(getAccessModes(), "ReadWriteOnce");
27+
}
28+
29+
String getAccessModes();
30+
}

src/main/java/org/csanchez/jenkins/plugins/kubernetes/volumes/workspace/DynamicPVCWorkspaceVolume.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@
2323

2424
/**
2525
* @author <a href="[email protected]">runzexia</a>
26+
*
27+
* @deprecated Use {@link GenericEphemeralWorkspaceVolume} instead.
2628
*/
2729
@SuppressFBWarnings(
2830
value = "SE_NO_SERIALVERSIONID",
2931
justification = "Serialization happens exclusively through XStream and not Java Serialization.")
32+
@Deprecated
3033
public class DynamicPVCWorkspaceVolume extends WorkspaceVolume implements DynamicPVC {
3134
private String storageClassName;
3235
private String requestsSize;
@@ -95,12 +98,12 @@ public int hashCode() {
9598
return Objects.hash(storageClassName, requestsSize, accessModes);
9699
}
97100

98-
@Extension
101+
@Extension(ordinal = -100) // Display at the end of the select list
99102
@Symbol("dynamicPVC")
100103
public static class DescriptorImpl extends Descriptor<WorkspaceVolume> {
101104
@Override
102105
public String getDisplayName() {
103-
return "Dynamic Persistent Volume Claim";
106+
return "Dynamic Persistent Volume Claim (deprecated)";
104107
}
105108

106109
@SuppressWarnings("unused") // by stapler
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package org.csanchez.jenkins.plugins.kubernetes.volumes.workspace;
2+
3+
import edu.umd.cs.findbugs.annotations.CheckForNull;
4+
import edu.umd.cs.findbugs.annotations.NonNull;
5+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
6+
import hudson.Extension;
7+
import hudson.Util;
8+
import hudson.model.Descriptor;
9+
import hudson.util.ListBoxModel;
10+
import io.fabric8.kubernetes.api.model.Volume;
11+
import org.csanchez.jenkins.plugins.kubernetes.volumes.EphemeralVolume;
12+
import org.csanchez.jenkins.plugins.kubernetes.volumes.PVCVolumeUtils;
13+
import org.jenkinsci.Symbol;
14+
import org.kohsuke.accmod.Restricted;
15+
import org.kohsuke.accmod.restrictions.DoNotUse;
16+
import org.kohsuke.stapler.DataBoundConstructor;
17+
import org.kohsuke.stapler.DataBoundSetter;
18+
import org.kohsuke.stapler.interceptor.RequirePOST;
19+
20+
/**
21+
* Uses a generic ephemeral volume, that is created before the agent pod is created, and terminated afterwards.
22+
*/
23+
@SuppressFBWarnings(
24+
value = "SE_NO_SERIALVERSIONID",
25+
justification = "Serialization happens exclusively through XStream and not Java Serialization.")
26+
public class GenericEphemeralWorkspaceVolume extends WorkspaceVolume implements EphemeralVolume {
27+
28+
private String storageClassName;
29+
private String requestsSize;
30+
private String accessModes;
31+
32+
@DataBoundConstructor
33+
public GenericEphemeralWorkspaceVolume() {}
34+
35+
@Override
36+
public String getStorageClassName() {
37+
return storageClassName;
38+
}
39+
40+
@DataBoundSetter
41+
public void setStorageClassName(String storageClassName) {
42+
this.storageClassName = Util.fixEmptyAndTrim(storageClassName);
43+
}
44+
45+
@Override
46+
public String getRequestsSize() {
47+
return requestsSize;
48+
}
49+
50+
@DataBoundSetter
51+
public void setRequestsSize(@CheckForNull String requestsSize) {
52+
this.requestsSize = Util.fixEmptyAndTrim(requestsSize);
53+
}
54+
55+
@Override
56+
public String getAccessModes() {
57+
return accessModes;
58+
}
59+
60+
@DataBoundSetter
61+
public void setAccessModes(String accessModes) {
62+
this.accessModes = accessModes;
63+
}
64+
65+
@Override
66+
public Volume buildVolume(String volumeName, String podName) {
67+
return buildEphemeralVolume(volumeName);
68+
}
69+
70+
@Extension
71+
@Symbol("genericEphemeralVolume")
72+
public static class DescriptorImpl extends Descriptor<WorkspaceVolume> {
73+
@Override
74+
@NonNull
75+
public String getDisplayName() {
76+
return "Generic Ephemeral Volume";
77+
}
78+
79+
@SuppressWarnings("unused") // by stapler
80+
@RequirePOST
81+
@Restricted(DoNotUse.class) // stapler only
82+
public ListBoxModel doFillAccessModesItems() {
83+
return PVCVolumeUtils.ACCESS_MODES_BOX;
84+
}
85+
}
86+
}

src/main/resources/org/csanchez/jenkins/plugins/kubernetes/volumes/DynamicPVCVolume/config.jelly

+3-4
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,17 @@
22
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"
33
xmlns:t="/lib/hudson" xmlns:f="/lib/form">
44
<f:description>
5+
<em>This feature is deprecated. Please use generic ephemeral volume instead, which provides the same functionality without requiring explicit RBAC permissions to create persistent volume claims.</em>
6+
<p>
57
Allocates a PVC dynamically using the specified parameters, then deletes it when the pod is deleted.
68
Note that this requires the Jenkins controller to have additional RBAC permissions than are typically needed for agent provisioning.
9+
</p>
710
</f:description>
811

912
<f:entry title="${%Mount path}" field="mountPath">
1013
<f:textbox />
1114
</f:entry>
1215

13-
<f:entry title="${%Mount subPath}" field="subPath">
14-
<f:textbox />
15-
</f:entry>
16-
1716
<f:entry title="${%Storage Class Name}" field="storageClassName">
1817
<f:textbox />
1918
</f:entry>

0 commit comments

Comments
 (0)