pkg/controller/jenkinsscript/jenkinsscript_controller.go (226 lines of code) (raw):

package jenkinsscript import ( "context" "fmt" "time" "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" 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/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/service/platform" ) const ( logNamespaceKey = "Request.Namespace" logNameKey = "Request.Name" requeueAfter = 60 * time.Second ) func NewReconcileJenkinsScript(k8sClient client.Client, scheme *runtime.Scheme, log logr.Logger, ps platform.PlatformService) *ReconcileJenkinsScript { return &ReconcileJenkinsScript{ client: k8sClient, scheme: scheme, platform: ps, log: log.WithName("jenkins-script"), } } type ReconcileJenkinsScript struct { client client.Client scheme *runtime.Scheme platform platform.PlatformService log logr.Logger } func (r *ReconcileJenkinsScript) SetupWithManager(mgr ctrl.Manager) error { p := predicate.Funcs{ UpdateFunc: func(e event.UpdateEvent) bool { oldObject, ok := e.ObjectOld.(*jenkinsApi.JenkinsScript) if !ok { return false } newObject, ok := e.ObjectNew.(*jenkinsApi.JenkinsScript) if !ok { return false } return oldObject.Status == newObject.Status }, } err := ctrl.NewControllerManagedBy(mgr). For(&jenkinsApi.JenkinsScript{}, builder.WithPredicates(p)). Complete(r) if err != nil { return fmt.Errorf("failed to create new managed controller: %w", err) } return nil } //nolint:funlen,cyclop // TODO: remove nolint. func (r *ReconcileJenkinsScript) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { log := r.log.WithValues(logNamespaceKey, request.Namespace, logNameKey, request.Name) log.Info("Reconciling JenkinsScript") instance := &jenkinsApi.JenkinsScript{} if err := r.client.Get(ctx, request.NamespacedName, instance); err != nil { if errors.IsNotFound(err) { // Request object not found, could have been deleted after reconcile request. // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. // Return and don't requeue log.Info("instance not found") return reconcile.Result{}, nil } return reconcile.Result{}, fmt.Errorf("failed to get client: %w", err) } jenkinsInstance, err := r.getOrCreateInstanceOwner(ctx, instance) if err != nil { return reconcile.Result{RequeueAfter: helper.DefaultRequeueTime * time.Second}, fmt.Errorf("failed to get owner for %v: %w", instance.Name, err) } if jenkinsInstance == nil { log.Info("Couldn't find Jenkins Script owner instance") return reconcile.Result{RequeueAfter: helper.DefaultRequeueTime * time.Second}, nil } if instance.Status.Executed { log.Info("Script already finished") return reconcile.Result{}, nil } log.Info("Applying the script") jc, err := jenkinsClient.InitJenkinsClient(jenkinsInstance, r.platform) if err != nil { log.Info("Failed to init Jenkins REST client") return reconcile.Result{RequeueAfter: helper.DefaultRequeueTime * time.Second}, fmt.Errorf("failed to init jenkins client for %v: %w", instance.Name, err) } if jc == nil { log.V(1).Info("Jenkins returns nil client") return reconcile.Result{RequeueAfter: helper.DefaultRequeueTime * time.Second}, nil } cm, err := r.platform.GetConfigMapData(instance.Namespace, instance.Spec.SourceCmName) if err != nil { return reconcile.Result{RequeueAfter: helper.DefaultRequeueTime * time.Second}, fmt.Errorf("failed to get config map for %v: %w", instance.Name, err) } if err := jc.RunScript(cm["context"]); err != nil { return reconcile.Result{RequeueAfter: helper.DefaultRequeueTime * time.Second}, fmt.Errorf("failed to RunScript: %w", err) } log.V(1).Info("Script has been executed successfully") if err := r.updateAvailableStatus(ctx, instance, true); err != nil { log.Info("Failed to update availability status") return reconcile.Result{RequeueAfter: helper.DefaultRequeueTime * time.Second}, err } if err := r.updateExecutedStatus(ctx, instance, true); err != nil { log.Info("Failed to update executed status") return reconcile.Result{RequeueAfter: helper.DefaultRequeueTime * time.Second}, err } log.Info("Reconciling has been finished") return reconcile.Result{RequeueAfter: requeueAfter}, nil } func (r *ReconcileJenkinsScript) getInstanceByName(ctx context.Context, namespace, name string) (*jenkinsApi.Jenkins, error) { namespacedName := types.NamespacedName{ Namespace: namespace, Name: name, } instance := &jenkinsApi.Jenkins{} if err := r.client.Get(ctx, namespacedName, instance); err != nil { return nil, fmt.Errorf("failed to get instance by owner %v: %w", name, err) } return instance, nil } func (r *ReconcileJenkinsScript) getInstanceByOwnerFromSpec(ctx context.Context, jenkinsScript *jenkinsApi.JenkinsScript) (*jenkinsApi.Jenkins, error) { log := r.log.WithValues(logNamespaceKey, jenkinsScript.Namespace, logNameKey, jenkinsScript.Name) nsn := types.NamespacedName{ Namespace: jenkinsScript.Namespace, Name: *jenkinsScript.Spec.OwnerName, } jenkinsInstance := &jenkinsApi.Jenkins{} if err := r.client.Get(ctx, nsn, jenkinsInstance); err != nil { log.Info(fmt.Sprintf("Failed to get owner CR %v", *jenkinsScript.Spec.OwnerName)) return nil, nil } jenkinsScript = r.setOwnerReference(jenkinsInstance, jenkinsScript) if err := r.client.Update(ctx, jenkinsScript); err != nil { return nil, fmt.Errorf("failed to set owner name from spec for %v: %w", jenkinsScript.Name, err) } return jenkinsInstance, nil } func (*ReconcileJenkinsScript) getOwnerByCr(jenkinsScript *jenkinsApi.JenkinsScript) *metav1.OwnerReference { owners := jenkinsScript.GetOwnerReferences() for _, owner := range owners { if owner.Kind == "Jenkins" { return &owner } } return nil } func (r *ReconcileJenkinsScript) getOrCreateInstanceOwner(ctx context.Context, jenkinsScript *jenkinsApi.JenkinsScript) (*jenkinsApi.Jenkins, error) { log := r.log.WithValues(logNamespaceKey, jenkinsScript.Namespace, logNameKey, jenkinsScript.Name) owner := r.getOwnerByCr(jenkinsScript) if owner != nil { return r.getInstanceByName(ctx, jenkinsScript.Namespace, owner.Name) } if jenkinsScript.Spec.OwnerName != nil { return r.getInstanceByOwnerFromSpec(ctx, jenkinsScript) } jenkinsInstance, err := r.getJenkinsInstance(ctx, jenkinsScript.Namespace) if err != nil { return nil, err } if jenkinsInstance == nil { return nil, nil } jenkinsScript = r.setOwnerReference(jenkinsInstance, jenkinsScript) log.Info(fmt.Sprintf("jenkinsScript.GetOwnerReferences() - %v", jenkinsScript.GetOwnerReferences())) if err = r.client.Update(ctx, jenkinsScript); err != nil { return nil, fmt.Errorf("failed to set owner reference for %v: %w", jenkinsScript.Name, err) } return jenkinsInstance, nil } func (r *ReconcileJenkinsScript) getJenkinsInstance(ctx context.Context, namespace string) (*jenkinsApi.Jenkins, error) { list := &jenkinsApi.JenkinsList{} if err := r.client.List(ctx, list, &client.ListOptions{Namespace: namespace}); err != nil { return nil, fmt.Errorf("failed to get Jenkins instance in namespace %v: %w", namespace, err) } if len(list.Items) == 0 { return nil, nil } return &list.Items[0], nil } func (*ReconcileJenkinsScript) setOwnerReference(owner *jenkinsApi.Jenkins, jenkinsScript *jenkinsApi.JenkinsScript) *jenkinsApi.JenkinsScript { jenkinsScriptOwners := jenkinsScript.GetOwnerReferences() newOwnerRef := metav1.OwnerReference{ APIVersion: owner.APIVersion, Kind: owner.Kind, Name: owner.Name, UID: owner.UID, BlockOwnerDeletion: helper.NewTrue(), Controller: helper.NewTrue(), } jenkinsScriptOwners = append(jenkinsScriptOwners, newOwnerRef) jenkinsScript.SetOwnerReferences(jenkinsScriptOwners) return jenkinsScript } func (r *ReconcileJenkinsScript) updateAvailableStatus(ctx context.Context, instance *jenkinsApi.JenkinsScript, value bool) error { log := r.log.WithValues(logNamespaceKey, instance.Namespace, logNameKey, instance.Name).WithName("status_update") if instance.Status.Available != value { instance.Status.Available = value instance.Status.LastTimeUpdated = metav1.NewTime(time.Now()) if err := r.client.Status().Update(ctx, instance); err != nil { if err := r.client.Update(ctx, instance); err != nil { return fmt.Errorf("failed to update availability status to %v: %w", value, err) } } log.Info(fmt.Sprintf("Availability status has been updated to '%v'", value)) } return nil } func (r *ReconcileJenkinsScript) updateExecutedStatus(ctx context.Context, instance *jenkinsApi.JenkinsScript, value bool) error { log := r.log.WithValues(logNamespaceKey, instance.Namespace, logNameKey, instance.Name).WithName("status_update") if instance.Status.Executed != value { instance.Status.Executed = value instance.Status.LastTimeUpdated = metav1.NewTime(time.Now()) if err := r.client.Status().Update(ctx, instance); err != nil { if err := r.client.Update(ctx, instance); err != nil { return fmt.Errorf("failed to update executed status to %v: %w", value, err) } } log.Info(fmt.Sprintf("Executed status has been updated to '%v'", value)) } return nil }