kubernetes-operator/pkg/configuration/base/pod.go

218 lines
9.9 KiB
Go

package base
import (
"context"
"fmt"
"reflect"
"github.com/jenkinsci/kubernetes-operator/api/v1alpha2"
"github.com/jenkinsci/kubernetes-operator/pkg/configuration/backuprestore"
"github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources"
"github.com/jenkinsci/kubernetes-operator/pkg/notifications/event"
"github.com/jenkinsci/kubernetes-operator/pkg/notifications/reason"
"github.com/jenkinsci/kubernetes-operator/version"
stackerr "github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
func (r *JenkinsBaseConfigurationReconciler) checkForPodRecreation(currentJenkinsMasterPod corev1.Pod, userAndPasswordHash string) reason.Reason {
var messages []string
var verbose []string
if currentJenkinsMasterPod.Status.Phase == corev1.PodFailed ||
currentJenkinsMasterPod.Status.Phase == corev1.PodSucceeded ||
currentJenkinsMasterPod.Status.Phase == corev1.PodUnknown {
//TODO add Jenkins last 10 line logs
messages = append(messages, fmt.Sprintf("Invalid Jenkins pod phase '%s'", currentJenkinsMasterPod.Status.Phase))
verbose = append(verbose, fmt.Sprintf("Invalid Jenkins pod phase '%+v'", currentJenkinsMasterPod.Status))
return reason.NewPodRestart(reason.KubernetesSource, messages, verbose...)
}
userAndPasswordHashIsDifferent := userAndPasswordHash != r.Configuration.Jenkins.Status.UserAndPasswordHash
userAndPasswordHashStatusNotEmpty := r.Configuration.Jenkins.Status.UserAndPasswordHash != ""
if userAndPasswordHashIsDifferent && userAndPasswordHashStatusNotEmpty {
messages = append(messages, "User or password have changed")
verbose = append(verbose, "User or password have changed, recreating pod")
}
if r.Configuration.Jenkins.Spec.Restore.RecoveryOnce != 0 && r.Configuration.Jenkins.Status.RestoredBackup != 0 {
messages = append(messages, "spec.restore.recoveryOnce is set")
verbose = append(verbose, "spec.restore.recoveryOnce is set, recreating pod")
}
if version.Version != r.Configuration.Jenkins.Status.OperatorVersion {
messages = append(messages, "Jenkins Operator version has changed")
verbose = append(verbose, fmt.Sprintf("Jenkins Operator version has changed, actual '%+v' new '%+v'",
r.Configuration.Jenkins.Status.OperatorVersion, version.Version))
}
//FIXME too hacky
var jenkinsSecurityContext *corev1.PodSecurityContext
if r.Configuration.Jenkins.Spec.Master.SecurityContext == nil {
jenkinsSecurityContext = &corev1.PodSecurityContext{}
} else {
jenkinsSecurityContext = r.Configuration.Jenkins.Spec.Master.SecurityContext
}
if !reflect.DeepEqual(jenkinsSecurityContext, currentJenkinsMasterPod.Spec.SecurityContext) {
messages = append(messages, "Jenkins pod security context has changed")
verbose = append(verbose, fmt.Sprintf("Jenkins pod security context has changed, actual '%+v' required '%+v'",
currentJenkinsMasterPod.Spec.SecurityContext, r.Configuration.Jenkins.Spec.Master.SecurityContext))
}
if !compareImagePullSecrets(r.Configuration.Jenkins.Spec.Master.ImagePullSecrets, currentJenkinsMasterPod.Spec.ImagePullSecrets) {
messages = append(messages, "Jenkins Pod ImagePullSecrets has changed")
verbose = append(verbose, fmt.Sprintf("Jenkins Pod ImagePullSecrets has changed, actual '%+v' required '%+v'",
currentJenkinsMasterPod.Spec.ImagePullSecrets, r.Configuration.Jenkins.Spec.Master.ImagePullSecrets))
}
if !compareMap(r.Configuration.Jenkins.Spec.Master.NodeSelector, currentJenkinsMasterPod.Spec.NodeSelector) {
messages = append(messages, "Jenkins pod node selector has changed")
verbose = append(verbose, fmt.Sprintf("Jenkins pod node selector has changed, actual '%+v' required '%+v'",
currentJenkinsMasterPod.Spec.NodeSelector, r.Configuration.Jenkins.Spec.Master.NodeSelector))
}
if !compareMap(r.Configuration.Jenkins.Spec.Master.Labels, currentJenkinsMasterPod.Labels) {
messages = append(messages, "Jenkins pod labels have changed")
verbose = append(verbose, fmt.Sprintf("Jenkins pod labels have changed, actual '%+v' required '%+v'",
currentJenkinsMasterPod.Labels, r.Configuration.Jenkins.Spec.Master.Labels))
}
if !compareMap(r.Configuration.Jenkins.Spec.Master.Annotations, currentJenkinsMasterPod.ObjectMeta.Annotations) {
messages = append(messages, "Jenkins pod annotations have changed")
verbose = append(verbose, fmt.Sprintf("Jenkins pod annotations have changed, actual '%+v' required '%+v'",
currentJenkinsMasterPod.ObjectMeta.Annotations, r.Configuration.Jenkins.Spec.Master.Annotations))
}
if !r.compareVolumes(currentJenkinsMasterPod) {
messages = append(messages, "Jenkins pod volumes have changed")
verbose = append(verbose, fmt.Sprintf("Jenkins pod volumes have changed, actual '%v' required '%v'",
currentJenkinsMasterPod.Spec.Volumes, r.Configuration.Jenkins.Spec.Master.Volumes))
}
if len(r.Configuration.Jenkins.Spec.Master.Containers) != len(currentJenkinsMasterPod.Spec.Containers) {
messages = append(messages, "Jenkins amount of containers has changed")
verbose = append(verbose, fmt.Sprintf("Jenkins amount of containers has changed, actual '%+v' required '%+v'",
len(currentJenkinsMasterPod.Spec.Containers), len(r.Configuration.Jenkins.Spec.Master.Containers)))
}
if r.Configuration.Jenkins.Spec.Master.PriorityClassName != currentJenkinsMasterPod.Spec.PriorityClassName {
messages = append(messages, "Jenkins priorityClassName has changed")
verbose = append(verbose, fmt.Sprintf("Jenkins priorityClassName has changed, actual '%+v' required '%+v'",
currentJenkinsMasterPod.Spec.PriorityClassName, r.Configuration.Jenkins.Spec.Master.PriorityClassName))
}
customResourceReplaced := (r.Configuration.Jenkins.Status.BaseConfigurationCompletedTime == nil ||
r.Configuration.Jenkins.Status.UserConfigurationCompletedTime == nil) &&
r.Configuration.Jenkins.Status.UserAndPasswordHash == ""
if customResourceReplaced {
messages = append(messages, "Jenkins CR has been replaced")
verbose = append(verbose, "Jenkins CR has been replaced")
}
for _, actualContainer := range currentJenkinsMasterPod.Spec.Containers {
if actualContainer.Name == resources.JenkinsMasterContainerName {
containerMessages, verboseMessages := r.compareContainers(resources.NewJenkinsMasterContainer(r.Configuration.Jenkins), actualContainer)
messages = append(messages, containerMessages...)
verbose = append(verbose, verboseMessages...)
continue
}
var expectedContainer *corev1.Container
for _, jenkinsContainer := range r.Configuration.Jenkins.Spec.Master.Containers {
if jenkinsContainer.Name == actualContainer.Name {
tmp := resources.ConvertJenkinsContainerToKubernetesContainer(jenkinsContainer)
expectedContainer = &tmp
}
}
if expectedContainer == nil {
messages = append(messages, fmt.Sprintf("Container '%s' not found in pod", actualContainer.Name))
verbose = append(verbose, fmt.Sprintf("Container '%+v' not found in pod", actualContainer))
continue
}
containerMessages, verboseMessages := r.compareContainers(*expectedContainer, actualContainer)
messages = append(messages, containerMessages...)
verbose = append(verbose, verboseMessages...)
}
return reason.NewPodRestart(reason.OperatorSource, messages, verbose...)
}
func (r *JenkinsBaseConfigurationReconciler) ensureJenkinsMasterPod(meta metav1.ObjectMeta) (reconcile.Result, error) {
userAndPasswordHash, err := r.calculateUserAndPasswordHash()
if err != nil {
return reconcile.Result{}, err
}
// Check if this Pod already exists
currentJenkinsMasterPod, err := r.Configuration.GetJenkinsMasterPod()
if err != nil && apierrors.IsNotFound(err) {
jenkinsMasterPod := resources.NewJenkinsMasterPod(meta, r.Configuration.Jenkins)
*r.Notifications <- event.Event{
Jenkins: *r.Configuration.Jenkins,
Phase: event.PhaseBase,
Level: v1alpha2.NotificationLevelInfo,
Reason: reason.NewPodCreation(reason.OperatorSource, []string{"Creating a new Jenkins Master Pod"}),
}
r.logger.Info(fmt.Sprintf("Creating a new Jenkins Master Pod %s/%s", jenkinsMasterPod.Namespace, jenkinsMasterPod.Name))
err = r.CreateResource(jenkinsMasterPod)
if err != nil {
return reconcile.Result{}, stackerr.WithStack(err)
}
now := metav1.Now()
r.Configuration.Jenkins.Status = v1alpha2.JenkinsStatus{
OperatorVersion: version.Version,
ProvisionStartTime: &now,
LastBackup: r.Configuration.Jenkins.Status.LastBackup,
PendingBackup: r.Configuration.Jenkins.Status.LastBackup,
UserAndPasswordHash: userAndPasswordHash,
}
return reconcile.Result{Requeue: true}, r.Client.Status().Update(context.TODO(), r.Configuration.Jenkins)
} else if err != nil && !apierrors.IsNotFound(err) {
return reconcile.Result{}, stackerr.WithStack(err)
}
if currentJenkinsMasterPod == nil {
return reconcile.Result{Requeue: true}, nil
}
if r.IsJenkinsTerminating(*currentJenkinsMasterPod) && r.Configuration.Jenkins.Status.UserConfigurationCompletedTime != nil {
backupAndRestore := backuprestore.New(r.Configuration, r.logger)
if backupAndRestore.IsBackupTriggerEnabled() {
backupAndRestore.StopBackupTrigger()
return reconcile.Result{Requeue: true}, nil
}
if r.Configuration.Jenkins.Spec.Backup.MakeBackupBeforePodDeletion && !r.Configuration.Jenkins.Status.BackupDoneBeforePodDeletion {
if r.Configuration.Jenkins.Status.LastBackup == r.Configuration.Jenkins.Status.PendingBackup {
r.Configuration.Jenkins.Status.PendingBackup++
}
if err = backupAndRestore.Backup(true); err != nil {
return reconcile.Result{}, err
}
}
return reconcile.Result{Requeue: true}, nil
}
if !r.IsJenkinsTerminating(*currentJenkinsMasterPod) {
restartReason := r.checkForPodRecreation(*currentJenkinsMasterPod, userAndPasswordHash)
if restartReason.HasMessages() {
for _, msg := range restartReason.Verbose() {
r.logger.Info(msg)
}
return reconcile.Result{Requeue: true}, r.Configuration.RestartJenkinsMasterPod(restartReason)
}
}
return reconcile.Result{}, nil
}