diff --git a/api/v1alpha2/jenkins_types.go b/api/v1alpha2/jenkins_types.go index 3e380c99..58a14c03 100644 --- a/api/v1alpha2/jenkins_types.go +++ b/api/v1alpha2/jenkins_types.go @@ -343,6 +343,11 @@ type JenkinsMaster struct { // +optional Volumes []corev1.Volume `json:"volumes,omitempty"` + // Storage settings for the jenkins home directory + // Can be tempDir or ephemeral + // +optional + StorageSettings StorageSettings `json:"storage,omitempty"` + // If specified, the pod's tolerations. // +optional Tolerations []corev1.Toleration `json:"tolerations,omitempty"` @@ -387,6 +392,14 @@ type JenkinsMaster struct { HostAliases []corev1.HostAlias `json:"hostAliases,omitempty"` } +// StorageSettings defines Jenkins master pod persistance attributes. +type StorageSettings struct { + UseTempDir bool `json:"useTempDir"` + UseEphemeralStorage bool `json:"useEphemeralStorage"` + StorageClassName string `json:"storageClassName"` + StorageRequest string `json:"storageRequest"` +} + // Service defines Kubernetes service attributes type Service struct { // Annotations is an unstructured key value map stored with a resource that may be diff --git a/pkg/configuration/base/resources/pod.go b/pkg/configuration/base/resources/pod.go index 1a294971..9182d358 100644 --- a/pkg/configuration/base/resources/pod.go +++ b/pkg/configuration/base/resources/pod.go @@ -8,6 +8,7 @@ import ( "github.com/jenkinsci/kubernetes-operator/pkg/constants" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -88,18 +89,67 @@ func getJenkinsHomePath(jenkins *v1alpha2.Jenkins) string { return defaultJenkinsHomePath } +func validateStorageSize(storageSize string) bool { + if strings.TrimSpace(storageSize) == "" { + return false + } + _, err := resource.ParseQuantity(storageSize) + if err != nil { + return false + } + + return true +} + +// get Jenkins home storage settings from the CRD +func getJenkinsHomeStorageSettings(jenkins *v1alpha2.Jenkins) corev1.Volume { + JenkinsHomeVolume := corev1.Volume{} + emptyDirVol := corev1.Volume{ + Name: JenkinsHomeVolumeName, + VolumeSource: corev1.VolumeSource{ + // Create empty dir for Jenkins home + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + } + + if jenkins.Spec.Master.StorageSettings.UseEphemeralStorage { + if !validateStorageSize(jenkins.Spec.Master.StorageSettings.StorageRequest) { + fmt.Println("Invalid storage size %s, falling back to empty dir" + jenkins.Spec.Master.StorageSettings.StorageRequest) + JenkinsHomeVolume = emptyDirVol + return JenkinsHomeVolume + } + JenkinsHomeVolume = corev1.Volume{ + Name: JenkinsHomeVolumeName, + VolumeSource: corev1.VolumeSource{ + // Create ephemeral storage for Jenkins home + Ephemeral: &corev1.EphemeralVolumeSource{ + VolumeClaimTemplate: &corev1.PersistentVolumeClaimTemplate{ + Spec: corev1.PersistentVolumeClaimSpec{ + StorageClassName: &jenkins.Spec.Master.StorageSettings.StorageClassName, + AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: resource.MustParse(jenkins.Spec.Master.StorageSettings.StorageRequest), + }, + }, + }, + }, + }, + }, + } + } else { + JenkinsHomeVolume = emptyDirVol + } + return JenkinsHomeVolume +} + // GetJenkinsMasterPodBaseVolumes returns Jenkins master pod volumes required by operator func GetJenkinsMasterPodBaseVolumes(jenkins *v1alpha2.Jenkins) []corev1.Volume { configMapVolumeSourceDefaultMode := corev1.ConfigMapVolumeSourceDefaultMode secretVolumeSourceDefaultMode := corev1.SecretVolumeSourceDefaultMode var scriptsVolumeDefaultMode int32 = 0777 volumes := []corev1.Volume{ - { - Name: JenkinsHomeVolumeName, - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{}, - }, - }, + getJenkinsHomeStorageSettings(jenkins), { Name: jenkinsScriptsVolumeName, VolumeSource: corev1.VolumeSource{ diff --git a/pkg/configuration/base/resources/pod_test.go b/pkg/configuration/base/resources/pod_test.go index 988c9fb7..c2c35013 100644 --- a/pkg/configuration/base/resources/pod_test.go +++ b/pkg/configuration/base/resources/pod_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" - "github.com/stretchr/testify/assert" ) @@ -144,6 +143,42 @@ func TestGetJenkinsMasterPodBaseVolumes(t *testing.T) { assert.True(t, groovyExists) assert.True(t, cascExists) }) + t.Run("home volume is present and is Tempdir", func(t *testing.T) { + jenkins := &v1alpha2.Jenkins{ + Spec: v1alpha2.JenkinsSpec{ + Master: v1alpha2.JenkinsMaster{ + StorageSettings: v1alpha2.StorageSettings{ + UseTempDir: true, + }, + }, + }, + } + + HomeExist, HomeTempdirExist, HomeEphemeralStorageExist := checkHomeVolumesPresence(jenkins) + + assert.True(t, HomeExist) + assert.True(t, HomeTempdirExist) + assert.False(t, HomeEphemeralStorageExist) + }) + t.Run("home volume is present and it's ephemeral", func(t *testing.T) { + jenkins := &v1alpha2.Jenkins{ + Spec: v1alpha2.JenkinsSpec{ + Master: v1alpha2.JenkinsMaster{ + StorageSettings: v1alpha2.StorageSettings{ + UseEphemeralStorage: true, + StorageClassName: "", + StorageRequest: "1Gi", + }, + }, + }, + } + + HomeExist, HomeTempdirExist, HomeEphemeralStorageExist := checkHomeVolumesPresence(jenkins) + + assert.True(t, HomeExist) + assert.False(t, HomeTempdirExist) + assert.True(t, HomeEphemeralStorageExist) + }) } func checkSecretVolumesPresence(jenkins *v1alpha2.Jenkins) (groovyExists bool, cascExists bool) { @@ -156,3 +191,55 @@ func checkSecretVolumesPresence(jenkins *v1alpha2.Jenkins) (groovyExists bool, c } return groovyExists, cascExists } + +func checkHomeVolumesPresence(jenkins *v1alpha2.Jenkins) (HomeExist bool, HomeTempdirExist bool, HomeEphemeralStorageExist bool) { + for _, volume := range GetJenkinsMasterPodBaseVolumes(jenkins) { + if volume.Name == ("jenkins-home") { + HomeExist = true + // check if the volume is an emptyDir + if volume.VolumeSource.EmptyDir != nil { + HomeTempdirExist = true + } else if volume.VolumeSource.Ephemeral != nil { + HomeEphemeralStorageExist = true + } + } else { + HomeExist = false + HomeTempdirExist = false + HomeEphemeralStorageExist = false + } + } + return HomeExist, HomeTempdirExist, HomeEphemeralStorageExist +} + +func Test_validateStorageSize(t *testing.T) { + type args struct { + storageSize string + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "1Gi", + args: args{ + storageSize: "1Gi", + }, + want: true, + }, + { + name: "1Gi1", + args: args{ + storageSize: "1Gi1", + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := validateStorageSize(tt.args.storageSize); got != tt.want { + t.Errorf("validateStorageSize() = %v, want %v", got, tt.want) + } + }) + } +}