Repository: kubesphere/ks-devops
Pull request #495 (2022-03-10 06:17:18)

Provider the APIs for the GitRepository custom resource
<!-- Thanks for sending a pull request! Here are some tips for you:

1. If you want **faster** PR reviews, read how: https://github.com/kubesphere/community/blob/master/developer-guide/development/the-pr-author-guide-to-getting-through-code-review.md
2. In case you want to know how your PR got reviewed, read: https://github.com/kubesphere/community/blob/master/developer-guide/development/code-review-guide.md
3. Here are some coding conventions followed by the KubeSphere community: https://github.com/kubesphere/community/blob/master/developer-guide/development/coding-conventions.md
4. Additional open-source best practice: https://github.com/LinuxSuRen/open-source-best-practice
-->

### What type of PR is this?
<!-- 
Add one of the following kinds:
/kind bug
/kind cleanup
/kind documentation
/kind feature
/kind design
/kind chore

Optionally add one or more of the following kinds if applicable:
/kind api-change
/kind deprecation
/kind failing-test
/kind flake
/kind regression
-->
/kind feature

### What this PR does / why we need it:
As I mentioned in the title, this PR will add the APIs of the custom resource GitRepository.

And I change the version of `GitRepository` and `Addon` to `v1alpha3`. Because it belongs to the core part. Currently, all the core part are using `v1alpha3`.

### Which issue(s) this PR fixes:
<!--
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
Please leave it or change # to be None if there is no corresponding issue that exists
-->
Fixes #

### Special notes for reviewers:
<!--
You can use the following command to let the DevOps SIG members help you to review your PR.
/cc @kubesphere/sig-devops 
And please avoid cc any individual.
-->
Please check the following list before waiting reviewers:

- [ ] Already committed the CRD files to [the Helm Chart](https://github.com/kubesphere-sigs/ks-devops-helm-chart/) if you created some new CRDs
- [ ] Already [added the permission](https://github.com/kubesphere/ks-installer/blob/9e063b085a0e43fdb3d0d9e3e7f4149146f14b9c/roles/ks-core/prepare/files/ks-init/role-templates.yaml) for the new API
- [ ] Already added the RBAC markers for the new controllers

### Does this PR introduce a user-facing change??
<!--
If no, just write "None" in the release-note block below.
If yes, a release note is required:
Enter your extended-release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".

Please keep the note be same as your PR title if you believe it should be in the release notes.
-->
```release-note
None
```


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

This pull-request has been approved by: *<a href="https://github.com/kubesphere/ks-devops/pull/495#" title="Author self-approved">LinuxSuRen</a>*

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

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

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

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

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

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

```diff
@@            Coverage Diff             @@
##           master     #495      +/-   ##
==========================================
+ Coverage   77.18%   77.26%   +0.08%     
==========================================
  Files         133      133              
  Lines       22764    22842      +78     
==========================================
+ Hits        17571    17650      +79     
+ Misses       4997     4996       -1     
  Partials      196      196              
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `77.26% <91.42%> (+0.08%)` | :arrow_up: |

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

| [Impacted Files](https://codecov.io/gh/kubesphere/ks-devops/pull/495?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere) | Coverage Δ | |
|---|---|---|
| [pkg/api/devops/v1alpha3/addon.go](https://codecov.io/gh/kubesphere/ks-devops/pull/495/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-cGtnL2FwaS9kZXZvcHMvdjFhbHBoYTMvYWRkb24uZ28=) | `100.00% <ø> (ø)` | |
| [pkg/api/devops/v1alpha3/addon\_strategy.go](https://codecov.io/gh/kubesphere/ks-devops/pull/495/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-cGtnL2FwaS9kZXZvcHMvdjFhbHBoYTMvYWRkb25fc3RyYXRlZ3kuZ28=) | `100.00% <ø> (ø)` | |
| [pkg/api/devops/v1alpha3/gitrepository.go](https://codecov.io/gh/kubesphere/ks-devops/pull/495/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-cGtnL2FwaS9kZXZvcHMvdjFhbHBoYTMvZ2l0cmVwb3NpdG9yeS5nbw==) | `100.00% <ø> (ø)` | |
| [pkg/api/devops/v1alpha3/webhook.go](https://codecov.io/gh/kubesphere/ks-devops/pull/495/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-cGtnL2FwaS9kZXZvcHMvdjFhbHBoYTMvd2ViaG9vay5nbw==) | `100.00% <ø> (ø)` | |
| [.../gitrepository/gitrepository\_webhook\_controller.go](https://codecov.io/gh/kubesphere/ks-devops/pull/495/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-Y29udHJvbGxlcnMvZ2l0cmVwb3NpdG9yeS9naXRyZXBvc2l0b3J5X3dlYmhvb2tfY29udHJvbGxlci5nbw==) | `50.83% <40.00%> (ø)` | |
| [controllers/gitrepository/webhook\_notify.go](https://codecov.io/gh/kubesphere/ks-devops/pull/495/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-Y29udHJvbGxlcnMvZ2l0cmVwb3NpdG9yeS93ZWJob29rX25vdGlmeS5nbw==) | `48.64% <40.00%> (ø)` | |
| [controllers/addon/addon\_controller.go](https://codecov.io/gh/kubesphere/ks-devops/pull/495/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-Y29udHJvbGxlcnMvYWRkb24vYWRkb25fY29udHJvbGxlci5nbw==) | `43.83% <50.00%> (ø)` | |
| [controllers/addon/operator\_controller.go](https://codecov.io/gh/kubesphere/ks-devops/pull/495/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-Y29udHJvbGxlcnMvYWRkb24vb3BlcmF0b3JfY29udHJvbGxlci5nbw==) | `71.42% <100.00%> (ø)` | |
| [...kapis/devops/v1alpha3/scm/gitrepository\_handler.go](https://codecov.io/gh/kubesphere/ks-devops/pull/495/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-cGtnL2thcGlzL2Rldm9wcy92MWFscGhhMy9zY20vZ2l0cmVwb3NpdG9yeV9oYW5kbGVyLmdv) | `100.00% <100.00%> (ø)` | |
| [pkg/kapis/devops/v1alpha3/scm/route.go](https://codecov.io/gh/kubesphere/ks-devops/pull/495/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-cGtnL2thcGlzL2Rldm9wcy92MWFscGhhMy9zY20vcm91dGUuZ28=) | `100.00% <100.00%> (ø)` | |
| ... and [4 more](https://codecov.io/gh/kubesphere/ks-devops/pull/495/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere) | |

------

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


Patch:
diff --git a/Makefile b/Makefile
index 18d0652..50da94f 100644
--- a/Makefile
+++ b/Makefile
@@ -92,6 +92,8 @@ docker-push-controller:
 # Build and push the docker image
 docker-build-push-controller: docker-build-controller docker-push-controller
 
+run-apiserver:
+	go run cmd/apiserver/apiserver.go
 # Build the docker image of apiserver
 docker-build-apiserver:
 	docker build --build-arg GOPROXY=https://goproxy.io . -f config/dockerfiles/apiserver/Dockerfile -t ${APISERVER_IMG}
diff --git a/config/crd/bases/devops.kubesphere.io_addons.yaml b/config/crd/bases/devops.kubesphere.io_addons.yaml
index 73567ff..7144441 100644
--- a/config/crd/bases/devops.kubesphere.io_addons.yaml
+++ b/config/crd/bases/devops.kubesphere.io_addons.yaml
@@ -21,7 +21,7 @@ spec:
       jsonPath: .spec.version
       name: Version
       type: string
-    name: v1alpha1
+    name: v1alpha3
     schema:
       openAPIV3Schema:
         description: Addon represents a plugin (addon) of ks-devops
diff --git a/config/crd/bases/devops.kubesphere.io_addonstrategies.yaml b/config/crd/bases/devops.kubesphere.io_addonstrategies.yaml
index 05cdb26..3651471 100644
--- a/config/crd/bases/devops.kubesphere.io_addonstrategies.yaml
+++ b/config/crd/bases/devops.kubesphere.io_addonstrategies.yaml
@@ -16,7 +16,7 @@ spec:
     singular: addonstrategy
   scope: Cluster
   versions:
-  - name: v1alpha1
+  - name: v1alpha3
     schema:
       openAPIV3Schema:
         description: AddonStrategy represents an addonStrategy
diff --git a/config/crd/bases/devops.kubesphere.io_gitrepositories.yaml b/config/crd/bases/devops.kubesphere.io_gitrepositories.yaml
index 473a271..f3be2d6 100644
--- a/config/crd/bases/devops.kubesphere.io_gitrepositories.yaml
+++ b/config/crd/bases/devops.kubesphere.io_gitrepositories.yaml
@@ -20,7 +20,7 @@ spec:
     - jsonPath: .spec.server
       name: Server
       type: string
-    name: v1alpha1
+    name: v1alpha3
     schema:
       openAPIV3Schema:
         description: GitRepository is the Schema for the webhook API
diff --git a/config/crd/bases/devops.kubesphere.io_webhooks.yaml b/config/crd/bases/devops.kubesphere.io_webhooks.yaml
index 8e98949..566669a 100644
--- a/config/crd/bases/devops.kubesphere.io_webhooks.yaml
+++ b/config/crd/bases/devops.kubesphere.io_webhooks.yaml
@@ -23,7 +23,7 @@ spec:
     - jsonPath: .spec.skipVerify
       name: SkipVerify
       type: boolean
-    name: v1alpha1
+    name: v1alpha3
     schema:
       openAPIV3Schema:
         description: Webhook is the Schema for the webhook API
diff --git a/config/samples/devops_v1alpha1_clustertemplate.yaml b/config/samples/devops_v1alpha1_clustertemplate.yaml
index ce304ff..d892d14 100644
--- a/config/samples/devops_v1alpha1_clustertemplate.yaml
+++ b/config/samples/devops_v1alpha1_clustertemplate.yaml
@@ -4,7 +4,7 @@ metadata:
   name: clustertemplate-sample
 spec:
   parameters:
-    - name: gitCloneURL
+    - name: cloneURL
       description: What is your repository URL you want to clone?
       type: string # ignorable
       validation:
@@ -17,11 +17,6 @@ spec:
       description: Do we really need build stage only?
       default: false # Valid JSON value
       type: bool
-    - name: matrix
-      description: Matrix versions of gradle
-      type: string-array
-      default: ["6.9.1-jdk11", "7.0.0-jdk11", "7.3.3-jdk11"]
-
   template: | # Go template
     pipeline {
         agent {
@@ -36,7 +31,7 @@ spec:
         stages {
             stage('Checkout') {
                 steps {
-                    git branch: '{{.params.revision}}', url: '{{.params.cloneURL}}'
+                    git branch: '$(.params.revision)', url: '$(.params.cloneURL)'
                 }
             }
             stage('Gradle Check') {
@@ -53,12 +48,12 @@ spec:
                     }
                 }
             }
-            {{if ne .params.buildOnly "true"}}
+            $(if ne .params.buildOnly "true")
             stage('Archive Assets') {
                 steps {
                     archiveArtifacts '**/build/libs/*.jar'
                 }
             }
-            {{end}}
+            $(end)
         }
     }
\ No newline at end of file
diff --git a/config/samples/gitrepository.yaml b/config/samples/gitrepository.yaml
index 7eb71d0..07bd1e9 100644
--- a/config/samples/gitrepository.yaml
+++ b/config/samples/gitrepository.yaml
@@ -1,4 +1,4 @@
-apiVersion: devops.kubesphere.io/v1alpha1
+apiVersion: devops.kubesphere.io/v1alpha3
 kind: GitRepository
 metadata:
   name: gitrepository
diff --git a/controllers/addon/addon_controller.go b/controllers/addon/addon_controller.go
index aec9086..06bc5bf 100644
--- a/controllers/addon/addon_controller.go
+++ b/controllers/addon/addon_controller.go
@@ -27,7 +27,7 @@ import (
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/client-go/tools/record"
-	"kubesphere.io/devops/pkg/api/devops/v1alpha1"
+	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
 	"kubesphere.io/devops/pkg/utils/k8sutil"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
@@ -48,7 +48,7 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (result ctrl.Result, err error)
 	ctx := context.Background()
 	r.log.Info(fmt.Sprintf("start to reconcile addon: %s", req.String()))
 
-	addon := &v1alpha1.Addon{}
+	addon := &v1alpha3.Addon{}
 	if err = r.Client.Get(ctx, req.NamespacedName, addon); err != nil {
 		err = client.IgnoreNotFound(err)
 		return
@@ -62,18 +62,18 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (result ctrl.Result, err error)
 	return
 }
 
-func beingDeleting(addon *v1alpha1.Addon) bool {
+func beingDeleting(addon *v1alpha3.Addon) bool {
 	return addon != nil && !addon.DeletionTimestamp.IsZero()
 }
 
-func (r *Reconciler) cleanup(addon *v1alpha1.Addon) (result ctrl.Result, err error) {
+func (r *Reconciler) cleanup(addon *v1alpha3.Addon) (result ctrl.Result, err error) {
 	var obj *unstructured.Unstructured
 	if obj, _, err = r.findTemplateInstance(context.Background(), addon); err != nil && !apierrors.IsNotFound(err) {
 		return
 	}
 
 	if err = r.Client.Delete(context.Background(), obj); err == nil {
-		k8sutil.RemoveFinalizer(&addon.ObjectMeta, v1alpha1.AddonFinalizerName)
+		k8sutil.RemoveFinalizer(&addon.ObjectMeta, v1alpha3.AddonFinalizerName)
 		err = r.Client.Update(context.Background(), addon)
 	}
 	return
@@ -84,7 +84,7 @@ const (
 	EventReasonMissing = "Missing"
 )
 
-func (r *Reconciler) addonHandle(ctx context.Context, addon *v1alpha1.Addon) (err error) {
+func (r *Reconciler) addonHandle(ctx context.Context, addon *v1alpha3.Addon) (err error) {
 	var tpl string
 	var obj *unstructured.Unstructured
 	if obj, tpl, err = r.findTemplateInstance(ctx, addon); err != nil {
@@ -106,16 +106,16 @@ func (r *Reconciler) addonHandle(ctx context.Context, addon *v1alpha1.Addon) (er
 
 	// add finalizer
 	if err == nil {
-		k8sutil.AddFinalizer(&addon.ObjectMeta, v1alpha1.AddonFinalizerName)
+		k8sutil.AddFinalizer(&addon.ObjectMeta, v1alpha3.AddonFinalizerName)
 		err = r.Update(ctx, addon)
 	}
 	return
 }
 
-func (r *Reconciler) findTemplateInstance(ctx context.Context, addon *v1alpha1.Addon) (instance *unstructured.Unstructured, tpl string, err error) {
+func (r *Reconciler) findTemplateInstance(ctx context.Context, addon *v1alpha3.Addon) (instance *unstructured.Unstructured, tpl string, err error) {
 	strategy := addon.Spec.Strategy
 
-	addonStrategy := &v1alpha1.AddonStrategy{}
+	addonStrategy := &v1alpha3.AddonStrategy{}
 	if err = r.Client.Get(ctx, types.NamespacedName{Name: strategy.Name}, addonStrategy); err != nil {
 		r.recorder.Eventf(addon, corev1.EventTypeWarning, EventReasonMissing, "failed to get AddonStrategy with: %s", strategy.Name)
 		return
@@ -149,7 +149,7 @@ func (r *Reconciler) findTemplateInstance(ctx context.Context, addon *v1alpha1.A
 	return
 }
 
-func getTemplate(tpl string, addon *v1alpha1.Addon) (result string, err error) {
+func getTemplate(tpl string, addon *v1alpha3.Addon) (result string, err error) {
 	var addonTpl *template.Template
 	if addonTpl, err = template.New("addon").Parse(tpl); err == nil {
 		buf := bytes.NewBuffer([]byte{})
@@ -160,10 +160,10 @@ func getTemplate(tpl string, addon *v1alpha1.Addon) (result string, err error) {
 	return
 }
 
-func (r *Reconciler) supportedStrategy(strategy *v1alpha1.AddonStrategy) bool {
+func (r *Reconciler) supportedStrategy(strategy *v1alpha3.AddonStrategy) bool {
 	// TODO support more types in the future
 	if strategy != nil {
-		return strategy.Spec.Type == v1alpha1.AddonInstallStrategySimpleOperator
+		return strategy.Spec.Type == v1alpha3.AddonInstallStrategySimpleOperator
 	}
 	return false
 }
@@ -173,6 +173,6 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
 	r.log = ctrl.Log.WithName("AddonReconciler")
 	r.recorder = mgr.GetEventRecorderFor("addon-controller")
 	return ctrl.NewControllerManagedBy(mgr).
-		For(&v1alpha1.Addon{}).
+		For(&v1alpha3.Addon{}).
 		Complete(r)
 }
diff --git a/controllers/addon/operator_controller.go b/controllers/addon/operator_controller.go
index 968d3d0..85ead2e 100644
--- a/controllers/addon/operator_controller.go
+++ b/controllers/addon/operator_controller.go
@@ -24,7 +24,7 @@ import (
 	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/types"
-	"kubesphere.io/devops/pkg/api/devops/v1alpha1"
+	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"strings"
 
@@ -68,17 +68,17 @@ func (r *OperatorCRDReconciler) operatorsHandle(name string, version string) (er
 		return
 	}
 
-	strategyName := getStrategyName(name, string(v1alpha1.AddonInstallStrategySimpleOperator))
+	strategyName := getStrategyName(name, string(v1alpha3.AddonInstallStrategySimpleOperator))
 
-	strategy := &v1alpha1.AddonStrategy{}
+	strategy := &v1alpha3.AddonStrategy{}
 	if err = r.Client.Get(ctx, types.NamespacedName{
 		Name: strategyName,
 	}, strategy); err != nil {
 		if apierrors.IsNotFound(err) {
 			// create addonStrategy
 			strategy.Name = strategyName
-			strategy.Spec = v1alpha1.AddStrategySpec{
-				Type: v1alpha1.AddonInstallStrategySimpleOperator,
+			strategy.Spec = v1alpha3.AddStrategySpec{
+				Type: v1alpha3.AddonInstallStrategySimpleOperator,
 				SimpleOperator: v1.ObjectReference{
 					APIVersion: version,
 					Kind:       name,
diff --git a/controllers/gitrepository/gitrepository_webhook_controller.go b/controllers/gitrepository/gitrepository_webhook_controller.go
index c7c05f5..e677ef9 100644
--- a/controllers/gitrepository/gitrepository_webhook_controller.go
+++ b/controllers/gitrepository/gitrepository_webhook_controller.go
@@ -19,7 +19,7 @@ import (
 	"github.com/drone/go-scm/scm"
 	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/types"
-	"kubesphere.io/devops/pkg/api/devops/v1alpha1"
+	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
 	"kubesphere.io/devops/pkg/client/git"
 	"strings"
 	"time"
@@ -50,7 +50,7 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (result ctrl.Result, err error)
 	ctx := context.Background()
 	log := r.log.WithValues("GitRepository", req.NamespacedName)
 
-	repo := &v1alpha1.GitRepository{}
+	repo := &v1alpha3.GitRepository{}
 	if err = r.Client.Get(ctx, req.NamespacedName, repo); err != nil {
 		log.Error(err, "unable to fetch GitRepository")
 		result = ctrl.Result{}
@@ -82,7 +82,7 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (result ctrl.Result, err error)
 	return
 }
 
-func (r *Reconciler) createOrUpdateWebhook(repo *v1alpha1.GitRepository) (err error) {
+func (r *Reconciler) createOrUpdateWebhook(repo *v1alpha3.GitRepository) (err error) {
 	var gitClient *scm.Client
 	if gitClient, err = r.getGitClient(repo); err != nil {
 		return
@@ -105,7 +105,7 @@ func (r *Reconciler) createOrUpdateWebhook(repo *v1alpha1.GitRepository) (err er
 
 	for index := range repo.Spec.Webhooks {
 		webhookRef := repo.Spec.Webhooks[index]
-		webhook := &v1alpha1.Webhook{}
+		webhook := &v1alpha3.Webhook{}
 		if err = r.Client.Get(context.TODO(), types.NamespacedName{
 			Namespace: repo.Namespace,
 			Name:      webhookRef.Name,
@@ -150,7 +150,7 @@ func exist(server string, hooks []*scm.Hook) (exist bool, id string) {
 	return
 }
 
-func (r *Reconciler) getGitClient(repo *v1alpha1.GitRepository) (client *scm.Client, err error) {
+func (r *Reconciler) getGitClient(repo *v1alpha3.GitRepository) (client *scm.Client, err error) {
 	spec := repo.Spec.DeepCopy()
 	provider := spec.Provider
 
@@ -192,7 +192,7 @@ func (r *Reconciler) getSecret(ref *v1.SecretReference, defaultNamespace string)
 	return
 }
 
-func getRepo(repo *v1alpha1.GitRepository) string {
+func getRepo(repo *v1alpha3.GitRepository) string {
 	if repo == nil || repo.Spec.Provider == "" {
 		return ""
 	}
@@ -207,7 +207,7 @@ func getRepo(repo *v1alpha1.GitRepository) string {
 	return ""
 }
 
-func (r *Reconciler) linkToWebhooks(repo *v1alpha1.GitRepository) (err error) {
+func (r *Reconciler) linkToWebhooks(repo *v1alpha3.GitRepository) (err error) {
 	var failedLinks []string
 	for i := range repo.Spec.Webhooks {
 		webhookRef := repo.Spec.Webhooks[i]
@@ -223,14 +223,14 @@ func (r *Reconciler) linkToWebhooks(repo *v1alpha1.GitRepository) (err error) {
 	return
 }
 
-func linkToWebhook(webhookRef v1.LocalObjectReference, repo *v1alpha1.GitRepository, client client.Client) (err error) {
-	webhook := &v1alpha1.Webhook{}
+func linkToWebhook(webhookRef v1.LocalObjectReference, repo *v1alpha3.GitRepository, client client.Client) (err error) {
+	webhook := &v1alpha3.Webhook{}
 	if err = client.Get(context.TODO(), types.NamespacedName{Namespace: repo.Namespace, Name: webhookRef.Name}, webhook); err != nil {
 		err = fmt.Errorf("cannot find webhook '%v', error： %v", webhookRef, err)
 		return
 	}
 
-	webhook.Annotations = addToArrayInAnnotations(webhook.Annotations, v1alpha1.AnnotationKeyGitRepos, repo.Name)
+	webhook.Annotations = addToArrayInAnnotations(webhook.Annotations, v1alpha3.AnnotationKeyGitRepos, repo.Name)
 	err = client.Update(context.TODO(), webhook)
 	return
 }
@@ -258,6 +258,6 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
 	r.recorder = mgr.GetEventRecorderFor("gitrepository-controller")
 	r.log = ctrl.Log.WithName("gitrepository-controller")
 	return ctrl.NewControllerManagedBy(mgr).
-		For(&v1alpha1.GitRepository{}).
+		For(&v1alpha3.GitRepository{}).
 		Complete(r)
 }
diff --git a/controllers/gitrepository/webhook_notify.go b/controllers/gitrepository/webhook_notify.go
index 7393b27..f5c21d3 100644
--- a/controllers/gitrepository/webhook_notify.go
+++ b/controllers/gitrepository/webhook_notify.go
@@ -23,7 +23,7 @@ import (
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apiserver/pkg/storage/names"
 	"k8s.io/client-go/tools/record"
-	"kubesphere.io/devops/pkg/api/devops/v1alpha1"
+	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"strings"
@@ -44,7 +44,7 @@ func (r *WebhookReconciler) Reconcile(req ctrl.Request) (result ctrl.Result, err
 	ctx := context.Background()
 	log := r.log.WithValues("webhook", req.NamespacedName)
 
-	webhook := &v1alpha1.Webhook{}
+	webhook := &v1alpha3.Webhook{}
 	if err = r.Client.Get(ctx, req.NamespacedName, webhook); err != nil {
 		log.Error(err, "unable to fetch webhook")
 		err = client.IgnoreNotFound(err)
@@ -52,7 +52,7 @@ func (r *WebhookReconciler) Reconcile(req ctrl.Request) (result ctrl.Result, err
 	}
 
 	// skip those don't have the desired annotation
-	repos, ok := webhook.Annotations[v1alpha1.AnnotationKeyGitRepos]
+	repos, ok := webhook.Annotations[v1alpha3.AnnotationKeyGitRepos]
 	if !ok {
 		return
 	}
@@ -81,7 +81,7 @@ func (r *WebhookReconciler) notifyGitRepos(ns, repos string) (err error) {
 }
 
 func (r *WebhookReconciler) notifyGitRepo(ns, name string) (err error) {
-	gitRepo := &v1alpha1.GitRepository{}
+	gitRepo := &v1alpha3.GitRepository{}
 
 	if err = r.Client.Get(context.TODO(), types.NamespacedName{
 		Namespace: ns,
@@ -93,7 +93,7 @@ func (r *WebhookReconciler) notifyGitRepo(ns, name string) (err error) {
 	if gitRepo.Annotations == nil {
 		gitRepo.Annotations = map[string]string{}
 	}
-	gitRepo.Annotations[v1alpha1.AnnotationKeyWebhookUpdates] = names.SimpleNameGenerator.GenerateName("")
+	gitRepo.Annotations[v1alpha3.AnnotationKeyWebhookUpdates] = names.SimpleNameGenerator.GenerateName("")
 	err = r.Client.Update(context.TODO(), gitRepo)
 	return
 }
@@ -102,6 +102,6 @@ func (r *WebhookReconciler) SetupWithManager(mgr ctrl.Manager) error {
 	r.recorder = mgr.GetEventRecorderFor("webhook-notify")
 	r.log = ctrl.Log.WithName("webhook-notify")
 	return ctrl.NewControllerManagedBy(mgr).
-		For(&v1alpha1.Webhook{}).
+		For(&v1alpha3.Webhook{}).
 		Complete(r)
 }
diff --git a/pkg/api/devops/v1alpha1/zz_generated.deepcopy.go b/pkg/api/devops/v1alpha1/zz_generated.deepcopy.go
index 0778c0b..fa5dd76 100644
--- a/pkg/api/devops/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/api/devops/v1alpha1/zz_generated.deepcopy.go
@@ -26,185 +26,6 @@ import (
 	runtime "k8s.io/apimachinery/pkg/runtime"
 )
 
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *AddStrategySpec) DeepCopyInto(out *AddStrategySpec) {
-	*out = *in
-	out.Operator = in.Operator
-	out.SimpleOperator = in.SimpleOperator
-	if in.Parameters != nil {
-		in, out := &in.Parameters, &out.Parameters
-		*out = make(map[string]string, len(*in))
-		for key, val := range *in {
-			(*out)[key] = val
-		}
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddStrategySpec.
-func (in *AddStrategySpec) DeepCopy() *AddStrategySpec {
-	if in == nil {
-		return nil
-	}
-	out := new(AddStrategySpec)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *Addon) DeepCopyInto(out *Addon) {
-	*out = *in
-	out.TypeMeta = in.TypeMeta
-	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
-	in.Spec.DeepCopyInto(&out.Spec)
-	out.Status = in.Status
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Addon.
-func (in *Addon) DeepCopy() *Addon {
-	if in == nil {
-		return nil
-	}
-	out := new(Addon)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
-func (in *Addon) DeepCopyObject() runtime.Object {
-	if c := in.DeepCopy(); c != nil {
-		return c
-	}
-	return nil
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *AddonList) DeepCopyInto(out *AddonList) {
-	*out = *in
-	out.TypeMeta = in.TypeMeta
-	in.ListMeta.DeepCopyInto(&out.ListMeta)
-	if in.Items != nil {
-		in, out := &in.Items, &out.Items
-		*out = make([]Addon, len(*in))
-		for i := range *in {
-			(*in)[i].DeepCopyInto(&(*out)[i])
-		}
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddonList.
-func (in *AddonList) DeepCopy() *AddonList {
-	if in == nil {
-		return nil
-	}
-	out := new(AddonList)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
-func (in *AddonList) DeepCopyObject() runtime.Object {
-	if c := in.DeepCopy(); c != nil {
-		return c
-	}
-	return nil
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *AddonSpec) DeepCopyInto(out *AddonSpec) {
-	*out = *in
-	out.Strategy = in.Strategy
-	if in.Parameters != nil {
-		in, out := &in.Parameters, &out.Parameters
-		*out = make(map[string]string, len(*in))
-		for key, val := range *in {
-			(*out)[key] = val
-		}
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddonSpec.
-func (in *AddonSpec) DeepCopy() *AddonSpec {
-	if in == nil {
-		return nil
-	}
-	out := new(AddonSpec)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *AddonStatus) DeepCopyInto(out *AddonStatus) {
-	*out = *in
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddonStatus.
-func (in *AddonStatus) DeepCopy() *AddonStatus {
-	if in == nil {
-		return nil
-	}
-	out := new(AddonStatus)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *AddonStrategy) DeepCopyInto(out *AddonStrategy) {
-	*out = *in
-	out.TypeMeta = in.TypeMeta
-	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
-	in.Spec.DeepCopyInto(&out.Spec)
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddonStrategy.
-func (in *AddonStrategy) DeepCopy() *AddonStrategy {
-	if in == nil {
-		return nil
-	}
-	out := new(AddonStrategy)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
-func (in *AddonStrategy) DeepCopyObject() runtime.Object {
-	if c := in.DeepCopy(); c != nil {
-		return c
-	}
-	return nil
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *AddonStrategyList) DeepCopyInto(out *AddonStrategyList) {
-	*out = *in
-	out.TypeMeta = in.TypeMeta
-	in.ListMeta.DeepCopyInto(&out.ListMeta)
-	if in.Items != nil {
-		in, out := &in.Items, &out.Items
-		*out = make([]AddonStrategy, len(*in))
-		for i := range *in {
-			(*in)[i].DeepCopyInto(&(*out)[i])
-		}
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddonStrategyList.
-func (in *AddonStrategyList) DeepCopy() *AddonStrategyList {
-	if in == nil {
-		return nil
-	}
-	out := new(AddonStrategyList)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
-func (in *AddonStrategyList) DeepCopyObject() runtime.Object {
-	if c := in.DeepCopy(); c != nil {
-		return c
-	}
-	return nil
-}
-
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *AuthConfig) DeepCopyInto(out *AuthConfig) {
 	*out = *in
@@ -380,89 +201,6 @@ func (in *EnvironmentSpec) DeepCopy() *EnvironmentSpec {
 	return out
 }
 
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *GitRepository) DeepCopyInto(out *GitRepository) {
-	*out = *in
-	out.TypeMeta = in.TypeMeta
-	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
-	in.Spec.DeepCopyInto(&out.Spec)
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRepository.
-func (in *GitRepository) DeepCopy() *GitRepository {
-	if in == nil {
-		return nil
-	}
-	out := new(GitRepository)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
-func (in *GitRepository) DeepCopyObject() runtime.Object {
-	if c := in.DeepCopy(); c != nil {
-		return c
-	}
-	return nil
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *GitRepositoryList) DeepCopyInto(out *GitRepositoryList) {
-	*out = *in
-	out.TypeMeta = in.TypeMeta
-	in.ListMeta.DeepCopyInto(&out.ListMeta)
-	if in.Items != nil {
-		in, out := &in.Items, &out.Items
-		*out = make([]GitRepository, len(*in))
-		for i := range *in {
-			(*in)[i].DeepCopyInto(&(*out)[i])
-		}
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRepositoryList.
-func (in *GitRepositoryList) DeepCopy() *GitRepositoryList {
-	if in == nil {
-		return nil
-	}
-	out := new(GitRepositoryList)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
-func (in *GitRepositoryList) DeepCopyObject() runtime.Object {
-	if c := in.DeepCopy(); c != nil {
-		return c
-	}
-	return nil
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *GitRepositorySpec) DeepCopyInto(out *GitRepositorySpec) {
-	*out = *in
-	if in.Secret != nil {
-		in, out := &in.Secret, &out.Secret
-		*out = new(v1.SecretReference)
-		**out = **in
-	}
-	if in.Webhooks != nil {
-		in, out := &in.Webhooks, &out.Webhooks
-		*out = make([]v1.LocalObjectReference, len(*in))
-		copy(*out, *in)
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRepositorySpec.
-func (in *GitRepositorySpec) DeepCopy() *GitRepositorySpec {
-	if in == nil {
-		return nil
-	}
-	out := new(GitRepositorySpec)
-	in.DeepCopyInto(out)
-	return out
-}
-
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Parameter) DeepCopyInto(out *Parameter) {
 	*out = *in
@@ -1107,86 +845,3 @@ func (in *VolumeSpec) DeepCopy() *VolumeSpec {
 	in.DeepCopyInto(out)
 	return out
 }
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *Webhook) DeepCopyInto(out *Webhook) {
-	*out = *in
-	out.TypeMeta = in.TypeMeta
-	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
-	in.Spec.DeepCopyInto(&out.Spec)
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Webhook.
-func (in *Webhook) DeepCopy() *Webhook {
-	if in == nil {
-		return nil
-	}
-	out := new(Webhook)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
-func (in *Webhook) DeepCopyObject() runtime.Object {
-	if c := in.DeepCopy(); c != nil {
-		return c
-	}
-	return nil
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *WebhookList) DeepCopyInto(out *WebhookList) {
-	*out = *in
-	out.TypeMeta = in.TypeMeta
-	in.ListMeta.DeepCopyInto(&out.ListMeta)
-	if in.Items != nil {
-		in, out := &in.Items, &out.Items
-		*out = make([]Webhook, len(*in))
-		for i := range *in {
-			(*in)[i].DeepCopyInto(&(*out)[i])
-		}
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookList.
-func (in *WebhookList) DeepCopy() *WebhookList {
-	if in == nil {
-		return nil
-	}
-	out := new(WebhookList)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
-func (in *WebhookList) DeepCopyObject() runtime.Object {
-	if c := in.DeepCopy(); c != nil {
-		return c
-	}
-	return nil
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *WebhookSpec) DeepCopyInto(out *WebhookSpec) {
-	*out = *in
-	if in.Secret != nil {
-		in, out := &in.Secret, &out.Secret
-		*out = new(v1.SecretReference)
-		**out = **in
-	}
-	if in.Events != nil {
-		in, out := &in.Events, &out.Events
-		*out = make([]string, len(*in))
-		copy(*out, *in)
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookSpec.
-func (in *WebhookSpec) DeepCopy() *WebhookSpec {
-	if in == nil {
-		return nil
-	}
-	out := new(WebhookSpec)
-	in.DeepCopyInto(out)
-	return out
-}
diff --git a/pkg/api/devops/v1alpha1/addon.go b/pkg/api/devops/v1alpha3/addon.go
similarity index 99%
rename from pkg/api/devops/v1alpha1/addon.go
rename to pkg/api/devops/v1alpha3/addon.go
index 046f086..661e022 100644
--- a/pkg/api/devops/v1alpha1/addon.go
+++ b/pkg/api/devops/v1alpha3/addon.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package v1alpha1
+package v1alpha3
 
 import (
 	v1 "k8s.io/api/core/v1"
diff --git a/pkg/api/devops/v1alpha1/addon_strategy.go b/pkg/api/devops/v1alpha3/addon_strategy.go
similarity index 99%
rename from pkg/api/devops/v1alpha1/addon_strategy.go
rename to pkg/api/devops/v1alpha3/addon_strategy.go
index eeecd9c..4df4e9d 100644
--- a/pkg/api/devops/v1alpha1/addon_strategy.go
+++ b/pkg/api/devops/v1alpha3/addon_strategy.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package v1alpha1
+package v1alpha3
 
 import (
 	v1 "k8s.io/api/core/v1"
diff --git a/pkg/api/devops/v1alpha1/gitrepository.go b/pkg/api/devops/v1alpha3/gitrepository.go
similarity index 99%
rename from pkg/api/devops/v1alpha1/gitrepository.go
rename to pkg/api/devops/v1alpha3/gitrepository.go
index 53c3ee8..a8956f0 100644
--- a/pkg/api/devops/v1alpha1/gitrepository.go
+++ b/pkg/api/devops/v1alpha3/gitrepository.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package v1alpha1
+package v1alpha3
 
 import (
 	v1 "k8s.io/api/core/v1"
diff --git a/pkg/api/devops/v1alpha1/webhook.go b/pkg/api/devops/v1alpha3/webhook.go
similarity index 99%
rename from pkg/api/devops/v1alpha1/webhook.go
rename to pkg/api/devops/v1alpha3/webhook.go
index f721646..fe3e772 100644
--- a/pkg/api/devops/v1alpha1/webhook.go
+++ b/pkg/api/devops/v1alpha3/webhook.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package v1alpha1
+package v1alpha3
 
 import (
 	v1 "k8s.io/api/core/v1"
diff --git a/pkg/api/devops/v1alpha3/zz_generated.deepcopy.go b/pkg/api/devops/v1alpha3/zz_generated.deepcopy.go
index 275e380..0608c5b 100644
--- a/pkg/api/devops/v1alpha3/zz_generated.deepcopy.go
+++ b/pkg/api/devops/v1alpha3/zz_generated.deepcopy.go
@@ -27,6 +27,185 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 )
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AddStrategySpec) DeepCopyInto(out *AddStrategySpec) {
+	*out = *in
+	out.Operator = in.Operator
+	out.SimpleOperator = in.SimpleOperator
+	if in.Parameters != nil {
+		in, out := &in.Parameters, &out.Parameters
+		*out = make(map[string]string, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddStrategySpec.
+func (in *AddStrategySpec) DeepCopy() *AddStrategySpec {
+	if in == nil {
+		return nil
+	}
+	out := new(AddStrategySpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Addon) DeepCopyInto(out *Addon) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+	out.Status = in.Status
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Addon.
+func (in *Addon) DeepCopy() *Addon {
+	if in == nil {
+		return nil
+	}
+	out := new(Addon)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *Addon) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AddonList) DeepCopyInto(out *AddonList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]Addon, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddonList.
+func (in *AddonList) DeepCopy() *AddonList {
+	if in == nil {
+		return nil
+	}
+	out := new(AddonList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *AddonList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AddonSpec) DeepCopyInto(out *AddonSpec) {
+	*out = *in
+	out.Strategy = in.Strategy
+	if in.Parameters != nil {
+		in, out := &in.Parameters, &out.Parameters
+		*out = make(map[string]string, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddonSpec.
+func (in *AddonSpec) DeepCopy() *AddonSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(AddonSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AddonStatus) DeepCopyInto(out *AddonStatus) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddonStatus.
+func (in *AddonStatus) DeepCopy() *AddonStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(AddonStatus)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AddonStrategy) DeepCopyInto(out *AddonStrategy) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddonStrategy.
+func (in *AddonStrategy) DeepCopy() *AddonStrategy {
+	if in == nil {
+		return nil
+	}
+	out := new(AddonStrategy)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *AddonStrategy) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AddonStrategyList) DeepCopyInto(out *AddonStrategyList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]AddonStrategy, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddonStrategyList.
+func (in *AddonStrategyList) DeepCopy() *AddonStrategyList {
+	if in == nil {
+		return nil
+	}
+	out := new(AddonStrategyList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *AddonStrategyList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ApplicationDestination) DeepCopyInto(out *ApplicationDestination) {
 	*out = *in
@@ -395,6 +574,89 @@ func (in *GitCloneOption) DeepCopy() *GitCloneOption {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GitRepository) DeepCopyInto(out *GitRepository) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRepository.
+func (in *GitRepository) DeepCopy() *GitRepository {
+	if in == nil {
+		return nil
+	}
+	out := new(GitRepository)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *GitRepository) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GitRepositoryList) DeepCopyInto(out *GitRepositoryList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]GitRepository, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRepositoryList.
+func (in *GitRepositoryList) DeepCopy() *GitRepositoryList {
+	if in == nil {
+		return nil
+	}
+	out := new(GitRepositoryList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *GitRepositoryList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GitRepositorySpec) DeepCopyInto(out *GitRepositorySpec) {
+	*out = *in
+	if in.Secret != nil {
+		in, out := &in.Secret, &out.Secret
+		*out = new(corev1.SecretReference)
+		**out = **in
+	}
+	if in.Webhooks != nil {
+		in, out := &in.Webhooks, &out.Webhooks
+		*out = make([]corev1.LocalObjectReference, len(*in))
+		copy(*out, *in)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRepositorySpec.
+func (in *GitRepositorySpec) DeepCopy() *GitRepositorySpec {
+	if in == nil {
+		return nil
+	}
+	out := new(GitRepositorySpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *GitSource) DeepCopyInto(out *GitSource) {
 	*out = *in
@@ -1203,3 +1465,86 @@ func (in *TimerTrigger) DeepCopy() *TimerTrigger {
 	in.DeepCopyInto(out)
 	return out
 }
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Webhook) DeepCopyInto(out *Webhook) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Webhook.
+func (in *Webhook) DeepCopy() *Webhook {
+	if in == nil {
+		return nil
+	}
+	out := new(Webhook)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *Webhook) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *WebhookList) DeepCopyInto(out *WebhookList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]Webhook, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookList.
+func (in *WebhookList) DeepCopy() *WebhookList {
+	if in == nil {
+		return nil
+	}
+	out := new(WebhookList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *WebhookList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *WebhookSpec) DeepCopyInto(out *WebhookSpec) {
+	*out = *in
+	if in.Secret != nil {
+		in, out := &in.Secret, &out.Secret
+		*out = new(corev1.SecretReference)
+		**out = **in
+	}
+	if in.Events != nil {
+		in, out := &in.Events, &out.Events
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookSpec.
+func (in *WebhookSpec) DeepCopy() *WebhookSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(WebhookSpec)
+	in.DeepCopyInto(out)
+	return out
+}
diff --git a/pkg/client/listers/devops/v1alpha1/gitrepository.go b/pkg/client/listers/devops/v1alpha1/gitrepository.go
index 538e04f..f73a1b6 100644
--- a/pkg/client/listers/devops/v1alpha1/gitrepository.go
+++ b/pkg/client/listers/devops/v1alpha1/gitrepository.go
@@ -23,12 +23,13 @@ import (
 	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/client-go/tools/cache"
 	v1alpha1 "kubesphere.io/devops/pkg/api/devops/v1alpha1"
+	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
 )
 
 // GitRepositoryLister helps list GitRepositories.
 type GitRepositoryLister interface {
 	// List lists all GitRepositories in the indexer.
-	List(selector labels.Selector) (ret []*v1alpha1.GitRepository, err error)
+	List(selector labels.Selector) (ret []*v1alpha3.GitRepository, err error)
 	// GitRepositories returns an object that can list and get GitRepositories.
 	GitRepositories(namespace string) GitRepositoryNamespaceLister
 	GitRepositoryListerExpansion
@@ -45,9 +46,9 @@ func NewGitRepositoryLister(indexer cache.Indexer) GitRepositoryLister {
 }
 
 // List lists all GitRepositories in the indexer.
-func (s *gitRepositoryLister) List(selector labels.Selector) (ret []*v1alpha1.GitRepository, err error) {
+func (s *gitRepositoryLister) List(selector labels.Selector) (ret []*v1alpha3.GitRepository, err error) {
 	err = cache.ListAll(s.indexer, selector, func(m interface{}) {
-		ret = append(ret, m.(*v1alpha1.GitRepository))
+		ret = append(ret, m.(*v1alpha3.GitRepository))
 	})
 	return ret, err
 }
@@ -60,9 +61,9 @@ func (s *gitRepositoryLister) GitRepositories(namespace string) GitRepositoryNam
 // GitRepositoryNamespaceLister helps list and get GitRepositories.
 type GitRepositoryNamespaceLister interface {
 	// List lists all GitRepositories in the indexer for a given namespace.
-	List(selector labels.Selector) (ret []*v1alpha1.GitRepository, err error)
+	List(selector labels.Selector) (ret []*v1alpha3.GitRepository, err error)
 	// Get retrieves the GitRepository from the indexer for a given namespace and name.
-	Get(name string) (*v1alpha1.GitRepository, error)
+	Get(name string) (*v1alpha3.GitRepository, error)
 	GitRepositoryNamespaceListerExpansion
 }
 
@@ -74,15 +75,15 @@ type gitRepositoryNamespaceLister struct {
 }
 
 // List lists all GitRepositories in the indexer for a given namespace.
-func (s gitRepositoryNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.GitRepository, err error) {
+func (s gitRepositoryNamespaceLister) List(selector labels.Selector) (ret []*v1alpha3.GitRepository, err error) {
 	err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
-		ret = append(ret, m.(*v1alpha1.GitRepository))
+		ret = append(ret, m.(*v1alpha3.GitRepository))
 	})
 	return ret, err
 }
 
 // Get retrieves the GitRepository from the indexer for a given namespace and name.
-func (s gitRepositoryNamespaceLister) Get(name string) (*v1alpha1.GitRepository, error) {
+func (s gitRepositoryNamespaceLister) Get(name string) (*v1alpha3.GitRepository, error) {
 	obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
 	if err != nil {
 		return nil, err
@@ -90,5 +91,5 @@ func (s gitRepositoryNamespaceLister) Get(name string) (*v1alpha1.GitRepository,
 	if !exists {
 		return nil, errors.NewNotFound(v1alpha1.Resource("gitrepository"), name)
 	}
-	return obj.(*v1alpha1.GitRepository), nil
+	return obj.(*v1alpha3.GitRepository), nil
 }
diff --git a/pkg/client/listers/devops/v1alpha1/webhook.go b/pkg/client/listers/devops/v1alpha1/webhook.go
index afedf8b..4b7287f 100644
--- a/pkg/client/listers/devops/v1alpha1/webhook.go
+++ b/pkg/client/listers/devops/v1alpha1/webhook.go
@@ -23,12 +23,13 @@ import (
 	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/client-go/tools/cache"
 	v1alpha1 "kubesphere.io/devops/pkg/api/devops/v1alpha1"
+	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
 )
 
 // WebhookLister helps list Webhooks.
 type WebhookLister interface {
 	// List lists all Webhooks in the indexer.
-	List(selector labels.Selector) (ret []*v1alpha1.Webhook, err error)
+	List(selector labels.Selector) (ret []*v1alpha3.Webhook, err error)
 	// Webhooks returns an object that can list and get Webhooks.
 	Webhooks(namespace string) WebhookNamespaceLister
 	WebhookListerExpansion
@@ -45,9 +46,9 @@ func NewWebhookLister(indexer cache.Indexer) WebhookLister {
 }
 
 // List lists all Webhooks in the indexer.
-func (s *webhookLister) List(selector labels.Selector) (ret []*v1alpha1.Webhook, err error) {
+func (s *webhookLister) List(selector labels.Selector) (ret []*v1alpha3.Webhook, err error) {
 	err = cache.ListAll(s.indexer, selector, func(m interface{}) {
-		ret = append(ret, m.(*v1alpha1.Webhook))
+		ret = append(ret, m.(*v1alpha3.Webhook))
 	})
 	return ret, err
 }
@@ -60,9 +61,9 @@ func (s *webhookLister) Webhooks(namespace string) WebhookNamespaceLister {
 // WebhookNamespaceLister helps list and get Webhooks.
 type WebhookNamespaceLister interface {
 	// List lists all Webhooks in the indexer for a given namespace.
-	List(selector labels.Selector) (ret []*v1alpha1.Webhook, err error)
+	List(selector labels.Selector) (ret []*v1alpha3.Webhook, err error)
 	// Get retrieves the Webhook from the indexer for a given namespace and name.
-	Get(name string) (*v1alpha1.Webhook, error)
+	Get(name string) (*v1alpha3.Webhook, error)
 	WebhookNamespaceListerExpansion
 }
 
@@ -74,15 +75,15 @@ type webhookNamespaceLister struct {
 }
 
 // List lists all Webhooks in the indexer for a given namespace.
-func (s webhookNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.Webhook, err error) {
+func (s webhookNamespaceLister) List(selector labels.Selector) (ret []*v1alpha3.Webhook, err error) {
 	err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
-		ret = append(ret, m.(*v1alpha1.Webhook))
+		ret = append(ret, m.(*v1alpha3.Webhook))
 	})
 	return ret, err
 }
 
 // Get retrieves the Webhook from the indexer for a given namespace and name.
-func (s webhookNamespaceLister) Get(name string) (*v1alpha1.Webhook, error) {
+func (s webhookNamespaceLister) Get(name string) (*v1alpha3.Webhook, error) {
 	obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
 	if err != nil {
 		return nil, err
@@ -90,5 +91,5 @@ func (s webhookNamespaceLister) Get(name string) (*v1alpha1.Webhook, error) {
 	if !exists {
 		return nil, errors.NewNotFound(v1alpha1.Resource("webhook"), name)
 	}
-	return obj.(*v1alpha1.Webhook), nil
+	return obj.(*v1alpha3.Webhook), nil
 }
diff --git a/pkg/kapis/devops/v1alpha3/scm/gitrepository_handler.go b/pkg/kapis/devops/v1alpha3/scm/gitrepository_handler.go
new file mode 100644
index 0000000..cacc023
--- /dev/null
+++ b/pkg/kapis/devops/v1alpha3/scm/gitrepository_handler.go
@@ -0,0 +1,96 @@
+/*
+Copyright 2022 The 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 scm
+
+import (
+	"context"
+	"github.com/emicklei/go-restful"
+	"k8s.io/apimachinery/pkg/types"
+	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
+	"kubesphere.io/devops/pkg/kapis/common"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func (h *handler) getGitRepository(req *restful.Request, res *restful.Response) {
+	namespace := common.GetPathParameter(req, common.NamespacePathParameter)
+	repoName := common.GetPathParameter(req, pathParameterGitRepository)
+
+	repo := &v1alpha3.GitRepository{}
+	err := h.Get(context.Background(), types.NamespacedName{
+		Namespace: namespace,
+		Name:      repoName,
+	}, repo)
+	common.Response(req, res, repo, err)
+}
+
+func (h *handler) createGitRepositories(req *restful.Request, res *restful.Response) {
+	namespace := common.GetPathParameter(req, common.NamespacePathParameter)
+	repo := &v1alpha3.GitRepository{}
+	err := req.ReadEntity(repo)
+	ctx := context.Background()
+
+	if err == nil {
+		repo.Namespace = namespace
+		err = h.Create(ctx, repo)
+	}
+	common.Response(req, res, repo, err)
+}
+
+func (h *handler) listGitRepositories(req *restful.Request, res *restful.Response) {
+	namespace := common.GetPathParameter(req, common.NamespacePathParameter)
+	repoList := &v1alpha3.GitRepositoryList{}
+	err := h.List(context.Background(), repoList, client.InNamespace(namespace))
+	common.Response(req, res, repoList, err)
+}
+
+func (h *handler) updateGitRepositories(req *restful.Request, res *restful.Response) {
+	namespace := common.GetPathParameter(req, common.NamespacePathParameter)
+	repoName := common.GetPathParameter(req, pathParameterGitRepository)
+	ctx := context.Background()
+
+	patchRepo := &v1alpha3.GitRepository{}
+	repo := &v1alpha3.GitRepository{}
+
+	err := req.ReadEntity(patchRepo)
+	if err == nil {
+		err = h.Get(ctx, types.NamespacedName{
+			Namespace: namespace,
+			Name:      repoName,
+		}, repo)
+		if err == nil {
+			repo.Spec = patchRepo.Spec
+			err = h.Update(ctx, repo)
+		}
+	}
+	common.Response(req, res, repo, err)
+}
+
+func (h *handler) deleteGitRepositories(req *restful.Request, res *restful.Response) {
+	namespace := common.GetPathParameter(req, common.NamespacePathParameter)
+	repoName := common.GetPathParameter(req, pathParameterGitRepository)
+	ctx := context.Background()
+
+	repo := &v1alpha3.GitRepository{}
+	err := h.Get(ctx, types.NamespacedName{
+		Namespace: namespace,
+		Name:      repoName,
+	}, repo)
+	if err == nil {
+		err = h.Delete(ctx, repo)
+	}
+	common.Response(req, res, repo, err)
+}
diff --git a/pkg/kapis/devops/v1alpha3/scm/route.go b/pkg/kapis/devops/v1alpha3/scm/route.go
index 0303a5d..cc8d25a 100644
--- a/pkg/kapis/devops/v1alpha3/scm/route.go
+++ b/pkg/kapis/devops/v1alpha3/scm/route.go
@@ -19,7 +19,9 @@ package scm
 import (
 	"github.com/emicklei/go-restful"
 	"kubesphere.io/devops/pkg/api"
+	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
 	"kubesphere.io/devops/pkg/client/git"
+	"kubesphere.io/devops/pkg/kapis/common"
 	"net/http"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 )
@@ -28,16 +30,21 @@ var (
 	pathParameterSCM          = restful.PathParameter("scm", "the SCM type")
 	pathParameterOrganization = restful.PathParameter("organization",
 		"The git provider organization. For a GitHub repository address: https://github.com/kubesphere/ks-devops. kubesphere is the organization name")
+	pathParameterGitRepository    = restful.PathParameter("gitrepository", "The GitRepository customs resource")
 	queryParameterSecret          = restful.QueryParameter("secret", "the secret name")
 	queryParameterSecretNamespace = restful.QueryParameter("secretNamespace", "the namespace of target secret")
 )
 
 // RegisterRoutersForSCM registers the APIs which related to scm
 func RegisterRoutersForSCM(k8sClient client.Client, ws *restful.WebService) {
-	handler := newHandler(k8sClient)
+	h := newHandler(k8sClient)
+	registerSCMAPIs(ws, h)
+	registerGitRepositoryAPIs(ws, h)
+}
 
+func registerSCMAPIs(ws *restful.WebService, h *handler) {
 	ws.Route(ws.POST("/scms/{scm}/verify").
-		To(handler.verify).
+		To(h.verify).
 		Param(pathParameterSCM).
 		Param(queryParameterSecret).
 		Param(queryParameterSecretNamespace).
@@ -45,7 +52,7 @@ func RegisterRoutersForSCM(k8sClient client.Client, ws *restful.WebService) {
 		Returns(http.StatusOK, api.StatusOK, git.VerifyResponse{}))
 
 	ws.Route(ws.GET("/scms/{scm}/organizations").
-		To(handler.listOrganizations).
+		To(h.listOrganizations).
 		Param(pathParameterSCM).
 		Param(queryParameterSecret).
 		Param(queryParameterSecretNamespace).
@@ -53,7 +60,7 @@ func RegisterRoutersForSCM(k8sClient client.Client, ws *restful.WebService) {
 		Returns(http.StatusOK, api.StatusOK, []organization{}))
 
 	ws.Route(ws.GET("/scms/{scm}/organizations/{organization}/repositories").
-		To(handler.listRepositories).
+		To(h.listRepositories).
 		Param(pathParameterSCM).
 		Param(pathParameterOrganization).
 		Param(queryParameterSecret).
@@ -61,3 +68,40 @@ func RegisterRoutersForSCM(k8sClient client.Client, ws *restful.WebService) {
 		Doc("List all the readable repositories").
 		Returns(http.StatusOK, api.StatusOK, []repository{}))
 }
+
+func registerGitRepositoryAPIs(ws *restful.WebService, h *handler) {
+	ws.Route(ws.GET("/namespaces/{namespace}/gitrepositories").
+		To(h.listGitRepositories).
+		Param(common.NamespacePathParameter).
+		Doc("List all the GitRepositories").
+		Returns(http.StatusOK, api.StatusOK, []v1alpha3.GitRepository{}))
+
+	ws.Route(ws.POST("/namespaces/{namespace}/gitrepositories/").
+		To(h.createGitRepositories).
+		Param(common.NamespacePathParameter).
+		Reads(v1alpha3.GitRepository{}).
+		Doc("List all the GitRepositories").
+		Returns(http.StatusOK, api.StatusOK, []v1alpha3.GitRepository{}))
+
+	ws.Route(ws.DELETE("/namespaces/{namespace}/gitrepositories/{gitrepository}").
+		To(h.deleteGitRepositories).
+		Param(common.NamespacePathParameter).
+		Param(pathParameterGitRepository).
+		Doc("Delete a GitRepository by name").
+		Returns(http.StatusOK, api.StatusOK, v1alpha3.GitRepository{}))
+
+	ws.Route(ws.GET("/namespaces/{namespace}/gitrepositories/{gitrepository}").
+		To(h.getGitRepository).
+		Param(common.NamespacePathParameter).
+		Param(pathParameterGitRepository).
+		Doc("Get a GitRepository by name").
+		Returns(http.StatusOK, api.StatusOK, v1alpha3.GitRepository{}))
+
+	ws.Route(ws.PUT("/namespaces/{namespace}/gitrepositories/{gitrepository}").
+		To(h.updateGitRepositories).
+		Param(common.NamespacePathParameter).
+		Param(pathParameterGitRepository).
+		Reads(v1alpha3.GitRepository{}).
+		Doc("Update a GitRepositories").
+		Returns(http.StatusOK, api.StatusOK, []v1alpha3.GitRepository{}))
+}
diff --git a/pkg/kapis/devops/v1alpha3/scm/scmhandler.go b/pkg/kapis/devops/v1alpha3/scm/scmhandler.go
index 74eee5c..a44739e 100644
--- a/pkg/kapis/devops/v1alpha3/scm/scmhandler.go
+++ b/pkg/kapis/devops/v1alpha3/scm/scmhandler.go
@@ -28,12 +28,14 @@ import (
 
 // handler holds all the API handlers of SCM
 type handler struct {
-	k8sClient client.Client
+	client.Client
 }
 
 // NewHandler creates the instance of the SCM handler
 func newHandler(k8sClient client.Client) *handler {
-	return &handler{k8sClient: k8sClient}
+	return &handler{
+		Client: k8sClient,
+	}
 }
 
 // verify checks a SCM auth
@@ -53,7 +55,7 @@ func (h *handler) verify(request *restful.Request, response *restful.Response) {
 func (h *handler) getOrganizations(scm, secret, namespace string, size int) (orgs []*goscm.Organization, code int, err error) {
 	factory := git.NewClientFactory(scm, &v1.SecretReference{
 		Namespace: namespace, Name: secret,
-	}, h.k8sClient)
+	}, h.Client)
 
 	var c *goscm.Client
 	if c, err = factory.GetClient(); err == nil {
@@ -73,7 +75,7 @@ func (h *handler) getOrganizations(scm, secret, namespace string, size int) (org
 func (h *handler) getRepositories(scm, org, secret, namespace string, size int) (repos []*goscm.Repository, code int, err error) {
 	factory := git.NewClientFactory(scm, &v1.SecretReference{
 		Namespace: namespace, Name: secret,
-	}, h.k8sClient)
+	}, h.Client)
 
 	var c *goscm.Client
 	if c, err = factory.GetClient(); err == nil {
diff --git a/pkg/kapis/devops/v1alpha3/template/render.go b/pkg/kapis/devops/v1alpha3/template/render.go
index 199f1fa..cca5050 100644
--- a/pkg/kapis/devops/v1alpha3/template/render.go
+++ b/pkg/kapis/devops/v1alpha3/template/render.go
@@ -41,6 +41,8 @@ func render(templateObject v1alpha3.TemplateObject, parameters []Parameter) (v1a
 		Namespace: templateObject.GetNamespace(),
 	}.String()
 	template := tmpl.New(templateName)
+	//TODO Make delimiters configurable
+	template.Delims("$(", ")")
 	if _, err := template.Parse(rawTemplate); err != nil {
 		klog.Errorf("failed to parse template: %s, and err = %v", templateName, err)
 		return nil, errors.NewBadRequest("Failed to render template, please check the pipeline template for syntax error.")


Test patch:
diff --git a/controllers/addon/addon_controller_test.go b/controllers/addon/addon_controller_test.go
index 6055d28..cf55734 100644
--- a/controllers/addon/addon_controller_test.go
+++ b/controllers/addon/addon_controller_test.go
@@ -26,7 +26,7 @@ import (
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/client-go/tools/record"
-	"kubesphere.io/devops/pkg/api/devops/v1alpha1"
+	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 	"testing"
@@ -34,7 +34,7 @@ import (
 
 func TestReconciler_supportedStrategy(t *testing.T) {
 	type args struct {
-		strategy *v1alpha1.AddonStrategy
+		strategy *v1alpha3.AddonStrategy
 	}
 	tests := []struct {
 		name string
@@ -45,18 +45,18 @@ func TestReconciler_supportedStrategy(t *testing.T) {
 		want: false,
 	}, {
 		name: "empty object",
-		args: args{strategy: &v1alpha1.AddonStrategy{}},
+		args: args{strategy: &v1alpha3.AddonStrategy{}},
 		want: false,
 	}, {
 		name: "fake type",
-		args: args{strategy: &v1alpha1.AddonStrategy{
-			Spec: v1alpha1.AddStrategySpec{Type: "fake"},
+		args: args{strategy: &v1alpha3.AddonStrategy{
+			Spec: v1alpha3.AddStrategySpec{Type: "fake"},
 		}},
 		want: false,
 	}, {
 		name: "simple-operator",
-		args: args{strategy: &v1alpha1.AddonStrategy{
-			Spec: v1alpha1.AddStrategySpec{Type: "simple-operator"},
+		args: args{strategy: &v1alpha3.AddonStrategy{
+			Spec: v1alpha3.AddStrategySpec{Type: "simple-operator"},
 		}},
 		want: true,
 	}}
@@ -71,7 +71,7 @@ func TestReconciler_supportedStrategy(t *testing.T) {
 func Test_getTemplate(t *testing.T) {
 	type args struct {
 		tpl   string
-		addon *v1alpha1.Addon
+		addon *v1alpha3.Addon
 	}
 	tests := []struct {
 		name       string
@@ -91,9 +91,9 @@ func Test_getTemplate(t *testing.T) {
 		name: "addon with version",
 		args: args{
 			tpl:   "{{.spec.version}}",
-			addon: &v1alpha1.Addon{Spec: v1alpha1.AddonSpec{Version: "v1alpha1"}},
+			addon: &v1alpha3.Addon{Spec: v1alpha3.AddonSpec{Version: "v1alpha3"}},
 		},
-		wantResult: "v1alpha1",
+		wantResult: "v1alpha3",
 		wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
 			return false
 		},
@@ -110,16 +110,16 @@ func Test_getTemplate(t *testing.T) {
 }
 
 func TestReconciler_addonHandle(t *testing.T) {
-	schema, err := v1alpha1.SchemeBuilder.Register().Build()
+	schema, err := v1alpha3.SchemeBuilder.Register().Build()
 	assert.Nil(t, err)
 
-	strategy := &v1alpha1.AddonStrategy{
+	strategy := &v1alpha3.AddonStrategy{
 		ObjectMeta: metav1.ObjectMeta{
 			Name: "simple-operator",
 		},
-		Spec: v1alpha1.AddStrategySpec{
+		Spec: v1alpha3.AddStrategySpec{
 			Available:      true,
-			Type:           v1alpha1.AddonInstallStrategySimpleOperator,
+			Type:           v1alpha3.AddonInstallStrategySimpleOperator,
 			SimpleOperator: v1.ObjectReference{},
 			Template: `apiVersion: devops.kubesphere.io/v1alpha1
 kind: ReleaserController
@@ -129,9 +129,9 @@ spec:
   webhook: false`,
 		},
 	}
-	addon := &v1alpha1.Addon{
+	addon := &v1alpha3.Addon{
 		ObjectMeta: metav1.ObjectMeta{Name: "ks-releaser", Namespace: "default"},
-		Spec: v1alpha1.AddonSpec{
+		Spec: v1alpha3.AddonSpec{
 			Version: "v0.0.1",
 			Strategy: v1.LocalObjectReference{
 				Name: "simple-operator",
@@ -144,7 +144,7 @@ spec:
 	}
 	type args struct {
 		ctx   context.Context
-		addon *v1alpha1.Addon
+		addon *v1alpha3.Addon
 	}
 	tests := []struct {
 		name    string
@@ -166,10 +166,10 @@ spec:
 		},
 		verify: func(t *testing.T, c client.Client) {
 			var err error
-			addon := &v1alpha1.Addon{}
+			addon := &v1alpha3.Addon{}
 			err = c.Get(context.Background(), types.NamespacedName{Name: "ks-releaser", Namespace: "default"}, addon)
 			assert.Nil(t, err)
-			assert.ElementsMatch(t, []string{v1alpha1.AddonFinalizerName}, addon.Finalizers)
+			assert.ElementsMatch(t, []string{v1alpha3.AddonFinalizerName}, addon.Finalizers)
 
 			obj := &unstructured.Unstructured{}
 			obj.SetKind("ReleaserController")
@@ -203,9 +203,9 @@ spec:
 		},
 		args: args{
 			ctx: context.TODO(),
-			addon: &v1alpha1.Addon{
+			addon: &v1alpha3.Addon{
 				ObjectMeta: metav1.ObjectMeta{Name: "ks-releaser"},
-				Spec: v1alpha1.AddonSpec{
+				Spec: v1alpha3.AddonSpec{
 					Version: "v0.0.1",
 					Strategy: v1.LocalObjectReference{
 						Name: "simple-operator",
@@ -254,7 +254,7 @@ func Test_beingDeleting(t *testing.T) {
 	nowTime := metav1.Now()
 
 	type args struct {
-		addon *v1alpha1.Addon
+		addon *v1alpha3.Addon
 	}
 	tests := []struct {
 		name string
@@ -263,13 +263,13 @@ func Test_beingDeleting(t *testing.T) {
 	}{{
 		name: "empty struct",
 		args: args{
-			addon: &v1alpha1.Addon{},
+			addon: &v1alpha3.Addon{},
 		},
 		want: false,
 	}, {
 		name: "has deletionTimestamp",
 		args: args{
-			addon: &v1alpha1.Addon{
+			addon: &v1alpha3.Addon{
 				ObjectMeta: metav1.ObjectMeta{
 					DeletionTimestamp: &nowTime,
 				},
diff --git a/controllers/addon/operator_controller_test.go b/controllers/addon/operator_controller_test.go
index 746b89d..98c834e 100644
--- a/controllers/addon/operator_controller_test.go
+++ b/controllers/addon/operator_controller_test.go
@@ -23,7 +23,7 @@ import (
 	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
-	"kubesphere.io/devops/pkg/api/devops/v1alpha1"
+	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 	"testing"
@@ -56,7 +56,7 @@ func Test_getStrategyName(t *testing.T) {
 }
 
 func TestOperatorCRDReconciler_operatorsHandle(t *testing.T) {
-	schema, err := v1alpha1.SchemeBuilder.Register().Build()
+	schema, err := v1alpha3.SchemeBuilder.Register().Build()
 	assert.Nil(t, err)
 
 	type fields struct {
@@ -97,25 +97,25 @@ func TestOperatorCRDReconciler_operatorsHandle(t *testing.T) {
 		},
 		wantErr: false,
 		verify: func(t *testing.T, c client.Client) {
-			result := &v1alpha1.AddonStrategy{}
+			result := &v1alpha3.AddonStrategy{}
 			err := c.Get(context.TODO(), types.NamespacedName{
 				Name: "simple-operator-releasercontroller",
 			}, result)
 			assert.Nil(t, err)
 			assert.NotNil(t, result)
-			assert.Equal(t, v1alpha1.AddonInstallStrategy("simple-operator"), result.Spec.Type)
+			assert.Equal(t, v1alpha3.AddonInstallStrategy("simple-operator"), result.Spec.Type)
 			assert.Equal(t, "ReleaserController", result.Spec.SimpleOperator.Kind)
 			assert.Equal(t, "devops.kubesphere.io/v1alpha1", result.Spec.SimpleOperator.APIVersion)
 		},
 	}, {
 		name: "update the existing",
 		fields: fields{
-			Client: fake.NewFakeClientWithScheme(schema, &v1alpha1.AddonStrategy{
+			Client: fake.NewFakeClientWithScheme(schema, &v1alpha3.AddonStrategy{
 				ObjectMeta: metav1.ObjectMeta{
 					Name: "simple-operator-releasercontroller",
 				},
-				Spec: v1alpha1.AddStrategySpec{
-					Type: v1alpha1.AddonInstallStrategySimpleOperator,
+				Spec: v1alpha3.AddStrategySpec{
+					Type: v1alpha3.AddonInstallStrategySimpleOperator,
 					SimpleOperator: v1.ObjectReference{
 						Kind:       "ReleaserController",
 						APIVersion: "devops.kubesphere.io/v1",
@@ -130,14 +130,14 @@ func TestOperatorCRDReconciler_operatorsHandle(t *testing.T) {
 		},
 		wantErr: false,
 		verify: func(t *testing.T, c client.Client) {
-			result := &v1alpha1.AddonStrategy{}
+			result := &v1alpha3.AddonStrategy{}
 			err := c.Get(context.TODO(), types.NamespacedName{
 				Name: "simple-operator-releasercontroller",
 			}, result)
 			assert.Nil(t, err)
 			assert.NotNil(t, result)
 			assert.True(t, result.Spec.Available)
-			assert.Equal(t, v1alpha1.AddonInstallStrategy("simple-operator"), result.Spec.Type)
+			assert.Equal(t, v1alpha3.AddonInstallStrategy("simple-operator"), result.Spec.Type)
 			assert.Equal(t, "ReleaserController", result.Spec.SimpleOperator.Kind)
 			assert.Equal(t, "devops.kubesphere.io/v1alpha1", result.Spec.SimpleOperator.APIVersion)
 		},
diff --git a/controllers/gitrepository/gitrepository_webhook_controller_test.go b/controllers/gitrepository/gitrepository_webhook_controller_test.go
index 0d6fa92..d00753e 100644
--- a/controllers/gitrepository/gitrepository_webhook_controller_test.go
+++ b/controllers/gitrepository/gitrepository_webhook_controller_test.go
@@ -28,7 +28,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/client-go/tools/record"
-	"kubesphere.io/devops/pkg/api/devops/v1alpha1"
+	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
 	"reflect"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client/fake"
@@ -38,7 +38,7 @@ import (
 
 func Test_getRepo(t *testing.T) {
 	type args struct {
-		repo *v1alpha1.GitRepository
+		repo *v1alpha3.GitRepository
 	}
 	tests := []struct {
 		name string
@@ -47,13 +47,13 @@ func Test_getRepo(t *testing.T) {
 	}{{
 		name: "not supported provider",
 		args: args{
-			repo: &v1alpha1.GitRepository{Spec: v1alpha1.GitRepositorySpec{Provider: "fake"}},
+			repo: &v1alpha3.GitRepository{Spec: v1alpha3.GitRepositorySpec{Provider: "fake"}},
 		},
 		want: "",
 	}, {
 		name: "provider is emtpy",
 		args: args{
-			repo: &v1alpha1.GitRepository{Spec: v1alpha1.GitRepositorySpec{
+			repo: &v1alpha3.GitRepository{Spec: v1alpha3.GitRepositorySpec{
 				URL: "https://github.com/linuxsuren/test",
 			}},
 		},
@@ -61,7 +61,7 @@ func Test_getRepo(t *testing.T) {
 	}, {
 		name: "github as the provider",
 		args: args{
-			repo: &v1alpha1.GitRepository{Spec: v1alpha1.GitRepositorySpec{
+			repo: &v1alpha3.GitRepository{Spec: v1alpha3.GitRepositorySpec{
 				Provider: "github",
 				URL:      "https://github.com/linuxsuren/test",
 			}},
@@ -70,7 +70,7 @@ func Test_getRepo(t *testing.T) {
 	}, {
 		name: "gitlab as the provider",
 		args: args{
-			repo: &v1alpha1.GitRepository{Spec: v1alpha1.GitRepositorySpec{
+			repo: &v1alpha3.GitRepository{Spec: v1alpha3.GitRepositorySpec{
 				Provider: "gitlab",
 				URL:      "https://gitlab.com/linuxsuren/test",
 			}},
@@ -143,34 +143,34 @@ func Test_exist(t *testing.T) {
 }
 
 func Test_linkToWebhook(t *testing.T) {
-	schema, err := v1alpha1.SchemeBuilder.Register().Build()
+	schema, err := v1alpha3.SchemeBuilder.Register().Build()
 	assert.Nil(t, err)
 
-	repo := &v1alpha1.GitRepository{
+	repo := &v1alpha3.GitRepository{
 		ObjectMeta: metav1.ObjectMeta{
 			Namespace: "test",
 			Name:      "name",
 		},
 	}
-	webhook := &v1alpha1.Webhook{
+	webhook := &v1alpha3.Webhook{
 		ObjectMeta: metav1.ObjectMeta{
 			Namespace: "test",
 			Name:      "webhook",
 		},
 	}
-	webhookA := &v1alpha1.Webhook{
+	webhookA := &v1alpha3.Webhook{
 		ObjectMeta: metav1.ObjectMeta{
 			Namespace: "test",
 			Name:      "webhook-a",
 			Annotations: map[string]string{
-				v1alpha1.AnnotationKeyGitRepos: "name",
+				v1alpha3.AnnotationKeyGitRepos: "name",
 			},
 		},
 	}
 
 	type args struct {
 		webhook v1.LocalObjectReference
-		repo    *v1alpha1.GitRepository
+		repo    *v1alpha3.GitRepository
 		client  client.Client
 	}
 	tests := []struct {
@@ -182,7 +182,7 @@ func Test_linkToWebhook(t *testing.T) {
 		name: "normal case",
 		args: args{
 			webhook: v1.LocalObjectReference{Name: "webhook"},
-			repo: &v1alpha1.GitRepository{
+			repo: &v1alpha3.GitRepository{
 				ObjectMeta: metav1.ObjectMeta{
 					Namespace: "test",
 					Name:      "name",
@@ -191,14 +191,14 @@ func Test_linkToWebhook(t *testing.T) {
 			client: fake.NewFakeClientWithScheme(schema, repo.DeepCopy(), webhook.DeepCopy()),
 		},
 		check: func(client client.Client) bool {
-			webh := &v1alpha1.Webhook{}
+			webh := &v1alpha3.Webhook{}
 			if err := client.Get(context.TODO(), types.NamespacedName{
 				Namespace: "test",
 				Name:      "webhook",
 			}, webh); err != nil {
 				return false
 			}
-			val := webh.Annotations[v1alpha1.AnnotationKeyGitRepos]
+			val := webh.Annotations[v1alpha3.AnnotationKeyGitRepos]
 			return val == "name"
 		},
 		wantErr: false,
@@ -206,7 +206,7 @@ func Test_linkToWebhook(t *testing.T) {
 		name: "not found the desired webhook",
 		args: args{
 			webhook: v1.LocalObjectReference{Name: "not-found"},
-			repo: &v1alpha1.GitRepository{
+			repo: &v1alpha3.GitRepository{
 				ObjectMeta: metav1.ObjectMeta{
 					Namespace: "test",
 					Name:      "name",
@@ -215,14 +215,14 @@ func Test_linkToWebhook(t *testing.T) {
 			client: fake.NewFakeClientWithScheme(schema, repo.DeepCopy(), webhook.DeepCopy()),
 		},
 		check: func(client client.Client) bool {
-			webh := &v1alpha1.Webhook{}
+			webh := &v1alpha3.Webhook{}
 			if err := client.Get(context.TODO(), types.NamespacedName{
 				Namespace: "test",
 				Name:      "webhook",
 			}, webh); err != nil {
 				return false
 			}
-			val := webh.Annotations[v1alpha1.AnnotationKeyGitRepos]
+			val := webh.Annotations[v1alpha3.AnnotationKeyGitRepos]
 			return val == "name"
 		},
 		wantErr: true,
@@ -230,7 +230,7 @@ func Test_linkToWebhook(t *testing.T) {
 		name: "has the same repo in the annotations",
 		args: args{
 			webhook: v1.LocalObjectReference{Name: "webhook-a"},
-			repo: &v1alpha1.GitRepository{
+			repo: &v1alpha3.GitRepository{
 				ObjectMeta: metav1.ObjectMeta{
 					Namespace: "test",
 					Name:      "name",
@@ -239,14 +239,14 @@ func Test_linkToWebhook(t *testing.T) {
 			client: fake.NewFakeClientWithScheme(schema, repo.DeepCopy(), webhookA.DeepCopy()),
 		},
 		check: func(client client.Client) (result bool) {
-			webh := &v1alpha1.Webhook{}
+			webh := &v1alpha3.Webhook{}
 			if err := client.Get(context.TODO(), types.NamespacedName{
 				Namespace: "test",
 				Name:      "webhook-a",
 			}, webh); err != nil {
 				return false
 			}
-			val := webh.Annotations[v1alpha1.AnnotationKeyGitRepos]
+			val := webh.Annotations[v1alpha3.AnnotationKeyGitRepos]
 			result = val == "name"
 			if !result {
 				fmt.Printf("expect 'name', the actual value is '%s'\n", val)
@@ -320,21 +320,21 @@ func Test_addToSlick(t *testing.T) {
 }
 
 func TestReconciler_linkToWebhooks(t *testing.T) {
-	schema, err := v1alpha1.SchemeBuilder.Register().Build()
+	schema, err := v1alpha3.SchemeBuilder.Register().Build()
 	assert.Nil(t, err)
 
-	repo := v1alpha1.GitRepository{
+	repo := v1alpha3.GitRepository{
 		ObjectMeta: metav1.ObjectMeta{
 			Namespace: "test",
 			Name:      "name",
 		},
-		Spec: v1alpha1.GitRepositorySpec{
+		Spec: v1alpha3.GitRepositorySpec{
 			Webhooks: []v1.LocalObjectReference{{
 				Name: "webhook",
 			}},
 		},
 	}
-	webhook := v1alpha1.Webhook{
+	webhook := v1alpha3.Webhook{
 		ObjectMeta: metav1.ObjectMeta{
 			Namespace: "test",
 			Name:      "webhook",
@@ -346,7 +346,7 @@ func TestReconciler_linkToWebhooks(t *testing.T) {
 		log    logr.Logger
 	}
 	type args struct {
-		repo *v1alpha1.GitRepository
+		repo *v1alpha3.GitRepository
 	}
 	tests := []struct {
 		name    string
@@ -365,14 +365,14 @@ func TestReconciler_linkToWebhooks(t *testing.T) {
 		},
 		wantErr: false,
 		check: func(client client.Client) bool {
-			webh := &v1alpha1.Webhook{}
+			webh := &v1alpha3.Webhook{}
 			if err := client.Get(context.TODO(), types.NamespacedName{
 				Namespace: "test",
 				Name:      "webhook",
 			}, webh); err != nil {
 				return false
 			}
-			val := webh.Annotations[v1alpha1.AnnotationKeyGitRepos]
+			val := webh.Annotations[v1alpha3.AnnotationKeyGitRepos]
 			return val == "name"
 		},
 	}, {
@@ -404,7 +404,7 @@ func TestReconciler_linkToWebhooks(t *testing.T) {
 }
 
 func TestReconciler_getTokenFromSecret(t *testing.T) {
-	schema, err := v1alpha1.SchemeBuilder.Register().Build()
+	schema, err := v1alpha3.SchemeBuilder.Register().Build()
 	assert.Nil(t, err)
 	err = v1.SchemeBuilder.AddToScheme(schema)
 	assert.Nil(t, err)
@@ -529,7 +529,7 @@ func TestReconciler_getTokenFromSecret(t *testing.T) {
 }
 
 func TestReconciler_getGitClient(t *testing.T) {
-	schema, err := v1alpha1.SchemeBuilder.Register().Build()
+	schema, err := v1alpha3.SchemeBuilder.Register().Build()
 	assert.Nil(t, err)
 	err = v1.SchemeBuilder.AddToScheme(schema)
 	assert.Nil(t, err)
@@ -550,7 +550,7 @@ func TestReconciler_getGitClient(t *testing.T) {
 		recorder record.EventRecorder
 	}
 	type args struct {
-		repo *v1alpha1.GitRepository
+		repo *v1alpha3.GitRepository
 	}
 	tests := []struct {
 		name       string
@@ -561,8 +561,8 @@ func TestReconciler_getGitClient(t *testing.T) {
 	}{{
 		name: "not support git provider",
 		args: args{
-			repo: &v1alpha1.GitRepository{
-				Spec: v1alpha1.GitRepositorySpec{
+			repo: &v1alpha3.GitRepository{
+				Spec: v1alpha3.GitRepositorySpec{
 					Provider: "not-support",
 				},
 			},
@@ -578,9 +578,9 @@ func TestReconciler_getGitClient(t *testing.T) {
 			Client: fake.NewFakeClientWithScheme(schema),
 		},
 		args: args{
-			repo: &v1alpha1.GitRepository{
+			repo: &v1alpha3.GitRepository{
 				ObjectMeta: metav1.ObjectMeta{Namespace: "ns"},
-				Spec: v1alpha1.GitRepositorySpec{
+				Spec: v1alpha3.GitRepositorySpec{
 					Provider: "github",
 					Secret: &v1.SecretReference{
 						Name: "basicSecret",
@@ -599,9 +599,9 @@ func TestReconciler_getGitClient(t *testing.T) {
 			Client: fake.NewFakeClientWithScheme(schema, basicSecret.DeepCopy()),
 		},
 		args: args{
-			repo: &v1alpha1.GitRepository{
+			repo: &v1alpha3.GitRepository{
 				ObjectMeta: metav1.ObjectMeta{Namespace: "ns"},
-				Spec: v1alpha1.GitRepositorySpec{
+				Spec: v1alpha3.GitRepositorySpec{
 					Provider: "github",
 					Secret: &v1.SecretReference{
 						Name: "basicSecret",
@@ -619,9 +619,9 @@ func TestReconciler_getGitClient(t *testing.T) {
 			Client: fake.NewFakeClientWithScheme(schema, basicSecret.DeepCopy()),
 		},
 		args: args{
-			repo: &v1alpha1.GitRepository{
+			repo: &v1alpha3.GitRepository{
 				ObjectMeta: metav1.ObjectMeta{Namespace: "ns"},
-				Spec: v1alpha1.GitRepositorySpec{
+				Spec: v1alpha3.GitRepositorySpec{
 					Provider: "gitlab",
 					Secret: &v1.SecretReference{
 						Name: "basicSecret",
diff --git a/controllers/gitrepository/webhook_notify_test.go b/controllers/gitrepository/webhook_notify_test.go
index 56af616..f362a0d 100644
--- a/controllers/gitrepository/webhook_notify_test.go
+++ b/controllers/gitrepository/webhook_notify_test.go
@@ -24,17 +24,17 @@ import (
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/client-go/tools/record"
-	"kubesphere.io/devops/pkg/api/devops/v1alpha1"
+	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 	"testing"
 )
 
 func TestWebhookReconciler_notifyGitRepo(t *testing.T) {
-	schema, err := v1alpha1.SchemeBuilder.Register().Build()
+	schema, err := v1alpha3.SchemeBuilder.Register().Build()
 	assert.Nil(t, err)
 
-	gitRepo := &v1alpha1.GitRepository{
+	gitRepo := &v1alpha3.GitRepository{
 		ObjectMeta: v1.ObjectMeta{
 			Namespace: "ns",
 			Name:      "repo",
@@ -70,14 +70,14 @@ func TestWebhookReconciler_notifyGitRepo(t *testing.T) {
 			return false
 		},
 		verify: func(t assert.TestingT, c client.Client) {
-			repo := &v1alpha1.GitRepository{}
+			repo := &v1alpha3.GitRepository{}
 			err := c.Get(context.TODO(), types.NamespacedName{
 				Namespace: "ns",
 				Name:      "repo",
 			}, repo)
 			assert.Nil(t, err)
 
-			assert.NotEmpty(t, repo.Annotations[v1alpha1.AnnotationKeyWebhookUpdates])
+			assert.NotEmpty(t, repo.Annotations[v1alpha3.AnnotationKeyWebhookUpdates])
 		},
 	}}
 	for _, tt := range tests {
@@ -94,16 +94,16 @@ func TestWebhookReconciler_notifyGitRepo(t *testing.T) {
 }
 
 func TestWebhookReconciler_notifyGitRepos(t *testing.T) {
-	schema, err := v1alpha1.SchemeBuilder.Register().Build()
+	schema, err := v1alpha3.SchemeBuilder.Register().Build()
 	assert.Nil(t, err)
 
-	gitRepo := &v1alpha1.GitRepository{
+	gitRepo := &v1alpha3.GitRepository{
 		ObjectMeta: v1.ObjectMeta{
 			Namespace: "ns",
 			Name:      "repo",
 		},
 	}
-	gitRepoA := &v1alpha1.GitRepository{
+	gitRepoA := &v1alpha3.GitRepository{
 		ObjectMeta: v1.ObjectMeta{
 			Namespace: "ns",
 			Name:      "repo-a",
@@ -140,21 +140,21 @@ func TestWebhookReconciler_notifyGitRepos(t *testing.T) {
 			return false
 		},
 		verify: func(t assert.TestingT, c client.Client) {
-			repo1 := &v1alpha1.GitRepository{}
+			repo1 := &v1alpha3.GitRepository{}
 			err := c.Get(context.TODO(), types.NamespacedName{
 				Namespace: "ns",
 				Name:      "repo",
 			}, repo1)
 			assert.Nil(t, err)
-			assert.NotEmpty(t, repo1.Annotations[v1alpha1.AnnotationKeyWebhookUpdates])
+			assert.NotEmpty(t, repo1.Annotations[v1alpha3.AnnotationKeyWebhookUpdates])
 
-			repo2 := &v1alpha1.GitRepository{}
+			repo2 := &v1alpha3.GitRepository{}
 			err = c.Get(context.TODO(), types.NamespacedName{
 				Namespace: "ns",
 				Name:      "repo-a",
 			}, repo2)
 			assert.Nil(t, err)
-			assert.NotEmpty(t, repo2.Annotations[v1alpha1.AnnotationKeyWebhookUpdates])
+			assert.NotEmpty(t, repo2.Annotations[v1alpha3.AnnotationKeyWebhookUpdates])
 		},
 	}, {
 		name: "has errors",
@@ -171,21 +171,21 @@ func TestWebhookReconciler_notifyGitRepos(t *testing.T) {
 			return true
 		},
 		verify: func(t assert.TestingT, c client.Client) {
-			repo1 := &v1alpha1.GitRepository{}
+			repo1 := &v1alpha3.GitRepository{}
 			err := c.Get(context.TODO(), types.NamespacedName{
 				Namespace: "ns",
 				Name:      "repo",
 			}, repo1)
 			assert.Nil(t, err)
-			assert.NotEmpty(t, repo1.Annotations[v1alpha1.AnnotationKeyWebhookUpdates])
+			assert.NotEmpty(t, repo1.Annotations[v1alpha3.AnnotationKeyWebhookUpdates])
 
-			repo2 := &v1alpha1.GitRepository{}
+			repo2 := &v1alpha3.GitRepository{}
 			err = c.Get(context.TODO(), types.NamespacedName{
 				Namespace: "ns",
 				Name:      "repo-a",
 			}, repo2)
 			assert.Nil(t, err)
-			assert.NotEmpty(t, repo2.Annotations[v1alpha1.AnnotationKeyWebhookUpdates])
+			assert.NotEmpty(t, repo2.Annotations[v1alpha3.AnnotationKeyWebhookUpdates])
 		},
 	}}
 	for _, tt := range tests {
diff --git a/pkg/api/devops/v1alpha1/addon_strategy_test.go b/pkg/api/devops/v1alpha3/addon_strategy_test.go
similarity index 98%
rename from pkg/api/devops/v1alpha1/addon_strategy_test.go
rename to pkg/api/devops/v1alpha3/addon_strategy_test.go
index 479eed3..665e517 100644
--- a/pkg/api/devops/v1alpha1/addon_strategy_test.go
+++ b/pkg/api/devops/v1alpha3/addon_strategy_test.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package v1alpha1
+package v1alpha3
 
 import (
 	"github.com/stretchr/testify/assert"
diff --git a/pkg/client/git/client_test.go b/pkg/client/git/client_test.go
index 814bafa..1db4cf0 100644
--- a/pkg/client/git/client_test.go
+++ b/pkg/client/git/client_test.go
@@ -24,6 +24,7 @@ import (
 	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"kubesphere.io/devops/pkg/api/devops/v1alpha1"
+	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 	"strings"
@@ -82,7 +83,7 @@ func TestGetClient(t *testing.T) {
 		k8sClient client.Client
 	}
 	type args struct {
-		repo *v1alpha1.GitRepository
+		repo *v1alpha3.GitRepository
 	}
 	tests := []struct {
 		name       string
@@ -93,8 +94,8 @@ func TestGetClient(t *testing.T) {
 	}{{
 		name: "not support git provider",
 		args: args{
-			repo: &v1alpha1.GitRepository{
-				Spec: v1alpha1.GitRepositorySpec{
+			repo: &v1alpha3.GitRepository{
+				Spec: v1alpha3.GitRepositorySpec{
 					Provider: "not-support",
 				},
 			},
diff --git a/pkg/kapis/devops/v1alpha3/scm/gitrepository_handler_test.go b/pkg/kapis/devops/v1alpha3/scm/gitrepository_handler_test.go
new file mode 100644
index 0000000..9e73ceb
--- /dev/null
+++ b/pkg/kapis/devops/v1alpha3/scm/gitrepository_handler_test.go
@@ -0,0 +1,179 @@
+/*
+Copyright 2022 The 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 scm
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"github.com/emicklei/go-restful"
+	"github.com/h2non/gock"
+	"github.com/stretchr/testify/assert"
+	"io"
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	runtimeSchema "k8s.io/apimachinery/pkg/runtime/schema"
+	"kubesphere.io/devops/pkg/api"
+	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
+	ksruntime "kubesphere.io/devops/pkg/apiserver/runtime"
+	"kubesphere.io/devops/pkg/constants"
+	"net/http"
+	"net/http/httptest"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+	"testing"
+)
+
+func TestGitRepositoryAPI(t *testing.T) {
+	schema, err := v1alpha3.SchemeBuilder.Register().Build()
+	assert.Nil(t, err)
+
+	err = v1.SchemeBuilder.AddToScheme(schema)
+	assert.Nil(t, err)
+
+	repo := &v1alpha3.GitRepository{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: "ns",
+			Name:      "repo-1",
+		},
+	}
+
+	type args struct {
+		method  string
+		uri     string
+		getBody func() io.Reader
+	}
+	tests := []struct {
+		name         string
+		args         args
+		getInstances func() []runtime.Object
+		verify       func(code int, response []byte, t *testing.T)
+	}{{
+		name: "get repository list",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/namespaces/ns/gitrepositories",
+		},
+		getInstances: func() []runtime.Object {
+			return []runtime.Object{repo.DeepCopy()}
+		},
+		verify: func(code int, response []byte, t *testing.T) {
+			assert.Equal(t, 200, code)
+
+			repoList := v1alpha3.GitRepositoryList{}
+			err := json.Unmarshal(response, &repoList)
+			assert.Nil(t, err)
+			assert.Equal(t, 1, len(repoList.Items))
+		},
+	}, {
+		name: "get a particular repository object",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/namespaces/ns/gitrepositories/repo-1",
+		},
+		getInstances: func() []runtime.Object {
+			return []runtime.Object{repo.DeepCopy()}
+		},
+		verify: func(code int, response []byte, t *testing.T) {
+			assert.Equal(t, 200, code)
+
+			repo := v1alpha3.GitRepository{}
+			err := json.Unmarshal(response, &repo)
+			assert.Nil(t, err)
+			assert.Equal(t, "repo-1", repo.Name)
+		},
+	}, {
+		name: "delete a particular repository object",
+		args: args{
+			method: http.MethodDelete,
+			uri:    "/namespaces/ns/gitrepositories/repo-1",
+		},
+		getInstances: func() []runtime.Object {
+			return []runtime.Object{repo.DeepCopy()}
+		},
+		verify: func(code int, response []byte, t *testing.T) {
+			assert.Equal(t, 200, code)
+
+			repo := v1alpha3.GitRepository{}
+			err := json.Unmarshal(response, &repo)
+			assert.Nil(t, err)
+			assert.Equal(t, "repo-1", repo.Name)
+		},
+	}, {
+		name: "create a repository",
+		args: args{
+			method: http.MethodPost,
+			uri:    "/namespaces/ns/gitrepositories",
+			getBody: func() io.Reader {
+				data, _ := json.Marshal(repo.DeepCopy())
+				return bytes.NewBuffer(data)
+			},
+		},
+		getInstances: func() []runtime.Object {
+			return []runtime.Object{}
+		},
+		verify: func(code int, response []byte, t *testing.T) {
+			assert.Equal(t, 200, code)
+		},
+	}, {
+		name: "update a repository",
+		args: args{
+			method: http.MethodPut,
+			uri:    "/namespaces/ns/gitrepositories/repo-1",
+			getBody: func() io.Reader {
+				newRepo := repo.DeepCopy()
+				newRepo.Spec.URL = "http://newrepo.com"
+				data, _ := json.Marshal(newRepo)
+				return bytes.NewBuffer(data)
+			},
+		},
+		getInstances: func() []runtime.Object {
+			return []runtime.Object{repo.DeepCopy()}
+		},
+		verify: func(code int, response []byte, t *testing.T) {
+			assert.Equal(t, 200, code)
+
+			repo := &v1alpha3.GitRepository{}
+			err := json.Unmarshal(response, repo)
+			assert.Nil(t, err)
+			assert.Equal(t, "http://newrepo.com", repo.Spec.URL)
+		},
+	}}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			defer gock.Off()
+
+			var requestBody io.Reader
+			if tt.args.getBody != nil {
+				requestBody = tt.args.getBody()
+			}
+			httpRequest, _ := http.NewRequest(tt.args.method,
+				"http://fake.com/kapis/devops.kubesphere.io/v1alpha3"+tt.args.uri, requestBody)
+			httpRequest = httpRequest.WithContext(context.WithValue(context.TODO(), constants.K8SToken, constants.ContextKeyK8SToken("")))
+			httpRequest.Header.Set("Content-Type", "application/json")
+
+			ws := ksruntime.NewWebService(runtimeSchema.GroupVersion{Group: api.GroupName, Version: "v1alpha3"})
+			RegisterRoutersForSCM(fake.NewFakeClientWithScheme(schema, tt.getInstances()...), ws)
+			container := restful.NewContainer()
+			container.Add(ws)
+
+			httpWriter := httptest.NewRecorder()
+			container.Dispatch(httpWriter, httpRequest)
+			tt.verify(httpWriter.Code, httpWriter.Body.Bytes(), t)
+		})
+	}
+}
diff --git a/pkg/kapis/devops/v1alpha3/template/render_test.go b/pkg/kapis/devops/v1alpha3/template/render_test.go
index a2cdc5e..2bc5ba1 100644
--- a/pkg/kapis/devops/v1alpha3/template/render_test.go
+++ b/pkg/kapis/devops/v1alpha3/template/render_test.go
@@ -58,7 +58,7 @@ func Test_render(t *testing.T) {
 	}, {
 		name: "Should render template with parameters",
 		args: args{
-			template: createTemplate("fake-name", "The number should be {{ .params.number }}"),
+			template: createTemplate("fake-name", "The number should be $(.params.number)"),
 			parameters: []Parameter{{
 				Name:  "number",
 				Value: "233",
@@ -72,7 +72,7 @@ func Test_render(t *testing.T) {
 	}, {
 		name: "Should render incorrectly without corresponding parameter",
 		args: args{
-			template: createTemplate("fake-name", "The number should be: {{ .params.number }}"),
+			template: createTemplate("fake-name", "The number should be: $(.params.number)"),
 			parameters: []Parameter{{
 				Name:  "name",
 				Value: "fake-name",
@@ -86,7 +86,7 @@ func Test_render(t *testing.T) {
 	}, {
 		name: "Should return error if the template is invalid",
 		args: args{
-			template: createTemplate("fake-name", "{{}}"),
+			template: createTemplate("fake-name", "$()"),
 			parameters: []Parameter{{
 				Name:  "name",
 				Value: "fake-name",
@@ -96,6 +96,33 @@ func Test_render(t *testing.T) {
 			assert.Nil(t, template)
 		},
 		wantErr: assert.Error,
+	}, {
+		name: "Should escape delim",
+		args: args{
+			template: createTemplate("fake-name", "$(`$(pwd)`)"),
+		},
+		verify: func(t *testing.T, template v1alpha3.TemplateObject) {
+			got := template.GetAnnotations()[devops.GroupName+devops.RenderResultAnnoKey]
+			assert.Equal(t, "$(pwd)", got)
+		},
+		wantErr: assert.NoError,
+	}, {
+		name: "Should render conditionally",
+		args: args{
+			// ${}
+			template: createTemplate("fake-name", "$(if .params.valid)Valid$(else)Invalid$(end)"),
+			parameters: []Parameter{
+				{
+					Name:  "valid",
+					Value: true,
+				},
+			},
+		},
+		verify: func(t *testing.T, template v1alpha3.TemplateObject) {
+			got := template.GetAnnotations()[devops.GroupName+devops.RenderResultAnnoKey]
+			assert.Equal(t, "Valid", got)
+		},
+		wantErr: assert.NoError,
 	}}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {