Allow configure sidecars in Jenkins pod
This commit is contained in:
		
							parent
							
								
									127992eb96
								
							
						
					
					
						commit
						af10a97299
					
				|  | @ -18,17 +18,35 @@ type JenkinsSpec struct { | ||||||
| 	SlaveService Service       `json:"slaveService,omitempty"` | 	SlaveService Service       `json:"slaveService,omitempty"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Container defines Kubernetes container attributes
 | ||||||
|  | type Container struct { | ||||||
|  | 	Name            string                      `json:"name"` | ||||||
|  | 	Image           string                      `json:"image"` | ||||||
|  | 	Command         []string                    `json:"command,omitempty"` | ||||||
|  | 	Args            []string                    `json:"args,omitempty"` | ||||||
|  | 	WorkingDir      string                      `json:"workingDir,omitempty"` | ||||||
|  | 	Ports           []corev1.ContainerPort      `json:"ports,omitempty"` | ||||||
|  | 	EnvFrom         []corev1.EnvFromSource      `json:"envFrom,omitempty"` | ||||||
|  | 	Env             []corev1.EnvVar             `json:"env,omitempty"` | ||||||
|  | 	Resources       corev1.ResourceRequirements `json:"resources,omitempty"` | ||||||
|  | 	VolumeMounts    []corev1.VolumeMount        `json:"volumeMounts,omitempty"` | ||||||
|  | 	LivenessProbe   *corev1.Probe               `json:"livenessProbe,omitempty"` | ||||||
|  | 	ReadinessProbe  *corev1.Probe               `json:"readinessProbe,omitempty"` | ||||||
|  | 	Lifecycle       *corev1.Lifecycle           `json:"lifecycle,omitempty"` | ||||||
|  | 	ImagePullPolicy corev1.PullPolicy           `json:"imagePullPolicy,omitempty"` | ||||||
|  | 	SecurityContext *corev1.SecurityContext     `json:"securityContext,omitempty"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // 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 { | ||||||
| 	Image           string                      `json:"image,omitempty"` | 	Container | ||||||
| 	ImagePullPolicy corev1.PullPolicy           `json:"imagePullPolicy,omitempty"` | 
 | ||||||
| 	NodeSelector    map[string]string           `json:"nodeSelector,omitempty"` | 	// pod properties
 | ||||||
| 	Annotations  map[string]string `json:"masterAnnotations,omitempty"` | 	Annotations  map[string]string `json:"masterAnnotations,omitempty"` | ||||||
| 	Resources       corev1.ResourceRequirements `json:"resources,omitempty"` | 	NodeSelector map[string]string `json:"nodeSelector,omitempty"` | ||||||
| 	Env             []corev1.EnvVar             `json:"env,omitempty"` | 	Containers   []Container       `json:"containers,omitempty"` | ||||||
| 	LivenessProbe   *corev1.Probe               `json:"livenessProbe,omitempty"` | 
 | ||||||
| 	ReadinessProbe  *corev1.Probe               `json:"readinessProbe,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"` | ||||||
| 	// Plugins contains plugins required by user
 | 	// Plugins contains plugins required by user
 | ||||||
|  |  | ||||||
|  | @ -373,7 +373,7 @@ func isPodTerminating(pod corev1.Pod) bool { | ||||||
| 
 | 
 | ||||||
| func (r *ReconcileJenkinsBaseConfiguration) isRecreatePodNeeded(currentJenkinsMasterPod corev1.Pod) bool { | func (r *ReconcileJenkinsBaseConfiguration) isRecreatePodNeeded(currentJenkinsMasterPod corev1.Pod) bool { | ||||||
| 	if version.Version != r.jenkins.Status.OperatorVersion { | 	if version.Version != r.jenkins.Status.OperatorVersion { | ||||||
| 		r.logger.Info(fmt.Sprintf("Jenkins Operator version has changed, actual '%+v' new '%+v' - recreating pod", | 		r.logger.Info(fmt.Sprintf("Jenkins Operator version has changed, actual '%+v' new '%+v', recreating pod", | ||||||
| 			r.jenkins.Status.OperatorVersion, version.Version)) | 			r.jenkins.Status.OperatorVersion, version.Version)) | ||||||
| 		return true | 		return true | ||||||
| 	} | 	} | ||||||
|  | @ -385,13 +385,9 @@ func (r *ReconcileJenkinsBaseConfiguration) isRecreatePodNeeded(currentJenkinsMa | ||||||
| 		return true | 		return true | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if r.jenkins.Spec.Master.Image != currentJenkinsMasterPod.Spec.Containers[0].Image { | 	if !reflect.DeepEqual(r.jenkins.Spec.Master.NodeSelector, currentJenkinsMasterPod.Spec.NodeSelector) { | ||||||
| 		r.logger.Info(fmt.Sprintf("Jenkins image has changed to '%+v', recreating pod", r.jenkins.Spec.Master.Image)) | 		r.logger.Info(fmt.Sprintf("Jenkins pod node selector has changed, actual '%+v' required '%+v', recreating pod", | ||||||
| 		return true | 			currentJenkinsMasterPod.Spec.NodeSelector, r.jenkins.Spec.Master.NodeSelector)) | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if r.jenkins.Spec.Master.ImagePullPolicy != currentJenkinsMasterPod.Spec.Containers[0].ImagePullPolicy { |  | ||||||
| 		r.logger.Info(fmt.Sprintf("Jenkins image pull policy has changed to '%+v', recreating pod", r.jenkins.Spec.Master.ImagePullPolicy)) |  | ||||||
| 		return true | 		return true | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -401,47 +397,119 @@ func (r *ReconcileJenkinsBaseConfiguration) isRecreatePodNeeded(currentJenkinsMa | ||||||
| 		return true | 		return true | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if !reflect.DeepEqual(r.jenkins.Spec.Master.Resources, currentJenkinsMasterPod.Spec.Containers[0].Resources) { | 	if (len(r.jenkins.Spec.Master.Containers) + 1) != len(currentJenkinsMasterPod.Spec.Containers) { | ||||||
| 		r.logger.Info(fmt.Sprintf("Jenkins pod resources have changed, actual '%+v' required '%+v' - recreating pod", | 		r.logger.Info(fmt.Sprintf("Jenkins amount of containers has changed to '%+v', recreating pod", len(r.jenkins.Spec.Master.Containers)+1)) | ||||||
| 			currentJenkinsMasterPod.Spec.Containers[0].Resources, r.jenkins.Spec.Master.Resources)) |  | ||||||
| 		return true | 		return true | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if !reflect.DeepEqual(r.jenkins.Spec.Master.ReadinessProbe, currentJenkinsMasterPod.Spec.Containers[0].ReadinessProbe) { | 	for _, actualContainer := range currentJenkinsMasterPod.Spec.Containers { | ||||||
| 		r.logger.Info(fmt.Sprintf("Jenkins pod readinessProbe have changed, actual '%+v' required '%+v' - recreating pod", | 		if actualContainer.Name == resources.JenkinsMasterContainerName { | ||||||
| 			currentJenkinsMasterPod.Spec.Containers[0].ReadinessProbe, r.jenkins.Spec.Master.ReadinessProbe)) | 			if changed := r.compareContainers(resources.NewJenkinsMasterContainer(r.jenkins), actualContainer); changed { | ||||||
|  | 				return true | ||||||
|  | 			} | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		var expectedContainer *corev1.Container | ||||||
|  | 		for _, jenkinsContainer := range r.jenkins.Spec.Master.Containers { | ||||||
|  | 			if jenkinsContainer.Name == actualContainer.Name { | ||||||
|  | 				tmp := resources.ConvertJenkinsContainerToKubernetesContainer(jenkinsContainer) | ||||||
|  | 				expectedContainer = &tmp | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if expectedContainer == nil { | ||||||
|  | 			r.logger.Info(fmt.Sprintf("Container '%+v' not found in pod, recreating pod", actualContainer)) | ||||||
| 			return true | 			return true | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 	if !reflect.DeepEqual(r.jenkins.Spec.Master.LivenessProbe, currentJenkinsMasterPod.Spec.Containers[0].LivenessProbe) { | 		if changed := r.compareContainers(*expectedContainer, actualContainer); changed { | ||||||
| 		r.logger.Info(fmt.Sprintf("Jenkins pod livenessProbe have changed, actual '%+v' required '%+v' - recreating pod", |  | ||||||
| 			currentJenkinsMasterPod.Spec.Containers[0].LivenessProbe, r.jenkins.Spec.Master.LivenessProbe)) |  | ||||||
| 			return true | 			return true | ||||||
| 		} | 		} | ||||||
| 
 |  | ||||||
| 	if !reflect.DeepEqual(r.jenkins.Spec.Master.NodeSelector, currentJenkinsMasterPod.Spec.NodeSelector) { |  | ||||||
| 		r.logger.Info(fmt.Sprintf("Jenkins pod node selector has changed, actual '%+v' required '%+v' - recreating pod", |  | ||||||
| 			currentJenkinsMasterPod.Spec.NodeSelector, r.jenkins.Spec.Master.NodeSelector)) |  | ||||||
| 		return true |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	requiredEnvs := resources.GetJenkinsMasterPodBaseEnvs() | 	return false | ||||||
| 	requiredEnvs = append(requiredEnvs, r.jenkins.Spec.Master.Env...) | } | ||||||
| 	if !reflect.DeepEqual(requiredEnvs, currentJenkinsMasterPod.Spec.Containers[0].Env) { | 
 | ||||||
| 		r.logger.Info(fmt.Sprintf("Jenkins env have changed, actual '%+v' required '%+v' - recreating pod", | func (r *ReconcileJenkinsBaseConfiguration) compareContainers(expected corev1.Container, actual corev1.Container) bool { | ||||||
| 			currentJenkinsMasterPod.Spec.Containers[0].Env, requiredEnvs)) | 	if !reflect.DeepEqual(expected.Args, actual.Args) { | ||||||
|  | 		r.logger.Info(fmt.Sprintf("Arguments have changed to '%+v' in container '%s', recreating pod", expected.Args, expected.Name)) | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	if !reflect.DeepEqual(expected.Command, actual.Command) { | ||||||
|  | 		r.logger.Info(fmt.Sprintf("Command has changed to '%+v' in container '%s', recreating pod", expected.Command, expected.Name)) | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	if !reflect.DeepEqual(expected.Env, actual.Env) { | ||||||
|  | 		r.logger.Info(fmt.Sprintf("Env has changed to '%+v' in container '%s', recreating pod", expected.Env, expected.Name)) | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	if !reflect.DeepEqual(expected.EnvFrom, actual.EnvFrom) { | ||||||
|  | 		r.logger.Info(fmt.Sprintf("EnvFrom has changed to '%+v' in container '%s', recreating pod", expected.EnvFrom, expected.Name)) | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	if !reflect.DeepEqual(expected.Image, actual.Image) { | ||||||
|  | 		r.logger.Info(fmt.Sprintf("Image has changed to '%+v' in container '%s', recreating pod", expected.Image, expected.Name)) | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	if !reflect.DeepEqual(expected.ImagePullPolicy, actual.ImagePullPolicy) { | ||||||
|  | 		r.logger.Info(fmt.Sprintf("Image pull policy has changed to '%+v' in container '%s', recreating pod", expected.ImagePullPolicy, expected.Name)) | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	if !reflect.DeepEqual(expected.Lifecycle, actual.Lifecycle) { | ||||||
|  | 		r.logger.Info(fmt.Sprintf("Lifecycle has changed to '%+v' in container '%s', recreating pod", expected.Lifecycle, expected.Name)) | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	if !reflect.DeepEqual(expected.LivenessProbe, actual.LivenessProbe) { | ||||||
|  | 		r.logger.Info(fmt.Sprintf("Liveness probe has changed to '%+v' in container '%s', recreating pod", expected.LivenessProbe, expected.Name)) | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	if !reflect.DeepEqual(expected.Ports, actual.Ports) { | ||||||
|  | 		r.logger.Info(fmt.Sprintf("Ports have changed to '%+v' in container '%s', recreating pod", expected.Ports, expected.Name)) | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	if !reflect.DeepEqual(expected.ReadinessProbe, actual.ReadinessProbe) { | ||||||
|  | 		r.logger.Info(fmt.Sprintf("Readiness probe has changed to '%+v' in container '%s', recreating pod", expected.ReadinessProbe, expected.Name)) | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	if !reflect.DeepEqual(expected.Resources, actual.Resources) { | ||||||
|  | 		r.logger.Info(fmt.Sprintf("Resources have changed to '%+v' in container '%s', recreating pod", expected.Resources, expected.Name)) | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	if !reflect.DeepEqual(expected.SecurityContext, actual.SecurityContext) { | ||||||
|  | 		r.logger.Info(fmt.Sprintf("Security context has changed to '%+v' in container '%s', recreating pod", expected.SecurityContext, expected.Name)) | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	if !reflect.DeepEqual(expected.WorkingDir, actual.WorkingDir) { | ||||||
|  | 		r.logger.Info(fmt.Sprintf("Working directory has changed to '%+v' in container '%s', recreating pod", expected.WorkingDir, expected.Name)) | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	if !CompareContainerVolumeMounts(expected, actual) { | ||||||
|  | 		r.logger.Info(fmt.Sprintf("Volume mounts has changed to '%+v' in container '%s', recreating pod", expected.VolumeMounts, expected.Name)) | ||||||
| 		return true | 		return true | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return false | 	return false | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // CompareContainerVolumeMounts returns true if two containers volume mounts are the same
 | ||||||
|  | func CompareContainerVolumeMounts(expected corev1.Container, actual corev1.Container) bool { | ||||||
|  | 	var withoutServiceAccount []corev1.VolumeMount | ||||||
|  | 	for _, volumeMount := range actual.VolumeMounts { | ||||||
|  | 		if volumeMount.MountPath != "/var/run/secrets/kubernetes.io/serviceaccount" { | ||||||
|  | 			withoutServiceAccount = append(withoutServiceAccount, volumeMount) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return reflect.DeepEqual(expected.VolumeMounts, 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) | ||||||
| 	r.logger.Info(fmt.Sprintf("Terminating Jenkins Master Pod %s/%s", currentJenkinsMasterPod.Namespace, currentJenkinsMasterPod.Name)) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | 	r.logger.Info(fmt.Sprintf("Terminating Jenkins Master Pod %s/%s", currentJenkinsMasterPod.Namespace, currentJenkinsMasterPod.Name)) | ||||||
| 	return stackerr.WithStack(r.k8sClient.Delete(context.TODO(), currentJenkinsMasterPod)) | 	return stackerr.WithStack(r.k8sClient.Delete(context.TODO(), currentJenkinsMasterPod)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -461,11 +529,20 @@ func (r *ReconcileJenkinsBaseConfiguration) waitForJenkins(meta metav1.ObjectMet | ||||||
| 		return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 5}, nil | 		return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 5}, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	containersReadyCount := 0 | ||||||
| 	for _, containerStatus := range jenkinsMasterPodStatus.Status.ContainerStatuses { | 	for _, containerStatus := range jenkinsMasterPodStatus.Status.ContainerStatuses { | ||||||
| 		if !containerStatus.Ready { | 		if containerStatus.State.Terminated != nil { | ||||||
| 			r.logger.V(log.VDebug).Info("Jenkins master pod not ready, readiness probe failed") | 			r.logger.Info(fmt.Sprintf("Container '%s' is terminated, status '%+v', recreating pod", containerStatus.Name, containerStatus)) | ||||||
| 			return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 5}, nil | 			return reconcile.Result{Requeue: true}, r.restartJenkinsMasterPod(meta) | ||||||
| 		} | 		} | ||||||
|  | 		if !containerStatus.Ready { | ||||||
|  | 			r.logger.V(log.VDebug).Info(fmt.Sprintf("Container '%s' not ready, readiness probe failed", containerStatus.Name)) | ||||||
|  | 		} else { | ||||||
|  | 			containersReadyCount++ | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if containersReadyCount != len(jenkinsMasterPodStatus.Status.ContainerStatuses) { | ||||||
|  | 		return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 5}, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return reconcile.Result{}, nil | 	return reconcile.Result{}, nil | ||||||
|  |  | ||||||
|  | @ -0,0 +1,83 @@ | ||||||
|  | package base | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	corev1 "k8s.io/api/core/v1" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestCompareContainerVolumeMounts(t *testing.T) { | ||||||
|  | 	t.Run("happy with service account", func(t *testing.T) { | ||||||
|  | 		expectedContainer := corev1.Container{ | ||||||
|  | 			VolumeMounts: []corev1.VolumeMount{ | ||||||
|  | 				{ | ||||||
|  | 					Name:      "volume-name", | ||||||
|  | 					MountPath: "/mount/path", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		actualContainer := corev1.Container{ | ||||||
|  | 			VolumeMounts: []corev1.VolumeMount{ | ||||||
|  | 				{ | ||||||
|  | 					Name:      "volume-name", | ||||||
|  | 					MountPath: "/mount/path", | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					Name:      "jenkins-operator-example-token-dh4r9", | ||||||
|  | 					MountPath: "/var/run/secrets/kubernetes.io/serviceaccount", | ||||||
|  | 					ReadOnly:  true, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		got := CompareContainerVolumeMounts(expectedContainer, actualContainer) | ||||||
|  | 
 | ||||||
|  | 		assert.True(t, got) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("happy without service account", func(t *testing.T) { | ||||||
|  | 		expectedContainer := corev1.Container{ | ||||||
|  | 			VolumeMounts: []corev1.VolumeMount{ | ||||||
|  | 				{ | ||||||
|  | 					Name:      "volume-name", | ||||||
|  | 					MountPath: "/mount/path", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		actualContainer := corev1.Container{ | ||||||
|  | 			VolumeMounts: []corev1.VolumeMount{ | ||||||
|  | 				{ | ||||||
|  | 					Name:      "volume-name", | ||||||
|  | 					MountPath: "/mount/path", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		got := CompareContainerVolumeMounts(expectedContainer, actualContainer) | ||||||
|  | 
 | ||||||
|  | 		assert.True(t, got) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("different volume mounts", func(t *testing.T) { | ||||||
|  | 		expectedContainer := corev1.Container{ | ||||||
|  | 			VolumeMounts: []corev1.VolumeMount{ | ||||||
|  | 				{ | ||||||
|  | 					Name:      "volume-name", | ||||||
|  | 					MountPath: "/mount/path", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		actualContainer := corev1.Container{ | ||||||
|  | 			VolumeMounts: []corev1.VolumeMount{ | ||||||
|  | 				{ | ||||||
|  | 					Name:      "jenkins-operator-example-token-dh4r9", | ||||||
|  | 					MountPath: "/var/run/secrets/kubernetes.io/serviceaccount", | ||||||
|  | 					ReadOnly:  true, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		got := CompareContainerVolumeMounts(expectedContainer, actualContainer) | ||||||
|  | 
 | ||||||
|  | 		assert.False(t, got) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | @ -11,6 +11,8 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
|  | 	// JenkinsMasterContainerName is the Jenkins master container name in pod
 | ||||||
|  | 	JenkinsMasterContainerName = "jenkins-master" | ||||||
| 	jenkinsHomeVolumeName      = "home" | 	jenkinsHomeVolumeName      = "home" | ||||||
| 	jenkinsPath                = "/var/jenkins" | 	jenkinsPath                = "/var/jenkins" | ||||||
| 	jenkinsHomePath            = jenkinsPath + "/home" | 	jenkinsHomePath            = jenkinsPath + "/home" | ||||||
|  | @ -72,28 +74,13 @@ func GetJenkinsMasterPodBaseEnvs() []corev1.EnvVar { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewJenkinsMasterPod builds Jenkins Master Kubernetes Pod resource
 | // NewJenkinsMasterContainer returns Jenkins master Kubernetes container
 | ||||||
| func NewJenkinsMasterPod(objectMeta metav1.ObjectMeta, jenkins *v1alpha1.Jenkins) *corev1.Pod { | func NewJenkinsMasterContainer(jenkins *v1alpha1.Jenkins) corev1.Container { | ||||||
| 	runAsUser := jenkinsUserUID |  | ||||||
| 
 |  | ||||||
| 	objectMeta.Annotations = jenkins.Spec.Master.Annotations |  | ||||||
| 	envs := GetJenkinsMasterPodBaseEnvs() | 	envs := GetJenkinsMasterPodBaseEnvs() | ||||||
| 	envs = append(envs, jenkins.Spec.Master.Env...) | 	envs = append(envs, jenkins.Spec.Master.Env...) | ||||||
| 
 | 
 | ||||||
| 	return &corev1.Pod{ | 	return corev1.Container{ | ||||||
| 		TypeMeta:   buildPodTypeMeta(), | 		Name:            JenkinsMasterContainerName, | ||||||
| 		ObjectMeta: objectMeta, |  | ||||||
| 		Spec: corev1.PodSpec{ |  | ||||||
| 			ServiceAccountName: objectMeta.Name, |  | ||||||
| 			RestartPolicy:      corev1.RestartPolicyNever, |  | ||||||
| 			SecurityContext: &corev1.PodSecurityContext{ |  | ||||||
| 				RunAsUser:  &runAsUser, |  | ||||||
| 				RunAsGroup: &runAsUser, |  | ||||||
| 			}, |  | ||||||
| 			NodeSelector: jenkins.Spec.Master.NodeSelector, |  | ||||||
| 			Containers: []corev1.Container{ |  | ||||||
| 				{ |  | ||||||
| 					Name:            "jenkins-master", |  | ||||||
| 		Image:           jenkins.Spec.Master.Image, | 		Image:           jenkins.Spec.Master.Image, | ||||||
| 		ImagePullPolicy: jenkins.Spec.Master.ImagePullPolicy, | 		ImagePullPolicy: jenkins.Spec.Master.ImagePullPolicy, | ||||||
| 		Command: []string{ | 		Command: []string{ | ||||||
|  | @ -106,10 +93,12 @@ func NewJenkinsMasterPod(objectMeta metav1.ObjectMeta, jenkins *v1alpha1.Jenkins | ||||||
| 			{ | 			{ | ||||||
| 				Name:          httpPortName, | 				Name:          httpPortName, | ||||||
| 				ContainerPort: constants.DefaultHTTPPortInt32, | 				ContainerPort: constants.DefaultHTTPPortInt32, | ||||||
|  | 				Protocol:      corev1.ProtocolTCP, | ||||||
| 			}, | 			}, | ||||||
| 			{ | 			{ | ||||||
| 				Name:          slavePortName, | 				Name:          slavePortName, | ||||||
| 				ContainerPort: constants.DefaultSlavePortInt32, | 				ContainerPort: constants.DefaultSlavePortInt32, | ||||||
|  | 				Protocol:      corev1.ProtocolTCP, | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 		Env:       envs, | 		Env:       envs, | ||||||
|  | @ -151,8 +140,58 @@ func NewJenkinsMasterPod(objectMeta metav1.ObjectMeta, jenkins *v1alpha1.Jenkins | ||||||
| 				ReadOnly:  true, | 				ReadOnly:  true, | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ConvertJenkinsContainerToKubernetesContainer converts Jenkins container to Kubernetes container
 | ||||||
|  | func ConvertJenkinsContainerToKubernetesContainer(container v1alpha1.Container) corev1.Container { | ||||||
|  | 	return corev1.Container{ | ||||||
|  | 		Name:            container.Name, | ||||||
|  | 		Image:           container.Image, | ||||||
|  | 		Command:         container.Command, | ||||||
|  | 		Args:            container.Args, | ||||||
|  | 		WorkingDir:      container.WorkingDir, | ||||||
|  | 		Ports:           container.Ports, | ||||||
|  | 		EnvFrom:         container.EnvFrom, | ||||||
|  | 		Env:             container.Env, | ||||||
|  | 		Resources:       container.Resources, | ||||||
|  | 		VolumeMounts:    container.VolumeMounts, | ||||||
|  | 		LivenessProbe:   container.LivenessProbe, | ||||||
|  | 		ReadinessProbe:  container.ReadinessProbe, | ||||||
|  | 		Lifecycle:       container.Lifecycle, | ||||||
|  | 		ImagePullPolicy: container.ImagePullPolicy, | ||||||
|  | 		SecurityContext: container.SecurityContext, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newContainers(jenkins *v1alpha1.Jenkins) (containers []corev1.Container) { | ||||||
|  | 	containers = append(containers, NewJenkinsMasterContainer(jenkins)) | ||||||
|  | 
 | ||||||
|  | 	for _, container := range jenkins.Spec.Master.Containers { | ||||||
|  | 		containers = append(containers, ConvertJenkinsContainerToKubernetesContainer(container)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewJenkinsMasterPod builds Jenkins Master Kubernetes Pod resource
 | ||||||
|  | func NewJenkinsMasterPod(objectMeta metav1.ObjectMeta, jenkins *v1alpha1.Jenkins) *corev1.Pod { | ||||||
|  | 	runAsUser := jenkinsUserUID | ||||||
|  | 
 | ||||||
|  | 	objectMeta.Annotations = jenkins.Spec.Master.Annotations | ||||||
|  | 
 | ||||||
|  | 	return &corev1.Pod{ | ||||||
|  | 		TypeMeta:   buildPodTypeMeta(), | ||||||
|  | 		ObjectMeta: objectMeta, | ||||||
|  | 		Spec: corev1.PodSpec{ | ||||||
|  | 			ServiceAccountName: objectMeta.Name, | ||||||
|  | 			RestartPolicy:      corev1.RestartPolicyNever, | ||||||
|  | 			SecurityContext: &corev1.PodSecurityContext{ | ||||||
|  | 				RunAsUser:  &runAsUser, | ||||||
|  | 				RunAsGroup: &runAsUser, | ||||||
| 			}, | 			}, | ||||||
| 			}, | 			NodeSelector: jenkins.Spec.Master.NodeSelector, | ||||||
|  | 			Containers:   newContainers(jenkins), | ||||||
| 			Volumes: []corev1.Volume{ | 			Volumes: []corev1.Volume{ | ||||||
| 				{ | 				{ | ||||||
| 					Name: jenkinsHomeVolumeName, | 					Name: jenkinsHomeVolumeName, | ||||||
|  |  | ||||||
|  | @ -18,15 +18,14 @@ 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 jenkins.Spec.Master.Image == "" { | 	if !r.validateContainer(jenkins.Spec.Master.Container) { | ||||||
| 		r.logger.V(log.VWarn).Info("Image not set") |  | ||||||
| 		return false, nil | 		return false, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if !dockerImageRegexp.MatchString(jenkins.Spec.Master.Image) && !docker.ReferenceRegexp.MatchString(jenkins.Spec.Master.Image) { | 	for _, container := range jenkins.Spec.Master.Containers { | ||||||
| 		r.logger.V(log.VWarn).Info("Invalid image") | 		if !r.validateContainer(container) { | ||||||
| 			return false, nil | 			return false, nil | ||||||
| 
 | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if !r.validatePlugins(jenkins.Spec.Master.OperatorPlugins, jenkins.Spec.Master.Plugins) { | 	if !r.validatePlugins(jenkins.Spec.Master.OperatorPlugins, jenkins.Spec.Master.Plugins) { | ||||||
|  | @ -40,6 +39,26 @@ func (r *ReconcileJenkinsBaseConfiguration) Validate(jenkins *v1alpha1.Jenkins) | ||||||
| 	return true, nil | 	return true, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (r *ReconcileJenkinsBaseConfiguration) validateContainer(container v1alpha1.Container) bool { | ||||||
|  | 	logger := r.logger.WithValues("container", container.Name) | ||||||
|  | 	if container.Image == "" { | ||||||
|  | 		logger.V(log.VWarn).Info("Image not set") | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !dockerImageRegexp.MatchString(container.Image) && !docker.ReferenceRegexp.MatchString(container.Image) { | ||||||
|  | 		r.logger.V(log.VWarn).Info("Invalid image") | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if container.ImagePullPolicy == "" { | ||||||
|  | 		logger.V(log.VWarn).Info("Image pull policy not set") | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (r *ReconcileJenkinsBaseConfiguration) validateJenkinsMasterPodEnvs() bool { | func (r *ReconcileJenkinsBaseConfiguration) validateJenkinsMasterPodEnvs() bool { | ||||||
| 	baseEnvs := resources.GetJenkinsMasterPodBaseEnvs() | 	baseEnvs := resources.GetJenkinsMasterPodBaseEnvs() | ||||||
| 	baseEnvNames := map[string]string{} | 	baseEnvNames := map[string]string{} | ||||||
|  |  | ||||||
|  | @ -74,6 +74,7 @@ func TestValidateJenkinsMasterPodEnvs(t *testing.T) { | ||||||
| 		jenkins := v1alpha1.Jenkins{ | 		jenkins := v1alpha1.Jenkins{ | ||||||
| 			Spec: v1alpha1.JenkinsSpec{ | 			Spec: v1alpha1.JenkinsSpec{ | ||||||
| 				Master: v1alpha1.JenkinsMaster{ | 				Master: v1alpha1.JenkinsMaster{ | ||||||
|  | 					Container: v1alpha1.Container{ | ||||||
| 						Env: []v1.EnvVar{ | 						Env: []v1.EnvVar{ | ||||||
| 							{ | 							{ | ||||||
| 								Name:  "SOME_VALUE", | 								Name:  "SOME_VALUE", | ||||||
|  | @ -82,6 +83,7 @@ func TestValidateJenkinsMasterPodEnvs(t *testing.T) { | ||||||
| 						}, | 						}, | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
|  | 			}, | ||||||
| 		} | 		} | ||||||
| 		baseReconcileLoop := New(nil, nil, logf.ZapLogger(false), | 		baseReconcileLoop := New(nil, nil, logf.ZapLogger(false), | ||||||
| 			&jenkins, false, false) | 			&jenkins, false, false) | ||||||
|  | @ -92,6 +94,7 @@ func TestValidateJenkinsMasterPodEnvs(t *testing.T) { | ||||||
| 		jenkins := v1alpha1.Jenkins{ | 		jenkins := v1alpha1.Jenkins{ | ||||||
| 			Spec: v1alpha1.JenkinsSpec{ | 			Spec: v1alpha1.JenkinsSpec{ | ||||||
| 				Master: v1alpha1.JenkinsMaster{ | 				Master: v1alpha1.JenkinsMaster{ | ||||||
|  | 					Container: v1alpha1.Container{ | ||||||
| 						Env: []v1.EnvVar{ | 						Env: []v1.EnvVar{ | ||||||
| 							{ | 							{ | ||||||
| 								Name:  "JENKINS_HOME", | 								Name:  "JENKINS_HOME", | ||||||
|  | @ -100,6 +103,7 @@ func TestValidateJenkinsMasterPodEnvs(t *testing.T) { | ||||||
| 						}, | 						}, | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
|  | 			}, | ||||||
| 		} | 		} | ||||||
| 		baseReconcileLoop := New(nil, nil, logf.ZapLogger(false), | 		baseReconcileLoop := New(nil, nil, logf.ZapLogger(false), | ||||||
| 			&jenkins, false, false) | 			&jenkins, false, false) | ||||||
|  |  | ||||||
|  | @ -119,8 +119,9 @@ func jenkinsCustomResource() *v1alpha1.Jenkins { | ||||||
| 		}, | 		}, | ||||||
| 		Spec: v1alpha1.JenkinsSpec{ | 		Spec: v1alpha1.JenkinsSpec{ | ||||||
| 			Master: v1alpha1.JenkinsMaster{ | 			Master: v1alpha1.JenkinsMaster{ | ||||||
| 				Image:       "jenkins/jenkins", |  | ||||||
| 				Annotations: map[string]string{"test": "label"}, | 				Annotations: map[string]string{"test": "label"}, | ||||||
|  | 				Container: v1alpha1.Container{ | ||||||
|  | 					Image: "jenkins/jenkins", | ||||||
| 					Resources: corev1.ResourceRequirements{ | 					Resources: corev1.ResourceRequirements{ | ||||||
| 						Requests: corev1.ResourceList{ | 						Requests: corev1.ResourceList{ | ||||||
| 							corev1.ResourceCPU:    resource.MustParse("300m"), | 							corev1.ResourceCPU:    resource.MustParse("300m"), | ||||||
|  | @ -132,6 +133,7 @@ func jenkinsCustomResource() *v1alpha1.Jenkins { | ||||||
| 						}, | 						}, | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
|  | 			}, | ||||||
| 			SeedJobs: []v1alpha1.SeedJob{ | 			SeedJobs: []v1alpha1.SeedJob{ | ||||||
| 				{ | 				{ | ||||||
| 					ID: "jenkins-operator-e2e", | 					ID: "jenkins-operator-e2e", | ||||||
|  |  | ||||||
|  | @ -252,7 +252,7 @@ func (r *ReconcileJenkins) setDefaults(jenkins *v1alpha1.Jenkins, logger logr.Lo | ||||||
| 					Scheme: corev1.URISchemeHTTP, | 					Scheme: corev1.URISchemeHTTP, | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 			InitialDelaySeconds: int32(30), | 			InitialDelaySeconds: int32(80), | ||||||
| 			TimeoutSeconds:      int32(5), | 			TimeoutSeconds:      int32(5), | ||||||
| 			FailureThreshold:    int32(12), | 			FailureThreshold:    int32(12), | ||||||
| 		} | 		} | ||||||
|  | @ -276,12 +276,8 @@ func (r *ReconcileJenkins) setDefaults(jenkins *v1alpha1.Jenkins, logger logr.Lo | ||||||
| 		changed = true | 		changed = true | ||||||
| 		jenkins.Spec.Master.Plugins = map[string][]string{"simple-theme-plugin:0.5.1": {}} | 		jenkins.Spec.Master.Plugins = map[string][]string{"simple-theme-plugin:0.5.1": {}} | ||||||
| 	} | 	} | ||||||
| 	_, requestCPUSet := jenkins.Spec.Master.Resources.Requests[corev1.ResourceCPU] | 	if isResourceRequirementsNotSet(jenkins.Spec.Master.Resources) { | ||||||
| 	_, requestMemporySet := jenkins.Spec.Master.Resources.Requests[corev1.ResourceMemory] | 		logger.Info("Setting default Jenkins master container resource requirements") | ||||||
| 	_, limitCPUSet := jenkins.Spec.Master.Resources.Limits[corev1.ResourceCPU] |  | ||||||
| 	_, limitMemporySet := jenkins.Spec.Master.Resources.Limits[corev1.ResourceMemory] |  | ||||||
| 	if !limitCPUSet || !limitMemporySet || !requestCPUSet || !requestMemporySet { |  | ||||||
| 		logger.Info("Setting default Jenkins master pod resource requirements") |  | ||||||
| 		changed = true | 		changed = true | ||||||
| 		jenkins.Spec.Master.Resources = corev1.ResourceRequirements{ | 		jenkins.Spec.Master.Resources = corev1.ResourceRequirements{ | ||||||
| 			Requests: corev1.ResourceList{ | 			Requests: corev1.ResourceList{ | ||||||
|  | @ -318,9 +314,49 @@ func (r *ReconcileJenkins) setDefaults(jenkins *v1alpha1.Jenkins, logger logr.Lo | ||||||
| 			Port: constants.DefaultSlavePortInt32, | 			Port: constants.DefaultSlavePortInt32, | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 	for i, container := range jenkins.Spec.Master.Containers { | ||||||
|  | 		if setDefaultsForContainer(jenkins, i, logger.WithValues("container", container.Name)) { | ||||||
|  | 			changed = true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	if changed { | 	if changed { | ||||||
| 		return errors.WithStack(r.client.Update(context.TODO(), jenkins)) | 		return errors.WithStack(r.client.Update(context.TODO(), jenkins)) | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func setDefaultsForContainer(jenkins *v1alpha1.Jenkins, containerIndex int, logger logr.Logger) bool { | ||||||
|  | 	changed := false | ||||||
|  | 
 | ||||||
|  | 	if len(jenkins.Spec.Master.Containers[containerIndex].ImagePullPolicy) == 0 { | ||||||
|  | 		logger.Info(fmt.Sprintf("Setting default container image pull policy: %s", corev1.PullAlways)) | ||||||
|  | 		changed = true | ||||||
|  | 		jenkins.Spec.Master.Containers[containerIndex].ImagePullPolicy = corev1.PullAlways | ||||||
|  | 	} | ||||||
|  | 	if isResourceRequirementsNotSet(jenkins.Spec.Master.Containers[containerIndex].Resources) { | ||||||
|  | 		logger.Info("Setting default container resource requirements") | ||||||
|  | 		changed = true | ||||||
|  | 		jenkins.Spec.Master.Containers[containerIndex].Resources = corev1.ResourceRequirements{ | ||||||
|  | 			Requests: corev1.ResourceList{ | ||||||
|  | 				corev1.ResourceCPU:    resource.MustParse("50m"), | ||||||
|  | 				corev1.ResourceMemory: resource.MustParse("50Mi"), | ||||||
|  | 			}, | ||||||
|  | 			Limits: corev1.ResourceList{ | ||||||
|  | 				corev1.ResourceCPU:    resource.MustParse("100m"), | ||||||
|  | 				corev1.ResourceMemory: resource.MustParse("100Mi"), | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return changed | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isResourceRequirementsNotSet(requirements corev1.ResourceRequirements) bool { | ||||||
|  | 	_, requestCPUSet := requirements.Requests[corev1.ResourceCPU] | ||||||
|  | 	_, requestMemporySet := requirements.Requests[corev1.ResourceMemory] | ||||||
|  | 	_, limitCPUSet := requirements.Limits[corev1.ResourceCPU] | ||||||
|  | 	_, limitMemorySet := requirements.Limits[corev1.ResourceMemory] | ||||||
|  | 
 | ||||||
|  | 	return !limitCPUSet || !limitMemorySet || !requestCPUSet || !requestMemporySet | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -387,8 +387,9 @@ func jenkinsCustomResource() *v1alpha1.Jenkins { | ||||||
| 		}, | 		}, | ||||||
| 		Spec: v1alpha1.JenkinsSpec{ | 		Spec: v1alpha1.JenkinsSpec{ | ||||||
| 			Master: v1alpha1.JenkinsMaster{ | 			Master: v1alpha1.JenkinsMaster{ | ||||||
| 				Image:       "jenkins/jenkins", |  | ||||||
| 				Annotations: map[string]string{"test": "label"}, | 				Annotations: map[string]string{"test": "label"}, | ||||||
|  | 				Container: v1alpha1.Container{ | ||||||
|  | 					Image: "jenkins/jenkins", | ||||||
| 					Resources: corev1.ResourceRequirements{ | 					Resources: corev1.ResourceRequirements{ | ||||||
| 						Requests: corev1.ResourceList{ | 						Requests: corev1.ResourceList{ | ||||||
| 							corev1.ResourceCPU:    resource.MustParse("300m"), | 							corev1.ResourceCPU:    resource.MustParse("300m"), | ||||||
|  | @ -400,6 +401,7 @@ func jenkinsCustomResource() *v1alpha1.Jenkins { | ||||||
| 						}, | 						}, | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
|  | 			}, | ||||||
| 			SeedJobs: []v1alpha1.SeedJob{ | 			SeedJobs: []v1alpha1.SeedJob{ | ||||||
| 				{ | 				{ | ||||||
| 					ID: "jenkins-operator-e2e", | 					ID: "jenkins-operator-e2e", | ||||||
|  |  | ||||||
|  | @ -3,11 +3,11 @@ package e2e | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"reflect" |  | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" | 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" | ||||||
| 	jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/client" | 	jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/client" | ||||||
|  | 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base" | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources" | 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources" | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/plugins" | 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/plugins" | ||||||
| 
 | 
 | ||||||
|  | @ -133,43 +133,57 @@ func verifyJenkinsMasterPodAttributes(t *testing.T, jenkins *v1alpha1.Jenkins) { | ||||||
| 	jenkinsPod := getJenkinsMasterPod(t, jenkins) | 	jenkinsPod := getJenkinsMasterPod(t, jenkins) | ||||||
| 	jenkins = getJenkins(t, jenkins.Namespace, jenkins.Name) | 	jenkins = getJenkins(t, jenkins.Namespace, jenkins.Name) | ||||||
| 
 | 
 | ||||||
| 	for key, value := range jenkins.Spec.Master.Annotations { | 	assert.Equal(t, jenkins.Spec.Master.Annotations, jenkinsPod.ObjectMeta.Annotations) | ||||||
| 		if jenkinsPod.ObjectMeta.Annotations[key] != value { | 	assert.Equal(t, jenkins.Spec.Master.NodeSelector, jenkinsPod.Spec.NodeSelector) | ||||||
| 			t.Fatalf("Invalid Jenkins pod annotation expected '%+v', actual '%+v'", jenkins.Spec.Master.Annotations, jenkinsPod.ObjectMeta.Annotations) | 
 | ||||||
|  | 	assert.Equal(t, resources.JenkinsMasterContainerName, jenkinsPod.Spec.Containers[0].Name) | ||||||
|  | 	assert.Equal(t, len(jenkins.Spec.Master.Containers)+1, len(jenkinsPod.Spec.Containers)) | ||||||
|  | 
 | ||||||
|  | 	for _, actualContainer := range jenkinsPod.Spec.Containers { | ||||||
|  | 		if actualContainer.Name == resources.JenkinsMasterContainerName { | ||||||
|  | 			verifyContainer(t, resources.NewJenkinsMasterContainer(jenkins), actualContainer) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		var expectedContainer *corev1.Container | ||||||
|  | 		for _, jenkinsContainer := range jenkins.Spec.Master.Containers { | ||||||
|  | 			if jenkinsContainer.Name == actualContainer.Name { | ||||||
|  | 				tmp := resources.ConvertJenkinsContainerToKubernetesContainer(jenkinsContainer) | ||||||
|  | 				expectedContainer = &tmp | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 	jenkinsContainer := jenkinsPod.Spec.Containers[0] | 		if expectedContainer == nil { | ||||||
| 
 | 			t.Errorf("Container '%+v' not found in pod", actualContainer) | ||||||
| 	if jenkinsContainer.Image != jenkins.Spec.Master.Image { | 			continue | ||||||
| 		t.Fatalf("Invalid jenkins pod image expected '%s', actual '%s'", jenkins.Spec.Master.Image, jenkinsContainer.Image) |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 	if !reflect.DeepEqual(jenkinsContainer.Resources, jenkins.Spec.Master.Resources) { | 		verifyContainer(t, *expectedContainer, actualContainer) | ||||||
| 		t.Fatalf("Invalid jenkins pod continer resources expected '%+v', actual '%+v'", jenkins.Spec.Master.Resources, jenkinsContainer.Resources) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if !reflect.DeepEqual(jenkinsPod.Spec.NodeSelector, jenkins.Spec.Master.NodeSelector) { |  | ||||||
| 		t.Fatalf("Invalid jenkins pod node selector expected '%+v', actual '%+v'", jenkins.Spec.Master.NodeSelector, jenkinsPod.Spec.NodeSelector) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if !reflect.DeepEqual(jenkinsContainer.ReadinessProbe, jenkins.Spec.Master.ReadinessProbe) { |  | ||||||
| 		t.Fatalf("Invalid jenkins pod readinessProbe. Expected '%+v', actual '%+v'", jenkins.Spec.Master.ReadinessProbe, jenkinsContainer.ReadinessProbe) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if !reflect.DeepEqual(jenkinsContainer.LivenessProbe, jenkins.Spec.Master.LivenessProbe) { |  | ||||||
| 		t.Fatalf("Invalid jenkins pod livenessProbe. Expected '%+v', actual '%+v'", jenkins.Spec.Master.LivenessProbe, jenkinsContainer.LivenessProbe) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	requiredEnvs := resources.GetJenkinsMasterPodBaseEnvs() |  | ||||||
| 	requiredEnvs = append(requiredEnvs, jenkins.Spec.Master.Env...) |  | ||||||
| 	if !reflect.DeepEqual(jenkinsContainer.Env, requiredEnvs) { |  | ||||||
| 		t.Fatalf("Invalid jenkins pod continer resources expected '%+v', actual '%+v'", requiredEnvs, jenkinsContainer.Env) |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	t.Log("Jenkins pod attributes are valid") | 	t.Log("Jenkins pod attributes are valid") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func verifyContainer(t *testing.T, expected corev1.Container, actual corev1.Container) { | ||||||
|  | 	assert.Equal(t, expected.Args, actual.Args, expected.Name, expected.Name) | ||||||
|  | 	assert.Equal(t, expected.Command, actual.Command, expected.Name) | ||||||
|  | 	assert.Equal(t, expected.Env, actual.Env, expected.Name) | ||||||
|  | 	assert.Equal(t, expected.EnvFrom, actual.EnvFrom, expected.Name) | ||||||
|  | 	assert.Equal(t, expected.Image, actual.Image, expected.Name) | ||||||
|  | 	assert.Equal(t, expected.ImagePullPolicy, actual.ImagePullPolicy, expected.Name) | ||||||
|  | 	assert.Equal(t, expected.Lifecycle, actual.Lifecycle, expected.Name) | ||||||
|  | 	assert.Equal(t, expected.LivenessProbe, actual.LivenessProbe, expected.Name) | ||||||
|  | 	assert.Equal(t, expected.Ports, actual.Ports, expected.Name) | ||||||
|  | 	assert.Equal(t, expected.ReadinessProbe, actual.ReadinessProbe, expected.Name) | ||||||
|  | 	assert.Equal(t, expected.Resources, actual.Resources, expected.Name) | ||||||
|  | 	assert.Equal(t, expected.SecurityContext, actual.SecurityContext, expected.Name) | ||||||
|  | 	assert.Equal(t, expected.WorkingDir, actual.WorkingDir, expected.Name) | ||||||
|  | 	if !base.CompareContainerVolumeMounts(expected, actual) { | ||||||
|  | 		t.Errorf("Volume mounts are different in container '%s': expected '%+v', actual '%+v'", | ||||||
|  | 			expected.Name, expected.VolumeMounts, actual.VolumeMounts) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func verifyPlugins(t *testing.T, jenkinsClient jenkinsclient.Jenkins, jenkins *v1alpha1.Jenkins) { | func verifyPlugins(t *testing.T, jenkinsClient jenkinsclient.Jenkins, jenkins *v1alpha1.Jenkins) { | ||||||
| 	installedPlugins, err := jenkinsClient.GetPlugins(1) | 	installedPlugins, err := jenkinsClient.GetPlugins(1) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  |  | ||||||
|  | @ -73,13 +73,9 @@ func createJenkinsCR(t *testing.T, name, namespace string, seedJob *[]v1alpha1.S | ||||||
| 		}, | 		}, | ||||||
| 		Spec: v1alpha1.JenkinsSpec{ | 		Spec: v1alpha1.JenkinsSpec{ | ||||||
| 			Master: v1alpha1.JenkinsMaster{ | 			Master: v1alpha1.JenkinsMaster{ | ||||||
| 				Image:       "jenkins/jenkins", |  | ||||||
| 				Annotations: map[string]string{"test": "label"}, | 				Annotations: map[string]string{"test": "label"}, | ||||||
| 				Plugins: map[string][]string{ | 				Container: v1alpha1.Container{ | ||||||
| 					"audit-trail:2.4":           {}, | 					Image: "jenkins/jenkins", | ||||||
| 					"simple-theme-plugin:0.5.1": {}, |  | ||||||
| 				}, |  | ||||||
| 				NodeSelector: map[string]string{"kubernetes.io/hostname": "minikube"}, |  | ||||||
| 					Env: []v1.EnvVar{ | 					Env: []v1.EnvVar{ | ||||||
| 						{ | 						{ | ||||||
| 							Name:  "TEST_ENV", | 							Name:  "TEST_ENV", | ||||||
|  | @ -94,7 +90,7 @@ func createJenkinsCR(t *testing.T, name, namespace string, seedJob *[]v1alpha1.S | ||||||
| 								Scheme: corev1.URISchemeHTTP, | 								Scheme: corev1.URISchemeHTTP, | ||||||
| 							}, | 							}, | ||||||
| 						}, | 						}, | ||||||
| 					InitialDelaySeconds: int32(35), | 						InitialDelaySeconds: int32(80), | ||||||
| 						TimeoutSeconds:      int32(4), | 						TimeoutSeconds:      int32(4), | ||||||
| 						FailureThreshold:    int32(10), | 						FailureThreshold:    int32(10), | ||||||
| 					}, | 					}, | ||||||
|  | @ -106,11 +102,23 @@ func createJenkinsCR(t *testing.T, name, namespace string, seedJob *[]v1alpha1.S | ||||||
| 								Scheme: corev1.URISchemeHTTP, | 								Scheme: corev1.URISchemeHTTP, | ||||||
| 							}, | 							}, | ||||||
| 						}, | 						}, | ||||||
| 					InitialDelaySeconds: int32(40), | 						InitialDelaySeconds: int32(80), | ||||||
| 						TimeoutSeconds:      int32(4), | 						TimeoutSeconds:      int32(4), | ||||||
| 						FailureThreshold:    int32(10), | 						FailureThreshold:    int32(10), | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
|  | 				Containers: []v1alpha1.Container{ | ||||||
|  | 					{ | ||||||
|  | 						Name:  "envoyproxy", | ||||||
|  | 						Image: "envoyproxy/envoy-alpine", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				Plugins: map[string][]string{ | ||||||
|  | 					"audit-trail:2.4":           {}, | ||||||
|  | 					"simple-theme-plugin:0.5.1": {}, | ||||||
|  | 				}, | ||||||
|  | 				NodeSelector: map[string]string{"kubernetes.io/hostname": "minikube"}, | ||||||
|  | 			}, | ||||||
| 			SeedJobs: seedJobs, | 			SeedJobs: seedJobs, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -2,7 +2,6 @@ package e2e | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	goctx "context" | 	goctx "context" | ||||||
| 	"fmt" |  | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
|  | @ -27,14 +26,14 @@ var ( | ||||||
| 	timeout       = time.Second * 60 | 	timeout       = time.Second * 60 | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // checkConditionFunc is used to check if a condition for the jenkins CR is true
 | // checkConditionFunc is used to check if a condition for the jenkins CR is set
 | ||||||
| type checkConditionFunc func(*v1alpha1.Jenkins) bool | type checkConditionFunc func(*v1alpha1.Jenkins, error) bool | ||||||
| 
 | 
 | ||||||
| func waitForJenkinsBaseConfigurationToComplete(t *testing.T, jenkins *v1alpha1.Jenkins) { | func waitForJenkinsBaseConfigurationToComplete(t *testing.T, jenkins *v1alpha1.Jenkins) { | ||||||
| 	t.Log("Waiting for Jenkins base configuration to complete") | 	t.Log("Waiting for Jenkins base configuration to complete") | ||||||
| 	_, err := WaitUntilJenkinsConditionTrue(retryInterval, 150, jenkins, func(jenkins *v1alpha1.Jenkins) bool { | 	_, err := WaitUntilJenkinsConditionSet(retryInterval, 150, jenkins, func(jenkins *v1alpha1.Jenkins, err error) bool { | ||||||
| 		t.Logf("Current Jenkins status '%+v'", jenkins.Status) | 		t.Logf("Current Jenkins status: '%+v', error '%s'", jenkins.Status, err) | ||||||
| 		return jenkins.Status.BaseConfigurationCompletedTime != nil | 		return err == nil && jenkins.Status.BaseConfigurationCompletedTime != nil | ||||||
| 	}) | 	}) | ||||||
| 	assert.NoError(t, err) | 	assert.NoError(t, err) | ||||||
| 	t.Log("Jenkins pod is running") | 	t.Log("Jenkins pod is running") | ||||||
|  | @ -68,9 +67,9 @@ func waitForRecreateJenkinsMasterPod(t *testing.T, jenkins *v1alpha1.Jenkins) { | ||||||
| 
 | 
 | ||||||
| func waitForJenkinsUserConfigurationToComplete(t *testing.T, jenkins *v1alpha1.Jenkins) { | func waitForJenkinsUserConfigurationToComplete(t *testing.T, jenkins *v1alpha1.Jenkins) { | ||||||
| 	t.Log("Waiting for Jenkins user configuration to complete") | 	t.Log("Waiting for Jenkins user configuration to complete") | ||||||
| 	_, err := WaitUntilJenkinsConditionTrue(retryInterval, 30, jenkins, func(jenkins *v1alpha1.Jenkins) bool { | 	_, err := WaitUntilJenkinsConditionSet(retryInterval, 70, jenkins, func(jenkins *v1alpha1.Jenkins, err error) bool { | ||||||
| 		t.Logf("Current Jenkins status '%+v'", jenkins.Status) | 		t.Logf("Current Jenkins status: '%+v', error '%s'", jenkins.Status, err) | ||||||
| 		return jenkins.Status.UserConfigurationCompletedTime != nil | 		return err == nil && jenkins.Status.UserConfigurationCompletedTime != nil | ||||||
| 	}) | 	}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
|  | @ -92,16 +91,13 @@ func waitForJenkinsSafeRestart(t *testing.T, jenkinsClient jenkinsclient.Jenkins | ||||||
| 	require.NoError(t, err) | 	require.NoError(t, err) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // WaitUntilJenkinsConditionTrue retries until the specified condition check becomes true for the jenkins CR
 | // WaitUntilJenkinsConditionSet retries until the specified condition check becomes true for the jenkins CR
 | ||||||
| func WaitUntilJenkinsConditionTrue(retryInterval time.Duration, retries int, jenkins *v1alpha1.Jenkins, checkCondition checkConditionFunc) (*v1alpha1.Jenkins, error) { | func WaitUntilJenkinsConditionSet(retryInterval time.Duration, retries int, jenkins *v1alpha1.Jenkins, checkCondition checkConditionFunc) (*v1alpha1.Jenkins, error) { | ||||||
| 	jenkinsStatus := &v1alpha1.Jenkins{} | 	jenkinsStatus := &v1alpha1.Jenkins{} | ||||||
| 	err := wait.Poll(retryInterval, time.Duration(retries)*retryInterval, func() (bool, error) { | 	err := wait.Poll(retryInterval, time.Duration(retries)*retryInterval, func() (bool, error) { | ||||||
| 		namespacedName := types.NamespacedName{Namespace: jenkins.Namespace, Name: jenkins.Name} | 		namespacedName := types.NamespacedName{Namespace: jenkins.Namespace, Name: jenkins.Name} | ||||||
| 		err := framework.Global.Client.Get(goctx.TODO(), namespacedName, jenkinsStatus) | 		err := framework.Global.Client.Get(goctx.TODO(), namespacedName, jenkinsStatus) | ||||||
| 		if err != nil { | 		return checkCondition(jenkinsStatus, err), nil | ||||||
| 			return false, fmt.Errorf("failed to get CR: %v", err) |  | ||||||
| 		} |  | ||||||
| 		return checkCondition(jenkinsStatus), nil |  | ||||||
| 	}) | 	}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue