Repository: kubesphere/ks-devops
Pull request #219 (2021-09-02 16:38:25)

Add event recorder into PipelineRun reconciler
### What this PR dose

Add event recorder into PipelineRun reconciler

### Why we need it

Please see https://github.com/kubesphere/ks-devops/pull/210#discussion_r699428922

### Special notes

![image](https://user-images.githubusercontent.com/16865714/131883139-d80f37d6-4e0e-4ad5-9b5d-c28410d91104.png)

/kind feature



Patch:
diff --git a/.github/workflows/nightly-build.yaml b/.github/workflows/nightly-build.yaml
new file mode 100644
index 0000000..4c84431
--- /dev/null
+++ b/.github/workflows/nightly-build.yaml
@@ -0,0 +1,192 @@
+name: NightlyBuild
+
+on:
+  schedule:
+    # This is a UTC time
+    - cron: "0 16 * * *"
+  # Keep it only for test purpose, comment it once everything is ok
+  workflow_dispatch:
+
+jobs:
+  BuildController:
+    runs-on: ubuntu-20.04
+    steps:
+      - uses: actions/checkout@v2
+      - name: Docker meta for kubesphere
+        id: meta
+        if: github.repository_owner == 'kubesphere'
+        uses: docker/metadata-action@v3
+        with:
+          images: |
+            kubespheredev/devops-controller
+            ghcr.io/${{ github.repository_owner }}/devops-controller
+          tags: |
+            type=schedule,pattern=nightly-{{date 'YYYYMMDD'}}
+            type=raw,enable=true,value=nightly-{{date 'YYYYMMDD'}}
+      - name: Docker meta for Contributors
+        id: metaContributors
+        if: github.repository_owner != 'kubesphere'
+        uses: docker/metadata-action@v3
+        with:
+          images: |
+            ghcr.io/${{ github.repository_owner }}/devops-controller
+          tags: |
+            type=schedule,pattern=nightly-{{date 'YYYYMMDD'}}
+            type=raw,enable=true,value=nightly-{{date 'YYYYMMDD'}}
+      - name: Set up QEMU
+        uses: docker/setup-qemu-action@v1
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v1
+      - name: Login to DockerHub
+        if: github.event_name != 'pull_request'
+        uses: docker/login-action@v1
+        with:
+          username: ${{ secrets.DOCKER_HUB_USER }}
+          password: ${{ secrets.DOCKER_HUB_SECRETS }}
+      - name: Login to GHCR
+        if: github.event_name != 'pull_request'
+        uses: docker/login-action@v1
+        with:
+          registry: ghcr.io
+          username: ${{ github.repository_owner }}
+          password: ${{ secrets.GHCR_TOKEN }}
+      - name: Build and push Docker images
+        uses: docker/build-push-action@v2.4.0
+        if: github.repository_owner == 'kubesphere'
+        with:
+          file: config/dockerfiles/controller-manager/Dockerfile
+          tags: ${{ steps.meta.outputs.tags }}
+          push: ${{ github.event_name != 'pull_request' }}
+          labels: ${{ steps.meta.outputs.labels }}
+          platforms: linux/amd64,linux/arm64
+      - name: Build and push Docker images for Contributors
+        uses: docker/build-push-action@v2.4.0
+        if: github.repository_owner != 'kubesphere'
+        with:
+          file: config/dockerfiles/controller-manager/Dockerfile
+          tags: ${{ steps.metaContributors.outputs.tags }}
+          push: ${{ github.event_name != 'pull_request' }}
+          labels: ${{ steps.metaContributors.outputs.labels }}
+          platforms: linux/amd64,linux/arm64
+
+  BuildAPIServer:
+    runs-on: ubuntu-20.04
+    steps:
+      - uses: actions/checkout@v2
+      - name: Docker meta for kubesphere
+        id: meta
+        if: github.repository_owner == 'kubesphere'
+        uses: docker/metadata-action@v3
+        with:
+          images: |
+            kubespheredev/devops-apiserver
+            ghcr.io/${{ github.repository_owner }}/devops-apiserver
+          tags: |
+            type=schedule,pattern=nightly-{{date 'YYYYMMDD'}}
+            type=raw,enable=true,value=nightly-{{date 'YYYYMMDD'}}
+      - name: Docker meta for Contributors
+        id: metaContributors
+        if: github.repository_owner != 'kubesphere'
+        uses: docker/metadata-action@v3
+        with:
+          images: |
+            ghcr.io/${{ github.repository_owner }}/devops-apiserver
+          tags: |
+            type=schedule,pattern=nightly-{{date 'YYYYMMDD'}}
+            type=raw,enable=true,value=nightly-{{date 'YYYYMMDD'}}
+      - name: Set up QEMU
+        uses: docker/setup-qemu-action@v1
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v1
+      - name: Login to DockerHub
+        if: github.event_name != 'pull_request'
+        uses: docker/login-action@v1
+        with:
+          username: ${{ secrets.DOCKER_HUB_USER }}
+          password: ${{ secrets.DOCKER_HUB_SECRETS }}
+      - name: Login to GHCR
+        if: github.event_name != 'pull_request'
+        uses: docker/login-action@v1
+        with:
+          registry: ghcr.io
+          username: ${{ github.repository_owner }}
+          password: ${{ secrets.GHCR_TOKEN }}
+      - name: Build and push Docker images
+        uses: docker/build-push-action@v2.4.0
+        if: github.repository_owner == 'kubesphere'
+        with:
+          file: config/dockerfiles/apiserver/Dockerfile
+          tags: ${{ steps.meta.outputs.tags }}
+          push: ${{ github.event_name != 'pull_request' }}
+          labels: ${{ steps.meta.outputs.labels }}
+          platforms: linux/amd64,linux/arm64
+      - name: Build and push Docker images for Contributors
+        uses: docker/build-push-action@v2.4.0
+        if: github.repository_owner != 'kubesphere'
+        with:
+          file: config/dockerfiles/apiserver/Dockerfile
+          tags: ${{ steps.metaContributors.outputs.tags }}
+          push: ${{ github.event_name != 'pull_request' }}
+          labels: ${{ steps.metaContributors.outputs.labels }}
+          platforms: linux/amd64,linux/arm64
+
+  BuildTools:
+    runs-on: ubuntu-20.04
+    steps:
+      - uses: actions/checkout@v2
+      - name: Docker meta for kubesphere
+        id: meta
+        if: github.repository_owner == 'kubesphere'
+        uses: docker/metadata-action@v3
+        with:
+          images: |
+            kubespheredev/devops-tools
+            ghcr.io/${{ github.repository_owner }}/devops-tools
+          tags: |
+            type=schedule,pattern=nightly-{{date 'YYYYMMDD'}}
+            type=raw,enable=true,value=nightly-{{date 'YYYYMMDD'}}
+      - name: Docker meta for Contributors
+        id: metaContributors
+        if: github.repository_owner != 'kubesphere'
+        uses: docker/metadata-action@v3
+        with:
+          images: |
+            ghcr.io/${{ github.repository_owner }}/devops-tools
+          tags: |
+            type=schedule,pattern=nightly-{{date 'YYYYMMDD'}}
+            type=raw,enable=true,value=nightly-{{date 'YYYYMMDD'}}
+      - name: Set up QEMU
+        uses: docker/setup-qemu-action@v1
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v1
+      - name: Login to DockerHub
+        if: github.event_name != 'pull_request'
+        uses: docker/login-action@v1
+        with:
+          username: ${{ secrets.DOCKER_HUB_USER }}
+          password: ${{ secrets.DOCKER_HUB_SECRETS }}
+      - name: Login to GHCR
+        if: github.event_name != 'pull_request'
+        uses: docker/login-action@v1
+        with:
+          registry: ghcr.io
+          username: ${{ github.repository_owner }}
+          password: ${{ secrets.GHCR_TOKEN }}
+      - name: Build and push Docker images
+        uses: docker/build-push-action@v2.4.0
+        if: github.repository_owner == 'kubesphere'
+        with:
+          file: config/dockerfiles/tools/Dockerfile
+          tags: ${{ steps.meta.outputs.tags }}
+          push: ${{ github.event_name != 'pull_request' }}
+          labels: ${{ steps.meta.outputs.labels }}
+          platforms: linux/amd64,linux/arm64
+      - name: Build and push Docker images for Contributors
+        uses: docker/build-push-action@v2.4.0
+        if: github.repository_owner != 'kubesphere'
+        with:
+          file: config/dockerfiles/tools/Dockerfile
+          tags: ${{ steps.metaContributors.outputs.tags }}
+          push: ${{ github.event_name != 'pull_request' }}
+          labels: ${{ steps.metaContributors.outputs.labels }}
+          platforms: linux/amd64,linux/arm64
diff --git a/controllers/devopsproject/devopsproject_controller.go b/controllers/devopsproject/devopsproject_controller.go
index 24da885..151117c 100644
--- a/controllers/devopsproject/devopsproject_controller.go
+++ b/controllers/devopsproject/devopsproject_controller.go
@@ -84,7 +84,6 @@ func NewController(client clientset.Interface,
 	devopsClinet devopsClient.Interface,
 	namespaceInformer corev1informer.NamespaceInformer,
 	devopsInformer devopsinformers.DevOpsProjectInformer) *Controller {
-
 	broadcaster := record.NewBroadcaster()
 	broadcaster.StartLogging(func(format string, args ...interface{}) {
 		klog.Info(fmt.Sprintf(format, args))
diff --git a/controllers/jenkins/pipelinerun/pipelinerun_controller.go b/controllers/jenkins/pipelinerun/pipelinerun_controller.go
index dd7a3fa..04fbeee 100644
--- a/controllers/jenkins/pipelinerun/pipelinerun_controller.go
+++ b/controllers/jenkins/pipelinerun/pipelinerun_controller.go
@@ -23,8 +23,10 @@ import (
 	"github.com/go-logr/logr"
 	"github.com/jenkins-zh/jenkins-client/pkg/core"
 	"github.com/jenkins-zh/jenkins-client/pkg/job"
+	corev1 "k8s.io/api/core/v1"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/client-go/tools/record"
 	"k8s.io/client-go/util/retry"
 	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
 	devopsv1alpha4 "kubesphere.io/devops/pkg/api/devops/v1alpha4"
@@ -32,7 +34,7 @@ import (
 	"reflect"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
-	time "time"
+	"time"
 )
 
 // Reconciler reconciles a PipelineRun object
@@ -42,6 +44,7 @@ type Reconciler struct {
 	Scheme       *runtime.Scheme
 	DevOpsClient devopsClient.Interface
 	JenkinsCore  core.JenkinsCore
+	recorder     record.EventRecorder
 }
 
 //+kubebuilder:rbac:groups=devops.kubesphere.io,resources=pipelineruns,verbs=get;list;watch;create;update;patch;delete
@@ -89,10 +92,10 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
 	// check PipelineRun status
 	if pr.HasStarted() {
 		log.V(5).Info("pipeline has already started, and we are retrieving run data from Jenkins.")
-
 		pipelineBuild, err := r.getPipelineRunResult(devopsProjectName, pipelineName, &pr)
 		if err != nil {
 			log.Error(err, "unable get PipelineRun data.")
+			r.recorder.Eventf(&pr, corev1.EventTypeWarning, devopsv1alpha4.RetrieveFailed, "Failed to retrieve running data from Jenkins, and error was %s", err)
 			return ctrl.Result{}, err
 		}
 
@@ -121,6 +124,7 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
 			log.Error(err, "unable to update PipelineRun status.")
 			return ctrl.Result{RequeueAfter: time.Second}, err
 		}
+		r.recorder.Eventf(&pr, corev1.EventTypeNormal, devopsv1alpha4.Updated, "Updated running data for PipelineRun %s", req.NamespacedName)
 		// until the status is okay
 		// TODO make the RequeueAfter configurable
 		return ctrl.Result{RequeueAfter: 3 * time.Second}, nil
@@ -130,6 +134,7 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
 	pipelineBuild, err := r.triggerJenkinsJob(devopsProjectName, pipelineName, &pr.Spec)
 	if err != nil {
 		log.Error(err, "unable to run pipeline", "devopsProjectName", devopsProjectName, "pipeline", pipeline.Name)
+		r.recorder.Eventf(&pr, corev1.EventTypeWarning, devopsv1alpha4.TriggerFailed, "Failed to trigger PipelineRun %s, and error was %s", req.NamespacedName, err)
 		return ctrl.Result{}, err
 	}
 	log.Info("Triggered a PipelineRun", "runID", pipelineBuild.ID)
@@ -160,7 +165,7 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
 		log.Error(err, "unable to update PipelineRun status.")
 		return ctrl.Result{}, err
 	}
-
+	r.recorder.Eventf(&pr, corev1.EventTypeNormal, devopsv1alpha4.Started, "Started PipelineRun %s", req.NamespacedName)
 	// requeue after 1 second
 	return ctrl.Result{RequeueAfter: 1 * time.Second}, nil
 }
@@ -263,6 +268,8 @@ func (r *Reconciler) makePipelineRunOrphan(ctx context.Context, pr *devopsv1alph
 
 // SetupWithManager sets up the controller with the Manager.
 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")
 	return ctrl.NewControllerManagedBy(mgr).
 		For(&devopsv1alpha4.PipelineRun{}).
 		Complete(r)
diff --git a/go.mod b/go.mod
index 9d24993..11c6602 100644
--- a/go.mod
+++ b/go.mod
@@ -47,7 +47,6 @@ require (
 	k8s.io/klog v1.0.0
 	k8s.io/klog/v2 v2.4.0
 	k8s.io/kube-openapi v0.0.0-20210527164424-3c818078ee3d
-	kubesphere.io/api v0.0.0-20210511124541-08f2d682bd07
 	sigs.k8s.io/controller-runtime v0.6.3
 )
 
diff --git a/go.sum b/go.sum
index 8c827ff..686efcc 100644
--- a/go.sum
+++ b/go.sum
@@ -53,7 +53,6 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
-github.com/alessio/shellescape v1.2.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
 github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
 github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
 github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
@@ -97,12 +96,9 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
 github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw=
 github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
-github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
-github.com/coreos/etcd v3.3.8+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
@@ -111,7 +107,6 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
 github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
@@ -126,7 +121,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ=
 github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
-github.com/dgrijalva/jwt-go v3.0.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
@@ -149,15 +143,12 @@ github.com/emicklei/go-restful-openapi v1.4.1 h1:SocVTIQWnXyit4dotTrwmncBAjtRaBm
 github.com/emicklei/go-restful-openapi v1.4.1/go.mod h1:kWQ8rQMVQ6G6lePwjDveJ00KjAUr/jq6z1X8DrDP3Gc=
 github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
 github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
-github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses=
 github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
-github.com/evanphx/json-patch/v5 v5.0.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
 github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
@@ -244,21 +235,16 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh
 github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
 github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
 github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
-github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
-github.com/go-playground/universal-translator v0.0.0-20170327191703-71201497bace/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
 github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4=
 github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
-github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80=
 github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
 github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
-github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
-github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
 github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/golang/example v0.0.0-20170904185048-46695d81d1fa h1:iqCQC2Z53KkwGgTN9szyL4q0OQHmuNjeoNnMT6lk66k=
@@ -266,7 +252,6 @@ github.com/golang/example v0.0.0-20170904185048-46695d81d1fa/go.mod h1:tO/5UvQ/u
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -278,12 +263,9 @@ github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3K
 github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
 github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
 github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
 github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
 github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
 github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
@@ -295,8 +277,6 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/google/addlicense v0.0.0-20200906110928-a0294312aa76/go.mod h1:EMjYTRimagHs1FwlIqKyX3wAM0u3rA+McvlIIWmSamA=
-github.com/google/btree v0.0.0-20161005200959-925471ac9e21/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -344,7 +324,6 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
-github.com/grpc-ecosystem/grpc-gateway v1.9.6/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
 github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
 github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
@@ -376,7 +355,6 @@ github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
 github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
-github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
 github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
@@ -408,15 +386,12 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
-github.com/kelseyhightower/envconfig v0.0.0-20180517194557-dd1402a4d99d h1:Tqg6yg0as+P38tbKytv9/yk+ifNq0CrvjlgADEniKog=
-github.com/kelseyhightower/envconfig v0.0.0-20180517194557-dd1402a4d99d/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
 github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
 github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -431,7 +406,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/kubesphere/sonargo v0.0.2 h1:hsSRE3sv3mkPcUAeSABdp7rtfcNW2zzeHXzFa01CTkU=
 github.com/kubesphere/sonargo v0.0.2/go.mod h1:ww8n9ANlDXhX5PBZ18iaRnCgEkXN0GMml3/KZXOZ11w=
-github.com/leodido/go-urn v0.0.0-20181204092800-a67a23e1c1af/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
 github.com/linuxsuren/cobra-extension v0.0.6/go.mod h1:qcEJv7BbL0UpK6MbrTESP/nKf1+z1wQdMAnE1NBl3QQ=
@@ -505,10 +479,8 @@ github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQ
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
 github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.4.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
 github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
@@ -517,13 +489,9 @@ github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvw
 github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
 github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
 github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
-github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
-github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
-github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
-github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
 github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
 github.com/onsi/gomega v1.10.4/go.mod h1:g/HbgYopi++010VEqkFgJHKC09uJiW9UkXvMUuKHUCQ=
@@ -544,13 +512,11 @@ github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIw
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
+github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI=
-github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
 github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
 github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
 github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
-github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
 github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -562,18 +528,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
 github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
-github.com/pquerna/ffjson v0.0.0-20190813045741-dac163c6c0a9/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M=
-github.com/projectcalico/go-json v0.0.0-20161128004156-6219dc7339ba h1:aaF2byUCZhzszHsfPEr2M3qcU4ibtD/yk/il2R7T1PU=
-github.com/projectcalico/go-json v0.0.0-20161128004156-6219dc7339ba/go.mod h1:q8EdCgBdMQzgiX/uk4GXLWLk+gIHd1a7mWUAamJKDb4=
-github.com/projectcalico/go-yaml v0.0.0-20161201183616-955bc3e451ef h1:Di9BaA9apb6DEstin8RdhKmlzQG76UMbmjPzjCVkMpc=
-github.com/projectcalico/go-yaml v0.0.0-20161201183616-955bc3e451ef/go.mod h1:1Ra2BftSa7Go38Gbq1q0bfmBFSSgUv+Cdc3SY8IL/C0=
-github.com/projectcalico/go-yaml-wrapper v0.0.0-20161127220527-598e54215bee h1:yVWsNSlAuYoJ0CznHsYRPiFgsotoj07k00k5rQvGlHM=
-github.com/projectcalico/go-yaml-wrapper v0.0.0-20161127220527-598e54215bee/go.mod h1:UgC0aTQ2KMDxlX3lU/stndk7DMUBJqzN40yFiILHgxc=
-github.com/projectcalico/libcalico-go v1.7.2-0.20191014160346-2382c6cdd056 h1:qs29Hus4cY8XlsmMLUsSAHT0metSTyqu2Tnpuwy5dkM=
-github.com/projectcalico/libcalico-go v1.7.2-0.20191014160346-2382c6cdd056/go.mod h1:tUt8rirmysRy7TR1S80XDriwBK1z2igwwX79lnUrSf4=
-github.com/prometheus/client_golang v0.0.0-20171005112915-5cec1d0429b0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
 github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
 github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
@@ -581,7 +536,6 @@ github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeD
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
 github.com/prometheus/client_golang v1.8.0 h1:zvJNkoCFAnYFNC24FV8nW4JdRJ3GIFcLbg65lL/JDcw=
 github.com/prometheus/client_golang v1.8.0/go.mod h1:O9VU6huf47PktckDQfMTX0Y8tY0/7TSWwj+ITvv0TnM=
-github.com/prometheus/client_model v0.0.0-20170216185247-6f3806018612/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -589,9 +543,7 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
 github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
 github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/common v0.0.0-20171104095907-e3fb1a1acd76/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
 github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
-github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
 github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
@@ -599,9 +551,7 @@ github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt2
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
 github.com/prometheus/common v0.14.0 h1:RHRyE8UocrbjU+6UvRzwi6HjiDfxrrBU91TtbKzkGp4=
 github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
-github.com/prometheus/procfs v0.0.0-20171017214025-a6e9df898b13/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@@ -612,14 +562,12 @@ github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULU
 github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
-github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
-github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
 github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
@@ -627,7 +575,6 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
-github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
@@ -648,7 +595,6 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
 github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
-github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
 github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
 github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
 github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
@@ -681,10 +627,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
-github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
-github.com/ugorji/go v0.0.0-20171019201919-bdcc60b419d1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
 github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
 github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
@@ -694,7 +638,6 @@ github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0B
 github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
-github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/zalando/go-keyring v0.0.0-20200121091418-667557018717/go.mod h1:RaxNwUITJaHVdQ0VC7pELPZ3tOWn13nr0gZMZEhpVU0=
@@ -724,7 +667,6 @@ go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
 go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
 go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
 go.uber.org/zap v1.8.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
-go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
 go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
@@ -749,9 +691,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
 golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
 golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
@@ -771,12 +711,10 @@ golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCc
 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
 golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180112015858-5ccada7d0a7b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -800,12 +738,10 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
 golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
@@ -831,7 +767,6 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180117170059-2c42eef0765b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -884,14 +819,12 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.1-0.20171227012246-e19ae1496984/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/time v0.0.0-20170420181420-c06e80d9300e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -904,7 +837,6 @@ golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGm
 golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
@@ -930,7 +862,6 @@ golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200616195046-dc31b401abb5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.1 h1:wGiQel/hW0NnEkJUk8lbzkX2gFJU6PFxf1v5OlCfuOs=
 golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
@@ -941,9 +872,6 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1N
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0=
 gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU=
-gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=
-gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
-gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ=
 google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
@@ -976,9 +904,7 @@ google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ij
 google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
 google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -988,7 +914,6 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -1000,9 +925,6 @@ gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
-gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
-gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
-gopkg.in/go-playground/validator.v9 v9.27.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
 gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
 gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
 gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
@@ -1013,12 +935,10 @@ gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76
 gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
 gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
 gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
-gopkg.in/tchap/go-patricia.v2 v2.2.6/go.mod h1:GjlIhdM7u6RWBtv58iEuqTR4NOShCtHo2EeySnNeNfs=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
-gopkg.in/yaml.v2 v2.0.0/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -1027,8 +947,6 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
-gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -1040,10 +958,6 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
-istio.io/api v0.0.0-20200812202721-24be265d41c3 h1:lFiPR81WQX0+AUHOZVTnVsIA9+nC1GCAv8mwVMrSTLI=
-istio.io/api v0.0.0-20200812202721-24be265d41c3/go.mod h1:88HN3o1fSD1jo+Z1WTLlJfMm9biopur6Ct9BFKjiB64=
-istio.io/gogo-genproto v0.0.0-20190930162913-45029607206a h1:w7zILua2dnYo9CxImhpNW4NE/8ZxEoc/wfBfHrhUhrE=
-istio.io/gogo-genproto v0.0.0-20190930162913-45029607206a/go.mod h1:OzpAts7jljZceG4Vqi5/zXy/pOg1b209T3jb7Nv5wIs=
 k8s.io/api v0.18.6 h1:osqrAXbOQjkKIWDTjrqxWQ3w0GkKb1KA1XkUGHHYpeE=
 k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI=
 k8s.io/apiextensions-apiserver v0.18.6 h1:vDlk7cyFsDyfwn2rNAO2DbmUbvXy5yT5GE3rrqOzaMo=
@@ -1054,17 +968,13 @@ k8s.io/apiserver v0.18.6 h1:HcWwcOfhj4Yv6y2igP4ZUuovyPjVLGoZcG0Tsph4Mxo=
 k8s.io/apiserver v0.18.6/go.mod h1:Zt2XvTHuaZjBz6EFYzpp+X4hTmgWGy8AthNVnTdm3Wg=
 k8s.io/client-go v0.18.6 h1:I+oWqJbibLSGsZj8Xs8F0aWVXJVIoUHWaaJV3kUN/Zw=
 k8s.io/client-go v0.18.6/go.mod h1:/fwtGLjYMS1MaM5oi+eXhKwG+1UHidUEXRh6cNsdO0Q=
-k8s.io/code-generator v0.0.0-20190814140513-6483f25b1faf/go.mod h1:PT1SOMszr9TJAi4EL8ncHI85SEdLvzWWYlfJ7ICZeYU=
 k8s.io/code-generator v0.18.6/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c=
-k8s.io/code-generator v0.18.9/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c=
 k8s.io/component-base v0.18.6 h1:Wd6cHGwJN2qpufnirVOB3oMhyhbioGsKEi5HeDBsV+s=
 k8s.io/component-base v0.18.6/go.mod h1:knSVsibPR5K6EW2XOjEHik6sdU5nCvKMrzMt2D4In14=
 k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
-k8s.io/gengo v0.0.0-20190813173942-955ffa8fcfc9/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
 k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
 k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
 k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
-k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
 k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
 k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
 k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
@@ -1072,33 +982,18 @@ k8s.io/klog/v2 v2.4.0 h1:7+X0fUguPyrKEC4WjH8iGDg3laWgMo5tMnRTIGTTxGQ=
 k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
 k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOECPVeXsVot0UkiaCGVyfGQY=
 k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
-k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
 k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
 k8s.io/utils v0.0.0-20200603063816-c1c6865ac451 h1:v8ud2Up6QK1lNOKFgiIVrZdMg7MpmSnvtrOieolJKoE=
 k8s.io/utils v0.0.0-20200603063816-c1c6865ac451/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
-kubesphere.io/api v0.0.0-20210511124541-08f2d682bd07 h1:n3wpImpNwfRkiqxhUP2Q7giMbdZ4PpRzZ5DTHRKlrrU=
-kubesphere.io/api v0.0.0-20210511124541-08f2d682bd07/go.mod h1:hYmBIGkM54MBMs/nYLuuO7GA0J6scIEDrdanDYuaYFc=
-modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
-modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
-modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
-modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
-modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
 moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8=
 moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0=
-sigs.k8s.io/application v0.8.4-0.20201016185654-c8e2959e57a0 h1:cH3Q4uNycL9LgzlyU+/UikIM1T6tx9LKc/Ie/QVIRM8=
-sigs.k8s.io/application v0.8.4-0.20201016185654-c8e2959e57a0/go.mod h1:wdTrELsIgKk8lnlRaoKWao9YpLelXpABdEgCM1aEEE4=
-sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZwfXkME1Lajns=
-sigs.k8s.io/controller-runtime v0.6.0/go.mod h1:CpYf5pdNY/B352A1TFLAS2JVSlnGQ5O2cftPHndTroo=
 sigs.k8s.io/controller-runtime v0.6.3 h1:SBbr+inLPEKhvlJtrvDcwIpm+uhDvp63Bl72xYJtoOE=
 sigs.k8s.io/controller-runtime v0.6.3/go.mod h1:WlZNXcM0++oyaQt4B7C2lEE5JYRs8vJUzRP4N4JpdAY=
-sigs.k8s.io/controller-tools v0.4.0/go.mod h1:G9rHdZMVlBDocIxGkK3jHLWqcTMNvveypYJwrvYKjWU=
-sigs.k8s.io/kind v0.8.1/go.mod h1:oNKTxUVPYkV9lWzY6CVMNluVq8cBsyq+UgPJdvA3uu4=
 sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
 sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E=
 sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
-sigs.k8s.io/testing_frameworks v0.1.2/go.mod h1:ToQrwSC3s8Xf/lADdZp3Mktcql9CG0UAmdJG9th5i0w=
 sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
 sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
 sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
diff --git a/pkg/api/devops/v1alpha4/pipelinerun_types.go b/pkg/api/devops/v1alpha4/pipelinerun_types.go
index 4d6af66..fd9d771 100644
--- a/pkg/api/devops/v1alpha4/pipelinerun_types.go
+++ b/pkg/api/devops/v1alpha4/pipelinerun_types.go
@@ -270,6 +270,18 @@ type Condition struct {
 	Message string `json:"message,omitempty" protobuf:"bytes,6,opt,name=message"`
 }
 
+// Valid values for event reasons (new reasons could be added in future)
+const (
+	// Started indicates PipelineRun has been triggered
+	Started string = "Started"
+	// Updated indicates PipelineRun's running data has been updated
+	Updated string = "Updated"
+	// TriggerFailed indicates that it failed to trigger build API
+	TriggerFailed string = "TriggerFailed"
+	// RetrieveFailed indicates that it failed to retrieve the latest running data
+	RetrieveFailed string = "RetrieveFailed"
+)
+
 func init() {
 	SchemeBuilder.Register(&PipelineRun{}, &PipelineRunList{})
 	apis.AddToSchemes = append(apis.AddToSchemes, AddToScheme)
diff --git a/pkg/client/clientset/versioned/fake/clientset_generated.go b/pkg/client/clientset/versioned/fake/clientset_generated.go
index 0b33e4e..516d3c4 100644
--- a/pkg/client/clientset/versioned/fake/clientset_generated.go
+++ b/pkg/client/clientset/versioned/fake/clientset_generated.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-// xCode generated by client-gen. DO NOT EDIT.
+// Code generated by client-gen. DO NOT EDIT.
 
 package fake
 
diff --git a/pkg/client/clientset/versioned/fake/register.go b/pkg/client/clientset/versioned/fake/register.go
index 74b70ca..4b0d867 100644
--- a/pkg/client/clientset/versioned/fake/register.go
+++ b/pkg/client/clientset/versioned/fake/register.go
@@ -24,40 +24,16 @@ import (
 	schema "k8s.io/apimachinery/pkg/runtime/schema"
 	serializer "k8s.io/apimachinery/pkg/runtime/serializer"
 	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
-	applicationv1alpha1 "kubesphere.io/api/application/v1alpha1"
-	auditingv1alpha1 "kubesphere.io/api/auditing/v1alpha1"
-	clusterv1alpha1 "kubesphere.io/api/cluster/v1alpha1"
-	devopsv1alpha1 "kubesphere.io/api/devops/v1alpha1"
-	devopsv1alpha3 "kubesphere.io/api/devops/v1alpha3"
-	iamv1alpha2 "kubesphere.io/api/iam/v1alpha2"
-	networkv1alpha1 "kubesphere.io/api/network/v1alpha1"
-	notificationv2beta1 "kubesphere.io/api/notification/v2beta1"
-	quotav1alpha2 "kubesphere.io/api/quota/v1alpha2"
-	servicemeshv1alpha2 "kubesphere.io/api/servicemesh/v1alpha2"
-	storagev1alpha1 "kubesphere.io/api/storage/v1alpha1"
-	tenantv1alpha1 "kubesphere.io/api/tenant/v1alpha1"
-	tenantv1alpha2 "kubesphere.io/api/tenant/v1alpha2"
-	typesv1beta1 "kubesphere.io/api/types/v1beta1"
+	devopsv1alpha1 "kubesphere.io/devops/pkg/api/devops/v1alpha1"
+	devopsv1alpha3 "kubesphere.io/devops/pkg/api/devops/v1alpha3"
 )
 
 var scheme = runtime.NewScheme()
 var codecs = serializer.NewCodecFactory(scheme)
 var parameterCodec = runtime.NewParameterCodec(scheme)
 var localSchemeBuilder = runtime.SchemeBuilder{
-	applicationv1alpha1.AddToScheme,
-	auditingv1alpha1.AddToScheme,
-	clusterv1alpha1.AddToScheme,
 	devopsv1alpha1.AddToScheme,
 	devopsv1alpha3.AddToScheme,
-	iamv1alpha2.AddToScheme,
-	networkv1alpha1.AddToScheme,
-	notificationv2beta1.AddToScheme,
-	quotav1alpha2.AddToScheme,
-	servicemeshv1alpha2.AddToScheme,
-	storagev1alpha1.AddToScheme,
-	tenantv1alpha1.AddToScheme,
-	tenantv1alpha2.AddToScheme,
-	typesv1beta1.AddToScheme,
 }
 
 // AddToScheme adds all types of this clientset into the given scheme. This allows composition
diff --git a/pkg/client/clientset/versioned/scheme/register.go b/pkg/client/clientset/versioned/scheme/register.go
index d4938b8..c13d810 100644
--- a/pkg/client/clientset/versioned/scheme/register.go
+++ b/pkg/client/clientset/versioned/scheme/register.go
@@ -20,11 +20,10 @@ package scheme
 
 import (
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	runtime "k8s.io/apimachinery/pkg/runtime"
-	schema "k8s.io/apimachinery/pkg/runtime/schema"
-	serializer "k8s.io/apimachinery/pkg/runtime/serializer"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/runtime/serializer"
 	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
-	typesv1beta1 "kubesphere.io/api/types/v1beta1"
 	devopsv1alpha1 "kubesphere.io/devops/pkg/api/devops/v1alpha1"
 	devopsv1alpha3 "kubesphere.io/devops/pkg/api/devops/v1alpha3"
 )
@@ -35,7 +34,6 @@ var ParameterCodec = runtime.NewParameterCodec(Scheme)
 var localSchemeBuilder = runtime.SchemeBuilder{
 	devopsv1alpha1.AddToScheme,
 	devopsv1alpha3.AddToScheme,
-	typesv1beta1.AddToScheme,
 }
 
 // AddToScheme adds all types of this clientset into the given scheme. This allows composition
diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go
index 5ce1b5f..7467231 100644
--- a/pkg/client/informers/externalversions/generic.go
+++ b/pkg/client/informers/externalversions/generic.go
@@ -21,10 +21,10 @@ package externalversions
 import (
 	"fmt"
 
-	schema "k8s.io/apimachinery/pkg/runtime/schema"
-	cache "k8s.io/client-go/tools/cache"
-	devopsv1alpha1 "kubesphere.io/api/devops/v1alpha1"
-	v1alpha3 "kubesphere.io/api/devops/v1alpha3"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/client-go/tools/cache"
+	devopsv1alpha1 "kubesphere.io/devops/pkg/api/devops/v1alpha1"
+	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
 )
 
 // GenericInformer is type of SharedIndexInformer which will locate and delegate to other
@@ -63,10 +63,10 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
 	case devopsv1alpha1.SchemeGroupVersion.WithResource("s2iruns"):
 		return &genericInformer{resource: resource.GroupResource(), informer: f.Devops().V1alpha1().S2iRuns().Informer()}, nil
 
-		// Group=devops.kubesphere.io, Version=v1alpha3
-	case v1alpha3.SchemeGroupVersion.WithResource("devopsprojects"):
+	// Group=devops.kubesphere.io, Version=v1alpha3
+	case v1alpha3.GroupVersion.WithResource("devopsprojects"):
 		return &genericInformer{resource: resource.GroupResource(), informer: f.Devops().V1alpha3().DevOpsProjects().Informer()}, nil
-	case v1alpha3.SchemeGroupVersion.WithResource("pipelines"):
+	case v1alpha3.GroupVersion.WithResource("pipelines"):
 		return &genericInformer{resource: resource.GroupResource(), informer: f.Devops().V1alpha3().Pipelines().Informer()}, nil
 
 	}
diff --git a/pkg/kapis/devops/v1alpha2/register.go b/pkg/kapis/devops/v1alpha2/register.go
index aa05236..53aee1a 100644
--- a/pkg/kapis/devops/v1alpha2/register.go
+++ b/pkg/kapis/devops/v1alpha2/register.go
@@ -31,7 +31,7 @@ import (
 	"k8s.io/apimachinery/pkg/util/proxy"
 	"k8s.io/klog"
 
-	devopsv1alpha1 "kubesphere.io/api/devops/v1alpha1"
+	devopsv1alpha1 "kubesphere.io/devops/pkg/api/devops/v1alpha1"
 
 	"kubesphere.io/devops/pkg/api"
 	"kubesphere.io/devops/pkg/client/clientset/versioned"
diff --git a/pkg/kapis/devops/v1alpha3/register.go b/pkg/kapis/devops/v1alpha3/register.go
index 13ad960..18b2a86 100644
--- a/pkg/kapis/devops/v1alpha3/register.go
+++ b/pkg/kapis/devops/v1alpha3/register.go
@@ -26,7 +26,7 @@ import (
 	restfulspec "github.com/emicklei/go-restful-openapi"
 	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/runtime/schema"
-	"kubesphere.io/api/devops/v1alpha3"
+	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
 
 	"kubesphere.io/devops/pkg/api"
 	"kubesphere.io/devops/pkg/apiserver/query"
diff --git a/pkg/models/resources/v1alpha3/devops/devops.go b/pkg/models/resources/v1alpha3/devops/devops.go
index d16d3a2..d42b4bb 100644
--- a/pkg/models/resources/v1alpha3/devops/devops.go
+++ b/pkg/models/resources/v1alpha3/devops/devops.go
@@ -19,7 +19,7 @@ package devops
 import (
 	"k8s.io/apimachinery/pkg/runtime"
 
-	devopsv1alpha3 "kubesphere.io/api/devops/v1alpha3"
+	devopsv1alpha3 "kubesphere.io/devops/pkg/api/devops/v1alpha3"
 
 	"kubesphere.io/devops/pkg/api"
 	"kubesphere.io/devops/pkg/apiserver/query"


Test patch:
diff --git a/controllers/devopscredential/devopscredential_controller_test.go b/controllers/devopscredential/devopscredential_controller_test.go
index c08a055..28d540e 100644
--- a/controllers/devopscredential/devopscredential_controller_test.go
+++ b/controllers/devopscredential/devopscredential_controller_test.go
@@ -16,386 +16,334 @@ limitations under the License.
 
 package devopscredential
 
-//import (
-//	"reflect"
-//	"testing"
-//	"time"
-//
-//	v1 "k8s.io/api/core/v1"
-//
-//	fakeDevOps "kubesphere.io/devops/pkg/client/devops/fake"
-//	"kubesphere.io/devops/pkg/constants"
-//
-//	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-//	"k8s.io/apimachinery/pkg/runtime"
-//	"k8s.io/apimachinery/pkg/runtime/schema"
-//	"k8s.io/apimachinery/pkg/util/diff"
-//	kubeinformers "k8s.io/client-go/informers"
-//	k8sfake "k8s.io/client-go/kubernetes/fake"
-//	core "k8s.io/client-go/testing"
-//	"k8s.io/client-go/tools/cache"
-//	"k8s.io/client-go/tools/record"
-//
-//	devops "kubesphere.io/devops/pkg/api/devops/v1alpha3"
-//)
-//
-//var (
-//	alwaysReady        = func() bool { return true }
-//	noResyncPeriodFunc = func() time.Duration { return 0 }
-//)
-//
-//type fixture struct {
-//	t *testing.T
-//
-//	kubeclient      *k8sfake.Clientset
-//	namespaceLister []*v1.Namespace
-//	secretLister    []*v1.Secret
-//	kubeactions     []core.Action
-//
-//	kubeobjects []runtime.Object
-//	// Objects from here preloaded into NewSimpleFake.
-//	objects []runtime.Object
-//	// Objects from here preloaded into devops
-//	initDevOpsProject string
-//	initCredential    []*v1.Secret
-//	expectCredential  []*v1.Secret
-//}
-//
-//func newFixture(t *testing.T) *fixture {
-//	f := &fixture{}
-//	f.t = t
-//	f.objects = []runtime.Object{}
-//	return f
-//}
-//
-//func newNamespace(name string, projectName string) *v1.Namespace {
-//	ns := &v1.Namespace{
-//		TypeMeta: metav1.TypeMeta{
-//			Kind:       "Namespace",
-//			APIVersion: v1.SchemeGroupVersion.String(),
-//		},
-//		ObjectMeta: metav1.ObjectMeta{
-//			Name:   name,
-//			Labels: map[string]string{constants.DevOpsProjectLabelKey: projectName},
-//		},
-//	}
-//	TRUE := true
-//	ns.ObjectMeta.OwnerReferences = []metav1.OwnerReference{
-//		{
-//			APIVersion:         devops.SchemeGroupVersion.String(),
-//			Kind:               devops.ResourceKindDevOpsProject,
-//			Name:               projectName,
-//			BlockOwnerDeletion: &TRUE,
-//			Controller:         &TRUE,
-//		},
-//	}
-//
-//	return ns
-//}
-//
-//func newSecret(namespace, name string, data map[string][]byte, withFinalizers bool, autoSync bool, syncOk bool) *v1.Secret {
-//	secret := &v1.Secret{
-//		TypeMeta: metav1.TypeMeta{
-//			Kind:       devops.ResourceKindPipeline,
-//			APIVersion: devops.SchemeGroupVersion.String(),
-//		},
-//		ObjectMeta: metav1.ObjectMeta{
-//			Namespace:   namespace,
-//			Name:        name,
-//			Annotations: map[string]string{},
-//		},
-//		Data: data,
-//		Type: devops.DevOpsCredentialPrefix + "test",
-//	}
-//	if withFinalizers {
-//		secret.Finalizers = append(secret.Finalizers, devops.CredentialFinalizerName)
-//	}
-//	if autoSync {
-//		secret.Annotations[devops.CredentialAutoSyncAnnoKey] = "true"
-//	}
-//	if syncOk {
-//		secret.Annotations[devops.CredentialSyncStatusAnnoKey] = constants.StatusSuccessful
-//	}
-//	return secret
-//}
-//
-//func newDeletingSecret(namespace, name string) *v1.Secret {
-//	now := metav1.Now()
-//	pipeline := &v1.Secret{
-//		TypeMeta: metav1.TypeMeta{
-//			Kind:       devops.ResourceKindPipeline,
-//			APIVersion: devops.SchemeGroupVersion.String(),
-//		},
-//		ObjectMeta: metav1.ObjectMeta{
-//			Namespace:         namespace,
-//			Name:              name,
-//			DeletionTimestamp: &now,
-//		},
-//		Type: devops.DevOpsCredentialPrefix + "test",
-//	}
-//	pipeline.Finalizers = append(pipeline.Finalizers, devops.CredentialFinalizerName)
-//
-//	return pipeline
-//}
-//
-//func (f *fixture) newController() (*Controller, kubeinformers.SharedInformerFactory, *fakeDevOps.Devops) {
-//	f.kubeclient = k8sfake.NewSimpleClientset(f.kubeobjects...)
-//
-//	k8sI := kubeinformers.NewSharedInformerFactory(f.kubeclient, noResyncPeriodFunc())
-//	dI := fakeDevOps.NewWithCredentials(f.initDevOpsProject, f.initCredential...)
-//
-//	c := NewController(f.kubeclient, dI, k8sI.Core().V1().Namespaces(),
-//		k8sI.Core().V1().Secrets())
-//
-//	c.secretSynced = alwaysReady
-//	c.eventRecorder = &record.FakeRecorder{}
-//	for _, f := range f.secretLister {
-//		k8sI.Core().V1().Secrets().Informer().GetIndexer().Add(f)
-//	}
-//
-//	for _, d := range f.namespaceLister {
-//		k8sI.Core().V1().Namespaces().Informer().GetIndexer().Add(d)
-//	}
-//
-//	return c, k8sI, dI
-//}
-//
-//func (f *fixture) run(fooName string) {
-//	f.runController(fooName, true, false)
-//}
-//
-//func (f *fixture) runExpectError(fooName string) {
-//	f.runController(fooName, true, true)
-//}
-//
-//func (f *fixture) runController(name string, startInformers bool, expectError bool) {
-//	c, k8sI, dI := f.newController()
-//	if startInformers {
-//		stopCh := make(chan struct{})
-//		defer close(stopCh)
-//		k8sI.Start(stopCh)
-//	}
-//
-//	err := c.syncHandler(name)
-//	if !expectError && err != nil {
-//		f.t.Errorf("error syncing foo: %v", err)
-//	} else if expectError && err == nil {
-//		f.t.Error("expected error syncing foo, got nil")
-//	}
-//
-//	k8sActions := filterInformerActions(f.kubeclient.Actions())
-//
-//	if len(f.kubeactions) > len(k8sActions) {
-//		f.t.Errorf("%d additional expected actions:%+v", len(f.kubeactions)-len(k8sActions), f.kubeactions[len(k8sActions):])
-//	}
-//
-//	if len(dI.Credentials[f.initDevOpsProject]) != len(f.expectCredential) {
-//		f.t.Errorf(" unexpected objects: %v", dI.Projects)
-//	}
-//	for _, credential := range f.expectCredential {
-//		actualCredential := dI.Credentials[f.initDevOpsProject][credential.Name]
-//		if !reflect.DeepEqual(actualCredential, credential) {
-//			f.t.Errorf(" credential %+v not match \n %+v", credential, actualCredential)
-//		}
-//	}
-//}
-//
-//// checkAction verifies that expected and actual actions are equal and both have
-//// same attached resources
-//func checkAction(expected, actual core.Action, t *testing.T) {
-//	if !(expected.Matches(actual.GetVerb(), actual.GetResource().Resource) && actual.GetSubresource() == expected.GetSubresource()) {
-//		t.Errorf("Expected\n\t%#v\ngot\n\t%#v", expected, actual)
-//		return
-//	}
-//
-//	if reflect.TypeOf(actual) != reflect.TypeOf(expected) {
-//		t.Errorf("Action has wrong type. Expected: %t. Got: %t", expected, actual)
-//		return
-//	}
-//
-//	switch a := actual.(type) {
-//	case core.CreateActionImpl:
-//		e, _ := expected.(core.CreateActionImpl)
-//		expObject := e.GetObject()
-//		object := a.GetObject()
-//
-//		if !reflect.DeepEqual(expObject, object) {
-//			t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
-//				a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expObject, object))
-//		}
-//	case core.UpdateActionImpl:
-//		e, _ := expected.(core.UpdateActionImpl)
-//		expObject := e.GetObject()
-//		object := a.GetObject()
-//
-//		if !reflect.DeepEqual(expObject, object) {
-//			t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
-//				a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expObject, object))
-//		}
-//	case core.PatchActionImpl:
-//		e, _ := expected.(core.PatchActionImpl)
-//		expPatch := e.GetPatch()
-//		patch := a.GetPatch()
-//
-//		if !reflect.DeepEqual(expPatch, patch) {
-//			t.Errorf("Action %s %s has wrong patch\nDiff:\n %s",
-//				a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expPatch, patch))
-//		}
-//	default:
-//		t.Errorf("Uncaptured Action %s %s, you should explicitly add a case to capture it",
-//			actual.GetVerb(), actual.GetResource().Resource)
-//	}
-//}
-//
-//// filterInformerActions filters list and watch actions for testing resources.
-//// Since list and watch don't change resource state we can filter it to lower
-//// nose level in our tests.
-//func filterInformerActions(actions []core.Action) []core.Action {
-//	ret := []core.Action{}
-//	for _, action := range actions {
-//		if len(action.GetNamespace()) == 0 &&
-//			(action.Matches("list", "secrets") ||
-//				action.Matches("watch", "secrets") ||
-//				action.Matches("list", "namespaces") ||
-//				action.Matches("watch", "namespaces")) {
-//			continue
-//		}
-//		ret = append(ret, action)
-//	}
-//
-//	return ret
-//}
-//
-//func (f *fixture) expectUpdateSecretAction(p *v1.Secret) {
-//	action := core.NewUpdateAction(schema.GroupVersionResource{
-//		Version:  "v1",
-//		Resource: "secrets",
-//	}, p.Namespace, p)
-//	f.kubeactions = append(f.kubeactions, action)
-//}
-//
-//func getKey(p *v1.Secret, t *testing.T) string {
-//	key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(p)
-//	if err != nil {
-//		t.Errorf("Unexpected error getting key for pipeline %v: %v", p.Name, err)
-//		return ""
-//	}
-//	return key
-//}
-//
-//func TestDoNothing(t *testing.T) {
-//	f := newFixture(t)
-//	nsName := "test-123"
-//	secretName := "test"
-//	projectName := "test_project"
-//
-//	ns := newNamespace(nsName, projectName)
-//	secret := newSecret(nsName, secretName, nil, true, true, false)
-//	expectSecret := newSecret(nsName, secretName, nil, true, true, true)
-//
-//	f.secretLister = append(f.secretLister, secret)
-//	f.namespaceLister = append(f.namespaceLister, ns)
-//	f.kubeobjects = append(f.kubeobjects, secret)
-//	f.initDevOpsProject = nsName
-//	f.initCredential = []*v1.Secret{secret}
-//	f.expectCredential = []*v1.Secret{expectSecret}
-//
-//	f.run(getKey(secret, t))
-//}
-//
-//func TestAddCredentialFinalizers(t *testing.T) {
-//	f := newFixture(t)
-//	nsName := "test-123"
-//	secretName := "test"
-//	projectName := "test_project"
-//
-//	ns := newNamespace(nsName, projectName)
-//	secret := newSecret(nsName, secretName, nil, false, true, false)
-//
-//	expectSecret := newSecret(nsName, secretName, nil, true, true, true)
-//
-//	f.secretLister = append(f.secretLister, secret)
-//	f.namespaceLister = append(f.namespaceLister, ns)
-//	f.kubeobjects = append(f.kubeobjects, secret)
-//	f.initDevOpsProject = nsName
-//	f.initCredential = []*v1.Secret{secret}
-//	f.expectCredential = []*v1.Secret{expectSecret}
-//	f.expectUpdateSecretAction(expectSecret)
-//	f.run(getKey(secret, t))
-//}
-//
-//func TestCreateCredential(t *testing.T) {
-//	f := newFixture(t)
-//	nsName := "test-123"
-//	secretName := "test"
-//	projectName := "test_project"
-//
-//	ns := newNamespace(nsName, projectName)
-//	secret := newSecret(nsName, secretName, nil, true, true, false)
-//	expectSecret := newSecret(nsName, secretName, nil, true, true, true)
-//
-//	f.secretLister = append(f.secretLister, secret)
-//	f.namespaceLister = append(f.namespaceLister, ns)
-//	f.kubeobjects = append(f.kubeobjects, secret)
-//	f.initDevOpsProject = nsName
-//	f.expectCredential = []*v1.Secret{expectSecret}
-//	f.run(getKey(secret, t))
-//}
-//
-//func TestDeleteCredential(t *testing.T) {
-//	f := newFixture(t)
-//	nsName := "test-123"
-//	secretName := "test"
-//	projectName := "test_project"
-//
-//	ns := newNamespace(nsName, projectName)
-//	secret := newDeletingSecret(nsName, secretName)
-//
-//	expectSecret := secret.DeepCopy()
-//	expectSecret.Finalizers = []string{}
-//	f.secretLister = append(f.secretLister, secret)
-//	f.namespaceLister = append(f.namespaceLister, ns)
-//	f.kubeobjects = append(f.kubeobjects, secret)
-//	f.initDevOpsProject = nsName
-//	f.initCredential = []*v1.Secret{secret}
-//	f.expectCredential = []*v1.Secret{}
-//	f.expectUpdateSecretAction(expectSecret)
-//	f.run(getKey(secret, t))
-//}
-//
-//func TestUpdateCredential(t *testing.T) {
-//	f := newFixture(t)
-//	nsName := "test-123"
-//	secretName := "test"
-//	projectName := "test_project"
-//
-//	ns := newNamespace(nsName, projectName)
-//	initSecret := newSecret(nsName, secretName, nil, true, true, false)
-//	modifiedSecret := newSecret(nsName, secretName, map[string][]byte{"a": []byte("aa")}, true, true, false)
-//	expectSecret := newSecret(nsName, secretName, map[string][]byte{"a": []byte("aa")}, true, true, true)
-//	f.secretLister = append(f.secretLister, modifiedSecret)
-//	f.namespaceLister = append(f.namespaceLister, ns)
-//	f.kubeobjects = append(f.kubeobjects, modifiedSecret)
-//	f.initDevOpsProject = nsName
-//	f.initCredential = []*v1.Secret{initSecret}
-//	f.expectCredential = []*v1.Secret{expectSecret}
-//	f.run(getKey(modifiedSecret, t))
-//}
-//
-//func TestNotUpdateCredential(t *testing.T) {
-//	f := newFixture(t)
-//	nsName := "test-123"
-//	secretName := "test"
-//	projectName := "test_project"
-//
-//	ns := newNamespace(nsName, projectName)
-//	initSecret := newSecret(nsName, secretName, nil, true, false, false)
-//	expectSecret := newSecret(nsName, secretName, map[string][]byte{"a": []byte("aa")}, true, false, true)
-//	f.secretLister = append(f.secretLister, expectSecret)
-//	f.namespaceLister = append(f.namespaceLister, ns)
-//	f.kubeobjects = append(f.kubeobjects, expectSecret)
-//	f.initDevOpsProject = nsName
-//	f.initCredential = []*v1.Secret{initSecret}
-//	f.expectCredential = []*v1.Secret{initSecret}
-//	f.run(getKey(expectSecret, t))
-//}
+import (
+	"reflect"
+	"testing"
+	"time"
+
+	v1 "k8s.io/api/core/v1"
+
+	fakeDevOps "kubesphere.io/devops/pkg/client/devops/fake"
+	"kubesphere.io/devops/pkg/constants"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	kubeinformers "k8s.io/client-go/informers"
+	k8sfake "k8s.io/client-go/kubernetes/fake"
+	core "k8s.io/client-go/testing"
+	"k8s.io/client-go/tools/cache"
+	"k8s.io/client-go/tools/record"
+
+	devops "kubesphere.io/devops/pkg/api/devops/v1alpha3"
+)
+
+var (
+	alwaysReady        = func() bool { return true }
+	noResyncPeriodFunc = func() time.Duration { return 0 }
+)
+
+type fixture struct {
+	t *testing.T
+
+	kubeclient      *k8sfake.Clientset
+	namespaceLister []*v1.Namespace
+	secretLister    []*v1.Secret
+	kubeactions     []core.Action
+
+	kubeobjects []runtime.Object
+	// Objects from here preloaded into NewSimpleFake.
+	objects []runtime.Object
+	// Objects from here preloaded into devops
+	initDevOpsProject string
+	initCredential    []*v1.Secret
+	expectCredential  []*v1.Secret
+}
+
+func newFixture(t *testing.T) *fixture {
+	f := &fixture{}
+	f.t = t
+	f.objects = []runtime.Object{}
+	return f
+}
+
+func newNamespace(name string, projectName string) *v1.Namespace {
+	ns := &v1.Namespace{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "Namespace",
+			APIVersion: v1.SchemeGroupVersion.String(),
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:   name,
+			Labels: map[string]string{constants.DevOpsProjectLabelKey: projectName},
+		},
+	}
+	TRUE := true
+	ns.ObjectMeta.OwnerReferences = []metav1.OwnerReference{
+		{
+			APIVersion:         devops.GroupVersion.String(),
+			Kind:               devops.ResourceKindDevOpsProject,
+			Name:               projectName,
+			BlockOwnerDeletion: &TRUE,
+			Controller:         &TRUE,
+		},
+	}
+
+	return ns
+}
+
+func newSecret(namespace, name string, data map[string][]byte, withFinalizers bool, autoSync bool, syncOk bool) *v1.Secret {
+	secret := &v1.Secret{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       devops.ResourceKindPipeline,
+			APIVersion: devops.GroupVersion.String(),
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace:   namespace,
+			Name:        name,
+			Annotations: map[string]string{},
+		},
+		Data: data,
+		Type: devops.DevOpsCredentialPrefix + "test",
+	}
+	if withFinalizers {
+		secret.Finalizers = append(secret.Finalizers, devops.CredentialFinalizerName)
+	}
+	if autoSync {
+		secret.Annotations[devops.CredentialAutoSyncAnnoKey] = "true"
+	}
+	if syncOk {
+		secret.Annotations[devops.CredentialSyncStatusAnnoKey] = constants.StatusSuccessful
+	}
+	return secret
+}
+
+func newDeletingSecret(namespace, name string) *v1.Secret {
+	now := metav1.Now()
+	pipeline := &v1.Secret{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       devops.ResourceKindPipeline,
+			APIVersion: devops.GroupVersion.String(),
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace:         namespace,
+			Name:              name,
+			DeletionTimestamp: &now,
+		},
+		Type: devops.DevOpsCredentialPrefix + "test",
+	}
+	pipeline.Finalizers = append(pipeline.Finalizers, devops.CredentialFinalizerName)
+
+	return pipeline
+}
+
+func (f *fixture) newController() (*Controller, kubeinformers.SharedInformerFactory, *fakeDevOps.Devops) {
+	f.kubeclient = k8sfake.NewSimpleClientset(f.kubeobjects...)
+
+	k8sI := kubeinformers.NewSharedInformerFactory(f.kubeclient, noResyncPeriodFunc())
+	dI := fakeDevOps.NewWithCredentials(f.initDevOpsProject, f.initCredential...)
+
+	c := NewController(f.kubeclient, dI, k8sI.Core().V1().Namespaces(),
+		k8sI.Core().V1().Secrets())
+
+	c.secretSynced = alwaysReady
+	c.eventRecorder = &record.FakeRecorder{}
+	for _, f := range f.secretLister {
+		_ = k8sI.Core().V1().Secrets().Informer().GetIndexer().Add(f)
+	}
+
+	for _, d := range f.namespaceLister {
+		_ = k8sI.Core().V1().Namespaces().Informer().GetIndexer().Add(d)
+	}
+
+	return c, k8sI, dI
+}
+
+func (f *fixture) run(fooName string) {
+	f.runController(fooName, true, false)
+}
+
+func (f *fixture) runController(name string, startInformers bool, expectError bool) {
+	c, k8sI, dI := f.newController()
+	if startInformers {
+		stopCh := make(chan struct{})
+		defer close(stopCh)
+		k8sI.Start(stopCh)
+	}
+
+	err := c.syncHandler(name)
+	if !expectError && err != nil {
+		f.t.Errorf("error syncing foo: %v", err)
+	} else if expectError && err == nil {
+		f.t.Error("expected error syncing foo, got nil")
+	}
+
+	k8sActions := filterInformerActions(f.kubeclient.Actions())
+
+	if len(f.kubeactions) > len(k8sActions) {
+		f.t.Errorf("%d additional expected actions:%+v", len(f.kubeactions)-len(k8sActions), f.kubeactions[len(k8sActions):])
+	}
+
+	if len(dI.Credentials[f.initDevOpsProject]) != len(f.expectCredential) {
+		f.t.Errorf(" unexpected objects: %v", dI.Projects)
+	}
+	for _, credential := range f.expectCredential {
+		actualCredential := dI.Credentials[f.initDevOpsProject][credential.Name]
+		if !reflect.DeepEqual(actualCredential, credential) {
+			f.t.Errorf(" credential %+v not match \n %+v", credential, actualCredential)
+		}
+	}
+}
+
+// filterInformerActions filters list and watch actions for testing resources.
+// Since list and watch don't change resource state we can filter it to lower
+// nose level in our tests.
+func filterInformerActions(actions []core.Action) []core.Action {
+	ret := []core.Action{}
+	for _, action := range actions {
+		if len(action.GetNamespace()) == 0 &&
+			(action.Matches("list", "secrets") ||
+				action.Matches("watch", "secrets") ||
+				action.Matches("list", "namespaces") ||
+				action.Matches("watch", "namespaces")) {
+			continue
+		}
+		ret = append(ret, action)
+	}
+
+	return ret
+}
+
+func (f *fixture) expectUpdateSecretAction(p *v1.Secret) {
+	action := core.NewUpdateAction(schema.GroupVersionResource{
+		Version:  "v1",
+		Resource: "secrets",
+	}, p.Namespace, p)
+	f.kubeactions = append(f.kubeactions, action)
+}
+
+func getKey(p *v1.Secret, t *testing.T) string {
+	key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(p)
+	if err != nil {
+		t.Errorf("Unexpected error getting key for pipeline %v: %v", p.Name, err)
+		return ""
+	}
+	return key
+}
+
+func TestDoNothing(t *testing.T) {
+	f := newFixture(t)
+	nsName := "test-123"
+	secretName := "test"
+	projectName := "test_project"
+
+	ns := newNamespace(nsName, projectName)
+	secret := newSecret(nsName, secretName, nil, true, true, false)
+	expectSecret := newSecret(nsName, secretName, nil, true, true, true)
+
+	f.secretLister = append(f.secretLister, secret)
+	f.namespaceLister = append(f.namespaceLister, ns)
+	f.kubeobjects = append(f.kubeobjects, secret)
+	f.initDevOpsProject = nsName
+	f.initCredential = []*v1.Secret{secret}
+	f.expectCredential = []*v1.Secret{expectSecret}
+
+	f.run(getKey(secret, t))
+}
+
+func TestAddCredentialFinalizers(t *testing.T) {
+	f := newFixture(t)
+	nsName := "test-123"
+	secretName := "test"
+	projectName := "test_project"
+
+	ns := newNamespace(nsName, projectName)
+	secret := newSecret(nsName, secretName, nil, false, true, false)
+
+	expectSecret := newSecret(nsName, secretName, nil, true, true, true)
+
+	f.secretLister = append(f.secretLister, secret)
+	f.namespaceLister = append(f.namespaceLister, ns)
+	f.kubeobjects = append(f.kubeobjects, secret)
+	f.initDevOpsProject = nsName
+	f.initCredential = []*v1.Secret{secret}
+	f.expectCredential = []*v1.Secret{expectSecret}
+	f.expectUpdateSecretAction(expectSecret)
+	f.run(getKey(secret, t))
+}
+
+func TestCreateCredential(t *testing.T) {
+	f := newFixture(t)
+	nsName := "test-123"
+	secretName := "test"
+	projectName := "test_project"
+
+	ns := newNamespace(nsName, projectName)
+	secret := newSecret(nsName, secretName, nil, true, true, false)
+	expectSecret := newSecret(nsName, secretName, nil, true, true, true)
+
+	f.secretLister = append(f.secretLister, secret)
+	f.namespaceLister = append(f.namespaceLister, ns)
+	f.kubeobjects = append(f.kubeobjects, secret)
+	f.initDevOpsProject = nsName
+	f.expectCredential = []*v1.Secret{expectSecret}
+	f.run(getKey(secret, t))
+}
+
+func TestDeleteCredential(t *testing.T) {
+	f := newFixture(t)
+	nsName := "test-123"
+	secretName := "test"
+	projectName := "test_project"
+
+	ns := newNamespace(nsName, projectName)
+	secret := newDeletingSecret(nsName, secretName)
+
+	expectSecret := secret.DeepCopy()
+	expectSecret.Finalizers = []string{}
+	f.secretLister = append(f.secretLister, secret)
+	f.namespaceLister = append(f.namespaceLister, ns)
+	f.kubeobjects = append(f.kubeobjects, secret)
+	f.initDevOpsProject = nsName
+	f.initCredential = []*v1.Secret{secret}
+	f.expectCredential = []*v1.Secret{}
+	f.expectUpdateSecretAction(expectSecret)
+	f.run(getKey(secret, t))
+}
+
+func TestUpdateCredential(t *testing.T) {
+	f := newFixture(t)
+	nsName := "test-123"
+	secretName := "test"
+	projectName := "test_project"
+
+	ns := newNamespace(nsName, projectName)
+	initSecret := newSecret(nsName, secretName, nil, true, true, false)
+	modifiedSecret := newSecret(nsName, secretName, map[string][]byte{"a": []byte("aa")}, true, true, false)
+	expectSecret := newSecret(nsName, secretName, map[string][]byte{"a": []byte("aa")}, true, true, true)
+	f.secretLister = append(f.secretLister, modifiedSecret)
+	f.namespaceLister = append(f.namespaceLister, ns)
+	f.kubeobjects = append(f.kubeobjects, modifiedSecret)
+	f.initDevOpsProject = nsName
+	f.initCredential = []*v1.Secret{initSecret}
+	f.expectCredential = []*v1.Secret{expectSecret}
+	f.run(getKey(modifiedSecret, t))
+}
+
+func TestNotUpdateCredential(t *testing.T) {
+	f := newFixture(t)
+	nsName := "test-123"
+	secretName := "test"
+	projectName := "test_project"
+
+	ns := newNamespace(nsName, projectName)
+	initSecret := newSecret(nsName, secretName, nil, true, false, false)
+	expectSecret := newSecret(nsName, secretName, map[string][]byte{"a": []byte("aa")}, true, false, true)
+	f.secretLister = append(f.secretLister, expectSecret)
+	f.namespaceLister = append(f.namespaceLister, ns)
+	f.kubeobjects = append(f.kubeobjects, expectSecret)
+	f.initDevOpsProject = nsName
+	f.initCredential = []*v1.Secret{initSecret}
+	f.expectCredential = []*v1.Secret{initSecret}
+	f.run(getKey(expectSecret, t))
+}
diff --git a/controllers/devopsproject/devopsproject_controller_test.go b/controllers/devopsproject/devopsproject_controller_test.go
index a1b3ac2..438019f 100644
--- a/controllers/devopsproject/devopsproject_controller_test.go
+++ b/controllers/devopsproject/devopsproject_controller_test.go
@@ -1,413 +1,357 @@
-///*
-//Copyright 2020 KubeSphere Authors
-//
-//Licensed under the Apache License, Version 2.0 (the "License");
-//you may not use this file except in compliance with the License.
-//You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-//Unless required by applicable law or agreed to in writing, software
-//distributed under the License is distributed on an "AS IS" BASIS,
-//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-//See the License for the specific language governing permissions and
-//limitations under the License.
-//*/
-//
+/*
+Copyright 2020 KubeSphere Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
 package devopsproject
 
-//
-//import (
-//	"reflect"
-//	"testing"
-//	"time"
-//
-//	v1 "k8s.io/api/core/v1"
-//
-//	devopsprojects "kubesphere.io/devops/pkg/api/devops/v1alpha3"
-//
-//	fakeDevOps "kubesphere.io/devops/pkg/client/devops/fake"
-//	"kubesphere.io/devops/pkg/constants"
-//
-//	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-//	"k8s.io/apimachinery/pkg/runtime"
-//	"k8s.io/apimachinery/pkg/runtime/schema"
-//	"k8s.io/apimachinery/pkg/util/diff"
-//	kubeinformers "k8s.io/client-go/informers"
-//	k8sfake "k8s.io/client-go/kubernetes/fake"
-//	core "k8s.io/client-go/testing"
-//	"k8s.io/client-go/tools/cache"
-//	"k8s.io/client-go/tools/record"
-//
-//	devops "kubesphere.io/devops/pkg/api/devops/v1alpha3"
-//
-//	"kubesphere.io/devops/pkg/client/clientset/versioned/fake"
-//	informers "kubesphere.io/devops/pkg/client/informers/externalversions"
-//)
-//
-//var (
-//	alwaysReady        = func() bool { return true }
-//	noResyncPeriodFunc = func() time.Duration { return 0 }
-//)
-//
-//type fixture struct {
-//	t *testing.T
-//
-//	client     *fake.Clientset
-//	kubeclient *k8sfake.Clientset
-//	// Objects to put in the store.
-//	devopsProjectLister []*devops.DevOpsProject
-//	namespaceLister     []*v1.Namespace
-//	actions             []core.Action
-//	kubeactions         []core.Action
-//
-//	kubeobjects []runtime.Object
-//	// Objects from here preloaded into NewSimpleFake.
-//	objects []runtime.Object
-//	// Objects from here preloaded into devops
-//	initDevOpsProject   []string
-//	expectDevOpsProject []string
-//}
-//
-//func newFixture(t *testing.T) *fixture {
-//	f := &fixture{}
-//	f.t = t
-//	f.objects = []runtime.Object{}
-//	return f
-//}
-//
-//func newDevOpsProject(name string, nsName string, withFinalizers bool, withStatus bool) *devopsprojects.DevOpsProject {
-//	project := &devopsprojects.DevOpsProject{
-//		TypeMeta: metav1.TypeMeta{APIVersion: devopsprojects.SchemeGroupVersion.String()},
-//		ObjectMeta: metav1.ObjectMeta{
-//			Name: name,
-//		},
-//	}
-//	if withFinalizers {
-//		project.Finalizers = []string{devopsprojects.DevOpsProjectFinalizerName}
-//	}
-//	if withStatus {
-//		project.Status = devops.DevOpsProjectStatus{AdminNamespace: nsName}
-//	}
-//	return project
-//}
-//
-//func newNamespace(name string, projectName string, useGenerateName, withOwnerReference bool) *v1.Namespace {
-//	ns := &v1.Namespace{
-//		TypeMeta: metav1.TypeMeta{
-//			Kind:       "Namespace",
-//			APIVersion: v1.SchemeGroupVersion.String(),
-//		},
-//		ObjectMeta: metav1.ObjectMeta{
-//			Name:   name,
-//			Labels: map[string]string{constants.DevOpsProjectLabelKey: projectName},
-//		},
-//	}
-//	if useGenerateName {
-//		ns.ObjectMeta.Name = ""
-//		ns.ObjectMeta.GenerateName = projectName
-//	}
-//	if withOwnerReference {
-//		TRUE := true
-//		ns.ObjectMeta.OwnerReferences = []metav1.OwnerReference{
-//			{
-//				APIVersion:         devops.SchemeGroupVersion.String(),
-//				Kind:               devops.ResourceKindDevOpsProject,
-//				Name:               projectName,
-//				BlockOwnerDeletion: &TRUE,
-//				Controller:         &TRUE,
-//			},
-//		}
-//	}
-//	return ns
-//}
-//
-//func newDeletingDevOpsProject(name string) *devopsprojects.DevOpsProject {
-//	now := metav1.Now()
-//	return &devopsprojects.DevOpsProject{
-//		TypeMeta: metav1.TypeMeta{APIVersion: devopsprojects.SchemeGroupVersion.String()},
-//		ObjectMeta: metav1.ObjectMeta{
-//			Name:              name,
-//			DeletionTimestamp: &now,
-//			Finalizers:        []string{devopsprojects.DevOpsProjectFinalizerName},
-//		},
-//	}
-//}
-//
-//func (f *fixture) newController() (*Controller, informers.SharedInformerFactory, kubeinformers.SharedInformerFactory, *fakeDevOps.Devops) {
-//	f.client = fake.NewSimpleClientset(f.objects...)
-//	f.kubeclient = k8sfake.NewSimpleClientset(f.kubeobjects...)
-//
-//	i := informers.NewSharedInformerFactory(f.client, noResyncPeriodFunc())
-//	k8sI := kubeinformers.NewSharedInformerFactory(f.kubeclient, noResyncPeriodFunc())
-//	dI := fakeDevOps.New(f.initDevOpsProject...)
-//
-//	c := NewController(f.kubeclient, f.client, dI,
-//		k8sI.Core().V1().Namespaces(),
-//		i.Devops().V1alpha3().DevOpsProjects(),
-//		i.Tenant().V1alpha1().Workspaces())
-//
-//	c.devOpsProjectSynced = alwaysReady
-//	c.eventRecorder = &record.FakeRecorder{}
-//
-//	for _, f := range f.devopsProjectLister {
-//		i.Devops().V1alpha3().DevOpsProjects().Informer().GetIndexer().Add(f)
-//	}
-//
-//	for _, d := range f.namespaceLister {
-//		k8sI.Core().V1().Namespaces().Informer().GetIndexer().Add(d)
-//	}
-//
-//	return c, i, k8sI, dI
-//}
-//
-//func (f *fixture) run(fooName string) {
-//	f.runController(fooName, true, false)
-//}
-//
-//func (f *fixture) runExpectError(fooName string) {
-//	f.runController(fooName, true, true)
-//}
-//
-//func (f *fixture) runController(projectName string, startInformers bool, expectError bool) {
-//	c, i, k8sI, dI := f.newController()
-//	if startInformers {
-//		stopCh := make(chan struct{})
-//		defer close(stopCh)
-//		i.Start(stopCh)
-//		k8sI.Start(stopCh)
-//	}
-//
-//	err := c.syncHandler(projectName)
-//	if !expectError && err != nil {
-//		f.t.Errorf("error syncing foo: %v", err)
-//	} else if expectError && err == nil {
-//		f.t.Error("expected error syncing foo, got nil")
-//	}
-//
-//	actions := filterInformerActions(f.client.Actions())
-//	k8sActions := filterInformerActions(f.kubeclient.Actions())
-//	if len(f.kubeactions) > len(k8sActions) {
-//		f.t.Errorf("%d additional expected actions:%+v", len(f.kubeactions)-len(k8sActions), f.kubeactions[len(k8sActions):])
-//	}
-//
-//	if len(f.actions) > len(actions) {
-//		f.t.Errorf("%d additional expected actions:%+v", len(f.actions)-len(actions), f.actions[len(actions):])
-//	}
-//	if len(dI.Projects) != len(f.expectDevOpsProject) {
-//		f.t.Errorf(" unexpected objects: %v", dI.Projects)
-//	}
-//}
-//
-//// checkAction verifies that expected and actual actions are equal and both have
-//// same attached resources
-//func checkAction(expected, actual core.Action, t *testing.T) {
-//	if !(expected.Matches(actual.GetVerb(), actual.GetResource().Resource) && actual.GetSubresource() == expected.GetSubresource()) {
-//		t.Errorf("Expected\n\t%#v\ngot\n\t%#v", expected, actual)
-//		return
-//	}
-//
-//	if reflect.TypeOf(actual) != reflect.TypeOf(expected) {
-//		t.Errorf("Action has wrong type. Expected: %t. Got: %t", expected, actual)
-//		return
-//	}
-//
-//	switch a := actual.(type) {
-//	case core.CreateActionImpl:
-//		e, _ := expected.(core.CreateActionImpl)
-//		expObject := e.GetObject()
-//		object := a.GetObject()
-//
-//		if !reflect.DeepEqual(expObject, object) {
-//			t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
-//				a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expObject, object))
-//		}
-//	case core.UpdateActionImpl:
-//		e, _ := expected.(core.UpdateActionImpl)
-//		expObject := e.GetObject()
-//		object := a.GetObject()
-//
-//		if !reflect.DeepEqual(expObject, object) {
-//			t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
-//				a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expObject, object))
-//		}
-//	case core.PatchActionImpl:
-//		e, _ := expected.(core.PatchActionImpl)
-//		expPatch := e.GetPatch()
-//		patch := a.GetPatch()
-//
-//		if !reflect.DeepEqual(expPatch, patch) {
-//			t.Errorf("Action %s %s has wrong patch\nDiff:\n %s",
-//				a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expPatch, patch))
-//		}
-//	default:
-//		t.Errorf("Uncaptured Action %s %s, you should explicitly add a case to capture it",
-//			actual.GetVerb(), actual.GetResource().Resource)
-//	}
-//}
-//
-//// filterInformerActions filters list and watch actions for testing resources.
-//// Since list and watch don't change resource state we can filter it to lower
-//// nose level in our tests.
-//func filterInformerActions(actions []core.Action) []core.Action {
-//	ret := []core.Action{}
-//	for _, action := range actions {
-//		if len(action.GetNamespace()) == 0 &&
-//			(action.Matches("list", devopsprojects.ResourcePluralDevOpsProject) ||
-//				action.Matches("watch", devopsprojects.ResourcePluralDevOpsProject) ||
-//				action.Matches("list", "namespaces") ||
-//				action.Matches("watch", "namespaces") ||
-//				action.Matches("watch", "workspaces") ||
-//				action.Matches("list", "workspaces")) {
-//			continue
-//		}
-//		ret = append(ret, action)
-//	}
-//
-//	return ret
-//}
-//
-//func (f *fixture) expectUpdateDevOpsProjectAction(p *devopsprojects.DevOpsProject) {
-//	action := core.NewUpdateAction(schema.GroupVersionResource{Resource: devopsprojects.ResourcePluralDevOpsProject},
-//		p.Namespace, p)
-//	f.actions = append(f.actions, action)
-//}
-//
-//func (f *fixture) expectUpdateNamespaceAction(p *v1.Namespace) {
-//	action := core.NewUpdateAction(schema.GroupVersionResource{
-//		Version:  "v1",
-//		Resource: "namespaces",
-//	}, p.Namespace, p)
-//	f.kubeactions = append(f.kubeactions, action)
-//}
-//
-//func (f *fixture) expectCreateNamespaceAction(p *v1.Namespace) {
-//	action := core.NewCreateAction(schema.GroupVersionResource{
-//		Version:  "v1",
-//		Resource: "namespaces",
-//	}, p.Namespace, p)
-//	f.kubeactions = append(f.kubeactions, action)
-//}
-//
-//func getKey(p *devopsprojects.DevOpsProject, t *testing.T) string {
-//	key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(p)
-//	if err != nil {
-//		t.Errorf("Unexpected error getting key for devopsprojects %v: %v", p.Name, err)
-//		return ""
-//	}
-//	return key
-//}
-//
-//func TestDoNothing(t *testing.T) {
-//	f := newFixture(t)
-//	nsName := "test-123"
-//	projectName := "test"
-//	project := newDevOpsProject(projectName, nsName, true, true)
-//	ns := newNamespace(nsName, projectName, false, true)
-//
-//	f.devopsProjectLister = append(f.devopsProjectLister, project)
-//	f.namespaceLister = append(f.namespaceLister, ns)
-//	f.objects = append(f.objects, project)
-//	f.initDevOpsProject = []string{ns.Name}
-//	f.expectDevOpsProject = []string{ns.Name}
-//
-//	f.run(getKey(project, t))
-//}
-//
-//func TestUpdateProjectFinalizers(t *testing.T) {
-//	f := newFixture(t)
-//	nsName := "test-123"
-//	projectName := "test"
-//	project := newDevOpsProject(projectName, nsName, false, true)
-//	ns := newNamespace(nsName, projectName, false, true)
-//
-//	f.devopsProjectLister = append(f.devopsProjectLister, project)
-//	f.namespaceLister = append(f.namespaceLister, ns)
-//	f.objects = append(f.objects, project)
-//	f.kubeobjects = append(f.kubeobjects, ns)
-//	f.initDevOpsProject = []string{ns.Name}
-//	f.expectDevOpsProject = []string{ns.Name}
-//	expectUpdateProject := project.DeepCopy()
-//	expectUpdateProject.Finalizers = []string{devops.DevOpsProjectFinalizerName}
-//	f.expectUpdateDevOpsProjectAction(expectUpdateProject)
-//	f.run(getKey(project, t))
-//}
-//
-//func TestUpdateProjectStatus(t *testing.T) {
-//	f := newFixture(t)
-//	nsName := "test-123"
-//	projectName := "test"
-//	project := newDevOpsProject(projectName, nsName, true, false)
-//	ns := newNamespace(nsName, projectName, false, true)
-//
-//	f.devopsProjectLister = append(f.devopsProjectLister, project)
-//	f.namespaceLister = append(f.namespaceLister, ns)
-//	f.objects = append(f.objects, project)
-//	f.kubeobjects = append(f.kubeobjects, ns)
-//	f.initDevOpsProject = []string{ns.Name}
-//	f.expectDevOpsProject = []string{ns.Name}
-//	expectUpdateProject := project.DeepCopy()
-//	expectUpdateProject.Status.AdminNamespace = nsName
-//	f.expectUpdateDevOpsProjectAction(expectUpdateProject)
-//	f.run(getKey(project, t))
-//}
-//
-//func TestUpdateNsOwnerReference(t *testing.T) {
-//	f := newFixture(t)
-//	nsName := "test-123"
-//	projectName := "test"
-//	project := newDevOpsProject(projectName, nsName, true, true)
-//	ns := newNamespace(nsName, projectName, false, false)
-//
-//	f.devopsProjectLister = append(f.devopsProjectLister, project)
-//	f.namespaceLister = append(f.namespaceLister, ns)
-//	f.objects = append(f.objects, project)
-//	f.kubeobjects = append(f.kubeobjects, ns)
-//	f.initDevOpsProject = []string{ns.Name}
-//	f.expectDevOpsProject = []string{ns.Name}
-//	expectUpdateNs := newNamespace(nsName, projectName, false, true)
-//
-//	f.expectUpdateNamespaceAction(expectUpdateNs)
-//	f.run(getKey(project, t))
-//}
-//
-//func TestCreateDevOpsProjects(t *testing.T) {
-//	f := newFixture(t)
-//	project := newDevOpsProject("test", "", true, false)
-//	ns := newNamespace("test", "test", false, true)
-//	f.devopsProjectLister = append(f.devopsProjectLister, project)
-//	f.objects = append(f.objects, project)
-//	f.expectDevOpsProject = []string{""}
-//	expect := project.DeepCopy()
-//	expect.Status.AdminNamespace = "test"
-//	f.expectUpdateDevOpsProjectAction(expect)
-//	f.expectCreateNamespaceAction(ns)
-//	f.run(getKey(project, t))
-//}
-//
-//func TestDeleteDevOpsProjects(t *testing.T) {
-//	f := newFixture(t)
-//	project := newDeletingDevOpsProject("test")
-//
-//	f.devopsProjectLister = append(f.devopsProjectLister, project)
-//	f.objects = append(f.objects, project)
-//	f.initDevOpsProject = []string{project.Name}
-//	f.expectDevOpsProject = []string{project.Name}
-//	expectProject := project.DeepCopy()
-//	expectProject.Finalizers = []string{}
-//	f.expectUpdateDevOpsProjectAction(expectProject)
-//	f.run(getKey(project, t))
-//}
-//
-//func TestDeleteDevOpsProjectsWithNull(t *testing.T) {
-//	f := newFixture(t)
-//	project := newDeletingDevOpsProject("test")
-//
-//	f.devopsProjectLister = append(f.devopsProjectLister, project)
-//	f.objects = append(f.objects, project)
-//	f.expectDevOpsProject = []string{}
-//	expectProject := project.DeepCopy()
-//	expectProject.Finalizers = []string{}
-//	f.expectUpdateDevOpsProjectAction(expectProject)
-//	f.run(getKey(project, t))
-//}
+import (
+	"testing"
+	"time"
+
+	v1 "k8s.io/api/core/v1"
+
+	devopsprojects "kubesphere.io/devops/pkg/api/devops/v1alpha3"
+
+	fakeDevOps "kubesphere.io/devops/pkg/client/devops/fake"
+	"kubesphere.io/devops/pkg/constants"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	kubeinformers "k8s.io/client-go/informers"
+	k8sfake "k8s.io/client-go/kubernetes/fake"
+	core "k8s.io/client-go/testing"
+	"k8s.io/client-go/tools/cache"
+	"k8s.io/client-go/tools/record"
+
+	devops "kubesphere.io/devops/pkg/api/devops/v1alpha3"
+
+	"kubesphere.io/devops/pkg/client/clientset/versioned/fake"
+	informers "kubesphere.io/devops/pkg/client/informers/externalversions"
+)
+
+var (
+	alwaysReady        = func() bool { return true }
+	noResyncPeriodFunc = func() time.Duration { return 0 }
+)
+
+type fixture struct {
+	t *testing.T
+
+	client     *fake.Clientset
+	kubeclient *k8sfake.Clientset
+	// Objects to put in the store.
+	devopsProjectLister []*devops.DevOpsProject
+	namespaceLister     []*v1.Namespace
+	actions             []core.Action
+	kubeactions         []core.Action
+
+	kubeobjects []runtime.Object
+	// Objects from here preloaded into NewSimpleFake.
+	objects []runtime.Object
+	// Objects from here preloaded into devops
+	initDevOpsProject   []string
+	expectDevOpsProject []string
+}
+
+func newFixture(t *testing.T) *fixture {
+	f := &fixture{}
+	f.t = t
+	f.objects = []runtime.Object{}
+	return f
+}
+
+func newDevOpsProject(name string, nsName string, withFinalizers bool, withStatus bool) *devopsprojects.DevOpsProject {
+	project := &devopsprojects.DevOpsProject{
+		TypeMeta: metav1.TypeMeta{APIVersion: devopsprojects.GroupVersion.String()},
+		ObjectMeta: metav1.ObjectMeta{
+			Name: name,
+		},
+	}
+	if withFinalizers {
+		project.Finalizers = []string{devopsprojects.DevOpsProjectFinalizerName}
+	}
+	if withStatus {
+		project.Status = devops.DevOpsProjectStatus{AdminNamespace: nsName}
+	}
+	return project
+}
+
+func newNamespace(name string, projectName string, useGenerateName, withOwnerReference bool) *v1.Namespace {
+	ns := &v1.Namespace{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "Namespace",
+			APIVersion: v1.SchemeGroupVersion.String(),
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:   name,
+			Labels: map[string]string{constants.DevOpsProjectLabelKey: projectName},
+		},
+	}
+	if useGenerateName {
+		ns.ObjectMeta.Name = ""
+		ns.ObjectMeta.GenerateName = projectName
+	}
+	if withOwnerReference {
+		TRUE := true
+		ns.ObjectMeta.OwnerReferences = []metav1.OwnerReference{
+			{
+				APIVersion:         devops.GroupVersion.String(),
+				Kind:               devops.ResourceKindDevOpsProject,
+				Name:               projectName,
+				BlockOwnerDeletion: &TRUE,
+				Controller:         &TRUE,
+			},
+		}
+	}
+	return ns
+}
+
+func newDeletingDevOpsProject(name string) *devopsprojects.DevOpsProject {
+	now := metav1.Now()
+	return &devopsprojects.DevOpsProject{
+		TypeMeta: metav1.TypeMeta{APIVersion: devopsprojects.GroupVersion.String()},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:              name,
+			DeletionTimestamp: &now,
+			Finalizers:        []string{devopsprojects.DevOpsProjectFinalizerName},
+		},
+	}
+}
+
+func (f *fixture) newController() (*Controller, informers.SharedInformerFactory, kubeinformers.SharedInformerFactory, *fakeDevOps.Devops) {
+	f.client = fake.NewSimpleClientset(f.objects...)
+	f.kubeclient = k8sfake.NewSimpleClientset(f.kubeobjects...)
+
+	i := informers.NewSharedInformerFactory(f.client, noResyncPeriodFunc())
+	k8sI := kubeinformers.NewSharedInformerFactory(f.kubeclient, noResyncPeriodFunc())
+	dI := fakeDevOps.New(f.initDevOpsProject...)
+
+	c := NewController(f.kubeclient, f.client, dI,
+		k8sI.Core().V1().Namespaces(),
+		i.Devops().V1alpha3().DevOpsProjects())
+
+	c.devOpsProjectSynced = alwaysReady
+	c.eventRecorder = &record.FakeRecorder{}
+
+	for _, f := range f.devopsProjectLister {
+		_ = i.Devops().V1alpha3().DevOpsProjects().Informer().GetIndexer().Add(f)
+	}
+
+	for _, d := range f.namespaceLister {
+		_ = k8sI.Core().V1().Namespaces().Informer().GetIndexer().Add(d)
+	}
+
+	return c, i, k8sI, dI
+}
+
+func (f *fixture) run(fooName string) {
+	f.runController(fooName, true, false)
+}
+
+func (f *fixture) runController(projectName string, startInformers bool, expectError bool) {
+	c, i, k8sI, dI := f.newController()
+	if startInformers {
+		stopCh := make(chan struct{})
+		defer close(stopCh)
+		i.Start(stopCh)
+		k8sI.Start(stopCh)
+	}
+
+	err := c.syncHandler(projectName)
+	if !expectError && err != nil {
+		f.t.Errorf("error syncing foo: %v", err)
+	} else if expectError && err == nil {
+		f.t.Error("expected error syncing foo, got nil")
+	}
+
+	actions := filterInformerActions(f.client.Actions())
+	k8sActions := filterInformerActions(f.kubeclient.Actions())
+	if len(f.kubeactions) > len(k8sActions) {
+		f.t.Errorf("%d additional expected actions:%+v", len(f.kubeactions)-len(k8sActions), f.kubeactions[len(k8sActions):])
+	}
+
+	if len(f.actions) > len(actions) {
+		f.t.Errorf("%d additional expected actions:%+v", len(f.actions)-len(actions), f.actions[len(actions):])
+	}
+	if len(dI.Projects) != len(f.expectDevOpsProject) {
+		f.t.Errorf(" unexpected objects: %v", dI.Projects)
+	}
+}
+
+// filterInformerActions filters list and watch actions for testing resources.
+// Since list and watch don't change resource state we can filter it to lower
+// nose level in our tests.
+func filterInformerActions(actions []core.Action) []core.Action {
+	ret := []core.Action{}
+	for _, action := range actions {
+		if len(action.GetNamespace()) == 0 &&
+			(action.Matches("list", devopsprojects.ResourcePluralDevOpsProject) ||
+				action.Matches("watch", devopsprojects.ResourcePluralDevOpsProject) ||
+				action.Matches("list", "namespaces") ||
+				action.Matches("watch", "namespaces") ||
+				action.Matches("watch", "workspaces") ||
+				action.Matches("list", "workspaces")) {
+			continue
+		}
+		ret = append(ret, action)
+	}
+
+	return ret
+}
+
+func (f *fixture) expectUpdateDevOpsProjectAction(p *devopsprojects.DevOpsProject) {
+	action := core.NewUpdateAction(schema.GroupVersionResource{Resource: devopsprojects.ResourcePluralDevOpsProject},
+		p.Namespace, p)
+	f.actions = append(f.actions, action)
+}
+
+func (f *fixture) expectUpdateNamespaceAction(p *v1.Namespace) {
+	action := core.NewUpdateAction(schema.GroupVersionResource{
+		Version:  "v1",
+		Resource: "namespaces",
+	}, p.Namespace, p)
+	f.kubeactions = append(f.kubeactions, action)
+}
+
+func (f *fixture) expectCreateNamespaceAction(p *v1.Namespace) {
+	action := core.NewCreateAction(schema.GroupVersionResource{
+		Version:  "v1",
+		Resource: "namespaces",
+	}, p.Namespace, p)
+	f.kubeactions = append(f.kubeactions, action)
+}
+
+func getKey(p *devopsprojects.DevOpsProject, t *testing.T) string {
+	key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(p)
+	if err != nil {
+		t.Errorf("Unexpected error getting key for devopsprojects %v: %v", p.Name, err)
+		return ""
+	}
+	return key
+}
+
+func TestDoNothing(t *testing.T) {
+	f := newFixture(t)
+	nsName := "test-123"
+	projectName := "test"
+	project := newDevOpsProject(projectName, nsName, true, true)
+	ns := newNamespace(nsName, projectName, false, true)
+
+	f.devopsProjectLister = append(f.devopsProjectLister, project)
+	f.namespaceLister = append(f.namespaceLister, ns)
+	f.objects = append(f.objects, project)
+	f.initDevOpsProject = []string{ns.Name}
+	f.expectDevOpsProject = []string{ns.Name}
+
+	f.run(getKey(project, t))
+}
+
+func TestUpdateProjectFinalizers(t *testing.T) {
+	f := newFixture(t)
+	nsName := "test-123"
+	projectName := "test"
+	project := newDevOpsProject(projectName, nsName, false, true)
+	ns := newNamespace(nsName, projectName, false, true)
+
+	f.devopsProjectLister = append(f.devopsProjectLister, project)
+	f.namespaceLister = append(f.namespaceLister, ns)
+	f.objects = append(f.objects, project)
+	f.kubeobjects = append(f.kubeobjects, ns)
+	f.initDevOpsProject = []string{ns.Name}
+	f.expectDevOpsProject = []string{ns.Name}
+	expectUpdateProject := project.DeepCopy()
+	expectUpdateProject.Finalizers = []string{devops.DevOpsProjectFinalizerName}
+	f.expectUpdateDevOpsProjectAction(expectUpdateProject)
+	f.run(getKey(project, t))
+}
+
+func TestUpdateProjectStatus(t *testing.T) {
+	f := newFixture(t)
+	nsName := "test-123"
+	projectName := "test"
+	project := newDevOpsProject(projectName, nsName, true, false)
+	ns := newNamespace(nsName, projectName, false, true)
+
+	f.devopsProjectLister = append(f.devopsProjectLister, project)
+	f.namespaceLister = append(f.namespaceLister, ns)
+	f.objects = append(f.objects, project)
+	f.kubeobjects = append(f.kubeobjects, ns)
+	f.initDevOpsProject = []string{ns.Name}
+	f.expectDevOpsProject = []string{ns.Name}
+	expectUpdateProject := project.DeepCopy()
+	expectUpdateProject.Status.AdminNamespace = nsName
+	f.expectUpdateDevOpsProjectAction(expectUpdateProject)
+	f.run(getKey(project, t))
+}
+
+func TestUpdateNsOwnerReference(t *testing.T) {
+	f := newFixture(t)
+	nsName := "test-123"
+	projectName := "test"
+	project := newDevOpsProject(projectName, nsName, true, true)
+	ns := newNamespace(nsName, projectName, false, false)
+
+	f.devopsProjectLister = append(f.devopsProjectLister, project)
+	f.namespaceLister = append(f.namespaceLister, ns)
+	f.objects = append(f.objects, project)
+	f.kubeobjects = append(f.kubeobjects, ns)
+	f.initDevOpsProject = []string{ns.Name}
+	f.expectDevOpsProject = []string{ns.Name}
+	expectUpdateNs := newNamespace(nsName, projectName, false, true)
+
+	f.expectUpdateNamespaceAction(expectUpdateNs)
+	f.run(getKey(project, t))
+}
+
+func TestCreateDevOpsProjects(t *testing.T) {
+	f := newFixture(t)
+	project := newDevOpsProject("test", "", true, false)
+	ns := newNamespace("test", "test", false, true)
+	f.devopsProjectLister = append(f.devopsProjectLister, project)
+	f.objects = append(f.objects, project)
+	f.expectDevOpsProject = []string{""}
+	expect := project.DeepCopy()
+	expect.Status.AdminNamespace = "test"
+	f.expectUpdateDevOpsProjectAction(expect)
+	f.expectCreateNamespaceAction(ns)
+	f.run(getKey(project, t))
+}
+
+func TestDeleteDevOpsProjects(t *testing.T) {
+	f := newFixture(t)
+	project := newDeletingDevOpsProject("test")
+
+	f.devopsProjectLister = append(f.devopsProjectLister, project)
+	f.objects = append(f.objects, project)
+	f.initDevOpsProject = []string{project.Name}
+	f.expectDevOpsProject = []string{project.Name}
+	expectProject := project.DeepCopy()
+	expectProject.Finalizers = []string{}
+	f.expectUpdateDevOpsProjectAction(expectProject)
+	f.run(getKey(project, t))
+}
+
+func TestDeleteDevOpsProjectsWithNull(t *testing.T) {
+	f := newFixture(t)
+	project := newDeletingDevOpsProject("test")
+	f.devopsProjectLister = append(f.devopsProjectLister, project)
+	f.objects = append(f.objects, project)
+	f.expectDevOpsProject = []string{}
+	expectProject := project.DeepCopy()
+	expectProject.Finalizers = []string{}
+	f.expectUpdateDevOpsProjectAction(expectProject)
+	f.run(getKey(project, t))
+}
diff --git a/controllers/pipeline/pipeline_controller_test.go b/controllers/pipeline/pipeline_controller_test.go
index 009885c..d72b8a3 100644
--- a/controllers/pipeline/pipeline_controller_test.go
+++ b/controllers/pipeline/pipeline_controller_test.go
@@ -16,407 +16,404 @@ limitations under the License.
 
 package pipeline
 
-//import (
-//	"reflect"
-//	"testing"
-//	"time"
-//
-//	v1 "k8s.io/api/core/v1"
-//
-//	fakeDevOps "kubesphere.io/devops/pkg/client/devops/fake"
-//	"kubesphere.io/devops/pkg/constants"
-//
-//	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-//	"k8s.io/apimachinery/pkg/runtime"
-//	"k8s.io/apimachinery/pkg/runtime/schema"
-//	"k8s.io/apimachinery/pkg/util/diff"
-//	kubeinformers "k8s.io/client-go/informers"
-//	k8sfake "k8s.io/client-go/kubernetes/fake"
-//	core "k8s.io/client-go/testing"
-//	"k8s.io/client-go/tools/cache"
-//	"k8s.io/client-go/tools/record"
-//
-//	devops "kubesphere.io/devops/pkg/api/devops/v1alpha3"
-//
-//	"kubesphere.io/devops/pkg/client/clientset/versioned/fake"
-//	informers "kubesphere.io/devops/pkg/client/informers/externalversions"
-//)
-//
-//var (
-//	alwaysReady        = func() bool { return true }
-//	noResyncPeriodFunc = func() time.Duration { return 0 }
-//)
-//
-//type fixture struct {
-//	t *testing.T
-//
-//	client          *fake.Clientset
-//	kubeclient      *k8sfake.Clientset
-//	namespaceLister []*v1.Namespace
-//	pipelineLister  []*devops.Pipeline
-//	actions         []core.Action
-//	kubeactions     []core.Action
-//
-//	kubeobjects []runtime.Object
-//	// Objects from here preloaded into NewSimpleFake.
-//	objects []runtime.Object
-//	// Objects from here preloaded into devops
-//	initDevOpsProject string
-//	initPipeline      []*devops.Pipeline
-//	expectPipeline    []*devops.Pipeline
-//}
-//
-//func newFixture(t *testing.T) *fixture {
-//	f := &fixture{}
-//	f.t = t
-//	f.objects = []runtime.Object{}
-//	return f
-//}
-//
-//func newNamespace(name string, projectName string) *v1.Namespace {
-//	ns := &v1.Namespace{
-//		TypeMeta: metav1.TypeMeta{
-//			Kind:       "Namespace",
-//			APIVersion: v1.SchemeGroupVersion.String(),
-//		},
-//		ObjectMeta: metav1.ObjectMeta{
-//			Name:   name,
-//			Labels: map[string]string{constants.DevOpsProjectLabelKey: projectName},
-//		},
-//	}
-//	TRUE := true
-//	ns.ObjectMeta.OwnerReferences = []metav1.OwnerReference{
-//		{
-//			APIVersion:         devops.SchemeGroupVersion.String(),
-//			Kind:               devops.ResourceKindDevOpsProject,
-//			Name:               projectName,
-//			BlockOwnerDeletion: &TRUE,
-//			Controller:         &TRUE,
-//		},
-//	}
-//
-//	return ns
-//}
-//
-//func newPipeline(namespace, name string, spec devops.PipelineSpec, withFinalizers bool, syncOk bool) *devops.Pipeline {
-//	pipeline := &devops.Pipeline{
-//		TypeMeta: metav1.TypeMeta{
-//			Kind:       devops.ResourceKindPipeline,
-//			APIVersion: devops.SchemeGroupVersion.String(),
-//		},
-//		ObjectMeta: metav1.ObjectMeta{
-//			Namespace:   namespace,
-//			Name:        name,
-//			Annotations: map[string]string{},
-//		},
-//		Spec:   spec,
-//		Status: devops.PipelineStatus{},
-//	}
-//	if withFinalizers {
-//		pipeline.Finalizers = append(pipeline.Finalizers, devops.PipelineFinalizerName)
-//	}
-//	if syncOk {
-//		pipeline.Annotations[devops.PipelineSyncStatusAnnoKey] = modelsdevops.StatusSuccessful
-//	}
-//	return pipeline
-//}
-//
-//func newDeletingPipeline(namespace, name string) *devops.Pipeline {
-//	now := metav1.Now()
-//	pipeline := &devops.Pipeline{
-//		TypeMeta: metav1.TypeMeta{
-//			Kind:       devops.ResourceKindPipeline,
-//			APIVersion: devops.SchemeGroupVersion.String(),
-//		},
-//		ObjectMeta: metav1.ObjectMeta{
-//			Namespace:         namespace,
-//			Name:              name,
-//			DeletionTimestamp: &now,
-//		},
-//	}
-//	pipeline.Finalizers = append(pipeline.Finalizers, devops.PipelineFinalizerName)
-//
-//	return pipeline
-//}
-//
-//func (f *fixture) newController() (*Controller, informers.SharedInformerFactory, kubeinformers.SharedInformerFactory, *fakeDevOps.Devops) {
-//	f.client = fake.NewSimpleClientset(f.objects...)
-//	f.kubeclient = k8sfake.NewSimpleClientset(f.kubeobjects...)
-//
-//	i := informers.NewSharedInformerFactory(f.client, noResyncPeriodFunc())
-//	k8sI := kubeinformers.NewSharedInformerFactory(f.kubeclient, noResyncPeriodFunc())
-//	dI := fakeDevOps.NewWithPipelines(f.initDevOpsProject, f.initPipeline...)
-//
-//	c := NewController(f.kubeclient, f.client, dI, k8sI.Core().V1().Namespaces(),
-//		i.Devops().V1alpha3().Pipelines())
-//
-//	c.pipelineSynced = alwaysReady
-//	c.eventRecorder = &record.FakeRecorder{}
-//
-//	for _, f := range f.pipelineLister {
-//		i.Devops().V1alpha3().Pipelines().Informer().GetIndexer().Add(f)
-//	}
-//
-//	for _, d := range f.namespaceLister {
-//		k8sI.Core().V1().Namespaces().Informer().GetIndexer().Add(d)
-//	}
-//
-//	return c, i, k8sI, dI
-//}
-//
-//func (f *fixture) run(fooName string) {
-//	f.runController(fooName, true, false)
-//}
-//
-//func (f *fixture) runExpectError(fooName string) {
-//	f.runController(fooName, true, true)
-//}
-//
-//func (f *fixture) runController(projectName string, startInformers bool, expectError bool) {
-//	c, i, k8sI, dI := f.newController()
-//	if startInformers {
-//		stopCh := make(chan struct{})
-//		defer close(stopCh)
-//		i.Start(stopCh)
-//		k8sI.Start(stopCh)
-//	}
-//
-//	err := c.syncHandler(projectName)
-//	if !expectError && err != nil {
-//		f.t.Errorf("error syncing foo: %v", err)
-//	} else if expectError && err == nil {
-//		f.t.Error("expected error syncing foo, got nil")
-//	}
-//
-//	actions := filterInformerActions(f.client.Actions())
-//	k8sActions := filterInformerActions(f.kubeclient.Actions())
-//	for i, action := range k8sActions {
-//		if len(f.kubeactions) < i+1 {
-//			f.t.Errorf("%d unexpected actions: %+v", len(k8sActions)-len(f.kubeactions), k8sActions[i:])
-//			break
-//		}
-//
-//		expectedAction := f.kubeactions[i]
-//		checkAction(expectedAction, action, f.t)
-//	}
-//
-//	if len(f.kubeactions) > len(k8sActions) {
-//		f.t.Errorf("%d additional expected actions:%+v", len(f.kubeactions)-len(k8sActions), f.kubeactions[len(k8sActions):])
-//	}
-//
-//	if len(f.actions) > len(actions) {
-//		f.t.Errorf("%d additional expected actions:%+v", len(f.actions)-len(actions), f.actions[len(actions):])
-//	}
-//	if len(dI.Pipelines[f.initDevOpsProject]) != len(f.expectPipeline) {
-//		f.t.Errorf(" unexpected objects: %v", dI.Projects)
-//	}
-//	for _, pipeline := range f.expectPipeline {
-//		actualPipeline := dI.Pipelines[f.initDevOpsProject][pipeline.Name]
-//		if !reflect.DeepEqual(actualPipeline, pipeline) {
-//			f.t.Errorf(" pipeline %+v not match %+v", pipeline, actualPipeline)
-//		}
-//
-//	}
-//}
-//
-//// checkAction verifies that expected and actual actions are equal and both have
-//// same attached resources
-//func checkAction(expected, actual core.Action, t *testing.T) {
-//	if !(expected.Matches(actual.GetVerb(), actual.GetResource().Resource) && actual.GetSubresource() == expected.GetSubresource()) {
-//		t.Errorf("Expected\n\t%#v\ngot\n\t%#v", expected, actual)
-//		return
-//	}
-//
-//	if reflect.TypeOf(actual) != reflect.TypeOf(expected) {
-//		t.Errorf("Action has wrong type. Expected: %t. Got: %t", expected, actual)
-//		return
-//	}
-//
-//	switch a := actual.(type) {
-//	case core.CreateActionImpl:
-//		e, _ := expected.(core.CreateActionImpl)
-//		expObject := e.GetObject()
-//		object := a.GetObject()
-//
-//		if !reflect.DeepEqual(expObject, object) {
-//			t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
-//				a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expObject, object))
-//		}
-//	case core.UpdateActionImpl:
-//		e, _ := expected.(core.UpdateActionImpl)
-//		expObject := e.GetObject()
-//		object := a.GetObject()
-//
-//		if !reflect.DeepEqual(expObject, object) {
-//			t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
-//				a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expObject, object))
-//		}
-//	case core.PatchActionImpl:
-//		e, _ := expected.(core.PatchActionImpl)
-//		expPatch := e.GetPatch()
-//		patch := a.GetPatch()
-//
-//		if !reflect.DeepEqual(expPatch, patch) {
-//			t.Errorf("Action %s %s has wrong patch\nDiff:\n %s",
-//				a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expPatch, patch))
-//		}
-//	default:
-//		t.Errorf("Uncaptured Action %s %s, you should explicitly add a case to capture it",
-//			actual.GetVerb(), actual.GetResource().Resource)
-//	}
-//}
-//
-//// filterInformerActions filters list and watch actions for testing resources.
-//// Since list and watch don't change resource state we can filter it to lower
-//// nose level in our tests.
-//func filterInformerActions(actions []core.Action) []core.Action {
-//	ret := []core.Action{}
-//	for _, action := range actions {
-//		if len(action.GetNamespace()) == 0 &&
-//			(action.Matches("list", devops.ResourcePluralPipeline) ||
-//				action.Matches("watch", devops.ResourcePluralPipeline) ||
-//				action.Matches("list", "namespaces") ||
-//				action.Matches("watch", "namespaces")) {
-//			continue
-//		}
-//		ret = append(ret, action)
-//	}
-//
-//	return ret
-//}
-//
-//func (f *fixture) expectUpdatePipelineAction(p *devops.Pipeline) {
-//	action := core.NewUpdateAction(schema.GroupVersionResource{
-//		Version:  devops.SchemeGroupVersion.Version,
-//		Resource: devops.ResourcePluralPipeline,
-//		Group:    devops.SchemeGroupVersion.Group,
-//	}, p.Namespace, p)
-//	f.actions = append(f.actions, action)
-//}
-//
-//func getKey(p *devops.Pipeline, t *testing.T) string {
-//	key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(p)
-//	if err != nil {
-//		t.Errorf("Unexpected error getting key for pipeline %v: %v", p.Name, err)
-//		return ""
-//	}
-//	return key
-//}
-//
-//func TestDoNothing(t *testing.T) {
-//	f := newFixture(t)
-//	nsName := "test-123"
-//	pipelineName := "test"
-//	projectName := "test_project"
-//
-//	ns := newNamespace(nsName, projectName)
-//	pipeline := newPipeline(nsName, pipelineName, devops.PipelineSpec{}, true, true)
-//
-//	f.pipelineLister = append(f.pipelineLister, pipeline)
-//	f.namespaceLister = append(f.namespaceLister, ns)
-//	f.objects = append(f.objects, pipeline)
-//	f.initDevOpsProject = nsName
-//	f.initPipeline = []*devops.Pipeline{pipeline}
-//	f.expectPipeline = []*devops.Pipeline{pipeline}
-//
-//	f.run(getKey(pipeline, t))
-//}
-//
-//func TestAddPipelineFinalizers(t *testing.T) {
-//	f := newFixture(t)
-//	nsName := "test-123"
-//	pipelineName := "test"
-//	projectName := "test_project"
-//
-//	ns := newNamespace(nsName, projectName)
-//	pipeline := newPipeline(nsName, pipelineName, devops.PipelineSpec{}, false, false)
-//
-//	expectPipeline := newPipeline(nsName, pipelineName, devops.PipelineSpec{}, true, true)
-//
-//	f.pipelineLister = append(f.pipelineLister, pipeline)
-//	f.namespaceLister = append(f.namespaceLister, ns)
-//	f.objects = append(f.objects, pipeline)
-//	f.initDevOpsProject = nsName
-//	f.initPipeline = []*devops.Pipeline{pipeline}
-//	f.expectPipeline = []*devops.Pipeline{pipeline}
-//	f.expectUpdatePipelineAction(expectPipeline)
-//	f.run(getKey(pipeline, t))
-//}
-//
-//func TestCreatePipeline(t *testing.T) {
-//	f := newFixture(t)
-//	nsName := "test-123"
-//	pipelineName := "test"
-//	projectName := "test_project"
-//
-//	ns := newNamespace(nsName, projectName)
-//	pipeline := newPipeline(nsName, pipelineName, devops.PipelineSpec{}, false, false)
-//	expectPipeline := newPipeline(nsName, pipelineName, devops.PipelineSpec{}, true, true)
-//	f.pipelineLister = append(f.pipelineLister, pipeline)
-//	f.namespaceLister = append(f.namespaceLister, ns)
-//	f.objects = append(f.objects, pipeline)
-//	f.initDevOpsProject = nsName
-//	f.expectPipeline = []*devops.Pipeline{expectPipeline}
-//	f.run(getKey(pipeline, t))
-//}
-//
-//func TestDeletePipeline(t *testing.T) {
-//	f := newFixture(t)
-//	nsName := "test-123"
-//	pipelineName := "test"
-//	projectName := "test_project"
-//
-//	ns := newNamespace(nsName, projectName)
-//	pipeline := newDeletingPipeline(nsName, pipelineName)
-//
-//	expectPipeline := pipeline.DeepCopy()
-//	expectPipeline.Finalizers = []string{}
-//	f.pipelineLister = append(f.pipelineLister, pipeline)
-//	f.namespaceLister = append(f.namespaceLister, ns)
-//	f.objects = append(f.objects, pipeline)
-//	f.initDevOpsProject = nsName
-//	f.initPipeline = []*devops.Pipeline{pipeline}
-//	f.expectPipeline = []*devops.Pipeline{}
-//	f.expectUpdatePipelineAction(expectPipeline)
-//	f.run(getKey(pipeline, t))
-//}
-//
-//func TestDeleteNotExistPipeline(t *testing.T) {
-//	f := newFixture(t)
-//	nsName := "test-123"
-//	pipelineName := "test"
-//	projectName := "test_project"
-//
-//	ns := newNamespace(nsName, projectName)
-//	pipeline := newDeletingPipeline(nsName, pipelineName)
-//
-//	expectPipeline := pipeline.DeepCopy()
-//	expectPipeline.Finalizers = []string{}
-//	f.pipelineLister = append(f.pipelineLister, pipeline)
-//	f.namespaceLister = append(f.namespaceLister, ns)
-//	f.objects = append(f.objects, pipeline)
-//	f.initDevOpsProject = nsName
-//	f.initPipeline = []*devops.Pipeline{}
-//	f.expectPipeline = []*devops.Pipeline{}
-//	f.expectUpdatePipelineAction(expectPipeline)
-//	f.run(getKey(pipeline, t))
-//}
-//
-//func TestUpdatePipelineConfig(t *testing.T) {
-//	f := newFixture(t)
-//	nsName := "test-123"
-//	pipelineName := "test"
-//	projectName := "test_project"
-//
-//	ns := newNamespace(nsName, projectName)
-//	initPipeline := newPipeline(nsName, pipelineName, devops.PipelineSpec{}, true, false)
-//	modifiedPipeline := newPipeline(nsName, pipelineName, devops.PipelineSpec{Type: "aa"}, true, false)
-//	expectPipeline := newPipeline(nsName, pipelineName, devops.PipelineSpec{Type: "aa"}, true, true)
-//	f.pipelineLister = append(f.pipelineLister, modifiedPipeline)
-//	f.namespaceLister = append(f.namespaceLister, ns)
-//	f.objects = append(f.objects, modifiedPipeline)
-//	f.initDevOpsProject = nsName
-//	f.initPipeline = []*devops.Pipeline{initPipeline}
-//	f.expectPipeline = []*devops.Pipeline{expectPipeline}
-//	f.run(getKey(modifiedPipeline, t))
-//}
+import (
+	"reflect"
+	"testing"
+	"time"
+
+	v1 "k8s.io/api/core/v1"
+
+	fakeDevOps "kubesphere.io/devops/pkg/client/devops/fake"
+	"kubesphere.io/devops/pkg/constants"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/util/diff"
+	kubeinformers "k8s.io/client-go/informers"
+	k8sfake "k8s.io/client-go/kubernetes/fake"
+	core "k8s.io/client-go/testing"
+	"k8s.io/client-go/tools/cache"
+	"k8s.io/client-go/tools/record"
+	modelsdevops "kubesphere.io/devops/pkg/models/devops"
+
+	devops "kubesphere.io/devops/pkg/api/devops/v1alpha3"
+
+	"kubesphere.io/devops/pkg/client/clientset/versioned/fake"
+	informers "kubesphere.io/devops/pkg/client/informers/externalversions"
+)
+
+var (
+	alwaysReady        = func() bool { return true }
+	noResyncPeriodFunc = func() time.Duration { return 0 }
+)
+
+type fixture struct {
+	t *testing.T
+
+	client          *fake.Clientset
+	kubeclient      *k8sfake.Clientset
+	namespaceLister []*v1.Namespace
+	pipelineLister  []*devops.Pipeline
+	actions         []core.Action
+	kubeactions     []core.Action
+
+	kubeobjects []runtime.Object
+	// Objects from here preloaded into NewSimpleFake.
+	objects []runtime.Object
+	// Objects from here preloaded into devops
+	initDevOpsProject string
+	initPipeline      []*devops.Pipeline
+	expectPipeline    []*devops.Pipeline
+}
+
+func newFixture(t *testing.T) *fixture {
+	f := &fixture{}
+	f.t = t
+	f.objects = []runtime.Object{}
+	return f
+}
+
+func newNamespace(name string, projectName string) *v1.Namespace {
+	ns := &v1.Namespace{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "Namespace",
+			APIVersion: v1.SchemeGroupVersion.String(),
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:   name,
+			Labels: map[string]string{constants.DevOpsProjectLabelKey: projectName},
+		},
+	}
+	TRUE := true
+	ns.ObjectMeta.OwnerReferences = []metav1.OwnerReference{
+		{
+			APIVersion:         devops.GroupVersion.String(),
+			Kind:               devops.ResourceKindDevOpsProject,
+			Name:               projectName,
+			BlockOwnerDeletion: &TRUE,
+			Controller:         &TRUE,
+		},
+	}
+
+	return ns
+}
+
+func newPipeline(namespace, name string, spec devops.PipelineSpec, withFinalizers bool, syncOk bool) *devops.Pipeline {
+	pipeline := &devops.Pipeline{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       devops.ResourceKindPipeline,
+			APIVersion: devops.GroupVersion.String(),
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace:   namespace,
+			Name:        name,
+			Annotations: map[string]string{},
+		},
+		Spec:   spec,
+		Status: devops.PipelineStatus{},
+	}
+	if withFinalizers {
+		pipeline.Finalizers = append(pipeline.Finalizers, devops.PipelineFinalizerName)
+	}
+	if syncOk {
+		pipeline.Annotations[devops.PipelineSyncStatusAnnoKey] = modelsdevops.StatusSuccessful
+	}
+	return pipeline
+}
+
+func newDeletingPipeline(namespace, name string) *devops.Pipeline {
+	now := metav1.Now()
+	pipeline := &devops.Pipeline{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       devops.ResourceKindPipeline,
+			APIVersion: devops.GroupVersion.String(),
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace:         namespace,
+			Name:              name,
+			DeletionTimestamp: &now,
+		},
+	}
+	pipeline.Finalizers = append(pipeline.Finalizers, devops.PipelineFinalizerName)
+
+	return pipeline
+}
+
+func (f *fixture) newController() (*Controller, informers.SharedInformerFactory, kubeinformers.SharedInformerFactory, *fakeDevOps.Devops) {
+	f.client = fake.NewSimpleClientset(f.objects...)
+	f.kubeclient = k8sfake.NewSimpleClientset(f.kubeobjects...)
+
+	i := informers.NewSharedInformerFactory(f.client, noResyncPeriodFunc())
+	k8sI := kubeinformers.NewSharedInformerFactory(f.kubeclient, noResyncPeriodFunc())
+	dI := fakeDevOps.NewWithPipelines(f.initDevOpsProject, f.initPipeline...)
+
+	c := NewController(f.kubeclient, f.client, dI, k8sI.Core().V1().Namespaces(),
+		i.Devops().V1alpha3().Pipelines())
+
+	c.pipelineSynced = alwaysReady
+	c.eventRecorder = &record.FakeRecorder{}
+
+	for _, f := range f.pipelineLister {
+		_ = i.Devops().V1alpha3().Pipelines().Informer().GetIndexer().Add(f)
+	}
+
+	for _, d := range f.namespaceLister {
+		_ = k8sI.Core().V1().Namespaces().Informer().GetIndexer().Add(d)
+	}
+
+	return c, i, k8sI, dI
+}
+
+func (f *fixture) run(fooName string) {
+	f.runController(fooName, true, false)
+}
+
+func (f *fixture) runController(projectName string, startInformers bool, expectError bool) {
+	c, i, k8sI, dI := f.newController()
+	if startInformers {
+		stopCh := make(chan struct{})
+		defer close(stopCh)
+		i.Start(stopCh)
+		k8sI.Start(stopCh)
+	}
+
+	err := c.syncHandler(projectName)
+	if !expectError && err != nil {
+		f.t.Errorf("error syncing foo: %v", err)
+	} else if expectError && err == nil {
+		f.t.Error("expected error syncing foo, got nil")
+	}
+
+	actions := filterInformerActions(f.client.Actions())
+	k8sActions := filterInformerActions(f.kubeclient.Actions())
+	for i, action := range k8sActions {
+		if len(f.kubeactions) < i+1 {
+			f.t.Errorf("%d unexpected actions: %+v", len(k8sActions)-len(f.kubeactions), k8sActions[i:])
+			break
+		}
+
+		expectedAction := f.kubeactions[i]
+		checkAction(expectedAction, action, f.t)
+	}
+
+	if len(f.kubeactions) > len(k8sActions) {
+		f.t.Errorf("%d additional expected actions:%+v", len(f.kubeactions)-len(k8sActions), f.kubeactions[len(k8sActions):])
+	}
+
+	if len(f.actions) > len(actions) {
+		f.t.Errorf("%d additional expected actions:%+v", len(f.actions)-len(actions), f.actions[len(actions):])
+	}
+	if len(dI.Pipelines[f.initDevOpsProject]) != len(f.expectPipeline) {
+		f.t.Errorf(" unexpected objects: %v", dI.Projects)
+	}
+	for _, pipeline := range f.expectPipeline {
+		actualPipeline := dI.Pipelines[f.initDevOpsProject][pipeline.Name]
+		if !reflect.DeepEqual(actualPipeline, pipeline) {
+			f.t.Errorf(" pipeline %+v not match %+v", pipeline, actualPipeline)
+		}
+
+	}
+}
+
+// checkAction verifies that expected and actual actions are equal and both have
+// same attached resources
+func checkAction(expected, actual core.Action, t *testing.T) {
+	if !(expected.Matches(actual.GetVerb(), actual.GetResource().Resource) && actual.GetSubresource() == expected.GetSubresource()) {
+		t.Errorf("Expected\n\t%#v\ngot\n\t%#v", expected, actual)
+		return
+	}
+
+	if reflect.TypeOf(actual) != reflect.TypeOf(expected) {
+		t.Errorf("Action has wrong type. Expected: %t. Got: %t", expected, actual)
+		return
+	}
+
+	switch a := actual.(type) {
+	case core.CreateActionImpl:
+		e, _ := expected.(core.CreateActionImpl)
+		expObject := e.GetObject()
+		object := a.GetObject()
+
+		if !reflect.DeepEqual(expObject, object) {
+			t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
+				a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expObject, object))
+		}
+	case core.UpdateActionImpl:
+		e, _ := expected.(core.UpdateActionImpl)
+		expObject := e.GetObject()
+		object := a.GetObject()
+
+		if !reflect.DeepEqual(expObject, object) {
+			t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
+				a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expObject, object))
+		}
+	case core.PatchActionImpl:
+		e, _ := expected.(core.PatchActionImpl)
+		expPatch := e.GetPatch()
+		patch := a.GetPatch()
+
+		if !reflect.DeepEqual(expPatch, patch) {
+			t.Errorf("Action %s %s has wrong patch\nDiff:\n %s",
+				a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expPatch, patch))
+		}
+	default:
+		t.Errorf("Uncaptured Action %s %s, you should explicitly add a case to capture it",
+			actual.GetVerb(), actual.GetResource().Resource)
+	}
+}
+
+// filterInformerActions filters list and watch actions for testing resources.
+// Since list and watch don't change resource state we can filter it to lower
+// nose level in our tests.
+func filterInformerActions(actions []core.Action) []core.Action {
+	ret := []core.Action{}
+	for _, action := range actions {
+		if len(action.GetNamespace()) == 0 &&
+			(action.Matches("list", devops.ResourcePluralPipeline) ||
+				action.Matches("watch", devops.ResourcePluralPipeline) ||
+				action.Matches("list", "namespaces") ||
+				action.Matches("watch", "namespaces")) {
+			continue
+		}
+		ret = append(ret, action)
+	}
+
+	return ret
+}
+
+func (f *fixture) expectUpdatePipelineAction(p *devops.Pipeline) {
+	action := core.NewUpdateAction(schema.GroupVersionResource{
+		Version:  devops.GroupVersion.Version,
+		Resource: devops.ResourcePluralPipeline,
+		Group:    devops.GroupVersion.Group,
+	}, p.Namespace, p)
+	f.actions = append(f.actions, action)
+}
+
+func getKey(p *devops.Pipeline, t *testing.T) string {
+	key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(p)
+	if err != nil {
+		t.Errorf("Unexpected error getting key for pipeline %v: %v", p.Name, err)
+		return ""
+	}
+	return key
+}
+
+func TestDoNothing(t *testing.T) {
+	f := newFixture(t)
+	nsName := "test-123"
+	pipelineName := "test"
+	projectName := "test_project"
+
+	ns := newNamespace(nsName, projectName)
+	pipeline := newPipeline(nsName, pipelineName, devops.PipelineSpec{}, true, true)
+
+	f.pipelineLister = append(f.pipelineLister, pipeline)
+	f.namespaceLister = append(f.namespaceLister, ns)
+	f.objects = append(f.objects, pipeline)
+	f.initDevOpsProject = nsName
+	f.initPipeline = []*devops.Pipeline{pipeline}
+	f.expectPipeline = []*devops.Pipeline{pipeline}
+
+	f.run(getKey(pipeline, t))
+}
+
+func TestAddPipelineFinalizers(t *testing.T) {
+	f := newFixture(t)
+	nsName := "test-123"
+	pipelineName := "test"
+	projectName := "test_project"
+
+	ns := newNamespace(nsName, projectName)
+	pipeline := newPipeline(nsName, pipelineName, devops.PipelineSpec{}, false, false)
+
+	expectPipeline := newPipeline(nsName, pipelineName, devops.PipelineSpec{}, true, true)
+
+	f.pipelineLister = append(f.pipelineLister, pipeline)
+	f.namespaceLister = append(f.namespaceLister, ns)
+	f.objects = append(f.objects, pipeline)
+	f.initDevOpsProject = nsName
+	f.initPipeline = []*devops.Pipeline{pipeline}
+	f.expectPipeline = []*devops.Pipeline{pipeline}
+	f.expectUpdatePipelineAction(expectPipeline)
+	f.run(getKey(pipeline, t))
+}
+
+func TestCreatePipeline(t *testing.T) {
+	f := newFixture(t)
+	nsName := "test-123"
+	pipelineName := "test"
+	projectName := "test_project"
+
+	ns := newNamespace(nsName, projectName)
+	pipeline := newPipeline(nsName, pipelineName, devops.PipelineSpec{}, false, false)
+	expectPipeline := newPipeline(nsName, pipelineName, devops.PipelineSpec{}, true, true)
+	f.pipelineLister = append(f.pipelineLister, pipeline)
+	f.namespaceLister = append(f.namespaceLister, ns)
+	f.objects = append(f.objects, pipeline)
+	f.initDevOpsProject = nsName
+	f.expectPipeline = []*devops.Pipeline{expectPipeline}
+	f.run(getKey(pipeline, t))
+}
+
+func TestDeletePipeline(t *testing.T) {
+	f := newFixture(t)
+	nsName := "test-123"
+	pipelineName := "test"
+	projectName := "test_project"
+
+	ns := newNamespace(nsName, projectName)
+	pipeline := newDeletingPipeline(nsName, pipelineName)
+
+	expectPipeline := pipeline.DeepCopy()
+	expectPipeline.Finalizers = []string{}
+	f.pipelineLister = append(f.pipelineLister, pipeline)
+	f.namespaceLister = append(f.namespaceLister, ns)
+	f.objects = append(f.objects, pipeline)
+	f.initDevOpsProject = nsName
+	f.initPipeline = []*devops.Pipeline{pipeline}
+	f.expectPipeline = []*devops.Pipeline{}
+	f.expectUpdatePipelineAction(expectPipeline)
+	f.run(getKey(pipeline, t))
+}
+
+func TestDeleteNotExistPipeline(t *testing.T) {
+	f := newFixture(t)
+	nsName := "test-123"
+	pipelineName := "test"
+	projectName := "test_project"
+
+	ns := newNamespace(nsName, projectName)
+	pipeline := newDeletingPipeline(nsName, pipelineName)
+
+	expectPipeline := pipeline.DeepCopy()
+	expectPipeline.Finalizers = []string{}
+	f.pipelineLister = append(f.pipelineLister, pipeline)
+	f.namespaceLister = append(f.namespaceLister, ns)
+	f.objects = append(f.objects, pipeline)
+	f.initDevOpsProject = nsName
+	f.initPipeline = []*devops.Pipeline{}
+	f.expectPipeline = []*devops.Pipeline{}
+	f.expectUpdatePipelineAction(expectPipeline)
+	f.run(getKey(pipeline, t))
+}
+
+func TestUpdatePipelineConfig(t *testing.T) {
+	f := newFixture(t)
+	nsName := "test-123"
+	pipelineName := "test"
+	projectName := "test_project"
+
+	ns := newNamespace(nsName, projectName)
+	initPipeline := newPipeline(nsName, pipelineName, devops.PipelineSpec{}, true, false)
+	modifiedPipeline := newPipeline(nsName, pipelineName, devops.PipelineSpec{Type: "aa"}, true, false)
+	expectPipeline := newPipeline(nsName, pipelineName, devops.PipelineSpec{Type: "aa"}, true, true)
+	f.pipelineLister = append(f.pipelineLister, modifiedPipeline)
+	f.namespaceLister = append(f.namespaceLister, ns)
+	f.objects = append(f.objects, modifiedPipeline)
+	f.initDevOpsProject = nsName
+	f.initPipeline = []*devops.Pipeline{initPipeline}
+	f.expectPipeline = []*devops.Pipeline{expectPipeline}
+	f.run(getKey(modifiedPipeline, t))
+}
diff --git a/controllers/s2ibinary/s2ibinary_controller_test.go b/controllers/s2ibinary/s2ibinary_controller_test.go
index 188a30d..328720c 100644
--- a/controllers/s2ibinary/s2ibinary_controller_test.go
+++ b/controllers/s2ibinary/s2ibinary_controller_test.go
@@ -16,240 +16,239 @@ limitations under the License.
 
 package s2ibinary
 
-//
-//import (
-//	"reflect"
-//	"testing"
-//	"time"
-//
-//	fakes3 "kubesphere.io/devops/pkg/client/s3/fake"
-//
-//	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-//	"k8s.io/apimachinery/pkg/runtime"
-//	"k8s.io/apimachinery/pkg/runtime/schema"
-//	"k8s.io/apimachinery/pkg/util/diff"
-//	k8sfake "k8s.io/client-go/kubernetes/fake"
-//	core "k8s.io/client-go/testing"
-//	"k8s.io/client-go/tools/cache"
-//	"k8s.io/client-go/tools/record"
-//
-//	s2i "kubesphere.io/devops/pkg/api/devops/v1alpha1"
-//
-//	"kubesphere.io/devops/pkg/client/clientset/versioned/fake"
-//	informers "kubesphere.io/devops/pkg/client/informers/externalversions"
-//)
-//
-//var (
-//	alwaysReady        = func() bool { return true }
-//	noResyncPeriodFunc = func() time.Duration { return 0 }
-//)
-//
-//type fixture struct {
-//	t *testing.T
-//
-//	client     *fake.Clientset
-//	kubeclient *k8sfake.Clientset
-//	// Objects to put in the store.
-//	s2ibinaryLister []*s2i.S2iBinary
-//	actions         []core.Action
-//	// Objects from here preloaded into NewSimpleFake.
-//	objects []runtime.Object
-//	// Objects from here preloaded into s3
-//	initS3Objects   []*fakes3.Object
-//	expectS3Objects []*fakes3.Object
-//}
-//
-//func newFixture(t *testing.T) *fixture {
-//	f := &fixture{}
-//	f.t = t
-//	f.objects = []runtime.Object{}
-//	return f
-//}
-//
-//func newS2iBinary(name string, spec s2i.S2iBinarySpec) *s2i.S2iBinary {
-//	return &s2i.S2iBinary{
-//		TypeMeta: metav1.TypeMeta{APIVersion: s2i.SchemeGroupVersion.String()},
-//		ObjectMeta: metav1.ObjectMeta{
-//			Name:      name,
-//			Namespace: metav1.NamespaceDefault,
-//		},
-//		Spec: spec,
-//	}
-//}
-//func newDeletingS2iBinary(name string) *s2i.S2iBinary {
-//	deleteTime := metav1.Now()
-//	return &s2i.S2iBinary{
-//		TypeMeta: metav1.TypeMeta{APIVersion: s2i.SchemeGroupVersion.String()},
-//		ObjectMeta: metav1.ObjectMeta{
-//			Name:              name,
-//			Namespace:         metav1.NamespaceDefault,
-//			Finalizers:        []string{s2i.S2iBinaryFinalizerName},
-//			DeletionTimestamp: &deleteTime,
-//		},
-//	}
-//}
-//
-//func (f *fixture) newController() (*Controller, informers.SharedInformerFactory, *fakes3.FakeS3) {
-//	f.client = fake.NewSimpleClientset(f.objects...)
-//	f.kubeclient = k8sfake.NewSimpleClientset()
-//
-//	i := informers.NewSharedInformerFactory(f.client, noResyncPeriodFunc())
-//	s3I := fakes3.NewFakeS3(f.expectS3Objects...)
-//
-//	c := NewController(f.kubeclient, f.client, i.Devops().V1alpha1().S2iBinaries(), s3I)
-//
-//	c.s2iBinarySynced = alwaysReady
-//	c.eventRecorder = &record.FakeRecorder{}
-//
-//	for _, f := range f.s2ibinaryLister {
-//		i.Devops().V1alpha1().S2iBinaries().Informer().GetIndexer().Add(f)
-//	}
-//
-//	return c, i, s3I
-//}
-//
-//func (f *fixture) run(fooName string) {
-//	f.runController(fooName, true, false)
-//}
-//
-//func (f *fixture) runExpectError(fooName string) {
-//	f.runController(fooName, true, true)
-//}
-//
-//func (f *fixture) runController(s2iBinaryName string, startInformers bool, expectError bool) {
-//	c, i, s3I := f.newController()
-//	if startInformers {
-//		stopCh := make(chan struct{})
-//		defer close(stopCh)
-//		i.Start(stopCh)
-//	}
-//
-//	err := c.syncHandler(s2iBinaryName)
-//	if !expectError && err != nil {
-//		f.t.Errorf("error syncing foo: %v", err)
-//	} else if expectError && err == nil {
-//		f.t.Error("expected error syncing foo, got nil")
-//	}
-//
-//	actions := filterInformerActions(f.client.Actions())
-//	for i, action := range actions {
-//		if len(f.actions) < i+1 {
-//			f.t.Errorf("%d unexpected actions: %+v", len(actions)-len(f.actions), actions[i:])
-//			break
-//		}
-//
-//		expectedAction := f.actions[i]
-//		checkAction(expectedAction, action, f.t)
-//	}
-//
-//	if len(f.actions) > len(actions) {
-//		f.t.Errorf("%d additional expected actions:%+v", len(f.actions)-len(actions), f.actions[len(actions):])
-//	}
-//	if len(s3I.Storage) != len(f.expectS3Objects) {
-//		f.t.Errorf(" unexpected objects: %v", s3I.Storage)
-//	}
-//}
-//
-//// checkAction verifies that expected and actual actions are equal and both have
-//// same attached resources
-//func checkAction(expected, actual core.Action, t *testing.T) {
-//	if !(expected.Matches(actual.GetVerb(), actual.GetResource().Resource) && actual.GetSubresource() == expected.GetSubresource()) {
-//		t.Errorf("Expected\n\t%#v\ngot\n\t%#v", expected, actual)
-//		return
-//	}
-//
-//	if reflect.TypeOf(actual) != reflect.TypeOf(expected) {
-//		t.Errorf("Action has wrong type. Expected: %t. Got: %t", expected, actual)
-//		return
-//	}
-//
-//	switch a := actual.(type) {
-//	case core.CreateActionImpl:
-//		e, _ := expected.(core.CreateActionImpl)
-//		expObject := e.GetObject()
-//		object := a.GetObject()
-//
-//		if !reflect.DeepEqual(expObject, object) {
-//			t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
-//				a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expObject, object))
-//		}
-//	case core.UpdateActionImpl:
-//		e, _ := expected.(core.UpdateActionImpl)
-//		expObject := e.GetObject()
-//		object := a.GetObject()
-//
-//		if !reflect.DeepEqual(expObject, object) {
-//			t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
-//				a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expObject, object))
-//		}
-//	case core.PatchActionImpl:
-//		e, _ := expected.(core.PatchActionImpl)
-//		expPatch := e.GetPatch()
-//		patch := a.GetPatch()
-//
-//		if !reflect.DeepEqual(expPatch, patch) {
-//			t.Errorf("Action %s %s has wrong patch\nDiff:\n %s",
-//				a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expPatch, patch))
-//		}
-//	default:
-//		t.Errorf("Uncaptured Action %s %s, you should explicitly add a case to capture it",
-//			actual.GetVerb(), actual.GetResource().Resource)
-//	}
-//}
-//
-//// filterInformerActions filters list and watch actions for testing resources.
-//// Since list and watch don't change resource state we can filter it to lower
-//// nose level in our tests.
-//func filterInformerActions(actions []core.Action) []core.Action {
-//	ret := []core.Action{}
-//	for _, action := range actions {
-//		if len(action.GetNamespace()) == 0 &&
-//			(action.Matches("list", s2i.ResourcePluralS2iBinary) ||
-//				action.Matches("watch", s2i.ResourcePluralS2iBinary)) {
-//			continue
-//		}
-//		ret = append(ret, action)
-//	}
-//
-//	return ret
-//}
-//
-//func (f *fixture) expectUpdateS2iBinaryAction(s2ibinary *s2i.S2iBinary) {
-//	action := core.NewUpdateAction(schema.GroupVersionResource{Resource: s2i.ResourcePluralS2iBinary}, s2ibinary.Namespace, s2ibinary)
-//	f.actions = append(f.actions, action)
-//}
-//
-//func getKey(s2ibinary *s2i.S2iBinary, t *testing.T) string {
-//	key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(s2ibinary)
-//	if err != nil {
-//		t.Errorf("Unexpected error getting key for s2ibinary %v: %v", s2ibinary.Name, err)
-//		return ""
-//	}
-//	return key
-//}
-//
-//func TestDoNothing(t *testing.T) {
-//	f := newFixture(t)
-//	s2iBinary := newS2iBinary("test", s2i.S2iBinarySpec{})
-//
-//	f.s2ibinaryLister = append(f.s2ibinaryLister, s2iBinary)
-//	f.objects = append(f.objects, s2iBinary)
-//
-//	f.expectUpdateS2iBinaryAction(s2iBinary)
-//	f.run(getKey(s2iBinary, t))
-//}
-//
-//func TestDeleteS3Object(t *testing.T) {
-//	f := newFixture(t)
-//	s2iBinary := newDeletingS2iBinary("test")
-//
-//	f.s2ibinaryLister = append(f.s2ibinaryLister, s2iBinary)
-//	f.objects = append(f.objects, s2iBinary)
-//	f.initS3Objects = []*fakes3.Object{{
-//		Key: "default-test",
-//	}}
-//	f.expectS3Objects = []*fakes3.Object{}
-//	f.expectUpdateS2iBinaryAction(s2iBinary)
-//	f.run(getKey(s2iBinary, t))
-//
-//}
+import (
+	"reflect"
+	"testing"
+	"time"
+
+	fakes3 "kubesphere.io/devops/pkg/client/s3/fake"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/util/diff"
+	k8sfake "k8s.io/client-go/kubernetes/fake"
+	core "k8s.io/client-go/testing"
+	"k8s.io/client-go/tools/cache"
+	"k8s.io/client-go/tools/record"
+
+	s2i "kubesphere.io/devops/pkg/api/devops/v1alpha1"
+
+	"kubesphere.io/devops/pkg/client/clientset/versioned/fake"
+	informers "kubesphere.io/devops/pkg/client/informers/externalversions"
+)
+
+var (
+	alwaysReady        = func() bool { return true }
+	noResyncPeriodFunc = func() time.Duration { return 0 }
+)
+
+type fixture struct {
+	t *testing.T
+
+	client     *fake.Clientset
+	kubeclient *k8sfake.Clientset
+	// Objects to put in the store.
+	s2ibinaryLister []*s2i.S2iBinary
+	actions         []core.Action
+	// Objects from here preloaded into NewSimpleFake.
+	objects []runtime.Object
+	// Objects from here preloaded into s3
+	initS3Objects   []*fakes3.Object
+	expectS3Objects []*fakes3.Object
+}
+
+func newFixture(t *testing.T) *fixture {
+	f := &fixture{}
+	f.t = t
+	f.objects = []runtime.Object{}
+	return f
+}
+
+func newS2iBinary(name string, spec s2i.S2iBinarySpec) *s2i.S2iBinary {
+	return &s2i.S2iBinary{
+		TypeMeta: metav1.TypeMeta{APIVersion: s2i.SchemeGroupVersion.String()},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      name,
+			Namespace: metav1.NamespaceDefault,
+		},
+		Spec: spec,
+	}
+}
+func newDeletingS2iBinary(name string) *s2i.S2iBinary {
+	deleteTime := metav1.Now()
+	return &s2i.S2iBinary{
+		TypeMeta: metav1.TypeMeta{APIVersion: s2i.SchemeGroupVersion.String()},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:              name,
+			Namespace:         metav1.NamespaceDefault,
+			Finalizers:        []string{s2i.S2iBinaryFinalizerName},
+			DeletionTimestamp: &deleteTime,
+		},
+	}
+}
+
+func (f *fixture) newController() (*Controller, informers.SharedInformerFactory, *fakes3.FakeS3) {
+	f.client = fake.NewSimpleClientset(f.objects...)
+	f.kubeclient = k8sfake.NewSimpleClientset()
+
+	i := informers.NewSharedInformerFactory(f.client, noResyncPeriodFunc())
+	s3I := fakes3.NewFakeS3(f.expectS3Objects...)
+
+	c := NewController(f.kubeclient, f.client, i.Devops().V1alpha1().S2iBinaries(), s3I)
+
+	c.s2iBinarySynced = alwaysReady
+	c.eventRecorder = &record.FakeRecorder{}
+
+	for _, f := range f.s2ibinaryLister {
+		i.Devops().V1alpha1().S2iBinaries().Informer().GetIndexer().Add(f)
+	}
+
+	return c, i, s3I
+}
+
+func (f *fixture) run(fooName string) {
+	f.runController(fooName, true, false)
+}
+
+func (f *fixture) runExpectError(fooName string) {
+	f.runController(fooName, true, true)
+}
+
+func (f *fixture) runController(s2iBinaryName string, startInformers bool, expectError bool) {
+	c, i, s3I := f.newController()
+	if startInformers {
+		stopCh := make(chan struct{})
+		defer close(stopCh)
+		i.Start(stopCh)
+	}
+
+	err := c.syncHandler(s2iBinaryName)
+	if !expectError && err != nil {
+		f.t.Errorf("error syncing foo: %v", err)
+	} else if expectError && err == nil {
+		f.t.Error("expected error syncing foo, got nil")
+	}
+
+	actions := filterInformerActions(f.client.Actions())
+	for i, action := range actions {
+		if len(f.actions) < i+1 {
+			f.t.Errorf("%d unexpected actions: %+v", len(actions)-len(f.actions), actions[i:])
+			break
+		}
+
+		expectedAction := f.actions[i]
+		checkAction(expectedAction, action, f.t)
+	}
+
+	if len(f.actions) > len(actions) {
+		f.t.Errorf("%d additional expected actions:%+v", len(f.actions)-len(actions), f.actions[len(actions):])
+	}
+	if len(s3I.Storage) != len(f.expectS3Objects) {
+		f.t.Errorf(" unexpected objects: %v", s3I.Storage)
+	}
+}
+
+// checkAction verifies that expected and actual actions are equal and both have
+// same attached resources
+func checkAction(expected, actual core.Action, t *testing.T) {
+	if !(expected.Matches(actual.GetVerb(), actual.GetResource().Resource) && actual.GetSubresource() == expected.GetSubresource()) {
+		t.Errorf("Expected\n\t%#v\ngot\n\t%#v", expected, actual)
+		return
+	}
+
+	if reflect.TypeOf(actual) != reflect.TypeOf(expected) {
+		t.Errorf("Action has wrong type. Expected: %t. Got: %t", expected, actual)
+		return
+	}
+
+	switch a := actual.(type) {
+	case core.CreateActionImpl:
+		e, _ := expected.(core.CreateActionImpl)
+		expObject := e.GetObject()
+		object := a.GetObject()
+
+		if !reflect.DeepEqual(expObject, object) {
+			t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
+				a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expObject, object))
+		}
+	case core.UpdateActionImpl:
+		e, _ := expected.(core.UpdateActionImpl)
+		expObject := e.GetObject()
+		object := a.GetObject()
+
+		if !reflect.DeepEqual(expObject, object) {
+			t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
+				a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expObject, object))
+		}
+	case core.PatchActionImpl:
+		e, _ := expected.(core.PatchActionImpl)
+		expPatch := e.GetPatch()
+		patch := a.GetPatch()
+
+		if !reflect.DeepEqual(expPatch, patch) {
+			t.Errorf("Action %s %s has wrong patch\nDiff:\n %s",
+				a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expPatch, patch))
+		}
+	default:
+		t.Errorf("Uncaptured Action %s %s, you should explicitly add a case to capture it",
+			actual.GetVerb(), actual.GetResource().Resource)
+	}
+}
+
+// filterInformerActions filters list and watch actions for testing resources.
+// Since list and watch don't change resource state we can filter it to lower
+// nose level in our tests.
+func filterInformerActions(actions []core.Action) []core.Action {
+	ret := []core.Action{}
+	for _, action := range actions {
+		if len(action.GetNamespace()) == 0 &&
+			(action.Matches("list", s2i.ResourcePluralS2iBinary) ||
+				action.Matches("watch", s2i.ResourcePluralS2iBinary)) {
+			continue
+		}
+		ret = append(ret, action)
+	}
+
+	return ret
+}
+
+func (f *fixture) expectUpdateS2iBinaryAction(s2ibinary *s2i.S2iBinary) {
+	action := core.NewUpdateAction(schema.GroupVersionResource{Resource: s2i.ResourcePluralS2iBinary}, s2ibinary.Namespace, s2ibinary)
+	f.actions = append(f.actions, action)
+}
+
+func getKey(s2ibinary *s2i.S2iBinary, t *testing.T) string {
+	key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(s2ibinary)
+	if err != nil {
+		t.Errorf("Unexpected error getting key for s2ibinary %v: %v", s2ibinary.Name, err)
+		return ""
+	}
+	return key
+}
+
+func TestDoNothing(t *testing.T) {
+	f := newFixture(t)
+	s2iBinary := newS2iBinary("test", s2i.S2iBinarySpec{})
+
+	f.s2ibinaryLister = append(f.s2ibinaryLister, s2iBinary)
+	f.objects = append(f.objects, s2iBinary)
+
+	f.expectUpdateS2iBinaryAction(s2iBinary)
+	f.run(getKey(s2iBinary, t))
+}
+
+func TestDeleteS3Object(t *testing.T) {
+	f := newFixture(t)
+	s2iBinary := newDeletingS2iBinary("test")
+
+	f.s2ibinaryLister = append(f.s2ibinaryLister, s2iBinary)
+	f.objects = append(f.objects, s2iBinary)
+	f.initS3Objects = []*fakes3.Object{{
+		Key: "default-test",
+	}}
+	f.expectS3Objects = []*fakes3.Object{}
+	f.expectUpdateS2iBinaryAction(s2iBinary)
+	f.run(getKey(s2iBinary, t))
+
+}
diff --git a/controllers/s2irun/s2irun_controller_test.go b/controllers/s2irun/s2irun_controller_test.go
index 8bff2ce..30a9cf6 100644
--- a/controllers/s2irun/s2irun_controller_test.go
+++ b/controllers/s2irun/s2irun_controller_test.go
@@ -16,308 +16,304 @@ limitations under the License.
 
 package s2irun
 
-//import (
-//	"reflect"
-//	"testing"
-//	"time"
-//
-//	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-//	"k8s.io/apimachinery/pkg/runtime"
-//	"k8s.io/apimachinery/pkg/runtime/schema"
-//	"k8s.io/apimachinery/pkg/util/diff"
-//	k8sfake "k8s.io/client-go/kubernetes/fake"
-//	core "k8s.io/client-go/testing"
-//	"k8s.io/client-go/tools/cache"
-//	"k8s.io/client-go/tools/record"
-//
-//	s2i "kubesphere.io/devops/pkg/api/devops/v1alpha1"
-//
-//	"kubesphere.io/devops/pkg/client/clientset/versioned/fake"
-//	informers "kubesphere.io/devops/pkg/client/informers/externalversions"
-//)
-//
-//var (
-//	alwaysReady        = func() bool { return true }
-//	noResyncPeriodFunc = func() time.Duration { return 0 }
-//)
-//
-//type fixture struct {
-//	t *testing.T
-//
-//	client     *fake.Clientset
-//	kubeclient *k8sfake.Clientset
-//	// Objects to put in the store.
-//	s2ibinaryLister []*s2i.S2iBinary
-//	s2irunLister    []*s2i.S2iRun
-//	actions         []core.Action
-//	// Objects from here preloaded into NewSimpleFake.
-//	objects []runtime.Object
-//}
-//
-//func newFixture(t *testing.T) *fixture {
-//	f := &fixture{}
-//	f.t = t
-//	f.objects = []runtime.Object{}
-//	return f
-//}
-//
-//func newS2iBinary(name string) *s2i.S2iBinary {
-//	return &s2i.S2iBinary{
-//		TypeMeta: metav1.TypeMeta{APIVersion: s2i.SchemeGroupVersion.String()},
-//		ObjectMeta: metav1.ObjectMeta{
-//			Name:      name,
-//			Namespace: metav1.NamespaceDefault,
-//		},
-//		Spec: s2i.S2iBinarySpec{},
-//	}
-//}
-//
-//func newS2iBinaryWithCreateTime(name string, createTime metav1.Time) *s2i.S2iBinary {
-//	return &s2i.S2iBinary{
-//		TypeMeta: metav1.TypeMeta{APIVersion: s2i.SchemeGroupVersion.String()},
-//		ObjectMeta: metav1.ObjectMeta{
-//			Name:              name,
-//			Namespace:         metav1.NamespaceDefault,
-//			CreationTimestamp: createTime,
-//		},
-//		Spec: s2i.S2iBinarySpec{},
-//	}
-//}
-//
-//func newS2iRun(name string, s2iBinaryName string) *s2i.S2iRun {
-//	return &s2i.S2iRun{
-//		TypeMeta: metav1.TypeMeta{APIVersion: s2i.SchemeGroupVersion.String()},
-//		ObjectMeta: metav1.ObjectMeta{
-//			Name:      name,
-//			Namespace: metav1.NamespaceDefault,
-//			Labels: map[string]string{
-//				s2i.S2iBinaryLabelKey: s2iBinaryName,
-//			},
-//		},
-//	}
-//}
-//
-//func newDeletetingS2iRun(name string, s2iBinaryName string) *s2i.S2iRun {
-//	now := metav1.Now()
-//	return &s2i.S2iRun{
-//		TypeMeta: metav1.TypeMeta{APIVersion: s2i.SchemeGroupVersion.String()},
-//		ObjectMeta: metav1.ObjectMeta{
-//			Name:      name,
-//			Namespace: metav1.NamespaceDefault,
-//			Labels: map[string]string{
-//				s2i.S2iBinaryLabelKey: s2iBinaryName,
-//			},
-//			Finalizers:        []string{s2i.S2iBinaryFinalizerName},
-//			DeletionTimestamp: &now,
-//		},
-//	}
-//}
-//
-//func (f *fixture) newController() (*Controller, informers.SharedInformerFactory) {
-//	f.client = fake.NewSimpleClientset(f.objects...)
-//	f.kubeclient = k8sfake.NewSimpleClientset()
-//
-//	i := informers.NewSharedInformerFactory(f.client, noResyncPeriodFunc())
-//
-//	c := NewS2iRunController(f.kubeclient, f.client,
-//		i.Devops().V1alpha1().S2iBinaries(), i.Devops().V1alpha1().S2iRuns())
-//
-//	c.s2iBinarySynced = alwaysReady
-//	c.eventRecorder = &record.FakeRecorder{}
-//
-//	for _, f := range f.s2ibinaryLister {
-//		i.Devops().V1alpha1().S2iBinaries().Informer().GetIndexer().Add(f)
-//	}
-//	for _, f := range f.s2irunLister {
-//		i.Devops().V1alpha1().S2iRuns().Informer().GetIndexer().Add(f)
-//	}
-//
-//	return c, i
-//}
-//
-//func (f *fixture) run(fooName string) {
-//	f.runController(fooName, true, false)
-//}
-//
-//func (f *fixture) runExpectError(fooName string) {
-//	f.runController(fooName, true, true)
-//}
-//
-//func (f *fixture) runController(s2iRunName string, startInformers bool, expectError bool) {
-//	c, i := f.newController()
-//	if startInformers {
-//		stopCh := make(chan struct{})
-//		defer close(stopCh)
-//		i.Start(stopCh)
-//	}
-//
-//	err := c.syncHandler(s2iRunName)
-//	if !expectError && err != nil {
-//		f.t.Errorf("error syncing foo: %v", err)
-//	} else if expectError && err == nil {
-//		f.t.Error("expected error syncing foo, got nil")
-//	}
-//
-//	actions := filterInformerActions(f.client.Actions())
-//	for i, action := range actions {
-//		if len(f.actions) < i+1 {
-//			f.t.Errorf("%d unexpected actions: %+v", len(actions)-len(f.actions), actions[i:])
-//			break
-//		}
-//
-//		expectedAction := f.actions[i]
-//		checkAction(expectedAction, action, f.t)
-//	}
-//
-//	if len(f.actions) > len(actions) {
-//		f.t.Errorf("%d additional expected actions:%+v", len(f.actions)-len(actions), f.actions[len(actions):])
-//	}
-//}
-//
-//// checkAction verifies that expected and actual actions are equal and both have
-//// same attached resources
-//func checkAction(expected, actual core.Action, t *testing.T) {
-//	if !(expected.Matches(actual.GetVerb(), actual.GetResource().Resource) && actual.GetSubresource() == expected.GetSubresource()) {
-//		t.Errorf("Expected\n\t%#v\ngot\n\t%#v", expected, actual)
-//		return
-//	}
-//
-//	if reflect.TypeOf(actual) != reflect.TypeOf(expected) {
-//		t.Errorf("Action has wrong type. Expected: %t. Got: %t", expected, actual)
-//		return
-//	}
-//
-//	switch a := actual.(type) {
-//	case core.CreateActionImpl:
-//		e, _ := expected.(core.CreateActionImpl)
-//		expObject := e.GetObject()
-//		object := a.GetObject()
-//
-//		if !reflect.DeepEqual(expObject, object) {
-//			t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
-//				a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expObject, object))
-//		}
-//	case core.UpdateActionImpl:
-//		e, _ := expected.(core.UpdateActionImpl)
-//		expObject := e.GetObject()
-//		object := a.GetObject()
-//
-//		if !reflect.DeepEqual(expObject, object) {
-//			t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
-//				a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expObject, object))
-//		}
-//	case core.PatchActionImpl:
-//		e, _ := expected.(core.PatchActionImpl)
-//		expPatch := e.GetPatch()
-//		patch := a.GetPatch()
-//
-//		if !reflect.DeepEqual(expPatch, patch) {
-//			t.Errorf("Action %s %s has wrong patch\nDiff:\n %s",
-//				a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expPatch, patch))
-//		}
-//	case core.DeleteActionImpl:
-//		e, _ := expected.(core.DeleteActionImpl)
-//
-//		expName := e.GetName()
-//		objectName := a.GetName()
-//
-//		if !reflect.DeepEqual(expName, objectName) {
-//			t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
-//				a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expName, objectName))
-//		}
-//		expNamespace := e.GetNamespace()
-//		objectNamespace := a.GetNamespace()
-//		if !reflect.DeepEqual(expNamespace, objectNamespace) {
-//			t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
-//				a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expNamespace, objectNamespace))
-//		}
-//	default:
-//		t.Errorf("Uncaptured Action %s %s, you should explicitly add a case to capture it",
-//			actual.GetVerb(), actual.GetResource().Resource)
-//	}
-//}
-//
-//// filterInformerActions filters list and watch actions for testing resources.
-//// Since list and watch don't change resource state we can filter it to lower
-//// nose level in our tests.
-//func filterInformerActions(actions []core.Action) []core.Action {
-//	ret := []core.Action{}
-//	for _, action := range actions {
-//		if len(action.GetNamespace()) == 0 &&
-//			(action.Matches("list", s2i.ResourcePluralS2iRun) ||
-//				action.Matches("watch", s2i.ResourcePluralS2iRun) ||
-//				action.Matches("list", s2i.ResourcePluralS2iBinary) ||
-//				action.Matches("watch", s2i.ResourcePluralS2iBinary)) {
-//			continue
-//		}
-//		ret = append(ret, action)
-//	}
-//
-//	return ret
-//}
-//
-//func (f *fixture) expectUpdateS2iRunAction(s2iRun *s2i.S2iRun) {
-//	action := core.NewUpdateAction(schema.GroupVersionResource{Resource: s2i.ResourcePluralS2iRun}, s2iRun.Namespace, s2iRun)
-//	f.actions = append(f.actions, action)
-//}
-//
-//func (f *fixture) expectDeleteS2iBinaryAction(s2iBinary *s2i.S2iBinary) {
-//	action := core.NewDeleteAction(schema.GroupVersionResource{Resource: s2i.ResourcePluralS2iBinary}, s2iBinary.Namespace, s2iBinary.Name)
-//	f.actions = append(f.actions, action)
-//}
-//
-//func getKey(s2i *s2i.S2iRun, t *testing.T) string {
-//	key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(s2i)
-//	if err != nil {
-//		t.Errorf("Unexpected error getting key for s2i %v: %v", s2i.Name, err)
-//		return ""
-//	}
-//	return key
-//}
-//
-//func TestDoNothing(t *testing.T) {
-//	f := newFixture(t)
-//	s2iBinary := newS2iBinary("test")
-//	s2iRun := newS2iRun("test", s2iBinary.Name)
-//
-//	f.s2ibinaryLister = append(f.s2ibinaryLister, s2iBinary)
-//	f.s2irunLister = append(f.s2irunLister, s2iRun)
-//	f.objects = append(f.objects, s2iBinary)
-//	f.objects = append(f.objects, s2iRun)
-//
-//	f.expectUpdateS2iRunAction(s2iRun)
-//	f.run(getKey(s2iRun, t))
-//}
-//
-//func TestDeleteS2iBinary(t *testing.T) {
-//	f := newFixture(t)
-//	s2iBinary := newS2iBinary("test")
-//	s2iRun := newDeletetingS2iRun("test", s2iBinary.Name)
-//
-//	f.s2ibinaryLister = append(f.s2ibinaryLister, s2iBinary)
-//	f.s2irunLister = append(f.s2irunLister, s2iRun)
-//	f.objects = append(f.objects, s2iBinary)
-//	f.objects = append(f.objects, s2iRun)
-//
-//	f.expectDeleteS2iBinaryAction(s2iBinary)
-//	f.expectUpdateS2iRunAction(s2iRun)
-//	f.run(getKey(s2iRun, t))
-//}
-//
-//func TestDeleteOtherS2iBinary(t *testing.T) {
-//	f := newFixture(t)
-//	s2iBinary := newS2iBinary("test")
-//	s2iRun := newDeletetingS2iRun("test", s2iBinary.Name)
-//	otherS2iBinary := newS2iBinaryWithCreateTime("test2", metav1.NewTime(time.Date(2000, 1, 1, 12, 0, 0, 0, time.UTC)))
-//
-//	f.s2ibinaryLister = append(f.s2ibinaryLister, s2iBinary)
-//	f.s2ibinaryLister = append(f.s2ibinaryLister, otherS2iBinary)
-//	f.s2irunLister = append(f.s2irunLister, s2iRun)
-//	f.objects = append(f.objects, s2iBinary)
-//	f.objects = append(f.objects, s2iRun)
-//	f.objects = append(f.objects, otherS2iBinary)
-//	f.expectDeleteS2iBinaryAction(s2iBinary)
-//	f.expectDeleteS2iBinaryAction(otherS2iBinary)
-//	f.expectUpdateS2iRunAction(s2iRun)
-//	f.run(getKey(s2iRun, t))
-//}
+import (
+	"reflect"
+	"testing"
+	"time"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/util/diff"
+	k8sfake "k8s.io/client-go/kubernetes/fake"
+	core "k8s.io/client-go/testing"
+	"k8s.io/client-go/tools/cache"
+	"k8s.io/client-go/tools/record"
+
+	s2i "kubesphere.io/devops/pkg/api/devops/v1alpha1"
+
+	"kubesphere.io/devops/pkg/client/clientset/versioned/fake"
+	informers "kubesphere.io/devops/pkg/client/informers/externalversions"
+)
+
+var (
+	alwaysReady        = func() bool { return true }
+	noResyncPeriodFunc = func() time.Duration { return 0 }
+)
+
+type fixture struct {
+	t *testing.T
+
+	client     *fake.Clientset
+	kubeclient *k8sfake.Clientset
+	// Objects to put in the store.
+	s2ibinaryLister []*s2i.S2iBinary
+	s2irunLister    []*s2i.S2iRun
+	actions         []core.Action
+	// Objects from here preloaded into NewSimpleFake.
+	objects []runtime.Object
+}
+
+func newFixture(t *testing.T) *fixture {
+	f := &fixture{}
+	f.t = t
+	f.objects = []runtime.Object{}
+	return f
+}
+
+func newS2iBinary(name string) *s2i.S2iBinary {
+	return &s2i.S2iBinary{
+		TypeMeta: metav1.TypeMeta{APIVersion: s2i.SchemeGroupVersion.String()},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      name,
+			Namespace: metav1.NamespaceDefault,
+		},
+		Spec: s2i.S2iBinarySpec{},
+	}
+}
+
+func newS2iBinaryWithCreateTime(name string, createTime metav1.Time) *s2i.S2iBinary {
+	return &s2i.S2iBinary{
+		TypeMeta: metav1.TypeMeta{APIVersion: s2i.SchemeGroupVersion.String()},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:              name,
+			Namespace:         metav1.NamespaceDefault,
+			CreationTimestamp: createTime,
+		},
+		Spec: s2i.S2iBinarySpec{},
+	}
+}
+
+func newS2iRun(name string, s2iBinaryName string) *s2i.S2iRun {
+	return &s2i.S2iRun{
+		TypeMeta: metav1.TypeMeta{APIVersion: s2i.SchemeGroupVersion.String()},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      name,
+			Namespace: metav1.NamespaceDefault,
+			Labels: map[string]string{
+				s2i.S2iBinaryLabelKey: s2iBinaryName,
+			},
+		},
+	}
+}
+
+func newDeletetingS2iRun(name string, s2iBinaryName string) *s2i.S2iRun {
+	now := metav1.Now()
+	return &s2i.S2iRun{
+		TypeMeta: metav1.TypeMeta{APIVersion: s2i.SchemeGroupVersion.String()},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      name,
+			Namespace: metav1.NamespaceDefault,
+			Labels: map[string]string{
+				s2i.S2iBinaryLabelKey: s2iBinaryName,
+			},
+			Finalizers:        []string{s2i.S2iBinaryFinalizerName},
+			DeletionTimestamp: &now,
+		},
+	}
+}
+
+func (f *fixture) newController() (*Controller, informers.SharedInformerFactory) {
+	f.client = fake.NewSimpleClientset(f.objects...)
+	f.kubeclient = k8sfake.NewSimpleClientset()
+
+	i := informers.NewSharedInformerFactory(f.client, noResyncPeriodFunc())
+
+	c := NewS2iRunController(f.kubeclient, f.client,
+		i.Devops().V1alpha1().S2iBinaries(), i.Devops().V1alpha1().S2iRuns())
+
+	c.s2iBinarySynced = alwaysReady
+	c.eventRecorder = &record.FakeRecorder{}
+
+	for _, f := range f.s2ibinaryLister {
+		_ = i.Devops().V1alpha1().S2iBinaries().Informer().GetIndexer().Add(f)
+	}
+	for _, f := range f.s2irunLister {
+		_ = i.Devops().V1alpha1().S2iRuns().Informer().GetIndexer().Add(f)
+	}
+
+	return c, i
+}
+
+func (f *fixture) run(fooName string) {
+	f.runController(fooName, true, false)
+}
+
+func (f *fixture) runController(s2iRunName string, startInformers bool, expectError bool) {
+	c, i := f.newController()
+	if startInformers {
+		stopCh := make(chan struct{})
+		defer close(stopCh)
+		i.Start(stopCh)
+	}
+
+	err := c.syncHandler(s2iRunName)
+	if !expectError && err != nil {
+		f.t.Errorf("error syncing foo: %v", err)
+	} else if expectError && err == nil {
+		f.t.Error("expected error syncing foo, got nil")
+	}
+
+	actions := filterInformerActions(f.client.Actions())
+	for i, action := range actions {
+		if len(f.actions) < i+1 {
+			f.t.Errorf("%d unexpected actions: %+v", len(actions)-len(f.actions), actions[i:])
+			break
+		}
+
+		expectedAction := f.actions[i]
+		checkAction(expectedAction, action, f.t)
+	}
+
+	if len(f.actions) > len(actions) {
+		f.t.Errorf("%d additional expected actions:%+v", len(f.actions)-len(actions), f.actions[len(actions):])
+	}
+}
+
+// checkAction verifies that expected and actual actions are equal and both have
+// same attached resources
+func checkAction(expected, actual core.Action, t *testing.T) {
+	if !(expected.Matches(actual.GetVerb(), actual.GetResource().Resource) && actual.GetSubresource() == expected.GetSubresource()) {
+		t.Errorf("Expected\n\t%#v\ngot\n\t%#v", expected, actual)
+		return
+	}
+
+	if reflect.TypeOf(actual) != reflect.TypeOf(expected) {
+		t.Errorf("Action has wrong type. Expected: %t. Got: %t", expected, actual)
+		return
+	}
+
+	switch a := actual.(type) {
+	case core.CreateActionImpl:
+		e, _ := expected.(core.CreateActionImpl)
+		expObject := e.GetObject()
+		object := a.GetObject()
+
+		if !reflect.DeepEqual(expObject, object) {
+			t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
+				a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expObject, object))
+		}
+	case core.UpdateActionImpl:
+		e, _ := expected.(core.UpdateActionImpl)
+		expObject := e.GetObject()
+		object := a.GetObject()
+
+		if !reflect.DeepEqual(expObject, object) {
+			t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
+				a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expObject, object))
+		}
+	case core.PatchActionImpl:
+		e, _ := expected.(core.PatchActionImpl)
+		expPatch := e.GetPatch()
+		patch := a.GetPatch()
+
+		if !reflect.DeepEqual(expPatch, patch) {
+			t.Errorf("Action %s %s has wrong patch\nDiff:\n %s",
+				a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expPatch, patch))
+		}
+	case core.DeleteActionImpl:
+		e, _ := expected.(core.DeleteActionImpl)
+
+		expName := e.GetName()
+		objectName := a.GetName()
+
+		if !reflect.DeepEqual(expName, objectName) {
+			t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
+				a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expName, objectName))
+		}
+		expNamespace := e.GetNamespace()
+		objectNamespace := a.GetNamespace()
+		if !reflect.DeepEqual(expNamespace, objectNamespace) {
+			t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
+				a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expNamespace, objectNamespace))
+		}
+	default:
+		t.Errorf("Uncaptured Action %s %s, you should explicitly add a case to capture it",
+			actual.GetVerb(), actual.GetResource().Resource)
+	}
+}
+
+// filterInformerActions filters list and watch actions for testing resources.
+// Since list and watch don't change resource state we can filter it to lower
+// nose level in our tests.
+func filterInformerActions(actions []core.Action) []core.Action {
+	ret := []core.Action{}
+	for _, action := range actions {
+		if len(action.GetNamespace()) == 0 &&
+			(action.Matches("list", s2i.ResourcePluralS2iRun) ||
+				action.Matches("watch", s2i.ResourcePluralS2iRun) ||
+				action.Matches("list", s2i.ResourcePluralS2iBinary) ||
+				action.Matches("watch", s2i.ResourcePluralS2iBinary)) {
+			continue
+		}
+		ret = append(ret, action)
+	}
+
+	return ret
+}
+
+func (f *fixture) expectUpdateS2iRunAction(s2iRun *s2i.S2iRun) {
+	action := core.NewUpdateAction(schema.GroupVersionResource{Resource: s2i.ResourcePluralS2iRun}, s2iRun.Namespace, s2iRun)
+	f.actions = append(f.actions, action)
+}
+
+func (f *fixture) expectDeleteS2iBinaryAction(s2iBinary *s2i.S2iBinary) {
+	action := core.NewDeleteAction(schema.GroupVersionResource{Resource: s2i.ResourcePluralS2iBinary}, s2iBinary.Namespace, s2iBinary.Name)
+	f.actions = append(f.actions, action)
+}
+
+func getKey(s2i *s2i.S2iRun, t *testing.T) string {
+	key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(s2i)
+	if err != nil {
+		t.Errorf("Unexpected error getting key for s2i %v: %v", s2i.Name, err)
+		return ""
+	}
+	return key
+}
+
+func TestDoNothing(t *testing.T) {
+	f := newFixture(t)
+	s2iBinary := newS2iBinary("test")
+	s2iRun := newS2iRun("test", s2iBinary.Name)
+
+	f.s2ibinaryLister = append(f.s2ibinaryLister, s2iBinary)
+	f.s2irunLister = append(f.s2irunLister, s2iRun)
+	f.objects = append(f.objects, s2iBinary)
+	f.objects = append(f.objects, s2iRun)
+
+	f.expectUpdateS2iRunAction(s2iRun)
+	f.run(getKey(s2iRun, t))
+}
+
+func TestDeleteS2iBinary(t *testing.T) {
+	f := newFixture(t)
+	s2iBinary := newS2iBinary("test")
+	s2iRun := newDeletetingS2iRun("test", s2iBinary.Name)
+
+	f.s2ibinaryLister = append(f.s2ibinaryLister, s2iBinary)
+	f.s2irunLister = append(f.s2irunLister, s2iRun)
+	f.objects = append(f.objects, s2iBinary)
+	f.objects = append(f.objects, s2iRun)
+
+	f.expectDeleteS2iBinaryAction(s2iBinary)
+	f.expectUpdateS2iRunAction(s2iRun)
+	f.run(getKey(s2iRun, t))
+}
+
+func TestDeleteOtherS2iBinary(t *testing.T) {
+	f := newFixture(t)
+	s2iBinary := newS2iBinary("test")
+	s2iRun := newDeletetingS2iRun("test", s2iBinary.Name)
+	otherS2iBinary := newS2iBinaryWithCreateTime("test2", metav1.NewTime(time.Date(2000, 1, 1, 12, 0, 0, 0, time.UTC)))
+
+	f.s2ibinaryLister = append(f.s2ibinaryLister, s2iBinary)
+	f.s2ibinaryLister = append(f.s2ibinaryLister, otherS2iBinary)
+	f.s2irunLister = append(f.s2irunLister, s2iRun)
+	f.objects = append(f.objects, s2iBinary)
+	f.objects = append(f.objects, s2iRun)
+	f.objects = append(f.objects, otherS2iBinary)
+	f.expectDeleteS2iBinaryAction(s2iBinary)
+	f.expectDeleteS2iBinaryAction(otherS2iBinary)
+	f.expectUpdateS2iRunAction(s2iRun)
+	f.run(getKey(s2iRun, t))
+}