mirror of
https://github.com/jenkinsci/kubernetes-operator.git
synced 2026-10-03 06:31:08 +02:00
Allow configure sidecars in Jenkins pod
This commit is contained in:
@@ -373,7 +373,7 @@ func isPodTerminating(pod corev1.Pod) bool {
|
||||
|
||||
func (r *ReconcileJenkinsBaseConfiguration) isRecreatePodNeeded(currentJenkinsMasterPod corev1.Pod) bool {
|
||||
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))
|
||||
return true
|
||||
}
|
||||
@@ -385,13 +385,9 @@ func (r *ReconcileJenkinsBaseConfiguration) isRecreatePodNeeded(currentJenkinsMa
|
||||
return true
|
||||
}
|
||||
|
||||
if r.jenkins.Spec.Master.Image != currentJenkinsMasterPod.Spec.Containers[0].Image {
|
||||
r.logger.Info(fmt.Sprintf("Jenkins image has changed to '%+v', recreating pod", r.jenkins.Spec.Master.Image))
|
||||
return true
|
||||
}
|
||||
|
||||
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))
|
||||
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
|
||||
}
|
||||
|
||||
@@ -401,47 +397,119 @@ func (r *ReconcileJenkinsBaseConfiguration) isRecreatePodNeeded(currentJenkinsMa
|
||||
return true
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(r.jenkins.Spec.Master.Resources, currentJenkinsMasterPod.Spec.Containers[0].Resources) {
|
||||
r.logger.Info(fmt.Sprintf("Jenkins pod resources have changed, actual '%+v' required '%+v' - recreating pod",
|
||||
currentJenkinsMasterPod.Spec.Containers[0].Resources, r.jenkins.Spec.Master.Resources))
|
||||
if (len(r.jenkins.Spec.Master.Containers) + 1) != len(currentJenkinsMasterPod.Spec.Containers) {
|
||||
r.logger.Info(fmt.Sprintf("Jenkins amount of containers has changed to '%+v', recreating pod", len(r.jenkins.Spec.Master.Containers)+1))
|
||||
return true
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(r.jenkins.Spec.Master.ReadinessProbe, currentJenkinsMasterPod.Spec.Containers[0].ReadinessProbe) {
|
||||
r.logger.Info(fmt.Sprintf("Jenkins pod readinessProbe have changed, actual '%+v' required '%+v' - recreating pod",
|
||||
currentJenkinsMasterPod.Spec.Containers[0].ReadinessProbe, r.jenkins.Spec.Master.ReadinessProbe))
|
||||
return true
|
||||
for _, actualContainer := range currentJenkinsMasterPod.Spec.Containers {
|
||||
if actualContainer.Name == resources.JenkinsMasterContainerName {
|
||||
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
|
||||
}
|
||||
|
||||
if changed := r.compareContainers(*expectedContainer, actualContainer); changed {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(r.jenkins.Spec.Master.LivenessProbe, currentJenkinsMasterPod.Spec.Containers[0].LivenessProbe) {
|
||||
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 false
|
||||
}
|
||||
|
||||
func (r *ReconcileJenkinsBaseConfiguration) compareContainers(expected corev1.Container, actual corev1.Container) bool {
|
||||
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(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))
|
||||
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
|
||||
}
|
||||
|
||||
requiredEnvs := resources.GetJenkinsMasterPodBaseEnvs()
|
||||
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",
|
||||
currentJenkinsMasterPod.Spec.Containers[0].Env, requiredEnvs))
|
||||
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 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 {
|
||||
currentJenkinsMasterPod, err := r.getJenkinsMasterPod(meta)
|
||||
r.logger.Info(fmt.Sprintf("Terminating Jenkins Master Pod %s/%s", currentJenkinsMasterPod.Namespace, currentJenkinsMasterPod.Name))
|
||||
if err != nil {
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -461,11 +529,20 @@ func (r *ReconcileJenkinsBaseConfiguration) waitForJenkins(meta metav1.ObjectMet
|
||||
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 5}, nil
|
||||
}
|
||||
|
||||
containersReadyCount := 0
|
||||
for _, containerStatus := range jenkinsMasterPodStatus.Status.ContainerStatuses {
|
||||
if !containerStatus.Ready {
|
||||
r.logger.V(log.VDebug).Info("Jenkins master pod not ready, readiness probe failed")
|
||||
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 5}, nil
|
||||
if containerStatus.State.Terminated != nil {
|
||||
r.logger.Info(fmt.Sprintf("Container '%s' is terminated, status '%+v', recreating pod", containerStatus.Name, containerStatus))
|
||||
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
|
||||
|
||||
@@ -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,9 +11,11 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
jenkinsHomeVolumeName = "home"
|
||||
jenkinsPath = "/var/jenkins"
|
||||
jenkinsHomePath = jenkinsPath + "/home"
|
||||
// JenkinsMasterContainerName is the Jenkins master container name in pod
|
||||
JenkinsMasterContainerName = "jenkins-master"
|
||||
jenkinsHomeVolumeName = "home"
|
||||
jenkinsPath = "/var/jenkins"
|
||||
jenkinsHomePath = jenkinsPath + "/home"
|
||||
|
||||
jenkinsScriptsVolumeName = "scripts"
|
||||
jenkinsScriptsVolumePath = jenkinsPath + "/scripts"
|
||||
@@ -72,13 +74,111 @@ func GetJenkinsMasterPodBaseEnvs() []corev1.EnvVar {
|
||||
}
|
||||
}
|
||||
|
||||
// NewJenkinsMasterContainer returns Jenkins master Kubernetes container
|
||||
func NewJenkinsMasterContainer(jenkins *v1alpha1.Jenkins) corev1.Container {
|
||||
envs := GetJenkinsMasterPodBaseEnvs()
|
||||
envs = append(envs, jenkins.Spec.Master.Env...)
|
||||
|
||||
return corev1.Container{
|
||||
Name: JenkinsMasterContainerName,
|
||||
Image: jenkins.Spec.Master.Image,
|
||||
ImagePullPolicy: jenkins.Spec.Master.ImagePullPolicy,
|
||||
Command: []string{
|
||||
"bash",
|
||||
fmt.Sprintf("%s/%s", jenkinsScriptsVolumePath, initScriptName),
|
||||
},
|
||||
LivenessProbe: jenkins.Spec.Master.LivenessProbe,
|
||||
ReadinessProbe: jenkins.Spec.Master.ReadinessProbe,
|
||||
Ports: []corev1.ContainerPort{
|
||||
{
|
||||
Name: httpPortName,
|
||||
ContainerPort: constants.DefaultHTTPPortInt32,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
},
|
||||
{
|
||||
Name: slavePortName,
|
||||
ContainerPort: constants.DefaultSlavePortInt32,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
},
|
||||
},
|
||||
Env: envs,
|
||||
Resources: jenkins.Spec.Master.Resources,
|
||||
VolumeMounts: []corev1.VolumeMount{
|
||||
{
|
||||
Name: jenkinsHomeVolumeName,
|
||||
MountPath: jenkinsHomePath,
|
||||
ReadOnly: false,
|
||||
},
|
||||
{
|
||||
Name: jenkinsScriptsVolumeName,
|
||||
MountPath: jenkinsScriptsVolumePath,
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: jenkinsInitConfigurationVolumeName,
|
||||
MountPath: jenkinsInitConfigurationVolumePath,
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: jenkinsBaseConfigurationVolumeName,
|
||||
MountPath: JenkinsBaseConfigurationVolumePath,
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: jenkinsUserConfigurationVolumeName,
|
||||
MountPath: JenkinsUserConfigurationVolumePath,
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: jenkinsOperatorCredentialsVolumeName,
|
||||
MountPath: jenkinsOperatorCredentialsVolumePath,
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: userConfigurationSecretVolumeName,
|
||||
MountPath: UserConfigurationSecretVolumePath,
|
||||
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
|
||||
envs := GetJenkinsMasterPodBaseEnvs()
|
||||
envs = append(envs, jenkins.Spec.Master.Env...)
|
||||
|
||||
return &corev1.Pod{
|
||||
TypeMeta: buildPodTypeMeta(),
|
||||
@@ -91,68 +191,7 @@ func NewJenkinsMasterPod(objectMeta metav1.ObjectMeta, jenkins *v1alpha1.Jenkins
|
||||
RunAsGroup: &runAsUser,
|
||||
},
|
||||
NodeSelector: jenkins.Spec.Master.NodeSelector,
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "jenkins-master",
|
||||
Image: jenkins.Spec.Master.Image,
|
||||
ImagePullPolicy: jenkins.Spec.Master.ImagePullPolicy,
|
||||
Command: []string{
|
||||
"bash",
|
||||
fmt.Sprintf("%s/%s", jenkinsScriptsVolumePath, initScriptName),
|
||||
},
|
||||
LivenessProbe: jenkins.Spec.Master.LivenessProbe,
|
||||
ReadinessProbe: jenkins.Spec.Master.ReadinessProbe,
|
||||
Ports: []corev1.ContainerPort{
|
||||
{
|
||||
Name: httpPortName,
|
||||
ContainerPort: constants.DefaultHTTPPortInt32,
|
||||
},
|
||||
{
|
||||
Name: slavePortName,
|
||||
ContainerPort: constants.DefaultSlavePortInt32,
|
||||
},
|
||||
},
|
||||
Env: envs,
|
||||
Resources: jenkins.Spec.Master.Resources,
|
||||
VolumeMounts: []corev1.VolumeMount{
|
||||
{
|
||||
Name: jenkinsHomeVolumeName,
|
||||
MountPath: jenkinsHomePath,
|
||||
ReadOnly: false,
|
||||
},
|
||||
{
|
||||
Name: jenkinsScriptsVolumeName,
|
||||
MountPath: jenkinsScriptsVolumePath,
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: jenkinsInitConfigurationVolumeName,
|
||||
MountPath: jenkinsInitConfigurationVolumePath,
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: jenkinsBaseConfigurationVolumeName,
|
||||
MountPath: JenkinsBaseConfigurationVolumePath,
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: jenkinsUserConfigurationVolumeName,
|
||||
MountPath: JenkinsUserConfigurationVolumePath,
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: jenkinsOperatorCredentialsVolumeName,
|
||||
MountPath: jenkinsOperatorCredentialsVolumePath,
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: userConfigurationSecretVolumeName,
|
||||
MountPath: UserConfigurationSecretVolumePath,
|
||||
ReadOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Containers: newContainers(jenkins),
|
||||
Volumes: []corev1.Volume{
|
||||
{
|
||||
Name: jenkinsHomeVolumeName,
|
||||
|
||||
@@ -18,15 +18,14 @@ var (
|
||||
|
||||
// Validate validates Jenkins CR Spec.master section
|
||||
func (r *ReconcileJenkinsBaseConfiguration) Validate(jenkins *v1alpha1.Jenkins) (bool, error) {
|
||||
if jenkins.Spec.Master.Image == "" {
|
||||
r.logger.V(log.VWarn).Info("Image not set")
|
||||
if !r.validateContainer(jenkins.Spec.Master.Container) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if !dockerImageRegexp.MatchString(jenkins.Spec.Master.Image) && !docker.ReferenceRegexp.MatchString(jenkins.Spec.Master.Image) {
|
||||
r.logger.V(log.VWarn).Info("Invalid image")
|
||||
return false, nil
|
||||
|
||||
for _, container := range jenkins.Spec.Master.Containers {
|
||||
if !r.validateContainer(container) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
baseEnvs := resources.GetJenkinsMasterPodBaseEnvs()
|
||||
baseEnvNames := map[string]string{}
|
||||
|
||||
@@ -74,10 +74,12 @@ func TestValidateJenkinsMasterPodEnvs(t *testing.T) {
|
||||
jenkins := v1alpha1.Jenkins{
|
||||
Spec: v1alpha1.JenkinsSpec{
|
||||
Master: v1alpha1.JenkinsMaster{
|
||||
Env: []v1.EnvVar{
|
||||
{
|
||||
Name: "SOME_VALUE",
|
||||
Value: "",
|
||||
Container: v1alpha1.Container{
|
||||
Env: []v1.EnvVar{
|
||||
{
|
||||
Name: "SOME_VALUE",
|
||||
Value: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -92,10 +94,12 @@ func TestValidateJenkinsMasterPodEnvs(t *testing.T) {
|
||||
jenkins := v1alpha1.Jenkins{
|
||||
Spec: v1alpha1.JenkinsSpec{
|
||||
Master: v1alpha1.JenkinsMaster{
|
||||
Env: []v1.EnvVar{
|
||||
{
|
||||
Name: "JENKINS_HOME",
|
||||
Value: "",
|
||||
Container: v1alpha1.Container{
|
||||
Env: []v1.EnvVar{
|
||||
{
|
||||
Name: "JENKINS_HOME",
|
||||
Value: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -119,16 +119,18 @@ func jenkinsCustomResource() *v1alpha1.Jenkins {
|
||||
},
|
||||
Spec: v1alpha1.JenkinsSpec{
|
||||
Master: v1alpha1.JenkinsMaster{
|
||||
Image: "jenkins/jenkins",
|
||||
Annotations: map[string]string{"test": "label"},
|
||||
Resources: corev1.ResourceRequirements{
|
||||
Requests: corev1.ResourceList{
|
||||
corev1.ResourceCPU: resource.MustParse("300m"),
|
||||
corev1.ResourceMemory: resource.MustParse("500Mi"),
|
||||
},
|
||||
Limits: corev1.ResourceList{
|
||||
corev1.ResourceCPU: resource.MustParse("2"),
|
||||
corev1.ResourceMemory: resource.MustParse("2Gi"),
|
||||
Container: v1alpha1.Container{
|
||||
Image: "jenkins/jenkins",
|
||||
Resources: corev1.ResourceRequirements{
|
||||
Requests: corev1.ResourceList{
|
||||
corev1.ResourceCPU: resource.MustParse("300m"),
|
||||
corev1.ResourceMemory: resource.MustParse("500Mi"),
|
||||
},
|
||||
Limits: corev1.ResourceList{
|
||||
corev1.ResourceCPU: resource.MustParse("2"),
|
||||
corev1.ResourceMemory: resource.MustParse("2Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -252,7 +252,7 @@ func (r *ReconcileJenkins) setDefaults(jenkins *v1alpha1.Jenkins, logger logr.Lo
|
||||
Scheme: corev1.URISchemeHTTP,
|
||||
},
|
||||
},
|
||||
InitialDelaySeconds: int32(30),
|
||||
InitialDelaySeconds: int32(80),
|
||||
TimeoutSeconds: int32(5),
|
||||
FailureThreshold: int32(12),
|
||||
}
|
||||
@@ -276,12 +276,8 @@ func (r *ReconcileJenkins) setDefaults(jenkins *v1alpha1.Jenkins, logger logr.Lo
|
||||
changed = true
|
||||
jenkins.Spec.Master.Plugins = map[string][]string{"simple-theme-plugin:0.5.1": {}}
|
||||
}
|
||||
_, requestCPUSet := jenkins.Spec.Master.Resources.Requests[corev1.ResourceCPU]
|
||||
_, requestMemporySet := jenkins.Spec.Master.Resources.Requests[corev1.ResourceMemory]
|
||||
_, 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")
|
||||
if isResourceRequirementsNotSet(jenkins.Spec.Master.Resources) {
|
||||
logger.Info("Setting default Jenkins master container resource requirements")
|
||||
changed = true
|
||||
jenkins.Spec.Master.Resources = corev1.ResourceRequirements{
|
||||
Requests: corev1.ResourceList{
|
||||
@@ -318,9 +314,49 @@ func (r *ReconcileJenkins) setDefaults(jenkins *v1alpha1.Jenkins, logger logr.Lo
|
||||
Port: constants.DefaultSlavePortInt32,
|
||||
}
|
||||
}
|
||||
for i, container := range jenkins.Spec.Master.Containers {
|
||||
if setDefaultsForContainer(jenkins, i, logger.WithValues("container", container.Name)) {
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
|
||||
if changed {
|
||||
return errors.WithStack(r.client.Update(context.TODO(), jenkins))
|
||||
}
|
||||
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,16 +387,18 @@ func jenkinsCustomResource() *v1alpha1.Jenkins {
|
||||
},
|
||||
Spec: v1alpha1.JenkinsSpec{
|
||||
Master: v1alpha1.JenkinsMaster{
|
||||
Image: "jenkins/jenkins",
|
||||
Annotations: map[string]string{"test": "label"},
|
||||
Resources: corev1.ResourceRequirements{
|
||||
Requests: corev1.ResourceList{
|
||||
corev1.ResourceCPU: resource.MustParse("300m"),
|
||||
corev1.ResourceMemory: resource.MustParse("500Mi"),
|
||||
},
|
||||
Limits: corev1.ResourceList{
|
||||
corev1.ResourceCPU: resource.MustParse("2"),
|
||||
corev1.ResourceMemory: resource.MustParse("2Gi"),
|
||||
Container: v1alpha1.Container{
|
||||
Image: "jenkins/jenkins",
|
||||
Resources: corev1.ResourceRequirements{
|
||||
Requests: corev1.ResourceList{
|
||||
corev1.ResourceCPU: resource.MustParse("300m"),
|
||||
corev1.ResourceMemory: resource.MustParse("500Mi"),
|
||||
},
|
||||
Limits: corev1.ResourceList{
|
||||
corev1.ResourceCPU: resource.MustParse("2"),
|
||||
corev1.ResourceMemory: resource.MustParse("2Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user