pkg/controller/jenkins_job/jenkins_job_controller.go (267 lines of code) (raw):

package jenkins import ( "context" "encoding/json" "fmt" "reflect" "time" "github.com/go-logr/logr" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" jenkinsApi "github.com/epam/edp-jenkins-operator/v2/pkg/apis/v2/v1" jenkinsClient "github.com/epam/edp-jenkins-operator/v2/pkg/client/jenkins" "github.com/epam/edp-jenkins-operator/v2/pkg/controller/helper" "github.com/epam/edp-jenkins-operator/v2/pkg/controller/jenkins_job/chain" "github.com/epam/edp-jenkins-operator/v2/pkg/service/platform" "github.com/epam/edp-jenkins-operator/v2/pkg/util/consts" "github.com/epam/edp-jenkins-operator/v2/pkg/util/finalizer" plutil "github.com/epam/edp-jenkins-operator/v2/pkg/util/platform" ) const ( jenkinsJobFinalizerName = "jenkinsjob.finalizer.name" logNamespaceKey = "Request.Namespace" logNameKey = "name" logJenkinsJobKey = "jenkins job" requeueAfter = 10 * time.Second ) func NewReconcileJenkinsJob(k8sClient client.Client, scheme *runtime.Scheme, log logr.Logger, ps platform.PlatformService) *ReconcileJenkinsJob { return &ReconcileJenkinsJob{ client: k8sClient, scheme: scheme, platform: ps, log: log.WithName("jenkins-job"), } } type ReconcileJenkinsJob struct { client client.Client scheme *runtime.Scheme platform platform.PlatformService log logr.Logger } func (r *ReconcileJenkinsJob) SetupWithManager(mgr ctrl.Manager, maxConcurrentReconciles int) error { p := predicate.Funcs{ UpdateFunc: func(e event.UpdateEvent) bool { oldObject, ok := e.ObjectOld.(*jenkinsApi.JenkinsJob) if !ok { return false } newObject, ok := e.ObjectNew.(*jenkinsApi.JenkinsJob) if !ok { return false } if !reflect.DeepEqual(oldObject.Spec, newObject.Spec) { return true } if newObject.DeletionTimestamp != nil { return true } return false }, } err := ctrl.NewControllerManagedBy(mgr). For(&jenkinsApi.JenkinsJob{}, builder.WithPredicates(p)). WithOptions(controller.Options{MaxConcurrentReconciles: maxConcurrentReconciles}). Complete(r) if err != nil { return fmt.Errorf("failed to create new managed controller: %w", err) } return nil } func (r *ReconcileJenkinsJob) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { log := r.log.WithValues(logNamespaceKey, request.Namespace, "Request.Name", request.Name) log.Info("reconciling JenkinsJob had started") jenkinsJob := &jenkinsApi.JenkinsJob{} if err := r.client.Get(ctx, request.NamespacedName, jenkinsJob); err != nil { if k8serrors.IsNotFound(err) { log.Info("instance not found") return reconcile.Result{}, nil } return reconcile.Result{}, fmt.Errorf("failed to Get JenkinsJob: %w", err) } if result, err := r.tryToDeleteJob(ctx, jenkinsJob); result != nil || err != nil { return *result, err } if err := r.setOwners(ctx, jenkinsJob); err != nil { return reconcile.Result{}, fmt.Errorf("failed to set owner reference: %w", err) } jobCanBeHandled, err := r.canJenkinsJobBeHandled(jenkinsJob) if err != nil { return reconcile.Result{}, fmt.Errorf("failed to check whether the jenkins job can be created: %w", err) } if !jobCanBeHandled { log.V(2).Info("jenkins folder for stages is not ready yet") return reconcile.Result{RequeueAfter: requeueAfter}, nil } result, err := r.handleJob(jenkinsJob) if err != nil { return reconcile.Result{}, fmt.Errorf("failed to handle JenkinsJob: %w", err) } if result.Requeue { r.log.Info("the next job provision will be triggered in few minutes", "minutes", result.RequeueAfter) return result, nil } log.Info("reconciling JenkinsJob has been finished") return result, nil } func (r *ReconcileJenkinsJob) handleJob(job *jenkinsApi.JenkinsJob) (reconcile.Result, error) { j, err := plutil.GetJenkinsInstanceOwner(r.client, job.Name, job.Namespace, job.Spec.OwnerName, job.GetOwnerReferences()) if err != nil { return reconcile.Result{}, fmt.Errorf("failed to get jenkins owner for jenkins job %v: %w", job.Name, err) } jc, err := jenkinsClient.InitGoJenkinsClient(j, r.platform) if err != nil { return reconcile.Result{}, fmt.Errorf("failed to init jenkins client %v: %w", j, err) } jobExists, err := jenkinsJobExists(jc, job.Spec.Job.Name) if err != nil { return reconcile.Result{}, fmt.Errorf("failed to retrieve jenkins job %v; %w", job.Spec.Job.Name, err) } ch, err := r.getChain(jobExists) if err != nil { return reconcile.Result{}, fmt.Errorf("failed to select chain: %w", err) } if err := chain.NewChain(ch).ServeRequest(job); err != nil { return reconcile.Result{}, fmt.Errorf("failed to ServeRequest: %w", err) } if jobExists && job.IsAutoTriggerEnabled() { period := time.Duration(*job.Spec.Job.AutoTriggerPeriod) * time.Minute return reconcile.Result{ Requeue: true, RequeueAfter: period, }, nil } return reconcile.Result{}, nil } func (r *ReconcileJenkinsJob) getChain(jobExist bool) (chain.Chain, error) { if jobExist { ch, err := chain.InitTriggerJobProvisionChain(r.scheme, r.client) if err != nil { return ch, fmt.Errorf("failed to InitTriggerJobProvisionChain: %w", err) } return ch, nil } ch, err := chain.InitDefChain(r.scheme, r.client) if err != nil { return ch, fmt.Errorf("failed to InitDefChain: %w", err) } return ch, nil } func jenkinsJobExists(jc *jenkinsClient.JenkinsClient, jp string) (bool, error) { _, err := jc.GoJenkins.GetJob(jp) if err != nil { if helper.JenkinsIsNotFoundErr(err) { return false, nil } return false, fmt.Errorf("failed to GetJob: %w", err) } return true, nil } func (r *ReconcileJenkinsJob) initGoJenkinsClient(jj *jenkinsApi.JenkinsJob) (*jenkinsClient.JenkinsClient, error) { j, err := plutil.GetJenkinsInstanceOwner(r.client, jj.Name, jj.Namespace, jj.Spec.OwnerName, jj.GetOwnerReferences()) if err != nil { return nil, fmt.Errorf("failed to get owner for jenkins folder %v: %w", jj.Name, err) } r.log.Info("Jenkins instance has been received", logNameKey, j.Name) jClient, err := jenkinsClient.InitGoJenkinsClient(j, r.platform) if err != nil { return nil, fmt.Errorf("failed to InitGoJenkinsClient: %w", err) } return jClient, nil } func (r *ReconcileJenkinsJob) tryToDeleteJob(ctx context.Context, jj *jenkinsApi.JenkinsJob) (*reconcile.Result, error) { if jj.GetDeletionTimestamp().IsZero() { if !finalizer.ContainsString(jj.ObjectMeta.Finalizers, jenkinsJobFinalizerName) { jj.ObjectMeta.Finalizers = append(jj.ObjectMeta.Finalizers, jenkinsJobFinalizerName) if err := r.client.Update(ctx, jj); err != nil { return &reconcile.Result{}, fmt.Errorf("failed to Update jenkins job: %w", err) } } return nil, nil } if err := r.deleteJob(jj); err != nil { return &reconcile.Result{}, err } jj.ObjectMeta.Finalizers = finalizer.RemoveString(jj.ObjectMeta.Finalizers, jenkinsJobFinalizerName) if err := r.client.Update(ctx, jj); err != nil { return &reconcile.Result{}, fmt.Errorf("failed to Update jenkins job: %w", err) } return &reconcile.Result{}, nil } func (r *ReconcileJenkinsJob) deleteJob(jj *jenkinsApi.JenkinsJob) error { jc, err := r.initGoJenkinsClient(jj) if err != nil { return fmt.Errorf("failed to create Go Jenkins client: %w", err) } j := r.getJobName(jj) _, err = jc.GoJenkins.DeleteJob(j) if err != nil { if helper.JenkinsIsNotFoundErr(err) { r.log.V(2).Info("job/folder doesn't exist. skip deleting", logNameKey, j) return nil } return fmt.Errorf("failed to DeleteJob: %w", err) } return nil } func (r *ReconcileJenkinsJob) getJobName(jj *jenkinsApi.JenkinsJob) string { if jj.Spec.JenkinsFolder != nil && *jj.Spec.JenkinsFolder != "" { jobName, err := r.getStageJobName(jj) if err != nil { return "an error had occurred while getting jenkins job name" } return fmt.Sprintf("%v-cd-pipeline/job/%v", *jj.Spec.JenkinsFolder, jobName) } return jj.Spec.Job.Name } func (r *ReconcileJenkinsJob) setOwners(ctx context.Context, jj *jenkinsApi.JenkinsJob) error { if err := r.tryToSetStageOwnerRef(jj); err != nil { return err } if err := r.client.Update(ctx, jj); err != nil { return fmt.Errorf("failed to update jenkins job %v: %w", jj.Name, err) } return nil } func (r *ReconcileJenkinsJob) tryToSetStageOwnerRef(jj *jenkinsApi.JenkinsJob) error { if ow := plutil.GetOwnerReference(consts.StageKind, jj.GetOwnerReferences()); ow != nil { r.log.V(2).Info("stage ref already exists", logJenkinsJobKey, jj.Name) return nil } s, err := plutil.GetStageInstance(r.client, *jj.Spec.StageName, jj.Namespace) if err != nil { return fmt.Errorf("failed to GetStageInstance: %w", err) } if err := controllerutil.SetControllerReference(s, jj, r.scheme); err != nil { return fmt.Errorf("failed to set stage owner ref: %w", err) } return nil } func (r *ReconcileJenkinsJob) canJenkinsJobBeHandled(jj *jenkinsApi.JenkinsJob) (bool, error) { if jj.Spec.JenkinsFolder != nil && *jj.Spec.JenkinsFolder != "" { jfn := fmt.Sprintf("%v-%v", *jj.Spec.JenkinsFolder, "cd-pipeline") jf, err := plutil.GetJenkinsFolderInstance(r.client, jfn, jj.Namespace) if err != nil { return false, fmt.Errorf("failed to GetJenkinsFolderInstance: %w", err) } r.log.V(2).Info("create job in Jenkins folder", logNameKey, jfn, "status folder", jf.Status.Available) return jf.Status.Available, nil } r.log.V(2).Info("create job in Jenkins root folder", logNameKey, jj.Name) return true, nil } func (*ReconcileJenkinsJob) getStageJobName(jj *jenkinsApi.JenkinsJob) (string, error) { jobConfig := make(map[string]string) if err := json.Unmarshal([]byte(jj.Spec.Job.Config), &jobConfig); err != nil { return "", fmt.Errorf("failed to Unmarshal: %w", err) } stageName := jobConfig["STAGE_NAME"] return stageName, nil }