Skip to content

Commit

Permalink
Merge pull request #1489 from idlefella/feature/add-generic-ephemeral…
Browse files Browse the repository at this point in the history
…-storage-support
  • Loading branch information
Vlatombe authored Jan 3, 2024
2 parents 38bf4cc + b5092f4 commit 4230d0c
Show file tree
Hide file tree
Showing 23 changed files with 452 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,16 @@
import io.fabric8.kubernetes.api.model.OwnerReferenceBuilder;
import io.fabric8.kubernetes.api.model.PersistentVolumeClaim;
import io.fabric8.kubernetes.api.model.PersistentVolumeClaimBuilder;
import io.fabric8.kubernetes.api.model.Quantity;
import io.fabric8.kubernetes.api.model.Volume;
import io.fabric8.kubernetes.api.model.VolumeBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import java.util.Collections;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang.StringUtils;

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

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

default String getStorageClassNameOrDefault() {
return getStorageClassName();
}

String getStorageClassName();

default Map<String, Quantity> getResourceMap() {
return Collections.singletonMap("storage", new Quantity(getRequestsSizeOrDefault()));
}

default String getRequestsSizeOrDefault() {
return StringUtils.defaultString(getRequestsSize(), "10Gi");
}

String getRequestsSize();

default String getAccessModesOrDefault() {
return StringUtils.defaultString(getAccessModes(), "ReadWriteOnce");
}

String getAccessModes();

String getPvcName(String podName);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@

