Repository: kubesphere/ks-devops
Pull request #244 (2021-09-18 01:26:57)

Provide Pipeline Synchronizer to synchronize PipelineRun from Jenkins
### What this PR dose

Provide Pipeline Synchronizer to synchronize PipelineRun from Jenkins.

### Why we need it

After we've done https://github.com/kubesphere/ks-devops/issues/41, we will missing some Jenkins runs which are triggered automatically or old data. So we need a synchronizer to ensure that we can convert them into PipelineRun.

### Special notes

This is the final effect:

![7b3d61a2630705ca6da5611519da075](https://user-images.githubusercontent.com/16865714/133867582-7479c829-6c2e-4189-8f31-c753f6122f44.png)

### Steps to test

Replace the docker image of `devops-apiserver` and `devops-controller` with `johnniang/devops-apiserver:pipelinerun-syncer` and `johnniang/devops-controller:pipelinerun-syncer` respectively.

/kind feature

ks-ci-bot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubesphere/ks-devops/pull/244#" title="Author self-approved">JohnNiang</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **linuxsuren** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @linuxsuren` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubesphere%2Fks-devops).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubesphere/ks-devops/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["linuxsuren"]} -->
codecov[bot]: # [Codecov](https://codecov.io/gh/kubesphere/ks-devops/pull/244?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere) Report
> Merging [#244](https://codecov.io/gh/kubesphere/ks-devops/pull/244?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere) (e32b4cb) into [master](https://codecov.io/gh/kubesphere/ks-devops/commit/e65d92fe8af8769b2c7e362ce2ffce71a0c9b9d5?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere) (e65d92f) will **decrease** coverage by `0.01%`.
> The diff coverage is `0.00%`.

[![Impacted file tree graph](https://codecov.io/gh/kubesphere/ks-devops/pull/244/graphs/tree.svg?width=650&height=150&src=pr&token=qSK4RBbyNg&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere)](https://codecov.io/gh/kubesphere/ks-devops/pull/244?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere)

```diff
@@            Coverage Diff            @@
##           master    #244      +/-   ##
=========================================
- Coverage    7.22%   7.20%   -0.02%     
=========================================
  Files          83      84       +1     
  Lines       21112   21177      +65     
=========================================
+ Hits         1525    1526       +1     
- Misses      19461   19525      +64     
  Partials      126     126              
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `7.20% <0.00%> (-0.02%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/kubesphere/ks-devops/pull/244?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere) | Coverage Δ | |
|---|---|---|
| [...trollers/devopsproject/devopsproject\_controller.go](https://codecov.io/gh/kubesphere/ks-devops/pull/244/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-Y29udHJvbGxlcnMvZGV2b3BzcHJvamVjdC9kZXZvcHNwcm9qZWN0X2NvbnRyb2xsZXIuZ28=) | `43.22% <ø> (+0.22%)` | :arrow_up: |
| [...lers/jenkins/pipelinerun/pipelinerun\_controller.go](https://codecov.io/gh/kubesphere/ks-devops/pull/244/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-Y29udHJvbGxlcnMvamVua2lucy9waXBlbGluZXJ1bi9waXBlbGluZXJ1bl9jb250cm9sbGVyLmdv) | `3.94% <0.00%> (-0.03%)` | :arrow_down: |
| [...rs/jenkins/pipelinerun/pipelinerun\_synchronizer.go](https://codecov.io/gh/kubesphere/ks-devops/pull/244/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-Y29udHJvbGxlcnMvamVua2lucy9waXBlbGluZXJ1bi9waXBlbGluZXJ1bl9zeW5jaHJvbml6ZXIuZ28=) | `0.00% <0.00%> (ø)` | |
| [controllers/jenkins/pipelinerun/utils.go](https://codecov.io/gh/kubesphere/ks-devops/pull/244/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-Y29udHJvbGxlcnMvamVua2lucy9waXBlbGluZXJ1bi91dGlscy5nbw==) | `100.00% <ø> (ø)` | |
| [controllers/pipeline/pipeline\_controller.go](https://codecov.io/gh/kubesphere/ks-devops/pull/244/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-Y29udHJvbGxlcnMvcGlwZWxpbmUvcGlwZWxpbmVfY29udHJvbGxlci5nbw==) | `38.23% <0.00%> (-4.31%)` | :arrow_down: |
| [pkg/kapis/devops/v1alpha3/pipelinerun/handler.go](https://codecov.io/gh/kubesphere/ks-devops/pull/244/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-cGtnL2thcGlzL2Rldm9wcy92MWFscGhhMy9waXBlbGluZXJ1bi9oYW5kbGVyLmdv) | `0.00% <0.00%> (ø)` | |
| [pkg/kapis/devops/v1alpha3/pipelinerun/util.go](https://codecov.io/gh/kubesphere/ks-devops/pull/244/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-cGtnL2thcGlzL2Rldm9wcy92MWFscGhhMy9waXBlbGluZXJ1bi91dGlsLmdv) | `40.81% <ø> (ø)` | |
| [...devops/v1alpha3/pipelinerun/backwardlisthandler.go](https://codecov.io/gh/kubesphere/ks-devops/pull/244/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-cGtnL2thcGlzL2Rldm9wcy92MWFscGhhMy9waXBlbGluZXJ1bi9iYWNrd2FyZGxpc3RoYW5kbGVyLmdv) | `91.66% <0.00%> (-4.17%)` | :arrow_down: |
| ... and [3 more](https://codecov.io/gh/kubesphere/ks-devops/pull/244/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/kubesphere/ks-devops/pull/244?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/kubesphere/ks-devops/pull/244?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere). Last update [e65d92f...e32b4cb](https://codecov.io/gh/kubesphere/ks-devops/pull/244?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere).

ks-ci-bot: @JohnNiang: PR needs rebase.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>

Patch:
diff --git a/cmd/controller/app/controllers.go b/cmd/controller/app/controllers.go
index 23a9abd..2892922 100644
--- a/cmd/controller/app/controllers.go
+++ b/cmd/controller/app/controllers.go
@@ -31,7 +31,6 @@ import (
 	"kubesphere.io/devops/pkg/client/k8s"
 	"kubesphere.io/devops/pkg/client/s3"
 	"kubesphere.io/devops/pkg/informers"
-	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/manager"
 )
 
@@ -92,11 +91,20 @@ func addControllers(mgr manager.Manager, client k8s.Client, informerFactory info
 		if err := (&pipelinerun.Reconciler{
 			Client:       mgr.GetClient(),
 			Scheme:       mgr.GetScheme(),
-			Log:          ctrl.Log.WithName("pipelinerun-controller"),
 			DevOpsClient: devopsClient,
 			JenkinsCore:  jenkinsCore,
 		}).SetupWithManager(mgr); err != nil {
-			klog.Errorf("unable to create jenkins-pipeline-controller, err: %v", err)
+			klog.Errorf("unable to create pipelinerun-controller, err: %v", err)
+			return err
+		}
+
+		// add PipelineRun Synchronizer
+		if err := (&pipelinerun.SyncReconciler{
+			Client:      mgr.GetClient(),
+			JenkinsCore: jenkinsCore,
+		}).SetupWithManager(mgr); err != nil {
+			klog.Errorf("unable to create pipelinerun-synchronizer, err: %v", err)
+			return err
 		}
 	}
 
diff --git a/controllers/devopsproject/devopsproject_controller.go b/controllers/devopsproject/devopsproject_controller.go
index 151117c..7bcf2f3 100644
--- a/controllers/devopsproject/devopsproject_controller.go
+++ b/controllers/devopsproject/devopsproject_controller.go
@@ -161,7 +161,6 @@ func (c *Controller) processNextWorkItem() bool {
 	}(obj)
 
 	if err != nil {
-		klog.Error(err, "could not reconcile devopsProject")
 		utilruntime.HandleError(err)
 		return true
 	}
diff --git a/controllers/jenkins/pipelinerun/pipelinerun_controller.go b/controllers/jenkins/pipelinerun/pipelinerun_controller.go
index bd4c44c..e0a3fd2 100644
--- a/controllers/jenkins/pipelinerun/pipelinerun_controller.go
+++ b/controllers/jenkins/pipelinerun/pipelinerun_controller.go
@@ -20,6 +20,11 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"reflect"
+	"strconv"
+	"strings"
+	"time"
+
 	"github.com/go-logr/logr"
 	"github.com/jenkins-zh/jenkins-client/pkg/core"
 	"github.com/jenkins-zh/jenkins-client/pkg/job"
@@ -33,18 +38,14 @@ import (
 	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
 	devopsClient "kubesphere.io/devops/pkg/client/devops"
 	"kubesphere.io/devops/pkg/utils/sliceutil"
-	"reflect"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
-	"strconv"
-	"strings"
-	"time"
 )
 
 // Reconciler reconciles a PipelineRun object
 type Reconciler struct {
 	client.Client
-	Log          logr.Logger
+	log          logr.Logger
 	Scheme       *runtime.Scheme
 	DevOpsClient devopsClient.Interface
 	JenkinsCore  core.JenkinsCore
@@ -58,7 +59,7 @@ type Reconciler struct {
 // move the current state of the cluster closer to the desired state.
 func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
 	ctx := context.Background()
-	log := r.Log.WithValues("PipelineRun", req.NamespacedName)
+	log := r.log.WithValues("PipelineRun", req.NamespacedName)
 
 	// get PipelineRun
 	var pr prv1alpha3.PipelineRun
@@ -250,7 +251,7 @@ func getJenkinsBuildNumber(pipelineRun *prv1alpha3.PipelineRun) (num int) {
 	return
 }
 
-func (r *Reconciler) triggerJenkinsJob(devopsProjectName, pipelineName string, prSpec *prv1alpha3.PipelineRunSpec) (*job.PipelineBuild, error) {
+func (r *Reconciler) triggerJenkinsJob(devopsProjectName, pipelineName string, prSpec *prv1alpha3.PipelineRunSpec) (*job.PipelineRun, error) {
 	c := job.BlueOceanClient{JenkinsCore: r.JenkinsCore, Organization: "jenkins"}
 
 	branch, err := getSCMRefName(prSpec)
@@ -276,7 +277,7 @@ func getSCMRefName(prSpec *prv1alpha3.PipelineRunSpec) (string, error) {
 	return branch, nil
 }
 
-func (r *Reconciler) getPipelineRunResult(devopsProjectName, pipelineName string, pr *prv1alpha3.PipelineRun) (*job.PipelineBuild, error) {
+func (r *Reconciler) getPipelineRunResult(devopsProjectName, pipelineName string, pr *prv1alpha3.PipelineRun) (*job.PipelineRun, error) {
 	runID, exists := pr.GetPipelineRunID()
 	if !exists {
 		return nil, fmt.Errorf("unable to get PipelineRun result due to not found run ID")
@@ -371,6 +372,7 @@ func (r *Reconciler) makePipelineRunOrphan(ctx context.Context, pr *prv1alpha3.P
 func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
 	// the name should obey Kubernetes naming convention: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/
 	r.recorder = mgr.GetEventRecorderFor("pipelinerun-controller")
+	r.log = ctrl.Log.WithName("pipelinerun-controller")
 	return ctrl.NewControllerManagedBy(mgr).
 		For(&prv1alpha3.PipelineRun{}).
 		Complete(r)
diff --git a/controllers/jenkins/pipelinerun/pipelinerun_synchronizer.go b/controllers/jenkins/pipelinerun/pipelinerun_synchronizer.go
new file mode 100644
index 0000000..d1f805e
--- /dev/null
+++ b/controllers/jenkins/pipelinerun/pipelinerun_synchronizer.go
@@ -0,0 +1,167 @@
+package pipelinerun
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/go-logr/logr"
+	"github.com/jenkins-zh/jenkins-client/pkg/core"
+	"github.com/jenkins-zh/jenkins-client/pkg/job"
+	v1 "k8s.io/api/core/v1"
+	"k8s.io/client-go/tools/record"
+	"k8s.io/client-go/util/retry"
+	prv1alpha3 "kubesphere.io/devops/pkg/api/devops/pipelinerun/v1alpha3"
+	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
+	"kubesphere.io/devops/pkg/kapis/devops/v1alpha3/pipelinerun"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/event"
+	"sigs.k8s.io/controller-runtime/pkg/predicate"
+)
+
+// Valid values for event reasons(new reasons could be added in the future)
+const (
+	PipelineRunSynced     = "PipelineRunSynced"
+	FailedPipelineRunSync = "FailedPipelineRunSync"
+)
+
+// SyncReconciler is a reconciler for synchronizing PipelineRuns from Jenkins.
+// This reconciler is an intermediate reconciler. When we complete the issue
+// https://github.com/kubesphere/ks-devops/issues/65, we will remove it.
+type SyncReconciler struct {
+	client.Client
+	log         logr.Logger
+	recorder    record.EventRecorder
+	JenkinsCore core.JenkinsCore
+}
+
+// Reconcile is part of the main kubernetes reconciliation loop which aims to
+// move the current state of the cluster closer to the desired state.
+func (r *SyncReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
+	ctx := context.Background()
+	log := r.log.WithValues("Pipeline", req.NamespacedName)
+	pipeline := &v1alpha3.Pipeline{}
+	if err := r.Client.Get(ctx, req.NamespacedName, pipeline); err != nil {
+		return ctrl.Result{}, client.IgnoreNotFound(err)
+	}
+
+	// get all pipelineruns
+	var prList prv1alpha3.PipelineRunList
+	if err := r.Client.List(ctx, &prList, client.InNamespace(pipeline.Namespace), client.MatchingLabels{
+		prv1alpha3.PipelineNameLabelKey: pipeline.Name,
+	}); err != nil {
+		return ctrl.Result{}, err
+	}
+	prContainer := make(map[string]prv1alpha3.PipelineRun)
+	for _, pr := range prList.Items {
+		runID := pr.Annotations[prv1alpha3.JenkinsPipelineRunIDKey]
+		prContainer[runID] = pr
+	}
+
+	boClient := job.BlueOceanClient{
+		JenkinsCore:  r.JenkinsCore,
+		Organization: "jenkins",
+	}
+
+	jenkinsRuns, err := boClient.GetPipelineRuns(pipeline.Name, pipeline.Namespace)
+	if err != nil {
+		r.recorder.Eventf(pipeline, v1.EventTypeWarning, FailedPipelineRunSync, "")
+		return ctrl.Result{}, err
+	}
+	var createdPipelineRuns []prv1alpha3.PipelineRun
+	for _, jenkinsRun := range jenkinsRuns {
+		if _, ok := prContainer[jenkinsRun.ID]; !ok {
+			pipelineRun, err := r.createPipelineRun(pipeline, &jenkinsRun)
+			if err == nil {
+				createdPipelineRuns = append(createdPipelineRuns, *pipelineRun)
+			} else {
+				log.Error(err, "failed to create bare PipelineRun", "JenkinsRun", jenkinsRun)
+			}
+		}
+	}
+
+	if err := r.synchronizedSuccessfully(req.NamespacedName); err != nil {
+		return ctrl.Result{}, err
+	}
+
+	log.Info("synchronized PipelineRun successfully")
+
+	r.recorder.Eventf(pipeline, v1.EventTypeNormal, PipelineRunSynced,
+		"Successfully synchronized %d PipelineRuns(s). PipelineRuns/JenkinsRuns proportion is %d/%d", len(createdPipelineRuns), len(prList.Items), len(jenkinsRuns))
+	return ctrl.Result{}, nil
+}
+
+func (r *SyncReconciler) synchronizedSuccessfully(key client.ObjectKey) error {
+	//pipelineToUpdate := *pipeline.DeepCopy()
+	return retry.RetryOnConflict(retry.DefaultRetry, func() error {
+		pipelineToUpdate := &v1alpha3.Pipeline{}
+		if err := r.Client.Get(context.Background(), key, pipelineToUpdate); err != nil {
+			return err
+		}
+		if _, ok := pipelineToUpdate.Annotations[v1alpha3.PipelineRequestToSyncRunsAnnoKey]; !ok {
+			return nil
+		}
+		// remove the annotation
+		delete(pipelineToUpdate.Annotations, v1alpha3.PipelineRequestToSyncRunsAnnoKey)
+		// update the Pipeline
+
+		// ignore the conflict
+		return r.Client.Update(context.Background(), pipelineToUpdate)
+	})
+}
+
+func (r *SyncReconciler) createPipelineRun(pipeline *v1alpha3.Pipeline, run *job.PipelineRun) (*prv1alpha3.PipelineRun, error) {
+	pipelineRun, err := createBarePipelineRun(pipeline, run)
+	if err != nil {
+		return nil, err
+	}
+	if err := r.Client.Create(context.Background(), pipelineRun); err != nil {
+		return nil, err
+	}
+	return pipelineRun, nil
+}
+
+func createBarePipelineRun(pipeline *v1alpha3.Pipeline, run *job.PipelineRun) (*prv1alpha3.PipelineRun, error) {
+	branch := ""
+	if run.Branch != nil {
+		// TODO(johnniang): Refine the branch structure in Jenkins client.
+		branch = fmt.Sprint(run.Branch.(map[string]interface{})["url"])
+	}
+	scm, err := pipelinerun.CreateScm(&pipeline.Spec, branch)
+	if err != nil {
+		return nil, err
+	}
+	pr := pipelinerun.CreatePipelineRun(pipeline, nil, scm)
+	pr.Annotations[prv1alpha3.JenkinsPipelineRunIDKey] = run.ID
+	return pr, nil
+}
+
+// SetupWithManager sets up the controller with the Manager.
+func (r *SyncReconciler) SetupWithManager(mgr ctrl.Manager) error {
+	r.recorder = mgr.GetEventRecorderFor("pipeline-synchronizer")
+	r.log = ctrl.Log.WithName("pipelinerun-synchronizer")
+
+	return ctrl.NewControllerManagedBy(mgr).
+		For(&v1alpha3.Pipeline{}).
+		WithEventFilter(predicate.And(predicate.ResourceVersionChangedPredicate{}, requestSyncPredicate())).
+		Complete(r)
+}
+
+func requestSyncPredicate() predicate.Predicate {
+	return predicate.Funcs{
+		CreateFunc: func(e event.CreateEvent) bool {
+			_, ok := e.Meta.GetAnnotations()[v1alpha3.PipelineRequestToSyncRunsAnnoKey]
+			return ok
+		},
+		UpdateFunc: func(e event.UpdateEvent) bool {
+			_, ok := e.MetaNew.GetAnnotations()[v1alpha3.PipelineRequestToSyncRunsAnnoKey]
+			return ok
+		},
+		DeleteFunc: func(e event.DeleteEvent) bool {
+			return false
+		},
+		GenericFunc: func(e event.GenericEvent) bool {
+			return false
+		},
+	}
+}
diff --git a/controllers/jenkins/pipelinerun/utils.go b/controllers/jenkins/pipelinerun/utils.go
index dc745ae..b16a848 100644
--- a/controllers/jenkins/pipelinerun/utils.go
+++ b/controllers/jenkins/pipelinerun/utils.go
@@ -39,7 +39,7 @@ func (result JenkinsRunResult) String() string {
 
 // pipelineBuildApplier applies PipelineBuilder to PipelineRunStatus.
 type pipelineBuildApplier struct {
-	*job.PipelineBuild
+	*job.PipelineRun
 }
 
 func (pbApplier pipelineBuildApplier) apply(prStatus *v1alpha3.PipelineRunStatus) {
diff --git a/controllers/pipeline/pipeline_controller.go b/controllers/pipeline/pipeline_controller.go
index 9f104a1..fc953bd 100644
--- a/controllers/pipeline/pipeline_controller.go
+++ b/controllers/pipeline/pipeline_controller.go
@@ -19,13 +19,14 @@ package pipeline
 import (
 	"context"
 	"fmt"
-	"kubesphere.io/devops/pkg/utils"
-	"kubesphere.io/devops/pkg/utils/k8sutil"
-	"kubesphere.io/devops/pkg/utils/sliceutil"
 	"net/http"
 	"reflect"
 	"time"
 
+	"kubesphere.io/devops/pkg/utils"
+	"kubesphere.io/devops/pkg/utils/k8sutil"
+	"kubesphere.io/devops/pkg/utils/sliceutil"
+
 	"github.com/emicklei/go-restful"
 	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/errors"
@@ -109,7 +110,13 @@ func NewController(client clientset.Interface,
 			if oldPipeline.ResourceVersion == newPipeline.ResourceVersion {
 				return
 			}
-			v.enqueuePipeline(newObj)
+
+			_, requestSync := newPipeline.Annotations[devopsv1alpha3.PipelineRequestToSyncRunsAnnoKey]
+
+			if !reflect.DeepEqual(oldPipeline.Spec, newPipeline.Spec) ||
+				(!reflect.DeepEqual(oldPipeline.ObjectMeta, newPipeline.ObjectMeta) && !requestSync) {
+				v.enqueuePipeline(newObj)
+			}
 		},
 		DeleteFunc: v.enqueuePipeline,
 	})
@@ -157,7 +164,6 @@ func (c *Controller) processNextWorkItem() bool {
 	}(obj)
 
 	if err != nil {
-		klog.Error(err, "could not reconcile devopsProject")
 		utilruntime.HandleError(err)
 		return true
 	}
@@ -166,7 +172,6 @@ func (c *Controller) processNextWorkItem() bool {
 }
 
 func (c *Controller) worker() {
-
 	for c.processNextWorkItem() {
 	}
 }
diff --git a/go.mod b/go.mod
index 853ab9e..826f69c 100644
--- a/go.mod
+++ b/go.mod
@@ -21,7 +21,7 @@ require (
 	github.com/golang/mock v1.6.0
 	github.com/google/go-cmp v0.5.5
 	github.com/google/go-querystring v1.1.0 // indirect
-	github.com/jenkins-zh/jenkins-client v0.0.3-0.20210915103438-8d848a9886c2
+	github.com/jenkins-zh/jenkins-client v0.0.3
 	github.com/kubesphere/sonargo v0.0.2
 	github.com/onsi/ginkgo v1.16.4
 	github.com/onsi/gomega v1.15.0
diff --git a/go.sum b/go.sum
index a8ea214..d1bc925 100644
--- a/go.sum
+++ b/go.sum
@@ -317,8 +317,8 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
 github.com/jenkins-zh/jenkins-cli v0.0.32/go.mod h1:uE1mH9PNITrg0sugv6HXuM/CSddg0zxXoYu3w57I3JY=
-github.com/jenkins-zh/jenkins-client v0.0.3-0.20210915103438-8d848a9886c2 h1:MKy6LEFZZk4WaPPCn7z7QCGFcLEJYXjAvXsGxQbMtpM=
-github.com/jenkins-zh/jenkins-client v0.0.3-0.20210915103438-8d848a9886c2/go.mod h1:ICBk7OOoTafVP//f/VfKZ34c0ff8vJwVnOsF9btiMYU=
+github.com/jenkins-zh/jenkins-client v0.0.3 h1:sLI0BksLd+NInZdhdyCE1wOEcBY2+D8leZ8pdLt33iM=
+github.com/jenkins-zh/jenkins-client v0.0.3/go.mod h1:ICBk7OOoTafVP//f/VfKZ34c0ff8vJwVnOsF9btiMYU=
 github.com/jenkins-zh/jenkins-formulas v0.0.5/go.mod h1:zS8fm8u5L6FcjZM0QznXsLV9T2UtSVK+hT6Sm76iUZ4=
 github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
diff --git a/pkg/api/devops/v1alpha3/pipeline_types.go b/pkg/api/devops/v1alpha3/pipeline_types.go
index cd19d08..011757b 100644
--- a/pkg/api/devops/v1alpha3/pipeline_types.go
+++ b/pkg/api/devops/v1alpha3/pipeline_types.go
@@ -34,6 +34,8 @@ const (
 	PipelineSyncStatusAnnoKey = PipelinePrefix + "syncstatus"
 	PipelineSyncTimeAnnoKey   = PipelinePrefix + "synctime"
 	PipelineSyncMsgAnnoKey    = PipelinePrefix + "syncmsg"
+	// PipelineRequestToSyncRunsAnnoKey is the key of requesting to synchronize PipelineRun after a dedicated time.
+	PipelineRequestToSyncRunsAnnoKey = PipelinePrefix + "request-to-sync-pipelineruns"
 )
 
 // PipelineSpec defines the desired state of Pipeline
diff --git a/pkg/kapis/devops/v1alpha3/pipelinerun/handler.go b/pkg/kapis/devops/v1alpha3/pipelinerun/handler.go
index 00a6662..64882fa 100644
--- a/pkg/kapis/devops/v1alpha3/pipelinerun/handler.go
+++ b/pkg/kapis/devops/v1alpha3/pipelinerun/handler.go
@@ -2,17 +2,18 @@ package pipelinerun
 
 import (
 	"context"
-	"io"
-	prv1alpha3 "kubesphere.io/devops/pkg/api/devops/pipelinerun/v1alpha3"
-	"strconv"
-
 	"github.com/emicklei/go-restful"
+	"io"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/klog"
 	"kubesphere.io/devops/pkg/api"
+	prv1alpha3 "kubesphere.io/devops/pkg/api/devops/pipelinerun/v1alpha3"
 	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
 	"kubesphere.io/devops/pkg/apiserver/query"
 	"kubesphere.io/devops/pkg/client/devops"
 	resourcesV1alpha3 "kubesphere.io/devops/pkg/models/resources/v1alpha3"
 	"sigs.k8s.io/controller-runtime/pkg/client"
+	"strconv"
 )
 
 // apiHandlerOption holds some useful tools for API handler.
@@ -43,15 +44,15 @@ func (h *apiHandler) listPipelineRuns(request *restful.Request, response *restfu
 	queryParam := query.ParseQueryParameter(request)
 
 	// validate the Pipeline
-	var pip v1alpha3.Pipeline
-	err = h.client.Get(context.Background(), client.ObjectKey{Namespace: nsName, Name: pipName}, &pip)
+	pipeline := &v1alpha3.Pipeline{}
+	err = h.client.Get(context.Background(), client.ObjectKey{Namespace: nsName, Name: pipName}, pipeline)
 	if err != nil {
 		api.HandleError(request, response, err)
 		return
 	}
 
 	// build label selector
-	labelSelector, err := buildLabelSelector(queryParam, pip.Name, branchName)
+	labelSelector, err := buildLabelSelector(queryParam, pipeline.Name, branchName)
 	if err != nil {
 		api.HandleError(request, response, err)
 		return
@@ -60,7 +61,7 @@ func (h *apiHandler) listPipelineRuns(request *restful.Request, response *restfu
 	var prs prv1alpha3.PipelineRunList
 	// fetch PipelineRuns
 	if err := h.client.List(context.Background(), &prs,
-		client.InNamespace(pip.Namespace),
+		client.InNamespace(pipeline.Namespace),
 		client.MatchingLabelsSelector{Selector: labelSelector}); err != nil {
 		api.HandleError(request, response, err)
 		return
@@ -72,6 +73,34 @@ func (h *apiHandler) listPipelineRuns(request *restful.Request, response *restfu
 	}
 	apiResult := resourcesV1alpha3.ToListResult(convertPipelineRunsToObject(prs.Items), queryParam, listHandler)
 	_ = response.WriteAsJson(apiResult)
+
+	go func() {
+		err := h.requestSyncPipelineRun(client.ObjectKey{Namespace: pipeline.Namespace, Name: pipeline.Name})
+		if err != nil {
+			klog.Errorf("failed to request to synchronize PipelineRuns. Pipeline = %s", pipeline.Name)
+			return
+		}
+	}()
+}
+
+func (h *apiHandler) requestSyncPipelineRun(key client.ObjectKey) error {
+	// get latest Pipeline
+	pipelineToUpdate := &v1alpha3.Pipeline{}
+	err := h.client.Get(context.Background(), key, pipelineToUpdate)
+	if err != nil {
+		return err
+	}
+	// update pipeline annotations
+	if pipelineToUpdate.Annotations == nil {
+		pipelineToUpdate.Annotations = make(map[string]string)
+	}
+	pipelineToUpdate.Annotations[v1alpha3.PipelineRequestToSyncRunsAnnoKey] = "true"
+	// update the pipeline
+	if err := h.client.Update(context.Background(), pipelineToUpdate); err != nil && !apierrors.IsConflict(err) {
+		// we allow the conflict error here
+		return err
+	}
+	return nil
 }
 
 func (h *apiHandler) createPipelineRuns(request *restful.Request, response *restful.Response) {
@@ -94,13 +123,13 @@ func (h *apiHandler) createPipelineRuns(request *restful.Request, response *rest
 		scm *prv1alpha3.SCM
 		err error
 	)
-	if scm, err = getScm(&pipeline.Spec, branch); err != nil {
+	if scm, err = CreateScm(&pipeline.Spec, branch); err != nil {
 		api.HandleBadRequest(response, request, err)
 		return
 	}
 
 	// create PipelineRun
-	pr := createPipelineRun(&pipeline, &payload, scm)
+	pr := CreatePipelineRun(&pipeline, &payload, scm)
 	if err := h.client.Create(context.Background(), pr); err != nil {
 		api.HandleError(request, response, err)
 		return
diff --git a/pkg/kapis/devops/v1alpha3/pipelinerun/util.go b/pkg/kapis/devops/v1alpha3/pipelinerun/util.go
index 9a7d1b4..c11528c 100644
--- a/pkg/kapis/devops/v1alpha3/pipelinerun/util.go
+++ b/pkg/kapis/devops/v1alpha3/pipelinerun/util.go
@@ -3,6 +3,7 @@ package pipelinerun
 import (
 	"errors"
 	"fmt"
+
 	prv1alpha3 "kubesphere.io/devops/pkg/api/devops/pipelinerun/v1alpha3"
 
 	corev1 "k8s.io/api/core/v1"
@@ -59,7 +60,8 @@ func convertParameters(payload *devops.RunPayload) []prv1alpha3.Parameter {
 	return parameters
 }
 
-func getScm(ps *v1alpha3.PipelineSpec, branch string) (*prv1alpha3.SCM, error) {
+// CreateScm creates SCM for multi-branch Pipeline.
+func CreateScm(ps *v1alpha3.PipelineSpec, branch string) (*prv1alpha3.SCM, error) {
 	var scm *prv1alpha3.SCM
 	if ps.Type == v1alpha3.MultiBranchPipelineType {
 		if branch == "" {
@@ -83,13 +85,16 @@ func getPipelineRef(pipeline *v1alpha3.Pipeline) *corev1.ObjectReference {
 	}
 }
 
-func createPipelineRun(pipeline *v1alpha3.Pipeline, payload *devops.RunPayload, scm *prv1alpha3.SCM) *prv1alpha3.PipelineRun {
+// CreatePipelineRun creates a bare PipelineRun.
+func CreatePipelineRun(pipeline *v1alpha3.Pipeline, payload *devops.RunPayload, scm *prv1alpha3.SCM) *prv1alpha3.PipelineRun {
 	controllerRef := metav1.NewControllerRef(pipeline, pipeline.GroupVersionKind())
 	return &prv1alpha3.PipelineRun{
 		ObjectMeta: metav1.ObjectMeta{
+			// the name should be like "pipeline-xyzmnt", so we set generate name "pipeline-" here.
 			GenerateName:    pipeline.GetName() + "-",
 			Namespace:       pipeline.GetNamespace(),
 			OwnerReferences: []metav1.OwnerReference{*controllerRef},
+			Annotations:     map[string]string{},
 		},
 		Spec: prv1alpha3.PipelineRunSpec{
 			PipelineRef:  getPipelineRef(pipeline),


Test patch:
diff --git a/controllers/jenkins/pipelinerun/pipelinerun_synchronizer_test.go b/controllers/jenkins/pipelinerun/pipelinerun_synchronizer_test.go
new file mode 100644
index 0000000..0a2fc7c
--- /dev/null
+++ b/controllers/jenkins/pipelinerun/pipelinerun_synchronizer_test.go
@@ -0,0 +1,197 @@
+package pipelinerun
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/jenkins-zh/jenkins-client/pkg/job"
+	"github.com/stretchr/testify/assert"
+	corev1 "k8s.io/api/core/v1"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	prv1alpha3 "kubesphere.io/devops/pkg/api/devops/pipelinerun/v1alpha3"
+	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
+	"sigs.k8s.io/controller-runtime/pkg/event"
+	"sigs.k8s.io/controller-runtime/pkg/predicate"
+)
+
+func Test_requestSyncPredicate(t *testing.T) {
+	tests := []struct {
+		name string
+		want func(predicate.Predicate)
+	}{{
+		name: "Request to sync while creating",
+		want: func(p predicate.Predicate) {
+			createEvent := event.CreateEvent{
+				Meta: &v1.ObjectMeta{
+					Annotations: map[string]string{
+						v1alpha3.PipelineRequestToSyncRunsAnnoKey: "true",
+					},
+				},
+			}
+			assert.True(t, p.Create(createEvent))
+		},
+	}, {
+		name: "Request to nothing while creating",
+		want: func(p predicate.Predicate) {
+			createEvent := event.CreateEvent{
+				Meta: &v1.ObjectMeta{},
+			}
+			assert.False(t, p.Create(createEvent))
+		},
+	}, {
+		name: "Request to sync while updating",
+		want: func(p predicate.Predicate) {
+			updateEvent := event.UpdateEvent{
+				MetaNew: &v1.ObjectMeta{
+					Annotations: map[string]string{
+						v1alpha3.PipelineRequestToSyncRunsAnnoKey: "true",
+					},
+				},
+			}
+			assert.True(t, p.Update(updateEvent))
+		},
+	}, {
+		name: "Request to nothing while updating",
+		want: func(p predicate.Predicate) {
+			updateEvent := event.UpdateEvent{
+				MetaNew: &v1.ObjectMeta{},
+			}
+			assert.False(t, p.Update(updateEvent))
+		},
+	}, {
+		name: "Nothing to do while deleting",
+		want: func(p predicate.Predicate) {
+			deleteEvent := event.DeleteEvent{
+				Meta: &v1.ObjectMeta{
+					Annotations: map[string]string{},
+				},
+			}
+			assert.False(t, p.Delete(deleteEvent))
+		},
+	}, {
+		name: "Nothing to do while genericing",
+		want: func(p predicate.Predicate) {
+			genericEvent := event.GenericEvent{
+				Meta: &v1.ObjectMeta{
+					Annotations: map[string]string{
+						v1alpha3.PipelineRequestToSyncRunsAnnoKey: "true",
+					},
+				},
+			}
+			assert.False(t, p.Generic(genericEvent))
+		},
+	}}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			tt.want(requestSyncPredicate())
+		})
+	}
+}
+
+func Test_createBarePipelineRun(t *testing.T) {
+	multiBranchPipeline := &v1alpha3.Pipeline{
+		ObjectMeta: v1.ObjectMeta{
+			Name:      "fake-pipeline",
+			Namespace: "fake-namespace",
+		},
+		Spec: v1alpha3.PipelineSpec{
+			Type: v1alpha3.MultiBranchPipelineType,
+		},
+	}
+
+	generalPipeline := &v1alpha3.Pipeline{
+		ObjectMeta: v1.ObjectMeta{
+			Name:      "fake-pipeline",
+			Namespace: "fake-namespace",
+		},
+		Spec: v1alpha3.PipelineSpec{
+			Type: v1alpha3.NoScmPipelineType,
+		},
+	}
+
+	type args struct {
+		pipeline *v1alpha3.Pipeline
+		run      *job.PipelineRun
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    *prv1alpha3.PipelineRun
+		wantErr bool
+	}{{
+		name: "Multi-branch pipeline",
+		args: args{
+			pipeline: multiBranchPipeline,
+			run: &job.PipelineRun{
+				ID: "123",
+				Branch: map[string]interface{}{
+					"url": "main",
+				},
+			},
+		},
+		want: &prv1alpha3.PipelineRun{
+			ObjectMeta: v1.ObjectMeta{
+				Namespace:    "fake-namespace",
+				GenerateName: "fake-pipeline-",
+				OwnerReferences: []v1.OwnerReference{
+					*v1.NewControllerRef(multiBranchPipeline, multiBranchPipeline.GroupVersionKind()),
+				},
+				Annotations: map[string]string{
+					prv1alpha3.JenkinsPipelineRunIDKey: "123",
+				},
+			},
+			Spec: prv1alpha3.PipelineRunSpec{
+				SCM: &prv1alpha3.SCM{
+					RefName: "main",
+					RefType: "",
+				},
+				PipelineRef: &corev1.ObjectReference{
+					Namespace: "fake-namespace",
+					Name:      "fake-pipeline",
+				},
+				PipelineSpec: &multiBranchPipeline.Spec,
+			},
+		},
+	},
+		{
+			name: "No SCM pipeline",
+			args: args{
+				pipeline: generalPipeline,
+				run: &job.PipelineRun{
+					ID: "123",
+				},
+			},
+			want: &prv1alpha3.PipelineRun{
+				ObjectMeta: v1.ObjectMeta{
+					Name:         "",
+					Namespace:    "fake-namespace",
+					GenerateName: "fake-pipeline-",
+					OwnerReferences: []v1.OwnerReference{
+						*v1.NewControllerRef(generalPipeline, generalPipeline.GroupVersionKind()),
+					},
+					Annotations: map[string]string{
+						prv1alpha3.JenkinsPipelineRunIDKey: "123",
+					},
+				},
+				Spec: prv1alpha3.PipelineRunSpec{
+					PipelineRef: &corev1.ObjectReference{
+						Namespace: "fake-namespace",
+						Name:      "fake-pipeline",
+					},
+					PipelineSpec: &generalPipeline.Spec,
+				},
+			},
+		}}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := createBarePipelineRun(tt.args.pipeline, tt.args.run)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("createBarePipelineRun() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("createBarePipelineRun() = \n%v, want \n%v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/controllers/jenkins/pipelinerun/utils_test.go b/controllers/jenkins/pipelinerun/utils_test.go
index e049a02..0195223 100644
--- a/controllers/jenkins/pipelinerun/utils_test.go
+++ b/controllers/jenkins/pipelinerun/utils_test.go
@@ -12,7 +12,7 @@ import (
 
 func Test_pipelineBuildApplier_apply(t *testing.T) {
 	type fields struct {
-		pb *job.PipelineBuild
+		pb *job.PipelineRun
 	}
 	type args struct {
 		prStatus *v1alpha3.PipelineRunStatus
@@ -33,7 +33,7 @@ func Test_pipelineBuildApplier_apply(t *testing.T) {
 	}{{
 		name: "PipelineRun was in queue",
 		fields: fields{
-			pb: &job.PipelineBuild{
+			pb: &job.PipelineRun{
 				ID:    "1",
 				State: Queued.String(),
 			},
@@ -50,7 +50,7 @@ func Test_pipelineBuildApplier_apply(t *testing.T) {
 	}, {
 		name: "PipelineRun was running",
 		fields: fields{
-			pb: &job.PipelineBuild{
+			pb: &job.PipelineRun{
 				ID:    "1",
 				State: Running.String(),
 			},
@@ -67,7 +67,7 @@ func Test_pipelineBuildApplier_apply(t *testing.T) {
 	}, {
 		name: "PipelineRun was paused",
 		fields: fields{
-			pb: &job.PipelineBuild{
+			pb: &job.PipelineRun{
 				ID:    "1",
 				State: Paused.String(),
 			},
@@ -84,7 +84,7 @@ func Test_pipelineBuildApplier_apply(t *testing.T) {
 	}, {
 		name: "PipelineRun was skipped",
 		fields: fields{
-			pb: &job.PipelineBuild{
+			pb: &job.PipelineRun{
 				ID:    "1",
 				State: Skipped.String(),
 			},
@@ -101,7 +101,7 @@ func Test_pipelineBuildApplier_apply(t *testing.T) {
 	}, {
 		name: "PipelineRun was not built",
 		fields: fields{
-			pb: &job.PipelineBuild{
+			pb: &job.PipelineRun{
 				ID:    "1",
 				State: NotBuiltState.String(),
 			},
@@ -118,7 +118,7 @@ func Test_pipelineBuildApplier_apply(t *testing.T) {
 	}, {
 		name: "Unknown PipelineRun state",
 		fields: fields{
-			pb: &job.PipelineBuild{
+			pb: &job.PipelineRun{
 				ID:    "1",
 				State: "this_is_an_invalid_state",
 			},
@@ -135,7 +135,7 @@ func Test_pipelineBuildApplier_apply(t *testing.T) {
 	}, {
 		name: "PipelineRun was finished with succeeded result",
 		fields: fields{
-			pb: &job.PipelineBuild{
+			pb: &job.PipelineRun{
 				ID:      "1",
 				State:   Finished.String(),
 				Result:  Success.String(),
@@ -155,7 +155,7 @@ func Test_pipelineBuildApplier_apply(t *testing.T) {
 	}, {
 		name: "PipelineRun was finished but with unstable result",
 		fields: fields{
-			pb: &job.PipelineBuild{
+			pb: &job.PipelineRun{
 				ID:     "1",
 				State:  Finished.String(),
 				Result: Unstable.String(),
@@ -173,7 +173,7 @@ func Test_pipelineBuildApplier_apply(t *testing.T) {
 	}, {
 		name: "PipelineRun was finished but failed",
 		fields: fields{
-			pb: &job.PipelineBuild{
+			pb: &job.PipelineRun{
 				ID:     "1",
 				State:  Finished.String(),
 				Result: Failure.String(),
@@ -191,7 +191,7 @@ func Test_pipelineBuildApplier_apply(t *testing.T) {
 	}, {
 		name: "PipelineRun was finished but with not built result",
 		fields: fields{
-			pb: &job.PipelineBuild{
+			pb: &job.PipelineRun{
 				ID:     "1",
 				State:  Finished.String(),
 				Result: NotBuiltResult.String(),
@@ -209,7 +209,7 @@ func Test_pipelineBuildApplier_apply(t *testing.T) {
 	}, {
 		name: "PipelineRun was finished but with unknown result",
 		fields: fields{
-			pb: &job.PipelineBuild{
+			pb: &job.PipelineRun{
 				ID:     "1",
 				State:  Finished.String(),
 				Result: Unknown.String(),
@@ -227,7 +227,7 @@ func Test_pipelineBuildApplier_apply(t *testing.T) {
 	}, {
 		name: "PipelineRun was finished but with aborted result",
 		fields: fields{
-			pb: &job.PipelineBuild{
+			pb: &job.PipelineRun{
 				ID:     "1",
 				State:  Finished.String(),
 				Result: Aborted.String(),
@@ -245,7 +245,7 @@ func Test_pipelineBuildApplier_apply(t *testing.T) {
 	}, {
 		name: "PipelineRun with new condition",
 		fields: fields{
-			pb: &job.PipelineBuild{
+			pb: &job.PipelineRun{
 				ID:     "1",
 				State:  Finished.String(),
 				Result: Success.String(),
@@ -274,7 +274,7 @@ func Test_pipelineBuildApplier_apply(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			pbApplier := &pipelineBuildApplier{
-				PipelineBuild: tt.fields.pb,
+				PipelineRun: tt.fields.pb,
 			}
 			pbApplier.apply(tt.args.prStatus)
 			tt.assertion(tt.args.prStatus)