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