/**
* Implements a dynamic PVC volume, that is created before the agent pod is created, and terminated afterwards.
*
* @deprecated Use {@link GenericEphemeralVolume} instead.
*/
@SuppressFBWarnings(
value = "SE_NO_SERIALVERSIONID",
justification = "Serialization happens exclusively through XStream and not Java Serialization.")
@Deprecated
public class DynamicPVCVolume extends PodVolume implements DynamicPVC {
private String id;
private String storageClassName;
Expand Down Expand Up @@ -109,12 +112,12 @@ public int hashCode() {
return Objects.hash(id, storageClassName, requestsSize, accessModes);
}

@Extension
@Extension(ordinal = -100) // Display at the end of the select list
@Symbol("dynamicPVC")
public static class DescriptorImpl extends Descriptor<PodVolume> {
@Override
public String getDisplayName() {
return "Dynamic Persistent Volume Claim";
return "Dynamic Persistent Volume Claim (deprecated)";
}

@SuppressWarnings("unused") // by stapler
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.csanchez.jenkins.plugins.kubernetes.volumes;

import io.fabric8.kubernetes.api.model.Volume;
import io.fabric8.kubernetes.api.model.VolumeBuilder;

/**
* Interface containing common code between {@link GenericEphemeralVolume} and {@link org.csanchez.jenkins.plugins.kubernetes.volumes.workspace.GenericEphemeralWorkspaceVolume}.
*/
public interface EphemeralVolume extends ProvisionedVolume {
default Volume buildEphemeralVolume(String volumeName) {
return new VolumeBuilder()
.withName(volumeName)
.withNewEphemeral()
.withNewVolumeClaimTemplate()
.withNewSpec()
.withAccessModes(getAccessModesOrDefault())
.withStorageClassName(getStorageClassNameOrDefault())
.withNewResources()
.withRequests(getResourceMap())
.endResources()
.endSpec()
.endVolumeClaimTemplate()
.endEphemeral()
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package org.csanchez.jenkins.plugins.kubernetes.volumes;

import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.Extension;
import hudson.Util;
import hudson.model.Descriptor;
import hudson.util.ListBoxModel;
import io.fabric8.kubernetes.api.model.Volume;
import java.util.Objects;
import org.jenkinsci.Symbol;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.interceptor.RequirePOST;

/**
* Uses a generic ephemeral volume, that is created before the agent pod is created, and terminated afterwards.
* See <a href="https://kubernetes.io/docs/concepts/storage/ephemeral-volumes/#generic-ephemeral-volumes">Kubernetes documentation</a>
*/
@SuppressFBWarnings(
value = "SE_NO_SERIALVERSIONID",
justification = "Serialization happens exclusively through XStream and not Java Serialization.")
public class GenericEphemeralVolume extends PodVolume implements EphemeralVolume {
private String storageClassName;
private String requestsSize;
private String accessModes;
private String mountPath;

@DataBoundConstructor
public GenericEphemeralVolume() {}

@CheckForNull
public String getAccessModes() {
return accessModes;
}

@DataBoundSetter
public void setAccessModes(@CheckForNull String accessModes) {
this.accessModes = accessModes;
}

@CheckForNull
public String getRequestsSize() {
return requestsSize;
}

@DataBoundSetter
public void setRequestsSize(@CheckForNull String requestsSize) {
this.requestsSize = Util.fixEmptyAndTrim(requestsSize);
}

@CheckForNull
public String getStorageClassName() {
return storageClassName;
}

@DataBoundSetter
public void setStorageClassName(@CheckForNull String storageClassName) {
this.storageClassName = Util.fixEmptyAndTrim(storageClassName);
}

@Override
public String getMountPath() {
return mountPath;
}

@Override
public Volume buildVolume(String volumeName, String podName) {
return buildEphemeralVolume(volumeName);
}

@DataBoundSetter
public void setMountPath(String mountPath) {
this.mountPath = mountPath;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GenericEphemeralVolume that = (GenericEphemeralVolume) o;
return Objects.equals(storageClassName, that.storageClassName)
&& Objects.equals(requestsSize, that.requestsSize)
&& Objects.equals(accessModes, that.accessModes)
&& Objects.equals(mountPath, that.mountPath);
}

@Override
public int hashCode() {
return Objects.hash(storageClassName, requestsSize, accessModes, mountPath);
}

@Extension
@Symbol("genericEphemeralVolume")
public static class DescriptorImpl extends Descriptor<PodVolume> {
@Override
@NonNull
public String getDisplayName() {
return "Generic ephemeral volume";
}

@SuppressWarnings("unused") // by stapler
@RequirePOST
@Restricted(DoNotUse.class) // stapler only
public ListBoxModel doFillAccessModesItems() {
return PVCVolumeUtils.ACCESS_MODES_BOX;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.csanchez.jenkins.plugins.kubernetes.volumes;

import io.fabric8.kubernetes.api.model.Quantity;
import java.util.Collections;
import java.util.Map;
import org.apache.commons.lang.StringUtils;

public interface ProvisionedVolume {
default String getStorageClassNameOrDefault() {
return getStorageClassName();
}

String getStorageClassName();

default Map<String, Quantity> getResourceMap() {
return Collections.singletonMap("storage", new Quantity(getRequestsSizeOrDefault()));
}

default String getRequestsSizeOrDefault() {
return StringUtils.defaultString(getRequestsSize(), "10Gi");
}

String getRequestsSize();

default String getAccessModesOrDefault() {
return StringUtils.defaultString(getAccessModes(), "ReadWriteOnce");
}

String getAccessModes();
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@

/**
* @author <a href="root@junwuhui.cn">runzexia</a>
*
* @deprecated Use {@link GenericEphemeralWorkspaceVolume} instead.
*/
@SuppressFBWarnings(
value = "SE_NO_SERIALVERSIONID",
justification = "Serialization happens exclusively through XStream and not Java Serialization.")
@Deprecated
public class DynamicPVCWorkspaceVolume extends WorkspaceVolume implements DynamicPVC {
private String storageClassName;
private String requestsSize;
Expand Down Expand Up @@ -95,12 +98,12 @@ public int hashCode() {
return Objects.hash(storageClassName, requestsSize, accessModes);
}

@Extension
@Extension(ordinal = -100) // Display at the end of the select list
@Symbol("dynamicPVC")
public static class DescriptorImpl extends Descriptor<WorkspaceVolume> {
@Override
public String getDisplayName() {
return "Dynamic Persistent Volume Claim";
return "Dynamic Persistent Volume Claim (deprecated)";
}

@SuppressWarnings("unused") // by stapler
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package org.csanchez.jenkins.plugins.kubernetes.volumes.workspace;

import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.Extension;
import hudson.Util;
import hudson.model.Descriptor;
import hudson.util.ListBoxModel;
import io.fabric8.kubernetes.api.model.Volume;
import org.csanchez.jenkins.plugins.kubernetes.volumes.EphemeralVolume;
import org.csanchez.jenkins.plugins.kubernetes.volumes.PVCVolumeUtils;
import org.jenkinsci.Symbol;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.interceptor.RequirePOST;

/**
* Uses a generic ephemeral volume, that is created before the agent pod is created, and terminated afterwards.
*/
@SuppressFBWarnings(
value = "SE_NO_SERIALVERSIONID",
justification = "Serialization happens exclusively through XStream and not Java Serialization.")
public class GenericEphemeralWorkspaceVolume extends WorkspaceVolume implements EphemeralVolume {

private String storageClassName;
private String requestsSize;
private String accessModes;

@DataBoundConstructor
public GenericEphemeralWorkspaceVolume() {}

@Override
public String getStorageClassName() {
return storageClassName;
}

@DataBoundSetter
public void setStorageClassName(String storageClassName) {
this.storageClassName = Util.fixEmptyAndTrim(storageClassName);
}

@Override
public String getRequestsSize() {
return requestsSize;
}

@DataBoundSetter
public void setRequestsSize(@CheckForNull String requestsSize) {
this.requestsSize = Util.fixEmptyAndTrim(requestsSize);
}

@Override
public String getAccessModes() {
return accessModes;
}

@DataBoundSetter
public void setAccessModes(String accessModes) {
this.accessModes = accessModes;
}

@Override
public Volume buildVolume(String volumeName, String podName) {
return buildEphemeralVolume(volumeName);
}

@Extension
@Symbol("genericEphemeralVolume")
public static class DescriptorImpl extends Descriptor<WorkspaceVolume> {
@Override
@NonNull
public String getDisplayName() {
return "Generic Ephemeral Volume";
}

@SuppressWarnings("unused") // by stapler
@RequirePOST
@Restricted(DoNotUse.class) // stapler only
public ListBoxModel doFillAccessModesItems() {
return PVCVolumeUtils.ACCESS_MODES_BOX;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"
xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:description>
<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>
<p>
Allocates a PVC dynamically using the specified parameters, then deletes it when the pod is deleted.
Note that this requires the Jenkins controller to have additional RBAC permissions than are typically needed for agent provisioning.
</p>
</f:description>

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

<f:entry title="${%Mount subPath}" field="subPath">
<f:textbox />
</f:entry>

<f:entry title="${%Storage Class Name}" field="storageClassName">
<f:textbox />
</f:entry>
Expand Down
Loading

0 comments on commit 4230d0c

Please sign in to comment.