#9 Allow mount secrets and configmaps in Jenkins pod
This commit is contained in:
		
							parent
							
								
									af10a97299
								
							
						
					
					
						commit
						55d6aecb93
					
				|  | @ -53,6 +53,12 @@ eval $(minikube docker-env) | ||||||
| make e2e | make e2e | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | Run the specific e2e test: | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | make e2e E2E_TEST_SELECTOR='^TestConfiguration$' | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| ## Tips & Tricks | ## Tips & Tricks | ||||||
| 
 | 
 | ||||||
| ### Building docker image on minikube (for e2e tests) | ### Building docker image on minikube (for e2e tests) | ||||||
|  |  | ||||||
|  | @ -40,12 +40,13 @@ type Container struct { | ||||||
| // JenkinsMaster defines the Jenkins master pod attributes and plugins,
 | // JenkinsMaster defines the Jenkins master pod attributes and plugins,
 | ||||||
| // every single change requires Jenkins master pod restart
 | // every single change requires Jenkins master pod restart
 | ||||||
| type JenkinsMaster struct { | type JenkinsMaster struct { | ||||||
| 	Container | 	Container //TODO move to containers
 | ||||||
| 
 | 
 | ||||||
| 	// pod properties
 | 	// pod properties
 | ||||||
| 	Annotations  map[string]string `json:"masterAnnotations,omitempty"` | 	Annotations  map[string]string `json:"masterAnnotations,omitempty"` | ||||||
| 	NodeSelector map[string]string `json:"nodeSelector,omitempty"` | 	NodeSelector map[string]string `json:"nodeSelector,omitempty"` | ||||||
| 	Containers   []Container       `json:"containers,omitempty"` | 	Containers   []Container       `json:"containers,omitempty"` | ||||||
|  | 	Volumes      []corev1.Volume   `json:"volumes,omitempty"` | ||||||
| 
 | 
 | ||||||
| 	// OperatorPlugins contains plugins required by operator
 | 	// OperatorPlugins contains plugins required by operator
 | ||||||
| 	OperatorPlugins map[string][]string `json:"basePlugins,omitempty"` | 	OperatorPlugins map[string][]string `json:"basePlugins,omitempty"` | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"reflect" | 	"reflect" | ||||||
|  | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" | 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" | ||||||
|  | @ -378,6 +379,8 @@ func (r *ReconcileJenkinsBaseConfiguration) isRecreatePodNeeded(currentJenkinsMa | ||||||
| 		return true | 		return true | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	//TODO check if image can't be pulled, volume can't be mounted etc. (get info from events)
 | ||||||
|  | 
 | ||||||
| 	if currentJenkinsMasterPod.Status.Phase == corev1.PodFailed || | 	if currentJenkinsMasterPod.Status.Phase == corev1.PodFailed || | ||||||
| 		currentJenkinsMasterPod.Status.Phase == corev1.PodSucceeded || | 		currentJenkinsMasterPod.Status.Phase == corev1.PodSucceeded || | ||||||
| 		currentJenkinsMasterPod.Status.Phase == corev1.PodUnknown { | 		currentJenkinsMasterPod.Status.Phase == corev1.PodUnknown { | ||||||
|  | @ -397,6 +400,12 @@ func (r *ReconcileJenkinsBaseConfiguration) isRecreatePodNeeded(currentJenkinsMa | ||||||
| 		return true | 		return true | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if !r.compareVolumes(currentJenkinsMasterPod) { | ||||||
|  | 		r.logger.Info(fmt.Sprintf("Jenkins pod volumes have changed, actual '%+v' required '%+v', recreating pod", | ||||||
|  | 			currentJenkinsMasterPod.Spec.Volumes, r.jenkins.Spec.Master.Volumes)) | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	if (len(r.jenkins.Spec.Master.Containers) + 1) != len(currentJenkinsMasterPod.Spec.Containers) { | 	if (len(r.jenkins.Spec.Master.Containers) + 1) != len(currentJenkinsMasterPod.Spec.Containers) { | ||||||
| 		r.logger.Info(fmt.Sprintf("Jenkins amount of containers has changed to '%+v', recreating pod", len(r.jenkins.Spec.Master.Containers)+1)) | 		r.logger.Info(fmt.Sprintf("Jenkins amount of containers has changed to '%+v', recreating pod", len(r.jenkins.Spec.Master.Containers)+1)) | ||||||
| 		return true | 		return true | ||||||
|  | @ -485,7 +494,7 @@ func (r *ReconcileJenkinsBaseConfiguration) compareContainers(expected corev1.Co | ||||||
| 		return true | 		return true | ||||||
| 	} | 	} | ||||||
| 	if !CompareContainerVolumeMounts(expected, actual) { | 	if !CompareContainerVolumeMounts(expected, actual) { | ||||||
| 		r.logger.Info(fmt.Sprintf("Volume mounts has changed to '%+v' in container '%s', recreating pod", expected.VolumeMounts, expected.Name)) | 		r.logger.Info(fmt.Sprintf("Volume mounts have changed to '%+v' in container '%s', recreating pod", expected.VolumeMounts, expected.Name)) | ||||||
| 		return true | 		return true | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -504,6 +513,21 @@ func CompareContainerVolumeMounts(expected corev1.Container, actual corev1.Conta | ||||||
| 	return reflect.DeepEqual(expected.VolumeMounts, withoutServiceAccount) | 	return reflect.DeepEqual(expected.VolumeMounts, withoutServiceAccount) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // compareVolumes returns true if Jenkins pod and Jenkins CR volumes are the same
 | ||||||
|  | func (r *ReconcileJenkinsBaseConfiguration) compareVolumes(actualPod corev1.Pod) bool { | ||||||
|  | 	var withoutServiceAccount []corev1.Volume | ||||||
|  | 	for _, volume := range actualPod.Spec.Volumes { | ||||||
|  | 		if !strings.HasPrefix(volume.Name, actualPod.Spec.ServiceAccountName) { | ||||||
|  | 			withoutServiceAccount = append(withoutServiceAccount, volume) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return reflect.DeepEqual( | ||||||
|  | 		append(resources.GetJenkinsMasterPodBaseVolumes(r.jenkins), r.jenkins.Spec.Master.Volumes...), | ||||||
|  | 		withoutServiceAccount, | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (r *ReconcileJenkinsBaseConfiguration) restartJenkinsMasterPod(meta metav1.ObjectMeta) error { | func (r *ReconcileJenkinsBaseConfiguration) restartJenkinsMasterPod(meta metav1.ObjectMeta) error { | ||||||
| 	currentJenkinsMasterPod, err := r.getJenkinsMasterPod(meta) | 	currentJenkinsMasterPod, err := r.getJenkinsMasterPod(meta) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  |  | ||||||
|  | @ -3,6 +3,9 @@ package base | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" | ||||||
|  | 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources" | ||||||
|  | 
 | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| 	corev1 "k8s.io/api/core/v1" | 	corev1 "k8s.io/api/core/v1" | ||||||
| ) | ) | ||||||
|  | @ -81,3 +84,68 @@ func TestCompareContainerVolumeMounts(t *testing.T) { | ||||||
| 		assert.False(t, got) | 		assert.False(t, got) | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func TestCompareVolumes(t *testing.T) { | ||||||
|  | 	t.Run("defaults", func(t *testing.T) { | ||||||
|  | 		jenkins := &v1alpha1.Jenkins{} | ||||||
|  | 		pod := corev1.Pod{ | ||||||
|  | 			Spec: corev1.PodSpec{ | ||||||
|  | 				ServiceAccountName: "service-account-name", | ||||||
|  | 				Volumes:            resources.GetJenkinsMasterPodBaseVolumes(jenkins), | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		reconciler := New(nil, nil, nil, jenkins, false, false) | ||||||
|  | 
 | ||||||
|  | 		got := reconciler.compareVolumes(pod) | ||||||
|  | 
 | ||||||
|  | 		assert.True(t, got) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("different", func(t *testing.T) { | ||||||
|  | 		jenkins := &v1alpha1.Jenkins{ | ||||||
|  | 			Spec: v1alpha1.JenkinsSpec{ | ||||||
|  | 				Master: v1alpha1.JenkinsMaster{ | ||||||
|  | 					Volumes: []corev1.Volume{ | ||||||
|  | 						{ | ||||||
|  | 							Name: "added", | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		pod := corev1.Pod{ | ||||||
|  | 			Spec: corev1.PodSpec{ | ||||||
|  | 				ServiceAccountName: "service-account-name", | ||||||
|  | 				Volumes:            resources.GetJenkinsMasterPodBaseVolumes(jenkins), | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		reconciler := New(nil, nil, nil, jenkins, false, false) | ||||||
|  | 
 | ||||||
|  | 		got := reconciler.compareVolumes(pod) | ||||||
|  | 
 | ||||||
|  | 		assert.False(t, got) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("added one volume", func(t *testing.T) { | ||||||
|  | 		jenkins := &v1alpha1.Jenkins{ | ||||||
|  | 			Spec: v1alpha1.JenkinsSpec{ | ||||||
|  | 				Master: v1alpha1.JenkinsMaster{ | ||||||
|  | 					Volumes: []corev1.Volume{ | ||||||
|  | 						{ | ||||||
|  | 							Name: "added", | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		pod := corev1.Pod{ | ||||||
|  | 			Spec: corev1.PodSpec{ | ||||||
|  | 				ServiceAccountName: "service-account-name", | ||||||
|  | 				Volumes:            append(resources.GetJenkinsMasterPodBaseVolumes(jenkins), corev1.Volume{Name: "added"}), | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		reconciler := New(nil, nil, nil, jenkins, false, false) | ||||||
|  | 
 | ||||||
|  | 		got := reconciler.compareVolumes(pod) | ||||||
|  | 
 | ||||||
|  | 		assert.True(t, got) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -13,7 +13,8 @@ import ( | ||||||
| const ( | const ( | ||||||
| 	// JenkinsMasterContainerName is the Jenkins master container name in pod
 | 	// JenkinsMasterContainerName is the Jenkins master container name in pod
 | ||||||
| 	JenkinsMasterContainerName = "jenkins-master" | 	JenkinsMasterContainerName = "jenkins-master" | ||||||
| 	jenkinsHomeVolumeName      = "home" | 	// JenkinsHomeVolumeName is the Jenkins home volume name
 | ||||||
|  | 	JenkinsHomeVolumeName = "home" | ||||||
| 	jenkinsPath           = "/var/jenkins" | 	jenkinsPath           = "/var/jenkins" | ||||||
| 	jenkinsHomePath       = jenkinsPath + "/home" | 	jenkinsHomePath       = jenkinsPath + "/home" | ||||||
| 
 | 
 | ||||||
|  | @ -74,38 +75,87 @@ func GetJenkinsMasterPodBaseEnvs() []corev1.EnvVar { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewJenkinsMasterContainer returns Jenkins master Kubernetes container
 | // GetJenkinsMasterPodBaseVolumes returns Jenkins master pod volumes required by operator
 | ||||||
| func NewJenkinsMasterContainer(jenkins *v1alpha1.Jenkins) corev1.Container { | func GetJenkinsMasterPodBaseVolumes(jenkins *v1alpha1.Jenkins) []corev1.Volume { | ||||||
| 	envs := GetJenkinsMasterPodBaseEnvs() | 	configMapVolumeSourceDefaultMode := corev1.ConfigMapVolumeSourceDefaultMode | ||||||
| 	envs = append(envs, jenkins.Spec.Master.Env...) | 	secretVolumeSourceDefaultMode := corev1.SecretVolumeSourceDefaultMode | ||||||
|  | 	return []corev1.Volume{ | ||||||
|  | 		{ | ||||||
|  | 			Name: JenkinsHomeVolumeName, | ||||||
|  | 			VolumeSource: corev1.VolumeSource{ | ||||||
|  | 				EmptyDir: &corev1.EmptyDirVolumeSource{}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Name: jenkinsScriptsVolumeName, | ||||||
|  | 			VolumeSource: corev1.VolumeSource{ | ||||||
|  | 				ConfigMap: &corev1.ConfigMapVolumeSource{ | ||||||
|  | 					DefaultMode: &configMapVolumeSourceDefaultMode, | ||||||
|  | 					LocalObjectReference: corev1.LocalObjectReference{ | ||||||
|  | 						Name: getScriptsConfigMapName(jenkins), | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Name: jenkinsInitConfigurationVolumeName, | ||||||
|  | 			VolumeSource: corev1.VolumeSource{ | ||||||
|  | 				ConfigMap: &corev1.ConfigMapVolumeSource{ | ||||||
|  | 					DefaultMode: &configMapVolumeSourceDefaultMode, | ||||||
|  | 					LocalObjectReference: corev1.LocalObjectReference{ | ||||||
|  | 						Name: GetInitConfigurationConfigMapName(jenkins), | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Name: jenkinsBaseConfigurationVolumeName, | ||||||
|  | 			VolumeSource: corev1.VolumeSource{ | ||||||
|  | 				ConfigMap: &corev1.ConfigMapVolumeSource{ | ||||||
|  | 					DefaultMode: &configMapVolumeSourceDefaultMode, | ||||||
|  | 					LocalObjectReference: corev1.LocalObjectReference{ | ||||||
|  | 						Name: GetBaseConfigurationConfigMapName(jenkins), | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Name: jenkinsUserConfigurationVolumeName, | ||||||
|  | 			VolumeSource: corev1.VolumeSource{ | ||||||
|  | 				ConfigMap: &corev1.ConfigMapVolumeSource{ | ||||||
|  | 					DefaultMode: &configMapVolumeSourceDefaultMode, | ||||||
|  | 					LocalObjectReference: corev1.LocalObjectReference{ | ||||||
|  | 						Name: GetUserConfigurationConfigMapNameFromJenkins(jenkins), | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Name: jenkinsOperatorCredentialsVolumeName, | ||||||
|  | 			VolumeSource: corev1.VolumeSource{ | ||||||
|  | 				Secret: &corev1.SecretVolumeSource{ | ||||||
|  | 					DefaultMode: &secretVolumeSourceDefaultMode, | ||||||
|  | 					SecretName:  GetOperatorCredentialsSecretName(jenkins), | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Name: userConfigurationSecretVolumeName, | ||||||
|  | 			VolumeSource: corev1.VolumeSource{ | ||||||
|  | 				Secret: &corev1.SecretVolumeSource{ | ||||||
|  | 					DefaultMode: &secretVolumeSourceDefaultMode, | ||||||
|  | 					SecretName:  GetUserConfigurationSecretNameFromJenkins(jenkins), | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| 	return corev1.Container{ | // GetJenkinsMasterContainerBaseVolumeMounts returns Jenkins master pod volume mounts required by operator
 | ||||||
| 		Name:            JenkinsMasterContainerName, | func GetJenkinsMasterContainerBaseVolumeMounts() []corev1.VolumeMount { | ||||||
| 		Image:           jenkins.Spec.Master.Image, | 	return []corev1.VolumeMount{ | ||||||
| 		ImagePullPolicy: jenkins.Spec.Master.ImagePullPolicy, |  | ||||||
| 		Command: []string{ |  | ||||||
| 			"bash", |  | ||||||
| 			fmt.Sprintf("%s/%s", jenkinsScriptsVolumePath, initScriptName), |  | ||||||
| 		}, |  | ||||||
| 		LivenessProbe:  jenkins.Spec.Master.LivenessProbe, |  | ||||||
| 		ReadinessProbe: jenkins.Spec.Master.ReadinessProbe, |  | ||||||
| 		Ports: []corev1.ContainerPort{ |  | ||||||
| 		{ | 		{ | ||||||
| 				Name:          httpPortName, | 			Name:      JenkinsHomeVolumeName, | ||||||
| 				ContainerPort: constants.DefaultHTTPPortInt32, |  | ||||||
| 				Protocol:      corev1.ProtocolTCP, |  | ||||||
| 			}, |  | ||||||
| 			{ |  | ||||||
| 				Name:          slavePortName, |  | ||||||
| 				ContainerPort: constants.DefaultSlavePortInt32, |  | ||||||
| 				Protocol:      corev1.ProtocolTCP, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 		Env:       envs, |  | ||||||
| 		Resources: jenkins.Spec.Master.Resources, |  | ||||||
| 		VolumeMounts: []corev1.VolumeMount{ |  | ||||||
| 			{ |  | ||||||
| 				Name:      jenkinsHomeVolumeName, |  | ||||||
| 			MountPath: jenkinsHomePath, | 			MountPath: jenkinsHomePath, | ||||||
| 			ReadOnly:  false, | 			ReadOnly:  false, | ||||||
| 		}, | 		}, | ||||||
|  | @ -139,7 +189,39 @@ func NewJenkinsMasterContainer(jenkins *v1alpha1.Jenkins) corev1.Container { | ||||||
| 			MountPath: UserConfigurationSecretVolumePath, | 			MountPath: UserConfigurationSecretVolumePath, | ||||||
| 			ReadOnly:  true, | 			ReadOnly:  true, | ||||||
| 		}, | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewJenkinsMasterContainer returns Jenkins master Kubernetes container
 | ||||||
|  | func NewJenkinsMasterContainer(jenkins *v1alpha1.Jenkins) corev1.Container { | ||||||
|  | 	envs := GetJenkinsMasterPodBaseEnvs() | ||||||
|  | 	envs = append(envs, jenkins.Spec.Master.Env...) | ||||||
|  | 
 | ||||||
|  | 	return corev1.Container{ | ||||||
|  | 		Name:            JenkinsMasterContainerName, | ||||||
|  | 		Image:           jenkins.Spec.Master.Image, | ||||||
|  | 		ImagePullPolicy: jenkins.Spec.Master.ImagePullPolicy, | ||||||
|  | 		Command: []string{ | ||||||
|  | 			"bash", | ||||||
|  | 			fmt.Sprintf("%s/%s", jenkinsScriptsVolumePath, initScriptName), | ||||||
| 		}, | 		}, | ||||||
|  | 		LivenessProbe:  jenkins.Spec.Master.LivenessProbe, | ||||||
|  | 		ReadinessProbe: jenkins.Spec.Master.ReadinessProbe, | ||||||
|  | 		Ports: []corev1.ContainerPort{ | ||||||
|  | 			{ | ||||||
|  | 				Name:          httpPortName, | ||||||
|  | 				ContainerPort: constants.DefaultHTTPPortInt32, | ||||||
|  | 				Protocol:      corev1.ProtocolTCP, | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				Name:          slavePortName, | ||||||
|  | 				ContainerPort: constants.DefaultSlavePortInt32, | ||||||
|  | 				Protocol:      corev1.ProtocolTCP, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		Env:          envs, | ||||||
|  | 		Resources:    jenkins.Spec.Master.Resources, | ||||||
|  | 		VolumeMounts: append(GetJenkinsMasterContainerBaseVolumeMounts(), jenkins.Spec.Master.VolumeMounts...), | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -192,70 +274,7 @@ func NewJenkinsMasterPod(objectMeta metav1.ObjectMeta, jenkins *v1alpha1.Jenkins | ||||||
| 			}, | 			}, | ||||||
| 			NodeSelector: jenkins.Spec.Master.NodeSelector, | 			NodeSelector: jenkins.Spec.Master.NodeSelector, | ||||||
| 			Containers:   newContainers(jenkins), | 			Containers:   newContainers(jenkins), | ||||||
| 			Volumes: []corev1.Volume{ | 			Volumes:      append(GetJenkinsMasterPodBaseVolumes(jenkins), jenkins.Spec.Master.Volumes...), | ||||||
| 				{ |  | ||||||
| 					Name: jenkinsHomeVolumeName, |  | ||||||
| 					VolumeSource: corev1.VolumeSource{ |  | ||||||
| 						EmptyDir: &corev1.EmptyDirVolumeSource{}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 				{ |  | ||||||
| 					Name: jenkinsScriptsVolumeName, |  | ||||||
| 					VolumeSource: corev1.VolumeSource{ |  | ||||||
| 						ConfigMap: &corev1.ConfigMapVolumeSource{ |  | ||||||
| 							LocalObjectReference: corev1.LocalObjectReference{ |  | ||||||
| 								Name: getScriptsConfigMapName(jenkins), |  | ||||||
| 							}, |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 				{ |  | ||||||
| 					Name: jenkinsInitConfigurationVolumeName, |  | ||||||
| 					VolumeSource: corev1.VolumeSource{ |  | ||||||
| 						ConfigMap: &corev1.ConfigMapVolumeSource{ |  | ||||||
| 							LocalObjectReference: corev1.LocalObjectReference{ |  | ||||||
| 								Name: GetInitConfigurationConfigMapName(jenkins), |  | ||||||
| 							}, |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 				{ |  | ||||||
| 					Name: jenkinsBaseConfigurationVolumeName, |  | ||||||
| 					VolumeSource: corev1.VolumeSource{ |  | ||||||
| 						ConfigMap: &corev1.ConfigMapVolumeSource{ |  | ||||||
| 							LocalObjectReference: corev1.LocalObjectReference{ |  | ||||||
| 								Name: GetBaseConfigurationConfigMapName(jenkins), |  | ||||||
| 							}, |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 				{ |  | ||||||
| 					Name: jenkinsUserConfigurationVolumeName, |  | ||||||
| 					VolumeSource: corev1.VolumeSource{ |  | ||||||
| 						ConfigMap: &corev1.ConfigMapVolumeSource{ |  | ||||||
| 							LocalObjectReference: corev1.LocalObjectReference{ |  | ||||||
| 								Name: GetUserConfigurationConfigMapNameFromJenkins(jenkins), |  | ||||||
| 							}, |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 				{ |  | ||||||
| 					Name: jenkinsOperatorCredentialsVolumeName, |  | ||||||
| 					VolumeSource: corev1.VolumeSource{ |  | ||||||
| 						Secret: &corev1.SecretVolumeSource{ |  | ||||||
| 							SecretName: GetOperatorCredentialsSecretName(jenkins), |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 				{ |  | ||||||
| 					Name: userConfigurationSecretVolumeName, |  | ||||||
| 					VolumeSource: corev1.VolumeSource{ |  | ||||||
| 						Secret: &corev1.SecretVolumeSource{ |  | ||||||
| 							SecretName: GetUserConfigurationSecretNameFromJenkins(jenkins), |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| package base | package base | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"regexp" | 	"regexp" | ||||||
| 
 | 
 | ||||||
|  | @ -10,6 +11,10 @@ import ( | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/log" | 	"github.com/jenkinsci/kubernetes-operator/pkg/log" | ||||||
| 
 | 
 | ||||||
| 	docker "github.com/docker/distribution/reference" | 	docker "github.com/docker/distribution/reference" | ||||||
|  | 	stackerr "github.com/pkg/errors" | ||||||
|  | 	corev1 "k8s.io/api/core/v1" | ||||||
|  | 	apierrors "k8s.io/apimachinery/pkg/api/errors" | ||||||
|  | 	"k8s.io/apimachinery/pkg/types" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var ( | var ( | ||||||
|  | @ -18,6 +23,15 @@ var ( | ||||||
| 
 | 
 | ||||||
| // Validate validates Jenkins CR Spec.master section
 | // Validate validates Jenkins CR Spec.master section
 | ||||||
| func (r *ReconcileJenkinsBaseConfiguration) Validate(jenkins *v1alpha1.Jenkins) (bool, error) { | func (r *ReconcileJenkinsBaseConfiguration) Validate(jenkins *v1alpha1.Jenkins) (bool, error) { | ||||||
|  | 	if !r.validateReservedVolumes() { | ||||||
|  | 		return false, nil | ||||||
|  | 	} | ||||||
|  | 	if valid, err := r.validateVolumes(); err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} else if !valid { | ||||||
|  | 		return false, nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	if !r.validateContainer(jenkins.Spec.Master.Container) { | 	if !r.validateContainer(jenkins.Spec.Master.Container) { | ||||||
| 		return false, nil | 		return false, nil | ||||||
| 	} | 	} | ||||||
|  | @ -39,6 +53,80 @@ func (r *ReconcileJenkinsBaseConfiguration) Validate(jenkins *v1alpha1.Jenkins) | ||||||
| 	return true, nil | 	return true, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (r *ReconcileJenkinsBaseConfiguration) validateVolumes() (bool, error) { | ||||||
|  | 	valid := true | ||||||
|  | 	for _, volume := range r.jenkins.Spec.Master.Volumes { | ||||||
|  | 		switch { | ||||||
|  | 		case volume.ConfigMap != nil: | ||||||
|  | 			if ok, err := r.validateConfigMapVolume(volume); err != nil { | ||||||
|  | 				return false, err | ||||||
|  | 			} else if !ok { | ||||||
|  | 				valid = false | ||||||
|  | 			} | ||||||
|  | 		case volume.Secret != nil: | ||||||
|  | 			if ok, err := r.validateSecretVolume(volume); err != nil { | ||||||
|  | 				return false, err | ||||||
|  | 			} else if !ok { | ||||||
|  | 				valid = false | ||||||
|  | 			} | ||||||
|  | 		default: //TODO add support for rest of volumes
 | ||||||
|  | 			valid = false | ||||||
|  | 			r.logger.V(log.VWarn).Info(fmt.Sprintf("Unsupported volume '%+v'", volume)) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return valid, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *ReconcileJenkinsBaseConfiguration) validateConfigMapVolume(volume corev1.Volume) (bool, error) { | ||||||
|  | 	if volume.ConfigMap.Optional != nil && *volume.ConfigMap.Optional { | ||||||
|  | 		return true, nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	configMap := &corev1.ConfigMap{} | ||||||
|  | 	err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: volume.ConfigMap.Name, Namespace: r.jenkins.ObjectMeta.Namespace}, configMap) | ||||||
|  | 	if err != nil && apierrors.IsNotFound(err) { | ||||||
|  | 		r.logger.V(log.VWarn).Info(fmt.Sprintf("ConfigMap '%s' not found for volume '%+v'", volume.ConfigMap.Name, volume)) | ||||||
|  | 		return false, nil | ||||||
|  | 	} else if err != nil && !apierrors.IsNotFound(err) { | ||||||
|  | 		return false, stackerr.WithStack(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return true, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *ReconcileJenkinsBaseConfiguration) validateSecretVolume(volume corev1.Volume) (bool, error) { | ||||||
|  | 	if volume.Secret.Optional != nil && *volume.Secret.Optional { | ||||||
|  | 		return true, nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	secret := &corev1.Secret{} | ||||||
|  | 	err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: volume.Secret.SecretName, Namespace: r.jenkins.ObjectMeta.Namespace}, secret) | ||||||
|  | 	if err != nil && apierrors.IsNotFound(err) { | ||||||
|  | 		r.logger.V(log.VWarn).Info(fmt.Sprintf("Secret '%s' not found for volume '%+v'", volume.Secret.SecretName, volume)) | ||||||
|  | 		return false, nil | ||||||
|  | 	} else if err != nil && !apierrors.IsNotFound(err) { | ||||||
|  | 		return false, stackerr.WithStack(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return true, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *ReconcileJenkinsBaseConfiguration) validateReservedVolumes() bool { | ||||||
|  | 	valid := true | ||||||
|  | 
 | ||||||
|  | 	for _, baseVolume := range resources.GetJenkinsMasterPodBaseVolumes(r.jenkins) { | ||||||
|  | 		for _, volume := range r.jenkins.Spec.Master.Volumes { | ||||||
|  | 			if baseVolume.Name == volume.Name { | ||||||
|  | 				r.logger.V(log.VWarn).Info(fmt.Sprintf("Jenkins Master pod volume '%s' is reserved please choose different one", volume.Name)) | ||||||
|  | 				valid = false | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return valid | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (r *ReconcileJenkinsBaseConfiguration) validateContainer(container v1alpha1.Container) bool { | func (r *ReconcileJenkinsBaseConfiguration) validateContainer(container v1alpha1.Container) bool { | ||||||
| 	logger := r.logger.WithValues("container", container.Name) | 	logger := r.logger.WithValues("container", container.Name) | ||||||
| 	if container.Image == "" { | 	if container.Image == "" { | ||||||
|  | @ -47,7 +135,7 @@ func (r *ReconcileJenkinsBaseConfiguration) validateContainer(container v1alpha1 | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if !dockerImageRegexp.MatchString(container.Image) && !docker.ReferenceRegexp.MatchString(container.Image) { | 	if !dockerImageRegexp.MatchString(container.Image) && !docker.ReferenceRegexp.MatchString(container.Image) { | ||||||
| 		r.logger.V(log.VWarn).Info("Invalid image") | 		logger.V(log.VWarn).Info("Invalid image") | ||||||
| 		return false | 		return false | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -56,9 +144,40 @@ func (r *ReconcileJenkinsBaseConfiguration) validateContainer(container v1alpha1 | ||||||
| 		return false | 		return false | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if !r.validateContainerVolumeMounts(container) { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	return true | 	return true | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (r *ReconcileJenkinsBaseConfiguration) validateContainerVolumeMounts(container v1alpha1.Container) bool { | ||||||
|  | 	logger := r.logger.WithValues("container", container.Name) | ||||||
|  | 	allVolumes := append(resources.GetJenkinsMasterPodBaseVolumes(r.jenkins), r.jenkins.Spec.Master.Volumes...) | ||||||
|  | 	valid := true | ||||||
|  | 
 | ||||||
|  | 	for _, volumeMount := range container.VolumeMounts { | ||||||
|  | 		if len(volumeMount.MountPath) == 0 { | ||||||
|  | 			logger.V(log.VWarn).Info(fmt.Sprintf("mountPath not set for '%s' volume mount", volumeMount.Name)) | ||||||
|  | 			valid = false | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		foundVolume := false | ||||||
|  | 		for _, volume := range allVolumes { | ||||||
|  | 			if volumeMount.Name == volume.Name { | ||||||
|  | 				foundVolume = true | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if !foundVolume { | ||||||
|  | 			logger.V(log.VWarn).Info(fmt.Sprintf("Not found volume for '%s' volume mount", volumeMount.Name)) | ||||||
|  | 			valid = false | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return valid | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (r *ReconcileJenkinsBaseConfiguration) validateJenkinsMasterPodEnvs() bool { | func (r *ReconcileJenkinsBaseConfiguration) validateJenkinsMasterPodEnvs() bool { | ||||||
| 	baseEnvs := resources.GetJenkinsMasterPodBaseEnvs() | 	baseEnvs := resources.GetJenkinsMasterPodBaseEnvs() | ||||||
| 	baseEnvNames := map[string]string{} | 	baseEnvNames := map[string]string{} | ||||||
|  |  | ||||||
|  | @ -1,12 +1,17 @@ | ||||||
| package base | package base | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" | 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" | ||||||
|  | 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources" | ||||||
| 
 | 
 | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| 	"k8s.io/api/core/v1" | 	"k8s.io/api/core/v1" | ||||||
|  | 	corev1 "k8s.io/api/core/v1" | ||||||
|  | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
|  | 	"sigs.k8s.io/controller-runtime/pkg/client/fake" | ||||||
| 	logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" | 	logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -111,3 +116,266 @@ func TestValidateJenkinsMasterPodEnvs(t *testing.T) { | ||||||
| 		assert.Equal(t, false, got) | 		assert.Equal(t, false, got) | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func TestValidateReservedVolumes(t *testing.T) { | ||||||
|  | 	t.Run("happy", func(t *testing.T) { | ||||||
|  | 		jenkins := v1alpha1.Jenkins{ | ||||||
|  | 			Spec: v1alpha1.JenkinsSpec{ | ||||||
|  | 				Master: v1alpha1.JenkinsMaster{ | ||||||
|  | 					Volumes: []v1.Volume{ | ||||||
|  | 						{ | ||||||
|  | 							Name: "not-used-name", | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		baseReconcileLoop := New(nil, nil, logf.ZapLogger(false), | ||||||
|  | 			&jenkins, false, false) | ||||||
|  | 		got := baseReconcileLoop.validateReservedVolumes() | ||||||
|  | 		assert.Equal(t, true, got) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("used reserved name", func(t *testing.T) { | ||||||
|  | 		jenkins := v1alpha1.Jenkins{ | ||||||
|  | 			Spec: v1alpha1.JenkinsSpec{ | ||||||
|  | 				Master: v1alpha1.JenkinsMaster{ | ||||||
|  | 					Volumes: []v1.Volume{ | ||||||
|  | 						{ | ||||||
|  | 							Name: resources.JenkinsHomeVolumeName, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		baseReconcileLoop := New(nil, nil, logf.ZapLogger(false), | ||||||
|  | 			&jenkins, false, false) | ||||||
|  | 		got := baseReconcileLoop.validateReservedVolumes() | ||||||
|  | 		assert.Equal(t, false, got) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestValidateContainerVolumeMounts(t *testing.T) { | ||||||
|  | 	t.Run("default Jenkins master container", func(t *testing.T) { | ||||||
|  | 		jenkins := v1alpha1.Jenkins{ | ||||||
|  | 			Spec: v1alpha1.JenkinsSpec{ | ||||||
|  | 				Master: v1alpha1.JenkinsMaster{}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		baseReconcileLoop := New(nil, nil, logf.ZapLogger(false), | ||||||
|  | 			&jenkins, false, false) | ||||||
|  | 		got := baseReconcileLoop.validateContainerVolumeMounts(jenkins.Spec.Master.Container) | ||||||
|  | 		assert.Equal(t, true, got) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("one extra volume", func(t *testing.T) { | ||||||
|  | 		jenkins := v1alpha1.Jenkins{ | ||||||
|  | 			Spec: v1alpha1.JenkinsSpec{ | ||||||
|  | 				Master: v1alpha1.JenkinsMaster{ | ||||||
|  | 					Volumes: []v1.Volume{ | ||||||
|  | 						{ | ||||||
|  | 							Name: "example", | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 					Container: v1alpha1.Container{ | ||||||
|  | 						VolumeMounts: []v1.VolumeMount{ | ||||||
|  | 							{ | ||||||
|  | 								Name:      "example", | ||||||
|  | 								MountPath: "/test", | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		baseReconcileLoop := New(nil, nil, logf.ZapLogger(false), | ||||||
|  | 			&jenkins, false, false) | ||||||
|  | 		got := baseReconcileLoop.validateContainerVolumeMounts(jenkins.Spec.Master.Container) | ||||||
|  | 		assert.Equal(t, true, got) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("empty mountPath", func(t *testing.T) { | ||||||
|  | 		jenkins := v1alpha1.Jenkins{ | ||||||
|  | 			Spec: v1alpha1.JenkinsSpec{ | ||||||
|  | 				Master: v1alpha1.JenkinsMaster{ | ||||||
|  | 					Volumes: []v1.Volume{ | ||||||
|  | 						{ | ||||||
|  | 							Name: "example", | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 					Container: v1alpha1.Container{ | ||||||
|  | 						VolumeMounts: []v1.VolumeMount{ | ||||||
|  | 							{ | ||||||
|  | 								Name:      "example", | ||||||
|  | 								MountPath: "", // empty
 | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		baseReconcileLoop := New(nil, nil, logf.ZapLogger(false), | ||||||
|  | 			&jenkins, false, false) | ||||||
|  | 		got := baseReconcileLoop.validateContainerVolumeMounts(jenkins.Spec.Master.Container) | ||||||
|  | 		assert.Equal(t, false, got) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("missing volume", func(t *testing.T) { | ||||||
|  | 		jenkins := v1alpha1.Jenkins{ | ||||||
|  | 			Spec: v1alpha1.JenkinsSpec{ | ||||||
|  | 				Master: v1alpha1.JenkinsMaster{ | ||||||
|  | 					Container: v1alpha1.Container{ | ||||||
|  | 						VolumeMounts: []v1.VolumeMount{ | ||||||
|  | 							{ | ||||||
|  | 								Name:      "missing-volume", | ||||||
|  | 								MountPath: "/test", | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		baseReconcileLoop := New(nil, nil, logf.ZapLogger(false), | ||||||
|  | 			&jenkins, false, false) | ||||||
|  | 		got := baseReconcileLoop.validateContainerVolumeMounts(jenkins.Spec.Master.Container) | ||||||
|  | 		assert.Equal(t, false, got) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestValidateConfigMapVolume(t *testing.T) { | ||||||
|  | 	namespace := "default" | ||||||
|  | 	t.Run("optional", func(t *testing.T) { | ||||||
|  | 		optional := true | ||||||
|  | 		volume := corev1.Volume{ | ||||||
|  | 			Name: "name", | ||||||
|  | 			VolumeSource: corev1.VolumeSource{ | ||||||
|  | 				ConfigMap: &corev1.ConfigMapVolumeSource{ | ||||||
|  | 					Optional: &optional, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		fakeClient := fake.NewFakeClient() | ||||||
|  | 		baseReconcileLoop := New(fakeClient, nil, logf.ZapLogger(false), | ||||||
|  | 			nil, false, false) | ||||||
|  | 
 | ||||||
|  | 		got, err := baseReconcileLoop.validateConfigMapVolume(volume) | ||||||
|  | 
 | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.True(t, got) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("happy, required", func(t *testing.T) { | ||||||
|  | 		optional := false | ||||||
|  | 		configMap := corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: "configmap-name"}} | ||||||
|  | 		jenkins := &v1alpha1.Jenkins{ObjectMeta: metav1.ObjectMeta{Namespace: namespace}} | ||||||
|  | 		volume := corev1.Volume{ | ||||||
|  | 			Name: "volume-name", | ||||||
|  | 			VolumeSource: corev1.VolumeSource{ | ||||||
|  | 				ConfigMap: &corev1.ConfigMapVolumeSource{ | ||||||
|  | 					Optional: &optional, | ||||||
|  | 					LocalObjectReference: corev1.LocalObjectReference{ | ||||||
|  | 						Name: configMap.Name, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		fakeClient := fake.NewFakeClient() | ||||||
|  | 		err := fakeClient.Create(context.TODO(), &configMap) | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		baseReconcileLoop := New(fakeClient, nil, logf.ZapLogger(false), | ||||||
|  | 			jenkins, false, false) | ||||||
|  | 
 | ||||||
|  | 		got, err := baseReconcileLoop.validateConfigMapVolume(volume) | ||||||
|  | 
 | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.True(t, got) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("missing configmap", func(t *testing.T) { | ||||||
|  | 		optional := false | ||||||
|  | 		configMap := corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: "configmap-name"}} | ||||||
|  | 		jenkins := &v1alpha1.Jenkins{ObjectMeta: metav1.ObjectMeta{Namespace: namespace}} | ||||||
|  | 		volume := corev1.Volume{ | ||||||
|  | 			Name: "volume-name", | ||||||
|  | 			VolumeSource: corev1.VolumeSource{ | ||||||
|  | 				ConfigMap: &corev1.ConfigMapVolumeSource{ | ||||||
|  | 					Optional: &optional, | ||||||
|  | 					LocalObjectReference: corev1.LocalObjectReference{ | ||||||
|  | 						Name: configMap.Name, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		fakeClient := fake.NewFakeClient() | ||||||
|  | 		baseReconcileLoop := New(fakeClient, nil, logf.ZapLogger(false), | ||||||
|  | 			jenkins, false, false) | ||||||
|  | 
 | ||||||
|  | 		got, err := baseReconcileLoop.validateConfigMapVolume(volume) | ||||||
|  | 
 | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.False(t, got) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestValidateSecretVolume(t *testing.T) { | ||||||
|  | 	namespace := "default" | ||||||
|  | 	t.Run("optional", func(t *testing.T) { | ||||||
|  | 		optional := true | ||||||
|  | 		volume := corev1.Volume{ | ||||||
|  | 			Name: "name", | ||||||
|  | 			VolumeSource: corev1.VolumeSource{ | ||||||
|  | 				Secret: &corev1.SecretVolumeSource{ | ||||||
|  | 					Optional: &optional, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		fakeClient := fake.NewFakeClient() | ||||||
|  | 		baseReconcileLoop := New(fakeClient, nil, logf.ZapLogger(false), | ||||||
|  | 			nil, false, false) | ||||||
|  | 
 | ||||||
|  | 		got, err := baseReconcileLoop.validateSecretVolume(volume) | ||||||
|  | 
 | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.True(t, got) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("happy, required", func(t *testing.T) { | ||||||
|  | 		optional := false | ||||||
|  | 		secret := corev1.Secret{ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: "secret-name"}} | ||||||
|  | 		jenkins := &v1alpha1.Jenkins{ObjectMeta: metav1.ObjectMeta{Namespace: namespace}} | ||||||
|  | 		volume := corev1.Volume{ | ||||||
|  | 			Name: "volume-name", | ||||||
|  | 			VolumeSource: corev1.VolumeSource{ | ||||||
|  | 				Secret: &corev1.SecretVolumeSource{ | ||||||
|  | 					Optional:   &optional, | ||||||
|  | 					SecretName: secret.Name, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		fakeClient := fake.NewFakeClient() | ||||||
|  | 		err := fakeClient.Create(context.TODO(), &secret) | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		baseReconcileLoop := New(fakeClient, nil, logf.ZapLogger(false), | ||||||
|  | 			jenkins, false, false) | ||||||
|  | 
 | ||||||
|  | 		got, err := baseReconcileLoop.validateSecretVolume(volume) | ||||||
|  | 
 | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.True(t, got) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("missing secret", func(t *testing.T) { | ||||||
|  | 		optional := false | ||||||
|  | 		secret := corev1.Secret{ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: "secret-name"}} | ||||||
|  | 		jenkins := &v1alpha1.Jenkins{ObjectMeta: metav1.ObjectMeta{Namespace: namespace}} | ||||||
|  | 		volume := corev1.Volume{ | ||||||
|  | 			Name: "volume-name", | ||||||
|  | 			VolumeSource: corev1.VolumeSource{ | ||||||
|  | 				Secret: &corev1.SecretVolumeSource{ | ||||||
|  | 					Optional:   &optional, | ||||||
|  | 					SecretName: secret.Name, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		fakeClient := fake.NewFakeClient() | ||||||
|  | 		baseReconcileLoop := New(fakeClient, nil, logf.ZapLogger(false), | ||||||
|  | 			jenkins, false, false) | ||||||
|  | 
 | ||||||
|  | 		got, err := baseReconcileLoop.validateSecretVolume(volume) | ||||||
|  | 
 | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.False(t, got) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -40,11 +40,31 @@ func TestConfiguration(t *testing.T) { | ||||||
| 			RepositoryURL:         "https://github.com/jenkinsci/kubernetes-operator.git", | 			RepositoryURL:         "https://github.com/jenkinsci/kubernetes-operator.git", | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  | 	volumes := []corev1.Volume{ | ||||||
|  | 		{ | ||||||
|  | 			Name: "test-configmap", | ||||||
|  | 			VolumeSource: corev1.VolumeSource{ | ||||||
|  | 				ConfigMap: &corev1.ConfigMapVolumeSource{ | ||||||
|  | 					LocalObjectReference: corev1.LocalObjectReference{ | ||||||
|  | 						Name: resources.GetUserConfigurationConfigMapName(jenkinsCRName), | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Name: "test-secret", | ||||||
|  | 			VolumeSource: corev1.VolumeSource{ | ||||||
|  | 				Secret: &corev1.SecretVolumeSource{ | ||||||
|  | 					SecretName: resources.GetUserConfigurationSecretName(jenkinsCRName), | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	// base
 | 	// base
 | ||||||
| 	createUserConfigurationSecret(t, jenkinsCRName, namespace, systemMessageEnvName, systemMessage) | 	createUserConfigurationSecret(t, jenkinsCRName, namespace, systemMessageEnvName, systemMessage) | ||||||
| 	createUserConfigurationConfigMap(t, jenkinsCRName, namespace, numberOfExecutors, fmt.Sprintf("${%s}", systemMessageEnvName)) | 	createUserConfigurationConfigMap(t, jenkinsCRName, namespace, numberOfExecutors, fmt.Sprintf("${%s}", systemMessageEnvName)) | ||||||
| 	jenkins := createJenkinsCR(t, jenkinsCRName, namespace, &[]v1alpha1.SeedJob{mySeedJob.SeedJob}) | 	jenkins := createJenkinsCR(t, jenkinsCRName, namespace, &[]v1alpha1.SeedJob{mySeedJob.SeedJob}, volumes) | ||||||
| 	createDefaultLimitsForContainersInNamespace(t, namespace) | 	createDefaultLimitsForContainersInNamespace(t, namespace) | ||||||
| 	createKubernetesCredentialsProviderSecret(t, namespace, mySeedJob) | 	createKubernetesCredentialsProviderSecret(t, namespace, mySeedJob) | ||||||
| 	waitForJenkinsBaseConfigurationToComplete(t, jenkins) | 	waitForJenkinsBaseConfigurationToComplete(t, jenkins) | ||||||
|  | @ -161,6 +181,20 @@ func verifyJenkinsMasterPodAttributes(t *testing.T, jenkins *v1alpha1.Jenkins) { | ||||||
| 		verifyContainer(t, *expectedContainer, actualContainer) | 		verifyContainer(t, *expectedContainer, actualContainer) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	for _, expectedVolume := range jenkins.Spec.Master.Volumes { | ||||||
|  | 		volumeFound := false | ||||||
|  | 		for _, actualVolume := range jenkinsPod.Spec.Volumes { | ||||||
|  | 			if expectedVolume.Name == actualVolume.Name { | ||||||
|  | 				volumeFound = true | ||||||
|  | 				assert.Equal(t, expectedVolume, actualVolume) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if !volumeFound { | ||||||
|  | 			t.Errorf("Missing volume '+%v', actaul volumes '%+v'", expectedVolume, jenkinsPod.Spec.Volumes) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	t.Log("Jenkins pod attributes are valid") | 	t.Log("Jenkins pod attributes are valid") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -60,7 +60,7 @@ func createJenkinsAPIClient(jenkins *v1alpha1.Jenkins) (jenkinsclient.Jenkins, e | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func createJenkinsCR(t *testing.T, name, namespace string, seedJob *[]v1alpha1.SeedJob) *v1alpha1.Jenkins { | func createJenkinsCR(t *testing.T, name, namespace string, seedJob *[]v1alpha1.SeedJob, volumes []corev1.Volume) *v1alpha1.Jenkins { | ||||||
| 	var seedJobs []v1alpha1.SeedJob | 	var seedJobs []v1alpha1.SeedJob | ||||||
| 	if seedJob != nil { | 	if seedJob != nil { | ||||||
| 		seedJobs = append(seedJobs, *seedJob...) | 		seedJobs = append(seedJobs, *seedJob...) | ||||||
|  | @ -118,6 +118,7 @@ func createJenkinsCR(t *testing.T, name, namespace string, seedJob *[]v1alpha1.S | ||||||
| 					"simple-theme-plugin:0.5.1": {}, | 					"simple-theme-plugin:0.5.1": {}, | ||||||
| 				}, | 				}, | ||||||
| 				NodeSelector: map[string]string{"kubernetes.io/hostname": "minikube"}, | 				NodeSelector: map[string]string{"kubernetes.io/hostname": "minikube"}, | ||||||
|  | 				Volumes:      volumes, | ||||||
| 			}, | 			}, | ||||||
| 			SeedJobs: seedJobs, | 			SeedJobs: seedJobs, | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  | @ -21,7 +21,7 @@ func TestJenkinsMasterPodRestart(t *testing.T) { | ||||||
| 	// Deletes test namespace
 | 	// Deletes test namespace
 | ||||||
| 	defer ctx.Cleanup() | 	defer ctx.Cleanup() | ||||||
| 
 | 
 | ||||||
| 	jenkins := createJenkinsCR(t, "e2e", namespace, nil) | 	jenkins := createJenkinsCR(t, "e2e", namespace, nil, []corev1.Volume{}) | ||||||
| 	waitForJenkinsBaseConfigurationToComplete(t, jenkins) | 	waitForJenkinsBaseConfigurationToComplete(t, jenkins) | ||||||
| 	restartJenkinsMasterPod(t, jenkins) | 	restartJenkinsMasterPod(t, jenkins) | ||||||
| 	waitForRecreateJenkinsMasterPod(t, jenkins) | 	waitForRecreateJenkinsMasterPod(t, jenkins) | ||||||
|  | @ -37,7 +37,7 @@ func TestSafeRestart(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 	jenkinsCRName := "e2e" | 	jenkinsCRName := "e2e" | ||||||
| 	configureAuthorizationToUnSecure(t, jenkinsCRName, namespace) | 	configureAuthorizationToUnSecure(t, jenkinsCRName, namespace) | ||||||
| 	jenkins := createJenkinsCR(t, jenkinsCRName, namespace, nil) | 	jenkins := createJenkinsCR(t, jenkinsCRName, namespace, nil, []corev1.Volume{}) | ||||||
| 	waitForJenkinsBaseConfigurationToComplete(t, jenkins) | 	waitForJenkinsBaseConfigurationToComplete(t, jenkins) | ||||||
| 	waitForJenkinsUserConfigurationToComplete(t, jenkins) | 	waitForJenkinsUserConfigurationToComplete(t, jenkins) | ||||||
| 	jenkinsClient := verifyJenkinsAPIConnection(t, jenkins) | 	jenkinsClient := verifyJenkinsAPIConnection(t, jenkins) | ||||||
|  |  | ||||||
|  | @ -51,7 +51,7 @@ func TestSeedJobs(t *testing.T) { | ||||||
| 		createKubernetesCredentialsProviderSecret(t, namespace, seedJobConfig) | 		createKubernetesCredentialsProviderSecret(t, namespace, seedJobConfig) | ||||||
| 		seedJobs = append(seedJobs, seedJobConfig.SeedJob) | 		seedJobs = append(seedJobs, seedJobConfig.SeedJob) | ||||||
| 	} | 	} | ||||||
| 	jenkins := createJenkinsCR(t, jenkinsCRName, namespace, &seedJobs) | 	jenkins := createJenkinsCR(t, jenkinsCRName, namespace, &seedJobs, []corev1.Volume{}) | ||||||
| 	waitForJenkinsBaseConfigurationToComplete(t, jenkins) | 	waitForJenkinsBaseConfigurationToComplete(t, jenkins) | ||||||
| 
 | 
 | ||||||
| 	verifyJenkinsMasterPodAttributes(t, jenkins) | 	verifyJenkinsMasterPodAttributes(t, jenkins) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue