Repository: kubesphere/ks-devops
Pull request #502 (2022-03-20 12:52:42)

Add operation data into Argo Application
### What type of PR is this?

/kind feature
/kind cleanup

### What this PR does / why we need it:

As I mentioned in the title, I added operation data into Argo Application and move argoApp field downward.

### Which issue(s) this PR fixes:

Fixes #

### Special notes for reviewers:

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??

```release-note
None
```

/cc @kubesphere/sig-devops 


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

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

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

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

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

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

```diff
@@            Coverage Diff             @@
##           master     #502      +/-   ##
==========================================
- Coverage   77.23%   77.20%   -0.03%     
==========================================
  Files         134      135       +1     
  Lines       22874    22930      +56     
==========================================
+ Hits        17666    17703      +37     
- Misses       5011     5030      +19     
  Partials      197      197              
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `77.20% <83.87%> (-0.03%)` | :arrow_down: |

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

| [Impacted Files](https://codecov.io/gh/kubesphere/ks-devops/pull/502?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere) | Coverage Δ | |
|---|---|---|
| [pkg/api/gitops/v1alpha1/application.go](https://codecov.io/gh/kubesphere/ks-devops/pull/502/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-cGtnL2FwaS9naXRvcHMvdjFhbHBoYTEvYXBwbGljYXRpb24uZ28=) | `100.00% <ø> (ø)` | |
| [controllers/argocd/application\_controller.go](https://codecov.io/gh/kubesphere/ks-devops/pull/502/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-Y29udHJvbGxlcnMvYXJnb2NkL2FwcGxpY2F0aW9uX2NvbnRyb2xsZXIuZ28=) | `58.92% <75.00%> (-3.34%)` | :arrow_down: |
| [controllers/argocd/utils.go](https://codecov.io/gh/kubesphere/ks-devops/pull/502/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-Y29udHJvbGxlcnMvYXJnb2NkL3V0aWxzLmdv) | `100.00% <100.00%> (ø)` | |
| [pkg/kapis/devops/v1alpha3/pipelinerun/handler.go](https://codecov.io/gh/kubesphere/ks-devops/pull/502/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-cGtnL2thcGlzL2Rldm9wcy92MWFscGhhMy9waXBlbGluZXJ1bi9oYW5kbGVyLmdv) | `30.30% <0.00%> (-2.36%)` | :arrow_down: |
| [pkg/kapis/devops/v1alpha2/devops.go](https://codecov.io/gh/kubesphere/ks-devops/pull/502/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-cGtnL2thcGlzL2Rldm9wcy92MWFscGhhMi9kZXZvcHMuZ28=) | `25.04% <0.00%> (ø)` | |
| [pkg/kapis/devops/v1alpha2/register.go](https://codecov.io/gh/kubesphere/ks-devops/pull/502/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-cGtnL2thcGlzL2Rldm9wcy92MWFscGhhMi9yZWdpc3Rlci5nbw==) | `91.80% <0.00%> (ø)` | |
| [pkg/kapis/devops/v1alpha3/register.go](https://codecov.io/gh/kubesphere/ks-devops/pull/502/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-cGtnL2thcGlzL2Rldm9wcy92MWFscGhhMy9yZWdpc3Rlci5nbw==) | `100.00% <0.00%> (ø)` | |
| [pkg/kapis/devops/v1alpha3/pipelinerun/register.go](https://codecov.io/gh/kubesphere/ks-devops/pull/502/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-cGtnL2thcGlzL2Rldm9wcy92MWFscGhhMy9waXBlbGluZXJ1bi9yZWdpc3Rlci5nbw==) | `100.00% <0.00%> (ø)` | |
| [...ns/devopscredential/devopscredential\_controller.go](https://codecov.io/gh/kubesphere/ks-devops/pull/502/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-Y29udHJvbGxlcnMvamVua2lucy9kZXZvcHNjcmVkZW50aWFsL2Rldm9wc2NyZWRlbnRpYWxfY29udHJvbGxlci5nbw==) | `36.73% <0.00%> (+3.40%)` | :arrow_up: |
| [...ontrollers/jenkins/pipeline/pipeline\_controller.go](https://codecov.io/gh/kubesphere/ks-devops/pull/502/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-Y29udHJvbGxlcnMvamVua2lucy9waXBlbGluZS9waXBlbGluZV9jb250cm9sbGVyLmdv) | `43.18% <0.00%> (+3.78%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/kubesphere/ks-devops/pull/502?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/502?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere). Last update [1493701...7796ba2](https://codecov.io/gh/kubesphere/ks-devops/pull/502?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/config/crd/bases/gitops.kubesphere.io_applications.yaml b/config/crd/bases/gitops.kubesphere.io_applications.yaml
index 1629b4f..d15c9b3 100644
--- a/config/crd/bases/gitops.kubesphere.io_applications.yaml
+++ b/config/crd/bases/gitops.kubesphere.io_applications.yaml
@@ -37,149 +37,673 @@ spec:
             description: ApplicationSpec is the specification of the Application
             properties:
               argoApp:
-                description: ArgoApplication represents an ArgoCD Application The
-                  fields simply are copied from the argo-cd project
+                description: ArgoApplication is a definition of Argo Application resource.
                 properties:
-                  destination:
-                    description: Destination is a reference to the target Kubernetes
-                      server and namespace
+                  operation:
+                    description: Operation contains information about a requested
+                      or running operation
                     properties:
-                      name:
-                        description: Name is an alternate way of specifying the target
-                          cluster by its symbolic name
-                        type: string
-                      namespace:
-                        description: Namespace specifies the target namespace for
-                          the application's resources. The namespace will only be
-                          set for namespace-scoped resources that have not set a value
-                          for .metadata.namespace
-                        type: string
-                      server:
-                        description: Server specifies the URL of the target cluster
-                          and must be set to the Kubernetes control plane API
-                        type: string
+                      info:
+                        description: Info is a list of informational items for this
+                          operation
+                        items:
+                          description: Info represents a name and value
+                          properties:
+                            name:
+                              type: string
+                            value:
+                              type: string
+                          required:
+                          - name
+                          - value
+                          type: object
+                        type: array
+                      initiatedBy:
+                        description: InitiatedBy contains information about who initiated
+                          the operations
+                        properties:
+                          automated:
+                            description: Automated is set to true if operation was
+                              initiated automatically by the application controller.
+                            type: boolean
+                          username:
+                            description: Username contains the name of a user who
+                              started operation
+                            type: string
+                        type: object
+                      retry:
+                        description: Retry controls the strategy to apply if a sync
+                          fails
+                        properties:
+                          backoff:
+                            description: Backoff controls how to backoff on subsequent
+                              retries of failed syncs
+                            properties:
+                              duration:
+                                description: Duration is the amount to back off. Default
+                                  unit is seconds, but could also be a duration (e.g.
+                                  "2m", "1h")
+                                type: string
+                              factor:
+                                description: Factor is a factor to multiply the base
+                                  duration after each failed retry
+                                format: int64
+                                type: integer
+                              maxDuration:
+                                description: MaxDuration is the maximum amount of
+                                  time allowed for the backoff strategy
+                                type: string
+                            type: object
+                          limit:
+                            description: Limit is the maximum number of attempts for
+                              retrying a failed sync. If set to 0, no retries will
+                              be performed.
+                            format: int64
+                            type: integer
+                        type: object
+                      sync:
+                        description: Sync contains parameters for the operation
+                        properties:
+                          dryRun:
+                            description: DryRun specifies to perform a `kubectl apply
+                              --dry-run` without actually performing the sync
+                            type: boolean
+                          manifests:
+                            description: Manifests is an optional field that overrides
+                              sync source with a local directory for development
+                            items:
+                              type: string
+                            type: array
+                          prune:
+                            description: Prune specifies to delete resources from
+                              the cluster that are no longer tracked in git
+                            type: boolean
+                          resources:
+                            description: Resources describes which resources shall
+                              be part of the sync
+                            items:
+                              description: SyncOperationResource contains resources
+                                to sync.
+                              properties:
+                                group:
+                                  type: string
+                                kind:
+                                  type: string
+                                name:
+                                  type: string
+                                namespace:
+                                  type: string
+                              required:
+                              - kind
+                              - name
+                              type: object
+                            type: array
+                          revision:
+                            description: Revision is the revision (Git) or chart version
+                              (Helm) which to sync the application to If omitted,
+                              will use the revision specified in app spec.
+                            type: string
+                          source:
+                            description: Source overrides the source definition set
+                              in the application. This is typically set in a Rollback
+                              operation and is nil during a Sync operation
+                            properties:
+                              chart:
+                                description: Chart is a Helm chart name, and must
+                                  be specified for applications sourced from a Helm
+                                  repo.
+                                type: string
+                              directory:
+                                description: Directory holds path/directory specific
+                                  options
+                                properties:
+                                  exclude:
+                                    description: Exclude contains a glob pattern to
+                                      match paths against that should be explicitly
+                                      excluded from being used during manifest generation
+                                    type: string
+                                  include:
+                                    description: Include contains a glob pattern to
+                                      match paths against that should be explicitly
+                                      included during manifest generation
+                                    type: string
+                                  jsonnet:
+                                    description: Jsonnet holds options specific to
+                                      Jsonnet
+                                    properties:
+                                      extVars:
+                                        description: ExtVars is a list of Jsonnet
+                                          External Variables
+                                        items:
+                                          description: JsonnetVar represents a variable
+                                            to be passed to jsonnet during manifest
+                                            generation
+                                          properties:
+                                            code:
+                                              type: boolean
+                                            name:
+                                              type: string
+                                            value:
+                                              type: string
+                                          required:
+                                          - name
+                                          - value
+                                          type: object
+                                        type: array
+                                      libs:
+                                        description: Additional library search dirs
+                                        items:
+                                          type: string
+                                        type: array
+                                      tlas:
+                                        description: TLAS is a list of Jsonnet Top-level
+                                          Arguments
+                                        items:
+                                          description: JsonnetVar represents a variable
+                                            to be passed to jsonnet during manifest
+                                            generation
+                                          properties:
+                                            code:
+                                              type: boolean
+                                            name:
+                                              type: string
+                                            value:
+                                              type: string
+                                          required:
+                                          - name
+                                          - value
+                                          type: object
+                                        type: array
+                                    type: object
+                                  recurse:
+                                    description: Recurse specifies whether to scan
+                                      a directory recursively for manifests
+                                    type: boolean
+                                type: object
+                              helm:
+                                description: Helm holds helm specific options
+                                properties:
+                                  fileParameters:
+                                    description: FileParameters are file parameters
+                                      to the helm template
+                                    items:
+                                      description: HelmFileParameter is a file parameter
+                                        that's passed to helm template during manifest
+                                        generation
+                                      properties:
+                                        name:
+                                          description: Name is the name of the Helm
+                                            parameter
+                                          type: string
+                                        path:
+                                          description: Path is the path to the file
+                                            containing the values for the Helm parameter
+                                          type: string
+                                      type: object
+                                    type: array
+                                  ignoreMissingValueFiles:
+                                    description: IgnoreMissingValueFiles prevents
+                                      helm template from failing when valueFiles do
+                                      not exist locally by not appending them to helm
+                                      template --values
+                                    type: boolean
+                                  parameters:
+                                    description: Parameters is a list of Helm parameters
+                                      which are passed to the helm template command
+                                      upon manifest generation
+                                    items:
+                                      description: HelmParameter is a parameter that's
+                                        passed to helm template during manifest generation
+                                      properties:
+                                        forceString:
+                                          description: ForceString determines whether
+                                            to tell Helm to interpret booleans and
+                                            numbers as strings
+                                          type: boolean
+                                        name:
+                                          description: Name is the name of the Helm
+                                            parameter
+                                          type: string
+                                        value:
+                                          description: Value is the value for the
+                                            Helm parameter
+                                          type: string
+                                      type: object
+                                    type: array
+                                  passCredentials:
+                                    description: PassCredentials pass credentials
+                                      to all domains (Helm's --pass-credentials)
+                                    type: boolean
+                                  releaseName:
+                                    description: ReleaseName is the Helm release name
+                                      to use. If omitted it will use the application
+                                      name
+                                    type: string
+                                  skipCrds:
+                                    description: SkipCrds skips custom resource definition
+                                      installation step (Helm's --skip-crds)
+                                    type: boolean
+                                  valueFiles:
+                                    description: ValuesFiles is a list of Helm value
+                                      files to use when generating a template
+                                    items:
+                                      type: string
+                                    type: array
+                                  values:
+                                    description: Values specifies Helm values to be
+                                      passed to helm template, typically defined as
+                                      a block
+                                    type: string
+                                  version:
+                                    description: Version is the Helm version to use
+                                      for templating (either "2" or "3")
+                                    type: string
+                                type: object
+                              ksonnet:
+                                description: Ksonnet holds ksonnet specific options
+                                properties:
+                                  environment:
+                                    description: Environment is a ksonnet application
+                                      environment name
+                                    type: string
+                                  parameters:
+                                    description: Parameters are a list of ksonnet
+                                      component parameter override values
+                                    items:
+                                      description: KsonnetParameter is a ksonnet component
+                                        parameter
+                                      properties:
+                                        component:
+                                          type: string
+                                        name:
+                                          type: string
+                                        value:
+                                          type: string
+                                      required:
+                                      - name
+                                      - value
+                                      type: object
+                                    type: array
+                                type: object
+                              kustomize:
+                                description: Kustomize holds kustomize specific options
+                                properties:
+                                  commonAnnotations:
+                                    additionalProperties:
+                                      type: string
+                                    description: CommonAnnotations is a list of additional
+                                      annotations to add to rendered manifests
+                                    type: object
+                                  commonLabels:
+                                    additionalProperties:
+                                      type: string
+                                    description: CommonLabels is a list of additional
+                                      labels to add to rendered manifests
+                                    type: object
+                                  forceCommonAnnotations:
+                                    description: ForceCommonAnnotations specifies
+                                      whether to force applying common annotations
+                                      to resources for Kustomize apps
+                                    type: boolean
+                                  forceCommonLabels:
+                                    description: ForceCommonLabels specifies whether
+                                      to force applying common labels to resources
+                                      for Kustomize apps
+                                    type: boolean
+                                  images:
+                                    description: Images is a list of Kustomize image
+                                      override specifications
+                                    items:
+                                      description: KustomizeImage represents a Kustomize
+                                        image definition in the format [old_image_name=]<image_name>:<image_tag>
+                                      type: string
+                                    type: array
+                                  namePrefix:
+                                    description: NamePrefix is a prefix appended to
+                                      resources for Kustomize apps
+                                    type: string
+                                  nameSuffix:
+                                    description: NameSuffix is a suffix appended to
+                                      resources for Kustomize apps
+                                    type: string
+                                  version:
+                                    description: Version controls which version of
+                                      Kustomize to use for rendering manifests
+                                    type: string
+                                type: object
+                              path:
+                                description: Path is a directory path within the Git
+                                  repository, and is only valid for applications sourced
+                                  from Git.
+                                type: string
+                              plugin:
+                                description: ConfigManagementPlugin holds config management
+                                  plugin specific options
+                                properties:
+                                  env:
+                                    description: Env is a list of environment variable
+                                      entries
+                                    items:
+                                      description: EnvEntry represents an entry in
+                                        the application's environment
+                                      properties:
+                                        name:
+                                          description: Name is the name of the variable,
+                                            usually expressed in uppercase
+                                          type: string
+                                        value:
+                                          description: Value is the value of the variable
+                                          type: string
+                                      required:
+                                      - name
+                                      - value
+                                      type: object
+                                    type: array
+                                  name:
+                                    type: string
+                                type: object
+                              repoURL:
+                                description: RepoURL is the URL to the repository
+                                  (Git or Helm) that contains the application manifests
+                                type: string
+                              targetRevision:
+                                description: TargetRevision defines the revision of
+                                  the source to sync the application to. In case of
+                                  Git, this can be commit, tag, or branch. If omitted,
+                                  will equal to HEAD. In case of Helm, this is a semver
+                                  tag for the Chart's version.
+                                type: string
+                            required:
+                            - repoURL
+                            type: object
+                          syncOptions:
+                            description: SyncOptions provide per-sync sync-options,
+                              e.g. Validate=false
+                            items:
+                              type: string
+                            type: array
+                          syncStrategy:
+                            description: SyncStrategy describes how to perform the
+                              sync
+                            properties:
+                              apply:
+                                description: Apply will perform a `kubectl apply`
+                                  to perform the sync.
+                                properties:
+                                  force:
+                                    description: Force indicates whether or not to
+                                      supply the --force flag to `kubectl apply`.
+                                      The --force flag deletes and re-create the resource,
+                                      when PATCH encounters conflict and has retried
+                                      for 5 times.
+                                    type: boolean
+                                type: object
+                              hook:
+                                description: Hook will submit any referenced resources
+                                  to perform the sync. This is the default strategy
+                                properties:
+                                  force:
+                                    description: Force indicates whether or not to
+                                      supply the --force flag to `kubectl apply`.
+                                      The --force flag deletes and re-create the resource,
+                                      when PATCH encounters conflict and has retried
+                                      for 5 times.
+                                    type: boolean
+                                type: object
+                            type: object
+                        type: object
                     type: object
-                  ignoreDifferences:
-                    description: IgnoreDifferences is a list of resources and their
-                      fields which should be ignored during comparison
-                    items:
-                      description: ResourceIgnoreDifferences contains resource filter
-                        and list of json paths which should be ignored during comparison
-                        with live state.
-                      properties:
-                        group:
-                          type: string
-                        jqPathExpressions:
-                          items:
+                  spec:
+                    description: ArgoApplicationSpec represents an ArgoCD Application
+                      The fields simply are copied from the argo-cd project
+                    properties:
+                      destination:
+                        description: Destination is a reference to the target Kubernetes
+                          server and namespace
+                        properties:
+                          name:
+                            description: Name is an alternate way of specifying the
+                              target cluster by its symbolic name
                             type: string
-                          type: array
-                        jsonPointers:
-                          items:
+                          namespace:
+                            description: Namespace specifies the target namespace
+                              for the application's resources. The namespace will
+                              only be set for namespace-scoped resources that have
+                              not set a value for .metadata.namespace
                             type: string
-                          type: array
-                        kind:
-                          type: string
-                        managedFieldsManagers:
-                          description: ManagedFieldsManagers is a list of trusted
-                            managers. Fields mutated by those managers will take precedence
-                            over the desired state defined in the SCM and won't be
-                            displayed in diffs
-                          items:
+                          server:
+                            description: Server specifies the URL of the target cluster
+                              and must be set to the Kubernetes control plane API
                             type: string
-                          type: array
-                        name:
-                          type: string
-                        namespace:
-                          type: string
-                      required:
-                      - kind
-                      type: object
-                    type: array
-                  info:
-                    description: Info contains a list of information (URLs, email
-                      addresses, and plain text) that relates to the application
-                    items:
-                      description: Info represents a name and value
-                      properties:
-                        name:
-                          type: string
-                        value:
-                          type: string
-                      required:
-                      - name
-                      - value
-                      type: object
-                    type: array
-                  project:
-                    description: Project is a reference to the project this application
-                      belongs to. The empty string means that application belongs
-                      to the 'default' project.
-                    type: string
-                  revisionHistoryLimit:
-                    description: RevisionHistoryLimit limits the number of items kept
-                      in the application's revision history, which is used for informational
-                      purposes as well as for rollbacks to previous versions. This
-                      should only be changed in exceptional circumstances. Setting
-                      to zero will store no history. This will reduce storage used.
-                      Increasing will increase the space used to store the history,
-                      so we do not recommend increasing it. Default is 10.
-                    format: int64
-                    type: integer
-                  source:
-                    description: Source is a reference to the location of the application's
-                      manifests or chart
-                    properties:
-                      chart:
-                        description: Chart is a Helm chart name, and must be specified
-                          for applications sourced from a Helm repo.
+                        type: object
+                      ignoreDifferences:
+                        description: IgnoreDifferences is a list of resources and
+                          their fields which should be ignored during comparison
+                        items:
+                          description: ResourceIgnoreDifferences contains resource
+                            filter and list of json paths which should be ignored
+                            during comparison with live state.
+                          properties:
+                            group:
+                              type: string
+                            jqPathExpressions:
+                              items:
+                                type: string
+                              type: array
+                            jsonPointers:
+                              items:
+                                type: string
+                              type: array
+                            kind:
+                              type: string
+                            managedFieldsManagers:
+                              description: ManagedFieldsManagers is a list of trusted
+                                managers. Fields mutated by those managers will take
+                                precedence over the desired state defined in the SCM
+                                and won't be displayed in diffs
+                              items:
+                                type: string
+                              type: array
+                            name:
+                              type: string
+                            namespace:
+                              type: string
+                          required:
+                          - kind
+                          type: object
+                        type: array
+                      info:
+                        description: Info contains a list of information (URLs, email
+                          addresses, and plain text) that relates to the application
+                        items:
+                          description: Info represents a name and value
+                          properties:
+                            name:
+                              type: string
+                            value:
+                              type: string
+                          required:
+                          - name
+                          - value
+                          type: object
+                        type: array
+                      project:
+                        description: Project is a reference to the project this application
+                          belongs to. The empty string means that application belongs
+                          to the 'default' project.
                         type: string
-                      directory:
-                        description: Directory holds path/directory specific options
+                      revisionHistoryLimit:
+                        description: RevisionHistoryLimit limits the number of items
+                          kept in the application's revision history, which is used
+                          for informational purposes as well as for rollbacks to previous
+                          versions. This should only be changed in exceptional circumstances.
+                          Setting to zero will store no history. This will reduce
+                          storage used. Increasing will increase the space used to
+                          store the history, so we do not recommend increasing it.
+                          Default is 10.
+                        format: int64
+                        type: integer
+                      source:
+                        description: Source is a reference to the location of the
+                          application's manifests or chart
                         properties:
-                          exclude:
-                            description: Exclude contains a glob pattern to match
-                              paths against that should be explicitly excluded from
-                              being used during manifest generation
-                            type: string
-                          include:
-                            description: Include contains a glob pattern to match
-                              paths against that should be explicitly included during
-                              manifest generation
+                          chart:
+                            description: Chart is a Helm chart name, and must be specified
+                              for applications sourced from a Helm repo.
                             type: string
-                          jsonnet:
-                            description: Jsonnet holds options specific to Jsonnet
+                          directory:
+                            description: Directory holds path/directory specific options
                             properties:
-                              extVars:
-                                description: ExtVars is a list of Jsonnet External
-                                  Variables
+                              exclude:
+                                description: Exclude contains a glob pattern to match
+                                  paths against that should be explicitly excluded
+                                  from being used during manifest generation
+                                type: string
+                              include:
+                                description: Include contains a glob pattern to match
+                                  paths against that should be explicitly included
+                                  during manifest generation
+                                type: string
+                              jsonnet:
+                                description: Jsonnet holds options specific to Jsonnet
+                                properties:
+                                  extVars:
+                                    description: ExtVars is a list of Jsonnet External
+                                      Variables
+                                    items:
+                                      description: JsonnetVar represents a variable
+                                        to be passed to jsonnet during manifest generation
+                                      properties:
+                                        code:
+                                          type: boolean
+                                        name:
+                                          type: string
+                                        value:
+                                          type: string
+                                      required:
+                                      - name
+                                      - value
+                                      type: object
+                                    type: array
+                                  libs:
+                                    description: Additional library search dirs
+                                    items:
+                                      type: string
+                                    type: array
+                                  tlas:
+                                    description: TLAS is a list of Jsonnet Top-level
+                                      Arguments
+                                    items:
+                                      description: JsonnetVar represents a variable
+                                        to be passed to jsonnet during manifest generation
+                                      properties:
+                                        code:
+                                          type: boolean
+                                        name:
+                                          type: string
+                                        value:
+                                          type: string
+                                      required:
+                                      - name
+                                      - value
+                                      type: object
+                                    type: array
+                                type: object
+                              recurse:
+                                description: Recurse specifies whether to scan a directory
+                                  recursively for manifests
+                                type: boolean
+                            type: object
+                          helm:
+                            description: Helm holds helm specific options
+                            properties:
+                              fileParameters:
+                                description: FileParameters are file parameters to
+                                  the helm template
                                 items:
-                                  description: JsonnetVar represents a variable to
-                                    be passed to jsonnet during manifest generation
+                                  description: HelmFileParameter is a file parameter
+                                    that's passed to helm template during manifest
+                                    generation
                                   properties:
-                                    code:
+                                    name:
+                                      description: Name is the name of the Helm parameter
+                                      type: string
+                                    path:
+                                      description: Path is the path to the file containing
+                                        the values for the Helm parameter
+                                      type: string
+                                  type: object
+                                type: array
+                              ignoreMissingValueFiles:
+                                description: IgnoreMissingValueFiles prevents helm
+                                  template from failing when valueFiles do not exist
+                                  locally by not appending them to helm template --values
+                                type: boolean
+                              parameters:
+                                description: Parameters is a list of Helm parameters
+                                  which are passed to the helm template command upon
+                                  manifest generation
+                                items:
+                                  description: HelmParameter is a parameter that's
+                                    passed to helm template during manifest generation
+                                  properties:
+                                    forceString:
+                                      description: ForceString determines whether
+                                        to tell Helm to interpret booleans and numbers
+                                        as strings
                                       type: boolean
                                     name:
+                                      description: Name is the name of the Helm parameter
                                       type: string
                                     value:
+                                      description: Value is the value for the Helm
+                                        parameter
                                       type: string
-                                  required:
-                                  - name
-                                  - value
                                   type: object
                                 type: array
-                              libs:
-                                description: Additional library search dirs
+                              passCredentials:
+                                description: PassCredentials pass credentials to all
+                                  domains (Helm's --pass-credentials)
+                                type: boolean
+                              releaseName:
+                                description: ReleaseName is the Helm release name
+                                  to use. If omitted it will use the application name
+                                type: string
+                              skipCrds:
+                                description: SkipCrds skips custom resource definition
+                                  installation step (Helm's --skip-crds)
+                                type: boolean
+                              valueFiles:
+                                description: ValuesFiles is a list of Helm value files
+                                  to use when generating a template
                                 items:
                                   type: string
                                 type: array
-                              tlas:
-                                description: TLAS is a list of Jsonnet Top-level Arguments
+                              values:
+                                description: Values specifies Helm values to be passed
+                                  to helm template, typically defined as a block
+                                type: string
+                              version:
+                                description: Version is the Helm version to use for
+                                  templating (either "2" or "3")
+                                type: string
+                            type: object
+                          ksonnet:
+                            description: Ksonnet holds ksonnet specific options
+                            properties:
+                              environment:
+                                description: Environment is a ksonnet application
+                                  environment name
+                                type: string
+                              parameters:
+                                description: Parameters are a list of ksonnet component
+                                  parameter override values
                                 items:
-                                  description: JsonnetVar represents a variable to
-                                    be passed to jsonnet during manifest generation
+                                  description: KsonnetParameter is a ksonnet component
+                                    parameter
                                   properties:
-                                    code:
-                                      type: boolean
+                                    component:
+                                      type: string
                                     name:
                                       type: string
                                     value:
@@ -190,256 +714,159 @@ spec:
                                   type: object
                                 type: array
                             type: object
-                          recurse:
-                            description: Recurse specifies whether to scan a directory
-                              recursively for manifests
-                            type: boolean
-                        type: object
-                      helm:
-                        description: Helm holds helm specific options
-                        properties:
-                          fileParameters:
-                            description: FileParameters are file parameters to the
-                              helm template
-                            items:
-                              description: HelmFileParameter is a file parameter that's
-                                passed to helm template during manifest generation
-                              properties:
-                                name:
-                                  description: Name is the name of the Helm parameter
+                          kustomize:
+                            description: Kustomize holds kustomize specific options
+                            properties:
+                              commonAnnotations:
+                                additionalProperties:
                                   type: string
-                                path:
-                                  description: Path is the path to the file containing
-                                    the values for the Helm parameter
+                                description: CommonAnnotations is a list of additional
+                                  annotations to add to rendered manifests
+                                type: object
+                              commonLabels:
+                                additionalProperties:
                                   type: string
-                              type: object
-                            type: array
-                          ignoreMissingValueFiles:
-                            description: IgnoreMissingValueFiles prevents helm template
-                              from failing when valueFiles do not exist locally by
-                              not appending them to helm template --values
-                            type: boolean
-                          parameters:
-                            description: Parameters is a list of Helm parameters which
-                              are passed to the helm template command upon manifest
-                              generation
-                            items:
-                              description: HelmParameter is a parameter that's passed
-                                to helm template during manifest generation
-                              properties:
-                                forceString:
-                                  description: ForceString determines whether to tell
-                                    Helm to interpret booleans and numbers as strings
-                                  type: boolean
-                                name:
-                                  description: Name is the name of the Helm parameter
-                                  type: string
-                                value:
-                                  description: Value is the value for the Helm parameter
+                                description: CommonLabels is a list of additional
+                                  labels to add to rendered manifests
+                                type: object
+                              forceCommonAnnotations:
+                                description: ForceCommonAnnotations specifies whether
+                                  to force applying common annotations to resources
+                                  for Kustomize apps
+                                type: boolean
+                              forceCommonLabels:
+                                description: ForceCommonLabels specifies whether to
+                                  force applying common labels to resources for Kustomize
+                                  apps
+                                type: boolean
+                              images:
+                                description: Images is a list of Kustomize image override
+                                  specifications
+                                items:
+                                  description: KustomizeImage represents a Kustomize
+                                    image definition in the format [old_image_name=]<image_name>:<image_tag>
                                   type: string
-                              type: object
-                            type: array
-                          passCredentials:
-                            description: PassCredentials pass credentials to all domains
-                              (Helm's --pass-credentials)
-                            type: boolean
-                          releaseName:
-                            description: ReleaseName is the Helm release name to use.
-                              If omitted it will use the application name
-                            type: string
-                          skipCrds:
-                            description: SkipCrds skips custom resource definition
-                              installation step (Helm's --skip-crds)
-                            type: boolean
-                          valueFiles:
-                            description: ValuesFiles is a list of Helm value files
-                              to use when generating a template
-                            items:
-                              type: string
-                            type: array
-                          values:
-                            description: Values specifies Helm values to be passed
-                              to helm template, typically defined as a block
+                                type: array
+                              namePrefix:
+                                description: NamePrefix is a prefix appended to resources
+                                  for Kustomize apps
+                                type: string
+                              nameSuffix:
+                                description: NameSuffix is a suffix appended to resources
+                                  for Kustomize apps
+                                type: string
+                              version:
+                                description: Version controls which version of Kustomize
+                                  to use for rendering manifests
+                                type: string
+                            type: object
+                          path:
+                            description: Path is a directory path within the Git repository,
+                              and is only valid for applications sourced from Git.
                             type: string
-                          version:
-                            description: Version is the Helm version to use for templating
-                              (either "2" or "3")
+                          plugin:
+                            description: ConfigManagementPlugin holds config management
+                              plugin specific options
+                            properties:
+                              env:
+                                description: Env is a list of environment variable
+                                  entries
+                                items:
+                                  description: EnvEntry represents an entry in the
+                                    application's environment
+                                  properties:
+                                    name:
+                                      description: Name is the name of the variable,
+                                        usually expressed in uppercase
+                                      type: string
+                                    value:
+                                      description: Value is the value of the variable
+                                      type: string
+                                  required:
+                                  - name
+                                  - value
+                                  type: object
+                                type: array
+                              name:
+                                type: string
+                            type: object
+                          repoURL:
+                            description: RepoURL is the URL to the repository (Git
+                              or Helm) that contains the application manifests
                             type: string
-                        type: object
-                      ksonnet:
-                        description: Ksonnet holds ksonnet specific options
-                        properties:
-                          environment:
-                            description: Environment is a ksonnet application environment
-                              name
+                          targetRevision:
+                            description: TargetRevision defines the revision of the
+                              source to sync the application to. In case of Git, this
+                              can be commit, tag, or branch. If omitted, will equal
+                              to HEAD. In case of Helm, this is a semver tag for the
+                              Chart's version.
                             type: string
-                          parameters:
-                            description: Parameters are a list of ksonnet component
-                              parameter override values
-                            items:
-                              description: KsonnetParameter is a ksonnet component
-                                parameter
-                              properties:
-                                component:
-                                  type: string
-                                name:
-                                  type: string
-                                value:
-                                  type: string
-                              required:
-                              - name
-                              - value
-                              type: object
-                            type: array
+                        required:
+                        - repoURL
                         type: object
-                      kustomize:
-                        description: Kustomize holds kustomize specific options
+                      syncPolicy:
+                        description: SyncPolicy controls when and how a sync will
+                          be performed
                         properties:
-                          commonAnnotations:
-                            additionalProperties:
-                              type: string
-                            description: CommonAnnotations is a list of additional
-                              annotations to add to rendered manifests
+                          automated:
+                            description: Automated will keep an application synced
+                              to the target revision
+                            properties:
+                              allowEmpty:
+                                description: 'AllowEmpty allows apps have zero live
+                                  resources (default: false)'
+                                type: boolean
+                              prune:
+                                description: 'Prune specifies whether to delete resources
+                                  from the cluster that are not found in the sources
+                                  anymore as part of automated sync (default: false)'
+                                type: boolean
+                              selfHeal:
+                                description: 'SelfHeal specifes whether to revert
+                                  resources back to their desired state upon modification
+                                  in the cluster (default: false)'
+                                type: boolean
                             type: object
-                          commonLabels:
-                            additionalProperties:
-                              type: string
-                            description: CommonLabels is a list of additional labels
-                              to add to rendered manifests
+                          retry:
+                            description: Retry controls failed sync retry behavior
+                            properties:
+                              backoff:
+                                description: Backoff controls how to backoff on subsequent
+                                  retries of failed syncs
+                                properties:
+                                  duration:
+                                    description: Duration is the amount to back off.
+                                      Default unit is seconds, but could also be a
+                                      duration (e.g. "2m", "1h")
+                                    type: string
+                                  factor:
+                                    description: Factor is a factor to multiply the
+                                      base duration after each failed retry
+                                    format: int64
+                                    type: integer
+                                  maxDuration:
+                                    description: MaxDuration is the maximum amount
+                                      of time allowed for the backoff strategy
+                                    type: string
+                                type: object
+                              limit:
+                                description: Limit is the maximum number of attempts
+                                  for retrying a failed sync. If set to 0, no retries
+                                  will be performed.
+                                format: int64
+                                type: integer
                             type: object
-                          forceCommonAnnotations:
-                            description: ForceCommonAnnotations specifies whether
-                              to force applying common annotations to resources for
-                              Kustomize apps
-                            type: boolean
-                          forceCommonLabels:
-                            description: ForceCommonLabels specifies whether to force
-                              applying common labels to resources for Kustomize apps
-                            type: boolean
-                          images:
-                            description: Images is a list of Kustomize image override
-                              specifications
+                          syncOptions:
+                            description: Options allow you to specify whole app sync-options
                             items:
-                              description: KustomizeImage represents a Kustomize image
-                                definition in the format [old_image_name=]<image_name>:<image_tag>
                               type: string
                             type: array
-                          namePrefix:
-                            description: NamePrefix is a prefix appended to resources
-                              for Kustomize apps
-                            type: string
-                          nameSuffix:
-                            description: NameSuffix is a suffix appended to resources
-                              for Kustomize apps
-                            type: string
-                          version:
-                            description: Version controls which version of Kustomize
-                              to use for rendering manifests
-                            type: string
                         type: object
-                      path:
-                        description: Path is a directory path within the Git repository,
-                          and is only valid for applications sourced from Git.
-                        type: string
-                      plugin:
-                        description: ConfigManagementPlugin holds config management
-                          plugin specific options
-                        properties:
-                          env:
-                            description: Env is a list of environment variable entries
-                            items:
-                              description: EnvEntry represents an entry in the application's
-                                environment
-                              properties:
-                                name:
-                                  description: Name is the name of the variable, usually
-                                    expressed in uppercase
-                                  type: string
-                                value:
-                                  description: Value is the value of the variable
-                                  type: string
-                              required:
-                              - name
-                              - value
-                              type: object
-                            type: array
-                          name:
-                            type: string
-                        type: object
-                      repoURL:
-                        description: RepoURL is the URL to the repository (Git or
-                          Helm) that contains the application manifests
-                        type: string
-                      targetRevision:
-                        description: TargetRevision defines the revision of the source
-                          to sync the application to. In case of Git, this can be
-                          commit, tag, or branch. If omitted, will equal to HEAD.
-                          In case of Helm, this is a semver tag for the Chart's version.
-                        type: string
                     required:
-                    - repoURL
-                    type: object
-                  syncPolicy:
-                    description: SyncPolicy controls when and how a sync will be performed
-                    properties:
-                      automated:
-                        description: Automated will keep an application synced to
-                          the target revision
-                        properties:
-                          allowEmpty:
-                            description: 'AllowEmpty allows apps have zero live resources
-                              (default: false)'
-                            type: boolean
-                          prune:
-                            description: 'Prune specifies whether to delete resources
-                              from the cluster that are not found in the sources anymore
-                              as part of automated sync (default: false)'
-                            type: boolean
-                          selfHeal:
-                            description: 'SelfHeal specifes whether to revert resources
-                              back to their desired state upon modification in the
-                              cluster (default: false)'
-                            type: boolean
-                        type: object
-                      retry:
-                        description: Retry controls failed sync retry behavior
-                        properties:
-                          backoff:
-                            description: Backoff controls how to backoff on subsequent
-                              retries of failed syncs
-                            properties:
-                              duration:
-                                description: Duration is the amount to back off. Default
-                                  unit is seconds, but could also be a duration (e.g.
-                                  "2m", "1h")
-                                type: string
-                              factor:
-                                description: Factor is a factor to multiply the base
-                                  duration after each failed retry
-                                format: int64
-                                type: integer
-                              maxDuration:
-                                description: MaxDuration is the maximum amount of
-                                  time allowed for the backoff strategy
-                                type: string
-                            type: object
-                          limit:
-                            description: Limit is the maximum number of attempts for
-                              retrying a failed sync. If set to 0, no retries will
-                              be performed.
-                            format: int64
-                            type: integer
-                        type: object
-                      syncOptions:
-                        description: Options allow you to specify whole app sync-options
-                        items:
-                          type: string
-                        type: array
+                    - destination
+                    - project
+                    - source
                     type: object
-                required:
-                - destination
-                - project
-                - source
                 type: object
             type: object
           status:
diff --git a/config/samples/devops_v1alpha1_application.yaml b/config/samples/devops_v1alpha1_application.yaml
deleted file mode 100644
index 4ab2fca..0000000
--- a/config/samples/devops_v1alpha1_application.yaml
+++ /dev/null
@@ -1,19 +0,0 @@
-apiVersion: gitops.kubesphere.io/v1alpha1
-kind: Application
-metadata:
-  name: app
-spec:
-  argoApp:
-    project: default
-    source:
-      repoURL: https://gitlab.com/LinuxSuRen1/argocd
-      targetRevision: HEAD
-      path: test
-      directory:
-        recurse: true
-    destination:
-      server: https://kubernetes.default.svc
-      namespace: default
-    syncPolicy:
-      automated:
-        prune: true
diff --git a/config/samples/gitops_v1alpha1_application.yaml b/config/samples/gitops_v1alpha1_application.yaml
new file mode 100644
index 0000000..41c7596
--- /dev/null
+++ b/config/samples/gitops_v1alpha1_application.yaml
@@ -0,0 +1,20 @@
+apiVersion: gitops.kubesphere.io/v1alpha1
+kind: Application
+metadata:
+  name: app
+spec:
+  argoApp:
+    spec:
+      project: default
+      source:
+        repoURL: https://gitlab.com/LinuxSuRen1/argocd
+        targetRevision: HEAD
+        path: test
+        directory:
+          recurse: true
+      destination:
+        server: https://kubernetes.default.svc
+        namespace: default
+      syncPolicy:
+        automated:
+          prune: true
diff --git a/controllers/argocd/application_controller.go b/controllers/argocd/application_controller.go
index 604c9f5..77bc98b 100644
--- a/controllers/argocd/application_controller.go
+++ b/controllers/argocd/application_controller.go
@@ -17,13 +17,12 @@ limitations under the License.
 package argocd
 
 import (
-	"bytes"
 	"context"
 	"fmt"
 	"github.com/go-logr/logr"
-	"html/template"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/client-go/tools/record"
 	"kubesphere.io/devops/pkg/api/gitops/v1alpha1"
@@ -66,7 +65,7 @@ func (r *ApplicationReconciler) reconcileArgoApplication(app *v1alpha1.Applicati
 	argoApp.SetKind("Application")
 	argoApp.SetAPIVersion("argoproj.io/v1alpha1")
 
-	if err = r.Client.Get(ctx, types.NamespacedName{
+	if err = r.Get(ctx, types.NamespacedName{
 		Namespace: app.GetNamespace(),
 		Name:      app.GetName(),
 	}, argoApp); err != nil {
@@ -78,70 +77,53 @@ func (r *ApplicationReconciler) reconcileArgoApplication(app *v1alpha1.Applicati
 			return
 		}
 
-		err = r.Client.Create(ctx, argoApp)
+		err = r.Create(ctx, argoApp)
 	} else {
 		var newArgoApp *unstructured.Unstructured
 		if newArgoApp, err = createUnstructuredApplication(app); err == nil {
 			argoApp.Object["spec"] = newArgoApp.Object["spec"]
 			k8sutil.AddOwnerReference(argoApp, app.TypeMeta, app.ObjectMeta)
-			err = r.Client.Update(ctx, argoApp)
+			err = r.Update(ctx, argoApp)
 		}
 	}
 	return
 }
 
 func createUnstructuredApplication(app *v1alpha1.Application) (result *unstructured.Unstructured, err error) {
-	app = app.DeepCopy()
-	argoApp := app.Spec.ArgoApp
-	if argoApp == nil {
+	if app.Spec.ArgoApp == nil {
 		err = fmt.Errorf("no argo found from the spec")
 		return
 	}
-
-	var tpl *template.Template
-	if tpl, err = template.New("argo").Parse(argoApplicationTemplate); err != nil {
-		return
+	argoApp := app.Spec.ArgoApp.DeepCopy()
+	// TODO set some default values
+	if argoApp.Spec.Project == "" {
+		argoApp.Spec.Project = "default"
 	}
 
-	// TODO set some default values
-	if argoApp.Project == "" {
-		argoApp.Project = "default"
+	newArgoApp := &unstructured.Unstructured{}
+	newArgoApp.SetGroupVersionKind(schema.GroupVersionKind{
+		Group:   "argoproj.io",
+		Version: "v1alpha1",
+		Kind:    "Application",
+	})
+	newArgoApp.SetName(app.GetName())
+	newArgoApp.SetNamespace(app.GetNamespace())
+
+	if err := SetNestedField(newArgoApp.Object, argoApp.Spec, "spec"); err != nil {
+		return nil, err
 	}
 
-	buffer := new(bytes.Buffer)
-	if err = tpl.Execute(buffer, argoApp); err == nil {
-		if result, err = GetObjectFromYaml(buffer.String()); err == nil {
-			result.SetName(app.GetName())
-			result.SetNamespace(app.GetNamespace())
-			k8sutil.AddOwnerReference(result, app.TypeMeta, app.ObjectMeta)
+	if argoApp.Operation != nil {
+		if err := SetNestedField(newArgoApp.Object, argoApp.Operation, "operation"); err != nil {
+			return nil, err
 		}
 	}
-	return
-}
 
-const argoApplicationTemplate = `apiVersion: argoproj.io/v1alpha1
-kind: Application
-spec:
-  project: "{{.Project}}"
-  source:
-    repoURL: "{{.Source.RepoURL}}"
-    targetRevision: {{.Source.TargetRevision}}
-    path: "{{.Source.Path}}"
-{{if .Source.Directory }}
-    directory:
-      recurse: {{.Source.Directory.Recurse}}
-{{end}}
-  destination:
-    server: "{{.Destination.Server}}"
-    namespace: "{{.Destination.Namespace}}"
-{{if .SyncPolicy }}
-{{if .SyncPolicy.Automated }}
-  syncPolicy:
-    automated:
-      prune: {{.SyncPolicy.Automated.Prune}}
-{{end}}
-{{end}}
-`
+	// set owner reference
+	k8sutil.AddOwnerReference(newArgoApp, app.TypeMeta, app.ObjectMeta)
+
+	return newArgoApp, nil
+}
 
 // GetName returns the name of this reconciler
 func (r *ApplicationReconciler) GetName() string {
diff --git a/controllers/argocd/utils.go b/controllers/argocd/utils.go
new file mode 100644
index 0000000..17b2b44
--- /dev/null
+++ b/controllers/argocd/utils.go
@@ -0,0 +1,47 @@
+// Copyright 2022 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 argocd
+
+import (
+	"encoding/json"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+)
+
+// InterfaceToMap converts value to map, which type of key is string and type of value is interface{} (Actually it is
+// map[string]interface{}).
+// The type of value must be struct. Any other types will lead an error.
+func InterfaceToMap(value interface{}) (map[string]interface{}, error) {
+	valueBytes, err := json.Marshal(value)
+	if err != nil {
+		return nil, err
+	}
+	uValue := map[string]interface{}{}
+	if err := json.Unmarshal(valueBytes, &uValue); err != nil {
+		return nil, err
+	}
+	return uValue, nil
+}
+
+// SetNestedField sets nested field into the object with map type. The type of field value must be struct. Any other
+// types will lead to an error.
+func SetNestedField(obj map[string]interface{}, value interface{}, fields ...string) error {
+	// convert value to unstructured
+	mapValue, err := InterfaceToMap(value)
+	if err != nil {
+		return err
+	}
+	return unstructured.SetNestedField(obj, mapValue, fields...)
+}
diff --git a/pkg/api/devops/v1alpha3/groupversion_info.go b/pkg/api/devops/v1alpha3/groupversion_info.go
index 06af619..be87a48 100644
--- a/pkg/api/devops/v1alpha3/groupversion_info.go
+++ b/pkg/api/devops/v1alpha3/groupversion_info.go
@@ -38,6 +38,8 @@ const (
 	PipelineRunCreatorAnnoKey = devops.GroupName + "/creator"
 	// PipelineRunSCMRefNameField is the field name of SCM reference name in PipelineRun spec.
 	PipelineRunSCMRefNameField = "spec.scm.ref-name"
+	// PipelineRunIdentifierIndexerName is an indexer name of PipelineRun identifier.
+	PipelineRunIdentifierIndexerName = "pipelinerun.identifier"
 )
 
 var (
diff --git a/pkg/api/devops/v1alpha3/pipelinerun_types.go b/pkg/api/devops/v1alpha3/pipelinerun_types.go
index 909dd67..a61c380 100644
--- a/pkg/api/devops/v1alpha3/pipelinerun_types.go
+++ b/pkg/api/devops/v1alpha3/pipelinerun_types.go
@@ -18,6 +18,7 @@ package v1alpha3
 
 import (
 	"sort"
+	"strings"
 
 	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -167,6 +168,35 @@ func (pr *PipelineRun) GetPipelineRunID() (pipelineRunID string, exist bool) {
 	return
 }
 
+// GetPipelineRunIdentifier returns an identifier string which can identify a PipelineRun,
+// even if the PipelineRun belongs to multi-branch or non multi-branch Pipeline.
+// Format of the string: [scm.ref-name]-runID
+// TODO Add unit test against this method
+func (pr *PipelineRun) GetPipelineRunIdentifier() string {
+	pipelineName := pr.GetLabels()[PipelineNameLabelKey]
+	scmRefName := ""
+	if pr.Spec.IsMultiBranchPipeline() && pr.Spec.SCM != nil {
+		scmRefName = pr.Spec.SCM.RefName
+	}
+	runID := pr.Annotations[JenkinsPipelineRunIDAnnoKey]
+	return BuildPipelineRunIdentifier(pipelineName, scmRefName, runID)
+}
+
+// BuildPipelineRunIdentifier builds PipelineRun identifier with Pipeline name, SCM reference name and run ID.
+func BuildPipelineRunIdentifier(pipelineName, scmRefName, runID string) string {
+	var identifierItem []string
+	if pipelineName != "" {
+		identifierItem = append(identifierItem, pipelineName)
+	}
+	if scmRefName != "" {
+		identifierItem = append(identifierItem, scmRefName)
+	}
+	if runID != "" {
+		identifierItem = append(identifierItem, runID)
+	}
+	return strings.Join(identifierItem, "-")
+}
+
 // Parameter is an option that can be passed with the endpoint to influence the Pipeline Run
 type Parameter struct {
 	// Name indicates that name of the parameter.
diff --git a/pkg/api/gitops/v1alpha1/application.go b/pkg/api/gitops/v1alpha1/application.go
index 2b80340..85ec4e0 100644
--- a/pkg/api/gitops/v1alpha1/application.go
+++ b/pkg/api/gitops/v1alpha1/application.go
@@ -25,9 +25,65 @@ type ApplicationSpec struct {
 	ArgoApp *ArgoApplication `json:"argoApp,omitempty"`
 }
 
-// ArgoApplication represents an ArgoCD Application
-// The fields simply are copied from the argo-cd project
+// ArgoApplication is a definition of Argo Application resource.
+// Those fields simply are copied from the argo-cd project
 type ArgoApplication struct {
+	Spec      ArgoApplicationSpec `json:"spec,omitempty"`
+	Operation *Operation          `json:"operation,omitempty"`
+}
+
+// OperationInitiator contains information about the initiator of an operation
+type OperationInitiator struct {
+	// Username contains the name of a user who started operation
+	Username string `json:"username,omitempty"`
+	// Automated is set to true if operation was initiated automatically by the application controller.
+	Automated bool `json:"automated,omitempty"`
+}
+
+// SyncOperationResource contains resources to sync.
+type SyncOperationResource struct {
+	Group     string `json:"group,omitempty"`
+	Kind      string `json:"kind"`
+	Name      string `json:"name"`
+	Namespace string `json:"namespace,omitempty"`
+}
+
+// SyncOperation contains details about a sync operation.
+type SyncOperation struct {
+	// Revision is the revision (Git) or chart version (Helm) which to sync the application to
+	// If omitted, will use the revision specified in app spec.
+	Revision string `json:"revision,omitempty"`
+	// Prune specifies to delete resources from the cluster that are no longer tracked in git
+	Prune bool `json:"prune,omitempty"`
+	// DryRun specifies to perform a `kubectl apply --dry-run` without actually performing the sync
+	DryRun bool `json:"dryRun,omitempty"`
+	// SyncStrategy describes how to perform the sync
+	SyncStrategy *SyncStrategy `json:"syncStrategy,omitempty"`
+	// Resources describes which resources shall be part of the sync
+	Resources []SyncOperationResource `json:"resources,omitempty"`
+	// Source overrides the source definition set in the application.
+	// This is typically set in a Rollback operation and is nil during a Sync operation
+	Source *ApplicationSource `json:"source,omitempty"`
+	// Manifests is an optional field that overrides sync source with a local directory for development
+	Manifests []string `json:"manifests,omitempty"`
+	// SyncOptions provide per-sync sync-options, e.g. Validate=false
+	SyncOptions SyncOptions `json:"syncOptions,omitempty"`
+}
+
+// Operation contains information about a requested or running operation
+type Operation struct {
+	// Sync contains parameters for the operation
+	Sync *SyncOperation `json:"sync,omitempty"`
+	// InitiatedBy contains information about who initiated the operations
+	InitiatedBy OperationInitiator `json:"initiatedBy,omitempty"`
+	// Info is a list of informational items for this operation
+	Info []*Info `json:"info,omitempty"`
+	// Retry controls the strategy to apply if a sync fails
+	Retry RetryStrategy `json:"retry,omitempty"`
+}
+
+// ArgoApplicationSpec represents an ArgoCD Application
+type ArgoApplicationSpec struct {
 	// Source is a reference to the location of the application's manifests or chart
 	Source ApplicationSource `json:"source"`
 	// Destination is a reference to the target Kubernetes server and namespace
diff --git a/pkg/api/gitops/v1alpha1/zz_generated.deepcopy.go b/pkg/api/gitops/v1alpha1/zz_generated.deepcopy.go
index 3eb25af..9e8d866 100644
--- a/pkg/api/gitops/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/api/gitops/v1alpha1/zz_generated.deepcopy.go
@@ -332,6 +332,27 @@ func (in *ApplicationStatus) DeepCopy() *ApplicationStatus {
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ArgoApplication) DeepCopyInto(out *ArgoApplication) {
+	*out = *in
+	in.Spec.DeepCopyInto(&out.Spec)
+	if in.Operation != nil {
+		in, out := &in.Operation, &out.Operation
+		*out = new(Operation)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoApplication.
+func (in *ArgoApplication) DeepCopy() *ArgoApplication {
+	if in == nil {
+		return nil
+	}
+	out := new(ArgoApplication)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ArgoApplicationSpec) DeepCopyInto(out *ArgoApplicationSpec) {
 	*out = *in
 	in.Source.DeepCopyInto(&out.Source)
 	out.Destination = in.Destination
@@ -359,12 +380,12 @@ func (in *ArgoApplication) DeepCopyInto(out *ArgoApplication) {
 	}
 }
 
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoApplication.
-func (in *ArgoApplication) DeepCopy() *ArgoApplication {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoApplicationSpec.
+func (in *ArgoApplicationSpec) DeepCopy() *ArgoApplicationSpec {
 	if in == nil {
 		return nil
 	}
-	out := new(ArgoApplication)
+	out := new(ArgoApplicationSpec)
 	in.DeepCopyInto(out)
 	return out
 }
@@ -523,6 +544,54 @@ func (in KustomizeImages) DeepCopy() KustomizeImages {
 	return *out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Operation) DeepCopyInto(out *Operation) {
+	*out = *in
+	if in.Sync != nil {
+		in, out := &in.Sync, &out.Sync
+		*out = new(SyncOperation)
+		(*in).DeepCopyInto(*out)
+	}
+	out.InitiatedBy = in.InitiatedBy
+	if in.Info != nil {
+		in, out := &in.Info, &out.Info
+		*out = make([]*Info, len(*in))
+		for i := range *in {
+			if (*in)[i] != nil {
+				in, out := &(*in)[i], &(*out)[i]
+				*out = new(Info)
+				**out = **in
+			}
+		}
+	}
+	in.Retry.DeepCopyInto(&out.Retry)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Operation.
+func (in *Operation) DeepCopy() *Operation {
+	if in == nil {
+		return nil
+	}
+	out := new(Operation)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *OperationInitiator) DeepCopyInto(out *OperationInitiator) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OperationInitiator.
+func (in *OperationInitiator) DeepCopy() *OperationInitiator {
+	if in == nil {
+		return nil
+	}
+	out := new(OperationInitiator)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ResourceIgnoreDifferences) DeepCopyInto(out *ResourceIgnoreDifferences) {
 	*out = *in
@@ -573,6 +642,61 @@ func (in *RetryStrategy) DeepCopy() *RetryStrategy {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SyncOperation) DeepCopyInto(out *SyncOperation) {
+	*out = *in
+	if in.SyncStrategy != nil {
+		in, out := &in.SyncStrategy, &out.SyncStrategy
+		*out = new(SyncStrategy)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.Resources != nil {
+		in, out := &in.Resources, &out.Resources
+		*out = make([]SyncOperationResource, len(*in))
+		copy(*out, *in)
+	}
+	if in.Source != nil {
+		in, out := &in.Source, &out.Source
+		*out = new(ApplicationSource)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.Manifests != nil {
+		in, out := &in.Manifests, &out.Manifests
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+	if in.SyncOptions != nil {
+		in, out := &in.SyncOptions, &out.SyncOptions
+		*out = make(SyncOptions, len(*in))
+		copy(*out, *in)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SyncOperation.
+func (in *SyncOperation) DeepCopy() *SyncOperation {
+	if in == nil {
+		return nil
+	}
+	out := new(SyncOperation)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SyncOperationResource) DeepCopyInto(out *SyncOperationResource) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SyncOperationResource.
+func (in *SyncOperationResource) DeepCopy() *SyncOperationResource {
+	if in == nil {
+		return nil
+	}
+	out := new(SyncOperationResource)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in SyncOptions) DeepCopyInto(out *SyncOptions) {
 	{
diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go
index 4ccfb83..d3a0c43 100644
--- a/pkg/apiserver/apiserver.go
+++ b/pkg/apiserver/apiserver.go
@@ -169,6 +169,10 @@ func (s *APIServer) Run(stopCh <-chan struct{}) (err error) {
 	if err := indexers.CreatePipelineRunSCMRefNameIndexer(s.RuntimeCache); err != nil {
 		return err
 	}
+	if err := indexers.CreatePipelineRunIdentityIndexer(s.RuntimeCache); err != nil {
+		return err
+	}
+
 	err = s.waitForResourceSync(stopCh)
 	if err != nil {
 		return err
diff --git a/pkg/event/README.md b/pkg/event/README.md
new file mode 100644
index 0000000..ada88fc
--- /dev/null
+++ b/pkg/event/README.md
@@ -0,0 +1,3 @@
+# Event package for Pipeline Event Plugin
+
+This package is design to easily handle the data sent by the [pipeline-event](https://github.com/JohnNiang/pipeline-event-plugin) plugin. In the future, it needs to be independent into a separate project. If anyone is interested, you are very welcome to complete this task.
diff --git a/pkg/event/common/types.go b/pkg/event/common/types.go
new file mode 100644
index 0000000..0d58f2f
--- /dev/null
+++ b/pkg/event/common/types.go
@@ -0,0 +1,44 @@
+/*
+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 common
+
+import (
+	"encoding/json"
+)
+
+const (
+	// RunInitialize represents Jenkins run is initializing.
+	RunInitialize string = "run.initialize"
+	// RunStarted represents Jenkins run has started.
+	RunStarted string = "run.started"
+	// RunFinalized represents Jenkins run has finalized.
+	RunFinalized string = "run.finalized"
+	// RunCompleted represents Jenkins run has completed.
+	RunCompleted string = "run.completed"
+	// RunDeleted represents Jenkins run has been deleted.
+	RunDeleted string = "run.deleted"
+)
+
+// Event contains common fields of event except event data.
+type Event struct {
+	Type     string          `json:"type"`
+	Source   string          `json:"source"`
+	ID       string          `json:"id"`
+	Time     string          `json:"time"`
+	DataType string          `json:"dataType"`
+	Data     json.RawMessage `json:"data"`
+}
diff --git a/pkg/event/workflowrun/actions.go b/pkg/event/workflowrun/actions.go
new file mode 100644
index 0000000..fabf1cf
--- /dev/null
+++ b/pkg/event/workflowrun/actions.go
@@ -0,0 +1,63 @@
+/*
+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 workflowrun
+
+import (
+	"encoding/json"
+)
+
+// Action is an item of actions, which contains kind and raw data of action for further unserialization.
+type Action struct {
+	// Raw data for futher unmarshal.
+	Raw json.RawMessage
+	// Kind represents what action would look like.
+	Kind string
+}
+
+var _ json.Unmarshaler = (*Action)(nil)
+
+// UnmarshalJSON will extract Kind field for action and store the raw data to action.
+func (action *Action) UnmarshalJSON(data []byte) error {
+	// get the _class field
+	kind := &struct {
+		Kind string `json:"_class"`
+	}{}
+	if err := json.Unmarshal(data, kind); err != nil {
+		return err
+	}
+	action.Kind = kind.Kind
+	action.Raw = data
+	return nil
+}
+
+// Actions are set of action carried by WorkflowRun.
+type Actions []Action
+
+// GetAction gets action from actions in WorkflowRun by full qualified class name.
+// Return nil if not found.
+func (actions Actions) GetAction(class string) *Action {
+	if class == "" {
+		return nil
+	}
+	for i := range actions {
+		action := &actions[i]
+		if action.Kind == class {
+			return action
+		}
+	}
+	return nil
+}
diff --git a/pkg/event/workflowrun/parameter_action.go b/pkg/event/workflowrun/parameter_action.go
new file mode 100644
index 0000000..122a2c0
--- /dev/null
+++ b/pkg/event/workflowrun/parameter_action.go
@@ -0,0 +1,56 @@
+/*
+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 workflowrun
+
+import "encoding/json"
+
+// Parameter is an action item storing all parameters passed to WorkflowRun.
+type Parameter struct {
+	// Kind represents what is the type of this parameter.
+	Kind string `json:"_class"`
+
+	// Name indicates that name of the parameter.
+	Name string `json:"name" description:"parameter name"`
+
+	// Value indicates that value of the parameter.
+	Value interface{} `json:"value" description:"parameter value"`
+}
+
+// ParameterAction is an action which carries parameters.
+type ParameterAction struct {
+	Parameters []Parameter `json:"parameters"`
+}
+
+// Kind returns kind of parameter action.
+func (*ParameterAction) Kind() string {
+	return "hudson.model.ParametersAction"
+}
+
+// GetParameters gets a set of parameters passed to WorkflowRun.
+func (actions *Actions) GetParameters() ([]Parameter, error) {
+	var parameterAction *ParameterAction
+	action := actions.GetAction(parameterAction.Kind())
+	if action == nil {
+		// If not parameter action found
+		return nil, nil
+	}
+	parameterAction = &ParameterAction{}
+	if err := json.Unmarshal(action.Raw, parameterAction); err != nil {
+		return nil, err
+	}
+	return parameterAction.Parameters, nil
+}
diff --git a/pkg/event/workflowrun/types.go b/pkg/event/workflowrun/types.go
new file mode 100644
index 0000000..47d1296
--- /dev/null
+++ b/pkg/event/workflowrun/types.go
@@ -0,0 +1,37 @@
+/*
+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 workflowrun
+
+// Data contains WorkflowJob brief information and WorkflowRun detail.
+type Data struct {
+	Actions           Actions `json:"actions"`
+	Building          bool    `json:"building"`
+	Description       string  `json:"description"`
+	DisplayName       string  `json:"displayName"`
+	Duration          int     `json:"duration"`
+	EstimatedDuration int     `json:"estimatedDuration"`
+	FullDisplayName   string  `json:"fullDisplayName"`
+	ID                string  `json:"id"`
+	KeepLog           bool    `json:"keepLog"`
+	Number            int     `json:"number"`
+	QueueID           int     `json:"queueId"`
+	Result            string  `json:"result"`
+	Timestamp         int64   `json:"timestamp"`
+	ParentFullName    string  `json:"_parentFullName"`
+	ProjectName       string  `json:"_projectName"`
+	IsMultiBranch     bool    `json:"_multiBranch"`
+}
diff --git a/pkg/event/workflowrun/workflowrun_handler.go b/pkg/event/workflowrun/workflowrun_handler.go
new file mode 100644
index 0000000..6e7dddc
--- /dev/null
+++ b/pkg/event/workflowrun/workflowrun_handler.go
@@ -0,0 +1,71 @@
+/*
+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 workflowrun
+
+import (
+	"encoding/json"
+	"kubesphere.io/devops/pkg/event/common"
+)
+
+// Type is a full qualified class name in Java.
+const Type = "org.jenkinsci.plugins.workflow.job.WorkflowRun"
+
+// Handler is a function definition of handling event.
+type Handler func(*Data) error
+
+// Handlers are collection of handlers for various event type.
+type Handlers struct {
+	HandleInitialize Handler
+	HandleStarted    Handler
+	HandleFinalized  Handler
+	HandleCompleted  Handler
+	HandleDeleted    Handler
+}
+
+// discardHandler will give up handling any data.
+var discardHandler Handler = func(data *Data) error {
+	// do nothing and return no error
+	return nil
+}
+
+func (handlers Handlers) getHandler(eventType string) Handler {
+	handlerMap := map[string]Handler{
+		common.RunInitialize: handlers.HandleInitialize,
+		common.RunStarted:    handlers.HandleStarted,
+		common.RunFinalized:  handlers.HandleFinalized,
+		common.RunCompleted:  handlers.HandleCompleted,
+		common.RunDeleted:    handlers.HandleDeleted,
+	}
+	handler, exist := handlerMap[eventType]
+	if !exist || handler == nil {
+		return discardHandler
+	}
+	return handler
+}
+
+// Handle handles WorkflowRun event.
+func (handlers Handlers) Handle(event *common.Event) error {
+	if event == nil || len(event.Data) == 0 || event.DataType != Type {
+		return nil
+	}
+	// unmarshal data to WorkflowRunData
+	data := &Data{}
+	if err := json.Unmarshal(event.Data, data); err != nil {
+		return err
+	}
+	return handlers.getHandler(event.Type)(data)
+}
diff --git a/pkg/indexers/indexers.go b/pkg/indexers/indexers.go
index f0a4514..4f57c2a 100644
--- a/pkg/indexers/indexers.go
+++ b/pkg/indexers/indexers.go
@@ -40,3 +40,15 @@ func CreatePipelineRunSCMRefNameIndexer(runtimeCache cache.Cache) error {
 			return []string{pipelineRun.Spec.SCM.RefName}
 		})
 }
+
+// CreatePipelineRunIdentityIndexer creates an indexer which aims for locating a PipelineRun with an identifier, like Pipeline name, SCM reference name and run ID.
+func CreatePipelineRunIdentityIndexer(runtimeCache cache.Cache) error {
+	// TODO Make the definition of index name in only one place
+	return runtimeCache.IndexField(context.Background(), &v1alpha3.PipelineRun{}, v1alpha3.PipelineRunIdentifierIndexerName, func(o runtime.Object) []string {
+		pipelineRun, ok := o.(*v1alpha3.PipelineRun)
+		if !ok || pipelineRun == nil {
+			return []string{}
+		}
+		return []string{pipelineRun.GetPipelineRunIdentifier()}
+	})
+}
diff --git a/pkg/kapis/devops/v1alpha3/handler.go b/pkg/kapis/devops/v1alpha3/handler.go
index 2eb814e..db1235a 100644
--- a/pkg/kapis/devops/v1alpha3/handler.go
+++ b/pkg/kapis/devops/v1alpha3/handler.go
@@ -22,12 +22,11 @@ import (
 	"k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/klog"
 	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
+	"kubesphere.io/devops/pkg/apiserver/query"
+	devopsClient "kubesphere.io/devops/pkg/client/devops"
 	"kubesphere.io/devops/pkg/client/k8s"
 	"kubesphere.io/devops/pkg/constants"
 	"kubesphere.io/devops/pkg/kapis"
-
-	"kubesphere.io/devops/pkg/apiserver/query"
-	devopsClient "kubesphere.io/devops/pkg/client/devops"
 	"kubesphere.io/devops/pkg/models/devops"
 	servererr "kubesphere.io/devops/pkg/server/errors"
 	"kubesphere.io/devops/pkg/server/params"
diff --git a/pkg/kapis/devops/v1alpha3/pipelinerun/handler.go b/pkg/kapis/devops/v1alpha3/pipelinerun/handler.go
index 5596d1a..40f5dc1 100644
--- a/pkg/kapis/devops/v1alpha3/pipelinerun/handler.go
+++ b/pkg/kapis/devops/v1alpha3/pipelinerun/handler.go
@@ -28,8 +28,6 @@ import (
 	"kubesphere.io/devops/pkg/kapis"
 
 	"github.com/emicklei/go-restful"
-	apierrors "k8s.io/apimachinery/pkg/api/errors"
-	"k8s.io/klog"
 	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
 	"kubesphere.io/devops/pkg/apiserver/query"
 	apiserverrequest "kubesphere.io/devops/pkg/apiserver/request"
@@ -103,34 +101,6 @@ func (h *apiHandler) listPipelineRuns(request *restful.Request, response *restfu
 	}
 	apiResult := resourcesV1alpha3.ToListResult(convertPipelineRunsToObject(prs.Items), queryParam, listHandler)
 	_ = response.WriteAsJson(apiResult)
-
-	go func() {
-		err := h.requestSyncPipelineRun(client.ObjectKey{Namespace: pipeline.Namespace, Name: pipeline.Name})
-		if err != nil {
-			klog.Errorf("failed to request to synchronize PipelineRuns. Pipeline = %s", pipeline.Name)
-			return
-		}
-	}()
-}
-
-func (h *apiHandler) requestSyncPipelineRun(key client.ObjectKey) error {
-	// get latest Pipeline
-	pipelineToUpdate := &v1alpha3.Pipeline{}
-	err := h.client.Get(context.Background(), key, pipelineToUpdate)
-	if err != nil {
-		return err
-	}
-	// update pipeline annotations
-	if pipelineToUpdate.Annotations == nil {
-		pipelineToUpdate.Annotations = make(map[string]string)
-	}
-	pipelineToUpdate.Annotations[v1alpha3.PipelineRequestToSyncRunsAnnoKey] = "true"
-	// update the pipeline
-	if err := h.client.Update(context.Background(), pipelineToUpdate); err != nil && !apierrors.IsConflict(err) {
-		// we allow the conflict error here
-		return err
-	}
-	return nil
 }
 
 func (h *apiHandler) createPipelineRun(request *restful.Request, response *restful.Response) {
diff --git a/pkg/kapis/devops/v1alpha3/pipelinerun/util.go b/pkg/kapis/devops/v1alpha3/pipelinerun/util.go
index c6c1f3e..d777054 100644
--- a/pkg/kapis/devops/v1alpha3/pipelinerun/util.go
+++ b/pkg/kapis/devops/v1alpha3/pipelinerun/util.go
@@ -93,6 +93,11 @@ func getPipelineRef(pipeline *v1alpha3.Pipeline) *corev1.ObjectReference {
 
 // CreatePipelineRun creates a bare PipelineRun.
 func CreatePipelineRun(pipeline *v1alpha3.Pipeline, payload *devops.RunPayload, scm *v1alpha3.SCM) *v1alpha3.PipelineRun {
+	return CreateBarePipelineRun(pipeline, convertParameters(payload), scm)
+}
+
+// CreateBarePipelineRun creates a bare PipelineRun.
+func CreateBarePipelineRun(pipeline *v1alpha3.Pipeline, parameters []v1alpha3.Parameter, scm *v1alpha3.SCM) *v1alpha3.PipelineRun {
 	controllerRef := metav1.NewControllerRef(pipeline, pipeline.GroupVersionKind())
 	pipelineRun := &v1alpha3.PipelineRun{
 		ObjectMeta: metav1.ObjectMeta{
@@ -108,7 +113,7 @@ func CreatePipelineRun(pipeline *v1alpha3.Pipeline, payload *devops.RunPayload,
 		Spec: v1alpha3.PipelineRunSpec{
 			PipelineRef:  getPipelineRef(pipeline),
 			PipelineSpec: &pipeline.Spec,
-			Parameters:   convertParameters(payload),
+			Parameters:   parameters,
 			SCM:          scm,
 		},
 	}
diff --git a/pkg/kapis/devops/v1alpha3/register.go b/pkg/kapis/devops/v1alpha3/register.go
index 6429de2..506b5e7 100644
--- a/pkg/kapis/devops/v1alpha3/register.go
+++ b/pkg/kapis/devops/v1alpha3/register.go
@@ -21,18 +21,18 @@ package v1alpha3
 import (
 	"net/http"
 
-	"kubesphere.io/devops/pkg/kapis/devops/v1alpha3/common"
-	"kubesphere.io/devops/pkg/kapis/devops/v1alpha3/scm"
-	"kubesphere.io/devops/pkg/kapis/devops/v1alpha3/template"
-
-	restful "github.com/emicklei/go-restful"
+	"github.com/emicklei/go-restful"
 	restfulspec "github.com/emicklei/go-restful-openapi"
 	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
 	"kubesphere.io/devops/pkg/client/k8s"
+	"kubesphere.io/devops/pkg/kapis/devops/v1alpha3/common"
 	"kubesphere.io/devops/pkg/kapis/devops/v1alpha3/pipeline"
 	"kubesphere.io/devops/pkg/kapis/devops/v1alpha3/pipelinerun"
+	"kubesphere.io/devops/pkg/kapis/devops/v1alpha3/scm"
+	"kubesphere.io/devops/pkg/kapis/devops/v1alpha3/template"
+	"kubesphere.io/devops/pkg/kapis/devops/v1alpha3/webhook"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	"kubesphere.io/devops/pkg/api"
@@ -62,6 +62,7 @@ func AddToContainer(container *restful.Container, devopsClient devopsClient.Inte
 		template.RegisterRoutes(service, &common.Options{
 			GenericClient: client,
 		})
+		webhook.RegisterWebhooks(client, service)
 		container.Add(service)
 	}
 	return services
diff --git a/pkg/kapis/devops/v1alpha3/webhook/handler.go b/pkg/kapis/devops/v1alpha3/webhook/handler.go
new file mode 100644
index 0000000..e5e34f5
--- /dev/null
+++ b/pkg/kapis/devops/v1alpha3/webhook/handler.go
@@ -0,0 +1,70 @@
+/*
+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 webhook
+
+import (
+	"github.com/emicklei/go-restful"
+	"k8s.io/apimachinery/pkg/util/errors"
+	"kubesphere.io/devops/pkg/event/common"
+	"kubesphere.io/devops/pkg/event/workflowrun"
+	"kubesphere.io/devops/pkg/kapis"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// Handler handles requests from webhooks.
+type Handler struct {
+	client.Client
+}
+
+// NewHandler creates a new handler for handling webhooks.
+func NewHandler(genericClient client.Client) *Handler {
+	return &Handler{
+		Client: genericClient,
+	}
+}
+
+// ReceiveEventsFromJenkins receives events from Jenkins
+func (handler *Handler) ReceiveEventsFromJenkins(request *restful.Request, response *restful.Response) {
+	// concrete event body
+	event := &common.Event{}
+	if err := request.ReadEntity(event); err != nil {
+		kapis.HandleError(request, response, err)
+		return
+	}
+
+	// TODO Make all handlers execute asynchronously
+
+	// register WorkflowRun event handler
+	var errs []error
+	workflowRunHandlers := workflowrun.Handlers{
+		HandleInitialize: handler.handleWorkflowRunInitialize,
+		// TODO Handler others
+		HandleStarted:   nil,
+		HandleFinalized: nil,
+		HandleCompleted: nil,
+		HandleDeleted:   nil,
+	}
+	if err := workflowRunHandlers.Handle(event); err != nil {
+		errs = append(errs, err)
+	}
+
+	// TODO Register other event handlers here
+
+	if len(errs) > 0 {
+		kapis.HandleError(request, response, errors.NewAggregate(errs))
+	}
+}
diff --git a/pkg/kapis/devops/v1alpha3/webhook/register.go b/pkg/kapis/devops/v1alpha3/webhook/register.go
new file mode 100644
index 0000000..ba351b1
--- /dev/null
+++ b/pkg/kapis/devops/v1alpha3/webhook/register.go
@@ -0,0 +1,34 @@
+/*
+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 webhook
+
+import (
+	"net/http"
+
+	"github.com/emicklei/go-restful"
+	"kubesphere.io/devops/pkg/api"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// RegisterWebhooks registers all webhooks into web service.
+func RegisterWebhooks(genericClient client.Client, ws *restful.WebService) {
+	webhookHandler := NewHandler(genericClient)
+	ws.Route(ws.POST("/webhooks/jenkins").
+		To(webhookHandler.ReceiveEventsFromJenkins).
+		Doc("Webhook for receiving events from Jenkins").
+		Returns(http.StatusOK, api.StatusOK, nil))
+}
diff --git a/pkg/kapis/devops/v1alpha3/webhook/workflowrun_handler.go b/pkg/kapis/devops/v1alpha3/webhook/workflowrun_handler.go
new file mode 100644
index 0000000..d25fe09
--- /dev/null
+++ b/pkg/kapis/devops/v1alpha3/webhook/workflowrun_handler.go
@@ -0,0 +1,140 @@
+/*
+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 webhook
+
+import (
+	"context"
+	"fmt"
+	"kubesphere.io/devops/pkg/event/workflowrun"
+	"strings"
+
+	"k8s.io/klog"
+	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
+	"kubesphere.io/devops/pkg/kapis/devops/v1alpha3/pipelinerun"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+type pipelineRunIdentifier struct {
+	namespaceName string
+	pipelineName  string
+	scmRefName    string
+	buildNumber   string
+}
+
+// String populates the identifier of the PipelineRun.
+func (identifier *pipelineRunIdentifier) String() string {
+	if identifier == nil {
+		return ""
+	}
+	return v1alpha3.BuildPipelineRunIdentifier(identifier.pipelineName, identifier.scmRefName, identifier.buildNumber)
+}
+
+func convertParameters(workflowRunParameters []workflowrun.Parameter) []v1alpha3.Parameter {
+	var parameters []v1alpha3.Parameter
+	for i := range workflowRunParameters {
+		workflowRunParameter := &workflowRunParameters[i]
+		if workflowRunParameter.Name == "" {
+			continue
+		}
+		parameters = append(parameters, v1alpha3.Parameter{
+			Name:  workflowRunParameter.Name,
+			Value: fmt.Sprint(workflowRunParameter.Value),
+		})
+	}
+	return parameters
+}
+
+func extractPipelineRunIdentifier(workflowRunData *workflowrun.Data) *pipelineRunIdentifier {
+	if workflowRunData == nil || workflowRunData.ParentFullName == "" {
+		return nil
+	}
+	identifier := &pipelineRunIdentifier{
+		buildNumber: workflowRunData.ID,
+	}
+
+	fullName := workflowRunData.ParentFullName
+	names := strings.Split(fullName, "/")
+	if workflowRunData.IsMultiBranch {
+		if len(names) != 2 {
+			// return if this is not a standard multi-branch Pipeline in ks-devops
+			return nil
+		}
+		identifier.namespaceName = names[0]
+		identifier.pipelineName = names[1]
+		identifier.scmRefName = workflowRunData.ProjectName
+	} else {
+		if len(names) != 1 {
+			// return if this is not a standard Pipeline in ks-devops
+			return nil
+		}
+		identifier.namespaceName = workflowRunData.ParentFullName
+		identifier.pipelineName = workflowRunData.ProjectName
+	}
+	return identifier
+}
+
+func (handler *Handler) handleWorkflowRunInitialize(workflowRunData *workflowrun.Data) error {
+	identifier := extractPipelineRunIdentifier(workflowRunData)
+	if identifier == nil {
+		// we should skip this event if the Pipeline is not a standard Pipeline in ks-devops.
+		return nil
+	}
+
+	// TODO Execute process below asynchronously
+
+	pipelineRunList := &v1alpha3.PipelineRunList{}
+	if err := handler.List(context.Background(), pipelineRunList,
+		client.InNamespace(identifier.namespaceName),
+		client.MatchingFields{v1alpha3.PipelineRunIdentifierIndexerName: identifier.String()}); err != nil {
+		return err
+	}
+
+	if len(pipelineRunList.Items) == 0 {
+		parameters, err := workflowRunData.Actions.GetParameters()
+		if err != nil {
+			return err
+		}
+
+		pipelineRun, err := handler.createPipelineRun(identifier, convertParameters(parameters))
+		if err != nil {
+			return err
+		}
+		klog.Infof("Created a PipelineRun: %s/%s", pipelineRun.Namespace, pipelineRun.Name)
+		return nil
+	}
+	return nil
+}
+
+func (handler *Handler) createPipelineRun(identifier *pipelineRunIdentifier, parameters []v1alpha3.Parameter) (*v1alpha3.PipelineRun, error) {
+	pipeline := &v1alpha3.Pipeline{}
+	if err := handler.Get(context.Background(), client.ObjectKey{Namespace: identifier.namespaceName, Name: identifier.pipelineName}, pipeline); err != nil {
+		return nil, err
+	}
+	scm, err := pipelinerun.CreateScm(&pipeline.Spec, identifier.scmRefName)
+	if err != nil {
+		return nil, err
+	}
+
+	pipelineRun := pipelinerun.CreateBarePipelineRun(pipeline, parameters, scm)
+
+	// Set the RunID manually
+	pipelineRun.GetAnnotations()[v1alpha3.JenkinsPipelineRunIDAnnoKey] = identifier.buildNumber
+	if err := handler.Create(context.Background(), pipelineRun); err != nil {
+		return nil, err
+	}
+	return pipelineRun, nil
+}
diff --git a/pkg/kapis/utils.go b/pkg/kapis/utils.go
index 82cf17f..61bfbb8 100644
--- a/pkg/kapis/utils.go
+++ b/pkg/kapis/utils.go
@@ -76,6 +76,9 @@ func HandleError(request *restful.Request, response *restful.Response, err error
 	default:
 		statusCode = http.StatusInternalServerError
 	}
+	if errors.IsNotFound(err) {
+		statusCode = http.StatusNotFound
+	}
 	handle(statusCode, request, response, err)
 }
 


Test patch:
diff --git a/controllers/argocd/application_controller_test.go b/controllers/argocd/application_controller_test.go
index 1040318..a4f11c4 100644
--- a/controllers/argocd/application_controller_test.go
+++ b/controllers/argocd/application_controller_test.go
@@ -70,9 +70,11 @@ func Test_createUnstructuredApplication(t *testing.T) {
 			app: &v1alpha1.Application{
 				Spec: v1alpha1.ApplicationSpec{
 					ArgoApp: &v1alpha1.ArgoApplication{
-						Destination: v1alpha1.ApplicationDestination{
-							Server:    "server",
-							Namespace: "namespace",
+						Spec: v1alpha1.ArgoApplicationSpec{
+							Destination: v1alpha1.ApplicationDestination{
+								Server:    "server",
+								Namespace: "namespace",
+							},
 						},
 					},
 				},
@@ -108,10 +110,12 @@ func TestApplicationReconciler_reconcileArgoApplication(t *testing.T) {
 		},
 		Spec: v1alpha1.ApplicationSpec{
 			ArgoApp: &v1alpha1.ArgoApplication{
-				Project: "project",
-				SyncPolicy: &v1alpha1.SyncPolicy{
-					Automated: &v1alpha1.SyncPolicyAutomated{
-						Prune: true,
+				Spec: v1alpha1.ArgoApplicationSpec{
+					Project: "project",
+					SyncPolicy: &v1alpha1.SyncPolicy{
+						Automated: &v1alpha1.SyncPolicyAutomated{
+							Prune: true,
+						},
 					},
 				},
 			},
diff --git a/controllers/argocd/utils_test.go b/controllers/argocd/utils_test.go
new file mode 100644
index 0000000..f48d66f
--- /dev/null
+++ b/controllers/argocd/utils_test.go
@@ -0,0 +1,112 @@
+// Copyright 2022 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 argocd
+
+import (
+	"fmt"
+	"github.com/stretchr/testify/assert"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"testing"
+)
+
+func TestSetNestedField(t *testing.T) {
+	type args struct {
+		obj    map[string]interface{}
+		value  interface{}
+		fields []string
+	}
+	tests := []struct {
+		name    string
+		args    args
+		wantErr assert.ErrorAssertionFunc
+		verify  func(obj map[string]interface{})
+	}{{
+		name: "Should set field correctly when struct is simple",
+		args: args{
+			obj: map[string]interface{}{
+				"name": "fake-obj-name",
+			},
+			value: struct {
+				Name string `json:"name"`
+			}{
+				Name: "fake-name",
+			},
+			fields: []string{"spec"},
+		},
+		wantErr: assert.NoError,
+		verify: func(obj map[string]interface{}) {
+			assert.Equal(t, map[string]interface{}{
+				"name": "fake-obj-name",
+				"spec": map[string]interface{}{
+					"name": "fake-name",
+				},
+			}, obj)
+		},
+	}, {
+		name: "Should set field correctly when struct is complex",
+		args: args{
+			obj: map[string]interface{}{},
+			value: struct {
+				Metadata metav1.ObjectMeta `json:"metadata"`
+			}{
+				Metadata: metav1.ObjectMeta{
+					Name:       "fake-name",
+					Namespace:  "fake-namespace",
+					Generation: 123,
+				},
+			},
+			fields: []string{"spec"},
+		},
+		wantErr: assert.NoError,
+		verify: func(obj map[string]interface{}) {
+			assert.Equal(t, map[string]interface{}{
+				"spec": map[string]interface{}{
+					"metadata": map[string]interface{}{
+						"name":              "fake-name",
+						"namespace":         "fake-namespace",
+						"generation":        float64(123),
+						"creationTimestamp": (interface{})(nil),
+					},
+				},
+			}, obj)
+		},
+	}, {
+		name: "Should occur error when value type is int",
+		args: args{
+			obj:    map[string]interface{}{},
+			value:  123,
+			fields: []string{"spec"},
+		},
+		wantErr: assert.Error,
+	}, {
+		name: "Should occur error when value type is func",
+		args: args{
+			obj:    map[string]interface{}{},
+			value:  func() {},
+			fields: []string{"spec"},
+		},
+		wantErr: assert.Error,
+	},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			tt.wantErr(t, SetNestedField(tt.args.obj, tt.args.value, tt.args.fields...), fmt.Sprintf("SetNestedField(%v, %v, %v)", tt.args.obj, tt.args.value, tt.args.fields))
+			if tt.verify != nil {
+				tt.verify(tt.args.obj)
+			}
+		})
+	}
+}
diff --git a/pkg/api/devops/v1alpha3/pipelinerun_types_test.go b/pkg/api/devops/v1alpha3/pipelinerun_types_test.go
index 240f775..f1b5dca 100644
--- a/pkg/api/devops/v1alpha3/pipelinerun_types_test.go
+++ b/pkg/api/devops/v1alpha3/pipelinerun_types_test.go
@@ -17,11 +17,11 @@ limitations under the License.
 package v1alpha3
 
 import (
-	"github.com/stretchr/testify/assert"
-	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"testing"
 
+	"github.com/stretchr/testify/assert"
 	corev1 "k8s.io/api/core/v1"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 func TestPipelineRunSpec_IsMultiBranchPipeline(t *testing.T) {
@@ -199,3 +199,44 @@ func TestPipelineRun_HasStarted(t *testing.T) {
 		})
 	}
 }
+
+func TestBuildPipelineRunIdentifier(t *testing.T) {
+	type args struct {
+		pipelineName string
+		scmRefName   string
+		runID        string
+	}
+	tests := []struct {
+		name string
+		args args
+		want string
+	}{{
+		name: "Without all of arguments",
+		args: args{},
+		want: "",
+	}, {
+		name: "Without SCM reference name",
+		args: args{
+			pipelineName: "fake-pipeline",
+			scmRefName:   "",
+			runID:        "1",
+		},
+		want: "fake-pipeline-1",
+	}, {
+		name: "With SCM reference name",
+		args: args{
+			pipelineName: "fake-pipeline",
+			scmRefName:   "main",
+			runID:        "1",
+		},
+		want: "fake-pipeline-main-1",
+	},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := BuildPipelineRunIdentifier(tt.args.pipelineName, tt.args.scmRefName, tt.args.runID); got != tt.want {
+				t.Errorf("BuildPipelineRunIdentifier() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/pkg/event/workflowrun/actions_test.go b/pkg/event/workflowrun/actions_test.go
new file mode 100644
index 0000000..8cc7631
--- /dev/null
+++ b/pkg/event/workflowrun/actions_test.go
@@ -0,0 +1,57 @@
+/*
+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 workflowrun
+
+import (
+	"encoding/json"
+	"reflect"
+	"testing"
+)
+
+func TestAction_UnmarshalJSON(t *testing.T) {
+	tests := []struct {
+		name       string
+		actionJSON string
+		wantErr    bool
+		wantAction *Action
+	}{{
+		name:       "Should return an action with kind while having _class field",
+		actionJSON: `{"_class": "fake.class"}`,
+		wantAction: &Action{
+			Raw:  json.RawMessage(`{"_class": "fake.class"}`),
+			Kind: "fake.class",
+		},
+	}, {
+		name:       "Should return an action without kind while having no _class field",
+		actionJSON: `{"_fake_class": "fake.class"}`,
+		wantAction: &Action{
+			Raw:  json.RawMessage(`{"_fake_class": "fake.class"}`),
+			Kind: "",
+		},
+	}}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			action := &Action{}
+			if err := json.Unmarshal([]byte(tt.actionJSON), action); err != nil != tt.wantErr {
+				t.Errorf("Action.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
+			}
+			if !reflect.DeepEqual(action, tt.wantAction) {
+				t.Errorf("failed to unmarshal action, got = %v, want = %v", action, tt.wantAction)
+			}
+		})
+	}
+}
diff --git a/pkg/event/workflowrun/parameter_action_test.go b/pkg/event/workflowrun/parameter_action_test.go
new file mode 100644
index 0000000..f8b05ab
--- /dev/null
+++ b/pkg/event/workflowrun/parameter_action_test.go
@@ -0,0 +1,121 @@
+/*
+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 workflowrun
+
+import (
+	"encoding/json"
+	"reflect"
+	"testing"
+)
+
+func TestParameterAction_Kind(t *testing.T) {
+	tests := []struct {
+		name            string
+		parameterAction *ParameterAction
+		want            string
+	}{{
+		name:            "Should return correct kind if the action is nil",
+		parameterAction: nil,
+		want:            "hudson.model.ParametersAction",
+	}, {
+		name:            "Should return correct kind if the action is not nil",
+		parameterAction: &ParameterAction{},
+		want:            "hudson.model.ParametersAction",
+	}}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := tt.parameterAction.Kind(); got != tt.want {
+				t.Errorf("ParameterAction.Kind() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_GetParameters(t *testing.T) {
+	type args struct {
+		actionsJSON string
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    []Parameter
+		wantErr bool
+	}{{
+		name: "Should return nil if no parameters",
+		args: args{
+			actionsJSON: `
+				[{
+                    "_class": "hudson.model.ParametersAction",
+                    "fake_parameters": [{
+                        "_class": "hudson.model.BooleanParameterValue",
+                        "name": "skip",
+                        "value": false
+                    }]
+                }]`,
+		},
+		want: nil,
+	}, {
+		name: "Should return nil if kind is not hudson.model.ParametersAction",
+		args: args{
+			actionsJSON: `
+				[{
+                    "_class": "fake.class",
+                    "parameters": [{
+                        "_class": "hudson.model.BooleanParameterValue",
+                        "name": "skip",
+                        "value": false
+                    }]
+                }]`,
+		},
+		want: nil,
+	}, {
+		name: "Should return parse parameter correctly if parameters are here",
+		args: args{
+			actionsJSON: `
+				[{
+                    "_class": "hudson.model.ParametersAction",
+                    "parameters": [{
+                        "_class": "hudson.model.BooleanParameterValue",
+                        "name": "skip",
+                        "value": false
+                    }]
+                }]`,
+		},
+		want: []Parameter{{
+			Kind:  "hudson.model.BooleanParameterValue",
+			Name:  "skip",
+			Value: false,
+		}},
+	}}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			actions := Actions{}
+			if err := json.Unmarshal([]byte(tt.args.actionsJSON), &actions); err != nil {
+				t.Errorf("failed to unmarshal action JSON, err: %v", err)
+				return
+			}
+			got, err := actions.GetParameters()
+			if err != nil != tt.wantErr {
+				t.Errorf("Want an error, err: %v", err)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("getParameters() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/pkg/event/workflowrun/workflowrun_handler_test.go b/pkg/event/workflowrun/workflowrun_handler_test.go
new file mode 100644
index 0000000..50778b9
--- /dev/null
+++ b/pkg/event/workflowrun/workflowrun_handler_test.go
@@ -0,0 +1,153 @@
+/*
+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 workflowrun
+
+import (
+	"encoding/json"
+	"errors"
+	"kubesphere.io/devops/pkg/event/common"
+	"testing"
+)
+
+func TestEvent_HandleWorkflowRun(t *testing.T) {
+	createEvent := func(eventType, dataType string, data *Data) *common.Event {
+		dataBytes, _ := json.Marshal(data)
+		return &common.Event{
+			ID:       "fake.id",
+			Type:     eventType,
+			Time:     "fake-time",
+			DataType: dataType,
+			Data:     dataBytes,
+		}
+	}
+	type args struct {
+		handlers Handlers
+	}
+	errInitialize := errors.New("initialized")
+	errStarted := errors.New("started")
+	errCompleted := errors.New("completed")
+	errFinalized := errors.New("finalized")
+	errDeleted := errors.New("deleted")
+	initializeHandler := func(*Data) error {
+		return errInitialize
+	}
+	startedHandler := func(*Data) error {
+		return errStarted
+	}
+	completedHandler := func(*Data) error {
+		return errCompleted
+	}
+	finalizedHandler := func(*Data) error {
+		return errFinalized
+	}
+	deletedHandler := func(*Data) error {
+		return errDeleted
+	}
+	tests := []struct {
+		name    string
+		event   *common.Event
+		args    args
+		wantErr error
+	}{{
+		name:    "Should not return error if event is nil",
+		event:   nil,
+		wantErr: nil,
+	}, {
+		name:    "Should not return error if data is nil",
+		event:   createEvent("", "", nil),
+		wantErr: nil,
+	}, {
+		name:  "Should invoke initialize handler",
+		event: createEvent(string(common.RunInitialize), Type, &Data{}),
+		args: args{
+			handlers: Handlers{
+				HandleInitialize: initializeHandler,
+			},
+		},
+		wantErr: errInitialize,
+	}, {
+		name:  "Should invoke started handler",
+		event: createEvent(string(common.RunStarted), Type, &Data{}),
+		args: args{
+			handlers: Handlers{
+				HandleStarted: startedHandler,
+			},
+		},
+		wantErr: errStarted,
+	}, {
+		name:  "Should invoke finalized handler",
+		event: createEvent(string(common.RunFinalized), Type, &Data{}),
+		args: args{
+			handlers: Handlers{
+				HandleFinalized: finalizedHandler,
+			},
+		},
+		wantErr: errFinalized,
+	}, {
+		name:  "Should invoke completed handler",
+		event: createEvent(string(common.RunCompleted), Type, &Data{}),
+		args: args{
+			handlers: Handlers{
+				HandleCompleted: completedHandler,
+			},
+		},
+		wantErr: errCompleted,
+	}, {
+		name:  "Should invoke deleted handler",
+		event: createEvent(string(common.RunDeleted), Type, &Data{}),
+		args: args{
+			handlers: Handlers{
+				HandleDeleted: deletedHandler,
+			},
+		},
+		wantErr: errDeleted,
+	}, {
+		name:  "Should return nil if event type is out of range",
+		event: createEvent("fake.event", Type, &Data{}),
+		args: args{
+			handlers: Handlers{
+				HandleInitialize: initializeHandler,
+				HandleStarted:    startedHandler,
+				HandleFinalized:  finalizedHandler,
+				HandleCompleted:  completedHandler,
+				HandleDeleted:    deletedHandler,
+			},
+		},
+		wantErr: nil,
+	}, {
+		name:  "Should return nil if data type is invalid",
+		event: createEvent(string(common.RunInitialize), "fake.data.type", &Data{}),
+		args: args{
+			handlers: Handlers{
+				HandleInitialize: initializeHandler,
+				HandleStarted:    startedHandler,
+				HandleFinalized:  finalizedHandler,
+				HandleCompleted:  completedHandler,
+				HandleDeleted:    deletedHandler,
+			},
+		},
+		wantErr: nil,
+	},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if err := tt.args.handlers.Handle(tt.event); err != tt.wantErr {
+				t.Errorf("Event.Handle() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
diff --git a/pkg/kapis/devops/v1alpha3/pipelinerun/register_test.go b/pkg/kapis/devops/v1alpha3/pipelinerun/register_test.go
index c81e362..971d6e9 100644
--- a/pkg/kapis/devops/v1alpha3/pipelinerun/register_test.go
+++ b/pkg/kapis/devops/v1alpha3/pipelinerun/register_test.go
@@ -70,6 +70,12 @@ func TestAPIsExist(t *testing.T) {
 			method: http.MethodGet,
 			uri:    "/namespaces/fake/pipelineruns/fake/nodedetails",
 		},
+	}, {
+		name: "receive pipeline event",
+		args: args{
+			method: http.MethodPost,
+			uri:    "/webhook/pipeline-event",
+		},
 	}, {
 		name: "download artifact",
 		args: args{
diff --git a/pkg/kapis/devops/v1alpha3/webhook/register_test.go b/pkg/kapis/devops/v1alpha3/webhook/register_test.go
new file mode 100644
index 0000000..cd96d0a
--- /dev/null
+++ b/pkg/kapis/devops/v1alpha3/webhook/register_test.go
@@ -0,0 +1,166 @@
+/*
+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 webhook
+
+import (
+	"context"
+	"io"
+	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
+	"k8s.io/client-go/kubernetes/scheme"
+	"net/http"
+	"net/http/httptest"
+	"strings"
+	"testing"
+
+	"github.com/emicklei/go-restful"
+	"github.com/stretchr/testify/assert"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
+	apiserverruntime "kubesphere.io/devops/pkg/apiserver/runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+)
+
+func TestJenkinsWebhook(t *testing.T) {
+
+	type args struct {
+		method     string
+		uri        string
+		bodyJSON   string
+		initObject []runtime.Object
+	}
+	tests := []struct {
+		name      string
+		args      args
+		assertion func(t *testing.T, c client.Client)
+	}{{
+		name: "Should create PipelineRun when workflow run initialized",
+		args: args{
+			method: http.MethodPost,
+			uri:    "/webhooks/jenkins",
+			initObject: []runtime.Object{
+				&v1alpha3.Pipeline{
+					ObjectMeta: v1.ObjectMeta{
+						Name:      "example-pipeline",
+						Namespace: "my-devops-project",
+					},
+				},
+			},
+			bodyJSON: `
+				{
+				  "data":     {
+					"_class": "io.jenkins.plugins.generic.event.data.WorkflowRunData",
+					"actions":         [
+					  {
+						"_class": "hudson.model.ParametersAction",
+						"parameters": [                {
+						  "_class": "hudson.model.BooleanParameterValue",
+						  "name": "skip",
+						  "value": false
+						}]
+					  },
+					  {"_class": "org.jenkinsci.plugins.displayurlapi.actions.RunDisplayAction"},
+					  {"_class": "org.jenkinsci.plugins.pipeline.modeldefinition.actions.RestartDeclarativePipelineAction"},
+					  {},
+					  {"_class": "org.jenkinsci.plugins.workflow.job.views.FlowGraphAction"},
+					  {},
+					  {},
+					  {}
+					],
+					"artifacts": [],
+					"building": true,
+					"description": null,
+					"displayName": "#1",
+					"duration": 0,
+					"estimatedDuration": -1,
+					"executor": {"_class": "hudson.model.OneOffExecutor"},
+					"fullDisplayName": "my-devops-project » example-pipeline #1",
+					"id": "1",
+					"keepLog": false,
+					"number": 1,
+					"queueId": 1,
+					"result": null,
+					"timestamp": 1644126495293,
+					"changeSets": [],
+				"culprits": [],
+				"nextBuild": null,
+				"previousBuild": null,
+				"_multiBranch": false,
+				"_parentFullName": "my-devops-project",
+				"_projectName": "example-pipeline"
+			  },
+			  "dataType": "org.jenkinsci.plugins.workflow.job.WorkflowRun",
+			  "id": "50c33b0e-d7f1-4a34-b57e-bb82cd453894",
+			  "source": "job/my-devops-project/job/example-pipeline/",
+			  "time": "2022-02-06T13:48:15.307+0800",
+			  "type": "run.initialize"
+			}`,
+		},
+		assertion: func(t *testing.T, c client.Client) {
+			pipelineruns := &v1alpha3.PipelineRunList{}
+			assert.Nil(t, c.List(context.Background(), pipelineruns))
+			assert.Equal(t, 1, len(pipelineruns.Items))
+		},
+	}, {
+		name: "Should not create any PipelineRuns when type doesn't start with run",
+		args: args{
+			method:     http.MethodPost,
+			uri:        "/webhooks/jenkins",
+			initObject: []runtime.Object{},
+			bodyJSON: `
+				{
+				  "dataType": "org.jenkinsci.plugins.workflow.job.WorkflowRun",
+				  "id": "50c33b0e-d7f1-4a34-b57e-bb82cd453894",
+				  "source": "job/my-devops-project/job/example-pipeline/",
+				  "time": "2022-02-06T13:48:15.307+0800",
+				  "type": "job.created"
+				}`,
+		},
+		assertion: func(t *testing.T, c client.Client) {
+			pipelineruns := &v1alpha3.PipelineRunList{}
+			assert.Nil(t, c.List(context.Background(), pipelineruns))
+			assert.Equal(t, 0, len(pipelineruns.Items))
+		},
+	}}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			utilruntime.Must(v1alpha3.AddToScheme(scheme.Scheme))
+			fakeClient := fake.NewFakeClientWithScheme(scheme.Scheme, tt.args.initObject...)
+
+			container := restful.NewContainer()
+			wsWithGroup := apiserverruntime.NewWebService(v1alpha3.GroupVersion)
+			RegisterWebhooks(fakeClient, wsWithGroup)
+			container.Add(wsWithGroup)
+
+			var bodyReader io.Reader
+			if tt.args.bodyJSON != "" {
+				bodyReader = strings.NewReader(tt.args.bodyJSON)
+			}
+
+			httpRequest, _ := http.NewRequest(tt.args.method,
+				"http://fake.com/kapis/devops.kubesphere.io/v1alpha3"+tt.args.uri, bodyReader)
+			httpRequest.Header.Set("Content-Type", "application/json")
+			httpWriter := httptest.NewRecorder()
+			container.Dispatch(httpWriter, httpRequest)
+			assert.Equal(t, 200, httpWriter.Code)
+			if tt.assertion != nil {
+				tt.assertion(t, fakeClient)
+			}
+		})
+	}
+}
diff --git a/pkg/kapis/devops/v1alpha3/webhook/workflowrun_handler_test.go b/pkg/kapis/devops/v1alpha3/webhook/workflowrun_handler_test.go
new file mode 100644
index 0000000..6c43fd0
--- /dev/null
+++ b/pkg/kapis/devops/v1alpha3/webhook/workflowrun_handler_test.go
@@ -0,0 +1,279 @@
+/*
+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 webhook
+
+import (
+	"context"
+	"kubesphere.io/devops/pkg/event/workflowrun"
+	"reflect"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+)
+
+func Test_pipelineRunIdentifier_String(t *testing.T) {
+	tests := []struct {
+		name       string
+		identifier *pipelineRunIdentifier
+		want       string
+	}{{
+		name: "Should return identifier not containing namespace name",
+		identifier: &pipelineRunIdentifier{
+			namespaceName: "fake.namespace",
+			pipelineName:  "fake.pipeline",
+			scmRefName:    "fake.ref",
+			buildNumber:   "1",
+		},
+		want: "fake.pipeline-fake.ref-1",
+	}, {
+		name:       "Should return empty string if PipelineRunIdentifier is nil",
+		identifier: nil,
+		want:       "",
+	}}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := tt.identifier.String(); got != tt.want {
+				t.Errorf("pipelineRunIdentifier.String() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_convertParameters(t *testing.T) {
+	type args struct {
+		parameters []workflowrun.Parameter
+	}
+	tests := []struct {
+		name string
+		args args
+		want []v1alpha3.Parameter
+	}{{
+		name: "Nil parameters",
+		args: args{
+			parameters: nil,
+		},
+		want: nil,
+	}, {
+		name: "Single parameter",
+		args: args{
+			parameters: []workflowrun.Parameter{{
+				Name:  "aname",
+				Value: "avalue",
+			}},
+		},
+		want: []v1alpha3.Parameter{{
+			Name:  "aname",
+			Value: "avalue",
+		}},
+	}, {
+		name: "Empty parameter",
+		args: args{
+			parameters: []workflowrun.Parameter{{
+				Name:  "",
+				Value: "",
+			}},
+		},
+		want: nil,
+	}, {
+		name: "Empty value only",
+		args: args{
+			parameters: []workflowrun.Parameter{{
+				Name:  "fakeName",
+				Value: "",
+			}},
+		},
+		want: []v1alpha3.Parameter{{
+			Name:  "fakeName",
+			Value: "",
+		}},
+	}, {
+		name: "Two parameters",
+		args: args{
+			parameters: []workflowrun.Parameter{{
+				Name:  "aname",
+				Value: "avalue",
+			}, {
+				Name:  "bname",
+				Value: "bvalue",
+			}},
+		},
+		want: []v1alpha3.Parameter{{
+			Name:  "aname",
+			Value: "avalue",
+		}, {
+			Name:  "bname",
+			Value: "bvalue",
+		}},
+	},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := convertParameters(tt.args.parameters); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("convertParameters() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+func createWorkflowRun(parentFullName, projectName, buildNumber string, isMultiBranch bool) *workflowrun.Data {
+	return &workflowrun.Data{
+		ParentFullName: parentFullName,
+		ProjectName:    projectName,
+		IsMultiBranch:  isMultiBranch,
+		ID:             buildNumber,
+	}
+}
+func Test_extractPipelineRunIdentifier(t *testing.T) {
+
+	type args struct {
+		workflowRunData *workflowrun.Data
+	}
+	tests := []struct {
+		name string
+		args args
+		want *pipelineRunIdentifier
+	}{{
+		name: "Should return nil if workflowRunData is nil",
+		args: args{
+			workflowRunData: nil,
+		},
+		want: nil,
+	}, {
+		name: "Should return nil if parent full name is empty",
+		args: args{
+			workflowRunData: createWorkflowRun("", "main", "1", true),
+		},
+		want: nil,
+	}, {
+		name: "Should return nil if parent full name contains more than one slash and multi branch is true",
+		args: args{
+			workflowRunData: createWorkflowRun("a/b/c", "main", "1", true),
+		},
+		want: nil,
+	}, {
+		name: "Should return nil if parent full name contains slash and multi branch is false",
+		args: args{
+			workflowRunData: createWorkflowRun("a/b", "c", "1", false),
+		},
+		want: nil,
+	}, {
+		name: "Should return identifier if parent full name does not contain slash and multi branch is false",
+		args: args{
+			workflowRunData: createWorkflowRun("a", "b", "1", false),
+		},
+		want: &pipelineRunIdentifier{
+			namespaceName: "a",
+			pipelineName:  "b",
+			buildNumber:   "1",
+			scmRefName:    "",
+		},
+	}, {
+		name: "Should return identifier if parent full name contains one slash and multi branch is true",
+		args: args{
+			workflowRunData: createWorkflowRun("a/b", "main", "1", true),
+		},
+		want: &pipelineRunIdentifier{
+			namespaceName: "a",
+			pipelineName:  "b",
+			buildNumber:   "1",
+			scmRefName:    "main",
+		},
+	},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := extractPipelineRunIdentifier(tt.args.workflowRunData); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("extractPipelineRunIdentifier() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestHandler_handleWorkflowRunInitialize(t *testing.T) {
+	createPipeline := func(namespace, name string) *v1alpha3.Pipeline {
+		return &v1alpha3.Pipeline{
+			ObjectMeta: v1.ObjectMeta{
+				Name:      name,
+				Namespace: namespace,
+			},
+		}
+	}
+	type args struct {
+		workflowRunData *workflowrun.Data
+		initObjs        []runtime.Object
+	}
+	tests := []struct {
+		name      string
+		args      args
+		wantErr   bool
+		assertion func(*testing.T, client.Client)
+	}{{
+		name: "Should create a new PipelineRun",
+		args: args{
+			workflowRunData: createWorkflowRun("fake-namespace", "fake-pipeline", "1", false),
+			initObjs: []runtime.Object{
+				createPipeline("fake-namespace", "fake-pipeline"),
+			},
+		},
+		wantErr: false,
+		assertion: func(t *testing.T, c client.Client) {
+			pipelineRuns := &v1alpha3.PipelineRunList{}
+			_ = c.List(context.Background(), pipelineRuns)
+			assert.Equal(t, 1, len(pipelineRuns.Items))
+		},
+	}, {
+		name: "Should return an not found error if Pipeline not found",
+		args: args{
+			workflowRunData: createWorkflowRun("fake-namepsace", "fake-pipeline", "1", false),
+		},
+		wantErr: true,
+	}, {
+		name: "Should create nothing if WorkflowRunData is invalid",
+		args: args{
+			workflowRunData: createWorkflowRun("", "", "", false),
+		},
+		wantErr: false,
+		assertion: func(t *testing.T, c client.Client) {
+			pipelineRuns := &v1alpha3.PipelineRunList{}
+			_ = c.List(context.Background(), pipelineRuns)
+			assert.Equal(t, 0, len(pipelineRuns.Items))
+		},
+	}}
+	for _, tt := range tests {
+		scheme := runtime.NewScheme()
+		_ = v1alpha3.AddToScheme(scheme)
+		fakeClient := fake.NewFakeClientWithScheme(scheme, tt.args.initObjs...)
+
+		t.Run(tt.name, func(t *testing.T) {
+			handler := &Handler{
+				Client: fakeClient,
+			}
+			if err := handler.handleWorkflowRunInitialize(tt.args.workflowRunData); (err != nil) != tt.wantErr {
+				t.Errorf("Handler.handleWorkflowRunInitialize() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if tt.assertion != nil {
+				// check the result
+				tt.assertion(t, fakeClient)
+			}
+		})
+	}
+}
diff --git a/pkg/kapis/gitops/v1alpha1/argocd/route_test.go b/pkg/kapis/gitops/v1alpha1/argocd/route_test.go
index 9437072..48e2ace 100644
--- a/pkg/kapis/gitops/v1alpha1/argocd/route_test.go
+++ b/pkg/kapis/gitops/v1alpha1/argocd/route_test.go
@@ -203,7 +203,9 @@ func TestAPIs(t *testing.T) {
   },
   "spec": {
     "argoApp": {
-      "project": "default"
+      "spec": {
+        "project": "default"
+      }
     }
   }
 }`))
@@ -246,7 +248,9 @@ func TestAPIs(t *testing.T) {
   },
   "spec": {
     "argoApp": {
-      "project": "good"
+      "spec": {
+        "project": "good"
+      }
     }
   }
 }`))
@@ -259,7 +263,7 @@ func TestAPIs(t *testing.T) {
 			err := yaml.Unmarshal(body, list)
 			assert.Nil(t, err)
 
-			project, _, err := unstructured.NestedString(list.Object, "spec", "argoApp", "project")
+			project, _, err := unstructured.NestedString(list.Object, "spec", "argoApp", "spec", "project")
 			assert.Equal(t, "good", project)
 			assert.Nil(t, err)
 		},